diff --git a/RELEASES.md b/RELEASES.md index b1352d6fa87..1b0136e5af0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -137,7 +137,7 @@ Specifically you need to upgrade to at least the pallet version ## Added - Added support for wildcard selectors ‒ [#1020](https://github.com/paritytech/ink/pull/1020). - - This enables writing upgradable smart contracts using the proxy/forward pattern. + - This enables writing upgradeable smart contracts using the proxy/forward pattern. We added a new example to illustrate this ‒ the [proxy](https://github.com/paritytech/ink/tree/master/examples/proxy) example. - Annotating a wildcard selector in traits is not supported. - The ink! codegen now heavily relies on static type information based on traits defined in `ink_lang` ‒ [#665](https://github.com/paritytech/ink/pull/665). diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index b5d351448de..fb96c44e87c 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -21,8 +21,10 @@ use crate::{ TypedEnvBackend, }, call::{ + Call, CallParams, CreateParams, + DelegateCall, }, engine::{ EnvInstance, @@ -33,6 +35,7 @@ use crate::{ HashOutput, }, topics::Topics, + types::Gas, Environment, Result, }; @@ -43,12 +46,12 @@ use ink_primitives::Key; /// # Errors /// /// If the returned caller cannot be properly decoded. -pub fn caller() -> T::AccountId +pub fn caller() -> E::AccountId where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::caller::(instance) + TypedEnvBackend::caller::(instance) }) } @@ -57,12 +60,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn transferred_value() -> T::Balance +pub fn transferred_value() -> E::Balance where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::transferred_value::(instance) + TypedEnvBackend::transferred_value::(instance) }) } @@ -71,12 +74,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn weight_to_fee(gas: u64) -> T::Balance +pub fn weight_to_fee(gas: Gas) -> E::Balance where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::weight_to_fee::(instance, gas) + TypedEnvBackend::weight_to_fee::(instance, gas) }) } @@ -85,12 +88,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn gas_left() -> u64 +pub fn gas_left() -> Gas where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::gas_left::(instance) + TypedEnvBackend::gas_left::(instance) }) } @@ -99,12 +102,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn block_timestamp() -> T::Timestamp +pub fn block_timestamp() -> E::Timestamp where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::block_timestamp::(instance) + TypedEnvBackend::block_timestamp::(instance) }) } @@ -117,12 +120,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn account_id() -> T::AccountId +pub fn account_id() -> E::AccountId where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::account_id::(instance) + TypedEnvBackend::account_id::(instance) }) } @@ -131,12 +134,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn balance() -> T::Balance +pub fn balance() -> E::Balance where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::balance::(instance) + TypedEnvBackend::balance::(instance) }) } @@ -145,12 +148,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn block_number() -> T::BlockNumber +pub fn block_number() -> E::BlockNumber where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::block_number::(instance) + TypedEnvBackend::block_number::(instance) }) } @@ -160,23 +163,23 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn minimum_balance() -> T::Balance +pub fn minimum_balance() -> E::Balance where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::minimum_balance::(instance) + TypedEnvBackend::minimum_balance::(instance) }) } /// Emits an event with the given event data. -pub fn emit_event(event: Event) +pub fn emit_event(event: Event) where - T: Environment, + E: Environment, Event: Topics + scale::Encode, { ::on_instance(|instance| { - TypedEnvBackend::emit_event::(instance, event) + TypedEnvBackend::emit_event::(instance, event) }) } @@ -230,14 +233,39 @@ pub fn clear_contract_storage(key: &Key) { /// - If the called contract execution has trapped. /// - If the called contract ran out of gas upon execution. /// - If the returned value failed to decode properly. -pub fn invoke_contract(params: &CallParams) -> Result +pub fn invoke_contract(params: &CallParams, Args, R>) -> Result where - T: Environment, + E: Environment, Args: scale::Encode, R: scale::Decode, { ::on_instance(|instance| { - TypedEnvBackend::invoke_contract::(instance, params) + TypedEnvBackend::invoke_contract::(instance, params) + }) +} + +/// Invokes a contract message via delegate call and returns its result. +/// +/// # Note +/// +/// This is a low level way to evaluate another smart contract via delegate call. +/// Prefer to use the ink! guided and type safe approach to using this. +/// +/// # Errors +/// +/// - If the specified code hash does not exist. +/// - If arguments passed to the called code message are invalid. +/// - If the called code execution has trapped. +pub fn invoke_contract_delegate( + params: &CallParams, Args, R>, +) -> Result +where + E: Environment, + Args: scale::Encode, + R: scale::Decode, +{ + ::on_instance(|instance| { + TypedEnvBackend::invoke_contract_delegate::(instance, params) }) } @@ -256,16 +284,16 @@ where /// - If the instantiation process runs out of gas. /// - If given insufficient endowment. /// - If the returned account ID failed to decode properly. -pub fn instantiate_contract( - params: &CreateParams, -) -> Result +pub fn instantiate_contract( + params: &CreateParams, +) -> Result where - T: Environment, + E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>, { ::on_instance(|instance| { - TypedEnvBackend::instantiate_contract::(instance, params) + TypedEnvBackend::instantiate_contract::(instance, params) }) } @@ -279,12 +307,12 @@ where /// This function never returns. Either the termination was successful and the /// execution of the destroyed contract is halted. Or it failed during the termination /// which is considered fatal and results in a trap and rollback. -pub fn terminate_contract(beneficiary: T::AccountId) -> ! +pub fn terminate_contract(beneficiary: E::AccountId) -> ! where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::terminate_contract::(instance, beneficiary) + TypedEnvBackend::terminate_contract::(instance, beneficiary) }) } @@ -302,12 +330,12 @@ where /// - If the transfer had brought the sender's total balance below the /// minimum balance. You need to use [`terminate_contract`] in case /// this is your intention. -pub fn transfer(destination: T::AccountId, value: T::Balance) -> Result<()> +pub fn transfer(destination: E::AccountId, value: E::Balance) -> Result<()> where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::transfer::(instance, destination, value) + TypedEnvBackend::transfer::(instance, destination, value) }) } @@ -376,12 +404,12 @@ where /// made afterwards), then ensure no further commitments may be made and repeatedly /// call this on later blocks until the block number returned is later than the latest /// commitment. -pub fn random(subject: &[u8]) -> Result<(T::Hash, T::BlockNumber)> +pub fn random(subject: &[u8]) -> Result<(E::Hash, E::BlockNumber)> where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::random::(instance, subject) + TypedEnvBackend::random::(instance, subject) }) } @@ -477,12 +505,12 @@ pub fn ecdsa_recover( /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn is_contract(account: &T::AccountId) -> bool +pub fn is_contract(account: &E::AccountId) -> bool where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::is_contract::(instance, account) + TypedEnvBackend::is_contract::(instance, account) }) } @@ -498,11 +526,11 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn caller_is_origin() -> bool +pub fn caller_is_origin() -> bool where - T: Environment, + E: Environment, { ::on_instance(|instance| { - TypedEnvBackend::caller_is_origin::(instance) + TypedEnvBackend::caller_is_origin::(instance) }) } diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 5fed490a2c9..cb7b7723a9e 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -14,8 +14,10 @@ use crate::{ call::{ + Call, CallParams, CreateParams, + DelegateCall, }, hash::{ CryptoHash, @@ -289,56 +291,56 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`caller`][`crate::caller`] - fn caller(&mut self) -> T::AccountId; + fn caller(&mut self) -> E::AccountId; /// Returns the transferred value for the contract execution. /// /// # Note /// /// For more details visit: [`transferred_value`][`crate::transferred_value`] - fn transferred_value(&mut self) -> T::Balance; + fn transferred_value(&mut self) -> E::Balance; /// Returns the price for the specified amount of gas. /// /// # Note /// /// For more details visit: [`weight_to_fee`][`crate::weight_to_fee`] - fn weight_to_fee(&mut self, gas: u64) -> T::Balance; + fn weight_to_fee(&mut self, gas: u64) -> E::Balance; /// Returns the amount of gas left for the contract execution. /// /// # Note /// /// For more details visit: [`gas_left`][`crate::gas_left`] - fn gas_left(&mut self) -> u64; + fn gas_left(&mut self) -> u64; /// Returns the timestamp of the current block. /// /// # Note /// /// For more details visit: [`block_timestamp`][`crate::block_timestamp`] - fn block_timestamp(&mut self) -> T::Timestamp; + fn block_timestamp(&mut self) -> E::Timestamp; /// Returns the address of the executed contract. /// /// # Note /// /// For more details visit: [`account_id`][`crate::account_id`] - fn account_id(&mut self) -> T::AccountId; + fn account_id(&mut self) -> E::AccountId; /// Returns the balance of the executed contract. /// /// # Note /// /// For more details visit: [`balance`][`crate::balance`] - fn balance(&mut self) -> T::Balance; + fn balance(&mut self) -> E::Balance; /// Returns the current block number. /// /// # Note /// /// For more details visit: [`block_number`][`crate::block_number`] - fn block_number(&mut self) -> T::BlockNumber; + fn block_number(&mut self) -> E::BlockNumber; /// Returns the minimum balance that is required for creating an account /// (i.e. the chain's existential deposit). @@ -346,16 +348,16 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`minimum_balance`][`crate::minimum_balance`] - fn minimum_balance(&mut self) -> T::Balance; + fn minimum_balance(&mut self) -> E::Balance; /// Emits an event with the given event data. /// /// # Note /// /// For more details visit: [`emit_event`][`crate::emit_event`] - fn emit_event(&mut self, event: Event) + fn emit_event(&mut self, event: Event) where - T: Environment, + E: Environment, Event: Topics + scale::Encode; /// Invokes a contract message and returns its result. @@ -363,12 +365,26 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`invoke_contract`][`crate::invoke_contract`] - fn invoke_contract( + fn invoke_contract( &mut self, - call_data: &CallParams, + call_data: &CallParams, Args, R>, ) -> Result where - T: Environment, + E: Environment, + Args: scale::Encode, + R: scale::Decode; + + /// Invokes a contract message via delegate call and returns its result. + /// + /// # Note + /// + /// For more details visit: [`invoke_contract_delegate`][`crate::invoke_contract_delegate`] + fn invoke_contract_delegate( + &mut self, + call_data: &CallParams, Args, R>, + ) -> Result + where + E: Environment, Args: scale::Encode, R: scale::Decode; @@ -377,12 +393,12 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`instantiate_contract`][`crate::instantiate_contract`] - fn instantiate_contract( + fn instantiate_contract( &mut self, - params: &CreateParams, - ) -> Result + params: &CreateParams, + ) -> Result where - T: Environment, + E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>; @@ -391,27 +407,27 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`terminate_contract`][`crate::terminate_contract`] - fn terminate_contract(&mut self, beneficiary: T::AccountId) -> ! + fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! where - T: Environment; + E: Environment; /// Transfers value from the contract to the destination account ID. /// /// # Note /// /// For more details visit: [`transfer`][`crate::transfer`] - fn transfer(&mut self, destination: T::AccountId, value: T::Balance) -> Result<()> + fn transfer(&mut self, destination: E::AccountId, value: E::Balance) -> Result<()> where - T: Environment; + E: Environment; /// Returns a random hash seed. /// /// # Note /// /// For more details visit: [`random`][`crate::random`] - fn random(&mut self, subject: &[u8]) -> Result<(T::Hash, T::BlockNumber)> + fn random(&mut self, subject: &[u8]) -> Result<(E::Hash, E::BlockNumber)> where - T: Environment; + E: Environment; /// Checks whether a specified account belongs to a contract. /// @@ -419,16 +435,16 @@ pub trait TypedEnvBackend: EnvBackend { /// /// For more details visit: [`is_contract`][`crate::is_contract`] #[allow(clippy::wrong_self_convention)] - fn is_contract(&mut self, account: &T::AccountId) -> bool + fn is_contract(&mut self, account: &E::AccountId) -> bool where - T: Environment; + E: Environment; /// Checks whether the caller of the current contract is the origin of the whole call stack. /// /// # Note /// /// For more details visit: [`caller_is_origin`][`crate::caller_is_origin`] - fn caller_is_origin(&mut self) -> bool + fn caller_is_origin(&mut self) -> bool where - T: Environment; + E: Environment; } diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index 3bef7651301..0afb7b9aed5 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -20,71 +20,87 @@ use crate::{ ReturnType, Set, Unset, - Unwrap, }, ExecutionInput, }, + types::Gas, + Clear, Environment, Error, }; use core::marker::PhantomData; +use num_traits::Zero; /// The final parameters to the cross-contract call. #[derive(Debug)] -pub struct CallParams +pub struct CallParams where E: Environment, { - /// The account ID of the to-be-called smart contract. - callee: E::AccountId, + /// A marker to indicate which type of call to perform. + call_type: CallType, /// The flags used to change the behavior of a contract call. call_flags: CallFlags, - /// The maximum gas costs allowed for the call. - gas_limit: u64, - /// The transferred value for the call. - transferred_value: E::Balance, /// The expected return type. _return_type: ReturnType, /// The inputs to the execution which is a selector and encoded arguments. exec_input: ExecutionInput, + /// `Environment` is used by `CallType` for correct types + _phantom: PhantomData E>, } -impl CallParams +impl CallParams where E: Environment, { - /// Returns the account ID of the called contract instance. - #[inline] - pub(crate) fn callee(&self) -> &E::AccountId { - &self.callee - } - /// Returns the call flags. #[inline] pub(crate) fn call_flags(&self) -> &CallFlags { &self.call_flags } + /// Returns the execution input. + #[inline] + pub(crate) fn exec_input(&self) -> &ExecutionInput { + &self.exec_input + } +} + +impl CallParams, Args, R> +where + E: Environment, +{ + /// Returns the account ID of the called contract instance. + #[inline] + pub(crate) fn callee(&self) -> &E::AccountId { + &self.call_type.callee + } + /// Returns the chosen gas limit for the called contract execution. #[inline] - pub(crate) fn gas_limit(&self) -> u64 { - self.gas_limit + pub(crate) fn gas_limit(&self) -> Gas { + self.call_type.gas_limit } /// Returns the transferred value for the called contract. #[inline] pub(crate) fn transferred_value(&self) -> &E::Balance { - &self.transferred_value + &self.call_type.transferred_value } +} - /// Returns the execution input. +impl CallParams, Args, R> +where + E: Environment, +{ + /// Returns the code hash which we use to perform a delegate call. #[inline] - pub(crate) fn exec_input(&self) -> &ExecutionInput { - &self.exec_input + pub(crate) fn code_hash(&self) -> &E::Hash { + &self.call_type.code_hash } } -impl CallParams +impl CallParams, Args, R> where E: Environment, Args: scale::Encode, @@ -98,6 +114,21 @@ where } } +impl CallParams, Args, R> +where + E: Environment, + Args: scale::Encode, + R: scale::Decode, +{ + /// Invokes the contract via delegated call with the given + /// built-up call parameters. + /// + /// Returns the result of the contract execution. + pub fn invoke(&self) -> Result { + crate::invoke_contract_delegate(self) + } +} + /// Returns a new [`CallBuilder`] to build up the parameters to a cross-contract call. /// /// # Example @@ -125,14 +156,14 @@ where /// # DefaultEnvironment, /// # call::{build_call, Selector, ExecutionInput} /// # }; +/// # use ink_env::call::Call; /// # type AccountId = ::AccountId; +/// # type Balance = ::Balance; /// build_call::() -/// .callee(AccountId::from([0x42; 32])) -/// .gas_limit(5000) -/// .transferred_value(10) +/// .set_call_type(Call::new().callee(AccountId::from([0x42; 32])).gas_limit(5000).transferred_value(10)) /// .exec_input( /// ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF])) -/// .push_arg(42) +/// .push_arg(42u8) /// .push_arg(true) /// .push_arg(&[0x10u8; 32]) /// ) @@ -158,18 +189,47 @@ where /// # use ::ink_env::{ /// # Environment, /// # DefaultEnvironment, -/// # call::{build_call, Selector, ExecutionInput}, +/// # call::{build_call, Selector, ExecutionInput, Call}, +/// # }; +/// # type AccountId = ::AccountId; +/// let my_return_value: i32 = build_call::() +/// .set_call_type(Call::new() +/// .callee(AccountId::from([0x42; 32])) +/// .gas_limit(5000) +/// .transferred_value(10)) +/// .exec_input( +/// ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF])) +/// .push_arg(42u8) +/// .push_arg(true) +/// .push_arg(&[0x10u8; 32]) +/// ) +/// .returns::() +/// .fire() +/// .unwrap(); +/// ``` +/// +/// ## Example 3: Delegate call +/// +/// **Note:** The shown example panics because there is currently no delegate calling +/// support in the off-chain testing environment. However, this code +/// should work fine in on-chain environments. +/// +/// ```should_panic +/// # use ::ink_env::{ +/// # Environment, +/// # DefaultEnvironment, +/// # Clear, +/// # call::{build_call, Selector, ExecutionInput, utils::ReturnType, DelegateCall}, /// # }; /// # type AccountId = ::AccountId; /// let my_return_value: i32 = build_call::() -/// .callee(AccountId::from([0x42; 32])) -/// .gas_limit(5000) -/// .transferred_value(10) +/// .set_call_type(DelegateCall::new() +/// .code_hash(::Hash::clear())) /// .exec_input( /// ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF])) -/// .push_arg(42) +/// .push_arg(42u8) /// .push_arg(true) -/// .push_arg(&[0x10; 32]) +/// .push_arg(&[0x10u8; 32]) /// ) /// .returns::() /// .fire() @@ -178,9 +238,7 @@ where #[allow(clippy::type_complexity)] pub fn build_call() -> CallBuilder< E, - Unset, - Unset, - Unset, + Unset>, Unset>, Unset>, > @@ -188,57 +246,133 @@ where E: Environment, { CallBuilder { - env: Default::default(), - callee: Default::default(), + call_type: Default::default(), call_flags: Default::default(), - gas_limit: Default::default(), - transferred_value: Default::default(), exec_input: Default::default(), return_type: Default::default(), + _phantom: Default::default(), + } +} + +/// The default call type for cross-contract calls. Performs a cross-contract call to `callee` +/// with gas limit `gas_limit`, transferring `transferred_value` of currency. +pub struct Call { + callee: E::AccountId, + gas_limit: Gas, + transferred_value: E::Balance, +} + +impl Default for Call { + fn default() -> Self { + Call { + callee: Default::default(), + gas_limit: Default::default(), + transferred_value: E::Balance::zero(), + } + } +} + +impl Call { + /// Returns a clean builder for [`Call`]. + pub fn new() -> Self { + Default::default() + } +} + +impl Call +where + E: Environment, +{ + /// Sets the `callee` for the current cross-contract call. + pub fn callee(self, callee: E::AccountId) -> Self { + Call { + callee, + gas_limit: self.gas_limit, + transferred_value: self.transferred_value, + } + } + + /// Sets the `gas_limit` for the current cross-contract call. + pub fn gas_limit(self, gas_limit: Gas) -> Self { + Call { + callee: self.callee, + gas_limit, + transferred_value: self.transferred_value, + } + } + + /// Sets the `transferred_value` for the current cross-contract call. + pub fn transferred_value(self, transferred_value: E::Balance) -> Self { + Call { + callee: self.callee, + gas_limit: self.gas_limit, + transferred_value, + } + } +} + +/// The `delegatecall` call type. Performs a call with the given code hash. +pub struct DelegateCall { + code_hash: E::Hash, +} + +impl DelegateCall { + /// Returns a clean builder for [`DelegateCall`] + pub fn new() -> Self { + Default::default() + } +} + +impl Default for DelegateCall { + fn default() -> Self { + DelegateCall { + code_hash: E::Hash::clear(), + } + } +} + +impl DelegateCall { + /// Sets the `code_hash` to perform a delegate call with. + pub fn code_hash(self, code_hash: E::Hash) -> Self { + DelegateCall { code_hash } } } /// Builds up a cross contract call. -pub struct CallBuilder +pub struct CallBuilder where E: Environment, { - env: PhantomData E>, /// The current parameters that have been built up so far. - callee: Callee, + call_type: CallType, call_flags: CallFlags, - gas_limit: GasLimit, - transferred_value: TransferredValue, exec_input: Args, return_type: RetType, + _phantom: PhantomData E>, } -impl - CallBuilder, GasLimit, TransferredValue, Args, RetType> +impl CallBuilder, Args, RetType> where E: Environment, { - /// Sets the called smart contract instance account ID to the given value. + /// The type of the call. #[inline] - pub fn callee( + #[must_use] + pub fn set_call_type( self, - callee: E::AccountId, - ) -> CallBuilder, GasLimit, TransferredValue, Args, RetType> - { + call_type: NewCallType, + ) -> CallBuilder, Args, RetType> { CallBuilder { - env: Default::default(), - callee: Set(callee), + call_type: Set(call_type), call_flags: self.call_flags, - gas_limit: self.gas_limit, - transferred_value: self.transferred_value, exec_input: self.exec_input, return_type: self.return_type, + _phantom: Default::default(), } } } -impl - CallBuilder +impl CallBuilder where E: Environment, { @@ -248,200 +382,146 @@ where pub fn call_flags( self, call_flags: CallFlags, - ) -> CallBuilder { + ) -> CallBuilder { CallBuilder { - env: Default::default(), - callee: self.callee, + call_type: self.call_type, call_flags, - gas_limit: self.gas_limit, - transferred_value: self.transferred_value, exec_input: self.exec_input, return_type: self.return_type, + _phantom: Default::default(), } } } -impl - CallBuilder, TransferredValue, Args, RetType> +impl CallBuilder>> where E: Environment, { - /// Sets the maximum allowed gas costs for the call. + /// Sets the type of the returned value upon the execution of the call. + /// + /// # Note + /// + /// Either use `.returns::<()>` to signal that the call does not return a value + /// or use `.returns::` to signal that the call returns a value of type `T`. #[inline] - pub fn gas_limit( - self, - gas_limit: u64, - ) -> CallBuilder, TransferredValue, Args, RetType> { + pub fn returns(self) -> CallBuilder>> { CallBuilder { - env: Default::default(), - callee: self.callee, + call_type: self.call_type, call_flags: self.call_flags, - gas_limit: Set(gas_limit), - transferred_value: self.transferred_value, exec_input: self.exec_input, - return_type: self.return_type, + return_type: Set(Default::default()), + _phantom: Default::default(), } } } -impl - CallBuilder, Args, RetType> +impl + CallBuilder>, RetType> where E: Environment, { - /// Sets the value transferred upon the execution of the call. - #[inline] - pub fn transferred_value( + /// Sets the execution input to the given value. + pub fn exec_input( self, - transferred_value: E::Balance, - ) -> CallBuilder, Args, RetType> { + exec_input: ExecutionInput, + ) -> CallBuilder>, RetType> { CallBuilder { - env: Default::default(), - callee: self.callee, + call_type: self.call_type, call_flags: self.call_flags, - gas_limit: self.gas_limit, - transferred_value: Set(transferred_value), - exec_input: self.exec_input, + exec_input: Set(exec_input), return_type: self.return_type, + _phantom: Default::default(), } } } -impl - CallBuilder>> +impl + CallBuilder>, Set>, Set>> where E: Environment, { - /// Sets the type of the returned value upon the execution of the call. - /// - /// # Note - /// - /// Either use `.returns::<()>` to signal that the call does not return a value - /// or use `.returns::` to signal that the call returns a value of type `T`. - #[inline] - pub fn returns( - self, - ) -> CallBuilder>> - { - CallBuilder { - env: Default::default(), - callee: self.callee, + /// Finalizes the call builder to call a function. + pub fn params(self) -> CallParams, Args, RetType> { + CallParams { + call_type: self.call_type.value(), call_flags: self.call_flags, - gas_limit: self.gas_limit, - transferred_value: self.transferred_value, - exec_input: self.exec_input, - return_type: Set(Default::default()), + _return_type: Default::default(), + exec_input: self.exec_input.value(), + _phantom: self._phantom, } } } -impl +impl CallBuilder< E, - Callee, - GasLimit, - TransferredValue, - Unset>, - RetType, + Set>, + Set>, + Set>, > where E: Environment, { - /// Sets the execution input to the given value. - pub fn exec_input( - self, - exec_input: ExecutionInput, - ) -> CallBuilder< - E, - Callee, - GasLimit, - TransferredValue, - Set>, - RetType, - > { - CallBuilder { - env: Default::default(), - callee: self.callee, + /// Finalizes the call builder to call a function. + pub fn params(self) -> CallParams, Args, RetType> { + CallParams { + call_type: self.call_type.value(), call_flags: self.call_flags, - gas_limit: self.gas_limit, - transferred_value: self.transferred_value, - exec_input: Set(exec_input), - return_type: self.return_type, + _return_type: Default::default(), + exec_input: self.exec_input.value(), + _phantom: self._phantom, } } } -impl - CallBuilder< - E, - Set, - GasLimit, - TransferredValue, - Set>, - Set>, - > +impl + CallBuilder>, Unset>, Unset> where E: Environment, - GasLimit: Unwrap, - TransferredValue: Unwrap, { /// Finalizes the call builder to call a function. - pub fn params(self) -> CallParams { + pub fn params(self) -> CallParams, EmptyArgumentList, ()> { CallParams { - callee: self.callee.value(), + call_type: self.call_type.value(), call_flags: self.call_flags, - gas_limit: self.gas_limit.unwrap_or_else(|| 0), - transferred_value: self - .transferred_value - .unwrap_or_else(|| E::Balance::from(0u32)), _return_type: Default::default(), - exec_input: self.exec_input.value(), + exec_input: Default::default(), + _phantom: self._phantom, } } } -impl +impl CallBuilder< E, - Set, - GasLimit, - TransferredValue, + Set>, Unset>, Unset, > where E: Environment, - GasLimit: Unwrap, - TransferredValue: Unwrap, { /// Finalizes the call builder to call a function. - pub fn params(self) -> CallParams { + pub fn params(self) -> CallParams, EmptyArgumentList, ()> { CallParams { - callee: self.callee.value(), + call_type: self.call_type.value(), call_flags: self.call_flags, - gas_limit: self.gas_limit.unwrap_or_else(|| 0), - transferred_value: self - .transferred_value - .unwrap_or_else(|| E::Balance::from(0u32)), _return_type: Default::default(), exec_input: Default::default(), + _phantom: self._phantom, } } } -impl +impl CallBuilder< E, - Set, - GasLimit, - TransferredValue, + Set>, Unset>, Unset>, > where E: Environment, - GasLimit: Unwrap, - TransferredValue: Unwrap, { /// Invokes the cross-chain function call. pub fn fire(self) -> Result<(), Error> { @@ -449,21 +529,41 @@ where } } -impl +impl CallBuilder< E, - Set, - GasLimit, - TransferredValue, - Set>, - Set>, + Set>, + Unset>, + Unset>, > where E: Environment, - GasLimit: Unwrap, +{ + /// Invokes the cross-chain function call. + pub fn fire(self) -> Result<(), Error> { + self.params().invoke() + } +} + +impl + CallBuilder>, Set>, Set>> +where + E: Environment, + Args: scale::Encode, + R: scale::Decode, +{ + /// Invokes the cross-chain function call and returns the result. + pub fn fire(self) -> Result { + self.params().invoke() + } +} + +impl + CallBuilder>, Set>, Set>> +where + E: Environment, Args: scale::Encode, R: scale::Decode, - TransferredValue: Unwrap, { /// Invokes the cross-chain function call and returns the result. pub fn fire(self) -> Result { diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs index 9fa95997f21..b5df8e24c54 100644 --- a/crates/env/src/call/create_builder.rs +++ b/crates/env/src/call/create_builder.rs @@ -130,13 +130,13 @@ pub struct CreateBuilder where E: Environment, { - env: PhantomData E>, code_hash: CodeHash, gas_limit: GasLimit, endowment: Endowment, exec_input: Args, salt: Salt, return_type: ReturnType, + _phantom: PhantomData E>, } /// Returns a new [`CreateBuilder`] to build up the parameters to a cross-contract instantiation. @@ -202,13 +202,13 @@ where R: FromAccountId, { CreateBuilder { - env: Default::default(), code_hash: Default::default(), gas_limit: Default::default(), endowment: Default::default(), exec_input: Default::default(), salt: Default::default(), return_type: Default::default(), + _phantom: Default::default(), } } @@ -224,13 +224,13 @@ where code_hash: E::Hash, ) -> CreateBuilder, GasLimit, Endowment, Args, Salt, R> { CreateBuilder { - env: Default::default(), code_hash: Set(code_hash), gas_limit: self.gas_limit, endowment: self.endowment, exec_input: self.exec_input, salt: self.salt, return_type: self.return_type, + _phantom: Default::default(), } } } @@ -247,13 +247,13 @@ where gas_limit: u64, ) -> CreateBuilder, Endowment, Args, Salt, R> { CreateBuilder { - env: Default::default(), code_hash: self.code_hash, gas_limit: Set(gas_limit), endowment: self.endowment, exec_input: self.exec_input, salt: self.salt, return_type: self.return_type, + _phantom: Default::default(), } } } @@ -270,13 +270,13 @@ where endowment: E::Balance, ) -> CreateBuilder, Args, Salt, R> { CreateBuilder { - env: Default::default(), code_hash: self.code_hash, gas_limit: self.gas_limit, endowment: Set(endowment), exec_input: self.exec_input, salt: self.salt, return_type: self.return_type, + _phantom: Default::default(), } } } @@ -302,13 +302,13 @@ where ) -> CreateBuilder>, Salt, R> { CreateBuilder { - env: Default::default(), code_hash: self.code_hash, gas_limit: self.gas_limit, endowment: self.endowment, exec_input: Set(exec_input), salt: self.salt, return_type: self.return_type, + _phantom: Default::default(), } } } @@ -328,13 +328,13 @@ where Salt: AsRef<[u8]>, { CreateBuilder { - env: Default::default(), code_hash: self.code_hash, gas_limit: self.gas_limit, endowment: self.endowment, exec_input: self.exec_input, salt: Set(salt), return_type: self.return_type, + _phantom: Default::default(), } } } diff --git a/crates/env/src/call/mod.rs b/crates/env/src/call/mod.rs index a4c6fa653f8..ac4ba6cc4bb 100644 --- a/crates/env/src/call/mod.rs +++ b/crates/env/src/call/mod.rs @@ -42,8 +42,10 @@ pub mod utils { pub use self::{ call_builder::{ build_call, + Call, CallBuilder, CallParams, + DelegateCall, }, create_builder::{ build_create, diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index d8a98edb6a5..7c302b16d21 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -15,8 +15,10 @@ use super::EnvInstance; use crate::{ call::{ + Call, CallParams, CreateParams, + DelegateCall, }, hash::{ Blake2x128, @@ -319,79 +321,79 @@ impl EnvBackend for EnvInstance { } impl TypedEnvBackend for EnvInstance { - fn caller(&mut self) -> T::AccountId { - self.get_property::(Engine::caller) + fn caller(&mut self) -> E::AccountId { + self.get_property::(Engine::caller) .unwrap_or_else(|error| { panic!("could not read `caller` property: {:?}", error) }) } - fn transferred_value(&mut self) -> T::Balance { - self.get_property::(Engine::value_transferred) + fn transferred_value(&mut self) -> E::Balance { + self.get_property::(Engine::value_transferred) .unwrap_or_else(|error| { panic!("could not read `transferred_value` property: {:?}", error) }) } - fn gas_left(&mut self) -> u64 { + fn gas_left(&mut self) -> u64 { self.get_property::(Engine::gas_left) .unwrap_or_else(|error| { panic!("could not read `gas_left` property: {:?}", error) }) } - fn block_timestamp(&mut self) -> T::Timestamp { - self.get_property::(Engine::block_timestamp) + fn block_timestamp(&mut self) -> E::Timestamp { + self.get_property::(Engine::block_timestamp) .unwrap_or_else(|error| { panic!("could not read `block_timestamp` property: {:?}", error) }) } - fn account_id(&mut self) -> T::AccountId { - self.get_property::(Engine::address) + fn account_id(&mut self) -> E::AccountId { + self.get_property::(Engine::address) .unwrap_or_else(|error| { panic!("could not read `account_id` property: {:?}", error) }) } - fn balance(&mut self) -> T::Balance { - self.get_property::(Engine::balance) + fn balance(&mut self) -> E::Balance { + self.get_property::(Engine::balance) .unwrap_or_else(|error| { panic!("could not read `balance` property: {:?}", error) }) } - fn block_number(&mut self) -> T::BlockNumber { - self.get_property::(Engine::block_number) + fn block_number(&mut self) -> E::BlockNumber { + self.get_property::(Engine::block_number) .unwrap_or_else(|error| { panic!("could not read `block_number` property: {:?}", error) }) } - fn minimum_balance(&mut self) -> T::Balance { - self.get_property::(Engine::minimum_balance) + fn minimum_balance(&mut self) -> E::Balance { + self.get_property::(Engine::minimum_balance) .unwrap_or_else(|error| { panic!("could not read `minimum_balance` property: {:?}", error) }) } - fn emit_event(&mut self, event: Event) + fn emit_event(&mut self, event: Event) where - T: Environment, + E: Environment, Event: Topics + scale::Encode, { let builder = TopicsBuilder::default(); - let enc_topics = event.topics::(builder.into()); + let enc_topics = event.topics::(builder.into()); let enc_data = &scale::Encode::encode(&event)[..]; self.engine.deposit_event(&enc_topics[..], enc_data); } - fn invoke_contract( + fn invoke_contract( &mut self, - params: &CallParams, + params: &CallParams, Args, R>, ) -> Result where - T: Environment, + E: Environment, Args: scale::Encode, R: scale::Decode, { @@ -403,12 +405,27 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("off-chain environment does not support contract invocation") } - fn instantiate_contract( + fn invoke_contract_delegate( &mut self, - params: &CreateParams, - ) -> Result + params: &CallParams, Args, R>, + ) -> Result + where + E: Environment, + Args: scale::Encode, + R: scale::Decode, + { + let _code_hash = params.code_hash(); + unimplemented!( + "off-chain environment does not support delegated contract invocation" + ) + } + + fn instantiate_contract( + &mut self, + params: &CreateParams, + ) -> Result where - T: Environment, + E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>, { @@ -420,17 +437,17 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("off-chain environment does not support contract instantiation") } - fn terminate_contract(&mut self, beneficiary: T::AccountId) -> ! + fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! where - T: Environment, + E: Environment, { let buffer = scale::Encode::encode(&beneficiary); self.engine.terminate(&buffer[..]) } - fn transfer(&mut self, destination: T::AccountId, value: T::Balance) -> Result<()> + fn transfer(&mut self, destination: E::AccountId, value: E::Balance) -> Result<()> where - T: Environment, + E: Environment, { let enc_destination = &scale::Encode::encode(&destination)[..]; let enc_value = &scale::Encode::encode(&value)[..]; @@ -439,7 +456,7 @@ impl TypedEnvBackend for EnvInstance { .map_err(Into::into) } - fn weight_to_fee(&mut self, gas: u64) -> T::Balance { + fn weight_to_fee(&mut self, gas: u64) -> E::Balance { let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; self.engine.weight_to_fee(gas, &mut &mut output[..]); scale::Decode::decode(&mut &output[..]).unwrap_or_else(|error| { @@ -447,25 +464,25 @@ impl TypedEnvBackend for EnvInstance { }) } - fn random(&mut self, subject: &[u8]) -> Result<(T::Hash, T::BlockNumber)> + fn random(&mut self, subject: &[u8]) -> Result<(E::Hash, E::BlockNumber)> where - T: Environment, + E: Environment, { let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; self.engine.random(subject, &mut &mut output[..]); scale::Decode::decode(&mut &output[..]).map_err(Into::into) } - fn is_contract(&mut self, _account: &T::AccountId) -> bool + fn is_contract(&mut self, _account: &E::AccountId) -> bool where - T: Environment, + E: Environment, { unimplemented!("off-chain environment does not support contract instantiation") } - fn caller_is_origin(&mut self) -> bool + fn caller_is_origin(&mut self) -> bool where - T: Environment, + E: Environment, { unimplemented!("off-chain environment does not support cross-contract calls") } diff --git a/crates/env/src/engine/on_chain/ext.rs b/crates/env/src/engine/on_chain/ext.rs index fca6a3c1b74..1b3766f8466 100644 --- a/crates/env/src/engine/on_chain/ext.rs +++ b/crates/env/src/engine/on_chain/ext.rs @@ -338,6 +338,15 @@ mod sys { message_hash_ptr: Ptr32<[u8]>, output_ptr: Ptr32Mut<[u8]>, ) -> ReturnCode; + + pub fn seal_delegate_call( + flags: u32, + code_hash_ptr: Ptr32<[u8]>, + input_data_ptr: Ptr32<[u8]>, + input_data_len: u32, + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ) -> ReturnCode; } } @@ -407,6 +416,29 @@ pub fn call( ret_code.into() } +pub fn delegate_call( + flags: u32, + code_hash: &[u8], + input: &[u8], + output: &mut &mut [u8], +) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::seal_delegate_call( + flags, + Ptr32::from_slice(code_hash), + Ptr32::from_slice(input), + input.len() as u32, + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() +} + pub fn transfer(account_id: &[u8], value: &[u8]) -> Result { let ret_code = unsafe { sys::seal_transfer( diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 548bf0dab7e..da6a54bba82 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -20,8 +20,10 @@ use super::{ }; use crate::{ call::{ + Call, CallParams, CreateParams, + DelegateCall, }, hash::{ Blake2x128, @@ -313,55 +315,55 @@ impl EnvBackend for EnvInstance { } impl TypedEnvBackend for EnvInstance { - fn caller(&mut self) -> T::AccountId { - self.get_property_inplace::(ext::caller) + fn caller(&mut self) -> E::AccountId { + self.get_property_inplace::(ext::caller) } - fn transferred_value(&mut self) -> T::Balance { - self.get_property_little_endian::(ext::value_transferred) + fn transferred_value(&mut self) -> E::Balance { + self.get_property_little_endian::(ext::value_transferred) } - fn gas_left(&mut self) -> u64 { + fn gas_left(&mut self) -> u64 { self.get_property_little_endian::(ext::gas_left) } - fn block_timestamp(&mut self) -> T::Timestamp { - self.get_property_little_endian::(ext::now) + fn block_timestamp(&mut self) -> E::Timestamp { + self.get_property_little_endian::(ext::now) } - fn account_id(&mut self) -> T::AccountId { - self.get_property_inplace::(ext::address) + fn account_id(&mut self) -> E::AccountId { + self.get_property_inplace::(ext::address) } - fn balance(&mut self) -> T::Balance { - self.get_property_little_endian::(ext::balance) + fn balance(&mut self) -> E::Balance { + self.get_property_little_endian::(ext::balance) } - fn block_number(&mut self) -> T::BlockNumber { - self.get_property_little_endian::(ext::block_number) + fn block_number(&mut self) -> E::BlockNumber { + self.get_property_little_endian::(ext::block_number) } - fn minimum_balance(&mut self) -> T::Balance { - self.get_property_little_endian::(ext::minimum_balance) + fn minimum_balance(&mut self) -> E::Balance { + self.get_property_little_endian::(ext::minimum_balance) } - fn emit_event(&mut self, event: Event) + fn emit_event(&mut self, event: Event) where - T: Environment, + E: Environment, Event: Topics + scale::Encode, { let (mut scope, enc_topics) = - event.topics::(TopicsBuilder::from(self.scoped_buffer()).into()); + event.topics::(TopicsBuilder::from(self.scoped_buffer()).into()); let enc_data = scope.take_encoded(&event); ext::deposit_event(enc_topics, enc_data); } - fn invoke_contract( + fn invoke_contract( &mut self, - params: &CallParams, + params: &CallParams, Args, R>, ) -> Result where - T: Environment, + E: Environment, Args: scale::Encode, R: scale::Decode, { @@ -394,12 +396,41 @@ impl TypedEnvBackend for EnvInstance { } } - fn instantiate_contract( + fn invoke_contract_delegate( &mut self, - params: &CreateParams, - ) -> Result + params: &CallParams, Args, R>, + ) -> Result + where + E: Environment, + Args: scale::Encode, + R: scale::Decode, + { + let mut scope = self.scoped_buffer(); + let call_flags = params.call_flags(); + let enc_code_hash = scope.take_encoded(params.code_hash()); + let enc_input = if !call_flags.forward_input() && !call_flags.clone_input() { + scope.take_encoded(params.exec_input()) + } else { + &mut [] + }; + let output = &mut scope.take_rest(); + let flags = params.call_flags().into_u32(); + let call_result = ext::delegate_call(flags, enc_code_hash, enc_input, output); + match call_result { + Ok(()) | Err(ext::Error::CalleeReverted) => { + let decoded = scale::Decode::decode(&mut &output[..])?; + Ok(decoded) + } + Err(actual_error) => Err(actual_error.into()), + } + } + + fn instantiate_contract( + &mut self, + params: &CreateParams, + ) -> Result where - T: Environment, + E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>, { @@ -431,17 +462,17 @@ impl TypedEnvBackend for EnvInstance { Ok(account_id) } - fn terminate_contract(&mut self, beneficiary: T::AccountId) -> ! + fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! where - T: Environment, + E: Environment, { let buffer = self.scoped_buffer().take_encoded(&beneficiary); ext::terminate(buffer); } - fn transfer(&mut self, destination: T::AccountId, value: T::Balance) -> Result<()> + fn transfer(&mut self, destination: E::AccountId, value: E::Balance) -> Result<()> where - T: Environment, + E: Environment, { let mut scope = self.scoped_buffer(); let enc_destination = scope.take_encoded(&destination); @@ -449,15 +480,15 @@ impl TypedEnvBackend for EnvInstance { ext::transfer(enc_destination, enc_value).map_err(Into::into) } - fn weight_to_fee(&mut self, gas: u64) -> T::Balance { - let mut result = ::Bytes::default(); + fn weight_to_fee(&mut self, gas: u64) -> E::Balance { + let mut result = ::Bytes::default(); ext::weight_to_fee(gas, &mut result.as_mut()); - ::from_le_bytes(result) + ::from_le_bytes(result) } - fn random(&mut self, subject: &[u8]) -> Result<(T::Hash, T::BlockNumber)> + fn random(&mut self, subject: &[u8]) -> Result<(E::Hash, E::BlockNumber)> where - T: Environment, + E: Environment, { let mut scope = self.scoped_buffer(); let enc_subject = scope.take_bytes(subject); @@ -466,18 +497,18 @@ impl TypedEnvBackend for EnvInstance { scale::Decode::decode(&mut &output[..]).map_err(Into::into) } - fn is_contract(&mut self, account_id: &T::AccountId) -> bool + fn is_contract(&mut self, account_id: &E::AccountId) -> bool where - T: Environment, + E: Environment, { let mut scope = self.scoped_buffer(); let enc_account_id = scope.take_encoded(account_id); ext::is_contract(enc_account_id) } - fn caller_is_origin(&mut self) -> bool + fn caller_is_origin(&mut self) -> bool where - T: Environment, + E: Environment, { ext::caller_is_origin() } diff --git a/crates/env/src/types.rs b/crates/env/src/types.rs index 7a628378d08..a655ce18b04 100644 --- a/crates/env/src/types.rs +++ b/crates/env/src/types.rs @@ -192,6 +192,9 @@ 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/lang/codegen/src/generator/as_dependency/call_builder.rs b/crates/lang/codegen/src/generator/as_dependency/call_builder.rs index 757d15ae2f5..2795b819f66 100644 --- a/crates/lang/codegen/src/generator/as_dependency/call_builder.rs +++ b/crates/lang/codegen/src/generator/as_dependency/call_builder.rs @@ -378,9 +378,7 @@ impl CallBuilder<'_> { let output_type = quote_spanned!(output_span=> ::ink_env::call::CallBuilder< Environment, - ::ink_env::call::utils::Set< ::AccountId >, - ::ink_env::call::utils::Unset< ::core::primitive::u64 >, - ::ink_env::call::utils::Unset< ::Balance >, + ::ink_env::call::utils::Set< ::ink_env::call::Call< Environment > >, ::ink_env::call::utils::Set< ::ink_env::call::ExecutionInput<#arg_list> >, ::ink_env::call::utils::Set< ::ink_env::call::utils::ReturnType<#return_type> >, > @@ -394,7 +392,7 @@ impl CallBuilder<'_> { #( , #input_bindings : #input_types )* ) -> #output_type { ::ink_env::call::build_call::() - .callee(::ink_lang::ToAccountId::to_account_id(self)) + .set_call_type(::ink_env::call::Call::new().callee(::ink_lang::ToAccountId::to_account_id(self))) .exec_input( ::ink_env::call::ExecutionInput::new( ::ink_env::call::Selector::new([ #( #selector_bytes ),* ]) diff --git a/crates/lang/codegen/src/generator/trait_def/call_builder.rs b/crates/lang/codegen/src/generator/trait_def/call_builder.rs index 21414335e8b..3da29dd3701 100644 --- a/crates/lang/codegen/src/generator/trait_def/call_builder.rs +++ b/crates/lang/codegen/src/generator/trait_def/call_builder.rs @@ -376,9 +376,7 @@ impl CallBuilder<'_> { #[allow(clippy::type_complexity)] type #output_ident = ::ink_env::call::CallBuilder< Self::Env, - ::ink_env::call::utils::Set< ::AccountId >, - ::ink_env::call::utils::Unset< ::core::primitive::u64 >, - ::ink_env::call::utils::Unset< ::Balance >, + ::ink_env::call::utils::Set< ::ink_env::call::Call< Self::Env > >, ::ink_env::call::utils::Set< ::ink_env::call::ExecutionInput<#arg_list> >, ::ink_env::call::utils::Set< ::ink_env::call::utils::ReturnType<#output_type> >, >; @@ -390,7 +388,7 @@ impl CallBuilder<'_> { #( , #input_bindings : #input_types )* ) -> Self::#output_ident { ::ink_env::call::build_call::() - .callee(::ink_lang::ToAccountId::to_account_id(self)) + .set_call_type(::ink_env::call::Call::new().callee(::ink_lang::ToAccountId::to_account_id(self))) .exec_input( ::ink_env::call::ExecutionInput::new( ::ink_env::call::Selector::new([ #( #selector_bytes ),* ]) diff --git a/crates/lang/src/codegen/implies_return.rs b/crates/lang/src/codegen/implies_return.rs index 3cecec062dc..4cc896a6673 100644 --- a/crates/lang/src/codegen/implies_return.rs +++ b/crates/lang/src/codegen/implies_return.rs @@ -37,29 +37,15 @@ use ink_env::{ pub trait ImpliesReturn {} impl ImpliesReturn for T {} -impl ImpliesReturn - for CallBuilder< - E, - Callee, - GasCost, - TransferredValue, - Set>, - Set>, - > +impl ImpliesReturn + for CallBuilder, Set>, Set>> where E: Environment, { } -impl ImpliesReturn<()> - for CallBuilder< - E, - Callee, - GasCost, - TransferredValue, - Set>, - Set<()>, - > +impl ImpliesReturn<()> + for CallBuilder, Set>, Set<()>> where E: Environment, { diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 572a529795a..8fb6be6a2e7 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -16,8 +16,10 @@ use crate::ChainExtensionInstance; use core::marker::PhantomData; use ink_env::{ call::{ + Call, CallParams, CreateParams, + DelegateCall, }, hash::{ CryptoHash, @@ -34,12 +36,12 @@ use ink_eth_compatibility::ECDSAPublicKey; /// This allows ink! messages to make use of the environment efficiently /// and user friendly while also maintaining access invariants. #[derive(Copy, Clone)] -pub struct EnvAccess<'a, T> { - /// Tricks the Rust compiler into thinking that we use `T`. - marker: PhantomData &'a T>, +pub struct EnvAccess<'a, E> { + /// Tricks the Rust compiler into thinking that we use `E`. + marker: PhantomData &'a E>, } -impl<'a, T> Default for EnvAccess<'a, T> { +impl<'a, E> Default for EnvAccess<'a, E> { #[inline] fn default() -> Self { Self { @@ -54,22 +56,22 @@ impl<'a, E> core::fmt::Debug for EnvAccess<'a, E> { } } -impl<'a, T> EnvAccess<'a, T> +impl<'a, E> EnvAccess<'a, E> where - T: Environment, - ::ChainExtension: ChainExtensionInstance, + E: Environment, + ::ChainExtension: ChainExtensionInstance, { /// Allows to call one of the available defined chain extension methods. pub fn extension( self, - ) -> <::ChainExtension as ChainExtensionInstance>::Instance { - <::ChainExtension as ChainExtensionInstance>::instantiate() + ) -> <::ChainExtension as ChainExtensionInstance>::Instance { + <::ChainExtension as ChainExtensionInstance>::instantiate() } } -impl<'a, T> EnvAccess<'a, T> +impl<'a, E> EnvAccess<'a, E> where - T: Environment, + E: Environment, { /// Returns the address of the caller of the executed contract. /// @@ -102,8 +104,8 @@ where /// # Note /// /// For more details visit: [`ink_env::caller`] - pub fn caller(self) -> T::AccountId { - ink_env::caller::() + pub fn caller(self) -> E::AccountId { + ink_env::caller::() } /// Returns the transferred value for the contract execution. @@ -139,8 +141,8 @@ where /// # Note /// /// For more details visit: [`ink_env::transferred_value`] - pub fn transferred_value(self) -> T::Balance { - ink_env::transferred_value::() + pub fn transferred_value(self) -> E::Balance { + ink_env::transferred_value::() } /// Returns the price for the specified amount of gas. @@ -182,8 +184,8 @@ where /// # Note /// /// For more details visit: [`ink_env::weight_to_fee`] - pub fn weight_to_fee(self, gas: u64) -> T::Balance { - ink_env::weight_to_fee::(gas) + pub fn weight_to_fee(self, gas: u64) -> E::Balance { + ink_env::weight_to_fee::(gas) } /// Returns the amount of gas left for the contract execution. @@ -222,7 +224,7 @@ where /// /// For more details visit: [`ink_env::gas_left`] pub fn gas_left(self) -> u64 { - ink_env::gas_left::() + ink_env::gas_left::() } /// Returns the timestamp of the current block. @@ -263,8 +265,8 @@ where /// defined by the chain environment on which this contract runs. /// /// For more details visit: [`ink_env::block_timestamp`] - pub fn block_timestamp(self) -> T::Timestamp { - ink_env::block_timestamp::() + pub fn block_timestamp(self) -> E::Timestamp { + ink_env::block_timestamp::() } /// Returns the account ID of the executed contract. @@ -309,8 +311,8 @@ where /// # Note /// /// For more details visit: [`ink_env::account_id`] - pub fn account_id(self) -> T::AccountId { - ink_env::account_id::() + pub fn account_id(self) -> E::AccountId { + ink_env::account_id::() } /// Returns the balance of the executed contract. @@ -343,8 +345,8 @@ where /// # Note /// /// For more details visit: [`ink_env::balance`] - pub fn balance(self) -> T::Balance { - ink_env::balance::() + pub fn balance(self) -> E::Balance { + ink_env::balance::() } /// Returns the current block number. @@ -383,8 +385,8 @@ where /// # Note /// /// For more details visit: [`ink_env::block_number`] - pub fn block_number(self) -> T::BlockNumber { - ink_env::block_number::() + pub fn block_number(self) -> E::BlockNumber { + ink_env::block_number::() } /// Returns the minimum balance that is required for creating an account @@ -417,8 +419,8 @@ where /// # Note /// /// For more details visit: [`ink_env::minimum_balance`] - pub fn minimum_balance(self) -> T::Balance { - ink_env::minimum_balance::() + pub fn minimum_balance(self) -> E::Balance { + ink_env::minimum_balance::() } /// Instantiates another contract. @@ -494,13 +496,13 @@ where /// For more details visit: [`ink_env::instantiate_contract`] pub fn instantiate_contract( self, - params: &CreateParams, - ) -> Result + params: &CreateParams, + ) -> Result where Args: scale::Encode, Salt: AsRef<[u8]>, { - ink_env::instantiate_contract::(params) + ink_env::instantiate_contract::(params) } /// Invokes a contract message and returns its result. @@ -513,7 +515,7 @@ where /// # pub mod my_contract { /// use ink_env::{ /// DefaultEnvironment, - /// call::{build_call, Selector, ExecutionInput} + /// call::{build_call, Call, Selector, ExecutionInput} /// }; /// /// # @@ -530,18 +532,80 @@ where /// #[ink(message)] /// pub fn invoke_contract(&self) -> i32 { /// let call_params = build_call::() - /// .callee(AccountId::from([0x42; 32])) - /// .gas_limit(5000) - /// .transferred_value(10) - /// .exec_input( - /// ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE])) - /// .push_arg(42) - /// .push_arg(true) - /// .push_arg(&[0x10u8; 32]) + /// .set_call_type( + /// Call::new() + /// .callee(AccountId::from([0x42; 32])) + /// .gas_limit(5000) + /// .transferred_value(10)) + /// .exec_input( + /// ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE])) + /// .push_arg(42u8) + /// .push_arg(true) + /// .push_arg(&[0x10u8; 32]) + /// ) + /// .returns::() + /// .params(); + /// self.env().invoke_contract(&call_params).expect("call invocation must succeed") + /// } + /// # + /// # } + /// # } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::invoke_contract`] + pub fn invoke_contract( + self, + params: &CallParams, Args, R>, + ) -> Result + where + Args: scale::Encode, + R: scale::Decode, + { + ink_env::invoke_contract::(params) + } + + /// Invokes in delegate manner a code message and returns its result. + /// + /// # Example + /// + /// ``` + /// # use ink_lang as ink; + /// # #[ink::contract] + /// # pub mod my_contract { + /// use ink_env::{ + /// DefaultEnvironment, + /// Clear, + /// call::{build_call, DelegateCall, Selector, ExecutionInput, utils::ReturnType} + /// }; + /// + /// # + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn new() -> Self { + /// # Self {} + /// # } + /// # + /// /// Invokes in delegate manner a contract message and fetches the result. + /// #[ink(message)] + /// pub fn invoke_contract_delegate(&self) -> i32 { + /// let call_params = build_call::() + /// .set_call_type( + /// DelegateCall::new() + /// .code_hash(::Hash::clear())) + /// .exec_input( + /// ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE])) + /// .push_arg(42u8) + /// .push_arg(true) + /// .push_arg(&[0x10u8; 32]) /// ) /// .returns::() /// .params(); - /// self.env().invoke_contract(&call_params).expect("call invocation must succeed") + /// self.env().invoke_contract_delegate(&call_params).expect("call delegate invocation must succeed") /// } /// # /// # } @@ -550,13 +614,16 @@ where /// /// # Note /// - /// For more details visit: [`ink_env::invoke_contract`] - pub fn invoke_contract(self, params: &CallParams) -> Result + /// For more details visit: [`ink_env::invoke_contract_delegate`] + pub fn invoke_contract_delegate( + self, + params: &CallParams, Args, R>, + ) -> Result where Args: scale::Encode, R: scale::Decode, { - ink_env::invoke_contract::(params) + ink_env::invoke_contract_delegate::(params) } /// Terminates the existence of a contract. @@ -589,8 +656,8 @@ where /// # Note /// /// For more details visit: [`ink_env::terminate_contract`] - pub fn terminate_contract(self, beneficiary: T::AccountId) -> ! { - ink_env::terminate_contract::(beneficiary) + pub fn terminate_contract(self, beneficiary: E::AccountId) -> ! { + ink_env::terminate_contract::(beneficiary) } /// Transfers value from the contract to the destination account ID. @@ -624,8 +691,8 @@ where /// # Note /// /// For more details visit: [`ink_env::transfer`] - pub fn transfer(self, destination: T::AccountId, value: T::Balance) -> Result<()> { - ink_env::transfer::(destination, value) + pub fn transfer(self, destination: E::AccountId, value: E::Balance) -> Result<()> { + ink_env::transfer::(destination, value) } /// Returns a random hash seed. @@ -659,8 +726,8 @@ where /// # Note /// /// For more details visit: [`ink_env::random`] - pub fn random(self, subject: &[u8]) -> (T::Hash, T::BlockNumber) { - ink_env::random::(subject).expect("couldn't decode randomized hash") + pub fn random(self, subject: &[u8]) -> (E::Hash, E::BlockNumber) { + ink_env::random::(subject).expect("couldn't decode randomized hash") } /// Computes the hash of the given bytes using the cryptographic hash `H`. @@ -809,8 +876,8 @@ where /// # Note /// /// For more details visit: [`ink_env::is_contract`] - pub fn is_contract(self, account_id: &T::AccountId) -> bool { - ink_env::is_contract::(account_id) + pub fn is_contract(self, account_id: &E::AccountId) -> bool { + ink_env::is_contract::(account_id) } /// Checks whether the caller of the current contract is the origin of the whole call stack. @@ -842,6 +909,6 @@ where /// /// For more details visit: [`ink_env::caller_is_origin`] pub fn caller_is_origin(self) -> bool { - ink_env::caller_is_origin::() + ink_env::caller_is_origin::() } } diff --git a/crates/lang/tests/ui/chain_extension/E-01-simple.rs b/crates/lang/tests/ui/chain_extension/E-01-simple.rs index 96bbfef087c..3355f43049f 100644 --- a/crates/lang/tests/ui/chain_extension/E-01-simple.rs +++ b/crates/lang/tests/ui/chain_extension/E-01-simple.rs @@ -123,7 +123,12 @@ impl Environment for CustomEnvironment { #[ink::contract(env = crate::CustomEnvironment)] mod read_writer { - use super::{Access, ReadWriteErrorCode, ReadWriteError, UnlockAccessError}; + use super::{ + Access, + ReadWriteError, + ReadWriteErrorCode, + UnlockAccessError, + }; #[ink(storage)] pub struct ReadWriter {} @@ -136,16 +141,15 @@ mod read_writer { #[ink(message)] pub fn read(&self, key: Vec) -> Result, ReadWriteErrorCode> { - self.env() - .extension() - .read(&key) + self.env().extension().read(&key) } #[ink(message)] - pub fn read_small(&self, key: Vec) -> Result<(u32, [u8; 32]), ReadWriteError> { - self.env() - .extension() - .read_small(&key) + pub fn read_small( + &self, + key: Vec, + ) -> Result<(u32, [u8; 32]), ReadWriteError> { + self.env().extension().read_small(&key) } #[ink(message)] @@ -154,23 +158,21 @@ mod read_writer { key: Vec, value: Vec, ) -> Result<(), ReadWriteErrorCode> { - self.env() - .extension() - .write(&key, &value) + self.env().extension().write(&key, &value) } #[ink(message)] pub fn access(&self, key: Vec) -> Option { - self.env() - .extension() - .access(&key) + self.env().extension().access(&key) } #[ink(message)] - pub fn unlock_access(&self, key: Vec, access: Access) -> Result<(), UnlockAccessError> { - self.env() - .extension() - .unlock_access(&key, access) + pub fn unlock_access( + &self, + key: Vec, + access: Access, + ) -> Result<(), UnlockAccessError> { + self.env().extension().unlock_access(&key, access) } } } diff --git a/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr index 853f789bf6d..6d058c8a6cb 100644 --- a/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr @@ -32,11 +32,11 @@ note: required by a bound in `ExecutionInput::>::push_arg` -error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder, Unset, Unset, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-input-non-codec.rs:18:9 | 18 | pub fn message(&self, _input: NonCodecType) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called on `ink_env::call::CallBuilder, Unset, Unset, Set, ArgumentList>>>, Set>>` due to unsatisfied trait bounds + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called on `ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>` due to unsatisfied trait bounds | ::: $WORKSPACE/crates/env/src/call/execution_input.rs | diff --git a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr index bbaedf2a244..c8ff440440e 100644 --- a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -26,7 +26,7 @@ note: required by a bound in `finalize_message` | R: scale::Encode + 'static, | ^^^^^^^^^^^^^ required by this bound in `finalize_message` -error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder, Unset, Unset, Set>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-returns-non-codec.rs:18:9 | 6 | pub struct NonCodecType; @@ -35,7 +35,7 @@ error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder NonCodecType { 19 | | NonCodecType 20 | | } - | |_________^ method cannot be called on `ink_env::call::CallBuilder, Unset, Unset, Set>>, Set>>` due to unsatisfied trait bounds + | |_________^ method cannot be called on `ink_env::call::CallBuilder>, Set>>, Set>>` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: `NonCodecType: parity_scale_codec::Decode` diff --git a/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr b/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr index 75e38274408..7b518f9f302 100644 --- a/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr +++ b/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr @@ -25,12 +25,12 @@ note: required by a bound in `ExecutionInput::>::push_arg` -error[E0599]: the method `fire` exists for struct `CallBuilder::AccountId>, Unset, Unset<::Balance>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `fire` exists for struct `CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/trait_def/fail/message_input_non_codec.rs:7:5 | 7 | / #[ink(message)] 8 | | fn message(&self, input: NonCodec); - | |_______________________________________^ method cannot be called on `CallBuilder::AccountId>, Unset, Unset<::Balance>, Set, ArgumentList>>>, Set>>` due to unsatisfied trait bounds + | |_______________________________________^ method cannot be called on `CallBuilder>, Set, ArgumentList>>>, Set>>` due to unsatisfied trait bounds | ::: $WORKSPACE/crates/env/src/call/execution_input.rs | diff --git a/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr index 102a12c5b8f..00e3062261d 100644 --- a/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -11,7 +11,7 @@ note: required by a bound in `DispatchOutput` | T: scale::Encode + 'static; | ^^^^^^^^^^^^^ required by this bound in `DispatchOutput` -error[E0599]: the method `fire` exists for struct `CallBuilder::AccountId>, Unset, Unset<::Balance>, Set>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `fire` exists for struct `CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/trait_def/fail/message_output_non_codec.rs:7:5 | 3 | pub struct NonCodec; @@ -19,7 +19,7 @@ error[E0599]: the method `fire` exists for struct `CallBuilder NonCodec; - | |__________________________________^ method cannot be called on `CallBuilder::AccountId>, Unset, Unset<::Balance>, Set>>, Set>>` due to unsatisfied trait bounds + | |__________________________________^ method cannot be called on `CallBuilder>, Set>>, Set>>` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: `NonCodec: parity_scale_codec::Decode` diff --git a/examples/erc1155/lib.rs b/examples/erc1155/lib.rs index 7060f57e9e5..b539de8e388 100644 --- a/examples/erc1155/lib.rs +++ b/examples/erc1155/lib.rs @@ -387,6 +387,7 @@ mod erc1155 { { use ink_env::call::{ build_call, + Call, ExecutionInput, Selector, }; @@ -394,8 +395,7 @@ mod erc1155 { // If our recipient is a smart contract we need to see if they accept or // reject this transfer. If they reject it we need to revert the call. let params = build_call::() - .callee(to) - .gas_limit(5000) + .set_call_type(Call::new().callee(to).gas_limit(5000)) .exec_input( ExecutionInput::new(Selector::new(ON_ERC_1155_RECEIVED_SELECTOR)) .push_arg(caller) diff --git a/examples/multisig/lib.rs b/examples/multisig/lib.rs index e6e643e8d9b..dacade479c2 100755 --- a/examples/multisig/lib.rs +++ b/examples/multisig/lib.rs @@ -66,6 +66,7 @@ use ink_lang as ink; mod multisig { use ink_env::call::{ build_call, + Call, ExecutionInput, }; use ink_prelude::vec::Vec; @@ -515,9 +516,12 @@ mod multisig { let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); assert!(self.env().transferred_value() == t.transferred_value); let result = build_call::<::Env>() - .callee(t.callee) - .gas_limit(t.gas_limit) - .transferred_value(t.transferred_value) + .set_call_type( + Call::new() + .callee(t.callee) + .gas_limit(t.gas_limit) + .transferred_value(t.transferred_value), + ) .exec_input( ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), ) @@ -544,9 +548,12 @@ mod multisig { self.ensure_confirmed(trans_id); let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); let result = build_call::<::Env>() - .callee(t.callee) - .gas_limit(t.gas_limit) - .transferred_value(t.transferred_value) + .set_call_type( + Call::new() + .callee(t.callee) + .gas_limit(t.gas_limit) + .transferred_value(t.transferred_value), + ) .exec_input( ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), ) diff --git a/examples/proxy/README.md b/examples/proxy/README.md index 1aaba8b1caf..402b1f42773 100644 --- a/examples/proxy/README.md +++ b/examples/proxy/README.md @@ -6,7 +6,7 @@ selector of itself to another, specified contract. The instantiator of the proxy contract on a blockchain can change the address to which calls are forwarded. -This allows building upgradable contracts following the proxy pattern. +This allows building upgradeable contracts following the proxy pattern. Note though that the state is still stored in the contract to which calls are forwarded. diff --git a/examples/proxy/lib.rs b/examples/proxy/lib.rs index 1224e54e119..34917ab5fe8 100644 --- a/examples/proxy/lib.rs +++ b/examples/proxy/lib.rs @@ -8,7 +8,7 @@ //! * The instantiator of the contract can modify this specified //! `forward_to` address at any point. //! -//! Using this pattern it is possible to implement upgradable contracts. +//! Using this pattern it is possible to implement upgradeable contracts. //! //! Note though that the contract to which calls are forwarded still //! contains it's own state. @@ -19,6 +19,8 @@ use ink_lang as ink; #[ink::contract] pub mod proxy { + use ink_env::call::Call; + /// A simple proxy contract. #[ink(storage)] pub struct Proxy { @@ -70,13 +72,17 @@ pub mod proxy { #[ink(message, payable, selector = _)] pub fn forward(&self) -> u32 { ink_env::call::build_call::() - .callee(self.forward_to) + .set_call_type( + Call::new() + .callee(self.forward_to) + .transferred_value(self.env().transferred_value()) + .gas_limit(0), + ) .call_flags( ink_env::CallFlags::default() .set_forward_input(true) .set_tail_call(true), ) - .transferred_value(self.env().transferred_value()) .fire() .unwrap_or_else(|err| { panic!( diff --git a/examples/upgradeable-contract/.gitignore b/examples/upgradeable-contract/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/examples/upgradeable-contract/.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/examples/upgradeable-contract/Cargo.toml b/examples/upgradeable-contract/Cargo.toml new file mode 100644 index 00000000000..f4e871c076c --- /dev/null +++ b/examples/upgradeable-contract/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "upgradeable_contract" +version = "3.0.0-rc9" +authors = ["Parity Technologies "] +edition = "2021" + +[dependencies] +ink_primitives = { version = "3.0.0-rc9", path = "../../crates/primitives", default-features = false } +ink_prelude = { version = "3.0.0-rc9", path = "../../crates/prelude", default-features = false } +ink_metadata = { version = "3.0.0-rc9", path = "../../crates/metadata", default-features = false, features = ["derive"], optional = true } +ink_env = { version = "3.0.0-rc9", path = "../../crates/env", default-features = false } +ink_storage = { version = "3.0.0-rc9", path = "../../crates/storage", default-features = false } +ink_lang = { version = "3.0.0-rc9", path = "../../crates/lang", default-features = false } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"], optional = true } + +[lib] +name = "upgradeable_contract" +path = "lib.rs" +crate-type = ["cdylib"] + +[features] +default = ["std"] +std = [ + "ink_primitives/std", + "ink_metadata/std", + "ink_env/std", + "ink_storage/std", + "ink_lang/std", + "scale/std", + "scale-info/std", +] +ink-as-dependency = [] diff --git a/examples/upgradeable-contract/README.md b/examples/upgradeable-contract/README.md new file mode 100644 index 00000000000..289935a171a --- /dev/null +++ b/examples/upgradeable-contract/README.md @@ -0,0 +1,48 @@ +# Proxy Smart Contract + +The proxy smart contract delegates any call that does not match a +selector of itself to another, specified contract. + +The instantiator of the proxy contract on a blockchain can change +the code to which calls are forwarded. + +This allows building upgradeable contracts following [the proxy pattern](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies). + +In order to test it out you need to do the following: + +1. Build a contract containing some logic, e.g. our upgradeable flipper example: + ``` + cargo +nightly contract build --manifest-path=examples/upgradeable-contract/upgradeable-flipper/Cargo.toml + ``` + You will receive the respective `upgradeable_flipper.contract` bundle in the + `examples/upgradeable-contract/upgradeable-flipper/target/ink/` folder. + + In order to perform migrations and have proxy working for contracts with different + [storage layouts](https://paritytech.github.io/ink-docs/datastructures/spread-storage-layout), + we use the [`Upgradeable`](upgradeable-flipper/upgradeable.rs) type + wrapper, which ensures that we write different fields of desired struct to different + storage locations, while also tracking the initialization status (e.g., we uploaded + the code on chain, but haven't called the constructor). + +1. Build the proxy contract: + ``` + cargo +nightly contract build --manifest-path=examples/upgradeable-contract/Cargo.toml + ``` + You will receive the respective `upgradeable_contract.contract` bundle + in the `examples/upgradeable-contract/target/ink/` folder. +1. Upload the `upgradeable_flipper.contract` to the chain. +1. Upload the `upgradeable_contract.contract` to the chain. During instantiation + specify the just instantiated `upgradeable_flipper` contract as the `delegate_to` parameter. +1. Switch the metadata of the just instantiated `upgradeable_contract` contract to the + metadata of the `upgradeable_flipper` contract. In the `polkadot-js` UI this can be + done this way: + 1. Click the icon left of the instantiated `upgradeable_contract` contract to copy the + address of it into your clipboard. + 1. Click `Add an existing contract`, insert the just copied address, upload the + `upgradeable_flipper.contract` for the `Contract ABI`. +1. Now you are able to run the operations provided by the `upgradeable_flipper` smart + contract via the `upgradeable_contract` contract. + +To change the address of the smart contract where calls are forwarded to you would +switch the metadata (i.e. the `Contract ABI`) back to the `upgradeable_contract` contract +and then invoke the `change_delegate_code` message. diff --git a/examples/upgradeable-contract/lib.rs b/examples/upgradeable-contract/lib.rs new file mode 100644 index 00000000000..cb089405653 --- /dev/null +++ b/examples/upgradeable-contract/lib.rs @@ -0,0 +1,148 @@ +//! This example demonstrates how the Proxy pattern can be +//! implemented in ink! to have upgradeable functionality. +//! +//! What the contract does is: +//! +//! * Any call to this contract that does not match a selector +//! of it is delegates to a specified address. +//! * The instantiator of the contract can modify this specified +//! `forward_to` address at any point. +//! +//! User ---- tx ---> Proxy ---------> Implementation_v0 +//! | ------------> Implementation_v1 +//! | ------------> Implementation_v2 + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +pub mod upgradeable_contract { + use ink_env::call::DelegateCall; + use ink_primitives::{ + Key, + KeyPtr, + }; + use ink_storage::traits::SpreadLayout; + + /// This struct contains the data related to the Proxy storage. + /// + /// The reason this is a separate structure is that we want to keep + /// the data for this contract in a separate place (as in the implementation + /// of [`SpreadLayout`](ink_storage::traits::SpreadLayout)), so that it does not get + /// overwritten by any contract upgrade, which might introduce storage changes. + #[derive(Debug)] + #[cfg_attr(feature = "std", derive(ink_storage::traits::StorageLayout))] + struct ProxyFields { + /// The `Hash` of a contract code where any call that does not match a + /// selector of this contract is forward to. + forward_to: Hash, + /// The `AccountId` of a privileged account that can update the + /// forwarding address. This address is set to the account that + /// instantiated this contract. + admin: AccountId, + } + + const PROXY_FIELDS_STORAGE_KEY: [u8; 32] = ink_lang::blake2x256!("ProxyFields"); + + /// `SpreadLayout` is implemented manually to use its own `PROXY_FIELDS_STORAGE_KEY` + /// storage key instead of the default contract storage `ContractRootKey::ROOT_KEY`. + /// + /// This allows us to store the proxy contract's storage in such a way that it will not + /// conflict with the the default storage layout of the contract we're proxying calls to. + impl SpreadLayout for ProxyFields { + const FOOTPRINT: u64 = + ::FOOTPRINT + ::FOOTPRINT; + + fn pull_spread(_: &mut KeyPtr) -> Self { + let mut ptr = KeyPtr::from(Key::from(PROXY_FIELDS_STORAGE_KEY)); + Self { + forward_to: SpreadLayout::pull_spread(&mut ptr), + admin: SpreadLayout::pull_spread(&mut ptr), + } + } + + fn push_spread(&self, _: &mut KeyPtr) { + let mut ptr = KeyPtr::from(Key::from(PROXY_FIELDS_STORAGE_KEY)); + SpreadLayout::push_spread(&self.forward_to, &mut ptr); + SpreadLayout::push_spread(&self.admin, &mut ptr); + } + + fn clear_spread(&self, _: &mut KeyPtr) { + let mut ptr = KeyPtr::from(Key::from(PROXY_FIELDS_STORAGE_KEY)); + SpreadLayout::clear_spread(&self.forward_to, &mut ptr); + SpreadLayout::clear_spread(&self.admin, &mut ptr); + } + } + + /// A simple proxy contract. + #[ink(storage)] + pub struct Proxy { + proxy: ProxyFields, + } + + impl Proxy { + /// Instantiate this contract with an address of the `logic` contract. + /// + /// Sets the privileged account to the caller. Only this account may + /// later changed the `forward_to` address. + #[ink(constructor)] + pub fn new(forward_to: Hash) -> Self { + Self { + proxy: ProxyFields { + forward_to, + admin: Self::env().caller(), + }, + } + } + + /// Changes the `Hash` of the contract where any call that does + /// not match a selector of this contract is delegated to. + #[ink(message)] + pub fn change_delegate_code(&mut self, new_code_hash: Hash) { + assert_eq!( + self.env().caller(), + self.proxy.admin, + "caller {:?} does not have sufficient permissions, only {:?} does", + self.env().caller(), + self.proxy.admin, + ); + self.proxy.forward_to = new_code_hash; + } + + /// Fallback message for a contract call that doesn't match any + /// of the other message selectors. Proxy contract delegates the execution + /// of that message to the `forward_to` contract with all input data. + /// + /// # Note: + /// + /// - We allow payable messages here and would forward any optionally supplied + /// value as well. + /// - If the self receiver were `forward(&mut self)` here, this would not + /// have any effect whatsoever on the contract we forward to. + #[ink(message, payable, selector = _)] + pub fn forward(&self) -> u32 { + ink_env::call::build_call::() + .set_call_type(DelegateCall::new().code_hash(self.proxy.forward_to)) + .call_flags( + ink_env::CallFlags::default() + // We don't plan to use the input data after the delegated call, so the + // input data can be forwarded to delegated contract to reduce the gas usage. + .set_forward_input(true) + // We don't plan to return back to that contract after execution, so we + // marked delegated call as "tail", to end the execution of the contract. + .set_tail_call(true), + ) + .fire() + .unwrap_or_else(|err| { + panic!( + "delegate call to {:?} failed due to {:?}", + self.proxy.forward_to, err + ) + }); + unreachable!( + "the forwarded call will never return since `tail_call` was set" + ); + } + } +} diff --git a/examples/upgradeable-contract/upgradeable-flipper/.gitignore b/examples/upgradeable-contract/upgradeable-flipper/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/examples/upgradeable-contract/upgradeable-flipper/.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/examples/upgradeable-contract/upgradeable-flipper/Cargo.toml b/examples/upgradeable-contract/upgradeable-flipper/Cargo.toml new file mode 100644 index 00000000000..dc936644d89 --- /dev/null +++ b/examples/upgradeable-contract/upgradeable-flipper/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "upgradeable_flipper" +version = "3.0.0-rc9" +authors = ["Parity Technologies "] +edition = "2021" + +[dependencies] +ink_primitives = { version = "3.0.0-rc9", path = "../../../crates/primitives", default-features = false } +ink_metadata = { version = "3.0.0-rc9", path = "../../../crates/metadata", default-features = false, features = ["derive"], optional = true } +ink_env = { version = "3.0.0-rc9", path = "../../../crates/env", default-features = false, features = ["ink-debug"] } +ink_storage = { version = "3.0.0-rc9", path = "../../../crates/storage", default-features = false } +ink_lang = { version = "3.0.0-rc9", path = "../../../crates/lang", default-features = false } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"], optional = true } + +[lib] +name = "upgradeable_flipper" +path = "lib.rs" +crate-type = ["cdylib"] + +[features] +default = ["std"] +std = [ + "ink_primitives/std", + "ink_metadata/std", + "ink_env/std", + "ink_storage/std", + "ink_lang/std", + "scale/std", + "scale-info/std", +] +ink-as-dependency = [] diff --git a/examples/upgradeable-contract/upgradeable-flipper/lib.rs b/examples/upgradeable-contract/upgradeable-flipper/lib.rs new file mode 100644 index 00000000000..17cd695a7a2 --- /dev/null +++ b/examples/upgradeable-contract/upgradeable-flipper/lib.rs @@ -0,0 +1,75 @@ +//! This is an example of an upgradable `Flipper`, that can be deployed by the developer +//! and used with `Proxy` from the `upgradeable_contract` crate. +//! The calls from the `Proxy` contract can be delegated to that contract. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod upgradeable; + +use ink_lang as ink; + +#[ink::contract] +pub mod flipper { + use crate::upgradeable::{ + NotInitialized, + Upgradeable, + }; + + #[ink(storage)] + pub struct Flipper { + /// The field is `Upgradeable`, which means if the field is not initialized, it will be. + /// + /// By default ink! would throw an error that the field is not initialized. + /// With that wrapper, you can initialize the field later during the method execution, + /// not in the constructor. + value: Upgradeable, + } + + impl Flipper { + /// Creates a new flipper smart contract initialized with the given value. + #[ink(constructor)] + pub fn new(init_value: bool) -> Self { + Self { + value: Upgradeable::new(init_value), + } + } + + /// Creates a new flipper smart contract initialized to `false`. + #[ink(constructor)] + pub fn default() -> Self { + Self::new(Default::default()) + } + + /// Flips the current value of the Flipper's boolean. + #[ink(message)] + pub fn flip(&mut self) { + *self.value = !*self.value; + } + + /// Returns the current value of the Flipper's boolean. + #[ink(message)] + pub fn get(&self) -> bool { + *self.value + } + } + + #[cfg(test)] + mod tests { + use super::*; + use ink_lang as ink; + + #[ink::test] + fn default_works() { + let flipper = Flipper::default(); + assert!(!flipper.get()); + } + + #[ink::test] + fn it_works() { + let mut flipper = Flipper::new(false); + assert!(!flipper.get()); + flipper.flip(); + assert!(flipper.get()); + } + } +} diff --git a/examples/upgradeable-contract/upgradeable-flipper/upgradeable.rs b/examples/upgradeable-contract/upgradeable-flipper/upgradeable.rs new file mode 100644 index 00000000000..ed422189005 --- /dev/null +++ b/examples/upgradeable-contract/upgradeable-flipper/upgradeable.rs @@ -0,0 +1,178 @@ +use core::marker::PhantomData; +use ink_primitives::{ + Key, + KeyPtr, +}; +use ink_storage::traits::{ + PackedAllocate, + PackedLayout, + SpreadAllocate, + SpreadLayout, +}; +use scale::{ + Decode, + Encode, +}; + +/// It is a status struct for `Upgradeable`, to specify that the inner type is initialized. +#[derive(Debug)] +pub struct Initialized; + +/// It is a status struct for `Upgradeable`, to specify that the inner type may be not +/// initialized and `pull_spread` should initialize it. +#[derive(Debug)] +pub struct NotInitialized; + +/// The `Upgradeable` means if the field is not initialized, it will be. +/// +/// By default ink! would throw an error that the field is not initialized. +/// With that wrapper, you can initialize the field later during the method execution, +/// not in the constructor. It can be done because `SpreadLayout` for +/// `Upgradeable` creates the object, if storage key is empty. +#[derive(Debug, Decode, Encode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub struct Upgradeable { + inner: T, + status: PhantomData InitializationStatus>, +} + +impl Upgradeable { + pub fn new(inner: T) -> Self { + Upgradeable { + inner, + status: Default::default(), + } + } +} + +/// It is default implementation of `SpreadLayout` for case when we don't need to init. +impl SpreadLayout for Upgradeable { + const FOOTPRINT: u64 = T::FOOTPRINT; + const REQUIRES_DEEP_CLEAN_UP: bool = T::REQUIRES_DEEP_CLEAN_UP; + + fn pull_spread(ptr: &mut KeyPtr) -> Self { + Upgradeable::new(T::pull_spread(ptr)) + } + + fn push_spread(&self, ptr: &mut KeyPtr) { + T::push_spread(&self.inner, ptr) + } + + fn clear_spread(&self, ptr: &mut KeyPtr) { + T::clear_spread(&self.inner, ptr) + } +} + +/// It is implementation of `SpreadLayout` that initialize the inner type if it is not initialized. +impl SpreadLayout for Upgradeable { + const FOOTPRINT: u64 = ::FOOTPRINT; + const REQUIRES_DEEP_CLEAN_UP: bool = ::REQUIRES_DEEP_CLEAN_UP; + + fn pull_spread(ptr: &mut KeyPtr) -> Self { + if ink_env::get_contract_storage::(ptr.advance_by(0)) + .expect("could not properly decode storage entry") + .is_none() + { + ::allocate_spread(ptr) + } else { + Upgradeable::new(::pull_spread(ptr)) + } + } + + fn push_spread(&self, ptr: &mut KeyPtr) { + ::push_spread(&self.inner, ptr) + } + + fn clear_spread(&self, ptr: &mut KeyPtr) { + ::clear_spread(&self.inner, ptr) + } +} + +/// Below the boilerplate code to implement `PackedLayout`, `SpreadAllocate`, `PackedAllocate`. + +impl PackedLayout for Upgradeable { + fn pull_packed(&mut self, at: &Key) { + ::pull_packed(&mut self.inner, at) + } + + fn push_packed(&self, at: &Key) { + ::push_packed(&self.inner, at) + } + + fn clear_packed(&self, at: &Key) { + ::clear_packed(&self.inner, at) + } +} + +impl PackedLayout for Upgradeable { + fn pull_packed(&mut self, at: &Key) { + ::pull_packed(&mut self.inner, at) + } + + fn push_packed(&self, at: &Key) { + ::push_packed(&self.inner, at) + } + + fn clear_packed(&self, at: &Key) { + ::clear_packed(&self.inner, at) + } +} + +impl SpreadAllocate for Upgradeable { + fn allocate_spread(ptr: &mut KeyPtr) -> Self { + Upgradeable::new(::allocate_spread(ptr)) + } +} + +impl SpreadAllocate for Upgradeable { + fn allocate_spread(ptr: &mut KeyPtr) -> Self { + Upgradeable::new(::allocate_spread(ptr)) + } +} + +impl PackedAllocate for Upgradeable { + fn allocate_packed(&mut self, at: &Key) { + ::allocate_packed(&mut self.inner, at) + } +} + +impl PackedAllocate for Upgradeable { + fn allocate_packed(&mut self, at: &Key) { + ::allocate_packed(&mut self.inner, at) + } +} + +impl core::ops::Deref for Upgradeable { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl core::ops::DerefMut for Upgradeable { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Default for Upgradeable { + fn default() -> Self { + Self::new(Default::default()) + } +} + +#[cfg(feature = "std")] +const _: () = { + use ink_metadata::layout::Layout; + use ink_storage::traits::StorageLayout; + + impl StorageLayout for Upgradeable + where + T: PackedLayout + StorageLayout + scale_info::TypeInfo + 'static, + { + fn layout(key_ptr: &mut KeyPtr) -> Layout { + ::layout(key_ptr) + } + } +};