Skip to content

Commit

Permalink
Implement seal_is_contract and seal_caller_is_origin (#1129)
Browse files Browse the repository at this point in the history
* [env][lang] add support for new `is_contract` API

* added `is_contract()` to ink_lang::EnvAccess to use from within contract with self.env()

* added `is-contract` example

* add support for `caller_is_origin()` API

* updated is-contract example to use `caller_is_origin()` as well

* Corrections in response to @HCastrano review

* removed `examples/is-contract`

* turned back whitespaces in `env_access.rs` not to mess up with the changed thing

* added new functions to `experimental_offchain_engine`

* fixed `wrong_self_convention` clippy warning for `is_` functions

* supressed `wrong_self_convention` clippy warning for `is_` functions

This reverts commit a40fab1.

* Update crates/env/src/api.rs

Co-authored-by: Michael Müller <mich@elmueller.net>

* set `is_contract` and `caller_is_origin` as `unimplemented` in experimental_off_chain eingine

* Apply suggestions from code review

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* message cant take ref as a param: revert suggested change

Co-authored-by: Michael Müller <mich@elmueller.net>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 23, 2022
1 parent 07a8ed9 commit ff5add1
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 0 deletions.
35 changes: 35 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(account: &T::AccountId) -> bool
where
T: Environment,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::is_contract::<T>(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<T>() -> bool
where
T: Environment,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::caller_is_origin::<T>(instance)
})
}
19 changes: 19 additions & 0 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,23 @@ pub trait TypedEnvBackend: EnvBackend {
fn random<T>(&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<T>(&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<T>(&mut self) -> bool
where
T: Environment;
}
14 changes: 14 additions & 0 deletions crates/env/src/engine/experimental_off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(&mut self, _account: &T::AccountId) -> bool
where
T: Environment,
{
unimplemented!("off-chain environment does not support contract instantiation")
}

fn caller_is_origin<T>(&mut self) -> bool
where
T: Environment,
{
unimplemented!("off-chain environment does not support cross-contract calls")
}
}
14 changes: 14 additions & 0 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,4 +470,18 @@ impl TypedEnvBackend for EnvInstance {
let block = self.current_block().expect(UNINITIALIZED_EXEC_CONTEXT);
Ok((block.random::<T>(subject)?, block.number::<T>()?))
}

fn is_contract<T>(&mut self, _account: &T::AccountId) -> bool
where
T: Environment,
{
unimplemented!("off-chain environment does not support contract instantiation")
}

fn caller_is_origin<T>(&mut self) -> bool
where
T: Environment,
{
unimplemented!("off-chain environment does not support cross-contract calls")
}
}
18 changes: 18 additions & 0 deletions crates/env/src/engine/on_chain/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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()
}
16 changes: 16 additions & 0 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(&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<T>(&mut self) -> bool
where
T: Environment,
{
ext::caller_is_origin()
}
}
64 changes: 64 additions & 0 deletions crates/lang/src/env_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<T>(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::<T>()
}
}

0 comments on commit ff5add1

Please sign in to comment.