mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 19:01:08 +00:00
contracts: is_contract(address) and caller_is_origin() are added to API (#10789)
* is_contract() and caller_is_origin() added to Ext API * is_contract() exposed in wasm runtime.rs * + test for is_contract() * + seal_is_contract benchmark * caller_is_origin() exposed to wasm/runtime.rs and covered by a test * + seal_caller_is_origin benchmark * Update frame/contracts/src/exec.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/src/exec.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/src/exec.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/src/wasm/runtime.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/src/wasm/runtime.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/src/wasm/runtime.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Update frame/contracts/src/exec.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * identation fix for benchmark macroses; test cosmetic improvement * benchmark fix * + is_contract() wasm test * + caller_is_origin() wasm test * Apply suggestions from code review Co-authored-by: Alexander Theißen <alex.theissen@me.com> * is_contract() to borrow param instead of taking ownership * phrasing improved Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * fixed wasm tests according to @athei feedback * dead code warnings suppressed by unstable-interface attributes * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> Co-authored-by: Parity Bot <admin@parity.io>
This commit is contained in:
committed by
GitHub
parent
89fa61125e
commit
d5c8593566
@@ -398,6 +398,63 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
seal_is_contract {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE)
|
||||
.map(|n| account::<T::AccountId>("account", n, 0))
|
||||
.collect::<Vec<_>>();
|
||||
let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0);
|
||||
let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::<Vec<_>>();
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "__unstable__",
|
||||
name: "seal_is_contract",
|
||||
params: vec![ValueType::I32],
|
||||
return_type: Some(ValueType::I32),
|
||||
}],
|
||||
data_segments: vec![
|
||||
DataSegment {
|
||||
offset: 0,
|
||||
value: accounts_bytes
|
||||
},
|
||||
],
|
||||
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
|
||||
Counter(0, account_len as u32), // address_ptr
|
||||
Regular(Instruction::Call(0)),
|
||||
Regular(Instruction::Drop),
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let info = instance.info()?;
|
||||
// every account would be a contract (worst case)
|
||||
for acc in accounts.iter() {
|
||||
<ContractInfoOf<T>>::insert(acc, info.clone());
|
||||
}
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
seal_caller_is_origin {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "__unstable__",
|
||||
name: "seal_caller_is_origin",
|
||||
params: vec![],
|
||||
return_type: Some(ValueType::I32),
|
||||
}],
|
||||
call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[
|
||||
Instruction::Call(0),
|
||||
Instruction::Drop,
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
seal_address {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let instance = Contract::<T>::new(WasmModule::getter(
|
||||
|
||||
@@ -158,6 +158,15 @@ pub trait Ext: sealing::Sealed {
|
||||
/// Returns a reference to the account id of the caller.
|
||||
fn caller(&self) -> &AccountIdOf<Self::T>;
|
||||
|
||||
/// Check if a contract lives at the specified `address`.
|
||||
fn is_contract(&self, address: &AccountIdOf<Self::T>) -> bool;
|
||||
|
||||
/// Check if the caller of the current contract is the origin of the whole call stack.
|
||||
///
|
||||
/// This can be checked with `is_contract(self.caller())` as well.
|
||||
/// However, this function does not require any storage lookup and therefore uses less weight.
|
||||
fn caller_is_origin(&self) -> bool;
|
||||
|
||||
/// Returns a reference to the account id of the current contract.
|
||||
fn address(&self) -> &AccountIdOf<Self::T>;
|
||||
|
||||
@@ -483,7 +492,7 @@ where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
E: Executable<T>,
|
||||
{
|
||||
/// Create an run a new call stack by calling into `dest`.
|
||||
/// Create and run a new call stack by calling into `dest`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
@@ -1024,6 +1033,14 @@ where
|
||||
self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin)
|
||||
}
|
||||
|
||||
fn is_contract(&self, address: &T::AccountId) -> bool {
|
||||
ContractInfoOf::<T>::contains_key(&address)
|
||||
}
|
||||
|
||||
fn caller_is_origin(&self) -> bool {
|
||||
self.caller() == &self.origin
|
||||
}
|
||||
|
||||
fn balance(&self) -> BalanceOf<T> {
|
||||
T::Currency::free_balance(&self.top_frame().account_id)
|
||||
}
|
||||
@@ -1620,6 +1637,70 @@ mod tests {
|
||||
WITNESSED_CALLER_CHARLIE.with(|caller| assert_eq!(*caller.borrow(), Some(dest)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_contract_returns_proper_values() {
|
||||
let bob_ch = MockLoader::insert(Call, |ctx, _| {
|
||||
// Verify that BOB is a contract
|
||||
assert!(ctx.ext.is_contract(&BOB));
|
||||
// Verify that ALICE is not a contract
|
||||
assert!(!ctx.ext.is_contract(&ALICE));
|
||||
exec_success()
|
||||
});
|
||||
|
||||
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 result = MockStack::run_call(
|
||||
ALICE,
|
||||
BOB,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caller_is_origin_returns_proper_values() {
|
||||
let code_charlie = MockLoader::insert(Call, |ctx, _| {
|
||||
// BOB is not the origin of the stack call
|
||||
assert!(!ctx.ext.caller_is_origin());
|
||||
exec_success()
|
||||
});
|
||||
|
||||
let code_bob = MockLoader::insert(Call, |ctx, _| {
|
||||
// ALICE is the origin of the call stack
|
||||
assert!(ctx.ext.caller_is_origin());
|
||||
// BOB calls CHARLIE
|
||||
ctx.ext.call(0, 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 mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
|
||||
// ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin)
|
||||
let result = MockStack::run_call(
|
||||
ALICE,
|
||||
BOB,
|
||||
&mut GasMeter::<Test>::new(GAS_LIMIT),
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
0,
|
||||
vec![0],
|
||||
None,
|
||||
);
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_returns_proper_values() {
|
||||
let bob_ch = MockLoader::insert(Call, |ctx, _| {
|
||||
|
||||
@@ -256,6 +256,12 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_caller`.
|
||||
pub caller: Weight,
|
||||
|
||||
/// Weight of calling `seal_is_contract`.
|
||||
pub is_contract: Weight,
|
||||
|
||||
/// Weight of calling `seal_caller_is_origin`.
|
||||
pub caller_is_origin: Weight,
|
||||
|
||||
/// Weight of calling `seal_address`.
|
||||
pub address: Weight,
|
||||
|
||||
@@ -571,6 +577,8 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
caller: cost_batched!(seal_caller),
|
||||
is_contract: cost_batched!(seal_is_contract),
|
||||
caller_is_origin: cost_batched!(seal_caller_is_origin),
|
||||
address: cost_batched!(seal_address),
|
||||
gas_left: cost_batched!(seal_gas_left),
|
||||
balance: cost_batched!(seal_balance),
|
||||
|
||||
@@ -409,6 +409,12 @@ mod tests {
|
||||
fn caller(&self) -> &AccountIdOf<Self::T> {
|
||||
&ALICE
|
||||
}
|
||||
fn is_contract(&self, _address: &AccountIdOf<Self::T>) -> bool {
|
||||
true
|
||||
}
|
||||
fn caller_is_origin(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn address(&self) -> &AccountIdOf<Self::T> {
|
||||
&BOB
|
||||
}
|
||||
@@ -2240,4 +2246,80 @@ mod tests {
|
||||
let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn is_contract_works() {
|
||||
const CODE_IS_CONTRACT: &str = r#"
|
||||
;; This runs `is_contract` check on zero account address
|
||||
(module
|
||||
(import "__unstable__" "seal_is_contract" (func $seal_is_contract (param i32) (result i32)))
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [0, 32) zero-adress
|
||||
(data (i32.const 0)
|
||||
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
|
||||
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
|
||||
)
|
||||
|
||||
;; [32, 36) here we store the return code of the `seal_is_contract`
|
||||
|
||||
(func (export "deploy"))
|
||||
|
||||
(func (export "call")
|
||||
(i32.store
|
||||
(i32.const 32)
|
||||
(call $seal_is_contract
|
||||
(i32.const 0) ;; ptr to destination address
|
||||
)
|
||||
)
|
||||
;; exit with success and take `seal_is_contract` return code to the output buffer
|
||||
(call $seal_return (i32.const 0) (i32.const 32) (i32.const 4))
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let output = execute(CODE_IS_CONTRACT, vec![], MockExt::default()).unwrap();
|
||||
|
||||
// The mock ext just always returns 1u32 (`true`).
|
||||
assert_eq!(
|
||||
output,
|
||||
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(1u32.encode()) },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn caller_is_origin_works() {
|
||||
const CODE_CALLER_IS_ORIGIN: &str = r#"
|
||||
;; This runs `caller_is_origin` check on zero account address
|
||||
(module
|
||||
(import "__unstable__" "seal_caller_is_origin" (func $seal_caller_is_origin (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 `seal_caller_is_origin` 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 $seal_caller_is_origin)
|
||||
)
|
||||
;; exit with success and take `seal_caller_is_origin` return code to the output buffer
|
||||
(call $seal_return (i32.const 0) (i32.const 0) (i32.const 4))
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let output = execute(CODE_CALLER_IS_ORIGIN, vec![], MockExt::default()).unwrap();
|
||||
|
||||
// The mock ext just always returns 0u32 (`false`)
|
||||
assert_eq!(
|
||||
output,
|
||||
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(0u32.encode()) },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,12 @@ pub enum RuntimeCosts {
|
||||
MeteringBlock(u32),
|
||||
/// Weight of calling `seal_caller`.
|
||||
Caller,
|
||||
/// Weight of calling `seal_is_contract`.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
IsContract,
|
||||
/// Weight of calling `seal_caller_is_origin`.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallerIsOrigin,
|
||||
/// Weight of calling `seal_address`.
|
||||
Address,
|
||||
/// Weight of calling `seal_gas_left`.
|
||||
@@ -225,6 +231,10 @@ impl RuntimeCosts {
|
||||
let weight = match *self {
|
||||
MeteringBlock(amount) => s.gas.saturating_add(amount.into()),
|
||||
Caller => s.caller,
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
IsContract => s.is_contract,
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallerIsOrigin => s.caller_is_origin,
|
||||
Address => s.address,
|
||||
GasLeft => s.gas_left,
|
||||
Balance => s.balance,
|
||||
@@ -1254,6 +1264,37 @@ define_env!(Env, <E: Ext>,
|
||||
)?)
|
||||
},
|
||||
|
||||
// Checks whether a specified address belongs to a contract.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - account_ptr: a pointer to the address of the beneficiary account
|
||||
// Should be decodable as an `T::AccountId`. Traps otherwise.
|
||||
//
|
||||
// Returned value is a u32-encoded boolean: (0 = false, 1 = true).
|
||||
[__unstable__] seal_is_contract(ctx, account_ptr: u32) -> u32 => {
|
||||
ctx.charge_gas(RuntimeCosts::IsContract)?;
|
||||
let address: <<E as Ext>::T as frame_system::Config>::AccountId =
|
||||
ctx.read_sandbox_memory_as(account_ptr)?;
|
||||
|
||||
Ok(ctx.ext.is_contract(&address) as u32)
|
||||
},
|
||||
|
||||
// Checks whether the caller of the current contract is the origin of the whole call stack.
|
||||
//
|
||||
// Prefer this over `seal_is_contract` when checking whether your contract is being called by a contract
|
||||
// or a plain account. The reason is that it performs better since it does not need to
|
||||
// do any storage lookups.
|
||||
//
|
||||
// A return value of`true` indicates that this contract is being called by a plain account
|
||||
// and `false` indicates that the caller is another contract.
|
||||
//
|
||||
// Returned value is a u32-encoded boolean: (0 = false, 1 = true).
|
||||
[__unstable__] seal_caller_is_origin(ctx) -> u32 => {
|
||||
ctx.charge_gas(RuntimeCosts::CallerIsOrigin)?;
|
||||
Ok(ctx.ext.caller_is_origin() 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`.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user