diff --git a/Cargo.lock b/Cargo.lock index ea3a6879b48ef..a444e0a29b29c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,6 +176,14 @@ dependencies = [ "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cmake" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "coco" version = "0.1.1" @@ -791,7 +799,7 @@ dependencies = [ [[package]] name = "integer-sqrt" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/paritytech/integer-sqrt-rs.git#f4cf61482096dc98c1273f46a10849d182b4c23c" [[package]] name = "iovec" @@ -1840,6 +1848,7 @@ dependencies = [ "substrate-serializer 0.1.0", "substrate-state-machine 0.1.0", "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1910,6 +1919,7 @@ dependencies = [ "substrate-serializer 0.1.0", "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", + "wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1963,7 +1973,7 @@ name = "substrate-runtime-council" version = "0.1.0" dependencies = [ "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", "safe-mix 0.1.0", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", @@ -2035,7 +2045,7 @@ dependencies = [ name = "substrate-runtime-primitives" version = "0.1.0" dependencies = [ - "integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2046,6 +2056,18 @@ dependencies = [ "substrate-runtime-support 0.1.0", ] +[[package]] +name = "substrate-runtime-sandbox" +version = "0.1.0" +dependencies = [ + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", + "wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "substrate-runtime-session" version = "0.1.0" @@ -2077,10 +2099,12 @@ dependencies = [ "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", + "substrate-runtime-sandbox 0.1.0", "substrate-runtime-session 0.1.0", "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2463,6 +2487,27 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wabt" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "wabt-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wabt-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cmake 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasmi" version = "0.1.0" @@ -2576,6 +2621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" +"checksum cmake 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "5cf678ceebedde428000cb3a34465cf3606d1a48da17014948a916deac39da7c" "checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" @@ -2626,7 +2672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)" = "549dbb86397490ce69d908425b9beebc85bbaad25157d67479d4995bb56fdf9a" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" "checksum igd 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "356a0dc23a4fa0f8ce4777258085d00a01ea4923b2efd93538fc44bf5e1bda76" -"checksum integer-sqrt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8833702c315192502093b244e29c6ab9c55454adfe21b879a87a039ea8fe8520" +"checksum integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)" = "" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2134e210e2a024b5684f90e1556d5f71a1ce7f8b12e9ac9924c67fb36f63b336" "checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2" @@ -2753,6 +2799,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e902997144209c90311321b90dd658d964dd8e58b23a5919e66a1d068a0050e5" +"checksum wabt-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fc67b1d96cd7839be6996edf94be66351d83f614e9cc7c6edc33accd9f5e6529" "checksum wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba06def0c95a653122299e68a44f2f227eeac2d1f707df61f33abbaf6dd55992" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" diff --git a/Cargo.toml b/Cargo.toml index 4c5c69275042e..606a989bd8ee4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "substrate/rpc-servers", "substrate/rpc", "substrate/runtime-io", + "substrate/runtime-sandbox", "substrate/runtime-std", "substrate/runtime-support", "substrate/runtime/consensus", diff --git a/build.sh b/build.sh index 4c3c7a4b79f43..9a2aaac463093 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,7 @@ #!/bin/sh # NOTE `cargo install wasm-gc` before running this script. +# NOTE `cargo install --git https://github.com/pepyakin/wasm-export-table.git` set -e export CARGO_INCREMENTAL=0 diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock index 45cc1fee9aec3..461b9c9645875 100644 --- a/demo/runtime/wasm/Cargo.lock +++ b/demo/runtime/wasm/Cargo.lock @@ -260,11 +260,6 @@ name = "integer-sqrt" version = "0.1.0" source = "git+https://github.com/paritytech/integer-sqrt-rs.git#f4cf61482096dc98c1273f46a10849d182b4c23c" -[[package]] -name = "integer-sqrt" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "isatty" version = "0.1.6" @@ -334,6 +329,11 @@ dependencies = [ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memorydb" version = "0.1.1" @@ -379,6 +379,16 @@ dependencies = [ "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-wasm" +version = "0.27.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.4.8" @@ -388,6 +398,15 @@ dependencies = [ "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.2.10" @@ -657,6 +676,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", + "wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -679,7 +699,7 @@ name = "substrate-runtime-council" version = "0.1.0" dependencies = [ "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", "safe-mix 0.1.0", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", @@ -747,7 +767,7 @@ dependencies = [ name = "substrate-runtime-primitives" version = "0.1.0" dependencies = [ - "integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -758,6 +778,18 @@ dependencies = [ "substrate-runtime-support 0.1.0", ] +[[package]] +name = "substrate-runtime-sandbox" +version = "0.1.0" +dependencies = [ + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", + "wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "substrate-runtime-session" version = "0.1.0" @@ -789,6 +821,7 @@ dependencies = [ "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", + "substrate-runtime-sandbox 0.1.0", "substrate-runtime-session 0.1.0", "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", @@ -969,6 +1002,16 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasmi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -1027,7 +1070,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd546ef520ab3745f1aae5f2cdc6de9e6498e94d1ab138b9eb3ddfbf335847fb" "checksum hex-literal-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2ea76da4c7f1a54d01d54985566d3fdd960b2bbd7b970da024821c883c2d9631" "checksum integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)" = "" -"checksum integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7536fe7f78abedb82f609d87f46f0e0ca0ad31e84597deb8dabd8ed9ad047257" "checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2" "checksum keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f300c1f149cd9ca5214eed24f6e713a597517420fb8b15499824aa916259ec1" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" @@ -1037,13 +1079,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" "checksum memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "013b7e4c5e10c764936ebc6bd3662d8e3c92292d267bf6a42ef3f5cad9c793ee" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bd4dc02a80a0315b109e48992c46942c79bcdb8fac416dd575d330ed9ced6cbd" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +"checksum parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd9d732f2de194336fb02fe11f9eed13d9e76f13f4315b4d88a14ca411750cd" "checksum parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9f35048d735bb93dd115a0030498785971aab3234d311fbe273d020084d26bd8" "checksum patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e2f638d79aba5c4a71a4f373df6e3cd702250a53b7f0ed4da1e2a7be9737ae" "checksum plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83ae80873992f511142c07d0ec6c44de5636628fdb7e204abd655932ea79d995" @@ -1085,6 +1130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26b20dbeb7caee04597a5d2c93e2b3e64872c6ea2af732d7ad49dbec44067c35" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/demo/runtime/wasm/init.sh b/demo/runtime/wasm/init.sh index 02a0059a87584..e8b4387a2f998 100755 --- a/demo/runtime/wasm/init.sh +++ b/demo/runtime/wasm/init.sh @@ -4,3 +4,4 @@ rustup update nightly rustup target add wasm32-unknown-unknown --toolchain nightly rustup update stable cargo install --git https://github.com/alexcrichton/wasm-gc +cargo install --git https://github.com/pepyakin/wasm-export-table.git diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index 5f7e20cdd3591..00a448b1bdd3c 100644 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index 8429047769831..ac94269910f2b 100755 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock index efb08193104fa..6e14575403a4a 100644 --- a/polkadot/runtime/wasm/Cargo.lock +++ b/polkadot/runtime/wasm/Cargo.lock @@ -225,11 +225,6 @@ name = "integer-sqrt" version = "0.1.0" source = "git+https://github.com/paritytech/integer-sqrt-rs.git#f4cf61482096dc98c1273f46a10849d182b4c23c" -[[package]] -name = "integer-sqrt" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "isatty" version = "0.1.6" @@ -299,6 +294,11 @@ dependencies = [ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memorydb" version = "0.1.1" @@ -344,6 +344,16 @@ dependencies = [ "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-wasm" +version = "0.27.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.4.8" @@ -353,6 +363,15 @@ dependencies = [ "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.2.10" @@ -657,6 +676,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", + "wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -679,7 +699,7 @@ name = "substrate-runtime-council" version = "0.1.0" dependencies = [ "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", "safe-mix 0.1.0", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", @@ -747,7 +767,7 @@ dependencies = [ name = "substrate-runtime-primitives" version = "0.1.0" dependencies = [ - "integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -758,6 +778,18 @@ dependencies = [ "substrate-runtime-support 0.1.0", ] +[[package]] +name = "substrate-runtime-sandbox" +version = "0.1.0" +dependencies = [ + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", + "wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "substrate-runtime-session" version = "0.1.0" @@ -789,6 +821,7 @@ dependencies = [ "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", + "substrate-runtime-sandbox 0.1.0", "substrate-runtime-session 0.1.0", "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", @@ -969,6 +1002,16 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasmi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -1027,7 +1070,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd546ef520ab3745f1aae5f2cdc6de9e6498e94d1ab138b9eb3ddfbf335847fb" "checksum hex-literal-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2ea76da4c7f1a54d01d54985566d3fdd960b2bbd7b970da024821c883c2d9631" "checksum integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)" = "" -"checksum integer-sqrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7536fe7f78abedb82f609d87f46f0e0ca0ad31e84597deb8dabd8ed9ad047257" "checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2" "checksum keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f300c1f149cd9ca5214eed24f6e713a597517420fb8b15499824aa916259ec1" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" @@ -1037,13 +1079,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" "checksum memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "013b7e4c5e10c764936ebc6bd3662d8e3c92292d267bf6a42ef3f5cad9c793ee" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bd4dc02a80a0315b109e48992c46942c79bcdb8fac416dd575d330ed9ced6cbd" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +"checksum parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd9d732f2de194336fb02fe11f9eed13d9e76f13f4315b4d88a14ca411750cd" "checksum parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9f35048d735bb93dd115a0030498785971aab3234d311fbe273d020084d26bd8" "checksum patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e2f638d79aba5c4a71a4f373df6e3cd702250a53b7f0ed4da1e2a7be9737ae" "checksum plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83ae80873992f511142c07d0ec6c44de5636628fdb7e204abd655932ea79d995" @@ -1085,6 +1130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26b20dbeb7caee04597a5d2c93e2b3e64872c6ea2af732d7ad49dbec44067c35" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/polkadot/runtime/wasm/init.sh b/polkadot/runtime/wasm/init.sh index 02a0059a87584..e8b4387a2f998 100755 --- a/polkadot/runtime/wasm/init.sh +++ b/polkadot/runtime/wasm/init.sh @@ -4,3 +4,4 @@ rustup update nightly rustup target add wasm32-unknown-unknown --toolchain nightly rustup update stable cargo install --git https://github.com/alexcrichton/wasm-gc +cargo install --git https://github.com/pepyakin/wasm-export-table.git diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index 428d6a94b9873..1d1a95c6ce946 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index 07c467e9a5a6a..e0991b6397f97 100755 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/substrate/codec/src/slicable.rs b/substrate/codec/src/slicable.rs index 592a348c194fd..8c3d8f4330dce 100644 --- a/substrate/codec/src/slicable.rs +++ b/substrate/codec/src/slicable.rs @@ -61,6 +61,31 @@ pub trait Slicable: Sized { } } +impl Slicable for Result { + fn decode(input: &mut I) -> Option { + match input.read_byte()? { + 0 => Some(Ok(T::decode(input)?)), + 1 => Some(Err(E::decode(input)?)), + _ => None, + } + } + + fn encode(&self) -> Vec { + let mut v = Vec::new(); + match *self { + Ok(ref t) => { + v.push(0); + t.using_encoded(|s| v.extend(s)); + } + Err(ref e) => { + v.push(1); + e.using_encoded(|s| v.extend(s)); + } + } + v + } +} + impl Slicable for Option { fn decode(input: &mut I) -> Option { match input.read_byte()? { diff --git a/substrate/executor/Cargo.toml b/substrate/executor/Cargo.toml index ef99cd0e3eafc..47ad4ee1697c0 100644 --- a/substrate/executor/Cargo.toml +++ b/substrate/executor/Cargo.toml @@ -22,3 +22,4 @@ log = "0.3" [dev-dependencies] assert_matches = "1.1" +wabt = "0.1.7" diff --git a/substrate/executor/src/lib.rs b/substrate/executor/src/lib.rs index 06c378e181511..6c79977c5efd6 100644 --- a/substrate/executor/src/lib.rs +++ b/substrate/executor/src/lib.rs @@ -47,12 +47,15 @@ extern crate error_chain; #[cfg(test)] extern crate assert_matches; +#[cfg(test)] +extern crate wabt; #[macro_use] mod wasm_utils; mod wasm_executor; #[macro_use] mod native_executor; +mod sandbox; pub mod error; pub use wasm_executor::WasmExecutor; diff --git a/substrate/executor/src/sandbox.rs b/substrate/executor/src/sandbox.rs new file mode 100644 index 0000000000000..1ab242e9d530a --- /dev/null +++ b/substrate/executor/src/sandbox.rs @@ -0,0 +1,483 @@ +// 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 . + +//! This module implements sandboxing support in the runtime. + +use codec::Slicable; +use primitives::sandbox as sandbox_primitives; +use std::collections::HashMap; +use wasm_utils::DummyUserError; +use wasmi::memory_units::Pages; +use wasmi::{Externals, FuncRef, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance, + ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind}; + +/// Index of a function inside the supervisor. +/// +/// This is a typically an index in the default table of the supervisor, however +/// the exact meaning of this index is depends on the implementation of dispatch function. +#[derive(Copy, Clone, Debug, PartialEq)] +struct SupervisorFuncIndex(usize); + +/// Index of a function within guest index space. +/// +/// This index is supposed to be used with as index for `Externals`. +#[derive(Copy, Clone, Debug, PartialEq)] +struct GuestFuncIndex(usize); + +/// This struct holds a mapping from guest index space to supervisor. +struct GuestToSuperviserFunctionMapping { + funcs: Vec, +} + +impl GuestToSuperviserFunctionMapping { + fn new() -> GuestToSuperviserFunctionMapping { + GuestToSuperviserFunctionMapping { funcs: Vec::new() } + } + + fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex { + let idx = self.funcs.len(); + self.funcs.push(supervisor_func); + GuestFuncIndex(idx) + } + + fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option { + self.funcs.get(guest_func_idx.0).cloned() + } +} + +struct Imports { + func_map: HashMap<(Vec, Vec), GuestFuncIndex>, + memories_map: HashMap<(Vec, Vec), MemoryRef>, +} + +impl ImportResolver for Imports { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + signature: &::wasmi::Signature, + ) -> Result { + let key = ( + module_name.as_bytes().to_owned(), + field_name.as_bytes().to_owned(), + ); + let idx = *self.func_map.get(&key).ok_or_else(|| { + ::wasmi::Error::Instantiation(format!( + "Export {}:{} not found", + module_name, field_name + )) + })?; + Ok(::wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) + } + + fn resolve_memory( + &self, + module_name: &str, + field_name: &str, + _memory_type: &::wasmi::MemoryDescriptor, + ) -> Result { + let key = ( + module_name.as_bytes().to_vec(), + field_name.as_bytes().to_vec(), + ); + let mem = self.memories_map + .get(&key) + .ok_or_else(|| { + ::wasmi::Error::Instantiation(format!( + "Export {}:{} not found", + module_name, field_name + )) + })? + .clone(); + Ok(mem) + } + + fn resolve_global( + &self, + _module_name: &str, + _field_name: &str, + _global_type: &::wasmi::GlobalDescriptor, + ) -> Result<::wasmi::GlobalRef, ::wasmi::Error> { + // TODO: + unimplemented!() + } + + fn resolve_table( + &self, + _module_name: &str, + _field_name: &str, + _table_type: &::wasmi::TableDescriptor, + ) -> Result<::wasmi::TableRef, ::wasmi::Error> { + // TODO: + unimplemented!() + } +} + +/// This trait encapsulates sandboxing capabilities. +/// +/// Note that this functions are only called in the `supervisor` context. +pub trait SandboxCapabilities { + /// Returns associated sandbox `Store`. + fn store(&self) -> &Store; + + /// Allocate space of the specified length in the supervisor memory. + /// + /// Returns pointer to the allocated block. + fn allocate(&mut self, len: u32) -> u32; + + /// Deallocate space specified by the pointer that was previously returned by [`allocate`]. + /// + /// [`allocate`]: #tymethod.allocate + fn deallocate(&mut self, ptr: u32); + + /// Write `data` into the supervisor memory at offset specified by `ptr`. + /// + /// # Errors + /// + /// Returns `Err` if `ptr + data.len()` is out of bounds. + fn write_memory(&mut self, ptr: u32, data: &[u8]) -> Result<(), DummyUserError>; + + /// Read `len` bytes from the supervisor memory. + /// + /// # Errors + /// + /// Returns `Err` if `ptr + len` is out of bounds. + fn read_memory(&self, ptr: u32, len: u32) -> Result, DummyUserError>; +} + +/// Implementation of [`Externals`] that allows execution of guest module with +/// [externals][`Externals`] that might refer functions defined by supervisor. +/// +/// [`Externals`]: ../../wasmi/trait.Externals.html +pub struct GuestExternals<'a, FE: SandboxCapabilities + Externals + 'a> { + supervisor_externals: &'a mut FE, + instance_idx: u32, + state: u32, +} + +impl<'a, FE: SandboxCapabilities + Externals + 'a> GuestExternals<'a, FE> { + /// Create a new instance of `GuestExternals`. + /// + /// It will use `supervisor_externals` to execute calls from guest to supervisor. + /// `instance_idx` required to fetch settings for this particular instance, e.g + /// associated dispatch thunk funtion and mapping between externals function ids to + /// functions in supervisor module. + /// `state` is just an integer that allows supervisor to have arbitrary state associated with the call, + /// typically used for implementing runtime functions. + pub fn new(supervisor_externals: &mut FE, instance_idx: u32, state: u32) -> GuestExternals { + GuestExternals { + supervisor_externals, + instance_idx, + state, + } + } +} + +fn trap() -> Trap { + TrapKind::Host(Box::new(DummyUserError)).into() +} + +fn deserialize_result(serialized_result: &[u8]) -> Result, Trap> { + use self::sandbox_primitives::{HostError, ReturnValue}; + let result_val = Result::::decode(&mut &serialized_result[..]) + .ok_or_else(|| trap())?; + + match result_val { + Ok(return_value) => Ok(match return_value { + ReturnValue::Unit => None, + ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)), + }), + Err(HostError) => Err(trap()), + } +} + +impl<'a, FE: SandboxCapabilities + Externals + 'a> Externals for GuestExternals<'a, FE> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + let (func_idx, dispatch_thunk) = { + let instance = &self.supervisor_externals.store().instances[self.instance_idx as usize]; + let dispatch_thunk = instance.dispatch_thunk.clone(); + let func_idx = instance + .guest_to_supervisor_mapping + .func_by_guest_index( + GuestFuncIndex(index) + ) + .expect( + "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; + `FuncInstance::alloc_host` is called with indexes that was obtained from `guest_to_supervisor_mapping`; + `func_by_guest_index` called with `index` can't return `None`; + qed" + ); + (func_idx, dispatch_thunk) + }; + + // Serialize arguments into a byte vector. + let invoke_args_data: Vec = args.as_ref() + .iter() + .cloned() + .map(sandbox_primitives::TypedValue::from) + .collect::>() + .encode(); + + let state = self.state; + + // Move serialized arguments inside the memory and invoke dispatch thunk and + // then free allocated memory. + let invoke_args_ptr = self.supervisor_externals + .allocate(invoke_args_data.len() as u32); + self.supervisor_externals + .write_memory(invoke_args_ptr, &invoke_args_data)?; + let result = ::wasmi::FuncInstance::invoke( + &dispatch_thunk, + &[ + RuntimeValue::I32(invoke_args_ptr as i32), + RuntimeValue::I32(invoke_args_data.len() as i32), + RuntimeValue::I32(state as i32), + RuntimeValue::I32(func_idx.0 as i32), + ], + self.supervisor_externals, + ); + self.supervisor_externals.deallocate(invoke_args_ptr); + + // dispatch_thunk returns pointer to serialized arguments. + let (serialized_result_val_ptr, serialized_result_val_len) = match result { + // Unpack pointer and len of the serialized result data. + Ok(Some(RuntimeValue::I64(v))) => { + // Cast to u64 to use zero-extension. + let v = v as u64; + let ptr = (v as u64 >> 32) as u32; + let len = (v & 0xFFFFFFFF) as u32; + (ptr, len) + } + _ => return Err(trap()), + }; + + let serialized_result_val = self.supervisor_externals + .read_memory(serialized_result_val_ptr, serialized_result_val_len)?; + self.supervisor_externals + .deallocate(serialized_result_val_ptr); + + // TODO: check the signature? + + deserialize_result(&serialized_result_val) + } +} + +struct SandboxInstance { + instance: ModuleRef, + dispatch_thunk: FuncRef, + guest_to_supervisor_mapping: GuestToSuperviserFunctionMapping, +} + +fn decode_environment_definition( + raw_env_def: &[u8], + memories: &[MemoryRef], +) -> Result<(Imports, GuestToSuperviserFunctionMapping), DummyUserError> { + let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..]).ok_or_else(|| DummyUserError)?; + + let mut func_map = HashMap::new(); + let mut memories_map = HashMap::new(); + let mut guest_to_supervisor_mapping = GuestToSuperviserFunctionMapping::new(); + + for entry in &env_def.entries { + let module = entry.module_name.clone(); + let field = entry.field_name.clone(); + + match entry.entity { + sandbox_primitives::ExternEntity::Function(func_idx) => { + let externals_idx = + guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize)); + func_map.insert((module, field), externals_idx); + } + sandbox_primitives::ExternEntity::Memory(memory_idx) => { + let memory_ref = memories + .get(memory_idx as usize) + .ok_or_else(|| DummyUserError)?; + memories_map.insert((module, field), memory_ref.clone()); + } + } + } + + Ok(( + Imports { + func_map, + memories_map, + }, + guest_to_supervisor_mapping, + )) +} + +/// This struct keeps track of all sandboxed components. +pub struct Store { + instances: Vec, + memories: Vec, +} + +impl Store { + /// Create new empty sandbox store. + pub fn new() -> Store { + Store { + instances: Vec::new(), + memories: Vec::new(), + } + } + + /// Instantiate a guest module and return it's index in the store. + /// + /// The guest module's code is specified in `wasm`. Environment that will be available to + /// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]). + /// `dispatch_thunk` is used as function that handle calls from guests. + /// + /// # Errors + /// + /// Returns `Err` if any of the following conditions happens: + /// + /// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`]. + /// - Module in `wasm` is invalid or couldn't be instantiated. + /// + /// [`EnvironmentDefinition`]: ../../sandbox/struct.EnvironmentDefinition.html + pub fn instantiate( + &mut self, + dispatch_thunk: FuncRef, + wasm: &[u8], + raw_env_def: &[u8], + _state: u32, + ) -> Result { + let (imports, guest_to_supervisor_mapping) = + decode_environment_definition(raw_env_def, &self.memories)?; + + // TODO: Run `start`. + let module = Module::from_buffer(wasm).map_err(|_| DummyUserError)?; + let instance = ModuleInstance::new(&module, &imports) + .map_err(|_| DummyUserError)? + .assert_no_start(); + + let instance_idx = self.instances.len(); + + self.instances.push(SandboxInstance { + instance, + dispatch_thunk, + guest_to_supervisor_mapping, + }); + + Ok(instance_idx as u32) + } + + /// Create a new memory instance and return it's index. + /// + /// # Errors + /// + /// Returns `Err` if the memory couldn't be created. + /// Typically happens if `initial` is more than `maximum`. + pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { + let maximum = match maximum { + sandbox_primitives::MEM_UNLIMITED => None, + specified_limit => Some(Pages(specified_limit as usize)), + }; + + let mem = MemoryInstance::alloc(Pages(initial as usize), maximum).map_err(|_| DummyUserError)?; + self.memories.push(mem); + let mem_idx = self.memories.len() - 1; + Ok(mem_idx as u32) + } + + /// Returns `ModuleRef` by `instance_idx`. + /// + /// # Errors + /// + /// Returns `Err` If `instance_idx` isn't a valid index of an instance. + pub fn instance(&self, instance_idx: u32) -> Result { + self.instances + .get(instance_idx as usize) + .map(|i| i.instance.clone()) + .ok_or_else(|| DummyUserError) + } + + /// Returns reference to a memory instance by `memory_idx`. + /// + /// # Errors + /// + /// Returns `Err` If `memory_idx` isn't a valid index of an instance. + pub fn memory(&self, memory_idx: u32) -> Result { + self.memories + .get(memory_idx as usize) + .cloned() + .ok_or_else(|| DummyUserError) + } +} + +#[cfg(test)] +mod tests { + use wasm_executor::WasmExecutor; + use state_machine::{TestExternalities, CodeExecutor}; + use wabt; + + #[test] + fn sandbox_should_work() { + let mut ext = TestExternalities::default(); + let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); + + let code = wabt::wat2wasm(r#" + (module + (import "env" "assert" (func $assert (param i32))) + (import "env" "inc_counter" (func $inc_counter (param i32) (result i32))) + (func (export "call") + (drop + (call $inc_counter (i32.const 5)) + ) + + (call $inc_counter (i32.const 3)) + ;; current counter value is on the stack + + ;; check whether current == 8 + i32.const 8 + i32.eq + + call $assert + ) + ) + "#).unwrap(); + + assert_eq!( + WasmExecutor.call(&mut ext, &test_code[..], "test_sandbox", &code).unwrap(), + vec![1], + ); + } + + #[test] + fn sandbox_trap() { + let mut ext = TestExternalities::default(); + let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); + + let code = wabt::wat2wasm(r#" + (module + (import "env" "assert" (func $assert (param i32))) + (func (export "call") + i32.const 0 + call $assert + ) + ) + "#).unwrap(); + + assert_eq!( + WasmExecutor.call(&mut ext, &test_code[..], "test_sandbox", &code).unwrap(), + vec![0], + ); + } +} diff --git a/substrate/executor/src/wasm_executor.rs b/substrate/executor/src/wasm_executor.rs index c02a7e630aa18..532275b80bcaa 100644 --- a/substrate/executor/src/wasm_executor.rs +++ b/substrate/executor/src/wasm_executor.rs @@ -18,7 +18,9 @@ use std::cmp::Ordering; use std::collections::HashMap; -use wasmi::{Module, ModuleInstance, MemoryInstance, MemoryRef, ImportsBuilder}; +use wasmi::{ + Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, +}; use wasmi::RuntimeValue::{I32, I64}; use wasmi::memory_units::{Pages, Bytes}; use state_machine::{Externalities, CodeExecutor}; @@ -26,7 +28,9 @@ use error::{Error, ErrorKind, Result}; use wasm_utils::{DummyUserError}; use primitives::{blake2_256, twox_128, twox_256}; use primitives::hexdisplay::HexDisplay; +use primitives::sandbox as sandbox_primitives; use triehash::ordered_trie_root; +use sandbox; struct Heap { end: u32, @@ -60,23 +64,45 @@ impl Heap { } struct FunctionExecutor<'e, E: Externalities + 'e> { + sandbox_store: sandbox::Store, heap: Heap, memory: MemoryRef, + table: Option, ext: &'e mut E, hash_lookup: HashMap, Vec>, } impl<'e, E: Externalities> FunctionExecutor<'e, E> { - fn new(m: MemoryRef, e: &'e mut E) -> Result { + fn new(m: MemoryRef, t: Option, e: &'e mut E) -> Result { Ok(FunctionExecutor { + sandbox_store: sandbox::Store::new(), heap: Heap::new(&m)?, memory: m, + table: t, ext: e, hash_lookup: HashMap::new(), }) } } +impl<'e, E: Externalities> sandbox::SandboxCapabilities for FunctionExecutor<'e, E> { + fn store(&self) -> &sandbox::Store { + &self.sandbox_store + } + fn allocate(&mut self, len: u32) -> u32 { + self.heap.allocate(len) + } + fn deallocate(&mut self, ptr: u32) { + self.heap.deallocate(ptr) + } + fn write_memory(&mut self, ptr: u32, data: &[u8]) -> ::std::result::Result<(), DummyUserError> { + self.memory.set(ptr, data).map_err(|_| DummyUserError) + } + fn read_memory(&self, ptr: u32, len: u32) -> ::std::result::Result, DummyUserError> { + self.memory.get(ptr, len as usize).map_err(|_| DummyUserError) + } +} + trait WritePrimitive { fn write_primitive(&self, offset: u32, t: T) -> ::std::result::Result<(), DummyUserError>; } @@ -310,6 +336,73 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, 5 }) }, + ext_sandbox_instantiate(dispatch_thunk_idx: usize, wasm_ptr: *const u8, wasm_len: usize, imports_ptr: *const u8, imports_len: usize, state: usize) -> u32 => { + let wasm = this.memory.get(wasm_ptr, wasm_len as usize).map_err(|_| DummyUserError)?; + let raw_env_def = this.memory.get(imports_ptr, imports_len as usize).map_err(|_| DummyUserError)?; + + let table = this.table.as_ref().ok_or_else(|| DummyUserError)?; + let dispatch_thunk = table.get(dispatch_thunk_idx) + .map_err(|_| DummyUserError)? + .ok_or_else(|| DummyUserError)? + .clone(); + + let instance_idx = this.sandbox_store.instantiate(dispatch_thunk, &wasm, &raw_env_def, state)?; + + Ok(instance_idx as u32) + }, + ext_sandbox_invoke(instance_idx: u32, export_ptr: *const u8, export_len: usize, state: usize) -> u32 => { + trace!(target: "runtime-sandbox", "invoke, instance_idx={}", instance_idx); + let export = this.memory.get(export_ptr, export_len as usize) + .map_err(|_| DummyUserError) + .and_then(|b| + String::from_utf8(b) + .map_err(|_| DummyUserError) + )?; + + let instance = this.sandbox_store.instance(instance_idx)?; + + let mut guest_externals = sandbox::GuestExternals::new(this, instance_idx, state); + + let result = instance.invoke_export(&export, &[], &mut guest_externals); + match result { + Ok(None) => Ok(sandbox_primitives::ERR_OK), + // TODO: Return value + Ok(_) => unimplemented!(), + Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), + } + }, + ext_sandbox_memory_new(initial: u32, maximum: u32) -> u32 => { + let mem_idx = this.sandbox_store.new_memory(initial, maximum)?; + Ok(mem_idx) + }, + ext_sandbox_memory_get(memory_idx: u32, offset: u32, buf_ptr: *mut u8, buf_len: usize) -> u32 => { + let dst_memory = this.sandbox_store.memory(memory_idx)?; + + let data: Vec = match dst_memory.get(offset, buf_len as usize) { + Ok(data) => data, + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + }; + match this.memory.set(buf_ptr, &data) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + _ => {}, + } + + Ok(sandbox_primitives::ERR_OK) + }, + ext_sandbox_memory_set(memory_idx: u32, offset: u32, val_ptr: *const u8, val_len: usize) -> u32 => { + let dst_memory = this.sandbox_store.memory(memory_idx)?; + + let data = match this.memory.get(offset, val_len as usize) { + Ok(data) => data, + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + }; + match dst_memory.set(val_ptr, &data) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + _ => {}, + } + + Ok(sandbox_primitives::ERR_OK) + }, => <'e, E: Externalities + 'e> ); @@ -341,7 +434,8 @@ impl CodeExecutor for WasmExecutor { .with_resolver("env", FunctionExecutor::::resolver()) )?; - // extract a reference to a linear memory and initialize FunctionExecutor. + // extract a reference to a linear memory, optional reference to a table + // and then initialize FunctionExecutor. let memory = intermediate_instance .not_started_instance() .export_by_name("memory") @@ -349,7 +443,11 @@ impl CodeExecutor for WasmExecutor { .as_memory() .expect("in module generated by rustc export named 'memory' should be a memory; qed") .clone(); - let mut fec = FunctionExecutor::new(memory.clone(), ext)?; + let table: Option = intermediate_instance + .not_started_instance() + .export_by_name("table") + .and_then(|e| e.as_table().cloned()); + let mut fec = FunctionExecutor::new(memory.clone(), table, ext)?; // finish instantiation by running 'start' function (if any). let instance = intermediate_instance.run_start(&mut fec)?; diff --git a/substrate/executor/wasm/Cargo.lock b/substrate/executor/wasm/Cargo.lock index 5b062eb2d91d5..a60f4b2df89e6 100644 --- a/substrate/executor/wasm/Cargo.lock +++ b/substrate/executor/wasm/Cargo.lock @@ -30,6 +30,7 @@ name = "runtime-test" version = "0.1.0" dependencies = [ "substrate-runtime-io 0.1.0", + "substrate-runtime-sandbox 0.1.0", ] [[package]] @@ -94,6 +95,17 @@ dependencies = [ "substrate-runtime-std 0.1.0", ] +[[package]] +name = "substrate-runtime-sandbox" +version = "0.1.0" +dependencies = [ + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", +] + [[package]] name = "substrate-runtime-std" version = "0.1.0" diff --git a/substrate/executor/wasm/Cargo.toml b/substrate/executor/wasm/Cargo.toml index 89f27a61b957d..2a479ab2e7c09 100644 --- a/substrate/executor/wasm/Cargo.toml +++ b/substrate/executor/wasm/Cargo.toml @@ -8,6 +8,7 @@ crate-type = ["cdylib"] [dependencies] substrate-runtime-io = { path = "../../runtime-io", version = "0.1", default_features = false } +substrate-runtime-sandbox = { path = "../../runtime-sandbox", version = "0.1", default_features = false } [profile.release] panic = "abort" diff --git a/substrate/executor/wasm/build.sh b/substrate/executor/wasm/build.sh index e173ccd36ec9c..83acc2700d37d 100755 --- a/substrate/executor/wasm/build.sh +++ b/substrate/executor/wasm/build.sh @@ -4,5 +4,8 @@ set -e cargo +nightly build --target=wasm32-unknown-unknown --release for i in test do + # Add export of the default table under name 'table'. + wasm-export-table target/wasm32-unknown-unknown/release/runtime_$i.wasm target/wasm32-unknown-unknown/release/runtime_$i.table.wasm + cp target/wasm32-unknown-unknown/release/runtime_$i.table.wasm target/wasm32-unknown-unknown/release/runtime_$i.wasm wasm-gc target/wasm32-unknown-unknown/release/runtime_$i.wasm target/wasm32-unknown-unknown/release/runtime_$i.compact.wasm done diff --git a/substrate/executor/wasm/src/lib.rs b/substrate/executor/wasm/src/lib.rs index 013ecca1ed7a8..e7c28e37eaec4 100644 --- a/substrate/executor/wasm/src/lib.rs +++ b/substrate/executor/wasm/src/lib.rs @@ -8,6 +8,8 @@ use alloc::vec::Vec; #[macro_use] extern crate substrate_runtime_io as runtime_io; +extern crate substrate_runtime_sandbox as sandbox; + use runtime_io::{ set_storage, storage, print, blake2_256, twox_128, twox_256, ed25519_verify, enumerated_trie_root @@ -50,5 +52,46 @@ impl_stubs!( }, test_enumerated_trie_root NO_DECODE => |_| { enumerated_trie_root(&[&b"zero"[..], &b"one"[..], &b"two"[..]]).to_vec() + }, + test_sandbox NO_DECODE => |code: &[u8]| { + let result = execute_sandboxed(code).is_ok(); + [result as u8].to_vec() } ); + +fn execute_sandboxed(code: &[u8]) -> Result { + struct State { + counter: u32, + } + + fn env_assert(_e: &mut State, args: &[sandbox::TypedValue]) -> Result { + if args.len() != 1 { + return Err(sandbox::HostError); + } + let condition = args[0].as_i32().ok_or_else(|| sandbox::HostError)?; + if condition != 0 { + Ok(sandbox::ReturnValue::Unit) + } else { + Err(sandbox::HostError) + } + } + fn env_inc_counter(e: &mut State, args: &[sandbox::TypedValue]) -> Result { + if args.len() != 1 { + return Err(sandbox::HostError); + } + let inc_by = args[0].as_i32().ok_or_else(|| sandbox::HostError)?; + e.counter += inc_by as u32; + Ok(sandbox::ReturnValue::Value(sandbox::TypedValue::I32(e.counter as i32))) + } + + let mut state = State { counter: 0 }; + + let mut env_builder = sandbox::EnvironmentDefinitionBuilder::new(); + env_builder.add_host_func("env", "assert", env_assert); + env_builder.add_host_func("env", "inc_counter", env_inc_counter); + + let mut instance = sandbox::Instance::new(code, &env_builder, &mut state)?; + let result = instance.invoke(b"call", &[], &mut state); + + result.map_err(|_| sandbox::HostError) +} diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm index 329c06d241f26..ed1b074c8500b 100644 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm index 93a053d21df1a..12a47f048cd8f 100755 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm differ diff --git a/substrate/primitives/Cargo.toml b/substrate/primitives/Cargo.toml index 89bc3347662d6..f5c8eb1d95eee 100644 --- a/substrate/primitives/Cargo.toml +++ b/substrate/primitives/Cargo.toml @@ -15,6 +15,7 @@ uint = { git = "https://github.com/rphmeier/primitives.git", branch = "compile-f twox-hash = { version = "1.1.0", optional = true } byteorder = { version = "1.1", default_features = false } blake2-rfc = { version = "0.2.18", optional = true } +wasmi = { version = "0.1", optional = true } [dev-dependencies] substrate-serializer = { path = "../serializer" } @@ -23,6 +24,7 @@ pretty_assertions = "0.4" [features] default = ["std"] std = [ + "wasmi", "uint/std", "fixed-hash/std", "substrate-codec/std", diff --git a/substrate/primitives/src/lib.rs b/substrate/primitives/src/lib.rs index 626efbf78727b..5f6d14f3d16c6 100644 --- a/substrate/primitives/src/lib.rs +++ b/substrate/primitives/src/lib.rs @@ -42,6 +42,8 @@ extern crate blake2_rfc; extern crate serde_derive; #[cfg(feature = "std")] extern crate core; +#[cfg(feature = "std")] +extern crate wasmi; extern crate substrate_runtime_std as rstd; @@ -83,6 +85,7 @@ pub mod hexdisplay; pub mod bft; pub mod block; pub mod hash; +pub mod sandbox; pub mod storage; pub mod uint; diff --git a/substrate/primitives/src/sandbox.rs b/substrate/primitives/src/sandbox.rs new file mode 100644 index 0000000000000..71d22afa9f4d1 --- /dev/null +++ b/substrate/primitives/src/sandbox.rs @@ -0,0 +1,357 @@ +// 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 . + +//! Definition of a sandbox environment. + +use codec::{Slicable, Input}; +use rstd::vec::Vec; + +/// Error error that can be returned from host function. +#[cfg_attr(feature = "std", derive(Debug))] +pub struct HostError; + +impl Slicable for HostError { + fn decode(_: &mut I) -> Option { + Some(HostError) + } + + fn using_encoded R>(&self, f: F) -> R { + f(&[]) + } + + fn encode(&self) -> Vec { + Vec::new() + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +#[repr(i8)] +enum ValueType { + I32 = 1, + I64 = 2, + F32 = 3, + F64 = 4, +} + +/// Representation of a typed wasm value. +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum TypedValue { + /// Value of 32-bit signed or unsigned integer. + I32(i32), + + /// Value of 64-bit signed or unsigned integer. + I64(i64), + + /// Value of 32-bit IEEE 754-2008 floating point number represented as a bit pattern. + F32(i32), + + /// Value of 64-bit IEEE 754-2008 floating point number represented as a bit pattern. + F64(i64), +} + +impl TypedValue { + /// Returns `Some` if this value of type `I32`. + pub fn as_i32(&self) -> Option { + match *self { + TypedValue::I32(v) => Some(v), + _ => None, + } + } +} + +#[cfg(feature = "std")] +impl From<::wasmi::RuntimeValue> for TypedValue { + fn from(val: ::wasmi::RuntimeValue) -> TypedValue { + use ::wasmi::RuntimeValue; + match val { + RuntimeValue::I32(v) => TypedValue::I32(v), + RuntimeValue::I64(v) => TypedValue::I64(v), + RuntimeValue::F32(v) => TypedValue::F32(v.to_bits() as i32), + RuntimeValue::F64(v) => TypedValue::F64(v.to_bits() as i64), + } + } +} + +#[cfg(feature = "std")] +impl From for ::wasmi::RuntimeValue { + fn from(val: TypedValue) -> ::wasmi::RuntimeValue { + use ::wasmi::RuntimeValue; + match val { + TypedValue::I32(v) => RuntimeValue::I32(v), + TypedValue::I64(v) => RuntimeValue::I64(v), + TypedValue::F32(v_bits) => RuntimeValue::F32(f32::from_bits(v_bits as u32)), + TypedValue::F64(v_bits) => RuntimeValue::F64(f64::from_bits(v_bits as u64)), + } + } +} + +impl Slicable for TypedValue { + fn encode(&self) -> Vec { + let mut v = Vec::new(); + match *self { + TypedValue::I32(i) => { + v.push(ValueType::I32 as u8); + i.using_encoded(|s| v.extend(s)); + } + TypedValue::I64(i) => { + v.push(ValueType::I64 as u8); + i.using_encoded(|s| v.extend(s)); + } + TypedValue::F32(f_bits) => { + v.push(ValueType::F32 as u8); + f_bits.using_encoded(|s| v.extend(s)); + } + TypedValue::F64(f_bits) => { + v.push(ValueType::F64 as u8); + f_bits.using_encoded(|s| v.extend(s)); + } + } + + v + } + + fn decode(value: &mut I) -> Option { + let typed_value = match i8::decode(value) { + Some(x) if x == ValueType::I32 as i8 => TypedValue::I32(i32::decode(value)?), + Some(x) if x == ValueType::I64 as i8 => TypedValue::I64(i64::decode(value)?), + Some(x) if x == ValueType::F32 as i8 => TypedValue::F32(i32::decode(value)?), + Some(x) if x == ValueType::F64 as i8 => TypedValue::F64(i64::decode(value)?), + _ => return None, + }; + Some(typed_value) + } +} + +/// Typed value that can be returned from a function. +/// +/// Basically a `TypedValue` plus `Unit`, for functions which return nothing. +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum ReturnValue { + /// For returning some concrete value. + Value(TypedValue), + + /// For returning nothing. + Unit, +} + +impl From for ReturnValue { + fn from(v: TypedValue) -> ReturnValue { + ReturnValue::Value(v) + } +} + +impl Slicable for ReturnValue { + fn encode(&self) -> Vec { + let mut v = Vec::new(); + match *self { + ReturnValue::Unit => { + v.push(0); + } + ReturnValue::Value(ref val) => { + v.push(1); + val.using_encoded(|s| v.extend(s)); + } + } + v + } + + fn decode(value: &mut I) -> Option { + match i8::decode(value) { + Some(0) => Some(ReturnValue::Unit), + Some(1) => Some(ReturnValue::Value(TypedValue::decode(value)?)), + _ => return None, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +#[repr(i8)] +enum ExternEntityKind { + Function = 1, + Memory = 2, +} + +/// Describes an entity to define or import into the environment. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum ExternEntity { + /// Function that is specified by an index in a default table of + /// a module that creates the sandbox. + Function(u32), + + /// Linear memory that is specified by some identifier returned by sandbox + /// module upon creation new sandboxed memory. + Memory(u32), +} + +impl Slicable for ExternEntity { + fn encode(&self) -> Vec { + let mut v = Vec::new(); + match *self { + ExternEntity::Function(ref index) => { + v.push(ExternEntityKind::Function as u8); + index.using_encoded(|s| v.extend(s)); + } + ExternEntity::Memory(ref mem_id) => { + v.push(ExternEntityKind::Memory as u8); + mem_id.using_encoded(|s| v.extend(s)); + } + } + + v + } + + fn decode(value: &mut I) -> Option { + match i8::decode(value) { + Some(x) if x == ExternEntityKind::Function as i8 => { + let idx = u32::decode(value)?; + Some(ExternEntity::Function(idx)) + } + Some(x) if x == ExternEntityKind::Memory as i8 => { + let mem_id = u32::decode(value)?; + Some(ExternEntity::Memory(mem_id)) + } + _ => None, + } + } +} + +/// An entry in a environment definition table. +/// +/// Each entry has a two-level name and description of an entity +/// being defined. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Entry { + /// Module name of which corresponding entity being defined. + pub module_name: Vec, + /// Field name in which corresponding entity being defined. + pub field_name: Vec, + /// External entity being defined. + pub entity: ExternEntity, +} + +impl Slicable for Entry { + fn encode(&self) -> Vec { + let mut v = Vec::new(); + self.module_name.using_encoded(|s| v.extend(s)); + self.field_name.using_encoded(|s| v.extend(s)); + self.entity.using_encoded(|s| v.extend(s)); + + v + } + + fn decode(value: &mut I) -> Option { + let module_name = Vec::decode(value)?; + let field_name = Vec::decode(value)?; + let entity = ExternEntity::decode(value)?; + + Some(Entry { + module_name, + field_name, + entity, + }) + } +} + +/// Definition of runtime that could be used by sandboxed code. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct EnvironmentDefinition { + /// Vector of all entries in the environment defintion. + pub entries: Vec, +} + +impl Slicable for EnvironmentDefinition { + fn encode(&self) -> Vec { + self.entries.encode() + } + + fn decode(value: &mut I) -> Option { + let entries = Vec::decode(value)?; + + Some(EnvironmentDefinition { + entries, + }) + } +} + +/// Constant for specifying no limit when creating a sandboxed +/// memory instance. For FFI purposes. +pub const MEM_UNLIMITED: u32 = -1i32 as u32; + +/// No error happened. +/// +/// For FFI purposes. +pub const ERR_OK: u32 = 0; + +/// Validation or instantiation error occured when creating new +/// sandboxed module instance. +/// +/// For FFI purposes. +pub const ERR_MODULE: u32 = -1i32 as u32; + +/// Out-of-bounds access attempted with memory or table. +/// +/// For FFI purposes. +pub const ERR_OUT_OF_BOUNDS: u32 = -2i32 as u32; + +/// Execution error occured (typically trap). +/// +/// For FFI purposes. +pub const ERR_EXECUTION: u32 = -3i32 as u32; + +#[cfg(test)] +mod tests { + use super::*; + use std::fmt; + + fn roundtrip(s: S) { + let encoded = s.encode(); + assert_eq!(S::decode(&mut &encoded[..]).unwrap(), s); + } + + #[test] + fn env_def_roundtrip() { + roundtrip(EnvironmentDefinition { + entries: vec![], + }); + + roundtrip(EnvironmentDefinition { + entries: vec![ + Entry { + module_name: b"kernel"[..].into(), + field_name: b"memory"[..].into(), + entity: ExternEntity::Memory(1337), + }, + ], + }); + + roundtrip(EnvironmentDefinition { + entries: vec![ + Entry { + module_name: b"env"[..].into(), + field_name: b"abort"[..].into(), + entity: ExternEntity::Function(228), + }, + ], + }); + } +} diff --git a/substrate/runtime-sandbox/Cargo.toml b/substrate/runtime-sandbox/Cargo.toml new file mode 100755 index 0000000000000..c47ef3ecbc846 --- /dev/null +++ b/substrate/runtime-sandbox/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "substrate-runtime-sandbox" +version = "0.1.0" +authors = ["Parity Technologies "] +build = "build.rs" + +[build-dependencies] +rustc_version = "0.2" + +[dependencies] +wasmi = { version = "0.1", optional = true } +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-codec = { path = "../codec", default_features = false } + +[features] +default = ["std"] +std = [ + "wasmi", + "substrate-primitives/std", + "substrate-runtime-std/std", + "substrate-codec/std", + "substrate-runtime-io/std", +] +nightly = [] +strict = [] diff --git a/substrate/runtime-sandbox/build.rs b/substrate/runtime-sandbox/build.rs new file mode 100755 index 0000000000000..35eb154f3a69a --- /dev/null +++ b/substrate/runtime-sandbox/build.rs @@ -0,0 +1,14 @@ +//! Set a nightly feature + +extern crate rustc_version; +use rustc_version::{version, version_meta, Channel}; + +fn main() { + // Assert we haven't travelled back in time + assert!(version().unwrap().major >= 1); + + // Set cfg flags depending on release channel + if let Channel::Nightly = version_meta().unwrap().channel { + println!("cargo:rustc-cfg=feature=\"nightly\""); + } +} diff --git a/substrate/runtime-sandbox/src/lib.rs b/substrate/runtime-sandbox/src/lib.rs new file mode 100755 index 0000000000000..f275404faec3b --- /dev/null +++ b/substrate/runtime-sandbox/src/lib.rs @@ -0,0 +1,203 @@ +// 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 . + +//! This crate provides means of instantiation and execution of wasm modules. +//! +//! It works even when the user of this library is itself executes +//! inside the wasm VM. In this case same VM is used for execution +//! of both the sandbox owner and the sandboxed module, without compromising security +//! and without performance penalty of full wasm emulation inside wasm. +//! +//! This is achieved by using bindings to wasm VM which are published by the host API. +//! This API is thin and consists of only handful functions. It contains functions for instantiating +//! modules and executing them and for example doesn't contain functions for inspecting the module +//! structure. The user of this library is supposed to read wasm module by it's own means. +//! +//! When this crate is used in `std` environment all these functions are implemented by directly +//! calling wasm VM. +//! +//! Typical use-case for this library might be used for implementing smart-contract runtimes +//! which uses wasm for contract code. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(lang_items))] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] +#![cfg_attr(not(feature = "std"), feature(alloc))] + +extern crate substrate_codec as codec; +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_runtime_std as rstd; +extern crate substrate_primitives as primitives; + +use rstd::prelude::*; + +pub use primitives::sandbox::{TypedValue, ReturnValue, HostError}; + +mod imp { + #[cfg(feature = "std")] + include!("../with_std.rs"); + + #[cfg(not(feature = "std"))] + include!("../without_std.rs"); +} + +/// Error that can occur while using this crate. +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Error { + /// Module is not valid, couldn't be instantiated or it's `start` function trapped + /// when executed. + Module, + + /// Access to a memory or table was made with an address or an index which is out of bounds. + /// + /// Note that if wasm module makes an out-of-bounds access then trap will occur. + OutOfBounds, + + /// Failed to invoke an exported function for some reason. + Execution, +} + +impl From for HostError { + fn from(_e: Error) -> HostError { + HostError + } +} + +/// Function pointer for specifying functions by the +/// supervisor in [`EnvironmentDefinitionBuilder`]. +/// +/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html +pub type HostFuncType = fn(&mut T, &[TypedValue]) -> Result; + +/// Reference to a sandboxed linear memory, that +/// will be used by the guest module. +/// +/// The memory can't be directly accessed by supervisor, but only +/// through designated functions [`get`] and [`set`]. +/// +/// [`get`]: #method.get +/// [`set`]: #method.set +#[derive(Clone)] +pub struct Memory { + inner: imp::Memory, +} + +impl Memory { + /// Construct a new linear memory instance. + /// + /// The memory allocated with initial number of pages specified by `initial`. + /// Minimal possible value for `initial` is 0 and maximum possible is `65536`. + /// (Since maximum addressible memory is 232 = 4GiB = 65536 * 64KiB). + /// + /// It is possible to limit maximum number of pages this memory instance can have by specifying + /// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB. + /// + /// Allocated memory is always zeroed. + pub fn new(initial: u32, maximum: Option) -> Result { + Ok(Memory { + inner: imp::Memory::new(initial, maximum)?, + }) + } + + /// Read a memory area at the address `ptr` with the size of the provided slice `buf`. + /// + /// Returns `Err` if the range is out-of-bounds. + pub fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> { + self.inner.get(ptr, buf) + } + + /// Write a memory area at the address `ptr` with contents of the provided slice `buf`. + /// + /// Returns `Err` if the range is out-of-bounds. + pub fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> { + self.inner.set(ptr, value) + } +} + +/// Struct that can be used for defining an environment for a sandboxed module. +/// +/// The sandboxed module can access only the entities which were defined and passed +/// to the module at the instantiation time. +pub struct EnvironmentDefinitionBuilder { + inner: imp::EnvironmentDefinitionBuilder, +} + +impl EnvironmentDefinitionBuilder { + /// Construct a new `EnvironmentDefinitionBuilder`. + pub fn new() -> EnvironmentDefinitionBuilder { + EnvironmentDefinitionBuilder { + inner: imp::EnvironmentDefinitionBuilder::new(), + } + } + + /// Register a host function in this environment defintion. + pub fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) + where + N1: Into>, + N2: Into>, + { + self.inner.add_host_func(module, field, f); + } + + /// Register a memory in this environment definition. + pub fn add_memory(&mut self, module: N1, field: N2, mem: Memory) + where + N1: Into>, + N2: Into>, + { + self.inner.add_memory(module, field, mem.inner); + } +} + +/// Sandboxed instance of a wasm module. +/// +/// This instance can be used for invoking exported functions. +pub struct Instance { + inner: imp::Instance, + +} + +impl Instance { + /// Instantiate a module with the given [`EnvironmentDefinitionBuilder`]. + /// + /// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html + pub fn new(code: &[u8], env_def_builder: &EnvironmentDefinitionBuilder, state: &mut T) -> Result, Error> { + Ok(Instance { + inner: imp::Instance::new(code, &env_def_builder.inner, state)?, + }) + } + + /// Invoke an exported function with the given name. + /// + /// # Errors + /// + /// Returns `Err(Error::Execution)` if: + /// + /// - An export function name isn't a proper utf8 byte sequence, + /// - This module doesn't have an exported function with the given name, + /// - If types of the arguments passed to the function doesn't match function signature + /// then trap occurs (as if the exported function was called via call_indirect), + /// - Trap occured at the execution time. + pub fn invoke( + &mut self, + name: &[u8], + args: &[TypedValue], + state: &mut T, + ) -> Result { + self.inner.invoke(name, args, state) + } +} diff --git a/substrate/runtime-sandbox/with_std.rs b/substrate/runtime-sandbox/with_std.rs new file mode 100755 index 0000000000000..5be7b494a7791 --- /dev/null +++ b/substrate/runtime-sandbox/with_std.rs @@ -0,0 +1,309 @@ +// 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 . + +extern crate wasmi; + +use rstd::collections::btree_map::BTreeMap; +use rstd::fmt; + + +use self::wasmi::{Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalRef, ImportResolver, + MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, + RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, TrapKind}; +use self::wasmi::memory_units::Pages; +use super::{Error, TypedValue, ReturnValue, HostFuncType, HostError}; + +#[derive(Clone)] +pub struct Memory { + memref: MemoryRef, +} + +impl Memory { + pub fn new(initial: u32, maximum: Option) -> Result { + Ok(Memory { + memref: MemoryInstance::alloc( + Pages(initial as usize), + maximum.map(|m| Pages(m as usize)), + ).map_err(|_| Error::Module)?, + }) + } + + pub fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> { + self.memref.get_into(ptr, buf).map_err(|_| Error::OutOfBounds)?; + Ok(()) + } + + pub fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> { + self.memref.set(ptr, value).map_err(|_| Error::OutOfBounds)?; + Ok(()) + } +} + +struct HostFuncIndex(usize); + +struct DefinedHostFunctions { + funcs: Vec>, +} + +impl Clone for DefinedHostFunctions { + fn clone(&self) -> DefinedHostFunctions { + DefinedHostFunctions { + funcs: self.funcs.clone(), + } + } +} + +impl DefinedHostFunctions { + fn new() -> DefinedHostFunctions { + DefinedHostFunctions { + funcs: Vec::new(), + } + } + + fn define(&mut self, f: HostFuncType) -> HostFuncIndex { + let idx = self.funcs.len(); + self.funcs.push(f); + HostFuncIndex(idx) + } +} + +#[derive(Debug)] +struct DummyHostError; + +impl fmt::Display for DummyHostError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DummyHostError") + } +} + +impl self::wasmi::HostError for DummyHostError { +} + +fn from_runtime_value(v: RuntimeValue) -> TypedValue { + match v { + RuntimeValue::I32(v) => TypedValue::I32(v), + RuntimeValue::I64(v) => TypedValue::I64(v), + RuntimeValue::F32(v) => TypedValue::F32(v.to_bits() as i32), + RuntimeValue::F64(v) => TypedValue::F64(v.to_bits() as i64), + } +} + +fn to_runtime_value(v: TypedValue) -> RuntimeValue { + match v { + TypedValue::I32(v) => RuntimeValue::I32(v as i32), + TypedValue::I64(v) => RuntimeValue::I64(v as i64), + TypedValue::F32(v_bits) => RuntimeValue::F32(f32::from_bits(v_bits as u32)), + TypedValue::F64(v_bits) => RuntimeValue::F64(f64::from_bits(v_bits as u64)), + } +} + +struct GuestExternals<'a, T: 'a> { + state: &'a mut T, + defined_host_functions: &'a DefinedHostFunctions, +} + +impl<'a, T> Externals for GuestExternals<'a, T> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + let args = args.as_ref() + .iter() + .cloned() + .map(from_runtime_value) + .collect::>(); + + let result = (self.defined_host_functions.funcs[index])(self.state, &args); + match result { + Ok(value) => Ok(match value { + ReturnValue::Value(v) => Some(to_runtime_value(v)), + ReturnValue::Unit => None, + }), + Err(HostError) => Err(TrapKind::Host(Box::new(DummyHostError)).into()), + } + } +} + +enum ExternVal { + HostFunc(HostFuncIndex), + Memory(Memory), +} + +pub struct EnvironmentDefinitionBuilder { + map: BTreeMap<(Vec, Vec), ExternVal>, + defined_host_functions: DefinedHostFunctions, +} + +impl EnvironmentDefinitionBuilder { + pub fn new() -> EnvironmentDefinitionBuilder { + EnvironmentDefinitionBuilder { + map: BTreeMap::new(), + defined_host_functions: DefinedHostFunctions::new(), + } + } + + pub fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) + where + N1: Into>, + N2: Into>, + { + let idx = self.defined_host_functions.define(f); + self.map + .insert((module.into(), field.into()), ExternVal::HostFunc(idx)); + } + + pub fn add_memory(&mut self, module: N1, field: N2, mem: Memory) + where + N1: Into>, + N2: Into>, + { + self.map + .insert((module.into(), field.into()), ExternVal::Memory(mem)); + } +} + +impl ImportResolver for EnvironmentDefinitionBuilder { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + signature: &Signature, + ) -> Result { + let key = ( + module_name.as_bytes().to_owned(), + field_name.as_bytes().to_owned(), + ); + let externval = self.map.get(&key).ok_or_else(|| { + wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) + })?; + let host_func_idx = match *externval { + ExternVal::HostFunc(ref idx) => idx, + _ => { + return Err(wasmi::Error::Instantiation(format!( + "Export {}:{} is not a host func", + module_name, field_name + ))) + } + }; + Ok(FuncInstance::alloc_host(signature.clone(), host_func_idx.0)) + } + + fn resolve_global( + &self, + _module_name: &str, + _field_name: &str, + _global_type: &GlobalDescriptor, + ) -> Result { + // TODO: Implement sandboxed globals. + unimplemented!() + } + + fn resolve_memory( + &self, + module_name: &str, + field_name: &str, + _memory_type: &MemoryDescriptor, + ) -> Result { + let key = ( + module_name.as_bytes().to_owned(), + field_name.as_bytes().to_owned(), + ); + let externval = self.map.get(&key).ok_or_else(|| { + wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) + })?; + let memory = match *externval { + ExternVal::Memory(ref m) => m, + _ => { + return Err(wasmi::Error::Instantiation(format!( + "Export {}:{} is not a memory", + module_name, field_name + ))) + } + }; + Ok(memory.memref.clone()) + } + + fn resolve_table( + &self, + _module_name: &str, + _field_name: &str, + _table_type: &TableDescriptor, + ) -> Result { + // TODO: Implement sandboxed tables. + unimplemented!() + } +} + +pub struct Instance { + instance: ModuleRef, + defined_host_functions: DefinedHostFunctions, + _marker: ::std::marker::PhantomData, +} + +impl Instance { + pub fn new(code: &[u8], env_def_builder: &EnvironmentDefinitionBuilder, state: &mut T) -> Result, Error> { + let module = Module::from_buffer(code).map_err(|_| Error::Module)?; + let not_started_instance = ModuleInstance::new(&module, env_def_builder) + .map_err(|_| Error::Module)?; + + + let defined_host_functions = env_def_builder.defined_host_functions.clone(); + let instance = { + let mut externals = GuestExternals { + state, + defined_host_functions: &defined_host_functions, + }; + let instance = not_started_instance.run_start(&mut externals).map_err(|_| Error::Module)?; + instance + }; + + Ok(Instance { + instance, + defined_host_functions, + _marker: ::std::marker::PhantomData::, + }) + } + + pub fn invoke( + &mut self, + name: &[u8], + args: &[TypedValue], + state: &mut T, + ) -> Result { + if args.len() > 0 { + // TODO: Convert args into `RuntimeValue` and use it. + unimplemented!(); + } + + let name = ::std::str::from_utf8(name).map_err(|_| Error::Execution)?; + let mut externals = GuestExternals { + state, + defined_host_functions: &self.defined_host_functions, + }; + let result = self.instance + .invoke_export(&name, &[], &mut externals); + + match result { + Ok(None) => Ok(ReturnValue::Unit), + Ok(_val) => { + // TODO: Convert result value into `TypedValue` and return it. + unimplemented!(); + } + Err(_err) => Err(Error::Execution), + } + } +} diff --git a/substrate/runtime-sandbox/without_std.rs b/substrate/runtime-sandbox/without_std.rs new file mode 100755 index 0000000000000..a31b0bdf117db --- /dev/null +++ b/substrate/runtime-sandbox/without_std.rs @@ -0,0 +1,267 @@ +// 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 . + +use rstd::prelude::*; +use rstd::{slice, marker, mem}; +use codec::Slicable; +use primitives::sandbox as sandbox_primitives; +use super::{Error, TypedValue, ReturnValue, HostFuncType}; + +mod ffi { + use rstd::mem; + use super::HostFuncType; + + /// Index into the default table that points to a `HostFuncType`. + pub type HostFuncIndex = usize; + + /// Coerce `HostFuncIndex` to a callable host function pointer. + /// + /// # Safety + /// + /// This function should be only called with a `HostFuncIndex` that was previously registered + /// in the environment defintion. Typically this should only + /// be called with an argument received in `dispatch_thunk`. + pub unsafe fn coerce_host_index_to_func(idx: HostFuncIndex) -> HostFuncType { + // We need to ensure that sizes of a callable function pointer and host function index is + // indeed equal. + // We can't use `static_assertions` create because it makes compiler panic, fallback to runtime assert. + // const_assert!(mem::size_of::() == mem::size_of::>(),); + assert!(mem::size_of::() == mem::size_of::>()); + mem::transmute::>(idx) + } + + extern "C" { + pub fn ext_sandbox_instantiate( + dispatch_thunk: extern "C" fn( + serialized_args_ptr: *const u8, + serialized_args_len: usize, + state: usize, + f: HostFuncIndex, + ) -> u64, + wasm_ptr: *const u8, + wasm_len: usize, + imports_ptr: *const u8, + imports_len: usize, + state: usize, + ) -> u32; + pub fn ext_sandbox_invoke( + instance_idx: u32, + export_ptr: *const u8, + export_len: usize, + state: usize, + ) -> u32; + pub fn ext_sandbox_memory_new(initial: u32, maximum: u32) -> u32; + pub fn ext_sandbox_memory_get( + memory_idx: u32, + offset: u32, + buf_ptr: *mut u8, + buf_len: usize, + ) -> u32; + pub fn ext_sandbox_memory_set( + memory_idx: u32, + offset: u32, + val_ptr: *const u8, + val_len: usize, + ) -> u32; + + // TODO: ext_instance_teardown + // TODO: ext_memory_teardown + } +} + +#[derive(Clone)] +pub struct Memory { + memory_idx: u32, +} + +impl Memory { + pub fn new(initial: u32, maximum: Option) -> Result { + let result = unsafe { + let maximum = if let Some(maximum) = maximum { + maximum + } else { + sandbox_primitives::MEM_UNLIMITED + }; + ffi::ext_sandbox_memory_new(initial, maximum) + }; + match result { + sandbox_primitives::ERR_MODULE => Err(Error::Module), + memory_idx => Ok(Memory { memory_idx }), + } + } + + pub fn get(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> { + let result = unsafe { ffi::ext_sandbox_memory_get(self.memory_idx, offset, buf.as_mut_ptr(), buf.len()) }; + match result { + sandbox_primitives::ERR_OK => Ok(()), + sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), + _ => unreachable!(), + } + } + + pub fn set(&self, offset: u32, val: &[u8]) -> Result<(), Error> { + let result = unsafe { ffi::ext_sandbox_memory_set(self.memory_idx, offset, val.as_ptr(), val.len()) }; + match result { + sandbox_primitives::ERR_OK => Ok(()), + sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), + _ => unreachable!(), + } + } +} + +pub struct EnvironmentDefinitionBuilder { + env_def: sandbox_primitives::EnvironmentDefinition, + _marker: marker::PhantomData, +} + +impl EnvironmentDefinitionBuilder { + pub fn new() -> EnvironmentDefinitionBuilder { + EnvironmentDefinitionBuilder { + env_def: sandbox_primitives::EnvironmentDefinition { + entries: Vec::new(), + }, + _marker: marker::PhantomData::, + } + } + + fn add_entry( + &mut self, + module: N1, + field: N2, + extern_entity: sandbox_primitives::ExternEntity, + ) where + N1: Into>, + N2: Into>, + { + let entry = sandbox_primitives::Entry { + module_name: module.into(), + field_name: field.into(), + entity: extern_entity, + }; + self.env_def.entries.push(entry); + } + + pub fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) + where + N1: Into>, + N2: Into>, + { + let f = sandbox_primitives::ExternEntity::Function(f as u32); + self.add_entry(module, field, f); + } + + pub fn add_memory(&mut self, module: N1, field: N2, mem: Memory) + where + N1: Into>, + N2: Into>, + { + let mem = sandbox_primitives::ExternEntity::Memory(mem.memory_idx as u32); + self.add_entry(module, field, mem); + } +} + +pub struct Instance { + instance_idx: u32, + _marker: marker::PhantomData, +} + +/// The primary responsibility of this thunk is to deserialize arguments and +/// call the original function, specified by the index. +extern "C" fn dispatch_thunk( + serialized_args_ptr: *const u8, + serialized_args_len: usize, + state: usize, + f: ffi::HostFuncIndex, +) -> u64 { + let serialized_args = unsafe { + if serialized_args_len == 0 { + &[] + } else { + slice::from_raw_parts(serialized_args_ptr, serialized_args_len) + } + }; + let args = Vec::::decode(&mut &serialized_args[..]).expect( + "serialized args should be provided by the runtime; + correctly serialized data should be deserializable; + qed", + ); + + unsafe { + // This should be safe since `coerce_host_index_to_func` is called with an argument + // received in an `dispatch_thunk` implementation, so `f` should point + // on a valid host function. + let f = ffi::coerce_host_index_to_func(f); + + // This should be safe since mutable reference to T is passed upon the invocation. + let state = &mut *(state as *mut T); + + // Pass control flow to the designated function. + let result = f(state, &args).encode(); + + // Leak the result vector and return the pointer to return data. + let result_ptr = result.as_ptr() as u64; + let result_len = result.len() as u64; + mem::forget(result); + + (result_ptr << 32) | result_len + } +} + +impl Instance { + pub fn new(code: &[u8], env_def_builder: &EnvironmentDefinitionBuilder, state: &mut T) -> Result, Error> { + let serialized_env_def: Vec = env_def_builder.env_def.encode(); + let result = unsafe { + // It's very important to instantiate thunk with the right type. + let dispatch_thunk = dispatch_thunk::; + + ffi::ext_sandbox_instantiate( + dispatch_thunk, + code.as_ptr(), + code.len(), + serialized_env_def.as_ptr(), + serialized_env_def.len(), + state as *const T as usize, + ) + }; + let instance_idx = match result { + sandbox_primitives::ERR_MODULE => return Err(Error::Module), + instance_idx => instance_idx, + }; + Ok(Instance { + instance_idx, + _marker: marker::PhantomData::, + }) + } + + pub fn invoke( + &mut self, + name: &[u8], + _args: &[TypedValue], + state: &mut T, + ) -> Result { + // TODO: Serialize arguments and pass them thru. + let result = + unsafe { ffi::ext_sandbox_invoke(self.instance_idx, name.as_ptr(), name.len(), state as *const T as usize) }; + match result { + sandbox_primitives::ERR_OK => { + // TODO: Fetch the result of the execution. + Ok(ReturnValue::Unit) + } + sandbox_primitives::ERR_EXECUTION => Err(Error::Execution), + _ => unreachable!(), + } + } +} diff --git a/substrate/runtime/council/Cargo.toml b/substrate/runtime/council/Cargo.toml index e3a5fe93e13de..f44136ec473ee 100644 --- a/substrate/runtime/council/Cargo.toml +++ b/substrate/runtime/council/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Parity Technologies "] [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 } diff --git a/substrate/runtime/primitives/Cargo.toml b/substrate/runtime/primitives/Cargo.toml index cc3f035715430..42c7134ec688c 100644 --- a/substrate/runtime/primitives/Cargo.toml +++ b/substrate/runtime/primitives/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Parity Technologies "] [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 } diff --git a/substrate/runtime/staking/Cargo.toml b/substrate/runtime/staking/Cargo.toml index 75350d1319bed..9c2d3d6a20c86 100644 --- a/substrate/runtime/staking/Cargo.toml +++ b/substrate/runtime/staking/Cargo.toml @@ -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", diff --git a/substrate/runtime/staking/src/contract.rs b/substrate/runtime/staking/src/contract.rs new file mode 100644 index 0000000000000..1235a1e52f3d2 --- /dev/null +++ b/substrate/runtime/staking/src/contract.rs @@ -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 . + +//! 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 { + 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(e: &mut ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + 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(e: &mut ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + 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(e: &mut ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + 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::::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(e: &mut ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + 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::::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::); + imports.add_host_func("env", "ext_get_storage", ext_get_storage::); + imports.add_host_func("env", "ext_transfer", ext_transfer::); + imports.add_host_func("env", "ext_create", ext_create::); + // 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() +} diff --git a/substrate/runtime/staking/src/lib.rs b/substrate/runtime/staking/src/lib.rs index 227b220e741d2..67f8eeef0b16b 100644 --- a/substrate/runtime/staking/src/lib.rs +++ b/substrate/runtime/staking/src/lib.rs @@ -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 { @@ -169,8 +175,8 @@ impl Module { /// 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) { - >::merge(&DirectAccountDb, commit); + if let Some(commit) = Self::effect_create(aux.ref_into(), code, value, &DirectAccountDb) { + >::merge(&mut DirectAccountDb, commit); } } @@ -180,8 +186,8 @@ impl Module { /// 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) { - >::merge(&DirectAccountDb, commit); + if let Some(commit) = Self::effect_transfer(aux.ref_into(), &dest, value, &DirectAccountDb) { + >::merge(&mut DirectAccountDb, commit); } } @@ -382,11 +388,11 @@ trait AccountDb { fn get_code(&self, account: &T::AccountId) -> Vec; fn get_balance(&self, account: &T::AccountId) -> T::Balance; - fn set_storage(&self, account: &T::AccountId, location: Vec, value: Option>); - fn set_code(&self, account: &T::AccountId, code: Vec); - fn set_balance(&self, account: &T::AccountId, balance: T::Balance); + fn set_storage(&mut self, account: &T::AccountId, location: Vec, value: Option>); + fn set_code(&mut self, account: &T::AccountId, code: Vec); + fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance); - fn merge(&self, state: State); + fn merge(&mut self, state: State); } struct DirectAccountDb; @@ -400,20 +406,20 @@ impl AccountDb for DirectAccountDb { fn get_balance(&self, account: &T::AccountId) -> T::Balance { >::get(account) } - fn set_storage(&self, account: &T::AccountId, location: Vec, value: Option>) { + fn set_storage(&mut self, account: &T::AccountId, location: Vec, value: Option>) { if let Some(value) = value { >::insert(&(account.clone(), location), &value); } else { >::remove(&(account.clone(), location)); } } - fn set_code(&self, account: &T::AccountId, code: Vec) { + fn set_code(&mut self, account: &T::AccountId, code: Vec) { >::insert(account, &code); } - fn set_balance(&self, account: &T::AccountId, balance: T::Balance) { + fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) { >::insert(account, balance); } - fn merge(&self, s: State) { + fn merge(&mut self, s: State) { for (address, changed) in s.into_iter() { if let Some(balance) = changed.balance { >::insert(&address, balance); @@ -450,40 +456,50 @@ impl<'a, T: Trait> OverlayAccountDb<'a, T> { } impl<'a, T: Trait> AccountDb for OverlayAccountDb<'a, T> { fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { - 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 { - 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, value: Option>) { - self.local.borrow_mut() + fn set_storage(&mut self, account: &T::AccountId, location: Vec, value: Option>) { + 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) { - self.local.borrow_mut() + fn set_code(&mut self, account: &T::AccountId, code: Vec) { + 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) { + fn merge(&mut self, s: State) { let mut local = self.local.borrow_mut(); for (address, changed) in s.into_iter() { @@ -511,7 +527,7 @@ impl Module { transactor: &T::AccountId, code: &[u8], value: T::Balance, - account_db: DB + account_db: &DB, ) -> Option> { let from_balance = account_db.get_balance(transactor); // TODO: a fee. @@ -538,32 +554,34 @@ impl Module { transactor: &T::AccountId, dest: &T::AccountId, value: T::Balance, - account_db: DB + account_db: &DB, ) -> Option> { let from_balance = account_db.get_balance(transactor); assert!(from_balance >= value); let to_balance = account_db.get_balance(dest); assert!(>::get(transactor) <= >::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), || { + >::insert(0, 111); + >::insert(1, 0); + >::insert(2, 30); + + >::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), || { + >::insert(0, 111); + >::insert(1, 0); + + >::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 = + ::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), || { + >::insert(0, 111); + >::insert(1, 0); + >::insert(1, code_adder); + + Staking::transfer(&0, 1, 1); + Staking::transfer(&0, 1, 1); + + let storage_addr = [0x01u8; 32]; + let value = + AccountDb::::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, + ] + ); + }); + } } diff --git a/substrate/test-runtime/wasm/Cargo.lock b/substrate/test-runtime/wasm/Cargo.lock index 6f379e9dad73c..20af98c6cd5d0 100644 --- a/substrate/test-runtime/wasm/Cargo.lock +++ b/substrate/test-runtime/wasm/Cargo.lock @@ -289,6 +289,11 @@ dependencies = [ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memorydb" version = "0.1.1" @@ -329,6 +334,16 @@ dependencies = [ "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-wasm" +version = "0.27.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.4.8" @@ -338,6 +353,15 @@ dependencies = [ "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.2.10" @@ -591,6 +615,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", + "wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -765,6 +790,16 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasmi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -831,12 +866,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" "checksum memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "013b7e4c5e10c764936ebc6bd3662d8e3c92292d267bf6a42ef3f5cad9c793ee" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bd4dc02a80a0315b109e48992c46942c79bcdb8fac416dd575d330ed9ced6cbd" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +"checksum parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd9d732f2de194336fb02fe11f9eed13d9e76f13f4315b4d88a14ca411750cd" "checksum parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9f35048d735bb93dd115a0030498785971aab3234d311fbe273d020084d26bd8" "checksum patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e2f638d79aba5c4a71a4f373df6e3cd702250a53b7f0ed4da1e2a7be9737ae" "checksum plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83ae80873992f511142c07d0ec6c44de5636628fdb7e204abd655932ea79d995" @@ -878,6 +916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum wasmi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26b20dbeb7caee04597a5d2c93e2b3e64872c6ea2af732d7ad49dbec44067c35" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/substrate/test-runtime/wasm/init.sh b/substrate/test-runtime/wasm/init.sh index 02a0059a87584..e8b4387a2f998 100755 --- a/substrate/test-runtime/wasm/init.sh +++ b/substrate/test-runtime/wasm/init.sh @@ -4,3 +4,4 @@ rustup update nightly rustup target add wasm32-unknown-unknown --toolchain nightly rustup update stable cargo install --git https://github.com/alexcrichton/wasm-gc +cargo install --git https://github.com/pepyakin/wasm-export-table.git diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index a73b67188887f..b5f369fe14b19 100644 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm index c8186f9c08c15..c62b44fcc0e06 100755 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm differ