diff --git a/docs/examples/solana/account_access.sol b/docs/examples/solana/account_access.sol new file mode 100644 index 000000000..28d2dbdeb --- /dev/null +++ b/docs/examples/solana/account_access.sol @@ -0,0 +1,18 @@ + +contract Foo { + @account(oneAccount) + @signer(mySigner) + @mutableAccount(otherAccount) + @mutableSigner(otherSigner) + function bar() external returns (uint64) { + assert(tx.accounts.mySigner.is_signer); + assert(tx.accounts.otherSigner.is_signer); + assert(tx.accounts.otherSigner.is_writable); + assert(tx.accounts.otherAccount.is_writable); + + tx.accounts.otherAccount.data[0] = 0xca; + tx.accounts.otherSigner.data[1] = 0xfe; + + return tx.accounts.oneAccount.lamports; + } +} \ No newline at end of file diff --git a/docs/examples/solana/create_contract_with_metas.sol b/docs/examples/solana/create_contract_with_metas.sol index 55fd859f9..e729771c7 100644 --- a/docs/examples/solana/create_contract_with_metas.sol +++ b/docs/examples/solana/create_contract_with_metas.sol @@ -1,14 +1,17 @@ import 'solana'; contract creator { - function create_with_metas(address data_account_to_initialize, address payer) public { + + @mutableSigner(data_account_to_initialize) + @mutableSigner(payer) + function create_with_metas() external { AccountMeta[3] metas = [ AccountMeta({ - pubkey: data_account_to_initialize, + pubkey: tx.accounts.data_account_to_initialize.key, is_signer: true, is_writable: true}), AccountMeta({ - pubkey: payer, + pubkey: tx.accounts.payer.key, is_signer: true, is_writable: true}), AccountMeta({ @@ -17,16 +20,16 @@ contract creator { is_signer: false}) ]; - Child.new{accounts: metas}(payer); + Child.new{accounts: metas}(); - Child.use_metas(); + Child.use_metas{accounts: []}(); } } @program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT") contract Child { @payer(payer) - constructor(address payer) { + constructor() { print("In child constructor"); } diff --git a/docs/examples/solana/expression_this_external_call.sol b/docs/examples/solana/expression_this_external_call.sol index 82ffbe40f..78ab7f138 100644 --- a/docs/examples/solana/expression_this_external_call.sol +++ b/docs/examples/solana/expression_this_external_call.sol @@ -1,7 +1,7 @@ @program_id("H3AthiA2C1pcMahg17nEwqr9628gkXUnnzWJJ3iSDekL") contract kadowari { function nomi() public { - this.nokogiri(102); + this.nokogiri{accounts: []}(102); } function nokogiri(int256 a) public { diff --git a/docs/examples/solana/function_call.sol b/docs/examples/solana/function_call.sol index 3acc5cf7c..0e93d17f7 100644 --- a/docs/examples/solana/function_call.sol +++ b/docs/examples/solana/function_call.sol @@ -10,7 +10,7 @@ contract A { uint32(5) ); - (bool success, bytes rawresult) = v.call(data); + (bool success, bytes rawresult) = v.call{accounts: []}(data); assert(success == true); diff --git a/docs/examples/solana/function_call_external.sol b/docs/examples/solana/function_call_external.sol index b355ff720..fb82b8d22 100644 --- a/docs/examples/solana/function_call_external.sol +++ b/docs/examples/solana/function_call_external.sol @@ -10,7 +10,7 @@ contract foo { 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}); + (address f1, bytes32 f2) = foo.bar1{program_id: f, accounts: []}(102, false); + bool f3 = foo.bar2{program_id: f, accounts: []}({x: 255, y: true}); } } diff --git a/docs/examples/solana/function_type_callback.sol b/docs/examples/solana/function_type_callback.sol index befae9c17..f42fd7a4a 100644 --- a/docs/examples/solana/function_type_callback.sol +++ b/docs/examples/solana/function_type_callback.sol @@ -1,5 +1,5 @@ contract ft { - function test(address p) public { + function test(address p) external { // this.callback can be used as an external function type value paffling.set_callback{program_id: p}(this.callback); } @@ -19,6 +19,6 @@ contract paffling { } function piffle() public { - callback(1, "paffled"); + callback{accounts: []}(1, "paffled"); } } diff --git a/docs/examples/solana/payer_annotation.sol b/docs/examples/solana/payer_annotation.sol index 74c3a09c2..4b0abf191 100644 --- a/docs/examples/solana/payer_annotation.sol +++ b/docs/examples/solana/payer_annotation.sol @@ -30,6 +30,10 @@ contract Builder { }) ]; BeingBuilt.new{accounts: metas}("my_seed"); + + + // No accounts are needed in this call, so we pass an empty vector. + BeingBuilt.say_this{accounts: []}("It's summertime!"); } } diff --git a/docs/examples/solana/program_id.sol b/docs/examples/solana/program_id.sol index 4a9a2a513..cf15b88a7 100644 --- a/docs/examples/solana/program_id.sol +++ b/docs/examples/solana/program_id.sol @@ -17,7 +17,7 @@ contract Bar { } function call_foo() public { - Foo.say_hello(); + Foo.say_hello{accounts: []}(); } function foo_at_another_address(address other_foo_id) external { diff --git a/docs/examples/solana/use_authority.sol b/docs/examples/solana/use_authority.sol index 94e8edbda..8e3ce6eb3 100644 --- a/docs/examples/solana/use_authority.sol +++ b/docs/examples/solana/use_authority.sol @@ -4,29 +4,19 @@ contract AuthorityExample { address authority; uint64 counter; - modifier needs_authority() { - for (uint64 i = 0; i < tx.accounts.length; i++) { - AccountInfo ai = tx.accounts[i]; - - if (ai.key == authority && ai.is_signer) { - _; - return; - } - } - - print("not signed by authority"); - revert(); - } - constructor(address initial_authority) { authority = initial_authority; } - function set_new_authority(address new_authority) needs_authority public { + @signer(authorityAccount) + function set_new_authority(address new_authority) external { + assert(tx.accounts.authorityAccount.key == authority && tx.accounts.authorityAccount.is_signer); authority = new_authority; } - function inc() needs_authority public { + @signer(authorityAccount) + function inc() external { + assert(tx.accounts.authorityAccount.key == authority && tx.accounts.authorityAccount.is_signer); counter += 1; } diff --git a/docs/language/functions.rst b/docs/language/functions.rst index 07123ef7f..92fc570d7 100644 --- a/docs/language/functions.rst +++ b/docs/language/functions.rst @@ -325,6 +325,16 @@ calling. (bool success, bytes rawresult) = foo.call{value: 102, gas: 1000}(rawcalldata); } +External calls with the ``call()`` method on Solana must have the ``accounts`` call argument, regardless of the +callee function visibility, because the compiler has no information about the caller function to generate the +``AccountMeta`` array automatically. + +.. code-block:: solidity + + function test(address foo, bytes rawcalldata) public { + (bool success, bytes rawresult) = foo.call{accounts: []}(rawcalldata); + } + .. _fallback_receive: Calling an external function using ``delegatecall`` diff --git a/docs/language/types.rst b/docs/language/types.rst index 0e46b67ea..06a959170 100644 --- a/docs/language/types.rst +++ b/docs/language/types.rst @@ -507,6 +507,16 @@ a function on particular contract instance. :code: solidity +On Solana, external calls from variables of type external functions require the ``accounts`` call argument. The +compiler cannot determine the accounts such a function needs, so it does not automatically generate the +``AccountsMeta`` array. + +.. code-block:: solidity + + function test(function(int32, string) external myFunc) public { + myFunc{accounts: []}(24, "accounts"); + } + Storage References __________________ diff --git a/docs/targets/solana.rst b/docs/targets/solana.rst index 49564cf36..87f079bfb 100644 --- a/docs/targets/solana.rst +++ b/docs/targets/solana.rst @@ -47,10 +47,11 @@ Runtime - 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 invoking a contract's constructor, one - :ref:`needs to provide ` the necessary accounts for the transaction. + :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. +- Accounts can be declared on functions using :ref:`annotations `. Compute budget @@ -399,13 +400,36 @@ an argument. This annotation defines a Solana account that is going to pay for t account. The syntax ``@payer(my_account)`` declares an account named ``my_account``, which will be required for every call to the constructor. +Similarly, for external functions in a contract, one can declare the necessary accounts, using function annotations. +``@account(myAcc)`` declares a read only account ``myAcc``, while ``@mutableAccount(otherAcc)`` declares a mutable +account ``otherAcc``. For signer accounts, the annotations follow the syntax ``@signer(mySigner)`` and +``@mutableSigner(myOtherSigner)``. + + +Accessing accounts' data +++++++++++++++++++++++++ + +Accounts declared on a constructor using the ``@payer`` annotation are available for access inside it. Likewise, +accounts declared on external functions with any of the aforementioned annotations are also available in the +``tx.accounts`` vector for easy access. For an account declared as ``@account(funder)``, the access follows the +syntax ``tx.accounts.funder``, which returns the :ref:`AccountInfo builtin struct `. + +.. include:: ../examples/solana/account_access.sol + :code: solidity + +.. _solana_cpi_accounts: + +External calls with accounts +++++++++++++++++++++++++++++ + In any Solana cross program invocation, including constructor calls, all the accounts a transaction needs must be -informed. Whenever possible, the compiler will automatically generate the ``AccountMeta`` array that satisfies -this requirement. Currently, that only works if the constructor call is done in an function declared external, as shown -in the example below. In any other case, the ``AccountMeta`` array must be manually created, following an account ordering -the IDL file specifies. +informed. If the ``{accounts: ...}`` call argument is missing from an external call, the compiler will automatically +generate the ``AccountMeta`` array that satisfies such a requirement. Currently, that only works if the call is done +in an function declared external, as shown in the example below. In any other case, the ``AccountMeta`` array must +be manually created, following the account ordering the IDL file specifies. If a certain call does not need any accounts, +an empty vector must be passed ``{accounts: []}``. -The following example shows two correct ways of calling a constructor. Note that the IDL for the ``BeingBuilt`` contract +The following example shows two correct ways of calling a contract. Note that the IDL for the ``BeingBuilt`` contract has an instruction called ``new``, representing the contract's constructor, whose accounts are specified in the following order: ``dataAccount``, ``payer_account``, ``systemAccount``. That is the order one must follow when invoking such a constructor. @@ -413,10 +437,3 @@ such a constructor. .. include:: ../examples/solana/payer_annotation.sol :code: solidity - -Accessing accounts' data -________________________ - -Accounts declared on a constructor using the ``@payer`` annotation are available for access inside it. -For an account declared as ``@payer(funder)``, the access follows the syntax ``tx.accounts.funder``, which returns -the :ref:`AccountInfo builtin struct `. \ No newline at end of file diff --git a/integration/solana/account_data.sol b/integration/solana/account_data.sol index 662622fbf..09bd03ba9 100644 --- a/integration/solana/account_data.sol +++ b/integration/solana/account_data.sol @@ -1,11 +1,13 @@ import '../../solana-library/spl_token.sol'; contract AccountData { - function token_account(address addr) view public returns (SplToken.TokenAccountData) { - return SplToken.get_token_account_data(addr); + @account(addr) + function token_account() view external returns (SplToken.TokenAccountData) { + return SplToken.get_token_account_data(tx.accounts.addr); } - function mint_account(address addr) view public returns (SplToken.MintAccountData) { - return SplToken.get_mint_account_data(addr); + @account(addr) + function mint_account() view external returns (SplToken.MintAccountData) { + return SplToken.get_mint_account_data(tx.accounts.addr); } } \ No newline at end of file diff --git a/integration/solana/account_data.spec.ts b/integration/solana/account_data.spec.ts index 113b77a84..e562ed915 100644 --- a/integration/solana/account_data.spec.ts +++ b/integration/solana/account_data.spec.ts @@ -48,13 +48,8 @@ describe('Deserialize account data', function () { owner.publicKey ); - let res = await program.methods.tokenAccount(token_account.address) - .accounts({dataAccount: storage.publicKey}) - .remainingAccounts( - [ - {pubkey: token_account.address, isSigner: false, isWritable: false} - ] - ) + let res = await program.methods.tokenAccount() + .accounts({addr: token_account.address}) .view(); expect(res.mintAccount).toEqual(token_account.mint); @@ -86,13 +81,8 @@ describe('Deserialize account data', function () { ); token_account = await getAccount(connection, token_account.address); - res = await program.methods.tokenAccount(token_account.address) - .accounts({dataAccount: storage.publicKey}) - .remainingAccounts( - [ - {pubkey: token_account.address, isSigner: false, isWritable: false} - ] - ) + res = await program.methods.tokenAccount() + .accounts({addr: token_account.address}) .view(); // The delegate account should be present now @@ -112,13 +102,8 @@ describe('Deserialize account data', function () { ); token_account = await getAccount(connection, token_account.address); - res = await program.methods.tokenAccount(token_account.address) - .accounts({dataAccount: storage.publicKey}) - .remainingAccounts( - [ - {pubkey: token_account.address, isSigner: false, isWritable: false} - ] - ) + res = await program.methods.tokenAccount() + .accounts({addr: token_account.address}) .view(); // The close authority should be present @@ -134,13 +119,8 @@ describe('Deserialize account data', function () { owner.publicKey ); - res = await program.methods.tokenAccount(token_account.address) - .accounts({dataAccount: storage.publicKey}) - .remainingAccounts( - [ - {pubkey: token_account.address, isSigner: false, isWritable: false} - ] - ) + res = await program.methods.tokenAccount() + .accounts({addr: token_account.address}) .view(); // Is native must be present @@ -182,13 +162,8 @@ describe('Deserialize account data', function () { let mint_data = await getMint(connection, mint); - let res = await program.methods.mintAccount(mint) - .accounts({dataAccount: storage.publicKey}) - .remainingAccounts( - [ - {pubkey: mint, isWritable: false, isSigner: false} - ] - ) + let res = await program.methods.mintAccount() + .accounts({addr: mint}) .view(); // Authorities are present @@ -222,13 +197,8 @@ describe('Deserialize account data', function () { mint_data = await getMint(connection, mint); - res = await program.methods.mintAccount(mint) - .accounts({dataAccount: storage.publicKey}) - .remainingAccounts( - [ - {pubkey: mint, isWritable: false, isSigner: false} - ] - ) + res = await program.methods.mintAccount() + .accounts({addr: mint}) .view(); // Authorities are not present diff --git a/integration/solana/calls.spec.ts b/integration/solana/calls.spec.ts index 9bafcdcc7..f9646bf59 100644 --- a/integration/solana/calls.spec.ts +++ b/integration/solana/calls.spec.ts @@ -18,57 +18,58 @@ describe('Testing calls', function () { await callee.program.methods.setX(new BN(102)) .accounts({ dataAccount: callee.storage.publicKey }) - .rpc(); + .rpc({commitment: "confirmed"}); let res = await callee.program.methods.getX() .accounts({ dataAccount: callee.storage.publicKey }) - .view(); + .view({commitment: "confirmed"}); expect(res).toEqual(new BN(102)); res = await caller.program.methods.whoAmI() - .view(); + .view({commitment: "confirmed"}); expect(res).toStrictEqual(caller.program_key); - await caller.program.methods.doCall(callee.program_key, new BN(13123)) + await caller.program.methods.doCall(new BN(13123)) .accounts({ callee_dataAccount: callee.storage.publicKey, - callee_programId: callee.program_key, + callee_pid: callee.program_key, }) - .rpc(); + .rpc({commitment: "confirmed"}); res = await callee.program.methods.getX() .accounts({ dataAccount: callee.storage.publicKey }) - .view(); + .view({commitment: "confirmed"}); expect(res).toEqual(new BN(13123)); - res = await caller.program.methods.doCall2(callee.program_key, new BN(20000)) + res = await caller.program.methods.doCall2(new BN(20000)) .accounts({ callee_dataAccount: callee.storage.publicKey, - callee_programId: callee.program_key, + callee_pid: callee.program_key, }) - .view(); + .view({commitment: "confirmed"}); expect(res).toEqual(new BN(33123)); - res = await caller.program.methods.doCall3(callee.program_key, callee2.program_key, [new BN(3), new BN(5), new BN(7), new BN(9)], "yo") + res = await caller.program.methods.doCall3([new BN(3), new BN(5), new BN(7), new BN(9)], "yo") .accounts({ - callee2_programId: callee2.program_key, - callee_programId: callee.program_key, + callee2_pid: callee2.program_key, + callee_pid: callee.program_key, }) - .view(); + .view({commitment: "confirmed"}); expect(res.return0).toEqual(new BN(24)); expect(res.return1).toBe("my name is callee"); - res = await caller.program.methods.doCall4(callee.program_key, callee2.program_key, [new BN(1), new BN(2), new BN(3), new BN(4)], "asda") + res = await caller.program.methods.doCall4([new BN(1), new BN(2), new BN(3), new BN(4)], "asda") .accounts({ - callee2_programId: callee2.program_key, - callee_programId: callee.program_key, + callee2_pid: callee2.program_key, + callee_pid: callee.program_key, + other_callee2: callee2.program_key, }) - .view(); + .view({commitment: "confirmed"}); expect(res.return0).toEqual(new BN(10)); expect(res.return1).toBe("x:asda"); diff --git a/integration/solana/create_contract.sol b/integration/solana/create_contract.sol index 7c904fd64..37d1a6a85 100644 --- a/integration/solana/create_contract.sol +++ b/integration/solana/create_contract.sol @@ -22,11 +22,13 @@ contract creator { Seed2.new(seed, space); } - function create_child_with_metas(address child, address payer) public { + @mutableSigner(child) + @mutableSigner(payer) + function create_child_with_metas() external { print("Going to create child with metas"); AccountMeta[3] metas = [ - AccountMeta({pubkey: child, is_signer: true, is_writable: true}), - AccountMeta({pubkey: payer, is_signer: true, is_writable: true}), + AccountMeta({pubkey: tx.accounts.child.key, is_signer: true, is_writable: true}), + AccountMeta({pubkey: tx.accounts.payer.key, is_signer: true, is_writable: true}), AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false}) ]; @@ -36,19 +38,14 @@ contract creator { function create_without_annotation() external { MyCreature.new(); - MyCreature.say_my_name(); + MyCreature.say_my_name{accounts: []}(); } - function call_with_signer(address signer) view public { - for(uint32 i=0; i < tx.accounts.length; i++) { - if (tx.accounts[i].key == signer) { - require(tx.accounts[i].is_signer, "the signer must sign the transaction"); - print("Signer found"); - return; - } - } - - revert("The signer account is missing"); } + @signer(my_signer) + function call_with_signer() view external { + require(tx.accounts.my_signer.is_signer, "the signer must sign the transaction"); + print("Signer found"); + } } @program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT") @@ -86,16 +83,15 @@ contract Seed1 { print("Hello from Seed1"); } - function sign(address creator_program_id) view public { + @account(creator_program_id) + function sign() view external { AccountMeta[1] metas = [ AccountMeta({pubkey: tx.accounts.dataAccount.key, is_signer: true, is_writable: false}) ]; - creator.call_with_signer{ - seeds: [ [ saved_seed, saved_bump ] ], - program_id: creator_program_id, - accounts: metas - }(tx.accounts.dataAccount.key); + creator.call_with_signer{seeds: [ [ saved_seed, saved_bump ] ], + accounts: metas, + program_id: tx.accounts.creator_program_id.key}(); } } @@ -119,10 +115,11 @@ contract Seed2 { } } - function sign(address creator_program_id) view public { + @account(creator_program_id) + function sign() view external { bytes[2][1] seeds = [ [ "sunflower", my_seed ] ]; - sign2(seeds, tx.accounts.dataAccount.key, creator_program_id); + sign2(seeds, tx.accounts.dataAccount.key, tx.accounts.creator_program_id.key); } function sign2(bytes[2][1] seeds, address child, address creator_program_id) view internal { @@ -130,7 +127,7 @@ contract Seed2 { AccountMeta({pubkey: child, is_signer: true, is_writable: false}) ]; - creator.call_with_signer{seeds: seeds, accounts: metas, program_id: creator_program_id}(child); + creator.call_with_signer{seeds: seeds, accounts: metas, program_id: creator_program_id}(); } } diff --git a/integration/solana/create_contract.spec.ts b/integration/solana/create_contract.spec.ts index 0aceacd4c..66462fcf7 100644 --- a/integration/solana/create_contract.spec.ts +++ b/integration/solana/create_contract.spec.ts @@ -74,8 +74,8 @@ describe('ChildContract', function () { const seed1 = new Program(idl, seed_program, provider); - let res = await seed1.methods.sign(program_key) - .accounts({ dataAccount: address, creator_programId: program_key }) + const res = await seed1.methods.sign() + .accounts({ dataAccount: address, creator_program_id: program_key }) .simulate(); expect(res.raw.toString()).toContain('Signer found'); @@ -119,8 +119,8 @@ describe('ChildContract', function () { expect(res.raw.toString()).toContain('I am PDA.'); - res = await seed2.methods.sign(program_key) - .accounts({ dataAccount: address, creator_programId: program_key }) + res = await seed2.methods.sign() + .accounts({ dataAccount: address, creator_program_id: program_key }) .simulate(); expect(res.raw.toString()).toContain('Signer found'); @@ -130,15 +130,12 @@ describe('ChildContract', function () { let child = Keypair.generate(); let child_program = new PublicKey("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT"); - const signature = await program.methods.createChildWithMetas(child.publicKey, payer.publicKey) + const signature = await program.methods.createChildWithMetas() .accounts({ - dataAccount: storage.publicKey, + child: child.publicKey, + payer: payer.publicKey, Child_programId: child_program, }) - .remainingAccounts([ - { pubkey: child.publicKey, isSigner: true, isWritable: true }, - { pubkey: payer.publicKey, isSigner: true, isWritable: true }, - ]) .signers([payer, child]) .rpc({ commitment: 'confirmed' }); diff --git a/integration/solana/external_call.sol b/integration/solana/external_call.sol index c95f812dd..b026f7082 100644 --- a/integration/solana/external_call.sol +++ b/integration/solana/external_call.sol @@ -1,21 +1,30 @@ contract caller { - function do_call(address e, int64 v) public { - callee.set_x{program_id: e}(v); + + @account(callee_pid) + function do_call(int64 v) external { + callee.set_x{program_id: tx.accounts.callee_pid.key}(v); } - function do_call2(address e, int64 v) view public returns (int64) { - return v + callee.get_x{program_id: e}(); + @account(callee_pid) + function do_call2(int64 v) view external returns (int64) { + return v + callee.get_x{program_id: tx.accounts.callee_pid.key}(); } // call two different functions - 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}()); + @account(callee_pid) + @account(callee2_pid) + function do_call3(int64[4] memory x, string memory y) external returns (int64, string memory) { + return (callee2.do_stuff{program_id: tx.accounts.callee2_pid.key}(x), + callee.get_name{program_id: tx.accounts.callee_pid.key}()); } // call two different functions - 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)); + @account(callee_pid) + @account(callee2_pid) + function do_call4(int64[4] memory x, string memory y) external returns (int64, string memory) { + return (callee2.do_stuff{program_id: tx.accounts.callee2_pid.key}(x), + callee.call2{program_id: tx.accounts.callee_pid.key}(y)); } function who_am_i() public view returns (address) { @@ -34,8 +43,9 @@ contract callee { return x; } - function call2(address e2, string s) public pure returns (string) { - return callee2.do_stuff2{program_id: e2}(s); + @account(other_callee2) + function call2(string s) external returns (string) { + return callee2.do_stuff2{program_id: tx.accounts.other_callee2.key}(s); } function get_name() public pure returns (string) { diff --git a/integration/solana/runtime_errors.sol b/integration/solana/runtime_errors.sol index cb76206a7..2df1f70ee 100644 --- a/integration/solana/runtime_errors.sol +++ b/integration/solana/runtime_errors.sol @@ -62,7 +62,7 @@ contract RuntimeErrors { // external call failed function call_ext() public { - Creature.say_my_name(); + Creature.say_my_name{accounts: []}(); } function i_will_revert() public { diff --git a/integration/solana/simple_collectible.sol b/integration/solana/simple_collectible.sol index e300a992a..9f3663680 100644 --- a/integration/solana/simple_collectible.sol +++ b/integration/solana/simple_collectible.sol @@ -23,29 +23,24 @@ contract SimpleCollectible { metadataAuthority = _metadataAuthority; } - /// Create a new NFT and associate it to an URI - /// - /// @param tokenURI a URI that leads to the NFT resource - /// @param mintAuthority an account that signs each new mint - /// @param ownerTokenAccount the owner associated token account - function createCollectible(string memory tokenURI, address mintAuthority, address ownerTokenAccount) public { - SplToken.TokenAccountData token_data = SplToken.get_token_account_data(ownerTokenAccount); - - // The mint will only work if the associated token account points to the mint account in this contract - // This assert is not necessary. The transaction will fail if this does not hold true. - assert(mintAccount == token_data.mintAccount); - SplToken.MintAccountData mint_data = SplToken.get_mint_account_data(token_data.mintAccount); + /// Create a new NFT + @mutableAccount(mintAccount) // The account of the mint. Its address must be the same as that of the 'mintAccount' contract variable. + @mutableAccount(ownerTokenAccount) // The owner's associated token account + @signer(mintAuthority) // The account that signs each new mint + function createCollectible() external { + SplToken.TokenAccountData token_data = SplToken.get_token_account_data(tx.accounts.ownerTokenAccount); + + SplToken.MintAccountData mint_data = SplToken.get_mint_account_data(tx.accounts.mintAccount); // Ensure the supply is zero. Otherwise, this is not an NFT. assert(mint_data.supply == 0); // An NFT on Solana is a SPL-Token with only one minted token. // The token account saves the owner of the tokens minted with the mint account, the respective mint account and the number // of tokens the owner account owns - SplToken.mint_to(token_data.mintAccount, ownerTokenAccount, mintAuthority, 1); - updateNftUri(tokenURI); + SplToken.mint_to(tx.accounts.mintAccount.key, tx.accounts.ownerTokenAccount.key, tx.accounts.mintAuthority.key, 1); // Set the mint authority to null. This prevents that any other new tokens be minted, ensuring we have an NFT. - SplToken.remove_mint_authority(mintAccount, mintAuthority); + SplToken.remove_mint_authority(tx.accounts.mintAccount.key, tx.accounts.mintAuthority.key); // Log on blockchain records information about the created token emit NFTMinted(token_data.owner, token_data.mintAccount); @@ -54,18 +49,21 @@ contract SimpleCollectible { /// Transfer ownership of this NFT from one account to another /// This function only wraps the innate SPL transfer, which can be used outside this contract. /// However, the difference here is the event 'NFTSold' exclusive to this function - /// - /// @param oldTokenAccount the token account for the current owner - /// @param newTokenAccount the token account for the new owner - function transferOwnership(address oldTokenAccount, address newTokenAccount) public { + @mutableAccount(oldTokenAccount) // The token account for the current owner + @mutableAccount(newTokenAccount) // The token account for the new owner + @signer(oldOwner) + function transferOwnership() external { // The current owner does not need to be the caller of this functions, but they need to sign the transaction // with their private key. - SplToken.TokenAccountData old_data = SplToken.get_token_account_data(oldTokenAccount); - SplToken.TokenAccountData new_data = SplToken.get_token_account_data(newTokenAccount); + SplToken.TokenAccountData old_data = SplToken.get_token_account_data(tx.accounts.oldTokenAccount); + SplToken.TokenAccountData new_data = SplToken.get_token_account_data(tx.accounts.newTokenAccount); // To transfer the ownership of a token, we need the current owner and the new owner. The payer account is the account used to derive // the correspondent token account in TypeScript. - SplToken.transfer(oldTokenAccount, newTokenAccount, old_data.owner, 1); + SplToken.transfer( + tx.accounts.oldTokenAccount.key, + tx.accounts.newTokenAccount.key, + tx.accounts.oldOwner.key, 1); emit NFTSold(old_data.owner, new_data.owner); } @@ -77,9 +75,9 @@ contract SimpleCollectible { /// Check if an NFT is owned by @param owner /// /// @param owner the account whose ownership we want to verify - /// @param tokenAccount the owner's associated token account - function isOwner(address owner, address tokenAccount) public view returns (bool) { - SplToken.TokenAccountData data = SplToken.get_token_account_data(tokenAccount); + @account(tokenAccount) // The owner's associated token account + function isOwner(address owner) external view returns (bool) { + SplToken.TokenAccountData data = SplToken.get_token_account_data(tx.accounts.tokenAccount); return owner == data.owner && mintAccount == data.mintAccount && data.balance == 1; } @@ -88,20 +86,10 @@ contract SimpleCollectible { /// The metadata authority must sign the transaction so that the update can succeed. /// /// @param newUri a new URI for the NFT - function updateNftUri(string newUri) public { - requireMetadataSigner(); + @signer(metadataSigner) // The metadata authority that can authorize changes in the NFT data. + function updateNftUri(string newUri) external { + require(tx.accounts.metadataSigner.is_signer, "the metadata authority must sign the transaction"); + assert(tx.accounts.metadataSigner.key == metadataAuthority); uri = newUri; } - - /// Requires the signature of the metadata authority. - function requireMetadataSigner() private { - for(uint32 i=0; i < tx.accounts.length; i++) { - if (tx.accounts[i].key == metadataAuthority) { - require(tx.accounts[i].is_signer, "the metadata authority must sign the transaction"); - return; - } - } - - revert("The metadata authority is missing"); - } } diff --git a/integration/solana/simple_collectible.spec.ts b/integration/solana/simple_collectible.spec.ts index a9959401c..61b534fe9 100644 --- a/integration/solana/simple_collectible.spec.ts +++ b/integration/solana/simple_collectible.spec.ts @@ -43,18 +43,14 @@ describe('Simple collectible', function () { const nft_uri = "www.nft.com"; // Create a collectible for an owner given a mint authority. - await program.methods.createCollectible( - nft_uri, - mint_authority.publicKey, - owner_token_account.address) - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([ - { pubkey: mint, isSigner: false, isWritable: true }, - { pubkey: owner_token_account.address, isSigner: false, isWritable: true }, - { pubkey: mint_authority.publicKey, isSigner: true, isWritable: false }, - { pubkey: metadata_authority.publicKey, isSigner: true, isWritable: true } - ]) - .signers([mint_authority, metadata_authority]) + await program.methods.createCollectible() + .accounts({ + dataAccount: storage.publicKey, + mintAccount: mint, + ownerTokenAccount: owner_token_account.address, + mintAuthority: mint_authority.publicKey, + }) + .signers([mint_authority]) .rpc(); const new_owner = Keypair.generate(); @@ -69,43 +65,33 @@ describe('Simple collectible', function () { // Transfer ownership to another owner - await program.methods.transferOwnership( - owner_token_account.address, - new_owner_token_account.address) - .remainingAccounts([ - { pubkey: new_owner_token_account.address, isSigner: false, isWritable: true }, - { pubkey: owner_token_account.address, isSigner: false, isWritable: true }, - { pubkey: nft_owner.publicKey, isSigner: true, isWritable: false }, - ]) + await program.methods.transferOwnership() + .accounts({ + oldTokenAccount: owner_token_account.address, + newTokenAccount: new_owner_token_account.address, + oldOwner: nft_owner.publicKey + }) .signers([nft_owner]) .rpc(); // Confirm that the ownership transference worked const verify_transfer_result = await program.methods.isOwner( - new_owner.publicKey, - new_owner_token_account.address) - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([ - { pubkey: new_owner_token_account.address, isSigner: false, isWritable: false }, - ]) + new_owner.publicKey) + .accounts({ + dataAccount: storage.publicKey, + tokenAccount: new_owner_token_account.address + }) .view(); expect(verify_transfer_result).toBe(true); - // Retrieve information about the NFT - const token_uri = await program.methods.getNftUri() - .accounts({ dataAccount: storage.publicKey }) - .view(); - - expect(token_uri).toBe(nft_uri); - // Update the NFT URI const new_uri = "www.token.com"; await program.methods.updateNftUri(new_uri) - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([ - { pubkey: metadata_authority.publicKey, isSigner: true, isWritable: true }, - ]) + .accounts({ + dataAccount: storage.publicKey, + metadataSigner: metadata_authority.publicKey + }) .signers([metadata_authority]) .rpc(); diff --git a/integration/solana/system_instruction.spec.ts b/integration/solana/system_instruction.spec.ts index 930272f9a..1fd0900e4 100644 --- a/integration/solana/system_instruction.spec.ts +++ b/integration/solana/system_instruction.spec.ts @@ -18,54 +18,55 @@ describe('Test system instructions', function () { const to_key_pair = Keypair.generate(); await program.methods.createAccount( - payer.publicKey, - to_key_pair.publicKey, new BN(100000000), new BN(5), - TOKEN_PROGRAM_ID) + TOKEN_PROGRAM_ID + ).accounts( + { + from: payer.publicKey, + to: to_key_pair.publicKey + } + ) .remainingAccounts([ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - { pubkey: payer.publicKey, isSigner: true, isWritable: false }, - { pubkey: to_key_pair.publicKey, isSigner: true, isWritable: true }, ]) .signers([payer, to_key_pair]).rpc(); }); it('create account with seed', async function create_account_with_seed() { - const { storage, payer, program } = await loadContractAndCallConstructor('TestingInstruction'); + const { payer, program } = await loadContractAndCallConstructor('TestingInstruction'); const base_keypair = Keypair.generate(); const to_key_pair = await PublicKey.createWithSeed(base_keypair.publicKey, seed, TOKEN_PROGRAM_ID); await program.methods.createAccountWithSeed( - payer.publicKey, - to_key_pair, - base_keypair.publicKey, seed, new BN(100000000), new BN(5), TOKEN_PROGRAM_ID) + .accounts({ + from: payer.publicKey, + to: to_key_pair, + base: base_keypair.publicKey + }) .remainingAccounts([ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - { pubkey: payer.publicKey, isSigner: true, isWritable: false }, - { pubkey: to_key_pair, isSigner: false, isWritable: true }, - { pubkey: base_keypair.publicKey, isSigner: true, isWritable: false }, ]) .signers([payer, base_keypair]).rpc(); }); it('assign', async function assign() { - const { storage, payer, program } = await loadContractAndCallConstructor('TestingInstruction'); + const { program } = await loadContractAndCallConstructor('TestingInstruction'); const to_key_pair = Keypair.generate(); const assign_account = new PublicKey('AddressLookupTab1e1111111111111111111111111'); await program.methods.assign( - to_key_pair.publicKey, assign_account) - .remainingAccounts([ - { pubkey: payer.publicKey, isSigner: false, isWritable: false }, - { pubkey: to_key_pair.publicKey, isSigner: true, isWritable: true }, - ]) - .signers([payer, to_key_pair]).rpc(); + .accounts( + { + assignAccount: to_key_pair.publicKey, + } + ) + .signers([to_key_pair]).rpc(); }); it('assign with seed', async function assign_with_with_seed() { @@ -74,15 +75,12 @@ describe('Test system instructions', function () { const to_key_pair = await PublicKey.createWithSeed(payer.publicKey, seed, assign_account); await program.methods.assignWithSeed( - to_key_pair, - payer.publicKey, seed, assign_account) - .remainingAccounts([ - { pubkey: assign_account, isSigner: false, isWritable: false }, - { pubkey: payer.publicKey, isSigner: false, isWritable: false }, - { pubkey: to_key_pair, isSigner: false, isWritable: true }, - ]) + .accounts({ + assignAccount: to_key_pair, + owner: payer.publicKey, + }) .signers([payer]).rpc(); }); @@ -91,18 +89,16 @@ describe('Test system instructions', function () { const dest = new Keypair(); await program.methods.transfer( - payer.publicKey, - dest.publicKey, new BN(100000000)) - .remainingAccounts([ - { pubkey: payer.publicKey, isSigner: false, isWritable: true }, - { pubkey: dest.publicKey, isSigner: false, isWritable: true }, - ]) + .accounts({ + from: payer.publicKey, + to: dest.publicKey, + }) .signers([payer]).rpc(); }); it('transfer with seed', async function transfer_with_seed() { - const { storage, payer, provider, program } = await loadContractAndCallConstructor('TestingInstruction'); + const { payer, provider, program } = await loadContractAndCallConstructor('TestingInstruction'); const dest = new Keypair(); const assign_account = new PublicKey('AddressLookupTab1e1111111111111111111111111'); const derived_payer = await PublicKey.createWithSeed(payer.publicKey, seed, assign_account); @@ -111,32 +107,27 @@ describe('Test system instructions', function () { await provider.connection.confirmTransaction(signature, 'confirmed'); await program.methods.transferWithSeed( - derived_payer, // from_pubkey - payer.publicKey, // from_base seed, // seed assign_account, // from_owner - dest.publicKey, // to_pubkey new BN(100000000)) - .remainingAccounts([ - { pubkey: assign_account, isSigner: false, isWritable: false }, - { pubkey: derived_payer, isSigner: false, isWritable: true }, - { pubkey: dest.publicKey, isSigner: false, isWritable: true }, - { pubkey: payer.publicKey, isSigner: true, isWritable: false }, - ]) - .signers([payer]).rpc(); + .accounts( + { + fromKey: derived_payer, + fromBase: payer.publicKey, + toKey: dest.publicKey, + } + ) + .signers([payer]).rpc({commitment: "confirmed"}); }); it('allocate', async function allocate() { - const { storage, payer, program } = await loadContractAndCallConstructor('TestingInstruction'); + const { program } = await loadContractAndCallConstructor('TestingInstruction'); const account = Keypair.generate(); await program.methods.allocate( - account.publicKey, new BN(2)) - .remainingAccounts([ - { pubkey: account.publicKey, isSigner: true, isWritable: true }, - ]) - .signers([payer, account]).rpc(); + .accounts({accKey: account.publicKey}) + .signers([account]).rpc(); }); it('allocate with seed', async function allocate_with_seed() { @@ -146,17 +137,14 @@ describe('Test system instructions', function () { const derived_key = await PublicKey.createWithSeed(account.publicKey, seed, owner); await program.methods.allocateWithSeed( - derived_key, - account.publicKey, seed, new BN(200), owner) - .remainingAccounts([ - { pubkey: owner, isSigner: false, isWritable: false }, - { pubkey: account.publicKey, isSigner: true, isWritable: false }, - { pubkey: derived_key, isSigner: false, isWritable: true }, - ]) - .signers([payer, account]).rpc(); + .accounts({ + accKey: derived_key, + base: account.publicKey, + }) + .signers([account]).rpc(); }); it('create nonce account with seed', async function create_nonce_account_with_seed() { @@ -166,20 +154,19 @@ describe('Test system instructions', function () { const authority = Keypair.generate(); await program.methods.createNonceAccountWithSeed( - payer.publicKey, - derived_account, - base_address.publicKey, seed, authority.publicKey, new BN(100000000)) + .accounts( + {from: payer.publicKey, + nonce: derived_account, + base: base_address.publicKey} + ) .remainingAccounts([ { pubkey: recent_block_hashes, isSigner: false, isWritable: false }, { pubkey: rentAddress, isSigner: false, isWritable: false }, - { pubkey: payer.publicKey, isSigner: false, isWritable: true }, - { pubkey: derived_account, isSigner: false, isWritable: true }, - { pubkey: base_address.publicKey, isSigner: true, isWritable: true }, ]) - .signers([payer, base_address]).rpc(); + .signers([payer, base_address]).rpc({commitment: "confirmed"}); }); it('nonce accounts', async function nonce_accounts() { @@ -188,52 +175,48 @@ describe('Test system instructions', function () { const authority = Keypair.generate(); await program.methods.createNonceAccount( - payer.publicKey, - nonce.publicKey, authority.publicKey, new BN(100000000)) + .accounts( + { + from: payer.publicKey, + nonce: nonce.publicKey + } + ) .remainingAccounts([ { pubkey: recent_block_hashes, isSigner: false, isWritable: false }, { pubkey: rentAddress, isSigner: false, isWritable: false }, - { pubkey: payer.publicKey, isSigner: false, isWritable: true }, - { pubkey: nonce.publicKey, isSigner: true, isWritable: true }, ]) .signers([payer, nonce]).rpc(); - await program.methods.advanceNonceAccount( - nonce.publicKey, - authority.publicKey) + await program.methods.advanceNonceAccount(authority.publicKey) + .accounts({nonce: nonce.publicKey}) .remainingAccounts([ { pubkey: recent_block_hashes, isSigner: false, isWritable: false }, { pubkey: authority.publicKey, isSigner: true, isWritable: false }, - { pubkey: nonce.publicKey, isSigner: false, isWritable: true }, ]) .signers([authority]).rpc(); await program.methods.withdrawNonceAccount( - nonce.publicKey, - authority.publicKey, - payer.publicKey, new BN(1000)) + .accounts({ + nonce: nonce.publicKey, + to: payer.publicKey, + authority: authority.publicKey + }) .remainingAccounts([ { pubkey: recent_block_hashes, isSigner: false, isWritable: false }, { pubkey: rentAddress, isSigner: false, isWritable: false }, - { pubkey: authority.publicKey, isSigner: true, isWritable: false }, - { pubkey: nonce.publicKey, isSigner: false, isWritable: true }, - { pubkey: payer.publicKey, isSigner: false, isWritable: true }, ]) .signers([authority]).rpc(); const new_authority = Keypair.generate(); await program.methods.authorizeNonceAccount( - nonce.publicKey, - authority.publicKey, new_authority.publicKey) - - .remainingAccounts([ - { pubkey: authority.publicKey, isSigner: true, isWritable: false }, - { pubkey: nonce.publicKey, isSigner: false, isWritable: true }, - ]) + .accounts({ + nonce: nonce.publicKey, + authority: authority.publicKey + }) .signers([authority]).rpc(); }); }); diff --git a/integration/solana/system_instruction_example.sol b/integration/solana/system_instruction_example.sol index c7dcc5b4c..35b9f07e8 100644 --- a/integration/solana/system_instruction_example.sol +++ b/integration/solana/system_instruction_example.sol @@ -3,60 +3,113 @@ import '../../solana-library/system_instruction.sol'; contract TestingInstruction { - function create_account(address from, address to, uint64 lamports, uint64 space, address owner) public { - SystemInstruction.create_account(from, to, lamports, space, owner); + + @mutableSigner(from) + @mutableSigner(to) + function create_account(uint64 lamports, uint64 space, address owner) external { + SystemInstruction.create_account(tx.accounts.from.key, tx.accounts.to.key, lamports, space, owner); } - function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) public { - SystemInstruction.create_account_with_seed(from, to, base, seed, lamports, space, owner); + @mutableSigner(from) + @mutableAccount(to) + @signer(base) + function create_account_with_seed(string seed, uint64 lamports, uint64 space, address owner) external { + SystemInstruction.create_account_with_seed( + tx.accounts.from.key, tx.accounts.to.key, tx.accounts.base.key, seed, lamports, space, owner); } - function assign(address account, address owner) public { - SystemInstruction.assign(account, owner); + @mutableSigner(assignAccount) + function assign(address owner) external { + SystemInstruction.assign(tx.accounts.assignAccount.key, owner); } - function assign_with_seed(address account, address base, string seed, address owner) public { - SystemInstruction.assign_with_seed(account, base, seed, owner); + @mutableAccount(assignAccount) + @signer(base) + function assign_with_seed(string seed, address owner) external { + SystemInstruction.assign_with_seed(tx.accounts.assignAccount.key, tx.accounts.base.key, seed, owner); } - function transfer(address from, address to, uint64 lamports) public { - SystemInstruction.transfer(from, to, lamports); + @mutableSigner(from) + @mutableAccount(to) + function transfer(uint64 lamports) external { + SystemInstruction.transfer(tx.accounts.from.key, tx.accounts.to.key, lamports); } - function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) public { - SystemInstruction.transfer_with_seed(from_pubkey, from_base, seed, from_owner, to_pubkey, lamports); + @mutableAccount(fromKey) + @signer(fromBase) + @mutableAccount(toKey) + function transfer_with_seed(string seed, address from_owner, uint64 lamports) external { + SystemInstruction.transfer_with_seed( + tx.accounts.fromKey.key, + tx.accounts.fromBase.key, + seed, + from_owner, + tx.accounts.toKey.key, + lamports); } - function allocate(address pub_key, uint64 space) public { - SystemInstruction.allocate(pub_key, space); + @mutableSigner(accKey) + function allocate(uint64 space) external { + SystemInstruction.allocate(tx.accounts.accKey.key, space); } - function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) public { - SystemInstruction.allocate_with_seed(addr, base, seed, space, owner); + @mutableAccount(accKey) + @signer(base) + function allocate_with_seed(string seed, uint64 space, address owner) external { + SystemInstruction.allocate_with_seed( + tx.accounts.accKey.key, + tx.accounts.base.key, + seed, + space, + owner); } - function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) public { - SystemInstruction.create_nonce_account_with_seed(from, nonce, base, seed, authority, lamports); + @mutableSigner(from) + @mutableAccount(nonce) + @signer(base) + function create_nonce_account_with_seed(string seed, address authority, uint64 lamports) external { + SystemInstruction.create_nonce_account_with_seed( + tx.accounts.from.key, + tx.accounts.nonce.key, + tx.accounts.base.key, + seed, + authority, + lamports); } - function create_nonce_account(address from, address nonce, address authority, uint64 lamports) public { - SystemInstruction.create_nonce_account(from, nonce, authority, lamports); + @mutableSigner(from) + @mutableSigner(nonce) + function create_nonce_account(address authority, uint64 lamports) external { + SystemInstruction.create_nonce_account(tx.accounts.from.key, + tx.accounts.nonce.key, authority, lamports); } - function advance_nonce_account(address nonce, address authorized) public { - SystemInstruction.advance_nonce_account(nonce, authorized); + @mutableAccount(nonce) + function advance_nonce_account(address authorized) external { + SystemInstruction.advance_nonce_account( + tx.accounts.nonce.key, authorized); } - function withdraw_nonce_account(address nonce, address authority, address to, uint64 lamports) public { - SystemInstruction.withdraw_nonce_account(nonce, authority, to, lamports); + @mutableAccount(nonce) + @mutableAccount(to) + @signer(authority) + function withdraw_nonce_account(uint64 lamports) external { + SystemInstruction.withdraw_nonce_account( + tx.accounts.nonce.key, + tx.accounts.authority.key, + tx.accounts.to.key, lamports); } - function authorize_nonce_account(address nonce, address authority, address new_authority) public { - SystemInstruction.authorize_nonce_account(nonce, authority, new_authority); + @mutableAccount(nonce) + @signer(authority) + function authorize_nonce_account(address new_authority) external { + SystemInstruction.authorize_nonce_account( + tx.accounts.nonce.key, + tx.accounts.authority.key, new_authority); } // This is not available on Solana v1.9.15 - // function upgrade_nonce_account(address nonce) public { + // function upgrade_nonce_account(address nonce) external { // SystemInstruction.upgrade_nonce_account(nonce); // } } \ No newline at end of file diff --git a/integration/solana/token.sol b/integration/solana/token.sol index d9d4bb649..586c8137a 100644 --- a/integration/solana/token.sol +++ b/integration/solana/token.sol @@ -7,23 +7,48 @@ contract Token { mint = _mint; } - function total_supply() public view returns (uint64) { - return SplToken.total_supply(mint); + @account(mint) + function total_supply() external view returns (uint64) { + assert(tx.accounts.mint.key == mint); + return SplToken.total_supply(tx.accounts.mint); } - function get_balance(address account) public view returns (uint64) { - return SplToken.get_balance(account); + @account(account) + function get_balance() external view returns (uint64) { + return SplToken.get_balance(tx.accounts.account); } - function mint_to(address account, address authority, uint64 amount) public { - SplToken.mint_to(mint, account, authority, amount); + @mutableAccount(mint) + @mutableAccount(account) + @signer(authority) + function mint_to(uint64 amount) external { + assert(tx.accounts.mint.key == mint); + SplToken.mint_to( + tx.accounts.mint.key, + tx.accounts.account.key, + tx.accounts.authority.key, + amount); } - function transfer(address from, address to, address owner, uint64 amount) public { - SplToken.transfer(from, to, owner, amount); + @mutableAccount(from) + @mutableAccount(to) + @signer(owner) + function transfer(uint64 amount) external { + SplToken.transfer( + tx.accounts.from.key, + tx.accounts.to.key, + tx.accounts.owner.key, + amount); } - function burn(address account, address owner, uint64 amount) public { - SplToken.burn(account, mint, owner, amount); + @mutableAccount(account) + @mutableAccount(mint) + @signer(owner) + function burn(uint64 amount) external { + SplToken.burn( + tx.accounts.account.key, + tx.accounts.mint.key, + tx.accounts.owner.key, + amount); } } \ No newline at end of file diff --git a/integration/solana/token.spec.ts b/integration/solana/token.spec.ts index d50b5f883..0632d0928 100644 --- a/integration/solana/token.spec.ts +++ b/integration/solana/token.spec.ts @@ -29,8 +29,10 @@ describe('Create spl-token and use from solidity', function () { .rpc(); let total_supply = await program.methods.totalSupply() - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([{ pubkey: mint, isSigner: false, isWritable: false }]) + .accounts({ + dataAccount: storage.publicKey, + mint: mint + }) .view(); expect(total_supply.toNumber()).toBe(0); @@ -41,35 +43,36 @@ describe('Create spl-token and use from solidity', function () { payer.publicKey ) - let balance = await program.methods.getBalance(tokenAccount.address) - .remainingAccounts([{ pubkey: tokenAccount.address, isSigner: false, isWritable: false }]) + let balance = await program.methods.getBalance() + .accounts({account: tokenAccount.address}) .view(); expect(balance.toNumber()).toBe(0); // Now let's mint some tokens await program.methods.mintTo( - tokenAccount.address, - mintAuthority.publicKey, new BN(100000)) - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([ - { pubkey: mint, isSigner: false, isWritable: true }, - { pubkey: tokenAccount.address, isSigner: false, isWritable: true }, - { pubkey: mintAuthority.publicKey, isSigner: true, isWritable: false }, - ]) + .accounts({ + dataAccount: storage.publicKey, + mint: mint, + account: tokenAccount.address, + authority: mintAuthority.publicKey, + }) .signers([mintAuthority]) .rpc(); // let's check the balances total_supply = await program.methods.totalSupply() - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([{ pubkey: mint, isSigner: false, isWritable: false }]) + .accounts({ + dataAccount: storage.publicKey, + mint: mint, + }) .view(); expect(total_supply.toNumber()).toBe(100000); - balance = await program.methods.getBalance(tokenAccount.address) - .remainingAccounts([{ pubkey: tokenAccount.address, isSigner: false, isWritable: false }]) + + balance = await program.methods.getBalance() + .accounts({account: tokenAccount.address}) .view(); expect(balance.toNumber()).toBe(100000); @@ -85,64 +88,63 @@ describe('Create spl-token and use from solidity', function () { ) await program.methods.transfer( - tokenAccount.address, - otherTokenAccount.address, - payer.publicKey, new BN(70000)) - .remainingAccounts([ - { pubkey: otherTokenAccount.address, isSigner: false, isWritable: true }, - { pubkey: tokenAccount.address, isSigner: false, isWritable: true }, - { pubkey: payer.publicKey, isSigner: true, isWritable: false }, - ]) + .accounts( + { + from: tokenAccount.address, + to: otherTokenAccount.address, + owner: payer.publicKey + } + ) .signers([payer]) .rpc(); total_supply = await program.methods.totalSupply() - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([{ pubkey: mint, isSigner: false, isWritable: false }]) + .accounts({ + dataAccount: storage.publicKey, + mint: mint, + }) .view(); expect(total_supply.toNumber()).toBe(100000); - balance = await program.methods.getBalance(tokenAccount.address) - .remainingAccounts([{ pubkey: tokenAccount.address, isSigner: false, isWritable: false }]) + balance = await program.methods.getBalance() + .accounts({account: tokenAccount.address}) .view(); expect(balance.toNumber()).toBe(30000); - balance = await program.methods.getBalance(otherTokenAccount.address) - .remainingAccounts([{ pubkey: otherTokenAccount.address, isSigner: false, isWritable: false }]) + balance = await program.methods.getBalance() + .accounts({account: otherTokenAccount.address}) .view(); expect(balance.toNumber()).toBe(70000); // burn await program.methods.burn( - otherTokenAccount.address, - theOutsider.publicKey, new BN(20000)) - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([ - { pubkey: otherTokenAccount.address, isSigner: false, isWritable: true }, - { pubkey: mint, isSigner: false, isWritable: true }, - { pubkey: theOutsider.publicKey, isSigner: true, isWritable: false }, - ]) + .accounts({ + mint: mint, + account: otherTokenAccount.address, + owner: theOutsider.publicKey, + }) .signers([theOutsider]) .rpc(); total_supply = await program.methods.totalSupply() - .accounts({ dataAccount: storage.publicKey }) - .remainingAccounts([{ pubkey: mint, isSigner: false, isWritable: false }]) + .accounts({ + dataAccount: storage.publicKey, + mint: mint, + }) .view(); expect(total_supply.toNumber()).toBe(80000); - balance = await program.methods.getBalance(tokenAccount.address) - .remainingAccounts([{ pubkey: tokenAccount.address, isSigner: false, isWritable: false }]) + balance = await program.methods.getBalance() + .accounts({account: tokenAccount.address}) .view(); expect(balance.toNumber()).toBe(30000); - - balance = await program.methods.getBalance(otherTokenAccount.address) - .remainingAccounts([{ pubkey: otherTokenAccount.address, isSigner: false, isWritable: false }]) + balance = await program.methods.getBalance() + .accounts({account: otherTokenAccount.address}) .view(); expect(balance.toNumber()).toBe(50000); diff --git a/solana-library/spl_token.sol b/solana-library/spl_token.sol index ce4cc58de..670f56a4d 100644 --- a/solana-library/spl_token.sol +++ b/solana-library/spl_token.sol @@ -146,20 +146,18 @@ library SplToken { } /// Get the total supply for the mint, i.e. the total amount in circulation - /// @param mint the mint for this token - function total_supply(address mint) internal view returns (uint64) { - AccountInfo account = get_account_info(mint); - + /// @param account The AccountInfo struct for the mint account + function total_supply(AccountInfo account) internal view returns (uint64) { + return account.data.readUint64LE(36); } /// Get the balance for an account. /// - /// @param account the account for which we want to know a balance - function get_balance(address account) internal view returns (uint64) { - AccountInfo ai = get_account_info(account); + /// @param account the struct AccountInfo whose account balance we want to retrive + function get_balance(AccountInfo account) internal view returns (uint64) { - return ai.data.readUint64LE(64); + return account.data.readUint64LE(64); } /// Get the account info for an account. This walks the transaction account infos @@ -201,11 +199,10 @@ library SplToken { /// Fetch the owner, mint account and balance for an associated token account. /// - /// @param tokenAccount The token account + /// @param ai the AccountInfo struct for the token account /// @return struct TokenAccountData - function get_token_account_data(address tokenAccount) public view returns (TokenAccountData) { - AccountInfo ai = get_account_info(tokenAccount); - + function get_token_account_data(AccountInfo ai) public pure returns (TokenAccountData) { + TokenAccountData data = TokenAccountData( { mintAccount: ai.data.readAddress(0), @@ -238,10 +235,9 @@ library SplToken { /// Retrieve the information saved in a mint account /// - /// @param mintAccount the account whose information we want to retrive + /// @param ai the AccountInfo struct for the mint accounts /// @return the MintAccountData struct - function get_mint_account_data(address mintAccount) public view returns (MintAccountData) { - AccountInfo ai = get_account_info(mintAccount); + function get_mint_account_data(AccountInfo ai) public pure returns (MintAccountData) { uint32 authority_present = ai.data.readUint32LE(0); uint32 freeze_authority_present = ai.data.readUint32LE(46); diff --git a/solang-parser/src/pt.rs b/solang-parser/src/pt.rs index 50fb38b94..fea255cf7 100644 --- a/solang-parser/src/pt.rs +++ b/solang-parser/src/pt.rs @@ -222,6 +222,19 @@ impl Loc { _ => not_a_file(), } } + + /// Performs the union of two locations + pub fn union(&mut self, other: &Self) { + match (self, other) { + (Self::File(r_file, r_start, r_end), Self::File(l_file, l_start, l_end)) => { + assert_eq!(r_file, l_file, "cannot perform union in different files"); + *r_start = std::cmp::min(*r_start, *l_start); + *r_end = std::cmp::max(*r_end, *l_end); + } + + _ => unimplemented!("cannot perform union in non File Loc"), + } + } } /// An identifier. diff --git a/solang-parser/src/solidity.lalrpop b/solang-parser/src/solidity.lalrpop index f9901c29a..c74da347b 100644 --- a/solang-parser/src/solidity.lalrpop +++ b/solang-parser/src/solidity.lalrpop @@ -442,7 +442,7 @@ NamedArgument: NamedArgument = { let name = Identifier { loc: Loc::File(file_no, l, ar), name: "address".into() }; NamedArgument{ loc: Loc::File(file_no, l, r), name, expr } - } + }, } FunctionCall: Expression = { @@ -488,7 +488,7 @@ NoFunctionTyPrecedence0: Expression = { 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) }, => Expression::Variable(<>), diff --git a/solang-parser/src/tests.rs b/solang-parser/src/tests.rs index 66f64fd78..a4942fb6a 100644 --- a/solang-parser/src/tests.rs +++ b/solang-parser/src/tests.rs @@ -1334,3 +1334,14 @@ contract MyTest { assert_eq!(expected_tree, actual_parse_tree); } + +#[test] +fn loc_union() { + let mut first = Loc::File(1, 10, 24); + let mut second = Loc::File(1, 4, 15); + let other_first = first; + first.union(&second); + assert_eq!(first, Loc::File(1, 4, 24)); + second.union(&other_first); + assert_eq!(second, Loc::File(1, 4, 24)); +} diff --git a/src/abi/tests.rs b/src/abi/tests.rs index 6e7f010d6..a45ee98ce 100644 --- a/src/abi/tests.rs +++ b/src/abi/tests.rs @@ -1723,19 +1723,19 @@ interface other_interface { contract Test { function call_1() public { - anchor.initialize(true); + anchor.initialize{accounts: []}(true); } function call_2() public { - associated.initialize(false); + associated.initialize{accounts: []}(false); } function call_3() public { - clock_interface.initialize(true); + clock_interface.initialize{accounts: []}(true); } function call_4() public { - other_interface.initialize(false); + other_interface.initialize{accounts: []}(false); } } "#; @@ -2027,8 +2027,9 @@ contract Foo { } contract Other { - function call_foo(address id) external { - Foo.new{program_id: id}(); + @account(foo_pid) + function call_foo() external { + Foo.new{program_id: tx.accounts.foo_pid.key}(); } } "#; @@ -2041,13 +2042,68 @@ contract Other { assert_eq!( idl.instructions[1].accounts, vec![ + idl_account("foo_pid", false, false), idl_account("Foo_dataAccount", true, false), - idl_account("Foo_programId", false, false), idl_account("systemProgram", false, false) ] ); } +#[test] +fn function_annotations() { + let src = r#" +contract Test1 { + @account(foo) + @mutableAccount(bar) + @signer(signerFoo) + @mutableSigner(signerBar) + function doThis() external returns (uint64) { + assert(tx.accounts.signerFoo.is_signer); + assert(tx.accounts.signerBar.is_signer); + + return tx.accounts.foo.lamports; + } +} + +contract Test2 { + @account(t1Id) + function callThat() external returns (uint64) { + uint64 res = Test1.doThis{program_id: tx.accounts.t1Id.key}(); + return res; + } +} + "#; + + let mut ns = generate_namespace(src); + codegen(&mut ns, &Options::default()); + let idl_1 = generate_anchor_idl(0, &ns, "0.1.0"); + let idl_2 = generate_anchor_idl(1, &ns, "0.1.0"); + + assert_eq!(idl_1.instructions[1].name, "doThis"); + assert_eq!( + idl_1.instructions[1].accounts, + vec![ + idl_account("foo", false, false), + idl_account("bar", true, false), + idl_account("signerFoo", false, true), + idl_account("signerBar", true, true), + ] + ); + + assert_eq!(idl_2.instructions[1].name, "callThat"); + assert_eq!( + idl_2.instructions[1].accounts, + vec![ + idl_account("t1Id", false, false), + idl_account("systemProgram", false, false), + idl_account("foo", false, false), + idl_account("bar", true, false), + idl_account("signerFoo", false, true), + idl_account("signerBar", true, true), + ] + ); +} + fn idl_account(name: &str, is_mut: bool, is_signer: bool) -> IdlAccountItem { IdlAccountItem::IdlAccount(IdlAccount { name: name.to_string(), diff --git a/src/codegen/cfg.rs b/src/codegen/cfg.rs index 8b2fe3ad2..0de3ad972 100644 --- a/src/codegen/cfg.rs +++ b/src/codegen/cfg.rs @@ -11,8 +11,8 @@ use super::{ use crate::codegen::subexpression_elimination::common_sub_expression_elimination; use crate::codegen::{undefined_variable, Expression, LLVMName}; use crate::sema::ast::{ - CallTy, Contract, FunctionAttributes, Namespace, Parameter, RetrieveType, Statement, - StringLocation, StructType, Type, + CallTy, Contract, ExternalCallAccounts, FunctionAttributes, Namespace, Parameter, RetrieveType, + Statement, StringLocation, StructType, Type, }; use crate::sema::{contracts::collect_base_args, diagnostics::Diagnostics, Recurse}; use crate::{sema::ast, Target}; @@ -126,7 +126,7 @@ pub enum Instr { salt: Option, address: Option, seeds: Option, - accounts: Option, + accounts: ExternalCallAccounts, loc: Loc, }, /// Call external functions. If the call fails, set the success failure @@ -135,7 +135,7 @@ pub enum Instr { loc: Loc, success: Option, address: Option, - accounts: Option, + accounts: ExternalCallAccounts, seeds: Option, payload: Expression, value: Expression, @@ -299,7 +299,7 @@ impl Instr { expr.recurse(cx, f); } - if let Some(expr) = accounts { + if let ExternalCallAccounts::Present(expr) = accounts { expr.recurse(cx, f); } } @@ -1202,7 +1202,7 @@ impl ControlFlowGraph { self.expr_to_string(contract, ns, payload), self.expr_to_string(contract, ns, value), self.expr_to_string(contract, ns, gas), - if let Some(accounts) = accounts { + if let ExternalCallAccounts::Present(accounts) = accounts { self.expr_to_string(contract, ns, accounts) } else { String::new() @@ -1285,7 +1285,7 @@ impl ControlFlowGraph { }, ns.contracts[*contract_no].name, self.expr_to_string(contract, ns, encoded_args), - if let Some(accounts) = accounts { + if let ExternalCallAccounts::Present(accounts) = accounts { self.expr_to_string(contract, ns, accounts) } else { String::new() diff --git a/src/codegen/constant_folding.rs b/src/codegen/constant_folding.rs index 32b54bc91..cc7e304e2 100644 --- a/src/codegen/constant_folding.rs +++ b/src/codegen/constant_folding.rs @@ -245,9 +245,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, dry_run: bool, ns: &mut Name let seeds = seeds .as_ref() .map(|expr| expression(expr, Some(&vars), cfg, ns).0); - let accounts = accounts - .as_ref() - .map(|expr| expression(expr, Some(&vars), cfg, ns).0); + let accounts = accounts.map(|expr| expression(expr, Some(&vars), cfg, ns).0); if !dry_run { cfg.blocks[block_no].instr[instr_no] = Instr::Constructor { @@ -285,9 +283,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, dry_run: bool, ns: &mut Name let address = address .as_ref() .map(|expr| expression(expr, Some(&vars), cfg, ns).0); - let accounts = accounts - .as_ref() - .map(|expr| expression(expr, Some(&vars), cfg, ns).0); + let accounts = accounts.map(|expr| expression(expr, Some(&vars), cfg, ns).0); let seeds = seeds .as_ref() .map(|expr| expression(expr, Some(&vars), cfg, ns).0); diff --git a/src/codegen/constructor.rs b/src/codegen/constructor.rs index 160223479..c020c987d 100644 --- a/src/codegen/constructor.rs +++ b/src/codegen/constructor.rs @@ -74,8 +74,7 @@ pub(super) fn call_constructor( .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt)); let accounts = call_args .accounts - .as_ref() - .map(|e| expression(e, cfg, callee_contract_no, func, ns, vartab, opt)); + .map(|expr| expression(expr, cfg, contract_no, func, ns, vartab, opt)); let mut constructor_args = constructor_args .iter() diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 73c245700..d3dd00dc0 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -16,6 +16,7 @@ use crate::codegen::array_boundary::handle_array_assign; use crate::codegen::constructor::call_constructor; use crate::codegen::unused_variable::should_remove_assignment; use crate::codegen::{Builtin, Expression}; +use crate::sema::ast::ExternalCallAccounts; use crate::sema::{ ast, ast::{ @@ -1586,7 +1587,7 @@ fn payable_send( loc: *loc, success: Some(success), address: Some(address), - accounts: None, + accounts: ExternalCallAccounts::AbsentArgument, seeds: None, payload: Expression::AllocDynamicBytes { loc: *loc, @@ -1655,7 +1656,7 @@ fn payable_transfer( Instr::ExternalCall { loc: *loc, success: None, - accounts: None, + accounts: ExternalCallAccounts::AbsentArgument, seeds: None, address: Some(address), payload: Expression::AllocDynamicBytes { @@ -2844,7 +2845,6 @@ pub fn emit_function_call( }; let accounts = call_args .accounts - .as_ref() .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); let seeds = call_args .seeds @@ -2932,7 +2932,6 @@ pub fn emit_function_call( }; let accounts = call_args .accounts - .as_ref() .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt)); let seeds = call_args .seeds @@ -3065,7 +3064,7 @@ pub fn emit_function_call( Instr::ExternalCall { loc: *loc, success, - accounts: None, + accounts: ExternalCallAccounts::AbsentArgument, seeds: None, address: Some(address), payload, diff --git a/src/codegen/solana_accounts/account_collection.rs b/src/codegen/solana_accounts/account_collection.rs index d31fd4c92..ff79f8565 100644 --- a/src/codegen/solana_accounts/account_collection.rs +++ b/src/codegen/solana_accounts/account_collection.rs @@ -3,7 +3,7 @@ use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy}; use crate::codegen::solana_accounts::account_from_number; use crate::codegen::{Builtin, Expression}; -use crate::sema::ast::{Contract, Function, Namespace, SolanaAccount}; +use crate::sema::ast::{Contract, ExternalCallAccounts, Function, Namespace, SolanaAccount}; use crate::sema::diagnostics::Diagnostics; use crate::sema::solana_accounts::BuiltinAccounts; use crate::sema::Recurse; @@ -334,39 +334,43 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) { salt.recurse(data, check_expression); } if let Some(address) = address { + // If the address is a number literal, it comes from the `@program_id` annotation, + // so we need to include it in the IDL. + // If it is not a literal, we assume users are fetching it from a declared account + // (@account(my_id) => tx.accounts.my_id.key) + if matches!(address, Expression::NumberLiteral { .. }) { + data.add_program_id(&data.contracts[*contract_no].name); + } + address.recurse(data, check_expression); } if let Some(seeds) = seeds { seeds.recurse(data, check_expression); } - if let Some(accounts) = accounts { + if let ExternalCallAccounts::Present(accounts) = accounts { accounts.recurse(data, check_expression); - } else { - // If the one passes the AccountMeta vector to the constructor call, there is no + } else if let Some(constructor_no) = constructor_no { + // If 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); - } else { - data.add_account( - format!("{}_dataAccount", data.contracts[*contract_no].name), - &SolanaAccount { - loc: *loc, - is_signer: false, - is_writer: true, - generated: true, - }, - ); - } + 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 { loc, address, accounts, - seeds, payload, value, gas, @@ -380,7 +384,7 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) { return; } - let mut program_id_populated = false; + let mut should_add_program_id = false; if let Some(address) = address { address.recurse(data, check_expression); if let Expression::NumberLiteral { value, .. } = address { @@ -395,28 +399,31 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) { generated: true, }, ); - program_id_populated = true; + } else { + // If the address is a literal, it came from the @program_id annotation, + // so it is not in the IDL. + should_add_program_id = true; + // If it is not a literal, we assume it is an account declared with @account, + // in which case it is already in the IDL. } } } - if let Some(seeds) = seeds { - seeds.recurse(data, check_expression); - } + payload.recurse(data, check_expression); value.recurse(data, check_expression); gas.recurse(data, check_expression); // External calls always need the system account data.add_system_account(); - if let Some(accounts) = accounts { + if let ExternalCallAccounts::Present(accounts) = accounts { accounts.recurse(data, check_expression); } if let Some((contract_no, function_no)) = contract_function_no { - if !program_id_populated { + if should_add_program_id { data.add_program_id(&data.contracts[*contract_no].name); } - if accounts.is_none() { + if accounts.is_absent() { transfer_accounts(loc, *contract_no, *function_no, data); } } diff --git a/src/codegen/solana_accounts/account_management.rs b/src/codegen/solana_accounts/account_management.rs index 84f21a977..042e8e110 100644 --- a/src/codegen/solana_accounts/account_management.rs +++ b/src/codegen/solana_accounts/account_management.rs @@ -3,7 +3,9 @@ use crate::codegen::cfg::Instr; use crate::codegen::dispatch::solana::SOLANA_DISPATCH_CFG_NAME; use crate::codegen::{Builtin, Expression}; -use crate::sema::ast::{ArrayLength, Contract, Function, Namespace, StructType, Type}; +use crate::sema::ast::{ + ArrayLength, Contract, ExternalCallAccounts, Function, Namespace, StructType, Type, +}; use crate::sema::solana_accounts::BuiltinAccounts; use num_bigint::BigInt; use solang_parser::pt::Loc; @@ -115,7 +117,7 @@ fn process_instruction( contract_function_no: Some((contract_no, func_no)), .. } => { - if accounts.is_some() { + if !accounts.is_absent() { return; } @@ -150,7 +152,7 @@ fn process_instruction( dimensions: vec![account_metas.len() as u32], values: account_metas, }; - *accounts = Some(metas_vector); + *accounts = ExternalCallAccounts::Present(metas_vector); } Instr::Constructor { contract_no, @@ -175,7 +177,7 @@ fn process_instruction( dimensions: vec![1], values: account_metas, }; - *accounts = Some(metas_vector); + *accounts = ExternalCallAccounts::Present(metas_vector); } Instr::AccountAccess { loc, name, var_no } => { // This could have been an Expression::AccountAccess if we had a three-address form. @@ -197,7 +199,6 @@ fn process_instruction( expr, }; } - _ => (), } } diff --git a/src/codegen/solana_deploy.rs b/src/codegen/solana_deploy.rs index 3224d758e..5c821a804 100644 --- a/src/codegen/solana_deploy.rs +++ b/src/codegen/solana_deploy.rs @@ -9,7 +9,8 @@ use crate::codegen::solana_accounts::account_management::{ account_meta_literal, retrieve_key_from_account_info, }; use crate::sema::ast::{ - self, ArrayLength, CallTy, Function, FunctionAttributes, Namespace, StructType, + self, ArrayLength, CallTy, ExternalCallAccounts, Function, FunctionAttributes, Namespace, + StructType, }; use crate::sema::diagnostics::Diagnostics; use crate::sema::eval::eval_const_number; @@ -589,7 +590,7 @@ pub(super) fn solana_deploy( ty: Type::Address(false), value: BigInt::from(0), }), // SystemProgram 11111111111111111111111111111111 - accounts: Some(Expression::Variable { + accounts: ExternalCallAccounts::Present(Expression::Variable { loc: Loc::Codegen, ty: metas_ty, var_no: metas, diff --git a/src/codegen/statements/try_catch.rs b/src/codegen/statements/try_catch.rs index ef779278d..cfad6deb5 100644 --- a/src/codegen/statements/try_catch.rs +++ b/src/codegen/statements/try_catch.rs @@ -12,7 +12,8 @@ use crate::codegen::{ Expression, }; use crate::sema::ast::{ - self, CallTy, Function, Namespace, RetrieveType, TryCatch, Type, Type::Uint, + self, CallTy, ExternalCallAccounts, Function, Namespace, RetrieveType, TryCatch, Type, + Type::Uint, }; use num_bigint::{BigInt, Sign}; use num_traits::Zero; @@ -260,7 +261,7 @@ fn exec_try( loc: *loc, success: Some(success), address: Some(address), - accounts: None, + accounts: ExternalCallAccounts::AbsentArgument, seeds: None, payload, value, diff --git a/src/codegen/strength_reduce/mod.rs b/src/codegen/strength_reduce/mod.rs index c0e20a94c..5f2324714 100644 --- a/src/codegen/strength_reduce/mod.rs +++ b/src/codegen/strength_reduce/mod.rs @@ -7,7 +7,7 @@ mod value; use super::cfg::{ControlFlowGraph, Instr}; use crate::codegen::Expression; -use crate::sema::ast::{Namespace, Type}; +use crate::sema::ast::{ExternalCallAccounts, Namespace, Type}; use bitvec::prelude::*; use expression_values::expression_values; use num_bigint::{BigInt, Sign}; @@ -174,7 +174,7 @@ fn block_reduce( if let Some(salt) = salt { *salt = expression_reduce(salt, &vars, ns); } - if let Some(accounts) = accounts { + if let ExternalCallAccounts::Present(accounts) = accounts { *accounts = expression_reduce(accounts, &vars, ns); } *gas = expression_reduce(gas, &vars, ns); diff --git a/src/codegen/subexpression_elimination/instruction.rs b/src/codegen/subexpression_elimination/instruction.rs index b3e36def5..665e784a2 100644 --- a/src/codegen/subexpression_elimination/instruction.rs +++ b/src/codegen/subexpression_elimination/instruction.rs @@ -5,6 +5,7 @@ use crate::codegen::subexpression_elimination::common_subexpression_tracker::Com use crate::codegen::subexpression_elimination::AvailableExpression; use crate::codegen::subexpression_elimination::{AvailableExpressionSet, AvailableVariable}; use crate::codegen::Expression; +use crate::sema::ast::ExternalCallAccounts; impl<'a, 'b: 'a> AvailableExpressionSet<'a> { /// Check if we can add the expressions of an instruction to the graph @@ -126,7 +127,7 @@ impl<'a, 'b: 'a> AvailableExpressionSet<'a> { let _ = self.gen_expression(expr, ave, cst); } - if let Some(expr) = accounts { + if let ExternalCallAccounts::Present(expr) = accounts { let _ = self.gen_expression(expr, ave, cst); } } @@ -143,7 +144,7 @@ impl<'a, 'b: 'a> AvailableExpressionSet<'a> { if let Some(expr) = address { let _ = self.gen_expression(expr, ave, cst); } - if let Some(expr) = accounts { + if let ExternalCallAccounts::Present(expr) = accounts { let _ = self.gen_expression(expr, ave, cst); } if let Some(expr) = seeds { diff --git a/src/codegen/subexpression_elimination/tests.rs b/src/codegen/subexpression_elimination/tests.rs index ca9854462..96010e3a7 100644 --- a/src/codegen/subexpression_elimination/tests.rs +++ b/src/codegen/subexpression_elimination/tests.rs @@ -7,7 +7,7 @@ use crate::codegen::subexpression_elimination::anticipated_expressions::Anticipa use crate::codegen::subexpression_elimination::common_subexpression_tracker::CommonSubExpressionTracker; use crate::codegen::subexpression_elimination::{AvailableExpression, AvailableExpressionSet}; use crate::codegen::Expression; -use crate::sema::ast::{StringLocation, Type}; +use crate::sema::ast::{ExternalCallAccounts, StringLocation, Type}; use num_bigint::{BigInt, Sign}; use num_rational::BigRational; use solang_parser::pt::Loc; @@ -433,7 +433,7 @@ fn string() { address: None, seeds: None, loc: Loc::Codegen, - accounts: None, + accounts: ExternalCallAccounts::AbsentArgument, }; let mut ave = AvailableExpression::default(); diff --git a/src/emit/binary.rs b/src/emit/binary.rs index fdf1129ab..2becab16c 100644 --- a/src/emit/binary.rs +++ b/src/emit/binary.rs @@ -126,6 +126,13 @@ macro_rules! emit_context { ) }; } + + #[allow(unused_macros)] + macro_rules! i8_basic_type_enum { + () => { + $binary.context.i8_type().as_basic_type_enum() + }; + } }; } @@ -866,12 +873,25 @@ impl<'a> Binary<'a> { /// Return the llvm type for the resolved type. pub(crate) fn llvm_type(&self, ty: &Type, ns: &Namespace) -> BasicTypeEnum<'a> { + emit_context!(self); if ty.is_builtin_struct() == Some(StructType::AccountInfo) { return self - .module - .get_struct_type("struct.SolAccountInfo") - .unwrap() - .into(); + .context + .struct_type( + &[ + byte_ptr!().as_basic_type_enum(), // SolPubkey * + byte_ptr!().as_basic_type_enum(), // uint64_t * + self.context.i64_type().as_basic_type_enum(), // uint64_t + byte_ptr!().as_basic_type_enum(), // uint8_t * + byte_ptr!().as_basic_type_enum(), // SolPubkey * + self.context.i64_type().as_basic_type_enum(), // uint64_t + i8_basic_type_enum!(), // bool + i8_basic_type_enum!(), // bool + i8_basic_type_enum!(), // bool + ], + false, + ) + .as_basic_type_enum(); } else { match ty { Type::Bool => BasicTypeEnum::IntType(self.context.bool_type()), diff --git a/src/emit/instructions.rs b/src/emit/instructions.rs index 5bc3bbc2e..085a9e67f 100644 --- a/src/emit/instructions.rs +++ b/src/emit/instructions.rs @@ -9,7 +9,7 @@ use crate::emit::binary::Binary; use crate::emit::cfg::{create_block, BasicBlock, Work}; use crate::emit::expression::expression; use crate::emit::{ContractArgs, TargetRuntime, Variable}; -use crate::sema::ast::{Contract, Namespace, RetrieveType, Type}; +use crate::sema::ast::{Contract, ExternalCallAccounts, Namespace, RetrieveType, Type}; use crate::Target; use inkwell::types::BasicType; use inkwell::values::{ @@ -700,36 +700,6 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>( expression(target, bin, address, &w.vars, function, ns).into_array_value(); bin.builder.build_store(address_stack, address); - } else if let Some((account_metas, _)) = llvm_accounts { - // On Solana, we must return the address of a contract after instantiating it with - // 'new'. When the 'address' parameter is not provided to the call argument, - // we fetch the address from the AccountMeta array provided in the 'accounts' - // call argument. We assume the data account will always be the first element in - // the array. - let vector_ty = bin.llvm_type(&accounts.as_ref().unwrap().ty(), ns); - let elem_ptr = unsafe { - bin.builder.build_gep( - vector_ty, - account_metas, - &[ - bin.context.i32_type().const_zero(), - bin.context.i32_type().const_zero(), - bin.context.i32_type().const_zero(), - ], - "contract_account", - ) - }; - let loaded_ptr = bin.builder.build_load( - bin.address_type(ns).ptr_type(AddressSpace::default()), - elem_ptr, - "", - ); - let loaded_address = bin.builder.build_load( - bin.address_type(ns), - loaded_ptr.into_pointer_value(), - "", - ); - bin.builder.build_store(address_stack, loaded_address); } let seeds = if let Some(seeds) = seeds { @@ -1092,13 +1062,13 @@ fn add_or_retrieve_block<'a>( /// we process its codegen representation here and return the pointer to it and its size. fn process_account_metas<'a, T: TargetRuntime<'a> + ?Sized>( target: &T, - accounts: &Option, + accounts: &ExternalCallAccounts, bin: &Binary<'a>, vartab: &HashMap>, function: FunctionValue<'a>, ns: &Namespace, ) -> Option<(PointerValue<'a>, IntValue<'a>)> { - if let Some(accounts) = accounts { + if let ExternalCallAccounts::Present(accounts) = accounts { let ty = accounts.ty(); let expr = expression(target, bin, accounts, vartab, function, ns); diff --git a/src/emit/solana/mod.rs b/src/emit/solana/mod.rs index 9cfbf728c..334843737 100644 --- a/src/emit/solana/mod.rs +++ b/src/emit/solana/mod.rs @@ -7,7 +7,7 @@ use crate::Target; use std::cmp::Ordering; use crate::codegen::{cfg::ReturnCode, Options}; -use crate::sema::ast::Type; +use crate::sema::ast::{Namespace, StructType, Type}; use inkwell::module::{Linkage, Module}; use inkwell::types::BasicType; use inkwell::values::{ @@ -217,6 +217,33 @@ impl SolanaTarget { function .as_global_value() .set_unnamed_address(UnnamedAddress::Local); + + let function = binary.module.add_function( + "sol_invoke_signed_c", + u64_ty.fn_type( + &[ + u8_ptr.into(), + binary + .module + .get_struct_type("struct.SolAccountInfo") + .unwrap() + .ptr_type(AddressSpace::default()) + .into(), + binary.context.i32_type().into(), + binary + .context + .i8_type() + .ptr_type(AddressSpace::default()) + .into(), + binary.context.i32_type().into(), + ], + false, + ), + None, + ); + function + .as_global_value() + .set_unnamed_address(UnnamedAddress::Local); } /// Returns the SolAccountInfo of the executing binary @@ -1050,60 +1077,6 @@ impl SolanaTarget { .as_basic_value_enum() } - /// Construct the LLVM-IR to call 'external_call' from solana.c - fn build_external_call<'b>( - &self, - binary: &Binary, - payload: PointerValue<'b>, - payload_len: IntValue<'b>, - contract_args: ContractArgs<'b>, - ns: &ast::Namespace, - ) { - let parameters = self.sol_parameters(binary); - let external_call = binary.module.get_function("external_call").unwrap(); - - let program_id = contract_args.program_id.unwrap_or_else(|| { - binary - .llvm_type(&Type::Address(false), ns) - .ptr_type(AddressSpace::default()) - .const_null() - }); - - let (seeds, seeds_len) = contract_args - .seeds - .map(|(seeds, len)| { - ( - seeds, - binary.builder.build_int_cast( - len, - external_call.get_type().get_param_types()[4].into_int_type(), - "len", - ), - ) - }) - .unwrap_or(( - external_call.get_type().get_param_types()[3] - .ptr_type(AddressSpace::default()) - .const_null(), - external_call.get_type().get_param_types()[4] - .into_int_type() - .const_zero(), - )); - - binary.builder.build_call( - external_call, - &[ - payload.into(), - payload_len.into(), - program_id.into(), - seeds.into(), - seeds_len.into(), - parameters.into(), - ], - "", - ); - } - /// Construct the LLVM-IR to call 'sol_invoke_signed_c'. fn build_invoke_signed_c<'b>( &self, @@ -1112,12 +1085,33 @@ impl SolanaTarget { payload: PointerValue<'b>, payload_len: IntValue<'b>, contract_args: ContractArgs<'b>, + ns: &Namespace, ) { let instruction_ty: BasicTypeEnum = binary - .module - .get_struct_type("struct.SolInstruction") - .unwrap() - .into(); + .context + .struct_type( + &[ + binary + .module + .get_struct_type("struct.SolPubkey") + .unwrap() + .ptr_type(AddressSpace::default()) + .as_basic_type_enum(), + binary + .llvm_type(&Type::Struct(StructType::AccountMeta), ns) + .ptr_type(AddressSpace::default()) + .as_basic_type_enum(), + binary.context.i64_type().as_basic_type_enum(), + binary + .context + .i8_type() + .ptr_type(AddressSpace::default()) + .as_basic_type_enum(), + binary.context.i64_type().as_basic_type_enum(), + ], + false, + ) + .as_basic_type_enum(); let instruction = binary.build_alloca(function, instruction_ty, "instruction"); diff --git a/src/emit/solana/target.rs b/src/emit/solana/target.rs index d113e4d19..49f3f5bb8 100644 --- a/src/emit/solana/target.rs +++ b/src/emit/solana/target.rs @@ -1232,7 +1232,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { encoded_args: BasicValueEnum<'b>, encoded_args_len: BasicValueEnum<'b>, mut contract_args: ContractArgs<'b>, - _ns: &ast::Namespace, + ns: &ast::Namespace, _loc: Loc, ) { contract_args.program_id = Some(address); @@ -1242,7 +1242,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { assert!(contract_args.accounts.is_some()); // The AccountMeta array is always present for Solana contracts - self.build_invoke_signed_c(binary, function, payload, payload_len, contract_args); + self.build_invoke_signed_c(binary, function, payload, payload_len, contract_args, ns); } fn builtin_function( @@ -1344,12 +1344,19 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { ) { let address = address.unwrap(); + if contract_args.accounts.is_none() { + contract_args.accounts = Some(( + binary + .context + .i64_type() + .ptr_type(AddressSpace::default()) + .const_zero(), + binary.context.i32_type().const_zero(), + )) + }; + contract_args.program_id = Some(address); - if contract_args.accounts.is_some() { - self.build_invoke_signed_c(binary, function, payload, payload_len, contract_args); - } else { - self.build_external_call(binary, payload, payload_len, contract_args, ns); - } + self.build_invoke_signed_c(binary, function, payload, payload_len, contract_args, ns); } /// Get return buffer for external call diff --git a/src/sema/ast.rs b/src/sema/ast.rs index a53ebdc67..c92702857 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -4,6 +4,7 @@ use super::symtable::Symtable; use crate::abi::anchor::discriminator; use crate::codegen::cfg::{ControlFlowGraph, Instr}; use crate::diagnostics::Diagnostics; +use crate::sema::ast::ExternalCallAccounts::{AbsentArgument, NoAccount}; use crate::sema::yul::ast::{InlineAssembly, YulFunction}; use crate::sema::Recurse; use crate::{codegen, Target}; @@ -1216,12 +1217,69 @@ pub struct CallArgs { pub gas: Option>, pub salt: Option>, pub value: Option>, - pub accounts: Option>, + pub accounts: ExternalCallAccounts>, pub seeds: Option>, pub flags: Option>, pub program_id: Option>, } +/// This enum manages the accounts in an external call on Solana. There can be three options: +/// 1. The developer explicitly specifies there are not accounts for the call (`NoAccount`). +/// 2. The accounts call argument is absent, in which case we attempt to generate the AccountMetas +/// vector automatically (`AbsentArgumet`). +/// 3. There are accounts specified in the accounts call argument (Present). +#[derive(PartialEq, Eq, Clone, Debug, Default)] +pub enum ExternalCallAccounts { + NoAccount, + #[default] + AbsentArgument, + Present(T), +} + +impl ExternalCallAccounts { + /// Is the accounts call argument missing? + pub fn is_absent(&self) -> bool { + matches!(self, ExternalCallAccounts::AbsentArgument) + } + + /// Returns if the accounts call argument was present in the call + pub fn argument_provided(&self) -> bool { + matches!( + self, + ExternalCallAccounts::Present(_) | ExternalCallAccounts::NoAccount + ) + } + + /// Applies a function on the nested objects + pub fn map(&self, func: F) -> ExternalCallAccounts

+ where + F: FnOnce(&T) -> P, + { + match self { + NoAccount => NoAccount, + AbsentArgument => AbsentArgument, + ExternalCallAccounts::Present(value) => ExternalCallAccounts::Present(func(value)), + } + } + + /// Transform the nested object into a reference + pub const fn as_ref(&self) -> ExternalCallAccounts<&T> { + match self { + ExternalCallAccounts::Present(value) => ExternalCallAccounts::Present(value), + NoAccount => NoAccount, + AbsentArgument => AbsentArgument, + } + } + + /// Return a reference to the nested object + pub fn unwrap(&self) -> &T { + match self { + ExternalCallAccounts::Present(value) => value, + _ => panic!("unwrap called at variant without a nested object"), + } + } +} + impl Recurse for CallArgs { type ArgType = Expression; fn recurse(&self, cx: &mut T, f: fn(expr: &Expression, ctx: &mut T) -> bool) { @@ -1234,7 +1292,7 @@ impl Recurse for CallArgs { if let Some(value) = &self.value { value.recurse(cx, f); } - if let Some(accounts) = &self.accounts { + if let ExternalCallAccounts::Present(accounts) = &self.accounts { accounts.recurse(cx, f); } if let Some(flags) = &self.flags { diff --git a/src/sema/contracts.rs b/src/sema/contracts.rs index 141e95ed1..948483ba5 100644 --- a/src/sema/contracts.rs +++ b/src/sema/contracts.rs @@ -8,12 +8,14 @@ use super::{ symtable::Symtable, using, variables, ContractDefinition, }; +use crate::sema::ast::SolanaAccount; use crate::sema::expression::constructor::match_constructor_to_args; use crate::{sema::ast::Namespace, sema::unused_variable::emit_warning_local_variable}; +use indexmap::{IndexMap, IndexSet}; use num_bigint::BigInt; use num_traits::Zero; use once_cell::unsync::OnceCell; -use solang_parser::diagnostics::Diagnostic; +use solang_parser::diagnostics::{Diagnostic, Note}; use solang_parser::pt::FunctionTy; use solang_parser::pt::{self, CodeLocation}; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -651,7 +653,6 @@ fn check_inheritance(contract_no: usize, ns: &mut ast::Namespace) { .skip(1) .map(|(_, function_no)| { let func = &ns.functions[*function_no]; - ast::Note { loc: func.loc, message: format!("previous definition of function '{}'", func.name), @@ -833,6 +834,108 @@ fn base_function_compatible( // rust compile wants this, already handled in first arm (None, None) => (), } + + let mut no_correspondence: Vec<(pt::Loc, &String)> = Vec::new(); + let mut incorrect_flag: IndexSet<(pt::Loc, pt::Loc, &String)> = IndexSet::new(); + let func_accounts = &*func.solana_accounts.borrow(); + let base_accounts = &*base.solana_accounts.borrow(); + let mut correct_ordering = true; + + let (correct, func_acc_locations) = check_override_accounts_compatible( + base_accounts, + func_accounts, + &mut no_correspondence, + &mut incorrect_flag, + false, + ); + correct_ordering &= correct; + + let (correct, base_acc_locations) = check_override_accounts_compatible( + func_accounts, + base_accounts, + &mut no_correspondence, + &mut incorrect_flag, + true, + ); + correct_ordering &= correct; + + if !no_correspondence.is_empty() { + let notes = no_correspondence + .iter() + .map(|(loc, account_name)| Note { + loc: *loc, + message: format!("corresponding account '{}' is missing", account_name), + }) + .collect::>(); + + diagnostics.push(Diagnostic::error_with_notes( + func.loc, + "functions must have the same declared accounts for correct overriding".to_string(), + notes, + )); + } + + if !incorrect_flag.is_empty() { + for (loc_1, loc_2, account_name) in &incorrect_flag { + diagnostics.push(Diagnostic::error_with_note( + *loc_1, + format!( + "account '{}' must be declared with the same annotation for overriding", + account_name + ), + *loc_2, + "location of other declaration".to_string(), + )); + } + } + + if !correct_ordering { + diagnostics.push(Diagnostic::error_with_note( + func_acc_locations.unwrap(), + "accounts must be declared in the same order for overriding".to_string(), + base_acc_locations.unwrap(), + "location of base function accounts".to_string(), + )); + } +} + +/// Checks if the accounts from the virtual function and the overriding one are compatible. +/// Returns true if the accounts have been declared in the same order in both functions and +/// the location of all the account declarations. +fn check_override_accounts_compatible<'a>( + func_accounts: &'a IndexMap, + other_accounts: &'a IndexMap, + no_correspondence: &mut Vec<(pt::Loc, &'a String)>, + incorrect_flag: &mut IndexSet<(pt::Loc, pt::Loc, &'a String)>, + reverse: bool, +) -> (bool, Option) { + let mut correct_order = true; + let mut locations = if let Some((_, acc)) = other_accounts.get_index(0) { + Some(acc.loc) + } else { + None + }; + + for (account_no, (account_name, account_flags)) in other_accounts.iter().enumerate() { + locations.as_mut().unwrap().union(&account_flags.loc); + if let Some((other_no, _, other_account)) = func_accounts.get_full(account_name) { + if other_account.is_signer != account_flags.is_signer + || other_account.is_writer != account_flags.is_writer + { + if reverse { + incorrect_flag.insert((other_account.loc, account_flags.loc, account_name)); + } else { + incorrect_flag.insert((account_flags.loc, other_account.loc, account_name)); + } + } else if account_no != other_no { + correct_order = false; + } + } else { + no_correspondence.push((account_flags.loc, account_name)); + } + } + + (correct_order, locations) } /// Function body which should be resolved. diff --git a/src/sema/dotgraphviz.rs b/src/sema/dotgraphviz.rs index 4871811b3..a6f0e1213 100644 --- a/src/sema/dotgraphviz.rs +++ b/src/sema/dotgraphviz.rs @@ -1474,7 +1474,7 @@ impl Dot { if let Some(salt) = &call_args.salt { self.add_expression(salt, func, ns, node, String::from("salt")); } - if let Some(accounts) = &call_args.accounts { + if let ExternalCallAccounts::Present(accounts) = &call_args.accounts { self.add_expression(accounts, func, ns, node, String::from("accounts")); } if let Some(seeds) = &call_args.seeds { diff --git a/src/sema/expression/assign.rs b/src/sema/expression/assign.rs index 37edc02c6..f05aa528f 100644 --- a/src/sema/expression/assign.rs +++ b/src/sema/expression/assign.rs @@ -197,7 +197,7 @@ pub(super) fn assign_expr( ) { ResolveTo::Unknown } else { - ResolveTo::Type(var_ty.deref_any()) + ResolveTo::Type(var_ty.deref_any().deref_any()) }; let set = expression(right, context, ns, symtable, diagnostics, resolve_to)?; diff --git a/src/sema/expression/constructor.rs b/src/sema/expression/constructor.rs index 8977b7fb0..936135307 100644 --- a/src/sema/expression/constructor.rs +++ b/src/sema/expression/constructor.rs @@ -647,7 +647,7 @@ pub(super) fn solana_constructor_check( )); } - if !context.in_a_loop() || call_args.accounts.is_some() { + if !context.in_a_loop() || !call_args.accounts.is_absent() { return; } diff --git a/src/sema/expression/function_call.rs b/src/sema/expression/function_call.rs index 2bc8770c3..d7269068b 100644 --- a/src/sema/expression/function_call.rs +++ b/src/sema/expression/function_call.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::sema::ast::{ - ArrayLength, Builtin, CallArgs, CallTy, Expression, Function, Mutability, Namespace, - RetrieveType, StructType, Symbol, Type, + ArrayLength, Builtin, CallArgs, CallTy, Expression, ExternalCallAccounts, Function, Mutability, + Namespace, RetrieveType, StructType, Symbol, Type, }; use crate::sema::contracts::is_base; use crate::sema::diagnostics::Diagnostics; @@ -1912,6 +1912,13 @@ pub(super) fn parse_call_args( return Err(()); } + if let pt::Expression::ArrayLiteral(_, vec) = &arg.expr { + if vec.is_empty() { + res.accounts = ExternalCallAccounts::NoAccount; + continue; + } + } + let expr = expression( &arg.expr, context, @@ -1949,7 +1956,7 @@ pub(super) fn parse_call_args( )); } - res.accounts = Some(Box::new(expr)); + res.accounts = ExternalCallAccounts::Present(Box::new(expr)); } "seeds" => { if ns.target != Target::Solana { @@ -2036,8 +2043,7 @@ pub(super) fn parse_call_args( } if ns.target == Target::Solana { - if !external_call - && res.accounts.is_none() + if res.accounts.is_absent() && !matches!( ns.functions[context.function_no.unwrap()].visibility, Visibility::External(_) diff --git a/src/sema/function_annotation.rs b/src/sema/function_annotation.rs index 49299a951..4b1f1b069 100644 --- a/src/sema/function_annotation.rs +++ b/src/sema/function_annotation.rs @@ -16,7 +16,7 @@ use crate::sema::solana_accounts::BuiltinAccounts; use crate::Target; use indexmap::map::Entry; use num_traits::ToPrimitive; -use solang_parser::pt::{self, Annotation, CodeLocation}; +use solang_parser::pt::{self, Annotation, CodeLocation, Visibility}; use std::str::FromStr; /// Annotations are processed in two different places during sema. When we are resolving the @@ -48,6 +48,28 @@ pub fn function_prototype_annotations( for annotation in annotations { match annotation.id.name.as_str() { "selector" => function_selector(func, annotation, &mut diagnostics, ns), + "account" | "signer" | "mutableAccount" | "mutableSigner" + if ns.target == Target::Solana => + { + if !func.is_constructor() && !matches!(func.visibility, Visibility::External(..)) { + diagnostics.push(Diagnostic::error( + annotation.loc, + "account declarations are only valid in functions declared as external" + .to_string(), + )); + continue; + } + + account_declaration( + &annotation.loc, + annotation.value.as_ref().unwrap(), + func, + annotation.id.name.as_str(), + &mut ns.diagnostics, + &mut ConstructorAnnotations::default(), + ); + } + _ if !func.has_body => { // function_body_annotations() is called iff there is a body diagnostics.push(Diagnostic::error( @@ -180,10 +202,8 @@ pub(super) fn function_body_annotations( // On Solana, the seeds and bump for a constructor can be specified using annotations, for example // - // @seed(param1) // @seed("fizbaz") - // @bump(param2) - // constructor(bytes param1, uint8 param2) {} + // constructor(@seed bytes param1, @bump uint8 param2) {} let mut has_annotation = false; @@ -249,62 +269,19 @@ pub(super) fn function_body_annotations( ); } "payer" if is_solana_constructor => { - let loc = note.loc; - if let pt::Expression::Variable(id) = note.value.as_ref().unwrap() { - if BuiltinAccounts::from_str(&id.name).is_ok() { - diagnostics.push(Diagnostic::error( - id.loc, - format!("'{}' is a reserved account name", id.name), - )); - continue; - } else if id.name.contains(BuiltinAccounts::DataAccount.as_str()) { - diagnostics.push(Diagnostic::error( - id.loc, - "account names that contain 'dataAccount' are reserved".to_string(), - )); - continue; - } - - match ns.functions[function_no] - .solana_accounts - .borrow_mut() - .entry(id.name.clone()) - { - Entry::Occupied(other_account) => { - diagnostics.push(Diagnostic::error_with_note( - id.loc, - format!("account '{}' already defined", id.name), - other_account.get().loc, - "previous definition".to_string(), - )); - } - Entry::Vacant(vacancy) => { - if let Some((prev, _)) = &annotations.payer { - duplicate_annotation( - &mut diagnostics, - "payer", - loc, - *prev, - ns.functions[function_no].ty.as_str(), - ); - } else { - vacancy.insert(SolanaAccount { - loc: note.loc, - is_signer: true, - is_writer: true, - generated: false, - }); - annotations.payer = Some((loc, id.name.clone())); - } - } - } - } else { - diagnostics.push(Diagnostic::error( - note.loc, - "invalid parameter for annotation".to_string(), - )); - } + account_declaration( + ¬e.loc, + note.value.as_ref().unwrap(), + &ns.functions[function_no], + note.id.name.as_str(), + &mut diagnostics, + &mut annotations, + ); } + "account" | "signer" | "mutableAccount" | "mutableSigner" + // We already deal with these cases in `function_prototype_annotation` + if ns.target == Target::Solana => (), + _ => diagnostics.push(Diagnostic::error( note.loc, format!( @@ -520,3 +497,69 @@ fn duplicate_annotation( format!("previous @{}", name), )); } + +fn account_declaration( + loc: &pt::Loc, + expr: &pt::Expression, + func: &Function, + annotation_name: &str, + diagnostics: &mut Diagnostics, + resolved_annotations: &mut ConstructorAnnotations, +) { + if let pt::Expression::Variable(id) = expr { + if BuiltinAccounts::from_str(&id.name).is_ok() { + diagnostics.push(Diagnostic::error( + id.loc, + format!("'{}' is a reserved account name", id.name), + )); + return; + } else if id.name.contains("dataAccount") { + diagnostics.push(Diagnostic::error( + id.loc, + "account names that contain 'dataAccount' are reserved".to_string(), + )); + return; + } + + match func.solana_accounts.borrow_mut().entry(id.name.clone()) { + Entry::Occupied(other_account) => { + diagnostics.push(Diagnostic::error_with_note( + id.loc, + format!("account '{}' already defined", id.name), + other_account.get().loc, + "previous definition".to_string(), + )); + } + Entry::Vacant(vacancy) => { + if let Some(prev) = &resolved_annotations.payer { + duplicate_annotation( + diagnostics, + annotation_name, + *loc, + prev.0, + func.ty.as_str(), + ); + } else { + vacancy.insert(SolanaAccount { + loc: *loc, + is_signer: matches!(annotation_name, "payer" | "signer" | "mutableSigner"), + is_writer: matches!( + annotation_name, + "mutableAccount" | "payer" | "mutableSigner" + ), + generated: false, + }); + + if annotation_name == "payer" { + resolved_annotations.payer = Some((*loc, id.name.clone())); + } + } + } + } + } else { + diagnostics.push(Diagnostic::error( + *loc, + "invalid parameter for annotation".to_string(), + )); + } +} diff --git a/src/sema/mutability.rs b/src/sema/mutability.rs index 8812be8e8..d72f63535 100644 --- a/src/sema/mutability.rs +++ b/src/sema/mutability.rs @@ -11,6 +11,7 @@ use super::{ use crate::sema::ast::SolanaAccount; use crate::sema::solana_accounts::BuiltinAccounts; use crate::sema::yul::builtin::YulBuiltInFunction; +use crate::Target; use bitflags::bitflags; use solang_parser::pt::Loc; use solang_parser::{helpers::CodeLocation, pt}; @@ -238,7 +239,7 @@ fn check_mutability(func: &Function, ns: &Namespace) -> Vec { } } - if state.data_account != DataAccountUsage::NONE { + if state.data_account != DataAccountUsage::NONE && ns.target == Target::Solana { func.solana_accounts.borrow_mut().insert( BuiltinAccounts::DataAccount.to_string(), SolanaAccount { diff --git a/src/sema/tests/mod.rs b/src/sema/tests/mod.rs index 56c03719a..5e6f6722e 100644 --- a/src/sema/tests/mod.rs +++ b/src/sema/tests/mod.rs @@ -542,7 +542,7 @@ contract creator { Child.new{accounts: metas}(payer); - Child.say_hello(); + Child.say_hello{accounts: []}(); } } diff --git a/src/sema/unused_variable.rs b/src/sema/unused_variable.rs index d07fc2c10..3c086a6db 100644 --- a/src/sema/unused_variable.rs +++ b/src/sema/unused_variable.rs @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::sema::ast::{ - Builtin, CallArgs, Diagnostic, EventDecl, Expression, Namespace, RetrieveType, + Builtin, CallArgs, Diagnostic, EventDecl, Expression, ExternalCallAccounts, Namespace, + RetrieveType, }; use crate::sema::symtable::{Symtable, VariableUsage}; use crate::sema::{ast, symtable}; @@ -272,7 +273,7 @@ fn check_call_args(ns: &mut Namespace, call_args: &CallArgs, symtable: &mut Symt if let Some(value) = &call_args.value { used_variable(ns, value.as_ref(), symtable); } - if let Some(accounts) = &call_args.accounts { + if let ExternalCallAccounts::Present(accounts) = &call_args.accounts { used_variable(ns, accounts.as_ref(), symtable); } if let Some(seeds) = &call_args.seeds { diff --git a/stdlib/solana.c b/stdlib/solana.c index 4001c6b4e..301c8b1af 100644 --- a/stdlib/solana.c +++ b/stdlib/solana.c @@ -55,35 +55,6 @@ uint64_t entrypoint(const uint8_t *input) return solang_dispatch(¶ms); } -uint64_t sol_invoke_signed_c(const SolInstruction *instruction, const SolAccountInfo *account_infos, - int account_infos_len, const SolSignerSeeds *signers_seeds, int signers_seeds_len); - -// Calls an external function when 'program_id' is NULL or -// creates a new contract and calls its constructor. -uint64_t external_call(uint8_t *input, uint32_t input_len, SolPubkey *program_id, const SolSignerSeeds *seeds, - int seeds_len, SolParameters *params) -{ - SolAccountMeta metas[10]; - SolInstruction instruction = { - .program_id = program_id, - .accounts = metas, - .account_len = params->ka_num, - .data = input, - .data_len = input_len, - }; - - // When the '{accounts: ...}' call argument is missing, we pass on all the accounts in the transaction. - for (int account_no = 0; account_no < params->ka_num; account_no++) - { - SolAccountInfo *acc = ¶ms->ka[account_no]; - metas[account_no].pubkey = acc->key; - metas[account_no].is_writable = acc->is_writable; - metas[account_no].is_signer = acc->is_signer; - } - - return sol_invoke_signed_c(&instruction, params->ka, params->ka_num, seeds, seeds_len); -} - uint64_t *sol_account_lamport(uint8_t *address, SolParameters *params) { SolPubkey *pubkey = (SolPubkey *)address; diff --git a/stdlib/solana_sdk.h b/stdlib/solana_sdk.h index b05ca101f..d4f47db34 100644 --- a/stdlib/solana_sdk.h +++ b/stdlib/solana_sdk.h @@ -400,7 +400,7 @@ typedef struct */ typedef struct { - const SolSignerSeed *addr; /** An arry of a signer's seeds */ + const SolSignerSeed *addr; /** An array of a signer's seeds */ uint64_t len; /** Number of seeds */ } SolSignerSeeds; diff --git a/tests/codegen_testcases/solidity/import_ext_call.sol b/tests/codegen_testcases/solidity/import_ext_call.sol index 29c9c67a4..ac91d307f 100644 --- a/tests/codegen_testcases/solidity/import_ext_call.sol +++ b/tests/codegen_testcases/solidity/import_ext_call.sol @@ -4,7 +4,7 @@ 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 { + function get_b(address id) external 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"); diff --git a/tests/codegen_testcases/solidity/solana_payer_account.sol b/tests/codegen_testcases/solidity/solana_payer_account.sol index 5a1d647f0..85b08d999 100644 --- a/tests/codegen_testcases/solidity/solana_payer_account.sol +++ b/tests/codegen_testcases/solidity/solana_payer_account.sol @@ -8,7 +8,7 @@ contract Builder { Built.new("my_seed"); } - function call_that() public view { + function call_that() external view { Built.say_this("Hold up! I'm calling!"); } } diff --git a/tests/contract_testcases/polkadot/annotations/solana_annotations.sol b/tests/contract_testcases/polkadot/annotations/solana_annotations.sol new file mode 100644 index 000000000..f33626111 --- /dev/null +++ b/tests/contract_testcases/polkadot/annotations/solana_annotations.sol @@ -0,0 +1,15 @@ +contract Test1 { + @account(foo) + @mutableAccount(bar) + @signer(signerFoo) + @mutableSigner(signerBar) + function doThis() external returns (uint64) { + return 64; + } +} + +// ---- Expect: diagnostics ---- +// error: 2:5-18: unknown annotation account for function +// error: 3:5-25: unknown annotation mutableAccount for function +// error: 4:5-23: unknown annotation signer for function +// error: 5:5-30: unknown annotation mutableSigner for function \ No newline at end of file diff --git a/tests/contract_testcases/solana/abstract_interface.sol b/tests/contract_testcases/solana/abstract_interface.sol index e09de85b4..84313c80f 100644 --- a/tests/contract_testcases/solana/abstract_interface.sol +++ b/tests/contract_testcases/solana/abstract_interface.sol @@ -8,3 +8,4 @@ contract C { } // ---- Expect: diagnostics ---- +// error: 6:3-25: 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/accounts/account_collision.sol b/tests/contract_testcases/solana/accounts/account_collision.sol new file mode 100644 index 000000000..c468932e8 --- /dev/null +++ b/tests/contract_testcases/solana/accounts/account_collision.sol @@ -0,0 +1,26 @@ +contract Test1 { + @account(foo) + @mutableAccount(bar) + @signer(signerFoo) + @mutableSigner(signerBar) + function doThis() external returns (uint64) { + assert(tx.accounts.signerFoo.is_signer); + assert(tx.accounts.signerBar.is_signer); + + return tx.accounts.foo.lamports; + } +} + +contract Test2 { + @account(t1Id) + @account(foo) + function callThat() external returns (uint64) { + uint64 res = Test1.doThis{program_id: tx.accounts.t1Id.key}(); + return res; + } +} + +// ---- Expect: diagnostics ---- +// warning: 6:5-48: function can be declared 'view' +// error: 16:5-18: 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 2:5-18: other declaration \ No newline at end of file diff --git a/tests/contract_testcases/solana/accounts/accounts_required.sol b/tests/contract_testcases/solana/accounts/accounts_required.sol new file mode 100644 index 000000000..73dfc3b59 --- /dev/null +++ b/tests/contract_testcases/solana/accounts/accounts_required.sol @@ -0,0 +1,22 @@ +@program_id("Ex9GgvN2ypqwFRGnsSZfAnXAnw5eRPDHyqRFnDWugxrb") +contract Test1 { + function doThis() external returns (uint64) { + return 3; + } +} + +contract Test2 { + function callThat() public returns (uint64) { + uint64 res = Test1.doThis(); + return res; + } + + function callThat2() public returns (uint64) { + // This is correct + uint64 res = Test1.doThis{accounts: []}(); + return res; + } +} + +// ---- Expect: diagnostics ---- +// error: 10:22-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 diff --git a/tests/contract_testcases/solana/accounts/incorrect_annotations.sol b/tests/contract_testcases/solana/accounts/incorrect_annotations.sol new file mode 100644 index 000000000..a0ab67749 --- /dev/null +++ b/tests/contract_testcases/solana/accounts/incorrect_annotations.sol @@ -0,0 +1,29 @@ +contract Test1 { + + uint32 g; + @account(foo) + @mutableAccount(bar) + function doThis() public returns (uint64) { + + return tx.accounts.foo.lamports; + } + + @account(32) + @signer("Hello") + function invalid_paramter() external view returns (uint32) { + return g; + } + + @bar(foo) + function invalid_annotation() public view returns (uint32) { + return g; + } +} + +// ---- Expect: diagnostics ---- +// error: 4:5-18: account declarations are only valid in functions declared as external +// error: 5:5-25: account declarations are only valid in functions declared as external +// error: 8:28-31: unrecognized account +// error: 11:5-17: invalid parameter for annotation +// error: 12:5-21: invalid parameter for annotation +// error: 17:5-14: unknown annotation bar for function \ No newline at end of file diff --git a/tests/contract_testcases/solana/accounts/repated_declaration.sol b/tests/contract_testcases/solana/accounts/repated_declaration.sol new file mode 100644 index 000000000..48112660e --- /dev/null +++ b/tests/contract_testcases/solana/accounts/repated_declaration.sol @@ -0,0 +1,15 @@ +contract Test0 { + @account(foo) + @mutableAccount(foo) + @signer(signerFoo) + @mutableSigner(signerFoo) + function doThis() external returns (uint64) { + return 64; + } +} + +// ---- Expect: diagnostics ---- +// error: 3:21-24: account 'foo' already defined +// note 2:5-18: previous definition +// error: 5:20-29: account 'signerFoo' already defined +// note 4:5-23: previous definition diff --git a/tests/contract_testcases/solana/annotations/abstract_function.sol b/tests/contract_testcases/solana/annotations/abstract_function.sol new file mode 100644 index 000000000..e92ca27a2 --- /dev/null +++ b/tests/contract_testcases/solana/annotations/abstract_function.sol @@ -0,0 +1,37 @@ + +abstract contract Base1 { + @account(acc1) + function test(uint64 val) virtual external {} +} + +abstract contract Base2 { + @mutableAccount(acc2) + function test(uint64 val) virtual external {} +} + +contract Derived1 is Base1 { + bool b; + @signer(other) + function test(uint64 val) override (Base1) external { + b = (tx.accounts.dataAccount.key == address(this)); + } +} + +contract Derived2 is Base1, Base2 { + bool b; + @account(acc1) + function test(uint64 val) override(Base1, Base2) external { + b = true; + } +} + +// ---- Expect: diagnostics ---- +// error: 4:5-47: functions must have the same declared accounts for correct overriding +// note 3:5-19: corresponding account 'acc1' is missing +// note 8:5-26: corresponding account 'acc2' is missing +// error: 15:5-56: functions must have the same declared accounts for correct overriding +// note 14:5-19: corresponding account 'other' is missing +// note 3:5-19: corresponding account 'acc1' is missing +// error: 23:5-62: functions must have the same declared accounts for correct overriding +// note 22:5-19: corresponding account 'acc1' is missing +// note 8:5-26: corresponding account 'acc2' is missing diff --git a/tests/contract_testcases/solana/annotations/accounts_on_interface.sol b/tests/contract_testcases/solana/annotations/accounts_on_interface.sol new file mode 100644 index 000000000..ed9d31104 --- /dev/null +++ b/tests/contract_testcases/solana/annotations/accounts_on_interface.sol @@ -0,0 +1,22 @@ + +interface Foo { + @signer(acc1) + @account(acc2) + function Bar() external; +} + +contract Derived is Foo { + + bool b; + @mutableSigner(acc1) + @mutableAccount(acc2) + function Bar() external { + b = false; + } +} + +// ---- Expect: diagnostics ---- +// error: 11:5-25: account 'acc1' must be declared with the same annotation for overriding +// note 3:5-18: location of other declaration +// error: 12:5-26: account 'acc2' must be declared with the same annotation for overriding +// note 4:5-19: location of other declaration diff --git a/tests/contract_testcases/solana/annotations/constructor_external_function.sol b/tests/contract_testcases/solana/annotations/constructor_external_function.sol index ded075f49..cc389d652 100644 --- a/tests/contract_testcases/solana/annotations/constructor_external_function.sol +++ b/tests/contract_testcases/solana/annotations/constructor_external_function.sol @@ -28,3 +28,4 @@ contract Bar { // ---- Expect: diagnostics ---- // 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 +// error: 24:9-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 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 22645e42f..5c69fbe4f 100644 --- a/tests/contract_testcases/solana/call/call_args_three_ways.sol +++ b/tests/contract_testcases/solana/call/call_args_three_ways.sol @@ -5,7 +5,7 @@ contract C { (D.new){value: 1}(); D.new{value: 1}(); } - function g() public { + function g() external { // Three different parse tree for callargs D.func{value: 1}(); (D.func){value: 1}(); @@ -16,7 +16,7 @@ contract C { @program_id("A2tWahcQqU7Mic5o4nGWPKt9rQaLVyh7cyF4MmCXksJt") contract D { constructor() payable {} - function func() payable public {} + function func() payable external {} } // ---- Expect: diagnostics ---- diff --git a/tests/contract_testcases/solana/constant/not_constant.sol b/tests/contract_testcases/solana/constant/not_constant.sol index 2544c6f59..659814bc1 100644 --- a/tests/contract_testcases/solana/constant/not_constant.sol +++ b/tests/contract_testcases/solana/constant/not_constant.sol @@ -4,7 +4,7 @@ } contract foo { - function f() public returns (uint) { + function f() external returns (uint) { uint a = C.STATIC(); return a; } 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 3bf2f8cac..279c39032 100644 --- a/tests/contract_testcases/solana/destructure_assign_struct_member_2.sol +++ b/tests/contract_testcases/solana/destructure_assign_struct_member_2.sol @@ -13,7 +13,7 @@ contract Contract { uint256 b; } - function test(address[] memory _tokens) public view { + function test(address[] memory _tokens) external view { uint size = 3; // get shares and eth required for each share diff --git a/tests/contract_testcases/solana/empty_vector.sol b/tests/contract_testcases/solana/empty_vector.sol new file mode 100644 index 000000000..8eb68c3b1 --- /dev/null +++ b/tests/contract_testcases/solana/empty_vector.sol @@ -0,0 +1,25 @@ +contract Test { + function testThis() public returns (uint64[]) { + uint64[] var = []; + return var; + } + + function initialize() public returns (string[2]) { + string[2] st = []; + return st; + } + + function callThat() public returns (uint32) { + changeThis([]); + return 2; + } + + function changeThis(uint32[] var) private view { + var[2] = 5; + } +} + +// ---- Expect: diagnostics ---- +// error: 3:24-26: array requires at least one element +// error: 8:24-26: array requires at least one element +// error: 13:20-22: array requires at least one element \ No newline at end of file diff --git a/tests/contract_testcases/solana/functions/external_functions.sol b/tests/contract_testcases/solana/functions/external_functions.sol index fe3adb0a6..9f5a13016 100644 --- a/tests/contract_testcases/solana/functions/external_functions.sol +++ b/tests/contract_testcases/solana/functions/external_functions.sol @@ -30,7 +30,7 @@ contract bar2 is bar1 { return hello({b: g, a: f}); } - function test4(int c, int d) public returns (int) { + function test4(int c, int d) external returns (int) { // This is allowed return this.this_is_external(c, d) + this.hello(d, c); } @@ -42,6 +42,7 @@ contract bar2 is bar1 { } // ---- Expect: diagnostics ---- +// error: 4:12-55: 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:16-27: functions declared external cannot be called via an internal function call // note 19:5-61: declaration of function 'hello' // error: 30:16-35: functions declared external cannot be called via an internal function call diff --git a/tests/contract_testcases/solana/garbage_function_args.sol b/tests/contract_testcases/solana/garbage_function_args.sol index 17bf79ba4..4fda5b833 100644 --- a/tests/contract_testcases/solana/garbage_function_args.sol +++ b/tests/contract_testcases/solana/garbage_function_args.sol @@ -9,10 +9,10 @@ contract c { function g() public { g({x: foo>1}); } - function g(int) public { + function g(int) external { this.g(oo); } - function g(bool) public { + function g(bool) external { this.g({x: foo>1}); } } diff --git a/tests/contract_testcases/solana/type_decl_import.sol b/tests/contract_testcases/solana/type_decl_import.sol index 5d57915ac..aafbffe9e 100644 --- a/tests/contract_testcases/solana/type_decl_import.sol +++ b/tests/contract_testcases/solana/type_decl_import.sol @@ -10,4 +10,5 @@ contract d { } // ---- Expect: diagnostics ---- +// error: 8:3-8: 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 // warning: 7:2-33: function can be declared 'pure' diff --git a/tests/solana_tests/abi_encode.rs b/tests/solana_tests/abi_encode.rs index bc1924058..1ed830949 100644 --- a/tests/solana_tests/abi_encode.rs +++ b/tests/solana_tests/abi_encode.rs @@ -1003,8 +1003,10 @@ contract caller { return b + 3; } - function do_call(address pid) view public returns (int64, int32) { - return (this.doThis{program_id: pid}(5), this.doThat{program_id: pid}(3)); + @account(pid) + function do_call() view external returns (int64, int32) { + return (this.doThis{program_id: tx.accounts.pid.key, accounts: []}(5), + this.doThat{program_id: tx.accounts.pid.key, accounts: []}(3)); } }"#, ); @@ -1017,11 +1019,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), - ]) + .accounts(vec![("systemProgram", [0; 32]), ("pid", caller_program_id)]) .call() .unwrap() .unwrap_tuple(); diff --git a/tests/solana_tests/accessor.rs b/tests/solana_tests/accessor.rs index 85d4a472f..c89d455dd 100644 --- a/tests/solana_tests/accessor.rs +++ b/tests/solana_tests/accessor.rs @@ -262,10 +262,13 @@ fn struct_accessor() { m[1023413412] = S({f1: 414243, f2: true, f3: E("niff")}); } - function f(address pid) public view { + @account(pid) + function f() external view { AccountMeta[1] meta = [ AccountMeta({pubkey: tx.accounts.dataAccount.key, is_writable: false, is_signer: false}) ]; + + address pid = tx.accounts.pid.key; (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, program_id: pid}(99); @@ -285,11 +288,10 @@ fn struct_accessor() { 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), + ("pid", program_id), ]) .call(); } diff --git a/tests/solana_tests/account_access.rs b/tests/solana_tests/account_access.rs index 765d69f6e..79617153d 100644 --- a/tests/solana_tests/account_access.rs +++ b/tests/solana_tests/account_access.rs @@ -121,3 +121,80 @@ contract hatchling { assert_eq!(res.unwrap(), 2); } + +#[test] +fn accounts_on_constructors() { + let mut vm = build_solidity( + r#" + contract Test { + @payer(my_payer) + @account(acc1) + @mutableAccount(acc2) + @signer(acc3) + @mutableSigner(acc4) + constructor () { + assert(tx.accounts.acc3.is_signer); + assert(tx.accounts.acc4.is_signer); + + assert(tx.accounts.acc1.lamports == 5); + + tx.accounts.acc2.lamports -= 7; + tx.accounts.acc4.lamports += 7; + } +} + "#, + ); + + let data_account = vm.initialize_data_account(); + let acc1 = account_new(); + let acc2 = account_new(); + let acc3 = account_new(); + let acc4 = account_new(); + let my_payer = account_new(); + + vm.account_data.insert( + acc1, + AccountState { + data: vec![], + owner: None, + lamports: 5, + }, + ); + + vm.account_data.insert( + acc2, + AccountState { + data: vec![], + owner: None, + lamports: 8, + }, + ); + + vm.account_data.insert(acc3, AccountState::default()); + + vm.account_data.insert( + acc4, + AccountState { + data: vec![], + owner: None, + lamports: 7, + }, + ); + + vm.account_data.insert(my_payer, AccountState::default()); + + vm.function("new") + .accounts(vec![ + ("dataAccount", data_account), + ("acc1", acc1), + ("acc2", acc2), + ("acc3", acc3), + ("acc4", acc4), + ("my_payer", my_payer), + ("systemProgram", [0; 32]), + ]) + .call(); + + assert_eq!(vm.account_data[&acc2].lamports, 1); + assert_eq!(vm.account_data[&acc4].lamports, 14); +} diff --git a/tests/solana_tests/account_info.rs b/tests/solana_tests/account_info.rs index 2d3d16109..897905161 100644 --- a/tests/solana_tests/account_info.rs +++ b/tests/solana_tests/account_info.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -use crate::{account_new, build_solidity, AccountMeta, AccountState, BorshToken, Pubkey}; +use crate::{account_new, build_solidity, AccountState, BorshToken}; use num_bigint::BigInt; #[test] @@ -9,20 +9,16 @@ fn lamports() { r#" import 'solana'; contract c { - function test(address needle) public payable returns (uint64) { - for (uint32 i = 0; i < tx.accounts.length; i++) { - AccountInfo ai = tx.accounts[i]; - assert(ai.is_writable); - assert(!ai.is_signer); - assert(ai.executable); + @mutableAccount(needle) + function test() external payable returns (uint64) { + AccountInfo ai = tx.accounts.needle; - if (ai.key == needle) { - return ai.lamports; - } - } + assert(ai.is_writable); + assert(!ai.is_signer); + assert(ai.executable); - revert("account not found"); + return ai.lamports; } }"#, ); @@ -44,12 +40,7 @@ fn lamports() { let returns = vm .function("test") - .arguments(&[BorshToken::Address(acc)]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(acc), - is_writable: true, - is_signer: false, - }]) + .accounts(vec![("needle", acc)]) .call() .unwrap(); @@ -168,10 +159,14 @@ fn modify_lamports() { import 'solana'; contract starter { - function createNewAccount(uint64 lamport1, uint64 lamport2, uint64 lamport3) public { - AccountInfo acc1 = tx.accounts[0]; - AccountInfo acc2 = tx.accounts[1]; - AccountInfo acc3 = tx.accounts[2]; + + @mutableAccount(acc1) + @mutableAccount(acc2) + @mutableAccount(acc3) + function createNewAccount(uint64 lamport1, uint64 lamport2, uint64 lamport3) external { + AccountInfo acc1 = tx.accounts.acc1; + AccountInfo acc2 = tx.accounts.acc2; + AccountInfo acc3 = tx.accounts.acc3; acc1.lamports -= lamport1; acc2.lamports = lamport2; @@ -214,24 +209,6 @@ contract starter { }, ); - let metas = vec![ - AccountMeta { - pubkey: Pubkey(acc1), - is_writable: true, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(acc2), - is_writable: true, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(acc3), - is_writable: true, - is_signer: false, - }, - ]; - let _ = vm .function("createNewAccount") .arguments(&[ @@ -248,7 +225,7 @@ contract starter { value: BigInt::from(9u8), }, ]) - .remaining_accounts(&metas) + .accounts(vec![("acc1", acc1), ("acc2", acc2), ("acc3", acc3)]) .call(); assert_eq!(vm.account_data.get(&acc1).unwrap().lamports, 5); @@ -263,8 +240,10 @@ fn account_data() { import 'solana'; contract C { + + @mutableAccount(acc) function test() external { - AccountInfo ai = tx.accounts[0]; + AccountInfo ai = tx.accounts.acc; ai.data[0] = 0xca; ai.data[1] = 0xff; ai.data[2] = 0xee; @@ -290,11 +269,7 @@ contract C { ); vm.function("test") - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(other_account), - is_writable: true, - is_signer: false, - }]) + .accounts(vec![("acc", other_account)]) .call(); assert_eq!(vm.account_data[&other_account].data[0], 0xca); diff --git a/tests/solana_tests/call.rs b/tests/solana_tests/call.rs index df63300b4..2b9f47f70 100644 --- a/tests/solana_tests/call.rs +++ b/tests/solana_tests/call.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - build_solidity, create_program_address, AccountMeta, AccountState, BorshToken, Instruction, - Pubkey, VirtualMachine, + build_solidity, create_program_address, AccountState, BorshToken, Instruction, Pubkey, + VirtualMachine, }; use base58::FromBase58; use num_bigint::BigInt; @@ -17,8 +17,9 @@ fn simple_external_call() { print("bar0 says: " + v); } - function test_other(address x) public { - bar1.test_bar{program_id: x}("cross contract call"); + @account(pid) + function test_other() external { + bar1.test_bar{program_id: tx.accounts.pid.key}("cross contract call"); } } @@ -59,23 +60,7 @@ fn simple_external_call() { vm.logs.truncate(0); vm.function("test_other") - .accounts(vec![ - ("bar1_programId", bar1_program_id), - ("systemProgram", [0; 32]), - ]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(bar1_account), - is_writable: false, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(bar1_program_id), - is_signer: false, - is_writable: false, - }, - ]) - .arguments(&[BorshToken::Address(bar1_program_id)]) + .accounts(vec![("pid", bar1_program_id), ("systemProgram", [0; 32])]) .call(); assert_eq!(vm.logs, "bar1 says: cross contract call"); @@ -86,8 +71,9 @@ fn external_call_with_returns() { let mut vm = build_solidity( r#" contract bar0 { - function test_other(address x) public returns (int64) { - return bar1.test_bar{program_id: x}(7) + 5; + @account(pid) + function test_other() external returns (int64) { + return bar1.test_bar{program_id: tx.accounts.pid.key}(7) + 5; } } @@ -130,23 +116,7 @@ fn external_call_with_returns() { let res = vm .function("test_other") - .arguments(&[BorshToken::Address(bar1_program_id)]) - .accounts(vec![ - ("bar1_programId", bar1_program_id), - ("systemProgram", [0; 32]), - ]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(bar1_account), - is_writable: false, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(bar1_program_id), - is_signer: false, - is_writable: false, - }, - ]) + .accounts(vec![("pid", bar1_program_id), ("systemProgram", [0; 32])]) .call() .unwrap(); @@ -166,11 +136,12 @@ fn external_raw_call_with_returns() { contract bar0 { bytes8 private constant SELECTOR = bytes8(sha256(bytes('global:test_bar'))); - function test_other(address x) public returns (int64) { + @account(bar1_pid) + function test_other() external 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"); - (, bytes raw) = address(x).call(signature); + (, bytes raw) = tx.accounts.bar1_pid.key.call{accounts: []}(signature); (int64 v) = abi.decode(raw, (int64)); return v + 5; } @@ -215,19 +186,9 @@ fn external_raw_call_with_returns() { let res = vm .function("test_other") - .arguments(&[BorshToken::Address(bar1_program_id)]) - .accounts(vec![("systemProgram", [0; 32])]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(bar1_account), - is_writable: false, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(bar1_program_id), - is_signer: false, - is_writable: false, - }, + .accounts(vec![ + ("bar1_pid", bar1_program_id), + ("systemProgram", [0; 32]), ]) .call() .unwrap(); @@ -254,7 +215,7 @@ fn call_external_func_type() { function doTest() public view returns (int, int) { function(int) external pure returns (int, int) sfPtr = this.testPtr; - (int a, int b) = sfPtr(2); + (int a, int b) = sfPtr{accounts: []}(2); return (a, b); } } @@ -293,15 +254,17 @@ fn external_call_with_string_returns() { let mut vm = build_solidity( r#" contract bar0 { - function test_other(address x) public returns (string) { - string y = bar1.test_bar{program_id: x}(7); + @account(pid) + function test_other() external returns (string) { + string y = bar1.test_bar{program_id: tx.accounts.pid.key}(7); print(y); return y; } - function test_this(address x) public { - address a = bar1.who_am_i{program_id: x}(); - assert(a == address(x)); + @account(pid) + function test_this() external { + address a = bar1.who_am_i{program_id: tx.accounts.pid.key}(); + assert(a == tx.accounts.pid.key); } } @@ -342,46 +305,14 @@ fn external_call_with_string_returns() { let res = vm .function("test_other") - .arguments(&[BorshToken::Address(bar1_program_id)]) - .accounts(vec![ - ("bar1_programId", bar1_program_id), - ("systemProgram", [0; 32]), - ]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(bar1_account), - is_writable: false, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(bar1_program_id), - is_signer: false, - is_writable: false, - }, - ]) + .accounts(vec![("pid", bar1_program_id), ("systemProgram", [0; 32])]) .call() .unwrap(); assert_eq!(res, BorshToken::String(String::from("foo:7"))); vm.function("test_this") - .arguments(&[BorshToken::Address(bar1_program_id)]) - .accounts(vec![ - ("bar1_programId", bar1_program_id), - ("systemProgram", [0; 32]), - ]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(bar1_account), - is_writable: false, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(bar1_program_id), - is_signer: false, - is_writable: false, - }, - ]) + .accounts(vec![("pid", bar1_program_id), ("systemProgram", [0; 32])]) .call(); } @@ -392,11 +323,12 @@ fn encode_call() { contract bar0 { bytes8 private constant SELECTOR = bytes8(sha256(bytes('global:test_bar'))); - function test_other(address x) public returns (int64) { + @account(bar1_pid) + function test_other() external returns (int64) { bytes select = abi.encodeWithSelector(SELECTOR, int64(7)); bytes signature = abi.encodeCall(bar1.test_bar, 7); require(select == signature, "must be the same"); - (, bytes raw) = address(x).call(signature); + (, bytes raw) = tx.accounts.bar1_pid.key.call{accounts: []}(signature); (int64 v) = abi.decode(raw, (int64)); return v + 5; } @@ -441,19 +373,9 @@ fn encode_call() { let res = vm .function("test_other") - .arguments(&[BorshToken::Address(bar1_program_id)]) - .accounts(vec![("systemProgram", [0; 32])]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(bar1_account), - is_writable: false, - is_signer: false, - }, - AccountMeta { - pubkey: Pubkey(bar1_program_id), - is_signer: false, - is_writable: false, - }, + .accounts(vec![ + ("bar1_pid", bar1_program_id), + ("systemProgram", [0; 32]), ]) .call() .unwrap(); diff --git a/tests/solana_tests/create_contract.rs b/tests/solana_tests/create_contract.rs index fb9037d22..e2dd46151 100644 --- a/tests/solana_tests/create_contract.rs +++ b/tests/solana_tests/create_contract.rs @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - account_new, build_solidity, create_program_address, Account, AccountMeta, AccountState, - BorshToken, Pubkey, + account_new, build_solidity, create_program_address, Account, AccountState, BorshToken, }; use base58::{FromBase58, ToBase58}; use num_bigint::BigInt; @@ -16,7 +15,7 @@ fn simple_create_contract_no_seed() { bar1.new("yo from bar0"); } - function call_bar1_at_address(string x) public { + function call_bar1_at_address(string x) external { bar1.say_hello(x); } } @@ -69,11 +68,6 @@ fn simple_create_contract_no_seed() { ("payer", payer), ("systemProgram", [0; 32]), ]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(acc), - is_writable: true, - is_signer: true, - }]) .call(); assert_eq!(vm.logs, "bar1 says: yo from bar0"); @@ -102,7 +96,7 @@ fn simple_create_contract() { bar1.new("yo from bar0"); } - function call_bar1_at_address(string x) public { + function call_bar1_at_address(string x) external { bar1.say_hello(x); } } @@ -275,7 +269,7 @@ fn missing_contract() { bar1.new("yo from bar0"); } - function call_bar1_at_address(string x) public { + function call_bar1_at_address(string x) external { bar1.say_hello(x); } } @@ -326,14 +320,18 @@ fn two_contracts() { import 'solana'; contract bar0 { - function test_other(address a, address b, address payer) external { + + @mutableSigner(a) + @mutableSigner(b) + @mutableSigner(payer) + function test_other() external { AccountMeta[2] bar1_metas = [ - AccountMeta({pubkey: a, is_writable: true, is_signer: true}), - AccountMeta({pubkey: payer, is_writable: true, is_signer: true}) + AccountMeta({pubkey: tx.accounts.a.key, is_writable: true, is_signer: true}), + AccountMeta({pubkey: tx.accounts.payer.key, is_writable: true, is_signer: true}) ]; AccountMeta[2] bar2_metas = [ - AccountMeta({pubkey: b, is_writable: true, is_signer: true}), - AccountMeta({pubkey: payer, is_writable: true, is_signer: true}) + AccountMeta({pubkey: tx.accounts.b.key, is_writable: true, is_signer: true}), + AccountMeta({pubkey: tx.accounts.payer.key, is_writable: true, is_signer: true}) ]; bar1.new{accounts: bar1_metas}("yo from bar0"); bar1.new{accounts: bar2_metas}("hi from bar0"); @@ -370,32 +368,13 @@ fn two_contracts() { vm.account_data.insert(payer, AccountState::default()); vm.function("test_other") - .arguments(&[ - BorshToken::Address(seed1.0), - BorshToken::Address(seed2.0), - BorshToken::Address(payer), - ]) .accounts(vec![ + ("a", seed1.0), + ("b", seed2.0), + ("payer", payer), ("systemProgram", [0; 32]), ("bar1_programId", program_id), ]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(seed1.0), - is_signer: true, - is_writable: true, - }, - AccountMeta { - pubkey: Pubkey(seed2.0), - is_signer: true, - is_writable: true, - }, - AccountMeta { - pubkey: Pubkey(payer), - is_signer: true, - is_writable: true, - }, - ]) .call(); assert_eq!(vm.logs, "bar1 says: yo from bar0bar1 says: hi from bar0"); @@ -660,11 +639,6 @@ fn create_child() { ("payer", payer), ("systemProgram", [0; 32]), ]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(seed.0), - is_signer: true, - is_writable: true, - }]) .call(); assert_eq!( @@ -680,16 +654,19 @@ fn create_child_with_meta() { import 'solana'; contract creator { - function create_child_with_meta(address child, address payer) public { + + @mutableSigner(child) + @mutableSigner(payer) + function create_child_with_meta() external { print("Going to create child"); AccountMeta[2] metas = [ - AccountMeta({pubkey: child, is_signer: true, is_writable: true}), - AccountMeta({pubkey: payer, is_signer: true, is_writable: true}) + AccountMeta({pubkey: tx.accounts.child.key, is_signer: true, is_writable: true}), + AccountMeta({pubkey: tx.accounts.payer.key, is_signer: true, is_writable: true}) // 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}) ]; Child.new{accounts: metas}(); - Child.say_hello(); + Child.say_hello{accounts: []}(); } } @@ -727,23 +704,12 @@ contract Child { .unwrap(); vm.function("create_child_with_meta") - .arguments(&[BorshToken::Address(seed.0), BorshToken::Address(payer)]) .accounts(vec![ ("Child_programId", child_program_id), + ("child", seed.0), + ("payer", payer), ("systemProgram", [0; 32]), ]) - .remaining_accounts(&[ - AccountMeta { - pubkey: Pubkey(seed.0), - is_signer: false, - is_writable: false, - }, - AccountMeta { - pubkey: Pubkey(payer), - is_signer: true, - is_writable: false, - }, - ]) .call(); assert_eq!( diff --git a/tests/solana_tests/metas.rs b/tests/solana_tests/metas.rs index fb2057c48..3c2ac53e3 100644 --- a/tests/solana_tests/metas.rs +++ b/tests/solana_tests/metas.rs @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -use crate::{ - account_new, build_solidity, build_solidity_with_cache, AccountMeta, AccountState, BorshToken, - Pubkey, -}; +use crate::{account_new, build_solidity, build_solidity_with_cache, AccountState, BorshToken}; use borsh::BorshSerialize; use num_bigint::BigInt; use solang::file_resolver::FileResolver; @@ -13,6 +10,7 @@ fn use_authority() { let mut vm = build_solidity(include_str!("../../docs/examples/solana/use_authority.sol")); let authority = account_new(); + let another_authority = account_new(); vm.account_data.insert( authority, @@ -23,6 +21,15 @@ fn use_authority() { }, ); + vm.account_data.insert( + another_authority, + AccountState { + data: vec![], + owner: Some([0; 32]), + lamports: 0, + }, + ); + let data_account = vm.initialize_data_account(); vm.function("new") .arguments(&[BorshToken::Address(authority)]) @@ -31,7 +38,10 @@ fn use_authority() { let res = vm .function("inc") - .accounts(vec![("dataAccount", data_account)]) + .accounts(vec![ + ("dataAccount", data_account), + ("authorityAccount", another_authority), + ]) .must_fail() .unwrap(); assert_ne!(res, 0); @@ -50,12 +60,10 @@ fn use_authority() { ); vm.function("inc") - .accounts(vec![("dataAccount", data_account)]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(authority), - is_signer: true, - is_writable: false, - }]) + .accounts(vec![ + ("dataAccount", data_account), + ("authorityAccount", authority), + ]) .call(); let res = vm @@ -83,8 +91,9 @@ fn token_account() { import './spl_token.sol'; contract Foo { - function token_account(address add) public returns (SplToken.TokenAccountData) { - return SplToken.get_token_account_data(add); + @account(add) + function token_account() external returns (SplToken.TokenAccountData) { + return SplToken.get_token_account_data(tx.accounts.add); } } "#; @@ -140,12 +149,7 @@ contract Foo { let res = vm .function("token_account") - .arguments(&[BorshToken::Address(account)]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(account), - is_signer: false, - is_writable: false, - }]) + .accounts(vec![("add", account)]) .call() .unwrap() .unwrap_tuple(); @@ -188,12 +192,7 @@ contract Foo { let res = vm .function("token_account") - .arguments(&[BorshToken::Address(account)]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(account), - is_signer: false, - is_writable: false, - }]) + .accounts(vec![("add", account)]) .call() .unwrap() .unwrap_tuple(); @@ -239,8 +238,9 @@ fn mint_account() { import './spl_token.sol'; contract Foo { - function mint_account(address add) public returns (SplToken.MintAccountData) { - return SplToken.get_mint_account_data(add); + @account(add) + function mint_account() external returns (SplToken.MintAccountData) { + return SplToken.get_mint_account_data(tx.accounts.add); } } "#; @@ -287,12 +287,7 @@ contract Foo { let res = vm .function("mint_account") - .arguments(&[BorshToken::Address(account)]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(account), - is_writable: false, - is_signer: false, - }]) + .accounts(vec![("add", account)]) .call() .unwrap() .unwrap_tuple(); @@ -324,12 +319,7 @@ contract Foo { let res = vm .function("mint_account") - .arguments(&[BorshToken::Address(account)]) - .remaining_accounts(&[AccountMeta { - pubkey: Pubkey(account), - is_writable: false, - is_signer: false, - }]) + .accounts(vec![("add", account)]) .call() .unwrap() .unwrap_tuple();