Explorar o código

Refactor payer annotation (#1345)

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>
Lucas Steuernagel %!s(int64=2) %!d(string=hai) anos
pai
achega
e93a136dfd
Modificáronse 46 ficheiros con 1005 adicións e 323 borrados
  1. 1 1
      docs/examples/solana/constructor_annotations.sol
  2. 1 1
      docs/examples/solana/contract_address.sol
  3. 1 1
      docs/examples/solana/contract_new.sol
  4. 48 0
      docs/examples/solana/payer_annotation.sol
  5. 1 1
      docs/examples/solana/program_id.sol
  6. 4 0
      docs/language/contracts.rst
  7. 24 2
      docs/targets/solana.rst
  8. 1 0
      integration/solana/MyCreature.key
  9. 28 10
      integration/solana/create_contract.sol
  10. 40 10
      integration/solana/create_contract.spec.ts
  11. 0 7
      integration/solana/runtime_errors.sol
  12. 16 44
      integration/solana/runtime_errors.spec.ts
  13. 11 7
      integration/solana/setup.ts
  14. 91 5
      src/abi/tests.rs
  15. 8 1
      src/bin/languageserver/mod.rs
  16. 8 1
      src/codegen/cfg.rs
  17. 2 0
      src/codegen/constant_folding.rs
  18. 1 0
      src/codegen/constructor.rs
  19. 10 2
      src/codegen/mod.rs
  20. 84 89
      src/codegen/solana_accounts/account_collection.rs
  21. 186 0
      src/codegen/solana_accounts/account_management.rs
  22. 61 0
      src/codegen/solana_accounts/mod.rs
  23. 25 42
      src/codegen/solana_deploy.rs
  24. 2 0
      src/codegen/subexpression_elimination/instruction.rs
  25. 1 0
      src/codegen/subexpression_elimination/tests.rs
  26. 1 0
      src/emit/instructions.rs
  27. 4 6
      src/emit/solana/target.rs
  28. 8 5
      src/sema/ast.rs
  29. 6 2
      src/sema/dotgraphviz.rs
  30. 15 0
      src/sema/expression/function_call.rs
  31. 49 20
      src/sema/function_annotation.rs
  32. 1 0
      src/sema/mod.rs
  33. 65 0
      src/sema/solana_accounts.rs
  34. 1 1
      src/sema/tests/mod.rs
  35. 2 2
      tests/codegen_testcases/solidity/constructor_with_metas.sol
  36. 31 0
      tests/codegen_testcases/solidity/solana_payer_account.sol
  37. 1 1
      tests/codegen_testcases/solidity/unused_variable_elimination.sol
  38. 27 0
      tests/contract_testcases/solana/annotations/account_name_collision.sol
  39. 0 0
      tests/contract_testcases/solana/annotations/annotations_bad.sol
  40. 72 0
      tests/contract_testcases/solana/annotations/bad_accounts.sol
  41. 27 0
      tests/contract_testcases/solana/annotations/constructor_external_function.sol
  42. 4 4
      tests/contract_testcases/solana/annotations/constructor_seeds.sol
  43. 0 0
      tests/contract_testcases/solana/annotations/constructor_seeds_bad.sol
  44. 4 3
      tests/solana.rs
  45. 24 30
      tests/solana_tests/create_contract.rs
  46. 8 25
      tests/solana_tests/runtime_errors.rs

+ 1 - 1
docs/examples/solana/constructor_annotations.sol

@@ -6,7 +6,7 @@ contract Foo {
     @seed(seed_val)
     @bump(bump_val)
     @payer(payer)
-    constructor(address payer, bytes seed_val, bytes1 bump_val) {
+    constructor(bytes seed_val, bytes1 bump_val) {
         // ...
     }
 }

+ 1 - 1
docs/examples/solana/contract_address.sol

@@ -9,7 +9,7 @@ contract hatchling {
 }
 
 contract adult {
-    function test(address addr) public {
+    function test(address addr) external {
         hatchling h = new hatchling{address: addr}("luna");
     }
 }

+ 1 - 1
docs/examples/solana/contract_new.sol

@@ -15,7 +15,7 @@ contract hatchling {
 }
 
 contract creator {
-    function create_hatchling(address new_address) public {
+    function create_hatchling(address new_address) external {
         hatchling h;
        
 	h = new hatchling{address: new_address}("luna", address(this));

+ 48 - 0
docs/examples/solana/payer_annotation.sol

@@ -0,0 +1,48 @@
+import 'solana';
+
+@program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER")
+contract Builder {
+    BeingBuilt other;
+    function build_this(address addr) external {
+        // When calling a constructor from an external function, the only call argument needed
+        // is the data account. The compiler automatically passes the necessary accounts to the call.
+        other = new BeingBuilt{address: addr}("my_seed");
+    }
+
+    function build_that(address data_account, address payer_account) public {
+        // In non-external functions, developers need to manually create the account metas array.
+        // The order of the accounts must match the order from the BeingBuilt IDL file for the "new"
+        // instruction.
+        AccountMeta[3] metas = [
+            AccountMeta({
+                pubkey: data_account, 
+                is_signer: true, 
+                is_writable: true
+                }),
+            AccountMeta({
+                pubkey: payer_account, 
+                is_signer: true, 
+                is_writable: true
+                }),
+            AccountMeta({
+                pubkey: address"11111111111111111111111111111111", 
+                is_writable: false,
+                is_signer: false
+                })
+        ];
+        other = new BeingBuilt{accounts: metas}("my_seed");
+    }
+}
+
+
+@program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
+contract BeingBuilt {
+    @seed(my_seed)
+    @space(1024)
+    @payer(payer_account)
+    constructor(bytes my_seed) {}
+
+    function say_this(string text) public pure {
+        print(text);
+    }
+}

+ 1 - 1
docs/examples/solana/program_id.sol

@@ -8,7 +8,7 @@ contract Foo {
 contract Bar {
     Foo public foo;
 
-    function create_foo(address new_address) public {
+    function create_foo(address new_address) external {
         foo = new Foo{address: new_address}();
     }
 

+ 4 - 0
docs/language/contracts.rst

@@ -102,6 +102,10 @@ by using the call argument ``address``:
 .. include:: ../examples/solana/contract_address.sol
   :code: solidity
 
+When the contract's data account is passed through the ``address`` call argument, the compiler will automatically create
+the ``AccountMeta`` array the constructor call needs. Due to the impossibility to track account ordering in
+private, internal and public functions, such a call argument is only allowed in external functions.
+
 Alternatively, the data account to be initialized can be provided using the ``accounts`` call argument. In this case,
 one needs to instantiate a fixed length array of type ``AccountMeta`` to pass to the call. The array must contain all
 the accounts the transaction is going to need, in addition to the data account to be initialized.

+ 24 - 2
docs/targets/solana.rst

@@ -236,8 +236,7 @@ arguments can be used in the annotations.
 
 Creating an account needs a payer, so at a minimum the ``@payer`` annotation must be
 specified. If it is missing, then the data account must be created client-side.
-The ``@payer`` requires an address. This can be a constructor argument or
-an address literal.
+The ``@payer`` annotation declares a Solana account that must be passed in the transaction.
 
 The size of the data account can be specified with ``@space``. This is a
 ``uint64`` expression which can either be a constant or use one of the constructor
@@ -446,3 +445,26 @@ The usage of system instructions needs the correct setting of writable and signe
 contracts on chain. Examples are available on Solang's integration tests.
 See `system_instruction_example.sol <https://github.com/hyperledger/solang/blob/main/integration/solana/system_instruction_example.sol>`_
 and `system_instruction.spec.ts <https://github.com/hyperledger/solang/blob/main/integration/solana/system_instruction.spec.ts>`_
+
+
+Solana Account Management
+_________________________
+
+In a contract constructor, one can optionally write the ``@payer`` annotation, which receives a character sequence as
+an argument. This annotation defines a Solana account that is going to pay for the initialization of the contract's data
+account. The syntax ``@payer(my_account)`` declares an account named ``my_account``, which will be
+required for every call to the constructor.
+
+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.
+
+The following example shows two correct ways of calling a constructor. 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.
+
+.. include:: ../examples/solana/payer_annotation.sol
+  :code: solidity

+ 1 - 0
integration/solana/MyCreature.key

@@ -0,0 +1 @@
+[142,133,49,70,40,211,189,33,145,137,236,147,103,244,109,168,121,51,95,90,157,169,250,130,147,236,48,186,86,232,131,133,114,29,179,43,64,68,123,172,216,9,188,236,189,2,240,61,50,213,75,83,51,86,132,242,189,155,182,170,210,173,98,33]

+ 28 - 10
integration/solana/create_contract.sol

@@ -4,23 +4,25 @@ contract creator {
     Child public c;
     Child public c_metas;
 
-    function create_child(address child, address payer) public {
+    function create_child(address child) external {
         print("Going to create child");
-        c = new Child{address: child}(payer);
+        c = new Child{address: child}();
 
         c.say_hello();
     }
 
-    function create_seed1(address child, address payer, bytes seed, bytes1 bump, uint64 space) public {
+    function create_seed1(address child, bytes seed, bytes1 bump, uint64 space) external {
         print("Going to create Seed1");
-        Seed1 s = new Seed1{address: child}(payer, seed, bump, space);
+        Seed1 s = new Seed1{address: child}(seed, bump, space);
 
         s.say_hello();
     }
 
-    function create_seed2(address child, address payer, bytes seed, uint32 space) public {
+    function create_seed2(address child, bytes seed, uint32 space) external {
         print("Going to create Seed2");
-        new Seed2{address: child}(payer, seed, space);
+
+        new Seed2{address: child}(seed, space);
+
     }
 
     function create_child_with_metas(address child, address payer) public {
@@ -31,16 +33,21 @@ contract creator {
             AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false})
         ];
 
-        c_metas = new Child{accounts: metas}(payer);        
+        c_metas = new Child{accounts: metas}();        
         c_metas.use_metas();
     }
+
+    function create_without_annotation(address child) external {
+        MyCreature cc = new MyCreature{address: child}();
+        cc.say_my_name();
+    }
 }
 
 @program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT")
 contract Child {
     @payer(payer)
     @space(511 + 7)
-    constructor(address payer) {
+    constructor() {
         print("In child constructor");
     }
 
@@ -60,7 +67,7 @@ contract Seed1 {
     @seed(seed)
     @bump(bump)
     @space(space)
-    constructor(address payer, bytes seed, bytes1 bump, uint64 space) {
+    constructor(bytes seed, bytes1 bump, uint64 space) {
         print("In Seed1 constructor");
     }
 
@@ -77,7 +84,7 @@ contract Seed2 {
     @seed("sunflower")
     @seed(seed)
     @space(space + 23)
-    constructor(address payer, bytes seed, uint64 space) {
+    constructor(bytes seed, uint64 space) {
         my_seed = seed;
 
         print("In Seed2 constructor");
@@ -90,4 +97,15 @@ contract Seed2 {
             print("I am PDA.");
         }
     }
+}
+
+@program_id("8gTkAidfM82u3DGbKcZpHwL5p47KQA16MDb4WmrHdmF6")
+contract MyCreature {
+    constructor() {
+        print("In child constructor");
+    }
+
+    function say_my_name() public pure {
+        print("say_my_name");
+    }
 }

+ 40 - 10
integration/solana/create_contract.spec.ts

@@ -3,7 +3,7 @@
 import { Connection, Keypair, PublicKey, sendAndConfirmTransaction, SystemProgram, Transaction } from '@solana/web3.js';
 import expect from 'expect';
 import { Program, Provider, BN } from '@project-serum/anchor';
-import { loadContract } from './setup';
+import {create_account, loadContract} from './setup';
 import fs from 'fs';
 
 describe('ChildContract', function () {
@@ -22,12 +22,14 @@ describe('ChildContract', function () {
         let child_program = new PublicKey("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT");
         let child = Keypair.generate();
 
-        const signature = await program.methods.createChild(child.publicKey, payer.publicKey)
-            .accounts({ dataAccount: storage.publicKey })
+        const signature = await program.methods.createChild(child.publicKey)
+            .accounts({
+                dataAccount: storage.publicKey,
+                payer: payer.publicKey,
+            })
             .remainingAccounts([
                 { pubkey: child_program, isSigner: false, isWritable: false },
                 { pubkey: child.publicKey, isSigner: true, isWritable: true },
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
             ])
             .signers([payer, child])
             .rpc({ commitment: 'confirmed' });
@@ -49,12 +51,14 @@ describe('ChildContract', function () {
         let [address, bump] = await PublicKey.findProgramAddress([seed], seed_program);
 
         const signature = await program.methods.createSeed1(
-            address, payer.publicKey, seed, Buffer.from([bump]), new BN(711))
-            .accounts({ dataAccount: storage.publicKey })
+            address, seed, Buffer.from([bump]), new BN(711))
+            .accounts({
+                dataAccount: storage.publicKey,
+                payer: payer.publicKey,
+            })
             .remainingAccounts([
                 { pubkey: seed_program, isSigner: false, isWritable: false },
                 { pubkey: address, isSigner: false, isWritable: true },
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
             ])
             .signers([payer])
             .rpc({ commitment: 'confirmed' });
@@ -80,12 +84,14 @@ describe('ChildContract', function () {
         let seed = Buffer.concat([bare_seed, Buffer.from([bump])]);
 
         const signature = await program.methods.createSeed2(
-            address, payer.publicKey, seed, new BN(9889))
-            .accounts({ dataAccount: storage.publicKey })
+            address, seed, new BN(9889))
+            .accounts({
+                dataAccount: storage.publicKey,
+                payer: payer.publicKey
+            })
             .remainingAccounts([
                 { pubkey: seed_program, isSigner: false, isWritable: false },
                 { pubkey: address, isSigner: false, isWritable: true },
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
             ])
             .signers([payer])
             .rpc({ commitment: 'confirmed' });
@@ -134,4 +140,28 @@ describe('ChildContract', function () {
 
         expect(info?.data.length).toEqual(518);
     });
+
+    it('Create Contract without annotations', async function () {
+        const child = Keypair.generate();
+        const child_program = new PublicKey("8gTkAidfM82u3DGbKcZpHwL5p47KQA16MDb4WmrHdmF6");
+
+        await create_account(child, child_program, 8192);
+        const signature = await program.methods.createWithoutAnnotation(child.publicKey)
+            .accounts( {dataAccount: storage.publicKey})
+            .remainingAccounts(
+                [
+                    {pubkey: child_program, isSigner: false, isWritable: false},
+                    {pubkey: child.publicKey, isSigner: true, isWritable: true}
+                ]
+            ).signers([child])
+            .rpc({ commitment: 'confirmed'});
+
+        const tx = await provider.connection.getTransaction(signature, {
+            commitment: 'confirmed',
+            maxSupportedTransactionVersion: undefined,
+        });
+
+        expect(tx?.meta?.logMessages!.toString()).toContain('In child constructor');
+        expect(tx?.meta?.logMessages!.toString()).toContain('say_my_name');
+    });
 });

+ 0 - 7
integration/solana/runtime_errors.sol

@@ -4,7 +4,6 @@ import 'solana';
 contract RuntimeErrors {
     bytes b = hex"0000_00fa";
     uint256[] arr;
-    Creature public c;
 
     constructor() {}
 
@@ -66,12 +65,6 @@ contract RuntimeErrors {
         e.say_my_name();
     }
 
-    // contract creation failed (contract was deplyed with no value)
-    function create_child(address child_contract_addr, address payer) public {
-        c = new Creature{address: child_contract_addr}(payer);
-        c.say_my_name();
-    }
-
     function i_will_revert() public {
         revert();
     }

+ 16 - 44
integration/solana/runtime_errors.spec.ts

@@ -1,9 +1,10 @@
 // SPDX-License-Identifier: Apache-2.0
 
-import { Keypair, PublicKey } from '@solana/web3.js';
+import {Keypair, PublicKey, sendAndConfirmTransaction, SystemProgram, Transaction} from '@solana/web3.js';
 import expect from 'expect';
-import { loadContract } from './setup';
-import { Program, Provider, BN } from '@project-serum/anchor';
+import {loadContract} from './setup';
+import {Program, Provider, BN, AnchorProvider} from '@project-serum/anchor';
+import {createAccount} from "@solana/spl-token";
 
 describe('Runtime Errors', function () {
     this.timeout(150000);
@@ -24,7 +25,7 @@ describe('Runtime Errors', function () {
         }
         catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: storage index out of bounds in runtime_errors.sol:42:11-12,
+            expect(logs).toContain(`Program log: runtime_error: storage index out of bounds in runtime_errors.sol:41:11-12,
 `);
         }
 
@@ -33,7 +34,7 @@ describe('Runtime Errors', function () {
         }
         catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: storage array index out of bounds in runtime_errors.sol:49:19-23,
+            expect(logs).toContain(`Program log: runtime_error: storage array index out of bounds in runtime_errors.sol:48:19-23,
 `);
         }
 
@@ -41,7 +42,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.popEmptyStorage().accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: pop from empty storage array in runtime_errors.sol:61:9-12,
+            expect(logs).toContain(`Program log: runtime_error: pop from empty storage array in runtime_errors.sol:60:9-12,
 `)
 
         }
@@ -50,7 +51,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.invalidInstruction().accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: reached invalid instruction in runtime_errors.sol:108:13-22,
+            expect(logs).toContain(`Program log: runtime_error: reached invalid instruction in runtime_errors.sol:101:13-22,
 `)
 
         }
@@ -59,7 +60,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.byteCastFailure(new BN(33)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: bytes cast error in runtime_errors.sol:114:23-40,
+            expect(logs).toContain(`Program log: runtime_error: bytes cast error in runtime_errors.sol:107:23-40,
 `)
 
         }
@@ -68,7 +69,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.iWillRevert().accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: revert encountered in runtime_errors.sol:76:9-17,
+            expect(logs).toContain(`Program log: runtime_error: revert encountered in runtime_errors.sol:69:9-17,
 `)
         }
 
@@ -76,7 +77,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.assertTest(new BN(9)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: assert failure in runtime_errors.sol:35:16-24,
+            expect(logs).toContain(`Program log: runtime_error: assert failure in runtime_errors.sol:34:16-24,
 `)
         }
 
@@ -84,7 +85,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.writeIntegerFailure(new BN(1)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: integer too large to write in buffer in runtime_errors.sol:81:18-31,
+            expect(logs).toContain(`Program log: runtime_error: integer too large to write in buffer in runtime_errors.sol:74:18-31,
 `)
         }
 
@@ -92,7 +93,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.writeBytesFailure(new BN(9)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: data does not fit into buffer in runtime_errors.sol:87:18-28,
+            expect(logs).toContain(`Program log: runtime_error: data does not fit into buffer in runtime_errors.sol:80:18-28,
 `)
         }
 
@@ -101,7 +102,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.readIntegerFailure(new BN(2)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: read integer out of bounds in runtime_errors.sol:92:18-30,
+            expect(logs).toContain(`Program log: runtime_error: read integer out of bounds in runtime_errors.sol:85:18-30,
 `)
         }
 
@@ -110,7 +111,7 @@ describe('Runtime Errors', function () {
             let res = await program.methods.outOfBounds(new BN(19)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: array index out of bounds in runtime_errors.sol:103:16-21,
+            expect(logs).toContain(`Program log: runtime_error: array index out of bounds in runtime_errors.sol:96:16-21,
 `)
         }
 
@@ -119,39 +120,10 @@ describe('Runtime Errors', function () {
             let res = await program.methods.truncFailure(new BN(99999999999999)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
-            expect(logs).toContain(`Program log: runtime_error: truncated type overflows in runtime_errors.sol:97:37-42,
+            expect(logs).toContain(`Program log: runtime_error: truncated type overflows in runtime_errors.sol:90:37-42,
 `)
         }
 
-        let child_program = new PublicKey("Cre7AzxtwSxXwU2jekYtCAQ57DkBhY9SjGDLdcrwhAo6");
-        let child = Keypair.generate();
-
-
-        const signature = await program.methods.createChild(child.publicKey, payer.publicKey)
-            .accounts({ dataAccount: storage.publicKey })
-            .remainingAccounts([
-                { pubkey: child_program, isSigner: false, isWritable: false },
-                { pubkey: child.publicKey, isSigner: true, isWritable: true },
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
-            ])
-            .signers([payer, child])
-            .rpc({ commitment: 'confirmed' });
-
-
-        const tx = await provider.connection.getTransaction(signature, { commitment: 'confirmed' });
-        try {
-            const signature = await program.methods.createChild(child.publicKey, payer.publicKey)
-                .accounts({ dataAccount: storage.publicKey })
-                .remainingAccounts([
-                    { pubkey: child_program, isSigner: false, isWritable: false },
-                    { pubkey: payer.publicKey, isSigner: true, isWritable: true },
-                ])
-                .signers([payer]).simulate();
-        } catch (e: any) {
-            const logs = e.simulationResponse.logs
-            expect(logs).toContain("Program log: new account needed");
-        }
-
     });
 
 });

+ 11 - 7
integration/solana/setup.ts

@@ -21,7 +21,7 @@ export async function loadContract(name: string, args: any[] = [], space: number
 
     const program_key = loadKey(`${name}.key`);
 
-    await create_account(provider, storage, program_key.publicKey, space);
+    await create_account(storage, program_key.publicKey, space);
 
     const program = new Program(idl, program_key.publicKey, provider);
 
@@ -32,7 +32,8 @@ export async function loadContract(name: string, args: any[] = [], space: number
     return { provider, program, payer, storage, program_key: program_key.publicKey };
 }
 
-async function create_account(provider: AnchorProvider, account: Keypair, programId: PublicKey, space: number) {
+export async function create_account(account: Keypair, programId: PublicKey, space: number) {
+    const provider = AnchorProvider.local(endpoint);
     const lamports = await provider.connection.getMinimumBalanceForRentExemption(space);
 
     const transaction = new Transaction();
@@ -63,7 +64,7 @@ export async function loadContractWithProvider(provider: AnchorProvider, name: s
     const storage = Keypair.generate();
     const program_key = loadKey(`${name}.key`);
 
-    await create_account(provider, storage, program_key.publicKey, space);
+    await create_account(storage, program_key.publicKey, space);
 
     const program = new Program(idl, program_key.publicKey, provider);
 
@@ -86,8 +87,13 @@ async function newAccountWithLamports(connection: Connection): Promise<Keypair>
 
     console.log('Airdropping SOL to a new wallet ...');
     let signature = await connection.requestAirdrop(account.publicKey, 100 * LAMPORTS_PER_SOL);
-    await connection.confirmTransaction(signature, 'confirmed');
+    const latestBlockHash = await connection.getLatestBlockhash();
 
+    await connection.confirmTransaction({
+        blockhash: latestBlockHash.blockhash,
+        lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
+        signature,
+    }, 'confirmed');
     return account;
 }
 
@@ -143,12 +149,10 @@ async function setup() {
 }
 
 function newConnection(): Connection {
-    const connection = new Connection(endpoint, {
+    return new Connection(endpoint, {
         commitment: "confirmed",
         confirmTransactionInitialTimeout: 1e6,
     });
-
-    return connection;
 }
 
 if (require.main === module) {

+ 91 - 5
src/abi/tests.rs

@@ -1545,7 +1545,7 @@ fn data_account_signer() {
             }),
             IdlAccountItem::IdlAccount(IdlAccount {
                 name: "wallet".to_string(),
-                is_mut: false,
+                is_mut: true,
                 is_signer: true,
                 is_optional: Some(false),
                 docs: None,
@@ -1596,7 +1596,7 @@ fn data_account_signer() {
             }),
             IdlAccountItem::IdlAccount(IdlAccount {
                 name: "wallet".to_string(),
-                is_mut: false,
+                is_mut: true,
                 is_signer: true,
                 is_optional: Some(false),
                 docs: None,
@@ -1824,8 +1824,8 @@ fn system_account_for_payer_annotation() {
                 relations: vec![],
             }),
             IdlAccountItem::IdlAccount(IdlAccount {
-                name: "wallet".to_string(),
-                is_mut: false,
+                name: "addr_".to_string(),
+                is_mut: true,
                 is_signer: true,
                 is_optional: Some(false),
                 docs: None,
@@ -2184,7 +2184,7 @@ fn multiple_contracts() {
 contract creator {
     Child public c;
 
-    function create_child(address child, address payer) public returns (uint64) {
+    function create_child(address child, address payer) external returns (uint64) {
         print("Going to create child");
         c = new Child{address: child}(payer);
 
@@ -2227,6 +2227,15 @@ contract Child {
                 pda: None,
                 relations: vec![],
             }),
+            IdlAccountItem::IdlAccount(IdlAccount {
+                name: "payer".to_string(),
+                is_mut: true,
+                is_signer: true,
+                is_optional: Some(false),
+                docs: None,
+                pda: None,
+                relations: vec![],
+            }),
             IdlAccountItem::IdlAccount(IdlAccount {
                 name: "systemProgram".to_string(),
                 is_mut: false,
@@ -2248,3 +2257,80 @@ contract Child {
         ]
     );
 }
+
+#[test]
+fn constructor_double_payer() {
+    let src = r#"
+    import 'solana';
+
+@program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER")
+contract Builder {
+    BeingBuilt other;
+
+    @payer(payer_account)
+    constructor(address addr) {
+        other = new BeingBuilt{address: addr}("abc");
+    }
+}
+
+
+@program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
+contract BeingBuilt {
+    @seed(my_seed)
+    @space(1024)
+    @payer(other_account)
+    constructor(bytes my_seed) {}
+
+    function say_this(string text) public pure {
+        print(text);
+    }
+}
+    "#;
+
+    let mut ns = generate_namespace(src);
+    codegen(&mut ns, &Options::default());
+    let idl = generate_anchor_idl(0, &ns);
+
+    assert_eq!(idl.instructions[0].name, "new");
+    assert_eq!(
+        idl.instructions[0].accounts,
+        vec![
+            IdlAccountItem::IdlAccount(IdlAccount {
+                name: "dataAccount".to_string(),
+                is_mut: true,
+                is_signer: true,
+                is_optional: Some(false),
+                docs: None,
+                pda: None,
+                relations: vec![],
+            }),
+            IdlAccountItem::IdlAccount(IdlAccount {
+                name: "payer_account".to_string(),
+                is_mut: true,
+                is_signer: true,
+                is_optional: Some(false),
+                docs: None,
+                pda: None,
+                relations: vec![],
+            }),
+            IdlAccountItem::IdlAccount(IdlAccount {
+                name: "systemProgram".to_string(),
+                is_mut: false,
+                is_signer: false,
+                is_optional: Some(false),
+                docs: None,
+                pda: None,
+                relations: vec![],
+            }),
+            IdlAccountItem::IdlAccount(IdlAccount {
+                name: "other_account".to_string(),
+                is_mut: true,
+                is_signer: true,
+                is_optional: Some(false),
+                docs: None,
+                pda: None,
+                relations: vec![]
+            })
+        ]
+    );
+}

+ 8 - 1
src/bin/languageserver/mod.rs

@@ -911,10 +911,17 @@ impl<'a> Builder<'a> {
                 match note {
                     ast::ConstructorAnnotation::Bump(expr)
                     | ast::ConstructorAnnotation::Seed(expr)
-                    | ast::ConstructorAnnotation::Payer(expr)
                     | ast::ConstructorAnnotation::Space(expr) => {
                         builder.expression(expr, &func.symtable)
                     }
+
+                    ast::ConstructorAnnotation::Payer(loc, name) => {
+                        builder.hovers.push(HoverEntry {
+                            start: loc.start(),
+                            stop: loc.end(),
+                            val: format!("payer account: {}", name),
+                        });
+                    }
                 }
             }
 

+ 8 - 1
src/codegen/cfg.rs

@@ -119,6 +119,7 @@ pub enum Instr {
         success: Option<usize>,
         res: usize,
         contract_no: usize,
+        constructor_no: Option<usize>,
         encoded_args: Expression,
         value: Option<Expression>,
         gas: Expression,
@@ -1234,14 +1235,20 @@ impl ControlFlowGraph {
                 value,
                 address,seeds,
                 accounts,
+                constructor_no,
                 loc:_
             } => format!(
-                "%{}, {} = constructor salt:{} value:{} gas:{} address:{} seeds:{} {} encoded buffer: {} accounts: {}",
+                "%{}, {} = constructor(no: {}) salt:{} value:{} gas:{} address:{} seeds:{} {} encoded buffer: {} accounts: {}",
                 self.vars[res].id.name,
                 match success {
                     Some(i) => format!("%{}", self.vars[i].id.name),
                     None => "_".to_string(),
                 },
+                if let Some(no) = constructor_no {
+                    format!("{}", no)
+                } else {
+                    String::new()
+                },
                 match salt {
                     Some(salt) => self.expr_to_string(contract, ns, salt),
                     None => "".to_string(),

+ 2 - 0
src/codegen/constant_folding.rs

@@ -197,6 +197,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                     seeds,
                     loc,
                     accounts,
+                    constructor_no,
                 } => {
                     let encoded_args = expression(encoded_args, Some(&vars), cfg, ns).0;
                     let value = value
@@ -220,6 +221,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         success: *success,
                         res: *res,
                         contract_no: *contract_no,
+                        constructor_no: *constructor_no,
                         encoded_args,
                         value,
                         gas,

+ 1 - 0
src/codegen/constructor.rs

@@ -88,6 +88,7 @@ pub(super) fn call_constructor(
             success,
             res: address_res,
             contract_no,
+            constructor_no: *constructor_no,
             encoded_args,
             value,
             gas,

+ 10 - 2
src/codegen/mod.rs

@@ -28,7 +28,7 @@ use self::{
     cfg::{optimize_and_check_cfg, ControlFlowGraph, Instr},
     dispatch::function_dispatch,
     expression::expression,
-    solana_accounts::collect_accounts_from_contract,
+    solana_accounts::account_collection::collect_accounts_from_contract,
     vartable::Vartable,
 };
 use crate::sema::ast::{
@@ -41,6 +41,7 @@ use crate::{
 use std::cmp::Ordering;
 
 use crate::codegen::cfg::ASTFunction;
+use crate::codegen::solana_accounts::account_management::manage_contract_accounts;
 use crate::codegen::yul::generate_yul_function_cfg;
 use crate::sema::Recurse;
 use num_bigint::{BigInt, Sign};
@@ -159,7 +160,14 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) {
     if ns.target == Target::Solana {
         for contract_no in 0..ns.contracts.len() {
             if ns.contracts[contract_no].instantiable {
-                collect_accounts_from_contract(contract_no, ns);
+                let diag = collect_accounts_from_contract(contract_no, ns);
+                ns.diagnostics.extend(diag);
+            }
+        }
+
+        for contract_no in 0..ns.contracts.len() {
+            if ns.contracts[contract_no].instantiable {
+                manage_contract_accounts(contract_no, ns);
             }
         }
     }

+ 84 - 89
src/codegen/solana_accounts.rs → src/codegen/solana_accounts/account_collection.rs

@@ -1,81 +1,16 @@
 // SPDX-License-Identifier: Apache-2.0
 
 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, Mutability, Namespace, SolanaAccount};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::solana_accounts::BuiltinAccounts;
 use crate::sema::Recurse;
-use base58::FromBase58;
 use indexmap::IndexSet;
-use num_bigint::{BigInt, Sign};
-use num_traits::Zero;
-use once_cell::sync::Lazy;
-use solang_parser::pt::FunctionTy;
-use std::collections::{HashMap, HashSet, VecDeque};
-
-/// These are the accounts that we can collect from a contract and that Anchor will populate
-/// automatically if their names match the source code description:
-/// https://github.com/coral-xyz/anchor/blob/06c42327d4241e5f79c35bc5588ec0a6ad2fedeb/ts/packages/anchor/src/program/accounts-resolver.ts#L54-L60
-static CLOCK_ACCOUNT: &str = "clock";
-static SYSTEM_ACCOUNT: &str = "systemProgram";
-static ASSOCIATED_TOKEN_PROGRAM: &str = "associatedTokenProgram";
-static RENT_ACCOUNT: &str = "rent";
-static TOKEN_PROGRAM_ID: &str = "tokenProgram";
-
-/// We automatically include the following accounts in the IDL, but these are not
-/// automatically populated
-static DATA_ACCOUNT: &str = "dataAccount";
-static WALLET_ACCOUNT: &str = "wallet";
-static INSTRUCTION_ACCOUNT: &str = "SysvarInstruction";
-
-/// If the public keys available in AVAILABLE_ACCOUNTS are hardcoded in a Solidity contract
-/// for external calls, we can detect them and leverage Anchor's public key auto populate feature.
-static AVAILABLE_ACCOUNTS: Lazy<HashMap<BigInt, &'static str>> = Lazy::new(|| {
-    HashMap::from([
-        (BigInt::zero(), SYSTEM_ACCOUNT),
-        (
-            BigInt::from_bytes_be(
-                Sign::Plus,
-                &"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
-                    .from_base58()
-                    .unwrap(),
-            ),
-            ASSOCIATED_TOKEN_PROGRAM,
-        ),
-        (
-            BigInt::from_bytes_be(
-                Sign::Plus,
-                &"SysvarRent111111111111111111111111111111111"
-                    .from_base58()
-                    .unwrap(),
-            ),
-            RENT_ACCOUNT,
-        ),
-        (
-            BigInt::from_bytes_be(
-                Sign::Plus,
-                &"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
-                    .from_base58()
-                    .unwrap(),
-            ),
-            TOKEN_PROGRAM_ID,
-        ),
-        (
-            BigInt::from_bytes_be(
-                Sign::Plus,
-                &"SysvarC1ock11111111111111111111111111111111"
-                    .from_base58()
-                    .unwrap(),
-            ),
-            CLOCK_ACCOUNT,
-        ),
-    ])
-});
-
-/// Retrieve a name from an account, according to Anchor's constant accounts map
-/// https://github.com/coral-xyz/anchor/blob/06c42327d4241e5f79c35bc5588ec0a6ad2fedeb/ts/packages/anchor/src/program/accounts-resolver.ts#L54-L60
-fn account_from_number(num: &BigInt) -> Option<&'static str> {
-    AVAILABLE_ACCOUNTS.get(num).cloned()
-}
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt::{FunctionTy, Loc};
+use std::collections::{HashSet, VecDeque};
 
 /// Struct to save the recursion data when traversing all the CFG instructions
 struct RecurseData<'a> {
@@ -93,6 +28,7 @@ struct RecurseData<'a> {
     contracts: &'a [Contract],
     /// The vector of functions from the contract
     functions: &'a [Function],
+    diagnostics: &'a mut Diagnostics,
 }
 
 impl RecurseData<'_> {
@@ -101,7 +37,15 @@ impl RecurseData<'_> {
         if self.functions[self.ast_no]
             .solana_accounts
             .borrow_mut()
-            .insert(account_name, account)
+            .insert(
+                account_name,
+                SolanaAccount {
+                    loc: account.loc,
+                    is_signer: account.is_signer,
+                    is_writer: account.is_writer,
+                    generated: true,
+                },
+            )
             .is_none()
         {
             self.accounts_added += 1;
@@ -111,18 +55,21 @@ impl RecurseData<'_> {
     /// Add the system account to the function's indexmap
     fn add_system_account(&mut self) {
         self.add_account(
-            SYSTEM_ACCOUNT.to_string(),
+            BuiltinAccounts::SystemAccount.to_string(),
             SolanaAccount {
+                loc: Loc::Codegen,
                 is_writer: false,
                 is_signer: false,
+                generated: true,
             },
         );
     }
 }
 
 /// Collect the accounts this contract needs
-pub(super) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace) {
+pub(crate) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace) -> Diagnostics {
     let mut visiting_queue: IndexSet<(usize, usize)> = IndexSet::new();
+    let mut diagnostics = Diagnostics::default();
 
     for func_no in ns.contracts[contract_no].all_functions.keys() {
         if ns.functions[*func_no].is_public()
@@ -135,18 +82,22 @@ pub(super) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace)
             match &func.mutability {
                 Mutability::Pure(_) => (),
                 Mutability::View(_) => {
-                    func.solana_accounts.borrow_mut().insert(
-                        DATA_ACCOUNT.to_string(),
+                    let (idx, _) = func.solana_accounts.borrow_mut().insert_full(
+                        BuiltinAccounts::DataAccount.to_string(),
                         SolanaAccount {
+                            loc: Loc::Codegen,
                             is_writer: false,
                             is_signer: false,
+                            generated: true,
                         },
                     );
+                    func.solana_accounts.borrow_mut().move_index(idx, 0);
                 }
                 _ => {
-                    func.solana_accounts.borrow_mut().insert(
-                        DATA_ACCOUNT.to_string(),
+                    let (idx, _) = func.solana_accounts.borrow_mut().insert_full(
+                        BuiltinAccounts::DataAccount.to_string(),
                         SolanaAccount {
+                            loc: Loc::Codegen,
                             is_writer: true,
                             /// With a @payer annotation, the account is created on-chain and needs a signer. The client
                             /// provides an address that does not exist yet, so SystemProgram.CreateAccount is called
@@ -156,23 +107,21 @@ pub(super) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace)
                             /// with the seed using program derived address (pda) when SystemProgram.CreateAccount is called,
                             /// so no signer is required from the client.
                             is_signer: func.has_payer_annotation() && !func.has_seed_annotation(),
+                            generated: true,
                         },
                     );
+
+                    func.solana_accounts.borrow_mut().move_index(idx, 0);
                 }
             }
             if func.is_constructor() && func.has_payer_annotation() {
                 func.solana_accounts.borrow_mut().insert(
-                    WALLET_ACCOUNT.to_string(),
-                    SolanaAccount {
-                        is_signer: true,
-                        is_writer: false,
-                    },
-                );
-                func.solana_accounts.borrow_mut().insert(
-                    SYSTEM_ACCOUNT.to_string(),
+                    BuiltinAccounts::SystemAccount.to_string(),
                     SolanaAccount {
+                        loc: Loc::Codegen,
                         is_signer: false,
                         is_writer: false,
+                        generated: true,
                     },
                 );
             }
@@ -191,6 +140,7 @@ pub(super) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace)
         contract_no,
         functions: &ns.functions,
         contracts: &ns.contracts,
+        diagnostics: &mut diagnostics,
     };
 
     let mut old_size: usize = 0;
@@ -223,6 +173,8 @@ pub(super) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace)
         std::mem::swap(&mut visiting_queue, &mut recurse_data.next_queue);
         recurse_data.next_queue.clear();
     }
+
+    diagnostics
 }
 
 /// Collect the accounts in a function
@@ -372,6 +324,7 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) {
             address,
             seeds,
             accounts,
+            constructor_no,
             ..
         } => {
             encoded_args.recurse(data, check_expression);
@@ -390,6 +343,42 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) {
             }
             if let Some(accounts) = accounts {
                 accounts.recurse(data, check_expression);
+            } else {
+                // If the one passes the AccountMeta vector to the constructor call, there is no
+                // need to collect accounts for the IDL.
+                if let Some(constructor_no) = constructor_no {
+                    let accounts_to_add = data.functions[*constructor_no]
+                        .solana_accounts
+                        .borrow()
+                        .clone();
+                    for (name, account) in accounts_to_add {
+                        if name == BuiltinAccounts::DataAccount {
+                            continue;
+                        }
+
+                        if let Some(other_account) = data.functions[data.ast_no]
+                            .solana_accounts
+                            .borrow()
+                            .get(&name)
+                        {
+                            // If the compiler did not generate this account entry, we have a name
+                            // collision.
+                            if !other_account.generated {
+                                data.diagnostics.push(
+                                    Diagnostic::error_with_note(
+                                        other_account.loc,
+                                        "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".to_string(),
+                                        account.loc,
+                                        "other declaration".to_string()
+                                    )
+                                );
+                            }
+                        }
+                        data.add_account(name, account);
+                    }
+                }
             }
 
             data.add_system_account();
@@ -410,10 +399,12 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) {
                     // Check if we can auto populate this account
                     if let Some(account) = account_from_number(value) {
                         data.add_account(
-                            account.to_string(),
+                            account,
                             SolanaAccount {
+                                loc: Loc::Codegen,
                                 is_signer: false,
                                 is_writer: false,
+                                generated: true,
                             },
                         );
                     }
@@ -471,10 +462,12 @@ fn check_expression(expr: &Expression, data: &mut RecurseData) -> bool {
             ..
         } => {
             data.add_account(
-                CLOCK_ACCOUNT.to_string(),
+                BuiltinAccounts::ClockAccount.to_string(),
                 SolanaAccount {
+                    loc: Loc::Codegen,
                     is_signer: false,
                     is_writer: false,
+                    generated: true,
                 },
             );
         }
@@ -483,10 +476,12 @@ fn check_expression(expr: &Expression, data: &mut RecurseData) -> bool {
             ..
         } => {
             data.add_account(
-                INSTRUCTION_ACCOUNT.to_string(),
+                BuiltinAccounts::InstructionAccount.to_string(),
                 SolanaAccount {
+                    loc: Loc::Codegen,
                     is_writer: false,
                     is_signer: false,
+                    generated: true,
                 },
             );
         }

+ 186 - 0
src/codegen/solana_accounts/account_management.rs

@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::codegen::cfg::{ControlFlowGraph, Instr};
+use crate::codegen::{Builtin, Expression};
+use crate::sema::ast::{ArrayLength, Function, Namespace, StructType, Type};
+use crate::sema::solana_accounts::BuiltinAccounts;
+use num_bigint::BigInt;
+use num_traits::Zero;
+use solang_parser::pt::Loc;
+use std::collections::{HashSet, VecDeque};
+
+/// This function walks over the CFG and automates the account management, so developers do not need
+/// to do so. For instance, when calling 'new construct{address: addr}()', we construct the correct
+/// AccountMeta array with all the accounts the constructor needs.
+pub(crate) fn manage_contract_accounts(contract_no: usize, ns: &mut Namespace) {
+    let contract_functions = ns.contracts[contract_no].functions.clone();
+    for function_no in &contract_functions {
+        let cfg_no = ns.contracts[contract_no]
+            .all_functions
+            .get(function_no)
+            .cloned()
+            .unwrap();
+        traverse_cfg(
+            &mut ns.contracts[contract_no].cfg[cfg_no],
+            &ns.functions,
+            *function_no,
+        );
+    }
+}
+
+/// This function walks over the CFG to process its instructions for the account management.
+fn traverse_cfg(cfg: &mut ControlFlowGraph, functions: &[Function], ast_no: usize) {
+    if cfg.blocks.is_empty() {
+        return;
+    }
+
+    let mut queue: VecDeque<usize> = VecDeque::new();
+    let mut visited: HashSet<usize> = HashSet::new();
+    queue.push_back(0);
+    visited.insert(0);
+
+    while let Some(cur_block) = queue.pop_front() {
+        for instr in cfg.blocks[cur_block].instr.iter_mut() {
+            process_instruction(instr, functions, ast_no);
+        }
+
+        for edge in cfg.blocks[cur_block].edges() {
+            if !visited.contains(&edge) {
+                queue.push_back(edge);
+                visited.insert(edge);
+            }
+        }
+    }
+}
+
+/// This function processes the instruction, creating the AccountMeta array when possible.
+/// Presently, we only check the Instr::Constructor, but more will come later.
+fn process_instruction(instr: &mut Instr, functions: &[Function], ast_no: usize) {
+    if let Instr::Constructor {
+        accounts,
+        address,
+        constructor_no,
+        ..
+    } = instr
+    {
+        if accounts.is_some() || constructor_no.is_none() {
+            return;
+        }
+
+        let mut account_metas: Vec<Expression> = Vec::new();
+        let constructor_func = &functions[constructor_no.unwrap()];
+        for (name, account) in constructor_func.solana_accounts.borrow().iter() {
+            if name == BuiltinAccounts::DataAccount {
+                let address_ref = Expression::GetRef {
+                    loc: Loc::Codegen,
+                    ty: Type::Ref(Box::new(Type::Address(false))),
+                    expr: Box::new(address.as_ref().unwrap().clone()),
+                };
+                let struct_literal =
+                    account_meta_literal(address_ref, account.is_signer, account.is_writer);
+                account_metas.push(struct_literal);
+            } else if name == BuiltinAccounts::SystemAccount {
+                let system_address = Expression::NumberLiteral {
+                    loc: Loc::Codegen,
+                    ty: Type::Address(false),
+                    value: BigInt::zero(),
+                };
+                let system_ref = Expression::GetRef {
+                    loc: Loc::Codegen,
+                    ty: Type::Ref(Box::new(Type::Address(false))),
+                    expr: Box::new(system_address),
+                };
+                let struct_literal = account_meta_literal(system_ref, false, false);
+                account_metas.push(struct_literal);
+            } else {
+                let account_index = functions[ast_no]
+                    .solana_accounts
+                    .borrow()
+                    .get_index_of(name)
+                    .unwrap();
+                let ptr_to_address = index_accounts_vector(account_index);
+                account_metas.push(account_meta_literal(
+                    ptr_to_address,
+                    account.is_signer,
+                    account.is_writer,
+                ));
+            }
+        }
+        let metas_vector = Expression::ArrayLiteral {
+            loc: Loc::Codegen,
+            ty: Type::Array(
+                Box::new(Type::Struct(StructType::AccountMeta)),
+                vec![ArrayLength::Fixed(BigInt::from(account_metas.len()))],
+            ),
+            dimensions: vec![account_metas.len() as u32],
+            values: account_metas,
+        };
+
+        *address = None;
+        *accounts = Some(metas_vector);
+    }
+}
+
+/// This function automates the process of retrieving 'tx.accounts[index].key'.
+pub(crate) fn index_accounts_vector(index: usize) -> Expression {
+    let accounts_vector = Expression::Builtin {
+        loc: Loc::Codegen,
+        tys: vec![Type::Array(
+            Box::new(Type::Struct(StructType::AccountInfo)),
+            vec![ArrayLength::Dynamic],
+        )],
+        kind: Builtin::Accounts,
+        args: vec![],
+    };
+
+    let payer_info = Expression::Subscript {
+        loc: Loc::Codegen,
+        ty: Type::Ref(Box::new(Type::Struct(StructType::AccountInfo))),
+        array_ty: Type::Array(
+            Box::new(Type::Struct(StructType::AccountInfo)),
+            vec![ArrayLength::Dynamic],
+        ),
+        expr: Box::new(accounts_vector),
+        index: Box::new(Expression::NumberLiteral {
+            loc: Loc::Codegen,
+            ty: Type::Uint(32),
+            value: BigInt::from(index),
+        }),
+    };
+
+    let address = Expression::StructMember {
+        loc: Loc::Codegen,
+        ty: Type::Ref(Box::new(Type::Ref(Box::new(Type::Address(false))))),
+        expr: Box::new(payer_info),
+        member: 0,
+    };
+
+    Expression::Load {
+        loc: Loc::Codegen,
+        ty: Type::Ref(Box::new(Type::Address(false))),
+        expr: Box::new(address),
+    }
+}
+
+/// This function creates an AccountMeta struct literal.
+pub(crate) fn account_meta_literal(
+    address: Expression,
+    is_signer: bool,
+    is_writer: bool,
+) -> Expression {
+    Expression::StructLiteral {
+        loc: Loc::Codegen,
+        ty: Type::Struct(StructType::AccountMeta),
+        values: vec![
+            address,
+            Expression::BoolLiteral {
+                loc: Loc::Codegen,
+                value: is_writer,
+            },
+            Expression::BoolLiteral {
+                loc: Loc::Codegen,
+                value: is_signer,
+            },
+        ],
+    }
+}

+ 61 - 0
src/codegen/solana_accounts/mod.rs

@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub(super) mod account_collection;
+pub(super) mod account_management;
+
+use crate::sema::solana_accounts::BuiltinAccounts;
+use base58::FromBase58;
+use num_bigint::{BigInt, Sign};
+use num_traits::Zero;
+use once_cell::sync::Lazy;
+use std::collections::HashMap;
+
+/// If the public keys available in AVAILABLE_ACCOUNTS are hardcoded in a Solidity contract
+/// for external calls, we can detect them and leverage Anchor's public key auto populate feature.
+static AVAILABLE_ACCOUNTS: Lazy<HashMap<BigInt, BuiltinAccounts>> = Lazy::new(|| {
+    HashMap::from([
+        (BigInt::zero(), BuiltinAccounts::SystemAccount),
+        (
+            BigInt::from_bytes_be(
+                Sign::Plus,
+                &"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+                    .from_base58()
+                    .unwrap(),
+            ),
+            BuiltinAccounts::AssociatedTokenProgram,
+        ),
+        (
+            BigInt::from_bytes_be(
+                Sign::Plus,
+                &"SysvarRent111111111111111111111111111111111"
+                    .from_base58()
+                    .unwrap(),
+            ),
+            BuiltinAccounts::RentAccount,
+        ),
+        (
+            BigInt::from_bytes_be(
+                Sign::Plus,
+                &"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+                    .from_base58()
+                    .unwrap(),
+            ),
+            BuiltinAccounts::TokenProgramId,
+        ),
+        (
+            BigInt::from_bytes_be(
+                Sign::Plus,
+                &"SysvarC1ock11111111111111111111111111111111"
+                    .from_base58()
+                    .unwrap(),
+            ),
+            BuiltinAccounts::ClockAccount,
+        ),
+    ])
+});
+
+/// Retrieve a name from an account, according to Anchor's constant accounts map
+/// https://github.com/coral-xyz/anchor/blob/06c42327d4241e5f79c35bc5588ec0a6ad2fedeb/ts/packages/anchor/src/program/accounts-resolver.ts#L54-L60
+fn account_from_number(num: &BigInt) -> Option<String> {
+    AVAILABLE_ACCOUNTS.get(num).map(|e| e.to_string())
+}

+ 25 - 42
src/codegen/solana_deploy.rs

@@ -4,6 +4,9 @@ use super::{
     cfg::ReturnCode, expression, Builtin, ControlFlowGraph, Expression, Instr, Options, Type,
     Vartable,
 };
+use crate::codegen::solana_accounts::account_management::{
+    account_meta_literal, index_accounts_vector,
+};
 use crate::sema::ast::{
     self, ArrayLength, CallTy, ConstructorAnnotation, Function, FunctionAttributes, Namespace,
     StructType,
@@ -233,7 +236,7 @@ pub(super) fn solana_deploy(
         }
     }
 
-    if let Some(ConstructorAnnotation::Payer(payer)) = func
+    if let Some(ConstructorAnnotation::Payer(_, name)) = func
         .annotations
         .iter()
         .find(|tag| matches!(tag, ConstructorAnnotation::Payer(..)))
@@ -245,7 +248,15 @@ pub(super) fn solana_deploy(
 
         let metas = vartab.temp_name("metas", &metas_ty);
 
-        let payer = expression(payer, cfg, contract_no, None, ns, vartab, opt);
+        // FIXME: The +1 accounts for the not yet added data account, which is always the first one.
+        // We need to fix a chicken and egg problem to solve this. The proper indexes are only
+        // calculated after that the deploy code is generated.
+        // Alternatively, we can conceive an Expression::NamedIndex to index the tx.account by
+        // account name and exchange it by a normal Expression::Subscript at the AccountManagement
+        // pass.
+        // THAT IS WORK FOR ANOTHER PR!
+        let payer_index = func.solana_accounts.borrow().get_index_of(name).unwrap() + 1;
+        let ptr_to_address = index_accounts_vector(payer_index);
 
         cfg.add(
             vartab,
@@ -257,51 +268,23 @@ pub(super) fn solana_deploy(
                     ty: metas_ty.clone(),
                     dimensions: vec![2],
                     values: vec![
-                        Expression::StructLiteral {
-                            loc: Loc::Codegen,
-                            ty: Type::Struct(StructType::AccountMeta),
-                            values: vec![
-                                Expression::GetRef {
-                                    loc: Loc::Codegen,
-                                    ty: Type::Address(false),
-                                    expr: Box::new(payer),
-                                },
-                                Expression::BoolLiteral {
-                                    loc: Loc::Codegen,
-                                    value: true,
-                                },
-                                Expression::BoolLiteral {
-                                    loc: Loc::Codegen,
-                                    value: true,
-                                },
-                            ],
-                        },
-                        Expression::StructLiteral {
-                            loc: Loc::Codegen,
-                            ty: Type::Struct(StructType::AccountMeta),
-                            values: vec![
-                                Expression::Builtin {
-                                    loc: Loc::Codegen,
-                                    tys: vec![Type::Ref(Box::new(Type::Address(false)))],
-                                    kind: Builtin::GetAddress,
-                                    args: vec![],
-                                },
-                                Expression::BoolLiteral {
-                                    loc: Loc::Codegen,
-                                    value: true,
-                                },
-                                Expression::BoolLiteral {
-                                    loc: Loc::Codegen,
-                                    value: true,
-                                },
-                            ],
-                        },
+                        account_meta_literal(ptr_to_address, true, true),
+                        account_meta_literal(
+                            Expression::Builtin {
+                                loc: Loc::Codegen,
+                                tys: vec![Type::Ref(Box::new(Type::Address(false)))],
+                                kind: Builtin::GetAddress,
+                                args: vec![],
+                            },
+                            true,
+                            true,
+                        ),
                     ],
                 },
             },
         );
 
-        // Calculate minimum balance for rent-excempt
+        // Calculate minimum balance for rent-exempt
         let (space, lamports) = if let Some(ConstructorAnnotation::Space(space_expr)) = func
             .annotations
             .iter()

+ 2 - 0
src/codegen/subexpression_elimination/instruction.rs

@@ -336,6 +336,7 @@ impl<'a, 'b: 'a> AvailableExpressionSet<'a> {
                 seeds,
                 loc,
                 accounts,
+                constructor_no,
             } => {
                 let new_value = value
                     .as_ref()
@@ -361,6 +362,7 @@ impl<'a, 'b: 'a> AvailableExpressionSet<'a> {
                     success: *success,
                     res: *res,
                     contract_no: *contract_no,
+                    constructor_no: *constructor_no,
                     encoded_args: self.regenerate_expression(encoded_args, ave, cst).1,
                     value: new_value,
                     gas: self.regenerate_expression(gas, ave, cst).1,

+ 1 - 0
src/codegen/subexpression_elimination/tests.rs

@@ -423,6 +423,7 @@ fn string() {
         success: None,
         res: 0,
         contract_no: 0,
+        constructor_no: None,
         encoded_args: concat.clone(),
         value: Some(compare.clone()),
         gas: concat2.clone(),

+ 1 - 0
src/emit/instructions.rs

@@ -671,6 +671,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             seeds,
             loc,
             accounts,
+            constructor_no: _,
         } => {
             let encoded_args = expression(target, bin, encoded_args, &w.vars, function, ns);
             let encoded_args_len = bin.vector_len(encoded_args).as_basic_value_enum();

+ 4 - 6
src/emit/solana/target.rs

@@ -1250,7 +1250,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         function: FunctionValue<'b>,
         _success: Option<&mut BasicValueEnum<'b>>,
         contract_no: usize,
-        address: PointerValue<'b>,
+        _address: PointerValue<'b>,
         encoded_args: BasicValueEnum<'b>,
         encoded_args_len: BasicValueEnum<'b>,
         mut contract_args: ContractArgs<'b>,
@@ -1267,11 +1267,9 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         let payload = binary.vector_bytes(encoded_args);
         let payload_len = encoded_args_len.into_int_value();
 
-        if contract_args.accounts.is_some() {
-            self.build_invoke_signed_c(binary, function, payload, payload_len, contract_args);
-        } else {
-            self.build_external_call(binary, address, payload, payload_len, contract_args, ns);
-        }
+        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);
     }
 
     fn builtin_function(

+ 8 - 5
src/sema/ast.rs

@@ -334,13 +334,16 @@ pub struct Function {
 /// it is stored in a IndexMap<String, SolanaAccount> (see above)
 #[derive(Clone, Copy, Debug)]
 pub struct SolanaAccount {
+    pub loc: pt::Loc,
     pub is_signer: bool,
     pub is_writer: bool,
+    /// Has the compiler automatically generated this account entry?
+    pub generated: bool,
 }
 
 pub enum ConstructorAnnotation {
     Seed(Expression),
-    Payer(Expression),
+    Payer(pt::Loc, String),
     Space(Expression),
     Bump(Expression),
 }
@@ -348,10 +351,10 @@ pub enum ConstructorAnnotation {
 impl CodeLocation for ConstructorAnnotation {
     fn loc(&self) -> pt::Loc {
         match self {
-            ConstructorAnnotation::Seed(expr) => expr.loc(),
-            ConstructorAnnotation::Payer(expr) => expr.loc(),
-            ConstructorAnnotation::Space(expr) => expr.loc(),
-            ConstructorAnnotation::Bump(expr) => expr.loc(),
+            ConstructorAnnotation::Seed(expr)
+            | ConstructorAnnotation::Space(expr)
+            | ConstructorAnnotation::Bump(expr) => expr.loc(),
+            ConstructorAnnotation::Payer(loc, _) => *loc,
         }
     }
 }

+ 6 - 2
src/sema/dotgraphviz.rs

@@ -227,8 +227,12 @@ impl Dot {
                     ConstructorAnnotation::Bump(expr) => {
                         self.add_expression(expr, Some(func), ns, node, "bump".into());
                     }
-                    ConstructorAnnotation::Payer(expr) => {
-                        self.add_expression(expr, Some(func), ns, node, "payer".into());
+                    ConstructorAnnotation::Payer(_, name) => {
+                        self.add_node(
+                            Node::new("payer", vec![name.clone()]),
+                            Some(node),
+                            Some(String::from("payer declaration")),
+                        );
                     }
                 };
             }

+ 15 - 0
src/sema/expression/function_call.rs

@@ -2209,6 +2209,21 @@ pub(super) fn parse_call_args(
                     .to_string(),
             ));
             return Err(());
+        } else if res.accounts.is_none()
+            && !matches!(
+                ns.functions[context.function_no.unwrap()].visibility,
+                Visibility::External(_)
+            )
+            && !ns.functions[context.function_no.unwrap()].is_constructor()
+        {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "accounts are required for calling a contract. You can either provide the \
+                accounts with the {accounts: ...} call argument or change this function's \
+                visibility to external"
+                    .to_string(),
+            ));
+            return Err(());
         }
     }
 

+ 49 - 20
src/sema/function_annotation.rs

@@ -9,11 +9,15 @@ use super::{
     unused_variable::used_variable,
     Symtable,
 };
+use crate::sema::ast::SolanaAccount;
 use crate::sema::expression::literals::number_literal;
 use crate::sema::expression::resolve_expression::expression;
+use crate::sema::solana_accounts::BuiltinAccounts;
 use crate::Target;
+use indexmap::map::Entry;
 use num_traits::ToPrimitive;
 use solang_parser::pt::{self, CodeLocation};
+use std::str::FromStr;
 
 /// Resolve the prototype annotation for functions (just the selector). These
 /// annotations can be resolved for functions without a body. This means they
@@ -261,31 +265,56 @@ pub fn function_body_annotations(
                 }
             }
             "payer" if is_solana_constructor => {
-                let ty = Type::Address(false);
                 let loc = note.loc;
 
-                if let Ok(expr) = expression(
-                    &note.value,
-                    context,
-                    ns,
-                    symtable,
-                    &mut diagnostics,
-                    ResolveTo::Type(&ty),
-                ) {
-                    if let Ok(expr) = expr.cast(&expr.loc(), &ty, true, ns, &mut diagnostics) {
-                        if let Some(prev) = &payer {
+                if let pt::Expression::Variable(id) = &note.value {
+                    if BuiltinAccounts::from_str(&id.name).is_ok() {
+                        diagnostics.push(Diagnostic::error(
+                            id.loc,
+                            format!("'{}' is a reserved account name", id.name),
+                        ));
+                        continue;
+                    }
+
+                    match ns.functions[function_no]
+                        .solana_accounts
+                        .borrow_mut()
+                        .entry(id.name.clone())
+                    {
+                        Entry::Occupied(other_account) => {
                             diagnostics.push(Diagnostic::error_with_note(
-                                loc,
-                                "duplicate @payer annotation for constructor".into(),
-                                *prev,
-                                "previous @payer".into(),
+                                id.loc,
+                                format!("account '{}' already defined", id.name),
+                                other_account.get().loc,
+                                "previous definition".to_string(),
                             ));
-                        } else {
-                            payer = Some(loc);
-                            used_variable(ns, &expr, symtable);
-                            resolved_annotations.push(ConstructorAnnotation::Payer(expr));
+                        }
+                        Entry::Vacant(vacancy) => {
+                            if let Some(prev) = &payer {
+                                diagnostics.push(Diagnostic::error_with_note(
+                                    loc,
+                                    "duplicate @payer annotation for constructor".into(),
+                                    *prev,
+                                    "previous @payer".into(),
+                                ));
+                            } else {
+                                payer = Some(loc);
+                                vacancy.insert(SolanaAccount {
+                                    loc: note.loc,
+                                    is_signer: true,
+                                    is_writer: true,
+                                    generated: false,
+                                });
+                                resolved_annotations
+                                    .push(ConstructorAnnotation::Payer(loc, id.name.clone()));
+                            }
                         }
                     }
+                } else {
+                    diagnostics.push(Diagnostic::error(
+                        note.loc,
+                        "invalid parameter for annotation".to_string(),
+                    ));
                 }
             }
             _ => diagnostics.push(Diagnostic::error(
@@ -300,7 +329,7 @@ pub fn function_body_annotations(
 
     if !resolved_annotations.is_empty() && diagnostics.is_empty() && payer.is_none() {
         diagnostics.push(Diagnostic::error(
-            resolved_annotations[0].loc(),
+            ns.functions[function_no].loc,
             "@payer annotation required for constructor".into(),
         ));
     }

+ 1 - 0
src/sema/mod.rs

@@ -31,6 +31,7 @@ mod function_annotation;
 mod functions;
 mod mutability;
 mod namespace;
+pub(crate) mod solana_accounts;
 mod statements;
 pub mod symtable;
 pub mod tags;

+ 65 - 0
src/sema/solana_accounts.rs

@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::str::FromStr;
+
+pub enum BuiltinAccounts {
+    /// These are the accounts that we can collect from a contract and that Anchor will populate
+    /// automatically if their names match the source code description:
+    /// https://github.com/coral-xyz/anchor/blob/06c42327d4241e5f79c35bc5588ec0a6ad2fedeb/ts/packages/anchor/src/program/accounts-resolver.ts#L54-L60
+    ClockAccount,
+    SystemAccount,
+    AssociatedTokenProgram,
+    RentAccount,
+    TokenProgramId,
+    /// We automatically include the following accounts in the IDL, but these are not
+    /// automatically populated
+    DataAccount,
+    InstructionAccount,
+}
+
+impl ToString for BuiltinAccounts {
+    fn to_string(&self) -> String {
+        let str = match self {
+            BuiltinAccounts::ClockAccount => "clock",
+            BuiltinAccounts::SystemAccount => "systemProgram",
+            BuiltinAccounts::AssociatedTokenProgram => "associatedTokenProgram",
+            BuiltinAccounts::RentAccount => "rent",
+            BuiltinAccounts::TokenProgramId => "tokenProgram",
+            BuiltinAccounts::DataAccount => "dataAccount",
+            BuiltinAccounts::InstructionAccount => "SysvarInstruction",
+        };
+
+        str.to_string()
+    }
+}
+
+impl FromStr for BuiltinAccounts {
+    type Err = ();
+
+    fn from_str(str: &str) -> Result<Self, Self::Err> {
+        let account = match str {
+            "clock" => BuiltinAccounts::ClockAccount,
+            "systemProgram" => BuiltinAccounts::SystemAccount,
+            "associatedTokenProgram" => BuiltinAccounts::AssociatedTokenProgram,
+            "rent" => BuiltinAccounts::RentAccount,
+            "tokenProgram" => BuiltinAccounts::TokenProgramId,
+            "dataAccount" => BuiltinAccounts::DataAccount,
+            "SysvarInstruction" => BuiltinAccounts::InstructionAccount,
+            _ => return Err(()),
+        };
+
+        Ok(account)
+    }
+}
+
+impl PartialEq<BuiltinAccounts> for &String {
+    fn eq(&self, other: &BuiltinAccounts) -> bool {
+        *self == &other.to_string()
+    }
+}
+
+impl PartialEq<BuiltinAccounts> for String {
+    fn eq(&self, other: &BuiltinAccounts) -> bool {
+        self == &other.to_string()
+    }
+}

+ 1 - 1
src/sema/tests/mod.rs

@@ -464,7 +464,7 @@ contract aborting {
 }
 
 contract runner {
-    function test(address a) public pure {
+    function test(address a) external pure {
         aborting abort = new aborting{address: a}();
 
         try abort.abort() returns (int32 a, bool b) {

+ 2 - 2
tests/codegen_testcases/solidity/constructor_with_metas.sol

@@ -5,12 +5,12 @@ import 'solana';
 contract creator {
     Child public c;
     // BEGIN-CHECK: creator::creator::function::create_child_with_meta__address_address
-    function create_child_with_meta(address child, address payer) public {
+    function create_child_with_meta(address child, address payer) external {
         AccountMeta[2] metas = [
             AccountMeta({pubkey: child, is_signer: false, is_writable: false}),
             AccountMeta({pubkey: payer, is_signer: true, is_writable: true})
         ];
-        // CHECK: constructor salt: value: gas:uint64 0 address: seeds: Child encoded buffer: %abi_encoded.temp.16 accounts: %metas
+        // CHECK: constructor(no: 4) salt: value: gas:uint64 0 address: seeds: Child encoded buffer: %abi_encoded.temp.16 accounts: %metas
         c = new Child{accounts: metas}(payer);
 
         c.say_hello();

+ 31 - 0
tests/codegen_testcases/solidity/solana_payer_account.sol

@@ -0,0 +1,31 @@
+// RUN: --target solana --emit cfg
+
+@program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER")
+contract Builder {
+    Built other;
+    // BEGIN-CHECK: Builder::Builder::function::build_this__address
+    function build_this(address addr) external {
+        // CHECK: constructor(no: 4) salt: value: gas:uint64 0 address: seeds: Built encoded buffer: %abi_encoded.temp.17 accounts: [3] [ struct { (deref (arg #0), true, false }, struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 1]) field 0)), true, true }, struct { (deref address 0x0, false, false } ]
+        other = new Built{address: addr}("my_seed");
+    }
+
+    function call_that() public pure {
+        other.say_this("Hold up! I'm calling!");
+    }
+}
+
+
+@program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
+contract Built {
+    @seed(my_seed)
+    @space(1024)
+    @payer(payer_account)
+    constructor(bytes my_seed) {}
+    // BEGIN-CHECK: solang_dispatch
+    // CHECK: ty:struct AccountMeta[2] %metas.temp.10 = [2] [ struct { (load (struct (subscript struct AccountInfo[] (builtin Accounts ())[uint32 1]) field 0)), true, true }, struct { (builtin GetAddress ()), true, true } ]
+    // The account metas should have the proper index in the AccountInfo array: 1
+
+    function say_this(string text) public pure {
+        print(text);
+    }
+}

+ 1 - 1
tests/codegen_testcases/solidity/unused_variable_elimination.sol

@@ -77,7 +77,7 @@ contract c3 {
         c2 ct = new c2();
 
         return 3;
-// CHECK: constructor salt: value: gas:uint64 0 address: seeds: c2 encoded buffer: %abi_encoded.temp.108 accounts: 
+// CHECK: constructor(no: ) salt: value: gas:uint64 0 address: seeds: c2 encoded buffer: %abi_encoded.temp.108 accounts: 
     }
 
 // BEGIN-CHECK: c3::function::test7

+ 27 - 0
tests/contract_testcases/solana/annotations/account_name_collision.sol

@@ -0,0 +1,27 @@
+@program_id("SoLDxXQ9GMoa15i4NavZc61XGkas2aom4aNiWT6KUER")
+contract Builder {
+    BeingBuilt other;
+    
+    @payer(payer_account)
+    constructor(address addr) {
+        other = new BeingBuilt{address: addr}("abc");
+    }
+}
+
+
+@program_id("SoLGijpEqEeXLEqa9ruh7a6Lu4wogd6rM8FNoR7e3wY")
+contract BeingBuilt {
+    @seed(my_seed)
+    @space(1024)
+    @payer(payer_account)
+    constructor(bytes my_seed) {}
+
+    function say_this(string text) public pure {
+        print(text);
+    }
+}
+
+// ---- Expect: diagnostics ----
+// warning: 3:5-21: storage variable 'other' has been assigned, but never read
+// error: 5:5-26: account name collision encountered. Calling a function that requires an account whose name is also defined in the current function will create duplicate names in the IDL. Please, rename one of the accounts
+// 	note 16:5-26: other declaration

+ 0 - 0
tests/contract_testcases/solana/annotations_bad.sol → tests/contract_testcases/solana/annotations/annotations_bad.sol


+ 72 - 0
tests/contract_testcases/solana/annotations/bad_accounts.sol

@@ -0,0 +1,72 @@
+
+contract BeingBuilt1 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(clock)
+    constructor(bytes my_seed) {}
+}
+
+contract BeingBuilt2 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(systemProgram)
+    constructor(bytes my_seed) {}
+}
+
+contract BeingBuilt3 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(associatedTokenProgram)
+    constructor(bytes my_seed) {}
+}
+
+contract BeingBuilt4 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(rent)
+    constructor(bytes my_seed) {}
+}
+
+contract BeingBuilt5 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(tokenProgram)
+    constructor(bytes my_seed) {}
+}
+
+contract BeingBuilt6 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(dataAccount)
+    constructor(bytes my_seed) {}
+}
+
+contract BeingBuilt7 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(SysvarInstruction)
+    constructor(bytes my_seed) {}
+}
+
+contract BeingBuilt8 {
+    @seed(my_seed)
+    @space(1024)
+    @payer(solang)
+    @payer(solang)
+    constructor(bytes my_seed) {}
+
+    function say_this(string text) public pure {
+        print(text);
+    }
+}
+
+// ---- Expect: diagnostics ----
+// error: 5:12-17: 'clock' is a reserved account name
+// error: 12:12-25: 'systemProgram' is a reserved account name
+// error: 19:12-34: 'associatedTokenProgram' is a reserved account name
+// error: 26:12-16: 'rent' is a reserved account name
+// error: 33:12-24: 'tokenProgram' is a reserved account name
+// error: 40:12-23: 'dataAccount' is a reserved account name
+// error: 47:12-29: 'SysvarInstruction' is a reserved account name
+// error: 55:12-18: account 'solang' already defined
+// 	note 54:5-19: previous definition

+ 27 - 0
tests/contract_testcases/solana/annotations/constructor_external_function.sol

@@ -0,0 +1,27 @@
+@program_id("Foo5mMfYo5RhRcWa4NZ2bwFn4Kdhe8rNK5jchxsKrivA")
+contract Foo {
+    function say_hello() public pure {
+        print("Hello from foo");
+    }
+}
+
+contract Bar {
+    Foo public foo;
+
+    function external_create_foo(address addr) external {
+        // This is allowed
+        foo = new Foo{address: addr}();
+    }
+
+    function create_foo(address new_address) public {
+        // This is not allowed
+        foo = new Foo{address: new_address}();
+    }
+
+    function call_foo() public pure {
+        foo.say_hello();
+    }
+}
+
+// ---- Expect: diagnostics ----
+// error: 18:15-46: 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

+ 4 - 4
tests/contract_testcases/solana/constructor_seeds.sol → tests/contract_testcases/solana/annotations/constructor_seeds.sol

@@ -31,7 +31,7 @@ contract c3 {
 	@seed(hex"41420044")
 	@bump(b)
 	@bump(5)
-	@payer(address"Chi1doxDSNjrmbZ5sq3H2cXyTq3KNfGepmbhyHaxcr8")
+	@payer(my_account)
 	@payer(bar)
 	@space(1025 + 5)
 	@space(4)
@@ -61,18 +61,18 @@ contract c4 {
 // error: 15:15: address literal 102 invalid character '0'
 // error: 20:8-9: duplicate @bump annotation for constructor
 // 	note 19:2-10: previous @bump
-// error: 21:9-60: address literal Chi1doxDSNjrmbZ5sq3H2cXyTq3KNfGepmbhyHaxcr incorrect length of 31
+// error: 21:2-61: invalid parameter for annotation
 // error: 24:2-11: duplicate @space annotation for constructor
 // 	note 23:2-18: previous @space
 // error: 28:1-17: annotion takes an account, for example '@program_id("BBH7Xi5ddus5EoQhzJLgyodVxJJGkvBRCY5AhBA1jwUr")'
 // error: 33:8-9: duplicate @bump annotation for constructor
 // 	note 32:2-10: previous @bump
 // error: 35:2-13: duplicate @payer annotation for constructor
-// 	note 34:2-62: previous @payer
+// 	note 34:2-20: previous @payer
 // error: 37:2-11: duplicate @space annotation for constructor
 // 	note 36:2-18: previous @space
 // error: 40:2-14: unknown annotation seed for function
 // error: 41:2-10: unknown annotation bump for function
 // error: 42:2-62: unknown annotation payer for function
 // error: 43:2-11: unknown annotation space for function
-// error: 49:8-21: @payer annotation required for constructor
+// error: 52:2-16: @payer annotation required for constructor

+ 0 - 0
tests/contract_testcases/solana/constructor_seeds_bad.sol → tests/contract_testcases/solana/annotations/constructor_seeds_bad.sol


+ 4 - 3
tests/solana.rs

@@ -66,6 +66,7 @@ pub fn account_new() -> Account {
     a
 }
 
+#[derive(Default)]
 struct AccountState {
     data: Vec<u8>,
     owner: Option<Account>,
@@ -1003,10 +1004,10 @@ impl Pubkey {
 pub struct AccountMeta {
     /// An account's public key
     pub pubkey: Pubkey,
-    /// True if an Instruction requires a Transaction signature matching `pubkey`.
-    pub is_signer: bool,
     /// True if the `pubkey` can be loaded as a read-write account.
     pub is_writable: bool,
+    /// True if an Instruction requires a Transaction signature matching `pubkey`.
+    pub is_signer: bool,
 }
 
 fn translate(
@@ -1344,7 +1345,7 @@ fn sol_invoke_signed_c(
             vm.stack.insert(0, p);
 
             let res = vm.execute(&instruction.accounts, &instruction.data);
-            assert!(matches!(res, StableResult::Ok(0)));
+            assert!(matches!(res, StableResult::Ok(0)), "external call failed");
 
             let refs = context.refs.try_borrow_mut().unwrap();
 

+ 24 - 30
tests/solana_tests/create_contract.rs

@@ -11,7 +11,7 @@ fn simple_create_contract_no_seed() {
     let mut vm = build_solidity(
         r#"
         contract bar0 {
-            function test_other(address foo, address payer) public returns (bar1) {
+            function test_other(address foo, address payer) external returns (bar1) {
                 bar1 x = new bar1{address: foo}("yo from bar0", payer);
 
                 return x;
@@ -59,6 +59,8 @@ fn simple_create_contract_no_seed() {
         },
     );
 
+    vm.account_data.insert([0; 32], AccountState::default());
+
     let bar1 = vm
         .function(
             "test_other",
@@ -85,7 +87,7 @@ fn simple_create_contract() {
     let mut vm = build_solidity(
         r#"
         contract bar0 {
-            function test_other(address foo, address payer) public returns (bar1) {
+            function test_other(address foo, address payer) external returns (bar1) {
                 bar1 x = new bar1{address: foo}("yo from bar0", payer);
 
                 return x;
@@ -122,6 +124,8 @@ fn simple_create_contract() {
     let seed = vm.create_pda(&program_id);
     let payer = account_new();
 
+    vm.account_data.insert([0; 32], AccountState::default());
+
     let bar1 = vm
         .function(
             "test_other",
@@ -212,12 +216,13 @@ fn create_contract_with_payer() {
 }
 
 #[test]
+#[should_panic(expected = "external call failed")]
 // 64424509440 = 15 << 32 (ERROR_NEW_ACCOUNT_NEEDED)
 fn missing_contract() {
     let mut vm = build_solidity(
         r#"
         contract bar0 {
-            function test_other(address foo) public returns (bar1) {
+            function test_other(address foo) external returns (bar1) {
                 bar1 x = new bar1{address: foo}("yo from bar0");
 
                 return x;
@@ -247,8 +252,9 @@ fn missing_contract() {
     let missing = account_new();
 
     vm.logs.clear();
+    vm.account_data.insert(missing, AccountState::default());
+    // There is no payer account, so the external call fails.
     let _ = vm.function_must_fail("test_other", &[BorshToken::Address(missing)]);
-    assert_eq!(vm.logs, "new account needed");
 }
 
 #[test]
@@ -256,7 +262,7 @@ fn two_contracts() {
     let mut vm = build_solidity(
         r#"
         contract bar0 {
-            function test_other(address a, address b) public returns (bar1) {
+            function test_other(address a, address b) external returns (bar1) {
                 bar1 x = new bar1{address: a}("yo from bar0");
                 bar1 y = new bar1{address: b}("hi from bar0");
 
@@ -266,7 +272,7 @@ fn two_contracts() {
 
         @program_id("CPDgqnhHDCsjFkJKMturRQ1QeM9EXZg3EYCeDoRP8pdT")
         contract bar1 {
-            @payer(address"3wvhRNAJSDCk5Mub8NEcShszRrHHVDHsuSUdAcL2aaMV")
+            @payer(payer_account)
             constructor(string v) {
                 print("bar1 says: " + v);
             }
@@ -286,6 +292,7 @@ fn two_contracts() {
     let seed1 = vm.create_pda(&program_id);
     let seed2 = vm.create_pda(&program_id);
 
+    vm.account_data.insert([0; 32], AccountState::default());
     let _bar1 = vm.function(
         "test_other",
         &[BorshToken::Address(seed1.0), BorshToken::Address(seed2.0)],
@@ -445,7 +452,7 @@ fn account_with_seed_bump_literals() {
             @space(2 << 8 + 4)
             @seed("meh")
             @bump(33) // 33 = ascii !
-            @payer(address"vS5Tf8mnHGbUCMLQWrnvsFvwHLfA5p3yQM3ozxPckn8")
+            @payer(my_account)
             constructor() {}
 
             function hello() public returns (bool) {
@@ -482,9 +489,9 @@ fn create_child() {
         contract creator {
             Child public c;
 
-            function create_child(address child, address payer) public {
+            function create_child(address child) external {
                 print("Going to create child");
-                c = new Child{address: child}(payer);
+                c = new Child{address: child}();
 
                 c.say_hello();
             }
@@ -494,7 +501,7 @@ fn create_child() {
         contract Child {
             @payer(payer)
             @space(511 + 7)
-            constructor(address payer) {
+            constructor() {
                 print("In child constructor");
             }
 
@@ -512,11 +519,12 @@ fn create_child() {
     let program_id = vm.stack[0].program;
 
     let seed = vm.create_pda(&program_id);
+    vm.account_data.insert(payer, AccountState::default());
+    vm.account_data.insert(seed.0, AccountState::default());
 
-    vm.function(
-        "create_child",
-        &[BorshToken::Address(seed.0), BorshToken::Address(payer)],
-    );
+    vm.account_data.insert([0; 32], AccountState::default());
+
+    vm.function("create_child", &[BorshToken::Address(seed.0)]);
 
     assert_eq!(
         vm.logs,
@@ -570,23 +578,9 @@ contract Child {
 
     let seed = vm.create_pda(&program_id);
 
-    vm.account_data.insert(
-        seed.0,
-        AccountState {
-            data: vec![],
-            owner: None,
-            lamports: 0,
-        },
-    );
+    vm.account_data.insert(seed.0, AccountState::default());
 
-    vm.account_data.insert(
-        payer,
-        AccountState {
-            data: vec![],
-            owner: None,
-            lamports: 0,
-        },
-    );
+    vm.account_data.insert(payer, AccountState::default());
 
     let mut metas = vm.default_metas();
     metas.push(AccountMeta {

+ 8 - 25
tests/solana_tests/runtime_errors.rs

@@ -62,18 +62,6 @@ contract RuntimeErrors {
         arr.pop();
     }
 
-
-    // contract creation failed
-    function create_child() public {
-        address a = address(0);
-        c = new child{address: a}();
-        //c2 = new child();
-        uint128 x = address(this).balance;
-        //print("sesa");
-        print("x = {}".format(x));
-
-    }
-
     function i_will_revert() public {
         revert();
     }
@@ -200,7 +188,7 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: read integer out of bounds in test.sol:86:18-30,\n"
+        "runtime_error: read integer out of bounds in test.sol:74:18-30,\n"
     );
     vm.logs.clear();
 
@@ -214,7 +202,7 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: truncated type overflows in test.sol:91:37-42,\n"
+        "runtime_error: truncated type overflows in test.sol:79:37-42,\n"
     );
     vm.logs.clear();
 
@@ -222,7 +210,7 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: reached invalid instruction in test.sol:102:13-22,\n"
+        "runtime_error: reached invalid instruction in test.sol:90:13-22,\n"
     );
 
     vm.logs.clear();
@@ -246,7 +234,7 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: data does not fit into buffer in test.sol:81:18-28,\n"
+        "runtime_error: data does not fit into buffer in test.sol:69:18-28,\n"
     );
 
     vm.logs.clear();
@@ -275,7 +263,7 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: array index out of bounds in test.sol:97:16-21,\n"
+        "runtime_error: array index out of bounds in test.sol:85:16-21,\n"
     );
 
     vm.logs.clear();
@@ -290,7 +278,7 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: integer too large to write in buffer in test.sol:75:18-31,\n"
+        "runtime_error: integer too large to write in buffer in test.sol:63:18-31,\n"
     );
 
     vm.logs.clear();
@@ -305,7 +293,7 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: bytes cast error in test.sol:110:23-40,\n"
+        "runtime_error: bytes cast error in test.sol:98:23-40,\n"
     );
 
     vm.logs.clear();
@@ -314,13 +302,8 @@ contract calle_contract {
 
     assert_eq!(
         vm.logs,
-        "runtime_error: revert encountered in test.sol:70:9-17,\n"
+        "runtime_error: revert encountered in test.sol:58:9-17,\n"
     );
 
     vm.logs.clear();
-
-    _res = vm.function_must_fail("create_child", &[]);
-
-    assert_eq!(vm.logs, "new account needed");
-    vm.logs.clear();
 }