mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 04:41:02 +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:
@@ -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(),
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user