From b0d88e8029a83d705f937a9e109fb3ad16d1c00a Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 29 Dec 2022 16:42:48 +0200 Subject: [PATCH 01/43] initial change for advanced ink test environment --- crates/engine/src/exec_context.rs | 4 + crates/engine/src/ext.rs | 116 ++++++++++++---- crates/engine/src/lib.rs | 2 +- crates/engine/src/test_api.rs | 62 ++++++--- crates/env/src/api.rs | 3 +- crates/env/src/backend.rs | 14 +- crates/env/src/contract.rs | 24 ++++ crates/env/src/engine/off_chain/impls.rs | 139 ++++++++++++++++--- crates/env/src/engine/off_chain/mod.rs | 12 +- crates/env/src/engine/off_chain/test_api.rs | 14 ++ crates/env/src/lib.rs | 1 + crates/ink/codegen/src/generator/dispatch.rs | 115 ++++++--------- examples/delegator/adder/lib.rs | 35 +++++ 13 files changed, 395 insertions(+), 146 deletions(-) create mode 100644 crates/env/src/contract.rs diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index 32d069e471e..ad5fae86a8b 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -43,6 +43,10 @@ pub struct ExecContext { pub block_number: BlockNumber, /// The current block timestamp. pub block_timestamp: BlockTimestamp, + /// The input of the call. + pub input: Vec, + /// The output buffer of the call. + pub output: Vec, } impl ExecContext { diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 560932daaa1..f344d037c38 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -32,7 +32,13 @@ use crate::{ }, }; use scale::Encode; -use std::panic::panic_any; +use std::{ + borrow::BorrowMut, + cell::RefCell, + collections::HashMap, + panic::panic_any, + rc::Rc, +}; type Result = core::result::Result<(), Error>; @@ -110,20 +116,32 @@ impl ReturnCode { } } +#[derive(Default)] +pub struct ContractStore { + pub instantiated: HashMap, Vec>, + pub deployed: HashMap, Contract>, +} + +pub struct Contract { + pub deploy: fn(), + pub call: fn(), +} + /// The off-chain engine. +#[derive(Clone)] pub struct Engine { /// The environment database. - pub database: Database, + pub database: Rc>, /// The current execution context. - pub exec_context: ExecContext, + pub exec_context: Rc>, /// Recorder for relevant interactions with the engine. /// This is specifically about debug info. This info is /// not available in the `contracts` pallet. - pub(crate) debug_info: DebugInfo, + pub(crate) debug_info: Rc>, /// The chain specification. - pub chain_spec: ChainSpec, + pub chain_spec: Rc>, /// Handler for registered chain extensions. - pub chain_extension_handler: ChainExtensionHandler, + pub chain_extension_handler: Rc>, } /// The chain specification. @@ -158,11 +176,11 @@ impl Engine { // Creates a new `Engine instance. pub fn new() -> Self { Self { - database: Database::new(), - exec_context: ExecContext::new(), - debug_info: DebugInfo::new(), - chain_spec: ChainSpec::default(), - chain_extension_handler: ChainExtensionHandler::new(), + database: Rc::new(RefCell::new(Database::new())), + exec_context: Rc::new(RefCell::new(ExecContext::new())), + debug_info: Rc::new(RefCell::new(DebugInfo::new())), + chain_spec: Rc::new(RefCell::new(ChainSpec::default())), + chain_extension_handler: Rc::new(RefCell::new(ChainExtensionHandler::new())), } } } @@ -190,8 +208,10 @@ impl Engine { .map_err(|_| Error::TransferFailed)?; self.database + .borrow_mut() .set_balance(&contract, contract_old_balance - increment); self.database + .borrow_mut() .set_balance(&dest, dest_old_balance + increment); Ok(()) } @@ -217,7 +237,7 @@ impl Engine { Vec::new() }; - self.debug_info.record_event(EmittedEvent { + self.debug_info.borrow_mut().record_event(EmittedEvent { topics: topics_vec, data: data.to_vec(), }); @@ -229,11 +249,13 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_writes(account_id.clone()); + self.debug_info.borrow_mut().inc_writes(account_id.clone()); self.debug_info + .borrow_mut() .record_cell_for_account(account_id, key.to_vec()); self.database + .borrow_mut() .insert_into_contract_storage(&callee, key, encoded_value.to_vec()) .map(|v| ::try_from(v.len()).expect("usize to u32 conversion failed")) } @@ -243,8 +265,12 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_reads(account_id); - match self.database.get_from_contract_storage(&callee, key) { + self.debug_info.borrow_mut().inc_reads(account_id); + match self + .database + .borrow_mut() + .get_from_contract_storage(&callee, key) + { Some(val) => { set_output(output, val); Ok(()) @@ -259,8 +285,12 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_writes(account_id); - match self.database.remove_contract_storage(&callee, key) { + self.debug_info.borrow_mut().inc_writes(account_id); + match self + .database + .borrow_mut() + .remove_contract_storage(&callee, key) + { Some(val) => { set_output(output, &val); Ok(()) @@ -274,8 +304,9 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_reads(account_id); + self.debug_info.borrow_mut().inc_reads(account_id); self.database + .borrow_mut() .get_from_contract_storage(&callee, key) .map(|val| val.len() as u32) } @@ -285,11 +316,13 @@ impl Engine { pub fn clear_storage(&mut self, key: &[u8]) -> Option { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_writes(account_id.clone()); + self.debug_info.borrow_mut().inc_writes(account_id.clone()); let _ = self .debug_info + .borrow_mut() .remove_cell_for_account(account_id, key.to_vec()); self.database + .borrow_mut() .remove_contract_storage(&callee, key) .map(|val| val.len() as u32) } @@ -320,23 +353,26 @@ impl Engine { pub fn caller(&self, output: &mut &mut [u8]) { let caller = self .exec_context + .borrow() .caller .as_ref() .expect("no caller has been set") .as_bytes(); - set_output(output, caller); + set_output(output, caller.as_bytes()); } /// Returns the balance of the executed contract. pub fn balance(&self, output: &mut &mut [u8]) { let contract = self .exec_context + .borrow() .callee .as_ref() .expect("no callee has been set"); let balance_in_storage = self .database + .borrow() .get_balance(contract.as_bytes()) .expect("currently executing contract must exist"); let balance = scale::Encode::encode(&balance_in_storage); @@ -346,7 +382,7 @@ impl Engine { /// Returns the transferred value for the called contract. pub fn value_transferred(&self, output: &mut &mut [u8]) { let value_transferred: Vec = - scale::Encode::encode(&self.exec_context.value_transferred); + scale::Encode::encode(&self.exec_context.borrow().value_transferred); set_output(output, &value_transferred[..]) } @@ -354,16 +390,19 @@ impl Engine { pub fn address(&self, output: &mut &mut [u8]) { let callee = self .exec_context + .borrow() .callee .as_ref() .expect("no callee has been set") - .as_bytes(); - set_output(output, callee) + .clone(); + set_output(output, callee.as_bytes()) } /// Records the given debug message and appends to stdout. pub fn debug_message(&mut self, message: &str) { - self.debug_info.record_debug_message(String::from(message)); + self.debug_info + .borrow_mut() + .record_debug_message(String::from(message)); print!("{}", message); } @@ -390,14 +429,14 @@ impl Engine { /// Returns the current block number. pub fn block_number(&self, output: &mut &mut [u8]) { let block_number: Vec = - scale::Encode::encode(&self.exec_context.block_number); + scale::Encode::encode(&self.exec_context.borrow().block_number); set_output(output, &block_number[..]) } /// Returns the timestamp of the current block. pub fn block_timestamp(&self, output: &mut &mut [u8]) { let block_timestamp: Vec = - scale::Encode::encode(&self.exec_context.block_timestamp); + scale::Encode::encode(&self.exec_context.borrow().block_timestamp); set_output(output, &block_timestamp[..]) } @@ -409,7 +448,7 @@ impl Engine { /// (i.e. the chain's existential deposit). pub fn minimum_balance(&self, output: &mut &mut [u8]) { let minimum_balance: Vec = - scale::Encode::encode(&self.chain_spec.minimum_balance); + scale::Encode::encode(&self.chain_spec.borrow().minimum_balance); set_output(output, &minimum_balance[..]) } @@ -440,7 +479,11 @@ impl Engine { /// Emulates gas price calculation. pub fn weight_to_fee(&self, gas: u64, output: &mut &mut [u8]) { - let fee = self.chain_spec.gas_price.saturating_mul(gas.into()); + let fee = self + .chain_spec + .borrow() + .gas_price + .saturating_mul(gas.into()); let fee: Vec = scale::Encode::encode(&fee); set_output(output, &fee[..]) } @@ -453,8 +496,8 @@ impl Engine { output: &mut &mut [u8], ) { let encoded_input = input.encode(); - let (status_code, out) = self - .chain_extension_handler + let chain_extension_handler = self.chain_extension_handler.borrow_mut(); + let (status_code, out) = chain_extension_handler .eval(func_id, &encoded_input) .unwrap_or_else(|error| { panic!( @@ -513,6 +556,19 @@ impl Engine { Err(_) => Err(Error::EcdsaRecoveryFailed), } } + + /// Register the contract into the local storage. + pub fn register_contract( + &mut self, + hash: &[u8], + deploy: fn(), + call: fn(), + ) -> Option { + self.contracts + .borrow_mut() + .deployed + .insert(hash.to_vec(), Contract { deploy, call }) + } } /// Copies the `slice` into `output`. diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 1fdafb4a9dd..fbb297b0f6a 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -22,7 +22,7 @@ pub mod test_api; mod chain_extension; mod database; -mod exec_context; +pub mod exec_context; mod hashing; mod types; diff --git a/crates/engine/src/test_api.rs b/crates/engine/src/test_api.rs index 6b3228fa30f..684eb71b1b1 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -21,7 +21,10 @@ use crate::{ AccountError, Error, }; -use std::collections::HashMap; +use std::{ + borrow::BorrowMut, + collections::HashMap, +}; /// Record for an emitted event. #[derive(Clone)] @@ -173,37 +176,57 @@ impl DebugInfo { impl Engine { /// Resets the environment. pub fn initialize_or_reset(&mut self) { - self.exec_context.reset(); - self.database.clear(); - self.debug_info.reset(); + self.exec_context.borrow_mut().reset(); + self.database.borrow_mut().clear(); + self.debug_info.borrow_mut().reset(); } /// Returns the total number of reads and writes of the contract's storage. pub fn get_contract_storage_rw(&self, account_id: Vec) -> (usize, usize) { let account_id = AccountId::from(account_id); - let reads = self.debug_info.count_reads.get(&account_id).unwrap_or(&0); - let writes = self.debug_info.count_writes.get(&account_id).unwrap_or(&0); + let reads = self + .debug_info + .borrow() + .count_reads + .get(&account_id) + .unwrap_or(&0); + let writes = self + .debug_info + .borrow() + .count_writes + .get(&account_id) + .unwrap_or(&0); (*reads, *writes) } /// Returns the total number of reads executed. pub fn count_reads(&self) -> usize { - self.debug_info.count_reads.iter().map(|(_, v)| v).sum() + self.debug_info + .borrow() + .count_reads + .iter() + .map(|(_, v)| v) + .sum() } /// Returns the total number of writes executed. pub fn count_writes(&self) -> usize { - self.debug_info.count_writes.iter().map(|(_, v)| v).sum() + self.debug_info + .borrow() + .count_writes + .iter() + .map(|(_, v)| v) + .sum() } /// Sets a caller for the next call. pub fn set_caller(&mut self, caller: Vec) { - self.exec_context.caller = Some(caller.into()); + self.exec_context.borrow_mut().caller = Some(caller.into()); } /// Sets the callee for the next call. pub fn set_callee(&mut self, callee: Vec) { - self.exec_context.callee = Some(callee.into()); + self.exec_context.borrow_mut().callee = Some(callee.into()); } /// Returns the amount of storage cells used by the account `account_id`. @@ -212,6 +235,7 @@ impl Engine { pub fn count_used_storage_cells(&self, account_id: &[u8]) -> Result { let cells = self .debug_info + .borrow() .cells_per_account .get(&account_id.to_owned().into()) .ok_or_else(|| { @@ -222,40 +246,44 @@ impl Engine { /// Advances the chain by a single block. pub fn advance_block(&mut self) { - self.exec_context.block_number += 1; - self.exec_context.block_timestamp += self.chain_spec.block_time; + self.exec_context.borrow_mut().block_number += 1; + self.exec_context.borrow_mut().block_timestamp += + self.chain_spec.borrow().block_time; } /// Returns the callee, i.e. the currently executing contract. pub fn get_callee(&self) -> Vec { - self.exec_context.callee() + self.exec_context.borrow().callee() } /// Returns the contents of the past performed environmental `debug_message` in order. pub fn get_emitted_debug_messages(&self) -> RecordedDebugMessages { - self.debug_info.emitted_debug_messages.clone() + self.debug_info.borrow().emitted_debug_messages.clone() } /// Returns the recorded emitted events in order. pub fn get_emitted_events(&self) -> impl Iterator { - self.debug_info.emitted_events.clone().into_iter() + self.debug_info.borrow().emitted_events.clone().into_iter() } /// Returns the current balance of `account_id`. pub fn get_balance(&self, account_id: Vec) -> Result { self.database + .borrow() .get_balance(&account_id) .ok_or(Error::Account(AccountError::NoAccountForId(account_id))) } /// Sets the balance of `account_id` to `new_balance`. pub fn set_balance(&mut self, account_id: Vec, new_balance: Balance) { - self.database.set_balance(&account_id, new_balance); + self.database + .borrow_mut() + .set_balance(&account_id, new_balance); } /// Sets the value transferred from the caller to the callee as part of the call. pub fn set_value_transferred(&mut self, value: Balance) { - self.exec_context.value_transferred = value; + self.exec_context.borrow_mut().value_transferred = value; } } diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index e4821480a67..4e004c7020d 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -18,6 +18,7 @@ use crate::{ backend::{ EnvBackend, ReturnFlags, + ReturnType, TypedEnvBackend, }, call::{ @@ -411,7 +412,7 @@ where /// # Note /// /// This function stops the execution of the contract immediately. -pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! +pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ReturnType where R: scale::Encode, { diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index dc3a8832b0e..53a54745cde 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -30,7 +30,7 @@ use crate::{ use ink_storage_traits::Storable; /// The flags to indicate further information about the end of a contract execution. -#[derive(Default)] +#[derive(Default, Clone)] pub struct ReturnFlags { value: u32, } @@ -51,6 +51,10 @@ impl ReturnFlags { self } + pub fn is_reverted(&self) -> bool { + self.value & 1 > 0 + } + /// Returns the underlying `u32` representation. #[cfg(not(feature = "std"))] pub(crate) fn into_u32(self) -> u32 { @@ -165,6 +169,12 @@ impl CallFlags { } } +#[cfg(all(not(feature = "std"), target_arch = "wasm32"))] +pub type ReturnType = !; + +#[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] +pub type ReturnType = (); + /// Environmental contract functionality that does not require `Environment`. pub trait EnvBackend { /// Writes the value to the contract storage under the given storage key. @@ -244,7 +254,7 @@ pub trait EnvBackend { /// /// The `flags` parameter can be used to revert the state changes of the /// entire execution if necessary. - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ReturnType where R: scale::Encode; diff --git a/crates/env/src/contract.rs b/crates/env/src/contract.rs new file mode 100644 index 00000000000..9ac7b2b3ae1 --- /dev/null +++ b/crates/env/src/contract.rs @@ -0,0 +1,24 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contract's stuff related to the environment. + +/// Entrypoint of the contract to execute constructors or messages. +pub trait Entrypoint { + /// Entrypoint to run a constructor for the contract. It deploys the contract to the environment. + fn deploy(); + + /// Entrypoint to run a message of the contract. + fn call(); +} diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 6954942e69c..0f4547893f0 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -14,6 +14,7 @@ use super::EnvInstance; use crate::{ + backend::ReturnType, call::{ Call, CallParams, @@ -41,6 +42,7 @@ use crate::{ TypedEnvBackend, }; use ink_engine::{ + exec_context::ExecContext, ext, ext::Engine, }; @@ -245,14 +247,18 @@ impl EnvBackend for EnvInstance { where T: scale::Decode, { - unimplemented!("the off-chain env does not implement `input`") + T::decode(&mut self.engine.exec_context.borrow().input.as_slice()) + .map_err(|_| Error::CalleeTrapped) } - fn return_value(&mut self, _flags: ReturnFlags, _return_value: &R) -> ! + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ReturnType where R: scale::Encode, { - unimplemented!("the off-chain env does not implement `return_value`") + if flags.is_reverted() { + panic!("the off-chain env does not implement revert in `seal_return_value`") + } + self.engine.exec_context.borrow_mut().output = return_value.encode(); } fn debug_message(&mut self, message: &str) { @@ -446,12 +452,53 @@ impl TypedEnvBackend for EnvInstance { Args: scale::Encode, R: scale::Decode, { + let callee = params.callee().as_ref().to_vec(); let _gas_limit = params.gas_limit(); - let _callee = params.callee(); + // TODO: Add support of `call_flags` let _call_flags = params.call_flags().into_u32(); - let _transferred_value = params.transferred_value(); - let _input = params.exec_input(); - unimplemented!("off-chain environment does not support contract invocation") + let transferred_value = params.transferred_value(); + let input = params.exec_input(); + + let callee_context = ExecContext { + caller: self.engine.exec_context.borrow().callee.clone(), + callee: Some(callee.clone().into()), + value_transferred: ::decode( + &mut transferred_value.encode().as_slice(), + )?, + block_number: self.engine.exec_context.borrow().block_number, + block_timestamp: self.engine.exec_context.borrow().block_timestamp, + input: input.encode(), + output: vec![], + }; + + let previous_context = self.engine.exec_context.replace(callee_context); + + let code_hash = self + .engine + .contracts + .borrow() + .instantiated + .get(&callee) + .ok_or(Error::NotCallable)? + .clone(); + + let call_fn = self + .engine + .contracts + .borrow() + .deployed + .get(&code_hash) + .ok_or(Error::CodeNotFound)? + .call; + + call_fn(); + + let return_value = + R::decode(&mut self.engine.exec_context.borrow().output.as_slice())?; + + let _ = self.engine.exec_context.replace(previous_context); + + Ok(return_value) } fn invoke_contract_delegate( @@ -478,12 +525,56 @@ impl TypedEnvBackend for EnvInstance { Args: scale::Encode, Salt: AsRef<[u8]>, { - let _code_hash = params.code_hash(); + let code_hash = params.code_hash().as_ref().to_vec(); + // Gas is not supported by off env. let _gas_limit = params.gas_limit(); - let _endowment = params.endowment(); - let _input = params.exec_input(); - let _salt_bytes = params.salt_bytes(); - unimplemented!("off-chain environment does not support contract instantiation") + let endowment = params.endowment(); + let input = params.exec_input(); + let salt_bytes = params.salt_bytes(); + + // TODO: User ruels from the `contract-pallet` to create an `AccountId`. + let mut callee = [0u8; 32]; + Sha2x256::hash( + [code_hash.as_ref(), salt_bytes.as_ref()] + .concat() + .as_slice(), + &mut callee, + ); + let callee = callee.as_ref().to_vec(); + + let callee_context = ExecContext { + caller: self.engine.exec_context.borrow().callee.clone(), + callee: Some(callee.clone().into()), + value_transferred: ::decode( + &mut endowment.encode().as_slice(), + )?, + block_number: self.engine.exec_context.borrow().block_number, + block_timestamp: self.engine.exec_context.borrow().block_timestamp, + input: input.encode(), + output: vec![], + }; + + let previous_context = self.engine.exec_context.replace(callee_context); + + let deploy_fn = self + .engine + .contracts + .borrow() + .deployed + .get(&code_hash) + .ok_or(Error::CodeNotFound)? + .deploy; + self.engine + .contracts + .borrow_mut() + .instantiated + .insert(callee.clone(), code_hash); + + deploy_fn(); + + let _ = self.engine.exec_context.replace(previous_context); + + Ok(<_ as scale::Decode>::decode(&mut callee.as_slice())?) } fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! @@ -513,11 +604,15 @@ impl TypedEnvBackend for EnvInstance { }) } - fn is_contract(&mut self, _account: &E::AccountId) -> bool + fn is_contract(&mut self, account: &E::AccountId) -> bool where E: Environment, { - unimplemented!("off-chain environment does not support contract instantiation") + self.engine + .contracts + .borrow() + .instantiated + .contains_key(account.as_ref().to_vec().as_slice()) } fn caller_is_origin(&mut self) -> bool @@ -527,17 +622,27 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("off-chain environment does not support cross-contract calls") } - fn code_hash(&mut self, _account: &E::AccountId) -> Result + fn code_hash(&mut self, account: &E::AccountId) -> Result where E: Environment, { - unimplemented!("off-chain environment does not support `code_hash`") + let code_hash = self + .engine + .contracts + .borrow() + .instantiated + .get(&account.as_ref().to_vec()) + .ok_or(Error::NotCallable)? + .clone(); + + Ok(<_ as scale::Decode>::decode(&mut code_hash.as_slice())?) } fn own_code_hash(&mut self) -> Result where E: Environment, { - unimplemented!("off-chain environment does not support `own_code_hash`") + let account_id = self.account_id::(); + self.code_hash::(&account_id) } } diff --git a/crates/env/src/engine/off_chain/mod.rs b/crates/env/src/engine/off_chain/mod.rs index 2d2d0be6e62..1639c296117 100644 --- a/crates/env/src/engine/off_chain/mod.rs +++ b/crates/env/src/engine/off_chain/mod.rs @@ -29,6 +29,7 @@ use derive_more::From; use ink_engine::ext::Engine; /// The off-chain environment. +#[derive(Clone)] pub struct EnvInstance { engine: Engine, } @@ -38,15 +39,12 @@ impl OnInstance for EnvInstance { where F: FnOnce(&mut Self) -> R, { - use core::cell::RefCell; thread_local!( - static INSTANCE: RefCell = RefCell::new( - EnvInstance { - engine: Engine::new() - } - ) + static INSTANCE: EnvInstance = EnvInstance { + engine: Engine::new() + } ); - INSTANCE.with(|instance| f(&mut instance.borrow_mut())) + INSTANCE.with(|instance| f(&mut instance.clone())) } } diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index a409bf40a82..ea313e81d73 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -27,6 +27,8 @@ use ink_engine::test_api::RecordedDebugMessages; use std::panic::UnwindSafe; pub use super::call_data::CallData; +use crate::contract::Entrypoint; +use ink_engine::ext::Contract; pub use ink_engine::ChainExtension; /// Record for an emitted event. @@ -339,6 +341,18 @@ pub fn recorded_events() -> impl Iterator { }) } +/// Registers the contract by the code hash. After registration, the contract can be instantiated. +pub fn register_contract(code_hash: &[u8]) -> Option +where + C: Entrypoint + ?Sized, +{ + ::on_instance(|instance| { + let deploy = C::deploy; + let call = C::call; + instance.engine.register_contract(code_hash, deploy, call) + }) +} + /// Tests if a contract terminates successfully after `self.env().terminate()` /// has been called. /// diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index f04a61306f9..ce340cd69cf 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -74,6 +74,7 @@ mod arithmetic; mod backend; pub mod call; pub mod chain_extension; +pub mod contract; mod engine; mod error; pub mod hash; diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 4246d982103..ea6a9d10ae2 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -79,12 +79,7 @@ impl GenerateCode for Dispatch<'_> { #contract_dispatchable_messages_infos #constructor_decoder_type #message_decoder_type - - #[cfg(not(test))] - #[cfg(not(feature = "ink-as-dependency"))] - const _: () = { - #entry_points - }; + #entry_points } } } @@ -412,79 +407,57 @@ impl Dispatch<'_> { self.any_message_accepts_payment_expr(message_spans); quote_spanned!(span=> #[cfg(not(test))] - #[no_mangle] - #[allow(clippy::nonminimal_bool)] - fn deploy() { - if !#any_constructor_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() - .unwrap_or_else(|error| ::core::panic!("{}", error)) + #[cfg(not(feature = "ink-as-dependency"))] + const _: () = { + #[no_mangle] + #[allow(clippy::nonminimal_bool)] + fn deploy() { + <#storage_ident as ::ink_env::contract::Entrypoint>::deploy() } - let dispatchable = match ::ink::env::decode_input::< - <#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type, - >() { - ::core::result::Result::Ok(decoded_dispatchable) => { - decoded_dispatchable - } - ::core::result::Result::Err(_decoding_error) => { - let error = ::ink::ConstructorResult::Err(::ink::LangError::CouldNotReadInput); - - // At this point we're unable to set the `Ok` variant to be the any "real" - // constructor output since we were unable to figure out what the caller wanted - // to dispatch in the first place, so we set it to `()`. - // - // This is okay since we're going to only be encoding the `Err` variant - // into the output buffer anyways. - ::ink::env::return_value::<::ink::ConstructorResult<()>>( - ::ink::env::ReturnFlags::new_with_reverted(true), - &error, - ); - } - }; - - <<#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type - as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(dispatchable) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! message failed: {}", error) - }) + #[no_mangle] + #[allow(clippy::nonminimal_bool)] + fn call() { + <#storage_ident as ::ink_env::contract::Entrypoint>::call() + } } - #[cfg(not(test))] - #[no_mangle] - #[allow(clippy::nonminimal_bool)] - fn call() { - if !#any_message_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() - .unwrap_or_else(|error| ::core::panic!("{}", error)) + impl ::ink_env::contract::Entrypoint for #storage_ident { + fn deploy() { + if !#any_constructor_accept_payment { + ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() + .unwrap_or_else(|error| ::core::panic!("{}", error)) + } + + ::ink_env::decode_input::< + <#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type>() + .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) + .and_then(|decoder| { + <<#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type + as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) + }) + .unwrap_or_else(|error| { + ::core::panic!("dispatching ink! constructor failed: {}", error) + }) } - let dispatchable = match ::ink::env::decode_input::< - <#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type, - >() { - ::core::result::Result::Ok(decoded_dispatchable) => { - decoded_dispatchable - } - ::core::result::Result::Err(_decoding_error) => { - let error = ::ink::MessageResult::Err(::ink::LangError::CouldNotReadInput); - - // At this point we're unable to set the `Ok` variant to be the any "real" - // message output since we were unable to figure out what the caller wanted - // to dispatch in the first place, so we set it to `()`. - // - // This is okay since we're going to only be encoding the `Err` variant - // into the output buffer anyways. - ::ink::env::return_value::<::ink::MessageResult<()>>( - ::ink::env::ReturnFlags::new_with_reverted(true), - &error, - ); + fn call() { + if !#any_message_accept_payment { + ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() + .unwrap_or_else(|error| ::core::panic!("{}", error)) } - }; - <<#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type - as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(dispatchable) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! message failed: {}", error) - }) + ::ink_env::decode_input::< + <#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type>() + .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) + .and_then(|decoder| { + <<#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type + as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) + }) + .unwrap_or_else(|error| { + ::core::panic!("dispatching ink! message failed: {}", error) + }) + } } ) } diff --git a/examples/delegator/adder/lib.rs b/examples/delegator/adder/lib.rs index 839915c6175..e784c388d89 100644 --- a/examples/delegator/adder/lib.rs +++ b/examples/delegator/adder/lib.rs @@ -30,3 +30,38 @@ mod adder { } } } + +#[cfg(test)] +mod test { + #[test] + fn it_works() { + use super::*; + use accumulator::{ + Accumulator, + AccumulatorRef, + }; + + // register Accumulator & Adder + let hash1 = ink::env::Hash::try_from([10u8; 32]).unwrap(); + let hash2 = ink::env::Hash::try_from([20u8; 32]).unwrap(); + ink::env::test::register_contract::(hash1.as_ref()); + ink::env::test::register_contract::(hash2.as_ref()); + + let acc = AccumulatorRef::new(0) + .code_hash(hash1.clone()) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `AccumulatorRef` contract"); + let mut adder = AdderRef::new(acc.clone()) + .code_hash(hash2.clone()) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `AdderRef` contract"); + + assert_eq!(acc.get(), 0); + adder.inc(1); + assert_eq!(acc.get(), 1); + } +} From 8d99c807f25acaaab0f02b0a8c6ce346ca3f02a2 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 29 Dec 2022 16:42:48 +0200 Subject: [PATCH 02/43] initial change for advanced ink test environment --- crates/engine/src/exec_context.rs | 4 + crates/engine/src/ext.rs | 116 ++++++++++++---- crates/engine/src/lib.rs | 2 +- crates/engine/src/test_api.rs | 62 ++++++--- crates/env/src/api.rs | 3 +- crates/env/src/backend.rs | 14 +- crates/env/src/contract.rs | 24 ++++ crates/env/src/engine/off_chain/impls.rs | 139 ++++++++++++++++--- crates/env/src/engine/off_chain/mod.rs | 12 +- crates/env/src/engine/off_chain/test_api.rs | 14 ++ crates/env/src/lib.rs | 1 + crates/ink/codegen/src/generator/dispatch.rs | 115 ++++++--------- examples/delegator/adder/lib.rs | 35 +++++ 13 files changed, 395 insertions(+), 146 deletions(-) create mode 100644 crates/env/src/contract.rs diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index 32d069e471e..ad5fae86a8b 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -43,6 +43,10 @@ pub struct ExecContext { pub block_number: BlockNumber, /// The current block timestamp. pub block_timestamp: BlockTimestamp, + /// The input of the call. + pub input: Vec, + /// The output buffer of the call. + pub output: Vec, } impl ExecContext { diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 560932daaa1..f344d037c38 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -32,7 +32,13 @@ use crate::{ }, }; use scale::Encode; -use std::panic::panic_any; +use std::{ + borrow::BorrowMut, + cell::RefCell, + collections::HashMap, + panic::panic_any, + rc::Rc, +}; type Result = core::result::Result<(), Error>; @@ -110,20 +116,32 @@ impl ReturnCode { } } +#[derive(Default)] +pub struct ContractStore { + pub instantiated: HashMap, Vec>, + pub deployed: HashMap, Contract>, +} + +pub struct Contract { + pub deploy: fn(), + pub call: fn(), +} + /// The off-chain engine. +#[derive(Clone)] pub struct Engine { /// The environment database. - pub database: Database, + pub database: Rc>, /// The current execution context. - pub exec_context: ExecContext, + pub exec_context: Rc>, /// Recorder for relevant interactions with the engine. /// This is specifically about debug info. This info is /// not available in the `contracts` pallet. - pub(crate) debug_info: DebugInfo, + pub(crate) debug_info: Rc>, /// The chain specification. - pub chain_spec: ChainSpec, + pub chain_spec: Rc>, /// Handler for registered chain extensions. - pub chain_extension_handler: ChainExtensionHandler, + pub chain_extension_handler: Rc>, } /// The chain specification. @@ -158,11 +176,11 @@ impl Engine { // Creates a new `Engine instance. pub fn new() -> Self { Self { - database: Database::new(), - exec_context: ExecContext::new(), - debug_info: DebugInfo::new(), - chain_spec: ChainSpec::default(), - chain_extension_handler: ChainExtensionHandler::new(), + database: Rc::new(RefCell::new(Database::new())), + exec_context: Rc::new(RefCell::new(ExecContext::new())), + debug_info: Rc::new(RefCell::new(DebugInfo::new())), + chain_spec: Rc::new(RefCell::new(ChainSpec::default())), + chain_extension_handler: Rc::new(RefCell::new(ChainExtensionHandler::new())), } } } @@ -190,8 +208,10 @@ impl Engine { .map_err(|_| Error::TransferFailed)?; self.database + .borrow_mut() .set_balance(&contract, contract_old_balance - increment); self.database + .borrow_mut() .set_balance(&dest, dest_old_balance + increment); Ok(()) } @@ -217,7 +237,7 @@ impl Engine { Vec::new() }; - self.debug_info.record_event(EmittedEvent { + self.debug_info.borrow_mut().record_event(EmittedEvent { topics: topics_vec, data: data.to_vec(), }); @@ -229,11 +249,13 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_writes(account_id.clone()); + self.debug_info.borrow_mut().inc_writes(account_id.clone()); self.debug_info + .borrow_mut() .record_cell_for_account(account_id, key.to_vec()); self.database + .borrow_mut() .insert_into_contract_storage(&callee, key, encoded_value.to_vec()) .map(|v| ::try_from(v.len()).expect("usize to u32 conversion failed")) } @@ -243,8 +265,12 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_reads(account_id); - match self.database.get_from_contract_storage(&callee, key) { + self.debug_info.borrow_mut().inc_reads(account_id); + match self + .database + .borrow_mut() + .get_from_contract_storage(&callee, key) + { Some(val) => { set_output(output, val); Ok(()) @@ -259,8 +285,12 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_writes(account_id); - match self.database.remove_contract_storage(&callee, key) { + self.debug_info.borrow_mut().inc_writes(account_id); + match self + .database + .borrow_mut() + .remove_contract_storage(&callee, key) + { Some(val) => { set_output(output, &val); Ok(()) @@ -274,8 +304,9 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_reads(account_id); + self.debug_info.borrow_mut().inc_reads(account_id); self.database + .borrow_mut() .get_from_contract_storage(&callee, key) .map(|val| val.len() as u32) } @@ -285,11 +316,13 @@ impl Engine { pub fn clear_storage(&mut self, key: &[u8]) -> Option { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.inc_writes(account_id.clone()); + self.debug_info.borrow_mut().inc_writes(account_id.clone()); let _ = self .debug_info + .borrow_mut() .remove_cell_for_account(account_id, key.to_vec()); self.database + .borrow_mut() .remove_contract_storage(&callee, key) .map(|val| val.len() as u32) } @@ -320,23 +353,26 @@ impl Engine { pub fn caller(&self, output: &mut &mut [u8]) { let caller = self .exec_context + .borrow() .caller .as_ref() .expect("no caller has been set") .as_bytes(); - set_output(output, caller); + set_output(output, caller.as_bytes()); } /// Returns the balance of the executed contract. pub fn balance(&self, output: &mut &mut [u8]) { let contract = self .exec_context + .borrow() .callee .as_ref() .expect("no callee has been set"); let balance_in_storage = self .database + .borrow() .get_balance(contract.as_bytes()) .expect("currently executing contract must exist"); let balance = scale::Encode::encode(&balance_in_storage); @@ -346,7 +382,7 @@ impl Engine { /// Returns the transferred value for the called contract. pub fn value_transferred(&self, output: &mut &mut [u8]) { let value_transferred: Vec = - scale::Encode::encode(&self.exec_context.value_transferred); + scale::Encode::encode(&self.exec_context.borrow().value_transferred); set_output(output, &value_transferred[..]) } @@ -354,16 +390,19 @@ impl Engine { pub fn address(&self, output: &mut &mut [u8]) { let callee = self .exec_context + .borrow() .callee .as_ref() .expect("no callee has been set") - .as_bytes(); - set_output(output, callee) + .clone(); + set_output(output, callee.as_bytes()) } /// Records the given debug message and appends to stdout. pub fn debug_message(&mut self, message: &str) { - self.debug_info.record_debug_message(String::from(message)); + self.debug_info + .borrow_mut() + .record_debug_message(String::from(message)); print!("{}", message); } @@ -390,14 +429,14 @@ impl Engine { /// Returns the current block number. pub fn block_number(&self, output: &mut &mut [u8]) { let block_number: Vec = - scale::Encode::encode(&self.exec_context.block_number); + scale::Encode::encode(&self.exec_context.borrow().block_number); set_output(output, &block_number[..]) } /// Returns the timestamp of the current block. pub fn block_timestamp(&self, output: &mut &mut [u8]) { let block_timestamp: Vec = - scale::Encode::encode(&self.exec_context.block_timestamp); + scale::Encode::encode(&self.exec_context.borrow().block_timestamp); set_output(output, &block_timestamp[..]) } @@ -409,7 +448,7 @@ impl Engine { /// (i.e. the chain's existential deposit). pub fn minimum_balance(&self, output: &mut &mut [u8]) { let minimum_balance: Vec = - scale::Encode::encode(&self.chain_spec.minimum_balance); + scale::Encode::encode(&self.chain_spec.borrow().minimum_balance); set_output(output, &minimum_balance[..]) } @@ -440,7 +479,11 @@ impl Engine { /// Emulates gas price calculation. pub fn weight_to_fee(&self, gas: u64, output: &mut &mut [u8]) { - let fee = self.chain_spec.gas_price.saturating_mul(gas.into()); + let fee = self + .chain_spec + .borrow() + .gas_price + .saturating_mul(gas.into()); let fee: Vec = scale::Encode::encode(&fee); set_output(output, &fee[..]) } @@ -453,8 +496,8 @@ impl Engine { output: &mut &mut [u8], ) { let encoded_input = input.encode(); - let (status_code, out) = self - .chain_extension_handler + let chain_extension_handler = self.chain_extension_handler.borrow_mut(); + let (status_code, out) = chain_extension_handler .eval(func_id, &encoded_input) .unwrap_or_else(|error| { panic!( @@ -513,6 +556,19 @@ impl Engine { Err(_) => Err(Error::EcdsaRecoveryFailed), } } + + /// Register the contract into the local storage. + pub fn register_contract( + &mut self, + hash: &[u8], + deploy: fn(), + call: fn(), + ) -> Option { + self.contracts + .borrow_mut() + .deployed + .insert(hash.to_vec(), Contract { deploy, call }) + } } /// Copies the `slice` into `output`. diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 1fdafb4a9dd..fbb297b0f6a 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -22,7 +22,7 @@ pub mod test_api; mod chain_extension; mod database; -mod exec_context; +pub mod exec_context; mod hashing; mod types; diff --git a/crates/engine/src/test_api.rs b/crates/engine/src/test_api.rs index 2d6703d7b20..684eb71b1b1 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -21,7 +21,10 @@ use crate::{ AccountError, Error, }; -use std::collections::HashMap; +use std::{ + borrow::BorrowMut, + collections::HashMap, +}; /// Record for an emitted event. #[derive(Clone)] @@ -173,37 +176,57 @@ impl DebugInfo { impl Engine { /// Resets the environment. pub fn initialize_or_reset(&mut self) { - self.exec_context.reset(); - self.database.clear(); - self.debug_info.reset(); + self.exec_context.borrow_mut().reset(); + self.database.borrow_mut().clear(); + self.debug_info.borrow_mut().reset(); } /// Returns the total number of reads and writes of the contract's storage. pub fn get_contract_storage_rw(&self, account_id: Vec) -> (usize, usize) { let account_id = AccountId::from(account_id); - let reads = self.debug_info.count_reads.get(&account_id).unwrap_or(&0); - let writes = self.debug_info.count_writes.get(&account_id).unwrap_or(&0); + let reads = self + .debug_info + .borrow() + .count_reads + .get(&account_id) + .unwrap_or(&0); + let writes = self + .debug_info + .borrow() + .count_writes + .get(&account_id) + .unwrap_or(&0); (*reads, *writes) } /// Returns the total number of reads executed. pub fn count_reads(&self) -> usize { - self.debug_info.count_reads.values().sum() + self.debug_info + .borrow() + .count_reads + .iter() + .map(|(_, v)| v) + .sum() } /// Returns the total number of writes executed. pub fn count_writes(&self) -> usize { - self.debug_info.count_writes.values().sum() + self.debug_info + .borrow() + .count_writes + .iter() + .map(|(_, v)| v) + .sum() } /// Sets a caller for the next call. pub fn set_caller(&mut self, caller: Vec) { - self.exec_context.caller = Some(caller.into()); + self.exec_context.borrow_mut().caller = Some(caller.into()); } /// Sets the callee for the next call. pub fn set_callee(&mut self, callee: Vec) { - self.exec_context.callee = Some(callee.into()); + self.exec_context.borrow_mut().callee = Some(callee.into()); } /// Returns the amount of storage cells used by the account `account_id`. @@ -212,6 +235,7 @@ impl Engine { pub fn count_used_storage_cells(&self, account_id: &[u8]) -> Result { let cells = self .debug_info + .borrow() .cells_per_account .get(&account_id.to_owned().into()) .ok_or_else(|| { @@ -222,40 +246,44 @@ impl Engine { /// Advances the chain by a single block. pub fn advance_block(&mut self) { - self.exec_context.block_number += 1; - self.exec_context.block_timestamp += self.chain_spec.block_time; + self.exec_context.borrow_mut().block_number += 1; + self.exec_context.borrow_mut().block_timestamp += + self.chain_spec.borrow().block_time; } /// Returns the callee, i.e. the currently executing contract. pub fn get_callee(&self) -> Vec { - self.exec_context.callee() + self.exec_context.borrow().callee() } /// Returns the contents of the past performed environmental `debug_message` in order. pub fn get_emitted_debug_messages(&self) -> RecordedDebugMessages { - self.debug_info.emitted_debug_messages.clone() + self.debug_info.borrow().emitted_debug_messages.clone() } /// Returns the recorded emitted events in order. pub fn get_emitted_events(&self) -> impl Iterator { - self.debug_info.emitted_events.clone().into_iter() + self.debug_info.borrow().emitted_events.clone().into_iter() } /// Returns the current balance of `account_id`. pub fn get_balance(&self, account_id: Vec) -> Result { self.database + .borrow() .get_balance(&account_id) .ok_or(Error::Account(AccountError::NoAccountForId(account_id))) } /// Sets the balance of `account_id` to `new_balance`. pub fn set_balance(&mut self, account_id: Vec, new_balance: Balance) { - self.database.set_balance(&account_id, new_balance); + self.database + .borrow_mut() + .set_balance(&account_id, new_balance); } /// Sets the value transferred from the caller to the callee as part of the call. pub fn set_value_transferred(&mut self, value: Balance) { - self.exec_context.value_transferred = value; + self.exec_context.borrow_mut().value_transferred = value; } } diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index e4821480a67..4e004c7020d 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -18,6 +18,7 @@ use crate::{ backend::{ EnvBackend, ReturnFlags, + ReturnType, TypedEnvBackend, }, call::{ @@ -411,7 +412,7 @@ where /// # Note /// /// This function stops the execution of the contract immediately. -pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! +pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ReturnType where R: scale::Encode, { diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index dc3a8832b0e..53a54745cde 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -30,7 +30,7 @@ use crate::{ use ink_storage_traits::Storable; /// The flags to indicate further information about the end of a contract execution. -#[derive(Default)] +#[derive(Default, Clone)] pub struct ReturnFlags { value: u32, } @@ -51,6 +51,10 @@ impl ReturnFlags { self } + pub fn is_reverted(&self) -> bool { + self.value & 1 > 0 + } + /// Returns the underlying `u32` representation. #[cfg(not(feature = "std"))] pub(crate) fn into_u32(self) -> u32 { @@ -165,6 +169,12 @@ impl CallFlags { } } +#[cfg(all(not(feature = "std"), target_arch = "wasm32"))] +pub type ReturnType = !; + +#[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] +pub type ReturnType = (); + /// Environmental contract functionality that does not require `Environment`. pub trait EnvBackend { /// Writes the value to the contract storage under the given storage key. @@ -244,7 +254,7 @@ pub trait EnvBackend { /// /// The `flags` parameter can be used to revert the state changes of the /// entire execution if necessary. - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ReturnType where R: scale::Encode; diff --git a/crates/env/src/contract.rs b/crates/env/src/contract.rs new file mode 100644 index 00000000000..9ac7b2b3ae1 --- /dev/null +++ b/crates/env/src/contract.rs @@ -0,0 +1,24 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contract's stuff related to the environment. + +/// Entrypoint of the contract to execute constructors or messages. +pub trait Entrypoint { + /// Entrypoint to run a constructor for the contract. It deploys the contract to the environment. + fn deploy(); + + /// Entrypoint to run a message of the contract. + fn call(); +} diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 6954942e69c..0f4547893f0 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -14,6 +14,7 @@ use super::EnvInstance; use crate::{ + backend::ReturnType, call::{ Call, CallParams, @@ -41,6 +42,7 @@ use crate::{ TypedEnvBackend, }; use ink_engine::{ + exec_context::ExecContext, ext, ext::Engine, }; @@ -245,14 +247,18 @@ impl EnvBackend for EnvInstance { where T: scale::Decode, { - unimplemented!("the off-chain env does not implement `input`") + T::decode(&mut self.engine.exec_context.borrow().input.as_slice()) + .map_err(|_| Error::CalleeTrapped) } - fn return_value(&mut self, _flags: ReturnFlags, _return_value: &R) -> ! + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ReturnType where R: scale::Encode, { - unimplemented!("the off-chain env does not implement `return_value`") + if flags.is_reverted() { + panic!("the off-chain env does not implement revert in `seal_return_value`") + } + self.engine.exec_context.borrow_mut().output = return_value.encode(); } fn debug_message(&mut self, message: &str) { @@ -446,12 +452,53 @@ impl TypedEnvBackend for EnvInstance { Args: scale::Encode, R: scale::Decode, { + let callee = params.callee().as_ref().to_vec(); let _gas_limit = params.gas_limit(); - let _callee = params.callee(); + // TODO: Add support of `call_flags` let _call_flags = params.call_flags().into_u32(); - let _transferred_value = params.transferred_value(); - let _input = params.exec_input(); - unimplemented!("off-chain environment does not support contract invocation") + let transferred_value = params.transferred_value(); + let input = params.exec_input(); + + let callee_context = ExecContext { + caller: self.engine.exec_context.borrow().callee.clone(), + callee: Some(callee.clone().into()), + value_transferred: ::decode( + &mut transferred_value.encode().as_slice(), + )?, + block_number: self.engine.exec_context.borrow().block_number, + block_timestamp: self.engine.exec_context.borrow().block_timestamp, + input: input.encode(), + output: vec![], + }; + + let previous_context = self.engine.exec_context.replace(callee_context); + + let code_hash = self + .engine + .contracts + .borrow() + .instantiated + .get(&callee) + .ok_or(Error::NotCallable)? + .clone(); + + let call_fn = self + .engine + .contracts + .borrow() + .deployed + .get(&code_hash) + .ok_or(Error::CodeNotFound)? + .call; + + call_fn(); + + let return_value = + R::decode(&mut self.engine.exec_context.borrow().output.as_slice())?; + + let _ = self.engine.exec_context.replace(previous_context); + + Ok(return_value) } fn invoke_contract_delegate( @@ -478,12 +525,56 @@ impl TypedEnvBackend for EnvInstance { Args: scale::Encode, Salt: AsRef<[u8]>, { - let _code_hash = params.code_hash(); + let code_hash = params.code_hash().as_ref().to_vec(); + // Gas is not supported by off env. let _gas_limit = params.gas_limit(); - let _endowment = params.endowment(); - let _input = params.exec_input(); - let _salt_bytes = params.salt_bytes(); - unimplemented!("off-chain environment does not support contract instantiation") + let endowment = params.endowment(); + let input = params.exec_input(); + let salt_bytes = params.salt_bytes(); + + // TODO: User ruels from the `contract-pallet` to create an `AccountId`. + let mut callee = [0u8; 32]; + Sha2x256::hash( + [code_hash.as_ref(), salt_bytes.as_ref()] + .concat() + .as_slice(), + &mut callee, + ); + let callee = callee.as_ref().to_vec(); + + let callee_context = ExecContext { + caller: self.engine.exec_context.borrow().callee.clone(), + callee: Some(callee.clone().into()), + value_transferred: ::decode( + &mut endowment.encode().as_slice(), + )?, + block_number: self.engine.exec_context.borrow().block_number, + block_timestamp: self.engine.exec_context.borrow().block_timestamp, + input: input.encode(), + output: vec![], + }; + + let previous_context = self.engine.exec_context.replace(callee_context); + + let deploy_fn = self + .engine + .contracts + .borrow() + .deployed + .get(&code_hash) + .ok_or(Error::CodeNotFound)? + .deploy; + self.engine + .contracts + .borrow_mut() + .instantiated + .insert(callee.clone(), code_hash); + + deploy_fn(); + + let _ = self.engine.exec_context.replace(previous_context); + + Ok(<_ as scale::Decode>::decode(&mut callee.as_slice())?) } fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! @@ -513,11 +604,15 @@ impl TypedEnvBackend for EnvInstance { }) } - fn is_contract(&mut self, _account: &E::AccountId) -> bool + fn is_contract(&mut self, account: &E::AccountId) -> bool where E: Environment, { - unimplemented!("off-chain environment does not support contract instantiation") + self.engine + .contracts + .borrow() + .instantiated + .contains_key(account.as_ref().to_vec().as_slice()) } fn caller_is_origin(&mut self) -> bool @@ -527,17 +622,27 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("off-chain environment does not support cross-contract calls") } - fn code_hash(&mut self, _account: &E::AccountId) -> Result + fn code_hash(&mut self, account: &E::AccountId) -> Result where E: Environment, { - unimplemented!("off-chain environment does not support `code_hash`") + let code_hash = self + .engine + .contracts + .borrow() + .instantiated + .get(&account.as_ref().to_vec()) + .ok_or(Error::NotCallable)? + .clone(); + + Ok(<_ as scale::Decode>::decode(&mut code_hash.as_slice())?) } fn own_code_hash(&mut self) -> Result where E: Environment, { - unimplemented!("off-chain environment does not support `own_code_hash`") + let account_id = self.account_id::(); + self.code_hash::(&account_id) } } diff --git a/crates/env/src/engine/off_chain/mod.rs b/crates/env/src/engine/off_chain/mod.rs index 2d2d0be6e62..1639c296117 100644 --- a/crates/env/src/engine/off_chain/mod.rs +++ b/crates/env/src/engine/off_chain/mod.rs @@ -29,6 +29,7 @@ use derive_more::From; use ink_engine::ext::Engine; /// The off-chain environment. +#[derive(Clone)] pub struct EnvInstance { engine: Engine, } @@ -38,15 +39,12 @@ impl OnInstance for EnvInstance { where F: FnOnce(&mut Self) -> R, { - use core::cell::RefCell; thread_local!( - static INSTANCE: RefCell = RefCell::new( - EnvInstance { - engine: Engine::new() - } - ) + static INSTANCE: EnvInstance = EnvInstance { + engine: Engine::new() + } ); - INSTANCE.with(|instance| f(&mut instance.borrow_mut())) + INSTANCE.with(|instance| f(&mut instance.clone())) } } diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index 087aedb79e3..41811c8f27a 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -27,6 +27,8 @@ use ink_engine::test_api::RecordedDebugMessages; use std::panic::UnwindSafe; pub use super::call_data::CallData; +use crate::contract::Entrypoint; +use ink_engine::ext::Contract; pub use ink_engine::ChainExtension; /// Record for an emitted event. @@ -326,6 +328,18 @@ pub fn recorded_events() -> impl Iterator { }) } +/// Registers the contract by the code hash. After registration, the contract can be instantiated. +pub fn register_contract(code_hash: &[u8]) -> Option +where + C: Entrypoint + ?Sized, +{ + ::on_instance(|instance| { + let deploy = C::deploy; + let call = C::call; + instance.engine.register_contract(code_hash, deploy, call) + }) +} + /// Tests if a contract terminates successfully after `self.env().terminate()` /// has been called. /// diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index e9d9be7dcf1..6ed3e727cf9 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -73,6 +73,7 @@ mod arithmetic; mod backend; pub mod call; pub mod chain_extension; +pub mod contract; mod engine; mod error; pub mod hash; diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 4246d982103..ea6a9d10ae2 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -79,12 +79,7 @@ impl GenerateCode for Dispatch<'_> { #contract_dispatchable_messages_infos #constructor_decoder_type #message_decoder_type - - #[cfg(not(test))] - #[cfg(not(feature = "ink-as-dependency"))] - const _: () = { - #entry_points - }; + #entry_points } } } @@ -412,79 +407,57 @@ impl Dispatch<'_> { self.any_message_accepts_payment_expr(message_spans); quote_spanned!(span=> #[cfg(not(test))] - #[no_mangle] - #[allow(clippy::nonminimal_bool)] - fn deploy() { - if !#any_constructor_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() - .unwrap_or_else(|error| ::core::panic!("{}", error)) + #[cfg(not(feature = "ink-as-dependency"))] + const _: () = { + #[no_mangle] + #[allow(clippy::nonminimal_bool)] + fn deploy() { + <#storage_ident as ::ink_env::contract::Entrypoint>::deploy() } - let dispatchable = match ::ink::env::decode_input::< - <#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type, - >() { - ::core::result::Result::Ok(decoded_dispatchable) => { - decoded_dispatchable - } - ::core::result::Result::Err(_decoding_error) => { - let error = ::ink::ConstructorResult::Err(::ink::LangError::CouldNotReadInput); - - // At this point we're unable to set the `Ok` variant to be the any "real" - // constructor output since we were unable to figure out what the caller wanted - // to dispatch in the first place, so we set it to `()`. - // - // This is okay since we're going to only be encoding the `Err` variant - // into the output buffer anyways. - ::ink::env::return_value::<::ink::ConstructorResult<()>>( - ::ink::env::ReturnFlags::new_with_reverted(true), - &error, - ); - } - }; - - <<#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type - as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(dispatchable) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! message failed: {}", error) - }) + #[no_mangle] + #[allow(clippy::nonminimal_bool)] + fn call() { + <#storage_ident as ::ink_env::contract::Entrypoint>::call() + } } - #[cfg(not(test))] - #[no_mangle] - #[allow(clippy::nonminimal_bool)] - fn call() { - if !#any_message_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() - .unwrap_or_else(|error| ::core::panic!("{}", error)) + impl ::ink_env::contract::Entrypoint for #storage_ident { + fn deploy() { + if !#any_constructor_accept_payment { + ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() + .unwrap_or_else(|error| ::core::panic!("{}", error)) + } + + ::ink_env::decode_input::< + <#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type>() + .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) + .and_then(|decoder| { + <<#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type + as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) + }) + .unwrap_or_else(|error| { + ::core::panic!("dispatching ink! constructor failed: {}", error) + }) } - let dispatchable = match ::ink::env::decode_input::< - <#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type, - >() { - ::core::result::Result::Ok(decoded_dispatchable) => { - decoded_dispatchable - } - ::core::result::Result::Err(_decoding_error) => { - let error = ::ink::MessageResult::Err(::ink::LangError::CouldNotReadInput); - - // At this point we're unable to set the `Ok` variant to be the any "real" - // message output since we were unable to figure out what the caller wanted - // to dispatch in the first place, so we set it to `()`. - // - // This is okay since we're going to only be encoding the `Err` variant - // into the output buffer anyways. - ::ink::env::return_value::<::ink::MessageResult<()>>( - ::ink::env::ReturnFlags::new_with_reverted(true), - &error, - ); + fn call() { + if !#any_message_accept_payment { + ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() + .unwrap_or_else(|error| ::core::panic!("{}", error)) } - }; - <<#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type - as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(dispatchable) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! message failed: {}", error) - }) + ::ink_env::decode_input::< + <#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type>() + .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) + .and_then(|decoder| { + <<#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type + as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) + }) + .unwrap_or_else(|error| { + ::core::panic!("dispatching ink! message failed: {}", error) + }) + } } ) } diff --git a/examples/delegator/adder/lib.rs b/examples/delegator/adder/lib.rs index 839915c6175..e784c388d89 100644 --- a/examples/delegator/adder/lib.rs +++ b/examples/delegator/adder/lib.rs @@ -30,3 +30,38 @@ mod adder { } } } + +#[cfg(test)] +mod test { + #[test] + fn it_works() { + use super::*; + use accumulator::{ + Accumulator, + AccumulatorRef, + }; + + // register Accumulator & Adder + let hash1 = ink::env::Hash::try_from([10u8; 32]).unwrap(); + let hash2 = ink::env::Hash::try_from([20u8; 32]).unwrap(); + ink::env::test::register_contract::(hash1.as_ref()); + ink::env::test::register_contract::(hash2.as_ref()); + + let acc = AccumulatorRef::new(0) + .code_hash(hash1.clone()) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `AccumulatorRef` contract"); + let mut adder = AdderRef::new(acc.clone()) + .code_hash(hash2.clone()) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `AdderRef` contract"); + + assert_eq!(acc.get(), 0); + adder.inc(1); + assert_eq!(acc.get(), 1); + } +} From 54e63e2202f4e35a7bdb39a5e393dfcaa02700f9 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 29 Dec 2022 22:51:51 +0200 Subject: [PATCH 03/43] fix never type --- crates/env/src/api.rs | 14 +++- crates/env/src/backend.rs | 15 ++-- crates/ink/codegen/src/generator/dispatch.rs | 86 ++++++++++++++------ 3 files changed, 80 insertions(+), 35 deletions(-) diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 4e004c7020d..44f13e3f14d 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -18,7 +18,6 @@ use crate::{ backend::{ EnvBackend, ReturnFlags, - ReturnType, TypedEnvBackend, }, call::{ @@ -412,7 +411,18 @@ where /// # Note /// /// This function stops the execution of the contract immediately. -pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ReturnType +#[cfg(all(not(feature = "std"), target_arch = "wasm32"))] +pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! +where + R: scale::Encode, +{ + ::on_instance(|instance| { + EnvBackend::return_value::(instance, return_flags, return_value) + }) +} + +#[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] +pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> () where R: scale::Encode, { diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 53a54745cde..24e7762436c 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -51,6 +51,7 @@ impl ReturnFlags { self } + /// Returns `true` if the execution is going to be reverted. pub fn is_reverted(&self) -> bool { self.value & 1 > 0 } @@ -169,12 +170,6 @@ impl CallFlags { } } -#[cfg(all(not(feature = "std"), target_arch = "wasm32"))] -pub type ReturnType = !; - -#[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] -pub type ReturnType = (); - /// Environmental contract functionality that does not require `Environment`. pub trait EnvBackend { /// Writes the value to the contract storage under the given storage key. @@ -254,7 +249,13 @@ pub trait EnvBackend { /// /// The `flags` parameter can be used to revert the state changes of the /// entire execution if necessary. - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ReturnType + #[cfg(all(not(feature = "std"), target_arch = "wasm32"))] + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! + where + R: scale::Encode; + + #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> () where R: scale::Encode; diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index ea6a9d10ae2..8335a37a73c 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -412,51 +412,85 @@ impl Dispatch<'_> { #[no_mangle] #[allow(clippy::nonminimal_bool)] fn deploy() { - <#storage_ident as ::ink_env::contract::Entrypoint>::deploy() + <#storage_ident as ::ink::env::contract::Entrypoint>::deploy() } #[no_mangle] #[allow(clippy::nonminimal_bool)] fn call() { - <#storage_ident as ::ink_env::contract::Entrypoint>::call() + <#storage_ident as ::ink::env::contract::Entrypoint>::call() } - } + }; - impl ::ink_env::contract::Entrypoint for #storage_ident { + impl ::ink::env::contract::Entrypoint for #storage_ident { fn deploy() { if !#any_constructor_accept_payment { - ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() + ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } - ::ink_env::decode_input::< - <#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type>() - .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) - .and_then(|decoder| { - <<#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type - as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) - }) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! constructor failed: {}", error) - }) + let dispatchable = match ::ink::env::decode_input::< + <#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type, + >() { + ::core::result::Result::Ok(decoded_dispatchable) => { + decoded_dispatchable + } + ::core::result::Result::Err(_decoding_error) => { + let error = ::ink::ConstructorResult::Err(::ink::LangError::CouldNotReadInput); + + // At this point we're unable to set the `Ok` variant to be the any "real" + // constructor output since we were unable to figure out what the caller wanted + // to dispatch in the first place, so we set it to `()`. + // + // This is okay since we're going to only be encoding the `Err` variant + // into the output buffer anyways. + ::ink::env::return_value::<::ink::ConstructorResult<()>>( + ::ink::env::ReturnFlags::new_with_reverted(true), + &error, + ); + } + }; + + <<#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type + as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(dispatchable) + .unwrap_or_else(|error| { + ::core::panic!("dispatching ink! message failed: {}", error) + }) } fn call() { if !#any_message_accept_payment { - ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() + ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } - ::ink_env::decode_input::< - <#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type>() - .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) - .and_then(|decoder| { - <<#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type - as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) - }) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! message failed: {}", error) - }) + let dispatchable = match ::ink::env::decode_input::< + <#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type, + >() { + ::core::result::Result::Ok(decoded_dispatchable) => { + decoded_dispatchable + } + ::core::result::Result::Err(_decoding_error) => { + let error = ::ink::MessageResult::Err(::ink::LangError::CouldNotReadInput); + + // At this point we're unable to set the `Ok` variant to be the any "real" + // message output since we were unable to figure out what the caller wanted + // to dispatch in the first place, so we set it to `()`. + // + // This is okay since we're going to only be encoding the `Err` variant + // into the output buffer anyways. + ::ink::env::return_value::<::ink::MessageResult<()>>( + ::ink::env::ReturnFlags::new_with_reverted(true), + &error, + ); + } + }; + + <<#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type + as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(dispatchable) + .unwrap_or_else(|error| { + ::core::panic!("dispatching ink! message failed: {}", error) + }) } } ) From 328882c581ed3afb3758481756c75bb9d3d38323 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 3 Jan 2023 15:07:00 +0200 Subject: [PATCH 04/43] fix bugs --- crates/engine/src/ext.rs | 11 +++++++---- crates/engine/src/test_api.rs | 20 ++++++++++---------- crates/env/src/api.rs | 7 ++++++- crates/env/src/backend.rs | 2 +- crates/env/src/engine/off_chain/impls.rs | 20 ++++++++++++++++---- crates/env/src/engine/off_chain/test_api.rs | 2 ++ crates/ink/codegen/src/generator/dispatch.rs | 10 +++++----- 7 files changed, 47 insertions(+), 25 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index f344d037c38..c87a876ff23 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -33,7 +33,6 @@ use crate::{ }; use scale::Encode; use std::{ - borrow::BorrowMut, cell::RefCell, collections::HashMap, panic::panic_any, @@ -142,6 +141,8 @@ pub struct Engine { pub chain_spec: Rc>, /// Handler for registered chain extensions. pub chain_extension_handler: Rc>, + /// Contracts' store. + pub contracts: Rc>, } /// The chain specification. @@ -181,6 +182,7 @@ impl Engine { debug_info: Rc::new(RefCell::new(DebugInfo::new())), chain_spec: Rc::new(RefCell::new(ChainSpec::default())), chain_extension_handler: Rc::new(RefCell::new(ChainExtensionHandler::new())), + contracts: Rc::new(RefCell::new(ContractStore::default())), } } } @@ -357,7 +359,7 @@ impl Engine { .caller .as_ref() .expect("no caller has been set") - .as_bytes(); + .clone(); set_output(output, caller.as_bytes()); } @@ -368,7 +370,8 @@ impl Engine { .borrow() .callee .as_ref() - .expect("no callee has been set"); + .expect("no callee has been set") + .clone(); let balance_in_storage = self .database @@ -496,7 +499,7 @@ impl Engine { output: &mut &mut [u8], ) { let encoded_input = input.encode(); - let chain_extension_handler = self.chain_extension_handler.borrow_mut(); + let mut chain_extension_handler = self.chain_extension_handler.borrow_mut(); let (status_code, out) = chain_extension_handler .eval(func_id, &encoded_input) .unwrap_or_else(|error| { diff --git a/crates/engine/src/test_api.rs b/crates/engine/src/test_api.rs index 684eb71b1b1..c5f3c3b9f30 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -21,10 +21,7 @@ use crate::{ AccountError, Error, }; -use std::{ - borrow::BorrowMut, - collections::HashMap, -}; +use std::collections::HashMap; /// Record for an emitted event. #[derive(Clone)] @@ -189,14 +186,16 @@ impl Engine { .borrow() .count_reads .get(&account_id) - .unwrap_or(&0); + .unwrap_or(&0) + .clone(); let writes = self .debug_info .borrow() .count_writes .get(&account_id) - .unwrap_or(&0); - (*reads, *writes) + .unwrap_or(&0) + .clone(); + (reads, writes) } /// Returns the total number of reads executed. @@ -233,15 +232,16 @@ impl Engine { /// /// Returns `None` if the `account_id` is non-existent. pub fn count_used_storage_cells(&self, account_id: &[u8]) -> Result { - let cells = self + let cells_len = self .debug_info .borrow() .cells_per_account .get(&account_id.to_owned().into()) .ok_or_else(|| { Error::Account(AccountError::NoAccountForId(account_id.to_vec())) - })?; - Ok(cells.len()) + })? + .len(); + Ok(cells_len) } /// Advances the chain by a single block. diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 44f13e3f14d..ac702740b4e 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -421,8 +421,13 @@ where }) } +/// Returns the value back to the caller of the executed contract. +/// +/// # Note +/// +/// This function stops the execution of the contract immediately. #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] -pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> () +pub fn return_value(return_flags: ReturnFlags, return_value: &R) where R: scale::Encode, { diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 24e7762436c..b95f2a4124f 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -255,7 +255,7 @@ pub trait EnvBackend { R: scale::Encode; #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> () + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where R: scale::Encode; diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 0f4547893f0..f6ddfa1172d 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -14,7 +14,6 @@ use super::EnvInstance; use crate::{ - backend::ReturnType, call::{ Call, CallParams, @@ -47,6 +46,7 @@ use ink_engine::{ ext::Engine, }; use ink_storage_traits::Storable; +use scale::Encode; /// The capacity of the static buffer. /// This is the same size as the ink! on-chain environment. We chose to use the same size @@ -251,7 +251,19 @@ impl EnvBackend for EnvInstance { .map_err(|_| Error::CalleeTrapped) } - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ReturnType + #[cfg(all(not(feature = "std"), target_arch = "wasm32"))] + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! + where + R: scale::Encode, + { + if flags.is_reverted() { + panic!("the off-chain env does not implement revert in `seal_return_value`") + } + self.engine.exec_context.borrow_mut().output = return_value.encode(); + } + + #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where R: scale::Encode, { @@ -463,7 +475,7 @@ impl TypedEnvBackend for EnvInstance { caller: self.engine.exec_context.borrow().callee.clone(), callee: Some(callee.clone().into()), value_transferred: ::decode( - &mut transferred_value.encode().as_slice(), + &mut scale::Encode::encode(transferred_value).as_slice(), )?, block_number: self.engine.exec_context.borrow().block_number, block_timestamp: self.engine.exec_context.borrow().block_timestamp, @@ -546,7 +558,7 @@ impl TypedEnvBackend for EnvInstance { caller: self.engine.exec_context.borrow().callee.clone(), callee: Some(callee.clone().into()), value_transferred: ::decode( - &mut endowment.encode().as_slice(), + &mut scale::Encode::encode(endowment).as_slice(), )?, block_number: self.engine.exec_context.borrow().block_number, block_timestamp: self.engine.exec_context.borrow().block_timestamp, diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index 41811c8f27a..d92803b6b19 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -96,6 +96,7 @@ where instance .engine .chain_extension_handler + .borrow_mut() .register(Box::new(extension)); }) } @@ -200,6 +201,7 @@ where let caller = instance .engine .exec_context + .borrow_mut() .caller .as_ref() .expect("no caller has been set") diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 8335a37a73c..ff9173de307 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -639,7 +639,7 @@ impl Dispatch<'_> { ); } - ::ink::env::return_value::< + Ok(::ink::env::return_value::< ::ink::ConstructorResult< ::core::result::Result<(), &#constructor_value::Error> >, @@ -648,7 +648,7 @@ impl Dispatch<'_> { // Currently no `LangError`s are raised at this level of the // dispatch logic so `Ok` is always returned to the caller. &::ink::ConstructorResult::Ok(output_result.map(|_| ())), - ); + )) } ) }); @@ -841,12 +841,12 @@ impl Dispatch<'_> { push_contract(contract, #mutates_storage); } - ::ink::env::return_value::<::ink::MessageResult::<#message_output>>( + Ok(::ink::env::return_value::<::ink::MessageResult::<#message_output>>( ::ink::env::ReturnFlags::new_with_reverted(is_reverted), // Currently no `LangError`s are raised at this level of the // dispatch logic so `Ok` is always returned to the caller. &::ink::MessageResult::Ok(result), - ) + )) } ) }); @@ -916,7 +916,7 @@ impl Dispatch<'_> { match self { #( #message_execute ),* - }; + } } } From ca5a333eb4f8ef8aaa2ac8be78a209c83ba794d1 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 3 Jan 2023 16:51:24 +0200 Subject: [PATCH 05/43] fix dispatch --- crates/ink/codegen/src/generator/dispatch.rs | 2 ++ examples/delegator/adder/lib.rs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index ff9173de307..808521b736c 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -448,6 +448,7 @@ impl Dispatch<'_> { ::ink::env::ReturnFlags::new_with_reverted(true), &error, ); + panic!("execute_constructor reverted"); } }; @@ -483,6 +484,7 @@ impl Dispatch<'_> { ::ink::env::ReturnFlags::new_with_reverted(true), &error, ); + panic!("execute_constructor reverted"); } }; diff --git a/examples/delegator/adder/lib.rs b/examples/delegator/adder/lib.rs index e784c388d89..db1f09b6e90 100644 --- a/examples/delegator/adder/lib.rs +++ b/examples/delegator/adder/lib.rs @@ -33,6 +33,8 @@ mod adder { #[cfg(test)] mod test { + use ink::primitives::Hash; + #[test] fn it_works() { use super::*; @@ -42,8 +44,8 @@ mod test { }; // register Accumulator & Adder - let hash1 = ink::env::Hash::try_from([10u8; 32]).unwrap(); - let hash2 = ink::env::Hash::try_from([20u8; 32]).unwrap(); + let hash1 = Hash::from([10u8; 32]); + let hash2 = Hash::from([20u8; 32]); ink::env::test::register_contract::(hash1.as_ref()); ink::env::test::register_contract::(hash2.as_ref()); From 2a357212691b29b014c586d7de3e9c5b0222a1ca Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 9 Jan 2023 23:21:38 +0200 Subject: [PATCH 06/43] initial implementation of call flags with tests --- crates/engine/src/exec_context.rs | 2 + crates/engine/src/ext.rs | 41 ++++++++++ crates/env/src/contract.rs | 2 - crates/env/src/engine/off_chain/impls.rs | 100 ++++++++++++++++++++--- crates/env/src/lib.rs | 1 + examples/reentrancy/Cargo.toml | 36 ++++++++ examples/reentrancy/contract1/.gitignore | 9 ++ examples/reentrancy/contract1/Cargo.toml | 26 ++++++ examples/reentrancy/contract1/lib.rs | 76 +++++++++++++++++ examples/reentrancy/contract2/.gitignore | 9 ++ examples/reentrancy/contract2/Cargo.toml | 29 +++++++ examples/reentrancy/contract2/lib.rs | 56 +++++++++++++ examples/reentrancy/lib.rs | 45 ++++++++++ 13 files changed, 420 insertions(+), 12 deletions(-) create mode 100644 examples/reentrancy/Cargo.toml create mode 100755 examples/reentrancy/contract1/.gitignore create mode 100755 examples/reentrancy/contract1/Cargo.toml create mode 100755 examples/reentrancy/contract1/lib.rs create mode 100755 examples/reentrancy/contract2/.gitignore create mode 100755 examples/reentrancy/contract2/Cargo.toml create mode 100755 examples/reentrancy/contract2/lib.rs create mode 100644 examples/reentrancy/lib.rs diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index ad5fae86a8b..1fd41efb629 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -47,6 +47,8 @@ pub struct ExecContext { pub input: Vec, /// The output buffer of the call. pub output: Vec, + /// The flags of the call. + pub call_flags: u32, } impl ExecContext { diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index c87a876ff23..316f5d3c96c 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -118,9 +118,50 @@ impl ReturnCode { #[derive(Default)] pub struct ContractStore { pub instantiated: HashMap, Vec>, + pub entrance_count: HashMap, u32>, + pub allow_reentry: HashMap, bool>, pub deployed: HashMap, Contract>, } +impl ContractStore { + pub fn get_entrance_count(&self, callee: Vec) -> u32 { + self.entrance_count.get(&callee).unwrap_or(&0).clone() + } + + pub fn get_allow_reentry(&self, callee: Vec) -> bool { + self.allow_reentry.get(&callee).unwrap_or(&false).clone() + } + + pub fn set_allow_reentry(&mut self, callee: Vec, allow: bool) { + if allow == false { + self.allow_reentry.remove(&callee); + } else { + self.allow_reentry.insert(callee, allow); + } + } + + pub fn increase_entrance(&mut self, callee: Vec) -> Result { + let entrance = self.entrance_count.get(&callee).unwrap_or(&0).clone(); + + self.entrance_count.insert(callee, entrance + 1); + + Ok(()) + } + + pub fn decrease_entrance(&mut self, callee: Vec) -> Result { + let entrance = self.entrance_count.get(&callee).unwrap_or(&0).clone(); + + if entrance == 0 { + // todo: update error + return Err(Error::Unknown) + } + + self.entrance_count.insert(callee, entrance - 1); + + Ok(()) + } +} + pub struct Contract { pub deploy: fn(), pub call: fn(), diff --git a/crates/env/src/contract.rs b/crates/env/src/contract.rs index 9ac7b2b3ae1..b5c3daafd26 100644 --- a/crates/env/src/contract.rs +++ b/crates/env/src/contract.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Contract's stuff related to the environment. - /// Entrypoint of the contract to execute constructors or messages. pub trait Entrypoint { /// Entrypoint to run a constructor for the contract. It deploys the contract to the environment. diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index f6ddfa1172d..34ebb902c11 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -47,7 +47,6 @@ use ink_engine::{ }; use ink_storage_traits::Storable; use scale::Encode; - /// The capacity of the static buffer. /// This is the same size as the ink! on-chain environment. We chose to use the same size /// to be as close to the on-chain behavior as possible. @@ -262,13 +261,14 @@ impl EnvBackend for EnvInstance { self.engine.exec_context.borrow_mut().output = return_value.encode(); } + // todo: check errors #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where R: scale::Encode, { if flags.is_reverted() { - panic!("the off-chain env does not implement revert in `seal_return_value`") + unimplemented!("revert is not implemented yet") } self.engine.exec_context.borrow_mut().output = return_value.encode(); } @@ -382,8 +382,21 @@ impl EnvBackend for EnvInstance { Ok(decoded) } - fn set_code_hash(&mut self, _code_hash: &[u8]) -> Result<()> { - unimplemented!("off-chain environment does not support `set_code_hash`") + fn set_code_hash(&mut self, code_hash: &[u8]) -> Result<()> { + let account_id = self + .engine + .exec_context + .borrow() + .callee + .clone() + .expect("callee is none"); + + self.engine + .contracts + .borrow_mut() + .instantiated + .insert(account_id.as_bytes().to_vec(), code_hash.to_vec()); + Ok(()) } } @@ -466,11 +479,59 @@ impl TypedEnvBackend for EnvInstance { { let callee = params.callee().as_ref().to_vec(); let _gas_limit = params.gas_limit(); - // TODO: Add support of `call_flags` - let _call_flags = params.call_flags().into_u32(); + + let call_flags = params.call_flags().into_u32(); + let transferred_value = params.transferred_value(); let input = params.exec_input(); + let previous_call_flags = self.engine.exec_context.borrow().call_flags; + + let forward_input = (previous_call_flags & 1) != 0; + let clone_input = ((previous_call_flags & 2) >> 1) != 0; + let tail_call = ((previous_call_flags & 4) >> 2) != 0; + let allow_reentry = ((previous_call_flags & 8) >> 3) != 0; + + // todo: remove unwrap + let caller = self.engine.exec_context.borrow().callee.clone(); + + if caller.is_some() { + self.engine.contracts.borrow_mut().set_allow_reentry( + caller.clone().unwrap().as_bytes().to_vec(), + allow_reentry, + ); + } + + if !self + .engine + .contracts + .borrow() + .get_allow_reentry(callee.clone()) + && self + .engine + .contracts + .borrow() + .get_entrance_count(callee.clone()) + > 0 + { + // todo: update error + return Err(Error::CalleeReverted) + } + + self.engine + .contracts + .borrow_mut() + .increase_entrance(callee.clone())?; + + let input = if forward_input { + // todo: maybe remove clone + self.engine.exec_context.borrow().input.clone() + } else if clone_input { + self.engine.exec_context.borrow().input.clone() + } else { + input.encode() + }; + let callee_context = ExecContext { caller: self.engine.exec_context.borrow().callee.clone(), callee: Some(callee.clone().into()), @@ -479,11 +540,12 @@ impl TypedEnvBackend for EnvInstance { )?, block_number: self.engine.exec_context.borrow().block_number, block_timestamp: self.engine.exec_context.borrow().block_timestamp, - input: input.encode(), + input, output: vec![], + call_flags, }; - let previous_context = self.engine.exec_context.replace(callee_context); + let mut previous_context = self.engine.exec_context.replace(callee_context); let code_hash = self .engine @@ -506,7 +568,24 @@ impl TypedEnvBackend for EnvInstance { call_fn(); let return_value = - R::decode(&mut self.engine.exec_context.borrow().output.as_slice())?; + R::decode(&mut self.engine.exec_context.borrow().output.as_slice().clone())?; + + if tail_call { + previous_context.output = self.engine.exec_context.borrow().output.clone(); + } + + self.engine + .contracts + .borrow_mut() + .decrease_entrance(callee)?; + + if caller.is_some() { + self.engine + .contracts + .borrow_mut() + .allow_reentry + .remove(&caller.unwrap().as_bytes().to_vec()); + } let _ = self.engine.exec_context.replace(previous_context); @@ -564,6 +643,7 @@ impl TypedEnvBackend for EnvInstance { block_timestamp: self.engine.exec_context.borrow().block_timestamp, input: input.encode(), output: vec![], + call_flags: 0, }; let previous_context = self.engine.exec_context.replace(callee_context); @@ -631,7 +711,7 @@ impl TypedEnvBackend for EnvInstance { where E: Environment, { - unimplemented!("off-chain environment does not support cross-contract calls") + unimplemented!("off-chain environment does not support `seal_caller_is_origin`") } fn code_hash(&mut self, account: &E::AccountId) -> Result diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index 6ed3e727cf9..e0016bdb88b 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -73,6 +73,7 @@ mod arithmetic; mod backend; pub mod call; pub mod chain_extension; +/// documentation ? pub mod contract; mod engine; mod error; diff --git a/examples/reentrancy/Cargo.toml b/examples/reentrancy/Cargo.toml new file mode 100644 index 00000000000..e996beb5f61 --- /dev/null +++ b/examples/reentrancy/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "test" +version = "4.0.0-beta" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } +contract1 = { path = "./contract1", default-features = false, features = ["ink-as-dependency"] } +contract2 = { path = "./contract2", default-features = false, features = ["ink-as-dependency"] } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } + +[lib] +name = "test" +path = "lib.rs" +crate-type = ["cdylib"] + +[features] +default = ["std"] +std = [ + "ink/std", + "scale/std", + "scale-info/std", + "contract1/std", + "contract2/std", +] +ink-as-dependency = [] + +[workspace] +members = [ + "contract1", + "contract2" +] diff --git a/examples/reentrancy/contract1/.gitignore b/examples/reentrancy/contract1/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/examples/reentrancy/contract1/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/examples/reentrancy/contract1/Cargo.toml b/examples/reentrancy/contract1/Cargo.toml new file mode 100755 index 00000000000..2a50d699006 --- /dev/null +++ b/examples/reentrancy/contract1/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "contract1" +version = "4.0.0-beta" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } + +[lib] +name = "contract1" +path = "lib.rs" +crate-type = ["cdylib", "rlib"] + +[features] +default = ["std"] +std = [ + "ink/std", + "scale/std", + "scale-info/std", +] +ink-as-dependency = [] diff --git a/examples/reentrancy/contract1/lib.rs b/examples/reentrancy/contract1/lib.rs new file mode 100755 index 00000000000..6907273bb0c --- /dev/null +++ b/examples/reentrancy/contract1/lib.rs @@ -0,0 +1,76 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use self::contract1::{ + Contract1, + Contract1Ref, +}; + +#[ink::contract] +mod contract1 { + use ink::env::{ + call::{ + build_call, + Call, + ExecutionInput, + }, + CallFlags, + DefaultEnvironment, + }; + + /// Defines the storage of your contract. + /// Add new fields to the below struct in order + /// to add new static storage fields to your contract. + #[derive(Default)] + #[ink(storage)] + pub struct Contract1 { + /// Stores a single `bool` value on the storage. + value: u32, + + callee: AccountId, + } + + impl Contract1 { + /// Constructor that initializes the `bool` value to the given `init_value`. + #[ink(constructor)] + pub fn new() -> Self { + Default::default() + } + + #[ink(message)] + pub fn get(&self) -> u32 { + self.value + } + + #[ink(message)] + pub fn get_address(&self) -> AccountId { + self.env().account_id() + } + + #[ink(message)] + pub fn set_callee(&mut self, callee: AccountId) { + self.callee = callee; + } + + #[ink(message)] + pub fn get_callee(&self) -> AccountId { + self.callee + } + + #[ink(message, selector = _)] + pub fn inc(&mut self) -> u32 { + self.value = self.value + 1; + + if self.value > 1 { + return self.value + } + + build_call::() + .call_type(Call::new().callee(self.callee)) + .call_flags(CallFlags::default().set_allow_reentry(true)) + .fire() + .unwrap(); + + 100 + } + } +} diff --git a/examples/reentrancy/contract2/.gitignore b/examples/reentrancy/contract2/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/examples/reentrancy/contract2/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/examples/reentrancy/contract2/Cargo.toml b/examples/reentrancy/contract2/Cargo.toml new file mode 100755 index 00000000000..d236d27cc9c --- /dev/null +++ b/examples/reentrancy/contract2/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "contract2" +version = "4.0.0-beta" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +contract1 = { path = "../contract1", default-features = false, features = ["ink-as-dependency"] } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } + +[lib] +name = "contract2" +path = "lib.rs" +crate-type = ["cdylib", "rlib"] + +[features] +default = ["std"] +std = [ + "ink/std", + "scale/std", + "scale-info/std", + "contract1/std", +] +ink-as-dependency = [] diff --git a/examples/reentrancy/contract2/lib.rs b/examples/reentrancy/contract2/lib.rs new file mode 100755 index 00000000000..4e017da14d4 --- /dev/null +++ b/examples/reentrancy/contract2/lib.rs @@ -0,0 +1,56 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use self::contract2::{ + Contract2, + Contract2Ref, +}; + +#[ink::contract] +mod contract2 { + use contract1::Contract1Ref; + + /// Defines the storage of your contract. + /// Add new fields to the below struct in order + /// to add new static storage fields to your contract. + #[ink(storage)] + pub struct Contract2 { + /// Stores a single `bool` value on the storage. + value: u32, + + callee: Contract1Ref, + } + + impl Contract2 { + /// Constructor that initializes the `bool` value to the given `init_value`. + #[ink(constructor)] + pub fn new(callee: Contract1Ref) -> Self { + Self { value: 0, callee } + } + + /// Simply returns the current value of our `bool`. + #[ink(message)] + pub fn get(&self) -> u32 { + self.value + } + + #[ink(message)] + pub fn set_callee(&mut self, callee: Contract1Ref) { + self.callee = callee; + } + + #[ink(message)] + pub fn get_callee(&self) -> AccountId { + self.callee.get_address() + } + + #[ink(message)] + pub fn get_address(&self) -> AccountId { + self.env().account_id() + } + + #[ink(message)] + pub fn inc(&mut self) { + self.callee.inc(); + } + } +} diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs new file mode 100644 index 00000000000..4baa3b627a8 --- /dev/null +++ b/examples/reentrancy/lib.rs @@ -0,0 +1,45 @@ +#[cfg(test)] +mod test { + use contract1::{ + Contract1, + Contract1Ref, + }; + use contract2::{ + Contract2, + Contract2Ref, + }; + use ink::primitives::Hash; + + #[test] + pub fn reentrancy_works() { + let hash1 = Hash::from([10u8; 32]); + let hash2 = Hash::from([20u8; 32]); + + ink::env::test::register_contract::(hash1.as_ref()); + ink::env::test::register_contract::(hash2.as_ref()); + + let mut contract1 = Contract1Ref::new() + .code_hash(hash1.clone()) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `Contract1Ref` contract"); + let mut contract2 = Contract2Ref::new(contract1.clone()) + .code_hash(hash2.clone()) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `Contract2Ref` contract"); + + let address1 = contract1.get_address(); + + let address2 = contract2.get_address(); + + contract1.set_callee(address2); + + assert_eq!(contract1.get_callee(), address2); + assert_eq!(contract2.get_callee(), address1); + + assert_eq!(contract1.inc(), 2); + } +} From 52485b98acb7097e1cd13ddfb12f58e3a96867fb Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 10 Jan 2023 11:52:07 +0200 Subject: [PATCH 07/43] fix call_flags bug --- crates/engine/src/exec_context.rs | 2 -- crates/env/src/engine/off_chain/impls.rs | 36 ++++++++++++++------ crates/ink/codegen/src/generator/dispatch.rs | 2 +- examples/reentrancy/contract1/lib.rs | 24 ++++++++++--- examples/reentrancy/contract2/lib.rs | 3 +- examples/reentrancy/lib.rs | 5 ++- 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index 1fd41efb629..ad5fae86a8b 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -47,8 +47,6 @@ pub struct ExecContext { pub input: Vec, /// The output buffer of the call. pub output: Vec, - /// The flags of the call. - pub call_flags: u32, } impl ExecContext { diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 34ebb902c11..d0d851dde28 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -485,14 +485,11 @@ impl TypedEnvBackend for EnvInstance { let transferred_value = params.transferred_value(); let input = params.exec_input(); - let previous_call_flags = self.engine.exec_context.borrow().call_flags; + let forward_input = (call_flags & 1) != 0; + let clone_input = ((call_flags & 2) >> 1) != 0; + let tail_call = ((call_flags & 4) >> 2) != 0; + let allow_reentry = ((call_flags & 8) >> 3) != 0; - let forward_input = (previous_call_flags & 1) != 0; - let clone_input = ((previous_call_flags & 2) >> 1) != 0; - let tail_call = ((previous_call_flags & 4) >> 2) != 0; - let allow_reentry = ((previous_call_flags & 8) >> 3) != 0; - - // todo: remove unwrap let caller = self.engine.exec_context.borrow().callee.clone(); if caller.is_some() { @@ -500,6 +497,7 @@ impl TypedEnvBackend for EnvInstance { caller.clone().unwrap().as_bytes().to_vec(), allow_reentry, ); + println!("set allow reentry for {:?} to {}", caller, allow_reentry); } if !self @@ -515,6 +513,18 @@ impl TypedEnvBackend for EnvInstance { > 0 { // todo: update error + panic!( + "reentrancy not allowed {} {} {}", + self.engine + .contracts + .borrow() + .get_allow_reentry(callee.clone()), + self.engine + .contracts + .borrow() + .get_entrance_count(callee.clone()), + allow_reentry + ); return Err(Error::CalleeReverted) } @@ -542,7 +552,6 @@ impl TypedEnvBackend for EnvInstance { block_timestamp: self.engine.exec_context.borrow().block_timestamp, input, output: vec![], - call_flags, }; let mut previous_context = self.engine.exec_context.replace(callee_context); @@ -553,7 +562,10 @@ impl TypedEnvBackend for EnvInstance { .borrow() .instantiated .get(&callee) - .ok_or(Error::NotCallable)? + .ok_or_else(|| { + panic!("not callable"); + Error::NotCallable + })? .clone(); let call_fn = self @@ -562,7 +574,10 @@ impl TypedEnvBackend for EnvInstance { .borrow() .deployed .get(&code_hash) - .ok_or(Error::CodeNotFound)? + .ok_or_else(|| { + panic!("code not found"); + Error::CodeNotFound + })? .call; call_fn(); @@ -643,7 +658,6 @@ impl TypedEnvBackend for EnvInstance { block_timestamp: self.engine.exec_context.borrow().block_timestamp, input: input.encode(), output: vec![], - call_flags: 0, }; let previous_context = self.engine.exec_context.replace(callee_context); diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 808521b736c..e10ba074712 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -484,7 +484,7 @@ impl Dispatch<'_> { ::ink::env::ReturnFlags::new_with_reverted(true), &error, ); - panic!("execute_constructor reverted"); + panic!("execute_message reverted"); } }; diff --git a/examples/reentrancy/contract1/lib.rs b/examples/reentrancy/contract1/lib.rs index 6907273bb0c..ef5c89af1d9 100755 --- a/examples/reentrancy/contract1/lib.rs +++ b/examples/reentrancy/contract1/lib.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate core; + pub use self::contract1::{ Contract1, Contract1Ref, @@ -11,11 +13,11 @@ mod contract1 { call::{ build_call, Call, - ExecutionInput, }, CallFlags, DefaultEnvironment, }; + use std::mem::ManuallyDrop; /// Defines the storage of your contract. /// Add new fields to the below struct in order @@ -56,21 +58,35 @@ mod contract1 { self.callee } - #[ink(message, selector = _)] + #[ink(message)] pub fn inc(&mut self) -> u32 { self.value = self.value + 1; + println!("value {}", self.value); if self.value > 1 { return self.value } + ink::env::set_contract_storage( + &::KEY, + self, + ); + build_call::() .call_type(Call::new().callee(self.callee)) .call_flags(CallFlags::default().set_allow_reentry(true)) .fire() - .unwrap(); + .unwrap_or_else(|err| panic!("failed to call callee: {:?}", err)); - 100 + let mut state = ink::env::get_contract_storage( + &::KEY, + ) + .unwrap_or_else(|error| panic!("Failed to load contract state: {:?}", error)) + .unwrap_or_else(|| panic!("Contract state is not initialized")); + core::mem::swap(self, &mut state); + let _ = ManuallyDrop::new(state); + + self.value } } } diff --git a/examples/reentrancy/contract2/lib.rs b/examples/reentrancy/contract2/lib.rs index 4e017da14d4..3d9ed388c51 100755 --- a/examples/reentrancy/contract2/lib.rs +++ b/examples/reentrancy/contract2/lib.rs @@ -48,8 +48,9 @@ mod contract2 { self.env().account_id() } - #[ink(message)] + #[ink(message, selector = _)] pub fn inc(&mut self) { + println!("{}", self.callee.get()); self.callee.inc(); } } diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index 4baa3b627a8..50f61af6e5b 100644 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -24,7 +24,7 @@ mod test { .salt_bytes([0u8; 0]) .instantiate() .expect("failed at instantiating the `Contract1Ref` contract"); - let mut contract2 = Contract2Ref::new(contract1.clone()) + let contract2 = Contract2Ref::new(contract1.clone()) .code_hash(hash2.clone()) .endowment(0) .salt_bytes([0u8; 0]) @@ -35,6 +35,9 @@ mod test { let address2 = contract2.get_address(); + println!("address1: {:?}", address1); + println!("address2: {:?}", address2); + contract1.set_callee(address2); assert_eq!(contract1.get_callee(), address2); From e347054440120b3a275510481d4e3f5633868c43 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 10 Jan 2023 22:12:35 +0200 Subject: [PATCH 08/43] implement revert storage --- crates/engine/src/exec_context.rs | 2 ++ crates/engine/src/ext.rs | 1 + crates/env/src/engine/off_chain/impls.rs | 46 ++++++++++++------------ crates/env/src/error.rs | 2 ++ examples/reentrancy/contract1/lib.rs | 35 +++++++++++++----- examples/reentrancy/contract2/lib.rs | 11 +++++- examples/reentrancy/lib.rs | 8 ++--- 7 files changed, 67 insertions(+), 38 deletions(-) diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index ad5fae86a8b..39371f0d9fa 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -47,6 +47,8 @@ pub struct ExecContext { pub input: Vec, /// The output buffer of the call. pub output: Vec, + /// The return flags of the call + pub return_flags: u32, } impl ExecContext { diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 316f5d3c96c..0ed1d13df7d 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -118,6 +118,7 @@ impl ReturnCode { #[derive(Default)] pub struct ContractStore { pub instantiated: HashMap, Vec>, + pub storage_keys: HashMap, Vec>, pub entrance_count: HashMap, u32>, pub allow_reentry: HashMap, bool>, pub deployed: HashMap, Contract>, diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index d0d851dde28..3524340b44d 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -268,7 +268,7 @@ impl EnvBackend for EnvInstance { R: scale::Encode, { if flags.is_reverted() { - unimplemented!("revert is not implemented yet") + self.engine.exec_context.borrow_mut().return_flags = 1; } self.engine.exec_context.borrow_mut().output = return_value.encode(); } @@ -497,7 +497,6 @@ impl TypedEnvBackend for EnvInstance { caller.clone().unwrap().as_bytes().to_vec(), allow_reentry, ); - println!("set allow reentry for {:?} to {}", caller, allow_reentry); } if !self @@ -512,20 +511,7 @@ impl TypedEnvBackend for EnvInstance { .get_entrance_count(callee.clone()) > 0 { - // todo: update error - panic!( - "reentrancy not allowed {} {} {}", - self.engine - .contracts - .borrow() - .get_allow_reentry(callee.clone()), - self.engine - .contracts - .borrow() - .get_entrance_count(callee.clone()), - allow_reentry - ); - return Err(Error::CalleeReverted) + return Err(Error::ReentranceDenied) } self.engine @@ -552,6 +538,7 @@ impl TypedEnvBackend for EnvInstance { block_timestamp: self.engine.exec_context.borrow().block_timestamp, input, output: vec![], + return_flags: 0, }; let mut previous_context = self.engine.exec_context.replace(callee_context); @@ -562,10 +549,7 @@ impl TypedEnvBackend for EnvInstance { .borrow() .instantiated .get(&callee) - .ok_or_else(|| { - panic!("not callable"); - Error::NotCallable - })? + .ok_or_else(|| Error::NotCallable)? .clone(); let call_fn = self @@ -574,14 +558,27 @@ impl TypedEnvBackend for EnvInstance { .borrow() .deployed .get(&code_hash) - .ok_or_else(|| { - panic!("code not found"); - Error::CodeNotFound - })? + .ok_or_else(|| Error::CodeNotFound)? .call; + let storage = self + .engine + .database + .borrow() + .get_from_contract_storage(&callee.as_slice(), &[0; 4]) + .expect("contract storage not found") + .clone(); + call_fn(); + if self.engine.exec_context.borrow().return_flags & 1 > 0 { + self.engine + .database + .borrow_mut() + .insert_into_contract_storage(&callee.as_slice(), &[0; 4], storage) + .unwrap(); + } + let return_value = R::decode(&mut self.engine.exec_context.borrow().output.as_slice().clone())?; @@ -658,6 +655,7 @@ impl TypedEnvBackend for EnvInstance { block_timestamp: self.engine.exec_context.borrow().block_timestamp, input: input.encode(), output: vec![], + return_flags: 0, }; let previous_context = self.engine.exec_context.replace(callee_context); diff --git a/crates/env/src/error.rs b/crates/env/src/error.rs index b8b613b0b08..127074e46fc 100644 --- a/crates/env/src/error.rs +++ b/crates/env/src/error.rs @@ -49,6 +49,8 @@ pub enum Error { LoggingDisabled, /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. EcdsaRecoveryFailed, + /// documentation + ReentranceDenied, } /// A result of environmental operations. diff --git a/examples/reentrancy/contract1/lib.rs b/examples/reentrancy/contract1/lib.rs index ef5c89af1d9..343a511d937 100755 --- a/examples/reentrancy/contract1/lib.rs +++ b/examples/reentrancy/contract1/lib.rs @@ -5,17 +5,21 @@ extern crate core; pub use self::contract1::{ Contract1, Contract1Ref, + Error, }; #[ink::contract] mod contract1 { - use ink::env::{ - call::{ - build_call, - Call, + use ink::{ + env::{ + call::{ + build_call, + Call, + }, + CallFlags, + DefaultEnvironment, }, - CallFlags, - DefaultEnvironment, + primitives::Key, }; use std::mem::ManuallyDrop; @@ -31,6 +35,13 @@ mod contract1 { callee: AccountId, } + #[derive(scale::Encode, scale::Decode, Debug, Ord, PartialOrd, Eq, PartialEq)] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub enum Error { + CalleeReverted, + Unknown, + } + impl Contract1 { /// Constructor that initializes the `bool` value to the given `init_value`. #[ink(constructor)] @@ -59,12 +70,18 @@ mod contract1 { } #[ink(message)] - pub fn inc(&mut self) -> u32 { + pub fn get_key(&self) -> Key { + ::KEY + } + + #[allow(unreachable_code)] + #[ink(message)] + pub fn inc(&mut self) -> Result { self.value = self.value + 1; println!("value {}", self.value); if self.value > 1 { - return self.value + return Ok(self.value) } ink::env::set_contract_storage( @@ -86,7 +103,7 @@ mod contract1 { core::mem::swap(self, &mut state); let _ = ManuallyDrop::new(state); - self.value + Ok(self.value) } } } diff --git a/examples/reentrancy/contract2/lib.rs b/examples/reentrancy/contract2/lib.rs index 3d9ed388c51..531d99bc0f0 100755 --- a/examples/reentrancy/contract2/lib.rs +++ b/examples/reentrancy/contract2/lib.rs @@ -8,6 +8,7 @@ pub use self::contract2::{ #[ink::contract] mod contract2 { use contract1::Contract1Ref; + use ink::primitives::Key; /// Defines the storage of your contract. /// Add new fields to the below struct in order @@ -48,9 +49,17 @@ mod contract2 { self.env().account_id() } + #[ink(message)] + pub fn get_key(&self) -> Key { + ::KEY + } + #[ink(message, selector = _)] pub fn inc(&mut self) { - println!("{}", self.callee.get()); + ink::env::set_contract_storage( + &::KEY, + self, + ); self.callee.inc(); } } diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index 50f61af6e5b..e67930a002e 100644 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -24,6 +24,7 @@ mod test { .salt_bytes([0u8; 0]) .instantiate() .expect("failed at instantiating the `Contract1Ref` contract"); + let contract2 = Contract2Ref::new(contract1.clone()) .code_hash(hash2.clone()) .endowment(0) @@ -35,14 +36,13 @@ mod test { let address2 = contract2.get_address(); - println!("address1: {:?}", address1); - println!("address2: {:?}", address2); - contract1.set_callee(address2); assert_eq!(contract1.get_callee(), address2); assert_eq!(contract2.get_callee(), address1); - assert_eq!(contract1.inc(), 2); + assert_eq!(contract1.inc(), Ok(2)); + assert_eq!(contract1.get(), 2); + assert_eq!(contract2.get(), 0); } } From 1af298d91398634524fa8470604abd35b3b04179 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 11 Jan 2023 16:20:13 +0200 Subject: [PATCH 09/43] minor changes and apply suggestions --- crates/engine/src/ext.rs | 118 +++++++++++++++++++---- crates/env/src/engine/off_chain/impls.rs | 77 ++++----------- examples/reentrancy/contract2/lib.rs | 2 +- 3 files changed, 118 insertions(+), 79 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 0ed1d13df7d..200b14418fd 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -102,6 +102,8 @@ define_error_codes! { LoggingDisabled = 9, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. EcdsaRecoveryFailed = 11, + /// Encountered the reentrancy that is not allowed + ReentranceDenied = 12, } /// The raw return code returned by the host side. @@ -116,7 +118,7 @@ impl ReturnCode { } #[derive(Default)] -pub struct ContractStore { +pub struct ContractStorage { pub instantiated: HashMap, Vec>, pub storage_keys: HashMap, Vec>, pub entrance_count: HashMap, u32>, @@ -124,7 +126,7 @@ pub struct ContractStore { pub deployed: HashMap, Contract>, } -impl ContractStore { +impl ContractStorage { pub fn get_entrance_count(&self, callee: Vec) -> u32 { self.entrance_count.get(&callee).unwrap_or(&0).clone() } @@ -141,25 +143,29 @@ impl ContractStore { } } - pub fn increase_entrance(&mut self, callee: Vec) -> Result { - let entrance = self.entrance_count.get(&callee).unwrap_or(&0).clone(); - - self.entrance_count.insert(callee, entrance + 1); + pub fn increase_entrance_count(&mut self, callee: Vec) -> Result { + let entrance_count = self + .entrance_count + .get(&callee) + .map_or(1, |count| count + 1); + self.entrance_count.insert(callee, entrance_count); Ok(()) } - pub fn decrease_entrance(&mut self, callee: Vec) -> Result { - let entrance = self.entrance_count.get(&callee).unwrap_or(&0).clone(); - - if entrance == 0 { - // todo: update error - return Err(Error::Unknown) - } - - self.entrance_count.insert(callee, entrance - 1); - - Ok(()) + pub fn decrease_entrance_count(&mut self, callee: Vec) -> Result { + self.entrance_count.get(&callee).map_or_else( + || Err(Error::CalleeTrapped), + |count| { + if *count == 0 { + self.entrance_count.remove(&callee); + Err(Error::CalleeTrapped) + } else { + self.entrance_count.insert(callee, count - 1); + Ok(()) + } + }, + ) } } @@ -184,7 +190,7 @@ pub struct Engine { /// Handler for registered chain extensions. pub chain_extension_handler: Rc>, /// Contracts' store. - pub contracts: Rc>, + pub contracts: Rc>, } /// The chain specification. @@ -224,7 +230,7 @@ impl Engine { debug_info: Rc::new(RefCell::new(DebugInfo::new())), chain_spec: Rc::new(RefCell::new(ChainSpec::default())), chain_extension_handler: Rc::new(RefCell::new(ChainExtensionHandler::new())), - contracts: Rc::new(RefCell::new(ContractStore::default())), + contracts: Rc::new(RefCell::new(ContractStorage::default())), } } } @@ -614,6 +620,80 @@ impl Engine { .deployed .insert(hash.to_vec(), Contract { deploy, call }) } + + /// Apply call flags for the call and return the input that might be changed + pub fn apply_code_flags_before_call( + &mut self, + caller: Option, + callee: Vec, + call_flags: u32, + input: Vec, + ) -> core::result::Result, Error> { + let forward_input = (call_flags & 1) != 0; + let clone_input = ((call_flags & 2) >> 1) != 0; + let allow_reentry = ((call_flags & 8) >> 3) != 0; + + // Allow/deny reentrancy to the caller + if caller.is_some() { + self.contracts.borrow_mut().set_allow_reentry( + caller.clone().unwrap().as_bytes().to_vec(), + allow_reentry, + ); + } + + // Check if reentrance that is not allowed is encountered + if !self.contracts.borrow().get_allow_reentry(callee.clone()) + && self.contracts.borrow().get_entrance_count(callee.clone()) > 0 + { + return Err(Error::ReentranceDenied) + } + + self.contracts + .borrow_mut() + .increase_entrance_count(callee.clone())?; + + let new_input = if forward_input { + let previous_input = self.exec_context.borrow().input.clone(); + + // delete the input because we will forward it + self.exec_context.borrow_mut().input.clear(); + + previous_input + } else if clone_input { + self.exec_context.borrow().input.clone() + } else { + input + }; + + Ok(new_input) + } + + /// Apply call flags after the call + pub fn apply_code_flags_after_call( + &mut self, + caller: Option, + callee: Vec, + call_flags: u32, + output: Vec, + ) -> core::result::Result<(), Error> { + let tail_call = ((call_flags & 4) >> 2) != 0; + + if tail_call { + self.exec_context.borrow_mut().output = output.clone(); + } + + self.contracts + .borrow_mut() + .decrease_entrance_count(callee)?; + + if caller.is_some() { + self.contracts + .borrow_mut() + .allow_reentry + .remove(&caller.unwrap().as_bytes().to_vec()); + } + Ok(()) + } } /// Copies the `slice` into `output`. diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 3524340b44d..01cd7078f36 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -114,6 +114,7 @@ impl From for crate::Error { ext::Error::NotCallable => Self::NotCallable, ext::Error::LoggingDisabled => Self::LoggingDisabled, ext::Error::EcdsaRecoveryFailed => Self::EcdsaRecoveryFailed, + ext::Error::ReentranceDenied => Self::ReentranceDenied, } } } @@ -261,7 +262,6 @@ impl EnvBackend for EnvInstance { self.engine.exec_context.borrow_mut().output = return_value.encode(); } - // todo: check errors #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where @@ -483,50 +483,16 @@ impl TypedEnvBackend for EnvInstance { let call_flags = params.call_flags().into_u32(); let transferred_value = params.transferred_value(); - let input = params.exec_input(); - - let forward_input = (call_flags & 1) != 0; - let clone_input = ((call_flags & 2) >> 1) != 0; - let tail_call = ((call_flags & 4) >> 2) != 0; - let allow_reentry = ((call_flags & 8) >> 3) != 0; let caller = self.engine.exec_context.borrow().callee.clone(); - if caller.is_some() { - self.engine.contracts.borrow_mut().set_allow_reentry( - caller.clone().unwrap().as_bytes().to_vec(), - allow_reentry, - ); - } - - if !self - .engine - .contracts - .borrow() - .get_allow_reentry(callee.clone()) - && self - .engine - .contracts - .borrow() - .get_entrance_count(callee.clone()) - > 0 - { - return Err(Error::ReentranceDenied) - } - - self.engine - .contracts - .borrow_mut() - .increase_entrance(callee.clone())?; - - let input = if forward_input { - // todo: maybe remove clone - self.engine.exec_context.borrow().input.clone() - } else if clone_input { - self.engine.exec_context.borrow().input.clone() - } else { - input.encode() - }; + // apply call flags before making a call and return the input that might be changed after that + let input = self.engine.apply_code_flags_before_call( + caller.clone(), + callee.clone(), + call_flags, + params.exec_input().encode(), + )?; let callee_context = ExecContext { caller: self.engine.exec_context.borrow().callee.clone(), @@ -561,6 +527,7 @@ impl TypedEnvBackend for EnvInstance { .ok_or_else(|| Error::CodeNotFound)? .call; + // save previous version of storage in case call will revert let storage = self .engine .database @@ -571,6 +538,7 @@ impl TypedEnvBackend for EnvInstance { call_fn(); + // revert contract's state in case of error if self.engine.exec_context.borrow().return_flags & 1 > 0 { self.engine .database @@ -582,25 +550,17 @@ impl TypedEnvBackend for EnvInstance { let return_value = R::decode(&mut self.engine.exec_context.borrow().output.as_slice().clone())?; - if tail_call { - previous_context.output = self.engine.exec_context.borrow().output.clone(); - } + let output = self.engine.exec_context.borrow().output.clone(); - self.engine - .contracts - .borrow_mut() - .decrease_entrance(callee)?; - - if caller.is_some() { - self.engine - .contracts - .borrow_mut() - .allow_reentry - .remove(&caller.unwrap().as_bytes().to_vec()); - } + // if the call was reverted, previous one should be reverted too + previous_context.return_flags |= self.engine.exec_context.borrow().return_flags; let _ = self.engine.exec_context.replace(previous_context); + // apply code flags after the call + self.engine + .apply_code_flags_after_call(caller, callee, call_flags, output)?; + Ok(return_value) } @@ -629,13 +589,12 @@ impl TypedEnvBackend for EnvInstance { Salt: AsRef<[u8]>, { let code_hash = params.code_hash().as_ref().to_vec(); - // Gas is not supported by off env. + // Gas is not supported by off-chain env. let _gas_limit = params.gas_limit(); let endowment = params.endowment(); let input = params.exec_input(); let salt_bytes = params.salt_bytes(); - // TODO: User ruels from the `contract-pallet` to create an `AccountId`. let mut callee = [0u8; 32]; Sha2x256::hash( [code_hash.as_ref(), salt_bytes.as_ref()] diff --git a/examples/reentrancy/contract2/lib.rs b/examples/reentrancy/contract2/lib.rs index 531d99bc0f0..7770def1cf4 100755 --- a/examples/reentrancy/contract2/lib.rs +++ b/examples/reentrancy/contract2/lib.rs @@ -60,7 +60,7 @@ mod contract2 { &::KEY, self, ); - self.callee.inc(); + self.callee.inc().unwrap(); } } } From d512e530ea9e321e2b53ef10e1122b429cf4ca84 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 11 Jan 2023 16:21:53 +0200 Subject: [PATCH 10/43] fix bug --- crates/engine/src/ext.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 200b14418fd..c8377e2d3ff 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -161,11 +161,13 @@ impl ContractStorage { self.entrance_count.remove(&callee); Err(Error::CalleeTrapped) } else { - self.entrance_count.insert(callee, count - 1); - Ok(()) + Ok(count - 1) } }, - ) + )?; + + self.entrance_count.insert(callee, entrance_count); + Ok(()) } } From e7ea5b5d590ee404e5fe5ec83a96d5c2fc6a2900 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 11 Jan 2023 16:22:34 +0200 Subject: [PATCH 11/43] fix bug --- crates/engine/src/ext.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index c8377e2d3ff..58fc115233c 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -154,11 +154,10 @@ impl ContractStorage { } pub fn decrease_entrance_count(&mut self, callee: Vec) -> Result { - self.entrance_count.get(&callee).map_or_else( + let entrance_count = self.entrance_count.get(&callee).map_or_else( || Err(Error::CalleeTrapped), |count| { if *count == 0 { - self.entrance_count.remove(&callee); Err(Error::CalleeTrapped) } else { Ok(count - 1) From 55443abc1b6466f637a9493481c4939f0286f115 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 11 Jan 2023 22:35:59 +0200 Subject: [PATCH 12/43] implement caller_is_origin --- crates/engine/src/exec_context.rs | 2 ++ crates/env/src/engine/off_chain/impls.rs | 25 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index 39371f0d9fa..76ec54e32d9 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -49,6 +49,8 @@ pub struct ExecContext { pub output: Vec, /// The return flags of the call pub return_flags: u32, + /// Origin of the call + pub origin: Option>, } impl ExecContext { diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 01cd7078f36..35413197bbe 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -494,6 +494,10 @@ impl TypedEnvBackend for EnvInstance { params.exec_input().encode(), )?; + if self.engine.exec_context.borrow().caller.clone().is_none() { + self.engine.exec_context.borrow_mut().origin = Some(callee.clone()) + } + let callee_context = ExecContext { caller: self.engine.exec_context.borrow().callee.clone(), callee: Some(callee.clone().into()), @@ -505,6 +509,7 @@ impl TypedEnvBackend for EnvInstance { input, output: vec![], return_flags: 0, + origin: self.engine.exec_context.borrow().origin.clone(), }; let mut previous_context = self.engine.exec_context.replace(callee_context); @@ -604,6 +609,10 @@ impl TypedEnvBackend for EnvInstance { ); let callee = callee.as_ref().to_vec(); + if self.engine.exec_context.borrow().caller.clone().is_none() { + self.engine.exec_context.borrow_mut().origin = Some(callee.clone()) + } + let callee_context = ExecContext { caller: self.engine.exec_context.borrow().callee.clone(), callee: Some(callee.clone().into()), @@ -615,6 +624,7 @@ impl TypedEnvBackend for EnvInstance { input: input.encode(), output: vec![], return_flags: 0, + origin: self.engine.exec_context.borrow().origin.clone(), }; let previous_context = self.engine.exec_context.replace(callee_context); @@ -682,7 +692,20 @@ impl TypedEnvBackend for EnvInstance { where E: Environment, { - unimplemented!("off-chain environment does not support `seal_caller_is_origin`") + self.engine + .exec_context + .borrow() + .origin + .clone() + .expect("stack should not be empty") + == self + .engine + .exec_context + .borrow() + .caller + .clone() + .expect("caller should exist") + .as_bytes() } fn code_hash(&mut self, account: &E::AccountId) -> Result From 029ed7f5f4a16326e2b1c8c9442641c45a65a427 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 11 Jan 2023 23:04:08 +0200 Subject: [PATCH 13/43] apply suggestions --- crates/engine/src/exec_context.rs | 6 +-- crates/engine/src/ext.rs | 6 +-- crates/env/src/engine/off_chain/impls.rs | 16 ++++--- crates/env/src/error.rs | 2 +- examples/delegator/adder/lib.rs | 2 +- examples/reentrancy/Cargo.toml | 12 +++--- .../.gitignore | 0 .../Cargo.toml | 8 ++-- .../{contract2 => fallback_contract}/lib.rs | 20 ++++----- examples/reentrancy/lib.rs | 42 +++++++++---------- .../{contract2 => main_contract}/.gitignore | 0 .../{contract1 => main_contract}/Cargo.toml | 4 +- .../{contract1 => main_contract}/lib.rs | 12 +++--- 13 files changed, 64 insertions(+), 66 deletions(-) rename examples/reentrancy/{contract1 => fallback_contract}/.gitignore (100%) rename examples/reentrancy/{contract2 => fallback_contract}/Cargo.toml (75%) rename examples/reentrancy/{contract2 => fallback_contract}/lib.rs (79%) rename examples/reentrancy/{contract2 => main_contract}/.gitignore (100%) rename examples/reentrancy/{contract1 => main_contract}/Cargo.toml (92%) rename examples/reentrancy/{contract1 => main_contract}/lib.rs (95%) diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index 76ec54e32d9..000ff3fbfb0 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -47,9 +47,9 @@ pub struct ExecContext { pub input: Vec, /// The output buffer of the call. pub output: Vec, - /// The return flags of the call - pub return_flags: u32, - /// Origin of the call + /// Is contract reverted. + pub reverted: bool, + /// Origin of the call. pub origin: Option>, } diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 58fc115233c..295eb0269a8 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -136,10 +136,10 @@ impl ContractStorage { } pub fn set_allow_reentry(&mut self, callee: Vec, allow: bool) { - if allow == false { - self.allow_reentry.remove(&callee); - } else { + if allow { self.allow_reentry.insert(callee, allow); + } else { + self.allow_reentry.remove(&callee); } } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 35413197bbe..4bc0aef46f1 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -268,7 +268,7 @@ impl EnvBackend for EnvInstance { R: scale::Encode, { if flags.is_reverted() { - self.engine.exec_context.borrow_mut().return_flags = 1; + self.engine.exec_context.borrow_mut().reverted = true; } self.engine.exec_context.borrow_mut().output = return_value.encode(); } @@ -481,9 +481,7 @@ impl TypedEnvBackend for EnvInstance { let _gas_limit = params.gas_limit(); let call_flags = params.call_flags().into_u32(); - let transferred_value = params.transferred_value(); - let caller = self.engine.exec_context.borrow().callee.clone(); // apply call flags before making a call and return the input that might be changed after that @@ -508,7 +506,7 @@ impl TypedEnvBackend for EnvInstance { block_timestamp: self.engine.exec_context.borrow().block_timestamp, input, output: vec![], - return_flags: 0, + reverted: false, origin: self.engine.exec_context.borrow().origin.clone(), }; @@ -544,7 +542,7 @@ impl TypedEnvBackend for EnvInstance { call_fn(); // revert contract's state in case of error - if self.engine.exec_context.borrow().return_flags & 1 > 0 { + if self.engine.exec_context.borrow().reverted { self.engine .database .borrow_mut() @@ -558,9 +556,9 @@ impl TypedEnvBackend for EnvInstance { let output = self.engine.exec_context.borrow().output.clone(); // if the call was reverted, previous one should be reverted too - previous_context.return_flags |= self.engine.exec_context.borrow().return_flags; + previous_context.reverted |= self.engine.exec_context.borrow().reverted; - let _ = self.engine.exec_context.replace(previous_context); + self.engine.exec_context.replace(previous_context); // apply code flags after the call self.engine @@ -623,7 +621,7 @@ impl TypedEnvBackend for EnvInstance { block_timestamp: self.engine.exec_context.borrow().block_timestamp, input: input.encode(), output: vec![], - return_flags: 0, + reverted: false, origin: self.engine.exec_context.borrow().origin.clone(), }; @@ -645,7 +643,7 @@ impl TypedEnvBackend for EnvInstance { deploy_fn(); - let _ = self.engine.exec_context.replace(previous_context); + self.engine.exec_context.replace(previous_context); Ok(<_ as scale::Decode>::decode(&mut callee.as_slice())?) } diff --git a/crates/env/src/error.rs b/crates/env/src/error.rs index 127074e46fc..6448d9d9e23 100644 --- a/crates/env/src/error.rs +++ b/crates/env/src/error.rs @@ -49,7 +49,7 @@ pub enum Error { LoggingDisabled, /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. EcdsaRecoveryFailed, - /// documentation + /// Encountered reentrancy, that is not allowed ReentranceDenied, } diff --git a/examples/delegator/adder/lib.rs b/examples/delegator/adder/lib.rs index db1f09b6e90..9a54869e552 100644 --- a/examples/delegator/adder/lib.rs +++ b/examples/delegator/adder/lib.rs @@ -36,7 +36,7 @@ mod test { use ink::primitives::Hash; #[test] - fn it_works() { + fn cross_contract_call_works_off_chain() { use super::*; use accumulator::{ Accumulator, diff --git a/examples/reentrancy/Cargo.toml b/examples/reentrancy/Cargo.toml index e996beb5f61..e9abf51198e 100644 --- a/examples/reentrancy/Cargo.toml +++ b/examples/reentrancy/Cargo.toml @@ -7,8 +7,8 @@ publish = false [dependencies] ink = { path = "../../crates/ink", default-features = false } -contract1 = { path = "./contract1", default-features = false, features = ["ink-as-dependency"] } -contract2 = { path = "./contract2", default-features = false, features = ["ink-as-dependency"] } +main_contract = { path = "main_contract", default-features = false, features = ["ink-as-dependency"] } +fallback_contract = { path = "fallback_contract", default-features = false, features = ["ink-as-dependency"] } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } @@ -24,13 +24,13 @@ std = [ "ink/std", "scale/std", "scale-info/std", - "contract1/std", - "contract2/std", + "main_contract/std", + "fallback_contract/std", ] ink-as-dependency = [] [workspace] members = [ - "contract1", - "contract2" + "main_contract", + "fallback_contract" ] diff --git a/examples/reentrancy/contract1/.gitignore b/examples/reentrancy/fallback_contract/.gitignore similarity index 100% rename from examples/reentrancy/contract1/.gitignore rename to examples/reentrancy/fallback_contract/.gitignore diff --git a/examples/reentrancy/contract2/Cargo.toml b/examples/reentrancy/fallback_contract/Cargo.toml similarity index 75% rename from examples/reentrancy/contract2/Cargo.toml rename to examples/reentrancy/fallback_contract/Cargo.toml index d236d27cc9c..4264cb44815 100755 --- a/examples/reentrancy/contract2/Cargo.toml +++ b/examples/reentrancy/fallback_contract/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "contract2" +name = "fallback_contract" version = "4.0.0-beta" authors = ["Parity Technologies "] edition = "2021" @@ -8,13 +8,13 @@ publish = false [dependencies] ink = { path = "../../../crates/ink", default-features = false } -contract1 = { path = "../contract1", default-features = false, features = ["ink-as-dependency"] } +main_contract = { path = "../main_contract", default-features = false, features = ["ink-as-dependency"] } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } [lib] -name = "contract2" +name = "fallback_contract" path = "lib.rs" crate-type = ["cdylib", "rlib"] @@ -24,6 +24,6 @@ std = [ "ink/std", "scale/std", "scale-info/std", - "contract1/std", + "main_contract/std", ] ink-as-dependency = [] diff --git a/examples/reentrancy/contract2/lib.rs b/examples/reentrancy/fallback_contract/lib.rs similarity index 79% rename from examples/reentrancy/contract2/lib.rs rename to examples/reentrancy/fallback_contract/lib.rs index 7770def1cf4..b4b359f8113 100755 --- a/examples/reentrancy/contract2/lib.rs +++ b/examples/reentrancy/fallback_contract/lib.rs @@ -1,30 +1,30 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub use self::contract2::{ - Contract2, - Contract2Ref, +pub use self::fallback_contract::{ + FallbackContract, + FallbackContractRef, }; #[ink::contract] -mod contract2 { - use contract1::Contract1Ref; +mod fallback_contract { use ink::primitives::Key; + use main_contract::MainContractRef; /// Defines the storage of your contract. /// Add new fields to the below struct in order /// to add new static storage fields to your contract. #[ink(storage)] - pub struct Contract2 { + pub struct FallbackContract { /// Stores a single `bool` value on the storage. value: u32, - callee: Contract1Ref, + callee: MainContractRef, } - impl Contract2 { + impl FallbackContract { /// Constructor that initializes the `bool` value to the given `init_value`. #[ink(constructor)] - pub fn new(callee: Contract1Ref) -> Self { + pub fn new(callee: MainContractRef) -> Self { Self { value: 0, callee } } @@ -35,7 +35,7 @@ mod contract2 { } #[ink(message)] - pub fn set_callee(&mut self, callee: Contract1Ref) { + pub fn set_callee(&mut self, callee: MainContractRef) { self.callee = callee; } diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index e67930a002e..8eba634eaf2 100644 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -1,48 +1,48 @@ #[cfg(test)] mod test { - use contract1::{ - Contract1, - Contract1Ref, - }; - use contract2::{ - Contract2, - Contract2Ref, + use fallback_contract::{ + FallbackContract, + FallbackContractRef, }; use ink::primitives::Hash; + use main_contract::{ + MainContract, + MainContractRef, + }; #[test] pub fn reentrancy_works() { let hash1 = Hash::from([10u8; 32]); let hash2 = Hash::from([20u8; 32]); - ink::env::test::register_contract::(hash1.as_ref()); - ink::env::test::register_contract::(hash2.as_ref()); + ink::env::test::register_contract::(hash1.as_ref()); + ink::env::test::register_contract::(hash2.as_ref()); - let mut contract1 = Contract1Ref::new() + let mut main_contract = MainContractRef::new() .code_hash(hash1.clone()) .endowment(0) .salt_bytes([0u8; 0]) .instantiate() - .expect("failed at instantiating the `Contract1Ref` contract"); + .expect("failed at instantiating the `main_contractRef` contract"); - let contract2 = Contract2Ref::new(contract1.clone()) + let fallback_contract = FallbackContractRef::new(main_contract.clone()) .code_hash(hash2.clone()) .endowment(0) .salt_bytes([0u8; 0]) .instantiate() - .expect("failed at instantiating the `Contract2Ref` contract"); + .expect("failed at instantiating the `fallback_contractRef` contract"); - let address1 = contract1.get_address(); + let address1 = main_contract.get_address(); - let address2 = contract2.get_address(); + let address2 = fallback_contract.get_address(); - contract1.set_callee(address2); + main_contract.set_callee(address2); - assert_eq!(contract1.get_callee(), address2); - assert_eq!(contract2.get_callee(), address1); + assert_eq!(main_contract.get_callee(), address2); + assert_eq!(fallback_contract.get_callee(), address1); - assert_eq!(contract1.inc(), Ok(2)); - assert_eq!(contract1.get(), 2); - assert_eq!(contract2.get(), 0); + assert_eq!(main_contract.inc(), Ok(2)); + assert_eq!(main_contract.get(), 2); + assert_eq!(fallback_contract.get(), 0); } } diff --git a/examples/reentrancy/contract2/.gitignore b/examples/reentrancy/main_contract/.gitignore similarity index 100% rename from examples/reentrancy/contract2/.gitignore rename to examples/reentrancy/main_contract/.gitignore diff --git a/examples/reentrancy/contract1/Cargo.toml b/examples/reentrancy/main_contract/Cargo.toml similarity index 92% rename from examples/reentrancy/contract1/Cargo.toml rename to examples/reentrancy/main_contract/Cargo.toml index 2a50d699006..827e22d10b6 100755 --- a/examples/reentrancy/contract1/Cargo.toml +++ b/examples/reentrancy/main_contract/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "contract1" +name = "main_contract" version = "4.0.0-beta" authors = ["Parity Technologies "] edition = "2021" @@ -12,7 +12,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } [lib] -name = "contract1" +name = "main_contract" path = "lib.rs" crate-type = ["cdylib", "rlib"] diff --git a/examples/reentrancy/contract1/lib.rs b/examples/reentrancy/main_contract/lib.rs similarity index 95% rename from examples/reentrancy/contract1/lib.rs rename to examples/reentrancy/main_contract/lib.rs index 343a511d937..5a976a0d947 100755 --- a/examples/reentrancy/contract1/lib.rs +++ b/examples/reentrancy/main_contract/lib.rs @@ -2,14 +2,14 @@ extern crate core; -pub use self::contract1::{ - Contract1, - Contract1Ref, +pub use self::main_contract::{ Error, + MainContract, + MainContractRef, }; #[ink::contract] -mod contract1 { +mod main_contract { use ink::{ env::{ call::{ @@ -28,7 +28,7 @@ mod contract1 { /// to add new static storage fields to your contract. #[derive(Default)] #[ink(storage)] - pub struct Contract1 { + pub struct MainContract { /// Stores a single `bool` value on the storage. value: u32, @@ -42,7 +42,7 @@ mod contract1 { Unknown, } - impl Contract1 { + impl MainContract { /// Constructor that initializes the `bool` value to the given `init_value`. #[ink(constructor)] pub fn new() -> Self { From a09c3882b1143e3567eb9488e23aa381cba3e548 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 12 Jan 2023 11:06:49 +0200 Subject: [PATCH 14/43] add README for the reentrancy example and apply suggestions --- crates/engine/src/ext.rs | 4 +--- crates/env/src/engine/off_chain/impls.rs | 6 +---- crates/env/src/error.rs | 2 -- examples/reentrancy/README.md | 24 ++++++++++++++++++++ examples/reentrancy/fallback_contract/lib.rs | 2 +- 5 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 examples/reentrancy/README.md diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 295eb0269a8..6b3bab87e5f 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -102,8 +102,6 @@ define_error_codes! { LoggingDisabled = 9, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. EcdsaRecoveryFailed = 11, - /// Encountered the reentrancy that is not allowed - ReentranceDenied = 12, } /// The raw return code returned by the host side. @@ -646,7 +644,7 @@ impl Engine { if !self.contracts.borrow().get_allow_reentry(callee.clone()) && self.contracts.borrow().get_entrance_count(callee.clone()) > 0 { - return Err(Error::ReentranceDenied) + return Err(Error::CalleeTrapped) } self.contracts diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 4bc0aef46f1..ad67192c9db 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -114,7 +114,6 @@ impl From for crate::Error { ext::Error::NotCallable => Self::NotCallable, ext::Error::LoggingDisabled => Self::LoggingDisabled, ext::Error::EcdsaRecoveryFailed => Self::EcdsaRecoveryFailed, - ext::Error::ReentranceDenied => Self::ReentranceDenied, } } } @@ -256,10 +255,7 @@ impl EnvBackend for EnvInstance { where R: scale::Encode, { - if flags.is_reverted() { - panic!("the off-chain env does not implement revert in `seal_return_value`") - } - self.engine.exec_context.borrow_mut().output = return_value.encode(); + panic!("the off-chain env does not implement revert in `seal_return_value`") } #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] diff --git a/crates/env/src/error.rs b/crates/env/src/error.rs index 6448d9d9e23..b8b613b0b08 100644 --- a/crates/env/src/error.rs +++ b/crates/env/src/error.rs @@ -49,8 +49,6 @@ pub enum Error { LoggingDisabled, /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. EcdsaRecoveryFailed, - /// Encountered reentrancy, that is not allowed - ReentranceDenied, } /// A result of environmental operations. diff --git a/examples/reentrancy/README.md b/examples/reentrancy/README.md new file mode 100644 index 00000000000..60ccdc31c25 --- /dev/null +++ b/examples/reentrancy/README.md @@ -0,0 +1,24 @@ +# Reentrancy example + +This example shows how reentrancy works in ink! smart contracts. +It consists of two smart contracts: + +- Main contract, which we initially call +- Fallback contract, which will reenter the main contract + +## Workflow + +The mechanism of work is the following: + +- You call the `inc` method in the main contract and increases the `value` by 1 +- It makes a call to `fallback` method in fallback contract, as it has no selector +- `fallback` makes a call back to `inc` method in main contract +- `inc` increases the `value` by 1 again and returns 2 as a result + +## Testing + +You can run unit-test this example by running the following command: + +```bash +cargo test +``` \ No newline at end of file diff --git a/examples/reentrancy/fallback_contract/lib.rs b/examples/reentrancy/fallback_contract/lib.rs index b4b359f8113..7aa57ff8ed8 100755 --- a/examples/reentrancy/fallback_contract/lib.rs +++ b/examples/reentrancy/fallback_contract/lib.rs @@ -55,7 +55,7 @@ mod fallback_contract { } #[ink(message, selector = _)] - pub fn inc(&mut self) { + pub fn fallback(&mut self) { ink::env::set_contract_storage( &::KEY, self, From 5fc7b08943fd73e0f4dd9ce3468cba0ed933bec7 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 14:25:01 +0200 Subject: [PATCH 15/43] cargo clippy --- crates/engine/src/ext.rs | 21 ++++++++++----------- crates/engine/src/test_api.rs | 14 ++------------ crates/env/src/engine/off_chain/impls.rs | 10 +++++----- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 6b3bab87e5f..3b043dde46b 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -126,11 +126,11 @@ pub struct ContractStorage { impl ContractStorage { pub fn get_entrance_count(&self, callee: Vec) -> u32 { - self.entrance_count.get(&callee).unwrap_or(&0).clone() + *self.entrance_count.get(&callee).unwrap_or(&0) } pub fn get_allow_reentry(&self, callee: Vec) -> bool { - self.allow_reentry.get(&callee).unwrap_or(&false).clone() + *self.allow_reentry.get(&callee).unwrap_or(&false) } pub fn set_allow_reentry(&mut self, callee: Vec, allow: bool) { @@ -633,11 +633,10 @@ impl Engine { let allow_reentry = ((call_flags & 8) >> 3) != 0; // Allow/deny reentrancy to the caller - if caller.is_some() { - self.contracts.borrow_mut().set_allow_reentry( - caller.clone().unwrap().as_bytes().to_vec(), - allow_reentry, - ); + if let Some(caller) = caller { + self.contracts + .borrow_mut() + .set_allow_reentry(caller.as_bytes().to_vec(), allow_reentry); } // Check if reentrance that is not allowed is encountered @@ -649,7 +648,7 @@ impl Engine { self.contracts .borrow_mut() - .increase_entrance_count(callee.clone())?; + .increase_entrance_count(callee)?; let new_input = if forward_input { let previous_input = self.exec_context.borrow().input.clone(); @@ -678,18 +677,18 @@ impl Engine { let tail_call = ((call_flags & 4) >> 2) != 0; if tail_call { - self.exec_context.borrow_mut().output = output.clone(); + self.exec_context.borrow_mut().output = output; } self.contracts .borrow_mut() .decrease_entrance_count(callee)?; - if caller.is_some() { + if let Some(caller) = caller { self.contracts .borrow_mut() .allow_reentry - .remove(&caller.unwrap().as_bytes().to_vec()); + .remove(&caller.as_bytes().to_vec()); } Ok(()) } diff --git a/crates/engine/src/test_api.rs b/crates/engine/src/test_api.rs index 12b1eab7e5c..a82fcd7ec89 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -200,22 +200,12 @@ impl Engine { /// Returns the total number of reads executed. pub fn count_reads(&self) -> usize { - self.debug_info - .borrow() - .count_reads - .iter() - .map(|(_, v)| v) - .sum() + self.debug_info.borrow().count_reads.values().sum() } /// Returns the total number of writes executed. pub fn count_writes(&self) -> usize { - self.debug_info - .borrow() - .count_writes - .iter() - .map(|(_, v)| v) - .sum() + self.debug_info.borrow().count_writes.values().sum() } /// Sets a caller for the next call. diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index c8eeeecb70d..995fe91ac36 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -514,7 +514,7 @@ impl TypedEnvBackend for EnvInstance { .borrow() .instantiated .get(&callee) - .ok_or_else(|| Error::NotCallable)? + .ok_or(Error::NotCallable)? .clone(); let call_fn = self @@ -523,7 +523,7 @@ impl TypedEnvBackend for EnvInstance { .borrow() .deployed .get(&code_hash) - .ok_or_else(|| Error::CodeNotFound)? + .ok_or(Error::CodeNotFound)? .call; // save previous version of storage in case call will revert @@ -531,7 +531,7 @@ impl TypedEnvBackend for EnvInstance { .engine .database .borrow() - .get_from_contract_storage(&callee.as_slice(), &[0; 4]) + .get_from_contract_storage(callee.as_slice(), &[0; 4]) .expect("contract storage not found") .clone(); @@ -542,12 +542,12 @@ impl TypedEnvBackend for EnvInstance { self.engine .database .borrow_mut() - .insert_into_contract_storage(&callee.as_slice(), &[0; 4], storage) + .insert_into_contract_storage(callee.as_slice(), &[0; 4], storage) .unwrap(); } let return_value = - R::decode(&mut self.engine.exec_context.borrow().output.as_slice().clone())?; + R::decode(&mut self.engine.exec_context.borrow().output.clone().as_slice())?; let output = self.engine.exec_context.borrow().output.clone(); From 251f7bae26742618489aa346c95639a5355a8c47 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 14:37:23 +0200 Subject: [PATCH 16/43] fix clippy --- crates/engine/src/test_api.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/test_api.rs b/crates/engine/src/test_api.rs index a82fcd7ec89..c1b54cef6e8 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -181,20 +181,18 @@ impl Engine { /// Returns the total number of reads and writes of the contract's storage. pub fn get_contract_storage_rw(&self, account_id: Vec) -> (usize, usize) { let account_id = AccountId::from(account_id); - let reads = self + let reads = *self .debug_info .borrow() .count_reads .get(&account_id) - .unwrap_or(&0) - .clone(); - let writes = self + .unwrap_or(&0); + let writes = *self .debug_info .borrow() .count_writes .get(&account_id) - .unwrap_or(&0) - .clone(); + .unwrap_or(&0); (reads, writes) } From 76cf2ce6cff8203cc07450ea54ddac058f99c088 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 15:20:18 +0200 Subject: [PATCH 17/43] fix unreachable code --- crates/env/src/api.rs | 16 ---------------- crates/env/src/backend.rs | 15 --------------- crates/env/src/engine/off_chain/impls.rs | 8 -------- crates/env/src/engine/on_chain/impls.rs | 2 +- examples/reentrancy/main_contract/lib.rs | 7 ++----- 5 files changed, 3 insertions(+), 45 deletions(-) diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index ac702740b4e..1299a554e6b 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -411,22 +411,6 @@ where /// # Note /// /// This function stops the execution of the contract immediately. -#[cfg(all(not(feature = "std"), target_arch = "wasm32"))] -pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! -where - R: scale::Encode, -{ - ::on_instance(|instance| { - EnvBackend::return_value::(instance, return_flags, return_value) - }) -} - -/// Returns the value back to the caller of the executed contract. -/// -/// # Note -/// -/// This function stops the execution of the contract immediately. -#[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] pub fn return_value(return_flags: ReturnFlags, return_value: &R) where R: scale::Encode, diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index b95f2a4124f..464b0ad5858 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -240,21 +240,6 @@ pub trait EnvBackend { where T: scale::Decode; - /// Returns the value back to the caller of the executed contract. - /// - /// # Note - /// - /// Calling this method will end contract execution immediately. - /// It will return the given return value back to its caller. - /// - /// The `flags` parameter can be used to revert the state changes of the - /// entire execution if necessary. - #[cfg(all(not(feature = "std"), target_arch = "wasm32"))] - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! - where - R: scale::Encode; - - #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where R: scale::Encode; diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 995fe91ac36..f8413f2e396 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -250,14 +250,6 @@ impl EnvBackend for EnvInstance { .map_err(|_| Error::CalleeTrapped) } - #[cfg(all(not(feature = "std"), target_arch = "wasm32"))] - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! - where - R: scale::Encode, - { - panic!("the off-chain env does not implement revert in `seal_return_value`") - } - #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index d140fc9949c..355f58389ed 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -293,7 +293,7 @@ impl EnvBackend for EnvInstance { self.get_property::(ext::input) } - fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where R: scale::Encode, { diff --git a/examples/reentrancy/main_contract/lib.rs b/examples/reentrancy/main_contract/lib.rs index 5a976a0d947..630376948d5 100755 --- a/examples/reentrancy/main_contract/lib.rs +++ b/examples/reentrancy/main_contract/lib.rs @@ -1,7 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] -extern crate core; - pub use self::main_contract::{ Error, MainContract, @@ -10,6 +8,7 @@ pub use self::main_contract::{ #[ink::contract] mod main_contract { + use core::mem::ManuallyDrop; use ink::{ env::{ call::{ @@ -21,7 +20,6 @@ mod main_contract { }, primitives::Key, }; - use std::mem::ManuallyDrop; /// Defines the storage of your contract. /// Add new fields to the below struct in order @@ -74,11 +72,9 @@ mod main_contract { ::KEY } - #[allow(unreachable_code)] #[ink(message)] pub fn inc(&mut self) -> Result { self.value = self.value + 1; - println!("value {}", self.value); if self.value > 1 { return Ok(self.value) @@ -100,6 +96,7 @@ mod main_contract { ) .unwrap_or_else(|error| panic!("Failed to load contract state: {:?}", error)) .unwrap_or_else(|| panic!("Contract state is not initialized")); + core::mem::swap(self, &mut state); let _ = ManuallyDrop::new(state); From 7f09f31f62556610172c9a03d00abb83ca04f44b Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 15:24:12 +0200 Subject: [PATCH 18/43] add allow nonminimal bool --- crates/ink/codegen/src/generator/dispatch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index e10ba074712..255c4ab29ac 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -410,19 +410,18 @@ impl Dispatch<'_> { #[cfg(not(feature = "ink-as-dependency"))] const _: () = { #[no_mangle] - #[allow(clippy::nonminimal_bool)] fn deploy() { <#storage_ident as ::ink::env::contract::Entrypoint>::deploy() } #[no_mangle] - #[allow(clippy::nonminimal_bool)] fn call() { <#storage_ident as ::ink::env::contract::Entrypoint>::call() } }; impl ::ink::env::contract::Entrypoint for #storage_ident { + #[allow(clippy::nonminimal_bool)] fn deploy() { if !#any_constructor_accept_payment { ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() @@ -459,6 +458,7 @@ impl Dispatch<'_> { }) } + #[allow(clippy::nonminimal_bool)] fn call() { if !#any_message_accept_payment { ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() From edd466fcb93deedc49bbd17957dd3372aa9915f2 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 15:27:31 +0200 Subject: [PATCH 19/43] apply clippy on examples --- examples/reentrancy/lib.rs | 4 ++-- examples/reentrancy/main_contract/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index 8eba634eaf2..78229ba6864 100644 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -19,14 +19,14 @@ mod test { ink::env::test::register_contract::(hash2.as_ref()); let mut main_contract = MainContractRef::new() - .code_hash(hash1.clone()) + .code_hash(hash1) .endowment(0) .salt_bytes([0u8; 0]) .instantiate() .expect("failed at instantiating the `main_contractRef` contract"); let fallback_contract = FallbackContractRef::new(main_contract.clone()) - .code_hash(hash2.clone()) + .code_hash(hash2) .endowment(0) .salt_bytes([0u8; 0]) .instantiate() diff --git a/examples/reentrancy/main_contract/lib.rs b/examples/reentrancy/main_contract/lib.rs index 630376948d5..c0eb8e1649b 100755 --- a/examples/reentrancy/main_contract/lib.rs +++ b/examples/reentrancy/main_contract/lib.rs @@ -74,7 +74,7 @@ mod main_contract { #[ink(message)] pub fn inc(&mut self) -> Result { - self.value = self.value + 1; + self.value += 1; if self.value > 1 { return Ok(self.value) From 61844026eb067b48b73c6bd8dcbec0c92c4d44f0 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 15:33:28 +0200 Subject: [PATCH 20/43] apply clippy on delegator example --- examples/delegator/adder/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/delegator/adder/lib.rs b/examples/delegator/adder/lib.rs index 9a54869e552..a665f7e9d5b 100644 --- a/examples/delegator/adder/lib.rs +++ b/examples/delegator/adder/lib.rs @@ -50,13 +50,13 @@ mod test { ink::env::test::register_contract::(hash2.as_ref()); let acc = AccumulatorRef::new(0) - .code_hash(hash1.clone()) + .code_hash(hash1) .endowment(0) .salt_bytes([0u8; 0]) .instantiate() .expect("failed at instantiating the `AccumulatorRef` contract"); let mut adder = AdderRef::new(acc.clone()) - .code_hash(hash2.clone()) + .code_hash(hash2) .endowment(0) .salt_bytes([0u8; 0]) .instantiate() From f6c885a1954e6f27557ce7efab76e878280ede1b Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 16:01:29 +0200 Subject: [PATCH 21/43] fix no_implicit_prelude error --- crates/ink/codegen/src/generator/dispatch.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 255c4ab29ac..ab3954b881e 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -447,7 +447,7 @@ impl Dispatch<'_> { ::ink::env::ReturnFlags::new_with_reverted(true), &error, ); - panic!("execute_constructor reverted"); + ::core::panic!("execute_constructor reverted"); } }; @@ -484,7 +484,7 @@ impl Dispatch<'_> { ::ink::env::ReturnFlags::new_with_reverted(true), &error, ); - panic!("execute_message reverted"); + ::core::panic!("execute_message reverted"); } }; @@ -641,7 +641,7 @@ impl Dispatch<'_> { ); } - Ok(::ink::env::return_value::< + ::core::result::Result::Ok(::ink::env::return_value::< ::ink::ConstructorResult< ::core::result::Result<(), &#constructor_value::Error> >, @@ -843,7 +843,7 @@ impl Dispatch<'_> { push_contract(contract, #mutates_storage); } - Ok(::ink::env::return_value::<::ink::MessageResult::<#message_output>>( + ::core::result::Result::Ok(::ink::env::return_value::<::ink::MessageResult::<#message_output>>( ::ink::env::ReturnFlags::new_with_reverted(is_reverted), // Currently no `LangError`s are raised at this level of the // dispatch logic so `Ok` is always returned to the caller. From 91ae09f0665989fbab1f41d358035c6c37044043 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 16:42:42 +0200 Subject: [PATCH 22/43] move tests to fallback_contract --- examples/reentrancy/Cargo.toml | 36 -------------- examples/reentrancy/README.md | 2 +- examples/reentrancy/fallback_contract/lib.rs | 51 ++++++++++++++++++++ examples/reentrancy/lib.rs | 48 ------------------ 4 files changed, 52 insertions(+), 85 deletions(-) delete mode 100644 examples/reentrancy/Cargo.toml delete mode 100644 examples/reentrancy/lib.rs diff --git a/examples/reentrancy/Cargo.toml b/examples/reentrancy/Cargo.toml deleted file mode 100644 index e9abf51198e..00000000000 --- a/examples/reentrancy/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "test" -version = "4.0.0-beta" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } -main_contract = { path = "main_contract", default-features = false, features = ["ink-as-dependency"] } -fallback_contract = { path = "fallback_contract", default-features = false, features = ["ink-as-dependency"] } - -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } -scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } - -[lib] -name = "test" -path = "lib.rs" -crate-type = ["cdylib"] - -[features] -default = ["std"] -std = [ - "ink/std", - "scale/std", - "scale-info/std", - "main_contract/std", - "fallback_contract/std", -] -ink-as-dependency = [] - -[workspace] -members = [ - "main_contract", - "fallback_contract" -] diff --git a/examples/reentrancy/README.md b/examples/reentrancy/README.md index 60ccdc31c25..8331674b41a 100644 --- a/examples/reentrancy/README.md +++ b/examples/reentrancy/README.md @@ -17,7 +17,7 @@ The mechanism of work is the following: ## Testing -You can run unit-test this example by running the following command: +You can run unit-test for this example by running the following command in fallback_contract: ```bash cargo test diff --git a/examples/reentrancy/fallback_contract/lib.rs b/examples/reentrancy/fallback_contract/lib.rs index 7aa57ff8ed8..676998e2a79 100755 --- a/examples/reentrancy/fallback_contract/lib.rs +++ b/examples/reentrancy/fallback_contract/lib.rs @@ -64,3 +64,54 @@ mod fallback_contract { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + pub fn reentrancy_works() { + use fallback_contract::{ + FallbackContract, + FallbackContractRef, + }; + use ink::primitives::Hash; + use main_contract::{ + MainContract, + MainContractRef, + }; + + let hash1 = Hash::from([10u8; 32]); + let hash2 = Hash::from([20u8; 32]); + + ink::env::test::register_contract::(hash1.as_ref()); + ink::env::test::register_contract::(hash2.as_ref()); + + let mut main_contract = MainContractRef::new() + .code_hash(hash1) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `main_contractRef` contract"); + + let fallback_contract = FallbackContractRef::new(main_contract.clone()) + .code_hash(hash2) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate() + .expect("failed at instantiating the `fallback_contractRef` contract"); + + let address1 = main_contract.get_address(); + + let address2 = fallback_contract.get_address(); + + main_contract.set_callee(address2); + + assert_eq!(main_contract.get_callee(), address2); + assert_eq!(fallback_contract.get_callee(), address1); + + assert_eq!(main_contract.inc(), Ok(2)); + assert_eq!(main_contract.get(), 2); + assert_eq!(fallback_contract.get(), 0); + } +} diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs deleted file mode 100644 index 78229ba6864..00000000000 --- a/examples/reentrancy/lib.rs +++ /dev/null @@ -1,48 +0,0 @@ -#[cfg(test)] -mod test { - use fallback_contract::{ - FallbackContract, - FallbackContractRef, - }; - use ink::primitives::Hash; - use main_contract::{ - MainContract, - MainContractRef, - }; - - #[test] - pub fn reentrancy_works() { - let hash1 = Hash::from([10u8; 32]); - let hash2 = Hash::from([20u8; 32]); - - ink::env::test::register_contract::(hash1.as_ref()); - ink::env::test::register_contract::(hash2.as_ref()); - - let mut main_contract = MainContractRef::new() - .code_hash(hash1) - .endowment(0) - .salt_bytes([0u8; 0]) - .instantiate() - .expect("failed at instantiating the `main_contractRef` contract"); - - let fallback_contract = FallbackContractRef::new(main_contract.clone()) - .code_hash(hash2) - .endowment(0) - .salt_bytes([0u8; 0]) - .instantiate() - .expect("failed at instantiating the `fallback_contractRef` contract"); - - let address1 = main_contract.get_address(); - - let address2 = fallback_contract.get_address(); - - main_contract.set_callee(address2); - - assert_eq!(main_contract.get_callee(), address2); - assert_eq!(fallback_contract.get_callee(), address1); - - assert_eq!(main_contract.inc(), Ok(2)); - assert_eq!(main_contract.get(), 2); - assert_eq!(fallback_contract.get(), 0); - } -} From 2bf8af8dca1fe784caf9eef52b0c82285613af69 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 16:47:20 +0200 Subject: [PATCH 23/43] move fallback_contract to reentrancy dir --- examples/reentrancy/{fallback_contract => }/.gitignore | 2 +- examples/reentrancy/{fallback_contract => }/Cargo.toml | 4 ++-- examples/reentrancy/README.md | 2 +- examples/reentrancy/{fallback_contract => }/lib.rs | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename examples/reentrancy/{fallback_contract => }/.gitignore (90%) rename examples/reentrancy/{fallback_contract => }/Cargo.toml (77%) rename examples/reentrancy/{fallback_contract => }/lib.rs (100%) diff --git a/examples/reentrancy/fallback_contract/.gitignore b/examples/reentrancy/.gitignore similarity index 90% rename from examples/reentrancy/fallback_contract/.gitignore rename to examples/reentrancy/.gitignore index 8de8f877e47..619b0eaaa87 100755 --- a/examples/reentrancy/fallback_contract/.gitignore +++ b/examples/reentrancy/.gitignore @@ -6,4 +6,4 @@ # Remove Cargo.lock when creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock +fallback_contract/Cargo.lock diff --git a/examples/reentrancy/fallback_contract/Cargo.toml b/examples/reentrancy/Cargo.toml similarity index 77% rename from examples/reentrancy/fallback_contract/Cargo.toml rename to examples/reentrancy/Cargo.toml index 4264cb44815..2ec26247329 100755 --- a/examples/reentrancy/fallback_contract/Cargo.toml +++ b/examples/reentrancy/Cargo.toml @@ -6,9 +6,9 @@ edition = "2021" publish = false [dependencies] -ink = { path = "../../../crates/ink", default-features = false } +ink = { path = "../../crates/ink", default-features = false } -main_contract = { path = "../main_contract", default-features = false, features = ["ink-as-dependency"] } +main_contract = { path = "main_contract", default-features = false, features = ["ink-as-dependency"] } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } diff --git a/examples/reentrancy/README.md b/examples/reentrancy/README.md index 8331674b41a..9b862593ba0 100644 --- a/examples/reentrancy/README.md +++ b/examples/reentrancy/README.md @@ -17,7 +17,7 @@ The mechanism of work is the following: ## Testing -You can run unit-test for this example by running the following command in fallback_contract: +You can run unit-test for this example by running the following command: ```bash cargo test diff --git a/examples/reentrancy/fallback_contract/lib.rs b/examples/reentrancy/lib.rs similarity index 100% rename from examples/reentrancy/fallback_contract/lib.rs rename to examples/reentrancy/lib.rs From 1134ad1dfd8cd61862333c52a877992c7feb640a Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 20:51:15 +0200 Subject: [PATCH 24/43] add tests for contract storage --- crates/engine/src/ext.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 3b043dde46b..3982de1b8a2 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -118,7 +118,6 @@ impl ReturnCode { #[derive(Default)] pub struct ContractStorage { pub instantiated: HashMap, Vec>, - pub storage_keys: HashMap, Vec>, pub entrance_count: HashMap, u32>, pub allow_reentry: HashMap, bool>, pub deployed: HashMap, Contract>, @@ -707,3 +706,36 @@ fn set_output(output: &mut &mut [u8], slice: &[u8]) { ); output[..slice.len()].copy_from_slice(slice); } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + pub fn contract_storage_works() { + let mut storage = ContractStorage::default(); + + let account = [0u8; 32].to_vec(); + + assert_eq!(storage.get_allow_reentry(account.clone()), false); + storage.set_allow_reentry(account.clone(), true); + assert_eq!(storage.get_allow_reentry(account.clone()), true); + + assert_eq!(storage.increase_entrance_count(account.clone()), Ok(())); + assert_eq!(storage.get_entrance_count(account.clone()), 1); + assert_eq!(storage.decrease_entrance_count(account.clone()), Ok(())); + assert_eq!(storage.get_entrance_count(account), 0); + } + + #[test] + pub fn decrease_entrance_count_fails() { + let mut storage = ContractStorage::default(); + + let account = [0u8; 32].to_vec(); + + assert_eq!( + storage.decrease_entrance_count(account), + Err(Error::CalleeTrapped) + ); + } +} From bd20f87571722362db6cddd94e888811ddb3684b Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 16 Jan 2023 20:57:32 +0200 Subject: [PATCH 25/43] fix clippy for test --- crates/engine/src/ext.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 3982de1b8a2..a41cc7ede9c 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -717,9 +717,9 @@ mod test { let account = [0u8; 32].to_vec(); - assert_eq!(storage.get_allow_reentry(account.clone()), false); + assert!(!storage.get_allow_reentry(account.clone())); storage.set_allow_reentry(account.clone(), true); - assert_eq!(storage.get_allow_reentry(account.clone()), true); + assert!(storage.get_allow_reentry(account.clone())); assert_eq!(storage.increase_entrance_count(account.clone()), Ok(())); assert_eq!(storage.get_entrance_count(account.clone()), 1); From 78c78695bc13de82c6bda48e4a8e9adcaf987e9d Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 17 Jan 2023 14:41:37 +0200 Subject: [PATCH 26/43] tail call is not supported yet --- crates/engine/src/ext.rs | 14 +++++++------- crates/env/src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index a41cc7ede9c..7ef9d236a3e 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -670,14 +670,14 @@ impl Engine { &mut self, caller: Option, callee: Vec, - call_flags: u32, - output: Vec, + _call_flags: u32, + _output: Vec, ) -> core::result::Result<(), Error> { - let tail_call = ((call_flags & 4) >> 2) != 0; - - if tail_call { - self.exec_context.borrow_mut().output = output; - } + // let _tail_call = ((call_flags & 4) >> 2) != 0; + // + // if _tail_call { + // self.exec_context.borrow_mut().output = output; + // } self.contracts .borrow_mut() diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index e0016bdb88b..eab3ae5e63b 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -73,7 +73,7 @@ mod arithmetic; mod backend; pub mod call; pub mod chain_extension; -/// documentation ? +/// Contract entrypoints pub mod contract; mod engine; mod error; From 5fe1137018631dc6affa37a1e8abd4dccb36520f Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 19 Jan 2023 14:59:55 +0200 Subject: [PATCH 27/43] add generate address method --- crates/engine/src/types.rs | 2 +- crates/env/src/engine/off_chain/impls.rs | 115 ++++++++++++++--------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/crates/engine/src/types.rs b/crates/engine/src/types.rs index 73307264ac6..89c1b983b31 100644 --- a/crates/engine/src/types.rs +++ b/crates/engine/src/types.rs @@ -27,7 +27,7 @@ pub type BlockTimestamp = u64; pub type Balance = u128; /// The Account Id type used by this crate. -#[derive(Debug, From, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Default, Debug, From, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct AccountId(Vec); diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index f8413f2e396..e7646e81985 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -47,6 +47,7 @@ use ink_engine::{ }; use ink_storage_traits::Storable; use scale::Encode; + /// The capacity of the static buffer. /// This is the same size as the ink! on-chain environment. We chose to use the same size /// to be as close to the on-chain behavior as possible. @@ -182,6 +183,52 @@ impl EnvInstance { ext_fn(&self.engine, full_scope); scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into) } + + fn generate_address( + &mut self, + caller: Vec, + code_hash: Vec, + input_data: Vec, + salt: Vec, + ) -> [u8; 32] { + let mut output = [0u8; 32]; + Sha2x256::hash( + [caller, code_hash, input_data, salt].concat().as_slice(), + &mut output, + ); + output + } + + /// Generates new execution context, replaces it as current and returns previous one + fn generate_callee_context( + &mut self, + callee: Vec, + input: Vec, + transferred_value: u128, + ) -> ExecContext { + let callee_context = ExecContext { + caller: self.engine.exec_context.borrow().callee.clone(), + callee: Some(callee.clone().into()), + value_transferred: transferred_value, + block_number: self.engine.exec_context.borrow().block_number, + block_timestamp: self.engine.exec_context.borrow().block_timestamp, + input, + output: vec![], + reverted: false, + origin: Some( + self.engine + .exec_context + .borrow() + .origin + .clone() + .unwrap_or(callee.clone()), + ), + }; + + let previous_context = self.engine.exec_context.replace(callee_context); + + previous_context + } } impl EnvBackend for EnvInstance { @@ -480,25 +527,13 @@ impl TypedEnvBackend for EnvInstance { params.exec_input().encode(), )?; - if self.engine.exec_context.borrow().caller.clone().is_none() { - self.engine.exec_context.borrow_mut().origin = Some(callee.clone()) - } - - let callee_context = ExecContext { - caller: self.engine.exec_context.borrow().callee.clone(), - callee: Some(callee.clone().into()), - value_transferred: ::decode( + let mut previous_context = self.generate_callee_context( + callee.clone(), + input.clone(), + ::decode( &mut scale::Encode::encode(transferred_value).as_slice(), )?, - block_number: self.engine.exec_context.borrow().block_number, - block_timestamp: self.engine.exec_context.borrow().block_timestamp, - input, - output: vec![], - reverted: false, - origin: self.engine.exec_context.borrow().origin.clone(), - }; - - let mut previous_context = self.engine.exec_context.replace(callee_context); + ); let code_hash = self .engine @@ -564,10 +599,8 @@ impl TypedEnvBackend for EnvInstance { Args: scale::Encode, R: scale::Decode, { - let _code_hash = params.code_hash(); - unimplemented!( - "off-chain environment does not support delegated contract invocation" - ) + let code_hash = params.code_hash().as_ref().to_vec(); + unimplemented!() } fn instantiate_contract( @@ -582,38 +615,27 @@ impl TypedEnvBackend for EnvInstance { let code_hash = params.code_hash().as_ref().to_vec(); // Gas is not supported by off-chain env. let _gas_limit = params.gas_limit(); + let caller = self.engine.exec_context.borrow().callee.clone(); let endowment = params.endowment(); let input = params.exec_input(); let salt_bytes = params.salt_bytes(); - let mut callee = [0u8; 32]; - Sha2x256::hash( - [code_hash.as_ref(), salt_bytes.as_ref()] - .concat() - .as_slice(), - &mut callee, - ); - let callee = callee.as_ref().to_vec(); - - if self.engine.exec_context.borrow().caller.clone().is_none() { - self.engine.exec_context.borrow_mut().origin = Some(callee.clone()) - } + let callee = self + .generate_address( + caller.unwrap_or_default().clone().as_bytes().to_vec(), + code_hash.clone(), + input.encode().clone(), + salt_bytes.as_ref().to_vec(), + ) + .to_vec(); - let callee_context = ExecContext { - caller: self.engine.exec_context.borrow().callee.clone(), - callee: Some(callee.clone().into()), - value_transferred: ::decode( + let previous_context = self.generate_callee_context( + callee.clone(), + input.clone().encode(), + ::decode( &mut scale::Encode::encode(endowment).as_slice(), )?, - block_number: self.engine.exec_context.borrow().block_number, - block_timestamp: self.engine.exec_context.borrow().block_timestamp, - input: input.encode(), - output: vec![], - reverted: false, - origin: self.engine.exec_context.borrow().origin.clone(), - }; - - let previous_context = self.engine.exec_context.replace(callee_context); + ); let deploy_fn = self .engine @@ -623,6 +645,7 @@ impl TypedEnvBackend for EnvInstance { .get(&code_hash) .ok_or(Error::CodeNotFound)? .deploy; + self.engine .contracts .borrow_mut() From df3dac29773acd8a4daf9eee40d82b879bce3c2f Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 19 Jan 2023 21:20:44 +0200 Subject: [PATCH 28/43] implement invoke_delegate --- crates/env/src/engine/off_chain/impls.rs | 73 +++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index e7646e81985..73b1b0aa836 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -600,7 +600,78 @@ impl TypedEnvBackend for EnvInstance { R: scale::Decode, { let code_hash = params.code_hash().as_ref().to_vec(); - unimplemented!() + let callee = self + .engine + .exec_context + .borrow() + .callee + .clone() + .unwrap_or_default() + .as_bytes() + .to_vec(); + + // apply call flags before making a call and return the input that might be changed after that + let input = self.engine.apply_code_flags_before_call( + caller.clone(), + callee.clone(), + call_flags, + params.exec_input().encode(), + )?; + + let call_fn = self + .engine + .contracts + .borrow() + .deployed + .get(&code_hash) + .ok_or(Error::CodeNotFound)? + .call; + + let mut previous_context = self.generate_callee_context( + callee.clone(), + params.exec_input().encode(), + ::decode( + &mut scale::Encode::encode(params.transferred_value()).as_slice(), + )?, + ); + + let storage = self + .engine + .database + .borrow() + .get_from_contract_storage(callee.as_slice(), &[0; 4]) + .expect("contract storage not found") + .clone(); + + call_fn(); + + if self.engine.exec_context.borrow().reverted { + self.engine + .database + .borrow_mut() + .insert_into_contract_storage(callee.as_slice(), &[0; 4], storage) + .unwrap(); + } + + let return_value = + R::decode(&mut self.engine.exec_context.borrow().output.clone().as_slice())?; + + let output = self.engine.exec_context.borrow().output.clone(); + + // if the call was reverted, previous one should be reverted too + previous_context.reverted |= self.engine.exec_context.borrow().reverted; + + self.engine.exec_context.replace(previous_context); + + // apply code flags after the call + self.engine.apply_code_flags_after_call( + callee.clone(), + callee, + call_flags, + output, + )?; + + Ok(return_value) } fn instantiate_contract( From 2fb253351a0010deb08e73876b298787d4fe568f Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Fri, 20 Jan 2023 14:00:44 +0200 Subject: [PATCH 29/43] fix compilation errors --- crates/env/src/engine/off_chain/impls.rs | 48 +++++++++++------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 73b1b0aa836..9d8c9b03f94 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -221,13 +221,11 @@ impl EnvInstance { .borrow() .origin .clone() - .unwrap_or(callee.clone()), + .unwrap_or(callee), ), }; - let previous_context = self.engine.exec_context.replace(callee_context); - - previous_context + self.engine.exec_context.replace(callee_context) } } @@ -529,7 +527,7 @@ impl TypedEnvBackend for EnvInstance { let mut previous_context = self.generate_callee_context( callee.clone(), - input.clone(), + input, ::decode( &mut scale::Encode::encode(transferred_value).as_slice(), )?, @@ -600,20 +598,13 @@ impl TypedEnvBackend for EnvInstance { R: scale::Decode, { let code_hash = params.code_hash().as_ref().to_vec(); - let callee = self - .engine - .exec_context - .borrow() - .callee - .clone() - .unwrap_or_default() - .as_bytes() - .to_vec(); + let callee = self.engine.exec_context.borrow().callee.clone(); + let call_flags = params.call_flags().into_u32(); // apply call flags before making a call and return the input that might be changed after that let input = self.engine.apply_code_flags_before_call( - caller.clone(), callee.clone(), + callee.clone().unwrap_or_default().as_bytes().to_vec(), call_flags, params.exec_input().encode(), )?; @@ -628,18 +619,19 @@ impl TypedEnvBackend for EnvInstance { .call; let mut previous_context = self.generate_callee_context( - callee.clone(), - params.exec_input().encode(), - ::decode( - &mut scale::Encode::encode(params.transferred_value()).as_slice(), - )?, + callee.clone().unwrap_or_default().as_bytes().to_vec(), + input, + 0, ); let storage = self .engine .database .borrow() - .get_from_contract_storage(callee.as_slice(), &[0; 4]) + .get_from_contract_storage( + callee.clone().unwrap_or_default().as_bytes(), + &[0; 4], + ) .expect("contract storage not found") .clone(); @@ -649,7 +641,11 @@ impl TypedEnvBackend for EnvInstance { self.engine .database .borrow_mut() - .insert_into_contract_storage(callee.as_slice(), &[0; 4], storage) + .insert_into_contract_storage( + callee.clone().unwrap_or_default().as_bytes(), + &[0; 4], + storage, + ) .unwrap(); } @@ -666,7 +662,7 @@ impl TypedEnvBackend for EnvInstance { // apply code flags after the call self.engine.apply_code_flags_after_call( callee.clone(), - callee, + callee.unwrap_or_default().as_bytes().to_vec(), call_flags, output, )?; @@ -693,16 +689,16 @@ impl TypedEnvBackend for EnvInstance { let callee = self .generate_address( - caller.unwrap_or_default().clone().as_bytes().to_vec(), + caller.unwrap_or_default().as_bytes().to_vec(), code_hash.clone(), - input.encode().clone(), + input.encode(), salt_bytes.as_ref().to_vec(), ) .to_vec(); let previous_context = self.generate_callee_context( callee.clone(), - input.clone().encode(), + input.encode(), ::decode( &mut scale::Encode::encode(endowment).as_slice(), )?, From 37f461cb7c111f4be16be36c11d6182ba2cc0492 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Fri, 20 Jan 2023 14:02:48 +0200 Subject: [PATCH 30/43] apply suggestions --- crates/engine/src/ext.rs | 6 ------ crates/env/src/backend.rs | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 7ef9d236a3e..7d9170a6899 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -673,12 +673,6 @@ impl Engine { _call_flags: u32, _output: Vec, ) -> core::result::Result<(), Error> { - // let _tail_call = ((call_flags & 4) >> 2) != 0; - // - // if _tail_call { - // self.exec_context.borrow_mut().output = output; - // } - self.contracts .borrow_mut() .decrease_entrance_count(callee)?; diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 464b0ad5858..875ec0b0c2e 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -240,6 +240,14 @@ pub trait EnvBackend { where T: scale::Decode; + /// Returns the value back to the caller of the executed contract. + /// # Note + /// + /// Calling this method will end contract execution immediately. + /// It will return the given return value back to its caller. + /// + /// The `flags` parameter can be used to revert the state changes of the + /// entire execution if necessary. fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where R: scale::Encode; From 50a8c9b9c62ecfcc8d960a801539b8b8bf3141dc Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 24 Jan 2023 10:52:42 +0200 Subject: [PATCH 31/43] minor code style changes --- crates/engine/src/ext.rs | 2 +- crates/env/src/engine/off_chain/impls.rs | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 7d9170a6899..9739fdf9e5f 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -638,7 +638,7 @@ impl Engine { .set_allow_reentry(caller.as_bytes().to_vec(), allow_reentry); } - // Check if reentrance that is not allowed is encountered + // Check if reentrancy that is not allowed is encountered if !self.contracts.borrow().get_allow_reentry(callee.clone()) && self.contracts.borrow().get_entrance_count(callee.clone()) > 0 { diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 9d8c9b03f94..7c8bdcfb26b 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -571,14 +571,12 @@ impl TypedEnvBackend for EnvInstance { .unwrap(); } - let return_value = - R::decode(&mut self.engine.exec_context.borrow().output.clone().as_slice())?; - - let output = self.engine.exec_context.borrow().output.clone(); - // if the call was reverted, previous one should be reverted too previous_context.reverted |= self.engine.exec_context.borrow().reverted; + let output = self.engine.exec_context.borrow().output.clone(); + let return_value = R::decode(&mut output.as_slice())?; + self.engine.exec_context.replace(previous_context); // apply code flags after the call @@ -637,6 +635,7 @@ impl TypedEnvBackend for EnvInstance { call_fn(); + // revert contract's state in case of error if self.engine.exec_context.borrow().reverted { self.engine .database @@ -649,14 +648,12 @@ impl TypedEnvBackend for EnvInstance { .unwrap(); } - let return_value = - R::decode(&mut self.engine.exec_context.borrow().output.clone().as_slice())?; - - let output = self.engine.exec_context.borrow().output.clone(); - // if the call was reverted, previous one should be reverted too previous_context.reverted |= self.engine.exec_context.borrow().reverted; + let output = self.engine.exec_context.borrow().output.clone(); + let return_value = R::decode(&mut output.as_slice())?; + self.engine.exec_context.replace(previous_context); // apply code flags after the call From c1fcf2f93af48f12ab330fe81c028ece27af3cf3 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 24 Jan 2023 15:55:59 +0200 Subject: [PATCH 32/43] wrap engine into Rc --- crates/engine/src/database.rs | 2 +- crates/engine/src/exec_context.rs | 2 +- crates/engine/src/ext.rs | 102 ++++++----------- crates/engine/src/test_api.rs | 48 +++----- crates/env/src/engine/off_chain/impls.rs | 117 +++++++++++--------- crates/env/src/engine/off_chain/mod.rs | 13 ++- crates/env/src/engine/off_chain/test_api.rs | 61 +++++++--- 7 files changed, 177 insertions(+), 168 deletions(-) diff --git a/crates/engine/src/database.rs b/crates/engine/src/database.rs index 730aebeec25..9f250b36d71 100644 --- a/crates/engine/src/database.rs +++ b/crates/engine/src/database.rs @@ -39,7 +39,7 @@ pub fn storage_of_contract_key(who: &[u8], key: &[u8]) -> [u8; 32] { /// /// Everything is stored in here: accounts, balances, contract storage, etc.. /// Just like in Substrate a prefix hash is computed for every contract. -#[derive(Default)] +#[derive(Default, Clone)] pub struct Database { hmap: HashMap, Vec>, } diff --git a/crates/engine/src/exec_context.rs b/crates/engine/src/exec_context.rs index 000ff3fbfb0..817197c7fd2 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -21,7 +21,7 @@ use super::types::{ /// The context of a contract execution. #[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Default)] +#[derive(Default, Clone)] pub struct ExecContext { /// The caller of the contract execution. Might be user or another contract. /// diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 9739fdf9e5f..bfd6e959a42 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -115,7 +115,7 @@ impl ReturnCode { } } -#[derive(Default)] +#[derive(Default, Clone)] pub struct ContractStorage { pub instantiated: HashMap, Vec>, pub entrance_count: HashMap, u32>, @@ -167,6 +167,7 @@ impl ContractStorage { } } +#[derive(Clone)] pub struct Contract { pub deploy: fn(), pub call: fn(), @@ -176,22 +177,23 @@ pub struct Contract { #[derive(Clone)] pub struct Engine { /// The environment database. - pub database: Rc>, + pub database: Database, /// The current execution context. - pub exec_context: Rc>, + pub exec_context: ExecContext, /// Recorder for relevant interactions with the engine. /// This is specifically about debug info. This info is /// not available in the `contracts` pallet. - pub(crate) debug_info: Rc>, + pub(crate) debug_info: DebugInfo, /// The chain specification. - pub chain_spec: Rc>, + pub chain_spec: ChainSpec, /// Handler for registered chain extensions. pub chain_extension_handler: Rc>, /// Contracts' store. - pub contracts: Rc>, + pub contracts: ContractStorage, } /// The chain specification. +#[derive(Clone)] pub struct ChainSpec { /// The current gas price. pub gas_price: Balance, @@ -223,12 +225,12 @@ impl Engine { // Creates a new `Engine instance. pub fn new() -> Self { Self { - database: Rc::new(RefCell::new(Database::new())), - exec_context: Rc::new(RefCell::new(ExecContext::new())), - debug_info: Rc::new(RefCell::new(DebugInfo::new())), - chain_spec: Rc::new(RefCell::new(ChainSpec::default())), + database: Database::new(), + exec_context: ExecContext::new(), + debug_info: DebugInfo::new(), + chain_spec: ChainSpec::default(), chain_extension_handler: Rc::new(RefCell::new(ChainExtensionHandler::new())), - contracts: Rc::new(RefCell::new(ContractStorage::default())), + contracts: ContractStorage::default(), } } } @@ -256,10 +258,8 @@ impl Engine { .map_err(|_| Error::TransferFailed)?; self.database - .borrow_mut() .set_balance(&contract, contract_old_balance - increment); self.database - .borrow_mut() .set_balance(&dest, dest_old_balance + increment); Ok(()) } @@ -285,7 +285,7 @@ impl Engine { Vec::new() }; - self.debug_info.borrow_mut().record_event(EmittedEvent { + self.debug_info.record_event(EmittedEvent { topics: topics_vec, data: data.to_vec(), }); @@ -297,13 +297,11 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.borrow_mut().inc_writes(account_id.clone()); + self.debug_info.inc_writes(account_id.clone()); self.debug_info - .borrow_mut() .record_cell_for_account(account_id, key.to_vec()); self.database - .borrow_mut() .insert_into_contract_storage(&callee, key, encoded_value.to_vec()) .map(|v| ::try_from(v.len()).expect("usize to u32 conversion failed")) } @@ -313,12 +311,8 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.borrow_mut().inc_reads(account_id); - match self - .database - .borrow_mut() - .get_from_contract_storage(&callee, key) - { + self.debug_info.inc_reads(account_id); + match self.database.get_from_contract_storage(&callee, key) { Some(val) => { set_output(output, val); Ok(()) @@ -333,12 +327,8 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.borrow_mut().inc_writes(account_id); - match self - .database - .borrow_mut() - .remove_contract_storage(&callee, key) - { + self.debug_info.inc_writes(account_id); + match self.database.remove_contract_storage(&callee, key) { Some(val) => { set_output(output, &val); Ok(()) @@ -352,9 +342,8 @@ impl Engine { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.borrow_mut().inc_reads(account_id); + self.debug_info.inc_reads(account_id); self.database - .borrow_mut() .get_from_contract_storage(&callee, key) .map(|val| val.len() as u32) } @@ -364,13 +353,11 @@ impl Engine { pub fn clear_storage(&mut self, key: &[u8]) -> Option { let callee = self.get_callee(); let account_id = AccountId::from_bytes(&callee[..]); - self.debug_info.borrow_mut().inc_writes(account_id.clone()); + self.debug_info.inc_writes(account_id.clone()); let _ = self .debug_info - .borrow_mut() .remove_cell_for_account(account_id, key.to_vec()); self.database - .borrow_mut() .remove_contract_storage(&callee, key) .map(|val| val.len() as u32) } @@ -401,7 +388,6 @@ impl Engine { pub fn caller(&self, output: &mut &mut [u8]) { let caller = self .exec_context - .borrow() .caller .as_ref() .expect("no caller has been set") @@ -413,7 +399,6 @@ impl Engine { pub fn balance(&self, output: &mut &mut [u8]) { let contract = self .exec_context - .borrow() .callee .as_ref() .expect("no callee has been set") @@ -421,7 +406,6 @@ impl Engine { let balance_in_storage = self .database - .borrow() .get_balance(contract.as_bytes()) .expect("currently executing contract must exist"); let balance = scale::Encode::encode(&balance_in_storage); @@ -431,7 +415,7 @@ impl Engine { /// Returns the transferred value for the called contract. pub fn value_transferred(&self, output: &mut &mut [u8]) { let value_transferred: Vec = - scale::Encode::encode(&self.exec_context.borrow().value_transferred); + scale::Encode::encode(&self.exec_context.value_transferred); set_output(output, &value_transferred[..]) } @@ -439,7 +423,6 @@ impl Engine { pub fn address(&self, output: &mut &mut [u8]) { let callee = self .exec_context - .borrow() .callee .as_ref() .expect("no callee has been set") @@ -449,9 +432,7 @@ impl Engine { /// Records the given debug message and appends to stdout. pub fn debug_message(&mut self, message: &str) { - self.debug_info - .borrow_mut() - .record_debug_message(String::from(message)); + self.debug_info.record_debug_message(String::from(message)); print!("{}", message); } @@ -478,14 +459,14 @@ impl Engine { /// Returns the current block number. pub fn block_number(&self, output: &mut &mut [u8]) { let block_number: Vec = - scale::Encode::encode(&self.exec_context.borrow().block_number); + scale::Encode::encode(&self.exec_context.block_number); set_output(output, &block_number[..]) } /// Returns the timestamp of the current block. pub fn block_timestamp(&self, output: &mut &mut [u8]) { let block_timestamp: Vec = - scale::Encode::encode(&self.exec_context.borrow().block_timestamp); + scale::Encode::encode(&self.exec_context.block_timestamp); set_output(output, &block_timestamp[..]) } @@ -497,7 +478,7 @@ impl Engine { /// (i.e. the chain's existential deposit). pub fn minimum_balance(&self, output: &mut &mut [u8]) { let minimum_balance: Vec = - scale::Encode::encode(&self.chain_spec.borrow().minimum_balance); + scale::Encode::encode(&self.chain_spec.minimum_balance); set_output(output, &minimum_balance[..]) } @@ -528,11 +509,7 @@ impl Engine { /// Emulates gas price calculation. pub fn weight_to_fee(&self, gas: u64, output: &mut &mut [u8]) { - let fee = self - .chain_spec - .borrow() - .gas_price - .saturating_mul(gas.into()); + let fee = self.chain_spec.gas_price.saturating_mul(gas.into()); let fee: Vec = scale::Encode::encode(&fee); set_output(output, &fee[..]) } @@ -545,8 +522,8 @@ impl Engine { output: &mut &mut [u8], ) { let encoded_input = input.encode(); - let mut chain_extension_handler = self.chain_extension_handler.borrow_mut(); - let (status_code, out) = chain_extension_handler + let mut chain_extension_hanler = self.chain_extension_handler.borrow_mut(); + let (status_code, out) = chain_extension_hanler .eval(func_id, &encoded_input) .unwrap_or_else(|error| { panic!( @@ -614,7 +591,6 @@ impl Engine { call: fn(), ) -> Option { self.contracts - .borrow_mut() .deployed .insert(hash.to_vec(), Contract { deploy, call }) } @@ -634,30 +610,27 @@ impl Engine { // Allow/deny reentrancy to the caller if let Some(caller) = caller { self.contracts - .borrow_mut() .set_allow_reentry(caller.as_bytes().to_vec(), allow_reentry); } // Check if reentrancy that is not allowed is encountered - if !self.contracts.borrow().get_allow_reentry(callee.clone()) - && self.contracts.borrow().get_entrance_count(callee.clone()) > 0 + if !self.contracts.get_allow_reentry(callee.clone()) + && self.contracts.get_entrance_count(callee.clone()) > 0 { return Err(Error::CalleeTrapped) } - self.contracts - .borrow_mut() - .increase_entrance_count(callee)?; + self.contracts.increase_entrance_count(callee)?; let new_input = if forward_input { - let previous_input = self.exec_context.borrow().input.clone(); + let previous_input = self.exec_context.input.clone(); // delete the input because we will forward it - self.exec_context.borrow_mut().input.clear(); + self.exec_context.input.clear(); previous_input } else if clone_input { - self.exec_context.borrow().input.clone() + self.exec_context.input.clone() } else { input }; @@ -673,13 +646,10 @@ impl Engine { _call_flags: u32, _output: Vec, ) -> core::result::Result<(), Error> { - self.contracts - .borrow_mut() - .decrease_entrance_count(callee)?; + self.contracts.decrease_entrance_count(callee)?; if let Some(caller) = caller { self.contracts - .borrow_mut() .allow_reentry .remove(&caller.as_bytes().to_vec()); } diff --git a/crates/engine/src/test_api.rs b/crates/engine/src/test_api.rs index c1b54cef6e8..f1168bea44d 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -72,6 +72,7 @@ impl IntoIterator for RecordedDebugMessages { } /// Recorder for relevant interactions with this crate. +#[derive(Clone)] pub struct DebugInfo { /// Emitted events recorder. emitted_events: Vec, @@ -173,47 +174,37 @@ impl DebugInfo { impl Engine { /// Resets the environment. pub fn initialize_or_reset(&mut self) { - self.exec_context.borrow_mut().reset(); - self.database.borrow_mut().clear(); - self.debug_info.borrow_mut().reset(); + self.exec_context.reset(); + self.database.clear(); + self.debug_info.reset(); } /// Returns the total number of reads and writes of the contract's storage. pub fn get_contract_storage_rw(&self, account_id: Vec) -> (usize, usize) { let account_id = AccountId::from(account_id); - let reads = *self - .debug_info - .borrow() - .count_reads - .get(&account_id) - .unwrap_or(&0); - let writes = *self - .debug_info - .borrow() - .count_writes - .get(&account_id) - .unwrap_or(&0); + let reads = *self.debug_info.count_reads.get(&account_id).unwrap_or(&0); + let writes = *self.debug_info.count_writes.get(&account_id).unwrap_or(&0); (reads, writes) } /// Returns the total number of reads executed. pub fn count_reads(&self) -> usize { - self.debug_info.borrow().count_reads.values().sum() + self.debug_info.count_reads.values().sum() } /// Returns the total number of writes executed. pub fn count_writes(&self) -> usize { - self.debug_info.borrow().count_writes.values().sum() + self.debug_info.count_writes.values().sum() } /// Sets a caller for the next call. pub fn set_caller(&mut self, caller: Vec) { - self.exec_context.borrow_mut().caller = Some(caller.into()); + self.exec_context.caller = Some(caller.into()); } /// Sets the callee for the next call. pub fn set_callee(&mut self, callee: Vec) { - self.exec_context.borrow_mut().callee = Some(callee.into()); + self.exec_context.callee = Some(callee.into()); } /// Returns the amount of storage cells used by the account `account_id`. @@ -222,7 +213,6 @@ impl Engine { pub fn count_used_storage_cells(&self, account_id: &[u8]) -> Result { let cells_len = self .debug_info - .borrow() .cells_per_account .get(&account_id.to_owned().into()) .ok_or_else(|| { @@ -234,44 +224,40 @@ impl Engine { /// Advances the chain by a single block. pub fn advance_block(&mut self) { - self.exec_context.borrow_mut().block_number += 1; - self.exec_context.borrow_mut().block_timestamp += - self.chain_spec.borrow().block_time; + self.exec_context.block_number += 1; + self.exec_context.block_timestamp += self.chain_spec.block_time; } /// Returns the callee, i.e. the currently executing contract. pub fn get_callee(&self) -> Vec { - self.exec_context.borrow().callee() + self.exec_context.callee() } /// Returns the contents of the past performed environmental `debug_message` in order. pub fn get_emitted_debug_messages(&self) -> RecordedDebugMessages { - self.debug_info.borrow().emitted_debug_messages.clone() + self.debug_info.emitted_debug_messages.clone() } /// Returns the recorded emitted events in order. pub fn get_emitted_events(&self) -> impl Iterator { - self.debug_info.borrow().emitted_events.clone().into_iter() + self.debug_info.emitted_events.clone().into_iter() } /// Returns the current balance of `account_id`. pub fn get_balance(&self, account_id: Vec) -> Result { self.database - .borrow() .get_balance(&account_id) .ok_or(Error::Account(AccountError::NoAccountForId(account_id))) } /// Sets the balance of `account_id` to `new_balance`. pub fn set_balance(&mut self, account_id: Vec, new_balance: Balance) { - self.database - .borrow_mut() - .set_balance(&account_id, new_balance); + self.database.set_balance(&account_id, new_balance); } /// Sets the value transferred from the caller to the callee as part of the call. pub fn set_value_transferred(&mut self, value: Balance) { - self.exec_context.borrow_mut().value_transferred = value; + self.exec_context.value_transferred = value; } } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 7c8bdcfb26b..bb2d493a00f 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -40,6 +40,7 @@ use crate::{ ReturnFlags, TypedEnvBackend, }; +use core::mem; use ink_engine::{ exec_context::ExecContext, ext, @@ -47,7 +48,6 @@ use ink_engine::{ }; use ink_storage_traits::Storable; use scale::Encode; - /// The capacity of the static buffer. /// This is the same size as the ink! on-chain environment. We chose to use the same size /// to be as close to the on-chain behavior as possible. @@ -180,7 +180,7 @@ impl EnvInstance { { let mut full_scope: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let full_scope = &mut &mut full_scope[..]; - ext_fn(&self.engine, full_scope); + ext_fn(&self.engine.borrow_mut(), full_scope); scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into) } @@ -207,25 +207,25 @@ impl EnvInstance { transferred_value: u128, ) -> ExecContext { let callee_context = ExecContext { - caller: self.engine.exec_context.borrow().callee.clone(), + caller: self.engine.borrow().exec_context.callee.clone(), callee: Some(callee.clone().into()), value_transferred: transferred_value, - block_number: self.engine.exec_context.borrow().block_number, - block_timestamp: self.engine.exec_context.borrow().block_timestamp, + block_number: self.engine.borrow().exec_context.block_number, + block_timestamp: self.engine.borrow().exec_context.block_timestamp, input, output: vec![], reverted: false, origin: Some( self.engine - .exec_context .borrow() + .exec_context .origin .clone() .unwrap_or(callee), ), }; - self.engine.exec_context.replace(callee_context) + mem::replace(&mut self.engine.borrow_mut().exec_context, callee_context) } } @@ -237,7 +237,7 @@ impl EnvBackend for EnvInstance { { let mut v = vec![]; Storable::encode(value, &mut v); - self.engine.set_storage(&key.encode(), &v[..]) + self.engine.borrow_mut().set_storage(&key.encode(), &v[..]) } fn get_contract_storage(&mut self, key: &K) -> Result> @@ -246,7 +246,11 @@ impl EnvBackend for EnvInstance { R: Storable, { let mut output: [u8; 9600] = [0; 9600]; - match self.engine.get_storage(&key.encode(), &mut &mut output[..]) { + match self + .engine + .borrow_mut() + .get_storage(&key.encode(), &mut &mut output[..]) + { Ok(_) => (), Err(ext::Error::KeyNotFound) => return Ok(None), Err(_) => panic!("encountered unexpected error"), @@ -263,6 +267,7 @@ impl EnvBackend for EnvInstance { let mut output: [u8; 9600] = [0; 9600]; match self .engine + .borrow_mut() .take_storage(&key.encode(), &mut &mut output[..]) { Ok(_) => (), @@ -277,21 +282,21 @@ impl EnvBackend for EnvInstance { where K: scale::Encode, { - self.engine.contains_storage(&key.encode()) + self.engine.borrow_mut().contains_storage(&key.encode()) } fn clear_contract_storage(&mut self, key: &K) -> Option where K: scale::Encode, { - self.engine.clear_storage(&key.encode()) + self.engine.borrow_mut().clear_storage(&key.encode()) } fn decode_input(&mut self) -> Result where T: scale::Decode, { - T::decode(&mut self.engine.exec_context.borrow().input.as_slice()) + T::decode(&mut self.engine.borrow().exec_context.input.as_slice()) .map_err(|_| Error::CalleeTrapped) } @@ -301,13 +306,13 @@ impl EnvBackend for EnvInstance { R: scale::Encode, { if flags.is_reverted() { - self.engine.exec_context.borrow_mut().reverted = true; + self.engine.borrow_mut().exec_context.reverted = true; } - self.engine.exec_context.borrow_mut().output = return_value.encode(); + self.engine.borrow_mut().exec_context.output = return_value.encode(); } fn debug_message(&mut self, message: &str) { - self.engine.debug_message(message) + self.engine.borrow_mut().debug_message(message) } fn hash_bytes(&mut self, input: &[u8], output: &mut ::Type) @@ -400,8 +405,11 @@ impl EnvBackend for EnvInstance { let enc_input = &scale::Encode::encode(input)[..]; let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - self.engine - .call_chain_extension(func_id, enc_input, &mut &mut output[..]); + self.engine.borrow_mut().call_chain_extension( + func_id, + enc_input, + &mut &mut output[..], + ); let (status, out): (u32, Vec) = scale::Decode::decode(&mut &output[..]) .unwrap_or_else(|error| { panic!( @@ -418,15 +426,15 @@ impl EnvBackend for EnvInstance { fn set_code_hash(&mut self, code_hash: &[u8]) -> Result<()> { let account_id = self .engine - .exec_context .borrow() + .exec_context .callee .clone() .expect("callee is none"); self.engine - .contracts .borrow_mut() + .contracts .instantiated .insert(account_id.as_bytes().to_vec(), code_hash.to_vec()); Ok(()) @@ -498,7 +506,9 @@ impl TypedEnvBackend for EnvInstance { let builder = TopicsBuilder::default(); let enc_topics = event.topics::(builder.into()); let enc_data = &scale::Encode::encode(&event)[..]; - self.engine.deposit_event(&enc_topics[..], enc_data); + self.engine + .borrow_mut() + .deposit_event(&enc_topics[..], enc_data); } fn invoke_contract( @@ -515,10 +525,10 @@ impl TypedEnvBackend for EnvInstance { let call_flags = params.call_flags().into_u32(); let transferred_value = params.transferred_value(); - let caller = self.engine.exec_context.borrow().callee.clone(); + let caller = self.engine.borrow().exec_context.callee.clone(); // apply call flags before making a call and return the input that might be changed after that - let input = self.engine.apply_code_flags_before_call( + let input = self.engine.borrow_mut().apply_code_flags_before_call( caller.clone(), callee.clone(), call_flags, @@ -535,8 +545,8 @@ impl TypedEnvBackend for EnvInstance { let code_hash = self .engine - .contracts .borrow() + .contracts .instantiated .get(&callee) .ok_or(Error::NotCallable)? @@ -544,8 +554,8 @@ impl TypedEnvBackend for EnvInstance { let call_fn = self .engine - .contracts .borrow() + .contracts .deployed .get(&code_hash) .ok_or(Error::CodeNotFound)? @@ -554,8 +564,8 @@ impl TypedEnvBackend for EnvInstance { // save previous version of storage in case call will revert let storage = self .engine - .database .borrow() + .database .get_from_contract_storage(callee.as_slice(), &[0; 4]) .expect("contract storage not found") .clone(); @@ -563,24 +573,26 @@ impl TypedEnvBackend for EnvInstance { call_fn(); // revert contract's state in case of error - if self.engine.exec_context.borrow().reverted { + if self.engine.borrow().exec_context.reverted { self.engine - .database .borrow_mut() + .database .insert_into_contract_storage(callee.as_slice(), &[0; 4], storage) .unwrap(); } // if the call was reverted, previous one should be reverted too - previous_context.reverted |= self.engine.exec_context.borrow().reverted; + previous_context.reverted |= self.engine.borrow().exec_context.reverted; - let output = self.engine.exec_context.borrow().output.clone(); + let output = self.engine.borrow().exec_context.output.clone(); let return_value = R::decode(&mut output.as_slice())?; - self.engine.exec_context.replace(previous_context); + let _ = + mem::replace(&mut self.engine.borrow_mut().exec_context, previous_context); // apply code flags after the call self.engine + .borrow_mut() .apply_code_flags_after_call(caller, callee, call_flags, output)?; Ok(return_value) @@ -596,11 +608,11 @@ impl TypedEnvBackend for EnvInstance { R: scale::Decode, { let code_hash = params.code_hash().as_ref().to_vec(); - let callee = self.engine.exec_context.borrow().callee.clone(); + let callee = self.engine.borrow().exec_context.callee.clone(); let call_flags = params.call_flags().into_u32(); // apply call flags before making a call and return the input that might be changed after that - let input = self.engine.apply_code_flags_before_call( + let input = self.engine.borrow_mut().apply_code_flags_before_call( callee.clone(), callee.clone().unwrap_or_default().as_bytes().to_vec(), call_flags, @@ -609,8 +621,8 @@ impl TypedEnvBackend for EnvInstance { let call_fn = self .engine - .contracts .borrow() + .contracts .deployed .get(&code_hash) .ok_or(Error::CodeNotFound)? @@ -624,8 +636,8 @@ impl TypedEnvBackend for EnvInstance { let storage = self .engine - .database .borrow() + .database .get_from_contract_storage( callee.clone().unwrap_or_default().as_bytes(), &[0; 4], @@ -636,10 +648,10 @@ impl TypedEnvBackend for EnvInstance { call_fn(); // revert contract's state in case of error - if self.engine.exec_context.borrow().reverted { + if self.engine.borrow().exec_context.reverted { self.engine - .database .borrow_mut() + .database .insert_into_contract_storage( callee.clone().unwrap_or_default().as_bytes(), &[0; 4], @@ -649,15 +661,16 @@ impl TypedEnvBackend for EnvInstance { } // if the call was reverted, previous one should be reverted too - previous_context.reverted |= self.engine.exec_context.borrow().reverted; + previous_context.reverted |= self.engine.borrow().exec_context.reverted; - let output = self.engine.exec_context.borrow().output.clone(); + let output = self.engine.borrow().exec_context.output.clone(); let return_value = R::decode(&mut output.as_slice())?; - self.engine.exec_context.replace(previous_context); + let _ = + mem::replace(&mut self.engine.borrow_mut().exec_context, previous_context); // apply code flags after the call - self.engine.apply_code_flags_after_call( + self.engine.borrow_mut().apply_code_flags_after_call( callee.clone(), callee.unwrap_or_default().as_bytes().to_vec(), call_flags, @@ -679,7 +692,7 @@ impl TypedEnvBackend for EnvInstance { let code_hash = params.code_hash().as_ref().to_vec(); // Gas is not supported by off-chain env. let _gas_limit = params.gas_limit(); - let caller = self.engine.exec_context.borrow().callee.clone(); + let caller = self.engine.borrow().exec_context.callee.clone(); let endowment = params.endowment(); let input = params.exec_input(); let salt_bytes = params.salt_bytes(); @@ -703,22 +716,23 @@ impl TypedEnvBackend for EnvInstance { let deploy_fn = self .engine - .contracts .borrow() + .contracts .deployed .get(&code_hash) .ok_or(Error::CodeNotFound)? .deploy; self.engine - .contracts .borrow_mut() + .contracts .instantiated .insert(callee.clone(), code_hash); deploy_fn(); - self.engine.exec_context.replace(previous_context); + let _ = + mem::replace(&mut self.engine.borrow_mut().exec_context, previous_context); Ok(<_ as scale::Decode>::decode(&mut callee.as_slice())?) } @@ -728,7 +742,7 @@ impl TypedEnvBackend for EnvInstance { E: Environment, { let buffer = scale::Encode::encode(&beneficiary); - self.engine.terminate(&buffer[..]) + self.engine.borrow_mut().terminate(&buffer[..]) } fn transfer(&mut self, destination: E::AccountId, value: E::Balance) -> Result<()> @@ -738,13 +752,16 @@ impl TypedEnvBackend for EnvInstance { let enc_destination = &scale::Encode::encode(&destination)[..]; let enc_value = &scale::Encode::encode(&value)[..]; self.engine + .borrow_mut() .transfer(enc_destination, enc_value) .map_err(Into::into) } fn weight_to_fee(&mut self, gas: u64) -> E::Balance { let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - self.engine.weight_to_fee(gas, &mut &mut output[..]); + self.engine + .borrow_mut() + .weight_to_fee(gas, &mut &mut output[..]); scale::Decode::decode(&mut &output[..]).unwrap_or_else(|error| { panic!("could not read `weight_to_fee` property: {:?}", error) }) @@ -755,8 +772,8 @@ impl TypedEnvBackend for EnvInstance { E: Environment, { self.engine - .contracts .borrow() + .contracts .instantiated .contains_key(account.as_ref().to_vec().as_slice()) } @@ -766,15 +783,15 @@ impl TypedEnvBackend for EnvInstance { E: Environment, { self.engine - .exec_context .borrow() + .exec_context .origin .clone() .expect("stack should not be empty") == self .engine - .exec_context .borrow() + .exec_context .caller .clone() .expect("caller should exist") @@ -787,8 +804,8 @@ impl TypedEnvBackend for EnvInstance { { let code_hash = self .engine - .contracts .borrow() + .contracts .instantiated .get(&account.as_ref().to_vec()) .ok_or(Error::NotCallable)? diff --git a/crates/env/src/engine/off_chain/mod.rs b/crates/env/src/engine/off_chain/mod.rs index 1639c296117..bb876b33be7 100644 --- a/crates/env/src/engine/off_chain/mod.rs +++ b/crates/env/src/engine/off_chain/mod.rs @@ -20,10 +20,13 @@ mod types; #[cfg(test)] mod tests; -pub use call_data::CallData; - use super::OnInstance; use crate::Error; +pub use call_data::CallData; +use std::{ + cell::RefCell, + rc::Rc, +}; use derive_more::From; use ink_engine::ext::Engine; @@ -31,7 +34,7 @@ use ink_engine::ext::Engine; /// The off-chain environment. #[derive(Clone)] pub struct EnvInstance { - engine: Engine, + engine: Rc>, } impl OnInstance for EnvInstance { @@ -41,8 +44,8 @@ impl OnInstance for EnvInstance { { thread_local!( static INSTANCE: EnvInstance = EnvInstance { - engine: Engine::new() - } + engine: Rc::new(RefCell::new(Engine::new())) + } ); INSTANCE.with(|instance| f(&mut instance.clone())) } diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index d92803b6b19..1e640412d7b 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -59,6 +59,7 @@ where ::on_instance(|instance| { instance .engine + .borrow_mut() .set_balance(scale::Encode::encode(&account_id), new_balance); }) } @@ -82,6 +83,7 @@ where ::on_instance(|instance| { instance .engine + .borrow() .get_balance(scale::Encode::encode(&account_id)) .map_err(Into::into) }) @@ -95,6 +97,7 @@ where ::on_instance(|instance| { instance .engine + .borrow_mut() .chain_extension_handler .borrow_mut() .register(Box::new(extension)); @@ -104,7 +107,7 @@ where /// Returns the contents of the past performed environmental debug messages in order. pub fn recorded_debug_messages() -> RecordedDebugMessages { ::on_instance(|instance| { - instance.engine.get_emitted_debug_messages() + instance.engine.borrow().get_emitted_debug_messages() }) } @@ -126,7 +129,7 @@ where T: Environment, { ::on_instance(|instance| { - instance.engine.advance_block(); + instance.engine.borrow_mut().advance_block(); }) } @@ -137,7 +140,10 @@ where ::AccountId: From<[u8; 32]>, { ::on_instance(|instance| { - instance.engine.set_caller(scale::Encode::encode(&caller)); + instance + .engine + .borrow_mut() + .set_caller(scale::Encode::encode(&caller)); }) } @@ -148,7 +154,10 @@ where ::AccountId: From<[u8; 32]>, { ::on_instance(|instance| { - instance.engine.set_callee(scale::Encode::encode(&callee)); + instance + .engine + .borrow_mut() + .set_callee(scale::Encode::encode(&callee)); }) } @@ -160,7 +169,7 @@ where T: Environment, { ::on_instance(|instance| { - let callee = instance.engine.get_callee(); + let callee = instance.engine.borrow().get_callee(); scale::Decode::decode(&mut &callee[..]) .unwrap_or_else(|err| panic!("encoding failed: {}", err)) }) @@ -174,6 +183,7 @@ where ::on_instance(|instance| { instance .engine + .borrow() .get_contract_storage_rw(scale::Encode::encode(&account_id)) }) } @@ -186,7 +196,7 @@ where T: Environment, // Just temporary for the MVP! { ::on_instance(|instance| { - instance.engine.set_value_transferred(value); + instance.engine.borrow_mut().set_value_transferred(value); }) } @@ -200,8 +210,8 @@ where ::on_instance(|instance| { let caller = instance .engine + .borrow() .exec_context - .borrow_mut() .caller .as_ref() .expect("no caller has been set") @@ -210,22 +220,26 @@ where let caller_old_balance = instance .engine + .borrow() .get_balance(caller.clone()) .unwrap_or_default(); - let callee = instance.engine.get_callee(); + let callee = instance.engine.borrow().get_callee(); let contract_old_balance = instance .engine + .borrow() .get_balance(callee.clone()) .unwrap_or_default(); instance .engine + .borrow_mut() .set_balance(caller, caller_old_balance - value); instance .engine + .borrow_mut() .set_balance(callee, contract_old_balance + value); - instance.engine.set_value_transferred(value); + instance.engine.borrow_mut().set_value_transferred(value); }); } @@ -239,6 +253,7 @@ where ::on_instance(|instance| { instance .engine + .borrow() .count_used_storage_cells(&scale::Encode::encode(&account_id)) .map_err(Into::into) }) @@ -254,30 +269,44 @@ where { let default_accounts = default_accounts::(); ::on_instance(|instance| { - instance.engine.initialize_or_reset(); + instance.engine.borrow_mut().initialize_or_reset(); let encoded_alice = scale::Encode::encode(&default_accounts.alice); - instance.engine.set_caller(encoded_alice.clone()); - instance.engine.set_callee(encoded_alice.clone()); + instance + .engine + .borrow_mut() + .set_caller(encoded_alice.clone()); + instance + .engine + .borrow_mut() + .set_callee(encoded_alice.clone()); // set up the funds for the default accounts let substantial = 1_000_000; let some = 1_000; - instance.engine.set_balance(encoded_alice, substantial); instance .engine + .borrow_mut() + .set_balance(encoded_alice, substantial); + instance + .engine + .borrow_mut() .set_balance(scale::Encode::encode(&default_accounts.bob), some); instance .engine + .borrow_mut() .set_balance(scale::Encode::encode(&default_accounts.charlie), some); instance .engine + .borrow_mut() .set_balance(scale::Encode::encode(&default_accounts.django), 0); instance .engine + .borrow_mut() .set_balance(scale::Encode::encode(&default_accounts.eve), 0); instance .engine + .borrow_mut() .set_balance(scale::Encode::encode(&default_accounts.frank), 0); }); f(default_accounts) @@ -324,6 +353,7 @@ pub fn recorded_events() -> impl Iterator { ::on_instance(|instance| { instance .engine + .borrow() .get_emitted_events() .into_iter() .map(|evt: ink_engine::test_api::EmittedEvent| evt.into()) @@ -338,7 +368,10 @@ where ::on_instance(|instance| { let deploy = C::deploy; let call = C::call; - instance.engine.register_contract(code_hash, deploy, call) + instance + .engine + .borrow_mut() + .register_contract(code_hash, deploy, call) }) } From cef7ab20d8dde5dc21b4972b99d83e5ca4b60c9b Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 24 Jan 2023 20:42:38 +0200 Subject: [PATCH 33/43] use `let engine = self.engine.borrow()` where it is possible --- crates/env/src/engine/off_chain/impls.rs | 25 +++++------- crates/env/src/engine/off_chain/test_api.rs | 44 ++++++--------------- 2 files changed, 20 insertions(+), 49 deletions(-) diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index bb2d493a00f..4896aca520a 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -206,23 +206,17 @@ impl EnvInstance { input: Vec, transferred_value: u128, ) -> ExecContext { + let engine = self.engine.borrow(); let callee_context = ExecContext { - caller: self.engine.borrow().exec_context.callee.clone(), + caller: engine.exec_context.callee.clone(), callee: Some(callee.clone().into()), value_transferred: transferred_value, - block_number: self.engine.borrow().exec_context.block_number, - block_timestamp: self.engine.borrow().exec_context.block_timestamp, + block_number: engine.exec_context.block_number, + block_timestamp: engine.exec_context.block_timestamp, input, output: vec![], reverted: false, - origin: Some( - self.engine - .borrow() - .exec_context - .origin - .clone() - .unwrap_or(callee), - ), + origin: Some(engine.exec_context.origin.clone().unwrap_or(callee)), }; mem::replace(&mut self.engine.borrow_mut().exec_context, callee_context) @@ -782,15 +776,14 @@ impl TypedEnvBackend for EnvInstance { where E: Environment, { - self.engine - .borrow() + let engine = self.engine.borrow(); + + engine .exec_context .origin .clone() .expect("stack should not be empty") - == self - .engine - .borrow() + == engine .exec_context .caller .clone() diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index 1e640412d7b..afb0bc71680 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -269,45 +269,23 @@ where { let default_accounts = default_accounts::(); ::on_instance(|instance| { - instance.engine.borrow_mut().initialize_or_reset(); + let mut engine = instance.engine.borrow_mut(); + engine.initialize_or_reset(); let encoded_alice = scale::Encode::encode(&default_accounts.alice); - instance - .engine - .borrow_mut() - .set_caller(encoded_alice.clone()); - instance - .engine - .borrow_mut() - .set_callee(encoded_alice.clone()); + engine.set_caller(encoded_alice.clone()); + engine.set_callee(encoded_alice.clone()); // set up the funds for the default accounts let substantial = 1_000_000; let some = 1_000; - instance - .engine - .borrow_mut() - .set_balance(encoded_alice, substantial); - instance - .engine - .borrow_mut() - .set_balance(scale::Encode::encode(&default_accounts.bob), some); - instance - .engine - .borrow_mut() - .set_balance(scale::Encode::encode(&default_accounts.charlie), some); - instance - .engine - .borrow_mut() - .set_balance(scale::Encode::encode(&default_accounts.django), 0); - instance - .engine - .borrow_mut() - .set_balance(scale::Encode::encode(&default_accounts.eve), 0); - instance - .engine - .borrow_mut() - .set_balance(scale::Encode::encode(&default_accounts.frank), 0); + + engine.set_balance(encoded_alice, substantial); + engine.set_balance(scale::Encode::encode(&default_accounts.bob), some); + engine.set_balance(scale::Encode::encode(&default_accounts.charlie), some); + engine.set_balance(scale::Encode::encode(&default_accounts.django), 0); + engine.set_balance(scale::Encode::encode(&default_accounts.eve), 0); + engine.set_balance(scale::Encode::encode(&default_accounts.frank), 0); }); f(default_accounts) } From bc5bb689279015492ddbf2299fdb912283376abb Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 25 Jan 2023 20:26:43 +0200 Subject: [PATCH 34/43] fix compilation with ink! 4.0.0-beta.1 --- crates/env/src/contract.rs | 4 +-- crates/env/src/engine/off_chain/impls.rs | 31 +++++++++++++++----- crates/ink/codegen/src/generator/dispatch.rs | 4 +-- examples/reentrancy/lib.rs | 6 ++-- examples/reentrancy/main_contract/lib.rs | 5 ++-- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/crates/env/src/contract.rs b/crates/env/src/contract.rs index 6ac0ebc63f7..fd4f329462f 100644 --- a/crates/env/src/contract.rs +++ b/crates/env/src/contract.rs @@ -134,10 +134,10 @@ pub trait ContractReference { } /// Entrypoint of the contract to execute constructors or messages. - /// Entrypoint to run a constructor for the contract. It deploys the contract to the environment. pub trait Entrypoint { + /// Entrypoint to run a constructor for the contract. It deploys the contract to the environment. fn deploy(); /// Entrypoint to run a message of the contract. fn call(); -} \ No newline at end of file +} diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 24778f10be9..9566d209bd5 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -208,17 +208,23 @@ impl EnvInstance { input: Vec, transferred_value: u128, ) -> ExecContext { - let engine = self.engine.borrow(); let callee_context = ExecContext { - caller: engine.exec_context.callee.clone(), + caller: self.engine.borrow().exec_context.callee.clone(), callee: Some(callee.clone().into()), value_transferred: transferred_value, - block_number: engine.exec_context.block_number, - block_timestamp: engine.exec_context.block_timestamp, + block_number: self.engine.borrow().exec_context.block_number, + block_timestamp: self.engine.borrow().exec_context.block_timestamp, input, output: vec![], reverted: false, - origin: Some(engine.exec_context.origin.clone().unwrap_or(callee)), + origin: Some( + self.engine + .borrow() + .exec_context + .origin + .clone() + .unwrap_or(callee), + ), }; mem::replace(&mut self.engine.borrow_mut().exec_context, callee_context) @@ -581,7 +587,7 @@ impl TypedEnvBackend for EnvInstance { previous_context.reverted |= self.engine.borrow().exec_context.reverted; let output = self.engine.borrow().exec_context.output.clone(); - let return_value = R::decode(&mut output.as_slice())?; + let return_value = scale::Decode::decode(&mut output.as_slice())?; let _ = mem::replace(&mut self.engine.borrow_mut().exec_context, previous_context); @@ -733,10 +739,21 @@ impl TypedEnvBackend for EnvInstance { deploy_fn(); + let output = self.engine.borrow().exec_context.output.clone(); + let instantiate_result = if self.engine.borrow().exec_context.reverted { + Err(Error::CalleeReverted) + } else { + Ok(()) + }; + let _ = mem::replace(&mut self.engine.borrow_mut().exec_context, previous_context); - Ok(<_ as scale::Decode>::decode(&mut callee.as_slice())?) + crate::engine::decode_instantiate_result::<_, E, ContractRef, R>( + instantiate_result.map_err(Into::into), + &mut &callee[..], + &mut &output[..], + ) } fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 3f5d6378e59..81b03f6bda4 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -424,7 +424,7 @@ impl Dispatch<'_> { #[allow(clippy::nonminimal_bool)] fn deploy() { if !#any_constructor_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() + ::ink::codegen::deny_payment::<<#storage_ident as ::ink::env::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } @@ -461,7 +461,7 @@ impl Dispatch<'_> { #[allow(clippy::nonminimal_bool)] fn call() { if !#any_message_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() + ::ink::codegen::deny_payment::<<#storage_ident as ::ink::env::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index 676998e2a79..ef639ad105d 100755 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -91,15 +91,13 @@ mod test { .code_hash(hash1) .endowment(0) .salt_bytes([0u8; 0]) - .instantiate() - .expect("failed at instantiating the `main_contractRef` contract"); + .instantiate(); let fallback_contract = FallbackContractRef::new(main_contract.clone()) .code_hash(hash2) .endowment(0) .salt_bytes([0u8; 0]) - .instantiate() - .expect("failed at instantiating the `fallback_contractRef` contract"); + .instantiate(); let address1 = main_contract.get_address(); diff --git a/examples/reentrancy/main_contract/lib.rs b/examples/reentrancy/main_contract/lib.rs index c0eb8e1649b..65a0baf4b85 100755 --- a/examples/reentrancy/main_contract/lib.rs +++ b/examples/reentrancy/main_contract/lib.rs @@ -88,8 +88,9 @@ mod main_contract { build_call::() .call_type(Call::new().callee(self.callee)) .call_flags(CallFlags::default().set_allow_reentry(true)) - .fire() - .unwrap_or_else(|err| panic!("failed to call callee: {:?}", err)); + .try_invoke() + .unwrap_or_else(|err| panic!("failed to call callee: {:?}", err)) + .unwrap_or_else(|err| panic!("callee reverted: {:?}", err)); let mut state = ink::env::get_contract_storage( &::KEY, From 9c8f9517323f071cde3b3e634c3c3512c3d68a4c Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 25 Jan 2023 20:48:24 +0200 Subject: [PATCH 35/43] fix tests and update test in delegator example --- .../tests/ui/contract/fail/message-returns-non-codec.stderr | 2 +- .../tests/ui/trait_def/fail/message_output_non_codec.stderr | 2 +- examples/delegator/adder/lib.rs | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr index 8b9240b02c3..ec7ba408570 100644 --- a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -46,7 +46,7 @@ error[E0599]: the method `try_invoke` exists for struct `ink::ink_env::call::Cal = note: the following trait bounds were not satisfied: `NonCodecType: parity_scale_codec::Decode` note: the following trait must be implemented - --> $CARGO/parity-scale-codec-3.2.2/src/codec.rs + --> $CARGO/parity-scale-codec-3.2.1/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr index 46e1e66dc6d..e5224df245d 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -33,7 +33,7 @@ error[E0599]: the method `try_invoke` exists for struct `CallBuilder $CARGO/parity-scale-codec-3.2.2/src/codec.rs + --> $CARGO/parity-scale-codec-3.2.1/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/delegator/adder/lib.rs b/examples/delegator/adder/lib.rs index a665f7e9d5b..a4ca6c7a981 100644 --- a/examples/delegator/adder/lib.rs +++ b/examples/delegator/adder/lib.rs @@ -53,14 +53,12 @@ mod test { .code_hash(hash1) .endowment(0) .salt_bytes([0u8; 0]) - .instantiate() - .expect("failed at instantiating the `AccumulatorRef` contract"); + .instantiate(); let mut adder = AdderRef::new(acc.clone()) .code_hash(hash2) .endowment(0) .salt_bytes([0u8; 0]) - .instantiate() - .expect("failed at instantiating the `AdderRef` contract"); + .instantiate(); assert_eq!(acc.get(), 0); adder.inc(1); From f0b1f359cd9a4263b3386aa8dd8e7ab2c6ecc088 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 26 Jan 2023 10:10:28 +0200 Subject: [PATCH 36/43] add unreachable to test to work with our api --- .../constructors-return-value/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/lang-err-integration-tests/constructors-return-value/lib.rs b/examples/lang-err-integration-tests/constructors-return-value/lib.rs index 111b1daa2c3..99835debba4 100644 --- a/examples/lang-err-integration-tests/constructors-return-value/lib.rs +++ b/examples/lang-err-integration-tests/constructors-return-value/lib.rs @@ -41,7 +41,8 @@ pub mod constructors_return_value { ink::env::return_value::>( ink::env::ReturnFlags::new_with_reverted(true), &Ok(AccountId::from([0u8; 32])), - ) + ); + unreachable!() } /// A constructor which reverts and fills the output buffer with an erroneously encoded @@ -56,7 +57,8 @@ pub mod constructors_return_value { ink::env::return_value::< ink::ConstructorResult>, - >(ink::env::ReturnFlags::new_with_reverted(true), &value) + >(ink::env::ReturnFlags::new_with_reverted(true), &value); + unreachable!(); } /// Returns the current value of the contract storage. From edb9b7b06481849429c7f5fc191b56270f7cd558 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Thu, 26 Jan 2023 10:53:45 +0200 Subject: [PATCH 37/43] overwrite ui tests --- .../ink/tests/ui/contract/fail/message-returns-non-codec.stderr | 2 +- .../ink/tests/ui/trait_def/fail/message_output_non_codec.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr index ec7ba408570..8b9240b02c3 100644 --- a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -46,7 +46,7 @@ error[E0599]: the method `try_invoke` exists for struct `ink::ink_env::call::Cal = note: the following trait bounds were not satisfied: `NonCodecType: parity_scale_codec::Decode` note: the following trait must be implemented - --> $CARGO/parity-scale-codec-3.2.1/src/codec.rs + --> $CARGO/parity-scale-codec-3.2.2/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr index e5224df245d..46e1e66dc6d 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -33,7 +33,7 @@ error[E0599]: the method `try_invoke` exists for struct `CallBuilder $CARGO/parity-scale-codec-3.2.1/src/codec.rs + --> $CARGO/parity-scale-codec-3.2.2/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ From 0a14dc25c9c27dfa8b7fc9ca7c9e0d875cfd30bf Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Fri, 27 Jan 2023 11:09:24 +0200 Subject: [PATCH 38/43] delete unused methods in examples --- crates/engine/src/ext.rs | 10 ++-- crates/env/src/engine/mod.rs | 1 - crates/env/src/engine/off_chain/impls.rs | 58 ++++++++++++------------ examples/reentrancy/lib.rs | 22 +-------- examples/reentrancy/main_contract/lib.rs | 20 +++----- 5 files changed, 40 insertions(+), 71 deletions(-) diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index bfd6e959a42..a2682caed32 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -119,7 +119,7 @@ impl ReturnCode { pub struct ContractStorage { pub instantiated: HashMap, Vec>, pub entrance_count: HashMap, u32>, - pub allow_reentry: HashMap, bool>, + pub reentrancy_allowed: HashMap, bool>, pub deployed: HashMap, Contract>, } @@ -129,14 +129,14 @@ impl ContractStorage { } pub fn get_allow_reentry(&self, callee: Vec) -> bool { - *self.allow_reentry.get(&callee).unwrap_or(&false) + *self.reentrancy_allowed.get(&callee).unwrap_or(&false) } pub fn set_allow_reentry(&mut self, callee: Vec, allow: bool) { if allow { - self.allow_reentry.insert(callee, allow); + self.reentrancy_allowed.insert(callee, allow); } else { - self.allow_reentry.remove(&callee); + self.reentrancy_allowed.remove(&callee); } } @@ -650,7 +650,7 @@ impl Engine { if let Some(caller) = caller { self.contracts - .allow_reentry + .reentrancy_allowed .remove(&caller.as_bytes().to_vec()); } Ok(()) diff --git a/crates/env/src/engine/mod.rs b/crates/env/src/engine/mod.rs index f75a32c4aed..e18a521280f 100644 --- a/crates/env/src/engine/mod.rs +++ b/crates/env/src/engine/mod.rs @@ -30,7 +30,6 @@ use ink_primitives::{ ConstructorResult, LangError, }; - pub trait OnInstance: EnvBackend + TypedEnvBackend { fn on_instance(f: F) -> R where diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 9566d209bd5..639a9651d82 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -103,6 +103,20 @@ impl CryptoHash for Keccak256 { } } +pub fn generate_address( + caller: Vec, + code_hash: Vec, + input_data: Vec, + salt: Vec, +) -> [u8; 32] { + let mut output = [0u8; 32]; + Sha2x256::hash( + [caller, code_hash, input_data, salt].concat().as_slice(), + &mut output, + ); + output +} + impl From for crate::Error { fn from(ext_error: ext::Error) -> Self { match ext_error { @@ -186,23 +200,8 @@ impl EnvInstance { scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into) } - fn generate_address( - &mut self, - caller: Vec, - code_hash: Vec, - input_data: Vec, - salt: Vec, - ) -> [u8; 32] { - let mut output = [0u8; 32]; - Sha2x256::hash( - [caller, code_hash, input_data, salt].concat().as_slice(), - &mut output, - ); - output - } - /// Generates new execution context, replaces it as current and returns previous one - fn generate_callee_context( + fn create_new_exec_context( &mut self, callee: Vec, input: Vec, @@ -302,7 +301,6 @@ impl EnvBackend for EnvInstance { .map_err(|_| Error::CalleeTrapped) } - #[cfg(not(all(not(feature = "std"), target_arch = "wasm32")))] fn return_value(&mut self, flags: ReturnFlags, return_value: &R) where R: scale::Encode, @@ -432,7 +430,7 @@ impl EnvBackend for EnvInstance { .exec_context .callee .clone() - .expect("callee is none"); + .ok_or(Error::CalleeTrapped)?; self.engine .borrow_mut() @@ -537,7 +535,7 @@ impl TypedEnvBackend for EnvInstance { params.exec_input().encode(), )?; - let mut previous_context = self.generate_callee_context( + let mut previous_context = self.create_new_exec_context( callee.clone(), input, ::decode( @@ -630,7 +628,7 @@ impl TypedEnvBackend for EnvInstance { .ok_or(Error::CodeNotFound)? .call; - let mut previous_context = self.generate_callee_context( + let mut previous_context = self.create_new_exec_context( callee.clone().unwrap_or_default().as_bytes().to_vec(), input, 0, @@ -705,16 +703,15 @@ impl TypedEnvBackend for EnvInstance { let input = params.exec_input(); let salt_bytes = params.salt_bytes(); - let callee = self - .generate_address( - caller.unwrap_or_default().as_bytes().to_vec(), - code_hash.clone(), - input.encode(), - salt_bytes.as_ref().to_vec(), - ) - .to_vec(); + let callee = generate_address( + caller.unwrap_or_default().as_bytes().to_vec(), + code_hash.clone(), + input.encode(), + salt_bytes.as_ref().to_vec(), + ) + .to_vec(); - let previous_context = self.generate_callee_context( + let previous_context = self.create_new_exec_context( callee.clone(), input.encode(), ::decode( @@ -740,6 +737,7 @@ impl TypedEnvBackend for EnvInstance { deploy_fn(); let output = self.engine.borrow().exec_context.output.clone(); + let instantiate_result = if self.engine.borrow().exec_context.reverted { Err(Error::CalleeReverted) } else { @@ -807,7 +805,7 @@ impl TypedEnvBackend for EnvInstance { .exec_context .origin .clone() - .expect("stack should not be empty") + .expect("origin should exist") == engine .exec_context .caller diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index ef639ad105d..ae14871506e 100755 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -7,7 +7,6 @@ pub use self::fallback_contract::{ #[ink::contract] mod fallback_contract { - use ink::primitives::Key; use main_contract::MainContractRef; /// Defines the storage of your contract. @@ -15,9 +14,6 @@ mod fallback_contract { /// to add new static storage fields to your contract. #[ink(storage)] pub struct FallbackContract { - /// Stores a single `bool` value on the storage. - value: u32, - callee: MainContractRef, } @@ -25,13 +21,7 @@ mod fallback_contract { /// Constructor that initializes the `bool` value to the given `init_value`. #[ink(constructor)] pub fn new(callee: MainContractRef) -> Self { - Self { value: 0, callee } - } - - /// Simply returns the current value of our `bool`. - #[ink(message)] - pub fn get(&self) -> u32 { - self.value + Self { callee } } #[ink(message)] @@ -49,17 +39,8 @@ mod fallback_contract { self.env().account_id() } - #[ink(message)] - pub fn get_key(&self) -> Key { - ::KEY - } - #[ink(message, selector = _)] pub fn fallback(&mut self) { - ink::env::set_contract_storage( - &::KEY, - self, - ); self.callee.inc().unwrap(); } } @@ -110,6 +91,5 @@ mod test { assert_eq!(main_contract.inc(), Ok(2)); assert_eq!(main_contract.get(), 2); - assert_eq!(fallback_contract.get(), 0); } } diff --git a/examples/reentrancy/main_contract/lib.rs b/examples/reentrancy/main_contract/lib.rs index 65a0baf4b85..6a294e3cb24 100755 --- a/examples/reentrancy/main_contract/lib.rs +++ b/examples/reentrancy/main_contract/lib.rs @@ -9,16 +9,13 @@ pub use self::main_contract::{ #[ink::contract] mod main_contract { use core::mem::ManuallyDrop; - use ink::{ - env::{ - call::{ - build_call, - Call, - }, - CallFlags, - DefaultEnvironment, + use ink::env::{ + call::{ + build_call, + Call, }, - primitives::Key, + CallFlags, + DefaultEnvironment, }; /// Defines the storage of your contract. @@ -67,11 +64,6 @@ mod main_contract { self.callee } - #[ink(message)] - pub fn get_key(&self) -> Key { - ::KEY - } - #[ink(message)] pub fn inc(&mut self) -> Result { self.value += 1; From 5b4761bd26de8d5412ac6dd37ca768102f26b620 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Fri, 27 Jan 2023 11:29:11 +0200 Subject: [PATCH 39/43] update ui tests --- .../ink/tests/ui/contract/fail/message-returns-non-codec.stderr | 2 +- .../ink/tests/ui/trait_def/fail/message_output_non_codec.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr index 8b9240b02c3..7e71ef2dea2 100644 --- a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -46,7 +46,7 @@ error[E0599]: the method `try_invoke` exists for struct `ink::ink_env::call::Cal = note: the following trait bounds were not satisfied: `NonCodecType: parity_scale_codec::Decode` note: the following trait must be implemented - --> $CARGO/parity-scale-codec-3.2.2/src/codec.rs + --> $CARGO/parity-scale-codec-3.3.0/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr index 46e1e66dc6d..d3221e593be 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -33,7 +33,7 @@ error[E0599]: the method `try_invoke` exists for struct `CallBuilder $CARGO/parity-scale-codec-3.2.2/src/codec.rs + --> $CARGO/parity-scale-codec-3.3.0/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ From d5524bda1d8ffb558c5f83f1a8f82749730b8e52 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Fri, 27 Jan 2023 11:59:38 +0200 Subject: [PATCH 40/43] update readme --- examples/reentrancy/README.md | 5 +++-- examples/reentrancy/lib.rs | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/reentrancy/README.md b/examples/reentrancy/README.md index 9b862593ba0..a94a0ae327a 100644 --- a/examples/reentrancy/README.md +++ b/examples/reentrancy/README.md @@ -10,8 +10,9 @@ It consists of two smart contracts: The mechanism of work is the following: -- You call the `inc` method in the main contract and increases the `value` by 1 -- It makes a call to `fallback` method in fallback contract, as it has no selector +- The `inc` method in the main contract is called +- `inc` method increases the `value` by 1 +- `inc` method makes a call to `fallback` method in fallback contract, as it has no selector - `fallback` makes a call back to `inc` method in main contract - `inc` increases the `value` by 1 again and returns 2 as a result diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index ae14871506e..79efdba0dd9 100755 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -81,7 +81,6 @@ mod test { .instantiate(); let address1 = main_contract.get_address(); - let address2 = fallback_contract.get_address(); main_contract.set_callee(address2); From 3e78b76d803248cb6bb446207bd5071d5450a31e Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 30 Jan 2023 14:16:28 +0200 Subject: [PATCH 41/43] Change the way contracts are instantiated in example, update readme --- examples/reentrancy/README.md | 24 ++++++++++++++- examples/reentrancy/lib.rs | 37 ++++++++++++++---------- examples/reentrancy/main_contract/lib.rs | 2 +- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/examples/reentrancy/README.md b/examples/reentrancy/README.md index a94a0ae327a..65fe52837bb 100644 --- a/examples/reentrancy/README.md +++ b/examples/reentrancy/README.md @@ -18,8 +18,30 @@ The mechanism of work is the following: ## Testing +### Unit testing You can run unit-test for this example by running the following command: ```bash cargo test -``` \ No newline at end of file +``` +### On-chain testing + +If you want to test example locally on your node, first you should run `substrate-contracts-node`: + +```bash +substrate-contracts-node --dev +``` + +Then build both contracts using `cargo-contract`: + +```bash +cargo contract build --release +``` + +Then you can use `contracts-ui` to upload and instantiate the contracts: + +- Firstly deploy `main_contract` +- Take it's code hash(you can get it from `main_contract.contract` file in `target/ink` folder of main contract) +- Then deploy `fallback_contract` using the code hash of `main_contract` as a constructor argument, it will instantiate `main_contract` inside. +- Also if you want to test both contracts, you need to get address of main contract from fallback contract, and instantiate main_contract with it and +it's metadata, which you can find in `metadata.json` file in `target/ink` folder of main contract. \ No newline at end of file diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index 79efdba0dd9..46d3f113f3a 100755 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -12,6 +12,7 @@ mod fallback_contract { /// Defines the storage of your contract. /// Add new fields to the below struct in order /// to add new static storage fields to your contract. + #[derive(Clone)] #[ink(storage)] pub struct FallbackContract { callee: MainContractRef, @@ -20,17 +21,22 @@ mod fallback_contract { impl FallbackContract { /// Constructor that initializes the `bool` value to the given `init_value`. #[ink(constructor)] - pub fn new(callee: MainContractRef) -> Self { + pub fn new(callee_code_hash: Hash) -> Self { + let callee = MainContractRef::new() + .code_hash(callee_code_hash) + .endowment(0) + .salt_bytes([0u8; 0]) + .instantiate(); Self { callee } } #[ink(message)] - pub fn set_callee(&mut self, callee: MainContractRef) { - self.callee = callee; + pub fn get_callee(&self) -> MainContractRef { + self.callee.clone() } #[ink(message)] - pub fn get_callee(&self) -> AccountId { + pub fn get_callee_address(&self) -> AccountId { self.callee.get_address() } @@ -57,10 +63,7 @@ mod test { FallbackContractRef, }; use ink::primitives::Hash; - use main_contract::{ - MainContract, - MainContractRef, - }; + use main_contract::MainContract; let hash1 = Hash::from([10u8; 32]); let hash2 = Hash::from([20u8; 32]); @@ -68,25 +71,27 @@ mod test { ink::env::test::register_contract::(hash1.as_ref()); ink::env::test::register_contract::(hash2.as_ref()); - let mut main_contract = MainContractRef::new() - .code_hash(hash1) - .endowment(0) - .salt_bytes([0u8; 0]) - .instantiate(); - - let fallback_contract = FallbackContractRef::new(main_contract.clone()) + let fallback_contract = FallbackContractRef::new(hash1.clone()) .code_hash(hash2) .endowment(0) .salt_bytes([0u8; 0]) .instantiate(); + let mut main_contract = fallback_contract.get_callee(); + let address1 = main_contract.get_address(); let address2 = fallback_contract.get_address(); main_contract.set_callee(address2); assert_eq!(main_contract.get_callee(), address2); - assert_eq!(fallback_contract.get_callee(), address1); + + println!( + "main_contract.get_callee_address(): {:?}", + main_contract.get_callee() + ); + + assert_eq!(fallback_contract.get_callee_address(), address1); assert_eq!(main_contract.inc(), Ok(2)); assert_eq!(main_contract.get(), 2); diff --git a/examples/reentrancy/main_contract/lib.rs b/examples/reentrancy/main_contract/lib.rs index 6a294e3cb24..eaf1886f7bc 100755 --- a/examples/reentrancy/main_contract/lib.rs +++ b/examples/reentrancy/main_contract/lib.rs @@ -21,7 +21,7 @@ mod main_contract { /// Defines the storage of your contract. /// Add new fields to the below struct in order /// to add new static storage fields to your contract. - #[derive(Default)] + #[derive(Default, Clone)] #[ink(storage)] pub struct MainContract { /// Stores a single `bool` value on the storage. From 68047636ce59f7c410a5ab6bb4a054c4cf8959e3 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 30 Jan 2023 15:37:30 +0200 Subject: [PATCH 42/43] fix clippy --- examples/reentrancy/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/reentrancy/lib.rs b/examples/reentrancy/lib.rs index 46d3f113f3a..8a385af5a19 100755 --- a/examples/reentrancy/lib.rs +++ b/examples/reentrancy/lib.rs @@ -71,7 +71,7 @@ mod test { ink::env::test::register_contract::(hash1.as_ref()); ink::env::test::register_contract::(hash2.as_ref()); - let fallback_contract = FallbackContractRef::new(hash1.clone()) + let fallback_contract = FallbackContractRef::new(hash1) .code_hash(hash2) .endowment(0) .salt_bytes([0u8; 0]) From 68c11bcf50aaec76110c10732218b5bfde7ae36f Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 1 Mar 2023 13:56:12 +0200 Subject: [PATCH 43/43] update tests to new ink! version --- {examples => integration-tests}/reentrancy/.gitignore | 0 {examples => integration-tests}/reentrancy/Cargo.toml | 0 {examples => integration-tests}/reentrancy/README.md | 0 {examples => integration-tests}/reentrancy/lib.rs | 2 +- .../reentrancy/main_contract/.gitignore | 0 .../reentrancy/main_contract/Cargo.toml | 0 .../reentrancy/main_contract/lib.rs | 8 ++++---- 7 files changed, 5 insertions(+), 5 deletions(-) rename {examples => integration-tests}/reentrancy/.gitignore (100%) rename {examples => integration-tests}/reentrancy/Cargo.toml (100%) rename {examples => integration-tests}/reentrancy/README.md (100%) rename {examples => integration-tests}/reentrancy/lib.rs (97%) rename {examples => integration-tests}/reentrancy/main_contract/.gitignore (100%) rename {examples => integration-tests}/reentrancy/main_contract/Cargo.toml (100%) rename {examples => integration-tests}/reentrancy/main_contract/lib.rs (93%) diff --git a/examples/reentrancy/.gitignore b/integration-tests/reentrancy/.gitignore similarity index 100% rename from examples/reentrancy/.gitignore rename to integration-tests/reentrancy/.gitignore diff --git a/examples/reentrancy/Cargo.toml b/integration-tests/reentrancy/Cargo.toml similarity index 100% rename from examples/reentrancy/Cargo.toml rename to integration-tests/reentrancy/Cargo.toml diff --git a/examples/reentrancy/README.md b/integration-tests/reentrancy/README.md similarity index 100% rename from examples/reentrancy/README.md rename to integration-tests/reentrancy/README.md diff --git a/examples/reentrancy/lib.rs b/integration-tests/reentrancy/lib.rs similarity index 97% rename from examples/reentrancy/lib.rs rename to integration-tests/reentrancy/lib.rs index 8a385af5a19..458112b2e75 100755 --- a/examples/reentrancy/lib.rs +++ b/integration-tests/reentrancy/lib.rs @@ -84,7 +84,7 @@ mod test { main_contract.set_callee(address2); - assert_eq!(main_contract.get_callee(), address2); + assert_eq!(main_contract.get_callee(), Some(address2)); println!( "main_contract.get_callee_address(): {:?}", diff --git a/examples/reentrancy/main_contract/.gitignore b/integration-tests/reentrancy/main_contract/.gitignore similarity index 100% rename from examples/reentrancy/main_contract/.gitignore rename to integration-tests/reentrancy/main_contract/.gitignore diff --git a/examples/reentrancy/main_contract/Cargo.toml b/integration-tests/reentrancy/main_contract/Cargo.toml similarity index 100% rename from examples/reentrancy/main_contract/Cargo.toml rename to integration-tests/reentrancy/main_contract/Cargo.toml diff --git a/examples/reentrancy/main_contract/lib.rs b/integration-tests/reentrancy/main_contract/lib.rs similarity index 93% rename from examples/reentrancy/main_contract/lib.rs rename to integration-tests/reentrancy/main_contract/lib.rs index eaf1886f7bc..a444cdacfe4 100755 --- a/examples/reentrancy/main_contract/lib.rs +++ b/integration-tests/reentrancy/main_contract/lib.rs @@ -27,7 +27,7 @@ mod main_contract { /// Stores a single `bool` value on the storage. value: u32, - callee: AccountId, + callee: Option, } #[derive(scale::Encode, scale::Decode, Debug, Ord, PartialOrd, Eq, PartialEq)] @@ -56,11 +56,11 @@ mod main_contract { #[ink(message)] pub fn set_callee(&mut self, callee: AccountId) { - self.callee = callee; + self.callee = Some(callee); } #[ink(message)] - pub fn get_callee(&self) -> AccountId { + pub fn get_callee(&self) -> Option { self.callee } @@ -78,7 +78,7 @@ mod main_contract { ); build_call::() - .call_type(Call::new().callee(self.callee)) + .call_type(Call::new(self.callee.unwrap())) .call_flags(CallFlags::default().set_allow_reentry(true)) .try_invoke() .unwrap_or_else(|err| panic!("failed to call callee: {:?}", err))