Skip to content

Commit

Permalink
[pallet-revive] Add host function to_account_id (#7091)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmichi authored Jan 15, 2025
1 parent 5be6587 commit 412aca6
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 0 deletions.
12 changes: 12 additions & 0 deletions prdoc/pr_7091.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
title: '[pallet-revive] Add new host function `to_account_id`'
doc:
- audience: Runtime Dev
description: A new host function `to_account_id` is added. It allows retrieving
the account id for a `H160` address.
crates:
- name: pallet-revive-fixtures
bump: minor
- name: pallet-revive
bump: minor
- name: pallet-revive-uapi
bump: minor
40 changes: 40 additions & 0 deletions substrate/frame/revive/fixtures/contracts/to_account_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![no_std]
#![no_main]

use common::input;
use uapi::{HostFn, HostFnImpl as api};

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
input!(
address: &[u8; 20],
expected_account_id: &[u8; 32],
);

let mut account_id = [0u8; 32];
api::to_account_id(address, &mut account_id);

assert!(&account_id == expected_account_id);
}
32 changes: 32 additions & 0 deletions substrate/frame/revive/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,38 @@ mod benchmarks {
assert_eq!(result.unwrap(), 1);
}

#[benchmark(pov_mode = Measured)]
fn seal_to_account_id() {
// use a mapped address for the benchmark, to ensure that we bench the worst
// case (and not the fallback case).
let address = {
let caller = account("seal_to_account_id", 0, 0);
T::Currency::set_balance(&caller, caller_funding::<T>());
T::AddressMapper::map(&caller).unwrap();
T::AddressMapper::to_address(&caller)
};

let len = <T::AccountId as MaxEncodedLen>::max_encoded_len();
build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]);

let result;
#[block]
{
result = runtime.bench_to_account_id(memory.as_mut_slice(), len as u32, 0);
}

assert_ok!(result);
assert_ne!(
memory.as_slice()[20..32],
[0xEE; 12],
"fallback suffix found where none should be"
);
assert_eq!(
T::AccountId::decode(&mut memory.as_slice()),
Ok(runtime.ext().to_account_id(&address))
);
}

#[benchmark(pov_mode = Measured)]
fn seal_code_hash() {
let contract = Contract::<T>::with_index(1, WasmModule::dummy(), vec![]).unwrap();
Expand Down
41 changes: 41 additions & 0 deletions substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ pub trait Ext: sealing::Sealed {
/// Check if a contract lives at the specified `address`.
fn is_contract(&self, address: &H160) -> bool;

/// Returns the account id for the given `address`.
fn to_account_id(&self, address: &H160) -> AccountIdOf<Self::T>;

/// Returns the code hash of the contract for the given `address`.
/// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`.
fn code_hash(&self, address: &H160) -> H256;
Expand Down Expand Up @@ -1572,6 +1575,10 @@ where
ContractInfoOf::<T>::contains_key(&address)
}

fn to_account_id(&self, address: &H160) -> T::AccountId {
T::AddressMapper::to_account_id(address)
}

fn code_hash(&self, address: &H160) -> H256 {
<ContractInfoOf<T>>::get(&address)
.map(|contract| contract.code_hash)
Expand Down Expand Up @@ -2582,6 +2589,40 @@ mod tests {
});
}

#[test]
fn to_account_id_returns_proper_values() {
let bob_code_hash = MockLoader::insert(Call, |ctx, _| {
let alice_account_id = <Test as Config>::AddressMapper::to_account_id(&ALICE_ADDR);
assert_eq!(ctx.ext.to_account_id(&ALICE_ADDR), alice_account_id);

const UNMAPPED_ADDR: H160 = H160([99u8; 20]);
let mut unmapped_fallback_account_id = [0xEE; 32];
unmapped_fallback_account_id[..20].copy_from_slice(UNMAPPED_ADDR.as_bytes());
assert_eq!(
ctx.ext.to_account_id(&UNMAPPED_ADDR),
AccountId32::new(unmapped_fallback_account_id)
);

exec_success()
});

ExtBuilder::default().build().execute_with(|| {
place_contract(&BOB, bob_code_hash);
let origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap();
let result = MockStack::run_call(
origin,
BOB_ADDR,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
U256::zero(),
vec![0],
false,
);
assert_matches!(result, Ok(_));
});
}

#[test]
fn code_hash_returns_proper_values() {
let bob_code_hash = MockLoader::insert(Call, |ctx, _| {
Expand Down
37 changes: 37 additions & 0 deletions substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4239,6 +4239,43 @@ fn origin_api_works() {
});
}

#[test]
fn to_account_id_works() {
let (code_hash_code, _) = compile_module("to_account_id").unwrap();

ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
let _ = <Test as Config>::Currency::set_balance(&EVE, 1_000_000);

let Contract { addr, .. } =
builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract();

// mapped account
<Pallet<Test>>::map_account(RuntimeOrigin::signed(EVE)).unwrap();
let expected_mapped_account_id = &<Test as Config>::AddressMapper::to_account_id(&EVE_ADDR);
assert_ne!(
expected_mapped_account_id.encode()[20..32],
[0xEE; 12],
"fallback suffix found where none should be"
);
assert_ok!(builder::call(addr)
.data((EVE_ADDR, expected_mapped_account_id).encode())
.build());

// fallback for unmapped accounts
let expected_fallback_account_id =
&<Test as Config>::AddressMapper::to_account_id(&BOB_ADDR);
assert_eq!(
expected_fallback_account_id.encode()[20..32],
[0xEE; 12],
"no fallback suffix found where one should be"
);
assert_ok!(builder::call(addr)
.data((BOB_ADDR, expected_fallback_account_id).encode())
.build());
});
}

#[test]
fn code_hash_works() {
let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap();
Expand Down
24 changes: 24 additions & 0 deletions substrate/frame/revive/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ pub enum RuntimeCosts {
CallDataSize,
/// Weight of calling `seal_return_data_size`.
ReturnDataSize,
/// Weight of calling `seal_to_account_id`.
ToAccountId,
/// Weight of calling `seal_origin`.
Origin,
/// Weight of calling `seal_is_contract`.
Expand Down Expand Up @@ -466,6 +468,7 @@ impl<T: Config> Token<T> for RuntimeCosts {
Caller => T::WeightInfo::seal_caller(),
Origin => T::WeightInfo::seal_origin(),
IsContract => T::WeightInfo::seal_is_contract(),
ToAccountId => T::WeightInfo::seal_to_account_id(),
CodeHash => T::WeightInfo::seal_code_hash(),
CodeSize => T::WeightInfo::seal_code_size(),
OwnCodeHash => T::WeightInfo::seal_own_code_hash(),
Expand Down Expand Up @@ -2140,4 +2143,25 @@ pub mod env {
},
}
}

/// Retrieves the account id for a specified contract address.
///
/// See [`pallet_revive_uapi::HostFn::to_account_id`].
fn to_account_id(
&mut self,
memory: &mut M,
addr_ptr: u32,
out_ptr: u32,
) -> Result<(), TrapReason> {
self.charge_gas(RuntimeCosts::ToAccountId)?;
let address = memory.read_h160(addr_ptr)?;
let account_id = self.ext.to_account_id(&address);
Ok(self.write_fixed_sandbox_output(
memory,
out_ptr,
&account_id.encode(),
false,
already_charged,
)?)
}
}
21 changes: 21 additions & 0 deletions substrate/frame/revive/src/weights.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions substrate/frame/revive/uapi/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ pub trait HostFn: private::Sealed {
/// - `output`: A reference to the output data buffer to write the origin's address.
fn origin(output: &mut [u8; 20]);

/// Retrieve the account id for a specified address.
///
/// # Parameters
///
/// - `addr`: A `H160` address.
/// - `output`: A reference to the output data buffer to write the account id.
///
/// # Note
///
/// If no mapping exists for `addr`, the fallback account id will be returned.
fn to_account_id(addr: &[u8; 20], output: &mut [u8]);

/// Retrieve the code hash for a specified contract address.
///
/// # Parameters
Expand Down
6 changes: 6 additions & 0 deletions substrate/frame/revive/uapi/src/host/riscv64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ mod sys {
pub fn caller(out_ptr: *mut u8);
pub fn origin(out_ptr: *mut u8);
pub fn is_contract(account_ptr: *const u8) -> ReturnCode;
pub fn to_account_id(address_ptr: *const u8, out_ptr: *mut u8);
pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8);
pub fn code_size(address_ptr: *const u8) -> u64;
pub fn own_code_hash(out_ptr: *mut u8);
Expand Down Expand Up @@ -456,6 +457,11 @@ impl HostFn for HostFnImpl {
unsafe { sys::ref_time_left() }
}

#[unstable_hostfn]
fn to_account_id(address: &[u8; 20], output: &mut [u8]) {
unsafe { sys::to_account_id(address.as_ptr(), output.as_mut_ptr()) }
}

#[unstable_hostfn]
fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) {
unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) };
Expand Down

0 comments on commit 412aca6

Please sign in to comment.