From d9629c6398dba66d15509bab8877f644330f1f99 Mon Sep 17 00:00:00 2001 From: green Date: Sun, 2 Oct 2022 20:02:39 +0100 Subject: [PATCH] The initial implementation of advanced test environment. --- crates/engine/src/exec_context.rs | 6 + crates/engine/src/ext.rs | 126 ++++++++++----- crates/engine/src/lib.rs | 2 +- crates/engine/src/test_api.rs | 68 ++++++--- crates/env/src/api.rs | 3 +- crates/env/src/backend.rs | 19 ++- crates/env/src/contract.rs | 24 +++ crates/env/src/engine/off_chain/impls.rs | 143 +++++++++++++++--- crates/env/src/engine/off_chain/mod.rs | 12 +- crates/env/src/engine/off_chain/test_api.rs | 15 ++ crates/env/src/lib.rs | 1 + crates/lang/codegen/src/generator/dispatch.rs | 87 ++++++----- crates/lang/src/codegen/dispatch/execution.rs | 4 +- examples/delegator/adder/lib.rs | 35 +++++ 14 files changed, 421 insertions(+), 124 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 271ce26fd0f..6d31b4d3c14 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -46,6 +46,10 @@ pub struct ExecContext { pub block_timestamp: BlockTimestamp, /// The randomization entropy for a block. pub entropy: Hash, + /// The input of the call. + pub input: Vec, + /// The output buffer of the call. + pub output: Vec, } impl Default for ExecContext { @@ -59,6 +63,8 @@ impl Default for ExecContext { block_number: 0, block_timestamp: 0, entropy, + input: vec![], + output: vec![], } } } diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 2a6cc01de13..ac09eb28e11 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -36,7 +36,12 @@ use rand::{ SeedableRng, }; use scale::Encode; -use std::panic::panic_any; +use std::{ + cell::RefCell, + collections::HashMap, + panic::panic_any, + rc::Rc, +}; type Result = core::result::Result<(), Error>; @@ -114,20 +119,34 @@ 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>, + /// Contracts' store. + pub contracts: Rc>, } /// The chain specification. @@ -162,11 +181,12 @@ 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())), + contracts: Rc::new(RefCell::new(Default::default())), } } } @@ -194,8 +214,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(()) } @@ -221,7 +243,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(), }); @@ -232,12 +254,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()); // We ignore if storage is already set for this key - let _ = self.database.insert_into_contract_storage( + let _ = self.database.borrow_mut().insert_into_contract_storage( &callee, key, encoded_value.to_vec(), @@ -259,11 +282,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")) } @@ -273,8 +298,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() + .get_from_contract_storage(&callee, key) + { Some(val) => { set_output(output, val); Ok(()) @@ -288,8 +317,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() .get_from_contract_storage(&callee, key) .map(|val| val.len() as u32) } @@ -298,11 +328,15 @@ impl Engine { pub fn clear_storage(&mut self, key: &[u8; 32]) { 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()); - let _ = self.database.remove_contract_storage(&callee, key); + let _ = self + .database + .borrow_mut() + .remove_contract_storage(&callee, key); } /// Remove the calling account and transfer remaining balance. @@ -329,23 +363,27 @@ 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); + .clone(); + 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"); + .expect("no callee has been set") + .clone(); 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); @@ -355,7 +393,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[..]) } @@ -363,16 +401,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); } @@ -399,14 +440,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[..]) } @@ -418,7 +459,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[..]) } @@ -449,7 +490,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[..]) } @@ -472,7 +517,7 @@ impl Engine { /// engine.random(&subject, &mut output.as_mut_slice()); /// ``` pub fn random(&self, subject: &[u8], output: &mut &mut [u8]) { - let seed = (self.exec_context.entropy, subject).encode(); + let seed = (self.exec_context.borrow().entropy, subject).encode(); let mut digest = [0u8; 32]; Engine::hash_blake2_256(&seed, &mut digest); @@ -489,9 +534,9 @@ impl Engine { input: &[u8], output: &mut &mut [u8], ) { + let mut chain_extension_handler = self.chain_extension_handler.borrow_mut(); let encoded_input = input.encode(); - let (status_code, out) = self - .chain_extension_handler + let (status_code, out) = chain_extension_handler .eval(func_id, &encoded_input) .unwrap_or_else(|error| { panic!( @@ -550,6 +595,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 f786371d1ef..9bda0708259 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -17,7 +17,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..c5f3c3b9f30 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -173,89 +173,117 @@ 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); - (*reads, *writes) + let reads = self + .debug_info + .borrow() + .count_reads + .get(&account_id) + .unwrap_or(&0) + .clone(); + let writes = self + .debug_info + .borrow() + .count_writes + .get(&account_id) + .unwrap_or(&0) + .clone(); + (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`. /// /// 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. 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 fc5acd3bfca..24a001e3161 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::{ @@ -416,7 +417,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 2c8f56207c1..cd3406b808a 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -30,7 +30,7 @@ use crate::{ use ink_primitives::Key; /// The flags to indicate further information about the end of a contract execution. -#[derive(Default)] +#[derive(Default, Clone)] pub struct ReturnFlags { value: u32, } @@ -46,6 +46,15 @@ impl ReturnFlags { self } + /// Returns `true` is reverte flag is raised. + pub fn is_reverted(&self) -> bool { + if (self.value & 1) > 0 { + true + } else { + false + } + } + /// Returns the underlying `u32` representation. #[cfg(not(feature = "std"))] pub(crate) fn into_u32(self) -> u32 { @@ -160,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 key. @@ -233,7 +248,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 75bd5993091..22adc4d1208 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,10 +42,12 @@ use crate::{ TypedEnvBackend, }; use ink_engine::{ + exec_context::ExecContext, ext, ext::Engine, }; use ink_primitives::Key; +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 @@ -226,14 +229,19 @@ impl EnvBackend for EnvInstance { where T: scale::Decode, { - unimplemented!("the off-chain env does not implement `seal_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 `seal_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) { @@ -427,12 +435,54 @@ 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, + entropy: self.engine.exec_context.borrow().entropy, + 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( @@ -459,12 +509,57 @@ 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(); + + // TOOD: 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, + entropy: self.engine.exec_context.borrow().entropy, + 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) -> ! @@ -503,11 +598,15 @@ impl TypedEnvBackend for EnvInstance { scale::Decode::decode(&mut &output[..]).map_err(Into::into) } - 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 @@ -517,17 +616,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 4d73be03ac3..7aa6ab456be 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. @@ -94,6 +96,7 @@ where instance .engine .chain_extension_handler + .borrow_mut() .register(Box::new(extension)); }) } @@ -355,3 +358,15 @@ pub fn assert_contract_termination( assert_eq!(value_transferred, expected_value_transferred_to_beneficiary); assert_eq!(beneficiary, expected_beneficiary); } + +/// 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) + }) +} diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index 306b6ea774e..97bbae00c36 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -70,6 +70,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/lang/codegen/src/generator/dispatch.rs b/crates/lang/codegen/src/generator/dispatch.rs index fee922033e5..ccc7963c36a 100644 --- a/crates/lang/codegen/src/generator/dispatch.rs +++ b/crates/lang/codegen/src/generator/dispatch.rs @@ -78,12 +78,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 } } } @@ -402,45 +397,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_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::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() } - ::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) - }) - } + #[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_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::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) + }) } - ::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) - }) + 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)) + } + + ::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/crates/lang/src/codegen/dispatch/execution.rs b/crates/lang/src/codegen/dispatch/execution.rs index 41cfe88c1ab..fd4c6c334eb 100644 --- a/crates/lang/src/codegen/dispatch/execution.rs +++ b/crates/lang/src/codegen/dispatch/execution.rs @@ -105,12 +105,12 @@ where // Constructor is fallible and failed. // // We need to revert the state of the transaction. - ink_env::return_value::< + Ok(ink_env::return_value::< as ConstructorReturnType>::ReturnValue, >( ReturnFlags::default().set_reverted(true), result.return_value(), - ) + )) } } } diff --git a/examples/delegator/adder/lib.rs b/examples/delegator/adder/lib.rs index 5996ed1a787..c88ae653f2d 100644 --- a/examples/delegator/adder/lib.rs +++ b/examples/delegator/adder/lib.rs @@ -32,3 +32,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); + } +}