mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 17:31:05 +00:00
contracts: Allow contracts to dispatch calls into the runtime (#9276)
* contracts: Allow contracts to dispatch calls into the runtime * Fix RPC tests * Fix typo * Replace () by AllowAllFilter and DenyAllFilter * Add rust doc * Fixup for `()` removal * Fix lowest gas calculation * Rename AllowAllFilter and DenyAllFilter * Updated changelog
This commit is contained in:
committed by
GitHub
parent
2f31602896
commit
e01ac8cea0
@@ -254,18 +254,22 @@ mod tests {
|
||||
rent::RentStatus,
|
||||
tests::{Test, Call, ALICE, BOB},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
};
|
||||
use sp_core::{Bytes, H256};
|
||||
use hex_literal::hex;
|
||||
use sp_runtime::DispatchError;
|
||||
use frame_support::{assert_ok, dispatch::DispatchResult, weights::Weight};
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
dispatch::{DispatchResult, DispatchResultWithPostInfo},
|
||||
weights::Weight,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
|
||||
use pretty_assertions::assert_eq;
|
||||
use sp_std::borrow::BorrowMut;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct DispatchEntry(Call);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct RestoreEntry {
|
||||
@@ -313,6 +317,7 @@ mod tests {
|
||||
restores: Vec<RestoreEntry>,
|
||||
// (topics, data)
|
||||
events: Vec<(Vec<H256>, Vec<u8>)>,
|
||||
runtime_calls: RefCell<Vec<Call>>,
|
||||
schedule: Schedule<Test>,
|
||||
rent_params: RentParams<Test>,
|
||||
gas_meter: GasMeter<Test>,
|
||||
@@ -335,6 +340,7 @@ mod tests {
|
||||
transfers: Default::default(),
|
||||
restores: Default::default(),
|
||||
events: Default::default(),
|
||||
runtime_calls: Default::default(),
|
||||
schedule: Default::default(),
|
||||
rent_params: Default::default(),
|
||||
gas_meter: GasMeter::new(10_000_000_000),
|
||||
@@ -481,6 +487,10 @@ mod tests {
|
||||
self.debug_buffer.extend(msg.as_bytes());
|
||||
true
|
||||
}
|
||||
fn call_runtime(&self, call: <Self::T as Config>::Call) -> DispatchResultWithPostInfo {
|
||||
self.runtime_calls.borrow_mut().push(call);
|
||||
Ok(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<E: BorrowMut<MockExt>>(
|
||||
@@ -2160,4 +2170,81 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
const CODE_CALL_RUNTIME: &str = r#"
|
||||
(module
|
||||
(import "__unstable__" "seal_call_runtime" (func $seal_call_runtime (param i32 i32) (result i32)))
|
||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; 0x1000 = 4k in little endian
|
||||
;; size of input buffer
|
||||
(data (i32.const 0) "\00\10")
|
||||
|
||||
(func (export "call")
|
||||
;; Receive the encoded call
|
||||
(call $seal_input
|
||||
(i32.const 4) ;; Pointer to the input buffer
|
||||
(i32.const 0) ;; Size of the length buffer
|
||||
)
|
||||
;; Just use the call passed as input and store result to memory
|
||||
(i32.store (i32.const 0)
|
||||
(call $seal_call_runtime
|
||||
(i32.const 4) ;; Pointer where the call is stored
|
||||
(i32.load (i32.const 0)) ;; Size of the call
|
||||
)
|
||||
)
|
||||
(call $seal_return
|
||||
(i32.const 0) ;; flags
|
||||
(i32.const 0) ;; returned value
|
||||
(i32.const 4) ;; length of returned value
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn call_runtime_works() {
|
||||
use std::convert::TryInto;
|
||||
let call = Call::System(frame_system::Call::remark(b"Hello World".to_vec()));
|
||||
let mut ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_CALL_RUNTIME,
|
||||
call.encode(),
|
||||
&mut ext,
|
||||
).unwrap();
|
||||
assert_eq!(
|
||||
*ext.runtime_calls.borrow(),
|
||||
vec![call],
|
||||
);
|
||||
// 0 = ReturnCode::Success
|
||||
assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn call_runtime_panics_on_invalid_call() {
|
||||
let mut ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_CALL_RUNTIME,
|
||||
vec![0x42],
|
||||
&mut ext,
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ExecError {
|
||||
error: Error::<Test>::DecodingFailed.into(),
|
||||
origin: ErrorOrigin::Caller,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
*ext.runtime_calls.borrow(),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ pub enum ReturnCode {
|
||||
/// recording was disabled.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
LoggingDisabled = 9,
|
||||
/// The call dispatched by `seal_call_runtime` was executed but returned an error.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallRuntimeReturnedError = 10,
|
||||
}
|
||||
|
||||
impl ConvertibleToWasm for ReturnCode {
|
||||
@@ -213,6 +216,12 @@ pub enum RuntimeCosts {
|
||||
HashBlake128(u32),
|
||||
/// Weight charged by a chain extension through `seal_call_chain_extension`.
|
||||
ChainExtension(u64),
|
||||
/// Weight charged for copying data from the sandbox.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CopyIn(u32),
|
||||
/// Weight charged for calling into the runtime.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallRuntime(Weight),
|
||||
}
|
||||
|
||||
impl RuntimeCosts {
|
||||
@@ -273,6 +282,10 @@ impl RuntimeCosts {
|
||||
HashBlake128(len) => s.hash_blake2_128
|
||||
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
|
||||
ChainExtension(amount) => amount,
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
CallRuntime(weight) => weight,
|
||||
};
|
||||
RuntimeToken {
|
||||
#[cfg(test)]
|
||||
@@ -457,6 +470,15 @@ where
|
||||
self.ext.gas_meter().charge(token)
|
||||
}
|
||||
|
||||
/// Adjust a previously charged amount down to its actual amount.
|
||||
///
|
||||
/// This is when a maximum a priori amount was charged and then should be partially
|
||||
/// refunded to match the actual amount.
|
||||
pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) {
|
||||
let token = actual_costs.token(&self.ext.schedule().host_fn_weights);
|
||||
self.ext.gas_meter().adjust_gas(charged, token);
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
@@ -797,7 +819,6 @@ where
|
||||
// data passed to the supervisor will lead to a trap. This is not documented explicitly
|
||||
// for every function.
|
||||
define_env!(Env, <E: Ext>,
|
||||
|
||||
// Account for used gas. Traps if gas used is greater than gas limit.
|
||||
//
|
||||
// NOTE: This is a implementation defined call and is NOT a part of the public API.
|
||||
@@ -1808,4 +1829,58 @@ define_env!(Env, <E: Ext>,
|
||||
out_ptr, out_len_ptr, &rent_status, false, already_charged
|
||||
)?)
|
||||
},
|
||||
|
||||
// Call some dispatchable of the runtime.
|
||||
//
|
||||
// This function decodes the passed in data as the overarching `Call` type of the
|
||||
// runtime and dispatches it. The weight as specified in the runtime is charged
|
||||
// from the gas meter. Any weight refunds made by the dispatchable are considered.
|
||||
//
|
||||
// The filter specified by `Config::CallFilter` is attached to the origin of
|
||||
// the dispatched call.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
//
|
||||
// # Return Value
|
||||
//
|
||||
// Returns `ReturnCode::Success` when the dispatchable was succesfully executed and
|
||||
// returned `Ok`. When the dispatchable was exeuted but returned an error
|
||||
// `ReturnCode::CallRuntimeReturnedError` is returned. The full error is not
|
||||
// provided because it is not guaranteed to be stable.
|
||||
//
|
||||
// # Comparison with `ChainExtension`
|
||||
//
|
||||
// Just as a chain extension this API allows the runtime to extend the functionality
|
||||
// of contracts. While making use of this function is generelly easier it cannot be
|
||||
// used in call cases. Consider writing a chain extension if you need to do perform
|
||||
// one of the following tasks:
|
||||
//
|
||||
// - Return data.
|
||||
// - Provide functionality **exclusively** to contracts.
|
||||
// - Provide custom weights.
|
||||
// - Avoid the need to keep the `Call` data structure stable.
|
||||
//
|
||||
// # Unstable
|
||||
//
|
||||
// This function is unstable and subject to change (or removal) in the future. Do not
|
||||
// deploy a contract using it to a production chain.
|
||||
[__unstable__] seal_call_runtime(ctx, call_ptr: u32, call_len: u32) -> ReturnCode => {
|
||||
use frame_support::{dispatch::GetDispatchInfo, weights::extract_actual_weight};
|
||||
ctx.charge_gas(RuntimeCosts::CopyIn(call_len))?;
|
||||
let call: <E::T as Config>::Call = ctx.read_sandbox_memory_as_unbounded(
|
||||
call_ptr, call_len
|
||||
)?;
|
||||
let dispatch_info = call.get_dispatch_info();
|
||||
let charged = ctx.charge_gas(RuntimeCosts::CallRuntime(dispatch_info.weight))?;
|
||||
let result = ctx.ext.call_runtime(call);
|
||||
let actual_weight = extract_actual_weight(&result, &dispatch_info);
|
||||
ctx.adjust_gas(charged, RuntimeCosts::CallRuntime(actual_weight));
|
||||
match result {
|
||||
Ok(_) => Ok(ReturnCode::Success),
|
||||
Err(_) => Ok(ReturnCode::CallRuntimeReturnedError),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user