Browse Source

When calling a contract, the instruction data should include the account

Now the instruction data includes multiple accounts. This makes it
possible to call a Solang contract with multiple accounts, and
automatically the right account will be picked.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 years ago
parent
commit
ba3db16af4
7 changed files with 305 additions and 337 deletions
  1. 11 2
      integration/solana/index.ts
  2. 8 8
      src/emit/mod.rs
  3. 204 293
      src/emit/solana.rs
  4. BIN
      stdlib/bpf/solana.bc
  5. 27 12
      stdlib/solana.c
  6. 25 15
      stdlib/solana_sdk.h
  7. 30 7
      tests/solana.rs

+ 11 - 2
integration/solana/index.ts

@@ -131,6 +131,11 @@ class Program {
 
         const input = Web3EthAbi.encodeParameters(inputs, params);
 
+        const data = Buffer.concat([
+            this.contractStorageAccount.publicKey.toBuffer(),
+            Buffer.from(input.substr(2), 'hex')
+        ]);
+
         console.log('calling constructor [' + params + ']');
 
         const instruction = new TransactionInstruction({
@@ -138,7 +143,7 @@ class Program {
                 { pubkey: this.returnDataAccount.publicKey, isSigner: false, isWritable: true },
                 { pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true }],
             programId: this.programId,
-            data: Buffer.from(input.substring(2), 'hex'),
+            data,
         });
 
         await sendAndConfirmTransaction(
@@ -157,6 +162,10 @@ class Program {
         let abi: AbiItem = JSON.parse(this.abi).find((e: AbiItem) => e.name == name);
 
         const input: string = Web3EthAbi.encodeFunctionCall(abi, params);
+        const data = Buffer.concat([
+            this.contractStorageAccount.publicKey.toBuffer(),
+            Buffer.from(input.substr(2), 'hex')
+        ]);
 
         let debug = 'calling function ' + name + ' [' + params + ']';
 
@@ -165,7 +174,7 @@ class Program {
                 { pubkey: this.returnDataAccount.publicKey, isSigner: false, isWritable: true },
                 { pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true }],
             programId: this.programId,
-            data: Buffer.from(input.substr(2), 'hex'),
+            data,
         });
 
         await sendAndConfirmTransaction(

+ 8 - 8
src/emit/mod.rs

@@ -3043,7 +3043,7 @@ pub trait TargetRuntime<'a> {
 
         // On Solana, the last argument is the accounts
         if contract.ns.target == Target::Solana {
-            contract.accounts = Some(function.get_last_param().unwrap().into_pointer_value());
+            contract.parameters = Some(function.get_last_param().unwrap().into_pointer_value());
         }
 
         // Create all the stack variables
@@ -3642,8 +3642,8 @@ pub trait TargetRuntime<'a> {
                             }
                         }
 
-                        if let Some(accounts) = contract.accounts {
-                            parms.push(accounts.into());
+                        if let Some(parameters) = contract.parameters {
+                            parms.push(parameters.into());
                         }
 
                         let ret = contract
@@ -3715,8 +3715,8 @@ pub trait TargetRuntime<'a> {
                             .collect::<Vec<BasicValueEnum>>();
 
                         // on Solana, we need to pass the accounts parameter around
-                        if let Some(accounts) = contract.accounts {
-                            parms.push(accounts.into());
+                        if let Some(parameters) = contract.parameters {
+                            parms.push(parameters.into());
                         }
 
                         if !res.is_empty() {
@@ -5294,7 +5294,7 @@ pub struct Contract<'a> {
     calldata_len: GlobalValue<'a>,
     scratch_len: Option<GlobalValue<'a>>,
     scratch: Option<GlobalValue<'a>>,
-    accounts: Option<PointerValue<'a>>,
+    parameters: Option<PointerValue<'a>>,
     return_values: HashMap<ReturnCode, IntValue<'a>>,
 }
 
@@ -5546,7 +5546,7 @@ impl<'a> Contract<'a> {
             calldata_len,
             scratch: None,
             scratch_len: None,
-            accounts: None,
+            parameters: None,
             return_values,
         }
     }
@@ -5843,7 +5843,7 @@ impl<'a> Contract<'a> {
         if self.ns.target == Target::Solana {
             args.push(
                 self.module
-                    .get_struct_type("struct.SolAccountInfo")
+                    .get_struct_type("struct.SolParameters")
                     .unwrap()
                     .ptr_type(AddressSpace::Generic)
                     .as_basic_type_enum(),

+ 204 - 293
src/emit/solana.rs

@@ -109,37 +109,166 @@ impl SolanaTarget {
             .set_unnamed_address(UnnamedAddress::Local);
     }
 
-    fn emit_dispatch(&mut self, contract: &mut Contract) {
-        let initializer = self.emit_initializer(contract);
+    /// Returns the SolAccountInfo of the executing contract
+    fn contract_storage_account<'b>(&self, contract: &Contract<'b>) -> PointerValue<'b> {
+        let parameters = contract
+            .builder
+            .get_insert_block()
+            .unwrap()
+            .get_parent()
+            .unwrap()
+            .get_last_param()
+            .unwrap()
+            .into_pointer_value();
 
-        let function = contract.module.get_function("solang_dispatch").unwrap();
+        let ka_cur = contract
+            .builder
+            .build_load(
+                contract
+                    .builder
+                    .build_struct_gep(parameters, 2, "ka_cur")
+                    .unwrap(),
+                "ka_cur",
+            )
+            .into_int_value();
 
-        let entry = contract.context.append_basic_block(function, "entry");
+        unsafe {
+            contract.builder.build_gep(
+                parameters,
+                &[
+                    contract.context.i32_type().const_int(0, false),
+                    contract.context.i32_type().const_int(0, false),
+                    ka_cur,
+                ],
+                "account",
+            )
+        }
+    }
 
-        contract.builder.position_at_end(entry);
+    /// Returns the account data of the executing contract
+    fn contract_storage_data<'b>(&self, contract: &Contract<'b>) -> PointerValue<'b> {
+        let parameters = contract
+            .builder
+            .get_insert_block()
+            .unwrap()
+            .get_parent()
+            .unwrap()
+            .get_last_param()
+            .unwrap()
+            .into_pointer_value();
 
-        let input = function.get_nth_param(0).unwrap().into_pointer_value();
-        let input_len = function.get_nth_param(1).unwrap().into_int_value();
-        let accounts = function.get_nth_param(2).unwrap().into_pointer_value();
+        let ka_cur = contract
+            .builder
+            .build_load(
+                contract
+                    .builder
+                    .build_struct_gep(parameters, 2, "ka_cur")
+                    .unwrap(),
+                "ka_cur",
+            )
+            .into_int_value();
 
-        // load magic value of contract storage
-        let contract_data = contract
+        contract
             .builder
             .build_load(
                 unsafe {
                     contract.builder.build_gep(
-                        accounts,
+                        parameters,
                         &[
-                            contract.context.i32_type().const_int(1, false),
+                            contract.context.i32_type().const_int(0, false),
+                            contract.context.i32_type().const_int(0, false),
+                            ka_cur,
                             contract.context.i32_type().const_int(3, false),
                         ],
-                        "contract_data",
+                        "data",
+                    )
+                },
+                "data",
+            )
+            .into_pointer_value()
+    }
+
+    /// Returns the account data length of the executing contract
+    fn contract_storage_datalen<'b>(&self, contract: &Contract<'b>) -> IntValue<'b> {
+        let parameters = contract
+            .builder
+            .get_insert_block()
+            .unwrap()
+            .get_parent()
+            .unwrap()
+            .get_last_param()
+            .unwrap()
+            .into_pointer_value();
+
+        let ka_cur = contract
+            .builder
+            .build_load(
+                contract
+                    .builder
+                    .build_struct_gep(parameters, 2, "ka_cur")
+                    .unwrap(),
+                "ka_cur",
+            )
+            .into_int_value();
+
+        contract
+            .builder
+            .build_load(
+                unsafe {
+                    contract.builder.build_gep(
+                        parameters,
+                        &[
+                            contract.context.i32_type().const_int(0, false),
+                            contract.context.i32_type().const_int(0, false),
+                            ka_cur,
+                            contract.context.i32_type().const_int(2, false),
+                        ],
+                        "data_len",
                     )
                 },
-                "contract_data",
+                "data_len",
+            )
+            .into_int_value()
+    }
+
+    fn emit_dispatch(&mut self, contract: &mut Contract) {
+        let initializer = self.emit_initializer(contract);
+
+        let function = contract.module.get_function("solang_dispatch").unwrap();
+
+        let entry = contract.context.append_basic_block(function, "entry");
+
+        contract.builder.position_at_end(entry);
+
+        let sol_params = function.get_nth_param(0).unwrap().into_pointer_value();
+
+        let input = contract
+            .builder
+            .build_load(
+                contract
+                    .builder
+                    .build_struct_gep(sol_params, 4, "input")
+                    .unwrap(),
+                "data",
             )
             .into_pointer_value();
 
+        let input_len = contract
+            .builder
+            .build_load(
+                contract
+                    .builder
+                    .build_struct_gep(sol_params, 5, "input_len")
+                    .unwrap(),
+                "data_len",
+            )
+            .into_int_value();
+
+        // load magic value of contract storage
+        contract.parameters = Some(sol_params);
+
+        let contract_data = self.contract_storage_data(contract);
+
         let magic_value_ptr = contract.builder.build_pointer_cast(
             contract_data,
             contract.context.i32_type().ptr_type(AddressSpace::Generic),
@@ -183,28 +312,11 @@ impl SolanaTarget {
             &contract.context.i64_type().const_int(4u64 << 32, false),
         ));
 
-        contract.accounts = Some(accounts);
-
         // generate constructor code
         contract.builder.position_at_end(constructor_block);
 
         // do we have enough contract data
-        let contract_data_len = contract
-            .builder
-            .build_load(
-                unsafe {
-                    contract.builder.build_gep(
-                        accounts,
-                        &[
-                            contract.context.i32_type().const_int(1, false),
-                            contract.context.i32_type().const_int(2, false),
-                        ],
-                        "contract_data_len_ptr",
-                    )
-                },
-                "contract_data_len",
-            )
-            .into_int_value();
+        let contract_data_len = self.contract_storage_datalen(contract);
 
         let fixed_fields_size = contract.contract.fixed_layout_size.to_u64().unwrap();
 
@@ -261,7 +373,7 @@ impl SolanaTarget {
 
         contract
             .builder
-            .build_call(initializer, &[accounts.into()], "");
+            .build_call(initializer, &[sol_params.into()], "");
 
         // There is only one possible constructor
         let ret = if let Some((cfg_no, cfg)) = contract
@@ -277,7 +389,7 @@ impl SolanaTarget {
             self.abi
                 .decode(contract, function, &mut args, input, input_len, &cfg.params);
 
-            args.push(accounts.into());
+            args.push(sol_params.into());
 
             contract
                 .builder
@@ -295,8 +407,6 @@ impl SolanaTarget {
         // Generate function call dispatch
         contract.builder.position_at_end(function_block);
 
-        contract.accounts = Some(accounts);
-
         let input = contract.builder.build_pointer_cast(
             input,
             contract.context.i32_type().ptr_type(AddressSpace::Generic),
@@ -319,14 +429,27 @@ impl SolanaTarget {
         &self,
         contract: &Contract<'b>,
     ) -> (PointerValue<'b>, PointerValue<'b>, IntValue<'b>) {
+        let parameters = contract
+            .builder
+            .get_insert_block()
+            .unwrap()
+            .get_parent()
+            .unwrap()
+            .get_last_param()
+            .unwrap()
+            .into_pointer_value();
+
+        // the first field is the array of
         // the first account passed in is the return buffer; 3 field of account is "data"
         let data = contract
             .builder
             .build_load(
                 unsafe {
                     contract.builder.build_gep(
-                        contract.accounts.unwrap(),
+                        parameters,
                         &[
+                            contract.context.i32_type().const_zero(),
+                            contract.context.i32_type().const_zero(),
                             contract.context.i32_type().const_zero(),
                             contract.context.i32_type().const_int(3, false),
                         ],
@@ -342,8 +465,10 @@ impl SolanaTarget {
             .build_load(
                 unsafe {
                     contract.builder.build_gep(
-                        contract.accounts.unwrap(),
+                        parameters,
                         &[
+                            contract.context.i32_type().const_zero(),
+                            contract.context.i32_type().const_zero(),
                             contract.context.i32_type().const_zero(),
                             contract.context.i32_type().const_int(2, false),
                         ],
@@ -382,7 +507,6 @@ impl SolanaTarget {
         &self,
         contract: &Contract<'b>,
         ty: &ast::Type,
-        account: PointerValue<'b>,
         data: PointerValue<'b>,
         slot: IntValue<'b>,
         function: FunctionValue<'b>,
@@ -410,7 +534,7 @@ impl SolanaTarget {
 
             contract.builder.build_call(
                 contract.module.get_function("account_data_free").unwrap(),
-                &[account.into(), offset.into()],
+                &[data.into(), offset.into()],
                 "",
             );
 
@@ -461,7 +585,6 @@ impl SolanaTarget {
                 self.storage_free(
                     contract,
                     &elem_ty.deref_any(),
-                    account,
                     data,
                     offset_val,
                     function,
@@ -490,7 +613,7 @@ impl SolanaTarget {
 
                 contract.builder.build_call(
                     contract.module.get_function("account_data_free").unwrap(),
-                    &[account.into(), slot.into()],
+                    &[data.into(), slot.into()],
                     "",
                 );
 
@@ -509,7 +632,7 @@ impl SolanaTarget {
                     "field_offset",
                 );
 
-                self.storage_free(contract, &field.ty, account, data, offset, function, zero);
+                self.storage_free(contract, &field.ty, data, offset, function, zero);
             }
         } else {
             let ty = contract.llvm_type(ty);
@@ -597,31 +720,7 @@ impl SolanaTarget {
                 .const_to_int(contract.context.i32_type())
         };
 
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                function.get_last_param().unwrap().into_pointer_value(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
 
         let member = unsafe { contract.builder.build_gep(data, &[offset], "data") };
         let offset_ptr = contract.builder.build_pointer_cast(
@@ -745,7 +844,7 @@ impl SolanaTarget {
                 .builder
                 .build_call(
                     contract.module.get_function("account_data_len").unwrap(),
-                    &[account.into(), entry_key.into()],
+                    &[data.into(), entry_key.into()],
                     "length",
                 )
                 .try_as_basic_value()
@@ -817,6 +916,8 @@ impl SolanaTarget {
             .unwrap()
             .const_cast(contract.context.i32_type(), false);
 
+        let account = self.contract_storage_account(contract);
+
         // account_data_alloc will return offset = 0 if the string is length 0
         let rc = contract
             .builder
@@ -998,16 +1099,21 @@ impl SolanaTarget {
 
         contract.builder.position_at_end(current_block);
 
+        let parameters = contract
+            .builder
+            .get_insert_block()
+            .unwrap()
+            .get_parent()
+            .unwrap()
+            .get_last_param()
+            .unwrap()
+            .into_pointer_value();
+
         let rc = contract
             .builder
             .build_call(
                 lookup,
-                &[
-                    slot.into(),
-                    index,
-                    offset.into(),
-                    contract.accounts.unwrap().into(),
-                ],
+                &[slot.into(), index, offset.into(), parameters.into()],
                 "mapping_lookup_res",
             )
             .try_as_basic_value()
@@ -1053,32 +1159,9 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         function: FunctionValue<'a>,
     ) {
         // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
+        let data = self.contract_storage_data(contract);
 
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
-
-        self.storage_free(contract, ty, account, data, *slot, function, true);
+        self.storage_free(contract, ty, data, *slot, function, true);
     }
 
     fn set_storage_extfunc(
@@ -1127,31 +1210,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: IntValue<'a>,
         index: IntValue<'a>,
     ) -> IntValue<'a> {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
 
         let member = unsafe { contract.builder.build_gep(data, &[slot], "data") };
         let offset_ptr = contract.builder.build_pointer_cast(
@@ -1169,7 +1228,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .builder
             .build_call(
                 contract.module.get_function("account_data_len").unwrap(),
-                &[account.into(), offset.into()],
+                &[data.into(), offset.into()],
                 "length",
             )
             .try_as_basic_value()
@@ -1218,31 +1277,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         index: IntValue,
         val: IntValue,
     ) {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
 
         let member = unsafe { contract.builder.build_gep(data, &[slot], "data") };
         let offset_ptr = contract.builder.build_pointer_cast(
@@ -1260,7 +1295,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .builder
             .build_call(
                 contract.module.get_function("account_data_len").unwrap(),
-                &[account.into(), offset.into()],
+                &[data.into(), offset.into()],
                 "length",
             )
             .try_as_basic_value()
@@ -1309,14 +1344,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: IntValue<'a>,
         index: BasicValueEnum<'a>,
     ) -> IntValue<'a> {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
+        let account = self.contract_storage_account(contract);
 
         if let ast::Type::Mapping(key, value) = ty.deref_any() {
             self.sparse_lookup(contract, function, key, value, slot, index)
@@ -1382,31 +1410,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: IntValue<'a>,
         val: BasicValueEnum<'a>,
     ) -> BasicValueEnum<'a> {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
+        let account = self.contract_storage_account(contract);
 
         let member = unsafe { contract.builder.build_gep(data, &[slot], "data") };
         let offset_ptr = contract.builder.build_pointer_cast(
@@ -1424,7 +1429,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .builder
             .build_call(
                 contract.module.get_function("account_data_len").unwrap(),
-                &[account.into(), offset.into()],
+                &[data.into(), offset.into()],
                 "length",
             )
             .try_as_basic_value()
@@ -1510,31 +1515,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         ty: &ast::Type,
         slot: IntValue<'a>,
     ) -> BasicValueEnum<'a> {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
+        let account = self.contract_storage_account(contract);
 
         let member = unsafe { contract.builder.build_gep(data, &[slot], "data") };
         let offset_ptr = contract.builder.build_pointer_cast(
@@ -1552,7 +1534,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .builder
             .build_call(
                 contract.module.get_function("account_data_len").unwrap(),
-                &[account.into(), offset.into()],
+                &[data.into(), offset.into()],
                 "length",
             )
             .try_as_basic_value()
@@ -1629,31 +1611,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: IntValue<'a>,
         elem_ty: &ast::Type,
     ) -> IntValue<'a> {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
 
         // the slot is simply the offset after the magic
         let member = unsafe { contract.builder.build_gep(data, &[slot], "data") };
@@ -1679,7 +1637,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .builder
             .build_call(
                 contract.module.get_function("account_data_len").unwrap(),
-                &[account.into(), offset.into()],
+                &[data.into(), offset.into()],
                 "length",
             )
             .try_as_basic_value()
@@ -1712,31 +1670,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: &mut IntValue<'a>,
         function: FunctionValue,
     ) -> BasicValueEnum<'a> {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
 
         // the slot is simply the offset after the magic
         let member = unsafe { contract.builder.build_gep(data, &[*slot], "data") };
@@ -1759,7 +1693,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                     .builder
                     .build_call(
                         contract.module.get_function("account_data_len").unwrap(),
-                        &[account.into(), offset.into()],
+                        &[data.into(), offset.into()],
                         "free",
                     )
                     .try_as_basic_value()
@@ -1958,31 +1892,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         val: BasicValueEnum<'a>,
         function: FunctionValue<'a>,
     ) {
-        // contract storage is in 2nd account
-        let account = unsafe {
-            contract.builder.build_gep(
-                contract.accounts.unwrap(),
-                &[contract.context.i32_type().const_int(1, false)],
-                "account",
-            )
-        };
-
-        // 3rd member of account is data pointer
-        let data = unsafe {
-            contract.builder.build_gep(
-                account,
-                &[
-                    contract.context.i32_type().const_zero(),
-                    contract.context.i32_type().const_int(3, false),
-                ],
-                "data",
-            )
-        };
-
-        let data = contract
-            .builder
-            .build_load(data, "data")
-            .into_pointer_value();
+        let data = self.contract_storage_data(contract);
+        let account = self.contract_storage_account(contract);
 
         // the slot is simply the offset after the magic
         let member = unsafe { contract.builder.build_gep(data, &[*slot], "data") };
@@ -2003,7 +1914,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 .builder
                 .build_call(
                     contract.module.get_function("account_data_len").unwrap(),
-                    &[account.into(), offset.into()],
+                    &[data.into(), offset.into()],
                     "length",
                 )
                 .try_as_basic_value()
@@ -2034,7 +1945,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             // do not realloc since we're copying everything
             contract.builder.build_call(
                 contract.module.get_function("account_data_free").unwrap(),
-                &[account.into(), offset.into()],
+                &[data.into(), offset.into()],
                 "free",
             );
 
@@ -2105,7 +2016,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             );
         } else if let ast::Type::Array(elem_ty, dim) = ty {
             // make sure any pointers are freed
-            self.storage_free(contract, ty, account, data, *slot, function, false);
+            self.storage_free(contract, ty, data, *slot, function, false);
 
             let offset_ptr = contract.builder.build_pointer_cast(
                 member,
@@ -2245,7 +2156,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 };
 
                 // free any existing dynamic storage
-                self.storage_free(contract, &field.ty, account, data, offset, function, false);
+                self.storage_free(contract, &field.ty, data, offset, function, false);
 
                 self.storage_store(
                     contract,

BIN
stdlib/bpf/solana.bc


+ 27 - 12
stdlib/solana.c

@@ -4,19 +4,38 @@
 #include "stdlib.h"
 #include "solana_sdk.h"
 
-extern uint64_t solang_dispatch(const uint8_t *input, uint64_t input_len, SolAccountInfo *ka);
+extern uint64_t solang_dispatch(const SolParameters *param);
 
 uint64_t
 entrypoint(const uint8_t *input)
 {
-    SolAccountInfo ka[2];
-    SolParameters params = (SolParameters){.ka = ka};
-    if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka)))
+    SolParameters params;
+
+    uint64_t ret = sol_deserialize(input, &params);
+    if (ret)
+    {
+        return ret;
+    }
+
+    int account_no;
+
+    // the first account is the returndata account; ignore that one
+    for (account_no = 1; account_no < params.ka_num; account_no++)
     {
-        return ERROR_INVALID_ARGUMENT;
+        if (SolPubkey_same(params.account_id, params.ka[account_no].key))
+        {
+            break;
+        }
     }
 
-    return solang_dispatch(params.data, params.data_len, ka);
+    if (account_no == params.ka_num)
+    {
+        return ERROR_INVALID_INSTRUCTION_DATA;
+    }
+
+    params.ka_cur = account_no;
+
+    return solang_dispatch(&params);
 }
 
 void *__malloc(uint32_t size)
@@ -140,10 +159,8 @@ uint64_t account_data_alloc(SolAccountInfo *ai, uint32_t size, uint32_t *res)
     }
 }
 
-uint32_t account_data_len(SolAccountInfo *ai, uint32_t offset)
+uint32_t account_data_len(void *data, uint32_t offset)
 {
-    void *data = ai->data;
-
     // Nothing to do
     if (!offset)
         return 0;
@@ -155,10 +172,8 @@ uint32_t account_data_len(SolAccountInfo *ai, uint32_t offset)
     return chunk->length;
 }
 
-void account_data_free(SolAccountInfo *ai, uint32_t offset)
+void account_data_free(void *data, uint32_t offset)
 {
-    void *data = ai->data;
-
     // Nothing to do
     if (!offset)
         return;

+ 25 - 15
stdlib/solana_sdk.h

@@ -217,11 +217,13 @@ void sol_panic_(const char *, uint64_t, uint64_t, uint64_t);
  */
 typedef struct
 {
-  SolAccountInfo *ka;          /** Pointer to an array of SolAccountInfo, must already
+  SolAccountInfo ka[10]; /** Pointer to an array of SolAccountInfo, must already
                           point to an array of SolAccountInfos */
-  uint64_t ka_num;             /** Number of SolAccountInfo entries in `ka` */
-  const uint8_t *data;         /** pointer to the instruction data */
-  uint64_t data_len;           /** Length in bytes of the instruction data */
+  uint64_t ka_num;       /** Number of SolAccountInfo entries in `ka` */
+  uint64_t ka_cur;
+  const SolPubkey *account_id;
+  const uint8_t *input;        /** pointer to the instruction data */
+  uint64_t input_len;          /** Length in bytes of the instruction data */
   const SolPubkey *program_id; /** program_id of the currently executing program */
 } SolParameters;
 
@@ -244,14 +246,13 @@ typedef struct
  * @param params Pointer to a SolParameters structure
  * @return Boolean true if successful.
  */
-static bool sol_deserialize(
+static uint64_t sol_deserialize(
     const uint8_t *input,
-    SolParameters *params,
-    uint64_t ka_num)
+    SolParameters *params)
 {
   if (NULL == input || NULL == params)
   {
-    return false;
+    return ERROR_INVALID_ARGUMENT;
   }
   params->ka_num = *(uint64_t *)input;
   input += sizeof(uint64_t);
@@ -261,7 +262,7 @@ static bool sol_deserialize(
     uint8_t dup_info = input[0];
     input += sizeof(uint8_t);
 
-    if (i >= ka_num)
+    if (i >= SOL_ARRAY_SIZE(params->ka))
     {
       if (dup_info == UINT8_MAX)
       {
@@ -336,15 +337,23 @@ static bool sol_deserialize(
     }
   }
 
-  params->data_len = *(uint64_t *)input;
+  uint64_t data_len = *(uint64_t *)input;
   input += sizeof(uint64_t);
-  params->data = input;
-  input += params->data_len;
+
+  if (data_len < SIZE_PUBKEY)
+  {
+    return ERROR_INVALID_INSTRUCTION_DATA;
+  }
+
+  params->account_id = (SolPubkey *)input;
+  params->input_len = data_len - SIZE_PUBKEY;
+  params->input = input + SIZE_PUBKEY;
+  input += data_len;
 
   params->program_id = (SolPubkey *)input;
   input += sizeof(SolPubkey);
 
-  return true;
+  return 0;
 }
 
 /**
@@ -543,8 +552,9 @@ static void sol_log_params(const SolParameters *params)
     sol_log("  - Rent Epoch");
     sol_log_64(0, 0, 0, 0, params->ka[i].rent_epoch);
   }
-  sol_log("- Instruction data\0");
-  sol_log_array(params->data, params->data_len);
+  sol_log("- Eth abi Instruction data\0");
+  sol_log_pubkey(params->account_id);
+  sol_log_array(params->input, params->input_len);
 }
 
 /**@}*/

+ 30 - 7
tests/solana.rs

@@ -3,6 +3,7 @@ mod solana_helpers;
 use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
 use ethabi::Token;
 use libc::c_char;
+use rand::Rng;
 use solana_helpers::allocator_bump::Allocator;
 use solana_rbpf::{
     error::EbpfError,
@@ -17,6 +18,18 @@ use std::mem::{align_of, size_of};
 
 mod solana_tests;
 
+type Account = [u8; 32];
+
+fn account_new() -> Account {
+    let mut rng = rand::thread_rng();
+
+    let mut a = [0u8; 32];
+
+    rng.fill(&mut a[..]);
+
+    a
+}
+
 fn build_solidity(src: &str) -> Program {
     let mut cache = FileCache::new();
 
@@ -44,6 +57,7 @@ fn build_solidity(src: &str) -> Program {
     Program {
         code,
         abi: ethabi::Contract::load(abi.as_bytes()).unwrap(),
+        account: account_new(),
         printbuf: String::new(),
         output: Vec::new(),
         data: Vec::new(),
@@ -52,7 +66,7 @@ fn build_solidity(src: &str) -> Program {
 
 const MAX_PERMITTED_DATA_INCREASE: usize = 10 * 1024;
 
-fn serialize_parameters(input: &[u8], data: &[u8]) -> Vec<u8> {
+fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec<u8> {
     let mut v: Vec<u8> = Vec::new();
 
     // ka_num
@@ -69,7 +83,11 @@ fn serialize_parameters(input: &[u8], data: &[u8]) -> Vec<u8> {
         // padding
         v.write_all(&[0u8; 4]).unwrap();
         // key
-        v.write_all(&[0u8; 32]).unwrap();
+        if account_no == 1 {
+            v.write_all(&account[..]).unwrap();
+        } else {
+            v.write_all(&account_new()).unwrap();
+        }
         // owner
         v.write_all(&[0u8; 32]).unwrap();
         // lamports
@@ -141,6 +159,7 @@ fn deserialize_parameters(input: &[u8]) -> Vec<Vec<u8>> {
 struct Program {
     code: Vec<u8>,
     abi: ethabi::Contract,
+    account: Account,
     printbuf: String,
     data: Vec<u8>,
     output: Vec<u8>,
@@ -225,7 +244,7 @@ impl Program {
     fn execute(&mut self, buf: &mut String, calldata: &[u8]) {
         println!("running bpf with calldata:{}", hex::encode(calldata));
 
-        let parameter_bytes = serialize_parameters(&calldata, &self.data);
+        let parameter_bytes = serialize_parameters(&calldata, &self.account, &self.data);
         let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
         let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START, true);
 
@@ -269,9 +288,11 @@ impl Program {
 
     fn constructor(&mut self, args: &[Token]) {
         let calldata = if let Some(constructor) = &self.abi.constructor {
-            constructor.encode_input(Vec::new(), args).unwrap()
+            constructor
+                .encode_input(self.account.to_vec(), args)
+                .unwrap()
         } else {
-            Vec::new()
+            self.account.to_vec()
         };
 
         let mut buf = String::new();
@@ -280,8 +301,10 @@ impl Program {
     }
 
     fn function(&mut self, name: &str, args: &[Token]) -> Vec<Token> {
-        let calldata = match self.abi.functions[name][0].encode_input(args) {
-            Ok(n) => n,
+        let mut calldata: Vec<u8> = self.account.to_vec();
+
+        match self.abi.functions[name][0].encode_input(args) {
+            Ok(n) => calldata.extend(&n),
             Err(x) => panic!("{}", x),
         };