From 751c8b37a11e75c35505c87fa019cdada057eeb3 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Thu, 18 Sep 2025 19:07:45 +0200 Subject: [PATCH 1/4] Stabilize `is_contract`, add tests, improve comments --- crates/env/src/api.rs | 11 +++- crates/env/src/backend.rs | 2 - crates/env/src/engine/off_chain/impls.rs | 1 - crates/env/src/engine/off_chain/test_api.rs | 1 - .../env/src/engine/on_chain/pallet_revive.rs | 11 ++-- crates/ink/src/env_access.rs | 27 ++++++++-- .../internal/misc-hostfns/Cargo.toml | 1 + .../internal/misc-hostfns/lib.rs | 53 +++++++++++++++++-- 8 files changed, 89 insertions(+), 18 deletions(-) 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..ff1d7d6b107 100644 --- a/crates/env/src/engine/on_chain/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/pallet_revive.rs @@ -1204,11 +1204,12 @@ 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 { + let addr: &[u8; 20] = addr + .as_ref() + .try_into() + .expect("failed converting `Address` to `[u8; 20]`"); + ext::code_size(addr) > 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..1ea7ab455ab 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -31,6 +31,7 @@ use ink_env::{ CryptoHash, HashOutput, }, + is_contract, }; use ink_primitives::{ Address, @@ -1009,8 +1010,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 +1038,20 @@ where /// pub fn is_contract(&mut self, addr: ink::Address) -> bool { /// self.env().is_contract(&addr) /// } + /// + /// #[ink(message)] + /// pub fn check(&mut self, addr: ink::Address) -> bool { + /// 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[..])) + /// ); + /// } /// # } /// # } /// ``` @@ -1037,7 +1059,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| { From fa6822575a82c665aadeab48d1e5effebc948fb7 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Tue, 23 Sep 2025 17:11:29 +0200 Subject: [PATCH 2/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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) From b412e0a26b8186c85ce60fa0b648fd90603ba58e Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Tue, 23 Sep 2025 20:48:09 +0200 Subject: [PATCH 3/4] Fix CI --- crates/ink/src/env_access.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 1ea7ab455ab..3ff40138f6a 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -31,7 +31,6 @@ use ink_env::{ CryptoHash, HashOutput, }, - is_contract, }; use ink_primitives::{ Address, @@ -1040,17 +1039,10 @@ where /// } /// /// #[ink(message)] - /// pub fn check(&mut self, addr: ink::Address) -> bool { + /// 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())); - /// - /// const SYSTEM_PRECOMPILE: [u8; 20] = - /// hex_literal::hex!("0000000000000000000000000000000000000900"); - /// assert!( - /// self.env() - /// .is_contract(&ink::H160::from_slice(&SYSTEM_PRECOMPILE[..])) - /// ); /// } /// # } /// # } From 32b16f4656179de7071397142ea1a254b2db954f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Tue, 23 Sep 2025 21:56:07 +0200 Subject: [PATCH 4/4] Update crates/env/src/engine/on_chain/pallet_revive.rs Co-authored-by: David Semakula --- crates/env/src/engine/on_chain/pallet_revive.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/env/src/engine/on_chain/pallet_revive.rs b/crates/env/src/engine/on_chain/pallet_revive.rs index ff1d7d6b107..cf28698b972 100644 --- a/crates/env/src/engine/on_chain/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/pallet_revive.rs @@ -1205,11 +1205,7 @@ impl TypedEnvBackend for EnvInstance { } fn is_contract(&mut self, addr: &Address) -> bool { - let addr: &[u8; 20] = addr - .as_ref() - .try_into() - .expect("failed converting `Address` to `[u8; 20]`"); - ext::code_size(addr) > 0 + ext::code_size(&addr.0) > 0 } fn caller_is_origin(&mut self) -> bool