diff --git a/docs/conf.py b/docs/conf.py index 49e35b9db..3e3bf47cf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -39,8 +39,11 @@ def setup(sphinx): # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'sphinx_tabs.tabs' ] +# Do not allow tabs to be closed +sphinx_tabs_disable_tab_closing = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/examples/base_contract_function_call.sol b/docs/examples/base_contract_function_call.sol index 798b8b1dc..a2642ca7a 100644 --- a/docs/examples/base_contract_function_call.sol +++ b/docs/examples/base_contract_function_call.sol @@ -22,6 +22,8 @@ abstract contract a { function bar2() internal returns (uint64) { // this explicitly says "call foo of base contract a", and dispatch is not virtual + // however, if the call is written as a.foo{program_id: id_var}(), this represents + // an external call to contract 'a' on Solana. return a.foo(); } } diff --git a/docs/examples/expression_this.sol b/docs/examples/polkadot/expression_this.sol similarity index 100% rename from docs/examples/expression_this.sol rename to docs/examples/polkadot/expression_this.sol diff --git a/docs/examples/expression_this_external_call.sol b/docs/examples/polkadot/expression_this_external_call.sol similarity index 100% rename from docs/examples/expression_this_external_call.sol rename to docs/examples/polkadot/expression_this_external_call.sol diff --git a/docs/examples/function_call.sol b/docs/examples/polkadot/function_call.sol similarity index 100% rename from docs/examples/function_call.sol rename to docs/examples/polkadot/function_call.sol diff --git a/docs/examples/function_call_external.sol b/docs/examples/polkadot/function_call_external.sol similarity index 100% rename from docs/examples/function_call_external.sol rename to docs/examples/polkadot/function_call_external.sol diff --git a/docs/examples/function_type_callback.sol b/docs/examples/polkadot/function_type_callback.sol similarity index 100% rename from docs/examples/function_type_callback.sol rename to docs/examples/polkadot/function_type_callback.sol diff --git a/docs/examples/solana/bobcat.sol b/docs/examples/solana/bobcat.sol index 6f9ee6889..f1e5ad188 100644 --- a/docs/examples/solana/bobcat.sol +++ b/docs/examples/solana/bobcat.sol @@ -1,5 +1,5 @@ -anchor_anchor constant bobcat = anchor_anchor(address'z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq'); -interface anchor_anchor { +@program_id("z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq") +interface bobcat { @selector([0xaf, 0xaf, 0x6d, 0x1f, 0x0d, 0x98, 0x9b, 0xed]) function pounce() view external returns(int64); } diff --git a/docs/examples/solana/contract_address.sol b/docs/examples/solana/contract_address.sol index 4626baf93..9c18d1089 100644 --- a/docs/examples/solana/contract_address.sol +++ b/docs/examples/solana/contract_address.sol @@ -1,4 +1,3 @@ -@program_id("5kQ3iJ43gHNDjqmSAtE1vDu18CiSAfNbRe4v5uoobh3U") contract hatchling { string name; @@ -9,7 +8,7 @@ contract hatchling { } contract adult { - function test(address addr) external { - hatchling h = new hatchling("luna"); + function test(address id) external { + hatchling.new{program_id: id}("luna"); } } diff --git a/docs/examples/solana/contract_call.sol b/docs/examples/solana/contract_call.sol new file mode 100644 index 000000000..eb6e649d8 --- /dev/null +++ b/docs/examples/solana/contract_call.sol @@ -0,0 +1,21 @@ +contract Polymath { + function call_math() external returns (uint) { + return Math.sum(1, 2); + } + function call_english(address english_id) external returns (string) { + return English.concatenate{program_id: english_id}("Hello", "world"); + } +} + +@program_id("5afzkvPkrshqu4onwBCsJccb1swrt4JdAjnpzK8N4BzZ") +contract Math { + function sum(uint a, uint b) external returns (uint) { + return a + b; + } +} + +contract English { + function concatenate(string a, string b) external returns (string) { + return a + b; + } +} \ No newline at end of file diff --git a/docs/examples/solana/contract_new.sol b/docs/examples/solana/contract_new.sol new file mode 100644 index 000000000..28f342147 --- /dev/null +++ b/docs/examples/solana/contract_new.sol @@ -0,0 +1,21 @@ +@program_id("5afzkvPkrshqu4onwBCsJccb1swrt4JdAjnpzK8N4BzZ") +contract hatchling { + string name; + address private origin; + + constructor(string id, address parent) { + require(id != "", "name must be provided"); + name = id; + origin = parent; + } + + function root() public returns (address) { + return origin; + } +} + +contract adult { + function test() external { + hatchling.new("luna", address(this)); + } +} diff --git a/docs/examples/solana/create_contract_with_metas.sol b/docs/examples/solana/create_contract_with_metas.sol index 163826b7c..55fd859f9 100644 --- a/docs/examples/solana/create_contract_with_metas.sol +++ b/docs/examples/solana/create_contract_with_metas.sol @@ -1,9 +1,6 @@ import 'solana'; contract creator { - Child public c; - Child public c_metas; - function create_with_metas(address data_account_to_initialize, address payer) public { AccountMeta[3] metas = [ AccountMeta({ @@ -20,9 +17,9 @@ contract creator { is_signer: false}) ]; - c_metas = new Child{accounts: metas}(payer); + Child.new{accounts: metas}(payer); - c_metas.use_metas(); + Child.use_metas(); } } diff --git a/docs/examples/solana/expression_this.sol b/docs/examples/solana/expression_this.sol new file mode 100644 index 000000000..bbec97cfc --- /dev/null +++ b/docs/examples/solana/expression_this.sol @@ -0,0 +1,6 @@ +contract kadowari { + function nomi() public { + // Contracts are not allowed as variables on Solana + address a = address(this); + } +} diff --git a/docs/examples/solana/expression_this_external_call.sol b/docs/examples/solana/expression_this_external_call.sol new file mode 100644 index 000000000..82ffbe40f --- /dev/null +++ b/docs/examples/solana/expression_this_external_call.sol @@ -0,0 +1,10 @@ +@program_id("H3AthiA2C1pcMahg17nEwqr9628gkXUnnzWJJ3iSDekL") +contract kadowari { + function nomi() public { + this.nokogiri(102); + } + + function nokogiri(int256 a) public { + // ... + } +} diff --git a/docs/examples/solana/function_call.sol b/docs/examples/solana/function_call.sol new file mode 100644 index 000000000..3acc5cf7c --- /dev/null +++ b/docs/examples/solana/function_call.sol @@ -0,0 +1,27 @@ +contract A { + function test(address v) public { + // the following four lines are equivalent to "uint32 res = v.foo(3,5);" + + // Note that the signature is only hashed and not parsed. So, ensure that the + // arguments are of the correct type. + bytes data = abi.encodeWithSignature( + "global:foo", + uint32(3), + uint32(5) + ); + + (bool success, bytes rawresult) = v.call(data); + + assert(success == true); + + uint32 res = abi.decode(rawresult, (uint32)); + + assert(res == 8); + } +} + +contract B { + function foo(uint32 a, uint32 b) pure public returns (uint32) { + return a + b; + } +} diff --git a/docs/examples/solana/function_call_external.sol b/docs/examples/solana/function_call_external.sol new file mode 100644 index 000000000..b355ff720 --- /dev/null +++ b/docs/examples/solana/function_call_external.sol @@ -0,0 +1,16 @@ +contract foo { + function bar1(uint32 x, bool y) public returns (address, bytes32) { + return (address(3), hex"01020304"); + } + + function bar2(uint32 x, bool y) public returns (bool) { + return !y; + } +} + +contract bar { + function test(address f) public { + (address f1, bytes32 f2) = foo.bar1{program_id: f}(102, false); + bool f3 = foo.bar2{program_id: f}({x: 255, y: true}); + } +} diff --git a/docs/examples/solana/function_type_callback.sol b/docs/examples/solana/function_type_callback.sol new file mode 100644 index 000000000..befae9c17 --- /dev/null +++ b/docs/examples/solana/function_type_callback.sol @@ -0,0 +1,24 @@ +contract ft { + function test(address p) public { + // this.callback can be used as an external function type value + paffling.set_callback{program_id: p}(this.callback); + } + + function callback(int32 count, string foo) public { + // ... + } +} + +contract paffling { + // the first visibility "external" is for the function type, the second "internal" is + // for the callback variables + function(int32, string) external internal callback; + + function set_callback(function(int32, string) external c) public { + callback = c; + } + + function piffle() public { + callback(1, "paffled"); + } +} diff --git a/docs/examples/solana/payer_annotation.sol b/docs/examples/solana/payer_annotation.sol index bc6b44b78..74c3a09c2 100644 --- a/docs/examples/solana/payer_annotation.sol +++ b/docs/examples/solana/payer_annotation.sol @@ -2,11 +2,10 @@ import 'solana'; @program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER") contract Builder { - BeingBuilt other; function build_this() external { // When calling a constructor from an external function, the data account for the contract // 'BeingBuilt' should be passed as the 'BeingBuilt_dataAccount' in the client code. - other = new BeingBuilt("my_seed"); + BeingBuilt.new("my_seed"); } function build_that(address data_account, address payer_account) public { @@ -30,7 +29,7 @@ contract Builder { is_signer: false }) ]; - other = new BeingBuilt{accounts: metas}("my_seed"); + BeingBuilt.new{accounts: metas}("my_seed"); } } diff --git a/docs/examples/solana/program_id.sol b/docs/examples/solana/program_id.sol index 8152d5f87..4a9a2a513 100644 --- a/docs/examples/solana/program_id.sol +++ b/docs/examples/solana/program_id.sol @@ -5,14 +5,23 @@ contract Foo { } } -contract Bar { - Foo public foo; +contract OtherFoo { + function say_bye() public pure { + print("Bye from other foo"); + } +} +contract Bar { function create_foo() external { - foo = new Foo(); + Foo.new(); } function call_foo() public { - foo.say_hello(); + Foo.say_hello(); + } + + function foo_at_another_address(address other_foo_id) external { + OtherFoo.new{program_id: other_foo_id}(); + OtherFoo.say_bye{program_id: other_foo_id}(); } } diff --git a/docs/language/contracts.rst b/docs/language/contracts.rst index cf8ea4990..283791b17 100644 --- a/docs/language/contracts.rst +++ b/docs/language/contracts.rst @@ -31,10 +31,24 @@ Instantiation using new _______________________ Contracts can be created using the ``new`` keyword. The contract that is being created might have -constructor arguments, which need to be provided. +constructor arguments, which need to be provided. While on Polkadot and Ethereum constructors return the address +of the instantiated contract, on Solana, the address is either passed to the call using the ``{program_id: ...}`` call +argument or is declared above a contract with the ``@program_id`` annotation. As the constructor does not return +anything and its purpose is only to initialize the data account, the syntax ``new Contract()``is not idiomatic on Solana. +Instead, a function ``new`` is made available to call the constructor. + +.. tabs:: + + .. group-tab:: Polkadot + + .. include:: ../examples/polkadot/contract_new.sol + :code: solidity -.. include:: ../examples/polkadot/contract_new.sol - :code: solidity + + .. group-tab:: Solana + + .. include:: ../examples/solana/contract_new.sol + :code: solidity The constructor might fail for various reasons, for example ``require()`` might fail here. This can be handled using the :ref:`try-catch` statement, else errors cause the transaction to fail. @@ -87,15 +101,16 @@ can use. gas is a ``uint64``. .. include:: ../examples/polkadot/contract_gas_limit.sol :code: solidity + .. _solana_constructor: -Instantiating a contract on Solana -__________________________________ +Solana constructors +___________________ -On Solana, the contract being created must have the ``@program_id()`` annotation that specifies the program account to -which the contract code has been deployed. This account holds only the contract's executable binary. -When calling a constructor only once from an external function, no call arguments are needed. The data account -necessary to initialize the contract should be present in the IDL and is identified as ``contractName_dataAccount``. +Solidity contracts are coupled to a data account, which stores the contract's state variables on the blockchain. +This account must be initialized before calling other contract functions, if they require one. A contract constructor +initializes the data account and can be called with the ``new`` function. When invoking the constructor from another +contract, the data account to initialize appears in the IDL file and is identified as ``contractName_dataAccount``. In the example below, the IDL for the instruction ``test`` requires the ``hatchling_dataAccount`` account to be initialized as the new contract's data account. @@ -125,6 +140,24 @@ The sequence of the accounts in the ``AccountMeta`` array matters and must follo :ref:`IDL ordering `. +.. _solana_contract_call: + +Calling a contract on Solana +____________________________ + +A call to a contract on Solana follows a different syntax than that of Solidity on Ethereum or Polkadot. As contracts +cannot be a variable, calling a contract's function follows the syntax ``Contract.function()``. If the contract +definition contains the ``@program_id`` annotation, the CPI will be directed to the address declared inside the +annotation. + +If that annotation is not present, the program address must be manually specified with the ``{program_id: ... }`` call +argument. When both the annotation and the call argument are present, the compiler will forward the call to the address +specified in the call argument. + +.. include:: ../examples/solana/contract_call.sol + :code: solidity + + Base contracts, abstract contracts and interfaces ------------------------------------------------- diff --git a/docs/language/expressions.rst b/docs/language/expressions.rst index 1c5e04c94..a975a902b 100644 --- a/docs/language/expressions.rst +++ b/docs/language/expressions.rst @@ -112,15 +112,35 @@ ____ The keyword ``this`` evaluates to the current contract. The type of this is the type of the current contract. It can be cast to ``address`` or ``address payable`` using a cast. -.. include:: ../examples/expression_this.sol - :code: solidity +.. tabs:: + + .. group-tab:: Polkadot + + .. include:: ../examples/polkadot/expression_this.sol + :code: solidity + + + .. group-tab:: Solana + + .. include:: ../examples/solana/expression_this.sol + :code: solidity Function calls made via this are function calls through the external call mechanism; i.e. they have to serialize and deserialise the arguments and have the external call overhead. In addition, this only works with public functions. -.. include:: ../examples/expression_this_external_call.sol - :code: solidity +.. tabs:: + + .. group-tab:: Polkadot + + .. include:: ../examples/polkadot/expression_this_external_call.sol + :code: solidity + + + .. group-tab:: Solana + + .. include:: ../examples/solana/expression_this_external_call.sol + :code: solidity .. note:: diff --git a/docs/language/functions.rst b/docs/language/functions.rst index 9cc637ab0..07123ef7f 100644 --- a/docs/language/functions.rst +++ b/docs/language/functions.rst @@ -66,12 +66,25 @@ external functions. The called function must be declared public. Calling external functions requires ABI encoding the arguments, and ABI decoding the return values. This much more costly than an internal function call. -.. include:: ../examples/function_call_external.sol - :code: solidity -The syntax for calling external call is the same as the external call, except for -that it must be done on a contract type variable. Any error in an external call can -be handled with :ref:`try-catch`. +.. tabs:: + + .. group-tab:: Polkadot + + .. include:: ../examples/polkadot/function_call_external.sol + :code: solidity + + + .. group-tab:: Solana + + .. include:: ../examples/solana/function_call_external.sol + :code: solidity + + + +The syntax for calling a contract is the same as that of the external call, except +that it must be done on a contract type variable. Errors in external calls can +be handled with :ref:`try-catch` only on Polkadot. Internal calls and externals calls ___________________________________ @@ -289,8 +302,18 @@ This takes a single argument, which should be the ABI encoded arguments. The ret values are a ``boolean`` which indicates success if true, and the ABI encoded return value in ``bytes``. -.. include:: ../examples/function_call.sol - :code: solidity +.. tabs:: + + .. group-tab:: Polkadot + + .. include:: ../examples/polkadot/function_call.sol + :code: solidity + + + .. group-tab:: Solana + + .. include:: ../examples/solana/function_call.sol + :code: solidity Any value or gas limit can be specified for the external call. Note that no check is done to see if the called function is ``payable``, since the compiler does not know what function you are diff --git a/docs/language/types.rst b/docs/language/types.rst index 4635ee3c6..0e46b67ea 100644 --- a/docs/language/types.rst +++ b/docs/language/types.rst @@ -460,6 +460,14 @@ The expression ``this`` evaluates to the current contract, which can be cast to .. include:: ../examples/contract_type_cast_address.sol :code: solidity +.. _contracts_not_types: + +.. note:: + On Solana, contracts cannot exist as types, so contracts cannot be function parameters, function returns + or variables. Contracts on Solana are deployed to a defined address, which is often known during compile time, + so there is no need to hold that address as a variable underneath a contract type. + + Function Types ______________ @@ -485,8 +493,19 @@ the contract, and the function selector. An internal function type only stores t assigning a value to an external function selector, the contract and function must be specified, by using a function on particular contract instance. -.. include:: ../examples/function_type_callback.sol - :code: solidity +.. tabs:: + + .. group-tab:: Polkadot + + .. include:: ../examples/polkadot/function_type_callback.sol + :code: solidity + + + .. group-tab:: Solana + + .. include:: ../examples/solana/function_type_callback.sol + :code: solidity + Storage References __________________ diff --git a/docs/requirements.txt b/docs/requirements.txt index efd6c94e0..3696da652 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,4 +3,5 @@ sphinx_rtd_theme>=0.5.2 pygments-lexer-solidity>=0.7.0 sphinx-a4doc>=1.2.1 sphinx>=2.1.0 +sphinx-tabs>=3.4.1 diff --git a/docs/targets/solana.rst b/docs/targets/solana.rst index 3995281d0..49564cf36 100644 --- a/docs/targets/solana.rst +++ b/docs/targets/solana.rst @@ -46,9 +46,11 @@ Runtime - The Solana target requires `Solana `_ v1.8.1. - Function selectors are eight bytes wide and known as *discriminators*. - Solana provides different builtins, e.g. ``block.slot`` and ``tx.accounts``. -- When calling an external function or instantiating a contract using ``new``, one +- When calling an external function or invoking a contract's constructor, one :ref:`needs to provide ` the necessary accounts for the transaction. - The keyword ``this`` returns the contract's program account, also know as program id. +- Contracts :ref:`cannot be types ` on Solana and :ref:`calls to contracts ` + follow a different syntax. Compute budget @@ -127,8 +129,8 @@ _____________________________________ When developing contracts for Solana, programs are usually deployed to a well known account. The account can be specified in the source code using an annotation -``@program_id``. If you want to instantiate a contract using the -``new ContractName()`` syntax, then the contract must have a program_id annotation. +``@program_id`` if it is known beforehand. If you want to call a contract via an external call, +either the contract must have a ``@program_id`` annotation or the ``{program_id: ..}`` call argument must be present. .. include:: ../examples/solana/program_id.sol :code: solidity diff --git a/integration/anchor/tests/call_anchor.spec.ts b/integration/anchor/tests/call_anchor.spec.ts index 36d3f1e91..13ac1e4ec 100644 --- a/integration/anchor/tests/call_anchor.spec.ts +++ b/integration/anchor/tests/call_anchor.spec.ts @@ -50,11 +50,8 @@ describe('Call Anchor program from Solidity via IDL', () => { const ret = await program.methods.data().accounts({ dataAccount: storage.publicKey }).view(); expect(ret).toEqual(data.publicKey); + const anchor_program_id = new PublicKey("z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq"); const remainingAccounts: AccountMeta[] = [{ - pubkey: new PublicKey("z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq"), - isSigner: false, - isWritable: false, - }, { pubkey: data.publicKey, isSigner: true, isWritable: true, @@ -65,7 +62,10 @@ describe('Call Anchor program from Solidity via IDL', () => { }]; await program.methods.test(payer.publicKey) - .accounts({ dataAccount: storage.publicKey }) + .accounts({ + dataAccount: storage.publicKey, + anchor_programId: anchor_program_id, + }) .remainingAccounts(remainingAccounts) .signers([data, payer]) .rpc(); diff --git a/integration/solana/create_contract.sol b/integration/solana/create_contract.sol index 4b12718b6..b195ff79b 100644 --- a/integration/solana/create_contract.sol +++ b/integration/solana/create_contract.sol @@ -1,27 +1,25 @@ import 'solana'; contract creator { - Child public c; - Child public c_metas; function create_child() external { print("Going to create child"); - c = new Child(); + Child.new(); - c.say_hello(); + Child.say_hello(); } function create_seed1(bytes seed, bytes1 bump, uint64 space) external { print("Going to create Seed1"); - Seed1 s = new Seed1(seed, bump, space); + Seed1.new(seed, bump, space); - s.say_hello(); + Seed1.say_hello(); } function create_seed2(bytes seed, uint32 space) external { print("Going to create Seed2"); - new Seed2(seed, space); + Seed2.new(seed, space); } function create_child_with_metas(address child, address payer) public { @@ -32,13 +30,13 @@ contract creator { AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false}) ]; - c_metas = new Child{accounts: metas}(); - c_metas.use_metas(); + Child.new{accounts: metas}(); + Child.use_metas(); } function create_without_annotation() external { - MyCreature cc = new MyCreature(); - cc.say_my_name(); + MyCreature.new(); + MyCreature.say_my_name(); } } diff --git a/integration/solana/external_call.sol b/integration/solana/external_call.sol index b4d194b8d..c95f812dd 100644 --- a/integration/solana/external_call.sol +++ b/integration/solana/external_call.sol @@ -1,21 +1,21 @@ contract caller { - function do_call(callee e, int64 v) public { - e.set_x(v); + function do_call(address e, int64 v) public { + callee.set_x{program_id: e}(v); } - function do_call2(callee e, int64 v) view public returns (int64) { - return v + e.get_x(); + function do_call2(address e, int64 v) view public returns (int64) { + return v + callee.get_x{program_id: e}(); } // call two different functions - function do_call3(callee e, callee2 e2, int64[4] memory x, string memory y) pure public returns (int64, string memory) { - return (e2.do_stuff(x), e.get_name()); + function do_call3(address e, address e2, int64[4] memory x, string memory y) pure public returns (int64, string memory) { + return (callee2.do_stuff{program_id: e2}(x), callee.get_name{program_id: e}()); } // call two different functions - function do_call4(callee e, callee2 e2, int64[4] memory x, string memory y) pure public returns (int64, string memory) { - return (e2.do_stuff(x), e.call2(e2, y)); + function do_call4(address e, address e2, int64[4] memory x, string memory y) pure public returns (int64, string memory) { + return (callee2.do_stuff{program_id: e2}(x), callee.call2{program_id: e}(e2, y)); } function who_am_i() public view returns (address) { @@ -34,8 +34,8 @@ contract callee { return x; } - function call2(callee2 e2, string s) public pure returns (string) { - return e2.do_stuff2(s); + function call2(address e2, string s) public pure returns (string) { + return callee2.do_stuff2{program_id: e2}(s); } function get_name() public pure returns (string) { diff --git a/integration/solana/runtime_errors.sol b/integration/solana/runtime_errors.sol index 6115b0935..cb76206a7 100644 --- a/integration/solana/runtime_errors.sol +++ b/integration/solana/runtime_errors.sol @@ -61,8 +61,8 @@ contract RuntimeErrors { } // external call failed - function call_ext(Creature e) public { - e.say_my_name(); + function call_ext() public { + Creature.say_my_name(); } function i_will_revert() public { diff --git a/solang-parser/src/solidity.lalrpop b/solang-parser/src/solidity.lalrpop index 7f5552c48..f9901c29a 100644 --- a/solang-parser/src/solidity.lalrpop +++ b/solang-parser/src/solidity.lalrpop @@ -483,6 +483,10 @@ NoFunctionTyPrecedence0: Expression = { Expression::MemberAccess(Loc::File(file_no, a, b), Box::new(e), Identifier { loc: Loc::File(file_no, al, b), name: "address".to_string() }) }, + "." "new" => { + Expression::MemberAccess(Loc::File(file_no, a, b), Box::new(e), + Identifier { loc: Loc::File(file_no, al, b), name: "new".to_string() }) + }, => Expression::Type(Loc::File(file_no, l, r), ty), "[" > "]" => { Expression::ArrayLiteral(Loc::File(file_no, a, b), v) diff --git a/src/abi/anchor.rs b/src/abi/anchor.rs index 3e9e983d5..28e013b36 100644 --- a/src/abi/anchor.rs +++ b/src/abi/anchor.rs @@ -103,7 +103,7 @@ fn idl_instructions( ) -> Vec { let mut instructions: Vec = Vec::new(); - if !contract.have_constructor(ns) { + if contract.constructors(ns).is_empty() { instructions.push(IdlInstruction { name: "new".to_string(), docs: None, diff --git a/src/abi/tests.rs b/src/abi/tests.rs index a657746a8..6e7f010d6 100644 --- a/src/abi/tests.rs +++ b/src/abi/tests.rs @@ -1697,30 +1697,25 @@ fn other_collected_public_keys() { let src = r#" import 'solana'; -anchor_anchor constant anchor = anchor_anchor(address'SysvarRent111111111111111111111111111111111'); - -interface anchor_anchor { +@program_id("SysvarRent111111111111111111111111111111111") +interface anchor { @selector([0xaf,0xaf,0x6d,0x1f,0x0d,0x98,0x9b,0xed]) function initialize(bool data1) view external; } -associated constant ass = associated(address'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); - +@program_id("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") interface associated { @selector([0xaf,0xaf,0x6d,0x1f,0x0d,0x98,0x9b,0xed]) function initialize(bool data1) view external; } - -clock_interface constant my_clock = clock_interface(address'SysvarC1ock11111111111111111111111111111111'); - +@program_id("SysvarC1ock11111111111111111111111111111111") interface clock_interface { @selector([0xaf,0xaf,0x6d,0x1f,0x0d,0x98,0x9b,0xed]) function initialize(bool data1) view external; } -other_interface constant other_inter = other_interface(address'z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq'); - +@program_id("z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq") interface other_interface { @selector([0xaf,0xaf,0x6d,0x1f,0x0d,0x98,0x9b,0xed]) function initialize(bool data1) view external; @@ -1732,15 +1727,15 @@ contract Test { } function call_2() public { - ass.initialize(false); + associated.initialize(false); } function call_3() public { - my_clock.initialize(true); + clock_interface.initialize(true); } function call_4() public { - other_inter.initialize(false); + other_interface.initialize(false); } } "#; @@ -1798,13 +1793,10 @@ fn multiple_contracts() { import 'solana'; contract creator { - Child public c; - function create_child() external returns (uint64) { print("Going to create child"); - c = new Child(); - - return c.say_hello(); + Child.new(); + return Child.say_hello(); } } @@ -1828,17 +1820,15 @@ contract Child { let idl = generate_anchor_idl(0, &ns, "0.1.0"); assert_eq!(idl.instructions[0].name, "new"); - assert_eq!(idl.instructions[1].name, "c"); - assert_eq!(idl.instructions[2].name, "create_child"); + assert_eq!(idl.instructions[1].name, "create_child"); assert_eq!( - idl.instructions[2].accounts, + idl.instructions[1].accounts, vec![ - idl_account("dataAccount", true, false), + idl_account("systemProgram", false, false), + idl_account("Child_programId", false, false), idl_account("payer", true, true), idl_account("Child_dataAccount", true, true), - idl_account("Child_programId", false, false), - idl_account("systemProgram", false, false), idl_account("clock", false, false), ] ); @@ -1851,11 +1841,9 @@ fn constructor_double_payer() { @program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER") contract Builder { - BeingBuilt other; - @payer(payer_account) constructor() { - other = new BeingBuilt("abc"); + BeingBuilt.new("abc"); } } @@ -1883,9 +1871,9 @@ contract BeingBuilt { idl_account("dataAccount", true, true), idl_account("payer_account", true, true), idl_account("systemProgram", false, false), + idl_account("BeingBuilt_programId", false, false), idl_account("other_account", true, true), idl_account("BeingBuilt_dataAccount", true, false), - idl_account("BeingBuilt_programId", false, false), ] ); } @@ -1956,19 +1944,17 @@ contract starter { fn account_transfer_recursive() { let src = r#" contract CT3 { - CT2 ct2; @payer(three_payer) constructor() { - ct2 = new CT2(); + CT2.new(); } } @program_id("Ha2EGxARbSYpqNZkkvZUUGEyx3pu7Mg9pvMsuEJuWNjH") contract CT2 { - CT1 ct1; @payer(two_payer) constructor() { - ct1 = new CT1(block.timestamp); + CT1.new(block.timestamp); } } @@ -2005,10 +1991,10 @@ contract CT1 { idl_account("dataAccount", true, true), idl_account("two_payer", true, true), idl_account("clock", false, false), + idl_account("systemProgram", false, false), + idl_account("CT1_programId", false, false), idl_account("one_payer", true, true), idl_account("CT1_dataAccount", true, true), - idl_account("CT1_programId", false, false), - idl_account("systemProgram", false, false), ] ); @@ -2019,13 +2005,45 @@ contract CT1 { idl_account("dataAccount", true, true), idl_account("three_payer", true, true), idl_account("systemProgram", false, false), + idl_account("CT2_programId", false, false), idl_account("two_payer", true, true), idl_account("CT2_dataAccount", true, true), - idl_account("CT2_programId", false, false), idl_account("clock", false, false), + idl_account("CT1_programId", false, false), idl_account("one_payer", true, true), idl_account("CT1_dataAccount", true, true), - idl_account("CT1_programId", false, false), + ] + ); +} + +#[test] +fn default_constructor() { + let src = r#" +contract Foo { + uint b; + function get_b() public returns (uint) { + return b; + } +} + +contract Other { + function call_foo(address id) external { + Foo.new{program_id: id}(); + } +} + "#; + + let mut ns = generate_namespace(src); + codegen(&mut ns, &Options::default()); + let idl = generate_anchor_idl(1, &ns, "0.0.1"); + + assert_eq!(idl.instructions[1].name, "call_foo"); + assert_eq!( + idl.instructions[1].accounts, + vec![ + idl_account("Foo_dataAccount", true, false), + idl_account("Foo_programId", false, false), + idl_account("systemProgram", false, false) ] ); } diff --git a/src/bin/idl/mod.rs b/src/bin/idl/mod.rs index d5ff7d491..db688203f 100644 --- a/src/bin/idl/mod.rs +++ b/src/bin/idl/mod.rs @@ -63,14 +63,6 @@ fn idl_file(file: &OsStr, output: &Option) { } fn write_solidity(idl: &Idl, mut f: File) -> Result<(), std::io::Error> { - if let Some(program_id) = program_id(idl) { - writeln!( - f, - "anchor_{} constant {} = anchor_{}(address'{}');\n", - idl.name, idl.name, idl.name, program_id - )?; - } - let mut ty_names = idl .types .iter() @@ -212,7 +204,10 @@ fn write_solidity(idl: &Idl, mut f: File) -> Result<(), std::io::Error> { docs(&mut f, 0, &idl.docs)?; - writeln!(f, "interface anchor_{} {{", idl.name)?; + if let Some(program_id) = program_id(idl) { + writeln!(f, "@program_id(\"{}\")", program_id)?; + } + writeln!(f, "interface {} {{", idl.name)?; let mut instruction_names = idl .instructions diff --git a/src/codegen/constructor.rs b/src/codegen/constructor.rs index 2616c8ddc..160223479 100644 --- a/src/codegen/constructor.rs +++ b/src/codegen/constructor.rs @@ -47,14 +47,24 @@ pub(super) fn call_constructor( .as_ref() .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt)); let address = if ns.target == Target::Solana { - Some(Expression::NumberLiteral { - loc: Loc::Codegen, - ty: Type::Address(false), - value: BigInt::from_bytes_be( - Sign::Plus, - ns.contracts[contract_no].program_id.as_ref().unwrap(), - ), - }) + if let Some(literal_id) = &ns.contracts[contract_no].program_id { + Some(Expression::NumberLiteral { + loc: Loc::Codegen, + ty: Type::Address(false), + value: BigInt::from_bytes_be(Sign::Plus, literal_id), + }) + } else { + let address = expression( + call_args.program_id.as_ref().unwrap(), + cfg, + callee_contract_no, + func, + ns, + vartab, + opt, + ); + Some(address) + } } else { None }; diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index cf22f3ed1..047f79002 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -234,7 +234,7 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options) { all_cfg.push(cfg); ns.contracts[contract_no].initializer = Some(pos); - if !ns.contracts[contract_no].have_constructor(ns) { + if ns.contracts[contract_no].constructors(ns).is_empty() { // generate the default constructor let func = ns.default_constructor(contract_no); let cfg_no = all_cfg.len(); diff --git a/src/codegen/solana_accounts/account_collection.rs b/src/codegen/solana_accounts/account_collection.rs index dc94a2dd8..d31fd4c92 100644 --- a/src/codegen/solana_accounts/account_collection.rs +++ b/src/codegen/solana_accounts/account_collection.rs @@ -72,6 +72,18 @@ impl RecurseData<'_> { }, ); } + + fn add_program_id(&mut self, contract_name: &String) { + self.add_account( + format!("{}_programId", contract_name), + &SolanaAccount { + loc: Loc::Codegen, + is_signer: false, + is_writer: false, + generated: true, + }, + ) + } } /// Collect the accounts this contract needs @@ -333,10 +345,21 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) { // If the one passes the AccountMeta vector to the constructor call, there is no // need to collect accounts for the IDL. if let Some(constructor_no) = constructor_no { - transfer_accounts(loc, *contract_no, *constructor_no, data, false); + transfer_accounts(loc, *contract_no, *constructor_no, data); + } else { + data.add_account( + format!("{}_dataAccount", data.contracts[*contract_no].name), + &SolanaAccount { + loc: *loc, + is_signer: false, + is_writer: true, + generated: true, + }, + ); } } + data.add_program_id(&data.contracts[*contract_no].name); data.add_system_account(); } Instr::ExternalCall { @@ -387,8 +410,15 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) { if let Some(accounts) = accounts { accounts.recurse(data, check_expression); - } else if let Some((contract_no, function_no)) = contract_function_no { - transfer_accounts(loc, *contract_no, *function_no, data, program_id_populated); + } + + if let Some((contract_no, function_no)) = contract_function_no { + if !program_id_populated { + data.add_program_id(&data.contracts[*contract_no].name); + } + if accounts.is_none() { + transfer_accounts(loc, *contract_no, *function_no, data); + } } } Instr::EmitEvent { @@ -464,7 +494,6 @@ fn transfer_accounts( contract_no: usize, function_no: usize, data: &mut RecurseData, - program_id_present: bool, ) { let accounts_to_add = data.functions[function_no].solana_accounts.borrow().clone(); @@ -515,21 +544,6 @@ fn transfer_accounts( data.add_account(name, &account); } - if !program_id_present { - data.functions[data.ast_no] - .solana_accounts - .borrow_mut() - .insert( - format!("{}_programId", data.contracts[contract_no].name), - SolanaAccount { - is_signer: false, - is_writer: false, - generated: true, - loc: *loc, - }, - ); - } - let cfg_no = data.contracts[contract_no].all_functions[&function_no]; data.next_queue.insert((contract_no, cfg_no)); data.next_queue.insert((data.contract_no, data.cfg_func_no)); diff --git a/src/codegen/solana_accounts/account_management.rs b/src/codegen/solana_accounts/account_management.rs index a35f6c7e1..84f21a977 100644 --- a/src/codegen/solana_accounts/account_management.rs +++ b/src/codegen/solana_accounts/account_management.rs @@ -152,6 +152,31 @@ fn process_instruction( }; *accounts = Some(metas_vector); } + Instr::Constructor { + contract_no, + constructor_no: None, + accounts, + .. + } => { + let name_to_index = format!("{}_dataAccount", contracts[*contract_no].name); + let account_index = functions[ast_no] + .solana_accounts + .borrow() + .get_index_of(&name_to_index) + .unwrap(); + let ptr_to_address = accounts_vector_key_at_index(account_index); + let account_metas = vec![account_meta_literal(ptr_to_address, false, true)]; + let metas_vector = Expression::ArrayLiteral { + loc: Loc::Codegen, + ty: Type::Array( + Box::new(Type::Struct(StructType::AccountMeta)), + vec![ArrayLength::Fixed(BigInt::from(account_metas.len()))], + ), + dimensions: vec![1], + values: account_metas, + }; + *accounts = Some(metas_vector); + } Instr::AccountAccess { loc, name, var_no } => { // This could have been an Expression::AccountAccess if we had a three-address form. // The amount of code necessary to traverse all Instructions and all expressions recursively diff --git a/src/codegen/statements/mod.rs b/src/codegen/statements/mod.rs index 74ab65688..5d25a3776 100644 --- a/src/codegen/statements/mod.rs +++ b/src/codegen/statements/mod.rs @@ -12,16 +12,15 @@ use super::{ yul::inline_assembly_cfg, Builtin, Expression, Options, }; -use crate::sema::{ - ast::{ - self, ArrayLength, DestructureField, Function, Namespace, RetrieveType, Statement, Type, - Type::Uint, - }, - Recurse, +use crate::sema::ast::{ + self, ArrayLength, DestructureField, Function, Namespace, RetrieveType, SolanaAccount, + Statement, Type, Type::Uint, }; +use crate::sema::solana_accounts::BuiltinAccounts; +use crate::sema::Recurse; use num_bigint::BigInt; use num_traits::Zero; -use solang_parser::pt::{self, CodeLocation, Loc::Codegen}; +use solang_parser::pt::{self, CodeLocation, Loc, Loc::Codegen}; mod try_catch; @@ -1154,6 +1153,15 @@ impl Namespace { func.body = vec![Statement::Return(Codegen, None)]; func.has_body = true; + func.solana_accounts.borrow_mut().insert( + BuiltinAccounts::DataAccount.to_string(), + SolanaAccount { + loc: Loc::Codegen, + is_signer: false, + is_writer: true, + generated: true, + }, + ); func } diff --git a/src/emit/solana/target.rs b/src/emit/solana/target.rs index f0e08a9c3..d113e4d19 100644 --- a/src/emit/solana/target.rs +++ b/src/emit/solana/target.rs @@ -1227,20 +1227,15 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { binary: &Binary<'b>, function: FunctionValue<'b>, _success: Option<&mut BasicValueEnum<'b>>, - contract_no: usize, - _address: PointerValue<'b>, + _contract_no: usize, + address: PointerValue<'b>, encoded_args: BasicValueEnum<'b>, encoded_args_len: BasicValueEnum<'b>, mut contract_args: ContractArgs<'b>, - ns: &ast::Namespace, + _ns: &ast::Namespace, _loc: Loc, ) { - let const_program_id = binary.emit_global_string( - "const_program_id", - ns.contracts[contract_no].program_id.as_ref().unwrap(), - true, - ); - contract_args.program_id = Some(const_program_id); + contract_args.program_id = Some(address); let payload = binary.vector_bytes(encoded_args); let payload_len = encoded_args_len.into_int_value(); diff --git a/src/sema/ast.rs b/src/sema/ast.rs index cd4dd592d..fe26c21f0 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -804,14 +804,17 @@ impl Contract { /// Does the constructor require arguments. Should be false is there is no constructor pub fn constructor_needs_arguments(&self, ns: &Namespace) -> bool { - self.have_constructor(ns) && self.no_args_constructor(ns).is_none() + !self.constructors(ns).is_empty() && self.no_args_constructor(ns).is_none() } - /// Does the contract have a constructor defined - pub fn have_constructor(&self, ns: &Namespace) -> bool { + /// Does the contract have a constructor defined? + /// Returns all the constructor function numbers if any + pub fn constructors(&self, ns: &Namespace) -> Vec { self.functions .iter() - .any(|func_no| ns.functions[*func_no].is_constructor()) + .copied() + .filter(|func_no| ns.functions[*func_no].is_constructor()) + .collect::>() } /// Return the constructor with no arguments @@ -1226,6 +1229,7 @@ pub struct CallArgs { pub accounts: Option>, pub seeds: Option>, pub flags: Option>, + pub program_id: Option>, } impl Recurse for CallArgs { diff --git a/src/sema/builtin.rs b/src/sema/builtin.rs index 2b8e38018..1908322d1 100644 --- a/src/sema/builtin.rs +++ b/src/sema/builtin.rs @@ -10,6 +10,7 @@ use super::expression::{ExprContext, ResolveTo}; use super::symtable::Symtable; use crate::sema::ast::{RetrieveType, Tag, UserTypeDecl}; use crate::sema::expression::resolve_expression::expression; +use crate::sema::namespace::ResolveTypeContext; use crate::Target; use num_bigint::BigInt; use num_traits::One; @@ -1082,7 +1083,7 @@ pub(super) fn resolve_namespace_call( let ty = ns.resolve_type( context.file_no, context.contract_no, - false, + ResolveTypeContext::None, ¶m.ty, diagnostics, )?; @@ -1123,7 +1124,7 @@ pub(super) fn resolve_namespace_call( let ty = ns.resolve_type( context.file_no, context.contract_no, - false, + ResolveTypeContext::None, args[1].remove_parenthesis(), diagnostics, )?; diff --git a/src/sema/contracts.rs b/src/sema/contracts.rs index abaf4a6b6..61fdb5e0c 100644 --- a/src/sema/contracts.rs +++ b/src/sema/contracts.rs @@ -1090,7 +1090,7 @@ fn check_base_args(contract_no: usize, ns: &mut ast::Namespace) { }) .collect::>(); - if contract.have_constructor(ns) { + if !contract.constructors(ns).is_empty() { for constructor_no in contract .functions .iter() diff --git a/src/sema/expression/constructor.rs b/src/sema/expression/constructor.rs index 09ed0ee8a..8977b7fb0 100644 --- a/src/sema/expression/constructor.rs +++ b/src/sema/expression/constructor.rs @@ -5,9 +5,9 @@ use crate::sema::diagnostics::Diagnostics; use crate::sema::expression::function_call::{collect_call_args, parse_call_args}; use crate::sema::expression::resolve_expression::expression; use crate::sema::expression::{ExprContext, ResolveTo}; +use crate::sema::namespace::ResolveTypeContext; use crate::sema::symtable::Symtable; use crate::sema::unused_variable::used_variable; -use crate::Target; use solang_parser::diagnostics::Diagnostic; use solang_parser::pt; use solang_parser::pt::{CodeLocation, Visibility}; @@ -60,8 +60,6 @@ fn constructor( return Err(()); } - solana_constructor_check(loc, no, diagnostics, context, &call_args, ns); - // check for circular references if circular_reference(no, context_contract_no, ns) { diagnostics.push(Diagnostic::error( @@ -214,9 +212,13 @@ pub fn constructor_named_args( ) -> Result { let (ty, call_args, _) = collect_call_args(ty, diagnostics)?; - let call_args = parse_call_args(loc, &call_args, false, context, ns, symtable, diagnostics)?; - - let no = match ns.resolve_type(context.file_no, context.contract_no, false, ty, diagnostics)? { + let no = match ns.resolve_type( + context.file_no, + context.contract_no, + ResolveTypeContext::None, + ty, + diagnostics, + )? { Type::Contract(n) => n, _ => { diagnostics.push(Diagnostic::error(*loc, "contract expected".to_string())); @@ -224,6 +226,17 @@ pub fn constructor_named_args( } }; + let call_args = parse_call_args( + loc, + &call_args, + Some(no), + false, + context, + ns, + symtable, + diagnostics, + )?; + // The current contract cannot be constructed with new. In order to create // the contract, we need the code hash of the contract. Part of that code // will be code we're emitted here. So we end up with a crypto puzzle. @@ -260,8 +273,6 @@ pub fn constructor_named_args( return Err(()); } - solana_constructor_check(loc, no, diagnostics, context, &call_args, ns); - // check for circular references if circular_reference(no, context_contract_no, ns) { diagnostics.push(Diagnostic::error( @@ -436,7 +447,13 @@ pub fn new( ty }; - let ty = ns.resolve_type(context.file_no, context.contract_no, false, ty, diagnostics)?; + let ty = ns.resolve_type( + context.file_no, + context.contract_no, + ResolveTypeContext::None, + ty, + diagnostics, + )?; match &ty { Type::Array(ty, dim) => { @@ -461,8 +478,16 @@ pub fn new( } Type::String | Type::DynamicBytes => {} Type::Contract(n) => { - let call_args = - parse_call_args(loc, &call_args, false, context, ns, symtable, diagnostics)?; + let call_args = parse_call_args( + loc, + &call_args, + Some(*n), + false, + context, + ns, + symtable, + diagnostics, + )?; return constructor(loc, *n, args, call_args, context, ns, symtable, diagnostics); } @@ -577,29 +602,51 @@ pub(super) fn deprecated_constructor_arguments( /// When calling a constructor on Solana, we must verify it the contract we are instantiating has /// a program id annotation and require the accounts call argument if the call is inside a loop. -fn solana_constructor_check( +pub(super) fn solana_constructor_check( loc: &pt::Loc, constructor_contract_no: usize, diagnostics: &mut Diagnostics, context: &ExprContext, call_args: &CallArgs, - ns: &Namespace, + ns: &mut Namespace, ) { - if ns.target != Target::Solana { - return; - } - - if ns.contracts[constructor_contract_no].program_id.is_none() { + if !ns.contracts[constructor_contract_no].instantiable { diagnostics.push(Diagnostic::error( *loc, format!( - "in order to instantiate contract '{}', a @program_id is required on contract '{}'", + "cannot construct '{}' of type '{}'", ns.contracts[constructor_contract_no].name, - ns.contracts[constructor_contract_no].name + ns.contracts[constructor_contract_no].ty ), )); } + if let Some(context_contract) = context.contract_no { + if circular_reference(constructor_contract_no, context_contract, ns) { + diagnostics.push(Diagnostic::error( + *loc, + format!( + "circular reference creating contract '{}'", + ns.contracts[constructor_contract_no].name + ), + )); + } + + if !ns.contracts[context_contract] + .creates + .contains(&constructor_contract_no) + { + ns.contracts[context_contract] + .creates + .push(constructor_contract_no); + } + } else { + diagnostics.push(Diagnostic::error( + *loc, + "constructors not allowed in free standing functions".to_string(), + )); + } + if !context.in_a_loop() || call_args.accounts.is_some() { return; } diff --git a/src/sema/expression/function_call.rs b/src/sema/expression/function_call.rs index 784012f72..2cf923595 100644 --- a/src/sema/expression/function_call.rs +++ b/src/sema/expression/function_call.rs @@ -6,15 +6,19 @@ use crate::sema::ast::{ }; use crate::sema::contracts::is_base; use crate::sema::diagnostics::Diagnostics; -use crate::sema::expression::constructor::{deprecated_constructor_arguments, new}; +use crate::sema::expression::constructor::{ + deprecated_constructor_arguments, new, solana_constructor_check, +}; use crate::sema::expression::literals::{named_struct_literal, struct_literal}; use crate::sema::expression::resolve_expression::expression; use crate::sema::expression::{ExprContext, ResolveTo}; use crate::sema::format::string_format; +use crate::sema::namespace::ResolveTypeContext; use crate::sema::symtable::Symtable; use crate::sema::unused_variable::check_function_call; use crate::sema::{builtin, using}; use crate::Target; +use num_bigint::{BigInt, Sign}; use solang_parser::diagnostics::Diagnostic; use solang_parser::pt; use solang_parser::pt::{CodeLocation, Loc, Visibility}; @@ -101,7 +105,16 @@ pub(super) fn call_function_type( mutability, } = ty { - let call_args = parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?; + let call_args = parse_call_args( + loc, + call_args, + None, + true, + context, + ns, + symtable, + diagnostics, + )?; if let Some(value) = &call_args.value { if !value.const_zero(ns) && !matches!(mutability, Mutability::Payable(_)) { @@ -495,6 +508,7 @@ fn try_namespace( var: &pt::Expression, func: &pt::Identifier, args: &[pt::Expression], + call_args: &[&pt::NamedArgument], call_args_loc: Option, context: &ExprContext, ns: &mut Namespace, @@ -592,7 +606,22 @@ fn try_namespace( // is a base contract of us if let Some(contract_no) = context.contract_no { if is_base(call_contract_no, contract_no, ns) { - if let Some(loc) = call_args_loc { + if ns.target == Target::Solana && call_args_loc.is_some() { + // On Solana, assume this is an external call + return contract_call_pos_args( + loc, + call_contract_no, + func, + None, + args, + call_args, + context, + ns, + symtable, + diagnostics, + resolve_to, + ); + } else if let Some(loc) = call_args_loc { diagnostics.push(Diagnostic::error( loc, "call arguments not allowed on internal calls".to_string(), @@ -619,13 +648,31 @@ fn try_namespace( symtable, diagnostics, )?)); - } else { + } else if ns.target != Target::Solana { diagnostics.push(Diagnostic::error( *loc, "function calls via contract name are only valid for base contracts".into(), )); } } + + if ns.target == Target::Solana { + // If the symbol resolves to a contract, this is an external call on Solana + // regardless of whether we are inside a contract or not. + return contract_call_pos_args( + loc, + call_contract_no, + func, + None, + args, + call_args, + context, + ns, + symtable, + diagnostics, + resolve_to, + ); + } } } @@ -873,7 +920,7 @@ fn try_user_type( if let Ok(Type::UserType(no)) = ns.resolve_type( context.file_no, context.contract_no, - false, + ResolveTypeContext::None, var, &mut Diagnostics::default(), ) { @@ -1069,148 +1116,19 @@ fn try_type_method( } Type::Contract(ext_contract_no) => { - let call_args = - parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?; - - let mut errors = Diagnostics::default(); - let mut name_matches: Vec = Vec::new(); - - for function_no in ns.contracts[*ext_contract_no].all_functions.keys() { - if func.name != ns.functions[*function_no].name - || ns.functions[*function_no].ty != pt::FunctionTy::Function - { - continue; - } - - name_matches.push(*function_no); - } - - for function_no in &name_matches { - let params_len = ns.functions[*function_no].params.len(); - - if params_len != args.len() { - errors.push(Diagnostic::error( - *loc, - format!( - "function expects {} arguments, {} provided", - params_len, - args.len() - ), - )); - continue; - } - - let mut matches = true; - let mut cast_args = Vec::new(); - - // check if arguments can be implicitly casted - for (i, arg) in args.iter().enumerate() { - let ty = ns.functions[*function_no].params[i].ty.clone(); - - let arg = match expression( - arg, - context, - ns, - symtable, - &mut errors, - ResolveTo::Type(&ty), - ) { - Ok(e) => e, - Err(_) => { - matches = false; - continue; - } - }; - - match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) { - Ok(expr) => cast_args.push(expr), - Err(()) => { - matches = false; - continue; - } - } - } - - if matches { - if !ns.functions[*function_no].is_public() { - diagnostics.push(Diagnostic::error( - *loc, - format!("function '{}' is not 'public' or 'external'", func.name), - )); - return Err(()); - } - - if let Some(value) = &call_args.value { - if !value.const_zero(ns) && !ns.functions[*function_no].is_payable() { - diagnostics.push(Diagnostic::error( - *loc, - format!( - "sending value to function '{}' which is not payable", - func.name - ), - )); - return Err(()); - } - } - - let func = &ns.functions[*function_no]; - let returns = function_returns(func, resolve_to); - let ty = function_type(func, true, resolve_to); - - return Ok(Some(Expression::ExternalFunctionCall { - loc: *loc, - returns, - function: Box::new(Expression::ExternalFunction { - loc: *loc, - ty, - function_no: *function_no, - address: Box::new(var_expr.cast( - &var.loc(), - &Type::Contract(func.contract_no.unwrap()), - true, - ns, - diagnostics, - )?), - }), - args: cast_args, - call_args, - })); - } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) { - return Err(()); - } - } - - // what about call args - match using::try_resolve_using_call( + return contract_call_pos_args( loc, + *ext_contract_no, func, - var_expr, - context, + Some(var_expr), args, + call_args, + context, + ns, symtable, diagnostics, - ns, resolve_to, - ) { - Ok(Some(expr)) => { - return Ok(Some(expr)); - } - Ok(None) => (), - Err(_) => { - return Err(()); - } - } - - if name_matches.len() == 1 { - diagnostics.extend(errors); - } else if name_matches.len() != 1 { - diagnostics.push(Diagnostic::error( - *loc, - "cannot find overloaded function which matches signature".to_string(), - )); - } - - return Err(()); + ); } Type::Address(is_payable) => { @@ -1287,8 +1205,16 @@ fn try_type_method( }; if let Some(ty) = ty { - let call_args = - parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?; + let call_args = parse_call_args( + loc, + call_args, + None, + true, + context, + ns, + symtable, + diagnostics, + )?; if ty != CallTy::Regular && call_args.value.is_some() { diagnostics.push(Diagnostic::error( @@ -1389,6 +1315,7 @@ pub(super) fn method_call_pos_args( var, func, args, + call_args, call_args_loc, context, ns, @@ -1397,6 +1324,7 @@ pub(super) fn method_call_pos_args( resolve_to, )? { return Ok(resolved_call); + } else { } if let Some(resolved_call) = try_user_type( @@ -1429,6 +1357,26 @@ pub(super) fn method_call_pos_args( &path, &mut Diagnostics::default(), ) { + if let Some(callee_contract) = + is_solana_external_call(&list, context.contract_no, &call_args_loc, ns) + { + if let Some(resolved_call) = contract_call_pos_args( + &var.loc(), + callee_contract, + func, + None, + args, + call_args, + context, + ns, + symtable, + diagnostics, + resolve_to, + )? { + return Ok(resolved_call); + } + } + if let Some(loc) = call_args_loc { diagnostics.push(Diagnostic::error( loc, @@ -1620,7 +1568,22 @@ pub(super) fn method_call_named_args( // is a base contract of us if let Some(contract_no) = context.contract_no { if is_base(call_contract_no, contract_no, ns) { - if let Some(loc) = call_args_loc { + if ns.target == Target::Solana && call_args_loc.is_some() { + // If on Solana, assume this is an external call + return contract_call_named_args( + loc, + None, + func_name, + args, + call_args, + call_contract_no, + context, + symtable, + ns, + diagnostics, + resolve_to, + ); + } else if let Some(loc) = call_args_loc { diagnostics.push(Diagnostic::error( loc, "call arguments not allowed on internal calls".to_string(), @@ -1646,13 +1609,31 @@ pub(super) fn method_call_named_args( symtable, diagnostics, ); - } else { + } else if ns.target != Target::Solana { diagnostics.push(Diagnostic::error( *loc, "function calls via contract name are only valid for base contracts".into(), )); } } + + if ns.target == Target::Solana { + // If the identifier symbol resolves to a contract, this an external call on Solana + // regardless of whether we are inside a contract or not. + return contract_call_named_args( + loc, + None, + func_name, + args, + call_args, + call_contract_no, + context, + symtable, + ns, + diagnostics, + resolve_to, + ); + } } } @@ -1664,6 +1645,24 @@ pub(super) fn method_call_named_args( &path, &mut Diagnostics::default(), ) { + if let Some(callee_contract) = + is_solana_external_call(&list, context.contract_no, &call_args_loc, ns) + { + return contract_call_named_args( + &var.loc(), + None, + func_name, + args, + call_args, + callee_contract, + context, + symtable, + ns, + diagnostics, + resolve_to, + ); + } + if let Some(loc) = call_args_loc { diagnostics.push(Diagnostic::error( loc, @@ -1690,192 +1689,19 @@ pub(super) fn method_call_named_args( let var_ty = var_expr.ty(); if let Type::Contract(external_contract_no) = &var_ty.deref_any() { - let call_args = parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?; - - let mut arguments = HashMap::new(); - - // check if the arguments are not garbage - for arg in args { - if arguments.contains_key(arg.name.name.as_str()) { - diagnostics.push(Diagnostic::error( - arg.name.loc, - format!("duplicate argument with name '{}'", arg.name.name), - )); - - let _ = expression( - &arg.expr, - context, - ns, - symtable, - diagnostics, - ResolveTo::Unknown, - ); - - continue; - } - - arguments.insert(arg.name.name.as_str(), &arg.expr); - } - - let mut errors = Diagnostics::default(); - let mut name_matches: Vec = Vec::new(); - - // function call - for function_no in ns.contracts[*external_contract_no].all_functions.keys() { - if ns.functions[*function_no].name != func_name.name - || ns.functions[*function_no].ty != pt::FunctionTy::Function - { - continue; - } - - name_matches.push(*function_no); - } - - for function_no in &name_matches { - let func = &ns.functions[*function_no]; - - let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count(); - let params_len = func.params.len(); - - let mut matches = true; - - if unnamed_params > 0 { - errors.push(Diagnostic::cast_error_with_note( - *loc, - format!( - "function cannot be called with named arguments as {unnamed_params} of its parameters do not have names" - ), - func.loc, - format!("definition of {}", func.name), - )); - matches = false; - } else if params_len != args.len() { - errors.push(Diagnostic::cast_error( - *loc, - format!( - "function expects {} arguments, {} provided", - params_len, - args.len() - ), - )); - matches = false; - } - let mut cast_args = Vec::new(); - - for i in 0..params_len { - let param = ns.functions[*function_no].params[i].clone(); - if param.id.is_none() { - continue; - } - - let arg = match arguments.get(param.name_as_str()) { - Some(a) => a, - None => { - matches = false; - diagnostics.push(Diagnostic::cast_error( - *loc, - format!( - "missing argument '{}' to function '{}'", - param.name_as_str(), - func_name.name, - ), - )); - continue; - } - }; - - let arg = match expression( - arg, - context, - ns, - symtable, - &mut errors, - ResolveTo::Type(¶m.ty), - ) { - Ok(e) => e, - Err(()) => { - matches = false; - continue; - } - }; - - match arg.cast(&arg.loc(), ¶m.ty, true, ns, &mut errors) { - Ok(expr) => cast_args.push(expr), - Err(()) => { - matches = false; - break; - } - } - } - - if matches { - if !ns.functions[*function_no].is_public() { - diagnostics.push(Diagnostic::error( - *loc, - format!( - "function '{}' is not 'public' or 'external'", - func_name.name - ), - )); - } else if let Some(value) = &call_args.value { - if !value.const_zero(ns) && !ns.functions[*function_no].is_payable() { - diagnostics.push(Diagnostic::error( - *loc, - format!( - "sending value to function '{}' which is not payable", - func_name.name - ), - )); - } - } - - let func = &ns.functions[*function_no]; - let returns = function_returns(func, resolve_to); - let ty = function_type(func, true, resolve_to); - - return Ok(Expression::ExternalFunctionCall { - loc: *loc, - returns, - function: Box::new(Expression::ExternalFunction { - loc: *loc, - ty, - function_no: *function_no, - address: Box::new(var_expr.cast( - &var.loc(), - &Type::Contract(func.contract_no.unwrap()), - true, - ns, - diagnostics, - )?), - }), - args: cast_args, - call_args, - }); - } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) { - return Err(()); - } - } - - match name_matches.len() { - 0 => { - diagnostics.push(Diagnostic::error( - *loc, - format!( - "contract '{}' does not have function '{}'", - var_ty.deref_any().to_string(ns), - func_name.name - ), - )); - } - 1 => diagnostics.extend(errors), - _ => { - diagnostics.push(Diagnostic::error( - *loc, - "cannot find overloaded function which matches signature".to_string(), - )); - } - } - return Err(()); + return contract_call_named_args( + loc, + Some(var_expr), + func_name, + args, + call_args, + *external_contract_no, + context, + symtable, + ns, + diagnostics, + resolve_to, + ); } diagnostics.push(Diagnostic::error( @@ -1941,6 +1767,7 @@ pub fn collect_call_args<'a>( pub(super) fn parse_call_args( loc: &pt::Loc, call_args: &[&pt::NamedArgument], + callee_contract: Option, external_call: bool, context: &ExprContext, ns: &mut Namespace, @@ -2148,13 +1975,37 @@ pub(super) fn parse_call_args( res.seeds = Some(Box::new(expr)); } - "flags" => { - if !(ns.target.is_polkadot() && external_call) { + "program_id" => { + if ns.target != Target::Solana { diagnostics.push(Diagnostic::error( arg.loc, - "'flags' are only permitted for external calls on polkadot".into(), - )); - return Err(()); + format!( + "'program_id' not permitted for external calls or constructors on {}", + ns.target + ), + )); + return Err(()); + } + + let ty = Type::Address(false); + let expr = expression( + &arg.expr, + context, + ns, + symtable, + diagnostics, + ResolveTo::Type(&ty), + )?; + + res.program_id = Some(Box::new(expr)); + } + "flags" => { + if !(ns.target.is_polkadot() && external_call) { + diagnostics.push(Diagnostic::error( + arg.loc, + "'flags' are only permitted for external calls on polkadot".into(), + )); + return Err(()); } let ty = Type::Uint(32); @@ -2179,24 +2030,37 @@ pub(super) fn parse_call_args( } } - // address is required on solana constructors - if ns.target == Target::Solana - && !external_call - && res.accounts.is_none() - && !matches!( - ns.functions[context.function_no.unwrap()].visibility, - Visibility::External(_) - ) - && !ns.functions[context.function_no.unwrap()].is_constructor() - { - diagnostics.push(Diagnostic::error( - *loc, - "accounts are required for calling a contract. You can either provide the \ + if ns.target == Target::Solana { + if !external_call + && res.accounts.is_none() + && !matches!( + ns.functions[context.function_no.unwrap()].visibility, + Visibility::External(_) + ) + && !ns.functions[context.function_no.unwrap()].is_constructor() + { + diagnostics.push(Diagnostic::error( + *loc, + "accounts are required for calling a contract. You can either provide the \ accounts with the {accounts: ...} call argument or change this function's \ visibility to external" - .to_string(), - )); - return Err(()); + .to_string(), + )); + return Err(()); + } + + if let Some(callee_contract_no) = callee_contract { + if res.program_id.is_none() && ns.contracts[callee_contract_no].program_id.is_none() { + diagnostics.push(Diagnostic::error( + *loc, + "a contract needs a program id to be called. Either a '@program_id' \ + must be declared above a contract or the {program_id: ...} call argument \ + must be present" + .to_string(), + )); + return Err(()); + } + } } Ok(res) @@ -2219,7 +2083,7 @@ pub fn named_call_expr( match ns.resolve_type( context.file_no, context.contract_no, - true, + ResolveTypeContext::Casting, ty, &mut nullsink, ) { @@ -2286,7 +2150,7 @@ pub fn call_expr( match ns.resolve_type( context.file_no, context.contract_no, - true, + ResolveTypeContext::Casting, ty, &mut nullsink, ) { @@ -2655,3 +2519,506 @@ fn resolve_internal_call( args: cast_args, }) } + +/// Resolve call to contract with named arguments +fn contract_call_named_args( + loc: &pt::Loc, + var_expr: Option, + func_name: &pt::Identifier, + args: &[pt::NamedArgument], + call_args: &[&pt::NamedArgument], + external_contract_no: usize, + context: &ExprContext, + symtable: &mut Symtable, + ns: &mut Namespace, + diagnostics: &mut Diagnostics, + resolve_to: ResolveTo, +) -> Result { + let mut arguments = HashMap::new(); + + // check if the arguments are not garbage + for arg in args { + if arguments.contains_key(arg.name.name.as_str()) { + diagnostics.push(Diagnostic::error( + arg.name.loc, + format!("duplicate argument with name '{}'", arg.name.name), + )); + + let _ = expression( + &arg.expr, + context, + ns, + symtable, + diagnostics, + ResolveTo::Unknown, + ); + + continue; + } + + arguments.insert(arg.name.name.as_str(), &arg.expr); + } + + let (call_args, name_matches) = match preprocess_contract_call( + loc, + call_args, + external_contract_no, + func_name, + args, + context, + ns, + symtable, + diagnostics, + ) { + PreProcessedCall::Success { + call_args, + name_matches, + } => (call_args, name_matches), + PreProcessedCall::DefaultConstructor(expr) => return Ok(expr), + PreProcessedCall::Error => return Err(()), + }; + + let mut errors = Diagnostics::default(); + + for function_no in &name_matches { + let func = &ns.functions[*function_no]; + + let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count(); + let params_len = func.params.len(); + + let mut matches = true; + + if unnamed_params > 0 { + errors.push(Diagnostic::cast_error_with_note( + *loc, + format!( + "function cannot be called with named arguments as {unnamed_params} of its parameters do not have names" + ), + func.loc, + format!("definition of {}", func.name), + )); + matches = false; + } else if params_len != args.len() { + errors.push(Diagnostic::cast_error( + *loc, + format!( + "function expects {} arguments, {} provided", + params_len, + args.len() + ), + )); + matches = false; + } + let mut cast_args = Vec::new(); + + for i in 0..params_len { + let param = ns.functions[*function_no].params[i].clone(); + if param.id.is_none() { + continue; + } + + let arg = match arguments.get(param.name_as_str()) { + Some(a) => a, + None => { + matches = false; + diagnostics.push(Diagnostic::cast_error( + *loc, + format!( + "missing argument '{}' to function '{}'", + param.name_as_str(), + func_name.name, + ), + )); + continue; + } + }; + + let arg = match expression( + arg, + context, + ns, + symtable, + &mut errors, + ResolveTo::Type(¶m.ty), + ) { + Ok(e) => e, + Err(()) => { + matches = false; + continue; + } + }; + + match arg.cast(&arg.loc(), ¶m.ty, true, ns, &mut errors) { + Ok(expr) => cast_args.push(expr), + Err(()) => { + matches = false; + break; + } + } + } + + if matches { + return contract_call_match( + loc, + func_name, + *function_no, + external_contract_no, + call_args, + cast_args, + var_expr.as_ref(), + ns, + diagnostics, + resolve_to, + ); + } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) { + return Err(()); + } + } + + match name_matches.len() { + 0 => { + diagnostics.push(Diagnostic::error( + *loc, + format!( + "contract '{}' does not have function '{}'", + ns.contracts[external_contract_no].name, func_name.name + ), + )); + } + 1 => diagnostics.extend(errors), + _ => { + diagnostics.push(Diagnostic::error( + *loc, + "cannot find overloaded function which matches signature".to_string(), + )); + } + } + Err(()) +} + +/// Resolve call to contract with positional arguments +fn contract_call_pos_args( + loc: &pt::Loc, + external_contract_no: usize, + func: &pt::Identifier, + var_expr: Option<&Expression>, + args: &[pt::Expression], + call_args: &[&pt::NamedArgument], + context: &ExprContext, + ns: &mut Namespace, + symtable: &mut Symtable, + diagnostics: &mut Diagnostics, + resolve_to: ResolveTo, +) -> Result, ()> { + let (call_args, name_matches) = match preprocess_contract_call( + loc, + call_args, + external_contract_no, + func, + args, + context, + ns, + symtable, + diagnostics, + ) { + PreProcessedCall::Success { + call_args, + name_matches, + } => (call_args, name_matches), + PreProcessedCall::DefaultConstructor(expr) => return Ok(Some(expr)), + PreProcessedCall::Error => return Err(()), + }; + + let mut errors = Diagnostics::default(); + + for function_no in &name_matches { + let params_len = ns.functions[*function_no].params.len(); + + if params_len != args.len() { + errors.push(Diagnostic::error( + *loc, + format!( + "function expects {} arguments, {} provided", + params_len, + args.len() + ), + )); + continue; + } + + let mut matches = true; + let mut cast_args = Vec::new(); + + // check if arguments can be implicitly casted + for (i, arg) in args.iter().enumerate() { + let ty = ns.functions[*function_no].params[i].ty.clone(); + + let arg = match expression( + arg, + context, + ns, + symtable, + &mut errors, + ResolveTo::Type(&ty), + ) { + Ok(e) => e, + Err(_) => { + matches = false; + continue; + } + }; + + match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) { + Ok(expr) => cast_args.push(expr), + Err(()) => { + matches = false; + continue; + } + } + } + + if matches { + let resolved_call = contract_call_match( + loc, + func, + *function_no, + external_contract_no, + call_args, + cast_args, + var_expr, + ns, + diagnostics, + resolve_to, + )?; + return Ok(Some(resolved_call)); + } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) { + return Err(()); + } + } + + if let Some(var) = var_expr { + // what about call args + match using::try_resolve_using_call( + loc, + func, + var, + context, + args, + symtable, + diagnostics, + ns, + resolve_to, + ) { + Ok(Some(expr)) => { + return Ok(Some(expr)); + } + Ok(None) => (), + Err(_) => { + return Err(()); + } + } + } + + if name_matches.len() == 1 { + diagnostics.extend(errors); + } else if name_matches.len() != 1 { + diagnostics.push(Diagnostic::error( + *loc, + "cannot find overloaded function which matches signature".to_string(), + )); + } + + Err(()) +} + +/// Checks if an identifier path is an external call on Solana. +/// For instance, my_file.my_contract.my_func() may be a call to a contract. +fn is_solana_external_call( + list: &[(pt::Loc, usize)], + contract_no: Option, + call_args_loc: &Option, + ns: &Namespace, +) -> Option { + if ns.target == Target::Solana + && list.len() == 1 + && ns.functions[list[0].1].contract_no != contract_no + { + if let (Some(callee), Some(caller)) = (ns.functions[list[0].1].contract_no, contract_no) { + if is_base(callee, caller, ns) && call_args_loc.is_none() { + return None; + } + } + return ns.functions[list[0].1].contract_no; + } + + None +} + +/// Data structure to manage the returns of 'preprocess_contract_call' +enum PreProcessedCall { + Success { + call_args: CallArgs, + name_matches: Vec, + }, + DefaultConstructor(Expression), + Error, +} + +/// This functions preprocesses calls to contracts, i.e. it parses the call arguments, +/// find function name matches and identifies if we are calling a constructor on Solana. +fn preprocess_contract_call( + loc: &pt::Loc, + call_args: &[&pt::NamedArgument], + external_contract_no: usize, + func: &pt::Identifier, + args: &[T], + context: &ExprContext, + ns: &mut Namespace, + symtable: &mut Symtable, + diagnostics: &mut Diagnostics, +) -> PreProcessedCall { + let call_args = if let Ok(call_args) = parse_call_args( + loc, + call_args, + Some(external_contract_no), + func.name != "new", + context, + ns, + symtable, + diagnostics, + ) { + call_args + } else { + return PreProcessedCall::Error; + }; + + let mut name_matches: Vec = Vec::new(); + + for function_no in ns.contracts[external_contract_no].all_functions.keys() { + if func.name != ns.functions[*function_no].name + || ns.functions[*function_no].ty != pt::FunctionTy::Function + { + continue; + } + + name_matches.push(*function_no); + } + + if ns.target == Target::Solana && func.name == "new" { + solana_constructor_check( + loc, + external_contract_no, + diagnostics, + context, + &call_args, + ns, + ); + + let constructor_nos = ns.contracts[external_contract_no].constructors(ns); + if !constructor_nos.is_empty() { + // Solana contracts shall have only a single constructor + assert_eq!(constructor_nos.len(), 1); + name_matches.push(constructor_nos[0]); + } else if !args.is_empty() { + // Default constructor must not receive arguments + diagnostics.push(Diagnostic::error( + *loc, + format!( + "'{}' constructor takes no argument", + ns.contracts[external_contract_no].name + ), + )); + return PreProcessedCall::Error; + } else { + // Default constructor case + return PreProcessedCall::DefaultConstructor(Expression::Constructor { + loc: *loc, + contract_no: external_contract_no, + constructor_no: None, + args: vec![], + call_args, + }); + } + } + + PreProcessedCall::Success { + call_args, + name_matches, + } +} + +/// This function generates the final expression when a contract's function is matched with both +/// the provided name and arguments +fn contract_call_match( + loc: &pt::Loc, + func: &pt::Identifier, + function_no: usize, + external_contract_no: usize, + call_args: CallArgs, + cast_args: Vec, + var_expr: Option<&Expression>, + ns: &Namespace, + diagnostics: &mut Diagnostics, + resolve_to: ResolveTo, +) -> Result { + if !ns.functions[function_no].is_public() { + diagnostics.push(Diagnostic::error( + *loc, + format!("function '{}' is not 'public' or 'external'", func.name), + )); + return Err(()); + } else if let Some(value) = &call_args.value { + if !value.const_zero(ns) && !ns.functions[function_no].is_payable() { + diagnostics.push(Diagnostic::error( + *loc, + format!( + "sending value to function '{}' which is not payable", + func.name + ), + )); + return Err(()); + } + } + + let func = &ns.functions[function_no]; + let returns = function_returns(func, resolve_to); + let ty = function_type(func, true, resolve_to); + + let (address, implicit) = if let Some(program_id_var) = &call_args.program_id { + (*program_id_var.clone(), false) + } else if let Some(address_id) = &ns.contracts[external_contract_no].program_id { + ( + Expression::NumberLiteral { + loc: *loc, + ty: Type::Address(false), + value: BigInt::from_bytes_be(Sign::Plus, address_id), + }, + false, + ) + } else if let Some(var) = var_expr { + (var.clone(), true) + } else { + unreachable!("address not found") + }; + + Ok({ + Expression::ExternalFunctionCall { + loc: *loc, + returns, + function: Box::new(Expression::ExternalFunction { + loc: *loc, + ty, + function_no, + address: Box::new(address.cast( + &address.loc(), + &Type::Contract(func.contract_no.unwrap()), + implicit, + ns, + diagnostics, + )?), + }), + args: cast_args, + call_args, + } + }) +} diff --git a/src/sema/expression/member_access.rs b/src/sema/expression/member_access.rs index c5105a2d9..a761aaa99 100644 --- a/src/sema/expression/member_access.rs +++ b/src/sema/expression/member_access.rs @@ -10,6 +10,7 @@ use crate::sema::expression::function_call::function_type; use crate::sema::expression::integers::bigint_to_expression; use crate::sema::expression::resolve_expression::expression; use crate::sema::expression::{ExprContext, ResolveTo}; +use crate::sema::namespace::ResolveTypeContext; use crate::sema::solana_accounts::BuiltinAccounts; use crate::sema::symtable::Symtable; use crate::sema::unused_variable::{assigned_variable, used_variable}; @@ -648,7 +649,7 @@ fn type_name_expr( let ty = ns.resolve_type( context.file_no, context.contract_no, - false, + ResolveTypeContext::FunctionType, &args[0], diagnostics, )?; diff --git a/src/sema/functions.rs b/src/sema/functions.rs index f3a6a1271..c670873ac 100644 --- a/src/sema/functions.rs +++ b/src/sema/functions.rs @@ -10,6 +10,7 @@ use super::{ }; use crate::sema::ast::ParameterAnnotation; use crate::sema::function_annotation::unexpected_parameter_annotation; +use crate::sema::namespace::ResolveTypeContext; use crate::Target; use solang_parser::pt::FunctionTy; use solang_parser::{ @@ -881,7 +882,13 @@ pub fn resolve_params( let mut ty_loc = p.ty.loc(); - match ns.resolve_type(file_no, contract_no, false, &p.ty, diagnostics) { + match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + &p.ty, + diagnostics, + ) { Ok(ty) => { if !is_internal { if ty.contains_internal_function(ns) { @@ -1002,7 +1009,13 @@ pub fn resolve_returns( let mut ty_loc = r.ty.loc(); - match ns.resolve_type(file_no, contract_no, false, &r.ty, diagnostics) { + match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + &r.ty, + diagnostics, + ) { Ok(ty) => { if !is_internal { if ty.contains_internal_function(ns) { diff --git a/src/sema/namespace.rs b/src/sema/namespace.rs index 69504fc66..9a5f4b5c7 100644 --- a/src/sema/namespace.rs +++ b/src/sema/namespace.rs @@ -25,6 +25,14 @@ use solang_parser::{ }; use std::collections::HashMap; +/// Provides context information for the `resolve_type` function. +#[derive(PartialEq, Eq)] +pub(super) enum ResolveTypeContext { + None, + Casting, + FunctionType, +} + impl Namespace { /// Create a namespace and populate with the parameters for the target pub fn new(target: Target) -> Self { @@ -892,7 +900,7 @@ impl Namespace { &mut self, file_no: usize, contract_no: Option, - casting: bool, + resolve_context: ResolveTypeContext, id: &pt::Expression, diagnostics: &mut Diagnostics, ) -> Result { @@ -946,10 +954,20 @@ impl Namespace { value_name, .. } => { - let key_ty = - self.resolve_type(file_no, contract_no, false, key, diagnostics)?; - let value_ty = - self.resolve_type(file_no, contract_no, false, value, diagnostics)?; + let key_ty = self.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + key, + diagnostics, + )?; + let value_ty = self.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + value, + diagnostics, + )?; match key_ty { Type::Mapping(..) => { @@ -1162,7 +1180,7 @@ impl Namespace { } } pt::Type::Payable => { - if !casting { + if resolve_context != ResolveTypeContext::Casting { diagnostics.push(Diagnostic::decl_error( id.loc(), "'payable' cannot be used for type declarations, only casting. use 'address payable'" @@ -1211,11 +1229,25 @@ impl Namespace { Box::new(Type::Struct(*str_ty)), resolve_dimensions(&dimensions, diagnostics)?, )), - Some(Symbol::Contract(_, n)) if dimensions.is_empty() => Ok(Type::Contract(*n)), - Some(Symbol::Contract(_, n)) => Ok(Type::Array( - Box::new(Type::Contract(*n)), - resolve_dimensions(&dimensions, diagnostics)?, - )), + Some(Symbol::Contract(_, n)) => { + if self.target == Target::Solana + && resolve_context != ResolveTypeContext::FunctionType + { + diagnostics.push(Diagnostic::error( + id.loc, + "contracts are not allowed as types on Solana".to_string(), + )); + return Err(()); + } + if dimensions.is_empty() { + Ok(Type::Contract(*n)) + } else { + Ok(Type::Array( + Box::new(Type::Contract(*n)), + resolve_dimensions(&dimensions, diagnostics)?, + )) + } + } Some(Symbol::Event(_)) => { diagnostics.push(Diagnostic::decl_error( id.loc, diff --git a/src/sema/statements.rs b/src/sema/statements.rs index 91d2b0512..a35ed659c 100644 --- a/src/sema/statements.rs +++ b/src/sema/statements.rs @@ -18,6 +18,7 @@ use crate::sema::expression::function_call::{ use crate::sema::expression::resolve_expression::expression; use crate::sema::function_annotation::function_body_annotations; use crate::sema::function_annotation::{unexpected_parameter_annotation, UnresolvedAnnotation}; +use crate::sema::namespace::ResolveTypeContext; use crate::sema::symtable::{VariableInitializer, VariableUsage}; use crate::sema::unused_variable::{assigned_variable, check_function_call, used_variable}; use crate::sema::yul::resolve_inline_assembly; @@ -1838,8 +1839,13 @@ fn resolve_var_decl_ty( diagnostics: &mut Diagnostics, ) -> Result<(Type, pt::Loc), ()> { let mut loc_ty = ty.loc(); - let mut var_ty = - ns.resolve_type(context.file_no, context.contract_no, false, ty, diagnostics)?; + let mut var_ty = ns.resolve_type( + context.file_no, + context.contract_no, + ResolveTypeContext::None, + ty, + diagnostics, + )?; if let Some(storage) = storage { if !var_ty.can_have_data_location() { diff --git a/src/sema/tests/mod.rs b/src/sema/tests/mod.rs index 13e976b44..56c03719a 100644 --- a/src/sema/tests/mod.rs +++ b/src/sema/tests/mod.rs @@ -457,9 +457,8 @@ contract aborting { contract runner { function test() external pure { - aborting abort = new aborting(); - try abort.abort() returns (int32 a, bool b) { + try aborting.abort() returns (int32 a, bool b) { // call succeeded; return values are in a and b } catch Error(string x) { @@ -535,16 +534,15 @@ fn dynamic_account_metas() { import 'solana'; contract creator { - Child public c; function create_child_with_meta(address child, address payer) public { AccountMeta[] metas = new AccountMeta[](2); metas[0] = AccountMeta({pubkey: child, is_signer: false, is_writable: false}); metas[1] = AccountMeta({pubkey: payer, is_signer: true, is_writable: true}); - c = new Child{accounts: metas}(payer); + Child.new{accounts: metas}(payer); - c.say_hello(); + Child.say_hello(); } } @@ -580,19 +578,17 @@ fn no_address_and_no_metas() { import 'solana'; contract creator { - Child public c; - function create_child_with_meta(address child, address payer) public { - - c = new Child(payer); - - c.say_hello(); + function create_child_with_meta(address child) public { + Child.new(); + Child.say_hello(); } } +@program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT") contract Child { @payer(payer) @space(511 + 7) - constructor(address payer) { + constructor() { print("In child constructor"); } diff --git a/src/sema/types.rs b/src/sema/types.rs index 2be049536..6b030a100 100644 --- a/src/sema/types.rs +++ b/src/sema/types.rs @@ -10,6 +10,7 @@ use super::{ diagnostics::Diagnostics, ContractDefinition, SOLANA_SPARSE_ARRAY_SIZE, }; +use crate::sema::namespace::ResolveTypeContext; use crate::Target; use base58::{FromBase58, FromBase58Error}; use indexmap::IndexMap; @@ -214,7 +215,13 @@ fn type_decl( ) { let mut diagnostics = Diagnostics::default(); - let mut ty = match ns.resolve_type(file_no, contract_no, false, &def.ty, &mut diagnostics) { + let mut ty = match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + &def.ty, + &mut diagnostics, + ) { Ok(ty) => ty, Err(_) => { ns.diagnostics.extend(diagnostics); @@ -700,7 +707,13 @@ pub fn struct_decl( for field in &def.fields { let mut diagnostics = Diagnostics::default(); - let ty = match ns.resolve_type(file_no, contract_no, false, &field.ty, &mut diagnostics) { + let ty = match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + &field.ty, + &mut diagnostics, + ) { Ok(s) => s, Err(()) => { ns.diagnostics.extend(diagnostics); @@ -795,8 +808,13 @@ fn event_decl( for field in &def.fields { let mut diagnostics = Diagnostics::default(); - let mut ty = match ns.resolve_type(file_no, contract_no, false, &field.ty, &mut diagnostics) - { + let mut ty = match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + &field.ty, + &mut diagnostics, + ) { Ok(s) => s, Err(()) => { ns.diagnostics.extend(diagnostics); @@ -913,8 +931,13 @@ fn error_decl( for field in &def.fields { let mut diagnostics = Diagnostics::default(); - let mut ty = match ns.resolve_type(file_no, contract_no, false, &field.ty, &mut diagnostics) - { + let mut ty = match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + &field.ty, + &mut diagnostics, + ) { Ok(s) => s, Err(()) => { ns.diagnostics.extend(diagnostics); diff --git a/src/sema/unused_variable.rs b/src/sema/unused_variable.rs index b39dc576d..d07fc2c10 100644 --- a/src/sema/unused_variable.rs +++ b/src/sema/unused_variable.rs @@ -281,6 +281,9 @@ fn check_call_args(ns: &mut Namespace, call_args: &CallArgs, symtable: &mut Symt if let Some(flags) = &call_args.flags { used_variable(ns, flags.as_ref(), symtable); } + if let Some(program_id) = &call_args.program_id { + used_variable(ns, program_id.as_ref(), symtable); + } } /// Marks as used variables that appear in an expression with right and left hand side. diff --git a/src/sema/using.rs b/src/sema/using.rs index f6117ac61..231f618ef 100644 --- a/src/sema/using.rs +++ b/src/sema/using.rs @@ -10,6 +10,7 @@ use super::{ }; use crate::sema::expression::function_call::{function_returns, function_type}; use crate::sema::expression::resolve_expression::expression; +use crate::sema::namespace::ResolveTypeContext; use solang_parser::pt::CodeLocation; use solang_parser::pt::{self}; use std::collections::HashSet; @@ -34,7 +35,13 @@ pub(crate) fn using_decl( } let ty = if let Some(expr) = &using.ty { - match ns.resolve_type(file_no, contract_no, false, expr, &mut diagnostics) { + match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + expr, + &mut diagnostics, + ) { Ok(Type::Contract(contract_no)) if ns.contracts[contract_no].is_library() => { ns.diagnostics.push(Diagnostic::error( expr.loc(), diff --git a/src/sema/variables.rs b/src/sema/variables.rs index d75c8bde3..3e66120b3 100644 --- a/src/sema/variables.rs +++ b/src/sema/variables.rs @@ -16,6 +16,7 @@ use super::{ }; use crate::sema::eval::check_term_for_constant_overflow; use crate::sema::expression::resolve_expression::expression; +use crate::sema::namespace::ResolveTypeContext; use crate::sema::Recurse; use solang_parser::{ doccomment::DocComment, @@ -120,7 +121,13 @@ pub fn variable_decl<'a>( let mut diagnostics = Diagnostics::default(); - let ty = match ns.resolve_type(file_no, contract_no, false, &ty, &mut diagnostics) { + let ty = match ns.resolve_type( + file_no, + contract_no, + ResolveTypeContext::None, + &ty, + &mut diagnostics, + ) { Ok(s) => s, Err(()) => { ns.diagnostics.extend(diagnostics); diff --git a/tests/codegen_testcases/import_test.sol b/tests/codegen_testcases/import_test.sol new file mode 100644 index 000000000..850692b8d --- /dev/null +++ b/tests/codegen_testcases/import_test.sol @@ -0,0 +1,6 @@ +@program_id("6qEm4QUJGFvqKNJGjTrAEiFhbVBY4ashpBjDHEFvEUmW") +contract Dog { + function barks(string what) public pure { + print(what); + } +} \ No newline at end of file diff --git a/tests/codegen_testcases/solidity/accounts_for_default_constructor.sol b/tests/codegen_testcases/solidity/accounts_for_default_constructor.sol new file mode 100644 index 000000000..00a0a0ac5 --- /dev/null +++ b/tests/codegen_testcases/solidity/accounts_for_default_constructor.sol @@ -0,0 +1,16 @@ +// RUN: --target solana --emit cfg +contract Foo { + uint b; + function get_b() public returns (uint) { + return b; + } +} + +contract Other { + // BEGIN-CHECK: Other::Other::function::call_foo__address + function call_foo(address id) external { + // The account must be properly indexed so that the call works. + // CHECK: constructor(no: ) salt: value: gas:uint64 0 address:(arg #0) seeds: Foo encoded buffer: %abi_encoded.temp.12 accounts: [1] [ struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 0]) field 0)), true, false } ] + Foo.new{program_id: id}(); + } +} \ No newline at end of file diff --git a/tests/codegen_testcases/solidity/borsh_decoding_simple_types.sol b/tests/codegen_testcases/solidity/borsh_decoding_simple_types.sol index 068aa1c60..cc196c3b0 100644 --- a/tests/codegen_testcases/solidity/borsh_decoding_simple_types.sol +++ b/tests/codegen_testcases/solidity/borsh_decoding_simple_types.sol @@ -1,19 +1,16 @@ // RUN: --target solana --emit cfg -contract Other { - -} contract Testing { // BEGIN-CHECK: Testing::Testing::function::addressContract__bytes - function addressContract(bytes memory buffer) public pure returns (address, Other) { - (address a, Other b) = abi.decode(buffer, (address, Other)); + function addressContract(bytes memory buffer) public pure returns (address, address) { + (address a, address b) = abi.decode(buffer, (address, address)); // CHECK: ty:bytes %buffer = (arg #0) - // CHECK: ty:uint32 %temp.64 = (builtin ArrayLength ((arg #0))) - // CHECK: branchcond (unsigned uint32 64 <= %temp.64), block1, block2 + // CHECK: ty:uint32 %temp.60 = (builtin ArrayLength ((arg #0))) + // CHECK: branchcond (unsigned uint32 64 <= %temp.60), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:address %temp.65 = (builtin ReadFromBuffer ((arg #0), uint32 0)) - // CHECK: ty:contract Other %temp.66 = (builtin ReadFromBuffer ((arg #0), uint32 32)) - // CHECK: branchcond (unsigned less uint32 64 < %temp.64), block3, block4 + // CHECK: ty:address %temp.61 = (builtin ReadFromBuffer ((arg #0), uint32 0)) + // CHECK: ty:address %temp.62 = (builtin ReadFromBuffer ((arg #0), uint32 32)) + // CHECK: branchcond (unsigned less uint32 64 < %temp.60), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure @@ -21,8 +18,8 @@ contract Testing { // CHECK: assert-failure // CHECK: block4: # buffer_read - // CHECK: ty:address %a = %temp.65 - // CHECK: ty:contract Other %b = %temp.66 + // CHECK: ty:address %a = %temp.61 + // CHECK: ty:address %b = %temp.62 return (a, b); } @@ -33,17 +30,17 @@ contract Testing { abi.decode(buffer, (uint8, uint16, uint32, uint64, uint128, uint256)); // CHECK: ty:bytes %buffer = (arg #0) - // CHECK: ty:uint32 %temp.67 = (builtin ArrayLength ((arg #0))) - // CHECK: branchcond (unsigned uint32 63 <= %temp.67), block1, block2 + // CHECK: ty:uint32 %temp.63 = (builtin ArrayLength ((arg #0))) + // CHECK: branchcond (unsigned uint32 63 <= %temp.63), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:uint8 %temp.68 = (builtin ReadFromBuffer ((arg #0), uint32 0)) - // CHECK: ty:uint16 %temp.69 = (builtin ReadFromBuffer ((arg #0), uint32 1)) - // CHECK: ty:uint32 %temp.70 = (builtin ReadFromBuffer ((arg #0), uint32 3)) - // CHECK: ty:uint64 %temp.71 = (builtin ReadFromBuffer ((arg #0), uint32 7)) - // CHECK: ty:uint128 %temp.72 = (builtin ReadFromBuffer ((arg #0), uint32 15)) - // CHECK: ty:uint256 %temp.73 = (builtin ReadFromBuffer ((arg #0), uint32 31)) - // CHECK: branchcond (unsigned less uint32 63 < %temp.67), block3, block4 + // CHECK: ty:uint8 %temp.64 = (builtin ReadFromBuffer ((arg #0), uint32 0)) + // CHECK: ty:uint16 %temp.65 = (builtin ReadFromBuffer ((arg #0), uint32 1)) + // CHECK: ty:uint32 %temp.66 = (builtin ReadFromBuffer ((arg #0), uint32 3)) + // CHECK: ty:uint64 %temp.67 = (builtin ReadFromBuffer ((arg #0), uint32 7)) + // CHECK: ty:uint128 %temp.68 = (builtin ReadFromBuffer ((arg #0), uint32 15)) + // CHECK: ty:uint256 %temp.69 = (builtin ReadFromBuffer ((arg #0), uint32 31)) + // CHECK: branchcond (unsigned less uint32 63 < %temp.63), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure @@ -52,12 +49,12 @@ contract Testing { // CHECK: assert-failure // CHECK: block4: # buffer_read - // CHECK: ty:uint8 %a = %temp.68 - // CHECK: ty:uint16 %b = %temp.69 - // CHECK: ty:uint32 %c = %temp.70 - // CHECK: ty:uint64 %d = %temp.71 - // CHECK: ty:uint128 %e = %temp.72 - // CHECK: ty:uint256 %f = %temp.73 + // CHECK: ty:uint8 %a = %temp.64 + // CHECK: ty:uint16 %b = %temp.65 + // CHECK: ty:uint32 %c = %temp.66 + // CHECK: ty:uint64 %d = %temp.67 + // CHECK: ty:uint128 %e = %temp.68 + // CHECK: ty:uint256 %f = %temp.69 return (a, b, c, d, e, f); } @@ -68,17 +65,17 @@ contract Testing { (int8 a, int16 b, int32 c, int64 d, int128 e, int256 f) = abi.decode(buffer, (int8, int16, int32, int64, int128, int256)); - // CHECK: ty:uint32 %temp.74 = (builtin ArrayLength ((arg #0))) - // CHECK: branchcond (unsigned uint32 63 <= %temp.74), block1, block2 + // CHECK: ty:uint32 %temp.70 = (builtin ArrayLength ((arg #0))) + // CHECK: branchcond (unsigned uint32 63 <= %temp.70), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:int8 %temp.75 = (builtin ReadFromBuffer ((arg #0), uint32 0)) - // CHECK: ty:int16 %temp.76 = (builtin ReadFromBuffer ((arg #0), uint32 1)) - // CHECK: ty:int32 %temp.77 = (builtin ReadFromBuffer ((arg #0), uint32 3)) - // CHECK: ty:int64 %temp.78 = (builtin ReadFromBuffer ((arg #0), uint32 7)) - // CHECK: ty:int128 %temp.79 = (builtin ReadFromBuffer ((arg #0), uint32 15)) - // CHECK: ty:int256 %temp.80 = (builtin ReadFromBuffer ((arg #0), uint32 31)) - // CHECK: branchcond (unsigned less uint32 63 < %temp.74), block3, block4 + // CHECK: ty:int8 %temp.71 = (builtin ReadFromBuffer ((arg #0), uint32 0)) + // CHECK: ty:int16 %temp.72 = (builtin ReadFromBuffer ((arg #0), uint32 1)) + // CHECK: ty:int32 %temp.73 = (builtin ReadFromBuffer ((arg #0), uint32 3)) + // CHECK: ty:int64 %temp.74 = (builtin ReadFromBuffer ((arg #0), uint32 7)) + // CHECK: ty:int128 %temp.75 = (builtin ReadFromBuffer ((arg #0), uint32 15)) + // CHECK: ty:int256 %temp.76 = (builtin ReadFromBuffer ((arg #0), uint32 31)) + // CHECK: branchcond (unsigned less uint32 63 < %temp.70), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure @@ -87,12 +84,12 @@ contract Testing { // CHECK: assert-failure // CHECK: block4: # buffer_read - // CHECK: ty:int8 %a = %temp.75 - // CHECK: ty:int16 %b = %temp.76 - // CHECK: ty:int32 %c = %temp.77 - // CHECK: ty:int64 %d = %temp.78 - // CHECK: ty:int128 %e = %temp.79 - // CHECK: ty:int256 %f = %temp.80 + // CHECK: ty:int8 %a = %temp.71 + // CHECK: ty:int16 %b = %temp.72 + // CHECK: ty:int32 %c = %temp.73 + // CHECK: ty:int64 %d = %temp.74 + // CHECK: ty:int128 %e = %temp.75 + // CHECK: ty:int256 %f = %temp.76 return (a, b, c, d, e, f); } @@ -101,15 +98,15 @@ contract Testing { function fixedBytes(bytes memory buffer) public pure returns (bytes1, bytes5, bytes20, bytes32) { (bytes1 a, bytes5 b, bytes20 c, bytes32 d) = abi.decode(buffer, (bytes1, bytes5, bytes20, bytes32)); - // CHECK: ty:uint32 %temp.81 = (builtin ArrayLength ((arg #0))) - // CHECK: branchcond (unsigned uint32 58 <= %temp.81), block1, block2 + // CHECK: ty:uint32 %temp.77 = (builtin ArrayLength ((arg #0))) + // CHECK: branchcond (unsigned uint32 58 <= %temp.77), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:bytes1 %temp.82 = (builtin ReadFromBuffer ((arg #0), uint32 0)) - // CHECK: ty:bytes5 %temp.83 = (builtin ReadFromBuffer ((arg #0), uint32 1)) - // CHECK: ty:bytes20 %temp.84 = (builtin ReadFromBuffer ((arg #0), uint32 6)) - // CHECK: ty:bytes32 %temp.85 = (builtin ReadFromBuffer ((arg #0), uint32 26)) - // CHECK: branchcond (unsigned less uint32 58 < %temp.81), block3, block4 + // CHECK: ty:bytes1 %temp.78 = (builtin ReadFromBuffer ((arg #0), uint32 0)) + // CHECK: ty:bytes5 %temp.79 = (builtin ReadFromBuffer ((arg #0), uint32 1)) + // CHECK: ty:bytes20 %temp.80 = (builtin ReadFromBuffer ((arg #0), uint32 6)) + // CHECK: ty:bytes32 %temp.81 = (builtin ReadFromBuffer ((arg #0), uint32 26)) + // CHECK: branchcond (unsigned less uint32 58 < %temp.77), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure @@ -118,10 +115,10 @@ contract Testing { // CHECK: assert-failure // CHECK: block4: # buffer_read - // CHECK: ty:bytes1 %a = %temp.82 - // CHECK: ty:bytes5 %b = %temp.83 - // CHECK: ty:bytes20 %c = %temp.84 - // CHECK: ty:bytes32 %d = %temp.85 + // CHECK: ty:bytes1 %a = %temp.78 + // CHECK: ty:bytes5 %b = %temp.79 + // CHECK: ty:bytes20 %c = %temp.80 + // CHECK: ty:bytes32 %d = %temp.81 return (a, b, c, d); } @@ -131,38 +128,38 @@ contract Testing { (bytes memory a, string memory b) = abi.decode(buffer, (bytes, string)); // CHECK: ty:bytes %buffer = (arg #0) - // CHECK: ty:uint32 %temp.86 = (builtin ArrayLength ((arg #0))) - // CHECK: ty:uint32 %temp.87 = (builtin ReadFromBuffer ((arg #0), uint32 0)) - // CHECK: branchcond (unsigned uint32 4 <= %temp.86), block1, block2 + // CHECK: ty:uint32 %temp.82 = (builtin ArrayLength ((arg #0))) + // CHECK: ty:uint32 %temp.83 = (builtin ReadFromBuffer ((arg #0), uint32 0)) + // CHECK: branchcond (unsigned uint32 4 <= %temp.82), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:uint32 %1.cse_temp = (uint32 0 + (%temp.87 + uint32 4)) - // CHECK: branchcond (unsigned %1.cse_temp <= %temp.86), block3, block4 + // CHECK: ty:uint32 %1.cse_temp = (uint32 0 + (%temp.83 + uint32 4)) + // CHECK: branchcond (unsigned %1.cse_temp <= %temp.82), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure // CHECK: block3: # inbounds - // CHECK: ty:bytes %temp.88 = (alloc bytes len %temp.87) - // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 4), dest: %temp.88, bytes_len: %temp.87 - // CHECK: ty:uint32 %temp.89 = (builtin ReadFromBuffer ((arg #0), (uint32 0 + (%temp.87 + uint32 4)))) + // CHECK: ty:bytes %temp.84 = (alloc bytes len %temp.83) + // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 4), dest: %temp.84, bytes_len: %temp.83 + // CHECK: ty:uint32 %temp.85 = (builtin ReadFromBuffer ((arg #0), (uint32 0 + (%temp.83 + uint32 4)))) // CHECK: ty:uint32 %2.cse_temp = (%1.cse_temp + uint32 4) - // CHECK: branchcond (unsigned %2.cse_temp <= %temp.86), block5, block6 + // CHECK: branchcond (unsigned %2.cse_temp <= %temp.82), block5, block6 // CHECK: block4: # out_of_bounds // CHECK: assert-failure // CHECK: block5: # inbounds - // CHECK: ty:uint32 %3.cse_temp = (%1.cse_temp + (%temp.89 + uint32 4)) - // CHECK: branchcond (unsigned %3.cse_temp <= %temp.86), block7, block8 + // CHECK: ty:uint32 %3.cse_temp = (%1.cse_temp + (%temp.85 + uint32 4)) + // CHECK: branchcond (unsigned %3.cse_temp <= %temp.82), block7, block8 // CHECK: block6: # out_of_bounds // CHECK: assert-failure // CHECK: block7: # inbounds - // CHECK: ty:string %temp.90 = (alloc string len %temp.89) - // CHECK: memcpy src: (advance ptr: %buffer, by: %2.cse_temp), dest: %temp.90, bytes_len: %temp.89 - // CHECK: branchcond (unsigned less %3.cse_temp < %temp.86), block9, block10 + // CHECK: ty:string %temp.86 = (alloc string len %temp.85) + // CHECK: memcpy src: (advance ptr: %buffer, by: %2.cse_temp), dest: %temp.86, bytes_len: %temp.85 + // CHECK: branchcond (unsigned less %3.cse_temp < %temp.82), block9, block10 // CHECK: block8: # out_of_bounds // CHECK: assert-failure @@ -171,8 +168,8 @@ contract Testing { // CHECK: assert-failure // CHECK: block10: # buffer_read - // CHECK: ty:bytes %a = %temp.88 - // CHECK: ty:string %b = %temp.90 + // CHECK: ty:bytes %a = %temp.84 + // CHECK: ty:string %b = %temp.86 return (a, b); } @@ -186,12 +183,12 @@ contract Testing { WeekDays a = abi.decode(buffer, (WeekDays)); // CHECK: ty:bytes %buffer = (arg #0) - // CHECK: ty:uint32 %temp.94 = (builtin ArrayLength ((arg #0))) - // CHECK: branchcond (unsigned uint32 1 <= %temp.94), block1, block2 + // CHECK: ty:uint32 %temp.90 = (builtin ArrayLength ((arg #0))) + // CHECK: branchcond (unsigned uint32 1 <= %temp.90), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:enum Testing.WeekDays %temp.95 = (builtin ReadFromBuffer ((arg #0), uint32 0)) - // CHECK: branchcond (unsigned less uint32 1 < %temp.94), block3, block4 + // CHECK: ty:enum Testing.WeekDays %temp.91 = (builtin ReadFromBuffer ((arg #0), uint32 0)) + // CHECK: branchcond (unsigned less uint32 1 < %temp.90), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure @@ -200,7 +197,7 @@ contract Testing { // CHECK: assert-failure // CHECK: block4: # buffer_read - // CHECK: ty:enum Testing.WeekDays %a = %temp.95 + // CHECK: ty:enum Testing.WeekDays %a = %temp.91 return a; } @@ -220,17 +217,17 @@ contract Testing { function decodeStruct(bytes memory buffer) public pure returns (noPadStruct memory, PaddedStruct memory) { (noPadStruct memory a, PaddedStruct memory b) = abi.decode(buffer, (noPadStruct, PaddedStruct)); - // CHECK: ty:uint32 %temp.96 = (builtin ArrayLength ((arg #0))) - // CHECK: branchcond (unsigned uint32 57 <= %temp.96), block1, block2 + // CHECK: ty:uint32 %temp.92 = (builtin ArrayLength ((arg #0))) + // CHECK: branchcond (unsigned uint32 57 <= %temp.92), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:struct Testing.noPadStruct %temp.97 = struct { } - // CHECK: memcpy src: %buffer, dest: %temp.97, bytes_len: uint32 8 - // CHECK: ty:uint128 %temp.98 = (builtin ReadFromBuffer ((arg #0), uint32 8)) - // CHECK: ty:uint8 %temp.99 = (builtin ReadFromBuffer ((arg #0), uint32 24)) - // CHECK: ty:bytes32 %temp.100 = (builtin ReadFromBuffer ((arg #0), uint32 25)) - // CHECK: ty:struct Testing.PaddedStruct %temp.101 = struct { %temp.98, %temp.99, %temp.100 } - // CHECK: branchcond (unsigned less uint32 57 < %temp.96), block3, block4 + // CHECK: ty:struct Testing.noPadStruct %temp.93 = struct { } + // CHECK: memcpy src: %buffer, dest: %temp.93, bytes_len: uint32 8 + // CHECK: ty:uint128 %temp.94 = (builtin ReadFromBuffer ((arg #0), uint32 8)) + // CHECK: ty:uint8 %temp.95 = (builtin ReadFromBuffer ((arg #0), uint32 24)) + // CHECK: ty:bytes32 %temp.96 = (builtin ReadFromBuffer ((arg #0), uint32 25)) + // CHECK: ty:struct Testing.PaddedStruct %temp.97 = struct { %temp.94, %temp.95, %temp.96 } + // CHECK: branchcond (unsigned less uint32 57 < %temp.92), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure @@ -239,8 +236,8 @@ contract Testing { // CHECK: assert-failure // CHECK: block4: # buffer_read - // CHECK: ty:struct Testing.noPadStruct %a = %temp.97 - // CHECK: ty:struct Testing.PaddedStruct %b = %temp.101 + // CHECK: ty:struct Testing.noPadStruct %a = %temp.93 + // CHECK: ty:struct Testing.PaddedStruct %b = %temp.97 return (a, b); } @@ -250,31 +247,31 @@ contract Testing { (uint32[4] memory a, noPadStruct[2] memory b, noPadStruct[] memory c) = abi.decode(buffer, (uint32[4], noPadStruct[2], noPadStruct[])); - // CHECK: ty:uint32 %temp.102 = (builtin ArrayLength ((arg #0))) - // CHECK: branchcond (unsigned uint32 32 <= %temp.102), block1, block2 + // CHECK: ty:uint32 %temp.98 = (builtin ArrayLength ((arg #0))) + // CHECK: branchcond (unsigned uint32 32 <= %temp.98), block1, block2 // CHECK: block1: # inbounds - // CHECK: ty:uint32[4] %temp.103 = [ ] - // CHECK: memcpy src: %buffer, dest: %temp.103, bytes_len: uint32 16 - // CHECK: ty:struct Testing.noPadStruct[2] %temp.104 = [ ] - // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 16), dest: %temp.104, bytes_len: uint32 16 - // CHECK: ty:uint32 %temp.105 = (builtin ReadFromBuffer ((arg #0), uint32 32)) - // CHECK: branchcond (unsigned uint32 36 <= %temp.102), block3, block4 + // CHECK: ty:uint32[4] %temp.99 = [ ] + // CHECK: memcpy src: %buffer, dest: %temp.99, bytes_len: uint32 16 + // CHECK: ty:struct Testing.noPadStruct[2] %temp.100 = [ ] + // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 16), dest: %temp.100, bytes_len: uint32 16 + // CHECK: ty:uint32 %temp.101 = (builtin ReadFromBuffer ((arg #0), uint32 32)) + // CHECK: branchcond (unsigned uint32 36 <= %temp.98), block3, block4 // CHECK: block2: # out_of_bounds // CHECK: assert-failure // CHECK: block3: # inbounds - // CHECK: ty:struct Testing.noPadStruct[] %temp.106 = (alloc struct Testing.noPadStruct[] len %temp.105) - // CHECK: ty:uint32 %1.cse_temp = (%temp.105 * uint32 8) - // CHECK: branchcond (unsigned (uint32 36 + %1.cse_temp) <= %temp.102), block5, block6 + // CHECK: ty:struct Testing.noPadStruct[] %temp.102 = (alloc struct Testing.noPadStruct[] len %temp.101) + // CHECK: ty:uint32 %1.cse_temp = (%temp.101 * uint32 8) + // CHECK: branchcond (unsigned (uint32 36 + %1.cse_temp) <= %temp.98), block5, block6 // CHECK: block4: # out_of_bounds // CHECK: assert-failure // CHECK: block5: # inbounds - // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 36), dest: %temp.106, bytes_len: %1.cse_temp - // CHECK: branchcond (unsigned less (uint32 32 + (%1.cse_temp + uint32 4)) < %temp.102), block7, block8 + // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 36), dest: %temp.102, bytes_len: %1.cse_temp + // CHECK: branchcond (unsigned less (uint32 32 + (%1.cse_temp + uint32 4)) < %temp.98), block7, block8 // CHECK: block6: # out_of_bounds // CHECK: assert-failure @@ -283,9 +280,9 @@ contract Testing { // CHECK: assert-failure // CHECK: block8: # buffer_read - // CHECK: ty:uint32[4] %a = %temp.103 - // CHECK: ty:struct Testing.noPadStruct[2] %b = %temp.104 - // CHECK: ty:struct Testing.noPadStruct[] %c = %temp.106 + // CHECK: ty:uint32[4] %a = %temp.99 + // CHECK: ty:struct Testing.noPadStruct[2] %b = %temp.100 + // CHECK: ty:struct Testing.noPadStruct[] %c = %temp.102 return (a, b, c); } diff --git a/tests/codegen_testcases/solidity/constructor_with_metas.sol b/tests/codegen_testcases/solidity/constructor_with_metas.sol index c3060d08a..cca315cf8 100644 --- a/tests/codegen_testcases/solidity/constructor_with_metas.sol +++ b/tests/codegen_testcases/solidity/constructor_with_metas.sol @@ -3,17 +3,16 @@ import 'solana'; contract creator { - Child public c; // BEGIN-CHECK: creator::creator::function::create_child_with_meta__address_address function create_child_with_meta(address child, address payer) external { AccountMeta[2] metas = [ AccountMeta({pubkey: child, is_signer: false, is_writable: false}), AccountMeta({pubkey: payer, is_signer: true, is_writable: true}) ]; - // CHECK: constructor(no: 4) salt: value: gas:uint64 0 address:address 0xadde28d6c5697771bb24a668136224c7aac8e8ba974c2881484973b2e762fb74 seeds: Child encoded buffer: %abi_encoded.temp.15 accounts: %metas - c = new Child{accounts: metas}(); + // CHECK: external call::regular address:address 0xadde28d6c5697771bb24a668136224c7aac8e8ba974c2881484973b2e762fb74 payload:%abi_encoded.temp.13 value:uint64 0 gas:uint64 0 accounts:%metas seeds: contract|function:(1, 3) flags: + Child.new{accounts: metas}(); - c.say_hello(); + Child.say_hello(); } } diff --git a/tests/codegen_testcases/solidity/import_ext_call.sol b/tests/codegen_testcases/solidity/import_ext_call.sol new file mode 100644 index 000000000..29c9c67a4 --- /dev/null +++ b/tests/codegen_testcases/solidity/import_ext_call.sol @@ -0,0 +1,25 @@ +// RUN: --target solana --emit cfg +import '../import_test.sol' as My; + +@program_id("6qEm4QUJGFvqKNJGjTrAEiFhbVBY4ashpBjDHEFvEUmW") +contract Foo { + // BEGIN-CHECK: Foo::Foo::function::get_b + function get_b(address id) public pure { + // External calls + // CHECK: external call::regular address:(arg #0) payload:%abi_encoded.temp.2 value:uint64 0 gas:uint64 0 accounts:[0] [ ] seeds: contract|function:(2, 2) flags: + My.Dog.barks{program_id: id}("woof"); + // CHECK: external call::regular address:(arg #0) payload:%abi_encoded.temp.4 value:uint64 0 gas:uint64 0 accounts:[0] [ ] seeds: contract|function:(2, 2) flags: + My.Dog.barks{program_id: id}({what: "meow"}); + } +} + +contract Cat is My.Dog { + // BEGIN-CHECK: Cat::Cat::function::try_cat + function try_cat() public pure { + // Internal calls + My.Dog.barks("woof"); + // CHECK: Cat::Dog::function::barks__string (alloc string uint32 4 "woof") + My.Dog.barks({what: "meow"}); + // CHECK: Cat::Dog::function::barks__string (alloc string uint32 4 "meow") + } +} \ No newline at end of file diff --git a/tests/codegen_testcases/solidity/solana_base_versus_external.sol b/tests/codegen_testcases/solidity/solana_base_versus_external.sol new file mode 100644 index 000000000..e0035b924 --- /dev/null +++ b/tests/codegen_testcases/solidity/solana_base_versus_external.sol @@ -0,0 +1,46 @@ +// RUN: --target solana --emit cfg + +import 'solana'; + +@program_id("6qEm4QUJGFvqKNJGjTrAEiFhbVBY4ashpBjDHEFvEUmW") +contract Foo { + uint b; + constructor(uint a) { + b = a; + } + + function get_b(address id) public returns (uint) { + return b; + } + + function get_b2(address id) public returns (uint) { + return b; + } +} + +@program_id("9VLAw4to9KsX9DvyzJUJwfUeQCouX79szENj78sZKiqA") +contract Other is Foo { + uint c; + constructor(uint d) Foo(d) { + c = d; + } + // BEGIN-CHECK: Other::Other::function::call_foo__address + function call_foo(address id) external { + // internal calls + Foo.get_b(id); + // CHECK: call Other::Foo::function::get_b__address (arg #0) + Foo.get_b2({id: id}); + // CHECK: call Other::Foo::function::get_b2__address (arg #0) + } + // BEGIN-CHECK: Other::Other::function::call_foo2__address_address + function call_foo2(address id, address acc) external { + AccountMeta[1] meta = [ + AccountMeta({pubkey: acc, is_writable: false, is_signer: false}) + ]; + // external calls + Foo.get_b{program_id: id, accounts: meta}(id); + // CHECK: external call::regular address:(arg #0) payload:%abi_encoded.temp.35 value:uint64 0 gas:uint64 0 accounts:%meta seeds: contract|function:(0, 3) flags: + Foo.get_b2{program_id: id, accounts: meta}(id); + // CHECK: external call::regular address:(arg #0) payload:%abi_encoded.temp.36 value:uint64 0 gas:uint64 0 accounts:%meta seeds: contract|function:(0, 4) flags: + } +} \ No newline at end of file diff --git a/tests/codegen_testcases/solidity/solana_payer_account.sol b/tests/codegen_testcases/solidity/solana_payer_account.sol index c4e59795c..5a1d647f0 100644 --- a/tests/codegen_testcases/solidity/solana_payer_account.sol +++ b/tests/codegen_testcases/solidity/solana_payer_account.sol @@ -2,15 +2,14 @@ @program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER") contract Builder { - Built other; // BEGIN-CHECK: Builder::Builder::function::build_this function build_this() external { - // CHECK: constructor(no: 4) salt: value: gas:uint64 0 address:address 0x69be884fd55a2306354c305323cc6b7ce91768be33d32a021155ef608806bcb seeds: Built encoded buffer: %abi_encoded.temp.18 accounts: [3] [ struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 2]) field 0)), true, false }, struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 1]) field 0)), true, true }, struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 4]) field 0)), false, false } ] - other = new Built("my_seed"); + // CHECK: external call::regular address:address 0x69be884fd55a2306354c305323cc6b7ce91768be33d32a021155ef608806bcb payload:%abi_encoded.temp.17 value:uint64 0 gas:uint64 0 accounts:[3] [ struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 3]) field 0)), true, false }, struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 2]) field 0)), true, true }, struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 0]) field 0)), false, false } ] seeds: contract|function:(1, 4) flags: + Built.new("my_seed"); } function call_that() public view { - other.say_this("Hold up! I'm calling!"); + Built.say_this("Hold up! I'm calling!"); } } diff --git a/tests/contract_testcases/polkadot/contracts/program_id.sol b/tests/contract_testcases/polkadot/contracts/program_id.sol new file mode 100644 index 000000000..3592c8a71 --- /dev/null +++ b/tests/contract_testcases/polkadot/contracts/program_id.sol @@ -0,0 +1,14 @@ +contract Foo { + function tryFoo() public {} +} + +contract Bar { + Foo f; + function foobar(address id) public { + f = new Foo(); + f.tryFoo{program_id: id}(); + } +} + +// ---- Expect: diagnostics ---- +// error: 9:18-32: 'program_id' not permitted for external calls or constructors on Polkadot diff --git a/tests/contract_testcases/solana/abstract_interface.sol b/tests/contract_testcases/solana/abstract_interface.sol index 5658c649c..e09de85b4 100644 --- a/tests/contract_testcases/solana/abstract_interface.sol +++ b/tests/contract_testcases/solana/abstract_interface.sol @@ -2,8 +2,8 @@ abstract contract A { function v(int) public virtual; } contract C { - function t(A a) public { - a.v(1); + function t(address id) public { + A.v{program_id: id}(1); } } diff --git a/tests/contract_testcases/solana/accounts/constructor_in_loop.sol b/tests/contract_testcases/solana/accounts/constructor_in_loop.sol index de1c502e0..a162aec51 100644 --- a/tests/contract_testcases/solana/accounts/constructor_in_loop.sol +++ b/tests/contract_testcases/solana/accounts/constructor_in_loop.sol @@ -1,16 +1,14 @@ contract foo { - X a; - Y ay; function contruct() external { - a = new X({varia: 2, varb: 5}); + X.new({varia: 2, varb: 5}); } function test1() external returns (uint) { // This is allowed - uint j = a.vara(1, 2); + uint j = X.vara(1, 2); for (uint i = 0; i < 64; i++) { - j += a.vara(i, j); + j += X.vara(i, j); } return j; } @@ -18,7 +16,7 @@ contract foo { function test2() external returns (uint) { uint j = 3; for (uint i = 0; i < 64; i++) { - a = new X(i, j); + X.new(i, j); } return j; } @@ -29,19 +27,19 @@ contract foo { for (uint i = 0; i < 64; i++) { n += i; } - a = new X(n, j); + X.new(n, j); return j; } function test4(uint v1, string v2) external { - ay = new Y({b: v2, a: v1}); + Y.new({b: v2, a: v1}); } function test5() external returns (uint) { uint j = 3; uint i=0; while (i < 64) { - a = new X(i, j); + X.new(i, j); i++; } return j; @@ -53,7 +51,7 @@ contract foo { while (i < 64) { i++; } - a = new X(i, j); + X.new(i, j); return j; } @@ -61,7 +59,7 @@ contract foo { uint j = 3; uint i=0; do { - a = new X(i, j); + X.new(i, j); i++; } while (i < 64); return j; @@ -73,7 +71,7 @@ contract foo { do { i++; } while (i < 64); - a = new X(i, j); + X.new(i, j); return j; } @@ -85,7 +83,7 @@ contract foo { for(uint i=0; i<80; i++) { n +=i; } - a = new X(j, n); + X.new(j, n); } return j; @@ -116,8 +114,8 @@ contract Y { } // ---- Expect: diagnostics ---- -// error: 21:8-19: the {accounts: ..} call argument is needed since the constructor may be called multiple times -// error: 37:14-35: in order to instantiate contract 'Y', a @program_id is required on contract 'Y' -// error: 44:8-19: the {accounts: ..} call argument is needed since the constructor may be called multiple times -// error: 64:8-19: the {accounts: ..} call argument is needed since the constructor may be called multiple times -// error: 88:17-28: the {accounts: ..} call argument is needed since the constructor may be called multiple times +// error: 19:4-15: the {accounts: ..} call argument is needed since the constructor may be called multiple times +// error: 35:9-30: a contract needs a program id to be called. Either a '@program_id' must be declared above a contract or the {program_id: ...} call argument must be present +// error: 42:4-15: the {accounts: ..} call argument is needed since the constructor may be called multiple times +// error: 62:4-15: the {accounts: ..} call argument is needed since the constructor may be called multiple times +// error: 86:13-24: the {accounts: ..} call argument is needed since the constructor may be called multiple times diff --git a/tests/contract_testcases/solana/accounts/double_calls.sol b/tests/contract_testcases/solana/accounts/double_calls.sol index fb271704e..58716493a 100644 --- a/tests/contract_testcases/solana/accounts/double_calls.sol +++ b/tests/contract_testcases/solana/accounts/double_calls.sol @@ -1,22 +1,21 @@ contract adult { - hatchling hh; function test() external { - hatchling h1 = new hatchling("luna"); - hatchling h2 = new hatchling("sol"); + hatchling.new("luna"); + hatchling.new("sol"); } function create(string id) external { - hh = new hatchling(id); + hatchling.new(id); } function call2() external { - hh.call_me("id"); - hh.call_me("not_id"); + hatchling.call_me("id"); + hatchling.call_me("not_id"); } function create2(string id2) external { - hh = new hatchling(id2); - hh.call_me(id2); + hatchling.new(id2); + hatchling.call_me(id2); } } @@ -40,12 +39,10 @@ contract hatchling { } // ---- Expect: diagnostics ---- -// warning: 4:19-21: local variable 'h1' is unused -// warning: 5:19-21: local variable 'h2' is unused -// error: 5:24-44: contract 'hatchling' is called more than once in this function, so automatic account collection cannot happen. Please, provide the necessary accounts using the {accounts:..} call argument -// note 4:24-45: other call -// warning: 12:5-30: function can be declared 'view' -// error: 14:9-29: contract 'hatchling' is called more than once in this function, so automatic account collection cannot happen. Please, provide the necessary accounts using the {accounts:..} call argument -// note 13:9-25: other call -// error: 19:9-24: contract 'hatchling' is called more than once in this function, so automatic account collection cannot happen. Please, provide the necessary accounts using the {accounts:..} call argument -// note 18:14-32: other call +// error: 4:9-29: contract 'hatchling' is called more than once in this function, so automatic account collection cannot happen. Please, provide the necessary accounts using the {accounts:..} call argument +// note 3:9-30: other call +// warning: 11:5-30: function can be declared 'view' +// error: 13:9-36: contract 'hatchling' is called more than once in this function, so automatic account collection cannot happen. Please, provide the necessary accounts using the {accounts:..} call argument +// note 12:9-32: other call +// error: 18:9-31: contract 'hatchling' is called more than once in this function, so automatic account collection cannot happen. Please, provide the necessary accounts using the {accounts:..} call argument +// note 17:9-27: other call diff --git a/tests/contract_testcases/solana/annotations/account_name_collision.sol b/tests/contract_testcases/solana/annotations/account_name_collision.sol index 1405b0a2e..aeb54964f 100644 --- a/tests/contract_testcases/solana/annotations/account_name_collision.sol +++ b/tests/contract_testcases/solana/annotations/account_name_collision.sol @@ -1,10 +1,9 @@ @program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER") contract Builder { - BeingBuilt other; @payer(payer_account) constructor() { - other = new BeingBuilt("abc"); + BeingBuilt.new("abc"); } } @@ -21,6 +20,5 @@ contract BeingBuilt { } // ---- Expect: diagnostics ---- -// warning: 3:5-21: storage variable 'other' has been assigned, but never read -// error: 5:5-26: account name collision encountered. Calling a function that requires an account whose name is also defined in the current function will create duplicate names in the IDL. Please, rename one of the accounts -// note 15:5-26: other declaration \ No newline at end of file +// error: 4:5-26: account name collision encountered. Calling a function that requires an account whose name is also defined in the current function will create duplicate names in the IDL. Please, rename one of the accounts +// note 14:5-26: other declaration \ No newline at end of file diff --git a/tests/contract_testcases/solana/annotations/constructor_external_function.sol b/tests/contract_testcases/solana/annotations/constructor_external_function.sol index 0f10ac5cd..ded075f49 100644 --- a/tests/contract_testcases/solana/annotations/constructor_external_function.sol +++ b/tests/contract_testcases/solana/annotations/constructor_external_function.sol @@ -6,27 +6,25 @@ contract Foo { } contract Bar { - Foo public foo; - function external_create_foo(address addr) external { // This not is allowed - foo = new Foo{address: addr}(); + Foo.new{address: addr}(); } function create_foo() public { // This is not allowed - foo = new Foo(); + Foo.new(); } function this_is_allowed() external { - foo = new Foo(); + Foo.new(); } function call_foo() public pure { - foo.say_hello(); + Foo.say_hello(); } } // ---- Expect: diagnostics ---- -// error: 13:23-36: 'address' not a valid call parameter -// error: 18:15-24: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external +// error: 11:17-30: 'address' not a valid call parameter +// error: 16:9-18: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external diff --git a/tests/contract_testcases/solana/call/call_args_three_ways.sol b/tests/contract_testcases/solana/call/call_args_three_ways.sol index 8e92be1af..22645e42f 100644 --- a/tests/contract_testcases/solana/call/call_args_three_ways.sol +++ b/tests/contract_testcases/solana/call/call_args_three_ways.sol @@ -1,15 +1,15 @@ contract C { function f() public { // Three different parse tree for callargs with new - D d = (new D{value: 1})(); - D dd = (new D){value: 1}(); - D ddd = new D{value: 1}(); + (D.new{value: 1})(); + (D.new){value: 1}(); + D.new{value: 1}(); } - function g(D d) public { + function g() public { // Three different parse tree for callargs - d.func{value: 1}(); - (d.func){value: 1}(); - (d.func{value: 1})(); + D.func{value: 1}(); + (D.func){value: 1}(); + (D.func{value: 1})(); } } @@ -20,8 +20,8 @@ contract D { } // ---- Expect: diagnostics ---- -// error: 4:9-28: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external -// error: 4:16-24: Solana Cross Program Invocation (CPI) cannot transfer native value. See https://solang.readthedocs.io/en/latest/language/functions.html#value_transfer +// error: 4:3-22: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external +// error: 4:10-18: Solana Cross Program Invocation (CPI) cannot transfer native value. See https://solang.readthedocs.io/en/latest/language/functions.html#value_transfer // error: 10:10-18: Solana Cross Program Invocation (CPI) cannot transfer native value. See https://solang.readthedocs.io/en/latest/language/functions.html#value_transfer // error: 11:12-20: Solana Cross Program Invocation (CPI) cannot transfer native value. See https://solang.readthedocs.io/en/latest/language/functions.html#value_transfer // error: 12:11-19: Solana Cross Program Invocation (CPI) cannot transfer native value. See https://solang.readthedocs.io/en/latest/language/functions.html#value_transfer diff --git a/tests/contract_testcases/solana/constant/not_constant.sol b/tests/contract_testcases/solana/constant/not_constant.sol index 2387df423..2544c6f59 100644 --- a/tests/contract_testcases/solana/constant/not_constant.sol +++ b/tests/contract_testcases/solana/constant/not_constant.sol @@ -11,5 +11,4 @@ } // ---- Expect: diagnostics ---- -// error: 8:26-27: 'C' is a contract -// error: 8:26-36: function calls via contract name are only valid for base contracts +// error: 8:26-36: a contract needs a program id to be called. Either a '@program_id' must be declared above a contract or the {program_id: ...} call argument must be present diff --git a/tests/contract_testcases/solana/contracts/abstract_constructor.sol b/tests/contract_testcases/solana/contracts/abstract_constructor.sol new file mode 100644 index 000000000..6ba82285a --- /dev/null +++ b/tests/contract_testcases/solana/contracts/abstract_constructor.sol @@ -0,0 +1,23 @@ +abstract contract Foo2 { + uint b; + constructor(uint a) { + b = a; + } +} + +contract Other2 { + uint c; + constructor(uint d) { + c = d; + } + function call_foo(address id) external { + Foo2.new{program_id: id}(5); + } + function call_foo2(address id) external { + Foo2.new{program_id: id}({a: 5}); + } +} + +// ---- Expect: diagnostics ---- +// error: 14:9-36: cannot construct 'Foo2' of type 'abstract contract' +// error: 17:9-41: cannot construct 'Foo2' of type 'abstract contract' diff --git a/tests/contract_testcases/solana/contracts/circular_reference.sol b/tests/contract_testcases/solana/contracts/circular_reference.sol new file mode 100644 index 000000000..7810a3343 --- /dev/null +++ b/tests/contract_testcases/solana/contracts/circular_reference.sol @@ -0,0 +1,48 @@ +contract Foo { + uint b; + function get_b(address id) external returns (uint) { + Other.new{program_id: id}(); + return b; + } +} + +contract Other { + function call_foo(address id) external { + Foo.new{program_id: id}(); + } +} + +contract Foo2 { + uint b; + constructor(uint a) { + b = a; + } + + function get_b(address id) external returns (uint) { + Other2.new{program_id: id}(2); + return b; + } + + function get_b2(address id) external returns (uint) { + Other2.new{program_id: id}({d: 2}); + return b; + } +} + +contract Other2 { + uint c; + constructor(uint d) { + c = d; + } + function call_foo(address id) external { + Foo2.new{program_id: id}(5); + } + function call_foo2(address id) external { + Foo2.new{program_id: id}({a: 5}); + } +} + +// ---- Expect: diagnostics ---- +// error: 11:9-34: circular reference creating contract 'Foo' +// error: 38:9-36: circular reference creating contract 'Foo2' +// error: 41:9-41: circular reference creating contract 'Foo2' diff --git a/tests/contract_testcases/solana/contracts/constructor_freestanding.sol b/tests/contract_testcases/solana/contracts/constructor_freestanding.sol new file mode 100644 index 000000000..63a71732d --- /dev/null +++ b/tests/contract_testcases/solana/contracts/constructor_freestanding.sol @@ -0,0 +1,29 @@ +import 'solana'; + +function standalone(address dataAccount) returns (address) { + AccountMeta[1] meta = [ + AccountMeta(dataAccount, false, false) + ]; + + hatchling.new{accounts: meta}("my_id", dataAccount); + return hatchling.root{accounts: meta}(); +} + +@program_id("5afzkvPkrshqu4onwBCsJccb1swrt4JdAjnpzK8N4BzZ") +contract hatchling { + string name; + address private origin; + + constructor(string id, address parent) { + require(id != "", "name must be provided"); + name = id; + origin = parent; + } + + function root() public returns (address) { + return origin; + } +} + +// ---- Expect: diagnostics ---- +// error: 8:5-56: constructors not allowed in free standing functions diff --git a/tests/contract_testcases/solana/contracts/contract_as_variables.sol b/tests/contract_testcases/solana/contracts/contract_as_variables.sol new file mode 100644 index 000000000..79869af1d --- /dev/null +++ b/tests/contract_testcases/solana/contracts/contract_as_variables.sol @@ -0,0 +1,51 @@ +import "solana"; + +contract B { + starter ss; + function declare_contract(address addr) external returns (bool) { + AccountMeta[1] meta = [ + AccountMeta({pubkey: addr, is_signer: true, is_writable: true}) + ]; + starter g = new starter{accounts: meta}(); + return g.get{accounts: meta}(); + } + + function receive_contract(starter g) public { + g.flip(); + } + + function return_contract() external returns (starter) { + starter c = new starter(); + return c; + } +} + + + +@program_id("CU8sqXecq7pxweQnJq6CARonEApGN2DXcv9ukRBRgnRf") +contract starter { + bool private value = true; + + modifier test_modifier() { + print("modifier"); + _; + } + + constructor() { + print("Hello, World!"); + } + + function flip() public test_modifier { + value = !value; + } + + function get() public view returns (bool) { + return value; + } +} + +// ---- Expect: diagnostics ---- +// error: 4:5-12: contracts are not allowed as types on Solana +// error: 9:9-16: contracts are not allowed as types on Solana +// error: 13:31-38: contracts are not allowed as types on Solana +// error: 17:50-57: contracts are not allowed as types on Solana diff --git a/tests/contract_testcases/solana/contracts/contract_call_freestanding.sol b/tests/contract_testcases/solana/contracts/contract_call_freestanding.sol new file mode 100644 index 000000000..adce2c393 --- /dev/null +++ b/tests/contract_testcases/solana/contracts/contract_call_freestanding.sol @@ -0,0 +1,28 @@ +import 'solana'; + +function standalone(address dataAccount) returns (address) { + AccountMeta[1] meta = [ + AccountMeta(dataAccount, false, false) + ]; + return hatchling.root{accounts: meta}(); +} + +@program_id("5afzkvPkrshqu4onwBCsJccb1swrt4JdAjnpzK8N4BzZ") +contract hatchling { + string name; + address private origin; + + constructor(string id, address parent) { + require(id != "", "name must be provided"); + name = id; + origin = parent; + } + + function root() public returns (address) { + return origin; + } +} + +// ---- Expect: diagnostics ---- +// warning: 12:5-16: storage variable 'name' has been assigned, but never read +// warning: 21:5-45: function can be declared 'view' diff --git a/tests/contract_testcases/solana/create_contract/syntax.sol b/tests/contract_testcases/solana/create_contract/syntax.sol index 6686f1088..b875569ae 100644 --- a/tests/contract_testcases/solana/create_contract/syntax.sol +++ b/tests/contract_testcases/solana/create_contract/syntax.sol @@ -1,10 +1,10 @@ contract y { function f() public { - x a = new x{gas: 102}(); + x.new{gas: 102}(); } } contract x {} // ---- Expect: diagnostics ---- -// error: 4:29-37: 'gas' not permitted for external calls or constructors on Solana +// error: 4:23-31: 'gas' not permitted for external calls or constructors on Solana diff --git a/tests/contract_testcases/solana/create_contract/syntax_01.sol b/tests/contract_testcases/solana/create_contract/syntax_01.sol index a42f00e81..982158b7b 100644 --- a/tests/contract_testcases/solana/create_contract/syntax_01.sol +++ b/tests/contract_testcases/solana/create_contract/syntax_01.sol @@ -1,10 +1,10 @@ contract y { function f() public { - x a = new x{salt: 102}(); + x.new{salt: 102}(); } } contract x {} // ---- Expect: diagnostics ---- -// error: 4:29-38: 'salt' not permitted for external calls or constructors on Solana +// error: 4:23-32: 'salt' not permitted for external calls or constructors on Solana diff --git a/tests/contract_testcases/solana/destructure_assign_struct_member_2.sol b/tests/contract_testcases/solana/destructure_assign_struct_member_2.sol index bc0fc6a24..3bf2f8cac 100644 --- a/tests/contract_testcases/solana/destructure_assign_struct_member_2.sol +++ b/tests/contract_testcases/solana/destructure_assign_struct_member_2.sol @@ -19,7 +19,7 @@ contract Contract { // get shares and eth required for each share Struct1[] memory struct_1 = new Struct1[](size); - (struct_1[0].a, struct_1[0].b,) = IUniswapV2Pair(_tokens[0]).getReserves(); + (struct_1[0].a, struct_1[0].b,) = IUniswapV2Pair.getReserves{program_id: _tokens[0]}(); } } diff --git a/tests/contract_testcases/solana/error.sol b/tests/contract_testcases/solana/error.sol index bc14c8d6e..0ac23bd2d 100644 --- a/tests/contract_testcases/solana/error.sol +++ b/tests/contract_testcases/solana/error.sol @@ -2,12 +2,11 @@ contract error { error X(); - function foo(error x) public { + function foo() public { } } // ---- Expect: diagnostics ---- // warning: 3:8-9: error 'X' has never been used -// warning: 5:2-30: function can be declared 'pure' -// warning: 5:21-22: function parameter 'x' is unused +// warning: 5:2-23: function can be declared 'pure' diff --git a/tests/contract_testcases/solana/expressions/contract_compare.sol b/tests/contract_testcases/solana/expressions/contract_compare.sol deleted file mode 100644 index 3d2fd7f84..000000000 --- a/tests/contract_testcases/solana/expressions/contract_compare.sol +++ /dev/null @@ -1,19 +0,0 @@ - -contract c { - function cmp(d left, d right) public returns (bool) { - return left < right; - } - - function cmp(d left, e right) public returns (bool) { - return left > right; - } - -} - -contract d {} -contract e {} - -// ---- Expect: diagnostics ---- -// error: 7:2-53: function 'cmp' overrides function in same contract -// note 3:2-53: previous definition of 'cmp' -// error: 8:10-14: expression of type contract d not allowed diff --git a/tests/contract_testcases/solana/expressions/contract_no_init.sol b/tests/contract_testcases/solana/expressions/contract_no_init.sol index ca162c075..7bb16586a 100644 --- a/tests/contract_testcases/solana/expressions/contract_no_init.sol +++ b/tests/contract_testcases/solana/expressions/contract_no_init.sol @@ -5,14 +5,13 @@ contract testing { function test(int x) public returns (int) { - other o; do { x--; - o = new other(); + other.new(); }while(x > 0); - return o.a(); + return other.a(); } } // ---- Expect: diagnostics ---- -// error: 11:25-36: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external +// error: 10:21-32: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external diff --git a/tests/contract_testcases/solana/expressions/selector_in_free_function_02.sol b/tests/contract_testcases/solana/expressions/selector_in_free_function_02.sol index 9dad5b1b9..19dc5b51c 100644 --- a/tests/contract_testcases/solana/expressions/selector_in_free_function_02.sol +++ b/tests/contract_testcases/solana/expressions/selector_in_free_function_02.sol @@ -4,11 +4,11 @@ } contract foo { - function f(I t) public returns (bytes8) { - return t.X.selector; + function f() public returns (bytes8) { + return I.X.selector; } } // ---- Expect: diagnostics ---- // warning: 3:13-34: function can be declared 'pure' -// warning: 7:13-52: function can be declared 'pure' +// warning: 7:13-49: function can be declared 'pure' diff --git a/tests/contract_testcases/solana/functions/external_functions.sol b/tests/contract_testcases/solana/functions/external_functions.sol index cd1936010..fe3adb0a6 100644 --- a/tests/contract_testcases/solana/functions/external_functions.sol +++ b/tests/contract_testcases/solana/functions/external_functions.sol @@ -1,7 +1,7 @@ -function doThis(bar1 bb) returns (int) { +function doThis(address id) returns (int) { // This is allwoed - return bb.this_is_external(1, 2); + return bar1.this_is_external{program_id: id}(1, 2); } contract bar1 { @@ -46,5 +46,6 @@ contract bar2 is bar1 { // note 19:5-61: declaration of function 'hello' // error: 30:16-35: functions declared external cannot be called via an internal function call // note 19:5-61: declaration of function 'hello' +// error: 35:16-43: a contract needs a program id to be called. Either a '@program_id' must be declared above a contract or the {program_id: ...} call argument must be present // error: 40:16-38: functions declared external cannot be called via an internal function call // note 10:5-72: declaration of function 'this_is_external' \ No newline at end of file diff --git a/tests/contract_testcases/solana/garbage_function_args.sol b/tests/contract_testcases/solana/garbage_function_args.sol index 0f8371157..17bf79ba4 100644 --- a/tests/contract_testcases/solana/garbage_function_args.sol +++ b/tests/contract_testcases/solana/garbage_function_args.sol @@ -1,3 +1,4 @@ +@program_id("A8A3VYtDN69E72gceahcfVjLbf7m3c1u2RDwnbWgfRAk") contract c { function g(address) public { require(rubbish, "rubbish n'existe pas!"); @@ -17,8 +18,8 @@ contract c { } // ---- Expect: diagnostics ---- -// error: 3:11-18: 'rubbish' not found -// error: 6:15-18: 'meh' not found -// error: 9:9-12: 'foo' not found -// error: 12:10-12: 'oo' not found -// error: 15:14-17: 'foo' not found +// error: 4:11-18: 'rubbish' not found +// error: 7:15-18: 'meh' not found +// error: 10:9-12: 'foo' not found +// error: 13:10-12: 'oo' not found +// error: 16:14-17: 'foo' not found diff --git a/tests/contract_testcases/solana/mapping_deletion.sol b/tests/contract_testcases/solana/mapping_deletion.sol index 327df92e1..4d61c9469 100644 --- a/tests/contract_testcases/solana/mapping_deletion.sol +++ b/tests/contract_testcases/solana/mapping_deletion.sol @@ -16,14 +16,14 @@ contract DeleteTest { } mapping(uint => data_struct) example; - mapping(uint => savedTest) example2; + mapping(uint => address) example2; - function addData() public { + function addData(address pid) public { data_struct dt = data_struct({addr1: address(this), addr2: tx.accounts.dataAccount.key}); uint id = 1; example[id] = dt; - savedTest tt = new savedTest(4); - example2[id] = tt; + savedTest.new{program_id: pid}(4); + example2[id] = pid; } function deltest() external { @@ -39,4 +39,4 @@ contract DeleteTest { } // ---- Expect: diagnostics ---- -// error: 25:24-40: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external +// error: 25:9-42: accounts are required for calling a contract. You can either provide the accounts with the {accounts: ...} call argument or change this function's visibility to external diff --git a/tests/contract_testcases/solana/type_decl_import.sol b/tests/contract_testcases/solana/type_decl_import.sol index 7f1b15d0d..5d57915ac 100644 --- a/tests/contract_testcases/solana/type_decl_import.sol +++ b/tests/contract_testcases/solana/type_decl_import.sol @@ -1,11 +1,11 @@ import "./type_decl.sol" as IMP; contract d { - function f(IMP.x c) public { + function f(address pid) public { IMP.Addr a = IMP.Addr.wrap(payable(this)); IMP.x.Binary b = IMP.x.Binary.wrap(false); - c.f(a, b); + IMP.x.f{program_id: pid}(a, b); } } diff --git a/tests/optimization_testcases/calls/54e619457eea3be2790d0fa66cba06d2e9a36f17.json b/tests/optimization_testcases/calls/54e619457eea3be2790d0fa66cba06d2e9a36f17.json deleted file mode 100644 index 517c0ebd7..000000000 --- a/tests/optimization_testcases/calls/54e619457eea3be2790d0fa66cba06d2e9a36f17.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "constructor": [], - "function": [ - [ - "test", - [] - ] - ] -} diff --git a/tests/optimization_testcases/programs/54e619457eea3be2790d0fa66cba06d2e9a36f17.sol b/tests/optimization_testcases/programs/54e619457eea3be2790d0fa66cba06d2e9a36f17.sol deleted file mode 100644 index f13287bec..000000000 --- a/tests/optimization_testcases/programs/54e619457eea3be2790d0fa66cba06d2e9a36f17.sol +++ /dev/null @@ -1,21 +0,0 @@ -interface I { - function f(int) external; -} - -library L { - function F(I i, bool b, int n) public { - if (b) { - print("Hello"); - } - } -} - -contract C { - using L for I; - - function test() public { - I i = I(address(0)); - - i.F(true, 102); - } -} diff --git a/tests/solana_tests/abi_decode.rs b/tests/solana_tests/abi_decode.rs index 08c1270b8..b647b4b54 100644 --- a/tests/solana_tests/abi_decode.rs +++ b/tests/solana_tests/abi_decode.rs @@ -110,10 +110,10 @@ fn decode_address() { r#" contract Testing { function testAddress(bytes memory buffer) public view { - (address a, Testing b) = abi.decode(buffer, (address, Testing)); + (address a, address b) = abi.decode(buffer, (address, address)); assert(a == address(this)); - assert(b == this); + assert(b == address(this)); } } "#, diff --git a/tests/solana_tests/abi_encode.rs b/tests/solana_tests/abi_encode.rs index a7244efbe..f01e09e95 100644 --- a/tests/solana_tests/abi_encode.rs +++ b/tests/solana_tests/abi_encode.rs @@ -1004,8 +1004,8 @@ contract caller { return b + 3; } - function do_call() view public returns (int64, int32) { - return (this.doThis(5), this.doThat(3)); + function do_call(address pid) view public returns (int64, int32) { + return (this.doThis{program_id: pid}(5), this.doThat{program_id: pid}(3)); } }"#, ); @@ -1018,6 +1018,7 @@ contract caller { let caller_program_id = vm.stack[0].id; let returns = vm .function("do_call") + .arguments(&[BorshToken::Address(caller_program_id)]) .accounts(vec![ ("systemProgram", [0; 32]), ("caller_programId", caller_program_id), diff --git a/tests/solana_tests/accessor.rs b/tests/solana_tests/accessor.rs index 6efcbb39d..85d4a472f 100644 --- a/tests/solana_tests/accessor.rs +++ b/tests/solana_tests/accessor.rs @@ -262,17 +262,17 @@ fn struct_accessor() { m[1023413412] = S({f1: 414243, f2: true, f3: E("niff")}); } - function f() public view { + function f(address pid) public view { AccountMeta[1] meta = [ AccountMeta({pubkey: tx.accounts.dataAccount.key, is_writable: false, is_signer: false}) ]; - (int64 a1, bool b, E memory c) = this.a{accounts: meta}(); + (int64 a1, bool b, E memory c) = this.a{accounts: meta, program_id: pid}(); require(a1 == -63 && !b && c.b4 == "nuff", "a"); - (a1, b, c) = this.s{accounts: meta}(99); + (a1, b, c) = this.s{accounts: meta, program_id: pid}(99); require(a1 == 65535 && b && c.b4 == "naff", "b"); - (a1, b, c) = this.m{accounts: meta}(1023413412); + (a1, b, c) = this.m{accounts: meta, program_id: pid}(1023413412); require(a1 == 414243 && b && c.b4 == "niff", "c"); - c.b4 = this.e{accounts: meta}(); + c.b4 = this.e{accounts: meta, program_id: pid}(); require(a1 == 414243 && b && c.b4 == "cons", "E"); } }"#, @@ -283,10 +283,13 @@ fn struct_accessor() { .accounts(vec![("dataAccount", data_account)]) .call(); + let program_id = vm.stack[0].id; vm.function("f") + .arguments(&[BorshToken::Address(program_id)]) .accounts(vec![ ("dataAccount", data_account), ("systemProgram", [0; 32]), + ("C_programId", program_id), ]) .call(); } diff --git a/tests/solana_tests/call.rs b/tests/solana_tests/call.rs index 90c5a1499..2a2974dbc 100644 --- a/tests/solana_tests/call.rs +++ b/tests/solana_tests/call.rs @@ -4,7 +4,7 @@ use crate::{ build_solidity, create_program_address, AccountMeta, AccountState, BorshToken, Instruction, Pubkey, VirtualMachine, }; -use base58::{FromBase58, ToBase58}; +use base58::FromBase58; use num_bigint::BigInt; use num_traits::One; @@ -17,8 +17,8 @@ fn simple_external_call() { print("bar0 says: " + v); } - function test_other(bar1 x) public { - x.test_bar("cross contract call"); + function test_other(address x) public { + bar1.test_bar{program_id: x}("cross contract call"); } } @@ -86,8 +86,8 @@ fn external_call_with_returns() { let mut vm = build_solidity( r#" contract bar0 { - function test_other(bar1 x) public returns (int64) { - return x.test_bar(7) + 5; + function test_other(address x) public returns (int64) { + return bar1.test_bar{program_id: x}(7) + 5; } } @@ -166,7 +166,7 @@ fn external_raw_call_with_returns() { contract bar0 { bytes8 private constant SELECTOR = bytes8(sha256(bytes('global:test_bar'))); - function test_other(bar1 x) public returns (int64) { + function test_other(address x) public returns (int64) { bytes select = abi.encodeWithSelector(SELECTOR, int64(7)); bytes signature = abi.encodeWithSignature("global:test_bar", int64(7)); require(select == signature, "must be the same"); @@ -293,14 +293,14 @@ fn external_call_with_string_returns() { let mut vm = build_solidity( r#" contract bar0 { - function test_other(bar1 x) public returns (string) { - string y = x.test_bar(7); + function test_other(address x) public returns (string) { + string y = bar1.test_bar{program_id: x}(7); print(y); return y; } - function test_this(bar1 x) public { - address a = x.who_am_i(); + function test_this(address x) public { + address a = bar1.who_am_i{program_id: x}(); assert(a == address(x)); } } @@ -392,7 +392,7 @@ fn encode_call() { contract bar0 { bytes8 private constant SELECTOR = bytes8(sha256(bytes('global:test_bar'))); - function test_other(bar1 x) public returns (int64) { + function test_other(address x) public returns (int64) { bytes select = abi.encodeWithSelector(SELECTOR, int64(7)); bytes signature = abi.encodeCall(bar1.test_bar, 7); require(select == signature, "must be the same"); @@ -439,7 +439,6 @@ fn encode_call() { .accounts(vec![("dataAccount", bar0_account)]) .call(); - std::println!("bar_acc: {}", bar1_account.to_base58()); let res = vm .function("test_other") .arguments(&[BorshToken::Address(bar1_program_id)]) diff --git a/tests/solana_tests/create_contract.rs b/tests/solana_tests/create_contract.rs index 0a6a5dfaf..cf592ce67 100644 --- a/tests/solana_tests/create_contract.rs +++ b/tests/solana_tests/create_contract.rs @@ -11,14 +11,12 @@ fn simple_create_contract_no_seed() { let mut vm = build_solidity( r#" contract bar0 { - function test_other() external returns (bar1) { - bar1 x = new bar1("yo from bar0"); - - return x; + function test_other() external { + bar1.new("yo from bar0"); } - function call_bar1_at_address(bar1 a, string x) public { - a.say_hello(x); + function call_bar1_at_address(string x) public { + bar1.say_hello(x); } } @@ -63,8 +61,7 @@ fn simple_create_contract_no_seed() { }, ); - let bar1 = vm - .function("test_other") + vm.function("test_other") .accounts(vec![ ("bar1_dataAccount", acc), ("bar1_programId", program_id), @@ -76,8 +73,7 @@ fn simple_create_contract_no_seed() { is_writable: true, is_signer: true, }]) - .call() - .unwrap(); + .call(); assert_eq!(vm.logs, "bar1 says: yo from bar0"); @@ -86,7 +82,7 @@ fn simple_create_contract_no_seed() { vm.logs.truncate(0); vm.function("call_bar1_at_address") - .arguments(&[bar1, BorshToken::String(String::from("xywoleh"))]) + .arguments(&[BorshToken::String(String::from("xywoleh"))]) .accounts(vec![ ("bar1_programId", program_id), ("systemProgram", [0; 32]), @@ -101,14 +97,12 @@ fn simple_create_contract() { let mut vm = build_solidity( r#" contract bar0 { - function test_other() external returns (bar1) { - bar1 x = new bar1("yo from bar0"); - - return x; + function test_other() external { + bar1.new("yo from bar0"); } - function call_bar1_at_address(bar1 a, string x) public { - a.say_hello(x); + function call_bar1_at_address(string x) public { + bar1.say_hello(x); } } @@ -142,25 +136,21 @@ fn simple_create_contract() { vm.account_data.insert(payer, AccountState::default()); - let bar1 = vm - .function("test_other") + vm.function("test_other") .accounts(vec![ ("bar1_dataAccount", seed.0), ("bar1_programId", program_id), ("pay", payer), ("systemProgram", [0; 32]), ]) - .call() - .unwrap(); + .call(); assert_eq!(vm.logs, "bar1 says: yo from bar0"); vm.logs.truncate(0); - println!("next test, {bar1:?}"); - vm.function("call_bar1_at_address") - .arguments(&[bar1, BorshToken::String(String::from("xywoleh"))]) + .arguments(&[BorshToken::String(String::from("xywoleh"))]) .accounts(vec![ ("bar1_programId", program_id), ("systemProgram", [0; 32]), @@ -280,14 +270,12 @@ fn missing_contract() { let mut vm = build_solidity( r#" contract bar0 { - function test_other() external returns (bar1) { - bar1 x = new bar1("yo from bar0"); - - return x; + function test_other() external { + bar1.new("yo from bar0"); } - function call_bar1_at_address(bar1 a, string x) public { - a.say_hello(x); + function call_bar1_at_address(string x) public { + bar1.say_hello(x); } } @@ -337,7 +325,7 @@ fn two_contracts() { import 'solana'; contract bar0 { - function test_other(address a, address b, address payer) external returns (bar1) { + function test_other(address a, address b, address payer) external { AccountMeta[2] bar1_metas = [ AccountMeta({pubkey: a, is_writable: true, is_signer: true}), AccountMeta({pubkey: payer, is_writable: true, is_signer: true}) @@ -346,10 +334,8 @@ fn two_contracts() { AccountMeta({pubkey: b, is_writable: true, is_signer: true}), AccountMeta({pubkey: payer, is_writable: true, is_signer: true}) ]; - bar1 x = new bar1{accounts: bar1_metas}("yo from bar0"); - bar1 y = new bar1{accounts: bar2_metas}("hi from bar0"); - - return x; + bar1.new{accounts: bar1_metas}("yo from bar0"); + bar1.new{accounts: bar2_metas}("hi from bar0"); } } @@ -382,14 +368,16 @@ fn two_contracts() { vm.account_data.insert(seed2.0, AccountState::default()); vm.account_data.insert(payer, AccountState::default()); - let _bar1 = vm - .function("test_other") + vm.function("test_other") .arguments(&[ BorshToken::Address(seed1.0), BorshToken::Address(seed2.0), BorshToken::Address(payer), ]) - .accounts(vec![("systemProgram", [0; 32])]) + .accounts(vec![ + ("systemProgram", [0; 32]), + ("bar1_programId", program_id), + ]) .remaining_accounts(&[ AccountMeta { pubkey: Pubkey(seed1.0), @@ -401,11 +389,6 @@ fn two_contracts() { is_signer: true, is_writable: true, }, - AccountMeta { - pubkey: Pubkey(program_id), - is_writable: false, - is_signer: false, - }, AccountMeta { pubkey: Pubkey(payer), is_signer: true, @@ -629,12 +612,10 @@ fn create_child() { let mut vm = build_solidity( r#" contract creator { - Child public c; - function create_child() external { print("Going to create child"); - c = new Child(); - c.say_hello(); + Child.new(); + Child.say_hello(); } } @@ -675,7 +656,6 @@ fn create_child() { .accounts(vec![ ("Child_dataAccount", seed.0), ("Child_programId", child_program_id), - ("dataAccount", data_account), ("payer", payer), ("systemProgram", [0; 32]), ]) @@ -699,7 +679,6 @@ fn create_child_with_meta() { import 'solana'; contract creator { - Child public c; function create_child_with_meta(address child, address payer) public { print("Going to create child"); AccountMeta[2] metas = [ @@ -708,8 +687,8 @@ contract creator { // Passing the system account here crashes the VM, even if I add it to vm.account_data // AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false}) ]; - c = new Child{accounts: metas}(); - c.say_hello(); + Child.new{accounts: metas}(); + Child.say_hello(); } } @@ -750,7 +729,6 @@ contract Child { .arguments(&[BorshToken::Address(seed.0), BorshToken::Address(payer)]) .accounts(vec![ ("Child_programId", child_program_id), - ("dataAccount", data_account), ("systemProgram", [0; 32]), ]) .remaining_accounts(&[ diff --git a/tests/solana_tests/mappings.rs b/tests/solana_tests/mappings.rs index d83c282ef..8f6bf5a19 100644 --- a/tests/solana_tests/mappings.rs +++ b/tests/solana_tests/mappings.rs @@ -292,20 +292,18 @@ fn string_mapping() { fn contract_mapping() { let mut vm = build_solidity( r#" - interface I {} + contract foo { + mapping (address => string) public map; - contract foo { - mapping (I => string) public map; - - function set(I index, string s) public { + function set(address index, string s) public { map[index] = s; } - function get(I index) public returns (string) { + function get(address index) public returns (string) { return map[index]; } - function rm(I index) public { + function rm(address index) public { delete map[index]; } }"#, diff --git a/tests/solana_tests/runtime_errors.rs b/tests/solana_tests/runtime_errors.rs index afd106229..3b21e942c 100644 --- a/tests/solana_tests/runtime_errors.rs +++ b/tests/solana_tests/runtime_errors.rs @@ -10,9 +10,6 @@ fn runtime_errors() { contract RuntimeErrors { bytes b = hex"0000_00fa"; uint256[] arr; - child public c; - child public c2; - constructor() {} function print_test(int8 num) public returns (int8) { @@ -149,7 +146,7 @@ contract calle_contract { .must_fail(); assert_eq!( vm.logs, - "runtime_error: math overflow in test.sol:22:20-29,\n" + "runtime_error: math overflow in test.sol:19:20-29,\n" ); vm.logs.clear(); @@ -163,7 +160,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: sesa require condition failed in test.sol:28:27-33,\n" + "runtime_error: sesa require condition failed in test.sol:25:27-33,\n" ); vm.logs.clear(); @@ -175,7 +172,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: storage array index out of bounds in test.sol:48:19-23,\n" + "runtime_error: storage array index out of bounds in test.sol:45:19-23,\n" ); vm.logs.clear(); @@ -187,7 +184,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: storage index out of bounds in test.sol:41:11-12,\n" + "runtime_error: storage index out of bounds in test.sol:38:11-12,\n" ); vm.logs.clear(); @@ -201,7 +198,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: read integer out of bounds in test.sol:74:18-30,\n" + "runtime_error: read integer out of bounds in test.sol:71:18-30,\n" ); vm.logs.clear(); @@ -215,7 +212,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: truncated type overflows in test.sol:79:37-42,\n" + "runtime_error: truncated type overflows in test.sol:76:37-42,\n" ); vm.logs.clear(); @@ -223,7 +220,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: reached invalid instruction in test.sol:90:13-22,\n" + "runtime_error: reached invalid instruction in test.sol:87:13-22,\n" ); vm.logs.clear(); @@ -235,7 +232,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: pop from empty storage array in test.sol:54:9-12,\n" + "runtime_error: pop from empty storage array in test.sol:51:9-12,\n" ); vm.logs.clear(); @@ -250,7 +247,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: data does not fit into buffer in test.sol:69:18-28,\n" + "runtime_error: data does not fit into buffer in test.sol:66:18-28,\n" ); vm.logs.clear(); @@ -265,7 +262,7 @@ contract calle_contract { println!("{}", vm.logs); assert_eq!( vm.logs, - "runtime_error: assert failure in test.sol:34:16-24,\n" + "runtime_error: assert failure in test.sol:31:16-24,\n" ); vm.logs.clear(); @@ -279,7 +276,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: array index out of bounds in test.sol:85:16-21,\n" + "runtime_error: array index out of bounds in test.sol:82:16-21,\n" ); vm.logs.clear(); @@ -294,7 +291,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: integer too large to write in buffer in test.sol:63:18-31,\n" + "runtime_error: integer too large to write in buffer in test.sol:60:18-31,\n" ); vm.logs.clear(); @@ -309,7 +306,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: bytes cast error in test.sol:98:23-40,\n" + "runtime_error: bytes cast error in test.sol:95:23-40,\n" ); vm.logs.clear(); @@ -318,7 +315,7 @@ contract calle_contract { assert_eq!( vm.logs, - "runtime_error: unspecified revert encountered in test.sol:58:9-17,\n" + "runtime_error: unspecified revert encountered in test.sol:55:9-17,\n" ); vm.logs.clear(); @@ -326,7 +323,7 @@ contract calle_contract { _res = vm.function("revert_with_message").must_fail(); assert_eq!( vm.logs, - "runtime_error: I reverted! revert encountered in test.sol:103:9-30,\n" + "runtime_error: I reverted! revert encountered in test.sol:100:9-30,\n" ); assert!(vm.return_data.is_none()); } diff --git a/tests/solana_tests/using.rs b/tests/solana_tests/using.rs index 7cd651406..cf93da594 100644 --- a/tests/solana_tests/using.rs +++ b/tests/solana_tests/using.rs @@ -1,98 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 +use crate::borsh_encoding::BorshToken; use crate::build_solidity; - -#[test] -fn using_for_contracts() { - let mut runtime = build_solidity( - r#" - interface I { - function f(int) external; - } - - library L { - function F(I i, bool b, int n) public { - if (b) { - print("Hello"); - } - } - } - - contract C { - using L for I; - - function test() public { - I i = I(address(0)); - - i.F(true, 102); - } - }"#, - ); - - let data_account = runtime.initialize_data_account(); - runtime - .function("new") - .accounts(vec![("dataAccount", data_account)]) - .call(); - runtime.function("test").call(); - - assert_eq!(runtime.logs, "Hello"); - - let mut runtime = build_solidity( - r#" - interface I { - function f1(int) external; - function X(int) external; - } - - library L { - function f1_2(I i) external { - i.f1(2); - } - - function X(I i) external { - print("X lib"); - } - } - - contract foo is I { - using L for I; - - function test() public { - I i = I(address(this)); - - i.X(); - i.X(2); - i.f1_2(); - } - - function f1(int x) public { - print("x:{}".format(x)); - } - - function X(int) public { - print("X contract"); - } - }"#, - ); - - let data_account = runtime.initialize_data_account(); - runtime - .function("new") - .accounts(vec![("dataAccount", data_account)]) - .call(); - - let program_id = runtime.stack[0].id; - runtime - .function("test") - .accounts(vec![ - ("I_programId", program_id), - ("systemProgram", [0; 32]), - ]) - .call(); - - assert_eq!(runtime.logs, "X libX contractx:2"); -} +use num_bigint::BigInt; #[test] fn user_defined_oper() { @@ -228,3 +138,120 @@ fn user_defined_oper() { runtime.function("test_arith").call(); runtime.function("test_bit").call(); } + +#[test] +fn using_for_struct() { + let mut vm = build_solidity( + r#" +struct Pet { + string name; + uint8 age; +} + +library Info { + function isCat(Pet memory myPet) public pure returns (bool) { + return myPet.name == "cat"; + } + + function setAge(Pet memory myPet, uint8 age) pure public { + myPet.age = age; + } +} + +contract C { + using Info for Pet; + + function testPet(string memory name, uint8 age) pure public returns (bool) { + Pet memory my_pet = Pet(name, age); + return my_pet.isCat(); + } + + function changeAge(Pet memory myPet) public pure returns (Pet memory) { + myPet.setAge(5); + return myPet; + } + +} + "#, + ); + + let data_account = vm.initialize_data_account(); + + vm.function("new") + .accounts(vec![("dataAccount", data_account)]) + .call(); + + let res = vm + .function("testPet") + .arguments(&[ + BorshToken::String("cat".to_string()), + BorshToken::Uint { + width: 8, + value: BigInt::from(2u8), + }, + ]) + .call() + .unwrap(); + + assert_eq!(res, BorshToken::Bool(true)); + + let res = vm + .function("changeAge") + .arguments(&[BorshToken::Tuple(vec![ + BorshToken::String("cat".to_string()), + BorshToken::Uint { + width: 8, + value: BigInt::from(2u8), + }, + ])]) + .call() + .unwrap(); + + assert_eq!( + res, + BorshToken::Tuple(vec![ + BorshToken::String("cat".to_string()), + BorshToken::Uint { + width: 8, + value: BigInt::from(5u8), + } + ]) + ); +} + +#[test] +fn using_overload() { + let mut vm = build_solidity( + r#" + library MyBytes { + function push(bytes memory b, uint8[] memory a) pure public returns (bool) { + return b[0] == bytes1(a[0]) && b[1] == bytes1(a[1]); + } +} + +contract C { + using MyBytes for bytes; + + function check() public pure returns (bool) { + bytes memory b; + b.push(1); + b.push(2); + uint8[] memory vec = new uint8[](2); + vec[0] = 1; + vec[1] = 2; + return b.push(vec); + } +} + + "#, + ); + + let data_account = vm.initialize_data_account(); + vm.function("new") + .accounts(vec![("dataAccount", data_account)]) + .call(); + + let res = vm.function("check").call().unwrap(); + + assert_eq!(res, BorshToken::Bool(true)); +}