diff --git a/CHANGELOG.md b/CHANGELOG.md index e81a30944a5..c9b11ef8d3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [E2E] Call builders and extra gas margin option - [#1917](https://github.com/paritytech/ink/pull/1917) - Make `set_code_hash` generic - [#1906](https://github.com/paritytech/ink/pull/1906) - Clean E2E configuration parsing - [#1922](https://github.com/paritytech/ink/pull/1922) +- Implement contract invokation in off-chain environment engine - [#1957](https://github.com/paritytech/ink/pull/1988) +- Implement `caller_is_origin()` in off-chain environment engine - [#1991](https://github.com/paritytech/ink/pull/1991) ### Changed - Fail when decoding from storage and not all bytes consumed - [#1897](https://github.com/paritytech/ink/pull/1897) diff --git a/crates/engine/src/database.rs b/crates/engine/src/database.rs index 310225be28c..2c7ea2b9251 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] { @@ -35,6 +38,31 @@ pub fn storage_of_contract_key(who: &[u8], key: &[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 +} + /// The chain database. /// /// Everything is stored in here: accounts, balances, contract storage, etc.. @@ -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.to_vec()).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/exec_context.rs b/crates/engine/src/exec_context.rs index 05bcf699b01..b8c1d9fe6df 100644 --- a/crates/engine/src/exec_context.rs +++ b/crates/engine/src/exec_context.rs @@ -45,6 +45,8 @@ pub struct ExecContext { pub block_timestamp: BlockTimestamp, /// Known contract accounts pub contracts: Vec>, + /// This value is used to know the origin of the contract calls. + pub depth: u8, } impl ExecContext { diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index 387eb57127c..d1a7245f557 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -505,6 +505,10 @@ impl Engine { Err(_) => Err(Error::EcdsaRecoveryFailed), } } + + pub fn caller_is_origin(&self) -> bool { + self.exec_context.depth == 0 + } } /// Copies the `slice` into `output`. diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index ded35d62c8b..18fcf02ae5a 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 5dd9275d2a3..129f3aa2d51 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -60,6 +60,7 @@ ink = { path = "../ink" } [features] default = ["std"] std = [ + "ink/std", "ink_allocator/std", "ink_prelude/std", "ink_primitives/std", @@ -77,6 +78,7 @@ std = [ "sha3", "blake2", ] +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 c0880cc9e8f..b90213068ae 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -37,8 +37,10 @@ use crate::{ CryptoHash, HashOutput, }, - types::Gas, - Environment, + types::{ + Environment, + Gas, + }, Result, }; use ink_storage_traits::Storable; @@ -336,7 +338,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, @@ -426,6 +430,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, @@ -435,6 +440,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 99455b5786e..7756e79fbc9 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -26,7 +26,7 @@ use crate::{ CryptoHash, HashOutput, }, - Environment, + types::Environment, Result, }; use ink_storage_traits::Storable; @@ -247,10 +247,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 @@ -469,7 +484,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.rs b/crates/env/src/call/call_builder.rs index c5e803a25dc..06194ba389a 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -23,8 +23,10 @@ use crate::{ }, ExecutionInput, }, - types::Gas, - Environment, + types::{ + Environment, + Gas, + }, Error, }; use core::marker::PhantomData; diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs index 74dc429287b..d3d1676420c 100644 --- a/crates/env/src/call/create_builder.rs +++ b/crates/env/src/call/create_builder.rs @@ -24,8 +24,8 @@ use crate::{ ExecutionInput, Selector, }, + types::Environment, ContractEnv, - Environment, Error, }; use core::marker::PhantomData; @@ -240,7 +240,11 @@ where impl CreateParams 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, @@ -721,7 +725,11 @@ impl > where E: Environment, - ContractRef: FromAccountId, + ContractRef: FromAccountId + crate::ContractReverseReference, + ::Type: + crate::reflect::ContractConstructorDecoder, + ::Type: + crate::reflect::ContractMessageDecoder, GasLimit: Unwrap, Args: scale::Encode, Salt: AsRef<[u8]>, diff --git a/crates/env/src/engine/mod.rs b/crates/env/src/engine/mod.rs index 3d94f996784..256c2393d68 100644 --- a/crates/env/src/engine/mod.rs +++ b/crates/env/src/engine/mod.rs @@ -77,7 +77,7 @@ pub(crate) fn decode_instantiate_result( ) -> EnvResult>::Output>> where I: scale::Input, - E: crate::Environment, + E: crate::types::Environment, ContractRef: FromAccountId, R: ConstructorReturnType, { @@ -102,7 +102,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 f170a8ab537..6fe5f323b53 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -34,9 +34,9 @@ use crate::{ Keccak256, Sha2x256, }, + types::Environment, Clear, EnvBackend, - Environment, Error, Result, ReturnFlags, @@ -60,6 +60,76 @@ 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; + env.engine.exec_context.depth += 1; + } + + let result = handler(input); + + if restore_callee { + env.engine.set_callee(old_callee); + env.engine.exec_context.depth -= 1; + } + + 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]; @@ -187,6 +257,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 { @@ -251,11 +338,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) { @@ -385,8 +483,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(()) } } @@ -465,12 +566,22 @@ impl TypedEnvBackend for EnvInstance { Args: scale::Encode, R: scale::Decode, { - let _gas_limit = params.gas_limit(); - let _callee = params.callee(); - 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 gas_limit = params.gas_limit(); + let call_flags = params.call_flags().into_u32(); + 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, + Some(gas_limit), + call_flags, + Some(transferred_value), + Some(callee_account), + None, + input, + ) } fn invoke_contract_delegate( @@ -482,9 +593,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().into_u32(); + 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, ) } @@ -498,17 +619,75 @@ 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 _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 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.exec_context.depth += 1; + 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); + self.engine.exec_context.depth -= 1; + + Ok(Ok(R::ok( + >::from_account_id(account_id), + ))) } fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! @@ -549,21 +728,39 @@ impl TypedEnvBackend for EnvInstance { where E: Environment, { - unimplemented!("off-chain environment does not support cross-contract calls") + self.engine.caller_is_origin() } - 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(Error::KeyNotFound) + } } 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(Error::KeyNotFound) + } } 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 1f463b4427f..589b794df1d 100644 --- a/crates/env/src/engine/off_chain/mod.rs +++ b/crates/env/src/engine/off_chain/mod.rs @@ -46,7 +46,7 @@ impl OnInstance for EnvInstance { } ) ); - INSTANCE.with(|instance| f(&mut instance.borrow_mut())) + 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 b36c58d2252..9be60564e6b 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; @@ -430,3 +430,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.rs b/crates/env/src/engine/on_chain/impls.rs index 3c5df7613ea..54f77cb4199 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -39,11 +39,11 @@ use crate::{ Keccak256, Sha2x256, }, + types::FromLittleEndian, Clear, EnvBackend, Environment, Error, - FromLittleEndian, Result, ReturnFlags, TypedEnvBackend, diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index 859ef7515e3..9c7f78cf831 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 881de493441..31c950f459b 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -84,17 +84,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; @@ -113,10 +110,6 @@ pub use self::{ CallFlags, ReturnFlags, }, - contract::{ - ContractEnv, - ContractReference, - }, error::{ Error, Result, @@ -124,14 +117,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 b4507437b39..00000000000 --- a/crates/env/src/types.rs +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (C) 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. - -//! 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 - + 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/crates/ink/Cargo.toml b/crates/ink/Cargo.toml index 7afe993fd72..12d4a6ba8ac 100644 --- a/crates/ink/Cargo.toml +++ b/crates/ink/Cargo.toml @@ -50,6 +50,9 @@ std = [ 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 f0a9a4541bf..38090e3ede9 100644 --- a/crates/ink/codegen/Cargo.toml +++ b/crates/ink/codegen/Cargo.toml @@ -39,5 +39,4 @@ default = ["std"] std = [ "itertools/use_std", "either/use_std", - "ir/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 bbedecc333e..461ba595d47 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 40711d1a9af..d374a2597a5 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -598,6 +598,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(()) } ) }); @@ -637,7 +640,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 ),* @@ -793,7 +796,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(()) } ) }); @@ -843,7 +849,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> { @@ -863,7 +869,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 b0fe1a25bdb..6d6471e53e3 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::{self,}, traits::GenerateCode, EnforcedErrors, }; diff --git a/crates/ink/codegen/src/lib.rs b/crates/ink/codegen/src/lib.rs index b9c97ee994d..bc7f9c41f6e 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 100efa06548..4cd8320f434 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::{self,}, error::ExtError as _, ir, ir::{ diff --git a/crates/ink/macro/Cargo.toml b/crates/ink/macro/Cargo.toml index e6e785f3827..31813b4cb7f 100644 --- a/crates/ink/macro/Cargo.toml +++ b/crates/ink/macro/Cargo.toml @@ -43,4 +43,5 @@ std = [ "scale/std", "ink_ir/std", "ink_primitives/std", + "ink_codegen/std", ] diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 0cc8e871e81..ad17a9df189 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -499,7 +499,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 4c00a39d9aa..3e3fc5af56d 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 ba04fb8f656..fa9169c252c 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 @@ -8,7 +8,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 3474db6f08c..f3b3f43a73f 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 @@ -34,7 +34,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 155369edd5e..9c358277f1a 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/std", "scale-decode", 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 b43c31162bc..6c2e157886c 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 2ff9822f13b..6e464646688 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 558a218ff7a..38a37439984 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 3eb77c66fe6..66987d1f714 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::{ @@ -150,3 +151,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/caller-is-origin/Cargo.toml b/integration-tests/caller-is-origin/Cargo.toml new file mode 100644 index 00000000000..73828e30354 --- /dev/null +++ b/integration-tests/caller-is-origin/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "invoke_contract" +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 } +contract_to_call = { path = "./contract_to_call", default-features = false, features = ["ink-as-dependency"] } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std", "test_instantiate"] +std = [ + "ink/std", + "scale/std", + "scale-info/std" +] +test_instantiate = ["ink/test_instantiate", "contract_to_call/test_instantiate"] +ink-as-dependency = [] +e2e-tests = [] \ No newline at end of file diff --git a/integration-tests/caller-is-origin/contract_to_call/Cargo.toml b/integration-tests/caller-is-origin/contract_to_call/Cargo.toml new file mode 100644 index 00000000000..26d64d11b7a --- /dev/null +++ b/integration-tests/caller-is-origin/contract_to_call/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "contract_to_call" +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"] +std = [ + "ink/std", + "scale/std", + "scale-info/std" +] +test_instantiate = ["ink/test_instantiate"] +ink-as-dependency = [] +e2e-tests = [] \ No newline at end of file diff --git a/integration-tests/caller-is-origin/contract_to_call/lib.rs b/integration-tests/caller-is-origin/contract_to_call/lib.rs new file mode 100644 index 00000000000..fbef2a20616 --- /dev/null +++ b/integration-tests/caller-is-origin/contract_to_call/lib.rs @@ -0,0 +1,32 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +pub use self::contract_to_call::{ + ContractToCall, + ContractToCallRef, +}; + +#[ink::contract] +mod contract_to_call { + #[ink(storage)] + pub struct ContractToCall {} + + impl ContractToCall { + /// Creates a new Template contract. + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Returns true if this contract has been called as the origin. + #[ink(message)] + pub fn im_the_origin(&self) -> bool { + self.env().caller_is_origin() + } + } + + impl Default for ContractToCall { + fn default() -> Self { + Self::new() + } + } +} diff --git a/integration-tests/caller-is-origin/lib.rs b/integration-tests/caller-is-origin/lib.rs new file mode 100644 index 00000000000..4f52a1222c8 --- /dev/null +++ b/integration-tests/caller-is-origin/lib.rs @@ -0,0 +1,197 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod invoke_contract { + use ink::env::{ + call::{ + build_call, + ExecutionInput, + Selector, + }, + DefaultEnvironment, + }; + + #[ink(storage)] + pub struct InvokeContract {} + + impl InvokeContract { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Try to call the `split_profit` function of the contract sent by parameter. + /// If the account id of the sent contract is not valid it will fail. + #[ink(message)] + pub fn invoke_call(&self, contract_to_call: [u8; 32]) -> bool { + let call = build_call::() + .call(AccountId::from(contract_to_call)) + .gas_limit(0) + .transferred_value(0) + .exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!( + "im_the_origin" + )))) + .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 InvokeContract { + fn default() -> Self { + Self::new() + } + } + + #[cfg(test)] + mod tests { + use super::*; + use contract_to_call::{ + ContractToCall, + ContractToCallRef, + }; + + #[ink::test] + fn call_contract_directly() { + let contract = ContractToCall::new(); + let is_the_origin = contract.im_the_origin(); + assert!(is_the_origin); + } + + #[ink::test] + fn call_contract_indirectly() { + let contract = InvokeContract::new(); + let code_hash = ink::env::test::upload_code::< + ink::env::DefaultEnvironment, + ContractToCallRef, + >(); + let create_params = ink::env::call::build_create::() + .code_hash(code_hash) + .gas_limit(0) + .endowment(0) + .exec_input(ExecutionInput::new(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) + }); + + let mut account_id = + ink::ToAccountId::::to_account_id(&cr); + let account_id: &[u8; 32] = account_id.as_mut(); + let is_the_origin = contract.invoke_call(*account_id); + assert!(!is_the_origin); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use contract_to_call::{ + ContractToCall, + ContractToCallRef, + }; + use ink_e2e::ContractsBackend; + + use super::*; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test(additional_contracts = "./contract_to_call/Cargo.toml")] + async fn call_contract_directly( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + // given + let mut contract_to_call_constructor = ContractToCallRef::new(); + + let contract = client + .instantiate( + "contract_to_call", + &ink_e2e::alice(), + &mut contract_to_call_constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let call = contract.call::(); + + // when + let im_the_origin_call = call.im_the_origin(); + + let result = client + .call(&ink_e2e::alice(), &im_the_origin_call) + .submit() + .await; + + // then + assert!(result + .expect("This call always returns a value") + .return_value()); + + Ok(()) + } + + #[ink_e2e::test(additional_contracts = "./contract_to_call/Cargo.toml")] + async fn call_contract_indirectly( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + // given + let mut original_contract_contructor = InvokeContractRef::new(); + let mut contract_to_call_constructor = ContractToCallRef::new(); + + let original_contract = client + .instantiate( + "invoke_contract", + &ink_e2e::alice(), + &mut original_contract_contructor, + ) + .submit() + .await + .expect("instantiate failed"); + + let contract_to_call_acc_id = client + .instantiate( + "contract_to_call", + &ink_e2e::alice(), + &mut contract_to_call_constructor, + ) + .submit() + .await + .expect("instantiate failed") + .account_id; + + let call = original_contract.call::(); + + // when + let invoke_call = call.invoke_call(*contract_to_call_acc_id.as_ref()); + + let result = client.call(&ink_e2e::bob(), &invoke_call).submit().await; + + // then + assert!(!result + .expect("This call always returns a value") + .return_value()); + + Ok(()) + } + } +} diff --git a/integration-tests/contract-invocation/Cargo.toml b/integration-tests/contract-invocation/Cargo.toml new file mode 100644 index 00000000000..8eb206ebe1b --- /dev/null +++ b/integration-tests/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.5", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.6", 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/contract-invocation/README.md b/integration-tests/contract-invocation/README.md new file mode 100644 index 00000000000..c0a20566257 --- /dev/null +++ b/integration-tests/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/contract-invocation/contract1/Cargo.toml b/integration-tests/contract-invocation/contract1/Cargo.toml new file mode 100644 index 00000000000..2a7ff467154 --- /dev/null +++ b/integration-tests/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.5", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.6", 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/contract-invocation/contract1/lib.rs b/integration-tests/contract-invocation/contract1/lib.rs new file mode 100644 index 00000000000..2f1053ac60d --- /dev/null +++ b/integration-tests/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/contract-invocation/contract2/Cargo.toml b/integration-tests/contract-invocation/contract2/Cargo.toml new file mode 100644 index 00000000000..76e239a51d7 --- /dev/null +++ b/integration-tests/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.5", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.6", 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/contract-invocation/contract2/lib.rs b/integration-tests/contract-invocation/contract2/lib.rs new file mode 100644 index 00000000000..01172827d38 --- /dev/null +++ b/integration-tests/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/contract-invocation/lib.rs b/integration-tests/contract-invocation/lib.rs new file mode 100644 index 00000000000..bd4604547ab --- /dev/null +++ b/integration-tests/contract-invocation/lib.rs @@ -0,0 +1,311 @@ +#![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) + .gas_limit(0) + .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) + .gas_limit(0) + .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)) + .gas_limit(0) + .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)) + .gas_limit(0) + .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)) + .gas_limit(0) + .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)) + .gas_limit(0) + .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) + .gas_limit(0) + .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/contract-invocation/virtual_contract/Cargo.toml b/integration-tests/contract-invocation/virtual_contract/Cargo.toml new file mode 100644 index 00000000000..c9e6e99100a --- /dev/null +++ b/integration-tests/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.5", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.6", 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/contract-invocation/virtual_contract/lib.rs b/integration-tests/contract-invocation/virtual_contract/lib.rs new file mode 100644 index 00000000000..60011b78647 --- /dev/null +++ b/integration-tests/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/contract-invocation/virtual_contract_ver1/Cargo.toml b/integration-tests/contract-invocation/virtual_contract_ver1/Cargo.toml new file mode 100644 index 00000000000..12474869862 --- /dev/null +++ b/integration-tests/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.5", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.6", 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/contract-invocation/virtual_contract_ver1/lib.rs b/integration-tests/contract-invocation/virtual_contract_ver1/lib.rs new file mode 100644 index 00000000000..940d3fa5d37 --- /dev/null +++ b/integration-tests/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 + 1 + } + } + + impl Default for VirtualContractVer1 { + fn default() -> Self { + Self::new() + } + } +} diff --git a/integration-tests/contract-invocation/virtual_contract_ver2/Cargo.toml b/integration-tests/contract-invocation/virtual_contract_ver2/Cargo.toml new file mode 100644 index 00000000000..d8cd9031868 --- /dev/null +++ b/integration-tests/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.5", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.6", 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/contract-invocation/virtual_contract_ver2/lib.rs b/integration-tests/contract-invocation/virtual_contract_ver2/lib.rs new file mode 100644 index 00000000000..7c37c838fa3 --- /dev/null +++ b/integration-tests/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 - 1; + } + + #[ink(message)] + pub fn get_x(&self) -> u32 { + self.x * 2 + } + } + + impl Default for VirtualContractVer2 { + fn default() -> Self { + Self::new() + } + } +} diff --git a/integration-tests/own-code-hash/.gitignore b/integration-tests/own-code-hash/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/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/own-code-hash/Cargo.toml b/integration-tests/own-code-hash/Cargo.toml new file mode 100644 index 00000000000..98b1134dbf9 --- /dev/null +++ b/integration-tests/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/own-code-hash/lib.rs b/integration-tests/own-code-hash/lib.rs new file mode 100644 index 00000000000..d0b4f1559f8 --- /dev/null +++ b/integration-tests/own-code-hash/lib.rs @@ -0,0 +1,117 @@ +#![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::simulate_code_upload::(); + let address = + { + let create_params = ink::env::call::build_create::() + .code_hash(code_hash) + .gas_limit(0) + .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