mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 18:37:59 +00:00
Contracts module rejig (#1358)
* Move prepare under code. * Schedule update * CodeHash * create takes code_hash * pass mem def and use code in vm::execute * Actually save and load code * Use T::Hash as CodeHash * Explicit entrypoint name * Return code_hash and deposit an Event * Charge for deployed code with gas. * ImportSatisfyCheck and FunctionImplProvider * Progress. * Use new infrastructure for checking imports * Rename entrypoint to entrypoint_name * Use strings instead of a Error enum * Clean * WIP * Fix macro_define_env test. * Fix vm code tests. * Remove tests for now. * Fix borked merge * Fix build for wasm * fmt * Scaffolding for abstracting vm. * Hook up execution to exec layer. * Fix vm tests. * Use schedule directly in WasmLoader * Implement test language. * Add input_data test. * Max depth test * ext_caller * Simplify test. * Add TODO * Some tests and todos. * top_level * Clean. * Restore a couple of integration tests. * Add a few comments. * Add ext_address runtime call. * Deduplicate caller/self_account * Add not_exists test. * Change bool to TransferCause. * Add address tests. * Remove output_buf from parameter. * return from start fn. * Smart gas meter * Tracing * Fix prepare tests. * Code moving * Add ExecFeeToken * Use tokens everywhere. * Make it compile in no_std. * Lift all test requirements to TestAuxiliaries * A minor clean * First create tests * Remove unneeded TODO * Docs. * Code shuffling * Rename create → instantiate * Add test address. * Code shuffling * Add base_fee tests. * rejig the code * Add some comments * on_finalise comment * Move event deposit further * Update Cargo.lock * Use crates.io version of pwasm-utils * Format todo comments * Fix formatting * Comments * EmptyOutputBuf and OutputBuf split. * Restore code_hash * Fix node-executor. * Fix typo * Fix fmt * Update srml/contract/src/account_db.rs Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Update srml/contract/src/lib.rs Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Line wraps * Wrapping macros * Add _ prefix * Grumbles * Doc updates. * Update srml/contract/src/wasm/mod.rs Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Update srml/contract/src/lib.rs Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Add comment * Use saturation to signal overflow * Add prepare_test! macro * Require deploy function. * Add entry point tests * Add comment. * Rename code → code_cache to better describe * Get rid of weird match! * Recompile binaries * Add comments * refuse_instantiate_with_value_below_existential_deposit * Little fix * Make test more complete * Clean * Add integration test for instantiation * Rebuild runtime. * Add some tests. * Attach an issue to a TODO * Attach another issue * Apply suggestions from code review Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Update srml/contract/src/exec.rs Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Update srml/contract/src/exec.rs Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Recompile node_runtime
This commit is contained in:
@@ -14,6 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// TODO: #1417 Add more integration tests
|
||||
// also remove the #![allow(unused)] below.
|
||||
|
||||
#![allow(unused)]
|
||||
|
||||
use runtime_io::with_externalities;
|
||||
use runtime_primitives::testing::{Digest, DigestItem, H256, Header};
|
||||
use runtime_primitives::traits::{BlakeTwo256, IdentityLookup};
|
||||
@@ -23,8 +28,8 @@ use substrate_primitives::{Blake2Hasher};
|
||||
use system::{Phase, EventRecord};
|
||||
use wabt;
|
||||
use {
|
||||
runtime_io, balances, system, CodeOf, ContractAddressFor,
|
||||
GenesisConfig, Module, StorageOf, Trait, RawEvent,
|
||||
balances, runtime_io, system, ContractAddressFor, GenesisConfig, Module, RawEvent, StorageOf,
|
||||
Trait,
|
||||
};
|
||||
|
||||
impl_outer_origin! {
|
||||
@@ -32,6 +37,9 @@ impl_outer_origin! {
|
||||
}
|
||||
|
||||
mod contract {
|
||||
// Re-export contents of the root. This basically
|
||||
// needs to give a name for the current crate.
|
||||
// This hack is required for `impl_outer_event!`.
|
||||
pub use super::super::*;
|
||||
}
|
||||
impl_outer_event! {
|
||||
@@ -73,13 +81,17 @@ type Contract = Module<Test>;
|
||||
type System = system::Module<Test>;
|
||||
|
||||
pub struct DummyContractAddressFor;
|
||||
impl ContractAddressFor<u64> for DummyContractAddressFor {
|
||||
fn contract_address_for(_code: &[u8], _data: &[u8], origin: &u64) -> u64 {
|
||||
origin + 1
|
||||
impl ContractAddressFor<H256, u64> for DummyContractAddressFor {
|
||||
fn contract_address_for(_code_hash: &H256, _data: &[u8], origin: &u64) -> u64 {
|
||||
*origin + 1
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtBuilder {
|
||||
const ALICE: u64 = 1;
|
||||
const BOB: u64 = 2;
|
||||
const CHARLIE: u64 = 3;
|
||||
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
gas_price: u64,
|
||||
block_gas_limit: u64,
|
||||
@@ -98,30 +110,31 @@ impl Default for ExtBuilder {
|
||||
}
|
||||
}
|
||||
impl ExtBuilder {
|
||||
fn existential_deposit(mut self, existential_deposit: u64) -> Self {
|
||||
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
|
||||
self.existential_deposit = existential_deposit;
|
||||
self
|
||||
}
|
||||
fn gas_price(mut self, gas_price: u64) -> Self {
|
||||
pub fn gas_price(mut self, gas_price: u64) -> Self {
|
||||
self.gas_price = gas_price;
|
||||
self
|
||||
}
|
||||
fn block_gas_limit(mut self, block_gas_limit: u64) -> Self {
|
||||
pub fn block_gas_limit(mut self, block_gas_limit: u64) -> Self {
|
||||
self.block_gas_limit = block_gas_limit;
|
||||
self
|
||||
}
|
||||
fn transfer_fee(mut self, transfer_fee: u64) -> Self {
|
||||
pub fn transfer_fee(mut self, transfer_fee: u64) -> Self {
|
||||
self.transfer_fee = transfer_fee;
|
||||
self
|
||||
}
|
||||
fn creation_fee(mut self, creation_fee: u64) -> Self {
|
||||
pub fn creation_fee(mut self, creation_fee: u64) -> Self {
|
||||
self.creation_fee = creation_fee;
|
||||
self
|
||||
}
|
||||
fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
pub fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::<Test>::default()
|
||||
.build_storage()
|
||||
.unwrap().0;
|
||||
.unwrap()
|
||||
.0;
|
||||
t.extend(
|
||||
balances::GenesisConfig::<Test> {
|
||||
balances: vec![],
|
||||
@@ -130,8 +143,10 @@ impl ExtBuilder {
|
||||
existential_deposit: self.existential_deposit,
|
||||
transfer_fee: self.transfer_fee,
|
||||
creation_fee: self.creation_fee,
|
||||
}.build_storage()
|
||||
.unwrap().0,
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap()
|
||||
.0,
|
||||
);
|
||||
t.extend(
|
||||
GenesisConfig::<Test> {
|
||||
@@ -142,544 +157,33 @@ impl ExtBuilder {
|
||||
max_depth: 100,
|
||||
block_gas_limit: self.block_gas_limit,
|
||||
current_schedule: Default::default(),
|
||||
}.build_storage()
|
||||
.unwrap().0,
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap()
|
||||
.0,
|
||||
);
|
||||
runtime_io::TestExternalities::new(t)
|
||||
}
|
||||
}
|
||||
|
||||
const CODE_TRANSFER: &str = r#"
|
||||
(module
|
||||
;; ext_call(
|
||||
;; callee_ptr: u32,
|
||||
;; callee_len: u32,
|
||||
;; gas: u64,
|
||||
;; value_ptr: u32,
|
||||
;; value_len: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32
|
||||
;; ) -> u32
|
||||
(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $ext_call
|
||||
(i32.const 4) ;; 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 12) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer.
|
||||
(i32.const 0) ;; Pointer to input data buffer address
|
||||
(i32.const 0) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
)
|
||||
;; Destination AccountId to transfer the funds.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 4) "\09\00\00\00\00\00\00\00")
|
||||
;; Amount of value to transfer.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 12) "\06\00\00\00\00\00\00\00")
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn contract_transfer() {
|
||||
const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6;
|
||||
const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9;
|
||||
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_transfer.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&1, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 26 - gas used by the contract (26) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (by transaction)
|
||||
// 2 * 135 - base gas fee for call (by the contract)
|
||||
100_000_000 - 3 - (2 * 26) - (2 * 135) - (2 * 135),
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&1),
|
||||
11 + 3 - CONTRACT_SHOULD_TRANSFER_VALUE,
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO),
|
||||
CONTRACT_SHOULD_TRANSFER_VALUE,
|
||||
);
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::balances(
|
||||
balances::RawEvent::NewAccount(
|
||||
CONTRACT_SHOULD_TRANSFER_TO,
|
||||
6
|
||||
)
|
||||
),
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Transfer(0, 1, 3)),
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Transfer(1, CONTRACT_SHOULD_TRANSFER_TO, 6)),
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer_to_death() {
|
||||
const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6;
|
||||
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(5).build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_transfer.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
Balances::set_free_balance(&1, 6);
|
||||
Balances::increase_total_stake_by(6);
|
||||
<StorageOf<Test>>::insert(1, b"foo".to_vec(), b"1".to_vec());
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 2 * 26 - gas used by the contract (26) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (by transaction)
|
||||
// 2 * 135 - base gas fee for call (by the contract)
|
||||
100_000_000 - (2 * 26) - (2 * 135) - (2 * 135),
|
||||
);
|
||||
|
||||
assert!(!<CodeOf<Test>>::exists(1));
|
||||
assert!(!<StorageOf<Test>>::exists(1, b"foo".to_vec()));
|
||||
assert_eq!(Balances::free_balance(&1), 0);
|
||||
|
||||
assert_eq!(Balances::free_balance(&9), CONTRACT_SHOULD_TRANSFER_VALUE);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer_takes_creation_fee() {
|
||||
const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6;
|
||||
const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9;
|
||||
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().creation_fee(105).build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_transfer.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&1, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 26 - gas used by the contract (26) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (by transaction)
|
||||
// 2 * 135 - base gas fee for call (by the contract)
|
||||
// 104 - (rounded) fee per creation (by the contract)
|
||||
100_000_000 - 3 - (2 * 26) - (2 * 135) - (2 * 135) - 104,
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&1),
|
||||
11 + 3 - CONTRACT_SHOULD_TRANSFER_VALUE,
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO),
|
||||
CONTRACT_SHOULD_TRANSFER_VALUE,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer_takes_transfer_fee() {
|
||||
const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6;
|
||||
const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9;
|
||||
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().creation_fee(105).transfer_fee(45).build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_transfer.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&1, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
// Create destination account here so we can check that transfer fee
|
||||
// is charged (and creation fee is not).
|
||||
Balances::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 25);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 26 - gas used by the contract (26) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (by transaction)
|
||||
// 44 - (rounded from 45) fee per transfer (by transaction)
|
||||
// 2 * 135 - base gas fee for call (by the contract)
|
||||
// 44 - (rounded from 45) fee per transfer (by the contract)
|
||||
100_000_000 - 3 - (2 * 26) - (2 * 135) - 44 - (2 * 135) - 44,
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&1),
|
||||
11 + 3 - CONTRACT_SHOULD_TRANSFER_VALUE,
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO),
|
||||
25 + CONTRACT_SHOULD_TRANSFER_VALUE,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer_oog() {
|
||||
const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9;
|
||||
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_transfer.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&1, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), (135 + 135 + 7).into(), Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 7 - gas used by the contract (7) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (by transaction)
|
||||
// 2 * 135 - base gas fee for call (by contract)
|
||||
100_000_000 - 3 - (2 * 7) - (2 * 135) - (2 * 135),
|
||||
);
|
||||
|
||||
// Transaction level transfer should succeed.
|
||||
assert_eq!(Balances::free_balance(&1), 14);
|
||||
// But `ext_call` should not.
|
||||
assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0);
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Transfer(0, 1, 3)),
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer_max_depth() {
|
||||
const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9;
|
||||
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
<CodeOf<Test>>::insert(CONTRACT_SHOULD_TRANSFER_TO, code_transfer.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), CONTRACT_SHOULD_TRANSFER_TO, 3.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 3 - value sent with the transaction
|
||||
// 2 * 26 * 100 - gas used by the contract (26) multiplied by gas price (2)
|
||||
// multiplied by max depth (100).
|
||||
// 2 * 135 * 100 - base gas fee for call (by transaction) multiplied by max depth (100).
|
||||
100_000_000 - 3 - (2 * 26 * 100) - (2 * 135 * 100),
|
||||
);
|
||||
assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 14);
|
||||
});
|
||||
}
|
||||
|
||||
/// Convert a byte slice to a string with hex values.
|
||||
///
|
||||
/// Each value is preceeded with a `\` character.
|
||||
fn escaped_bytestring(bytes: &[u8]) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut result = String::new();
|
||||
for b in bytes {
|
||||
write!(result, "\\{:02x}", b).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Create a constructor for the specified code.
|
||||
///
|
||||
/// When constructor is executed, it will call `ext_return` with code that
|
||||
/// specified in `child_bytecode`.
|
||||
fn code_ctor(child_bytecode: &[u8]) -> String {
|
||||
format!(
|
||||
r#"
|
||||
(module
|
||||
;; ext_return(data_ptr: u32, data_len: u32) -> !
|
||||
(import "env" "ext_return" (func $ext_return (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(call $ext_return
|
||||
(i32.const 4)
|
||||
(i32.const {code_len})
|
||||
)
|
||||
;; ext_return is diverging, i.e. doesn't return.
|
||||
unreachable
|
||||
)
|
||||
(data (i32.const 4) "{escaped_bytecode}")
|
||||
)
|
||||
"#,
|
||||
escaped_bytecode = escaped_bytestring(child_bytecode),
|
||||
code_len = child_bytecode.len(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns code that uses `ext_create` runtime call.
|
||||
///
|
||||
/// Takes bytecode of the contract that needs to be deployed.
|
||||
fn code_create(constructor: &[u8]) -> String {
|
||||
format!(
|
||||
r#"
|
||||
(module
|
||||
;; ext_create(
|
||||
;; code_ptr: u32,
|
||||
;; code_len: u32,
|
||||
;; gas: u64,
|
||||
;; value_ptr: u32,
|
||||
;; value_len: u32,
|
||||
;; input_data_ptr: u32,
|
||||
;; input_data_len: u32,
|
||||
;; ) -> u32
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $ext_create
|
||||
(i32.const 12) ;; Pointer to `code`
|
||||
(i32.const {code_len}) ;; Length of `code`
|
||||
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
|
||||
(i32.const 4) ;; Pointer to the buffer with value to transfer
|
||||
(i32.const 8) ;; Length of the buffer with value to transfer
|
||||
(i32.const 0) ;; Pointer to input data buffer address
|
||||
(i32.const 0) ;; Length of input data buffer
|
||||
)
|
||||
)
|
||||
)
|
||||
;; Amount of value to transfer.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 4) "\03\00\00\00\00\00\00\00")
|
||||
;; Embedded wasm code.
|
||||
(data (i32.const 12) "{escaped_constructor}")
|
||||
)
|
||||
"#,
|
||||
escaped_constructor = escaped_bytestring(constructor),
|
||||
code_len = constructor.len(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_create() {
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
let code_ctor_transfer = wabt::wat2wasm(&code_ctor(&code_transfer)).unwrap();
|
||||
let code_create = wabt::wat2wasm(&code_create(&code_ctor_transfer)).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&1, 0);
|
||||
Balances::set_free_balance(&9, 30);
|
||||
Balances::increase_total_stake_by(30);
|
||||
|
||||
<CodeOf<Test>>::insert(1, code_create.to_vec());
|
||||
|
||||
// When invoked, the contract at address `1` must create a contract with 'transfer' code.
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 11.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
let derived_address = <Test as Trait>::DetermineContractAddress::contract_address_for(
|
||||
&code_ctor_transfer,
|
||||
&[],
|
||||
&1,
|
||||
);
|
||||
|
||||
// 11 - value sent with the transaction
|
||||
// 2 * 362 - gas spent by the deployer contract (362) multiplied by gas price (2)
|
||||
// 2 * 135 - base gas fee for call (top level)
|
||||
// 2 * 175 - base gas fee for create (by contract)
|
||||
// ((21 / 2) * 2) - price per account creation
|
||||
let expected_gas_after_create =
|
||||
100_000_000 - 11 - (2 * 362) - (2 * 135) - (2 * 175) - ((21 / 2) * 2);
|
||||
assert_eq!(Balances::free_balance(&0), expected_gas_after_create);
|
||||
assert_eq!(Balances::free_balance(&1), 8);
|
||||
assert_eq!(Balances::free_balance(&derived_address), 3);
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::balances(
|
||||
balances::RawEvent::NewAccount(
|
||||
derived_address,
|
||||
3
|
||||
)
|
||||
),
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Transfer(0, 1, 11)),
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Transfer(1, 2, 3)),
|
||||
},
|
||||
]);
|
||||
|
||||
// Initiate transfer to the newly created contract.
|
||||
assert_ok!(Contract::call(Origin::signed(0), derived_address, 22.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 22 - value sent with the transaction
|
||||
// (2 * 26) - gas used by the contract
|
||||
// (2 * 135) - base gas fee for call (top level)
|
||||
// (2 * 135) - base gas fee for call (by transfer contract)
|
||||
expected_gas_after_create - 22 - (2 * 26) - (2 * 135) - (2 * 135),
|
||||
);
|
||||
assert_eq!(Balances::free_balance(&derived_address), 22 - 3);
|
||||
assert_eq!(Balances::free_balance(&9), 36);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_create() {
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
let code_ctor_transfer = wabt::wat2wasm(&code_ctor(&code_transfer)).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().gas_price(3).build(), || {
|
||||
let derived_address = <Test as Trait>::DetermineContractAddress::contract_address_for(
|
||||
&code_ctor_transfer,
|
||||
&[],
|
||||
&0,
|
||||
);
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&derived_address, 30);
|
||||
Balances::increase_total_stake_by(30);
|
||||
|
||||
assert_ok!(Contract::create(
|
||||
Origin::signed(0),
|
||||
11.into(),
|
||||
100_000.into(),
|
||||
code_ctor_transfer.clone(),
|
||||
Vec::new(),
|
||||
));
|
||||
|
||||
// 11 - value sent with the transaction
|
||||
// (3 * 129) - gas spent by the init_code.
|
||||
// (3 * 175) - base gas fee for create (175) (top level) multipled by gas price (3)
|
||||
// ((21 / 3) * 3) - price for contract creation
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
100_000_000 - 11 - (3 * 129) - (3 * 175) - ((21 / 3) * 3)
|
||||
);
|
||||
assert_eq!(Balances::free_balance(&derived_address), 30 + 11);
|
||||
|
||||
assert_eq!(<CodeOf<Test>>::get(&derived_address), code_transfer);
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Transfer(0, derived_address, 11)),
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Created(0, 1)),
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
const CODE_NOP: &'static str = r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
nop
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn refunds_unused_gas() {
|
||||
let code_nop = wabt::wat2wasm(CODE_NOP).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_nop.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
assert_eq!(Balances::free_balance(&0), 100_000_000 - 4 - (2 * 135));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_with_zero_value() {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
<CodeOf<Test>>::insert(1, vec![]);
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new()));
|
||||
assert_ok!(Contract::call(
|
||||
Origin::signed(0),
|
||||
1,
|
||||
0.into(),
|
||||
100_000.into(),
|
||||
Vec::new()
|
||||
));
|
||||
|
||||
assert_eq!(Balances::free_balance(&0), 100_000_000 - (2 * 135));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_with_zero_endowment() {
|
||||
let code_nop = wabt::wat2wasm(CODE_NOP).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
assert_ok!(Contract::create(Origin::signed(0), 0.into(), 100_000.into(), code_nop, Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
Balances::free_balance(&0),
|
||||
// 4 - for the gas spent by the constructor
|
||||
// 2 * 175 - base gas fee for create (175) multiplied by gas price (2) (top level)
|
||||
100_000_000 - 4 - (2 * 175),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_removal_removes_storage() {
|
||||
with_externalities(
|
||||
@@ -723,232 +227,74 @@ fn account_removal_removes_storage() {
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_UNREACHABLE: &'static str = r#"
|
||||
const CODE_RETURN_FROM_START_FN: &str = r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
nop
|
||||
unreachable
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn top_level_call_refunds_even_if_fails() {
|
||||
let code_unreachable = wabt::wat2wasm(CODE_UNREACHABLE).unwrap();
|
||||
with_externalities(&mut ExtBuilder::default().gas_price(4).build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_unreachable.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
assert_err!(
|
||||
Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new()),
|
||||
"vm execute returned error while call"
|
||||
);
|
||||
|
||||
assert_eq!(Balances::free_balance(&0), 100_000_000 - (4 * 3) - (4 * 135));
|
||||
|
||||
assert_eq!(System::events(), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
const CODE_LOOP: &'static str = r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
(loop
|
||||
(br 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn block_gas_limit() {
|
||||
let code_loop = wabt::wat2wasm(CODE_LOOP).unwrap();
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default().block_gas_limit(100_000).build(),
|
||||
|| {
|
||||
<CodeOf<Test>>::insert(1, code_loop.to_vec());
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
// Spend 50_000 units of gas (OOG).
|
||||
assert_err!(
|
||||
Contract::call(Origin::signed(0), 1, 0.into(), 50_000.into(), Vec::new()),
|
||||
"vm execute returned error while call"
|
||||
);
|
||||
|
||||
// Ensure we can't spend more gas than available in block gas limit.
|
||||
assert_err!(
|
||||
Contract::call(Origin::signed(0), 1, 0.into(), 50_001.into(), Vec::new()),
|
||||
"block gas limit is reached"
|
||||
);
|
||||
|
||||
// However, we can spend another 50_000
|
||||
assert_err!(
|
||||
Contract::call(Origin::signed(0), 1, 0.into(), 50_000.into(), Vec::new()),
|
||||
"vm execute returned error while call"
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_INPUT_DATA: &'static str = r#"
|
||||
(module
|
||||
(import "env" "ext_input_size" (func $ext_input_size (result i32)))
|
||||
(import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32)))
|
||||
(import "env" "ext_return" (func $ext_return (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call")
|
||||
(block $fail
|
||||
;; fail if ext_input_size != 4
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.const 4)
|
||||
(call $ext_input_size)
|
||||
)
|
||||
)
|
||||
|
||||
(call $ext_input_copy
|
||||
(i32.const 0)
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
)
|
||||
|
||||
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 0))
|
||||
(i32.const 0)
|
||||
)
|
||||
)
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 1))
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 2))
|
||||
(i32.const 2)
|
||||
)
|
||||
)
|
||||
(br_if $fail
|
||||
(i32.ne
|
||||
(i32.load8_u (i32.const 3))
|
||||
(i32.const 3)
|
||||
)
|
||||
)
|
||||
|
||||
(return)
|
||||
(start $start)
|
||||
(func $start
|
||||
(call $ext_return
|
||||
(i32.const 8)
|
||||
(i32.const 4)
|
||||
)
|
||||
unreachable
|
||||
(unreachable)
|
||||
)
|
||||
|
||||
(func (export "call")
|
||||
(unreachable)
|
||||
)
|
||||
(func (export "deploy"))
|
||||
|
||||
(data (i32.const 8) "\01\02\03\04")
|
||||
)
|
||||
"#;
|
||||
const HASH_RETURN_FROM_START_FN: [u8; 32] = hex!("e6411d12daa2a19e4e9c7d8306c31c7d53a352cb8ed84385c8a1d48fc232e708");
|
||||
|
||||
#[test]
|
||||
fn input_data() {
|
||||
let code_input_data = wabt::wat2wasm(CODE_INPUT_DATA).unwrap();
|
||||
fn instantiate_and_call() {
|
||||
let wasm = wabt::wat2wasm(CODE_RETURN_FROM_START_FN).unwrap();
|
||||
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default().build(),
|
||||
&mut ExtBuilder::default().existential_deposit(100).build(),
|
||||
|| {
|
||||
<CodeOf<Test>>::insert(1, code_input_data.to_vec());
|
||||
Balances::set_free_balance(&ALICE, 1_000_000);
|
||||
Balances::increase_total_stake_by(1_000_000);
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
assert_ok!(Contract::put_code(
|
||||
Origin::signed(ALICE),
|
||||
100_000.into(),
|
||||
wasm,
|
||||
));
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 50_000.into(), vec![0, 1, 2, 3]));
|
||||
assert_ok!(Contract::create(
|
||||
Origin::signed(ALICE),
|
||||
100.into(),
|
||||
100_000.into(),
|
||||
HASH_RETURN_FROM_START_FN.into(),
|
||||
vec![],
|
||||
));
|
||||
|
||||
// all asserts are made within contract code itself.
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::CodeStored(HASH_RETURN_FROM_START_FN.into())),
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::balances(
|
||||
balances::RawEvent::NewAccount(BOB, 100)
|
||||
)
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100))
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB))
|
||||
}
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Stores the caller into the storage under the [0x00; 32] key in the contract's storage.
|
||||
const CODE_CALLER_LOGGER: &'static str = r#"
|
||||
(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_set_storage" (func $ext_set_storage (param i32 i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; Memory layout
|
||||
;; [0..32]: the storage key (passed as the key for ext_set_storage)
|
||||
;; [32..40]: contents of the scratch buffer (which is expected to be 8 bytes long)
|
||||
|
||||
(func (export "call")
|
||||
;; Fill the scratch buffer with the caller.
|
||||
(call $ext_caller)
|
||||
|
||||
;; Copy contents of the scratch buffer into the contract's memory.
|
||||
(call $ext_scratch_copy
|
||||
(i32.const 32) ;; Store scratch's buffer contents at this address.
|
||||
(i32.const 0) ;; Offset from the start of the scratch buffer.
|
||||
(i32.const 8) ;; Count of bytes to copy.
|
||||
)
|
||||
|
||||
(call $ext_set_storage
|
||||
(i32.const 0) ;; The storage key to save the value at. 32 bytes long.
|
||||
(i32.const 1) ;; value_not_null=1, i.e. we are not removing the value
|
||||
(i32.const 32) ;; the pointer to the value to store
|
||||
(i32.const 8) ;; the length of the value
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn caller_top_level() {
|
||||
let code_caller_logger = wabt::wat2wasm(CODE_CALLER_LOGGER).unwrap();
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default().build(),
|
||||
|| {
|
||||
<CodeOf<Test>>::insert(1, code_caller_logger.to_vec());
|
||||
|
||||
Balances::set_free_balance(&2, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(2), 1, 0.into(), 50_000.into(), vec![]));
|
||||
|
||||
// Load the zero-th slot of the storage of the caller logger contract.
|
||||
// We verify here that the caller logger contract has witnessed the call coming from
|
||||
// the account with address 0x02 (see the origin above) - the origin of the tx.
|
||||
assert_eq!(
|
||||
<StorageOf<Test>>::get(1, vec![0; 32]),
|
||||
Some(vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caller_contract() {
|
||||
const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9;
|
||||
|
||||
let code_caller_logger = wabt::wat2wasm(CODE_CALLER_LOGGER).unwrap();
|
||||
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
|
||||
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
<CodeOf<Test>>::insert(1, code_transfer.to_vec());
|
||||
<CodeOf<Test>>::insert(CONTRACT_SHOULD_TRANSFER_TO, code_caller_logger);
|
||||
|
||||
Balances::set_free_balance(&0, 100_000_000);
|
||||
Balances::increase_total_stake_by(100_000_000);
|
||||
Balances::set_free_balance(&1, 11);
|
||||
Balances::increase_total_stake_by(11);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new()));
|
||||
|
||||
// Load the zero-th slot of the storage of the caller logger contract.
|
||||
// We verify here that the caller logger contract has witnessed the call coming from
|
||||
// the caller contract - 0x01.
|
||||
assert_eq!(
|
||||
<StorageOf<Test>>::get(CONTRACT_SHOULD_TRANSFER_TO, vec![0; 32]),
|
||||
Some(vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user