diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c66fbaa65..c43222e2fed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Marks the `pallet-revive` host function `account_id` stable - [#2578](https://github.com/use-ink/ink/pull/2578) +- Stabilize `is_contract` - [#2654](https://github.com/use-ink/ink/pull/2654) ### Fixed - `name` override fixes for message id computation and trait definitions - [#2649](https://github.com/use-ink/ink/pull/2649) diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 75342ad4635..61cf72e5517 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -680,12 +680,19 @@ pub fn sr25519_verify( }) } -/// Checks whether the specified account is a contract. +/// Checks whether `addr` is a contract. +/// +/// # Notes +/// +/// If `addr` references a precompile address, the return value will be `true`. +/// +/// The function [`caller_is_origin`] performs better when checking whether your +/// contract is being called by a contract or an account. It performs better +/// for this case as it does not require any storage lookups. /// /// # Errors /// /// If the returned value cannot be properly decoded. -#[cfg(feature = "unstable-hostfn")] pub fn is_contract(account: &Address) -> bool { ::on_instance(|instance| { TypedEnvBackend::is_contract(instance, account) diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 0aebf8a9898..53aebb4dbf4 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -373,8 +373,6 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`is_contract`][`crate::is_contract`] - #[allow(clippy::wrong_self_convention)] - #[cfg(feature = "unstable-hostfn")] fn is_contract(&mut self, account: &Address) -> bool; /// Checks whether the caller of the current contract is the origin of the whole call diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index c2bfaa3e3b9..5f9f06dbe1f 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -760,7 +760,6 @@ impl TypedEnvBackend for EnvInstance { }) } - #[cfg(feature = "unstable-hostfn")] fn is_contract(&mut self, account: &Address) -> bool { self.engine.is_contract(account) } diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index 3e917153e15..a2e838831a3 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -127,7 +127,6 @@ pub fn set_contract(contract: Address) { } /// Returns a boolean to indicate whether an account is a contract -#[cfg(feature = "unstable-hostfn")] pub fn is_contract(contract: Address) -> bool { ::on_instance(|instance| { instance.engine.is_contract(&contract) diff --git a/crates/env/src/engine/on_chain/pallet_revive.rs b/crates/env/src/engine/on_chain/pallet_revive.rs index 2d8a7edd45a..cf28698b972 100644 --- a/crates/env/src/engine/on_chain/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/pallet_revive.rs @@ -1204,11 +1204,8 @@ impl TypedEnvBackend for EnvInstance { ::from_le_bytes(result) } - #[cfg(feature = "unstable-hostfn")] - fn is_contract(&mut self, _addr: &Address) -> bool { - panic!( - "todo call code() precompile, see https://github.com/paritytech/polkadot-sdk/pull/9001" - ); + fn is_contract(&mut self, addr: &Address) -> bool { + ext::code_size(&addr.0) > 0 } fn caller_is_origin(&mut self) -> bool diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 82c5bca363b..3ff40138f6a 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -1009,8 +1009,15 @@ where .map_err(|_| ReturnErrorCode::Sr25519VerifyFailed.into()) } - /// Checks whether a contract lives under `addr`. - /// todo update comment + /// Checks whether `addr` is a contract. + /// + /// # Notes + /// + /// If `addr` references a precompile address, the return value will be `true`. + /// + /// The function [`caller_is_origin`] performs better when checking whether your + /// contract is being called by a contract or an account. It performs better + /// for this case as it does not require any storage lookups. /// /// # Example /// @@ -1030,6 +1037,13 @@ where /// pub fn is_contract(&mut self, addr: ink::Address) -> bool { /// self.env().is_contract(&addr) /// } + /// + /// #[ink(message)] + /// pub fn check(&mut self) { + /// let this_contract = self.env().address(); + /// assert!(self.env().is_contract(&this_contract)); + /// assert!(!self.env().is_contract(&self.env().caller())); + /// } /// # } /// # } /// ``` @@ -1037,7 +1051,6 @@ where /// # Note /// /// For more details visit: [`ink_env::is_contract`] - #[cfg(feature = "unstable-hostfn")] pub fn is_contract(self, addr: &Address) -> bool { ink_env::is_contract(addr) } diff --git a/integration-tests/internal/misc-hostfns/Cargo.toml b/integration-tests/internal/misc-hostfns/Cargo.toml index 76e4a325aa5..27e7b955c88 100755 --- a/integration-tests/internal/misc-hostfns/Cargo.toml +++ b/integration-tests/internal/misc-hostfns/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] ink = { path = "../../../crates/ink", default-features = false, features = ["unstable-hostfn"] } +hex-literal = "1" [dev-dependencies] ink_e2e = { path = "../../../crates/e2e" } diff --git a/integration-tests/internal/misc-hostfns/lib.rs b/integration-tests/internal/misc-hostfns/lib.rs index bf07f82223c..9ecbf560e46 100755 --- a/integration-tests/internal/misc-hostfns/lib.rs +++ b/integration-tests/internal/misc-hostfns/lib.rs @@ -24,6 +24,21 @@ mod misc_hostfns { "failed asserting equality for the account id" ); } + + /// Utilizes `ink_env::is_contract`. + #[ink(message)] + pub fn is_contract(&self) { + let this_contract = self.env().address(); + assert!(self.env().is_contract(&this_contract)); + assert!(!self.env().is_contract(&self.env().caller())); + + const SYSTEM_PRECOMPILE: [u8; 20] = + hex_literal::hex!("0000000000000000000000000000000000000900"); + assert!( + self.env() + .is_contract(&ink::H160::from_slice(&SYSTEM_PRECOMPILE[..])) + ); + } } #[cfg(test)] @@ -49,18 +64,48 @@ mod misc_hostfns { mut client: Client, ) -> E2EResult<()> { // given - let mut constructor = MiscHostfnsRef::new(); let contract = client - .instantiate("misc_hostfns", &ink_e2e::alice(), &mut constructor) + .instantiate( + "misc_hostfns", + &ink_e2e::alice(), + &mut MiscHostfnsRef::new(), + ) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // then + let _call_res = client + .call(&ink_e2e::alice(), &call_builder.addr_account_id()) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_is_contract_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let contract = client + .instantiate( + "misc_hostfns", + &ink_e2e::alice(), + &mut MiscHostfnsRef::new(), + ) .submit() .await .expect("instantiate failed"); let call_builder = contract.call_builder::(); // then - let acc = call_builder.addr_account_id(); let _call_res = client - .call(&ink_e2e::alice(), &acc) + .call(&ink_e2e::alice(), &call_builder.is_contract()) .submit() .await .unwrap_or_else(|err| {