mirror of
https://github.com/fluencelabs/wasm-utils
synced 2025-03-16 03:20:50 +00:00
remove demos (moved to https://github.com/nikvolf/wasm-tools)
This commit is contained in:
parent
0410ae407a
commit
050c0b76a8
1
js-runner/.gitignore
vendored
1
js-runner/.gitignore
vendored
@ -1 +0,0 @@
|
||||
out
|
@ -1,33 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# "Compile rust source and put it as a tested contract"
|
||||
|
||||
mkdir -p out
|
||||
|
||||
file=$1
|
||||
if [ ${file: -3} == ".rs" ]
|
||||
then
|
||||
# Rust is compiled with rustc
|
||||
rustc $file -o out/contract.js -O --target wasm32-unknown-emscripten -C linker=./linker_emcc.sh
|
||||
|
||||
# Gas injector
|
||||
cargo run --manifest-path=./../gas/Cargo.toml --release -- ./out/contract.wasm ./out/contract.wasm
|
||||
|
||||
# Allocator replacer
|
||||
cargo run --manifest-path=./../ext/Cargo.toml --release -- ./out/contract.wasm ./out/contract.wasm
|
||||
|
||||
# Symbols optimizer
|
||||
cargo run --manifest-path=./../opt/Cargo.toml --release -- ./out/contract.wasm ./out/contract.wasm
|
||||
|
||||
else
|
||||
# c/c++ can be compiled directly by emcc
|
||||
emcc $file -O3 -s WASM=1 -s SIDE_MODULE=1 -o out/contract.wasm
|
||||
|
||||
# Gas injector
|
||||
cargo run --manifest-path=./../gas/Cargo.toml --release -- ./out/contract.wasm ./out/contract.wasm
|
||||
|
||||
# Symbols optimizer
|
||||
cargo run --manifest-path=./../opt/Cargo.toml --release -- ./out/contract.wasm ./out/contract.wasm
|
||||
fi
|
||||
|
||||
|
@ -1,343 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script>
|
||||
// Check for wasm support.
|
||||
if (!('WebAssembly' in window)) {
|
||||
alert('you need a browser with wasm support enabled :(');
|
||||
}
|
||||
|
||||
if (!ArrayBuffer.transfer) {
|
||||
ArrayBuffer.transfer = function(source, length) {
|
||||
source = Object(source);
|
||||
var dest = new ArrayBuffer(length);
|
||||
if (!(source instanceof ArrayBuffer) || !(dest instanceof ArrayBuffer)) {
|
||||
throw new TypeError('Source and destination must be ArrayBuffer instances');
|
||||
}
|
||||
if (dest.byteLength >= source.byteLength) {
|
||||
var nextOffset = 0;
|
||||
var leftBytes = source.byteLength;
|
||||
var wordSizes = [8, 4, 2, 1];
|
||||
wordSizes.forEach(function(_wordSize_) {
|
||||
if (leftBytes >= _wordSize_) {
|
||||
var done = transferWith(_wordSize_, source, dest, nextOffset, leftBytes);
|
||||
nextOffset = done.nextOffset;
|
||||
leftBytes = done.leftBytes;
|
||||
}
|
||||
});
|
||||
}
|
||||
return dest;
|
||||
function transferWith(wordSize, source, dest, nextOffset, leftBytes) {
|
||||
var ViewClass = Uint8Array;
|
||||
switch (wordSize) {
|
||||
case 8:
|
||||
ViewClass = Float64Array;
|
||||
break;
|
||||
case 4:
|
||||
ViewClass = Float32Array;
|
||||
break;
|
||||
case 2:
|
||||
ViewClass = Uint16Array;
|
||||
break;
|
||||
case 1:
|
||||
ViewClass = Uint8Array;
|
||||
break;
|
||||
default:
|
||||
ViewClass = Uint8Array;
|
||||
break;
|
||||
}
|
||||
var view_source = new ViewClass(source, nextOffset, Math.trunc(leftBytes / wordSize));
|
||||
var view_dest = new ViewClass(dest, nextOffset, Math.trunc(leftBytes / wordSize));
|
||||
for (var i = 0; i < view_dest.length; i++) {
|
||||
view_dest[i] = view_source[i];
|
||||
}
|
||||
return {
|
||||
nextOffset : view_source.byteOffset + view_source.byteLength,
|
||||
leftBytes : source.byteLength - (view_source.byteOffset + view_source.byteLength)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function Storage(memoryBuf) {
|
||||
var self = this;
|
||||
self.size = 16 * 1024;
|
||||
self.buffer = new ArrayBuffer(self.size);
|
||||
self.memory = memoryBuf;
|
||||
self.total = 0;
|
||||
|
||||
self.write = function(offset, len, ptr) {
|
||||
var oldSize = false;
|
||||
while (offset + len > self.size) {
|
||||
oldSize || (oldSize = self.size);
|
||||
self.size = self.size * 2;
|
||||
}
|
||||
if (oldSize) {
|
||||
self.buffer = ArrayBuffer.transfer(self.buffer, self.size);
|
||||
}
|
||||
|
||||
if (offset + len > self.total) {
|
||||
self.total = offset + len;
|
||||
}
|
||||
|
||||
let memView = new DataView(self.memory);
|
||||
let storageView = new DataView(self.buffer);
|
||||
for (i = 0; i < len; i++) {
|
||||
storageView.setInt8(offset+i, memView.getInt8(ptr+i));
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
self.read = function(offset, len, ptr) {
|
||||
if (offset + len > self.total) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let memView = new DataView(self.memory);
|
||||
let storageView = new DataView(self.buffer);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
memView.setInt8(ptr+i, storageView.getInt8(offset+i));
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
self.size = function() {
|
||||
return self.total;
|
||||
}
|
||||
|
||||
self.toArr = function() {
|
||||
let result = [];
|
||||
let dataView = new DataView(self.buffer);
|
||||
for (var i = 0; i < self.total; i++) {
|
||||
result.push(dataView.getInt8(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function Runtime() {
|
||||
var self = this;
|
||||
|
||||
self.memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
|
||||
self.storage = new Storage(self.memory.buffer);
|
||||
|
||||
// todo: figure out how to do counter with multiple executables
|
||||
self.gasCounter = 0;
|
||||
|
||||
self.dynamicTopPtr = 1024;
|
||||
|
||||
self.malloc = function(size) {
|
||||
let result = self.dynamicTopPtr;
|
||||
self.dynamicTopPtr += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
self.free = function() {
|
||||
}
|
||||
|
||||
self.resolveAlloc = function(instance) {
|
||||
return self.malloc;
|
||||
}
|
||||
|
||||
self.resolveFree = function(instance) {
|
||||
return self.free;
|
||||
}
|
||||
|
||||
self.gas = function(val) {
|
||||
self.gasCounter += val;
|
||||
}
|
||||
|
||||
self.call = function(instance, args) {
|
||||
let alloc = self.resolveAlloc(instance);
|
||||
|
||||
// call descriptor size
|
||||
let ptr = alloc(16);
|
||||
let dataView = new DataView(self.memory.buffer);
|
||||
|
||||
var arg_ptr = false;
|
||||
if (args.length > 0) {
|
||||
arg_ptr = alloc(args.length);
|
||||
dataView.setInt32(ptr, arg_ptr, true);
|
||||
dataView.setInt32(ptr+4, args.length, true);
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
dataView.setInt8(arg_ptr+i, args[i], false);
|
||||
}
|
||||
|
||||
} else {
|
||||
dataView.setInt32(ptr, 0, true);
|
||||
dataView.setInt32(ptr+4, 0, true);
|
||||
}
|
||||
|
||||
// zero result
|
||||
dataView.setInt32(ptr+8, 0, true);
|
||||
dataView.setInt32(ptr+12, 0, true);
|
||||
|
||||
|
||||
self.gasCounter = 0;
|
||||
instance.exports._call(ptr);
|
||||
|
||||
let result_ptr = dataView.getInt32(ptr+8, true);
|
||||
let result_length = dataView.getInt32(ptr+12, true);
|
||||
|
||||
let free = self.resolveFree(instance);
|
||||
|
||||
let result = [];
|
||||
if (result_ptr != 0) {
|
||||
for (var i = 0; i < result_length; i++) {
|
||||
result.push(dataView.getInt8(result_ptr + i));
|
||||
}
|
||||
}
|
||||
|
||||
arg_ptr && (free(arg_ptr));
|
||||
result_ptr && (free(result_ptr));
|
||||
free(ptr);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
imports = imports || {};
|
||||
imports.env = imports.env || {};
|
||||
var env = imports.env;
|
||||
imports.env.memoryBase = imports.env.memoryBase || 1024;
|
||||
imports.env.tableBase = imports.env.tableBase || 0;
|
||||
|
||||
window.runtime = new Runtime();
|
||||
|
||||
env.STACKTOP = env.STACKTOP || 0;
|
||||
env.STACK_MAX = env.STACK_MAX || 5*1024*1024;
|
||||
env.DYNAMICTOP_PTR = env.STACK_MAX;
|
||||
env.enlargeMemory = env.enlargeMemory || function() {
|
||||
return 1;
|
||||
};
|
||||
env.getTotalMemory = env.getTotalMemory || function() {
|
||||
return 16 * 1024 * 1024;
|
||||
};
|
||||
env.abortOnCannotGrowMemory = env.abortOnCannotGrowMemory || function() { throw "abort growing memory"; };
|
||||
env._abort = env._abort || function() { throw "_abort"; };
|
||||
env.abort = env.abort || function() { throw "abort"; };
|
||||
env.___setErrNo = env.___setErrNo || function() { throw "setting error no"; };
|
||||
|
||||
// dead symbols in rust wasm32-unknown-emscripten target
|
||||
// todo: strip/raise issue in rust compiler
|
||||
env.invoke_vi = function() { throw "invoke_vi: unreachable!"; }
|
||||
env.invoke_v = function() { throw "invoke_v: unreachable!"; }
|
||||
|
||||
// todo: also test unwind about those two
|
||||
env._rust_begin_unwind = function() { throw "_rust_begin_unwind: unreachable!"; }
|
||||
env._llvm_trap = function() { throw "_llvm_trap: unreachable!"; }
|
||||
|
||||
env._emscripten_memcpy_big = function() { throw "_emscripten_memcpy_big: unreachable!"; }
|
||||
env.___gxx_personality_v0 = function() { throw "___gxx_personality_v0: unreachable!"; }
|
||||
env.___resumeException = function() { throw "___resumeException: unreachable!"; }
|
||||
env.___cxa_find_matching_catch_2 = function() { throw "___cxa_find_matching_catch_2: unreachable!"; }
|
||||
env.___gxx_personality_v0 = function() { throw "___gxx_personality_v0: unreachable!"; }
|
||||
|
||||
env.memoryBase = env.memoryBase || 0;
|
||||
env.tableBase = env.tableBase || 0;
|
||||
|
||||
env._storage_read = runtime.storage.read;
|
||||
env._storage_write = runtime.storage.write;
|
||||
env._storage_size = runtime.storage.size;
|
||||
env.gas = runtime.gas;
|
||||
env._malloc = runtime.malloc;
|
||||
env._free = runtime.free;
|
||||
|
||||
if (!imports.env.memory) {
|
||||
imports.env.memory = runtime.memory;
|
||||
}
|
||||
if (!imports.env.table) {
|
||||
imports.env.table = new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc' });
|
||||
}
|
||||
return new WebAssembly.Instance(module, imports);
|
||||
});
|
||||
}
|
||||
|
||||
function strToArray(str) {
|
||||
var src = str.trim().substr(1, str.length-2);
|
||||
if (src.length == 0) {
|
||||
return [];
|
||||
} else {
|
||||
return src.split(",").map(p => Number(p.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
function arrayToStr(arr) {
|
||||
return "[" + arr.join(", ") + "]";
|
||||
}
|
||||
|
||||
loadWebAssembly('out/contract.wasm')
|
||||
.then(instance => {
|
||||
var button = document.getElementById('do-call');
|
||||
button.value = 'Execute call';
|
||||
button.addEventListener('click', function() {
|
||||
button.setAttribute("disabled", "1");
|
||||
let args = strToArray(document.getElementById("context").value);
|
||||
let result = runtime.call(instance, args);
|
||||
document.getElementById("result").value = arrayToStr(result);
|
||||
document.getElementById("storage").value = arrayToStr(runtime.storage.toArr());
|
||||
document.getElementById("gas").innerHTML = "Gas used: <b>" + runtime.gasCounter + "</b>";
|
||||
button.removeAttribute("disabled");
|
||||
}, false);
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="position: absolute; margin-top: 24px; top: 0px; left: 600px; height: 300px; width: 600px">
|
||||
<textarea readonly style="width: 100%; height: 100%; padding: 8px; resize: none">
|
||||
pub fn call(descriptor: *mut u8) {
|
||||
// This initializes safe wrapper for contract input and output
|
||||
let mut ctx = CallArgs::from_raw(descriptor);
|
||||
|
||||
// Copies all contract input data to the separate buffer
|
||||
let data = ctx.context().to_vec();
|
||||
|
||||
// Appends all input to the storage (as it is a logger contract)
|
||||
let _ = storage::append(&data);
|
||||
|
||||
// Returns all that passed to this contract as an output
|
||||
*ctx.result_mut() = data;
|
||||
|
||||
// Saves the wrapper state to commit return stream
|
||||
ctx.save(descriptor);
|
||||
}
|
||||
</textarea>
|
||||
<a href="https://github.com/NikVolf/wasm-tools/blob/master/samples/logger_contract.rs">Full source with preamble</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="context" style="display: block">Input</label>
|
||||
<textarea style="width: 480px; height: 96px; margin-bottom: 24px; resize: none" id="context">[10, 12, 16]</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="storage" style="display: block">Storage</label>
|
||||
<div style="padding: 5px; background-color: lightgray; display: inline-block">
|
||||
<textarea readonly style="width: 480px; height: 120px; margin-bottom: 24px; resize: none" id="storage">[]</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="result" style="display: block">Result</label>
|
||||
<div style="padding: 5px; background-color: lightgray; display: inline-block">
|
||||
<textarea readonly style="width: 480px; height: 96px; margin-bottom: 24px; resize: none" id="result">[]</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height: 5px; margin-top: 5px; border-top: 1px solid black"></div>
|
||||
|
||||
<input type="button" id="do-call" value="(waiting for WebAssembly)" style="background-color: rgb(240, 64, 64); color: white; font-size: 120%" ></input>
|
||||
<span id="gas" style="margin-left: 32px">Gas cost: <b>0</b></span>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
args="$*"
|
||||
filtered_args=${args/ERROR_ON_UNDEFINED_SYMBOLS\=1/ERROR_ON_UNDEFINED_SYMBOLS\=0}
|
||||
emcc $filtered_args
|
@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
python -m SimpleHTTPServer 8000
|
2
rust-runner/.gitignore
vendored
2
rust-runner/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
target
|
||||
Cargo.lock
|
@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "rust-runner"
|
||||
version = "0.1.0"
|
||||
authors = ["NikVolf <nikvolf@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
parity-wasm = { git="https://github.com/nikvolf/parity-wasm" }
|
||||
wasm-utils = { path = "../" }
|
@ -1,27 +0,0 @@
|
||||
use parity_wasm::interpreter::{self, ModuleInstance};
|
||||
use runtime::Runtime;
|
||||
|
||||
pub struct Arena {
|
||||
pub runtime: Runtime,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error;
|
||||
|
||||
impl Arena {
|
||||
pub fn alloc(&self, size: u32) -> Result<u32, Error> {
|
||||
// todo: maybe use unsafe cell since it has nothing to do with threads
|
||||
let previous_top = self.runtime.env().dynamic_top.get();
|
||||
self.runtime.env().dynamic_top.set(previous_top + size);
|
||||
Ok(previous_top)
|
||||
}
|
||||
}
|
||||
|
||||
impl interpreter::UserFunctionInterface for Arena {
|
||||
fn call(&mut self, _module: &ModuleInstance, context: interpreter::CallerContext) -> Result<Option<interpreter::RuntimeValue>, interpreter::Error> {
|
||||
let amount = context.value_stack.pop_as::<i32>()?;
|
||||
self.alloc(amount as u32)
|
||||
.map(|val| Some((val as i32).into()))
|
||||
.map_err(|e| interpreter::Error::Trap(format!("Allocator failure: {}", "todo: format arg")))
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use parity_wasm::interpreter;
|
||||
use runtime;
|
||||
|
||||
use WasmMemoryPtr;
|
||||
|
||||
fn write_u32(dst: &mut [u8], val: u32) {
|
||||
dst[0] = (val & 0x000000ff) as u8;
|
||||
dst[1] = ((val & 0x0000ff00) >> 8) as u8;
|
||||
dst[2] = ((val & 0x00ff0000) >> 16) as u8;
|
||||
dst[3] = ((val & 0xff000000) >> 24) as u8;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Allocator(runtime::ErrorAlloc),
|
||||
Interpreter(interpreter::Error),
|
||||
}
|
||||
|
||||
impl From<runtime::ErrorAlloc> for Error {
|
||||
fn from(err: runtime::ErrorAlloc) -> Self {
|
||||
Error::Allocator(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<interpreter::Error> for Error {
|
||||
fn from(err: interpreter::Error) -> Self {
|
||||
Error::Interpreter(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
memory: &interpreter::MemoryInstance,
|
||||
runtime: &mut runtime::Runtime,
|
||||
input: &[u8],
|
||||
) -> Result<WasmMemoryPtr, Error> {
|
||||
let mut input_ptr_slc = [0u8; 4];
|
||||
let mut input_length = [0u8; 4];
|
||||
|
||||
let descriptor_ptr = runtime.alloc(16)?;
|
||||
|
||||
println!("descriptor_ptr: {}", descriptor_ptr);
|
||||
|
||||
if input.len() > 0 {
|
||||
let input_ptr = runtime.alloc(input.len() as u32)?;
|
||||
write_u32(&mut input_ptr_slc, input_ptr);
|
||||
write_u32(&mut input_length, input.len() as u32);
|
||||
memory.set(input_ptr, input)?;
|
||||
println!("input_ptr: {}", input_ptr);
|
||||
} else {
|
||||
write_u32(&mut input_ptr_slc, 0);
|
||||
write_u32(&mut input_length, 0);
|
||||
}
|
||||
|
||||
memory.set(descriptor_ptr, &input_ptr_slc)?;
|
||||
memory.set(descriptor_ptr+4, &input_length)?;
|
||||
|
||||
// zero result ptr/len
|
||||
memory.set(descriptor_ptr+8, &[0u8; 4])?;
|
||||
memory.set(descriptor_ptr+12, &[0u8; 4])?;
|
||||
|
||||
println!("descriptor: {:?}", memory.get(descriptor_ptr, 16));
|
||||
|
||||
Ok(descriptor_ptr as i32)
|
||||
}
|
||||
|
||||
fn _read_u32(slc: &[u8]) -> u32 {
|
||||
use std::ops::Shl;
|
||||
(slc[0] as u32) + (slc[1] as u32).shl(8) + (slc[2] as u32).shl(16) + (slc[3] as u32).shl(24)
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
/*
|
||||
|
||||
Rust contract demo runner
|
||||
|
||||
*/
|
||||
|
||||
extern crate parity_wasm;
|
||||
extern crate wasm_utils;
|
||||
|
||||
mod call_args;
|
||||
mod runtime;
|
||||
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use parity_wasm::interpreter::{self, ModuleInstanceInterface};
|
||||
use parity_wasm::elements;
|
||||
|
||||
pub const DEFAULT_MEMORY_INDEX: interpreter::ItemIndex = interpreter::ItemIndex::Internal(0);
|
||||
pub type WasmMemoryPtr = i32;
|
||||
|
||||
fn main() {
|
||||
// First, load wasm contract as a module
|
||||
wasm_utils::init_log();
|
||||
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
if args.len() != 2 {
|
||||
println!("Usage: {} contract.wasm", args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed");
|
||||
|
||||
let program = parity_wasm::interpreter::ProgramInstance::new()
|
||||
.expect("Program instance to be created");
|
||||
|
||||
// Add module to the programm
|
||||
let module_instance = program.add_module("contract", module).expect("Module to be added successfully");
|
||||
|
||||
{
|
||||
let env_instance = program.module("env").expect("env module to exist");
|
||||
let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0))
|
||||
.expect("liner memory to exist");
|
||||
|
||||
// Second, create runtime and program instance
|
||||
let mut runtime = runtime::Runtime::with_params(
|
||||
env_memory.clone(), // memory shared ptr
|
||||
5*1024*1024, // default stack space
|
||||
65536, // runner arbitrary gas limit
|
||||
);
|
||||
|
||||
// Initialize call descriptor
|
||||
let descriptor = call_args::init(
|
||||
&*env_memory,
|
||||
&mut runtime,
|
||||
&[3u8; 128],
|
||||
).expect("call descriptor initialization to succeed");
|
||||
|
||||
// create native env module with native add && sub implementations
|
||||
let functions = interpreter::UserFunctions {
|
||||
executor: &mut runtime,
|
||||
functions: vec![
|
||||
interpreter::UserFunction {
|
||||
name: "_storage_read".to_owned(),
|
||||
params: vec![elements::ValueType::I32, elements::ValueType::I32],
|
||||
result: Some(elements::ValueType::I32),
|
||||
},
|
||||
interpreter::UserFunction {
|
||||
name: "_storage_write".to_owned(),
|
||||
params: vec![elements::ValueType::I32, elements::ValueType::I32],
|
||||
result: Some(elements::ValueType::I32),
|
||||
},
|
||||
interpreter::UserFunction {
|
||||
name: "_malloc".to_owned(),
|
||||
params: vec![elements::ValueType::I32],
|
||||
result: Some(elements::ValueType::I32),
|
||||
},
|
||||
interpreter::UserFunction {
|
||||
name: "_debug".to_owned(),
|
||||
params: vec![elements::ValueType::I32, elements::ValueType::I32],
|
||||
result: None,
|
||||
},
|
||||
interpreter::UserFunction {
|
||||
name: "gas".to_owned(),
|
||||
params: vec![elements::ValueType::I32],
|
||||
result: None,
|
||||
},
|
||||
interpreter::UserFunction {
|
||||
name: "_free".to_owned(),
|
||||
params: vec![elements::ValueType::I32],
|
||||
result: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
let native_env_instance = Arc::new(interpreter::env_native_module(env_instance, functions).unwrap());
|
||||
|
||||
// Form ExecutionParams (payload + env link)
|
||||
let params = interpreter::ExecutionParams::with_external("env".into(), native_env_instance)
|
||||
.add_argument(interpreter::RuntimeValue::I32(descriptor));
|
||||
|
||||
module_instance.execute_export("_call", params)
|
||||
.expect("_call to execute successfully");
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use parity_wasm::interpreter;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug)]
|
||||
pub struct StorageKey([u8; 32]);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StorageValue([u8; 32]);
|
||||
|
||||
struct ErrorStorage;
|
||||
|
||||
impl StorageKey {
|
||||
// todo: deal with memory views
|
||||
fn from_mem(vec: Vec<u8>) -> Result<Self, ErrorStorage> {
|
||||
if vec.len() != 32 { return Err(ErrorStorage); }
|
||||
let mut result = StorageKey([0u8; 32]);
|
||||
result.0.copy_from_slice(&vec[0..32]);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl StorageValue {
|
||||
// todo: deal with memory views
|
||||
// todo: deal with variable-length values when it comes
|
||||
fn from_mem(vec: Vec<u8>) -> Result<Self, ErrorStorage> {
|
||||
if vec.len() != 32 { return Err(ErrorStorage); }
|
||||
let mut result = StorageValue([0u8; 32]);
|
||||
result.0.copy_from_slice(&vec[0..32]);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn as_slice(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Runtime {
|
||||
gas_counter: u64,
|
||||
gas_limit: u64,
|
||||
dynamic_top: u32,
|
||||
storage: HashMap<StorageKey, StorageValue>,
|
||||
memory: Arc<interpreter::MemoryInstance>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ErrorAlloc;
|
||||
|
||||
impl Runtime {
|
||||
pub fn with_params(memory: Arc<interpreter::MemoryInstance>, stack_space: u32, gas_limit: u64) -> Runtime {
|
||||
Runtime {
|
||||
gas_counter: 0,
|
||||
gas_limit: gas_limit,
|
||||
dynamic_top: stack_space,
|
||||
storage: HashMap::new(),
|
||||
memory: memory,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storage_write(&mut self, context: interpreter::CallerContext)
|
||||
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
|
||||
{
|
||||
let val_ptr = context.value_stack.pop_as::<i32>()?;
|
||||
let key_ptr = context.value_stack.pop_as::<i32>()?;
|
||||
|
||||
let key = StorageKey::from_mem(self.memory.get(key_ptr as u32, 32)?)
|
||||
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?;
|
||||
let val = StorageValue::from_mem(self.memory.get(val_ptr as u32, 32)?)
|
||||
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?;
|
||||
|
||||
println!("write storage {:?} = {:?}", key, val);
|
||||
|
||||
self.storage.insert(key, val);
|
||||
|
||||
Ok(Some(0i32.into()))
|
||||
}
|
||||
|
||||
pub fn storage_read(&mut self, context: interpreter::CallerContext)
|
||||
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
|
||||
{
|
||||
// arguments passed are in backward order (since it is stack)
|
||||
let val_ptr = context.value_stack.pop_as::<i32>()?;
|
||||
let key_ptr = context.value_stack.pop_as::<i32>()?;
|
||||
|
||||
let key = StorageKey::from_mem(self.memory.get(key_ptr as u32, 32)?)
|
||||
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?;
|
||||
let empty = StorageValue([0u8; 32]);
|
||||
let val = self.storage.get(&key).unwrap_or(&empty);
|
||||
|
||||
self.memory.set(val_ptr as u32, val.as_slice())?;
|
||||
|
||||
println!("read storage {:?} (evaluated as {:?})", key, val);
|
||||
|
||||
Ok(Some(0.into()))
|
||||
}
|
||||
|
||||
pub fn malloc(&mut self, context: interpreter::CallerContext)
|
||||
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
|
||||
{
|
||||
let amount = context.value_stack.pop_as::<i32>()? as u32;
|
||||
let previous_top = self.dynamic_top;
|
||||
self.dynamic_top = previous_top + amount;
|
||||
Ok(Some((previous_top as i32).into()))
|
||||
}
|
||||
|
||||
pub fn alloc(&mut self, amount: u32) -> Result<u32, ErrorAlloc> {
|
||||
let previous_top = self.dynamic_top;
|
||||
self.dynamic_top = previous_top + amount;
|
||||
Ok(previous_top.into())
|
||||
}
|
||||
|
||||
fn gas(&mut self, context: interpreter::CallerContext)
|
||||
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
|
||||
{
|
||||
let prev = self.gas_counter;
|
||||
let update = context.value_stack.pop_as::<i32>()? as u64;
|
||||
if prev + update > self.gas_limit {
|
||||
// exceeds gas
|
||||
Err(interpreter::Error::Trap(format!("Gas exceeds limits of {}", self.gas_limit)))
|
||||
} else {
|
||||
self.gas_counter = prev + update;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn debug(&mut self, context: interpreter::CallerContext)
|
||||
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
|
||||
{
|
||||
let msg_len = context.value_stack.pop_as::<i32>()? as u32;
|
||||
let msg_ptr = context.value_stack.pop_as::<i32>()? as u32;
|
||||
|
||||
let msg = unsafe { String::from_utf8_unchecked(self.memory.get(msg_ptr, msg_len as usize)?) };
|
||||
println!("DEBUG: {}", msg);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn user_trap(&mut self, _context: interpreter::CallerContext)
|
||||
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
|
||||
{
|
||||
Err(interpreter::Error::Trap("unknown trap".to_owned()))
|
||||
}
|
||||
|
||||
fn user_noop(&mut self,
|
||||
_context: interpreter::CallerContext
|
||||
) -> Result<Option<interpreter::RuntimeValue>, interpreter::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl interpreter::UserFunctionExecutor for Runtime {
|
||||
fn execute(&mut self, name: &str, context: interpreter::CallerContext)
|
||||
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
|
||||
{
|
||||
match name {
|
||||
"_malloc" => {
|
||||
self.malloc(context)
|
||||
},
|
||||
"_free" => {
|
||||
self.user_noop(context)
|
||||
},
|
||||
"_storage_read" => {
|
||||
self.storage_read(context)
|
||||
},
|
||||
"_storage_write" => {
|
||||
self.storage_write(context)
|
||||
},
|
||||
"gas" => {
|
||||
self.gas(context)
|
||||
},
|
||||
"_debug" => {
|
||||
self.debug(context)
|
||||
},
|
||||
_ => {
|
||||
self.user_trap(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
#![feature(link_args)]
|
||||
#![no_main]
|
||||
|
||||
// as it is experimental preamble
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[link_args = "-s WASM=1 -s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1"]
|
||||
extern {}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn call() {
|
||||
}
|
||||
|
||||
/* This produces the following code (after injecting gas counter & optimizing)
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (param i32)))
|
||||
(import "env" "memory" (memory (;0;) 256 256))
|
||||
(import "env" "table" (table (;0;) 0 0 anyfunc))
|
||||
(import "env" "gas" (func (;0;) (type 1)))
|
||||
(func (;1;) (type 0)
|
||||
i32.const 2
|
||||
call 0
|
||||
nop)
|
||||
(export "_call" (func 1))
|
||||
(data (i32.const 1212) "\1c\05"))
|
||||
*/
|
@ -1,43 +0,0 @@
|
||||
int data;
|
||||
|
||||
extern void log_event(void* ptr);
|
||||
|
||||
int main() {
|
||||
log_event(0);
|
||||
}
|
||||
|
||||
void call() {
|
||||
log_event(0);
|
||||
}
|
||||
|
||||
/* produces the following code (with gas counter and call optimization)
|
||||
(module
|
||||
(type (;0;) (func (param i32) (result i32)))
|
||||
(type (;1;) (func))
|
||||
(type (;2;) (func (param i32)))
|
||||
(import "env" "memoryBase" (global (;0;) i32))
|
||||
(import "env" "memory" (memory (;0;) 256))
|
||||
(import "env" "table" (table (;0;) 0 anyfunc))
|
||||
(import "env" "gas" (func (;0;) (type 2)))
|
||||
(func (;1;) (type 0) (param i32) (result i32)
|
||||
i32.const 2
|
||||
call 0
|
||||
block i32 ;; label = @1
|
||||
i32.const 13
|
||||
call 0
|
||||
get_global 0
|
||||
i32.const 5242880
|
||||
i32.add
|
||||
get_global 0
|
||||
i32.const 5242880
|
||||
i32.add
|
||||
i32.load
|
||||
get_local 0
|
||||
i32.load offset=4
|
||||
i32.add
|
||||
i32.store
|
||||
i32.const 0
|
||||
end)
|
||||
(export "_call" (func 1)))
|
||||
|
||||
*/
|
@ -1,135 +0,0 @@
|
||||
#![feature(link_args)]
|
||||
#![no_main]
|
||||
|
||||
// as it is experimental preamble
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::slice;
|
||||
|
||||
#[link_args = "-s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1 -s"]
|
||||
extern {}
|
||||
|
||||
/// Wrapper over storage read/write externs
|
||||
/// Storage api is a key-value storage where both key and value are 32 bytes in len
|
||||
mod storage {
|
||||
pub struct Error;
|
||||
|
||||
#[link(name = "env")]
|
||||
extern {
|
||||
fn storage_read(key: *const u8, dst: *mut u8) -> i32;
|
||||
fn storage_write(key: *const u8, src: *const u8) -> i32;
|
||||
}
|
||||
|
||||
/// Performs read from storage to the specified slice `dst`
|
||||
/// Can return `Error` if data is read from outside of the storage boundaries
|
||||
pub fn read(key: &[u8; 32], dst: &mut [u8; 32]) -> Result<(), Error> {
|
||||
match unsafe {
|
||||
let mut dst = dst;
|
||||
storage_read(key.as_ptr(), dst.as_mut_ptr())
|
||||
} {
|
||||
x if x < 0 => Err(Error),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs write to the storage from the specified `src`
|
||||
pub fn write(key: &[u8; 32], src: &[u8; 32]) -> Result<(), Error> {
|
||||
match unsafe {
|
||||
storage_write(key.as_ptr(), src.as_ptr())
|
||||
} {
|
||||
x if x < 0 => Err(Error),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper for call context
|
||||
struct CallArgs {
|
||||
context: Box<[u8]>,
|
||||
result: Vec<u8>,
|
||||
}
|
||||
|
||||
unsafe fn read_ptr_mut(slc: &[u8]) -> *mut u8 {
|
||||
std::ptr::null_mut().offset(read_u32(slc) as isize)
|
||||
}
|
||||
|
||||
fn read_u32(slc: &[u8]) -> u32 {
|
||||
use std::ops::Shl;
|
||||
(slc[0] as u32) + (slc[1] as u32).shl(8) + (slc[2] as u32).shl(16) + (slc[3] as u32).shl(24)
|
||||
}
|
||||
|
||||
fn write_u32(dst: &mut [u8], val: u32) {
|
||||
dst[0] = (val & 0x000000ff) as u8;
|
||||
dst[1] = ((val & 0x0000ff00) >> 8) as u8;
|
||||
dst[2] = ((val & 0x00ff0000) >> 16) as u8;
|
||||
dst[3] = ((val & 0xff000000) >> 24) as u8;
|
||||
}
|
||||
|
||||
fn write_ptr(dst: &mut [u8], ptr: *mut u8) {
|
||||
// todo: consider: add assert that arch is 32bit
|
||||
write_u32(dst, ptr as usize as u32);
|
||||
}
|
||||
|
||||
impl CallArgs {
|
||||
pub fn from_raw(ptr: *mut u8) -> CallArgs {
|
||||
let desc_slice = unsafe { slice::from_raw_parts(ptr, 4 * 4) };
|
||||
|
||||
let context_ptr = unsafe { read_ptr_mut(&desc_slice[0..4]) };
|
||||
let context_len = read_u32(&desc_slice[4..8]) as usize;
|
||||
|
||||
let result_ptr = unsafe { read_ptr_mut(&desc_slice[8..12]) };
|
||||
let result_len = read_u32(&desc_slice[12..16]) as usize;
|
||||
|
||||
CallArgs {
|
||||
context: unsafe { Box::<[u8]>::from_raw(slice::from_raw_parts_mut(context_ptr, context_len)) },
|
||||
result: unsafe { Vec::from_raw_parts(result_ptr, result_len, result_len) },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &[u8] {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub fn result_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.result
|
||||
}
|
||||
|
||||
pub fn save(self, ptr: *mut u8) {
|
||||
let dst = unsafe { slice::from_raw_parts_mut(ptr.offset(8), 2 * 4) };
|
||||
let context = self.context;
|
||||
let mut result = self.result;
|
||||
|
||||
// context unmodified and memory is managed in calling code
|
||||
std::mem::forget(context);
|
||||
|
||||
if result.len() > 0 {
|
||||
// result
|
||||
write_ptr(&mut dst[0..4], result.as_mut_ptr());
|
||||
write_u32(&mut dst[4..8], result.len() as u32);
|
||||
// managed in calling code
|
||||
std::mem::forget(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn call(descriptor: *mut u8) {
|
||||
// This initializes safe wrapper for contract input and output
|
||||
let mut ctx = CallArgs::from_raw(descriptor);
|
||||
|
||||
// Copies all contract input data to the separate buffer
|
||||
let data = ctx.context().to_vec();
|
||||
|
||||
let storage_key = [1u8; 32];
|
||||
let mut storage_val = [0u8; 32];
|
||||
storage_val.copy_from_slice(&data[0..32]);
|
||||
|
||||
// Sets the key [1, 1, 1 ..., 1] to the first 32 bytes of passed input
|
||||
let _ = storage::write(&storage_key, &mut storage_val);
|
||||
|
||||
// Returns all that passed to this contract as an output
|
||||
*ctx.result_mut() = data;
|
||||
|
||||
// Saves the wrapper state to commit return stream
|
||||
ctx.save(descriptor);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
int data;
|
||||
|
||||
int call(void* descriptor) {
|
||||
int* input_length = (int*)(descriptor+4);
|
||||
data += *input_length;
|
||||
}
|
||||
|
||||
/* produces the following code (with gas counter and call optimization)
|
||||
(module
|
||||
(type (;0;) (func (param i32) (result i32)))
|
||||
(type (;1;) (func))
|
||||
(type (;2;) (func (param i32)))
|
||||
(import "env" "memoryBase" (global (;0;) i32))
|
||||
(import "env" "memory" (memory (;0;) 256))
|
||||
(import "env" "table" (table (;0;) 0 anyfunc))
|
||||
(import "env" "gas" (func (;0;) (type 2)))
|
||||
(func (;1;) (type 0) (param i32) (result i32)
|
||||
i32.const 2
|
||||
call 0
|
||||
block i32 ;; label = @1
|
||||
i32.const 13
|
||||
call 0
|
||||
get_global 0
|
||||
i32.const 5242880
|
||||
i32.add
|
||||
get_global 0
|
||||
i32.const 5242880
|
||||
i32.add
|
||||
i32.load
|
||||
get_local 0
|
||||
i32.load offset=4
|
||||
i32.add
|
||||
i32.store
|
||||
i32.const 0
|
||||
end)
|
||||
(export "_call" (func 1)))
|
||||
|
||||
*/
|
@ -1,8 +0,0 @@
|
||||
extern {
|
||||
#[link(name="env")]
|
||||
fn log_event(id: *const u8);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe { log_event(::std::ptr::null()); }
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
#![feature(link_args)]
|
||||
#![no_main]
|
||||
|
||||
// as it is experimental preamble
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::slice;
|
||||
|
||||
#[link_args = "-s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1 -s"]
|
||||
extern {}
|
||||
|
||||
/// Wrapper over storage read/write externs
|
||||
/// Storage api is a key-value storage where both key and value are 32 bytes in len
|
||||
mod storage {
|
||||
pub struct Error;
|
||||
|
||||
#[link(name = "env")]
|
||||
extern {
|
||||
fn storage_read(key: *const u8, dst: *mut u8) -> i32;
|
||||
fn storage_write(key: *const u8, src: *const u8) -> i32;
|
||||
}
|
||||
|
||||
/// Performs read from storage to the specified slice `dst`, using all slice length
|
||||
/// Can return `Error` if data is read from outside of the storage boundaries
|
||||
pub fn read(key: &[u8; 32], dst: &mut [u8; 32]) -> Result<(), Error> {
|
||||
match unsafe {
|
||||
let mut dst = dst;
|
||||
storage_read(key.as_ptr(), dst.as_mut_ptr())
|
||||
} {
|
||||
x if x < 0 => Err(Error),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs write to the storage from the specified slice `src`
|
||||
pub fn write(key: &[u8; 32], src: &[u8; 32]) -> Result<(), Error> {
|
||||
match unsafe {
|
||||
storage_write(key.as_ptr(), src.as_ptr())
|
||||
} {
|
||||
x if x < 0 => Err(Error),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn call(_descriptor: *mut u8) {
|
||||
let storage_key = [1u8; 32];
|
||||
let mut storage_val = [2u8; 32];
|
||||
let storage_dup_key = [3u8; 32];
|
||||
|
||||
let _ = storage::write(&storage_key, &storage_val);
|
||||
let _ = storage::read(&storage_dup_key, &mut storage_val);
|
||||
let _ = storage::write(&storage_key, &storage_val);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user