Skip to content

Commit

Permalink
[pallet-revive] implement tx origin API (#6105)
Browse files Browse the repository at this point in the history
Implement a syscall to retreive the transaction origin.

---------

Signed-off-by: Cyrill Leutwiler <[email protected]>
Signed-off-by: xermicus <[email protected]>
Co-authored-by: GitHub Action <[email protected]>
Co-authored-by: command-bot <>
Co-authored-by: Alexander Theißen <[email protected]>
  • Loading branch information
3 people authored Oct 29, 2024
1 parent 54c19f5 commit 35535ef
Show file tree
Hide file tree
Showing 9 changed files with 670 additions and 447 deletions.
14 changes: 14 additions & 0 deletions prdoc/pr_6105.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
title: '[pallet-revive] implement tx origin API'

doc:
- audience:
- Runtime Dev
description: Implement a syscall to retreive the transaction origin.

crates:
- name: pallet-revive
bump: minor
- name: pallet-revive-uapi
bump: minor
- name: pallet-revive-fixtures
bump: patch
62 changes: 62 additions & 0 deletions substrate/frame/revive/fixtures/contracts/origin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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.

//! Tests that the `origin` syscall works.
//! The fixture returns the observed origin if the caller is not the origin,
//! otherwise call itself recursively and assert the returned origin to match.

#![no_std]
#![no_main]

extern crate common;
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() {
let mut caller = [0; 20];
api::caller(&mut caller);

let mut origin = [0; 20];
api::origin(&mut origin);

if caller != origin {
api::return_value(Default::default(), &origin);
}

let mut addr = [0u8; 20];
api::address(&mut addr);

let mut buf = [0u8; 20];
api::call(
uapi::CallFlags::ALLOW_REENTRY,
&addr,
0u64,
0u64,
None,
&[0; 32],
&[],
Some(&mut &mut buf[..]),
)
.unwrap();

assert_eq!(buf, origin);
}
18 changes: 18 additions & 0 deletions substrate/frame/revive/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,24 @@ mod benchmarks {
);
}

#[benchmark(pov_mode = Measured)]
fn seal_origin() {
let len = H160::len_bytes();
build_runtime!(runtime, memory: [vec![0u8; len as _], ]);

let result;
#[block]
{
result = runtime.bench_origin(memory.as_mut_slice(), 0);
}

assert_ok!(result);
assert_eq!(
<H160 as Decode>::decode(&mut &memory[..]).unwrap(),
T::AddressMapper::to_address(&runtime.ext().origin().account_id().unwrap())
);
}

#[benchmark(pov_mode = Measured)]
fn seal_is_contract() {
let Contract { account_id, .. } =
Expand Down
72 changes: 72 additions & 0 deletions substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ pub trait Ext: sealing::Sealed {
/// Returns the caller.
fn caller(&self) -> Origin<Self::T>;

/// Return the origin of the whole call stack.
fn origin(&self) -> &Origin<Self::T>;

/// Check if a contract lives at the specified `address`.
fn is_contract(&self, address: &H160) -> bool;

Expand Down Expand Up @@ -1547,6 +1550,10 @@ where
}
}

fn origin(&self) -> &Origin<T> {
&self.origin
}

fn is_contract(&self, address: &H160) -> bool {
ContractInfoOf::<T>::contains_key(&address)
}
Expand Down Expand Up @@ -2381,6 +2388,71 @@ mod tests {
assert_eq!(WitnessedCallerCharlie::get(), Some(BOB_ADDR));
}

#[test]
fn origin_returns_proper_values() {
parameter_types! {
static WitnessedCallerBob: Option<H160> = None;
static WitnessedCallerCharlie: Option<H160> = None;
}

let bob_ch = MockLoader::insert(Call, |ctx, _| {
// Record the origin for bob.
WitnessedCallerBob::mutate(|witness| {
let origin = ctx.ext.origin();
*witness = Some(<Test as Config>::AddressMapper::to_address(
&origin.account_id().unwrap(),
));
});

// Call into CHARLIE contract.
assert_matches!(
ctx.ext.call(
Weight::zero(),
U256::zero(),
&CHARLIE_ADDR,
U256::zero(),
vec![],
true,
false
),
Ok(_)
);
exec_success()
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
// Record the origin for charlie.
WitnessedCallerCharlie::mutate(|witness| {
let origin = ctx.ext.origin();
*witness = Some(<Test as Config>::AddressMapper::to_address(
&origin.account_id().unwrap(),
));
});
exec_success()
});

ExtBuilder::default().build().execute_with(|| {
place_contract(&BOB, bob_ch);
place_contract(&CHARLIE, charlie_ch);
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,
0,
vec![],
None,
);

assert_matches!(result, Ok(_));
});

assert_eq!(WitnessedCallerBob::get(), Some(ALICE_ADDR));
assert_eq!(WitnessedCallerCharlie::get(), Some(ALICE_ADDR));
}

#[test]
fn is_contract_returns_proper_values() {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
Expand Down
16 changes: 16 additions & 0 deletions substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4476,6 +4476,22 @@ mod run_tests {
});
}

#[test]
fn origin_api_works() {
let (code, _) = compile_module("origin").unwrap();

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

// Create fixture: Constructor does nothing
let Contract { addr, .. } =
builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract();

// Call the contract: Asserts the origin API to work as expected
assert_ok!(builder::call(addr).build());
});
}

#[test]
fn code_hash_works() {
let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap();
Expand Down
18 changes: 18 additions & 0 deletions substrate/frame/revive/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ pub enum RuntimeCosts {
CopyToContract(u32),
/// Weight of calling `seal_caller`.
Caller,
/// Weight of calling `seal_origin`.
Origin,
/// Weight of calling `seal_is_contract`.
IsContract,
/// Weight of calling `seal_code_hash`.
Expand Down Expand Up @@ -448,6 +450,7 @@ impl<T: Config> Token<T> for RuntimeCosts {
CopyToContract(len) => T::WeightInfo::seal_input(len),
CopyFromContract(len) => T::WeightInfo::seal_return(len),
Caller => T::WeightInfo::seal_caller(),
Origin => T::WeightInfo::seal_origin(),
IsContract => T::WeightInfo::seal_is_contract(),
CodeHash => T::WeightInfo::seal_code_hash(),
OwnCodeHash => T::WeightInfo::seal_own_code_hash(),
Expand Down Expand Up @@ -1388,6 +1391,21 @@ pub mod env {
)?)
}

/// Stores the address of the call stack origin into the supplied buffer.
/// See [`pallet_revive_uapi::HostFn::origin`].
#[api_version(0)]
fn origin(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
self.charge_gas(RuntimeCosts::Origin)?;
let origin = <E::T as Config>::AddressMapper::to_address(self.ext.origin().account_id()?);
Ok(self.write_fixed_sandbox_output(
memory,
out_ptr,
origin.as_bytes(),
false,
already_charged,
)?)
}

/// Checks whether a specified address belongs to a contract.
/// See [`pallet_revive_uapi::HostFn::is_contract`].
#[api_version(0)]
Expand Down
Loading

0 comments on commit 35535ef

Please sign in to comment.