mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 13:48:00 +00:00
srml-contracts: Contract calls/instantiations to return exit statuses (#3320)
* srml-contracts: Rename ext_scratch_copy to ext_scratch_read. This is to disambiguate from the next ext_scratch_write function. * Remove unnecessary OutputBuf and EmptyOutputBuf. * Replace VmExecError with a result type of custom structs. * Do not drop the scratch buffer on traps and regular returns. This just reduces the number of allocations required during nested contract calls and instantiations. * Semantics for returning a status code and data from contract calls. * Remove CallReceipt and InstantiateReceipt. With forthcoming changes to return data from instantiate calls, the two types of receipts become very similar to each other and to ExecReturnValue. Instead, replace them with ExecReturnValue and a regular 2-tuple in the case of instantiation. * Modify contract function signatures to allow returning status codes. * Introduce ext_sandbox_write runtime function. * Test all the things. * Bump node runtime spec version. * Style fixes.
This commit is contained in:
@@ -53,8 +53,7 @@ mod imp {
|
||||
/// Error that can occur while using this crate.
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub enum Error {
|
||||
/// Module is not valid, couldn't be instantiated or it's `start` function trapped
|
||||
/// when executed.
|
||||
/// Module is not valid, couldn't be instantiated.
|
||||
Module,
|
||||
|
||||
/// Access to a memory or table was made with an address or an index which is out of bounds.
|
||||
@@ -62,7 +61,7 @@ pub enum Error {
|
||||
/// Note that if wasm module makes an out-of-bounds access then trap will occur.
|
||||
OutOfBounds,
|
||||
|
||||
/// Failed to invoke an exported function for some reason.
|
||||
/// Failed to invoke the start function or an exported function for some reason.
|
||||
Execution,
|
||||
}
|
||||
|
||||
|
||||
@@ -690,7 +690,7 @@ mod tests {
|
||||
;; ) -> u32
|
||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "deploy")
|
||||
)
|
||||
@@ -705,7 +705,7 @@ mod tests {
|
||||
)
|
||||
)
|
||||
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 0)
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
|
||||
@@ -80,8 +80,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
// and set impl_version to equal spec_version. If only runtime
|
||||
// implementation changes and behavior does not, then leave spec_version as
|
||||
// is and increment impl_version.
|
||||
spec_version: 138,
|
||||
impl_version: 138,
|
||||
spec_version: 139,
|
||||
impl_version: 139,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ This function returns the size of the scratch buffer.
|
||||
|
||||
**complexity**: This function is of constant complexity.
|
||||
|
||||
## ext_scratch_copy
|
||||
## ext_scratch_read
|
||||
|
||||
This function copies slice of data from the scratch buffer to the sandbox memory. The calling code specifies the slice length. Execution of the function consists of the following steps:
|
||||
|
||||
@@ -379,6 +379,14 @@ This function copies slice of data from the scratch buffer to the sandbox memory
|
||||
|
||||
**complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required.
|
||||
|
||||
## ext_scratch_write
|
||||
|
||||
This function copies slice of data from the sandbox memory to the scratch buffer. The calling code specifies the slice length. Execution of the function consists of the following steps:
|
||||
|
||||
1. Loading a slice from the sandbox memory into the (see sandboxing memory get)
|
||||
|
||||
**complexity**: Complexity is proportional to the length of the slice.
|
||||
|
||||
## ext_set_rent_allowance
|
||||
|
||||
This function receives the following argument:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,7 @@ mod rent;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::exec::ExecutionContext;
|
||||
use crate::exec::{ExecutionContext, ExecResult};
|
||||
use crate::account_db::{AccountDb, DirectAccountDb};
|
||||
pub use crate::gas::{Gas, GasMeter};
|
||||
use crate::wasm::{WasmLoader, WasmVm};
|
||||
@@ -560,7 +560,7 @@ decl_module! {
|
||||
let dest = T::Lookup::lookup(dest)?;
|
||||
|
||||
Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| {
|
||||
ctx.call(dest, value, gas_meter, &data, exec::EmptyOutputBuf::new()).map(|_| ())
|
||||
ctx.call(dest, value, gas_meter, data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -584,7 +584,8 @@ decl_module! {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| {
|
||||
ctx.instantiate(endowment, gas_meter, &code_hash, &data).map(|_| ())
|
||||
ctx.instantiate(endowment, gas_meter, &code_hash, data)
|
||||
.map(|(_address, output)| output)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -631,7 +632,7 @@ impl<T: Trait> Module<T> {
|
||||
fn execute_wasm(
|
||||
origin: T::AccountId,
|
||||
gas_limit: Gas,
|
||||
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> Result
|
||||
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> ExecResult
|
||||
) -> Result {
|
||||
// Pay for the gas upfront.
|
||||
//
|
||||
@@ -646,7 +647,7 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
let result = func(&mut ctx, &mut gas_meter);
|
||||
|
||||
if result.is_ok() {
|
||||
if result.as_ref().map(|output| output.is_success()).unwrap_or(false) {
|
||||
// Commit all changes that made it thus far into the persistent storage.
|
||||
DirectAccountDb.commit(ctx.overlay.into_change_set());
|
||||
}
|
||||
@@ -688,6 +689,8 @@ impl<T: Trait> Module<T> {
|
||||
});
|
||||
|
||||
result
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.reason)
|
||||
}
|
||||
|
||||
fn restore_to(
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
|
||||
use crate::{
|
||||
BalanceOf, ComputeDispatchFee, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig,
|
||||
Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, TrieIdGenerator,
|
||||
Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter,
|
||||
TrieIdGenerator, Schedule,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use hex_literal::*;
|
||||
@@ -279,7 +280,10 @@ impl ExtBuilder {
|
||||
vesting: vec![],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
GenesisConfig::<Test> {
|
||||
current_schedule: Default::default(),
|
||||
current_schedule: Schedule {
|
||||
enable_println: true,
|
||||
..Default::default()
|
||||
},
|
||||
gas_price: self.gas_price,
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
runtime_io::TestExternalities::new(t)
|
||||
@@ -698,7 +702,7 @@ const CODE_SET_RENT: &str = r#"
|
||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32)))
|
||||
(import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32)))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; insert a value of 4 bytes into storage
|
||||
@@ -781,7 +785,7 @@ const CODE_SET_RENT: &str = r#"
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
)
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 0)
|
||||
(i32.const 0)
|
||||
(get_local $input_size)
|
||||
@@ -1173,7 +1177,7 @@ const CODE_CHECK_DEFAULT_RENT_ALLOWANCE: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_rent_allowance" (func $ext_rent_allowance))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -1200,7 +1204,7 @@ const CODE_CHECK_DEFAULT_RENT_ALLOWANCE: &str = r#"
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -1304,10 +1308,10 @@ const CODE_RESTORATION: &str = r#"
|
||||
;; Address of bob
|
||||
(data (i32.const 256) "\02\00\00\00\00\00\00\00")
|
||||
|
||||
;; Code hash of SET_CODE
|
||||
;; Code hash of SET_RENT
|
||||
(data (i32.const 264)
|
||||
"\69\ae\df\b4\f6\c1\c3\98\e9\7f\8a\52\04\de\0f\95"
|
||||
"\ad\5e\7d\c3\54\09\60\be\ab\11\a8\6c\56\9f\bf\cf"
|
||||
"\14\eb\65\3c\86\98\d6\b2\3d\8d\3c\4a\54\c6\c4\71"
|
||||
"\b9\fc\19\36\df\ca\a0\a1\f2\dc\ad\9d\e5\36\0b\25"
|
||||
)
|
||||
|
||||
;; Rent allowance
|
||||
@@ -1445,6 +1449,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
|
||||
} else {
|
||||
// Here we expect that the restoration is succeeded. Check that the restoration
|
||||
// contract `DJANGO` ceased to exist and that `BOB` returned back.
|
||||
println!("{:?}", ContractInfoOf::<Test>::get(BOB));
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap()
|
||||
.get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, 50);
|
||||
@@ -1462,7 +1467,7 @@ const CODE_STORAGE_SIZE: &str = r#"
|
||||
(import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32)))
|
||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32)))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 16 16))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -1484,7 +1489,7 @@ const CODE_STORAGE_SIZE: &str = r#"
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 32) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 4) ;; Count of bytes to copy.
|
||||
@@ -1566,3 +1571,357 @@ fn storage_max_value_limit() {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_RETURN_WITH_DATA: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; Deploy routine is the same as call.
|
||||
(func (export "deploy") (result i32)
|
||||
(call $call)
|
||||
)
|
||||
|
||||
;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data.
|
||||
(func $call (export "call") (result i32)
|
||||
(local $buf_size i32)
|
||||
(local $exit_status i32)
|
||||
|
||||
;; Find out the size of the scratch buffer
|
||||
(set_local $buf_size (call $ext_scratch_size))
|
||||
|
||||
;; Copy scratch buffer into this contract memory.
|
||||
(call $ext_scratch_read
|
||||
(i32.const 0) ;; The pointer where to store the scratch buffer contents,
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(get_local $buf_size) ;; Count of bytes to copy.
|
||||
)
|
||||
|
||||
;; Copy all but the first 4 bytes of the input data as the output data.
|
||||
(call $ext_scratch_write
|
||||
(i32.const 4) ;; Offset from the start of the scratch buffer.
|
||||
(i32.sub ;; Count of bytes to copy.
|
||||
(get_local $buf_size)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
|
||||
;; Return the first 4 bytes of the input data as the exit status.
|
||||
(i32.load (i32.const 0))
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
const CODE_CALLER_CONTRACT: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "ext_balance" (func $ext_balance))
|
||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "ext_println" (func $ext_println (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
(block $ok
|
||||
(br_if $ok
|
||||
(get_local 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
|
||||
(func $current_balance (param $sp i32) (result i64)
|
||||
(call $ext_balance)
|
||||
(call $assert
|
||||
(i32.eq (call $ext_scratch_size) (i32.const 8))
|
||||
)
|
||||
(call $ext_scratch_read
|
||||
(i32.sub (get_local $sp) (i32.const 8))
|
||||
(i32.const 0)
|
||||
(i32.const 8)
|
||||
)
|
||||
(i64.load (i32.sub (get_local $sp) (i32.const 8)))
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
|
||||
(func (export "call")
|
||||
(local $sp i32)
|
||||
(local $exit_code i32)
|
||||
(local $balance i64)
|
||||
|
||||
;; Input data is the code hash of the contract to be deployed.
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(call $ext_scratch_size)
|
||||
(i32.const 32)
|
||||
)
|
||||
)
|
||||
|
||||
;; Copy code hash from scratch buffer into this contract's memory.
|
||||
(call $ext_scratch_read
|
||||
(i32.const 24) ;; The pointer where to store the scratch buffer contents,
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 32) ;; Count of bytes to copy.
|
||||
)
|
||||
|
||||
;; Read current balance into local variable.
|
||||
(set_local $sp (i32.const 1024))
|
||||
(set_local $balance
|
||||
(call $current_balance (get_local $sp))
|
||||
)
|
||||
|
||||
;; Fail to deploy the contract since it returns a non-zero exit status.
|
||||
(set_local $exit_code
|
||||
(call $ext_create
|
||||
(i32.const 24) ;; Pointer to the code hash.
|
||||
(i32.const 32) ;; Length of the code hash.
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 9) ;; Pointer to input data buffer address
|
||||
(i32.const 7) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
|
||||
;; Check non-zero exit status.
|
||||
(call $assert
|
||||
(i32.eq (get_local $exit_code) (i32.const 0x11))
|
||||
)
|
||||
|
||||
;; Check that scratch buffer is empty since contract instantiation failed.
|
||||
(call $assert
|
||||
(i32.eq (call $ext_scratch_size) (i32.const 0))
|
||||
)
|
||||
|
||||
;; Check that balance has not changed.
|
||||
(call $assert
|
||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
||||
)
|
||||
|
||||
;; Fail to deploy the contract due to insufficient gas.
|
||||
(set_local $exit_code
|
||||
(call $ext_create
|
||||
(i32.const 24) ;; Pointer to the code hash.
|
||||
(i32.const 32) ;; Length of the code hash.
|
||||
(i64.const 200) ;; How much gas to devote for the execution.
|
||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 8) ;; Pointer to input data buffer address
|
||||
(i32.const 8) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
|
||||
;; Check for special trap exit status.
|
||||
(call $assert
|
||||
(i32.eq (get_local $exit_code) (i32.const 0x0100))
|
||||
)
|
||||
|
||||
;; Check that scratch buffer is empty since contract instantiation failed.
|
||||
(call $assert
|
||||
(i32.eq (call $ext_scratch_size) (i32.const 0))
|
||||
)
|
||||
|
||||
;; Check that balance has not changed.
|
||||
(call $assert
|
||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
||||
)
|
||||
|
||||
;; Deploy the contract successfully.
|
||||
(set_local $exit_code
|
||||
(call $ext_create
|
||||
(i32.const 24) ;; Pointer to the code hash.
|
||||
(i32.const 32) ;; Length of the code hash.
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 8) ;; Pointer to input data buffer address
|
||||
(i32.const 8) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
|
||||
;; Check for success exit status.
|
||||
(call $assert
|
||||
(i32.eq (get_local $exit_code) (i32.const 0x00))
|
||||
)
|
||||
|
||||
;; Check that scratch buffer contains the address of the new contract.
|
||||
(call $assert
|
||||
(i32.eq (call $ext_scratch_size) (i32.const 8))
|
||||
)
|
||||
|
||||
;; Copy contract address from scratch buffer into this contract's memory.
|
||||
(call $ext_scratch_read
|
||||
(i32.const 16) ;; The pointer where to store the scratch buffer contents,
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
)
|
||||
|
||||
;; Check that balance has been deducted.
|
||||
(set_local $balance
|
||||
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
|
||||
)
|
||||
(call $assert
|
||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
||||
)
|
||||
|
||||
;; Call the new contract and expect it to return failing exit code.
|
||||
(set_local $exit_code
|
||||
(call $ext_call
|
||||
(i32.const 16) ;; Pointer to "callee" address.
|
||||
(i32.const 8) ;; Length of "callee" address.
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 9) ;; Pointer to input data buffer address
|
||||
(i32.const 7) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
|
||||
;; Check non-zero exit status.
|
||||
(call $assert
|
||||
(i32.eq (get_local $exit_code) (i32.const 0x11))
|
||||
)
|
||||
|
||||
;; Check that scratch buffer contains the expected return data.
|
||||
(call $assert
|
||||
(i32.eq (call $ext_scratch_size) (i32.const 3))
|
||||
)
|
||||
(i32.store
|
||||
(i32.sub (get_local $sp) (i32.const 4))
|
||||
(i32.const 0)
|
||||
)
|
||||
(call $ext_scratch_read
|
||||
(i32.sub (get_local $sp) (i32.const 4))
|
||||
(i32.const 0)
|
||||
(i32.const 3)
|
||||
)
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(i32.load (i32.sub (get_local $sp) (i32.const 4)))
|
||||
(i32.const 0x00776655)
|
||||
)
|
||||
)
|
||||
|
||||
;; Check that balance has not changed.
|
||||
(call $assert
|
||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
||||
)
|
||||
|
||||
;; Fail to call the contract due to insufficient gas.
|
||||
(set_local $exit_code
|
||||
(call $ext_call
|
||||
(i32.const 16) ;; Pointer to "callee" address.
|
||||
(i32.const 8) ;; Length of "callee" address.
|
||||
(i64.const 100) ;; How much gas to devote for the execution.
|
||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 8) ;; Pointer to input data buffer address
|
||||
(i32.const 8) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
|
||||
;; Check for special trap exit status.
|
||||
(call $assert
|
||||
(i32.eq (get_local $exit_code) (i32.const 0x0100))
|
||||
)
|
||||
|
||||
;; Check that scratch buffer is empty since call trapped.
|
||||
(call $assert
|
||||
(i32.eq (call $ext_scratch_size) (i32.const 0))
|
||||
)
|
||||
|
||||
;; Check that balance has not changed.
|
||||
(call $assert
|
||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
||||
)
|
||||
|
||||
;; Call the contract successfully.
|
||||
(set_local $exit_code
|
||||
(call $ext_call
|
||||
(i32.const 16) ;; Pointer to "callee" address.
|
||||
(i32.const 8) ;; Length of "callee" address.
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 0) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 8) ;; Pointer to input data buffer address
|
||||
(i32.const 8) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
|
||||
;; Check for success exit status.
|
||||
(call $assert
|
||||
(i32.eq (get_local $exit_code) (i32.const 0x00))
|
||||
)
|
||||
|
||||
;; Check that scratch buffer contains the expected return data.
|
||||
(call $assert
|
||||
(i32.eq (call $ext_scratch_size) (i32.const 4))
|
||||
)
|
||||
(i32.store
|
||||
(i32.sub (get_local $sp) (i32.const 4))
|
||||
(i32.const 0)
|
||||
)
|
||||
(call $ext_scratch_read
|
||||
(i32.sub (get_local $sp) (i32.const 4))
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
)
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(i32.load (i32.sub (get_local $sp) (i32.const 4)))
|
||||
(i32.const 0x77665544)
|
||||
)
|
||||
)
|
||||
|
||||
;; Check that balance has been deducted.
|
||||
(set_local $balance
|
||||
(i64.sub (get_local $balance) (i64.load (i32.const 0)))
|
||||
)
|
||||
(call $assert
|
||||
(i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
|
||||
)
|
||||
)
|
||||
|
||||
(data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls.
|
||||
;; Chosen to be greater than existential deposit.
|
||||
(data (i32.const 8) "\00\11\22\33\44\55\66\77") ;; The input data to instantiations and calls.
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn deploy_and_call_other_contract() {
|
||||
let (callee_wasm, callee_code_hash) = compile_module::<Test>(CODE_RETURN_WITH_DATA).unwrap();
|
||||
let (caller_wasm, caller_code_hash) = compile_module::<Test>(CODE_CALLER_CONTRACT).unwrap();
|
||||
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||
|| {
|
||||
// Create
|
||||
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm));
|
||||
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm));
|
||||
|
||||
assert_ok!(Contract::create(
|
||||
Origin::signed(ALICE),
|
||||
100_000,
|
||||
100_000,
|
||||
caller_code_hash.into(),
|
||||
vec![],
|
||||
));
|
||||
|
||||
// Call BOB contract, which attempts to instantiate and call the callee contract and
|
||||
// makes various assertions on the results from those calls.
|
||||
assert_ok!(Contract::call(
|
||||
Origin::signed(ALICE),
|
||||
BOB,
|
||||
0,
|
||||
200_000,
|
||||
callee_code_hash.as_ref().to_vec(),
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
use crate::{CodeHash, Schedule, Trait};
|
||||
use crate::wasm::env_def::FunctionImplProvider;
|
||||
use crate::exec::{Ext, EmptyOutputBuf, VmExecResult};
|
||||
use crate::exec::{Ext, ExecResult};
|
||||
use crate::gas::GasMeter;
|
||||
|
||||
use rstd::prelude::*;
|
||||
@@ -110,10 +110,9 @@ impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a> {
|
||||
&self,
|
||||
exec: &WasmExecutable,
|
||||
mut ext: E,
|
||||
input_data: &[u8],
|
||||
empty_output_buf: EmptyOutputBuf,
|
||||
input_data: Vec<u8>,
|
||||
gas_meter: &mut GasMeter<E::T>,
|
||||
) -> VmExecResult {
|
||||
) -> ExecResult {
|
||||
let memory =
|
||||
sandbox::Memory::new(exec.prefab_module.initial, Some(exec.prefab_module.maximum))
|
||||
.unwrap_or_else(|_| {
|
||||
@@ -134,38 +133,17 @@ impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a> {
|
||||
|
||||
let mut runtime = Runtime::new(
|
||||
&mut ext,
|
||||
input_data.to_vec(),
|
||||
empty_output_buf,
|
||||
input_data,
|
||||
&self.schedule,
|
||||
memory,
|
||||
gas_meter,
|
||||
);
|
||||
|
||||
// Instantiate the instance from the instrumented module code.
|
||||
match sandbox::Instance::new(&exec.prefab_module.code, &imports, &mut runtime) {
|
||||
// No errors or traps were generated on instantiation! That
|
||||
// means we can now invoke the contract entrypoint.
|
||||
Ok(mut instance) => {
|
||||
let err = instance
|
||||
.invoke(exec.entrypoint_name, &[], &mut runtime)
|
||||
.err();
|
||||
to_execution_result(runtime, err)
|
||||
}
|
||||
// `start` function trapped. Treat it in the same manner as an execution error.
|
||||
Err(err @ sandbox::Error::Execution) => to_execution_result(runtime, Some(err)),
|
||||
Err(_err @ sandbox::Error::Module) => {
|
||||
// `Error::Module` is returned only if instantiation or linking failed (i.e.
|
||||
// wasm binary tried to import a function that is not provided by the host).
|
||||
// This shouldn't happen because validation process ought to reject such binaries.
|
||||
//
|
||||
// Because panics are really undesirable in the runtime code, we treat this as
|
||||
// a trap for now. Eventually, we might want to revisit this.
|
||||
return VmExecResult::Trap("validation error");
|
||||
}
|
||||
// Other instantiation errors.
|
||||
// Return without executing anything.
|
||||
Err(_) => return VmExecResult::Trap("during start function"),
|
||||
}
|
||||
// Instantiate the instance from the instrumented module code and invoke the contract
|
||||
// entrypoint.
|
||||
let result = sandbox::Instance::new(&exec.prefab_module.code, &imports, &mut runtime)
|
||||
.and_then(|mut instance| instance.invoke(exec.entrypoint_name, &[], &mut runtime));
|
||||
to_execution_result(runtime, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,16 +152,18 @@ mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use primitives::H256;
|
||||
use crate::exec::{CallReceipt, Ext, InstantiateReceipt, EmptyOutputBuf, StorageKey};
|
||||
use crate::exec::{Ext, StorageKey, ExecError, ExecReturnValue, STATUS_SUCCESS};
|
||||
use crate::gas::{Gas, GasMeter};
|
||||
use crate::tests::{Test, Call};
|
||||
use crate::wasm::prepare::prepare_contract;
|
||||
use crate::CodeHash;
|
||||
use wabt;
|
||||
use hex_literal::hex;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct DispatchEntry(Call);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct RestoreEntry {
|
||||
dest: u64,
|
||||
@@ -191,6 +171,7 @@ mod tests {
|
||||
rent_allowance: u64,
|
||||
delta: Vec<StorageKey>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct CreateEntry {
|
||||
code_hash: H256,
|
||||
@@ -198,6 +179,7 @@ mod tests {
|
||||
data: Vec<u8>,
|
||||
gas_left: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct TransferEntry {
|
||||
to: u64,
|
||||
@@ -205,6 +187,7 @@ mod tests {
|
||||
data: Vec<u8>,
|
||||
gas_left: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MockExt {
|
||||
storage: HashMap<StorageKey, Vec<u8>>,
|
||||
@@ -217,6 +200,7 @@ mod tests {
|
||||
events: Vec<(Vec<H256>, Vec<u8>)>,
|
||||
next_account_id: u64,
|
||||
}
|
||||
|
||||
impl Ext for MockExt {
|
||||
type T = Test;
|
||||
|
||||
@@ -234,8 +218,8 @@ mod tests {
|
||||
code_hash: &CodeHash<Test>,
|
||||
endowment: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
data: &[u8],
|
||||
) -> Result<InstantiateReceipt<u64>, &'static str> {
|
||||
data: Vec<u8>,
|
||||
) -> Result<(u64, ExecReturnValue), ExecError> {
|
||||
self.creates.push(CreateEntry {
|
||||
code_hash: code_hash.clone(),
|
||||
endowment,
|
||||
@@ -245,16 +229,15 @@ mod tests {
|
||||
let address = self.next_account_id;
|
||||
self.next_account_id += 1;
|
||||
|
||||
Ok(InstantiateReceipt { address })
|
||||
Ok((address, ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }))
|
||||
}
|
||||
fn call(
|
||||
&mut self,
|
||||
to: &u64,
|
||||
value: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
data: &[u8],
|
||||
_output_data: EmptyOutputBuf,
|
||||
) -> Result<CallReceipt, &'static str> {
|
||||
data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
self.transfers.push(TransferEntry {
|
||||
to: *to,
|
||||
value,
|
||||
@@ -263,9 +246,7 @@ mod tests {
|
||||
});
|
||||
// Assume for now that it was just a plain transfer.
|
||||
// TODO: Add tests for different call outcomes.
|
||||
Ok(CallReceipt {
|
||||
output_data: Vec::new(),
|
||||
})
|
||||
Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() })
|
||||
}
|
||||
fn note_dispatch_call(&mut self, call: Call) {
|
||||
self.dispatches.push(DispatchEntry(call));
|
||||
@@ -321,6 +302,7 @@ mod tests {
|
||||
|
||||
fn max_value_size(&self) -> u32 { 16_384 }
|
||||
}
|
||||
|
||||
impl Ext for &mut MockExt {
|
||||
type T = <MockExt as Ext>::T;
|
||||
|
||||
@@ -337,8 +319,8 @@ mod tests {
|
||||
code: &CodeHash<Test>,
|
||||
value: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
input_data: &[u8]
|
||||
) -> Result<InstantiateReceipt<u64>, &'static str> {
|
||||
input_data: Vec<u8>,
|
||||
) -> Result<(u64, ExecReturnValue), ExecError> {
|
||||
(**self).instantiate(code, value, gas_meter, input_data)
|
||||
}
|
||||
fn call(
|
||||
@@ -346,10 +328,9 @@ mod tests {
|
||||
to: &u64,
|
||||
value: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
input_data: &[u8],
|
||||
empty_output_buf: EmptyOutputBuf
|
||||
) -> Result<CallReceipt, &'static str> {
|
||||
(**self).call(to, value, gas_meter, input_data, empty_output_buf)
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
(**self).call(to, value, gas_meter, input_data)
|
||||
}
|
||||
fn note_dispatch_call(&mut self, call: Call) {
|
||||
(**self).note_dispatch_call(call)
|
||||
@@ -405,11 +386,10 @@ mod tests {
|
||||
|
||||
fn execute<E: Ext>(
|
||||
wat: &str,
|
||||
input_data: &[u8],
|
||||
output_data: &mut Vec<u8>,
|
||||
input_data: Vec<u8>,
|
||||
ext: E,
|
||||
gas_meter: &mut GasMeter<E::T>,
|
||||
) -> Result<(), &'static str> {
|
||||
) -> ExecResult {
|
||||
use crate::exec::Vm;
|
||||
|
||||
let wasm = wabt::wat2wasm(wat).unwrap();
|
||||
@@ -426,11 +406,7 @@ mod tests {
|
||||
let cfg = Default::default();
|
||||
let vm = WasmVm::new(&cfg);
|
||||
|
||||
*output_data = vm
|
||||
.execute(&exec, ext, input_data, EmptyOutputBuf::new(), gas_meter)
|
||||
.into_result()?;
|
||||
|
||||
Ok(())
|
||||
vm.execute(&exec, ext, input_data, gas_meter)
|
||||
}
|
||||
|
||||
const CODE_TRANSFER: &str = r#"
|
||||
@@ -475,14 +451,12 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_transfer() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_TRANSFER,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.transfers,
|
||||
@@ -539,14 +513,12 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_create() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_CREATE,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.creates,
|
||||
@@ -601,14 +573,12 @@ mod tests {
|
||||
#[test]
|
||||
fn contract_call_limited_gas() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
let _ = execute(
|
||||
&CODE_TRANSFER_LIMITED_GAS,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.transfers,
|
||||
@@ -625,7 +595,7 @@ mod tests {
|
||||
(module
|
||||
(import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32)))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "ext_return" (func $ext_return (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
@@ -661,7 +631,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; Copy scratch buffer into this contract memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 36) ;; The pointer where to store the scratch buffer contents,
|
||||
;; 36 = 4 + 32
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
@@ -696,17 +666,14 @@ mod tests {
|
||||
.storage
|
||||
.insert([0x11; 32], [0x22; 32].to_vec());
|
||||
|
||||
let mut return_buf = Vec::new();
|
||||
execute(
|
||||
let output = execute(
|
||||
CODE_GET_STORAGE,
|
||||
&[],
|
||||
&mut return_buf,
|
||||
vec![],
|
||||
mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(return_buf, [0x22; 32].to_vec());
|
||||
assert_eq!(output, ExecReturnValue { status: STATUS_SUCCESS, data: [0x22; 32].to_vec() });
|
||||
}
|
||||
|
||||
/// calls `ext_caller`, loads the address from the scratch buffer and
|
||||
@@ -715,7 +682,7 @@ mod tests {
|
||||
(module
|
||||
(import "env" "ext_caller" (func $ext_caller))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -740,7 +707,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -763,14 +730,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn caller() {
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_CALLER,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
/// calls `ext_address`, loads the address from the scratch buffer and
|
||||
@@ -779,7 +744,7 @@ mod tests {
|
||||
(module
|
||||
(import "env" "ext_address" (func $ext_address))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -804,7 +769,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -827,21 +792,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn address() {
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_ADDRESS,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
const CODE_BALANCE: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_balance" (func $ext_balance))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -866,7 +829,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -889,21 +852,19 @@ mod tests {
|
||||
#[test]
|
||||
fn balance() {
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1);
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_BALANCE,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter,
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
const CODE_GAS_PRICE: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_gas_price" (func $ext_gas_price))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -928,7 +889,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -951,21 +912,19 @@ mod tests {
|
||||
#[test]
|
||||
fn gas_price() {
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1312);
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_GAS_PRICE,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter,
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
const CODE_GAS_LEFT: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_gas_left" (func $ext_gas_left))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "ext_return" (func $ext_return (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
@@ -991,7 +950,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -1012,17 +971,14 @@ mod tests {
|
||||
fn gas_left() {
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1312);
|
||||
|
||||
let mut return_buf = Vec::new();
|
||||
execute(
|
||||
let output = execute(
|
||||
CODE_GAS_LEFT,
|
||||
&[],
|
||||
&mut return_buf,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter,
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
let gas_left = Gas::decode(&mut &return_buf[..]).unwrap();
|
||||
let gas_left = Gas::decode(&mut output.data.as_slice()).unwrap();
|
||||
assert!(gas_left < 50_000, "gas_left must be less than initial");
|
||||
assert!(gas_left > gas_meter.gas_left(), "gas_left must be greater than final");
|
||||
}
|
||||
@@ -1031,7 +987,7 @@ mod tests {
|
||||
(module
|
||||
(import "env" "ext_value_transferred" (func $ext_value_transferred))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -1056,7 +1012,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -1079,14 +1035,12 @@ mod tests {
|
||||
#[test]
|
||||
fn value_transferred() {
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1);
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_VALUE_TRANSFERRED,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter,
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
const CODE_DISPATCH_CALL: &str = r#"
|
||||
@@ -1112,14 +1066,12 @@ mod tests {
|
||||
// let's rewrite so as we use this module controlled call or we serialize it in runtime.
|
||||
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_DISPATCH_CALL,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.dispatches,
|
||||
@@ -1154,24 +1106,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn return_from_start_fn() {
|
||||
let mut output_data = Vec::new();
|
||||
execute(
|
||||
let output = execute(
|
||||
CODE_RETURN_FROM_START_FN,
|
||||
&[],
|
||||
&mut output_data,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(output_data, vec![1, 2, 3, 4]);
|
||||
assert_eq!(output, ExecReturnValue { status: STATUS_SUCCESS, data: vec![1, 2, 3, 4] });
|
||||
}
|
||||
|
||||
const CODE_TIMESTAMP_NOW: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_now" (func $ext_now))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -1196,7 +1145,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -1219,21 +1168,19 @@ mod tests {
|
||||
#[test]
|
||||
fn now() {
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1);
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_TIMESTAMP_NOW,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter,
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
const CODE_RANDOM: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_random" (func $ext_random (param i32 i32)))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "ext_return" (func $ext_return (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
@@ -1262,7 +1209,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 32) ;; Count of bytes to copy.
|
||||
@@ -1290,20 +1237,20 @@ mod tests {
|
||||
fn random() {
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1);
|
||||
|
||||
let mut return_buf = Vec::new();
|
||||
execute(
|
||||
let output = execute(
|
||||
CODE_RANDOM,
|
||||
&[],
|
||||
&mut return_buf,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter,
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
// The mock ext just returns the same data that was passed as the subject.
|
||||
assert_eq!(
|
||||
&return_buf,
|
||||
&hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F")
|
||||
output,
|
||||
ExecReturnValue {
|
||||
status: STATUS_SUCCESS,
|
||||
data: hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F").to_vec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1334,14 +1281,12 @@ mod tests {
|
||||
fn deposit_event() {
|
||||
let mut mock_ext = MockExt::default();
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1);
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_DEPOSIT_EVENT,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
&mut mock_ext,
|
||||
&mut gas_meter
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(mock_ext.events, vec![
|
||||
(vec![H256::repeat_byte(0x33)],
|
||||
@@ -1387,15 +1332,14 @@ mod tests {
|
||||
// Checks that the runtime traps if there are more than `max_topic_events` topics.
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1);
|
||||
|
||||
assert_eq!(
|
||||
assert_matches!(
|
||||
execute(
|
||||
CODE_DEPOSIT_EVENT_MAX_TOPICS,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter
|
||||
),
|
||||
Err("during execution"),
|
||||
Err(ExecError { reason: "during execution", buffer: _ })
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1430,15 +1374,14 @@ mod tests {
|
||||
// Checks that the runtime traps if there are duplicates.
|
||||
let mut gas_meter = GasMeter::with_limit(50_000, 1);
|
||||
|
||||
assert_eq!(
|
||||
assert_matches!(
|
||||
execute(
|
||||
CODE_DEPOSIT_EVENT_DUPLICATES,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut gas_meter
|
||||
),
|
||||
Err("during execution"),
|
||||
Err(ExecError { reason: "during execution", buffer: _ })
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1448,7 +1391,7 @@ mod tests {
|
||||
(module
|
||||
(import "env" "ext_block_number" (func $ext_block_number))
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func $assert (param i32)
|
||||
@@ -1473,7 +1416,7 @@ mod tests {
|
||||
)
|
||||
|
||||
;; copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(call $ext_scratch_read
|
||||
(i32.const 8) ;; Pointer in memory to the place where to copy.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
@@ -1496,14 +1439,137 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn block_number() {
|
||||
execute(
|
||||
let _ = execute(
|
||||
CODE_BLOCK_NUMBER,
|
||||
&[],
|
||||
&mut Vec::new(),
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
)
|
||||
.unwrap();
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
// asserts that the size of the input data is 4.
|
||||
const CODE_SIMPLE_ASSERT: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
|
||||
(func $assert (param i32)
|
||||
(block $ok
|
||||
(br_if $ok
|
||||
(get_local 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
|
||||
(func (export "call")
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(call $ext_scratch_size)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn output_buffer_capacity_preserved_on_success() {
|
||||
let mut input_data = Vec::with_capacity(1_234);
|
||||
input_data.extend_from_slice(&[1, 2, 3, 4][..]);
|
||||
|
||||
let output = execute(
|
||||
CODE_SIMPLE_ASSERT,
|
||||
input_data,
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(output.data.len(), 0);
|
||||
assert_eq!(output.data.capacity(), 1_234);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_buffer_capacity_preserved_on_failure() {
|
||||
let mut input_data = Vec::with_capacity(1_234);
|
||||
input_data.extend_from_slice(&[1, 2, 3, 4, 5][..]);
|
||||
|
||||
let error = execute(
|
||||
CODE_SIMPLE_ASSERT,
|
||||
input_data,
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
).err().unwrap();
|
||||
|
||||
assert_eq!(error.buffer.capacity(), 1_234);
|
||||
}
|
||||
|
||||
const CODE_RETURN_WITH_DATA: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; Deploy routine is the same as call.
|
||||
(func (export "deploy") (result i32)
|
||||
(call $call)
|
||||
)
|
||||
|
||||
;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data.
|
||||
(func $call (export "call") (result i32)
|
||||
(local $buf_size i32)
|
||||
(local $exit_status i32)
|
||||
|
||||
;; Find out the size of the scratch buffer
|
||||
(set_local $buf_size (call $ext_scratch_size))
|
||||
|
||||
;; Copy scratch buffer into this contract memory.
|
||||
(call $ext_scratch_read
|
||||
(i32.const 0) ;; The pointer where to store the scratch buffer contents,
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(get_local $buf_size) ;; Count of bytes to copy.
|
||||
)
|
||||
|
||||
;; Copy all but the first 4 bytes of the input data as the output data.
|
||||
(call $ext_scratch_write
|
||||
(i32.const 4) ;; Offset from the start of the scratch buffer.
|
||||
(i32.sub ;; Count of bytes to copy.
|
||||
(get_local $buf_size)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
|
||||
;; Return the first 4 bytes of the input data as the exit status.
|
||||
(i32.load (i32.const 0))
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn return_with_success_status() {
|
||||
let output = execute(
|
||||
CODE_RETURN_WITH_DATA,
|
||||
hex!("00112233445566778899").to_vec(),
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(output, ExecReturnValue { status: 0, data: hex!("445566778899").to_vec() });
|
||||
assert!(output.is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_with_failure_status() {
|
||||
let output = execute(
|
||||
CODE_RETURN_WITH_DATA,
|
||||
hex!("112233445566778899").to_vec(),
|
||||
MockExt::default(),
|
||||
&mut GasMeter::with_limit(50_000, 1),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(output, ExecReturnValue { status: 17, data: hex!("5566778899").to_vec() });
|
||||
assert!(!output.is_success());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,14 +227,20 @@ impl<'a> ContractModule<'a> {
|
||||
};
|
||||
|
||||
// Then check the signature.
|
||||
// Both "call" and "deploy" has a () -> () function type.
|
||||
// Both "call" and "deploy" has a [] -> [] or [] -> [i32] function type.
|
||||
//
|
||||
// The [] -> [] signature predates the [] -> [i32] signature and is supported for
|
||||
// backwards compatibility. This will likely be removed once ink! is updated to
|
||||
// generate modules with the new function signatures.
|
||||
let func_ty_idx = func_entries.get(fn_idx as usize)
|
||||
.ok_or_else(|| "export refers to non-existent function")?
|
||||
.type_ref();
|
||||
let Type::Function(ref func_ty) = types
|
||||
.get(func_ty_idx as usize)
|
||||
.ok_or_else(|| "function has a non-existent type")?;
|
||||
if !(func_ty.params().is_empty() && func_ty.return_type().is_none()) {
|
||||
if !func_ty.params().is_empty() ||
|
||||
!(func_ty.return_type().is_none() ||
|
||||
func_ty.return_type() == Some(ValueType::I32)) {
|
||||
return Err("entry point has wrong signature");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,32 +18,34 @@
|
||||
|
||||
use crate::{Schedule, Trait, CodeHash, ComputeDispatchFee, BalanceOf};
|
||||
use crate::exec::{
|
||||
Ext, VmExecResult, OutputBuf, EmptyOutputBuf, CallReceipt, InstantiateReceipt, StorageKey,
|
||||
TopicOf,
|
||||
Ext, ExecResult, ExecError, ExecReturnValue, StorageKey, TopicOf, STATUS_SUCCESS,
|
||||
};
|
||||
use crate::gas::{Gas, GasMeter, Token, GasMeterResult, approx_gas_for_balance};
|
||||
use sandbox;
|
||||
use system;
|
||||
use rstd::prelude::*;
|
||||
use rstd::convert::TryInto;
|
||||
use rstd::mem;
|
||||
use codec::{Decode, Encode};
|
||||
use sr_primitives::traits::{Bounded, SaturatedConversion};
|
||||
|
||||
/// The value returned from ext_call and ext_create contract external functions if the call or
|
||||
/// instantiation traps. This value is chosen as if the execution does not trap, the return value
|
||||
/// will always be an 8-bit integer, so 0x0100 is the smallest value that could not be returned.
|
||||
const TRAP_RETURN_CODE: u32 = 0x0100;
|
||||
|
||||
/// Enumerates all possible *special* trap conditions.
|
||||
///
|
||||
/// In this runtime traps used not only for signaling about errors but also
|
||||
/// to just terminate quickly in some cases.
|
||||
enum SpecialTrap {
|
||||
/// Signals that trap was generated in response to call `ext_return` host function.
|
||||
Return(OutputBuf),
|
||||
Return(Vec<u8>),
|
||||
}
|
||||
|
||||
/// Can only be used for one call.
|
||||
pub(crate) struct Runtime<'a, E: Ext + 'a> {
|
||||
ext: &'a mut E,
|
||||
// A VM can return a result only once and only by value. So
|
||||
// we wrap output buffer to make it possible to take the buffer out.
|
||||
empty_output_buf: Option<EmptyOutputBuf>,
|
||||
scratch_buf: Vec<u8>,
|
||||
schedule: &'a Schedule,
|
||||
memory: sandbox::Memory,
|
||||
@@ -54,14 +56,12 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
||||
pub(crate) fn new(
|
||||
ext: &'a mut E,
|
||||
input_data: Vec<u8>,
|
||||
empty_output_buf: EmptyOutputBuf,
|
||||
schedule: &'a Schedule,
|
||||
memory: sandbox::Memory,
|
||||
gas_meter: &'a mut GasMeter<E::T>,
|
||||
) -> Self {
|
||||
Runtime {
|
||||
ext,
|
||||
empty_output_buf: Some(empty_output_buf),
|
||||
// Put the input data into the scratch buffer immediately.
|
||||
scratch_buf: input_data,
|
||||
schedule,
|
||||
@@ -78,20 +78,42 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
||||
|
||||
pub(crate) fn to_execution_result<E: Ext>(
|
||||
runtime: Runtime<E>,
|
||||
sandbox_err: Option<sandbox::Error>,
|
||||
) -> VmExecResult {
|
||||
// Check the exact type of the error. It could be plain trap or
|
||||
// special runtime trap the we must recognize.
|
||||
match (sandbox_err, runtime.special_trap) {
|
||||
sandbox_result: Result<sandbox::ReturnValue, sandbox::Error>,
|
||||
) -> ExecResult {
|
||||
// Special case. The trap was the result of the execution `return` host function.
|
||||
if let Some(SpecialTrap::Return(data)) = runtime.special_trap {
|
||||
return Ok(ExecReturnValue { status: STATUS_SUCCESS, data });
|
||||
}
|
||||
|
||||
// Check the exact type of the error.
|
||||
match sandbox_result {
|
||||
// No traps were generated. Proceed normally.
|
||||
(None, None) => VmExecResult::Ok,
|
||||
// Special case. The trap was the result of the execution `return` host function.
|
||||
(Some(sandbox::Error::Execution), Some(SpecialTrap::Return(buf))) => VmExecResult::Returned(buf),
|
||||
Ok(sandbox::ReturnValue::Unit) => {
|
||||
let mut buffer = runtime.scratch_buf;
|
||||
buffer.clear();
|
||||
Ok(ExecReturnValue { status: STATUS_SUCCESS, data: buffer })
|
||||
}
|
||||
Ok(sandbox::ReturnValue::Value(sandbox::TypedValue::I32(exit_code))) => {
|
||||
let status = (exit_code & 0xFF).try_into()
|
||||
.expect("exit_code is masked into the range of a u8; qed");
|
||||
Ok(ExecReturnValue { status, data: runtime.scratch_buf })
|
||||
}
|
||||
// This should never happen as the return type of exported functions should have been
|
||||
// validated by the code preparation process. However, because panics are really
|
||||
// undesirable in the runtime code, we treat this as a trap for now. Eventually, we might
|
||||
// want to revisit this.
|
||||
Ok(_) => Err(ExecError { reason: "return type error", buffer: runtime.scratch_buf }),
|
||||
// `Error::Module` is returned only if instantiation or linking failed (i.e.
|
||||
// wasm binary tried to import a function that is not provided by the host).
|
||||
// This shouldn't happen because validation process ought to reject such binaries.
|
||||
//
|
||||
// Because panics are really undesirable in the runtime code, we treat this as
|
||||
// a trap for now. Eventually, we might want to revisit this.
|
||||
Err(sandbox::Error::Module) =>
|
||||
Err(ExecError { reason: "validation error", buffer: runtime.scratch_buf }),
|
||||
// Any other kind of a trap should result in a failure.
|
||||
(Some(_), _) => VmExecResult::Trap("during execution"),
|
||||
// Any other case (such as special trap flag without actual trap) signifies
|
||||
// a logic error.
|
||||
_ => unreachable!(),
|
||||
Err(sandbox::Error::Execution) | Err(sandbox::Error::OutOfBounds) =>
|
||||
Err(ExecError { reason: "during execution", buffer: runtime.scratch_buf }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,14 +208,31 @@ fn read_sandbox_memory<E: Ext>(
|
||||
) -> Result<Vec<u8>, sandbox::HostError> {
|
||||
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
buf.resize(len as usize, 0);
|
||||
|
||||
ctx.memory().get(ptr, &mut buf)?;
|
||||
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
ctx.memory.get(ptr, buf.as_mut_slice()).map_err(|_| sandbox::HostError)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory into the scratch buffer, consuming an
|
||||
/// appropriate amount of gas. Resizes the scratch buffer to the specified length on success.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
fn read_sandbox_memory_into_scratch<E: Ext>(
|
||||
ctx: &mut Runtime<E>,
|
||||
ptr: u32,
|
||||
len: u32,
|
||||
) -> Result<(), sandbox::HostError> {
|
||||
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?;
|
||||
|
||||
ctx.scratch_buf.resize(len as usize, 0);
|
||||
ctx.memory.get(ptr, ctx.scratch_buf.as_mut_slice()).map_err(|_| sandbox::HostError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory into the supplied buffer, consuming
|
||||
/// an appropriate amount of gas.
|
||||
///
|
||||
@@ -209,7 +248,7 @@ fn read_sandbox_memory_into_buf<E: Ext>(
|
||||
) -> Result<(), sandbox::HostError> {
|
||||
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(buf.len() as u32))?;
|
||||
|
||||
ctx.memory().get(ptr, buf).map_err(Into::into)
|
||||
ctx.memory.get(ptr, buf).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory, consuming an appropriate amount of
|
||||
@@ -318,8 +357,15 @@ define_env!(Env, <E: Ext>,
|
||||
|
||||
// Make a call to another contract.
|
||||
//
|
||||
// Returns 0 on the successful execution and puts the result data returned by the callee into
|
||||
// the scratch buffer. Otherwise, returns a non-zero value and clears the scratch buffer.
|
||||
// If the called contract runs to completion, then this returns the status code the callee
|
||||
// returns on exit in the bottom 8 bits of the return value. The top 24 bits are 0s. A status
|
||||
// code of 0 indicates success, and any other code indicates a failure. On failure, any state
|
||||
// changes made by the called contract are reverted. The scratch buffer is filled with the
|
||||
// output data returned by the called contract, even in the case of a failure status.
|
||||
//
|
||||
// If the contract traps during execution or otherwise fails to complete successfully, then
|
||||
// this function clears the scratch buffer and returns 0x0100. As with a failure status, any
|
||||
// state changes made by the called contract are reverted.
|
||||
//
|
||||
// - callee_ptr: a pointer to the address of the callee contract.
|
||||
// Should be decodable as an `T::AccountId`. Traps otherwise.
|
||||
@@ -344,12 +390,10 @@ define_env!(Env, <E: Ext>,
|
||||
read_sandbox_memory_as(ctx, callee_ptr, callee_len)?;
|
||||
let value: BalanceOf<<E as Ext>::T> =
|
||||
read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
||||
let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?;
|
||||
|
||||
// Grab the scratch buffer and put in its' place an empty one.
|
||||
// We will use it for creating `EmptyOutputBuf` container for the call.
|
||||
let scratch_buf = mem::replace(&mut ctx.scratch_buf, Vec::new());
|
||||
let empty_output_buf = EmptyOutputBuf::from_spare_vec(scratch_buf);
|
||||
// Read input data into the scratch buffer, then take ownership of it.
|
||||
read_sandbox_memory_into_scratch(ctx, input_data_ptr, input_data_len)?;
|
||||
let input_data = mem::replace(&mut ctx.scratch_buf, Vec::new());
|
||||
|
||||
let nested_gas_limit = if gas == 0 {
|
||||
ctx.gas_meter.gas_left()
|
||||
@@ -364,22 +408,25 @@ define_env!(Env, <E: Ext>,
|
||||
&callee,
|
||||
value,
|
||||
nested_meter,
|
||||
&input_data,
|
||||
empty_output_buf
|
||||
input_data,
|
||||
)
|
||||
.map_err(|_| ())
|
||||
.map_err(|err| err.buffer)
|
||||
}
|
||||
// there is not enough gas to allocate for the nested call.
|
||||
None => Err(()),
|
||||
None => Err(input_data),
|
||||
}
|
||||
});
|
||||
|
||||
match call_outcome {
|
||||
Ok(CallReceipt { output_data }) => {
|
||||
ctx.scratch_buf = output_data;
|
||||
Ok(0)
|
||||
Ok(output) => {
|
||||
ctx.scratch_buf = output.data;
|
||||
Ok(output.status.into())
|
||||
},
|
||||
Err(buffer) => {
|
||||
ctx.scratch_buf = buffer;
|
||||
ctx.scratch_buf.clear();
|
||||
Ok(TRAP_RETURN_CODE)
|
||||
},
|
||||
Err(_) => Ok(1),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -388,6 +435,20 @@ define_env!(Env, <E: Ext>,
|
||||
// This function creates an account and executes the constructor defined in the code specified
|
||||
// by the code hash.
|
||||
//
|
||||
// If the constructor runs to completion, then this returns the status code that the newly
|
||||
// created contract returns on exit in the bottom 8 bits of the return value. The top 24 bits
|
||||
// are 0s. A status code of 0 indicates success, and any other code indicates a failure. On
|
||||
// failure, any state changes made by the called contract are reverted and the contract is not
|
||||
// instantiated. On a success status, the scratch buffer is filled with the encoded address of
|
||||
// the newly created contract. In the case of a failure status, the scratch buffer is cleared.
|
||||
//
|
||||
// If the contract traps during execution or otherwise fails to complete successfully, then
|
||||
// this function clears the scratch buffer and returns 0x0100. As with a failure status, any
|
||||
// state changes made by the called contract are reverted.
|
||||
|
||||
// This function creates an account and executes initializer code. After the execution,
|
||||
// the returned buffer is saved as the code of the created account.
|
||||
//
|
||||
// Returns 0 on the successful contract creation and puts the address of the created contract
|
||||
// into the scratch buffer. Otherwise, returns non-zero value and clears the scratch buffer.
|
||||
//
|
||||
@@ -413,10 +474,10 @@ define_env!(Env, <E: Ext>,
|
||||
read_sandbox_memory_as(ctx, code_hash_ptr, code_hash_len)?;
|
||||
let value: BalanceOf<<E as Ext>::T> =
|
||||
read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
||||
let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?;
|
||||
|
||||
// Clear the scratch buffer in any case.
|
||||
ctx.scratch_buf.clear();
|
||||
// Read input data into the scratch buffer, then take ownership of it.
|
||||
read_sandbox_memory_into_scratch(ctx, input_data_ptr, input_data_len)?;
|
||||
let input_data = mem::replace(&mut ctx.scratch_buf, Vec::new());
|
||||
|
||||
let nested_gas_limit = if gas == 0 {
|
||||
ctx.gas_meter.gas_left()
|
||||
@@ -431,21 +492,30 @@ define_env!(Env, <E: Ext>,
|
||||
&code_hash,
|
||||
value,
|
||||
nested_meter,
|
||||
&input_data
|
||||
input_data
|
||||
)
|
||||
.map_err(|_| ())
|
||||
.map_err(|err| err.buffer)
|
||||
}
|
||||
// there is not enough gas to allocate for the nested call.
|
||||
None => Err(()),
|
||||
None => Err(input_data),
|
||||
}
|
||||
});
|
||||
match instantiate_outcome {
|
||||
Ok(InstantiateReceipt { address }) => {
|
||||
// Write the address to the scratch buffer.
|
||||
address.encode_to(&mut ctx.scratch_buf);
|
||||
Ok(0)
|
||||
Ok((address, output)) => {
|
||||
let is_success = output.is_success();
|
||||
ctx.scratch_buf = output.data;
|
||||
ctx.scratch_buf.clear();
|
||||
if is_success {
|
||||
// Write the address to the scratch buffer.
|
||||
address.encode_to(&mut ctx.scratch_buf);
|
||||
}
|
||||
Ok(output.status.into())
|
||||
},
|
||||
Err(buffer) => {
|
||||
ctx.scratch_buf = buffer;
|
||||
ctx.scratch_buf.clear();
|
||||
Ok(TRAP_RETURN_CODE)
|
||||
},
|
||||
Err(_) => Ok(1),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -454,33 +524,11 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// This is the only way to return a data buffer to the caller.
|
||||
ext_return(ctx, data_ptr: u32, data_len: u32) => {
|
||||
match ctx
|
||||
.gas_meter
|
||||
.charge(
|
||||
ctx.schedule,
|
||||
RuntimeToken::ReturnData(data_len)
|
||||
)
|
||||
{
|
||||
GasMeterResult::Proceed => (),
|
||||
GasMeterResult::OutOfGas => return Err(sandbox::HostError),
|
||||
}
|
||||
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReturnData(data_len))?;
|
||||
|
||||
read_sandbox_memory_into_scratch(ctx, data_ptr, data_len)?;
|
||||
let output_buf = mem::replace(&mut ctx.scratch_buf, Vec::new());
|
||||
|
||||
let empty_output_buf = ctx
|
||||
.empty_output_buf
|
||||
.take()
|
||||
.expect(
|
||||
"`empty_output_buf` is taken only here;
|
||||
`ext_return` traps;
|
||||
`Runtime` can only be used only for one execution;
|
||||
qed"
|
||||
);
|
||||
let output_buf = empty_output_buf.fill(
|
||||
data_len as usize,
|
||||
|slice_mut| {
|
||||
// Read the memory at the specified pointer to the provided slice.
|
||||
ctx.memory.get(data_ptr, slice_mut)
|
||||
}
|
||||
)?;
|
||||
ctx.special_trap = Some(SpecialTrap::Return(output_buf));
|
||||
|
||||
// The trap mechanism is used to immediately terminate the execution.
|
||||
@@ -658,7 +706,7 @@ define_env!(Env, <E: Ext>,
|
||||
|
||||
// Returns the size of the scratch buffer.
|
||||
//
|
||||
// For more details on the scratch buffer see `ext_scratch_copy`.
|
||||
// For more details on the scratch buffer see `ext_scratch_read`.
|
||||
ext_scratch_size(ctx) -> u32 => {
|
||||
Ok(ctx.scratch_buf.len() as u32)
|
||||
},
|
||||
@@ -669,7 +717,7 @@ define_env!(Env, <E: Ext>,
|
||||
// In order to get size of the scratch buffer use `ext_scratch_size`. At the start of contract
|
||||
// execution, the scratch buffer is filled with the input data. Whenever a contract calls
|
||||
// function that uses the scratch buffer the contents of the scratch buffer are overwritten.
|
||||
ext_scratch_copy(ctx, dest_ptr: u32, offset: u32, len: u32) => {
|
||||
ext_scratch_read(ctx, dest_ptr: u32, offset: u32, len: u32) => {
|
||||
let offset = offset as usize;
|
||||
if offset > ctx.scratch_buf.len() {
|
||||
// Offset can't be larger than scratch buffer length.
|
||||
@@ -694,6 +742,15 @@ define_env!(Env, <E: Ext>,
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Copy data from contract memory starting from `src_ptr` with length `len` into the scratch
|
||||
// buffer. This overwrites the entire scratch buffer and resizes to `len`. Specifying a `len`
|
||||
// of zero clears the scratch buffer.
|
||||
//
|
||||
// This should be used before exiting a call or instantiation in order to set the return data.
|
||||
ext_scratch_write(ctx, src_ptr: u32, len: u32) => {
|
||||
read_sandbox_memory_into_scratch(ctx, src_ptr, len)
|
||||
},
|
||||
|
||||
// Deposit a contract event with the data buffer and optional list of topics. There is a limit
|
||||
// on the maximum number of topics specified by `max_event_topics`.
|
||||
//
|
||||
@@ -720,16 +777,11 @@ define_env!(Env, <E: Ext>,
|
||||
|
||||
let event_data = read_sandbox_memory(ctx, data_ptr, data_len)?;
|
||||
|
||||
match ctx
|
||||
.gas_meter
|
||||
.charge(
|
||||
ctx.schedule,
|
||||
RuntimeToken::DepositEvent(topics.len() as u32, data_len)
|
||||
)
|
||||
{
|
||||
GasMeterResult::Proceed => (),
|
||||
GasMeterResult::OutOfGas => return Err(sandbox::HostError),
|
||||
}
|
||||
charge_gas(
|
||||
ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
RuntimeToken::DepositEvent(topics.len() as u32, data_len)
|
||||
)?;
|
||||
ctx.ext.deposit_event(topics, event_data);
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user