diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 24c8e826f2b..065d4112978 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -498,3 +498,38 @@ pub fn ecdsa_recover( instance.ecdsa_recover(signature, message_hash, output) }) } + +/// Checks whether the specified account is a contract. +/// +/// # Errors +/// +/// If the returned value cannot be properly decoded. +pub fn is_contract(account: &T::AccountId) -> bool +where + T: Environment, +{ + ::on_instance(|instance| { + TypedEnvBackend::is_contract::(instance, account) + }) +} + +/// Checks whether the caller of the current contract is the origin of the whole call stack. +/// +/// Prefer this over [`is_contract`] when checking whether your contract is being called by +/// a contract or a plain account. The reason is that it performs better since it does not +/// need to do any storage lookups. +/// +/// A return value of `true` indicates that this contract is being called by a plain account. +/// and `false` indicates that the caller is another contract. +/// +/// # Errors +/// +/// If the returned value cannot be properly decoded. +pub fn caller_is_origin() -> bool +where + T: Environment, +{ + ::on_instance(|instance| { + TypedEnvBackend::caller_is_origin::(instance) + }) +} diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 733bce851b7..f0ae2922e28 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -426,4 +426,23 @@ pub trait TypedEnvBackend: EnvBackend { fn random(&mut self, subject: &[u8]) -> Result<(T::Hash, T::BlockNumber)> where T: Environment; + + /// Checks whether a specified account belongs to a contract. + /// + /// # Note + /// + /// For more details visit: [`is_contract`][`crate::is_contract`] + #[allow(clippy::wrong_self_convention)] + fn is_contract(&mut self, account: &T::AccountId) -> bool + where + T: 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 + where + T: Environment; } diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index 62932548640..7eb83397d3f 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -460,4 +460,18 @@ impl TypedEnvBackend for EnvInstance { 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 + where + T: Environment, + { + unimplemented!("off-chain environment does not support contract instantiation") + } + + fn caller_is_origin(&mut self) -> bool + where + T: Environment, + { + unimplemented!("off-chain environment does not support cross-contract calls") + } } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 625ddd2e72c..79726e973cf 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -470,4 +470,18 @@ impl TypedEnvBackend for EnvInstance { let block = self.current_block().expect(UNINITIALIZED_EXEC_CONTEXT); Ok((block.random::(subject)?, block.number::()?)) } + + fn is_contract(&mut self, _account: &T::AccountId) -> bool + where + T: Environment, + { + unimplemented!("off-chain environment does not support contract instantiation") + } + + fn caller_is_origin(&mut self) -> bool + where + T: 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 3d74bd118fe..365c0dc27ea 100644 --- a/crates/env/src/engine/on_chain/ext.rs +++ b/crates/env/src/engine/on_chain/ext.rs @@ -186,6 +186,10 @@ impl ReturnCode { pub fn into_u32(self) -> u32 { self.0 } + /// Returns the underlying `u32` converted into `bool`. + pub fn into_bool(self) -> bool { + self.0.ne(&0) + } } type Result = core::result::Result<(), Error>; @@ -330,6 +334,10 @@ mod sys { message_hash_ptr: Ptr32<[u8]>, output_ptr: Ptr32Mut<[u8]>, ) -> ReturnCode; + + pub fn seal_is_contract(account_id_ptr: Ptr32<[u8]>) -> ReturnCode; + + pub fn seal_caller_is_origin() -> ReturnCode; } } @@ -626,3 +634,13 @@ pub fn ecdsa_recover( }; ret_code.into() } + +pub fn is_contract(account_id: &[u8]) -> bool { + let ret_val = unsafe { sys::seal_is_contract(Ptr32::from_slice(account_id)) }; + ret_val.into_bool() +} + +pub fn caller_is_origin() -> bool { + let ret_val = unsafe { sys::seal_caller_is_origin() }; + ret_val.into_bool() +} diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 2a2e585a77a..0c8d60a5426 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -490,4 +490,20 @@ impl TypedEnvBackend for EnvInstance { ext::random(enc_subject, output); scale::Decode::decode(&mut &output[..]).map_err(Into::into) } + + fn is_contract(&mut self, account_id: &T::AccountId) -> bool + where + T: 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 + where + T: Environment, + { + ext::caller_is_origin() + } } diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index 7168cd75258..11ca46d40ef 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -839,4 +839,68 @@ where .map(|_| output.into()) .map_err(|_| Error::EcdsaRecoveryFailed) } + + /// Checks whether a specified account belongs to a contract. + /// + /// # Example + /// + /// ``` + /// # use ink_lang as ink; + /// # #[ink::contract] + /// # pub mod my_contract { + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn new() -> Self { + /// # Self {} + /// # } + /// # + /// #[ink(message)] + /// pub fn is_contract(&mut self, account_id: AccountId) -> bool { + /// self.env().is_contract(&account_id) + /// } + /// # } + /// # } + /// ``` + /// + /// # 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) + } + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// + /// # Example + /// + /// ``` + /// # use ink_lang as ink; + /// # #[ink::contract] + /// # pub mod my_contract { + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn new() -> Self { + /// # Self {} + /// # } + /// # + /// #[ink(message)] + /// pub fn caller_is_origin(&mut self) -> bool { + /// self.env().caller_is_origin() + /// } + /// # } + /// # } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::caller_is_origin`] + pub fn caller_is_origin(self) -> bool { + ink_env::caller_is_origin::() + } }