Explorar el Código

Refactor payer annotation (#1345)

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>
Lucas Steuernagel hace 2 años
padre
commit
e93a136dfd
Se han modificado 46 ficheros con 1005 adiciones y 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)
     @seed(seed_val)
     @bump(bump_val)
     @bump(bump_val)
     @payer(payer)
     @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 {
 contract adult {
-    function test(address addr) public {
+    function test(address addr) external {
         hatchling h = new hatchling{address: addr}("luna");
         hatchling h = new hatchling{address: addr}("luna");
     }
     }
 }
 }

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

@@ -15,7 +15,7 @@ contract hatchling {
 }
 }
 
 
 contract creator {
 contract creator {
-    function create_hatchling(address new_address) public {
+    function create_hatchling(address new_address) external {
         hatchling h;
         hatchling h;
        
        
 	h = new hatchling{address: new_address}("luna", address(this));
 	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 {
 contract Bar {
     Foo public foo;
     Foo public foo;
 
 
-    function create_foo(address new_address) public {
+    function create_foo(address new_address) external {
         foo = new Foo{address: new_address}();
         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
 .. include:: ../examples/solana/contract_address.sol
   :code: solidity
   :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,
 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
 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.
 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
 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.
 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
 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
 ``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.
 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>`_
 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>`_
 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;
     Child public c_metas;
     Child public c_metas;
 
 
-    function create_child(address child, address payer) public {
+    function create_child(address child) external {
         print("Going to create child");
         print("Going to create child");
-        c = new Child{address: child}(payer);
+        c = new Child{address: child}();
 
 
         c.say_hello();
         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");
         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();
         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");
         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 {
     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})
             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();
         c_metas.use_metas();
     }
     }
+
+    function create_without_annotation(address child) external {
+        MyCreature cc = new MyCreature{address: child}();
+        cc.say_my_name();
+    }
 }
 }
 
 
 @program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT")
 @program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT")
 contract Child {
 contract Child {
     @payer(payer)
     @payer(payer)
     @space(511 + 7)
     @space(511 + 7)
-    constructor(address payer) {
+    constructor() {
         print("In child constructor");
         print("In child constructor");
     }
     }
 
 
@@ -60,7 +67,7 @@ contract Seed1 {
     @seed(seed)
     @seed(seed)
     @bump(bump)
     @bump(bump)
     @space(space)
     @space(space)
-    constructor(address payer, bytes seed, bytes1 bump, uint64 space) {
+    constructor(bytes seed, bytes1 bump, uint64 space) {
         print("In Seed1 constructor");
         print("In Seed1 constructor");
     }
     }
 
 
@@ -77,7 +84,7 @@ contract Seed2 {
     @seed("sunflower")
     @seed("sunflower")
     @seed(seed)
     @seed(seed)
     @space(space + 23)
     @space(space + 23)
-    constructor(address payer, bytes seed, uint64 space) {
+    constructor(bytes seed, uint64 space) {
         my_seed = seed;
         my_seed = seed;
 
 
         print("In Seed2 constructor");
         print("In Seed2 constructor");
@@ -90,4 +97,15 @@ contract Seed2 {
             print("I am PDA.");
             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 { Connection, Keypair, PublicKey, sendAndConfirmTransaction, SystemProgram, Transaction } from '@solana/web3.js';
 import expect from 'expect';
 import expect from 'expect';
 import { Program, Provider, BN } from '@project-serum/anchor';
 import { Program, Provider, BN } from '@project-serum/anchor';
-import { loadContract } from './setup';
+import {create_account, loadContract} from './setup';
 import fs from 'fs';
 import fs from 'fs';
 
 
 describe('ChildContract', function () {
 describe('ChildContract', function () {
@@ -22,12 +22,14 @@ describe('ChildContract', function () {
         let child_program = new PublicKey("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT");
         let child_program = new PublicKey("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT");
         let child = Keypair.generate();
         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([
             .remainingAccounts([
                 { pubkey: child_program, isSigner: false, isWritable: false },
                 { pubkey: child_program, isSigner: false, isWritable: false },
                 { pubkey: child.publicKey, isSigner: true, isWritable: true },
                 { pubkey: child.publicKey, isSigner: true, isWritable: true },
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
             ])
             ])
             .signers([payer, child])
             .signers([payer, child])
             .rpc({ commitment: 'confirmed' });
             .rpc({ commitment: 'confirmed' });
@@ -49,12 +51,14 @@ describe('ChildContract', function () {
         let [address, bump] = await PublicKey.findProgramAddress([seed], seed_program);
         let [address, bump] = await PublicKey.findProgramAddress([seed], seed_program);
 
 
         const signature = await program.methods.createSeed1(
         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([
             .remainingAccounts([
                 { pubkey: seed_program, isSigner: false, isWritable: false },
                 { pubkey: seed_program, isSigner: false, isWritable: false },
                 { pubkey: address, isSigner: false, isWritable: true },
                 { pubkey: address, isSigner: false, isWritable: true },
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
             ])
             ])
             .signers([payer])
             .signers([payer])
             .rpc({ commitment: 'confirmed' });
             .rpc({ commitment: 'confirmed' });
@@ -80,12 +84,14 @@ describe('ChildContract', function () {
         let seed = Buffer.concat([bare_seed, Buffer.from([bump])]);
         let seed = Buffer.concat([bare_seed, Buffer.from([bump])]);
 
 
         const signature = await program.methods.createSeed2(
         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([
             .remainingAccounts([
                 { pubkey: seed_program, isSigner: false, isWritable: false },
                 { pubkey: seed_program, isSigner: false, isWritable: false },
                 { pubkey: address, isSigner: false, isWritable: true },
                 { pubkey: address, isSigner: false, isWritable: true },
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },
             ])
             ])
             .signers([payer])
             .signers([payer])
             .rpc({ commitment: 'confirmed' });
             .rpc({ commitment: 'confirmed' });
@@ -134,4 +140,28 @@ describe('ChildContract', function () {
 
 
         expect(info?.data.length).toEqual(518);
         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 {
 contract RuntimeErrors {
     bytes b = hex"0000_00fa";
     bytes b = hex"0000_00fa";
     uint256[] arr;
     uint256[] arr;
-    Creature public c;
 
 
     constructor() {}
     constructor() {}
 
 
@@ -66,12 +65,6 @@ contract RuntimeErrors {
         e.say_my_name();
         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 {
     function i_will_revert() public {
         revert();
         revert();
     }
     }

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

@@ -1,9 +1,10 @@
 // SPDX-License-Identifier: Apache-2.0
 // 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 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 () {
 describe('Runtime Errors', function () {
     this.timeout(150000);
     this.timeout(150000);
@@ -24,7 +25,7 @@ describe('Runtime Errors', function () {
         }
         }
         catch (e: any) {
         catch (e: any) {
             const logs = e.simulationResponse.logs;
             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) {
         catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.popEmptyStorage().accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.invalidInstruction().accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.byteCastFailure(new BN(33)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.iWillRevert().accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.assertTest(new BN(9)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.writeIntegerFailure(new BN(1)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.writeBytesFailure(new BN(9)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.readIntegerFailure(new BN(2)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.outOfBounds(new BN(19)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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();
             let res = await program.methods.truncFailure(new BN(99999999999999)).accounts({ dataAccount: storage.publicKey }).simulate();
         } catch (e: any) {
         } catch (e: any) {
             const logs = e.simulationResponse.logs;
             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`);
     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);
     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 };
     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 lamports = await provider.connection.getMinimumBalanceForRentExemption(space);
 
 
     const transaction = new Transaction();
     const transaction = new Transaction();
@@ -63,7 +64,7 @@ export async function loadContractWithProvider(provider: AnchorProvider, name: s
     const storage = Keypair.generate();
     const storage = Keypair.generate();
     const program_key = loadKey(`${name}.key`);
     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);
     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 ...');
     console.log('Airdropping SOL to a new wallet ...');
     let signature = await connection.requestAirdrop(account.publicKey, 100 * LAMPORTS_PER_SOL);
     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;
     return account;
 }
 }
 
 
@@ -143,12 +149,10 @@ async function setup() {
 }
 }
 
 
 function newConnection(): Connection {
 function newConnection(): Connection {
-    const connection = new Connection(endpoint, {
+    return new Connection(endpoint, {
         commitment: "confirmed",
         commitment: "confirmed",
         confirmTransactionInitialTimeout: 1e6,
         confirmTransactionInitialTimeout: 1e6,
     });
     });
-
-    return connection;
 }
 }
 
 
 if (require.main === module) {
 if (require.main === module) {

+ 91 - 5
src/abi/tests.rs

@@ -1545,7 +1545,7 @@ fn data_account_signer() {
             }),
             }),
             IdlAccountItem::IdlAccount(IdlAccount {
             IdlAccountItem::IdlAccount(IdlAccount {
                 name: "wallet".to_string(),
                 name: "wallet".to_string(),
-                is_mut: false,
+                is_mut: true,
                 is_signer: true,
                 is_signer: true,
                 is_optional: Some(false),
                 is_optional: Some(false),
                 docs: None,
                 docs: None,
@@ -1596,7 +1596,7 @@ fn data_account_signer() {
             }),
             }),
             IdlAccountItem::IdlAccount(IdlAccount {
             IdlAccountItem::IdlAccount(IdlAccount {
                 name: "wallet".to_string(),
                 name: "wallet".to_string(),
-                is_mut: false,
+                is_mut: true,
                 is_signer: true,
                 is_signer: true,
                 is_optional: Some(false),
                 is_optional: Some(false),
                 docs: None,
                 docs: None,
@@ -1824,8 +1824,8 @@ fn system_account_for_payer_annotation() {
                 relations: vec![],
                 relations: vec![],
             }),
             }),
             IdlAccountItem::IdlAccount(IdlAccount {
             IdlAccountItem::IdlAccount(IdlAccount {
-                name: "wallet".to_string(),
-                is_mut: false,
+                name: "addr_".to_string(),
+                is_mut: true,
                 is_signer: true,
                 is_signer: true,
                 is_optional: Some(false),
                 is_optional: Some(false),
                 docs: None,
                 docs: None,
@@ -2184,7 +2184,7 @@ fn multiple_contracts() {
 contract creator {
 contract creator {
     Child public c;
     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");
         print("Going to create child");
         c = new Child{address: child}(payer);
         c = new Child{address: child}(payer);
 
 
@@ -2227,6 +2227,15 @@ contract Child {
                 pda: None,
                 pda: None,
                 relations: vec![],
                 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 {
             IdlAccountItem::IdlAccount(IdlAccount {
                 name: "systemProgram".to_string(),
                 name: "systemProgram".to_string(),
                 is_mut: false,
                 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 {
                 match note {
                     ast::ConstructorAnnotation::Bump(expr)
                     ast::ConstructorAnnotation::Bump(expr)
                     | ast::ConstructorAnnotation::Seed(expr)
                     | ast::ConstructorAnnotation::Seed(expr)
-                    | ast::ConstructorAnnotation::Payer(expr)
                     | ast::ConstructorAnnotation::Space(expr) => {
                     | ast::ConstructorAnnotation::Space(expr) => {
                         builder.expression(expr, &func.symtable)
                         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>,
         success: Option<usize>,
         res: usize,
         res: usize,
         contract_no: usize,
         contract_no: usize,
+        constructor_no: Option<usize>,
         encoded_args: Expression,
         encoded_args: Expression,
         value: Option<Expression>,
         value: Option<Expression>,
         gas: Expression,
         gas: Expression,
@@ -1234,14 +1235,20 @@ impl ControlFlowGraph {
                 value,
                 value,
                 address,seeds,
                 address,seeds,
                 accounts,
                 accounts,
+                constructor_no,
                 loc:_
                 loc:_
             } => format!(
             } => 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,
                 self.vars[res].id.name,
                 match success {
                 match success {
                     Some(i) => format!("%{}", self.vars[i].id.name),
                     Some(i) => format!("%{}", self.vars[i].id.name),
                     None => "_".to_string(),
                     None => "_".to_string(),
                 },
                 },
+                if let Some(no) = constructor_no {
+                    format!("{}", no)
+                } else {
+                    String::new()
+                },
                 match salt {
                 match salt {
                     Some(salt) => self.expr_to_string(contract, ns, salt),
                     Some(salt) => self.expr_to_string(contract, ns, salt),
                     None => "".to_string(),
                     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,
                     seeds,
                     loc,
                     loc,
                     accounts,
                     accounts,
+                    constructor_no,
                 } => {
                 } => {
                     let encoded_args = expression(encoded_args, Some(&vars), cfg, ns).0;
                     let encoded_args = expression(encoded_args, Some(&vars), cfg, ns).0;
                     let value = value
                     let value = value
@@ -220,6 +221,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         success: *success,
                         success: *success,
                         res: *res,
                         res: *res,
                         contract_no: *contract_no,
                         contract_no: *contract_no,
+                        constructor_no: *constructor_no,
                         encoded_args,
                         encoded_args,
                         value,
                         value,
                         gas,
                         gas,

+ 1 - 0
src/codegen/constructor.rs

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

+ 10 - 2
src/codegen/mod.rs

@@ -28,7 +28,7 @@ use self::{
     cfg::{optimize_and_check_cfg, ControlFlowGraph, Instr},
     cfg::{optimize_and_check_cfg, ControlFlowGraph, Instr},
     dispatch::function_dispatch,
     dispatch::function_dispatch,
     expression::expression,
     expression::expression,
-    solana_accounts::collect_accounts_from_contract,
+    solana_accounts::account_collection::collect_accounts_from_contract,
     vartable::Vartable,
     vartable::Vartable,
 };
 };
 use crate::sema::ast::{
 use crate::sema::ast::{
@@ -41,6 +41,7 @@ use crate::{
 use std::cmp::Ordering;
 use std::cmp::Ordering;
 
 
 use crate::codegen::cfg::ASTFunction;
 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::codegen::yul::generate_yul_function_cfg;
 use crate::sema::Recurse;
 use crate::sema::Recurse;
 use num_bigint::{BigInt, Sign};
 use num_bigint::{BigInt, Sign};
@@ -159,7 +160,14 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) {
     if ns.target == Target::Solana {
     if ns.target == Target::Solana {
         for contract_no in 0..ns.contracts.len() {
         for contract_no in 0..ns.contracts.len() {
             if ns.contracts[contract_no].instantiable {
             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
 // SPDX-License-Identifier: Apache-2.0
 
 
 use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy};
 use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy};
+use crate::codegen::solana_accounts::account_from_number;
 use crate::codegen::{Builtin, Expression};
 use crate::codegen::{Builtin, Expression};
 use crate::sema::ast::{Contract, Function, Mutability, Namespace, SolanaAccount};
 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 crate::sema::Recurse;
-use base58::FromBase58;
 use indexmap::IndexSet;
 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 to save the recursion data when traversing all the CFG instructions
 struct RecurseData<'a> {
 struct RecurseData<'a> {
@@ -93,6 +28,7 @@ struct RecurseData<'a> {
     contracts: &'a [Contract],
     contracts: &'a [Contract],
     /// The vector of functions from the contract
     /// The vector of functions from the contract
     functions: &'a [Function],
     functions: &'a [Function],
+    diagnostics: &'a mut Diagnostics,
 }
 }
 
 
 impl RecurseData<'_> {
 impl RecurseData<'_> {
@@ -101,7 +37,15 @@ impl RecurseData<'_> {
         if self.functions[self.ast_no]
         if self.functions[self.ast_no]
             .solana_accounts
             .solana_accounts
             .borrow_mut()
             .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()
             .is_none()
         {
         {
             self.accounts_added += 1;
             self.accounts_added += 1;
@@ -111,18 +55,21 @@ impl RecurseData<'_> {
     /// Add the system account to the function's indexmap
     /// Add the system account to the function's indexmap
     fn add_system_account(&mut self) {
     fn add_system_account(&mut self) {
         self.add_account(
         self.add_account(
-            SYSTEM_ACCOUNT.to_string(),
+            BuiltinAccounts::SystemAccount.to_string(),
             SolanaAccount {
             SolanaAccount {
+                loc: Loc::Codegen,
                 is_writer: false,
                 is_writer: false,
                 is_signer: false,
                 is_signer: false,
+                generated: true,
             },
             },
         );
         );
     }
     }
 }
 }
 
 
 /// Collect the accounts this contract needs
 /// 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 visiting_queue: IndexSet<(usize, usize)> = IndexSet::new();
+    let mut diagnostics = Diagnostics::default();
 
 
     for func_no in ns.contracts[contract_no].all_functions.keys() {
     for func_no in ns.contracts[contract_no].all_functions.keys() {
         if ns.functions[*func_no].is_public()
         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 {
             match &func.mutability {
                 Mutability::Pure(_) => (),
                 Mutability::Pure(_) => (),
                 Mutability::View(_) => {
                 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 {
                         SolanaAccount {
+                            loc: Loc::Codegen,
                             is_writer: false,
                             is_writer: false,
                             is_signer: 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 {
                         SolanaAccount {
+                            loc: Loc::Codegen,
                             is_writer: true,
                             is_writer: true,
                             /// With a @payer annotation, the account is created on-chain and needs a signer. The client
                             /// 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
                             /// 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,
                             /// with the seed using program derived address (pda) when SystemProgram.CreateAccount is called,
                             /// so no signer is required from the client.
                             /// so no signer is required from the client.
                             is_signer: func.has_payer_annotation() && !func.has_seed_annotation(),
                             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() {
             if func.is_constructor() && func.has_payer_annotation() {
                 func.solana_accounts.borrow_mut().insert(
                 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 {
                     SolanaAccount {
+                        loc: Loc::Codegen,
                         is_signer: false,
                         is_signer: false,
                         is_writer: false,
                         is_writer: false,
+                        generated: true,
                     },
                     },
                 );
                 );
             }
             }
@@ -191,6 +140,7 @@ pub(super) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace)
         contract_no,
         contract_no,
         functions: &ns.functions,
         functions: &ns.functions,
         contracts: &ns.contracts,
         contracts: &ns.contracts,
+        diagnostics: &mut diagnostics,
     };
     };
 
 
     let mut old_size: usize = 0;
     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);
         std::mem::swap(&mut visiting_queue, &mut recurse_data.next_queue);
         recurse_data.next_queue.clear();
         recurse_data.next_queue.clear();
     }
     }
+
+    diagnostics
 }
 }
 
 
 /// Collect the accounts in a function
 /// Collect the accounts in a function
@@ -372,6 +324,7 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) {
             address,
             address,
             seeds,
             seeds,
             accounts,
             accounts,
+            constructor_no,
             ..
             ..
         } => {
         } => {
             encoded_args.recurse(data, check_expression);
             encoded_args.recurse(data, check_expression);
@@ -390,6 +343,42 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) {
             }
             }
             if let Some(accounts) = accounts {
             if let Some(accounts) = accounts {
                 accounts.recurse(data, check_expression);
                 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();
             data.add_system_account();
@@ -410,10 +399,12 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) {
                     // Check if we can auto populate this account
                     // Check if we can auto populate this account
                     if let Some(account) = account_from_number(value) {
                     if let Some(account) = account_from_number(value) {
                         data.add_account(
                         data.add_account(
-                            account.to_string(),
+                            account,
                             SolanaAccount {
                             SolanaAccount {
+                                loc: Loc::Codegen,
                                 is_signer: false,
                                 is_signer: false,
                                 is_writer: false,
                                 is_writer: false,
+                                generated: true,
                             },
                             },
                         );
                         );
                     }
                     }
@@ -471,10 +462,12 @@ fn check_expression(expr: &Expression, data: &mut RecurseData) -> bool {
             ..
             ..
         } => {
         } => {
             data.add_account(
             data.add_account(
-                CLOCK_ACCOUNT.to_string(),
+                BuiltinAccounts::ClockAccount.to_string(),
                 SolanaAccount {
                 SolanaAccount {
+                    loc: Loc::Codegen,
                     is_signer: false,
                     is_signer: false,
                     is_writer: false,
                     is_writer: false,
+                    generated: true,
                 },
                 },
             );
             );
         }
         }
@@ -483,10 +476,12 @@ fn check_expression(expr: &Expression, data: &mut RecurseData) -> bool {
             ..
             ..
         } => {
         } => {
             data.add_account(
             data.add_account(
-                INSTRUCTION_ACCOUNT.to_string(),
+                BuiltinAccounts::InstructionAccount.to_string(),
                 SolanaAccount {
                 SolanaAccount {
+                    loc: Loc::Codegen,
                     is_writer: false,
                     is_writer: false,
                     is_signer: 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,
     cfg::ReturnCode, expression, Builtin, ControlFlowGraph, Expression, Instr, Options, Type,
     Vartable,
     Vartable,
 };
 };
+use crate::codegen::solana_accounts::account_management::{
+    account_meta_literal, index_accounts_vector,
+};
 use crate::sema::ast::{
 use crate::sema::ast::{
     self, ArrayLength, CallTy, ConstructorAnnotation, Function, FunctionAttributes, Namespace,
     self, ArrayLength, CallTy, ConstructorAnnotation, Function, FunctionAttributes, Namespace,
     StructType,
     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
         .annotations
         .iter()
         .iter()
         .find(|tag| matches!(tag, ConstructorAnnotation::Payer(..)))
         .find(|tag| matches!(tag, ConstructorAnnotation::Payer(..)))
@@ -245,7 +248,15 @@ pub(super) fn solana_deploy(
 
 
         let metas = vartab.temp_name("metas", &metas_ty);
         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(
         cfg.add(
             vartab,
             vartab,
@@ -257,51 +268,23 @@ pub(super) fn solana_deploy(
                     ty: metas_ty.clone(),
                     ty: metas_ty.clone(),
                     dimensions: vec![2],
                     dimensions: vec![2],
                     values: vec![
                     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
         let (space, lamports) = if let Some(ConstructorAnnotation::Space(space_expr)) = func
             .annotations
             .annotations
             .iter()
             .iter()

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

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

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

@@ -423,6 +423,7 @@ fn string() {
         success: None,
         success: None,
         res: 0,
         res: 0,
         contract_no: 0,
         contract_no: 0,
+        constructor_no: None,
         encoded_args: concat.clone(),
         encoded_args: concat.clone(),
         value: Some(compare.clone()),
         value: Some(compare.clone()),
         gas: concat2.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,
             seeds,
             loc,
             loc,
             accounts,
             accounts,
+            constructor_no: _,
         } => {
         } => {
             let encoded_args = expression(target, bin, encoded_args, &w.vars, function, ns);
             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();
             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>,
         function: FunctionValue<'b>,
         _success: Option<&mut BasicValueEnum<'b>>,
         _success: Option<&mut BasicValueEnum<'b>>,
         contract_no: usize,
         contract_no: usize,
-        address: PointerValue<'b>,
+        _address: PointerValue<'b>,
         encoded_args: BasicValueEnum<'b>,
         encoded_args: BasicValueEnum<'b>,
         encoded_args_len: BasicValueEnum<'b>,
         encoded_args_len: BasicValueEnum<'b>,
         mut contract_args: ContractArgs<'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 = binary.vector_bytes(encoded_args);
         let payload_len = encoded_args_len.into_int_value();
         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(
     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)
 /// it is stored in a IndexMap<String, SolanaAccount> (see above)
 #[derive(Clone, Copy, Debug)]
 #[derive(Clone, Copy, Debug)]
 pub struct SolanaAccount {
 pub struct SolanaAccount {
+    pub loc: pt::Loc,
     pub is_signer: bool,
     pub is_signer: bool,
     pub is_writer: bool,
     pub is_writer: bool,
+    /// Has the compiler automatically generated this account entry?
+    pub generated: bool,
 }
 }
 
 
 pub enum ConstructorAnnotation {
 pub enum ConstructorAnnotation {
     Seed(Expression),
     Seed(Expression),
-    Payer(Expression),
+    Payer(pt::Loc, String),
     Space(Expression),
     Space(Expression),
     Bump(Expression),
     Bump(Expression),
 }
 }
@@ -348,10 +351,10 @@ pub enum ConstructorAnnotation {
 impl CodeLocation for ConstructorAnnotation {
 impl CodeLocation for ConstructorAnnotation {
     fn loc(&self) -> pt::Loc {
     fn loc(&self) -> pt::Loc {
         match self {
         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) => {
                     ConstructorAnnotation::Bump(expr) => {
                         self.add_expression(expr, Some(func), ns, node, "bump".into());
                         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(),
                     .to_string(),
             ));
             ));
             return Err(());
             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,
     unused_variable::used_variable,
     Symtable,
     Symtable,
 };
 };
+use crate::sema::ast::SolanaAccount;
 use crate::sema::expression::literals::number_literal;
 use crate::sema::expression::literals::number_literal;
 use crate::sema::expression::resolve_expression::expression;
 use crate::sema::expression::resolve_expression::expression;
+use crate::sema::solana_accounts::BuiltinAccounts;
 use crate::Target;
 use crate::Target;
+use indexmap::map::Entry;
 use num_traits::ToPrimitive;
 use num_traits::ToPrimitive;
 use solang_parser::pt::{self, CodeLocation};
 use solang_parser::pt::{self, CodeLocation};
+use std::str::FromStr;
 
 
 /// Resolve the prototype annotation for functions (just the selector). These
 /// Resolve the prototype annotation for functions (just the selector). These
 /// annotations can be resolved for functions without a body. This means they
 /// 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 => {
             "payer" if is_solana_constructor => {
-                let ty = Type::Address(false);
                 let loc = note.loc;
                 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(
                             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(
             _ => diagnostics.push(Diagnostic::error(
@@ -300,7 +329,7 @@ pub fn function_body_annotations(
 
 
     if !resolved_annotations.is_empty() && diagnostics.is_empty() && payer.is_none() {
     if !resolved_annotations.is_empty() && diagnostics.is_empty() && payer.is_none() {
         diagnostics.push(Diagnostic::error(
         diagnostics.push(Diagnostic::error(
-            resolved_annotations[0].loc(),
+            ns.functions[function_no].loc,
             "@payer annotation required for constructor".into(),
             "@payer annotation required for constructor".into(),
         ));
         ));
     }
     }

+ 1 - 0
src/sema/mod.rs

@@ -31,6 +31,7 @@ mod function_annotation;
 mod functions;
 mod functions;
 mod mutability;
 mod mutability;
 mod namespace;
 mod namespace;
+pub(crate) mod solana_accounts;
 mod statements;
 mod statements;
 pub mod symtable;
 pub mod symtable;
 pub mod tags;
 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 {
 contract runner {
-    function test(address a) public pure {
+    function test(address a) external pure {
         aborting abort = new aborting{address: a}();
         aborting abort = new aborting{address: a}();
 
 
         try abort.abort() returns (int32 a, bool b) {
         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 {
 contract creator {
     Child public c;
     Child public c;
     // BEGIN-CHECK: creator::creator::function::create_child_with_meta__address_address
     // 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[2] metas = [
             AccountMeta({pubkey: child, is_signer: false, is_writable: false}),
             AccountMeta({pubkey: child, is_signer: false, is_writable: false}),
             AccountMeta({pubkey: payer, is_signer: true, is_writable: true})
             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 = new Child{accounts: metas}(payer);
 
 
         c.say_hello();
         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();
         c2 ct = new c2();
 
 
         return 3;
         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
 // 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")
 	@seed(hex"41420044")
 	@bump(b)
 	@bump(b)
 	@bump(5)
 	@bump(5)
-	@payer(address"Chi1doxDSNjrmbZ5sq3H2cXyTq3KNfGepmbhyHaxcr8")
+	@payer(my_account)
 	@payer(bar)
 	@payer(bar)
 	@space(1025 + 5)
 	@space(1025 + 5)
 	@space(4)
 	@space(4)
@@ -61,18 +61,18 @@ contract c4 {
 // error: 15:15: address literal 102 invalid character '0'
 // error: 15:15: address literal 102 invalid character '0'
 // error: 20:8-9: duplicate @bump annotation for constructor
 // error: 20:8-9: duplicate @bump annotation for constructor
 // 	note 19:2-10: previous @bump
 // 	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
 // error: 24:2-11: duplicate @space annotation for constructor
 // 	note 23:2-18: previous @space
 // 	note 23:2-18: previous @space
 // error: 28:1-17: annotion takes an account, for example '@program_id("BBH7Xi5ddus5EoQhzJLgyodVxJJGkvBRCY5AhBA1jwUr")'
 // error: 28:1-17: annotion takes an account, for example '@program_id("BBH7Xi5ddus5EoQhzJLgyodVxJJGkvBRCY5AhBA1jwUr")'
 // error: 33:8-9: duplicate @bump annotation for constructor
 // error: 33:8-9: duplicate @bump annotation for constructor
 // 	note 32:2-10: previous @bump
 // 	note 32:2-10: previous @bump
 // error: 35:2-13: duplicate @payer annotation for constructor
 // 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
 // error: 37:2-11: duplicate @space annotation for constructor
 // 	note 36:2-18: previous @space
 // 	note 36:2-18: previous @space
 // error: 40:2-14: unknown annotation seed for function
 // error: 40:2-14: unknown annotation seed for function
 // error: 41:2-10: unknown annotation bump for function
 // error: 41:2-10: unknown annotation bump for function
 // error: 42:2-62: unknown annotation payer for function
 // error: 42:2-62: unknown annotation payer for function
 // error: 43:2-11: unknown annotation space 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
     a
 }
 }
 
 
+#[derive(Default)]
 struct AccountState {
 struct AccountState {
     data: Vec<u8>,
     data: Vec<u8>,
     owner: Option<Account>,
     owner: Option<Account>,
@@ -1003,10 +1004,10 @@ impl Pubkey {
 pub struct AccountMeta {
 pub struct AccountMeta {
     /// An account's public key
     /// An account's public key
     pub pubkey: Pubkey,
     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.
     /// True if the `pubkey` can be loaded as a read-write account.
     pub is_writable: bool,
     pub is_writable: bool,
+    /// True if an Instruction requires a Transaction signature matching `pubkey`.
+    pub is_signer: bool,
 }
 }
 
 
 fn translate(
 fn translate(
@@ -1344,7 +1345,7 @@ fn sol_invoke_signed_c(
             vm.stack.insert(0, p);
             vm.stack.insert(0, p);
 
 
             let res = vm.execute(&instruction.accounts, &instruction.data);
             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();
             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(
     let mut vm = build_solidity(
         r#"
         r#"
         contract bar0 {
         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);
                 bar1 x = new bar1{address: foo}("yo from bar0", payer);
 
 
                 return x;
                 return x;
@@ -59,6 +59,8 @@ fn simple_create_contract_no_seed() {
         },
         },
     );
     );
 
 
+    vm.account_data.insert([0; 32], AccountState::default());
+
     let bar1 = vm
     let bar1 = vm
         .function(
         .function(
             "test_other",
             "test_other",
@@ -85,7 +87,7 @@ fn simple_create_contract() {
     let mut vm = build_solidity(
     let mut vm = build_solidity(
         r#"
         r#"
         contract bar0 {
         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);
                 bar1 x = new bar1{address: foo}("yo from bar0", payer);
 
 
                 return x;
                 return x;
@@ -122,6 +124,8 @@ fn simple_create_contract() {
     let seed = vm.create_pda(&program_id);
     let seed = vm.create_pda(&program_id);
     let payer = account_new();
     let payer = account_new();
 
 
+    vm.account_data.insert([0; 32], AccountState::default());
+
     let bar1 = vm
     let bar1 = vm
         .function(
         .function(
             "test_other",
             "test_other",
@@ -212,12 +216,13 @@ fn create_contract_with_payer() {
 }
 }
 
 
 #[test]
 #[test]
+#[should_panic(expected = "external call failed")]
 // 64424509440 = 15 << 32 (ERROR_NEW_ACCOUNT_NEEDED)
 // 64424509440 = 15 << 32 (ERROR_NEW_ACCOUNT_NEEDED)
 fn missing_contract() {
 fn missing_contract() {
     let mut vm = build_solidity(
     let mut vm = build_solidity(
         r#"
         r#"
         contract bar0 {
         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");
                 bar1 x = new bar1{address: foo}("yo from bar0");
 
 
                 return x;
                 return x;
@@ -247,8 +252,9 @@ fn missing_contract() {
     let missing = account_new();
     let missing = account_new();
 
 
     vm.logs.clear();
     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)]);
     let _ = vm.function_must_fail("test_other", &[BorshToken::Address(missing)]);
-    assert_eq!(vm.logs, "new account needed");
 }
 }
 
 
 #[test]
 #[test]
@@ -256,7 +262,7 @@ fn two_contracts() {
     let mut vm = build_solidity(
     let mut vm = build_solidity(
         r#"
         r#"
         contract bar0 {
         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 x = new bar1{address: a}("yo from bar0");
                 bar1 y = new bar1{address: b}("hi from bar0");
                 bar1 y = new bar1{address: b}("hi from bar0");
 
 
@@ -266,7 +272,7 @@ fn two_contracts() {
 
 
         @program_id("CPDgqnhHDCsjFkJKMturRQ1QeM9EXZg3EYCeDoRP8pdT")
         @program_id("CPDgqnhHDCsjFkJKMturRQ1QeM9EXZg3EYCeDoRP8pdT")
         contract bar1 {
         contract bar1 {
-            @payer(address"3wvhRNAJSDCk5Mub8NEcShszRrHHVDHsuSUdAcL2aaMV")
+            @payer(payer_account)
             constructor(string v) {
             constructor(string v) {
                 print("bar1 says: " + v);
                 print("bar1 says: " + v);
             }
             }
@@ -286,6 +292,7 @@ fn two_contracts() {
     let seed1 = vm.create_pda(&program_id);
     let seed1 = vm.create_pda(&program_id);
     let seed2 = vm.create_pda(&program_id);
     let seed2 = vm.create_pda(&program_id);
 
 
+    vm.account_data.insert([0; 32], AccountState::default());
     let _bar1 = vm.function(
     let _bar1 = vm.function(
         "test_other",
         "test_other",
         &[BorshToken::Address(seed1.0), BorshToken::Address(seed2.0)],
         &[BorshToken::Address(seed1.0), BorshToken::Address(seed2.0)],
@@ -445,7 +452,7 @@ fn account_with_seed_bump_literals() {
             @space(2 << 8 + 4)
             @space(2 << 8 + 4)
             @seed("meh")
             @seed("meh")
             @bump(33) // 33 = ascii !
             @bump(33) // 33 = ascii !
-            @payer(address"vS5Tf8mnHGbUCMLQWrnvsFvwHLfA5p3yQM3ozxPckn8")
+            @payer(my_account)
             constructor() {}
             constructor() {}
 
 
             function hello() public returns (bool) {
             function hello() public returns (bool) {
@@ -482,9 +489,9 @@ fn create_child() {
         contract creator {
         contract creator {
             Child public c;
             Child public c;
 
 
-            function create_child(address child, address payer) public {
+            function create_child(address child) external {
                 print("Going to create child");
                 print("Going to create child");
-                c = new Child{address: child}(payer);
+                c = new Child{address: child}();
 
 
                 c.say_hello();
                 c.say_hello();
             }
             }
@@ -494,7 +501,7 @@ fn create_child() {
         contract Child {
         contract Child {
             @payer(payer)
             @payer(payer)
             @space(511 + 7)
             @space(511 + 7)
-            constructor(address payer) {
+            constructor() {
                 print("In child constructor");
                 print("In child constructor");
             }
             }
 
 
@@ -512,11 +519,12 @@ fn create_child() {
     let program_id = vm.stack[0].program;
     let program_id = vm.stack[0].program;
 
 
     let seed = vm.create_pda(&program_id);
     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!(
     assert_eq!(
         vm.logs,
         vm.logs,
@@ -570,23 +578,9 @@ contract Child {
 
 
     let seed = vm.create_pda(&program_id);
     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();
     let mut metas = vm.default_metas();
     metas.push(AccountMeta {
     metas.push(AccountMeta {

+ 8 - 25
tests/solana_tests/runtime_errors.rs

@@ -62,18 +62,6 @@ contract RuntimeErrors {
         arr.pop();
         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 {
     function i_will_revert() public {
         revert();
         revert();
     }
     }
@@ -200,7 +188,7 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
 
 
@@ -214,7 +202,7 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
 
 
@@ -222,7 +210,7 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
@@ -246,7 +234,7 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
@@ -275,7 +263,7 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
@@ -290,7 +278,7 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
@@ -305,7 +293,7 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
@@ -314,13 +302,8 @@ contract calle_contract {
 
 
     assert_eq!(
     assert_eq!(
         vm.logs,
         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();
     vm.logs.clear();
-
-    _res = vm.function_must_fail("create_child", &[]);
-
-    assert_eq!(vm.logs, "new account needed");
-    vm.logs.clear();
 }
 }