mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-09 02:28:05 +00:00
Sandboxing and the simplest smart-contract runtime (#140)
* Add primitives for sandboxing. * Add sandbox module. * Implement the runtime part of the sandbox. * Rebuild binaries. * Implement smart-contract execution. * Add more documentation.
This commit is contained in:
committed by
Robert Habermeier
parent
f116f67382
commit
5a56fbcea3
@@ -5,7 +5,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
hex-literal = "0.1.0"
|
||||
integer-sqrt = "0.1.0"
|
||||
integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" }
|
||||
serde = { version = "1.0", default_features = false }
|
||||
safe-mix = { path = "../../../safe-mix", default_features = false}
|
||||
substrate-keyring = { path = "../../keyring", optional = true }
|
||||
|
||||
@@ -5,7 +5,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
num-traits = { version = "0.2", default_features = false }
|
||||
integer-sqrt = "0.1.0"
|
||||
integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" }
|
||||
serde = { version = "1.0", optional = true }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
substrate-codec = { path = "../../codec", default_features = false }
|
||||
|
||||
@@ -12,12 +12,16 @@ substrate-codec = { path = "../../codec", default_features = false }
|
||||
substrate-primitives = { path = "../../primitives", default_features = false }
|
||||
substrate-runtime-std = { path = "../../runtime-std", default_features = false }
|
||||
substrate-runtime-io = { path = "../../runtime-io", default_features = false }
|
||||
substrate-runtime-sandbox = { path = "../../runtime-sandbox", default_features = false }
|
||||
substrate-runtime-support = { path = "../../runtime-support", default_features = false }
|
||||
substrate-runtime-primitives = { path = "../primitives", default_features = false }
|
||||
substrate-runtime-consensus = { path = "../consensus", default_features = false }
|
||||
substrate-runtime-system = { path = "../system", default_features = false }
|
||||
substrate-runtime-session = { path = "../session", default_features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
wabt = "0.1.7"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
@@ -28,6 +32,7 @@ std = [
|
||||
"substrate-primitives/std",
|
||||
"substrate-runtime-std/std",
|
||||
"substrate-runtime-io/std",
|
||||
"substrate-runtime-sandbox/std",
|
||||
"substrate-runtime-support/std",
|
||||
"substrate-runtime-primitives/std",
|
||||
"substrate-runtime-session/std",
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Smart-contract execution module.
|
||||
|
||||
use codec::Slicable;
|
||||
use primitives::traits::As;
|
||||
use rstd::prelude::*;
|
||||
use sandbox;
|
||||
use {AccountDb, Module, OverlayAccountDb, Trait};
|
||||
|
||||
struct ExecutionExt<'a, 'b: 'a, T: Trait + 'b> {
|
||||
account_db: &'a mut OverlayAccountDb<'b, T>,
|
||||
account: T::AccountId,
|
||||
memory: sandbox::Memory,
|
||||
}
|
||||
impl<'a, 'b: 'a, T: Trait> ExecutionExt<'a, 'b, T> {
|
||||
fn account(&self) -> &T::AccountId {
|
||||
&self.account
|
||||
}
|
||||
fn account_db(&self) -> &OverlayAccountDb<T> {
|
||||
self.account_db
|
||||
}
|
||||
fn account_db_mut(&mut self) -> &mut OverlayAccountDb<'b, T> {
|
||||
self.account_db
|
||||
}
|
||||
fn memory(&self) -> &sandbox::Memory {
|
||||
&self.memory
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn execute<'a, 'b: 'a, T: Trait>(
|
||||
code: &[u8],
|
||||
account: &T::AccountId,
|
||||
account_db: &'a mut OverlayAccountDb<'b, T>,
|
||||
) -> bool {
|
||||
// ext_put_storage(location_ptr: u32, value_non_null: u32, value_ptr: u32);
|
||||
//
|
||||
// Change the value at the given location in storage or remove it.
|
||||
//
|
||||
// - location_ptr: pointer into the linear
|
||||
// memory where the location of the requested value is placed.
|
||||
// - value_non_null: if set to 0, then the entry
|
||||
// at the given location will be removed.
|
||||
// - value_ptr: pointer into the linear memory
|
||||
// where the value to set is placed. If `value_non_null` is set to 0, then this parameter is ignored.
|
||||
fn ext_set_storage<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
let location_ptr = args[0].as_i32().unwrap() as u32;
|
||||
let value_non_null = args[1].as_i32().unwrap() as u32;
|
||||
let value_ptr = args[2].as_i32().unwrap() as u32;
|
||||
|
||||
let mut location = [0; 32];
|
||||
|
||||
e.memory().get(location_ptr, &mut location)?;
|
||||
let account = e.account().clone();
|
||||
|
||||
if value_non_null != 0 {
|
||||
let mut value = [0; 32];
|
||||
e.memory().get(value_ptr, &mut value)?;
|
||||
e.account_db_mut()
|
||||
.set_storage(&account, location.to_vec(), Some(value.to_vec()));
|
||||
} else {
|
||||
e.account_db_mut()
|
||||
.set_storage(&account, location.to_vec(), None);
|
||||
}
|
||||
|
||||
Ok(sandbox::ReturnValue::Unit)
|
||||
}
|
||||
|
||||
// ext_get_storage(location_ptr: u32, dest_ptr: u32);
|
||||
//
|
||||
// Retrieve the value at the given location from the strorage.
|
||||
// If there is no entry at the given location then all-zero-value
|
||||
// will be returned.
|
||||
//
|
||||
// - location_ptr: pointer into the linear
|
||||
// memory where the location of the requested value is placed.
|
||||
// - dest_ptr: pointer where contents of the specified storage location
|
||||
// should be placed.
|
||||
fn ext_get_storage<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
let location_ptr = args[0].as_i32().unwrap() as u32;
|
||||
let dest_ptr = args[1].as_i32().unwrap() as u32;
|
||||
|
||||
let mut location = [0; 32];
|
||||
e.memory().get(location_ptr, &mut location)?;
|
||||
|
||||
let account = e.account().clone();
|
||||
if let Some(value) = e.account_db_mut().get_storage(&account, &location) {
|
||||
e.memory().set(dest_ptr, &value)?;
|
||||
} else {
|
||||
e.memory().set(dest_ptr, &[0u8; 32])?;
|
||||
}
|
||||
|
||||
Ok(sandbox::ReturnValue::Unit)
|
||||
}
|
||||
|
||||
// ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32)
|
||||
fn ext_transfer<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
let transfer_to_ptr = args[0].as_i32().unwrap() as u32;
|
||||
let transfer_to_len = args[1].as_i32().unwrap() as u32;
|
||||
let value = args[2].as_i32().unwrap() as u64;
|
||||
|
||||
// TODO: slicable
|
||||
let mut transfer_to = Vec::new();
|
||||
transfer_to.resize(transfer_to_len as usize, 0);
|
||||
e.memory().get(transfer_to_ptr, &mut transfer_to)?;
|
||||
let value = T::Balance::sa(value as usize);
|
||||
let transfer_to = T::AccountId::decode(&mut &transfer_to[..]).unwrap();
|
||||
|
||||
let account = e.account().clone();
|
||||
if let Some(commit_state) =
|
||||
Module::<T>::effect_transfer(&account, &transfer_to, value, e.account_db())
|
||||
{
|
||||
e.account_db_mut().merge(commit_state);
|
||||
}
|
||||
|
||||
Ok(sandbox::ReturnValue::Unit)
|
||||
}
|
||||
|
||||
// ext_create(code_ptr: u32, code_len: u32, value: u32)
|
||||
fn ext_create<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
let code_ptr = args[0].as_i32().unwrap() as u32;
|
||||
let code_len = args[1].as_i32().unwrap() as u32;
|
||||
let value = args[2].as_i32().unwrap() as u32;
|
||||
|
||||
// TODO: slicable
|
||||
let value = T::Balance::sa(value as usize);
|
||||
|
||||
let mut code = Vec::new();
|
||||
code.resize(code_len as usize, 0u8);
|
||||
e.memory().get(code_ptr, &mut code)?;
|
||||
|
||||
let account = e.account().clone();
|
||||
if let Some(commit_state) =
|
||||
Module::<T>::effect_create(&account, &code, value, e.account_db())
|
||||
{
|
||||
e.account_db_mut().merge(commit_state);
|
||||
}
|
||||
|
||||
Ok(sandbox::ReturnValue::Unit)
|
||||
}
|
||||
|
||||
// TODO: Inspect the binary to extract the initial page count.
|
||||
let memory = match sandbox::Memory::new(1, None) {
|
||||
Ok(memory) => memory,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let mut imports = sandbox::EnvironmentDefinitionBuilder::new();
|
||||
imports.add_host_func("env", "ext_set_storage", ext_set_storage::<T>);
|
||||
imports.add_host_func("env", "ext_get_storage", ext_get_storage::<T>);
|
||||
imports.add_host_func("env", "ext_transfer", ext_transfer::<T>);
|
||||
imports.add_host_func("env", "ext_create", ext_create::<T>);
|
||||
// TODO: ext_balance, ext_address, ext_callvalue, etc.
|
||||
imports.add_memory("env", "memory", memory.clone());
|
||||
|
||||
let mut exec_ext = ExecutionExt {
|
||||
account: account.clone(),
|
||||
account_db,
|
||||
memory,
|
||||
};
|
||||
|
||||
let mut instance = match sandbox::Instance::new(code, &imports, &mut exec_ext) {
|
||||
Ok(instance) => instance,
|
||||
Err(_err) => return false,
|
||||
};
|
||||
instance.invoke(b"call", &[], &mut exec_ext).is_ok()
|
||||
}
|
||||
@@ -21,6 +21,9 @@
|
||||
#[cfg(feature = "std")]
|
||||
extern crate serde;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate wabt;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_runtime_support as runtime_support;
|
||||
|
||||
@@ -32,6 +35,7 @@ extern crate substrate_primitives;
|
||||
extern crate substrate_runtime_io as runtime_io;
|
||||
extern crate substrate_runtime_primitives as primitives;
|
||||
extern crate substrate_runtime_consensus as consensus;
|
||||
extern crate substrate_runtime_sandbox as sandbox;
|
||||
extern crate substrate_runtime_session as session;
|
||||
extern crate substrate_runtime_system as system;
|
||||
|
||||
@@ -44,6 +48,8 @@ use codec::Slicable;
|
||||
use runtime_support::{StorageValue, StorageMap, Parameter};
|
||||
use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment};
|
||||
|
||||
mod contract;
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum LockStatus<BlockNumber: Debug + PartialEq + Clone> {
|
||||
@@ -169,8 +175,8 @@ impl<T: Trait> Module<T> {
|
||||
/// Create a smart-contract account.
|
||||
pub fn create(aux: &T::PublicAux, code: &[u8], value: T::Balance) {
|
||||
// commit anything that made it this far to storage
|
||||
if let Some(commit) = Self::effect_create(aux.ref_into(), code, value, DirectAccountDb) {
|
||||
<AccountDb<T>>::merge(&DirectAccountDb, commit);
|
||||
if let Some(commit) = Self::effect_create(aux.ref_into(), code, value, &DirectAccountDb) {
|
||||
<AccountDb<T>>::merge(&mut DirectAccountDb, commit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +186,8 @@ impl<T: Trait> Module<T> {
|
||||
/// TODO: probably want to state gas-limit and gas-price.
|
||||
fn transfer(aux: &T::PublicAux, dest: T::AccountId, value: T::Balance) {
|
||||
// commit anything that made it this far to storage
|
||||
if let Some(commit) = Self::effect_transfer(aux.ref_into(), &dest, value, DirectAccountDb) {
|
||||
<AccountDb<T>>::merge(&DirectAccountDb, commit);
|
||||
if let Some(commit) = Self::effect_transfer(aux.ref_into(), &dest, value, &DirectAccountDb) {
|
||||
<AccountDb<T>>::merge(&mut DirectAccountDb, commit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,11 +388,11 @@ trait AccountDb<T: Trait> {
|
||||
fn get_code(&self, account: &T::AccountId) -> Vec<u8>;
|
||||
fn get_balance(&self, account: &T::AccountId) -> T::Balance;
|
||||
|
||||
fn set_storage(&self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>);
|
||||
fn set_code(&self, account: &T::AccountId, code: Vec<u8>);
|
||||
fn set_balance(&self, account: &T::AccountId, balance: T::Balance);
|
||||
fn set_storage(&mut self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>);
|
||||
fn set_code(&mut self, account: &T::AccountId, code: Vec<u8>);
|
||||
fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance);
|
||||
|
||||
fn merge(&self, state: State<T>);
|
||||
fn merge(&mut self, state: State<T>);
|
||||
}
|
||||
|
||||
struct DirectAccountDb;
|
||||
@@ -400,20 +406,20 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
||||
fn get_balance(&self, account: &T::AccountId) -> T::Balance {
|
||||
<FreeBalance<T>>::get(account)
|
||||
}
|
||||
fn set_storage(&self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>) {
|
||||
fn set_storage(&mut self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>) {
|
||||
if let Some(value) = value {
|
||||
<StorageOf<T>>::insert(&(account.clone(), location), &value);
|
||||
} else {
|
||||
<StorageOf<T>>::remove(&(account.clone(), location));
|
||||
}
|
||||
}
|
||||
fn set_code(&self, account: &T::AccountId, code: Vec<u8>) {
|
||||
fn set_code(&mut self, account: &T::AccountId, code: Vec<u8>) {
|
||||
<CodeOf<T>>::insert(account, &code);
|
||||
}
|
||||
fn set_balance(&self, account: &T::AccountId, balance: T::Balance) {
|
||||
fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) {
|
||||
<FreeBalance<T>>::insert(account, balance);
|
||||
}
|
||||
fn merge(&self, s: State<T>) {
|
||||
fn merge(&mut self, s: State<T>) {
|
||||
for (address, changed) in s.into_iter() {
|
||||
if let Some(balance) = changed.balance {
|
||||
<FreeBalance<T>>::insert(&address, balance);
|
||||
@@ -450,40 +456,50 @@ impl<'a, T: Trait> OverlayAccountDb<'a, T> {
|
||||
}
|
||||
impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
|
||||
fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option<Vec<u8>> {
|
||||
self.local.borrow().get(account)
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|a| a.storage.get(location))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.underlying.get_storage(account, location))
|
||||
}
|
||||
fn get_code(&self, account: &T::AccountId) -> Vec<u8> {
|
||||
self.local.borrow().get(account)
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|a| a.code.clone())
|
||||
.unwrap_or_else(|| self.underlying.get_code(account))
|
||||
}
|
||||
fn get_balance(&self, account: &T::AccountId) -> T::Balance {
|
||||
self.local.borrow().get(account)
|
||||
self.local
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|a| a.balance)
|
||||
.unwrap_or_else(|| self.underlying.get_balance(account))
|
||||
}
|
||||
fn set_storage(&self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>) {
|
||||
self.local.borrow_mut()
|
||||
fn set_storage(&mut self, account: &T::AccountId, location: Vec<u8>, value: Option<Vec<u8>>) {
|
||||
self.local
|
||||
.borrow_mut()
|
||||
.entry(account.clone())
|
||||
.or_insert(Default::default())
|
||||
.storage.insert(location, value);
|
||||
.storage
|
||||
.insert(location, value);
|
||||
}
|
||||
fn set_code(&self, account: &T::AccountId, code: Vec<u8>) {
|
||||
self.local.borrow_mut()
|
||||
fn set_code(&mut self, account: &T::AccountId, code: Vec<u8>) {
|
||||
self.local
|
||||
.borrow_mut()
|
||||
.entry(account.clone())
|
||||
.or_insert(Default::default())
|
||||
.code = Some(code);
|
||||
}
|
||||
fn set_balance(&self, account: &T::AccountId, balance: T::Balance) {
|
||||
self.local.borrow_mut()
|
||||
fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) {
|
||||
self.local
|
||||
.borrow_mut()
|
||||
.entry(account.clone())
|
||||
.or_insert(Default::default())
|
||||
.balance = Some(balance);
|
||||
}
|
||||
fn merge(&self, s: State<T>) {
|
||||
fn merge(&mut self, s: State<T>) {
|
||||
let mut local = self.local.borrow_mut();
|
||||
|
||||
for (address, changed) in s.into_iter() {
|
||||
@@ -511,7 +527,7 @@ impl<T: Trait> Module<T> {
|
||||
transactor: &T::AccountId,
|
||||
code: &[u8],
|
||||
value: T::Balance,
|
||||
account_db: DB
|
||||
account_db: &DB,
|
||||
) -> Option<State<T>> {
|
||||
let from_balance = account_db.get_balance(transactor);
|
||||
// TODO: a fee.
|
||||
@@ -538,32 +554,34 @@ impl<T: Trait> Module<T> {
|
||||
transactor: &T::AccountId,
|
||||
dest: &T::AccountId,
|
||||
value: T::Balance,
|
||||
account_db: DB
|
||||
account_db: &DB,
|
||||
) -> Option<State<T>> {
|
||||
let from_balance = account_db.get_balance(transactor);
|
||||
assert!(from_balance >= value);
|
||||
|
||||
let to_balance = account_db.get_balance(dest);
|
||||
assert!(<Bondage<T>>::get(transactor) <= <Bondage<T>>::get(dest));
|
||||
assert!(to_balance + value > to_balance); // no overflow
|
||||
assert!(to_balance + value > to_balance); // no overflow
|
||||
|
||||
// TODO: a fee, based upon gaslimit/gasprice.
|
||||
// TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime
|
||||
// code in contract itself and use that.
|
||||
|
||||
// Our local overlay: Should be used for any transfers and creates that happen internally.
|
||||
let overlay = OverlayAccountDb::new(&account_db);
|
||||
let mut overlay = OverlayAccountDb::new(account_db);
|
||||
|
||||
if transactor != dest {
|
||||
overlay.set_balance(transactor, from_balance - value);
|
||||
overlay.set_balance(dest, to_balance + value);
|
||||
}
|
||||
|
||||
let should_commit = {
|
||||
let dest_code = overlay.get_code(dest);
|
||||
let should_commit = if dest_code.is_empty() {
|
||||
true
|
||||
} else {
|
||||
// TODO: logging (logs are just appended into a notable storage-based vector and cleared every
|
||||
// block).
|
||||
// TODO: if `overlay.get_code(dest)` isn't empty then execute code with `overlay`.
|
||||
true
|
||||
contract::execute(&dest_code, dest, &mut overlay)
|
||||
};
|
||||
|
||||
if should_commit {
|
||||
@@ -1025,4 +1043,179 @@ mod tests {
|
||||
assert_eq!(Staking::free_balance(&2), 42);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer() {
|
||||
let code_transfer = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_transfer
|
||||
(i32.const 4) ;; Pointer to "Transfer to" address.
|
||||
(i32.const 8) ;; Length of "Transfer to" address.
|
||||
(i32.const 6) ;; value to transfer
|
||||
)
|
||||
)
|
||||
|
||||
;; Destination AccountId to transfer the funds.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 4) "\02\00\00\00\00\00\00\00")
|
||||
)
|
||||
"#,
|
||||
).unwrap();
|
||||
|
||||
with_externalities(&mut new_test_ext(1, 3, 1, false), || {
|
||||
<FreeBalance<Test>>::insert(0, 111);
|
||||
<FreeBalance<Test>>::insert(1, 0);
|
||||
<FreeBalance<Test>>::insert(2, 30);
|
||||
|
||||
<CodeOf<Test>>::insert(1, code_transfer.to_vec());
|
||||
|
||||
Staking::transfer(&0, 1, 11);
|
||||
|
||||
assert_eq!(Staking::balance(&0), 100);
|
||||
assert_eq!(Staking::balance(&1), 5);
|
||||
assert_eq!(Staking::balance(&2), 36);
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_create() {
|
||||
let code_transfer = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_transfer
|
||||
(i32.const 4) ;; Pointer to "Transfer to" address.
|
||||
(i32.const 8) ;; Length of "Transfer to" address.
|
||||
(i32.const 6) ;; value to transfer
|
||||
)
|
||||
)
|
||||
|
||||
;; Destination AccountId to transfer the funds.
|
||||
;; Represented by u64 (8 bytes long) in little endian.
|
||||
(data (i32.const 4) "\02\00\00\00\00\00\00\00")
|
||||
)
|
||||
"#,
|
||||
).unwrap();
|
||||
|
||||
let code_create = wabt::wat2wasm(format!(
|
||||
r#"
|
||||
(module
|
||||
;; ext_create(code_ptr: u32, code_len: u32, value: u32)
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_create
|
||||
(i32.const 4) ;; Pointer to `code`
|
||||
(i32.const {code_length}) ;; Length of `code`
|
||||
(i32.const 3) ;; Value to transfer
|
||||
)
|
||||
)
|
||||
(data (i32.const 4) "{escaped_code_transfer}")
|
||||
)
|
||||
"#,
|
||||
escaped_code_transfer = escaped_bytestring(&code_transfer),
|
||||
code_length = code_transfer.len(),
|
||||
)).unwrap();
|
||||
|
||||
with_externalities(&mut new_test_ext(1, 3, 1, false), || {
|
||||
<FreeBalance<Test>>::insert(0, 111);
|
||||
<FreeBalance<Test>>::insert(1, 0);
|
||||
|
||||
<CodeOf<Test>>::insert(1, code_create.to_vec());
|
||||
|
||||
// When invoked, the contract at address `1` must create a contract with 'transfer' code.
|
||||
Staking::transfer(&0, 1, 11);
|
||||
|
||||
let derived_address =
|
||||
<Test as Trait>::DetermineContractAddress::contract_address_for(&code_transfer, &1);
|
||||
|
||||
assert_eq!(Staking::balance(&0), 100);
|
||||
assert_eq!(Staking::balance(&1), 8);
|
||||
assert_eq!(Staking::balance(&derived_address), 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_adder() {
|
||||
let code_adder = wabt::wat2wasm(r#"
|
||||
(module
|
||||
;; ext_set_storage(location_ptr: i32, value_non_null: bool, value_ptr: i32)
|
||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
|
||||
;; ext_get_storage(location_ptr: i32, value_ptr: i32)
|
||||
(import "env" "ext_get_storage" (func $ext_get_storage (param i32 i32)))
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_get_storage
|
||||
(i32.const 4) ;; Point to a location of the storage.
|
||||
(i32.const 36) ;; The result will be written at this address.
|
||||
)
|
||||
(i32.store
|
||||
(i32.const 36)
|
||||
(i32.add
|
||||
(i32.load
|
||||
(i32.const 36)
|
||||
)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
|
||||
(call $ext_set_storage
|
||||
(i32.const 4) ;; Pointer to a location of the storage.
|
||||
(i32.const 1) ;; Value is not null.
|
||||
(i32.const 36) ;; Pointer to a data we want to put in the storage.
|
||||
)
|
||||
)
|
||||
|
||||
;; Location of storage to put the data. 32 bytes.
|
||||
(data (i32.const 4) "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01")
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
with_externalities(&mut new_test_ext(1, 3, 1, false), || {
|
||||
<FreeBalance<Test>>::insert(0, 111);
|
||||
<FreeBalance<Test>>::insert(1, 0);
|
||||
<CodeOf<Test>>::insert(1, code_adder);
|
||||
|
||||
Staking::transfer(&0, 1, 1);
|
||||
Staking::transfer(&0, 1, 1);
|
||||
|
||||
let storage_addr = [0x01u8; 32];
|
||||
let value =
|
||||
AccountDb::<Test>::get_storage(&DirectAccountDb, &1, &storage_addr).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&value,
|
||||
&[
|
||||
2, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user