Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,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)
- Add `#[ink::contract_ref]` attribute - [#2648](https://github.com/use-ink/ink/pull/2648)

### Changed
- Marks the `pallet-revive` host function `account_id` stable - [#2578](https://github.com/use-ink/ink/pull/2578)

### Fixed
- `name` override fixes for message id computation and trait definitions - [#2649](https://github.com/use-ink/ink/pull/2649)

Expand Down
23 changes: 21 additions & 2 deletions crates/engine/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand All @@ -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
Expand Down Expand Up @@ -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<Balance> {
pub fn get_acc_balance(&self, _account_id: &AccountId) -> Option<Balance> {
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<u8> {
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<U256> {
let hashed_key = balance_of_key(addr);
self.get(&hashed_key).map(|encoded_balance| {
Expand Down
18 changes: 18 additions & 0 deletions crates/engine/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,20 @@ where
})
}

/// Retrieves the account id for a specified address.
///
/// # Errors
///
/// If the returned value cannot be properly decoded.
pub fn to_account_id<E>(addr: Address) -> E::AccountId
where
E: Environment,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::to_account_id::<E>(instance, addr)
})
}

/// Returns the account ID of the executed contract.
///
/// # Note
Expand Down
7 changes: 7 additions & 0 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,13 @@ pub trait TypedEnvBackend: EnvBackend {
/// For more details visit: [`block_timestamp`][`crate::block_timestamp`]
fn block_timestamp<E: Environment>(&mut self) -> E::Timestamp;

/// Retrieves the account id for a specified address.
///
/// # Note
///
/// For more details visit: [`to_account_id`][`crate::to_account_id`]
fn to_account_id<E: Environment>(&mut self, addr: Address) -> E::AccountId;

/// Returns the address of the executed contract.
///
/// # Note
Expand Down
10 changes: 8 additions & 2 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,15 @@ impl TypedEnvBackend for EnvInstance {
})
}

fn to_account_id<E: Environment>(&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<E: Environment>(&mut self) -> E::AccountId {
// todo should not use `Engine::account_id`
self.get_property::<E::AccountId>(Engine::address)
self.get_property::<E::AccountId>(Engine::account_id)
.unwrap_or_else(|error| {
panic!("could not read `account_id` property: {error:?}")
})
Expand Down
83 changes: 64 additions & 19 deletions crates/env/src/engine/on_chain/pallet_revive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,41 @@ fn call_storage_precompile(
)
}

/// Simple decoder for a Solidity `bytes` type.
///
/// # Important
///
/// This function assumes that it is decoding a single
/// bytes return type!
///
/// - Fine: `function foo() returns (bytes memory);`
/// - Not Fine: `function foo() returns (bool, bytes memory);`
///
/// # Return Valu
///
/// Returns the number of bytes written to `out`.
fn decode_bytes(input: &[u8], out: &mut [u8]) -> usize {
let mut buf = [0u8; 4];
buf[..].copy_from_slice(&input[28..32]);
debug_assert_eq!(
{
let offset = u32::from_be_bytes(buf) as usize;
offset
},
64
);

let mut buf = [0u8; 4];
buf[..].copy_from_slice(&input[60..64]);
let bytes_len = u32::from_be_bytes(buf) as usize;

// we start decoding at the start of the payload.
// the payload starts at the `len` word here:
// `bytes = offset (32 bytes) | len (32 bytes) | data`
out[..bytes_len].copy_from_slice(&input[64..64 + bytes_len]);
bytes_len
}

const STORAGE_FLAGS: StorageFlags = StorageFlags::empty();

impl EnvBackend for EnvInstance {
Expand Down Expand Up @@ -847,37 +882,47 @@ impl TypedEnvBackend for EnvInstance {
}

fn account_id<E: Environment>(&mut self) -> E::AccountId {
let mut scope = self.scoped_buffer();

let h160: &mut [u8; 20] = scope.take(20).try_into().unwrap();
ext::address(h160);

// 32 bytes offset + 32 bytes len + 32 bytes account_id
let output: &mut [u8; 96] = scope.take(96).try_into().unwrap();

let selector = const { solidity_selector("toAccountId(address)") };
let input: &mut [u8; 36] = &mut scope.take(4 + 32).try_into().unwrap();
let h160 = {
let mut scope = self.scoped_buffer();
let h160: &mut [u8; 20] = scope.take(20).try_into().unwrap();
ext::address(h160);
h160.clone()
};
self.to_account_id::<E>(h160.into())
}

input[..4].copy_from_slice(&selector[..]);
input[16..36].copy_from_slice(&h160[..]);
fn to_account_id<E: Environment>(&mut self, addr: Address) -> E::AccountId {
let mut scope = self.scoped_buffer();
let output: &mut [u8; 32 + SOL_BYTES_ENCODING_OVERHEAD] = scope
.take(32 + SOL_BYTES_ENCODING_OVERHEAD)
.try_into()
.unwrap();
let addr = addr.as_fixed_bytes();

const ADDR: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000900");
let _ = ext::delegate_call(

let mut input = [0u8; 32 + 4];
let sel = const { solidity_selector("toAccountId(address)") };
input[..4].copy_from_slice(&sel[..4]);
input[16..36].copy_from_slice(&addr[..]);

let _ = ext::call(
CallFlags::empty(),
&ADDR,
u64::MAX, // `ref_time` to devote for execution. `u64::MAX` = all
u64::MAX, // `proof_size` to devote for execution. `u64::MAX` = all
&[u8::MAX; 32], // No deposit limit.
&U256::zero().to_little_endian(), // Value transferred to the contract.
&input[..],
Some(&mut &mut output[..]),
)
.expect("call host function failed");
);

// We start decoding at the start of the payload.
// The payload starts at the `len` word here:
// `bytes = offset (32 bytes) | len (32 bytes) | data`
scale::Decode::decode(&mut &output[64..96]).expect("must exist")
let account_id: &mut [u8; 32] = scope.take(32).try_into().unwrap();
let n = decode_bytes(&output[..], &mut account_id[..]);
debug_assert_eq!(n, 32, "length of decoded bytes must be 32");
scale::Decode::decode(&mut &account_id[..])
.expect("A contract being executed must have a valid account id.")
}

fn address(&mut self) -> Address {
Expand Down
52 changes: 46 additions & 6 deletions crates/ink/src/env_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -224,37 +225,76 @@ where
ink_env::block_timestamp::<E>()
}

/// 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`]
pub fn to_account_id(self, addr: Address) -> E::AccountId {
ink_env::to_account_id::<E>(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,
/// }
///
/// impl OnlyOwner {
/// #[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;
/// }
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/internal/lang-err/call-builder/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
27 changes: 27 additions & 0 deletions integration-tests/internal/misc-hostfns/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "misc_hostfns"
description = "E2E tests for various host functions"
version = "6.0.0-alpha.1"
authors = ["Use Ink <ink@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"
Loading
Loading