Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

contracts: is_contract(address) and caller_is_origin() are added to API #10789

Merged
merged 24 commits into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9c76672
is_contract() and caller_is_origin() added to Ext API
agryaznov Feb 1, 2022
7af3ab1
is_contract() exposed in wasm runtime.rs
agryaznov Feb 1, 2022
fe5e2a6
+ test for is_contract()
agryaznov Feb 2, 2022
5ed28c8
+ seal_is_contract benchmark
agryaznov Feb 2, 2022
ef9129f
caller_is_origin() exposed to wasm/runtime.rs and covered by a test
agryaznov Feb 3, 2022
83e1000
+ seal_caller_is_origin benchmark
agryaznov Feb 3, 2022
a0311fc
Update frame/contracts/src/exec.rs
agryaznov Feb 3, 2022
7b98d32
Update frame/contracts/src/exec.rs
agryaznov Feb 3, 2022
835d875
Update frame/contracts/src/exec.rs
agryaznov Feb 3, 2022
12bda73
Update frame/contracts/src/wasm/runtime.rs
agryaznov Feb 3, 2022
171bb8e
Update frame/contracts/src/wasm/runtime.rs
agryaznov Feb 3, 2022
ea41d79
Update frame/contracts/src/wasm/runtime.rs
agryaznov Feb 3, 2022
e4c7f4d
Update frame/contracts/src/exec.rs
agryaznov Feb 3, 2022
d1f3a41
identation fix for benchmark macroses; test cosmetic improvement
agryaznov Feb 3, 2022
67d91d4
benchmark fix
agryaznov Feb 3, 2022
04aca2a
+ is_contract() wasm test
agryaznov Feb 3, 2022
3ad46ec
+ caller_is_origin() wasm test
agryaznov Feb 3, 2022
c7baa4c
Apply suggestions from code review
agryaznov Feb 3, 2022
8e966ba
is_contract() to borrow param instead of taking ownership
agryaznov Feb 3, 2022
f2d6fcb
phrasing improved
agryaznov Feb 3, 2022
e22a338
fixed wasm tests according to @athei feedback
agryaznov Feb 4, 2022
c0b360e
dead code warnings suppressed by unstable-interface attributes
agryaznov Feb 4, 2022
1b3fc99
Merge branch 'master' of https://github.com/paritytech/substrate into…
Feb 4, 2022
b3ee151
cargo run --quiet --profile=production --features=runtime-benchmarks…
Feb 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
83 changes: 82 additions & 1 deletion frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;

Expand Down Expand Up @@ -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
///
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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, _| {
Expand Down
8 changes: 8 additions & 0 deletions frame/contracts/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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),
Expand Down
82 changes: 82 additions & 0 deletions frame/contracts/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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()) },
);
}
}
41 changes: 41 additions & 0 deletions frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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`.
Expand Down
Loading