diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dfdb9f8d6..9328d03177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased] +### Added +- Implements the API for the `pallet-revive` host function `to_account_id` - [#2578](https://github.com/use-ink/ink/pull/2578) + +### Changed +- Marks the `pallet-revive` host function `account_id` stable - [#2578](https://github.com/use-ink/ink/pull/2578) + ## Version 6.0.0-alpha.4 ### Added diff --git a/crates/engine/src/database.rs b/crates/engine/src/database.rs index 39c23c1a07..349bfa752e 100644 --- a/crates/engine/src/database.rs +++ b/crates/engine/src/database.rs @@ -27,6 +27,7 @@ const STORAGE_OF: &[u8] = b"contract-storage:"; const CONTRACT_PREFIX: &[u8] = b"contract:"; const MSG_HANDLER_OF: &[u8] = b"message-handler:"; const CODE_HASH_OF: &[u8] = b"code-hash:"; +const ACCOUNT_ID_OF: &[u8] = b"account-id:"; /// Returns the database key under which to find the balance for contract `who`. pub fn balance_of_key(who: &Address) -> [u8; 32] { @@ -36,6 +37,14 @@ pub fn balance_of_key(who: &Address) -> [u8; 32] { hashed_key } +/// Returns the database key under which to find the account id for the address `who`. +pub fn account_id_of_key(who: &Address) -> [u8; 32] { + let keyed = who.0.to_vec().to_keyed_vec(ACCOUNT_ID_OF); + let mut hashed_key: [u8; 32] = [0; 32]; + super::hashing::blake2b_256(&keyed[..], &mut hashed_key); + hashed_key +} + /// Returns the database key under which to find the storage for contract `who`. pub fn storage_of_contract_key(who: &Address, key: &[u8]) -> [u8; 32] { let keyed = who @@ -152,15 +161,25 @@ impl Database { } /// Returns the balance of the contract at `addr`, if available. - pub fn get_acc_balance(&self, _addr: &AccountId) -> Option { + pub fn get_acc_balance(&self, _account_id: &AccountId) -> Option { todo!() } /// Sets the balance of `addr` to `new_balance`. - pub fn set_acc_balance(&mut self, _addr: &AccountId, _new_balance: Balance) { + pub fn set_acc_balance(&mut self, _account_id: &AccountId, _new_balance: Balance) { todo!() } + /// Retrieves the account id for a specified address. + pub fn to_account_id(&self, addr: &Address) -> Vec { + let hashed_key = account_id_of_key(addr); + self.get(&hashed_key).cloned().unwrap_or_else(|| { + let mut bytes = [0xEE; 32]; + bytes[..20].copy_from_slice(&addr.as_bytes()[..20]); + Vec::from(bytes) + }) + } + pub fn get_balance(&self, addr: &Address) -> Option { let hashed_key = balance_of_key(addr); self.get(&hashed_key).map(|encoded_balance| { diff --git a/crates/engine/src/ext.rs b/crates/engine/src/ext.rs index c4c95bd5d8..af56496ba7 100644 --- a/crates/engine/src/ext.rs +++ b/crates/engine/src/ext.rs @@ -249,6 +249,24 @@ impl Engine { set_output(output, callee) } + pub fn account_id(&self, output: &mut &mut [u8]) { + let callee = self + .exec_context + .callee + .as_ref() + .expect("no callee has been set"); + let account_id = self.database.to_account_id(callee); + set_output(output, account_id.as_slice()) + } + + /// Retrieves the account id for a specified address. + pub fn to_account_id(&self, input: &[u8], output: &mut &mut [u8]) { + let addr = + scale::Decode::decode(&mut &input[..]).expect("unable to decode Address"); + let account_id = self.database.to_account_id(&addr); + set_output(output, account_id.as_slice()) + } + /// Conduct the BLAKE-2 256-bit hash and place the result into `output`. pub fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]) { super::hashing::blake2b_256(input, output); diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 3420af8622..17fb29d108 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -109,6 +109,21 @@ where }) } +/// Retrieves the account id for a specified address. +/// +/// # Errors +/// +/// If the returned value cannot be properly decoded. +#[cfg(feature = "unstable-hostfn")] +pub fn to_account_id(addr: Address) -> E::AccountId +where + E: Environment, +{ + ::on_instance(|instance| { + TypedEnvBackend::to_account_id::(instance, addr) + }) +} + /// Returns the account ID of the executed contract. /// /// # Note diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 65ec867afb..a1d2e64ab6 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -246,6 +246,14 @@ pub trait TypedEnvBackend: EnvBackend { /// For more details visit: [`block_timestamp`][`crate::block_timestamp`] fn block_timestamp(&mut self) -> E::Timestamp; + /// Retrieves the account id for a specified address. + /// + /// # Note + /// + /// For more details visit: [`to_account_id`][`crate::to_account_id`] + #[cfg(feature = "unstable-hostfn")] + fn to_account_id(&mut self, addr: Address) -> E::AccountId; + /// Returns the address of the executed contract. /// /// # Note diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 93d773a22e..1f5a1b818c 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -560,9 +560,16 @@ impl TypedEnvBackend for EnvInstance { }) } + #[cfg(feature = "unstable-hostfn")] + fn to_account_id(&mut self, addr: Address) -> E::AccountId { + let mut full_scope: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + let full_scope = &mut &mut full_scope[..]; + Engine::to_account_id(&self.engine, addr.as_bytes(), full_scope); + scale::Decode::decode(&mut &full_scope[..]).unwrap() + } + fn account_id(&mut self) -> E::AccountId { - // todo should not use `Engine::account_id` - self.get_property::(Engine::address) + self.get_property::(Engine::account_id) .unwrap_or_else(|error| { panic!("could not read `account_id` property: {error:?}") }) diff --git a/crates/env/src/engine/on_chain/pallet_revive.rs b/crates/env/src/engine/on_chain/pallet_revive.rs index e8114ef2ae..61c533539c 100644 --- a/crates/env/src/engine/on_chain/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/pallet_revive.rs @@ -880,6 +880,15 @@ impl TypedEnvBackend for EnvInstance { scale::Decode::decode(&mut &output[64..96]).expect("must exist") } + #[cfg(feature = "unstable-hostfn")] + fn to_account_id(&mut self, addr: Address) -> E::AccountId { + let mut scope = self.scoped_buffer(); + let account_id: &mut [u8; 32] = scope.take(32).try_into().unwrap(); + ext::to_account_id(addr.as_fixed_bytes(), account_id); + scale::Decode::decode(&mut &account_id[..]) + .expect("A contract being executed must have a valid account id.") + } + fn address(&mut self) -> Address { let mut scope = self.scoped_buffer(); diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 0da0f81f0a..142f9e4ad1 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -158,6 +158,7 @@ where /// #[ink(message)] /// pub fn foo(&self) {} /// + /// // todo /// // /// Returns a tuple of /// // /// - the result of adding the `rhs` to the `lhs` /// // /// - the gas costs of this addition operation @@ -224,17 +225,58 @@ where ink_env::block_timestamp::() } + /// Retrieves the account id for a specified address. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// pub mod only_owner { + /// #[ink(storage)] + /// pub struct OnlyOwner { + /// owner: AccountId, + /// value: u32, + /// } + /// + /// impl OnlyOwner { + /// #[ink(constructor)] + /// pub fn new(owner: AccountId) -> Self { + /// Self { owner, value: 0 } + /// } + /// + /// /// Allows incrementing the contract's `value` only + /// /// for the owner. + /// /// + /// /// The contract panics if the caller is not the owner. + /// #[ink(message)] + /// pub fn increment(&mut self) { + /// let caller = self.env().address(); + /// let caller_acc = self.env().to_account_id(caller); + /// assert!(self.owner == caller_acc); + /// self.value = self.value + 1; + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::to_account_id`] + #[cfg(feature = "unstable-hostfn")] + pub fn to_account_id(self, addr: Address) -> E::AccountId { + ink_env::to_account_id::(addr) + } + /// Returns the account ID of the executed contract. /// /// # Example /// - /// todo this code example doesn't use `account_id()`. /// ``` /// #[ink::contract] /// pub mod only_owner { /// #[ink(storage)] /// pub struct OnlyOwner { - /// owner: ink::Address, + /// owner: AccountId, /// value: u32, /// } /// @@ -242,19 +284,18 @@ where /// #[ink(constructor)] /// pub fn new() -> Self { /// Self { - /// owner: Self::env().caller(), + /// owner: Self::env().account_id(), /// value: 0, /// } /// } /// /// /// Allows incrementing the contract's `value` only - /// /// for the owner (i.e. the account which instantiated - /// /// this contract. + /// /// for the owner. /// /// /// /// The contract panics if the caller is not the owner. /// #[ink(message)] /// pub fn increment(&mut self) { - /// let caller = self.env().caller(); + /// let caller = self.env().account_id(); /// assert!(self.owner == caller); /// self.value = self.value + 1; /// } diff --git a/integration-tests/internal/lang-err/call-builder/lib.rs b/integration-tests/internal/lang-err/call-builder/lib.rs index c8c0499421..caf1380279 100755 --- a/integration-tests/internal/lang-err/call-builder/lib.rs +++ b/integration-tests/internal/lang-err/call-builder/lib.rs @@ -115,7 +115,7 @@ mod call_builder { let result = params .try_instantiate() - .expect("Error from the Contracts pallet."); + .expect("Error from the `pallet-revive`."); match result { Ok(_) => None, diff --git a/integration-tests/internal/misc-hostfns/Cargo.toml b/integration-tests/internal/misc-hostfns/Cargo.toml new file mode 100755 index 0000000000..76e4a325aa --- /dev/null +++ b/integration-tests/internal/misc-hostfns/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "misc_hostfns" +description = "E2E tests for various host functions" +version = "6.0.0-alpha.1" +authors = ["Use Ink "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false, features = ["unstable-hostfn"] } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "ink" diff --git a/integration-tests/internal/misc-hostfns/lib.rs b/integration-tests/internal/misc-hostfns/lib.rs new file mode 100755 index 0000000000..5cec7434e5 --- /dev/null +++ b/integration-tests/internal/misc-hostfns/lib.rs @@ -0,0 +1,65 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +mod misc_hostfns { + #[ink(storage)] + pub struct MiscHostfns {} + + impl MiscHostfns { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Takes an auction data struct as input and returns it back. + #[ink(message)] + pub fn addr_account_id(&self) { + let addr = self.env().address(); + let to_account_id = self.env().to_account_id(addr); + let account_id = self.env().account_id(); + assert_eq!(to_account_id, account_id); + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn works() { + let contract = MiscHostfns::new(); + contract.addr_account_id(); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_works(mut client: Client) -> E2EResult<()> { + // given + let mut constructor = MiscHostfnsRef::new(); + let contract = client + .instantiate("misc_hostfns", &ink_e2e::alice(), &mut constructor) + .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) + .submit() + .await + .expect("call failed"); + + Ok(()) + } + } +} diff --git a/integration-tests/public/runtime-call-contract/sandbox-runtime/pallet-revive-caller/src/executor.rs b/integration-tests/public/runtime-call-contract/sandbox-runtime/pallet-revive-caller/src/executor.rs index ff9391889a..4c7c121547 100644 --- a/integration-tests/public/runtime-call-contract/sandbox-runtime/pallet-revive-caller/src/executor.rs +++ b/integration-tests/public/runtime-call-contract/sandbox-runtime/pallet-revive-caller/src/executor.rs @@ -25,8 +25,6 @@ use pallet_revive::{ use sp_runtime::traits::Bounded; pub struct PalletReviveExecutor { - // todo - //pub origin: AccountIdOf, pub origin: OriginFor, pub contract: Address, pub value: BalanceOf, diff --git a/integration-tests/public/upgradeable-contracts/delegator/lib.rs b/integration-tests/public/upgradeable-contracts/delegator/lib.rs index 82bcfbbb71..7018a663d8 100644 --- a/integration-tests/public/upgradeable-contracts/delegator/lib.rs +++ b/integration-tests/public/upgradeable-contracts/delegator/lib.rs @@ -141,6 +141,7 @@ pub mod delegator { .await; /* + // todo let code_hash = client .upload("delegatee", &origin) .submit() @@ -284,6 +285,7 @@ pub mod delegator { .await; /* + // todo let code_hash = client .upload("delegatee", &origin) .submit() @@ -308,6 +310,7 @@ pub mod delegator { let delegatee_addr = contract.addr; /* + // todo let code_hash2 = client .upload("delegatee2", &origin) .submit()