contracts: Make Origin information available (#13708)

* contracts: allow root calls

* contracts: update ContractOrigin

* contracts: test allow root calls

* contracts: rustfmt

* contracts: test root caller traps

* contracts: add Caller enum

* contracts: improve some comments

* contracts: improve some comments

* contracts: format code with +nightly

* contracts: fix failing tests

* contracts: improve charte instantiate call when root origin

* contract: refactor common, call and instantiate inputs

* contracts: fix some failing test

* contracts: trap caller when there is no account id

* Update frame/contracts/src/lib.rs

Co-authored-by: PG Herveou <pgherveou@gmail.com>

* Update frame/contracts/src/exec.rs

Co-authored-by: PG Herveou <pgherveou@gmail.com>

* contracts: make `into_deposit` return zero when the origin is root

* contracts: cargo fmt

* contracts: charging and terminating tests refactored

* contracts: improved errors

* contracts: refactor & merge ContractOrigin cand Caller enums

* Update frame/contracts/src/lib.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* contracts: apply suggested pr changes

* contracts: rename Caller to Origin

* contracts: refactor run fn

* contracts: cr improvements

* contracts: allow root to use delegate call

* contracts: add caller_is_root weight

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* contracts: add caller_is_root benchmarking

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* contracts: add caller_is_root benchmarking

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* contracts: add some minor improvements

* contracts: make caller_is_root runtime fn unstable

* contracts: fix failing wasm test

* contracts: update benchmarking for origin_is_root

* contracts: improve seal_caller_is_root benchmarking

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* contracts: add small pr improvements

* contracts: fix broken tests after master merge

* contracts: acknowledge the default storage deposit limit

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* contracts: move origin to CommonInput

* contracts: add some extra tests

* contracts: move ensure origin logic inside invokable::run_guarded

* contracts: add minor improvements

* contracts: fix caller_is_root benchmarking

* contracts: improve function description

* Update frame/contracts/src/lib.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Update frame/contracts/src/lib.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Update frame/contracts/src/wasm/runtime.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* contracts: improve function description

---------

Co-authored-by: parity-processbot <>
Co-authored-by: PG Herveou <pgherveou@gmail.com>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
juangirini
2023-05-02 18:45:14 +02:00
committed by GitHub
parent 0e28645153
commit 5db4119f93
10 changed files with 1835 additions and 1234 deletions
@@ -534,6 +534,28 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
#[pov_mode = Measured]
seal_caller_is_root {
let r in 0 .. API_BENCHMARK_RUNS;
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "caller_is_root",
params: vec![],
return_type: Some(ValueType::I32),
}],
call_body: Some(body::repeated(r, &[
Instruction::Call(0),
Instruction::Drop,
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![])?;
let origin = RawOrigin::Root;
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
#[pov_mode = Measured]
seal_address {
let r in 0 .. API_BENCHMARK_RUNS;
+313 -110
View File
@@ -19,7 +19,7 @@ use crate::{
gas::GasMeter,
storage::{self, DepositAccount, WriteOutcome},
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error,
Event, Nonce, Pallet as Contracts, Schedule, System, LOG_TARGET,
Event, Nonce, Origin, Pallet as Contracts, Schedule, System, LOG_TARGET,
};
use frame_support::{
crypto::ecdsa::ECDSAExt,
@@ -203,8 +203,8 @@ pub trait Ext: sealing::Sealed {
take_old: bool,
) -> Result<WriteOutcome, DispatchError>;
/// Returns a reference to the account id of the caller.
fn caller(&self) -> &AccountIdOf<Self::T>;
/// Returns the caller.
fn caller(&self) -> Origin<Self::T>;
/// Check if a contract lives at the specified `address`.
fn is_contract(&self, address: &AccountIdOf<Self::T>) -> bool;
@@ -223,6 +223,9 @@ pub trait Ext: sealing::Sealed {
/// However, this function does not require any storage lookup and therefore uses less weight.
fn caller_is_origin(&self) -> bool;
/// Check if the caller is origin, and this origin is root.
fn caller_is_root(&self) -> bool;
/// Returns a reference to the account id of the current contract.
fn address(&self) -> &AccountIdOf<Self::T>;
@@ -373,14 +376,15 @@ pub trait Executable<T: Config>: Sized {
/// This type implements `Ext` and by that exposes the business logic of contract execution to
/// the runtime module which interfaces with the contract (the wasm blob) itself.
pub struct Stack<'a, T: Config, E> {
/// The account id of a plain account that initiated the call stack.
/// The origin that initiated the call stack. It could either be a Signed plain account that
/// holds an account id or Root.
///
/// # Note
///
/// Please note that it is possible that the id belongs to a contract rather than a plain
/// account when being called through one of the contract RPCs where the client can freely
/// choose the origin. This usually makes no sense but is still possible.
origin: T::AccountId,
/// Please note that it is possible that the id of a Signed origin belongs to a contract rather
/// than a plain account when being called through one of the contract RPCs where the
/// client can freely choose the origin. This usually makes no sense but is still possible.
origin: Origin<T>,
/// The cost schedule used when charging from the gas meter.
schedule: &'a Schedule<T>,
/// The gas meter where costs are charged to.
@@ -436,15 +440,15 @@ pub struct Frame<T: Config> {
/// If `false` the contract enabled its defense against reentrance attacks.
allows_reentry: bool,
/// The caller of the currently executing frame which was spawned by `delegate_call`.
delegate_caller: Option<T::AccountId>,
delegate_caller: Option<Origin<T>>,
}
/// Used in a delegate call frame arguments in order to override the executable and caller.
struct DelegatedCall<T: Config, E> {
/// The executable which is run instead of the contracts own `executable`.
executable: E,
/// The account id of the caller contract.
caller: T::AccountId,
/// The caller of the contract.
caller: Origin<T>,
}
/// Parameter passed in when creating a new `Frame`.
@@ -617,7 +621,7 @@ where
///
/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
pub fn run_call(
origin: T::AccountId,
origin: Origin<T>,
dest: T::AccountId,
gas_meter: &'a mut GasMeter<T>,
storage_meter: &'a mut storage::meter::Meter<T>,
@@ -669,7 +673,7 @@ where
salt,
input_data: input_data.as_ref(),
},
origin,
Origin::from_account_id(origin),
gas_meter,
storage_meter,
schedule,
@@ -684,7 +688,7 @@ where
/// Create a new call stack.
fn new(
args: FrameArgs<T, E>,
origin: T::AccountId,
origin: Origin<T>,
gas_meter: &'a mut GasMeter<T>,
storage_meter: &'a mut storage::meter::Meter<T>,
schedule: &'a Schedule<T>,
@@ -846,9 +850,12 @@ where
// We need to charge the storage deposit before the initial transfer so that
// it can create the account in case the initial transfer is < ed.
if entry_point == ExportedFunction::Constructor {
// Root origin can't be used to instantiate a contract, so it is safe to assume that
// if we reached this point the origin has an associated account.
let origin = &self.origin.account_id()?;
let frame = top_frame_mut!(self);
frame.nested_storage.charge_instantiate(
&self.origin,
origin,
&frame.account_id,
frame.contract_info.get(&frame.account_id),
)?;
@@ -894,13 +901,12 @@ where
let contract = frame.contract_info.as_contract();
frame.nested_storage.enforce_subcall_limit(contract)?;
let caller = self.caller().account_id()?.clone();
// Deposit an instantiation event.
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(self.caller()), T::Hashing::hash_of(account_id)],
Event::Instantiated {
deployer: self.caller().clone(),
contract: account_id.clone(),
},
vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(account_id)],
Event::Instantiated { deployer: caller, contract: account_id.clone() },
);
},
(ExportedFunction::Call, Some(code_hash)) => {
@@ -918,7 +924,7 @@ where
let caller = self.caller();
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(caller), T::Hashing::hash_of(&account_id)],
vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(&account_id)],
Event::Called { caller: caller.clone(), contract: account_id.clone() },
);
},
@@ -1076,7 +1082,16 @@ where
}
let value = frame.value_transferred;
Self::transfer(ExistenceRequirement::KeepAlive, self.caller(), &frame.account_id, value)
// Get the account id from the caller.
// If the caller is root there is no account to transfer from, and therefore we can't take
// any `value` other than 0.
let caller = match self.caller() {
Origin::Signed(caller) => caller,
Origin::Root if value.is_zero() => return Ok(()),
Origin::Root => return DispatchError::RootNotAllowed.into(),
};
Self::transfer(ExistenceRequirement::KeepAlive, &caller, &frame.account_id, value)
}
/// Reference to the current (top) frame.
@@ -1282,11 +1297,14 @@ where
&self.top_frame().account_id
}
fn caller(&self) -> &T::AccountId {
fn caller(&self) -> Origin<T> {
if let Some(caller) = &self.top_frame().delegate_caller {
caller
caller.clone()
} else {
self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin)
self.frames()
.nth(1)
.map(|f| Origin::from_account_id(f.account_id.clone()))
.unwrap_or(self.origin.clone())
}
}
@@ -1303,7 +1321,12 @@ where
}
fn caller_is_origin(&self) -> bool {
self.caller() == &self.origin
self.origin == self.caller()
}
fn caller_is_root(&self) -> bool {
// if the caller isn't origin, then it can't be root.
self.caller_is_origin() && self.origin == Origin::Root
}
fn balance(&self) -> BalanceOf<T> {
@@ -1637,11 +1660,13 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, exec_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), value).unwrap();
let mut storage_meter =
storage::meter::Meter::new(&Origin::from_account_id(ALICE), Some(0), value)
.unwrap();
assert_matches!(
MockStack::run_call(
ALICE,
Origin::from_account_id(ALICE),
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -1692,10 +1717,12 @@ mod tests {
place_contract(&dest, success_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), value).unwrap();
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap();
let _ = MockStack::run_call(
origin.clone(),
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -1734,10 +1761,12 @@ mod tests {
place_contract(&dest, delegate_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap();
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap();
let _ = MockStack::run_call(
origin.clone(),
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -1770,10 +1799,12 @@ mod tests {
place_contract(&dest, return_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap();
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap();
let output = MockStack::run_call(
origin.clone(),
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -1821,11 +1852,13 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(origin);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
place_contract(&BOB, return_ch);
let result = MockStack::run_call(
origin,
contract_origin,
dest,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -1855,10 +1888,12 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(origin);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
origin,
contract_origin,
dest,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -1886,10 +1921,12 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, input_data_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -1918,7 +1955,9 @@ mod tests {
let executable =
MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, min_balance).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
let result = MockStack::run_instantiate(
ALICE,
@@ -1966,10 +2005,12 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
set_balance(&BOB, 1);
place_contract(&BOB, recurse_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), value).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -1996,7 +2037,9 @@ mod tests {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
// Record the caller for bob.
WitnessedCallerBob::mutate(|caller| *caller = Some(ctx.ext.caller().clone()));
WitnessedCallerBob::mutate(|caller| {
*caller = Some(ctx.ext.caller().account_id().unwrap().clone())
});
// Call into CHARLIE contract.
assert_matches!(
@@ -2008,7 +2051,9 @@ mod tests {
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
// Record the caller for charlie.
WitnessedCallerCharlie::mutate(|caller| *caller = Some(ctx.ext.caller().clone()));
WitnessedCallerCharlie::mutate(|caller| {
*caller = Some(ctx.ext.caller().account_id().unwrap().clone())
});
exec_success()
});
@@ -2016,10 +2061,12 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&dest, bob_ch);
place_contract(&CHARLIE, charlie_ch);
let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
origin.clone(),
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2051,9 +2098,11 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2080,10 +2129,12 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// ALICE (not contract) -> BOB (contract)
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2108,10 +2159,12 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// ALICE (not contract) -> BOB (contract)
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2145,10 +2198,111 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin)
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn root_caller_succeeds() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
// root is the origin of the call stack.
assert!(ctx.ext.caller_is_root());
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
let contract_origin = Origin::Root;
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// root -> BOB (caller is root)
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn root_caller_does_not_succeed_when_value_not_zero() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
// root is the origin of the call stack.
assert!(ctx.ext.caller_is_root());
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
let contract_origin = Origin::Root;
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// root -> BOB (caller is root)
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
1,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Err(_));
});
}
#[test]
fn root_caller_succeeds_with_consecutive_calls() {
let code_charlie = MockLoader::insert(Call, |ctx, _| {
// BOB is not root, even though the origin is root.
assert!(!ctx.ext.caller_is_root());
exec_success()
});
let code_bob = MockLoader::insert(Call, |ctx, _| {
// root is the origin of the call stack.
assert!(ctx.ext.caller_is_root());
// BOB calls CHARLIE.
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true)
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let contract_origin = Origin::Root;
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// root -> BOB (caller is root) -> CHARLIE (caller is not root)
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2185,10 +2339,12 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
place_contract(&CHARLIE, charlie_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2212,7 +2368,9 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
assert_matches!(
MockStack::run_instantiate(
@@ -2244,8 +2402,10 @@ mod tests {
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap();
storage::meter::Meter::new(&contract_origin, Some(min_balance * 100), min_balance)
.unwrap();
let instantiated_contract_address = assert_matches!(
MockStack::run_instantiate(
@@ -2288,8 +2448,10 @@ mod tests {
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap();
storage::meter::Meter::new(&contract_origin, Some(min_balance * 100), min_balance)
.unwrap();
let instantiated_contract_address = assert_matches!(
MockStack::run_instantiate(
@@ -2342,13 +2504,17 @@ mod tests {
let min_balance = <Test as Config>::Currency::minimum_balance();
set_balance(&ALICE, min_balance * 100);
place_contract(&BOB, instantiator_ch);
let mut storage_meter =
storage::meter::Meter::new(&ALICE, Some(min_balance * 10), min_balance * 10)
.unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(
&contract_origin,
Some(min_balance * 10),
min_balance * 10,
)
.unwrap();
assert_matches!(
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2374,7 +2540,7 @@ mod tests {
&events(),
&[
Event::Instantiated { deployer: BOB, contract: instantiated_contract_address },
Event::Called { caller: ALICE, contract: BOB },
Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },
]
);
});
@@ -2410,11 +2576,13 @@ mod tests {
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
place_contract(&BOB, instantiator_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(200), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(200), 0).unwrap();
assert_matches!(
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2429,7 +2597,10 @@ mod tests {
// The contract wasn't instantiated so we don't expect to see an instantiation
// event here.
assert_eq!(&events(), &[Event::Called { caller: ALICE, contract: BOB },]);
assert_eq!(
&events(),
&[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },]
);
});
}
@@ -2446,7 +2617,9 @@ mod tests {
let executable =
MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, 10_000);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 100).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, 100).unwrap();
assert_eq!(
MockStack::run_instantiate(
@@ -2510,10 +2683,12 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2544,7 +2719,9 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, min_balance).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
let result = MockStack::run_instantiate(
ALICE,
@@ -2577,9 +2754,11 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -2611,9 +2790,11 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -2648,9 +2829,11 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -2679,11 +2862,13 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// Calling another contract should succeed
assert_ok!(MockStack::run_call(
ALICE,
contract_origin.clone(),
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2697,7 +2882,7 @@ mod tests {
// Calling into oneself fails
assert_err!(
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2733,12 +2918,14 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
// BOB -> CHARLIE -> BOB fails as BOB denies reentry.
assert_err!(
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -2770,10 +2957,12 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
System::reset_events();
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -2800,10 +2989,10 @@ mod tests {
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: BOB,
}),
topics: vec![hash(&ALICE), hash(&BOB)],
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&BOB)],
},
]
);
@@ -2855,10 +3044,12 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
System::reset_events();
MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -2898,10 +3089,10 @@ mod tests {
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: BOB,
}),
topics: vec![hash(&ALICE), hash(&BOB)],
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&BOB)],
},
]
);
@@ -2959,8 +3150,9 @@ mod tests {
let succ_succ_executable =
MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&ALICE, None, min_balance * 100).unwrap();
storage::meter::Meter::new(&contract_origin, None, min_balance * 100).unwrap();
MockStack::run_instantiate(
ALICE,
@@ -3069,9 +3261,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -3196,9 +3389,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -3235,9 +3429,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -3274,9 +3469,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -3330,9 +3526,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -3386,9 +3583,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -3418,9 +3616,11 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
@@ -3480,9 +3680,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
@@ -3510,9 +3711,11 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_hash);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
ALICE,
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
+84 -27
View File
@@ -97,7 +97,6 @@ pub mod weights;
#[cfg(test)]
mod tests;
use crate::{
exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack},
gas::GasMeter,
@@ -105,19 +104,20 @@ use crate::{
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
weights::WeightInfo,
};
use codec::{Codec, Encode, HasCompact};
use codec::{Codec, Decode, Encode, HasCompact};
use environmental::*;
use frame_support::{
dispatch::{Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo},
dispatch::{DispatchError, Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo, RawOrigin},
ensure,
error::BadOrigin,
traits::{
tokens::fungible::Inspect, ConstU32, Contains, Currency, Get, Randomness,
ReservableCurrency, Time,
},
weights::Weight,
BoundedVec, WeakBoundedVec,
BoundedVec, RuntimeDebugNoBound, WeakBoundedVec,
};
use frame_system::Pallet as System;
use frame_system::{ensure_signed, pallet_prelude::OriginFor, Pallet as System};
use pallet_contracts_primitives::{
Code, CodeUploadResult, CodeUploadReturnValue, ContractAccessError, ContractExecResult,
ContractInstantiateResult, ExecReturnValue, GetStorageResult, InstantiateReturnValue,
@@ -584,17 +584,15 @@ pub mod pallet {
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo {
let gas_limit: Weight = gas_limit.into();
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let common = CommonInput {
origin,
origin: Origin::from_runtime_origin(origin)?,
value,
data,
gas_limit,
gas_limit: gas_limit.into(),
storage_deposit_limit: storage_deposit_limit.map(Into::into),
debug_message: None,
};
let dest = T::Lookup::lookup(dest)?;
let mut output =
CallInput::<T> { dest, determinism: Determinism::Enforced }.run_guarded(common);
if let Ok(retval) = &output.result {
@@ -645,12 +643,11 @@ pub mod pallet {
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let code_len = code.len() as u32;
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
let common = CommonInput {
origin,
origin: Origin::from_runtime_origin(origin)?,
value,
data,
gas_limit,
@@ -688,11 +685,10 @@ pub mod pallet {
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
let common = CommonInput {
origin,
origin: Origin::from_runtime_origin(origin)?,
value,
data,
gas_limit,
@@ -764,8 +760,8 @@ pub mod pallet {
/// calls. This is because on failure all storage changes including events are
/// rolled back.
Called {
/// The account that called the `contract`.
caller: T::AccountId,
/// The caller of the `contract`.
caller: Origin<T>,
/// The contract that was called.
contract: T::AccountId,
},
@@ -924,9 +920,38 @@ pub mod pallet {
StorageValue<_, DeletionQueueManager<T>, ValueQuery>;
}
/// The type of origins supported by the contracts pallet.
#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebugNoBound)]
pub enum Origin<T: Config> {
Root,
Signed(T::AccountId),
}
impl<T: Config> Origin<T> {
/// Creates a new Signed Caller from an AccountId.
pub fn from_account_id(account_id: T::AccountId) -> Self {
Origin::Signed(account_id)
}
/// Creates a new Origin from a `RuntimeOrigin`.
pub fn from_runtime_origin(o: OriginFor<T>) -> Result<Self, DispatchError> {
match o.into() {
Ok(RawOrigin::Root) => Ok(Self::Root),
Ok(RawOrigin::Signed(t)) => Ok(Self::Signed(t)),
_ => Err(BadOrigin.into()),
}
}
/// Returns the AccountId of a Signed Origin or an error if the origin is Root.
pub fn account_id(&self) -> Result<&T::AccountId, DispatchError> {
match self {
Origin::Signed(id) => Ok(id),
Origin::Root => Err(DispatchError::RootNotAllowed),
}
}
}
/// Context of a contract invocation.
struct CommonInput<'a, T: Config> {
origin: T::AccountId,
origin: Origin<T>,
value: BalanceOf<T>,
data: Vec<u8>,
gas_limit: Weight,
@@ -975,6 +1000,19 @@ trait Invokable<T: Config> {
environmental!(executing_contract: bool);
let gas_limit = common.gas_limit;
// Check whether the origin is allowed here. The logic of the access rules
// is in the `ensure_origin`, this could vary for different implementations of this
// trait. For example, some actions might not allow Root origin as they could require an
// AccountId associated with the origin.
if let Err(e) = self.ensure_origin(common.origin.clone()) {
return InternalOutput {
gas_meter: GasMeter::new(gas_limit),
storage_deposit: Default::default(),
result: Err(ExecError { error: e.into(), origin: ErrorOrigin::Caller }),
}
}
executing_contract::using_once(&mut false, || {
executing_contract::with(|f| {
// Fail if already entered contract execution
@@ -1010,6 +1048,11 @@ trait Invokable<T: Config> {
common: CommonInput<T>,
gas_meter: GasMeter<T>,
) -> InternalOutput<T, Self::Output>;
/// This method ensures that the given `origin` is allowed to invoke the current `Invokable`.
///
/// Called by dispatchables and public functions through the [`Invokable::run_guarded`].
fn ensure_origin(&self, origin: Origin<T>) -> Result<(), DispatchError>;
}
impl<T: Config> Invokable<T> for CallInput<T> {
@@ -1020,8 +1063,10 @@ impl<T: Config> Invokable<T> for CallInput<T> {
common: CommonInput<T>,
mut gas_meter: GasMeter<T>,
) -> InternalOutput<T, Self::Output> {
let CallInput { dest, determinism } = self;
let CommonInput { origin, value, data, debug_message, .. } = common;
let mut storage_meter =
match StorageMeter::new(&common.origin, common.storage_deposit_limit, common.value) {
match StorageMeter::new(&origin, common.storage_deposit_limit, common.value) {
Ok(meter) => meter,
Err(err) =>
return InternalOutput {
@@ -1031,8 +1076,6 @@ impl<T: Config> Invokable<T> for CallInput<T> {
},
};
let schedule = T::Schedule::get();
let CallInput { dest, determinism } = self;
let CommonInput { origin, value, data, debug_message, .. } = common;
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
origin.clone(),
dest.clone(),
@@ -1054,6 +1097,10 @@ impl<T: Config> Invokable<T> for CallInput<T> {
},
}
}
fn ensure_origin(&self, _origin: Origin<T>) -> Result<(), DispatchError> {
Ok(())
}
}
impl<T: Config> Invokable<T> for InstantiateInput<T> {
@@ -1067,12 +1114,15 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
let mut storage_deposit = Default::default();
let try_exec = || {
let schedule = T::Schedule::get();
let InstantiateInput { salt, .. } = self;
let CommonInput { origin: contract_origin, .. } = common;
let origin = contract_origin.account_id()?;
let (extra_deposit, executable) = match &self.code {
Code::Upload(binary) => {
let executable = PrefabWasmModule::from_code(
binary.clone(),
&schedule,
common.origin.clone(),
origin.clone(),
Determinism::Enforced,
TryInstantiate::Skip,
)
@@ -1094,14 +1144,13 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
PrefabWasmModule::from_storage(*hash, &schedule, &mut gas_meter)?,
),
};
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter = StorageMeter::new(
&common.origin,
&contract_origin,
common.storage_deposit_limit,
common.value.saturating_add(extra_deposit),
)?;
let InstantiateInput { salt, .. } = self;
let CommonInput { origin, value, data, debug_message, .. } = common;
let CommonInput { value, data, debug_message, .. } = common;
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin.clone(),
executable,
@@ -1115,12 +1164,19 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
);
storage_deposit = storage_meter
.try_into_deposit(&origin)?
.try_into_deposit(&contract_origin)?
.saturating_add(&StorageDeposit::Charge(extra_deposit));
result
};
InternalOutput { result: try_exec(), gas_meter, storage_deposit }
}
fn ensure_origin(&self, origin: Origin<T>) -> Result<(), DispatchError> {
match origin {
Origin::Signed(_) => Ok(()),
Origin::Root => Err(DispatchError::RootNotAllowed),
}
}
}
impl<T: Config> Pallet<T> {
@@ -1147,6 +1203,7 @@ impl<T: Config> Pallet<T> {
determinism: Determinism,
) -> ContractExecResult<BalanceOf<T>> {
let mut debug_message = if debug { Some(DebugBufferVec::<T>::default()) } else { None };
let origin = Origin::from_account_id(origin);
let common = CommonInput {
origin,
value,
@@ -1189,7 +1246,7 @@ impl<T: Config> Pallet<T> {
) -> ContractInstantiateResult<T::AccountId, BalanceOf<T>> {
let mut debug_message = if debug { Some(DebugBufferVec::<T>::default()) } else { None };
let common = CommonInput {
origin,
origin: Origin::from_account_id(origin),
value,
data,
gas_limit,
@@ -260,6 +260,9 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_caller_is_origin`.
pub caller_is_origin: Weight,
/// Weight of calling `seal_caller_is_root`.
pub caller_is_root: Weight,
/// Weight of calling `seal_address`.
pub address: Weight,
@@ -557,6 +560,7 @@ impl<T: Config> Default for HostFnWeights<T> {
code_hash: cost!(seal_code_hash),
own_code_hash: cost!(seal_own_code_hash),
caller_is_origin: cost!(seal_caller_is_origin),
caller_is_root: cost!(seal_caller_is_root),
address: cost!(seal_address),
gas_left: cost!(seal_gas_left),
balance: cost!(seal_balance),
+164 -103
View File
@@ -19,7 +19,7 @@
use crate::{
storage::{ContractInfo, DepositAccount},
BalanceOf, Config, Error, Inspect, Pallet, System,
BalanceOf, Config, Error, Inspect, Origin, Pallet, System,
};
use codec::Encode;
use frame_support::{
@@ -352,12 +352,21 @@ where
///
/// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible.
pub fn new(
origin: &T::AccountId,
origin: &Origin<T>,
limit: Option<BalanceOf<T>>,
min_leftover: BalanceOf<T>,
) -> Result<Self, DispatchError> {
let limit = E::check_limit(origin, limit, min_leftover)?;
Ok(Self { limit, ..Default::default() })
// Check the limit only if the origin is not root.
return match origin {
Origin::Root => Ok(Self {
limit: limit.unwrap_or(T::DefaultDepositLimit::get()),
..Default::default()
}),
Origin::Signed(o) => {
let limit = E::check_limit(o, limit, min_leftover)?;
Ok(Self { limit, ..Default::default() })
},
}
}
/// The total amount of deposit that should change hands as result of the execution
@@ -366,7 +375,13 @@ where
///
/// This drops the root meter in order to make sure it is only called when the whole
/// execution did finish.
pub fn try_into_deposit(self, origin: &T::AccountId) -> Result<DepositOf<T>, DispatchError> {
pub fn try_into_deposit(self, origin: &Origin<T>) -> Result<DepositOf<T>, DispatchError> {
// Only refund or charge deposit if the origin is not root.
let origin = match origin {
Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
Origin::Signed(o) => o,
};
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated)?;
}
@@ -625,6 +640,12 @@ mod tests {
TestExtTestValue::mutate(|ext| ext.clear())
}
struct ChargingTestCase {
origin: Origin<Test>,
deposit: DepositOf<Test>,
expected: TestExt,
}
#[derive(Default)]
struct StorageInfo {
bytes: u32,
@@ -650,7 +671,7 @@ mod tests {
fn new_reserves_balance_works() {
clear_ext();
TestMeter::new(&ALICE, Some(1_000), 0).unwrap();
TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
assert_eq!(
TestExtTestValue::get(),
@@ -665,7 +686,7 @@ mod tests {
fn empty_charge_works() {
clear_ext();
let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap();
let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
assert_eq!(meter.available(), 1_000);
// an empty charge does not create a `Charge` entry
@@ -684,118 +705,158 @@ mod tests {
#[test]
fn charging_works() {
clear_ext();
let test_cases = vec![
ChargingTestCase {
origin: Origin::<Test>::from_account_id(ALICE),
deposit: Deposit::Refund(28),
expected: TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }],
charges: vec![
Charge {
origin: ALICE,
contract: DepositAccount(CHARLIE),
amount: Deposit::Refund(10),
terminated: false,
},
Charge {
origin: ALICE,
contract: DepositAccount(CHARLIE),
amount: Deposit::Refund(20),
terminated: false,
},
Charge {
origin: ALICE,
contract: DepositAccount(BOB),
amount: Deposit::Charge(2),
terminated: false,
},
],
},
},
ChargingTestCase {
origin: Origin::<Test>::Root,
deposit: Deposit::Charge(0),
expected: TestExt { limit_checks: vec![], charges: vec![] },
},
];
let mut meter = TestMeter::new(&ALICE, Some(100), 0).unwrap();
assert_eq!(meter.available(), 100);
for test_case in test_cases {
clear_ext();
let mut nested0_info =
new_info(StorageInfo { bytes: 100, items: 5, bytes_deposit: 100, items_deposit: 10 });
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 108,
bytes_removed: 5,
items_added: 1,
items_removed: 2,
});
nested0.charge(&Diff { bytes_removed: 99, ..Default::default() });
let mut meter = TestMeter::new(&test_case.origin, Some(100), 0).unwrap();
assert_eq!(meter.available(), 100);
let mut nested1_info =
new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 });
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested0.absorb(nested1, DepositAccount(CHARLIE), Some(&mut nested1_info));
let mut nested0_info = new_info(StorageInfo {
bytes: 100,
items: 5,
bytes_deposit: 100,
items_deposit: 10,
});
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 108,
bytes_removed: 5,
items_added: 1,
items_removed: 2,
});
nested0.charge(&Diff { bytes_removed: 99, ..Default::default() });
let mut nested2_info =
new_info(StorageInfo { bytes: 100, items: 7, bytes_deposit: 100, items_deposit: 20 });
let mut nested2 = nested0.nested(BalanceOf::<Test>::zero());
nested2.charge(&Diff { items_removed: 7, ..Default::default() });
nested0.absorb(nested2, DepositAccount(CHARLIE), Some(&mut nested2_info));
let mut nested1_info = new_info(StorageInfo {
bytes: 100,
items: 10,
bytes_deposit: 100,
items_deposit: 20,
});
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested0.absorb(nested1, DepositAccount(CHARLIE), Some(&mut nested1_info));
nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
meter.absorb(nested0, DepositAccount(BOB), Some(&mut nested0_info));
let mut nested2_info = new_info(StorageInfo {
bytes: 100,
items: 7,
bytes_deposit: 100,
items_deposit: 20,
});
let mut nested2 = nested0.nested(BalanceOf::<Test>::zero());
nested2.charge(&Diff { items_removed: 7, ..Default::default() });
nested0.absorb(nested2, DepositAccount(CHARLIE), Some(&mut nested2_info));
meter.try_into_deposit(&ALICE).unwrap();
nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
meter.absorb(nested0, DepositAccount(BOB), Some(&mut nested0_info));
assert_eq!(nested0_info.extra_deposit(), 112);
assert_eq!(nested1_info.extra_deposit(), 110);
assert_eq!(nested2_info.extra_deposit(), 100);
assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
assert_eq!(
TestExtTestValue::get(),
TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }],
charges: vec![
Charge {
origin: ALICE,
contract: DepositAccount(CHARLIE),
amount: Deposit::Refund(10),
terminated: false
},
Charge {
origin: ALICE,
contract: DepositAccount(CHARLIE),
amount: Deposit::Refund(20),
terminated: false
},
Charge {
origin: ALICE,
contract: DepositAccount(BOB),
amount: Deposit::Charge(2),
terminated: false
}
]
}
)
assert_eq!(nested0_info.extra_deposit(), 112);
assert_eq!(nested1_info.extra_deposit(), 110);
assert_eq!(nested2_info.extra_deposit(), 100);
assert_eq!(TestExtTestValue::get(), test_case.expected)
}
}
#[test]
fn termination_works() {
clear_ext();
let test_cases = vec![
ChargingTestCase {
origin: Origin::<Test>::from_account_id(ALICE),
deposit: Deposit::Refund(107),
expected: TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
charges: vec![
Charge {
origin: ALICE,
contract: DepositAccount(CHARLIE),
amount: Deposit::Refund(119),
terminated: true,
},
Charge {
origin: ALICE,
contract: DepositAccount(BOB),
amount: Deposit::Charge(12),
terminated: false,
},
],
},
},
ChargingTestCase {
origin: Origin::<Test>::Root,
deposit: Deposit::Charge(0),
expected: TestExt { limit_checks: vec![], charges: vec![] },
},
];
let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap();
assert_eq!(meter.available(), 1_000);
for test_case in test_cases {
clear_ext();
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 5,
bytes_removed: 1,
items_added: 3,
items_removed: 1,
});
nested0.charge(&Diff { items_added: 2, ..Default::default() });
let mut meter = TestMeter::new(&test_case.origin, Some(1_000), 0).unwrap();
assert_eq!(meter.available(), 1_000);
let mut nested1_info =
new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 });
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
nested1.terminate(&nested1_info);
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
nested0.absorb(nested1, DepositAccount(CHARLIE), None);
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 5,
bytes_removed: 1,
items_added: 3,
items_removed: 1,
});
nested0.charge(&Diff { items_added: 2, ..Default::default() });
meter.absorb(nested0, DepositAccount(BOB), None);
meter.try_into_deposit(&ALICE).unwrap();
let mut nested1_info = new_info(StorageInfo {
bytes: 100,
items: 10,
bytes_deposit: 100,
items_deposit: 20,
});
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
nested1.terminate(&nested1_info);
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
nested0.absorb(nested1, DepositAccount(CHARLIE), None);
assert_eq!(
TestExtTestValue::get(),
TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
charges: vec![
Charge {
origin: ALICE,
contract: DepositAccount(CHARLIE),
amount: Deposit::Refund(119),
terminated: true
},
Charge {
origin: ALICE,
contract: DepositAccount(BOB),
amount: Deposit::Charge(12),
terminated: false
}
]
}
)
meter.absorb(nested0, DepositAccount(BOB), None);
assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
assert_eq!(TestExtTestValue::get(), test_case.expected)
}
}
}
+151 -18
View File
@@ -28,13 +28,13 @@ use crate::{
wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode},
weights::WeightInfo,
BalanceOf, Code, CodeStorage, Config, ContractInfo, ContractInfoOf, DefaultAddressGenerator,
DeletionQueueCounter, Error, Pallet, Schedule,
DeletionQueueCounter, Error, Origin, Pallet, Schedule,
};
use assert_matches::assert_matches;
use codec::Encode;
use frame_support::{
assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok,
dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
dispatch::{DispatchError, DispatchErrorWithPostInfo, PostDispatchInfo},
parameter_types,
storage::child,
traits::{
@@ -517,6 +517,11 @@ impl<'a> From<ExtensionInput<'a>> for Vec<u8> {
}
}
impl Default for Origin<Test> {
fn default() -> Self {
Self::Signed(ALICE)
}
}
// Perform a call to a plain account.
// The actual transfer fails because we can only call contracts.
// Then we check that at least the base costs where charged (no runtime gas costs.)
@@ -986,18 +991,21 @@ fn deploy_and_call_other_contract() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: caller_addr.clone(),
caller: Origin::from_account_id(caller_addr.clone()),
contract: callee_addr.clone(),
}),
topics: vec![hash(&caller_addr), hash(&callee_addr)],
topics: vec![
hash(&Origin::<Test>::from_account_id(caller_addr.clone())),
hash(&callee_addr)
],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: caller_addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&caller_addr)],
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&caller_addr)],
},
]
);
@@ -1305,10 +1313,10 @@ fn self_destruct_works() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&addr)],
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&addr)],
},
EventRecord {
phase: Phase::Initialization,
@@ -3626,10 +3634,10 @@ fn storage_deposit_works() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&addr)],
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&addr)],
},
EventRecord {
phase: Phase::Initialization,
@@ -3643,10 +3651,10 @@ fn storage_deposit_works() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&addr)],
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&addr)],
},
EventRecord {
phase: Phase::Initialization,
@@ -3660,10 +3668,10 @@ fn storage_deposit_works() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&addr)],
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&addr)],
},
EventRecord {
phase: Phase::Initialization,
@@ -4115,18 +4123,24 @@ fn set_code_hash() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: contract_addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&contract_addr)],
topics: vec![
hash(&Origin::<Test>::from_account_id(ALICE)),
hash(&contract_addr)
],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
caller: Origin::from_account_id(ALICE),
contract: contract_addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&contract_addr)],
topics: vec![
hash(&Origin::<Test>::from_account_id(ALICE)),
hash(&contract_addr)
],
},
],
);
@@ -5015,3 +5029,122 @@ fn account_reentrance_count_works() {
assert_eq!(result2.data, 0.encode());
});
}
#[test]
fn root_cannot_upload_code() {
let (wasm, _) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().build().execute_with(|| {
assert_noop!(
Contracts::upload_code(RuntimeOrigin::root(), wasm, None, Determinism::Enforced),
DispatchError::BadOrigin,
);
});
}
#[test]
fn root_cannot_remove_code() {
let (_, code_hash) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().build().execute_with(|| {
assert_noop!(
Contracts::remove_code(RuntimeOrigin::root(), code_hash),
DispatchError::BadOrigin,
);
});
}
#[test]
fn signed_cannot_set_code() {
let (_, code_hash) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().build().execute_with(|| {
assert_noop!(
Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB, code_hash),
DispatchError::BadOrigin,
);
});
}
#[test]
fn none_cannot_call_code() {
ExtBuilder::default().build().execute_with(|| {
assert_noop!(
Contracts::call(RuntimeOrigin::none(), BOB, 0, GAS_LIMIT, None, Vec::new()),
DispatchError::BadOrigin,
);
});
}
#[test]
fn root_can_call() {
let (wasm, _) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let addr = Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm),
vec![],
vec![],
false,
)
.result
.unwrap()
.account_id;
// Call the contract.
assert_ok!(Contracts::call(
RuntimeOrigin::root(),
addr.clone(),
0,
GAS_LIMIT,
None,
vec![]
));
});
}
#[test]
fn root_cannot_instantiate_with_code() {
let (wasm, _) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().build().execute_with(|| {
assert_err_ignore_postinfo!(
Contracts::instantiate_with_code(
RuntimeOrigin::root(),
0,
GAS_LIMIT,
None,
wasm,
vec![],
vec![],
),
DispatchError::RootNotAllowed,
);
});
}
#[test]
fn root_cannot_instantiate() {
let (_, code_hash) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().build().execute_with(|| {
assert_err_ignore_postinfo!(
Contracts::instantiate(
RuntimeOrigin::root(),
0,
GAS_LIMIT,
None,
code_hash,
vec![],
vec![],
),
DispatchError::RootNotAllowed
);
});
}
+57 -3
View File
@@ -370,7 +370,7 @@ mod tests {
gas::GasMeter,
storage::WriteOutcome,
tests::{RuntimeCall, Test, ALICE, BOB},
BalanceOf, CodeHash, Error, Pallet as Contracts,
BalanceOf, CodeHash, Error, Origin, Pallet as Contracts,
};
use assert_matches::assert_matches;
use frame_support::{
@@ -436,6 +436,7 @@ mod tests {
ecdsa_recover: RefCell<Vec<([u8; 65], [u8; 32])>>,
sr25519_verify: RefCell<Vec<([u8; 64], Vec<u8>, [u8; 32])>>,
code_hashes: Vec<CodeHash<Test>>,
caller: Origin<Test>,
}
/// The call is mocked and just returns this hardcoded value.
@@ -459,6 +460,7 @@ mod tests {
gas_meter: GasMeter::new(Weight::from_parts(10_000_000_000, 10 * 1024 * 1024)),
debug_buffer: Default::default(),
ecdsa_recover: Default::default(),
caller: Default::default(),
sr25519_verify: Default::default(),
}
}
@@ -545,8 +547,8 @@ mod tests {
}
Ok(result)
}
fn caller(&self) -> &AccountIdOf<Self::T> {
&ALICE
fn caller(&self) -> Origin<Self::T> {
self.caller.clone()
}
fn is_contract(&self, _address: &AccountIdOf<Self::T>) -> bool {
true
@@ -561,6 +563,9 @@ mod tests {
fn caller_is_origin(&self) -> bool {
false
}
fn caller_is_root(&self) -> bool {
&self.caller == &Origin::Root
}
fn address(&self) -> &AccountIdOf<Self::T> {
&BOB
}
@@ -1499,6 +1504,16 @@ mod tests {
assert_ok!(execute(CODE_CALLER, vec![], MockExt::default()));
}
#[test]
fn caller_traps_when_no_account_id() {
let mut ext = MockExt::default();
ext.caller = Origin::Root;
assert_eq!(
execute(CODE_CALLER, vec![], ext),
Err(ExecError { error: DispatchError::RootNotAllowed, origin: ErrorOrigin::Caller })
);
}
/// calls `seal_address` and compares the result with the constant (BOB's address part).
const CODE_ADDRESS: &str = r#"
(module
@@ -2977,6 +2992,45 @@ mod tests {
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 0u32.encode() },);
}
#[test]
fn caller_is_root_works() {
const CODE_CALLER_IS_ROOT: &str = r#"
;; This runs `caller_is_root` check on zero account address
(module
(import "seal0" "caller_is_root" (func $caller_is_root (result i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 4) here the return code of the `caller_is_root` will be stored
;; we initialize it with non-zero value to be sure that it's being overwritten below
(data (i32.const 0) "\10\10\10\10")
(func (export "deploy"))
(func (export "call")
(i32.store
(i32.const 0)
(call $caller_is_root)
)
;; exit with success and take `caller_is_root` return code to the output buffer
(call $seal_return (i32.const 0) (i32.const 0) (i32.const 4))
)
)
"#;
// The default `caller` is ALICE. Therefore not root.
let output = execute(CODE_CALLER_IS_ROOT, vec![], MockExt::default()).unwrap();
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 0u32.encode() },);
// The caller is forced to be root instead of using the default ALICE.
let output = execute(
CODE_CALLER_IS_ROOT,
vec![],
MockExt { caller: Origin::Root, ..MockExt::default() },
)
.unwrap();
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 1u32.encode() },);
}
#[test]
fn set_code_hash() {
const CODE: &str = r#"
+23 -1
View File
@@ -191,6 +191,8 @@ pub enum RuntimeCosts {
OwnCodeHash,
/// Weight of calling `seal_caller_is_origin`.
CallerIsOrigin,
/// Weight of calling `caller_is_root`.
CallerIsRoot,
/// Weight of calling `seal_address`.
Address,
/// Weight of calling `seal_gas_left`.
@@ -283,6 +285,7 @@ impl RuntimeCosts {
CodeHash => s.code_hash,
OwnCodeHash => s.own_code_hash,
CallerIsOrigin => s.caller_is_origin,
CallerIsRoot => s.caller_is_root,
Address => s.address,
GasLeft => s.gas_left,
Balance => s.balance,
@@ -1750,14 +1753,18 @@ pub mod env {
/// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the
/// extrinsic will be returned. Otherwise, if this call is initiated by another contract then
/// the address of the contract will be returned. The value is encoded as T::AccountId.
///
/// If there is no address associated with the caller (e.g. because the caller is root) then
/// it traps with `BadOrigin`.
#[prefixed_alias]
fn caller(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
ctx.charge_gas(RuntimeCosts::Caller)?;
let caller = ctx.ext.caller().account_id()?.clone();
Ok(ctx.write_sandbox_output(
memory,
out_ptr,
out_len_ptr,
&ctx.ext.caller().encode(),
&caller.encode(),
false,
already_charged,
)?)
@@ -1856,6 +1863,21 @@ pub mod env {
Ok(ctx.ext.caller_is_origin() as u32)
}
/// Checks whether the caller of the current contract is root.
///
/// Note that only the origin of the call stack can be root. Hence this function returning
/// `true` implies that the contract is being called by the origin.
///
/// A return value of `true` indicates that this contract is being called by a root origin,
/// and `false` indicates that the caller is a signed origin.
///
/// Returned value is a `u32`-encoded boolean: (`0 = false`, `1 = true`).
#[unstable]
fn caller_is_root(ctx: _, _memory: _) -> Result<u32, TrapReason> {
ctx.charge_gas(RuntimeCosts::CallerIsRoot)?;
Ok(ctx.ext.caller_is_root() as u32)
}
/// Stores the address of the current contract into the supplied buffer.
///
/// The value is stored to linear memory at the address pointed to by `out_ptr`.
+1013 -972
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -553,6 +553,8 @@ pub enum DispatchError {
Corruption,
/// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later.
Unavailable,
/// Root origin is not allowed.
RootNotAllowed,
}
/// Result of a `Dispatchable` which contains the `DispatchResult` and additional information about
@@ -678,6 +680,7 @@ impl From<DispatchError> for &'static str {
Exhausted => "Resources exhausted",
Corruption => "State corrupt",
Unavailable => "Resource unavailable",
RootNotAllowed => "Root not allowed",
}
}
}
@@ -724,6 +727,7 @@ impl traits::Printable for DispatchError {
Exhausted => "Resources exhausted".print(),
Corruption => "State corrupt".print(),
Unavailable => "Resource unavailable".print(),
RootNotAllowed => "Root not allowed".print(),
}
}
}