diff --git a/CHANGELOG.md b/CHANGELOG.md index db4c143e351..6c64b43eaef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased] +- Implement contract invokation in off-chain environment engine - [#1957](https://github.com/paritytech/ink/pull/1988) + ## Changed - Restrict which `cfg` attributes can be used ‒ [#2313](https://github.com/use-ink/ink/pull/2313) diff --git a/Cargo.lock b/Cargo.lock index bf151590890..fc2f10e4948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2851,8 +2851,12 @@ dependencies = [ name = "ink_primitives" version = "5.1.0" dependencies = [ + "cfg-if", "derive_more 1.0.0", + "ink", + "ink_env", "ink_prelude", + "num-traits", "parity-scale-codec", "scale-decode", "scale-encode", diff --git a/crates/engine/src/database.rs b/crates/engine/src/database.rs index 84604a60355..1ae633b687f 100644 --- a/crates/engine/src/database.rs +++ b/crates/engine/src/database.rs @@ -18,6 +18,9 @@ use std::collections::HashMap; const BALANCE_OF: &[u8] = b"balance:"; const STORAGE_OF: &[u8] = b"contract-storage:"; +const CONTRACT_PREFIX: &[u8] = b"contract:"; +const MSG_HANDLER_OF: &[u8] = b"message-handler:"; +const CODE_HASH_OF: &[u8] = b"code-hash:"; /// Returns the database key under which to find the balance for account `who`. pub fn balance_of_key(who: &[u8]) -> [u8; 32] { @@ -27,6 +30,31 @@ pub fn balance_of_key(who: &[u8]) -> [u8; 32] { hashed_key } +pub type MessageHandler = fn(Vec) -> Vec; + +pub fn contract_key(f: MessageHandler) -> [u8; 32] { + let f = f as usize; + let f = f.to_le_bytes(); + let keyed = f.to_vec().to_keyed_vec(CONTRACT_PREFIX); + let mut ret: [u8; 32] = [0; 32]; + super::hashing::blake2b_256(&keyed[..], &mut ret); + ret +} + +pub fn message_handler_of_contract_key(key: &[u8]) -> [u8; 32] { + let keyed = key.to_vec().to_keyed_vec(MSG_HANDLER_OF); + let mut hashed_key: [u8; 32] = [0; 32]; + super::hashing::blake2b_256(&keyed[..], &mut hashed_key); + hashed_key +} + +pub fn code_hash_of_key(key: &Vec) -> [u8; 32] { + let keyed = key.to_keyed_vec(CODE_HASH_OF); + let mut hashed_key: [u8; 32] = [0; 32]; + super::hashing::blake2b_256(&keyed[..], &mut hashed_key); + hashed_key +} + /// Returns the database key under which to find the balance for account `who`. pub fn storage_of_contract_key(who: &[u8], key: &[u8]) -> [u8; 32] { let keyed = who.to_vec().to_keyed_vec(key).to_keyed_vec(STORAGE_OF); @@ -42,6 +70,7 @@ pub fn storage_of_contract_key(who: &[u8], key: &[u8]) -> [u8; 32] { #[derive(Default)] pub struct Database { hmap: HashMap, Vec>, + fmap: HashMap, MessageHandler>, } impl Database { @@ -49,6 +78,7 @@ impl Database { pub fn new() -> Self { Database { hmap: HashMap::new(), + fmap: HashMap::new(), } } @@ -128,6 +158,34 @@ impl Database { .and_modify(|v| *v = encoded_balance.clone()) .or_insert(encoded_balance); } + + pub fn set_contract_message_handler(&mut self, handler: MessageHandler) -> [u8; 32] { + let key = contract_key(handler); + let hashed_key = message_handler_of_contract_key(&key); + self.fmap + .entry(hashed_key.to_vec()) + .and_modify(|x| *x = handler) + .or_insert(handler); + key + } + + pub fn get_contract_message_handler(&mut self, key: &[u8]) -> MessageHandler { + let hashed_key = message_handler_of_contract_key(key); + *self.fmap.get(hashed_key.as_slice()).unwrap() + } + + pub fn set_code_hash(&mut self, account: &Vec, code_hash: &[u8]) { + let hashed_key = code_hash_of_key(account); + self.hmap + .entry(hashed_key.to_vec()) + .and_modify(|x| *x = code_hash.to_vec()) + .or_insert(code_hash.to_vec()); + } + + pub fn get_code_hash(&self, account: &Vec) -> Option> { + let hashed_key = code_hash_of_key(account); + self.get(&hashed_key).cloned() + } } #[cfg(test)] diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 5155af87a2f..367c90dc28d 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -23,7 +23,7 @@ pub mod test_api; mod chain_extension; mod database; mod exec_context; -mod hashing; +pub mod hashing; mod types; #[cfg(test)] diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index ac23f175042..588fe646ed0 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -71,6 +71,7 @@ ink = { path = "../ink" } default = [ "std" ] std = [ "blake2", + "ink/std", "ink_allocator/std", "ink_prelude/std", "ink_primitives/std", @@ -92,6 +93,7 @@ std = [ "xcm/std", "derive_more/std" ] +test_instantiate = [] # Enable contract debug messages via `debug_print!` and `debug_println!`. ink-debug = [] diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index dfd07fff13b..6dbce70786f 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -42,8 +42,10 @@ use crate::{ CryptoHash, HashOutput, }, - types::Gas, - Environment, + types::{ + Environment, + Gas, + }, Result, }; use ink_storage_traits::Storable; @@ -383,7 +385,9 @@ pub fn instantiate_contract( > where E: Environment, - ContractRef: FromAccountId, + ContractRef: FromAccountId + crate::ContractReverseReference, + ::Type: + crate::reflect::ContractConstructorDecoder, Args: scale::Encode, Salt: AsRef<[u8]>, R: ConstructorReturnType, @@ -512,6 +516,7 @@ where /// # Note /// /// This function stops the execution of the contract immediately. +#[cfg(not(feature = "test_instantiate"))] pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! where R: scale::Encode, @@ -521,6 +526,22 @@ where }) } +/// Returns the value back to the caller of the executed contract. +/// +/// # Note +/// +/// When the test_instantiate feature is used, the contract is allowed to +/// return normally. This feature should only be used for integration tests. +#[cfg(feature = "test_instantiate")] +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) + }) +} + /// Appends the given message to the debug message buffer. pub fn debug_message(message: &str) { ::on_instance(|instance| { diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index b3f09f6ff83..3e7af4f8c6d 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -32,7 +32,7 @@ use crate::{ CryptoHash, HashOutput, }, - Environment, + types::Environment, Result, }; use ink_storage_traits::Storable; @@ -121,10 +121,25 @@ pub trait EnvBackend { /// /// The `flags` parameter can be used to revert the state changes of the /// entire execution if necessary. + #[cfg(not(feature = "test_instantiate"))] fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! where R: scale::Encode; + /// Returns the value back to the caller of the executed contract. + /// + /// # Note + /// + /// When the test_instantiate feature is used, the contract is allowed to + /// return normally. This feature should only be used for integration tests. + /// + /// The `flags` parameter can be used to revert the state changes of the + /// entire execution if necessary. + #[cfg(feature = "test_instantiate")] + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) + where + R: scale::Encode; + /// Emit a custom debug message. /// /// The message is appended to the debug buffer which is then supplied to the calling @@ -363,7 +378,9 @@ pub trait TypedEnvBackend: EnvBackend { > where E: Environment, - ContractRef: FromAccountId, + ContractRef: FromAccountId + crate::ContractReverseReference, + ::Type: + crate::reflect::ContractConstructorDecoder, Args: scale::Encode, Salt: AsRef<[u8]>, R: ConstructorReturnType; diff --git a/crates/env/src/call/call_builder/call.rs b/crates/env/src/call/call_builder/call.rs index 1f69556972d..fafe894ebe9 100644 --- a/crates/env/src/call/call_builder/call.rs +++ b/crates/env/src/call/call_builder/call.rs @@ -26,9 +26,11 @@ use crate::{ CallParams, ExecutionInput, }, - Environment, + types::{ + Environment, + Gas, + }, Error, - Gas, }; use num_traits::Zero; #[cfg(not(feature = "revive"))] diff --git a/crates/env/src/call/call_builder/call_v1.rs b/crates/env/src/call/call_builder/call_v1.rs index 484d31bd72c..3f4fe31b5f8 100644 --- a/crates/env/src/call/call_builder/call_v1.rs +++ b/crates/env/src/call/call_builder/call_v1.rs @@ -24,9 +24,11 @@ use crate::{ CallBuilder, ExecutionInput, }, - Environment, + types::{ + Environment, + Gas, + }, Error, - Gas, }; use num_traits::Zero; #[cfg(not(feature = "revive"))] diff --git a/crates/env/src/call/call_builder/delegate.rs b/crates/env/src/call/call_builder/delegate.rs index c78d5e8d8b9..dd4c28d6e76 100644 --- a/crates/env/src/call/call_builder/delegate.rs +++ b/crates/env/src/call/call_builder/delegate.rs @@ -24,7 +24,7 @@ use crate::{ CallParams, ExecutionInput, }, - Environment, + types::Environment, Error, }; #[cfg(not(feature = "revive"))] diff --git a/crates/env/src/call/call_builder/mod.rs b/crates/env/src/call/call_builder/mod.rs index 395239c2e09..c83c9277412 100644 --- a/crates/env/src/call/call_builder/mod.rs +++ b/crates/env/src/call/call_builder/mod.rs @@ -33,7 +33,7 @@ use crate::{ Execution, ExecutionInput, }, - Environment, + types::Environment, }; use core::marker::PhantomData; diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs index 2b17eb577d4..a154f3b8262 100644 --- a/crates/env/src/call/create_builder.rs +++ b/crates/env/src/call/create_builder.rs @@ -24,7 +24,7 @@ use crate::{ Selector, }, ContractEnv, - Environment, + types::Environment, Error, }; use core::marker::PhantomData; @@ -293,7 +293,11 @@ impl CreateParams, Args, Salt, R> where E: Environment, - ContractRef: FromAccountId, + ContractRef: FromAccountId + crate::ContractReverseReference, + ::Type: + crate::reflect::ContractConstructorDecoder, + ::Type: + crate::reflect::ContractMessageDecoder, Args: scale::Encode, Salt: AsRef<[u8]>, R: ConstructorReturnType, @@ -883,7 +887,11 @@ impl > where E: Environment, - ContractRef: FromAccountId, + ContractRef: FromAccountId + crate::ContractReverseReference, + ::Type: + crate::reflect::ContractConstructorDecoder, + ::Type: + crate::reflect::ContractMessageDecoder, Args: scale::Encode, Salt: AsRef<[u8]>, RetType: ConstructorReturnType, diff --git a/crates/env/src/engine/mod.rs b/crates/env/src/engine/mod.rs index 8cb83f551cd..cbd2d8457a0 100644 --- a/crates/env/src/engine/mod.rs +++ b/crates/env/src/engine/mod.rs @@ -81,7 +81,7 @@ pub(crate) fn decode_instantiate_result( ) -> EnvResult>::Output>> where I: scale::Input, - E: crate::Environment, + E: crate::types::Environment, ContractRef: FromAccountId, R: ConstructorReturnType, { @@ -106,7 +106,7 @@ fn decode_instantiate_err( ) -> EnvResult>::Output>> where I: scale::Input, - E: crate::Environment, + E: crate::types::Environment, ContractRef: FromAccountId, R: ConstructorReturnType, { diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 9f1e0718c90..f0a66f1196a 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -42,7 +42,7 @@ use crate::{ }, Clear, EnvBackend, - Environment, + types::Environment, Result, TypedEnvBackend, }; @@ -71,6 +71,74 @@ use schnorrkel::{ /// to be as close to the on-chain behavior as possible. const BUFFER_SIZE: usize = crate::BUFFER_SIZE; +/// Proxy function used to simulate code hash and to invoke contract methods. +fn execute_contract_call(input: Vec) -> Vec +where + ContractRef: crate::ContractReverseReference, + ::Type: + crate::reflect::ContractMessageDecoder, +{ + let dispatch = < + < + < + ContractRef + as crate::ContractReverseReference + >::Type + as crate::reflect::ContractMessageDecoder + >::Type + as scale::Decode + >::decode(&mut &input[..]) + .unwrap_or_else(|e| panic!("Failed to decode constructor call: {:?}", e)); + + crate::reflect::ExecuteDispatchable::execute_dispatchable(dispatch) + .unwrap_or_else(|e| panic!("Message call failed: {:?}", e)); + + crate::test::get_return_value() +} + +fn invoke_contract_impl( + env: &mut EnvInstance, + _gas_limit: Option, + _call_flags: u32, + _transferred_value: Option<&::Balance>, + callee_account: Option<&::AccountId>, + code_hash: Option<&::Hash>, + input: Vec, +) -> Result> +where + E: Environment, + R: scale::Decode, +{ + let mut callee_code_hash = match callee_account { + Some(ca) => env.code_hash::(ca)?, + None => *code_hash.unwrap(), + }; + + let handler = env + .engine + .database + .get_contract_message_handler(callee_code_hash.as_mut()); + let old_callee = env.engine.get_callee(); + let mut restore_callee = false; + if let Some(callee_account) = callee_account { + let encoded_callee = scale::Encode::encode(callee_account); + env.engine.set_callee(encoded_callee); + restore_callee = true; + } + + let result = handler(input); + + if restore_callee { + env.engine.set_callee(old_callee); + } + + let result = + as scale::Decode>::decode(&mut &result[..]) + .expect("failed to decode return value"); + + Ok(result) +} + impl CryptoHash for Blake2x128 { fn hash(input: &[u8], output: &mut ::Type) { type OutputType = [u8; 16]; @@ -179,6 +247,23 @@ impl EnvInstance { ext_fn(&self.engine, full_scope); scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into) } + + pub fn get_return_value(&mut self) -> Vec { + self.engine.get_storage(&[255_u8; 32]).unwrap().to_vec() + } + + pub fn upload_code(&mut self) -> ink_primitives::types::Hash + where + ContractRef: crate::ContractReverseReference, + ::Type: + crate::reflect::ContractMessageDecoder, + { + ink_primitives::types::Hash::from( + self.engine + .database + .set_contract_message_handler(execute_contract_call::), + ) + } } impl EnvBackend for EnvInstance { @@ -243,11 +328,22 @@ impl EnvBackend for EnvInstance { unimplemented!("the off-chain env does not implement `input`") } + #[cfg(not(feature = "test_instantiate"))] fn return_value(&mut self, _flags: ReturnFlags, _return_value: &R) -> ! where R: scale::Encode, { - unimplemented!("the off-chain env does not implement `return_value`") + panic!("enable feature test_instantiate to use return_value()") + } + + #[cfg(feature = "test_instantiate")] + fn return_value(&mut self, _flags: ReturnFlags, return_value: &R) + where + R: scale::Encode, + { + let mut v = vec![]; + return_value.encode_to(&mut v); + self.engine.set_storage(&[255_u8; 32], &v[..]); } fn debug_message(&mut self, message: &str) { @@ -377,8 +473,11 @@ 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<()> { + self.engine + .database + .set_code_hash(&self.engine.get_callee(), code_hash); + Ok(()) } } @@ -464,14 +563,28 @@ impl TypedEnvBackend for EnvInstance { fn invoke_contract( &mut self, - _params: &CallParams, Args, R>, + params: &CallParams, Args, R>, ) -> Result> where E: Environment, Args: scale::Encode, R: scale::Decode, { - unimplemented!("off-chain environment does not support contract invocation") + let call_flags = params.call_flags().bits(); + let transferred_value = params.transferred_value(); + let input = params.exec_input(); + let callee_account = params.callee(); + let input = scale::Encode::encode(input); + + invoke_contract_impl::( + self, + None, + call_flags, + Some(transferred_value), + Some(callee_account), + None, + input, + ) } fn invoke_contract_delegate( @@ -483,9 +596,19 @@ 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 call_flags = params.call_flags().bits(); + let input = params.exec_input(); + let code_hash = params.code_hash(); + let input = scale::Encode::encode(input); + + invoke_contract_impl::( + self, + None, + call_flags, + None, + None, + Some(code_hash), + input, ) } @@ -499,19 +622,71 @@ impl TypedEnvBackend for EnvInstance { > where E: Environment, - ContractRef: FromAccountId, + ContractRef: FromAccountId + crate::ContractReverseReference, + ::Type: + crate::reflect::ContractConstructorDecoder, Args: scale::Encode, Salt: AsRef<[u8]>, R: ConstructorReturnType, { - let _code_hash = params.code_hash(); - let _ref_time_limit = params.ref_time_limit(); - let _proof_size_limit = params.proof_size_limit(); - let _storage_deposit_limit = params.storage_deposit_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 endowment = scale::Encode::encode(endowment); + let endowment: u128 = scale::Decode::decode(&mut &endowment[..])?; + + let salt_bytes = params.salt_bytes(); + + let code_hash = params.code_hash(); + let code_hash = scale::Encode::encode(code_hash); + + let input = params.exec_input(); + let input = scale::Encode::encode(input); + + // Compute account for instantiated contract. + let account_id_vec = { + let mut account_input = Vec::::new(); + account_input.extend(&b"contract_addr_v1".to_vec()); + if let Some(caller) = &self.engine.exec_context.caller { + scale::Encode::encode_to(&caller.as_bytes(), &mut account_input); + } + account_input.extend(&code_hash); + account_input.extend(&input); + account_input.extend(salt_bytes.as_ref()); + let mut account_id = [0_u8; 32]; + ink_engine::hashing::blake2b_256(&account_input[..], &mut account_id); + account_id.to_vec() + }; + + let mut account_id = + ::AccountId::decode(&mut &account_id_vec[..]).unwrap(); + + let old_callee = self.engine.get_callee(); + self.engine.set_callee(account_id_vec.clone()); + + let dispatch = < + < + < + ContractRef + as crate::ContractReverseReference + >::Type + as crate::reflect::ContractConstructorDecoder + >::Type + as scale::Decode + >::decode(&mut &input[..]) + .unwrap_or_else(|e| panic!("Failed to decode constructor call: {:?}", e)); + crate::reflect::ExecuteDispatchable::execute_dispatchable(dispatch) + .unwrap_or_else(|e| panic!("Constructor call failed: {:?}", e)); + + self.set_code_hash(code_hash.as_slice())?; + self.engine.set_contract(account_id_vec.clone()); + self.engine + .database + .set_balance(account_id.as_mut(), endowment); + + self.engine.set_callee(old_callee); + + Ok(Ok(R::ok( + >::from_account_id(account_id), + ))) } #[cfg(not(feature = "revive"))] @@ -586,18 +761,36 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("off-chain environment does not support `caller_is_root`") } - 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 + .database + .get_code_hash(&scale::Encode::encode(&account)); + if let Some(code_hash) = code_hash { + let code_hash = + ::Hash::decode(&mut &code_hash[..]).unwrap(); + Ok(code_hash) + } else { + Err(ReturnErrorCode::KeyNotFound.into()) + } } fn own_code_hash(&mut self) -> Result where E: Environment, { - unimplemented!("off-chain environment does not support `own_code_hash`") + let callee = &self.engine.get_callee(); + let code_hash = self.engine.database.get_code_hash(callee); + if let Some(code_hash) = code_hash { + let code_hash = + ::Hash::decode(&mut &code_hash[..]).unwrap(); + Ok(code_hash) + } else { + Err(ReturnErrorCode::KeyNotFound.into()) + } } fn call_runtime(&mut self, _call: &Call) -> Result<()> diff --git a/crates/env/src/engine/off_chain/mod.rs b/crates/env/src/engine/off_chain/mod.rs index 3b856ba71fd..8c5914f4566 100644 --- a/crates/env/src/engine/off_chain/mod.rs +++ b/crates/env/src/engine/off_chain/mod.rs @@ -44,7 +44,14 @@ impl OnInstance for EnvInstance { } ) ); - INSTANCE.with(|instance| f(&mut instance.borrow_mut())) + /* + * This unsafe block is needed to be able to return a mut reference + * while another mut reference is still borrowed, because now that + * contracts can invoke other contracts some API functions are called + * nested. This should be safe, as the object is in a TLS, so there's no + * possibility of undefined behavior arising from race conditions. + */ + INSTANCE.with(|instance| f(unsafe { &mut *instance.as_ptr() })) } } diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index 5c1e00bfeb2..238c0c2b727 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -19,7 +19,7 @@ use super::{ OnInstance, }; use crate::{ - Environment, + types::Environment, Result, }; use core::fmt::Debug; @@ -447,3 +447,21 @@ macro_rules! pay_with_call { $contract.$message($ ($params) ,*) }} } + +/// Retrieves the value stored by return_value(). +pub fn get_return_value() -> Vec { + ::on_instance(|instance| instance.get_return_value()) +} + +/// Gets a pseudo code hash for a contract ref. +pub fn upload_code() -> ink_primitives::types::Hash +where + E: Environment, + ContractRef: crate::ContractReverseReference, + ::Type: + crate::reflect::ContractMessageDecoder, +{ + ::on_instance(|instance| { + instance.upload_code::() + }) +} diff --git a/crates/env/src/engine/on_chain/impls/pallet_contracts.rs b/crates/env/src/engine/on_chain/impls/pallet_contracts.rs index 1bb27d193a6..dac1126f814 100644 --- a/crates/env/src/engine/on_chain/impls/pallet_contracts.rs +++ b/crates/env/src/engine/on_chain/impls/pallet_contracts.rs @@ -46,7 +46,7 @@ use crate::{ Clear, EnvBackend, Environment, - FromLittleEndian, + types::FromLittleEndian, Result, TypedEnvBackend, }; diff --git a/crates/env/src/engine/on_chain/impls/pallet_revive.rs b/crates/env/src/engine/on_chain/impls/pallet_revive.rs index 8a15edd1eaf..1ec31da4dcf 100644 --- a/crates/env/src/engine/on_chain/impls/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/impls/pallet_revive.rs @@ -42,7 +42,7 @@ use crate::{ Clear, EnvBackend, Environment, - FromLittleEndian, + types::FromLittleEndian, Result, TypedEnvBackend, }; diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index ec4b9d75a32..7397b04a6c1 100644 --- a/crates/env/src/event.rs +++ b/crates/env/src/event.rs @@ -14,7 +14,7 @@ //! This module contains the implementation for the event topic logic. -use crate::Environment; +use crate::types::Environment; /// The concrete implementation that is guided by the topics builder. /// diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index eff8fec1aac..25021c6a96d 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -83,17 +83,14 @@ fn panic(info: &core::panic::PanicInfo) -> ! { extern crate ink_allocator; mod api; -mod arithmetic; mod backend; pub mod call; pub mod chain_extension; -mod contract; mod engine; mod error; #[doc(hidden)] pub mod event; pub mod hash; -mod types; #[cfg(test)] mod tests; @@ -123,10 +120,6 @@ use self::backend::{ }; pub use self::{ api::*, - contract::{ - ContractEnv, - ContractReference, - }, error::{ Error, Result, @@ -134,14 +127,27 @@ pub use self::{ event::Event, types::{ AccountIdGuard, + Balance, + BlockNumber, + CodecAsType, DefaultEnvironment, Environment, FromLittleEndian, Gas, NoChainExtension, + Timestamp, }, }; use ink_primitives::Clear; +pub use ink_primitives::{ + contract::{ + ContractEnv, + ContractReference, + ContractReverseReference, + }, + reflect, + types, +}; cfg_if::cfg_if! { if #[cfg(any(feature = "ink-debug", feature = "std"))] { diff --git a/crates/env/src/types.rs b/crates/env/src/types.rs deleted file mode 100644 index 0e26f9b29b1..00000000000 --- a/crates/env/src/types.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (C) Use Ink (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. - -//! Types for the default environment. -//! -//! These are simple mirrored types from the default substrate FRAME configuration. -//! Their interfaces and functionality might not be complete. -//! -//! Users are required to provide their own type definitions and `Environment` -//! implementations in order to write ink! contracts for other chain configurations. -//! -//! # Note -//! -//! When authoring a contract, the concrete `Environment` are available via aliases -//! generated by the `lang` macro. Therefore all functionality of the concrete -//! types is accessible in the contract, not constrained by the required trait -//! bounds. -//! -//! Outside the contract and its tests (e.g. in the off-chain environment), where -//! there is no knowledge of the concrete types, the functionality is restricted to -//! the trait bounds on the `Environment` trait types. - -use super::arithmetic::AtLeast32BitUnsigned; -use ink_primitives::{ - AccountId, - Clear, - Hash, -}; -#[cfg(feature = "std")] -use scale_info::TypeInfo; - -/// Allows to instantiate a type from its little-endian bytes representation. -pub trait FromLittleEndian { - /// The little-endian bytes representation. - type Bytes: Default + AsRef<[u8]> + AsMut<[u8]>; - - /// Create a new instance from the little-endian bytes representation. - fn from_le_bytes(bytes: Self::Bytes) -> Self; -} - -impl FromLittleEndian for u8 { - type Bytes = [u8; 1]; - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - u8::from_le_bytes(bytes) - } -} - -impl FromLittleEndian for u16 { - type Bytes = [u8; 2]; - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - u16::from_le_bytes(bytes) - } -} - -impl FromLittleEndian for u32 { - type Bytes = [u8; 4]; - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - u32::from_le_bytes(bytes) - } -} - -impl FromLittleEndian for u64 { - type Bytes = [u8; 8]; - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - u64::from_le_bytes(bytes) - } -} - -impl FromLittleEndian for u128 { - type Bytes = [u8; 16]; - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - u128::from_le_bytes(bytes) - } -} - -/// A trait to enforce that a type should be an [`Environment::AccountId`]. -/// -/// If you have an [`Environment`] which uses an [`Environment::AccountId`] type other -/// than the ink! provided [`AccountId`](https://docs.rs/ink_primitives/latest/ink_primitives/struct.AccountId.html) -/// you will need to implement this trait for your [`Environment::AccountId`] concrete -/// type. -pub trait AccountIdGuard {} - -/// The ink! provided [`AccountId`](https://docs.rs/ink_primitives/latest/ink_primitives/struct.AccountId.html) -/// used in the [`DefaultEnvironment`]. -impl AccountIdGuard for AccountId {} - -cfg_if::cfg_if! { - if #[cfg(feature = "std")] { - pub trait CodecAsType: scale_decode::DecodeAsType + scale_encode::EncodeAsType {} - impl CodecAsType for T {} - } else { - pub trait CodecAsType {} - impl CodecAsType for T {} - } -} - -/// The environmental types usable by contracts defined with ink!. -pub trait Environment: Clone { - /// The maximum number of supported event topics provided by the runtime. - /// - /// The value must match the maximum number of supported event topics of the used - /// runtime. - const MAX_EVENT_TOPICS: usize; - - /// The account id type. - type AccountId: 'static - + scale::Codec - + scale::MaxEncodedLen - + CodecAsType - + Clone - + PartialEq - + Eq - + Ord - + AsRef<[u8]> - + AsMut<[u8]>; - - /// The type of balances. - type Balance: 'static - + scale::Codec - + CodecAsType - + Copy - + Clone - + PartialEq - + Eq - + AtLeast32BitUnsigned - + FromLittleEndian; - - /// The type of hash. - type Hash: 'static - + scale::Codec - + scale::MaxEncodedLen - + CodecAsType - + Copy - + Clone - + Clear - + PartialEq - + Eq - + Ord - + AsRef<[u8]> - + AsMut<[u8]>; - - /// The type of a timestamp. - type Timestamp: 'static - + scale::Codec - + CodecAsType - + Copy - + Clone - + PartialEq - + Eq - + AtLeast32BitUnsigned - + FromLittleEndian; - - /// The type of block number. - type BlockNumber: 'static - + scale::Codec - + CodecAsType - + Copy - + Clone - + PartialEq - + Eq - + AtLeast32BitUnsigned - + FromLittleEndian; - - /// The chain extension for the environment. - /// - /// This is a type that is defined through the `#[ink::chain_extension]` procedural - /// macro. For more information about usage and definition click - /// [this][chain_extension] link. - /// - /// [chain_extension]: https://use-ink.github.io/ink/ink/attr.chain_extension.html - type ChainExtension; -} - -/// Placeholder for chains that have no defined chain extension. -#[cfg_attr(feature = "std", derive(TypeInfo))] -pub enum NoChainExtension {} - -/// The fundamental types of the default configuration. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(TypeInfo))] -pub enum DefaultEnvironment {} - -impl Environment for DefaultEnvironment { - const MAX_EVENT_TOPICS: usize = 4; - - type AccountId = AccountId; - type Balance = Balance; - type Hash = Hash; - type Timestamp = Timestamp; - type BlockNumber = BlockNumber; - type ChainExtension = NoChainExtension; -} - -/// The default balance type. -pub type Balance = u128; - -/// The default timestamp type. -pub type Timestamp = u64; - -/// The default gas type. -pub type Gas = u64; - -/// The default block number type. -pub type BlockNumber = u32; diff --git a/crates/ink/Cargo.toml b/crates/ink/Cargo.toml index c1b07ea42c3..59a94d281a6 100644 --- a/crates/ink/Cargo.toml +++ b/crates/ink/Cargo.toml @@ -56,6 +56,9 @@ std = [ ] # Enable contract debug messages via `debug_print!` and `debug_println!`. ink-debug = [ "ink_env/ink-debug" ] +test_instantiate = [ + "ink_env/test_instantiate" +] show-codegen-docs = [] diff --git a/crates/ink/codegen/Cargo.toml b/crates/ink/codegen/Cargo.toml index bb05d9d94b2..9290e1f3c82 100644 --- a/crates/ink/codegen/Cargo.toml +++ b/crates/ink/codegen/Cargo.toml @@ -41,7 +41,6 @@ default = [ "std" ] std = [ "either/use_std", "ink_primitives/std", - "ir/std", "itertools/use_std", "scale/std", "serde/std", diff --git a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs index 69ebceda761..b21b2ca05d3 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -107,6 +107,10 @@ impl ContractRef<'_> { type Type = #ref_ident; } + impl ::ink::env::ContractReverseReference for #ref_ident { + type Type = #storage_ident; + } + impl ::ink::env::call::ConstructorReturnType<#ref_ident> for #storage_ident { type Output = #ref_ident; type Error = (); diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 22e7bef18bd..7660f8ddfb4 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -715,6 +715,9 @@ impl Dispatch<'_> { // dispatch logic so `Ok` is always returned to the caller. &::ink::ConstructorResult::Ok(output_result.map(|_| ())), ); + + #[cfg(feature="test_instantiate")] + ::core::result::Result::Ok(()) } ) }); @@ -754,7 +757,7 @@ impl Dispatch<'_> { } impl ::ink::reflect::ExecuteDispatchable for __ink_ConstructorDecoder { - #[allow(clippy::nonminimal_bool)] + #[allow(clippy::nonminimal_bool, dead_code)] fn execute_dispatchable(self) -> ::core::result::Result<(), ::ink::reflect::DispatchError> { match self { #( #constructor_execute ),* @@ -915,7 +918,10 @@ impl Dispatch<'_> { // 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), - ) + ); + + #[cfg(feature="test_instantiate")] + ::core::result::Result::Ok(()) } ) }); @@ -965,7 +971,7 @@ impl Dispatch<'_> { } impl ::ink::reflect::ExecuteDispatchable for __ink_MessageDecoder { - #[allow(clippy::nonminimal_bool, clippy::let_unit_value)] + #[allow(clippy::nonminimal_bool, clippy::let_unit_value, dead_code)] fn execute_dispatchable( self ) -> ::core::result::Result<(), ::ink::reflect::DispatchError> { @@ -985,7 +991,7 @@ impl Dispatch<'_> { match self { #( #message_execute ),* - }; + } } } diff --git a/crates/ink/codegen/src/generator/trait_def/trait_registry.rs b/crates/ink/codegen/src/generator/trait_def/trait_registry.rs index 85267921f4d..51fc821cf59 100644 --- a/crates/ink/codegen/src/generator/trait_def/trait_registry.rs +++ b/crates/ink/codegen/src/generator/trait_def/trait_registry.rs @@ -21,9 +21,7 @@ use super::TraitDefinition; use crate::{ - generator::{ - self, - }, + generator, traits::GenerateCode, EnforcedErrors, }; diff --git a/crates/ink/codegen/src/lib.rs b/crates/ink/codegen/src/lib.rs index 48f3ef4beb7..3413a595b15 100644 --- a/crates/ink/codegen/src/lib.rs +++ b/crates/ink/codegen/src/lib.rs @@ -35,6 +35,7 @@ html_favicon_url = "https://use.ink/crate-docs/favicon.png" )] +pub use ink_primitives::reflect; mod enforced_error; mod generator; mod traits; diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 03dc97d576e..7470b024cf8 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -32,9 +32,7 @@ use syn::{ }; use crate::{ - ast::{ - self, - }, + ast, error::ExtError as _, ir, ir::{ diff --git a/crates/ink/macro/Cargo.toml b/crates/ink/macro/Cargo.toml index 678e8f84a57..6adc487305c 100644 --- a/crates/ink/macro/Cargo.toml +++ b/crates/ink/macro/Cargo.toml @@ -44,7 +44,8 @@ std = [ "ink_ir/std", "ink_primitives/std", "scale/std", - "scale-info/std" + "scale-info/std", + "ink_codegen/std", ] revive = [ "ink/revive", diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 60439d63e2e..e1067b93116 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -514,7 +514,9 @@ where >, > where - ContractRef: FromAccountId, + ContractRef: FromAccountId + ink_env::ContractReverseReference, + ::Type: + ink_env::reflect::ContractConstructorDecoder, Args: scale::Encode, Salt: AsRef<[u8]>, R: ConstructorReturnType, @@ -603,7 +605,9 @@ where >, > where - ContractRef: FromAccountId, + ContractRef: FromAccountId + ink_env::ContractReverseReference, + ::Type: + ink_env::reflect::ContractConstructorDecoder, Args: scale::Encode, Salt: AsRef<[u8]>, R: ConstructorReturnType, diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index d60d7972c0a..4916f0b2988 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -29,7 +29,7 @@ pub mod result_info; #[cfg_attr(not(feature = "show-codegen-docs"), doc(hidden))] pub mod codegen; -pub mod reflect; +pub use ink_env::reflect; mod chain_extension; mod contract_ref; diff --git a/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr b/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr index 3abf2659964..b8cd9940561 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr @@ -10,7 +10,7 @@ error[E0277]: the trait bound `Result, LangError>: note: required by a bound in `return_value` --> $WORKSPACE/crates/env/src/api.rs | - | pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! + | pub fn return_value(return_flags: ReturnFlags, return_value: &R) | ------------ required by a bound in this function | where | R: scale::Encode, 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 d3cdec99dfe..4240f0173d3 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 @@ -36,7 +36,7 @@ error[E0277]: the trait bound `Result: Encode` is not s note: required by a bound in `return_value` --> $WORKSPACE/crates/env/src/api.rs | - | pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! + | pub fn return_value(return_flags: ReturnFlags, return_value: &R) | ------------ required by a bound in this function | where | R: scale::Encode, diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 90c0292e887..15a26936272 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -22,10 +22,18 @@ scale-decode = { workspace = true, features = ["derive"], optional = true } scale-encode = { workspace = true, features = ["derive"], optional = true } scale-info = { workspace = true, features = ["derive"], optional = true } xxhash-rust = { workspace = true, features = ["const_xxh32"] } +cfg-if = { workspace = true } +num-traits = { workspace = true, features = ["i128"] } + +[dev-dependencies] +ink = { workspace = true, default-features = false } +ink_env = { workspace = true, default-features = false } [features] default = [ "std" ] std = [ + "ink/std", + "ink_env/std", "ink_prelude/std", "scale-decode", "scale-encode", diff --git a/crates/env/src/arithmetic.rs b/crates/primitives/src/arithmetic.rs similarity index 100% rename from crates/env/src/arithmetic.rs rename to crates/primitives/src/arithmetic.rs diff --git a/crates/env/src/contract.rs b/crates/primitives/src/contract.rs similarity index 95% rename from crates/env/src/contract.rs rename to crates/primitives/src/contract.rs index 86b5d1b524d..5c3f3d65ea2 100644 --- a/crates/env/src/contract.rs +++ b/crates/primitives/src/contract.rs @@ -139,3 +139,10 @@ pub trait ContractReference { /// The generated contract reference type. type Type; } + +/// Refers back to the original contract from the generated ink! smart contract +/// reference type. +pub trait ContractReverseReference { + /// The original contract type. + type Type; +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index f7832572f6a..3ed9a8c5ff3 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -28,8 +28,10 @@ )] #![cfg_attr(not(feature = "std"), no_std)] +mod arithmetic; mod key; -mod types; +pub mod reflect; +pub mod types; pub use self::{ key::{ @@ -39,9 +41,11 @@ pub use self::{ types::{ AccountId, Clear, + Environment, Hash, }, }; +pub mod contract; /// An error emitted by the smart contracting language. /// diff --git a/crates/ink/src/reflect/contract.rs b/crates/primitives/src/reflect/contract.rs similarity index 100% rename from crates/ink/src/reflect/contract.rs rename to crates/primitives/src/reflect/contract.rs diff --git a/crates/ink/src/reflect/dispatch.rs b/crates/primitives/src/reflect/dispatch.rs similarity index 99% rename from crates/ink/src/reflect/dispatch.rs rename to crates/primitives/src/reflect/dispatch.rs index a3155c7e26f..fa262b58fb4 100644 --- a/crates/ink/src/reflect/dispatch.rs +++ b/crates/primitives/src/reflect/dispatch.rs @@ -263,6 +263,7 @@ pub trait ConstructorOutput: private::Sealed { pub struct ConstructorOutputValue(T); impl ConstructorOutputValue { + /// Stores the actual value of the constructor return type. pub fn new(val: T) -> Self { Self(val) } @@ -615,5 +616,6 @@ impl From for scale::Error { /// } /// ``` pub trait DecodeDispatch: scale::Decode { + /// Decodes an ink! dispatch input into a known selector and its expected parameters. fn decode_dispatch(input: &mut I) -> Result; } diff --git a/crates/primitives/src/reflect/event.rs b/crates/primitives/src/reflect/event.rs new file mode 100644 index 00000000000..69181659e3c --- /dev/null +++ b/crates/primitives/src/reflect/event.rs @@ -0,0 +1,52 @@ +// 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. + +/// Defines a base event type for the contract. +/// +/// This is usually the event enum that comprises all defined event types. +/// +/// # Usage +/// +/// ``` +/// #[ink::contract] +/// pub mod contract { +/// #[ink(storage)] +/// pub struct Contract {} +/// +/// #[ink(event)] +/// pub struct Event1 {} +/// +/// #[ink(event)] +/// pub struct Event2 {} +/// +/// impl Contract { +/// #[ink(constructor)] +/// pub fn constructor() -> Self { +/// Self {} +/// } +/// +/// #[ink(message)] +/// pub fn message(&self) {} +/// } +/// } +/// +/// use contract::Contract; +/// # use ink::reflect::ContractEventBase; +/// +/// type BaseEvent = ::Type; +/// ``` +pub trait ContractEventBase { + /// The generated base event enum. + type Type; +} diff --git a/crates/ink/src/reflect/mod.rs b/crates/primitives/src/reflect/mod.rs similarity index 100% rename from crates/ink/src/reflect/mod.rs rename to crates/primitives/src/reflect/mod.rs diff --git a/crates/ink/src/reflect/trait_def/info.rs b/crates/primitives/src/reflect/trait_def/info.rs similarity index 100% rename from crates/ink/src/reflect/trait_def/info.rs rename to crates/primitives/src/reflect/trait_def/info.rs diff --git a/crates/ink/src/reflect/trait_def/mod.rs b/crates/primitives/src/reflect/trait_def/mod.rs similarity index 100% rename from crates/ink/src/reflect/trait_def/mod.rs rename to crates/primitives/src/reflect/trait_def/mod.rs diff --git a/crates/ink/src/reflect/trait_def/registry.rs b/crates/primitives/src/reflect/trait_def/registry.rs similarity index 97% rename from crates/ink/src/reflect/trait_def/registry.rs rename to crates/primitives/src/reflect/trait_def/registry.rs index af123dd9b8a..c80cdcc7449 100644 --- a/crates/ink/src/reflect/trait_def/registry.rs +++ b/crates/primitives/src/reflect/trait_def/registry.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::contract::ContractEnv; use core::marker::PhantomData; -use ink_env::ContractEnv; /// Type that is guaranteed by ink! to implement all ink! trait definitions. /// @@ -56,7 +56,7 @@ pub struct TraitDefinitionRegistry { impl ContractEnv for TraitDefinitionRegistry where - E: ink_env::Environment, + E: crate::Environment, { type Env = E; } diff --git a/crates/primitives/src/types.rs b/crates/primitives/src/types.rs index ffb4a073af9..348eb0bd7ea 100644 --- a/crates/primitives/src/types.rs +++ b/crates/primitives/src/types.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::arithmetic::AtLeast32BitUnsigned; use core::array::TryFromSliceError; use derive_more::From; use scale::{ @@ -163,3 +164,186 @@ impl Clear for Hash { <[u8; 32] as Clear>::is_clear(&self.0) } } + +/// Allows to instantiate a type from its little-endian bytes representation. +pub trait FromLittleEndian { + /// The little-endian bytes representation. + type Bytes: Default + AsRef<[u8]> + AsMut<[u8]>; + + /// Create a new instance from the little-endian bytes representation. + fn from_le_bytes(bytes: Self::Bytes) -> Self; +} + +impl FromLittleEndian for u8 { + type Bytes = [u8; 1]; + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + u8::from_le_bytes(bytes) + } +} + +impl FromLittleEndian for u16 { + type Bytes = [u8; 2]; + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + u16::from_le_bytes(bytes) + } +} + +impl FromLittleEndian for u32 { + type Bytes = [u8; 4]; + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + u32::from_le_bytes(bytes) + } +} + +impl FromLittleEndian for u64 { + type Bytes = [u8; 8]; + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + u64::from_le_bytes(bytes) + } +} + +impl FromLittleEndian for u128 { + type Bytes = [u8; 16]; + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + u128::from_le_bytes(bytes) + } +} + +/// A trait to enforce that a type should be an [`Environment::AccountId`]. +/// +/// If you have an [`Environment`] which uses an [`Environment::AccountId`] type other +/// than the ink! provided [`AccountId`](https://docs.rs/ink_primitives/latest/ink_primitives/struct.AccountId.html) +/// you will need to implement this trait for your [`Environment::AccountId`] concrete +/// type. +pub trait AccountIdGuard {} + +/// The ink! provided [`AccountId`](https://docs.rs/ink_primitives/latest/ink_primitives/struct.AccountId.html) +/// used in the [`DefaultEnvironment`]. +impl AccountIdGuard for AccountId {} + +cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + pub trait CodecAsType: scale_decode::DecodeAsType + scale_encode::EncodeAsType {} + impl CodecAsType for T {} + } else { + pub trait CodecAsType {} + impl CodecAsType for T {} + } +} + +/// The environmental types usable by contracts defined with ink!. +pub trait Environment: Clone { + /// The maximum number of supported event topics provided by the runtime. + /// + /// The value must match the maximum number of supported event topics of the used + /// runtime. + const MAX_EVENT_TOPICS: usize; + + /// The account id type. + type AccountId: 'static + + scale::Codec + + CodecAsType + + Clone + + PartialEq + + Eq + + Ord + + AsRef<[u8]> + + AsMut<[u8]>; + + /// The type of balances. + type Balance: 'static + + scale::Codec + + CodecAsType + + Copy + + Clone + + PartialEq + + Eq + + AtLeast32BitUnsigned + + FromLittleEndian; + + /// The type of hash. + type Hash: 'static + + scale::Codec + + CodecAsType + + Copy + + Clone + + Clear + + PartialEq + + Eq + + Ord + + AsRef<[u8]> + + AsMut<[u8]>; + + /// The type of a timestamp. + type Timestamp: 'static + + scale::Codec + + CodecAsType + + Copy + + Clone + + PartialEq + + Eq + + AtLeast32BitUnsigned + + FromLittleEndian; + + /// The type of block number. + type BlockNumber: 'static + + scale::Codec + + CodecAsType + + Copy + + Clone + + PartialEq + + Eq + + AtLeast32BitUnsigned + + FromLittleEndian; + + /// The chain extension for the environment. + /// + /// This is a type that is defined through the `#[ink::chain_extension]` procedural + /// macro. For more information about usage and definition click + /// [this][chain_extension] link. + /// + /// [chain_extension]: https://paritytech.github.io/ink/ink/attr.chain_extension.html + type ChainExtension; +} + +/// Placeholder for chains that have no defined chain extension. +#[cfg_attr(feature = "std", derive(TypeInfo))] +pub enum NoChainExtension {} + +/// The fundamental types of the default configuration. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(TypeInfo))] +pub enum DefaultEnvironment {} + +impl Environment for DefaultEnvironment { + const MAX_EVENT_TOPICS: usize = 4; + + type AccountId = AccountId; + type Balance = Balance; + type Hash = Hash; + type Timestamp = Timestamp; + type BlockNumber = BlockNumber; + type ChainExtension = NoChainExtension; +} + +/// The default balance type. +pub type Balance = u128; + +/// The default timestamp type. +pub type Timestamp = u64; + +/// The default gas type. +pub type Gas = u64; + +/// The default block number type. +pub type BlockNumber = u32; diff --git a/integration-tests/public/contract-invocation/Cargo.toml b/integration-tests/public/contract-invocation/Cargo.toml new file mode 100644 index 00000000000..3e9c3c7057d --- /dev/null +++ b/integration-tests/public/contract-invocation/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "instantiate_contract" +version = "0.1.0" +edition = "2021" +authors = ["Víctor M. González "] + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = [ + "ink/std", + "scale/std", + "scale-info/std", + "contract1/std", + "contract2/std", + "virtual_contract/std", + "virtual_contract_ver1/std", + "virtual_contract_ver2/std", +] +ink-as-dependency = [] +e2e-tests = [] +test_instantiate = [ + "ink/test_instantiate", + "contract1/test_instantiate", + "contract2/test_instantiate", + "virtual_contract/test_instantiate", + "virtual_contract_ver1/test_instantiate", + "virtual_contract_ver2/test_instantiate", +] + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } +scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.11.1", default-features = false, features = [ + "derive", +], optional = true } +contract1 = { path = "./contract1", default-features = false, features = [ + "ink-as-dependency", +] } +contract2 = { path = "./contract2", default-features = false, features = [ + "ink-as-dependency", +] } +virtual_contract = { path = "./virtual_contract", default-features = false, features = [ + "ink-as-dependency", +] } +virtual_contract_ver1 = { path = "./virtual_contract_ver1", default-features = false, features = [ + "ink-as-dependency", +] } +virtual_contract_ver2 = { path = "./virtual_contract_ver2", default-features = false, features = [ + "ink-as-dependency", +] } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[profile.dev] +overflow-checks = false + +[profile.release] +overflow-checks = false diff --git a/integration-tests/public/contract-invocation/README.md b/integration-tests/public/contract-invocation/README.md new file mode 100644 index 00000000000..c0a20566257 --- /dev/null +++ b/integration-tests/public/contract-invocation/README.md @@ -0,0 +1,41 @@ +# Function `instantiate_contract` + +```rust +pub fn instantiate_contract( + params: &CreateParams +) -> Result>::Output>> +where + E: Environment, + ContractRef: FromAccountId, + Args: Encode, + Salt: AsRef<[u8]>, + R: ConstructorReturnType, +``` + +## Description + +`instantiate_contract` is a low level way to instantiate another smart contract. + +## Related ink! functions + +- [instantiate_contract](https://paritytech.github.io/ink/ink_env/fn.instantiate_contract.html) + +## Test case (original status) + +### Comparison Integration vs E2E + +The End-to-End test works correctly since it invokes successfully the call to the second contract. In Integration did not work since it's [unimplemented](https://github.com/paritytech/ink/blob/c2af39883aab48c71dc09dac5d06583f2e84dc54/crates/env/src/engine/off_chain/impls.rs#L464). + +| \# | Test | Integration | E2E | +| --- | --------------------------------------------------------------- | :---------: | :-: | +| 1 | Attempts to instantiate a contract. | ❌ | ✅ | + +### Result + +The implementation of instantiate_contract() is somewhat tied to that of code_hash and own_code_hash(). The strategy picked for one will condition the solution for the other. The simpler of the two may take up to roughly 15 days. There are some things to work out, such as how to call the required function, that add some uncertainty to the estimate. + +## Status after implementation + +| \# | Test | Integration | E2E | +| --- | --------------------------------------------------------------- | :---------: | :-: | +| 1 | Attempts to instantiate a contract. | ✅ | ✅ | diff --git a/integration-tests/public/contract-invocation/contract1/Cargo.toml b/integration-tests/public/contract-invocation/contract1/Cargo.toml new file mode 100644 index 00000000000..8dce80b4796 --- /dev/null +++ b/integration-tests/public/contract-invocation/contract1/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "contract1" +version = "0.1.0" +edition = "2021" +authors = ["Víctor M. González "] + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = ["ink/std", "scale/std", "scale-info/std"] +ink-as-dependency = [] +e2e-tests = [] +test_instantiate = [ + "ink/test_instantiate" +] + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } +scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.11.1", default-features = false, features = [ + "derive", +], optional = true } + +[dev-dependencies] +ink_e2e = { path = "../../../../crates/e2e" } + +[profile.dev] +overflow-checks = false + +[profile.release] +overflow-checks = false diff --git a/integration-tests/public/contract-invocation/contract1/lib.rs b/integration-tests/public/contract-invocation/contract1/lib.rs new file mode 100644 index 00000000000..2f1053ac60d --- /dev/null +++ b/integration-tests/public/contract-invocation/contract1/lib.rs @@ -0,0 +1,42 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::contract1::Contract1Ref; + +#[ink::contract()] +mod contract1 { + + #[ink(storage)] + pub struct Contract1 { + x: u32, + } + + impl Contract1 { + /// Creates a new Template contract. + #[ink(constructor)] + pub fn new() -> Self { + Self { x: 42 } + } + + #[ink(message)] + pub fn set_x(&mut self, x: u32) { + self.x = x; + } + + #[ink(message)] + pub fn get_x(&self) -> u32 { + self.x + } + + /// Returns the hash code of the contract through the function 'own_code_hash'. + #[ink(message)] + pub fn own_code_hash(&self) -> Hash { + self.env().own_code_hash().unwrap() + } + } + + impl Default for Contract1 { + fn default() -> Self { + Self::new() + } + } +} diff --git a/integration-tests/public/contract-invocation/contract2/Cargo.toml b/integration-tests/public/contract-invocation/contract2/Cargo.toml new file mode 100644 index 00000000000..4888f746b2c --- /dev/null +++ b/integration-tests/public/contract-invocation/contract2/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "contract2" +version = "0.1.0" +edition = "2021" +authors = ["Víctor M. González "] + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = ["ink/std", "scale/std", "scale-info/std"] +ink-as-dependency = [] +e2e-tests = [] +test_instantiate = [ + "ink/test_instantiate" +] + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } +scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.11.1", default-features = false, features = [ + "derive", +], optional = true } + +[dev-dependencies] +ink_e2e = { path = "../../../../crates/e2e" } + +[profile.dev] +overflow-checks = false + +[profile.release] +overflow-checks = false diff --git a/integration-tests/public/contract-invocation/contract2/lib.rs b/integration-tests/public/contract-invocation/contract2/lib.rs new file mode 100644 index 00000000000..01172827d38 --- /dev/null +++ b/integration-tests/public/contract-invocation/contract2/lib.rs @@ -0,0 +1,42 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::contract2::Contract2Ref; + +#[ink::contract()] +mod contract2 { + + #[ink(storage)] + pub struct Contract2 { + x: u64, + } + + impl Contract2 { + /// Creates a new Template contract. + #[ink(constructor)] + pub fn new() -> Self { + Self { x: 0 } + } + + #[ink(message)] + pub fn get_x(&self) -> u32 { + 123456 + } + + #[ink(message)] + pub fn set_x(&mut self, x: u64) { + self.x = x; + } + + /// Returns the hash code of the contract through the function 'own_code_hash'. + #[ink(message)] + pub fn own_code_hash(&self) -> Hash { + self.env().own_code_hash().unwrap() + } + } + + impl Default for Contract2 { + fn default() -> Self { + Self::new() + } + } +} diff --git a/integration-tests/public/contract-invocation/lib.rs b/integration-tests/public/contract-invocation/lib.rs new file mode 100644 index 00000000000..a56fc7a3f8f --- /dev/null +++ b/integration-tests/public/contract-invocation/lib.rs @@ -0,0 +1,304 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod instantiate_contract { + use contract1::Contract1Ref; + use contract2::Contract2Ref; + use ink::env::{ + call::{ + build_create, + build_call, + ExecutionInput, + Selector, + }, + }; + + #[ink(storage)] + pub struct ContractTester {} + + impl ContractTester { + #[ink(constructor)] + pub fn new() -> Self { + Self{ } + } + + #[ink(message)] + pub fn instantiate_contract1(&self, code_hash: Hash, salt: u32) -> Contract1Ref { + let salt = salt.to_le_bytes(); + let create_params = build_create::() + .code_hash(code_hash) + .endowment(0) + .exec_input(ExecutionInput::new( + Selector::new(ink::selector_bytes!("new")), + )) + .salt_bytes(&salt) + .returns::() + .params(); + + self.env() + .instantiate_contract(&create_params) + .unwrap_or_else(|error| { + panic!( + "Received an error from the Contracts pallet while instantiating: {:?}", + error + ) + }) + .unwrap_or_else(|error| { + panic!("Received a `LangError` while instatiating: {:?}", error) + }) + } + + #[ink(message)] + pub fn instantiate_contract2(&self, code_hash: Hash, salt: u32) -> Contract2Ref { + let salt = salt.to_le_bytes(); + let create_params = build_create::() + .code_hash(code_hash) + .endowment(0) + .exec_input(ExecutionInput::new( + Selector::new(ink::selector_bytes!("new")), + )) + .salt_bytes(&salt) + .returns::() + .params(); + + self.env() + .instantiate_contract(&create_params) + .unwrap_or_else(|error| { + panic!( + "Received an error from the Contracts pallet while instantiating: {:?}", + error + ) + }) + .unwrap_or_else(|error| { + panic!("Received a `LangError` while instatiating: {:?}", error) + }) + } + + #[ink(message)] + pub fn contract1_get_x(&self, contract1_address: [u8; 32]) -> u32 { + let call = build_call() + .call(AccountId::from(contract1_address)) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!("get_x"))) + ) + .returns::() + .params(); + + self.env() + .invoke_contract(&call) + .unwrap_or_else(|env_err| { + panic!("Received an error from the Environment: {:?}", env_err) + }) + .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err)) + } + + #[ink(message)] + pub fn contract2_get_x(&self, contract2_address: [u8; 32]) -> u32 { + let call = build_call() + .call(AccountId::from(contract2_address)) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!("get_x"))) + ) + .returns::() + .params(); + + self.env() + .invoke_contract(&call) + .unwrap_or_else(|env_err| { + panic!("Received an error from the Environment: {:?}", env_err) + }) + .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err)) + } + + #[ink(message)] + pub fn contract1_set_x(&self, contract1_address: [u8; 32], new_x: u32) { + let call = ink::env::call::build_call() + .call(AccountId::from(contract1_address)) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!("set_x"))) + .push_arg(new_x) + ) + .returns::<()>() + .params(); + + self.env() + .invoke_contract(&call) + .unwrap_or_else(|env_err| { + panic!("Received an error from the Environment: {:?}", env_err) + }) + .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err)) + } + + #[ink(message)] + pub fn contract2_set_x(&self, contract2_address: [u8; 32], new_x: u64) { + let call = ink::env::call::build_call() + .call(AccountId::from(contract2_address)) + .transferred_value(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!("set_x"))) + .push_arg(new_x) + ) + .returns::<()>() + .params(); + + self.env() + .invoke_contract(&call) + .unwrap_or_else(|env_err| { + panic!("Received an error from the Environment: {:?}", env_err) + }) + .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err)) + } + } + + impl Default for ContractTester { + fn default() -> Self { + Self::new() + } + } + + #[cfg(all(test, feature = "test_instantiate"))] + mod tests { + use super::*; + use ink::{ + env::{ + DefaultEnvironment, + }, + primitives::AccountId, + }; + use virtual_contract::VirtualContractRef; + use virtual_contract_ver1::VirtualContractVer1Ref; + use virtual_contract_ver2::VirtualContractVer2Ref; + + fn instantiate_contract1(contract: &ContractTester, code_hash: Hash, salt: u32) -> AccountId{ + let cr = contract.instantiate_contract1(code_hash, salt); + ink::ToAccountId::::to_account_id(&cr) + } + + fn instantiate_contract2(contract: &ContractTester, code_hash: Hash, salt: u32) -> AccountId{ + let cr = contract.instantiate_contract2(code_hash, salt); + ink::ToAccountId::::to_account_id(&cr) + } + + fn to_array(address: &mut AccountId) -> [u8; 32]{ + let temp: &[u8; 32] = address.as_mut(); + *temp + } + + #[ink::test] + fn test_invoke() { + let contract = ContractTester::new(); + let code_hash1 = ink::env::test::upload_code::(); + let code_hash2 = ink::env::test::upload_code::(); + + let mut contract1_address1_account = instantiate_contract1(&contract, code_hash1, 1); + let mut contract1_address2_account = instantiate_contract1(&contract, code_hash1, 2); + let mut contract2_address1_account = instantiate_contract2(&contract, code_hash2, 3); + let mut contract2_address2_account = instantiate_contract2(&contract, code_hash2, 4); + + let contract1_address1 = to_array(&mut contract1_address1_account); + let contract1_address2 = to_array(&mut contract1_address2_account); + let contract2_address1 = to_array(&mut contract2_address1_account); + let contract2_address2 = to_array(&mut contract2_address2_account); + + let check_hashes = |a, b, c|{ + let x = ink::env::code_hash::(a) + .expect("failed to get code hash"); + let y = ink::env::code_hash::(b) + .expect("failed to get code hash"); + + assert_eq!(x, c); + assert_eq!(y, c); + }; + check_hashes(&contract1_address1_account, &contract1_address2_account, code_hash1); + check_hashes(&contract2_address1_account, &contract2_address2_account, code_hash2); + + let check_values1 = |a, b| { + let x = contract.contract1_get_x(contract1_address1); + let y = contract.contract1_get_x(contract1_address2); + assert_eq!(x, a); + assert_eq!(y, b); + }; + let check_values2 = |a, b| { + let x = contract.contract2_get_x(contract2_address1); + let y = contract.contract2_get_x(contract2_address2); + assert_eq!(x, a); + assert_eq!(y, b); + }; + + check_values1(42, 42); + check_values2(123456, 123456); + contract.contract2_set_x(contract2_address1, 78); + check_values1(42, 42); + check_values2(123456, 123456); + contract.contract1_set_x(contract1_address1, 123); + contract.contract2_set_x(contract2_address2, 87); + check_values1(123, 42); + check_values2(123456, 123456); + contract.contract1_set_x(contract1_address2, 321); + check_values1(123, 321); + check_values2(123456, 123456); + + } + + #[ink::test] + fn test_invoke_delegate() { + let code_hash1 = ink::env::test::upload_code::(); + let code_hash2 = ink::env::test::upload_code::(); + let code_hash3 = ink::env::test::upload_code::(); + + let ic = |hash, salt: u32, x|{ + let salt = salt.to_le_bytes(); + let create_params = build_create::() + .code_hash(code_hash1) + .endowment(0) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!("new"))) + .push_arg(hash) + .push_arg(x), + ) + .salt_bytes(&salt) + .returns::() + .params(); + + ink::env::instantiate_contract(&create_params) + .unwrap_or_else(|error| { + panic!( + "Received an error from the Contracts pallet while instantiating: {:?}", + error + ) + }) + .unwrap_or_else(|error| { + panic!("Received a `LangError` while instatiating: {:?}", error) + }) + }; + + let mut ref1 = ic(code_hash2, 1, 42); + let mut ref2 = ic(code_hash3, 2, 74); + + let check_values = |r1: &VirtualContractRef, r2: &VirtualContractRef, a, b, c, d|{ + let v1 = r1.real_get_x(); + let v2 = r2.real_get_x(); + let v3 = r1.get_x(); + let v4 = r2.get_x(); + assert_eq!(v1, a); + assert_eq!(v2, b); + assert_eq!(v3, c); + assert_eq!(v4, d); + }; + + check_values(&ref1, &ref2, 42, 74, 43, 148); + ref1.set_x(15); + check_values(&ref1, &ref2, 42, 74, 43, 148); + ref1.real_set_x(15); + check_values(&ref1, &ref2, 15, 74, 16, 148); + ref2.set_x(39); + check_values(&ref1, &ref2, 15, 74, 16, 148); + ref2.real_set_x(39); + check_values(&ref1, &ref2, 15, 39, 16, 78); + + } + } +} diff --git a/integration-tests/public/contract-invocation/virtual_contract/Cargo.toml b/integration-tests/public/contract-invocation/virtual_contract/Cargo.toml new file mode 100644 index 00000000000..9cb1783ac82 --- /dev/null +++ b/integration-tests/public/contract-invocation/virtual_contract/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "virtual_contract" +version = "0.1.0" +edition = "2021" +authors = ["Víctor M. González "] + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = ["ink/std", "scale/std", "scale-info/std"] +ink-as-dependency = [] +e2e-tests = [] +test_instantiate = [ + "ink/test_instantiate" +] + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } +scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.11.1", default-features = false, features = [ + "derive", +], optional = true } + +[dev-dependencies] +ink_e2e = { path = "../../../../crates/e2e" } + +[profile.dev] +overflow-checks = false + +[profile.release] +overflow-checks = false diff --git a/integration-tests/public/contract-invocation/virtual_contract/lib.rs b/integration-tests/public/contract-invocation/virtual_contract/lib.rs new file mode 100644 index 00000000000..60011b78647 --- /dev/null +++ b/integration-tests/public/contract-invocation/virtual_contract/lib.rs @@ -0,0 +1,87 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::virtual_contract::VirtualContractRef; + +#[ink::contract()] +mod virtual_contract { + use ink::env::call::{ + build_call, + ExecutionInput, + Selector, + }; + + #[ink(storage)] + pub struct VirtualContract { + version: [u8; 32], + x: u32, + } + + impl VirtualContract { + /// Creates a new Template contract. + #[ink(constructor)] + pub fn new(version: [u8; 32], x: u32) -> Self { + Self { + version, + x, + } + } + + #[ink(message)] + pub fn set_version(&mut self, version: [u8; 32]) { + self.version = version; + } + + #[ink(message)] + pub fn real_set_x(&mut self, x: u32) { + self.x = x; + } + + #[ink(message)] + pub fn real_get_x(&self) -> u32 { + self.x + } + + #[ink(message)] + pub fn set_x(&mut self, x: u32) { + let call = build_call() + .delegate(Hash::from(self.version)) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!("set_x"))) + .push_arg(x) + ) + .returns::<()>() + .params(); + + self.env() + .invoke_contract_delegate(&call) + .unwrap_or_else(|env_err| { + panic!("Received an error from the Environment: {:?}", env_err) + }) + .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err)); + } + + #[ink(message)] + pub fn get_x(&self) -> u32 { + let call = build_call() + .delegate(Hash::from(self.version)) + .exec_input( + ExecutionInput::new(Selector::new(ink::selector_bytes!("get_x"))) + ) + .returns::() + .params(); + + self.env() + .invoke_contract_delegate(&call) + .unwrap_or_else(|env_err| { + panic!("Received an error from the Environment: {:?}", env_err) + }) + .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err)) + } + } + + impl Default for VirtualContract { + fn default() -> Self { + Self::new([0; 32], 0) + } + } +} diff --git a/integration-tests/public/contract-invocation/virtual_contract_ver1/Cargo.toml b/integration-tests/public/contract-invocation/virtual_contract_ver1/Cargo.toml new file mode 100644 index 00000000000..9050db60316 --- /dev/null +++ b/integration-tests/public/contract-invocation/virtual_contract_ver1/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "virtual_contract_ver1" +version = "0.1.0" +edition = "2021" +authors = ["Víctor M. González "] + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = ["ink/std", "scale/std", "scale-info/std"] +ink-as-dependency = [] +e2e-tests = [] +test_instantiate = [ + "ink/test_instantiate" +] + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } +scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.11.1", default-features = false, features = [ + "derive", +], optional = true } + +[dev-dependencies] +ink_e2e = { path = "../../../../crates/e2e" } + +[profile.dev] +overflow-checks = false + +[profile.release] +overflow-checks = false diff --git a/integration-tests/public/contract-invocation/virtual_contract_ver1/lib.rs b/integration-tests/public/contract-invocation/virtual_contract_ver1/lib.rs new file mode 100644 index 00000000000..d9d39cad73d --- /dev/null +++ b/integration-tests/public/contract-invocation/virtual_contract_ver1/lib.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::virtual_contract_ver1::VirtualContractVer1Ref; + +#[ink::contract()] +mod virtual_contract_ver1 { + + #[ink(storage)] + pub struct VirtualContractVer1 { + version: [u8; 32], + x: u32, + } + + impl VirtualContractVer1 { + /// Creates a new Template contract. + #[ink(constructor)] + pub fn new() -> Self { + Self { + version: [0; 32], + x: 42 + } + } + + #[ink(message)] + pub fn set_x(&mut self, x: u32) { + self.x = x / 2; + } + + #[ink(message)] + pub fn get_x(&self) -> u32 { + self.x.saturating_add(1) + } + } + + impl Default for VirtualContractVer1 { + fn default() -> Self { + Self::new() + } + } +} diff --git a/integration-tests/public/contract-invocation/virtual_contract_ver2/Cargo.toml b/integration-tests/public/contract-invocation/virtual_contract_ver2/Cargo.toml new file mode 100644 index 00000000000..f8d36cf17c3 --- /dev/null +++ b/integration-tests/public/contract-invocation/virtual_contract_ver2/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "virtual_contract_ver2" +version = "0.1.0" +edition = "2021" +authors = ["Víctor M. González "] + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = ["ink/std", "scale/std", "scale-info/std"] +ink-as-dependency = [] +e2e-tests = [] +test_instantiate = [ + "ink/test_instantiate" +] + +[dependencies] +ink = { path = "../../../../crates/ink", default-features = false } +scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.11.1", default-features = false, features = [ + "derive", +], optional = true } + +[dev-dependencies] +ink_e2e = { path = "../../../../crates/e2e" } + +[profile.dev] +overflow-checks = false + +[profile.release] +overflow-checks = false diff --git a/integration-tests/public/contract-invocation/virtual_contract_ver2/lib.rs b/integration-tests/public/contract-invocation/virtual_contract_ver2/lib.rs new file mode 100644 index 00000000000..6ef2ea29207 --- /dev/null +++ b/integration-tests/public/contract-invocation/virtual_contract_ver2/lib.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::virtual_contract_ver2::VirtualContractVer2Ref; + +#[ink::contract()] +mod virtual_contract_ver2 { + + #[ink(storage)] + pub struct VirtualContractVer2 { + version: [u8; 32], + x: u32, + } + + impl VirtualContractVer2 { + /// Creates a new Template contract. + #[ink(constructor)] + pub fn new() -> Self { + Self { + version: [0; 32], + x: 42 + } + } + + #[ink(message)] + pub fn set_x(&mut self, x: u32) { + self.x = x.saturating_sub(1); + } + + #[ink(message)] + pub fn get_x(&self) -> u32 { + self.x.saturating_mul(2) + } + } + + impl Default for VirtualContractVer2 { + fn default() -> Self { + Self::new() + } + } +} diff --git a/integration-tests/public/own-code-hash/.gitignore b/integration-tests/public/own-code-hash/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/public/own-code-hash/.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 \ No newline at end of file diff --git a/integration-tests/public/own-code-hash/Cargo.toml b/integration-tests/public/own-code-hash/Cargo.toml new file mode 100644 index 00000000000..61dd31d32a5 --- /dev/null +++ b/integration-tests/public/own-code-hash/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "own-code-hash" +version = "4.3.0" +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.5", default-features = false, features = ["derive"], optional = true } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = [ + "ink/std", + "scale/std", + "scale-info/std" +] +ink-as-dependency = [] +e2e-tests = [] +test_instantiate = [ + "ink/test_instantiate", +] diff --git a/integration-tests/public/own-code-hash/lib.rs b/integration-tests/public/own-code-hash/lib.rs new file mode 100644 index 00000000000..fa2f6438269 --- /dev/null +++ b/integration-tests/public/own-code-hash/lib.rs @@ -0,0 +1,116 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod own_code_hash { + + #[ink(storage)] + pub struct OwnCodeHash {} + + impl OwnCodeHash { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Returns the code hash of the contract + #[ink(message)] + pub fn own_code_hash(&self) -> Hash { + self.env().own_code_hash().unwrap() + } + + /// Returns the code hash of the contract by providing it's `account_id` + #[ink(message)] + pub fn get_code(&self) -> Hash { + self.env() + .code_hash(&self.env().account_id()) + .expect("Failed to get code hash") + } + } + + impl Default for OwnCodeHash { + fn default() -> Self { + Self::new() + } + } + + #[cfg(all(test, feature = "test_instantiate"))] + mod tests { + use super::*; + + #[ink::test] + fn get_own_code_hash() { + let code_hash = ink::env::test::upload_code::(); + let address = + { + let create_params = ink::env::call::build_create::() + .code_hash(code_hash) + .endowment(0) + .exec_input(ink::env::call::ExecutionInput::new( + ink::env::call::Selector::new(ink::selector_bytes!("new")), + )) + .salt_bytes(&[0_u8; 4]) + .returns::() + .params(); + + let cr = ink::env::instantiate_contract(&create_params) + .unwrap_or_else(|error| { + panic!( + "Received an error from the Contracts pallet while instantiating: {:?}", + error + ) + }) + .unwrap_or_else(|error| { + panic!("Received a `LangError` while instatiating: {:?}", error) + }); + ink::ToAccountId::::to_account_id(&cr) + }; + + let own_code_hash = OwnCodeHash::new(); + ink::env::test::set_callee::(address); + let code_hash_via_own: Hash = own_code_hash.own_code_hash(); + + assert_eq!(code_hash_via_own, code_hash); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use ink_e2e::build_message; + + use super::*; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn get_own_code_hash(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = OwnCodeHashRef::new(); + let contract_acc_id = client + .instantiate("own_code_hash", &ink_e2e::bob(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let own_code_hash = build_message::(contract_acc_id) + .call(|contract| contract.own_code_hash()); + let own_code_hash_res = client + .call(&ink_e2e::bob(), own_code_hash, 0, None) + .await + .expect("own_code_hash failed"); + + // Compare codes obtained differently with own_code_hash and code_hash + let get_code = build_message::(contract_acc_id) + .call(|contract| contract.get_code()); + let get_code_res = client + .call(&ink_e2e::alice(), get_code, 0, None) + .await + .expect("get_code failed"); + + let code_hash_via_own = own_code_hash_res.return_value(); + let code_hash_via_get = get_code_res.return_value(); + + assert_eq!(code_hash_via_own, code_hash_via_get); + + Ok(()) + } + } +} \ No newline at end of file