From e4a0c2c8ed1f5f0ccf9bf93285a693d0dccd5e8b Mon Sep 17 00:00:00 2001 From: Sean Young Date: Tue, 13 Apr 2021 18:35:21 +0100 Subject: [PATCH 1/8] 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 --- integration/solana/index.ts | 13 +- src/emit/mod.rs | 16 +- src/emit/solana.rs | 497 +++++++++++++++--------------------- stdlib/bpf/solana.bc | Bin 18736 -> 20252 bytes stdlib/solana.c | 39 ++- stdlib/solana_sdk.h | 40 +-- tests/solana.rs | 37 ++- 7 files changed, 305 insertions(+), 337 deletions(-) diff --git a/integration/solana/index.ts b/integration/solana/index.ts index 8587d5611..e05ea4d4d 100644 --- a/integration/solana/index.ts +++ b/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( diff --git a/src/emit/mod.rs b/src/emit/mod.rs index afae41950..a73624da2 100644 --- a/src/emit/mod.rs +++ b/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::>(); // 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>, scratch: Option>, - accounts: Option>, + parameters: Option>, return_values: HashMap>, } @@ -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(), diff --git a/src/emit/solana.rs b/src/emit/solana.rs index e72b34869..cb2eb65ee 100644 --- a/src/emit/solana.rs +++ b/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, diff --git a/stdlib/bpf/solana.bc b/stdlib/bpf/solana.bc index c8ae19ef7385aeea3585ce3b6f16b71b1a50b78f..4930199a916c8dbbaccf552205f3dfad942ab4ce 100644 GIT binary patch literal 20252 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 DaBDA# literal 18736 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 Gc>OQN38WtY diff --git a/stdlib/solana.c b/stdlib/solana.c index 0392e0e8c..0f953b2e1 100644 --- a/stdlib/solana.c +++ b/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, ¶ms, SOL_ARRAY_SIZE(ka))) + SolParameters params; + + uint64_t ret = sol_deserialize(input, ¶ms); + 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(¶ms); } 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; diff --git a/stdlib/solana_sdk.h b/stdlib/solana_sdk.h index ca01e0fdd..69f59e625 100644 --- a/stdlib/solana_sdk.h +++ b/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); } /**@}*/ diff --git a/tests/solana.rs b/tests/solana.rs index b89516cc2..fc885860d 100644 --- a/tests/solana.rs +++ b/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 { +fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec { let mut v: Vec = Vec::new(); // ka_num @@ -69,7 +83,11 @@ fn serialize_parameters(input: &[u8], data: &[u8]) -> Vec { // 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> { struct Program { code: Vec, abi: ethabi::Contract, + account: Account, printbuf: String, data: Vec, output: Vec, @@ -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 { - let calldata = match self.abi.functions[name][0].encode_input(args) { - Ok(n) => n, + let mut calldata: Vec = self.account.to_vec(); + + match self.abi.functions[name][0].encode_input(args) { + Ok(n) => calldata.extend(&n), Err(x) => panic!("{}", x), }; From 6439fb5251698bd96f96df032d3a57237cac3c9f Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 14 Apr 2021 11:43:58 +0100 Subject: [PATCH 2/8] Refactor eth abi encoder To implement cross program calls in Solana, we need to use the eth abi encoder in a slightly different way. A builder-style interface is very suited for this, so we can emit parts in steps while maintaining some state. Signed-off-by: Sean Young --- src/emit/ethabiencoder.rs | 1807 +++++++++++++++++++------------------ src/emit/ewasm.rs | 72 +- src/emit/generic.rs | 69 +- src/emit/mod.rs | 2 +- src/emit/sabre.rs | 69 +- src/emit/solana.rs | 83 +- 6 files changed, 964 insertions(+), 1138 deletions(-) diff --git a/src/emit/ethabiencoder.rs b/src/emit/ethabiencoder.rs index bb465595d..bccd411a6 100644 --- a/src/emit/ethabiencoder.rs +++ b/src/emit/ethabiencoder.rs @@ -8,143 +8,232 @@ use num_traits::ToPrimitive; use super::loop_builder::LoopBuilder; use super::{Contract, ReturnCode}; -pub struct EthAbiEncoder { - pub bswap: bool, +/// Generate an in-place abi encoder. This is done in several stages: +/// 1) EncoderBuilder::new() generates the code which calculates the required encoded length at runtime +/// 2) EncoderBuilder::encoded_length() returns the required length +/// 3) EncoderBuilder::finish() generates the code which encodes the data to the pointer provided. The +/// called should ensure there is enough space. +pub struct EncoderBuilder<'a, 'b> { + length: IntValue<'a>, + offset: IntValue<'a>, + selector: Option>, + args: &'b [BasicValueEnum<'a>], + load_args: bool, + tys: &'b [ast::Type], + bswap: bool, } -impl EthAbiEncoder { - /// Recursively encode a value in arg. The load argument specifies if the arg is a pointer - /// to the value, or the value itself. The fixed pointer points to the fixed, non-dynamic part - /// of the encoded data. The offset is current offset for dynamic fields. - pub fn encode_ty<'a>( - &self, +impl<'a, 'b> EncoderBuilder<'a, 'b> { + /// Create a new encoder. This will generate the code which calculates the length of encoded data + pub fn new( contract: &Contract<'a>, - load: bool, - function: FunctionValue<'a>, - ty: &ast::Type, - arg: BasicValueEnum<'a>, - fixed: &mut PointerValue<'a>, - offset: &mut IntValue<'a>, - dynamic: &mut PointerValue<'a>, - ) { - match &ty { - ast::Type::Bool - | ast::Type::Address(_) - | ast::Type::Contract(_) - | ast::Type::Int(_) - | ast::Type::Uint(_) - | ast::Type::Bytes(_) => { - self.encode_primitive(contract, load, function, ty, *fixed, arg); + function: FunctionValue, + selector: Option>, + load_args: bool, + args: &'b [BasicValueEnum<'a>], + tys: &'b [ast::Type], + bswap: bool, + ) -> Self { + let offset = contract.context.i32_type().const_int( + tys.iter() + .map(|ty| EncoderBuilder::encoded_fixed_length(ty, contract.ns)) + .sum(), + false, + ); - *fixed = unsafe { - contract.builder.build_gep( - *fixed, - &[contract.context.i32_type().const_int(32, false)], - "", - ) - }; - } - ast::Type::Enum(n) => { - self.encode_primitive( - contract, - load, - function, - &contract.ns.enums[*n].ty, - *fixed, - arg, - ); + let mut length = offset; - *fixed = unsafe { - contract.builder.build_gep( - *fixed, - &[contract.context.i32_type().const_int(32, false)], - "", - ) - }; - } - ast::Type::Array(elem_ty, dim) if ty.is_dynamic(contract.ns) => { + // now add the dynamic lengths + for (i, ty) in tys.iter().enumerate() { + length = contract.builder.build_int_add( + length, + EncoderBuilder::encoded_dynamic_length(args[i], load_args, ty, function, contract), + "", + ); + } + + if selector.is_some() { + length = contract.builder.build_int_add( + length, + contract + .context + .i32_type() + .const_int(std::mem::size_of::() as u64, false), + "", + ); + } + + EncoderBuilder { + length, + offset, + selector, + args, + load_args, + tys, + bswap, + } + } + + /// Return the total length + pub fn encoded_length(&self) -> IntValue<'a> { + self.length + } + + /// Return the amount of fixed and dynamic storage required to store a type + fn encoded_dynamic_length<'c>( + arg: BasicValueEnum<'c>, + load: bool, + ty: &ast::Type, + function: FunctionValue, + contract: &Contract<'c>, + ) -> IntValue<'c> { + match ty { + ast::Type::Struct(n) if ty.is_dynamic(contract.ns) => { let arg = if load { contract.builder.build_load(arg.into_pointer_value(), "") } else { arg }; - // if the array is of dynamic length, or has dynamic array elements, then it is written to - // the dynamic section. + let normal_struct = contract + .context + .append_basic_block(function, "normal_struct"); + let null_struct = contract.context.append_basic_block(function, "null_struct"); + let done_struct = contract.context.append_basic_block(function, "done_struct"); - // write the current offset to fixed - self.encode_primitive( - contract, - false, - function, - &ast::Type::Uint(32), - *fixed, - (*offset).into(), - ); + let is_null = contract + .builder + .build_is_null(arg.into_pointer_value(), "is_null"); - *fixed = unsafe { - contract.builder.build_gep( - *fixed, - &[contract.context.i32_type().const_int(32, false)], - "", - ) - }; + contract + .builder + .build_conditional_branch(is_null, null_struct, normal_struct); - let array_length = if let Some(d) = &dim[0] { - // fixed length - contract - .context - .i32_type() - .const_int(d.to_u64().unwrap(), false) - } else { - // Now, write the length to dynamic - let len = contract.vector_len(arg); + contract.builder.position_at_end(normal_struct); - // write the current offset to fixed - self.encode_primitive( - contract, - false, - function, - &ast::Type::Uint(32), - *dynamic, - len.into(), + let mut normal_sum = contract.context.i32_type().const_zero(); + + for (i, field) in contract.ns.structs[*n].fields.iter().enumerate() { + // a struct with dynamic fields gets stored in the dynamic part + normal_sum = contract.builder.build_int_add( + normal_sum, + contract.context.i32_type().const_int( + EncoderBuilder::encoded_fixed_length(&field.ty, contract.ns), + false, + ), + "", ); - *dynamic = unsafe { + let elem = unsafe { contract.builder.build_gep( - *dynamic, - &[contract.context.i32_type().const_int(32, false)], - "", + arg.into_pointer_value(), + &[ + contract.context.i32_type().const_zero(), + contract.context.i32_type().const_int(i as u64, false), + ], + &field.name, ) }; - *offset = contract.builder.build_int_add( - *offset, - contract.context.i32_type().const_int(32, false), + let len = EncoderBuilder::encoded_dynamic_length( + elem.into(), + true, + &field.ty, + function, + contract, + ); + + normal_sum = contract.builder.build_int_add(normal_sum, len, ""); + } + + contract.builder.build_unconditional_branch(done_struct); + + let normal_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(null_struct); + + let mut null_sum = contract.context.i32_type().const_zero(); + + for field in &contract.ns.structs[*n].fields { + // a struct with dynamic fields gets stored in the dynamic part + null_sum = contract.builder.build_int_add( + null_sum, + contract.context.i32_type().const_int( + EncoderBuilder::encoded_fixed_length(&field.ty, contract.ns), + false, + ), "", ); - len + null_sum = contract.builder.build_int_add( + null_sum, + EncoderBuilder::encoded_dynamic_length( + contract.default_value(&field.ty), + false, + &field.ty, + function, + contract, + ), + "", + ); + } + + contract.builder.build_unconditional_branch(done_struct); + + let null_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(done_struct); + + let sum = contract + .builder + .build_phi(contract.context.i32_type(), "sum"); + + sum.add_incoming(&[(&normal_sum, normal_struct), (&null_sum, null_struct)]); + + sum.as_basic_value().into_int_value() + } + ast::Type::Array(elem_ty, dims) if ty.is_dynamic(contract.ns) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg }; - let array_data_offset = contract.builder.build_int_mul( - contract.context.i32_type().const_int( - EthAbiEncoder::encoded_fixed_length(&elem_ty, contract.ns), - false, - ), - array_length, - "array_data_offset", - ); + let mut sum = contract.context.i32_type().const_zero(); - let normal_fixed = *dynamic; - let null_fixed = *dynamic; + let len = match dims.last().unwrap() { + None => { + let array_len = contract.vector_len(arg); - *dynamic = unsafe { - contract - .builder - .build_gep(*dynamic, &[array_data_offset], "") + // A dynamic array will store its own length + sum = contract.builder.build_int_add( + sum, + contract.context.i32_type().const_int(32, false), + "", + ); + + array_len + } + Some(d) => contract + .context + .i32_type() + .const_int(d.to_u64().unwrap(), false), }; + // plus fixed size elements + sum = contract.builder.build_int_add( + sum, + contract.builder.build_int_mul( + len, + contract.context.i32_type().const_int( + EncoderBuilder::encoded_fixed_length(&elem_ty, contract.ns), + false, + ), + "", + ), + "", + ); + let normal_array = contract .context .append_basic_block(function, "normal_array"); @@ -161,63 +250,35 @@ impl EthAbiEncoder { contract.builder.position_at_end(normal_array); - let mut builder = LoopBuilder::new(contract, function); - - let mut normal_fixed = builder - .add_loop_phi( - contract, - "fixed", - contract.context.i8_type().ptr_type(AddressSpace::Generic), - normal_fixed.into(), - ) - .into_pointer_value(); - - let mut normal_array_data_offset = builder - .add_loop_phi( - contract, - "offset", - contract.context.i32_type(), - array_data_offset.into(), - ) - .into_int_value(); - - let mut normal_dynamic = builder - .add_loop_phi( - contract, - "dynamic", - contract.context.i8_type().ptr_type(AddressSpace::Generic), - (*dynamic).into(), - ) - .into_pointer_value(); - - let index = builder.over( - contract, - contract.context.i32_type().const_zero(), - array_length, - ); - - // loop body - let elem = contract.array_subscript(ty, arg.into_pointer_value(), index); - - self.encode_ty( - contract, - true, - function, - &elem_ty.deref_any(), - elem.into(), - &mut normal_fixed, - &mut normal_array_data_offset, - &mut normal_dynamic, - ); + let mut normal_length = sum; - builder.set_loop_phi_value(contract, "fixed", normal_fixed.into()); - builder.set_loop_phi_value(contract, "offset", normal_array_data_offset.into()); - builder.set_loop_phi_value(contract, "dynamic", normal_dynamic.into()); + contract.builder.position_at_end(normal_array); - builder.finish(contract); + // the element of the array are dynamic; we need to iterate over the array to find the encoded length + if elem_ty.is_dynamic(contract.ns) { + contract.emit_loop_cond_first_with_int( + function, + contract.context.i32_type().const_zero(), + len, + &mut normal_length, + |index, sum| { + let elem = + contract.array_subscript(ty, arg.into_pointer_value(), index); - let normal_dynamic = builder.get_loop_phi("dynamic"); - let normal_array_data_offset = builder.get_loop_phi("offset"); + *sum = contract.builder.build_int_add( + EncoderBuilder::encoded_dynamic_length( + elem.into(), + true, + &elem_ty, + function, + contract, + ), + *sum, + "", + ); + }, + ); + } contract.builder.build_unconditional_branch(done_array); @@ -225,137 +286,332 @@ impl EthAbiEncoder { contract.builder.position_at_end(null_array); - let mut builder = LoopBuilder::new(contract, function); - - let mut null_fixed = builder - .add_loop_phi( - contract, - "fixed", - contract.context.i8_type().ptr_type(AddressSpace::Generic), - null_fixed.into(), - ) - .into_pointer_value(); - - let mut null_array_data_offset = builder - .add_loop_phi( - contract, - "offset", - contract.context.i32_type(), - array_data_offset.into(), - ) - .into_int_value(); - - let mut null_dynamic = builder - .add_loop_phi( - contract, - "dynamic", - contract.context.i8_type().ptr_type(AddressSpace::Generic), - (*dynamic).into(), - ) - .into_pointer_value(); - - let _ = builder.over( - contract, - contract.context.i32_type().const_zero(), - array_length, - ); - - // loop body let elem = contract.default_value(&elem_ty.deref_any()); - self.encode_ty( - contract, - false, - function, - &elem_ty.deref_any(), - elem, - &mut null_fixed, - &mut null_array_data_offset, - &mut null_dynamic, + let null_length = contract.builder.build_int_add( + contract.builder.build_int_mul( + EncoderBuilder::encoded_dynamic_length( + elem, false, elem_ty, function, contract, + ), + len, + "", + ), + sum, + "", ); - builder.set_loop_phi_value(contract, "fixed", null_fixed.into()); - builder.set_loop_phi_value(contract, "offset", null_array_data_offset.into()); - builder.set_loop_phi_value(contract, "dynamic", null_dynamic.into()); - - builder.finish(contract); - - let null_dynamic = builder.get_loop_phi("dynamic"); - let null_array_data_offset = builder.get_loop_phi("offset"); - contract.builder.build_unconditional_branch(done_array); let null_array = contract.builder.get_insert_block().unwrap(); contract.builder.position_at_end(done_array); - let dynamic_phi = contract.builder.build_phi( - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "dynamic", - ); - - dynamic_phi - .add_incoming(&[(&normal_dynamic, normal_array), (&null_dynamic, null_array)]); - - *dynamic = dynamic_phi.as_basic_value().into_pointer_value(); - - let array_array_offset_phi = contract + let encoded_length = contract .builder - .build_phi(contract.context.i32_type(), "array_data_offset"); + .build_phi(contract.context.i32_type(), "encoded_length"); - array_array_offset_phi.add_incoming(&[ - (&normal_array_data_offset, normal_array), - (&null_array_data_offset, null_array), - ]); + encoded_length + .add_incoming(&[(&normal_length, normal_array), (&null_length, null_array)]); - *offset = contract.builder.build_int_add( - array_array_offset_phi.as_basic_value().into_int_value(), - *offset, - "new_offset", - ); + encoded_length.as_basic_value().into_int_value() } - ast::Type::Array(elem_ty, dim) => { + ast::Type::String | ast::Type::DynamicBytes => { let arg = if load { contract.builder.build_load(arg.into_pointer_value(), "") } else { arg }; - let dim = dim[0].as_ref().unwrap().to_u64().unwrap(); + // The dynamic part is the length (=32 bytes) and the string + // data itself. Length 0 occupies no space, length 1-32 occupies + // 32 bytes, etc + contract.builder.build_and( + contract.builder.build_int_add( + contract.vector_len(arg), + contract.context.i32_type().const_int(32 + 31, false), + "", + ), + contract.context.i32_type().const_int(!31, false), + "", + ) + } + _ => contract.context.i32_type().const_zero(), + } + } - let normal_array = contract - .context - .append_basic_block(function, "normal_array"); - let null_array = contract.context.append_basic_block(function, "null_array"); - let done_array = contract.context.append_basic_block(function, "done_array"); + /// Return the encoded length of the given type, fixed part only + fn encoded_fixed_length(ty: &ast::Type, ns: &ast::Namespace) -> u64 { + match ty { + ast::Type::Bool + | ast::Type::Contract(_) + | ast::Type::Address(_) + | ast::Type::Int(_) + | ast::Type::Uint(_) + | ast::Type::Bytes(_) + | ast::Type::ExternalFunction { .. } => 32, + // String and Dynamic bytes use 32 bytes for the offset into dynamic encoded + ast::Type::String + | ast::Type::DynamicBytes + | ast::Type::Struct(_) + | ast::Type::Array(_, _) + if ty.is_dynamic(ns) => + { + 32 + } + ast::Type::Enum(_) => 32, + ast::Type::Struct(n) => ns.structs[*n] + .fields + .iter() + .map(|f| EncoderBuilder::encoded_fixed_length(&f.ty, ns)) + .sum(), + ast::Type::Array(ty, dims) => { + // The array must be fixed, dynamic arrays are handled above + let product: u64 = dims + .iter() + .map(|d| d.as_ref().unwrap().to_u64().unwrap()) + .product(); - let is_null = contract - .builder - .build_is_null(arg.into_pointer_value(), "is_null"); + product * EncoderBuilder::encoded_fixed_length(&ty, ns) + } + ast::Type::Ref(r) => EncoderBuilder::encoded_fixed_length(r, ns), + ast::Type::StorageRef(r) => EncoderBuilder::encoded_fixed_length(r, ns), + _ => unreachable!(), + } + } - contract - .builder - .build_conditional_branch(is_null, null_array, normal_array); + /// Make it so + pub fn finish( + self, + contract: &Contract<'a>, + function: FunctionValue<'a>, + output: PointerValue<'a>, + ) { + let mut output = output; - contract.builder.position_at_end(normal_array); + if let Some(selector) = self.selector { + contract.builder.build_store( + contract.builder.build_pointer_cast( + output, + contract.context.i32_type().ptr_type(AddressSpace::Generic), + "", + ), + selector, + ); - let mut builder = LoopBuilder::new(contract, function); + output = unsafe { + contract.builder.build_gep( + output, + &[contract + .context + .i32_type() + .const_int(std::mem::size_of::() as u64, false)], + "", + ) + }; + } - let mut normal_fixed = builder - .add_loop_phi( - contract, - "fixed", + // We use a little trick here. The length might or might not include the selector. + // The length will be a multiple of 32 plus the selector (4). So by dividing by 8, + // we lose the selector. + contract.builder.build_call( + contract.module.get_function("__bzero8").unwrap(), + &[ + output.into(), + contract + .builder + .build_int_unsigned_div( + self.length, + contract.context.i32_type().const_int(8, false), + "", + ) + .into(), + ], + "", + ); + + let mut output = output; + let mut offset = self.offset; + let mut dynamic = unsafe { contract.builder.build_gep(output, &[self.offset], "") }; + + for (i, ty) in self.tys.iter().enumerate() { + self.encode_ty( + contract, + self.load_args, + function, + ty, + self.args[i], + &mut output, + &mut offset, + &mut dynamic, + ); + } + } + + /// Recursively encode a value in arg. The load argument specifies if the arg is a pointer + /// to the value, or the value itself. The fixed pointer points to the fixed, non-dynamic part + /// of the encoded data. The offset is current offset for dynamic fields. + fn encode_ty( + &self, + contract: &Contract<'a>, + load: bool, + function: FunctionValue<'a>, + ty: &ast::Type, + arg: BasicValueEnum<'a>, + fixed: &mut PointerValue<'a>, + offset: &mut IntValue<'a>, + dynamic: &mut PointerValue<'a>, + ) { + match &ty { + ast::Type::Bool + | ast::Type::Address(_) + | ast::Type::Contract(_) + | ast::Type::Int(_) + | ast::Type::Uint(_) + | ast::Type::Bytes(_) => { + self.encode_primitive(contract, load, function, ty, *fixed, arg); + + *fixed = unsafe { + contract.builder.build_gep( + *fixed, + &[contract.context.i32_type().const_int(32, false)], + "", + ) + }; + } + ast::Type::Enum(n) => { + self.encode_primitive( + contract, + load, + function, + &contract.ns.enums[*n].ty, + *fixed, + arg, + ); + + *fixed = unsafe { + contract.builder.build_gep( + *fixed, + &[contract.context.i32_type().const_int(32, false)], + "", + ) + }; + } + ast::Type::Array(elem_ty, dim) if ty.is_dynamic(contract.ns) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + // if the array is of dynamic length, or has dynamic array elements, then it is written to + // the dynamic section. + + // write the current offset to fixed + self.encode_primitive( + contract, + false, + function, + &ast::Type::Uint(32), + *fixed, + (*offset).into(), + ); + + *fixed = unsafe { + contract.builder.build_gep( + *fixed, + &[contract.context.i32_type().const_int(32, false)], + "", + ) + }; + + let array_length = if let Some(d) = &dim[0] { + // fixed length + contract + .context + .i32_type() + .const_int(d.to_u64().unwrap(), false) + } else { + // Now, write the length to dynamic + let len = contract.vector_len(arg); + + // write the current offset to fixed + self.encode_primitive( + contract, + false, + function, + &ast::Type::Uint(32), + *dynamic, + len.into(), + ); + + *dynamic = unsafe { + contract.builder.build_gep( + *dynamic, + &[contract.context.i32_type().const_int(32, false)], + "", + ) + }; + + *offset = contract.builder.build_int_add( + *offset, + contract.context.i32_type().const_int(32, false), + "", + ); + + len + }; + + let array_data_offset = contract.builder.build_int_mul( + contract.context.i32_type().const_int( + EncoderBuilder::encoded_fixed_length(&elem_ty, contract.ns), + false, + ), + array_length, + "array_data_offset", + ); + + let normal_fixed = *dynamic; + let null_fixed = *dynamic; + + *dynamic = unsafe { + contract + .builder + .build_gep(*dynamic, &[array_data_offset], "") + }; + + let normal_array = contract + .context + .append_basic_block(function, "normal_array"); + let null_array = contract.context.append_basic_block(function, "null_array"); + let done_array = contract.context.append_basic_block(function, "done_array"); + + let is_null = contract + .builder + .build_is_null(arg.into_pointer_value(), "is_null"); + + contract + .builder + .build_conditional_branch(is_null, null_array, normal_array); + + contract.builder.position_at_end(normal_array); + + let mut builder = LoopBuilder::new(contract, function); + + let mut normal_fixed = builder + .add_loop_phi( + contract, + "fixed", contract.context.i8_type().ptr_type(AddressSpace::Generic), - (*fixed).into(), + normal_fixed.into(), ) .into_pointer_value(); - let mut normal_offset = builder + let mut normal_array_data_offset = builder .add_loop_phi( contract, "offset", contract.context.i32_type(), - (*offset).into(), + array_data_offset.into(), ) .into_int_value(); @@ -370,18 +626,12 @@ impl EthAbiEncoder { let index = builder.over( contract, - contract.context.i64_type().const_zero(), - contract.context.i64_type().const_int(dim, false), + contract.context.i32_type().const_zero(), + array_length, ); // loop body - let elem = unsafe { - contract.builder.build_gep( - arg.into_pointer_value(), - &[contract.context.i32_type().const_zero(), index], - "index_access", - ) - }; + let elem = contract.array_subscript(ty, arg.into_pointer_value(), index); self.encode_ty( contract, @@ -390,19 +640,18 @@ impl EthAbiEncoder { &elem_ty.deref_any(), elem.into(), &mut normal_fixed, - &mut normal_offset, + &mut normal_array_data_offset, &mut normal_dynamic, ); builder.set_loop_phi_value(contract, "fixed", normal_fixed.into()); - builder.set_loop_phi_value(contract, "offset", normal_offset.into()); + builder.set_loop_phi_value(contract, "offset", normal_array_data_offset.into()); builder.set_loop_phi_value(contract, "dynamic", normal_dynamic.into()); builder.finish(contract); - let normal_fixed = builder.get_loop_phi("fixed"); - let normal_offset = builder.get_loop_phi("offset"); let normal_dynamic = builder.get_loop_phi("dynamic"); + let normal_array_data_offset = builder.get_loop_phi("offset"); contract.builder.build_unconditional_branch(done_array); @@ -410,11 +659,6 @@ impl EthAbiEncoder { contract.builder.position_at_end(null_array); - // Create a loop for generating an array of empty values - // FIXME: all fixed-length types are encoded as zeros, and the memory has - // already been zero'ed out, so this is pointless. Just step over it. - let elem = contract.default_value(&elem_ty.deref_any()); - let mut builder = LoopBuilder::new(contract, function); let mut null_fixed = builder @@ -422,16 +666,16 @@ impl EthAbiEncoder { contract, "fixed", contract.context.i8_type().ptr_type(AddressSpace::Generic), - (*fixed).into(), + null_fixed.into(), ) .into_pointer_value(); - let mut null_offset = builder + let mut null_array_data_offset = builder .add_loop_phi( contract, "offset", contract.context.i32_type(), - (*offset).into(), + array_data_offset.into(), ) .into_int_value(); @@ -444,13 +688,15 @@ impl EthAbiEncoder { ) .into_pointer_value(); - builder.over( + let _ = builder.over( contract, - contract.context.i64_type().const_zero(), - contract.context.i64_type().const_int(dim, false), + contract.context.i32_type().const_zero(), + array_length, ); // loop body + let elem = contract.default_value(&elem_ty.deref_any()); + self.encode_ty( contract, false, @@ -458,19 +704,18 @@ impl EthAbiEncoder { &elem_ty.deref_any(), elem, &mut null_fixed, - &mut null_offset, + &mut null_array_data_offset, &mut null_dynamic, ); builder.set_loop_phi_value(contract, "fixed", null_fixed.into()); - builder.set_loop_phi_value(contract, "offset", null_offset.into()); + builder.set_loop_phi_value(contract, "offset", null_array_data_offset.into()); builder.set_loop_phi_value(contract, "dynamic", null_dynamic.into()); builder.finish(contract); - let null_fixed = builder.get_loop_phi("fixed"); - let null_offset = builder.get_loop_phi("offset"); let null_dynamic = builder.get_loop_phi("dynamic"); + let null_array_data_offset = builder.get_loop_phi("offset"); contract.builder.build_unconditional_branch(done_array); @@ -478,24 +723,6 @@ impl EthAbiEncoder { contract.builder.position_at_end(done_array); - let fixed_phi = contract.builder.build_phi( - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "fixed", - ); - - fixed_phi.add_incoming(&[(&normal_fixed, normal_array), (&null_fixed, null_array)]); - - *fixed = fixed_phi.as_basic_value().into_pointer_value(); - - let offset_phi = contract - .builder - .build_phi(contract.context.i32_type(), "offset"); - - offset_phi - .add_incoming(&[(&normal_offset, normal_array), (&null_offset, null_array)]); - - *offset = offset_phi.as_basic_value().into_int_value(); - let dynamic_phi = contract.builder.build_phi( contract.context.i8_type().ptr_type(AddressSpace::Generic), "dynamic", @@ -505,17 +732,224 @@ impl EthAbiEncoder { .add_incoming(&[(&normal_dynamic, normal_array), (&null_dynamic, null_array)]); *dynamic = dynamic_phi.as_basic_value().into_pointer_value(); - } - ast::Type::Struct(n) if ty.is_dynamic(contract.ns) => { - let arg = if load { - contract.builder.build_load(arg.into_pointer_value(), "") - } else { - arg - }; - // write the current offset to fixed - self.encode_primitive( - contract, + let array_array_offset_phi = contract + .builder + .build_phi(contract.context.i32_type(), "array_data_offset"); + + array_array_offset_phi.add_incoming(&[ + (&normal_array_data_offset, normal_array), + (&null_array_data_offset, null_array), + ]); + + *offset = contract.builder.build_int_add( + array_array_offset_phi.as_basic_value().into_int_value(), + *offset, + "new_offset", + ); + } + ast::Type::Array(elem_ty, dim) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + let dim = dim[0].as_ref().unwrap().to_u64().unwrap(); + + let normal_array = contract + .context + .append_basic_block(function, "normal_array"); + let null_array = contract.context.append_basic_block(function, "null_array"); + let done_array = contract.context.append_basic_block(function, "done_array"); + + let is_null = contract + .builder + .build_is_null(arg.into_pointer_value(), "is_null"); + + contract + .builder + .build_conditional_branch(is_null, null_array, normal_array); + + contract.builder.position_at_end(normal_array); + + let mut builder = LoopBuilder::new(contract, function); + + let mut normal_fixed = builder + .add_loop_phi( + contract, + "fixed", + contract.context.i8_type().ptr_type(AddressSpace::Generic), + (*fixed).into(), + ) + .into_pointer_value(); + + let mut normal_offset = builder + .add_loop_phi( + contract, + "offset", + contract.context.i32_type(), + (*offset).into(), + ) + .into_int_value(); + + let mut normal_dynamic = builder + .add_loop_phi( + contract, + "dynamic", + contract.context.i8_type().ptr_type(AddressSpace::Generic), + (*dynamic).into(), + ) + .into_pointer_value(); + + let index = builder.over( + contract, + contract.context.i64_type().const_zero(), + contract.context.i64_type().const_int(dim, false), + ); + + // loop body + let elem = unsafe { + contract.builder.build_gep( + arg.into_pointer_value(), + &[contract.context.i32_type().const_zero(), index], + "index_access", + ) + }; + + self.encode_ty( + contract, + true, + function, + &elem_ty.deref_any(), + elem.into(), + &mut normal_fixed, + &mut normal_offset, + &mut normal_dynamic, + ); + + builder.set_loop_phi_value(contract, "fixed", normal_fixed.into()); + builder.set_loop_phi_value(contract, "offset", normal_offset.into()); + builder.set_loop_phi_value(contract, "dynamic", normal_dynamic.into()); + + builder.finish(contract); + + let normal_fixed = builder.get_loop_phi("fixed"); + let normal_offset = builder.get_loop_phi("offset"); + let normal_dynamic = builder.get_loop_phi("dynamic"); + + contract.builder.build_unconditional_branch(done_array); + + let normal_array = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(null_array); + + // Create a loop for generating an array of empty values + // FIXME: all fixed-length types are encoded as zeros, and the memory has + // already been zero'ed out, so this is pointless. Just step over it. + let elem = contract.default_value(&elem_ty.deref_any()); + + let mut builder = LoopBuilder::new(contract, function); + + let mut null_fixed = builder + .add_loop_phi( + contract, + "fixed", + contract.context.i8_type().ptr_type(AddressSpace::Generic), + (*fixed).into(), + ) + .into_pointer_value(); + + let mut null_offset = builder + .add_loop_phi( + contract, + "offset", + contract.context.i32_type(), + (*offset).into(), + ) + .into_int_value(); + + let mut null_dynamic = builder + .add_loop_phi( + contract, + "dynamic", + contract.context.i8_type().ptr_type(AddressSpace::Generic), + (*dynamic).into(), + ) + .into_pointer_value(); + + builder.over( + contract, + contract.context.i64_type().const_zero(), + contract.context.i64_type().const_int(dim, false), + ); + + // loop body + self.encode_ty( + contract, + false, + function, + &elem_ty.deref_any(), + elem, + &mut null_fixed, + &mut null_offset, + &mut null_dynamic, + ); + + builder.set_loop_phi_value(contract, "fixed", null_fixed.into()); + builder.set_loop_phi_value(contract, "offset", null_offset.into()); + builder.set_loop_phi_value(contract, "dynamic", null_dynamic.into()); + + builder.finish(contract); + + let null_fixed = builder.get_loop_phi("fixed"); + let null_offset = builder.get_loop_phi("offset"); + let null_dynamic = builder.get_loop_phi("dynamic"); + + contract.builder.build_unconditional_branch(done_array); + + let null_array = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(done_array); + + let fixed_phi = contract.builder.build_phi( + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "fixed", + ); + + fixed_phi.add_incoming(&[(&normal_fixed, normal_array), (&null_fixed, null_array)]); + + *fixed = fixed_phi.as_basic_value().into_pointer_value(); + + let offset_phi = contract + .builder + .build_phi(contract.context.i32_type(), "offset"); + + offset_phi + .add_incoming(&[(&normal_offset, normal_array), (&null_offset, null_array)]); + + *offset = offset_phi.as_basic_value().into_int_value(); + + let dynamic_phi = contract.builder.build_phi( + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "dynamic", + ); + + dynamic_phi + .add_incoming(&[(&normal_dynamic, normal_array), (&null_dynamic, null_array)]); + + *dynamic = dynamic_phi.as_basic_value().into_pointer_value(); + } + ast::Type::Struct(n) if ty.is_dynamic(contract.ns) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + // write the current offset to fixed + self.encode_primitive( + contract, false, function, &ast::Type::Uint(32), @@ -538,7 +972,7 @@ impl EthAbiEncoder { let fixed_field_length = contract.ns.structs[*n] .fields .iter() - .map(|f| EthAbiEncoder::encoded_fixed_length(&f.ty, contract.ns)) + .map(|f| EncoderBuilder::encoded_fixed_length(&f.ty, contract.ns)) .sum(); *dynamic = unsafe { @@ -877,7 +1311,7 @@ impl EthAbiEncoder { } /// ABI encode a single primitive - fn encode_primitive<'a>( + fn encode_primitive( &self, contract: &Contract<'a>, load: bool, @@ -1177,408 +1611,114 @@ impl EthAbiEncoder { .into_int_value(); contract.builder.build_call( - contract.module.get_function("__memset8").unwrap(), - &[ - dest8.into(), - signval.into(), - contract.context.i32_type().const_int(4, false).into(), - ], - "", - ); - } - } - - let temp = contract.build_alloca( - function, - arg.into_int_value().get_type(), - &format!("uint{}", n), - ); - - contract.builder.build_store(temp, arg.into_int_value()); - - contract.builder.build_call( - contract.module.get_function("__leNtobe32").unwrap(), - &[ - contract - .builder - .build_pointer_cast( - temp, - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "store", - ) - .into(), - dest8.into(), - contract - .context - .i32_type() - .const_int(n as u64 / 8, false) - .into(), - ], - "", - ); - } - ast::Type::Bytes(1) => { - let arg = if load { - contract.builder.build_load(arg.into_pointer_value(), "") - } else { - arg - }; - - let dest8 = contract.builder.build_pointer_cast( - dest, - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "destvoid", - ); - - contract.builder.build_store(dest8, arg); - } - ast::Type::Bytes(n) => { - let val = if load { - arg.into_pointer_value() - } else { - let temp = contract.build_alloca( - function, - arg.into_int_value().get_type(), - &format!("bytes{}", n), - ); - - contract.builder.build_store(temp, arg.into_int_value()); - - temp - }; - - contract.builder.build_call( - contract.module.get_function("__leNtobeN").unwrap(), - &[ - contract - .builder - .build_pointer_cast( - val, - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "store", - ) - .into(), - contract - .builder - .build_pointer_cast( - dest, - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "dest", - ) - .into(), - contract - .context - .i32_type() - .const_int(*n as u64, false) - .into(), - ], - "", - ); - } - _ => unimplemented!(), - } - } - - /// Return the amount of fixed and dynamic storage required to store a type - pub fn encoded_dynamic_length<'a>( - arg: BasicValueEnum<'a>, - load: bool, - ty: &ast::Type, - function: FunctionValue, - contract: &Contract<'a>, - ) -> IntValue<'a> { - match ty { - ast::Type::Struct(n) if ty.is_dynamic(contract.ns) => { - let arg = if load { - contract.builder.build_load(arg.into_pointer_value(), "") - } else { - arg - }; - - let normal_struct = contract - .context - .append_basic_block(function, "normal_struct"); - let null_struct = contract.context.append_basic_block(function, "null_struct"); - let done_struct = contract.context.append_basic_block(function, "done_struct"); - - let is_null = contract - .builder - .build_is_null(arg.into_pointer_value(), "is_null"); - - contract - .builder - .build_conditional_branch(is_null, null_struct, normal_struct); - - contract.builder.position_at_end(normal_struct); - - let mut normal_sum = contract.context.i32_type().const_zero(); - - for (i, field) in contract.ns.structs[*n].fields.iter().enumerate() { - // a struct with dynamic fields gets stored in the dynamic part - normal_sum = contract.builder.build_int_add( - normal_sum, - contract.context.i32_type().const_int( - EthAbiEncoder::encoded_fixed_length(&field.ty, contract.ns), - false, - ), - "", - ); - - let elem = unsafe { - contract.builder.build_gep( - arg.into_pointer_value(), - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_int(i as u64, false), - ], - &field.name, - ) - }; - - let len = EthAbiEncoder::encoded_dynamic_length( - elem.into(), - true, - &field.ty, - function, - contract, - ); - - normal_sum = contract.builder.build_int_add(normal_sum, len, ""); - } - - contract.builder.build_unconditional_branch(done_struct); - - let normal_struct = contract.builder.get_insert_block().unwrap(); - - contract.builder.position_at_end(null_struct); - - let mut null_sum = contract.context.i32_type().const_zero(); - - for field in &contract.ns.structs[*n].fields { - // a struct with dynamic fields gets stored in the dynamic part - null_sum = contract.builder.build_int_add( - null_sum, - contract.context.i32_type().const_int( - EthAbiEncoder::encoded_fixed_length(&field.ty, contract.ns), - false, - ), - "", - ); - - null_sum = contract.builder.build_int_add( - null_sum, - EthAbiEncoder::encoded_dynamic_length( - contract.default_value(&field.ty), - false, - &field.ty, - function, - contract, - ), - "", - ); - } - - contract.builder.build_unconditional_branch(done_struct); - - let null_struct = contract.builder.get_insert_block().unwrap(); - - contract.builder.position_at_end(done_struct); - - let sum = contract - .builder - .build_phi(contract.context.i32_type(), "sum"); - - sum.add_incoming(&[(&normal_sum, normal_struct), (&null_sum, null_struct)]); - - sum.as_basic_value().into_int_value() - } - ast::Type::Array(elem_ty, dims) if ty.is_dynamic(contract.ns) => { - let arg = if load { - contract.builder.build_load(arg.into_pointer_value(), "") - } else { - arg - }; - - let mut sum = contract.context.i32_type().const_zero(); - - let len = match dims.last().unwrap() { - None => { - let array_len = contract.vector_len(arg); - - // A dynamic array will store its own length - sum = contract.builder.build_int_add( - sum, - contract.context.i32_type().const_int(32, false), - "", - ); - - array_len - } - Some(d) => contract - .context - .i32_type() - .const_int(d.to_u64().unwrap(), false), - }; - - // plus fixed size elements - sum = contract.builder.build_int_add( - sum, - contract.builder.build_int_mul( - len, - contract.context.i32_type().const_int( - EthAbiEncoder::encoded_fixed_length(&elem_ty, contract.ns), - false, - ), - "", - ), - "", - ); - - let normal_array = contract - .context - .append_basic_block(function, "normal_array"); - let null_array = contract.context.append_basic_block(function, "null_array"); - let done_array = contract.context.append_basic_block(function, "done_array"); - - let is_null = contract - .builder - .build_is_null(arg.into_pointer_value(), "is_null"); - - contract - .builder - .build_conditional_branch(is_null, null_array, normal_array); - - contract.builder.position_at_end(normal_array); - - let mut normal_length = sum; - - contract.builder.position_at_end(normal_array); - - // the element of the array are dynamic; we need to iterate over the array to find the encoded length - if elem_ty.is_dynamic(contract.ns) { - contract.emit_loop_cond_first_with_int( - function, - contract.context.i32_type().const_zero(), - len, - &mut normal_length, - |index, sum| { - let elem = - contract.array_subscript(ty, arg.into_pointer_value(), index); - - *sum = contract.builder.build_int_add( - EthAbiEncoder::encoded_dynamic_length( - elem.into(), - true, - &elem_ty, - function, - contract, - ), - *sum, - "", - ); - }, - ); - } - - contract.builder.build_unconditional_branch(done_array); - - let normal_array = contract.builder.get_insert_block().unwrap(); - - contract.builder.position_at_end(null_array); - - let elem = contract.default_value(&elem_ty.deref_any()); - - let null_length = contract.builder.build_int_add( - contract.builder.build_int_mul( - EthAbiEncoder::encoded_dynamic_length( - elem, false, elem_ty, function, contract, - ), - len, - "", - ), - sum, - "", - ); - - contract.builder.build_unconditional_branch(done_array); - - let null_array = contract.builder.get_insert_block().unwrap(); - - contract.builder.position_at_end(done_array); + contract.module.get_function("__memset8").unwrap(), + &[ + dest8.into(), + signval.into(), + contract.context.i32_type().const_int(4, false).into(), + ], + "", + ); + } + } - let encoded_length = contract - .builder - .build_phi(contract.context.i32_type(), "encoded_length"); + let temp = contract.build_alloca( + function, + arg.into_int_value().get_type(), + &format!("uint{}", n), + ); - encoded_length - .add_incoming(&[(&normal_length, normal_array), (&null_length, null_array)]); + contract.builder.build_store(temp, arg.into_int_value()); - encoded_length.as_basic_value().into_int_value() + contract.builder.build_call( + contract.module.get_function("__leNtobe32").unwrap(), + &[ + contract + .builder + .build_pointer_cast( + temp, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "store", + ) + .into(), + dest8.into(), + contract + .context + .i32_type() + .const_int(n as u64 / 8, false) + .into(), + ], + "", + ); } - ast::Type::String | ast::Type::DynamicBytes => { + ast::Type::Bytes(1) => { let arg = if load { contract.builder.build_load(arg.into_pointer_value(), "") } else { arg }; - // The dynamic part is the length (=32 bytes) and the string - // data itself. Length 0 occupies no space, length 1-32 occupies - // 32 bytes, etc - contract.builder.build_and( - contract.builder.build_int_add( - contract.vector_len(arg), - contract.context.i32_type().const_int(32 + 31, false), - "", - ), - contract.context.i32_type().const_int(!31, false), - "", - ) - } - _ => contract.context.i32_type().const_zero(), - } - } + let dest8 = contract.builder.build_pointer_cast( + dest, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "destvoid", + ); - /// Return the encoded length of the given type, fixed part only - pub fn encoded_fixed_length(ty: &ast::Type, ns: &ast::Namespace) -> u64 { - match ty { - ast::Type::Bool - | ast::Type::Contract(_) - | ast::Type::Address(_) - | ast::Type::Int(_) - | ast::Type::Uint(_) - | ast::Type::Bytes(_) - | ast::Type::ExternalFunction { .. } => 32, - // String and Dynamic bytes use 32 bytes for the offset into dynamic encoded - ast::Type::String - | ast::Type::DynamicBytes - | ast::Type::Struct(_) - | ast::Type::Array(_, _) - if ty.is_dynamic(ns) => - { - 32 + contract.builder.build_store(dest8, arg); } - ast::Type::Enum(_) => 32, - ast::Type::Struct(n) => ns.structs[*n] - .fields - .iter() - .map(|f| EthAbiEncoder::encoded_fixed_length(&f.ty, ns)) - .sum(), - ast::Type::Array(ty, dims) => { - // The array must be fixed, dynamic arrays are handled above - let product: u64 = dims - .iter() - .map(|d| d.as_ref().unwrap().to_u64().unwrap()) - .product(); + ast::Type::Bytes(n) => { + let val = if load { + arg.into_pointer_value() + } else { + let temp = contract.build_alloca( + function, + arg.into_int_value().get_type(), + &format!("bytes{}", n), + ); + + contract.builder.build_store(temp, arg.into_int_value()); + + temp + }; - product * EthAbiEncoder::encoded_fixed_length(&ty, ns) + contract.builder.build_call( + contract.module.get_function("__leNtobeN").unwrap(), + &[ + contract + .builder + .build_pointer_cast( + val, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "store", + ) + .into(), + contract + .builder + .build_pointer_cast( + dest, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "dest", + ) + .into(), + contract + .context + .i32_type() + .const_int(*n as u64, false) + .into(), + ], + "", + ); } - ast::Type::Ref(r) => EthAbiEncoder::encoded_fixed_length(r, ns), - ast::Type::StorageRef(r) => EthAbiEncoder::encoded_fixed_length(r, ns), - _ => unreachable!(), + _ => unimplemented!(), } } +} + +pub struct EthAbiDecoder { + pub bswap: bool, +} +impl EthAbiDecoder { /// decode a single primitive which is always encoded in 32 bytes fn decode_primitive<'a>( &self, @@ -2350,201 +2490,104 @@ impl EthAbiEncoder { )); } } +} - /// Calculate length of encoded data and the offset where the dynamic part starts - pub fn total_encoded_length<'b>( - contract: &Contract<'b>, - selector: Option>, - load: bool, - function: FunctionValue, - args: &[BasicValueEnum<'b>], - tys: &[ast::Type], - ) -> (IntValue<'b>, IntValue<'b>) { - let offset = contract.context.i32_type().const_int( - tys.iter() - .map(|ty| EthAbiEncoder::encoded_fixed_length(ty, contract.ns)) - .sum(), - false, - ); - - let mut length = offset; - - // now add the dynamic lengths - for (i, ty) in tys.iter().enumerate() { - length = contract.builder.build_int_add( - length, - EthAbiEncoder::encoded_dynamic_length(args[i], load, ty, function, contract), - "", - ); - } - - if selector.is_some() { - length = contract.builder.build_int_add( - length, - contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false), - "", - ); - } - - (length, offset) +/// ABI encode into a vector for abi.encode* style builtin functions +pub fn encode_to_vector<'b>( + contract: &Contract<'b>, + selector: Option>, + function: FunctionValue<'b>, + packed: bool, + args: &[BasicValueEnum<'b>], + tys: &[ast::Type], + bswap: bool, +) -> PointerValue<'b> { + if packed { + unimplemented!(); } - /// ABI encode into a vector for abi.encode* style builtin functions - pub fn encode_to_vector<'b>( - &self, - contract: &Contract<'b>, - selector: Option>, - function: FunctionValue<'b>, - packed: bool, - args: &[BasicValueEnum<'b>], - tys: &[ast::Type], - ) -> PointerValue<'b> { - if packed { - unimplemented!(); - } - - let (length, mut offset) = - EthAbiEncoder::total_encoded_length(contract, selector, false, function, args, tys); + let encoder = EncoderBuilder::new(contract, function, selector, false, args, tys, bswap); - let malloc_length = contract.builder.build_int_add( - length, - contract - .module - .get_struct_type("struct.vector") - .unwrap() - .size_of() - .unwrap() - .const_cast(contract.context.i32_type(), false), - "size", - ); + let length = encoder.encoded_length(); - let p = contract - .builder - .build_call( - contract.module.get_function("__malloc").unwrap(), - &[malloc_length.into()], - "", - ) - .try_as_basic_value() - .left() + let malloc_length = contract.builder.build_int_add( + length, + contract + .module + .get_struct_type("struct.vector") .unwrap() - .into_pointer_value(); - - let v = contract.builder.build_pointer_cast( - p, - contract - .module - .get_struct_type("struct.vector") - .unwrap() - .ptr_type(AddressSpace::Generic), - "string", - ); - - let data_len = unsafe { - contract.builder.build_gep( - v, - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_zero(), - ], - "data_len", - ) - }; - - contract.builder.build_store(data_len, length); - - let data_size = unsafe { - contract.builder.build_gep( - v, - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_int(1, false), - ], - "data_size", - ) - }; - - contract.builder.build_store(data_size, length); - - let data = unsafe { - contract.builder.build_gep( - v, - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_int(2, false), - ], - "data", - ) - }; - - let mut data = contract.builder.build_pointer_cast( - data, - contract.context.i8_type().ptr_type(AddressSpace::Generic), + .size_of() + .unwrap() + .const_cast(contract.context.i32_type(), false), + "size", + ); + + let p = contract + .builder + .build_call( + contract.module.get_function("__malloc").unwrap(), + &[malloc_length.into()], "", - ); + ) + .try_as_basic_value() + .left() + .unwrap() + .into_pointer_value(); + + let v = contract.builder.build_pointer_cast( + p, + contract + .module + .get_struct_type("struct.vector") + .unwrap() + .ptr_type(AddressSpace::Generic), + "string", + ); - if let Some(selector) = selector { - contract.builder.build_store( - contract.builder.build_pointer_cast( - data, - contract.context.i32_type().ptr_type(AddressSpace::Generic), - "", - ), - selector, - ); + let data_len = unsafe { + contract.builder.build_gep( + v, + &[ + contract.context.i32_type().const_zero(), + contract.context.i32_type().const_zero(), + ], + "data_len", + ) + }; - data = unsafe { - contract.builder.build_gep( - data, - &[contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false)], - "", - ) - }; - } + contract.builder.build_store(data_len, length); - let mut data = contract.builder.build_pointer_cast( - data, - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "", - ); + let data_size = unsafe { + contract.builder.build_gep( + v, + &[ + contract.context.i32_type().const_zero(), + contract.context.i32_type().const_int(1, false), + ], + "data_size", + ) + }; - contract.builder.build_call( - contract.module.get_function("__bzero8").unwrap(), + contract.builder.build_store(data_size, length); + + let data = unsafe { + contract.builder.build_gep( + v, &[ - data.into(), - contract - .builder - .build_int_unsigned_div( - length, - contract.context.i32_type().const_int(8, false), - "", - ) - .into(), + contract.context.i32_type().const_zero(), + contract.context.i32_type().const_int(2, false), ], - "", - ); + "data", + ) + }; - let mut dynamic = unsafe { contract.builder.build_gep(data, &[offset], "") }; + let data = contract.builder.build_pointer_cast( + data, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "", + ); - for (i, ty) in tys.iter().enumerate() { - self.encode_ty( - contract, - false, - function, - ty, - args[i], - &mut data, - &mut offset, - &mut dynamic, - ); - } + encoder.finish(contract, function, data); - v - } + v } diff --git a/src/emit/ewasm.rs b/src/emit/ewasm.rs index 7b481ac38..17783e464 100644 --- a/src/emit/ewasm.rs +++ b/src/emit/ewasm.rs @@ -19,7 +19,7 @@ use super::ethabiencoder; use super::{Contract, TargetRuntime, Variable}; pub struct EwasmTarget { - abi: ethabiencoder::EthAbiEncoder, + abi: ethabiencoder::EthAbiDecoder, } impl EwasmTarget { @@ -33,7 +33,7 @@ impl EwasmTarget { ) -> Contract<'a> { // first emit runtime code let mut b = EwasmTarget { - abi: ethabiencoder::EthAbiEncoder { bswap: false }, + abi: ethabiencoder::EthAbiDecoder { bswap: false }, }; let mut runtime_code = Contract::new( context, @@ -60,7 +60,7 @@ impl EwasmTarget { // Now we have the runtime code, create the deployer let mut b = EwasmTarget { - abi: ethabiencoder::EthAbiEncoder { bswap: false }, + abi: ethabiencoder::EthAbiDecoder { bswap: false }, }; let mut deploy_code = Contract::new( context, @@ -684,10 +684,12 @@ impl EwasmTarget { args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> (PointerValue<'b>, IntValue<'b>) { - let (mut length, mut offset) = ethabiencoder::EthAbiEncoder::total_encoded_length( - contract, selector, load, function, args, tys, + let encoder = ethabiencoder::EncoderBuilder::new( + contract, function, selector, load, args, tys, false, ); + let mut length = encoder.encoded_length(); + if let Some((_, len)) = constant { length = contract.builder.build_int_add( length, @@ -708,31 +710,8 @@ impl EwasmTarget { .unwrap() .into_pointer_value(); - // malloc returns u8* let mut data = encoded_data; - if let Some(selector) = selector { - contract.builder.build_store( - contract.builder.build_pointer_cast( - data, - contract.context.i32_type().ptr_type(AddressSpace::Generic), - "", - ), - selector, - ); - - data = unsafe { - contract.builder.build_gep( - data, - &[contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false)], - "", - ) - }; - } - if let Some((code, code_len)) = constant { contract.builder.build_call( contract.module.get_function("__memcpy").unwrap(), @@ -764,39 +743,7 @@ impl EwasmTarget { }; } - // We use a little trick here. The length might or might not include the selector. - // The length will be a multiple of 32 plus the selector (4). So by dividing by 8, - // we lose the selector. - contract.builder.build_call( - contract.module.get_function("__bzero8").unwrap(), - &[ - data.into(), - contract - .builder - .build_int_unsigned_div( - length, - contract.context.i32_type().const_int(8, false), - "", - ) - .into(), - ], - "", - ); - - let mut dynamic = unsafe { contract.builder.build_gep(data, &[offset], "") }; - - for (i, ty) in tys.iter().enumerate() { - self.abi.encode_ty( - contract, - load, - function, - ty, - args[i], - &mut data, - &mut offset, - &mut dynamic, - ); - } + encoder.finish(contract, function, data); (encoded_data, length) } @@ -1198,8 +1145,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget { args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> PointerValue<'b> { - self.abi - .encode_to_vector(contract, selector, function, packed, args, tys) + ethabiencoder::encode_to_vector(contract, selector, function, packed, args, tys, false) } fn abi_encode<'b>( diff --git a/src/emit/generic.rs b/src/emit/generic.rs index e03048184..8bdeafc3d 100644 --- a/src/emit/generic.rs +++ b/src/emit/generic.rs @@ -16,7 +16,7 @@ use super::ethabiencoder; use super::{Contract, TargetRuntime, Variable}; pub struct GenericTarget { - abi: ethabiencoder::EthAbiEncoder, + abi: ethabiencoder::EthAbiDecoder, } impl GenericTarget { @@ -29,7 +29,7 @@ impl GenericTarget { math_overflow_check: bool, ) -> Contract<'a> { let mut b = GenericTarget { - abi: ethabiencoder::EthAbiEncoder { bswap: false }, + abi: ethabiencoder::EthAbiDecoder { bswap: false }, }; let mut c = Contract::new( @@ -533,10 +533,12 @@ impl<'a> TargetRuntime<'a> for GenericTarget { args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> (PointerValue<'b>, IntValue<'b>) { - let (length, mut offset) = ethabiencoder::EthAbiEncoder::total_encoded_length( - contract, selector, load, function, args, tys, + let encoder = ethabiencoder::EncoderBuilder::new( + contract, function, selector, load, args, tys, false, ); + let length = encoder.encoded_length(); + let encoded_data = contract .builder .build_call( @@ -549,64 +551,7 @@ impl<'a> TargetRuntime<'a> for GenericTarget { .unwrap() .into_pointer_value(); - // malloc returns u8* - let mut data = encoded_data; - - if let Some(selector) = selector { - contract.builder.build_store( - contract.builder.build_pointer_cast( - data, - contract.context.i32_type().ptr_type(AddressSpace::Generic), - "", - ), - selector, - ); - - data = unsafe { - contract.builder.build_gep( - data, - &[contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false)], - "", - ) - }; - } - - // We use a little trick here. The length might or might not include the selector. - // The length will be a multiple of 32 plus the selector (4). So by dividing by 8, - // we lose the selector. - contract.builder.build_call( - contract.module.get_function("__bzero8").unwrap(), - &[ - data.into(), - contract - .builder - .build_int_unsigned_div( - length, - contract.context.i32_type().const_int(8, false), - "", - ) - .into(), - ], - "", - ); - - let mut dynamic = unsafe { contract.builder.build_gep(data, &[offset], "") }; - - for (i, ty) in tys.iter().enumerate() { - self.abi.encode_ty( - contract, - load, - function, - ty, - args[i], - &mut data, - &mut offset, - &mut dynamic, - ); - } + encoder.finish(contract, function, encoded_data); (encoded_data, length) } diff --git a/src/emit/mod.rs b/src/emit/mod.rs index a73624da2..9d76aeec1 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -2997,7 +2997,7 @@ pub trait TargetRuntime<'a> { fn emit_cfg( &mut self, contract: &mut Contract<'a>, - cfg: &ControlFlowGraph, + cfg: &'a ControlFlowGraph, function: FunctionValue<'a>, ) { // recurse through basic blocks diff --git a/src/emit/sabre.rs b/src/emit/sabre.rs index 1ca7c9617..eba7694ae 100644 --- a/src/emit/sabre.rs +++ b/src/emit/sabre.rs @@ -16,7 +16,7 @@ use super::ethabiencoder; use super::{Contract, TargetRuntime, Variable}; pub struct SabreTarget { - abi: ethabiencoder::EthAbiEncoder, + abi: ethabiencoder::EthAbiDecoder, } impl SabreTarget { @@ -29,7 +29,7 @@ impl SabreTarget { math_overflow_check: bool, ) -> Contract<'a> { let mut b = SabreTarget { - abi: ethabiencoder::EthAbiEncoder { bswap: false }, + abi: ethabiencoder::EthAbiDecoder { bswap: false }, }; let mut c = Contract::new( context, @@ -617,10 +617,12 @@ impl<'a> TargetRuntime<'a> for SabreTarget { args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> (PointerValue<'b>, IntValue<'b>) { - let (length, mut offset) = ethabiencoder::EthAbiEncoder::total_encoded_length( - contract, selector, load, function, args, tys, + let encoder = ethabiencoder::EncoderBuilder::new( + contract, function, selector, load, args, tys, false, ); + let length = encoder.encoded_length(); + let encoded_data = contract .builder .build_call( @@ -633,64 +635,7 @@ impl<'a> TargetRuntime<'a> for SabreTarget { .unwrap() .into_pointer_value(); - // malloc returns u8* - let mut data = encoded_data; - - if let Some(selector) = selector { - contract.builder.build_store( - contract.builder.build_pointer_cast( - data, - contract.context.i32_type().ptr_type(AddressSpace::Generic), - "", - ), - selector, - ); - - data = unsafe { - contract.builder.build_gep( - data, - &[contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false)], - "", - ) - }; - } - - // We use a little trick here. The length might or might not include the selector. - // The length will be a multiple of 32 plus the selector (4). So by dividing by 8, - // we lose the selector. - contract.builder.build_call( - contract.module.get_function("__bzero8").unwrap(), - &[ - data.into(), - contract - .builder - .build_int_unsigned_div( - length, - contract.context.i32_type().const_int(8, false), - "", - ) - .into(), - ], - "", - ); - - let mut dynamic = unsafe { contract.builder.build_gep(data, &[offset], "") }; - - for (i, ty) in tys.iter().enumerate() { - self.abi.encode_ty( - contract, - load, - function, - ty, - args[i], - &mut data, - &mut offset, - &mut dynamic, - ); - } + encoder.finish(contract, function, encoded_data); (encoded_data, length) } diff --git a/src/emit/solana.rs b/src/emit/solana.rs index cb2eb65ee..9dfffb9d9 100644 --- a/src/emit/solana.rs +++ b/src/emit/solana.rs @@ -17,7 +17,7 @@ use super::loop_builder::LoopBuilder; use super::{Contract, ReturnCode, TargetRuntime, Variable}; pub struct SolanaTarget { - abi: ethabiencoder::EthAbiEncoder, + abi: ethabiencoder::EthAbiDecoder, magic: u32, } @@ -42,7 +42,7 @@ impl SolanaTarget { magic.copy_from_slice(&hash[0..4]); let mut target = SolanaTarget { - abi: ethabiencoder::EthAbiEncoder { bswap: true }, + abi: ethabiencoder::EthAbiDecoder { bswap: true }, magic: u32::from_le_bytes(magic), }; @@ -2245,14 +2245,14 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { /// ABI encode into a vector for abi.encode* style builtin functions fn abi_encode_to_vector<'b>( &self, - _contract: &Contract<'b>, - _selector: Option>, - _function: FunctionValue<'b>, - _packed: bool, - _args: &[BasicValueEnum<'b>], - _spec: &[ast::Type], + contract: &Contract<'b>, + selector: Option>, + function: FunctionValue<'b>, + packed: bool, + args: &[BasicValueEnum<'b>], + tys: &[ast::Type], ) -> PointerValue<'b> { - unimplemented!(); + ethabiencoder::encode_to_vector(contract, selector, function, packed, args, tys, false) } fn abi_encode( @@ -2264,11 +2264,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { args: &[BasicValueEnum<'a>], tys: &[ast::Type], ) -> (PointerValue<'a>, IntValue<'a>) { - let (output_len, mut output, output_size) = self.return_buffer(contract); + let (output_len, output, output_size) = self.return_buffer(contract); - let (length, mut offset) = ethabiencoder::EthAbiEncoder::total_encoded_length( - contract, selector, load, function, args, tys, - ); + let encoder = + ethabiencoder::EncoderBuilder::new(contract, function, selector, load, args, tys, true); + + let length = encoder.encoded_length(); let length64 = contract @@ -2308,61 +2309,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { contract.builder.build_store(output_len, length64); - if let Some(selector) = selector { - contract.builder.build_store( - contract.builder.build_pointer_cast( - output, - contract.context.i32_type().ptr_type(AddressSpace::Generic), - "", - ), - selector, - ); - - output = unsafe { - contract.builder.build_gep( - output, - &[contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false)], - "", - ) - }; - } - - // We use a little trick here. The length might or might not include the selector. - // The length will be a multiple of 32 plus the selector (4). So by dividing by 8, - // we lose the selector. - contract.builder.build_call( - contract.module.get_function("__bzero8").unwrap(), - &[ - output.into(), - contract - .builder - .build_int_unsigned_div( - length, - contract.context.i32_type().const_int(8, false), - "", - ) - .into(), - ], - "", - ); - - let mut dynamic = unsafe { contract.builder.build_gep(output, &[offset], "") }; - - for (i, ty) in tys.iter().enumerate() { - self.abi.encode_ty( - contract, - load, - function, - ty, - args[i], - &mut output, - &mut offset, - &mut dynamic, - ); - } + encoder.finish(contract, function, output); (output, length) } From 28362e3c8a4d918a0d44361c37a8256ddfae44ac Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 14 Apr 2021 16:48:56 +0100 Subject: [PATCH 3/8] Instr::AbiEncodeVector should be an expression ABI encoding has no side affects, so it should be an expression. This means it can take part in various optimization passes. Signed-off-by: Sean Young --- src/codegen/cfg.rs | 45 ++++++++++------------- src/codegen/constant_folding.rs | 50 ++++++++++++++------------ src/codegen/dead_storage.rs | 3 +- src/codegen/expression.rs | 56 ++++++++++++++++++----------- src/codegen/reaching_definitions.rs | 3 +- src/codegen/strength_reduce.rs | 13 ------- src/codegen/vector_to_slice.rs | 1 - src/emit/mod.rs | 51 ++++++++++++-------------- src/sema/ast.rs | 25 +++++++++++++ src/sema/expression.rs | 3 +- 10 files changed, 133 insertions(+), 117 deletions(-) diff --git a/src/codegen/cfg.rs b/src/codegen/cfg.rs index 2b18d13fb..ee6f7806b 100644 --- a/src/codegen/cfg.rs +++ b/src/codegen/cfg.rs @@ -131,14 +131,6 @@ pub enum Instr { tys: Vec, data: Expression, }, - /// ABI encode data, and store the result - AbiEncodeVector { - res: usize, - tys: Vec, - packed: bool, - selector: Option, - args: Vec, - }, /// Insert unreachable instruction after e.g. self-destruct Unreachable, /// Self destruct @@ -642,6 +634,23 @@ impl ControlFlowGraph { .collect::>() .join(", ") ), + Expression::AbiEncode { + selector, + packed, + args, + .. + } => format!( + "(abiencode{}:(%{} {}))", + if *packed { "packed" } else { "" }, + match selector { + None => "".to_string(), + Some(expr) => self.expr_to_string(contract, ns, expr), + }, + args.iter() + .map(|expr| self.expr_to_string(contract, ns, expr)) + .collect::>() + .join(", ") + ), _ => panic!("{:?}", expr), } } @@ -886,25 +895,7 @@ impl ControlFlowGraph { .collect::>() .join(", "), ), - Instr::AbiEncodeVector { - res, - selector, - packed, - args, - .. - } => format!( - "{} = (abiencode{}:(%{} {})", - format!("%{}", self.vars[res].id.name), - if *packed { "packed" } else { "" }, - match selector { - None => "".to_string(), - Some(expr) => self.expr_to_string(contract, ns, expr), - }, - args.iter() - .map(|expr| self.expr_to_string(contract, ns, expr)) - .collect::>() - .join(", ") - ), + Instr::Store { dest, pos } => format!( "store {}, {}", self.expr_to_string(contract, ns, dest), diff --git a/src/codegen/constant_folding.rs b/src/codegen/constant_folding.rs index 339ecf504..18811005a 100644 --- a/src/codegen/constant_folding.rs +++ b/src/codegen/constant_folding.rs @@ -246,29 +246,6 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) { data, } } - Instr::AbiEncodeVector { - res, - tys, - packed, - selector, - args, - } => { - let args = args - .iter() - .map(|e| expression(e, Some(&vars), &cur, cfg, ns).0) - .collect(); - let selector = selector - .as_ref() - .map(|expr| expression(expr, Some(&vars), &cur, cfg, ns).0); - - cfg.blocks[block_no].instr[instr_no] = Instr::AbiEncodeVector { - res: *res, - tys: tys.clone(), - packed: *packed, - selector, - args, - } - } Instr::SelfDestruct { recipient } => { let (recipient, _) = expression(recipient, Some(&vars), &cur, cfg, ns); @@ -1051,6 +1028,33 @@ fn expression( address.1, ) } + Expression::AbiEncode { + loc, + tys, + packed, + selector, + args, + } => { + let args = args + .iter() + .map(|expr| expression(expr, vars, pos, cfg, ns).0) + .collect(); + + let selector = selector + .as_ref() + .map(|expr| Box::new(expression(&expr, vars, pos, cfg, ns).0)); + + ( + Expression::AbiEncode { + loc: *loc, + tys: tys.clone(), + packed: *packed, + selector, + args, + }, + false, + ) + } Expression::NumberLiteral(_, _, _) | Expression::BoolLiteral(_, _) | Expression::BytesLiteral(_, _, _) diff --git a/src/codegen/dead_storage.rs b/src/codegen/dead_storage.rs index 7c79df0f3..15d1efc2b 100644 --- a/src/codegen/dead_storage.rs +++ b/src/codegen/dead_storage.rs @@ -199,8 +199,7 @@ fn instr_transfers(block_no: usize, block: &BasicBlock) -> Vec> { Instr::PopMemory { array, .. } => { vec![Transfer::Kill { var_no: *array }] } - Instr::AbiEncodeVector { res, .. } - | Instr::ExternalCall { + Instr::ExternalCall { success: Some(res), .. } | Instr::Constructor { diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 5af263c87..6d3dd4755 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -903,12 +903,16 @@ pub fn expression( cfg.add( vartab, - Instr::AbiEncodeVector { + Instr::Set { + loc: *loc, res, - tys, - selector: None, - packed: false, - args, + expr: Expression::AbiEncode { + loc: *loc, + tys, + selector: None, + packed: false, + args, + }, }, ); @@ -931,12 +935,16 @@ pub fn expression( cfg.add( vartab, - Instr::AbiEncodeVector { + Instr::Set { + loc: *loc, res, - tys, - selector: None, - packed: true, - args, + expr: Expression::AbiEncode { + loc: *loc, + tys, + selector: None, + packed: true, + args, + }, }, ); @@ -961,12 +969,16 @@ pub fn expression( cfg.add( vartab, - Instr::AbiEncodeVector { + Instr::Set { + loc: *loc, res, - tys, - selector: Some(selector), - packed: false, - args, + expr: Expression::AbiEncode { + loc: *loc, + tys, + selector: Some(Box::new(selector)), + packed: false, + args, + }, }, ); @@ -998,12 +1010,16 @@ pub fn expression( cfg.add( vartab, - Instr::AbiEncodeVector { + Instr::Set { + loc: *loc, res, - tys, - selector: Some(selector), - packed: false, - args, + expr: Expression::AbiEncode { + loc: *loc, + tys, + selector: Some(Box::new(selector)), + packed: false, + args, + }, }, ); diff --git a/src/codegen/reaching_definitions.rs b/src/codegen/reaching_definitions.rs index ab311c9ee..cbe17d21f 100644 --- a/src/codegen/reaching_definitions.rs +++ b/src/codegen/reaching_definitions.rs @@ -130,8 +130,7 @@ fn instr_transfers(block_no: usize, block: &BasicBlock) -> Vec> { Instr::PopMemory { array, .. } => { vec![Transfer::Mod { var_no: *array }] } - Instr::AbiEncodeVector { res, .. } - | Instr::ExternalCall { + Instr::ExternalCall { success: Some(res), .. } | Instr::Constructor { diff --git a/src/codegen/strength_reduce.rs b/src/codegen/strength_reduce.rs index 43983b9ed..c64149a0f 100644 --- a/src/codegen/strength_reduce.rs +++ b/src/codegen/strength_reduce.rs @@ -184,19 +184,6 @@ fn block_reduce( Instr::AbiDecode { data, .. } => { *data = expression_reduce(data, &vars, ns); } - Instr::AbiEncodeVector { selector, args, .. } => { - *args = args - .iter() - .map(|e| expression_reduce(e, &vars, ns)) - .collect(); - - if let Some(selector) = selector { - *selector = expression_reduce(selector, &vars, ns); - } - } - Instr::SelfDestruct { recipient } => { - *recipient = expression_reduce(recipient, &vars, ns); - } Instr::EmitEvent { topics, data, .. } => { *topics = topics .iter() diff --git a/src/codegen/vector_to_slice.rs b/src/codegen/vector_to_slice.rs index 8373b16f0..cb127bb2e 100644 --- a/src/codegen/vector_to_slice.rs +++ b/src/codegen/vector_to_slice.rs @@ -98,7 +98,6 @@ fn find_writable_vectors( | Instr::Constructor { .. } | Instr::Unreachable | Instr::Print { .. } - | Instr::AbiEncodeVector { .. } | Instr::AssertFailure { .. } | Instr::ValueTransfer { .. } => { apply_transfers(&block.transfers[instr_no], vars, writable); diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 9d76aeec1..81df1924c 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -2577,6 +2577,28 @@ pub trait TargetRuntime<'a> { self.storage_array_length(contract, function, slot, elem_ty) .into() } + Expression::AbiEncode { + tys, + selector, + packed, + args, + .. + } => self + .abi_encode_to_vector( + contract, + selector.as_ref().map(|s| { + self.expression(contract, &s, vartab, function) + .into_int_value() + }), + function, + *packed, + &args + .iter() + .map(|a| self.expression(contract, &a, vartab, function)) + .collect::>(), + tys, + ) + .into(), Expression::Builtin(_, _, Builtin::Calldata, _) if contract.ns.target != Target::Substrate => { @@ -4024,10 +4046,7 @@ pub trait TargetRuntime<'a> { ) } } - _ => { - println!("foo {:?}", payload); - unreachable!(); - } + _ => unreachable!(), }; let gas = self @@ -4108,30 +4127,6 @@ pub trait TargetRuntime<'a> { self.value_transfer(contract, function, success, addr, value); } - Instr::AbiEncodeVector { - res, - tys, - selector, - packed, - args, - } => { - w.vars.get_mut(res).unwrap().value = self - .abi_encode_to_vector( - contract, - selector.as_ref().map(|s| { - self.expression(contract, &s, &w.vars, function) - .into_int_value() - }), - function, - *packed, - &args - .iter() - .map(|a| self.expression(contract, &a, &w.vars, function)) - .collect::>(), - tys, - ) - .into(); - } Instr::AbiDecode { res, selector, diff --git a/src/sema/ast.rs b/src/sema/ast.rs index 22f0e4d3a..3e49c6d34 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -553,6 +553,13 @@ pub enum Expression { ReturnData(pt::Loc), Builtin(pt::Loc, Vec, Builtin, Vec), + AbiEncode { + loc: pt::Loc, + tys: Vec, + selector: Option>, + packed: bool, + args: Vec, + }, List(pt::Loc, Vec), Poison, } @@ -916,6 +923,24 @@ impl Expression { Expression::Builtin(*loc, tys.clone(), *builtin, args) } + Expression::AbiEncode { + loc, + tys, + selector, + packed, + args, + } => { + let args = args.iter().map(|e| filter(e, ctx)).collect(); + let selector = selector.as_ref().map(|e| Box::new(filter(&e, ctx))); + + Expression::AbiEncode { + loc: *loc, + tys: tys.clone(), + selector, + packed: *packed, + args, + } + } _ => self.clone(), }, ctx, diff --git a/src/sema/expression.rs b/src/sema/expression.rs index 8e9f89da5..e254381b0 100644 --- a/src/sema/expression.rs +++ b/src/sema/expression.rs @@ -96,6 +96,7 @@ impl Expression { | Expression::Assign(loc, _, _, _) | Expression::List(loc, _) | Expression::FormatString(loc, _) + | Expression::AbiEncode { loc, .. } | Expression::And(loc, _, _) => *loc, Expression::InternalFunctionCfg(_) | Expression::Poison => unreachable!(), } @@ -116,7 +117,7 @@ impl Expression { | Expression::NotEqual(_, _, _) | Expression::Not(_, _) | Expression::StringCompare(_, _, _) => Type::Bool, - Expression::CodeLiteral(_, _, _) => Type::DynamicBytes, + Expression::AbiEncode { .. } | Expression::CodeLiteral(_, _, _) => Type::DynamicBytes, Expression::StringConcat(_, ty, _, _) | Expression::FunctionArg(_, ty, _) | Expression::BytesLiteral(_, ty, _) From 94e8243874686fed6194bb2764517a0a4889348a Mon Sep 17 00:00:00 2001 From: Sean Young Date: Thu, 15 Apr 2021 11:30:37 +0100 Subject: [PATCH 4/8] At least rust 1.51.0 is required Ensure CI will catch if we start using features from newer versions. Signed-off-by: Sean Young --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 4 ++-- docs/installing.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ea3384fc..b2ea8891c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: # checkout@v2 requires git 2.18 or higher, which is not in our image uses: actions/checkout@v1 - name: Rust stable - run: rustup default stable + run: rustup default 1.51.0 - name: Build run: cargo build --verbose --release - name: Run tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b48ff5085..b46e36cbf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - name: Rust stable - run: rustup default stable + run: rustup default 1.51.0 - name: Run cargo fmt run: cargo fmt --all -- --check - name: Run cargo clippy @@ -34,7 +34,7 @@ jobs: # checkout@v2 requires git 2.18 or higher, which is not in our image uses: actions/checkout@v1 - name: Rust stable - run: rustup default stable + run: rustup default 1.51.0 - name: Build run: cargo build --verbose - name: Run tests diff --git a/docs/installing.rst b/docs/installing.rst index 213c3fba1..9886ecece 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -50,7 +50,7 @@ Then you can build the image using: Building Solang from source --------------------------- -In order to build Solang from source, you will need rust 1.48.0 or higher, +In order to build Solang from source, you will need rust 1.51.0 or higher, and a build of llvm based on the Solana llvm tree. There are a few patches which are not upstream yet. First, follow the steps below for installing llvm and then proceed from there. From 933239908b5b3fc9e6c38d57e42602d522ea257a Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 14 Apr 2021 21:58:32 +0100 Subject: [PATCH 5/8] Separate ABI encoding from Instr::ExternalCall instruction Instr::ExternalCall does implicit ABI encoding for the arguments. Make Instr::ExternalCall just accept an encoded payload and do ABI encoding in an explicit expression. This has some advantages: - Makes it possible to run optimizing passes over the explicit ABI encoding expression - Make the emitting stage much simpler, and no longer aware of different external call types; it's just a single external call - ABI encoding for Solana cross contract calls are encoded slightly differently. This can now be handled during codegen. Signed-off-by: Sean Young --- src/codegen/cfg.rs | 62 ++------ src/codegen/constant_folding.rs | 6 - src/codegen/expression.rs | 62 ++++++-- src/codegen/statements.rs | 32 +++- src/codegen/strength_reduce.rs | 5 - src/emit/mod.rs | 197 ++---------------------- tests/substrate_tests/function_types.rs | 2 +- 7 files changed, 102 insertions(+), 264 deletions(-) diff --git a/src/codegen/cfg.rs b/src/codegen/cfg.rs index ee6f7806b..e489f744e 100644 --- a/src/codegen/cfg.rs +++ b/src/codegen/cfg.rs @@ -111,7 +111,6 @@ pub enum Instr { success: Option, address: Option, payload: Expression, - args: Vec, value: Expression, gas: Expression, callty: CallTy, @@ -804,55 +803,26 @@ impl ControlFlowGraph { success, address, payload, - args, value, gas, callty, } => { - if let Expression::ExternalFunction { - address, - function_no, - .. - } = payload - { - format!( - "{} = external call::{} address:{} signature:{} value:{} gas:{} {} {}", - match success { - Some(i) => format!("%{}", self.vars[i].id.name), - None => "_".to_string(), - }, - callty, - self.expr_to_string(contract, ns, address), - ns.functions[*function_no].signature, - self.expr_to_string(contract, ns, value), - self.expr_to_string(contract, ns, gas), - ns.functions[*function_no].print_name(ns), - args.iter() - .map(|expr| self.expr_to_string(contract, ns, expr)) - .collect::>() - .join(", ") - ) - } else if let Some(address) = address { - format!( - "{} = external call address:{} value:{}", - match success { - Some(i) => format!("%{}", self.vars[i].id.name), - None => "_".to_string(), - }, - self.expr_to_string(contract, ns, address), - self.expr_to_string(contract, ns, value), - ) - } else { - format!( - "{} = external call payload:{} value:{}", - match success { - Some(i) => format!("%{}", self.vars[i].id.name), - None => "_".to_string(), - }, - self.expr_to_string(contract, ns, payload), - self.expr_to_string(contract, ns, value), - ) - } + format!( + "{} = external call::{} address:{} payload:{} value:{} gas:{}", + match success { + Some(i) => format!("%{}", self.vars[i].id.name), + None => "_".to_string(), + }, + callty, + if let Some(address) = address { + self.expr_to_string(contract, ns, address) + } else { + String::new() + }, + self.expr_to_string(contract, ns, payload), + self.expr_to_string(contract, ns, value), + self.expr_to_string(contract, ns, gas), + ) } Instr::ValueTransfer { success, diff --git a/src/codegen/constant_folding.rs b/src/codegen/constant_folding.rs index 18811005a..27deb14ca 100644 --- a/src/codegen/constant_folding.rs +++ b/src/codegen/constant_folding.rs @@ -203,15 +203,10 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) { success, address, payload, - args, value, gas, callty, } => { - let args = args - .iter() - .map(|e| expression(e, Some(&vars), &cur, cfg, ns).0) - .collect(); let value = expression(value, Some(&vars), &cur, cfg, ns).0; let gas = expression(gas, Some(&vars), &cur, cfg, ns).0; let payload = expression(payload, Some(&vars), &cur, cfg, ns).0; @@ -223,7 +218,6 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) { success: *success, address, payload, - args, value, gas, callty: callty.clone(), diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 6d3dd4755..250e9fa2b 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -838,7 +838,6 @@ pub fn expression( success: Some(success), address: Some(address), payload: Expression::BytesLiteral(*loc, Type::DynamicBytes, vec![]), - args: Vec::new(), value, gas: Expression::NumberLiteral( *loc, @@ -872,7 +871,6 @@ pub fn expression( success: None, address: Some(address), payload: Expression::BytesLiteral(*loc, Type::DynamicBytes, vec![]), - args: Vec::new(), value, gas: Expression::NumberLiteral( *loc, @@ -1275,7 +1273,6 @@ pub fn emit_function_call( success: Some(success), address: Some(address), payload: args, - args: vec![], value, gas, callty: ty.clone(), @@ -1298,11 +1295,11 @@ pub fn emit_function_call( if let Expression::ExternalFunction { function_no, address, - ty, .. } = function.as_ref() { let ftype = &ns.functions[*function_no]; + let tys = args.iter().map(|a| a.ty()).collect(); let args = args .iter() .map(|a| expression(a, cfg, callee_contract_no, ns, vartab)) @@ -1311,18 +1308,32 @@ pub fn emit_function_call( let gas = expression(gas, cfg, callee_contract_no, ns, vartab); let value = expression(value, cfg, callee_contract_no, ns, vartab); + let dest_func = &ns.functions[*function_no]; + + let selector = if ns.target == Target::Substrate { + dest_func.selector().to_be() + } else { + dest_func.selector() + }; + + let payload = Expression::AbiEncode { + loc: *loc, + packed: false, + tys, + selector: Some(Box::new(Expression::NumberLiteral( + *loc, + Type::Uint(32), + BigInt::from(selector), + ))), + args, + }; + cfg.add( vartab, Instr::ExternalCall { success: None, - address: None, - payload: Expression::ExternalFunction { - function_no: *function_no, - address: Box::new(address), - loc: *loc, - ty: ty.clone(), - }, - args, + address: Some(address), + payload, value, gas, callty: CallTy::Regular, @@ -1363,21 +1374,42 @@ pub fn emit_function_call( .. } = function.ty() { + let tys = args.iter().map(|a| a.ty()).collect(); let args = args .iter() .map(|a| expression(a, cfg, callee_contract_no, ns, vartab)) .collect(); - let payload = expression(function, cfg, callee_contract_no, ns, vartab); + let function = expression(function, cfg, callee_contract_no, ns, vartab); let gas = expression(gas, cfg, callee_contract_no, ns, vartab); let value = expression(value, cfg, callee_contract_no, ns, vartab); + let selector = Expression::Builtin( + *loc, + vec![Type::Uint(32)], + Builtin::ExternalFunctionSelector, + vec![function.clone()], + ); + let address = Expression::Builtin( + *loc, + vec![Type::Address(false)], + Builtin::ExternalFunctionAddress, + vec![function], + ); + + let payload = Expression::AbiEncode { + loc: *loc, + packed: false, + tys, + selector: Some(Box::new(selector)), + args, + }; + cfg.add( vartab, Instr::ExternalCall { success: None, - address: None, + address: Some(address), payload, - args, value, gas, callty: CallTy::Regular, diff --git a/src/codegen/statements.rs b/src/codegen/statements.rs index 108312d60..a376665b5 100644 --- a/src/codegen/statements.rs +++ b/src/codegen/statements.rs @@ -5,7 +5,7 @@ use super::cfg::{ControlFlowGraph, Instr, Vartable}; use super::expression::{assign_single, emit_function_call, expression}; use crate::parser::pt; use crate::sema::ast::{ - CallTy, DestructureField, Expression, Function, Namespace, Parameter, Statement, Type, + Builtin, CallTy, DestructureField, Expression, Function, Namespace, Parameter, Statement, Type, }; use crate::sema::expression::cast; use num_traits::Zero; @@ -744,6 +744,7 @@ fn try_catch( match &fcall { Expression::ExternalFunctionCall { + loc, function, args, value, @@ -759,18 +760,41 @@ fn try_catch( let gas = expression(gas, cfg, callee_contract_no, ns, vartab); let function = expression(function, cfg, callee_contract_no, ns, vartab); + let tys = args.iter().map(|a| a.ty()).collect(); + let args = args .iter() .map(|a| expression(a, cfg, callee_contract_no, ns, vartab)) .collect(); + let selector = Expression::Builtin( + *loc, + vec![Type::Uint(32)], + Builtin::ExternalFunctionSelector, + vec![function.clone()], + ); + + let address = Expression::Builtin( + *loc, + vec![Type::Address(false)], + Builtin::ExternalFunctionAddress, + vec![function], + ); + + let payload = Expression::AbiEncode { + loc: *loc, + packed: false, + tys, + selector: Some(Box::new(selector)), + args, + }; + cfg.add( vartab, Instr::ExternalCall { success: Some(success), - address: None, - payload: function, - args, + address: Some(address), + payload, value, gas, callty: CallTy::Regular, diff --git a/src/codegen/strength_reduce.rs b/src/codegen/strength_reduce.rs index c64149a0f..86e07cd0b 100644 --- a/src/codegen/strength_reduce.rs +++ b/src/codegen/strength_reduce.rs @@ -159,17 +159,12 @@ fn block_reduce( *gas = expression_reduce(gas, &vars, ns); } Instr::ExternalCall { - args, address, payload, value, gas, .. } => { - *args = args - .iter() - .map(|e| expression_reduce(e, &vars, ns)) - .collect(); *value = expression_reduce(value, &vars, ns); if let Some(address) = address { *address = expression_reduce(address, &vars, ns); diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 81df1924c..185dccec0 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -2856,7 +2856,11 @@ pub trait TargetRuntime<'a> { .expression(contract, address, vartab, function) .into_int_value(); - let selector = contract.ns.functions[*function_no].selector(); + let mut selector = contract.ns.functions[*function_no].selector(); + + if contract.ns.target == Target::Substrate { + selector = selector.to_be(); + } assert!(matches!(ty, ast::Type::ExternalFunction { .. })); @@ -3861,200 +3865,19 @@ pub trait TargetRuntime<'a> { success, address, payload, - args, value, gas, callty, } => { - let (payload, payload_len, address) = match payload.ty() { - ast::Type::ExternalFunction { params, .. } => { - if let ast::Expression::ExternalFunction { - address, - function_no, - .. - } = payload - { - let dest_func = &contract.ns.functions[*function_no]; - - let selector = if contract.ns.target == Target::Ewasm { - dest_func.selector().to_le() - } else { - dest_func.selector() - }; - - let selector = contract - .context - .i32_type() - .const_int(selector as u64, false); - - let tys: Vec = - dest_func.params.iter().map(|p| p.ty.clone()).collect(); - - let (payload, payload_len) = self.abi_encode( - contract, - Some(selector), - false, - function, - &args - .iter() - .map(|a| { - self.expression(contract, &a, &w.vars, function) - }) - .collect::>(), - &tys, - ); - - let address = self - .expression(contract, address, &w.vars, function) - .into_int_value(); - - (payload, payload_len, address) - } else { - // load selector from function expression - let ft = self - .expression(contract, payload, &w.vars, function) - .into_pointer_value(); - - let selector_member = unsafe { - contract.builder.build_gep( - ft, - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_int(1, false), - ], - "selector", - ) - }; - - let selector = contract - .builder - .build_load(selector_member, "selector") - .into_int_value(); - - // we don't know the names of the parameters any more - let params = params - .iter() - .map(|ty| ast::Parameter { - ty: ty.clone(), - name: String::new(), - ty_loc: pt::Loc(0, 0, 0), - name_loc: None, - loc: pt::Loc(0, 0, 0), - indexed: false, - }) - .collect::>(); - - let tys: Vec = - params.iter().map(|p| p.ty.clone()).collect(); - - let (payload, payload_len) = self.abi_encode( - contract, - Some(selector), - false, - function, - &args - .iter() - .map(|a| { - self.expression(contract, &a, &w.vars, function) - }) - .collect::>(), - &tys, - ); - - let address_member = unsafe { - contract.builder.build_gep( - ft, - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_zero(), - ], - "address", - ) - }; - - let address = contract - .builder - .build_load(address_member, "address") - .into_int_value(); - - (payload, payload_len, address) - } - } - ast::Type::DynamicBytes => { - let address = self - .expression( - contract, - address.as_ref().unwrap(), - &w.vars, - function, - ) - .into_int_value(); - - if let ast::Expression::BytesLiteral(_, _, bs) = payload { - assert_eq!(bs.len(), 0); - - ( - contract - .context - .i8_type() - .ptr_type(AddressSpace::Generic) - .const_null(), - contract.context.i32_type().const_zero(), - address, - ) - } else { - let raw = self - .expression(contract, payload, &w.vars, function) - .into_pointer_value(); - - let data = unsafe { - contract.builder.build_gep( - raw, - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_int(2, false), - ], - "rawdata", - ) - }; - - let data_len = unsafe { - contract.builder.build_gep( - raw, - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_zero(), - ], - "rawdata_len", - ) - }; - - ( - contract.builder.build_pointer_cast( - data, - contract - .context - .i8_type() - .ptr_type(AddressSpace::Generic), - "data", - ), - contract - .builder - .build_load(data_len, "data_len") - .into_int_value(), - address, - ) - } - } - _ => unreachable!(), - }; - let gas = self .expression(contract, gas, &w.vars, function) .into_int_value(); let value = self .expression(contract, value, &w.vars, function) .into_int_value(); + let payload = self.expression(contract, payload, &w.vars, function); + let address = + self.expression(contract, address.as_ref().unwrap(), &w.vars, function); let addr = contract.builder.build_array_alloca( contract.context.i8_type(), @@ -4083,8 +3906,8 @@ pub trait TargetRuntime<'a> { contract, function, success, - payload, - payload_len, + contract.vector_bytes(payload), + contract.vector_len(payload), addr, gas, value, diff --git a/tests/substrate_tests/function_types.rs b/tests/substrate_tests/function_types.rs index cf98f1961..5c530a29c 100644 --- a/tests/substrate_tests/function_types.rs +++ b/tests/substrate_tests/function_types.rs @@ -482,7 +482,7 @@ fn ext() { function(int32) external returns (bool) func = this.foo; assert(address(this) == func.address); - assert(func.selector == 0x37117642); + assert(func.selector == 0x42761137); } function foo(int32) public returns (bool) { From 988cc075ccbf90dc597738e4e7fffb471fdae155 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Thu, 15 Apr 2021 16:59:05 +0100 Subject: [PATCH 6/8] Implement packed abi eth encoding This is used for the builtin `bytes foo = abi.encodePacked("xyz", true);`, and we want to re-use it to pack the instruction for Solana cross program calls. Note this fixes various endian problem with abi encoding, scale codec or eth abi. The function selector should now have the correct type `bytes4` rather than `uint32`. Signed-off-by: Sean Young --- docs/targets.rst | 1 - src/abi/substrate.rs | 2 +- src/codegen/cfg.rs | 19 +- src/codegen/constant_folding.rs | 12 +- src/codegen/expression.rs | 52 +- src/codegen/statements.rs | 9 +- src/emit/ethabiencoder.rs | 1137 ++++++++++++++++++----- src/emit/ewasm.rs | 25 +- src/emit/generic.rs | 24 +- src/emit/mod.rs | 41 +- src/emit/sabre.rs | 14 +- src/emit/solana.rs | 18 +- src/emit/substrate.rs | 83 +- src/sema/ast.rs | 11 +- src/sema/contracts.rs | 8 +- src/sema/expression.rs | 2 +- tests/ewasm.rs | 11 + tests/ewasm_tests/abi.rs | 2 +- tests/solana_tests/abi.rs | 43 + tests/solana_tests/mod.rs | 1 + tests/substrate_tests/function_types.rs | 2 +- 21 files changed, 1115 insertions(+), 402 deletions(-) create mode 100644 tests/solana_tests/abi.rs diff --git a/docs/targets.rst b/docs/targets.rst index 0813b7058..178082ff4 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -90,7 +90,6 @@ Please use the latest master version of burrow, as ewasm support is still maturi Some language features have not been fully implemented yet on ewasm: -- The built in function ``abi.encodePacked()`` - Contract storage variables types ``string``, ``bytes`` and function types are not implemented Hyperledger Sawtooth diff --git a/src/abi/substrate.rs b/src/abi/substrate.rs index 672aedbff..6aa29af35 100644 --- a/src/abi/substrate.rs +++ b/src/abi/substrate.rs @@ -642,7 +642,7 @@ fn parameter_to_abi(param: &ast::Parameter, ns: &ast::Namespace, registry: &mut /// Given an u32 selector, generate a byte string like: 0xF81E7E1A fn render_selector(f: &ast::Function) -> String { - format!("0x{}", hex::encode(f.selector().to_le_bytes())) + format!("0x{}", hex::encode(f.selector().to_be_bytes())) } /// Given a selector like "0xF81E7E1A", parse the bytes. This function diff --git a/src/codegen/cfg.rs b/src/codegen/cfg.rs index e489f744e..1ee434356 100644 --- a/src/codegen/cfg.rs +++ b/src/codegen/cfg.rs @@ -633,18 +633,13 @@ impl ControlFlowGraph { .collect::>() .join(", ") ), - Expression::AbiEncode { - selector, - packed, - args, - .. - } => format!( - "(abiencode{}:(%{} {}))", - if *packed { "packed" } else { "" }, - match selector { - None => "".to_string(), - Some(expr) => self.expr_to_string(contract, ns, expr), - }, + Expression::AbiEncode { packed, args, .. } => format!( + "(abiencode packed:{} non-packed:{})", + packed + .iter() + .map(|expr| self.expr_to_string(contract, ns, expr)) + .collect::>() + .join(", "), args.iter() .map(|expr| self.expr_to_string(contract, ns, expr)) .collect::>() diff --git a/src/codegen/constant_folding.rs b/src/codegen/constant_folding.rs index 27deb14ca..485f15b98 100644 --- a/src/codegen/constant_folding.rs +++ b/src/codegen/constant_folding.rs @@ -1026,24 +1026,22 @@ fn expression( loc, tys, packed, - selector, args, } => { + let packed = packed + .iter() + .map(|expr| expression(expr, vars, pos, cfg, ns).0) + .collect(); let args = args .iter() .map(|expr| expression(expr, vars, pos, cfg, ns).0) .collect(); - let selector = selector - .as_ref() - .map(|expr| Box::new(expression(&expr, vars, pos, cfg, ns).0)); - ( Expression::AbiEncode { loc: *loc, tys: tys.clone(), - packed: *packed, - selector, + packed, args, }, false, diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 250e9fa2b..307f1ca5d 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -431,7 +431,7 @@ pub fn expression( if let Expression::ExternalFunction { function_no, .. } = &func[0] { let selector = ns.functions[*function_no].selector(); - Expression::NumberLiteral(*loc, Type::Uint(32), BigInt::from(selector)) + Expression::NumberLiteral(*loc, Type::Bytes(4), BigInt::from(selector)) } else { let func = expression(&func[0], cfg, contract_no, ns, vartab); @@ -907,8 +907,7 @@ pub fn expression( expr: Expression::AbiEncode { loc: *loc, tys, - selector: None, - packed: false, + packed: vec![], args, }, }, @@ -918,7 +917,7 @@ pub fn expression( } Expression::Builtin(loc, _, Builtin::AbiEncodePacked, args) => { let tys = args.iter().map(|a| a.ty()).collect(); - let args = args + let packed = args .iter() .map(|v| expression(&v, cfg, contract_no, ns, vartab)) .collect(); @@ -939,9 +938,8 @@ pub fn expression( expr: Expression::AbiEncode { loc: *loc, tys, - selector: None, - packed: true, - args, + packed, + args: vec![], }, }, ); @@ -949,7 +947,7 @@ pub fn expression( Expression::Variable(*loc, Type::DynamicBytes, res) } Expression::Builtin(loc, _, Builtin::AbiEncodeWithSelector, args) => { - let tys = args.iter().skip(1).map(|a| a.ty()).collect(); + let mut tys: Vec = args.iter().skip(1).map(|a| a.ty()).collect(); // first argument is selector let mut args_iter = args.iter(); let selector = expression(&args_iter.next().unwrap(), cfg, contract_no, ns, vartab); @@ -965,6 +963,8 @@ pub fn expression( &Type::DynamicBytes, ); + tys.insert(0, Type::Bytes(4)); + cfg.add( vartab, Instr::Set { @@ -973,8 +973,7 @@ pub fn expression( expr: Expression::AbiEncode { loc: *loc, tys, - selector: Some(Box::new(selector)), - packed: false, + packed: vec![selector], args, }, }, @@ -983,7 +982,7 @@ pub fn expression( Expression::Variable(*loc, Type::DynamicBytes, res) } Expression::Builtin(loc, _, Builtin::AbiEncodeWithSignature, args) => { - let tys = args.iter().skip(1).map(|a| a.ty()).collect(); + let mut tys: Vec = args.iter().skip(1).map(|a| a.ty()).collect(); // first argument is signature which needs hashing and shifting let mut args_iter = args.iter(); let hash = Expression::Builtin( @@ -1006,6 +1005,8 @@ pub fn expression( &Type::DynamicBytes, ); + tys.insert(0, Type::Bytes(4)); + cfg.add( vartab, Instr::Set { @@ -1014,8 +1015,7 @@ pub fn expression( expr: Expression::AbiEncode { loc: *loc, tys, - selector: Some(Box::new(selector)), - packed: false, + packed: vec![selector], args, }, }, @@ -1299,7 +1299,7 @@ pub fn emit_function_call( } = function.as_ref() { let ftype = &ns.functions[*function_no]; - let tys = args.iter().map(|a| a.ty()).collect(); + let mut tys: Vec = args.iter().map(|a| a.ty()).collect(); let args = args .iter() .map(|a| expression(a, cfg, callee_contract_no, ns, vartab)) @@ -1310,21 +1310,16 @@ pub fn emit_function_call( let dest_func = &ns.functions[*function_no]; - let selector = if ns.target == Target::Substrate { - dest_func.selector().to_be() - } else { - dest_func.selector() - }; + tys.insert(0, Type::Bytes(4)); let payload = Expression::AbiEncode { loc: *loc, - packed: false, tys, - selector: Some(Box::new(Expression::NumberLiteral( + packed: vec![Expression::NumberLiteral( *loc, - Type::Uint(32), - BigInt::from(selector), - ))), + Type::Bytes(4), + BigInt::from(dest_func.selector()), + )], args, }; @@ -1374,7 +1369,7 @@ pub fn emit_function_call( .. } = function.ty() { - let tys = args.iter().map(|a| a.ty()).collect(); + let mut tys: Vec = args.iter().map(|a| a.ty()).collect(); let args = args .iter() .map(|a| expression(a, cfg, callee_contract_no, ns, vartab)) @@ -1385,7 +1380,7 @@ pub fn emit_function_call( let selector = Expression::Builtin( *loc, - vec![Type::Uint(32)], + vec![Type::Bytes(4)], Builtin::ExternalFunctionSelector, vec![function.clone()], ); @@ -1396,11 +1391,12 @@ pub fn emit_function_call( vec![function], ); + tys.insert(0, Type::Bytes(4)); + let payload = Expression::AbiEncode { loc: *loc, - packed: false, tys, - selector: Some(Box::new(selector)), + packed: vec![selector], args, }; diff --git a/src/codegen/statements.rs b/src/codegen/statements.rs index a376665b5..03f11b186 100644 --- a/src/codegen/statements.rs +++ b/src/codegen/statements.rs @@ -760,7 +760,9 @@ fn try_catch( let gas = expression(gas, cfg, callee_contract_no, ns, vartab); let function = expression(function, cfg, callee_contract_no, ns, vartab); - let tys = args.iter().map(|a| a.ty()).collect(); + let mut tys: Vec = args.iter().map(|a| a.ty()).collect(); + + tys.insert(0, Type::Bytes(4)); let args = args .iter() @@ -769,7 +771,7 @@ fn try_catch( let selector = Expression::Builtin( *loc, - vec![Type::Uint(32)], + vec![Type::Bytes(4)], Builtin::ExternalFunctionSelector, vec![function.clone()], ); @@ -783,9 +785,8 @@ fn try_catch( let payload = Expression::AbiEncode { loc: *loc, - packed: false, tys, - selector: Some(Box::new(selector)), + packed: vec![selector], args, }; diff --git a/src/emit/ethabiencoder.rs b/src/emit/ethabiencoder.rs index bccd411a6..e38d576d6 100644 --- a/src/emit/ethabiencoder.rs +++ b/src/emit/ethabiencoder.rs @@ -16,9 +16,9 @@ use super::{Contract, ReturnCode}; pub struct EncoderBuilder<'a, 'b> { length: IntValue<'a>, offset: IntValue<'a>, - selector: Option>, - args: &'b [BasicValueEnum<'a>], load_args: bool, + packed: &'b [BasicValueEnum<'a>], + args: &'b [BasicValueEnum<'a>], tys: &'b [ast::Type], bswap: bool, } @@ -28,14 +28,19 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { pub fn new( contract: &Contract<'a>, function: FunctionValue, - selector: Option>, load_args: bool, + packed: &'b [BasicValueEnum<'a>], args: &'b [BasicValueEnum<'a>], tys: &'b [ast::Type], bswap: bool, ) -> Self { + debug_assert_eq!(packed.len() + args.len(), tys.len()); + + let args_tys = &tys[packed.len()..]; + let offset = contract.context.i32_type().const_int( - tys.iter() + args_tys + .iter() .map(|ty| EncoderBuilder::encoded_fixed_length(ty, contract.ns)) .sum(), false, @@ -43,22 +48,26 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { let mut length = offset; - // now add the dynamic lengths - for (i, ty) in tys.iter().enumerate() { + // calculate the packed length + for (i, arg) in packed.iter().enumerate() { length = contract.builder.build_int_add( length, - EncoderBuilder::encoded_dynamic_length(args[i], load_args, ty, function, contract), + EncoderBuilder::encoded_packed_length(*arg, load_args, &tys[i], function, contract), "", ); } - if selector.is_some() { + // now add the dynamic lengths + for (i, arg) in args.iter().enumerate() { length = contract.builder.build_int_add( length, - contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false), + EncoderBuilder::encoded_dynamic_length( + *arg, + load_args, + &args_tys[i], + function, + contract, + ), "", ); } @@ -66,9 +75,9 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { EncoderBuilder { length, offset, - selector, - args, load_args, + packed, + args, tys, bswap, } @@ -79,6 +88,242 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { self.length } + /// Return the amount of fixed and dynamic storage required to store a type + fn encoded_packed_length<'c>( + arg: BasicValueEnum<'c>, + load: bool, + ty: &ast::Type, + function: FunctionValue, + contract: &Contract<'c>, + ) -> IntValue<'c> { + match ty { + ast::Type::Struct(n) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + let normal_struct = contract + .context + .append_basic_block(function, "normal_struct"); + let null_struct = contract.context.append_basic_block(function, "null_struct"); + let done_struct = contract.context.append_basic_block(function, "done_struct"); + + let is_null = contract + .builder + .build_is_null(arg.into_pointer_value(), "is_null"); + + contract + .builder + .build_conditional_branch(is_null, null_struct, normal_struct); + + contract.builder.position_at_end(normal_struct); + + let mut normal_sum = contract.context.i32_type().const_zero(); + + for (i, field) in contract.ns.structs[*n].fields.iter().enumerate() { + let elem = unsafe { + contract.builder.build_gep( + arg.into_pointer_value(), + &[ + contract.context.i32_type().const_zero(), + contract.context.i32_type().const_int(i as u64, false), + ], + &field.name, + ) + }; + + let len = EncoderBuilder::encoded_packed_length( + elem.into(), + true, + &field.ty, + function, + contract, + ); + + normal_sum = contract.builder.build_int_add(normal_sum, len, ""); + } + + contract.builder.build_unconditional_branch(done_struct); + + let normal_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(null_struct); + + let mut null_sum = contract.context.i32_type().const_zero(); + + for field in &contract.ns.structs[*n].fields { + null_sum = contract.builder.build_int_add( + null_sum, + EncoderBuilder::encoded_packed_length( + contract.default_value(&field.ty), + false, + &field.ty, + function, + contract, + ), + "", + ); + } + + contract.builder.build_unconditional_branch(done_struct); + + let null_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(done_struct); + + let sum = contract + .builder + .build_phi(contract.context.i32_type(), "sum"); + + sum.add_incoming(&[(&normal_sum, normal_struct), (&null_sum, null_struct)]); + + sum.as_basic_value().into_int_value() + } + ast::Type::Array(elem_ty, dims) if elem_ty.is_dynamic(contract.ns) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + let sum = contract.context.i32_type().const_zero(); + + let len = match dims.last().unwrap() { + None => contract.vector_len(arg), + Some(d) => contract + .context + .i32_type() + .const_int(d.to_u64().unwrap(), false), + }; + + let normal_array = contract + .context + .append_basic_block(function, "normal_array"); + let null_array = contract.context.append_basic_block(function, "null_array"); + let done_array = contract.context.append_basic_block(function, "done_array"); + + let is_null = contract + .builder + .build_is_null(arg.into_pointer_value(), "is_null"); + + contract + .builder + .build_conditional_branch(is_null, null_array, normal_array); + + contract.builder.position_at_end(normal_array); + + let mut normal_length = sum; + + contract.builder.position_at_end(normal_array); + + // the element of the array are dynamic; we need to iterate over the array to find the encoded length + contract.emit_loop_cond_first_with_int( + function, + contract.context.i32_type().const_zero(), + len, + &mut normal_length, + |index, sum| { + let elem = contract.array_subscript(ty, arg.into_pointer_value(), index); + + *sum = contract.builder.build_int_add( + EncoderBuilder::encoded_packed_length( + elem.into(), + true, + &elem_ty, + function, + contract, + ), + *sum, + "", + ); + }, + ); + + contract.builder.build_unconditional_branch(done_array); + + let normal_array = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(null_array); + + let elem = contract.default_value(&elem_ty.deref_any()); + + let null_length = contract.builder.build_int_add( + contract.builder.build_int_mul( + EncoderBuilder::encoded_packed_length( + elem, false, elem_ty, function, contract, + ), + len, + "", + ), + sum, + "", + ); + + contract.builder.build_unconditional_branch(done_array); + + let null_array = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(done_array); + + let encoded_length = contract + .builder + .build_phi(contract.context.i32_type(), "encoded_length"); + + encoded_length + .add_incoming(&[(&normal_length, normal_array), (&null_length, null_array)]); + + encoded_length.as_basic_value().into_int_value() + } + ast::Type::Array(elem_ty, dims) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + let len = match dims.last().unwrap() { + None => contract.vector_len(arg), + Some(d) => contract + .context + .i32_type() + .const_int(d.to_u64().unwrap(), false), + }; + + // plus fixed size elements + contract.builder.build_int_mul( + len, + EncoderBuilder::encoded_packed_length(arg, false, &elem_ty, function, contract), + "", + ) + } + ast::Type::String | ast::Type::DynamicBytes => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + contract.vector_len(arg) + } + ast::Type::Uint(n) | ast::Type::Int(n) => contract + .context + .i32_type() + .const_int((*n as u64) / 8, false), + ast::Type::Bytes(n) => contract.context.i32_type().const_int(*n as u64, false), + ast::Type::Enum(_) | ast::Type::Bool => contract.context.i32_type().const_int(1, false), + ast::Type::Contract(_) | ast::Type::Address(_) => contract + .context + .i32_type() + .const_int(contract.ns.address_length as u64, false), + ast::Type::Ref(ty) => { + EncoderBuilder::encoded_packed_length(arg, false, ty, function, contract) + } + _ => unreachable!(), + } + } + /// Return the amount of fixed and dynamic storage required to store a type fn encoded_dynamic_length<'c>( arg: BasicValueEnum<'c>, @@ -387,27 +632,12 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { output: PointerValue<'a>, ) { let mut output = output; + let mut ty_iter = self.tys.iter(); - if let Some(selector) = self.selector { - contract.builder.build_store( - contract.builder.build_pointer_cast( - output, - contract.context.i32_type().ptr_type(AddressSpace::Generic), - "", - ), - selector, - ); + for arg in self.packed.iter() { + let ty = ty_iter.next().unwrap(); - output = unsafe { - contract.builder.build_gep( - output, - &[contract - .context - .i32_type() - .const_int(std::mem::size_of::() as u64, false)], - "", - ) - }; + self.encode_packed_ty(contract, self.load_args, function, ty, *arg, &mut output); } // We use a little trick here. The length might or might not include the selector. @@ -433,13 +663,15 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { let mut offset = self.offset; let mut dynamic = unsafe { contract.builder.build_gep(output, &[self.offset], "") }; - for (i, ty) in self.tys.iter().enumerate() { + for arg in self.args.iter() { + let ty = ty_iter.next().unwrap(); + self.encode_ty( contract, self.load_args, function, ty, - self.args[i], + *arg, &mut output, &mut offset, &mut dynamic, @@ -975,22 +1207,566 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { .map(|f| EncoderBuilder::encoded_fixed_length(&f.ty, contract.ns)) .sum(); - *dynamic = unsafe { - contract.builder.build_gep( - *dynamic, - &[contract - .context - .i32_type() - .const_int(fixed_field_length, false)], - "", - ) + *dynamic = unsafe { + contract.builder.build_gep( + *dynamic, + &[contract + .context + .i32_type() + .const_int(fixed_field_length, false)], + "", + ) + }; + + let null_struct = contract.context.append_basic_block(function, "null_struct"); + let normal_struct = contract + .context + .append_basic_block(function, "normal_struct"); + let done_struct = contract.context.append_basic_block(function, "done_struct"); + + let is_null = contract + .builder + .build_is_null(arg.into_pointer_value(), "is_null"); + + contract + .builder + .build_conditional_branch(is_null, null_struct, normal_struct); + + let mut normal_dynamic = *dynamic; + let mut null_dynamic = *dynamic; + let normal_offset = *offset; + let null_offset = *offset; + + contract.builder.position_at_end(normal_struct); + + let mut temp_offset = contract + .context + .i32_type() + .const_int(fixed_field_length, false); + + for (i, field) in contract.ns.structs[*n].fields.iter().enumerate() { + let elem = unsafe { + contract.builder.build_gep( + arg.into_pointer_value(), + &[ + contract.context.i32_type().const_zero(), + contract.context.i32_type().const_int(i as u64, false), + ], + &field.name, + ) + }; + + self.encode_ty( + contract, + true, + function, + &field.ty, + elem.into(), + &mut normal_fields_dynamic, + &mut temp_offset, + &mut normal_dynamic, + ); + } + + let normal_offset = contract + .builder + .build_int_add(normal_offset, temp_offset, ""); + + contract.builder.build_unconditional_branch(done_struct); + + let normal_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(null_struct); + + let mut temp_offset = contract + .context + .i32_type() + .const_int(fixed_field_length, false); + + for field in &contract.ns.structs[*n].fields { + let elem = contract.default_value(&field.ty); + + self.encode_ty( + contract, + false, + function, + &field.ty, + elem, + &mut null_fields_dynamic, + &mut temp_offset, + &mut null_dynamic, + ); + } + + let null_offset = contract.builder.build_int_add(null_offset, temp_offset, ""); + + contract.builder.build_unconditional_branch(done_struct); + + let null_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(done_struct); + + let dynamic_phi = contract.builder.build_phi( + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "dynamic", + ); + + dynamic_phi.add_incoming(&[ + (&normal_dynamic, normal_struct), + (&null_dynamic, null_struct), + ]); + + *dynamic = dynamic_phi.as_basic_value().into_pointer_value(); + + let offset_phi = contract + .builder + .build_phi(contract.context.i32_type(), "offset"); + + offset_phi + .add_incoming(&[(&normal_offset, normal_struct), (&null_offset, null_struct)]); + + *offset = offset_phi.as_basic_value().into_int_value(); + } + ast::Type::Struct(n) => { + let arg = if load { + contract + .builder + .build_load(arg.into_pointer_value(), "") + .into_pointer_value() + } else { + arg.into_pointer_value() + }; + + let null_struct = contract.context.append_basic_block(function, "null_struct"); + let normal_struct = contract + .context + .append_basic_block(function, "normal_struct"); + let done_struct = contract.context.append_basic_block(function, "done_struct"); + + let is_null = contract.builder.build_is_null(arg, "is_null"); + + contract + .builder + .build_conditional_branch(is_null, null_struct, normal_struct); + + contract.builder.position_at_end(normal_struct); + + let mut normal_fixed = *fixed; + let mut normal_offset = *offset; + let mut normal_dynamic = *dynamic; + + for (i, field) in contract.ns.structs[*n].fields.iter().enumerate() { + let elem = unsafe { + contract.builder.build_gep( + arg, + &[ + contract.context.i32_type().const_zero(), + contract.context.i32_type().const_int(i as u64, false), + ], + &field.name, + ) + }; + + self.encode_ty( + contract, + true, + function, + &field.ty, + elem.into(), + &mut normal_fixed, + &mut normal_offset, + &mut normal_dynamic, + ); + } + + contract.builder.build_unconditional_branch(done_struct); + + let normal_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(null_struct); + + let mut null_fixed = *fixed; + let mut null_offset = *offset; + let mut null_dynamic = *dynamic; + + // FIXME: abi encoding fixed length fields with default values. This should always be 0 + for field in &contract.ns.structs[*n].fields { + let elem = contract.default_value(&field.ty); + + self.encode_ty( + contract, + false, + function, + &field.ty, + elem, + &mut null_fixed, + &mut null_offset, + &mut null_dynamic, + ); + } + + contract.builder.build_unconditional_branch(done_struct); + + let null_struct = contract.builder.get_insert_block().unwrap(); + + contract.builder.position_at_end(done_struct); + + let fixed_phi = contract.builder.build_phi( + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "fixed", + ); + + fixed_phi + .add_incoming(&[(&normal_fixed, normal_struct), (&null_fixed, null_struct)]); + + *fixed = fixed_phi.as_basic_value().into_pointer_value(); + + let dynamic_phi = contract.builder.build_phi( + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "dynamic", + ); + + dynamic_phi.add_incoming(&[ + (&normal_dynamic, normal_struct), + (&null_dynamic, null_struct), + ]); + + *dynamic = dynamic_phi.as_basic_value().into_pointer_value(); + + let offset_phi = contract + .builder + .build_phi(contract.context.i32_type(), "offset"); + + offset_phi + .add_incoming(&[(&normal_offset, normal_struct), (&null_offset, null_struct)]); + + *offset = offset_phi.as_basic_value().into_int_value(); + } + ast::Type::Ref(ty) => { + self.encode_ty(contract, load, function, ty, arg, fixed, offset, dynamic); + } + ast::Type::String | ast::Type::DynamicBytes => { + // write the current offset to fixed + self.encode_primitive( + contract, + false, + function, + &ast::Type::Uint(32), + *fixed, + (*offset).into(), + ); + + *fixed = unsafe { + contract.builder.build_gep( + *fixed, + &[contract.context.i32_type().const_int(32, false)], + "", + ) + }; + + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + let len = contract.vector_len(arg); + + // write the length to dynamic + self.encode_primitive( + contract, + false, + function, + &ast::Type::Uint(32), + *dynamic, + len.into(), + ); + + *dynamic = unsafe { + contract.builder.build_gep( + *dynamic, + &[contract.context.i32_type().const_int(32, false)], + "", + ) + }; + + *offset = contract.builder.build_int_add( + *offset, + contract.context.i32_type().const_int(32, false), + "", + ); + + // now copy the string data + let string_start = contract.vector_bytes(arg); + + contract.builder.build_call( + contract.module.get_function("__memcpy").unwrap(), + &[ + contract + .builder + .build_pointer_cast( + *dynamic, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "encoded_string", + ) + .into(), + contract + .builder + .build_pointer_cast( + string_start, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "string_start", + ) + .into(), + len.into(), + ], + "", + ); + + // round up the length to the next 32 bytes block + let len = contract.builder.build_and( + contract.builder.build_int_add( + len, + contract.context.i32_type().const_int(31, false), + "", + ), + contract.context.i32_type().const_int(!31, false), + "", + ); + + *dynamic = unsafe { contract.builder.build_gep(*dynamic, &[len], "") }; + + *offset = contract.builder.build_int_add(*offset, len, ""); + } + _ => unreachable!(), + }; + } + + /// Recursively encode a value in arg. The load argument specifies if the arg is a pointer + /// to the value, or the value itself. The fixed pointer points to the fixed, non-dynamic part + /// of the encoded data. The offset is current offset for dynamic fields. + fn encode_packed_ty( + &self, + contract: &Contract<'a>, + load: bool, + function: FunctionValue<'a>, + ty: &ast::Type, + arg: BasicValueEnum<'a>, + output: &mut PointerValue<'a>, + ) { + match &ty { + ast::Type::Bool => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + let value = contract.builder.build_select( + arg.into_int_value(), + contract.context.i8_type().const_int(1, false), + contract.context.i8_type().const_zero(), + "bool_val", + ); + + contract.builder.build_store(*output, value); + + *output = unsafe { + contract.builder.build_gep( + *output, + &[contract.context.i32_type().const_int(1, false)], + "", + ) + }; + } + ast::Type::Bytes(1) | ast::Type::Int(8) | ast::Type::Uint(8) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + contract.builder.build_store(*output, arg.into_int_value()); + + *output = unsafe { + contract.builder.build_gep( + *output, + &[contract.context.i32_type().const_int(1, false)], + "", + ) + }; + } + ast::Type::Uint(n) | ast::Type::Int(n) + if self.bswap && (*n == 16 || *n == 32 || *n == 64) => + { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + // now convert to be + let bswap = contract.llvm_bswap(*n as u32); + + let val = contract + .builder + .build_call(bswap, &[arg], "") + .try_as_basic_value() + .left() + .unwrap() + .into_int_value(); + + contract.builder.build_store( + contract.builder.build_pointer_cast( + *output, + val.get_type().ptr_type(AddressSpace::Generic), + "", + ), + val, + ); + + *output = unsafe { + contract.builder.build_gep( + *output, + &[contract.context.i32_type().const_int(*n as u64 / 8, false)], + "", + ) + }; + } + ast::Type::Contract(_) + | ast::Type::Address(_) + | ast::Type::Uint(_) + | ast::Type::Int(_) + if load => + { + let n = match ty { + ast::Type::Contract(_) | ast::Type::Address(_) => { + contract.ns.address_length as u16 * 8 + } + ast::Type::Uint(b) => *b, + ast::Type::Int(b) => *b, + _ => unreachable!(), + }; + + let arg8 = contract.builder.build_pointer_cast( + arg.into_pointer_value(), + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "arg8", + ); + + let len = contract.context.i32_type().const_int(n as u64 / 8, false); + + contract.builder.build_call( + contract.module.get_function("__leNtobeN").unwrap(), + &[arg8.into(), (*output).into(), len.into()], + "", + ); + + *output = unsafe { contract.builder.build_gep(*output, &[len], "") }; + } + ast::Type::Contract(_) + | ast::Type::Address(_) + | ast::Type::Uint(_) + | ast::Type::Int(_) + if !load => + { + let n = match ty { + ast::Type::Contract(_) | ast::Type::Address(_) => { + contract.ns.address_length as u16 * 8 + } + ast::Type::Uint(b) => *b, + ast::Type::Int(b) => *b, + _ => unreachable!(), + }; + + let temp = contract.build_alloca( + function, + arg.into_int_value().get_type(), + &format!("uint{}", n), + ); + + contract.builder.build_store(temp, arg.into_int_value()); + + let len = contract.context.i32_type().const_int(n as u64 / 8, false); + + contract.builder.build_call( + contract.module.get_function("__leNtobeN").unwrap(), + &[ + contract + .builder + .build_pointer_cast( + temp, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "store", + ) + .into(), + (*output).into(), + len.into(), + ], + "", + ); + + *output = unsafe { contract.builder.build_gep(*output, &[len], "") }; + } + ast::Type::Bytes(n) => { + let val = if load { + arg.into_pointer_value() + } else { + let temp = contract.build_alloca( + function, + arg.into_int_value().get_type(), + &format!("bytes{}", n), + ); + + contract.builder.build_store(temp, arg.into_int_value()); + + temp + }; + + let len = contract.context.i32_type().const_int(*n as u64, false); + + contract.builder.build_call( + contract.module.get_function("__leNtobeN").unwrap(), + &[ + contract + .builder + .build_pointer_cast( + val, + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "store", + ) + .into(), + (*output).into(), + len.into(), + ], + "", + ); + + *output = unsafe { contract.builder.build_gep(*output, &[len], "") }; + } + ast::Type::Array(elem_ty, dim) => { + let arg = if load { + contract.builder.build_load(arg.into_pointer_value(), "") + } else { + arg + }; + + let array_length = if let Some(d) = &dim[0] { + // fixed length + contract + .context + .i32_type() + .const_int(d.to_u64().unwrap(), false) + } else { + // Now, write the length to dynamic + contract.vector_len(arg) }; - let null_struct = contract.context.append_basic_block(function, "null_struct"); - let normal_struct = contract + let normal_array = contract .context - .append_basic_block(function, "normal_struct"); - let done_struct = contract.context.append_basic_block(function, "done_struct"); + .append_basic_block(function, "normal_array"); + let null_array = contract.context.append_basic_block(function, "null_array"); + let done_array = contract.context.append_basic_block(function, "done_array"); let is_null = contract .builder @@ -998,102 +1774,100 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { contract .builder - .build_conditional_branch(is_null, null_struct, normal_struct); + .build_conditional_branch(is_null, null_array, normal_array); - let mut normal_dynamic = *dynamic; - let mut null_dynamic = *dynamic; - let normal_offset = *offset; - let null_offset = *offset; + contract.builder.position_at_end(normal_array); - contract.builder.position_at_end(normal_struct); + let mut builder = LoopBuilder::new(contract, function); - let mut temp_offset = contract - .context - .i32_type() - .const_int(fixed_field_length, false); + let mut normal_output = builder + .add_loop_phi( + contract, + "output", + contract.context.i8_type().ptr_type(AddressSpace::Generic), + (*output).into(), + ) + .into_pointer_value(); - for (i, field) in contract.ns.structs[*n].fields.iter().enumerate() { - let elem = unsafe { - contract.builder.build_gep( - arg.into_pointer_value(), - &[ - contract.context.i32_type().const_zero(), - contract.context.i32_type().const_int(i as u64, false), - ], - &field.name, - ) - }; + let index = builder.over( + contract, + contract.context.i32_type().const_zero(), + array_length, + ); - self.encode_ty( - contract, - true, - function, - &field.ty, - elem.into(), - &mut normal_fields_dynamic, - &mut temp_offset, - &mut normal_dynamic, - ); - } + // loop body + let elem = contract.array_subscript(ty, arg.into_pointer_value(), index); - let normal_offset = contract - .builder - .build_int_add(normal_offset, temp_offset, ""); + self.encode_packed_ty( + contract, + true, + function, + &elem_ty.deref_any(), + elem.into(), + &mut normal_output, + ); - contract.builder.build_unconditional_branch(done_struct); + builder.set_loop_phi_value(contract, "output", normal_output.into()); - let normal_struct = contract.builder.get_insert_block().unwrap(); + builder.finish(contract); - contract.builder.position_at_end(null_struct); + contract.builder.build_unconditional_branch(done_array); - let mut temp_offset = contract - .context - .i32_type() - .const_int(fixed_field_length, false); + let normal_output = builder.get_loop_phi("output"); + let normal_array = contract.builder.get_insert_block().unwrap(); - for field in &contract.ns.structs[*n].fields { - let elem = contract.default_value(&field.ty); + contract.builder.position_at_end(null_array); - self.encode_ty( + let mut builder = LoopBuilder::new(contract, function); + + let mut null_output = builder + .add_loop_phi( contract, - false, - function, - &field.ty, - elem, - &mut null_fields_dynamic, - &mut temp_offset, - &mut null_dynamic, - ); - } + "output", + contract.context.i8_type().ptr_type(AddressSpace::Generic), + (*output).into(), + ) + .into_pointer_value(); - let null_offset = contract.builder.build_int_add(null_offset, temp_offset, ""); + let _ = builder.over( + contract, + contract.context.i32_type().const_zero(), + array_length, + ); - contract.builder.build_unconditional_branch(done_struct); + // loop body + let elem = contract.default_value(&elem_ty.deref_any()); - let null_struct = contract.builder.get_insert_block().unwrap(); + self.encode_packed_ty( + contract, + false, + function, + &elem_ty.deref_any(), + elem, + &mut null_output, + ); - contract.builder.position_at_end(done_struct); + builder.set_loop_phi_value(contract, "output", null_output.into()); - let dynamic_phi = contract.builder.build_phi( - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "dynamic", - ); + builder.finish(contract); - dynamic_phi.add_incoming(&[ - (&normal_dynamic, normal_struct), - (&null_dynamic, null_struct), - ]); + let null_output = builder.get_loop_phi("output"); - *dynamic = dynamic_phi.as_basic_value().into_pointer_value(); + contract.builder.build_unconditional_branch(done_array); - let offset_phi = contract - .builder - .build_phi(contract.context.i32_type(), "offset"); + let null_array = contract.builder.get_insert_block().unwrap(); - offset_phi - .add_incoming(&[(&normal_offset, normal_struct), (&null_offset, null_struct)]); + contract.builder.position_at_end(done_array); - *offset = offset_phi.as_basic_value().into_int_value(); + let output_phi = contract.builder.build_phi( + contract.context.i8_type().ptr_type(AddressSpace::Generic), + "output", + ); + + output_phi + .add_incoming(&[(&normal_output, normal_array), (&null_output, null_array)]); + + *output = output_phi.as_basic_value().into_pointer_value(); } ast::Type::Struct(n) => { let arg = if load { @@ -1119,9 +1893,7 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { contract.builder.position_at_end(normal_struct); - let mut normal_fixed = *fixed; - let mut normal_offset = *offset; - let mut normal_dynamic = *dynamic; + let mut normal_output = *output; for (i, field) in contract.ns.structs[*n].fields.iter().enumerate() { let elem = unsafe { @@ -1135,15 +1907,13 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { ) }; - self.encode_ty( + self.encode_packed_ty( contract, true, function, &field.ty, elem.into(), - &mut normal_fixed, - &mut normal_offset, - &mut normal_dynamic, + &mut normal_output, ); } @@ -1153,23 +1923,19 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { contract.builder.position_at_end(null_struct); - let mut null_fixed = *fixed; - let mut null_offset = *offset; - let mut null_dynamic = *dynamic; + let mut null_output = *output; // FIXME: abi encoding fixed length fields with default values. This should always be 0 for field in &contract.ns.structs[*n].fields { let elem = contract.default_value(&field.ty); - self.encode_ty( + self.encode_packed_ty( contract, false, function, &field.ty, elem, - &mut null_fixed, - &mut null_offset, - &mut null_dynamic, + &mut null_output, ); } @@ -1179,59 +1945,20 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { contract.builder.position_at_end(done_struct); - let fixed_phi = contract.builder.build_phi( - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "fixed", - ); - - fixed_phi - .add_incoming(&[(&normal_fixed, normal_struct), (&null_fixed, null_struct)]); - - *fixed = fixed_phi.as_basic_value().into_pointer_value(); - - let dynamic_phi = contract.builder.build_phi( + let output_phi = contract.builder.build_phi( contract.context.i8_type().ptr_type(AddressSpace::Generic), - "dynamic", + "output", ); - dynamic_phi.add_incoming(&[ - (&normal_dynamic, normal_struct), - (&null_dynamic, null_struct), - ]); - - *dynamic = dynamic_phi.as_basic_value().into_pointer_value(); - - let offset_phi = contract - .builder - .build_phi(contract.context.i32_type(), "offset"); - - offset_phi - .add_incoming(&[(&normal_offset, normal_struct), (&null_offset, null_struct)]); + output_phi + .add_incoming(&[(&normal_output, normal_struct), (&null_output, null_struct)]); - *offset = offset_phi.as_basic_value().into_int_value(); + *output = output_phi.as_basic_value().into_pointer_value(); } ast::Type::Ref(ty) => { - self.encode_ty(contract, load, function, ty, arg, fixed, offset, dynamic); + self.encode_packed_ty(contract, load, function, ty, arg, output); } ast::Type::String | ast::Type::DynamicBytes => { - // write the current offset to fixed - self.encode_primitive( - contract, - false, - function, - &ast::Type::Uint(32), - *fixed, - (*offset).into(), - ); - - *fixed = unsafe { - contract.builder.build_gep( - *fixed, - &[contract.context.i32_type().const_int(32, false)], - "", - ) - }; - let arg = if load { contract.builder.build_load(arg.into_pointer_value(), "") } else { @@ -1240,30 +1967,6 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { let len = contract.vector_len(arg); - // write the length to dynamic - self.encode_primitive( - contract, - false, - function, - &ast::Type::Uint(32), - *dynamic, - len.into(), - ); - - *dynamic = unsafe { - contract.builder.build_gep( - *dynamic, - &[contract.context.i32_type().const_int(32, false)], - "", - ) - }; - - *offset = contract.builder.build_int_add( - *offset, - contract.context.i32_type().const_int(32, false), - "", - ); - // now copy the string data let string_start = contract.vector_bytes(arg); @@ -1273,7 +1976,7 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { contract .builder .build_pointer_cast( - *dynamic, + *output, contract.context.i8_type().ptr_type(AddressSpace::Generic), "encoded_string", ) @@ -1291,20 +1994,7 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> { "", ); - // round up the length to the next 32 bytes block - let len = contract.builder.build_and( - contract.builder.build_int_add( - len, - contract.context.i32_type().const_int(31, false), - "", - ), - contract.context.i32_type().const_int(!31, false), - "", - ); - - *dynamic = unsafe { contract.builder.build_gep(*dynamic, &[len], "") }; - - *offset = contract.builder.build_int_add(*offset, len, ""); + *output = unsafe { contract.builder.build_gep(*output, &[len], "") }; } _ => unreachable!(), }; @@ -2495,18 +3185,13 @@ impl EthAbiDecoder { /// ABI encode into a vector for abi.encode* style builtin functions pub fn encode_to_vector<'b>( contract: &Contract<'b>, - selector: Option>, function: FunctionValue<'b>, - packed: bool, + packed: &[BasicValueEnum<'b>], args: &[BasicValueEnum<'b>], tys: &[ast::Type], bswap: bool, ) -> PointerValue<'b> { - if packed { - unimplemented!(); - } - - let encoder = EncoderBuilder::new(contract, function, selector, false, args, tys, bswap); + let encoder = EncoderBuilder::new(contract, function, false, packed, args, tys, bswap); let length = encoder.encoded_length(); diff --git a/src/emit/ewasm.rs b/src/emit/ewasm.rs index 17783e464..677377ca7 100644 --- a/src/emit/ewasm.rs +++ b/src/emit/ewasm.rs @@ -677,16 +677,15 @@ impl EwasmTarget { fn encode<'b>( &self, contract: &Contract<'b>, - selector: Option>, constant: Option<(PointerValue<'b>, u64)>, load: bool, function: FunctionValue<'b>, + packed: &[BasicValueEnum<'b>], args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> (PointerValue<'b>, IntValue<'b>) { - let encoder = ethabiencoder::EncoderBuilder::new( - contract, function, selector, load, args, tys, false, - ); + let encoder = + ethabiencoder::EncoderBuilder::new(contract, function, load, packed, args, tys, false); let mut length = encoder.encoded_length(); @@ -1139,13 +1138,12 @@ impl<'a> TargetRuntime<'a> for EwasmTarget { fn abi_encode_to_vector<'b>( &self, contract: &Contract<'b>, - selector: Option>, function: FunctionValue<'b>, - packed: bool, + packed: &[BasicValueEnum<'b>], args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> PointerValue<'b> { - ethabiencoder::encode_to_vector(contract, selector, function, packed, args, tys, false) + ethabiencoder::encode_to_vector(contract, function, packed, args, tys, false) } fn abi_encode<'b>( @@ -1157,7 +1155,16 @@ impl<'a> TargetRuntime<'a> for EwasmTarget { args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> (PointerValue<'b>, IntValue<'b>) { - self.encode(contract, selector, None, load, function, args, tys) + let mut tys = tys.to_vec(); + + let packed = if let Some(selector) = selector { + tys.insert(0, ast::Type::Uint(32)); + vec![selector.into()] + } else { + vec![] + }; + + self.encode(contract, None, load, function, &packed, args, &tys) } fn abi_decode<'b>( @@ -1226,10 +1233,10 @@ impl<'a> TargetRuntime<'a> for EwasmTarget { // input let (input, input_len) = self.encode( contract, - None, Some((code, wasm.len() as u64)), false, function, + &[], args, &tys, ); diff --git a/src/emit/generic.rs b/src/emit/generic.rs index 8bdeafc3d..9ffa0c47b 100644 --- a/src/emit/generic.rs +++ b/src/emit/generic.rs @@ -514,14 +514,13 @@ impl<'a> TargetRuntime<'a> for GenericTarget { /// ABI encode into a vector for abi.encode* style builtin functions fn abi_encode_to_vector<'b>( &self, - _contract: &Contract<'b>, - _selector: Option>, - _function: FunctionValue<'b>, - _packed: bool, - _args: &[BasicValueEnum<'b>], - _spec: &[ast::Type], + contract: &Contract<'b>, + function: FunctionValue<'b>, + packed: &[BasicValueEnum<'b>], + args: &[BasicValueEnum<'b>], + tys: &[ast::Type], ) -> PointerValue<'b> { - unimplemented!(); + ethabiencoder::encode_to_vector(contract, function, packed, args, tys, false) } fn abi_encode<'b>( @@ -533,8 +532,17 @@ impl<'a> TargetRuntime<'a> for GenericTarget { args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> (PointerValue<'b>, IntValue<'b>) { + let mut tys = tys.to_vec(); + + let packed = if let Some(selector) = selector { + tys.insert(0, ast::Type::Uint(32)); + vec![selector.into()] + } else { + vec![] + }; + let encoder = ethabiencoder::EncoderBuilder::new( - contract, function, selector, load, args, tys, false, + contract, function, load, args, &packed, &tys, false, ); let length = encoder.encoded_length(); diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 185dccec0..80dbc5619 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -99,9 +99,8 @@ pub trait TargetRuntime<'a> { fn abi_encode_to_vector<'b>( &self, contract: &Contract<'b>, - selector: Option>, function: FunctionValue<'b>, - packed: bool, + packed: &[BasicValueEnum<'b>], args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> PointerValue<'b>; @@ -2578,20 +2577,15 @@ pub trait TargetRuntime<'a> { .into() } Expression::AbiEncode { - tys, - selector, - packed, - args, - .. + tys, packed, args, .. } => self .abi_encode_to_vector( contract, - selector.as_ref().map(|s| { - self.expression(contract, &s, vartab, function) - .into_int_value() - }), function, - *packed, + &packed + .iter() + .map(|a| self.expression(contract, &a, vartab, function)) + .collect::>(), &args .iter() .map(|a| self.expression(contract, &a, vartab, function)) @@ -2856,11 +2850,7 @@ pub trait TargetRuntime<'a> { .expression(contract, address, vartab, function) .into_int_value(); - let mut selector = contract.ns.functions[*function_no].selector(); - - if contract.ns.target == Target::Substrate { - selector = selector.to_be(); - } + let selector = contract.ns.functions[*function_no].selector(); assert!(matches!(ty, ast::Type::ExternalFunction { .. })); @@ -3613,11 +3603,7 @@ pub trait TargetRuntime<'a> { Instr::AssertFailure { expr: Some(expr) } => { let v = self.expression(contract, expr, &w.vars, function); - let selector = if contract.ns.target == Target::Ewasm { - 0x08c3_79a0u32.to_be() - } else { - 0x08c3_79a0u32 - }; + let selector = 0x08c3_79a0u32; let (data, len) = self.abi_encode( contract, @@ -4035,11 +4021,10 @@ pub trait TargetRuntime<'a> { ) .into_int_value(); - // ewasm stores the selector little endian - let selector = if contract.ns.target == Target::Ewasm { - (*selector).to_be() - } else { + let selector = if contract.ns.target == Target::Substrate { *selector + } else { + selector.to_be() }; let correct_selector = contract.builder.build_int_compare( @@ -4401,7 +4386,7 @@ pub trait TargetRuntime<'a> { None, true, function, - &args[f.params.len()..], + &args[f.params.len()..f.params.len() + f.returns.len()], &tys, ); @@ -4416,7 +4401,7 @@ pub trait TargetRuntime<'a> { contract .context .i32_type() - .const_int(f.selector as u64, false), + .const_int(f.selector.to_be() as u64, false), bb, )); } diff --git a/src/emit/sabre.rs b/src/emit/sabre.rs index eba7694ae..68303e91a 100644 --- a/src/emit/sabre.rs +++ b/src/emit/sabre.rs @@ -599,9 +599,8 @@ impl<'a> TargetRuntime<'a> for SabreTarget { fn abi_encode_to_vector<'b>( &self, _contract: &Contract<'b>, - _selector: Option>, _function: FunctionValue<'b>, - _packed: bool, + _packed: &[BasicValueEnum<'b>], _args: &[BasicValueEnum<'b>], _spec: &[ast::Type], ) -> PointerValue<'b> { @@ -617,8 +616,17 @@ impl<'a> TargetRuntime<'a> for SabreTarget { args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> (PointerValue<'b>, IntValue<'b>) { + let mut tys = tys.to_vec(); + + let packed = if let Some(selector) = selector { + tys.insert(0, ast::Type::Uint(32)); + vec![selector.into()] + } else { + vec![] + }; + let encoder = ethabiencoder::EncoderBuilder::new( - contract, function, selector, load, args, tys, false, + contract, function, load, args, &packed, &tys, false, ); let length = encoder.encoded_length(); diff --git a/src/emit/solana.rs b/src/emit/solana.rs index 9dfffb9d9..1ff2e38d0 100644 --- a/src/emit/solana.rs +++ b/src/emit/solana.rs @@ -2246,13 +2246,12 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { fn abi_encode_to_vector<'b>( &self, contract: &Contract<'b>, - selector: Option>, function: FunctionValue<'b>, - packed: bool, + packed: &[BasicValueEnum<'b>], args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> PointerValue<'b> { - ethabiencoder::encode_to_vector(contract, selector, function, packed, args, tys, false) + ethabiencoder::encode_to_vector(contract, function, packed, args, tys, true) } fn abi_encode( @@ -2264,10 +2263,21 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { args: &[BasicValueEnum<'a>], tys: &[ast::Type], ) -> (PointerValue<'a>, IntValue<'a>) { + debug_assert_eq!(args.len(), tys.len()); + let (output_len, output, output_size) = self.return_buffer(contract); + let mut tys = tys.to_vec(); + + let packed = if let Some(selector) = selector { + tys.insert(0, ast::Type::Uint(32)); + vec![selector.into()] + } else { + vec![] + }; + let encoder = - ethabiencoder::EncoderBuilder::new(contract, function, selector, load, args, tys, true); + ethabiencoder::EncoderBuilder::new(contract, function, load, &packed, args, &tys, true); let length = encoder.encoded_length(); diff --git a/src/emit/substrate.rs b/src/emit/substrate.rs index 4ea13f1e0..f808112b9 100644 --- a/src/emit/substrate.rs +++ b/src/emit/substrate.rs @@ -2985,32 +2985,35 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget { fn abi_encode_to_vector<'b>( &self, contract: &Contract<'b>, - selector: Option>, function: FunctionValue<'b>, - packed: bool, + packed: &[BasicValueEnum<'b>], args: &[BasicValueEnum<'b>], tys: &[ast::Type], ) -> PointerValue<'b> { // first calculate how much memory we need to allocate let mut length = contract.context.i32_type().const_zero(); + debug_assert_eq!(packed.len() + args.len(), tys.len()); + + let mut tys_iter = tys.iter(); + // note that encoded_length return the exact value for packed encoding - for (i, ty) in tys.iter().enumerate() { + for arg in packed { + let ty = tys_iter.next().unwrap(); + length = contract.builder.build_int_add( length, - self.encoded_length(args[i], false, packed, &ty, function, contract), + self.encoded_length(*arg, false, true, ty, function, contract), "", ); } - if selector.is_some() { + for arg in args { + let ty = tys_iter.next().unwrap(); + length = contract.builder.build_int_add( length, - contract - .context - .i32_type() - .size_of() - .const_cast(contract.context.i32_type(), false), + self.encoded_length(*arg, false, false, ty, function, contract), "", ); } @@ -3050,7 +3053,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget { ); // if it's packed, we have the correct length already - if packed { + if args.is_empty() { let data_len = unsafe { contract.builder.build_gep( v, @@ -3098,59 +3101,21 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget { let mut argsdata = data; - if let Some(selector) = selector { - // we need to byte-swap our bytes4 type - - let temp = contract - .builder - .build_alloca(selector.get_type(), "selector"); - - contract.builder.build_store(temp, selector); + let mut tys_iter = tys.iter(); - // byte order needs to be reversed. e.g. hex"11223344" should be 0x10 0x11 0x22 0x33 0x44 - contract.builder.build_call( - contract.module.get_function("__leNtobeN").unwrap(), - &[ - contract - .builder - .build_pointer_cast( - temp, - contract.context.i8_type().ptr_type(AddressSpace::Generic), - "", - ) - .into(), - data.into(), - contract.context.i32_type().const_int(4, false).into(), - ], - "", - ); + for arg in packed { + let ty = tys_iter.next().unwrap(); - argsdata = unsafe { - contract.builder.build_gep( - argsdata, - &[contract - .context - .i32_type() - .size_of() - .const_cast(contract.context.i32_type(), false)], - "", - ) - }; + self.encode_ty(contract, false, true, function, ty, *arg, &mut argsdata); } - for (i, ty) in tys.iter().enumerate() { - self.encode_ty( - contract, - false, - packed, - function, - &ty, - args[i], - &mut argsdata, - ); + for arg in args { + let ty = tys_iter.next().unwrap(); + + self.encode_ty(contract, false, false, function, ty, *arg, &mut argsdata); } - if !packed { + if !args.is_empty() { let length = contract.builder.build_int_sub( contract .builder @@ -3343,7 +3308,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget { contract .context .i32_type() - .const_int(constructor.selector() as u64, false), + .const_int(constructor.selector().to_be() as u64, false), ), false, function, diff --git a/src/sema/ast.rs b/src/sema/ast.rs index 3e49c6d34..b294e6758 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -188,7 +188,7 @@ impl Function { hasher.update(self.signature.as_bytes()); hasher.finalize(&mut res); - u32::from_le_bytes([res[0], res[1], res[2], res[3]]) + u32::from_be_bytes([res[0], res[1], res[2], res[3]]) } /// Is this a constructor @@ -556,8 +556,7 @@ pub enum Expression { AbiEncode { loc: pt::Loc, tys: Vec, - selector: Option>, - packed: bool, + packed: Vec, args: Vec, }, List(pt::Loc, Vec), @@ -926,18 +925,16 @@ impl Expression { Expression::AbiEncode { loc, tys, - selector, packed, args, } => { + let packed = packed.iter().map(|e| filter(e, ctx)).collect(); let args = args.iter().map(|e| filter(e, ctx)).collect(); - let selector = selector.as_ref().map(|e| Box::new(filter(&e, ctx))); Expression::AbiEncode { loc: *loc, tys: tys.clone(), - selector, - packed: *packed, + packed, args, } } diff --git a/src/sema/contracts.rs b/src/sema/contracts.rs index 350f44f98..fda5cc6d7 100644 --- a/src/sema/contracts.rs +++ b/src/sema/contracts.rs @@ -57,8 +57,12 @@ impl ast::Contract { for cfg in &self.cfg { if !cfg.is_placeholder() { out += &format!( - "\n# {} {} public:{} nonpayable:{}\n", - cfg.ty, cfg.name, cfg.public, cfg.nonpayable + "\n# {} {} public:{} selector:{} nonpayable:{}\n", + cfg.ty, + cfg.name, + cfg.public, + hex::encode(cfg.selector.to_be_bytes()), + cfg.nonpayable, ); out += &format!( diff --git a/src/sema/expression.rs b/src/sema/expression.rs index e254381b0..3fca18fa7 100644 --- a/src/sema/expression.rs +++ b/src/sema/expression.rs @@ -4446,7 +4446,7 @@ fn member_access( if id.name == "selector" { return Ok(Expression::Builtin( e.loc(), - vec![Type::Uint(32)], + vec![Type::Bytes(4)], Builtin::ExternalFunctionSelector, vec![expr], )); diff --git a/tests/ewasm.rs b/tests/ewasm.rs index c09aad003..9173ca69d 100644 --- a/tests/ewasm.rs +++ b/tests/ewasm.rs @@ -2052,11 +2052,22 @@ fn external_call() { function test() public returns (int32) { return x.get_x(); } + + function enc() public returns (bytes) { + return abi.encodeWithSignature("get_x()"); + } }"##, ); runtime.constructor(&[]); + let ret = runtime.function("enc", &[]); + + assert_eq!( + ret, + vec!(ethabi::Token::Bytes(0x3829050au32.to_be_bytes().into())) + ); + let ret = runtime.function("test", &[]); assert_eq!( diff --git a/tests/ewasm_tests/abi.rs b/tests/ewasm_tests/abi.rs index 9d5e9ddc1..0f01e1308 100644 --- a/tests/ewasm_tests/abi.rs +++ b/tests/ewasm_tests/abi.rs @@ -34,7 +34,7 @@ fn abi_encode() { let returns = vm.function("test", &[]); - let mut bytes = vec![1, 2, 3, 4]; + let mut bytes = vec![4, 3, 2, 1]; bytes.extend( encode(&[ diff --git a/tests/solana_tests/abi.rs b/tests/solana_tests/abi.rs new file mode 100644 index 000000000..bcce95b74 --- /dev/null +++ b/tests/solana_tests/abi.rs @@ -0,0 +1,43 @@ +use crate::build_solidity; + +#[test] +fn packed() { + let mut vm = build_solidity( + r#" + struct s { + int32 f1; + uint8 f2; + string f3; + uint16[2] f4; + } + + contract bar { + function test() public { + uint16 a = 0xfd01; + assert(abi.encodePacked(a) == hex"fd01"); + uint32 b = 0xaabbccdd; + assert(abi.encodePacked(true, b, false) == hex"01aabbccdd00"); + } + + function test2() public { + string b = "foobar"; + assert(abi.encodePacked(b) == "foobar"); + + assert(abi.encodePacked("foobar") == "foobar"); + assert(abi.encodePacked("foo", "bar") == "foobar"); + } + + function test3() public { + s x = s({ f1: 511, f2: 0xf7, f3: "testie", f4: [ 4, 5 ] }); + + assert(abi.encodePacked(x) == hex"000001fff774657374696500040005"); + } + }"#, + ); + + vm.constructor(&[]); + + vm.function("test", &[]); + vm.function("test2", &[]); + vm.function("test3", &[]); +} diff --git a/tests/solana_tests/mod.rs b/tests/solana_tests/mod.rs index dc3b28aa2..529b200d6 100644 --- a/tests/solana_tests/mod.rs +++ b/tests/solana_tests/mod.rs @@ -1,3 +1,4 @@ +mod abi; mod arrays; mod mappings; mod primitives; diff --git a/tests/substrate_tests/function_types.rs b/tests/substrate_tests/function_types.rs index 5c530a29c..9d4c98daa 100644 --- a/tests/substrate_tests/function_types.rs +++ b/tests/substrate_tests/function_types.rs @@ -482,7 +482,7 @@ fn ext() { function(int32) external returns (bool) func = this.foo; assert(address(this) == func.address); - assert(func.selector == 0x42761137); + assert(func.selector == hex"42761137"); } function foo(int32) public returns (bool) { From acd900617c128f2fe466c0a961579bc43d7dd182 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Fri, 16 Apr 2021 22:51:59 +0100 Subject: [PATCH 7/8] Replace substr(2) with clearer expression We're stripping of the leading "0x", which is not clear. Replace this with replace('0x', '') to improve readability. Signed-off-by: Sean Young --- integration/solana/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/solana/index.ts b/integration/solana/index.ts index e05ea4d4d..11e089348 100644 --- a/integration/solana/index.ts +++ b/integration/solana/index.ts @@ -133,7 +133,7 @@ class Program { const data = Buffer.concat([ this.contractStorageAccount.publicKey.toBuffer(), - Buffer.from(input.substr(2), 'hex') + Buffer.from(input.replace('0x', ''), 'hex') ]); console.log('calling constructor [' + params + ']'); @@ -164,7 +164,7 @@ class Program { const input: string = Web3EthAbi.encodeFunctionCall(abi, params); const data = Buffer.concat([ this.contractStorageAccount.publicKey.toBuffer(), - Buffer.from(input.substr(2), 'hex') + Buffer.from(input.replace('0x', ''), 'hex') ]); let debug = 'calling function ' + name + ' [' + params + ']'; From 97a1cb4628d0032c7e3e9b00db8164119ae4cd55 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Fri, 16 Apr 2021 13:58:26 +0100 Subject: [PATCH 8/8] Implement external function calls for Solana No return data can be passed back yet, this will be implemented in another commit. Signed-off-by: Sean Young --- integration/solana/external_call.sol | 18 ++ integration/solana/index.ts | 28 +- integration/solana/simple.spec.ts | 28 ++ src/codegen/expression.rs | 61 +++- src/emit/ewasm.rs | 4 +- src/emit/generic.rs | 2 +- src/emit/mod.rs | 62 ++-- src/emit/sabre.rs | 2 +- src/emit/solana.rs | 99 +++++- src/emit/substrate.rs | 4 +- src/sema/expression.rs | 7 + stdlib/bpf/solana.bc | Bin 20252 -> 22380 bytes stdlib/solana.c | 53 ++++ tests/solana.rs | 431 ++++++++++++++++++++++----- tests/solana_tests/call.rs | 103 +++++++ tests/solana_tests/mod.rs | 1 + tests/solana_tests/simple.rs | 8 +- tests/solana_tests/storage.rs | 24 +- 18 files changed, 796 insertions(+), 139 deletions(-) create mode 100644 integration/solana/external_call.sol create mode 100644 tests/solana_tests/call.rs diff --git a/integration/solana/external_call.sol b/integration/solana/external_call.sol new file mode 100644 index 000000000..6d0479928 --- /dev/null +++ b/integration/solana/external_call.sol @@ -0,0 +1,18 @@ + +contract caller { + function do_call(callee e, int64 v) public { + e.set_x(v); + } +} + +contract callee { + int64 x; + + function set_x(int64 v) public { + x = v; + } + + function get_x() public view returns (int64) { + return x; + } +} \ No newline at end of file diff --git a/integration/solana/index.ts b/integration/solana/index.ts index 11e089348..dedc1f7c0 100644 --- a/integration/solana/index.ts +++ b/integration/solana/index.ts @@ -158,7 +158,7 @@ class Program { ); } - async call_function(test: TestConnection, name: string, params: any[]): Promise<{ [key: string]: any }> { + async call_function(test: TestConnection, name: string, params: any[], pubkeys: PublicKey[] = []): Promise<{ [key: string]: any }> { let abi: AbiItem = JSON.parse(this.abi).find((e: AbiItem) => e.name == name); const input: string = Web3EthAbi.encodeFunctionCall(abi, params); @@ -169,10 +169,17 @@ class Program { let debug = 'calling function ' + name + ' [' + params + ']'; + let keys = [ + { pubkey: this.returnDataAccount.publicKey, isSigner: false, isWritable: true }, + { pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true }]; + + + for (let i = 0; i < pubkeys.length; i++) { + keys.push({ pubkey: pubkeys[i], isSigner: false, isWritable: true }); + } + const instruction = new TransactionInstruction({ - keys: [ - { pubkey: this.returnDataAccount.publicKey, isSigner: false, isWritable: true }, - { pubkey: this.contractStorageAccount.publicKey, isSigner: false, isWritable: true }], + keys, programId: this.programId, data, }); @@ -222,4 +229,17 @@ class Program { return accountInfo!.data; } + + + all_keys(): PublicKey[] { + return [this.programId, this.contractStorageAccount.publicKey]; + } + + get_program_key(): PublicKey { + return this.programId; + } + + get_storage_key(): PublicKey { + return this.contractStorageAccount.publicKey; + } } diff --git a/integration/solana/simple.spec.ts b/integration/solana/simple.spec.ts index f0f528d66..0b2626a98 100644 --- a/integration/solana/simple.spec.ts +++ b/integration/solana/simple.spec.ts @@ -532,4 +532,32 @@ describe('Deploy solang contract and test', () => { expect(res).toBe(JSON.stringify([false])); }); + + it('external_call', async function () { + this.timeout(50000); + + let conn = await establishConnection(); + + let caller = await conn.loadProgram("caller.so", "caller.abi"); + let callee = await conn.loadProgram("callee.so", "callee.abi"); + + // call the constructor + await caller.call_constructor(conn, []); + await callee.call_constructor(conn, []); + + await callee.call_function(conn, "set_x", ["102"]); + + let res = await callee.call_function(conn, "get_x", []); + + expect(res["0"]).toBe("102"); + + let address = '0x' + callee.get_storage_key().toBuffer().toString('hex'); + console.log("addres: " + address); + + await caller.call_function(conn, "do_call", [address, "13123"], callee.all_keys()); + + res = await callee.call_function(conn, "get_x", []); + + expect(res["0"]).toBe("13123"); + }); }); diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 307f1ca5d..9eddb6e13 100644 --- a/src/codegen/expression.rs +++ b/src/codegen/expression.rs @@ -1267,12 +1267,26 @@ pub fn emit_function_call( let success = vartab.temp_name("success", &Type::Bool); + let (payload, address) = if ns.target == Target::Solana { + ( + Expression::AbiEncode { + loc: *loc, + packed: vec![address, args], + args: Vec::new(), + tys: vec![Type::Address(false), Type::DynamicBytes], + }, + None, + ) + } else { + (args, Some(address)) + }; + cfg.add( vartab, Instr::ExternalCall { success: Some(success), - address: Some(address), - payload: args, + address, + payload, value, gas, callty: ty.clone(), @@ -1312,22 +1326,45 @@ pub fn emit_function_call( tys.insert(0, Type::Bytes(4)); - let payload = Expression::AbiEncode { - loc: *loc, - tys, - packed: vec![Expression::NumberLiteral( - *loc, - Type::Bytes(4), - BigInt::from(dest_func.selector()), - )], - args, + let (payload, address) = if ns.target == Target::Solana { + tys.insert(0, Type::Address(false)); + ( + Expression::AbiEncode { + loc: *loc, + tys, + packed: vec![ + address, + Expression::NumberLiteral( + *loc, + Type::Bytes(4), + BigInt::from(dest_func.selector()), + ), + ], + args, + }, + None, + ) + } else { + ( + Expression::AbiEncode { + loc: *loc, + tys, + packed: vec![Expression::NumberLiteral( + *loc, + Type::Bytes(4), + BigInt::from(dest_func.selector()), + )], + args, + }, + Some(address), + ) }; cfg.add( vartab, Instr::ExternalCall { success: None, - address: Some(address), + address, payload, value, gas, diff --git a/src/emit/ewasm.rs b/src/emit/ewasm.rs index 677377ca7..b3a95a1f1 100644 --- a/src/emit/ewasm.rs +++ b/src/emit/ewasm.rs @@ -1354,7 +1354,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget { success: Option<&mut BasicValueEnum<'b>>, payload: PointerValue<'b>, payload_len: IntValue<'b>, - address: PointerValue<'b>, + address: Option>, gas: IntValue<'b>, value: IntValue<'b>, callty: ast::CallTy, @@ -1370,7 +1370,7 @@ impl<'a> TargetRuntime<'a> for EwasmTarget { contract .builder .build_pointer_cast( - address, + address.unwrap(), contract.context.i8_type().ptr_type(AddressSpace::Generic), "", ) diff --git a/src/emit/generic.rs b/src/emit/generic.rs index 9ffa0c47b..3604c7b2e 100644 --- a/src/emit/generic.rs +++ b/src/emit/generic.rs @@ -610,7 +610,7 @@ impl<'a> TargetRuntime<'a> for GenericTarget { _success: Option<&mut BasicValueEnum<'b>>, _payload: PointerValue<'b>, _payload_len: IntValue<'b>, - _address: PointerValue<'b>, + _address: Option>, _gas: IntValue<'b>, _value: IntValue<'b>, _ty: ast::CallTy, diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 80dbc5619..aab18721d 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -247,7 +247,7 @@ pub trait TargetRuntime<'a> { success: Option<&mut BasicValueEnum<'b>>, payload: PointerValue<'b>, payload_len: IntValue<'b>, - address: PointerValue<'b>, + address: Option>, gas: IntValue<'b>, value: IntValue<'b>, ty: ast::CallTy, @@ -3862,26 +3862,32 @@ pub trait TargetRuntime<'a> { .expression(contract, value, &w.vars, function) .into_int_value(); let payload = self.expression(contract, payload, &w.vars, function); - let address = - self.expression(contract, address.as_ref().unwrap(), &w.vars, function); - let addr = contract.builder.build_array_alloca( - contract.context.i8_type(), - contract - .context - .i32_type() - .const_int(contract.ns.address_length as u64, false), - "address", - ); + let address = if let Some(address) = address { + let address = self.expression(contract, address, &w.vars, function); - contract.builder.build_store( - contract.builder.build_pointer_cast( - addr, - address.get_type().ptr_type(AddressSpace::Generic), + let addr = contract.builder.build_array_alloca( + contract.context.i8_type(), + contract + .context + .i32_type() + .const_int(contract.ns.address_length as u64, false), "address", - ), - address, - ); + ); + + contract.builder.build_store( + contract.builder.build_pointer_cast( + addr, + address.get_type().ptr_type(AddressSpace::Generic), + "address", + ), + address, + ); + + Some(addr) + } else { + None + }; let success = match success { Some(n) => Some(&mut w.vars.get_mut(n).unwrap().value), @@ -3894,7 +3900,7 @@ pub trait TargetRuntime<'a> { success, contract.vector_bytes(payload), contract.vector_len(payload), - addr, + address, gas, value, callty.clone(), @@ -4349,7 +4355,23 @@ pub trait TargetRuntime<'a> { } if contract.ns.target == Target::Solana { - args.push(function.get_last_param().unwrap()); + let params_ty = dest + .get_type() + .get_param_types() + .last() + .unwrap() + .into_pointer_type(); + + args.push( + contract + .builder + .build_pointer_cast( + function.get_last_param().unwrap().into_pointer_value(), + params_ty, + "", + ) + .into(), + ); } let ret = contract diff --git a/src/emit/sabre.rs b/src/emit/sabre.rs index 68303e91a..b4ef1811e 100644 --- a/src/emit/sabre.rs +++ b/src/emit/sabre.rs @@ -698,7 +698,7 @@ impl<'a> TargetRuntime<'a> for SabreTarget { _success: Option<&mut BasicValueEnum<'b>>, _payload: PointerValue<'b>, _payload_len: IntValue<'b>, - _address: PointerValue<'b>, + _address: Option>, _gas: IntValue<'b>, _value: IntValue<'b>, _ty: ast::CallTy, diff --git a/src/emit/solana.rs b/src/emit/solana.rs index 1ff2e38d0..ca616440a 100644 --- a/src/emit/solana.rs +++ b/src/emit/solana.rs @@ -371,9 +371,16 @@ impl SolanaTarget { contract.context.i32_type().const_int(heap_offset, false), ); - contract - .builder - .build_call(initializer, &[sol_params.into()], ""); + let arg_ty = initializer.get_type().get_param_types()[0].into_pointer_type(); + + contract.builder.build_call( + initializer, + &[contract + .builder + .build_pointer_cast(sol_params, arg_ty, "") + .into()], + "", + ); // There is only one possible constructor let ret = if let Some((cfg_no, cfg)) = contract @@ -389,11 +396,24 @@ impl SolanaTarget { self.abi .decode(contract, function, &mut args, input, input_len, &cfg.params); - args.push(sol_params.into()); + let function = contract.functions[&cfg_no]; + let params_ty = function + .get_type() + .get_param_types() + .last() + .unwrap() + .into_pointer_type(); + + args.push( + contract + .builder + .build_pointer_cast(sol_params, params_ty, "") + .into(), + ); contract .builder - .build_call(contract.functions[&cfg_no], &args, "") + .build_call(function, &args, "") .try_as_basic_value() .left() .unwrap() @@ -1250,6 +1270,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { .build_conditional_branch(in_range, get_block, bang_block); contract.builder.position_at_end(bang_block); + self.assert_failure( contract, contract @@ -2370,17 +2391,71 @@ impl<'a> TargetRuntime<'a> for SolanaTarget { /// Call external contract fn external_call<'b>( &self, - _contract: &Contract<'b>, - _function: FunctionValue, - _success: Option<&mut BasicValueEnum<'b>>, - _payload: PointerValue<'b>, - _payload_len: IntValue<'b>, - _address: PointerValue<'b>, + contract: &Contract<'b>, + function: FunctionValue, + success: Option<&mut BasicValueEnum<'b>>, + payload: PointerValue<'b>, + payload_len: IntValue<'b>, + _address: Option>, _gas: IntValue<'b>, _value: IntValue<'b>, _ty: ast::CallTy, ) { - unimplemented!(); + let parameters = contract + .builder + .get_insert_block() + .unwrap() + .get_parent() + .unwrap() + .get_last_param() + .unwrap(); + + let ret = contract + .builder + .build_call( + contract.module.get_function("external_call").unwrap(), + &[payload.into(), payload_len.into(), parameters], + "", + ) + .try_as_basic_value() + .left() + .unwrap() + .into_int_value(); + + let is_success = contract.builder.build_int_compare( + IntPredicate::EQ, + ret, + contract.context.i64_type().const_zero(), + "success", + ); + + if let Some(success) = success { + // we're in a try statement. This means: + // do not abort execution; return success or not in success variable + *success = is_success.into(); + } else { + let success_block = contract.context.append_basic_block(function, "success"); + let bail_block = contract.context.append_basic_block(function, "bail"); + + contract + .builder + .build_conditional_branch(is_success, success_block, bail_block); + + contract.builder.position_at_end(bail_block); + + // should we log "call failed?" + self.assert_failure( + contract, + contract + .context + .i8_type() + .ptr_type(AddressSpace::Generic) + .const_null(), + contract.context.i32_type().const_zero(), + ); + + contract.builder.position_at_end(success_block); + } } /// Get return buffer for external call diff --git a/src/emit/substrate.rs b/src/emit/substrate.rs index f808112b9..e53abbde3 100644 --- a/src/emit/substrate.rs +++ b/src/emit/substrate.rs @@ -3468,7 +3468,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget { success: Option<&mut BasicValueEnum<'b>>, payload: PointerValue<'b>, payload_len: IntValue<'b>, - address: PointerValue<'b>, + address: Option>, gas: IntValue<'b>, value: IntValue<'b>, _ty: ast::CallTy, @@ -3500,7 +3500,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget { .build_call( contract.module.get_function("seal_call").unwrap(), &[ - address.into(), + address.unwrap().into(), contract .context .i32_type() diff --git a/src/sema/expression.rs b/src/sema/expression.rs index 3fca18fa7..56d2c727e 100644 --- a/src/sema/expression.rs +++ b/src/sema/expression.rs @@ -6688,6 +6688,13 @@ fn parse_call_args( )?)); } "gas" => { + if ns.target == Target::Solana { + diagnostics.push(Diagnostic::error( + arg.loc, + format!("‘gas’ not permitted for external calls on {}", ns.target), + )); + return Err(()); + } let ty = Type::Uint(64); let expr = expression( diff --git a/stdlib/bpf/solana.bc b/stdlib/bpf/solana.bc index 4930199a916c8dbbaccf552205f3dfad942ab4ce..9fbb22102bc04a9d9075f24465dd61e07e850ecf 100644 GIT binary patch literal 22380 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 DaBDA# diff --git a/stdlib/solana.c b/stdlib/solana.c index 0f953b2e1..6db1a391b 100644 --- a/stdlib/solana.c +++ b/stdlib/solana.c @@ -43,6 +43,59 @@ void *__malloc(uint32_t size) return sol_alloc_free_(size, NULL); } +uint64_t external_call(uint8_t *input, uint32_t input_len, const SolParameters *params) +{ + uint64_t sol_invoke_signed_c( + const SolInstruction *instruction, + const SolAccountInfo *account_infos, + int account_infos_len, + const SolSignerSeeds *signers_seeds, + int signers_seeds_len); + + // The first 32 bytes of the input is the destination address + const SolPubkey *dest = (const SolPubkey *)input; + + for (int account_no = 1; account_no < params->ka_num; account_no++) + { + const SolAccountInfo *acc = ¶ms->ka[account_no]; + + if (SolPubkey_same(dest, acc->key)) + { + // found it + SolAccountMeta metas[3] = { + { + .pubkey = params->ka[0].key, + .is_writable = true, + .is_signer = false, + }, + { + .pubkey = acc->key, + .is_writable = true, + .is_signer = false, + }, + { + .pubkey = acc->owner, + .is_writable = false, + .is_signer = false, + }}; + + SolInstruction instruction = { + .program_id = acc->owner, + .accounts = metas, + .account_len = SOL_ARRAY_SIZE(metas), + .data = input, + .data_len = input_len, + }; + + return sol_invoke_signed_c(&instruction, params->ka, params->ka_num, NULL, 0); + } + } + + sol_log("call to account not in transaction"); + + return ERROR_INVALID_ACCOUNT_DATA; +} + struct account_data_header { uint32_t magic; diff --git a/tests/solana.rs b/tests/solana.rs index fc885860d..cb8442a8f 100644 --- a/tests/solana.rs +++ b/tests/solana.rs @@ -11,10 +11,19 @@ use solana_rbpf::{ user_error::UserError, vm::{Config, DefaultInstructionMeter, EbpfVm, Executable, Syscall, SyscallObject}, }; -use solang::{compile, file_cache::FileCache, sema::diagnostics, Target}; +use solang::{ + compile, + file_cache::FileCache, + sema::{ast, diagnostics}, + Target, +}; use std::alloc::Layout; +use std::cell::RefCell; +use std::collections::HashMap; +use std::convert::TryInto; use std::io::Write; use std::mem::{align_of, size_of}; +use std::rc::Rc; mod solana_tests; @@ -30,7 +39,23 @@ fn account_new() -> Account { a } -fn build_solidity(src: &str) -> Program { +struct VirtualMachine { + account_data: HashMap, Option)>, + programs: Vec, + stack: Vec, + returndata: Account, + printbuf: String, + output: Vec, +} + +#[derive(Clone)] +struct Contract { + program: Account, + abi: ethabi::Contract, + data: Account, +} + +fn build_solidity(src: &str) -> VirtualMachine { let mut cache = FileCache::new(); cache.set_file_contents("test.sol", src.to_string()); @@ -51,27 +76,46 @@ fn build_solidity(src: &str) -> Program { assert_eq!(res.is_empty(), false); + let mut account_data = HashMap::new(); + let mut programs = Vec::new(); + // resolve - let (code, abi) = res.last().unwrap().clone(); + for (code, abi) in res { + let program = account_new(); + + account_data.insert(program, (code.clone(), None)); + + let abi = ethabi::Contract::load(abi.as_bytes()).unwrap(); + + let data = account_new(); + + account_data.insert(data, ([0u8; 4096].to_vec(), Some(program))); + + programs.push(Contract { program, abi, data }); + } - Program { - code, - abi: ethabi::Contract::load(abi.as_bytes()).unwrap(), - account: account_new(), + let returndata = account_new(); + + account_data.insert(returndata, ([0u8; 1024].to_vec(), None)); + + let cur = programs.last().unwrap().clone(); + + VirtualMachine { + account_data, + programs, + stack: vec![cur], + returndata, printbuf: String::new(), output: Vec::new(), - data: Vec::new(), } } const MAX_PERMITTED_DATA_INCREASE: usize = 10 * 1024; -fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec { +fn serialize_parameters(input: &[u8], vm: &VirtualMachine) -> Vec { let mut v: Vec = Vec::new(); - // ka_num - v.write_u64::(2).unwrap(); - for account_no in 0..2 { + fn serialize_account(v: &mut Vec, key: &Account, acc: &(Vec, Option)) { // dup_info v.write_u8(0xff).unwrap(); // signer @@ -83,27 +127,15 @@ fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec // padding v.write_all(&[0u8; 4]).unwrap(); // key - if account_no == 1 { - v.write_all(&account[..]).unwrap(); - } else { - v.write_all(&account_new()).unwrap(); - } + v.write_all(key).unwrap(); // owner - v.write_all(&[0u8; 32]).unwrap(); + v.write_all(&acc.1.unwrap_or([0u8; 32])).unwrap(); // lamports v.write_u64::(0).unwrap(); // account data - // data len - if account_no == 1 { - v.write_u64::(4096).unwrap(); - let mut data = data.to_vec(); - data.resize(4096, 0); - v.write_all(&data).unwrap(); - } else { - v.write_u64::(4096).unwrap(); - v.write_all(&[0u8; 4096]).unwrap(); - } + v.write_u64::(acc.0.len() as u64).unwrap(); + v.write_all(&acc.0).unwrap(); v.write_all(&[0u8; MAX_PERMITTED_DATA_INCREASE]).unwrap(); let padding = v.len() % 8; @@ -116,6 +148,18 @@ fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec v.write_u64::(0).unwrap(); } + // ka_num + v.write_u64::(vm.account_data.len() as u64) + .unwrap(); + + serialize_account(&mut v, &vm.returndata, &vm.account_data[&vm.returndata]); + + for (key, data) in &vm.account_data { + if key != &vm.returndata { + serialize_account(&mut v, key, data); + } + } + // calldata v.write_u64::(input.len() as u64).unwrap(); v.write_all(input).unwrap(); @@ -127,21 +171,29 @@ fn serialize_parameters(input: &[u8], account: &Account, data: &[u8]) -> Vec } // We want to extract the account data -fn deserialize_parameters(input: &[u8]) -> Vec> { +fn deserialize_parameters( + input: &[u8], + accounts_data: &mut HashMap, Option)>, +) { let mut start = 0; let ka_num = LittleEndian::read_u64(&input[start..]); start += size_of::(); - let mut res = Vec::new(); - for _ in 0..ka_num { - start += 8 + 32 + 32 + 8; + start += 8; + + let account: Account = input[start..start + 32].try_into().unwrap(); + + start += 32 + 32 + 8; let data_len = LittleEndian::read_u64(&input[start..]) as usize; start += size_of::(); + let data = input[start..start + data_len].to_vec(); - res.push(input[start..start + data_len].to_vec()); + if let Some(entry) = accounts_data.get_mut(&account) { + entry.0 = data; + } start += data_len + MAX_PERMITTED_DATA_INCREASE; @@ -152,21 +204,10 @@ fn deserialize_parameters(input: &[u8]) -> Vec> { start += size_of::(); } - - res -} - -struct Program { - code: Vec, - abi: ethabi::Contract, - account: Account, - printbuf: String, - data: Vec, - output: Vec, } struct Printer<'a> { - buf: &'a mut String, + context: Rc>, } impl<'a> SyscallObject for Printer<'a> { @@ -194,7 +235,9 @@ impl<'a> SyscallObject for Printer<'a> { )) .unwrap(); println!("log: {}", message); - self.buf.push_str(message); + if let Ok(mut context) = self.context.try_borrow_mut() { + context.printbuf.push_str(message); + } Ok(0) } } @@ -240,15 +283,220 @@ impl SyscallObject for SyscallAllocFree { } } -impl Program { - fn execute(&mut self, buf: &mut String, calldata: &[u8]) { +/// Rust representation of C's SolInstruction +#[derive(Debug)] +struct SolInstruction { + program_id_addr: u64, + accounts_addr: u64, + accounts_len: usize, + data_addr: u64, + data_len: usize, +} + +/// Rust representation of C's SolAccountMeta +#[derive(Debug)] +struct SolAccountMeta { + pubkey_addr: u64, + is_writable: bool, + is_signer: bool, +} + +/// Rust representation of C's SolAccountInfo +#[derive(Debug)] +struct SolAccountInfo { + key_addr: u64, + lamports_addr: u64, + data_len: u64, + data_addr: u64, + owner_addr: u64, + rent_epoch: u64, + is_signer: bool, + is_writable: bool, + executable: bool, +} + +/// Rust representation of C's SolSignerSeed +#[derive(Debug)] +struct SolSignerSeedC { + addr: u64, + len: u64, +} + +/// Rust representation of C's SolSignerSeeds +#[derive(Debug)] +struct SolSignerSeedsC { + addr: u64, + len: u64, +} + +#[derive(Debug)] +pub struct Instruction { + /// Pubkey of the instruction processor that executes this instruction + pub program_id: Pubkey, + /// Metadata for what accounts should be passed to the instruction processor + pub accounts: Vec, + /// Opaque data passed to the instruction processor + pub data: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Pubkey([u8; 32]); + +#[derive(Debug, PartialEq, Clone)] +pub struct AccountMeta { + /// An account's public key + pub pubkey: Pubkey, + /// True if an Instruction requires a Transaction signature matching `pubkey`. + pub is_signer: bool, + /// True if the `pubkey` can be loaded as a read-write account. + pub is_writable: bool, +} + +fn translate( + memory_mapping: &MemoryMapping, + access_type: AccessType, + vm_addr: u64, + len: u64, +) -> Result> { + memory_mapping.map::(access_type, vm_addr, len) +} + +fn translate_type_inner<'a, T>( + memory_mapping: &MemoryMapping, + access_type: AccessType, + vm_addr: u64, +) -> Result<&'a mut T, EbpfError> { + unsafe { + translate(memory_mapping, access_type, vm_addr, size_of::() as u64) + .map(|value| &mut *(value as *mut T)) + } +} +fn translate_type<'a, T>( + memory_mapping: &MemoryMapping, + vm_addr: u64, +) -> Result<&'a T, EbpfError> { + translate_type_inner::(memory_mapping, AccessType::Load, vm_addr).map(|value| &*value) +} +fn translate_slice<'a, T>( + memory_mapping: &MemoryMapping, + vm_addr: u64, + len: u64, +) -> Result<&'a [T], EbpfError> { + translate_slice_inner::(memory_mapping, AccessType::Load, vm_addr, len).map(|value| &*value) +} +fn translate_slice_inner<'a, T>( + memory_mapping: &MemoryMapping, + access_type: AccessType, + vm_addr: u64, + len: u64, +) -> Result<&'a mut [T], EbpfError> { + if len == 0 { + Ok(&mut []) + } else { + match translate( + memory_mapping, + access_type, + vm_addr, + len.saturating_mul(size_of::() as u64), + ) { + Ok(value) => { + Ok(unsafe { std::slice::from_raw_parts_mut(value as *mut T, len as usize) }) + } + Err(e) => Err(e), + } + } +} + +struct SyscallInvokeSignedC<'a> { + context: Rc>, +} + +impl<'a> SyscallInvokeSignedC<'a> { + fn translate_instruction( + &self, + addr: u64, + memory_mapping: &MemoryMapping, + ) -> Result> { + let ix_c = translate_type::(memory_mapping, addr)?; + + let program_id = translate_type::(memory_mapping, ix_c.program_id_addr)?; + let meta_cs = translate_slice::( + memory_mapping, + ix_c.accounts_addr, + ix_c.accounts_len as u64, + )?; + let data = + translate_slice::(memory_mapping, ix_c.data_addr, ix_c.data_len as u64)?.to_vec(); + let accounts = meta_cs + .iter() + .map(|meta_c| { + let pubkey = translate_type::(memory_mapping, meta_c.pubkey_addr)?; + Ok(AccountMeta { + pubkey: pubkey.clone(), + is_signer: meta_c.is_signer, + is_writable: meta_c.is_writable, + }) + }) + .collect::, EbpfError>>()?; + + Ok(Instruction { + program_id: program_id.clone(), + accounts, + data, + }) + } +} + +impl<'a> SyscallObject for SyscallInvokeSignedC<'a> { + fn call( + &mut self, + instruction_addr: u64, + _account_infos_addr: u64, + _account_infos_len: u64, + _signers_seeds_addr: u64, + _signers_seeds_len: u64, + memory_mapping: &MemoryMapping, + ) -> Result> { + let instruction = self + .translate_instruction(instruction_addr, memory_mapping) + .expect("instruction not valid"); + + let data_id: Account = instruction.data[..32].try_into().unwrap(); + + println!("instruction:{:?}", instruction); + + if let Ok(mut context) = self.context.try_borrow_mut() { + let p = context + .programs + .iter() + .find(|p| p.program == instruction.program_id.0 && p.data == data_id) + .unwrap() + .clone(); + + context.stack.insert(0, p); + + context.execute(&instruction.data); + + context.stack.remove(0); + } + + Ok(0) + } +} + +impl VirtualMachine { + fn execute(&mut self, calldata: &[u8]) { println!("running bpf with calldata:{}", hex::encode(calldata)); - let parameter_bytes = serialize_parameters(&calldata, &self.account, &self.data); + let parameter_bytes = serialize_parameters(&calldata, &self); let heap = vec![0_u8; DEFAULT_HEAP_SIZE]; let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START, true); - let executable = Executable::::from_elf(&self.code, None).expect("should work"); + let program = &self.stack[0]; + + let executable = + Executable::::from_elf(&self.account_data[&program.program].0, None) + .expect("should work"); let mut vm = EbpfVm::::new( executable.as_ref(), Config::default(), @@ -257,9 +505,23 @@ impl Program { ) .unwrap(); - vm.register_syscall_ex("sol_log_", Syscall::Object(Box::new(Printer { buf }))) - .unwrap(); + let context = Rc::new(RefCell::new(self)); + + vm.register_syscall_ex( + "sol_log_", + Syscall::Object(Box::new(Printer { + context: context.clone(), + })), + ) + .unwrap(); + vm.register_syscall_ex( + "sol_invoke_signed_c", + Syscall::Object(Box::new(SyscallInvokeSignedC { + context: context.clone(), + })), + ) + .unwrap(); vm.register_syscall_ex( "sol_alloc_free_", Syscall::Object(Box::new(SyscallAllocFree { @@ -272,52 +534,81 @@ impl Program { .execute_program_interpreted(&mut DefaultInstructionMeter {}) .unwrap(); - let mut accounts = deserialize_parameters(¶meter_bytes); + let mut elf = context.try_borrow_mut().unwrap(); - let output = accounts.remove(0); - let data = accounts.remove(0); + deserialize_parameters(¶meter_bytes, &mut elf.account_data); + + let output = &elf.account_data[&elf.returndata].0; let len = LittleEndian::read_u64(&output); - self.output = output[8..len as usize + 8].to_vec(); - self.data = data; + elf.output = output[8..len as usize + 8].to_vec(); - println!("account: {}", hex::encode(&self.output)); + println!("return: {}", hex::encode(&elf.output)); assert_eq!(res, 0); } fn constructor(&mut self, args: &[Token]) { - let calldata = if let Some(constructor) = &self.abi.constructor { + let program = &self.stack[0]; + + let calldata = if let Some(constructor) = &program.abi.constructor { constructor - .encode_input(self.account.to_vec(), args) + .encode_input(program.data.to_vec(), args) .unwrap() } else { - self.account.to_vec() + program.data.to_vec() }; - let mut buf = String::new(); - self.execute(&mut buf, &calldata); - self.printbuf = buf; + self.execute(&calldata); } fn function(&mut self, name: &str, args: &[Token]) -> Vec { - let mut calldata: Vec = self.account.to_vec(); + let program = &self.stack[0]; - match self.abi.functions[name][0].encode_input(args) { + let mut calldata: Vec = program.data.to_vec(); + + match program.abi.functions[name][0].encode_input(args) { Ok(n) => calldata.extend(&n), Err(x) => panic!("{}", x), }; println!("input: {}", hex::encode(&calldata)); - let mut buf = String::new(); - self.execute(&mut buf, &calldata); - self.printbuf = buf; + self.execute(&calldata); println!("output: {}", hex::encode(&self.output)); - self.abi.functions[name][0] + let program = &self.stack[0]; + + program.abi.functions[name][0] .decode_output(&self.output) .unwrap() } + + fn data(&self) -> &Vec { + let program = &self.stack[0]; + + &self.account_data[&program.data].0 + } + + fn set_program(&mut self, no: usize) { + let cur = self.programs[no].clone(); + + self.stack = vec![cur]; + } +} + +pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace { + let mut cache = FileCache::new(); + + cache.set_file_contents("test.sol", src.to_string()); + + solang::parse_and_resolve("test.sol", &mut cache, target) +} + +pub fn first_error(errors: Vec) -> String { + match errors.iter().find(|m| m.level == ast::Level::Error) { + Some(m) => m.message.to_owned(), + None => panic!("no errors found"), + } } diff --git a/tests/solana_tests/call.rs b/tests/solana_tests/call.rs new file mode 100644 index 000000000..0c5f7da54 --- /dev/null +++ b/tests/solana_tests/call.rs @@ -0,0 +1,103 @@ +use crate::{build_solidity, first_error, parse_and_resolve}; +use ethabi::Token; +use solang::Target; + +#[test] +fn calltys() { + let ns = parse_and_resolve( + r#" + contract main { + function test() public { + address x = address(0); + + x.staticcall(hex"1222"); + } + }"#, + Target::Solana, + ); + + assert_eq!( + first_error(ns.diagnostics), + "method ‘staticcall’ does not exist" + ); + + let ns = parse_and_resolve( + r#" + contract main { + function test() public { + address x = address(0); + + x.delegatecall(hex"1222"); + } + }"#, + Target::Solana, + ); + + assert_eq!( + first_error(ns.diagnostics), + "method ‘delegatecall’ does not exist" + ); + + let ns = parse_and_resolve( + r#" + contract main { + function test() public { + address x = address(0); + + (bool success, bytes bs) = x.call{gas: 5}(hex"1222"); + } + }"#, + Target::Solana, + ); + + assert_eq!( + first_error(ns.diagnostics), + "‘gas’ not permitted for external calls on solana" + ); +} + +#[test] +fn two_contracts() { + let mut vm = build_solidity( + r#" + contract bar0 { + function test_bar(string v) public { + print("bar0 says: " + v); + } + + function test_other(bar1 x) public { + x.test_bar("cross contract call"); + } + } + + contract bar1 { + function test_bar(string v) public { + print("bar1 says: " + v); + } + }"#, + ); + + vm.constructor(&[]); + + vm.function("test_bar", &[Token::String(String::from("yo"))]); + + assert_eq!(vm.printbuf, "bar1 says: yo"); + + vm.printbuf.truncate(0); + + let bar1_account = vm.stack[0].data; + + vm.set_program(0); + + vm.constructor(&[]); + + vm.function("test_bar", &[Token::String(String::from("uncle beau"))]); + + assert_eq!(vm.printbuf, "bar0 says: uncle beau"); + + vm.printbuf.truncate(0); + + vm.function("test_other", &[Token::FixedBytes(bar1_account.to_vec())]); + + assert_eq!(vm.printbuf, "bar1 says: cross contract call"); +} diff --git a/tests/solana_tests/mod.rs b/tests/solana_tests/mod.rs index 529b200d6..68cb1062b 100644 --- a/tests/solana_tests/mod.rs +++ b/tests/solana_tests/mod.rs @@ -1,5 +1,6 @@ mod abi; mod arrays; +mod call; mod mappings; mod primitives; mod simple; diff --git a/tests/solana_tests/simple.rs b/tests/solana_tests/simple.rs index 112c08b06..8dfb7d490 100644 --- a/tests/solana_tests/simple.rs +++ b/tests/solana_tests/simple.rs @@ -19,7 +19,7 @@ fn simple() { assert_eq!(vm.printbuf, "Hello from constructor"); - vm.printbuf = String::new(); + vm.printbuf.truncate(0); vm.function("test", &[]); @@ -76,6 +76,8 @@ fn parameters() { assert_eq!(vm.printbuf, "x is 10"); + vm.printbuf.truncate(0); + vm.function( "test", &[ @@ -164,7 +166,7 @@ fn flipper() { vm.constructor(&[ethabi::Token::Bool(true)]); assert_eq!( - vm.data[0..9].to_vec(), + vm.data()[0..9].to_vec(), hex::decode("6fc90ec51000000001").unwrap() ); @@ -175,7 +177,7 @@ fn flipper() { vm.function("flip", &[]); assert_eq!( - vm.data[0..9].to_vec(), + vm.data()[0..9].to_vec(), hex::decode("6fc90ec51000000000").unwrap() ); diff --git a/tests/solana_tests/storage.rs b/tests/solana_tests/storage.rs index 9cbdde25c..020699184 100644 --- a/tests/solana_tests/storage.rs +++ b/tests/solana_tests/storage.rs @@ -21,7 +21,7 @@ fn string() { vm.constructor(&[]); assert_eq!( - vm.data[0..12].to_vec(), + vm.data()[0..12].to_vec(), vec![65, 177, 160, 100, 16, 0, 0, 0, 0, 0, 0, 0] ); @@ -32,11 +32,11 @@ fn string() { vm.function("set", &[Token::String(String::from("Hello, World!"))]); assert_eq!( - vm.data[0..12].to_vec(), + vm.data()[0..12].to_vec(), vec![65, 177, 160, 100, 16, 0, 0, 0, 32, 0, 0, 0] ); - assert_eq!(vm.data[32..45].to_vec(), b"Hello, World!"); + assert_eq!(vm.data()[32..45].to_vec(), b"Hello, World!"); let returns = vm.function("get", &[]); @@ -51,7 +51,7 @@ fn string() { assert_eq!(returns, vec![Token::String(String::from("Hallo, Werld!"))]); assert_eq!( - vm.data[0..12].to_vec(), + vm.data()[0..12].to_vec(), vec![65, 177, 160, 100, 16, 0, 0, 0, 32, 0, 0, 0] ); @@ -64,7 +64,7 @@ fn string() { assert_eq!(returns, vec![Token::String(String::from(""))]); assert_eq!( - vm.data[0..12].to_vec(), + vm.data()[0..12].to_vec(), vec![65, 177, 160, 100, 16, 0, 0, 0, 0, 0, 0, 0] ); } @@ -97,7 +97,7 @@ fn bytes() { vm.constructor(&[]); assert_eq!( - vm.data[0..12].to_vec(), + vm.data()[0..12].to_vec(), vec![11, 66, 182, 57, 16, 0, 0, 0, 0, 0, 0, 0] ); @@ -113,7 +113,7 @@ fn bytes() { ); assert_eq!( - vm.data[0..12].to_vec(), + vm.data()[0..12].to_vec(), vec![11, 66, 182, 57, 16, 0, 0, 0, 32, 0, 0, 0] ); @@ -254,7 +254,7 @@ fn storage_alignment() { vm.constructor(&[]); assert_eq!( - vm.data[0..32].to_vec(), + vm.data()[0..32].to_vec(), vec![ 11, 66, 182, 57, 32, 0, 0, 0, 1, 0, 3, 2, 4, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 16, 15, 14, 13, 12, 11, 10, 9 @@ -299,7 +299,7 @@ fn bytes_push_pop() { vm.function("push", &[Token::FixedBytes(vec![0x41])]); - println!("data:{}", hex::encode(&vm.data)); + println!("data:{}", hex::encode(&vm.data())); let returns = vm.function("get_bs", &[]); @@ -363,7 +363,7 @@ fn simple_struct() { vm.function("set_s2", &[]); assert_eq!( - vm.data[0..24].to_vec(), + vm.data()[0..24].to_vec(), vec![ 11, 66, 182, 57, 24, 0, 0, 0, 173, 222, 0, 0, 254, 0, 0, 0, 173, 222, 0, 0, 0, 0, 0, 0 ] @@ -436,7 +436,7 @@ fn struct_in_struct() { vm.function("set_s2", &[]); assert_eq!( - vm.data[0..44].to_vec(), + vm.data()[0..44].to_vec(), vec![ 11, 66, 182, 57, 40, 0, 0, 0, 173, 222, 0, 0, 0, 0, 0, 0, 254, 0, 0, 0, 102, 0, 0, 0, 114, 97, 98, 111, 111, 102, 0, 0, 210, 2, 150, 73, 0, 0, 0, 0, 0, 0, 0, 0 @@ -517,7 +517,7 @@ fn string_in_struct() { vm.function("set_s2", &[]); assert_eq!( - vm.data[0..56].to_vec(), + vm.data()[0..56].to_vec(), vec![ 11, 66, 182, 57, 32, 0, 0, 0, 173, 222, 0, 0, 0, 0, 0, 0, 254, 48, 0, 0, 0, 0, 0, 0, 210, 2, 150, 73, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 102, 111,