diff --git a/Cargo.lock b/Cargo.lock index dadfd97ed793..64ccb3e4767e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,14 +125,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "getrandom 0.2.10", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -1311,7 +1312,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object 0.32.0", + "object 0.32.2", "rustc-demangle", ] @@ -4487,7 +4488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -4864,7 +4865,7 @@ checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ "curve25519-dalek 4.1.1", "ed25519", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "rand_core 0.6.4", "sha2 0.10.7", @@ -5178,6 +5179,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "1.9.0" @@ -6107,7 +6114,7 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" dependencies = [ - "fallible-iterator", + "fallible-iterator 0.2.0", "indexmap 1.9.3", "stable_deref_trait", ] @@ -6117,6 +6124,10 @@ name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +dependencies = [ + "fallible-iterator 0.3.0", + "stable_deref_trait", +] [[package]] name = "glob" @@ -6262,16 +6273,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", ] [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "allocator-api2", "serde", ] @@ -6282,7 +6293,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -6642,7 +6653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -8899,9 +8910,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -9589,9 +9600,9 @@ name = "pallet-contracts-fixtures" version = "1.0.0" dependencies = [ "anyhow", - "cfg-if", "frame-system", "parity-wasm", + "polkavm-linker", "sp-runtime", "tempfile", "toml 0.8.2", @@ -9599,10 +9610,6 @@ dependencies = [ "wat", ] -[[package]] -name = "pallet-contracts-fixtures-common" -version = "1.0.0" - [[package]] name = "pallet-contracts-mock-network" version = "1.0.0" @@ -9657,6 +9664,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", + "polkavm-derive", "scale-info", ] @@ -13753,6 +13761,54 @@ dependencies = [ "westend-runtime", ] +[[package]] +name = "polkavm-common" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01363cf0a778e8d93eff31e8a03bc59992cba35faa419ea4f3e80146b69195ba" + +[[package]] +name = "polkavm-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e869d66a254db6c7069992f240626416aba8e87d65c00e4be443135babfe82" + +[[package]] +name = "polkavm-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26501292b2cb980cbeaac3304f0fc4480ff1bac2473045453d7333d775658b6a" +dependencies = [ + "polkavm-derive-impl", + "syn 2.0.47", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903e16ad3ed768f35e6f40acff2e8aaf6afb9f2889b0a8982dd43dcbee29db2d" +dependencies = [ + "polkavm-common 0.2.0", + "proc-macro2", + "quote", + "syn 2.0.47", +] + +[[package]] +name = "polkavm-linker" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8719d37effca6df1cecf5c816d84ab09b7d18e960511f61c254a7581fa50c3" +dependencies = [ + "gimli 0.28.0", + "hashbrown 0.14.3", + "log", + "object 0.32.2", + "polkavm-common 0.3.0", + "rustc-demangle", +] + [[package]] name = "polling" version = "2.8.0" @@ -15661,7 +15717,7 @@ dependencies = [ name = "sc-consensus-grandpa" version = "0.10.0-dev" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "array-bytes 6.1.0", "assert_matches", "async-trait", @@ -16045,7 +16101,7 @@ dependencies = [ name = "sc-network-gossip" version = "0.10.0-dev" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "async-trait", "futures", "futures-timer", @@ -16730,7 +16786,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "cfg-if", "hashbrown 0.13.2", ] @@ -17384,7 +17440,7 @@ dependencies = [ "fnv", "futures-lite", "futures-util", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "hmac 0.12.1", "itertools 0.11.0", @@ -17433,7 +17489,7 @@ dependencies = [ "futures-channel", "futures-lite", "futures-util", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "itertools 0.11.0", "log", @@ -18807,7 +18863,7 @@ dependencies = [ name = "sp-trie" version = "22.0.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "array-bytes 6.1.0", "criterion 0.4.0", "hash-db", @@ -22003,6 +22059,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.47", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 55958b0d83ee..a1983a5efd5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -310,7 +310,6 @@ members = [ "substrate/frame/collective", "substrate/frame/contracts", "substrate/frame/contracts/fixtures", - "substrate/frame/contracts/fixtures/contracts/common", "substrate/frame/contracts/mock-network", "substrate/frame/contracts/proc-macro", "substrate/frame/contracts/uapi", diff --git a/substrate/frame/contracts/fixtures/Cargo.toml b/substrate/frame/contracts/fixtures/Cargo.toml index 97606479f259..565adc6f6e8d 100644 --- a/substrate/frame/contracts/fixtures/Cargo.toml +++ b/substrate/frame/contracts/fixtures/Cargo.toml @@ -21,5 +21,5 @@ parity-wasm = "0.45.0" tempfile = "3.8.1" toml = "0.8.2" twox-hash = "1.6.3" +polkavm-linker = "0.3.0" anyhow = "1.0.0" -cfg-if = { version = "1.0", default-features = false } diff --git a/substrate/frame/contracts/fixtures/build.rs b/substrate/frame/contracts/fixtures/build.rs index 49deb94a7faa..de95d199b448 100644 --- a/substrate/frame/contracts/fixtures/build.rs +++ b/substrate/frame/contracts/fixtures/build.rs @@ -16,7 +16,7 @@ // limitations under the License. //! Compile contracts to wasm and RISC-V binaries. -use anyhow::Result; +use anyhow::{bail, format_err, Context, Result}; use parity_wasm::elements::{deserialize_file, serialize_to_file, Internal}; use std::{ env, fs, @@ -65,16 +65,41 @@ impl Entry { .expect("name is valid unicode; qed") } + /// Return whether the contract has already been compiled. + fn is_cached(&self, out_dir: &Path) -> bool { + out_dir.join(self.name()).join(&self.hash).exists() + } + + /// Update the cache file for the contract. + fn update_cache(&self, out_dir: &Path) -> Result<()> { + let cache_dir = out_dir.join(self.name()); + + // clear the cache dir if it exists + if cache_dir.exists() { + fs::remove_dir_all(&cache_dir)?; + } + + // re-populate the cache dir with the new hash + fs::create_dir_all(&cache_dir)?; + fs::write(out_dir.join(&self.hash), "")?; + Ok(()) + } + /// Return the name of the output wasm file. fn out_wasm_filename(&self) -> String { format!("{}.wasm", self.name()) } + + /// Return the name of the RISC-V polkavm file. + fn out_riscv_filename(&self) -> String { + format!("{}.polkavm", self.name()) + } } /// Collect all contract entries from the given source directory. /// Contracts that have already been compiled are filtered out. fn collect_entries(contracts_dir: &Path, out_dir: &Path) -> Vec { - fs::read_dir(&contracts_dir) + fs::read_dir(contracts_dir) .expect("src dir exists; qed") .filter_map(|file| { let path = file.expect("file exists; qed").path(); @@ -83,7 +108,7 @@ fn collect_entries(contracts_dir: &Path, out_dir: &Path) -> Vec { } let entry = Entry::new(path); - if out_dir.join(&entry.hash).exists() { + if entry.is_cached(out_dir) { None } else { Some(entry) @@ -98,41 +123,29 @@ fn create_cargo_toml<'a>( entries: impl Iterator, output_dir: &Path, ) -> Result<()> { - let uapi_path = fixtures_dir.join("../uapi").canonicalize()?; - let common_path = fixtures_dir.join("./contracts/common").canonicalize()?; - let mut cargo_toml: toml::Value = toml::from_str(&format!( - " -[package] -name = 'contracts' -version = '0.1.0' -edition = '2021' - -# Binary targets are injected below. -[[bin]] - -[dependencies] -uapi = {{ package = 'pallet-contracts-uapi', default-features = false, path = {uapi_path:?}}} -common = {{ package = 'pallet-contracts-fixtures-common', path = {common_path:?}}} - -[profile.release] -opt-level = 3 -lto = true -codegen-units = 1 -" - ))?; - - let binaries = entries - .map(|entry| { - let name = entry.name(); - let path = entry.path(); - toml::Value::Table(toml::toml! { - name = name - path = path + let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?; + let mut set_dep = |name, path| -> Result<()> { + cargo_toml["dependencies"][name]["path"] = toml::Value::String( + fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(), + ); + Ok(()) + }; + set_dep("uapi", "../uapi")?; + set_dep("common", "./contracts/common")?; + + cargo_toml["bin"] = toml::Value::Array( + entries + .map(|entry| { + let name = entry.name(); + let path = entry.path(); + toml::Value::Table(toml::toml! { + name = name + path = path + }) }) - }) - .collect::>(); + .collect::>(), + ); - cargo_toml["bin"] = toml::Value::Array(binaries); let cargo_toml = toml::to_string_pretty(&cargo_toml)?; fs::write(output_dir.join("Cargo.toml"), cargo_toml).map_err(Into::into) } @@ -145,7 +158,7 @@ fn invoke_cargo_fmt<'a>( ) -> Result<()> { // If rustfmt is not installed, skip the check. if !Command::new("rustup") - .args(&["run", "nightly", "rustfmt", "--version"]) + .args(["run", "nightly", "rustfmt", "--version"]) .output() .map_or(false, |o| o.status.success()) { @@ -153,7 +166,7 @@ fn invoke_cargo_fmt<'a>( } let fmt_res = Command::new("rustup") - .args(&["run", "nightly", "rustfmt", "--check", "--config-path"]) + .args(["run", "nightly", "rustfmt", "--check", "--config-path"]) .arg(config_path) .args(files) .output() @@ -176,8 +189,8 @@ fn invoke_cargo_fmt<'a>( anyhow::bail!("Fixtures files are not formatted") } -/// Invoke `cargo build` to compile the contracts. -fn invoke_build(current_dir: &Path) -> Result<()> { +/// Build contracts for wasm. +fn invoke_wasm_build(current_dir: &Path) -> Result<()> { let encoded_rustflags = [ "-Clink-arg=-zstack-size=65536", "-Clink-arg=--import-memory", @@ -190,7 +203,7 @@ fn invoke_build(current_dir: &Path) -> Result<()> { let build_res = Command::new(env::var("CARGO")?) .current_dir(current_dir) .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) - .args(&["build", "--release", "--target=wasm32-unknown-unknown"]) + .args(["build", "--release", "--target=wasm32-unknown-unknown"]) .output() .expect("failed to execute process"); @@ -200,12 +213,13 @@ fn invoke_build(current_dir: &Path) -> Result<()> { let stderr = String::from_utf8_lossy(&build_res.stderr); eprintln!("{}", stderr); - anyhow::bail!("Failed to build contracts"); + bail!("Failed to build wasm contracts"); } /// Post-process the compiled wasm contracts. fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> { - let mut module = deserialize_file(input_path)?; + let mut module = + deserialize_file(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; if let Some(section) = module.export_section_mut() { section.entries_mut().retain(|entry| { matches!(entry.internal(), Internal::Function(_)) && @@ -216,6 +230,50 @@ fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> { serialize_to_file(output_path, module).map_err(Into::into) } +/// Build contracts for RISC-V. +fn invoke_riscv_build(current_dir: &Path) -> Result<()> { + let encoded_rustflags = + ["-Crelocation-model=pie", "-Clink-arg=--emit-relocs", "-Clink-arg=-Tmemory.ld"] + .join("\x1f"); + + fs::write(current_dir.join("memory.ld"), include_bytes!("./build/riscv_memory_layout.ld"))?; + + let build_res = Command::new(env::var("CARGO")?) + .current_dir(current_dir) + .env_clear() + .env("PATH", env::var("PATH").unwrap()) + .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) + .env("RUSTUP_TOOLCHAIN", "rve-nightly") + .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap()) + .args(["build", "--release", "--target=riscv32em-unknown-none-elf"]) + .output() + .expect("failed to execute process"); + + if build_res.status.success() { + return Ok(()) + } + + let stderr = String::from_utf8_lossy(&build_res.stderr); + + if stderr.contains("'rve-nightly' is not installed") { + eprintln!("RISC-V toolchain is not installed.\nDownload and install toolchain from https://github.com/paritytech/rustc-rv32e-toolchain."); + eprintln!("{}", stderr); + } else { + eprintln!("{}", stderr); + } + + bail!("Failed to build contracts"); +} +/// Post-process the compiled wasm contracts. +fn post_process_riscv(input_path: &Path, output_path: &Path) -> Result<()> { + let mut config = polkavm_linker::Config::default(); + config.set_strip(true); + let orig = fs::read(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; + let linked = polkavm_linker::program_from_elf(config, orig.as_ref()) + .map_err(|err| format_err!("Failed to link polkavm program: {}", err))?; + fs::write(output_path, linked.as_bytes()).map_err(Into::into) +} + /// Write the compiled contracts to the given output directory. fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result<()> { for entry in entries { @@ -224,7 +282,13 @@ fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result &build_dir.join("target/wasm32-unknown-unknown/release").join(&wasm_output), &out_dir.join(&wasm_output), )?; - fs::write(out_dir.join(&entry.hash), "")?; + + post_process_riscv( + &build_dir.join("target/riscv32em-unknown-none-elf/release").join(entry.name()), + &out_dir.join(entry.out_riscv_filename()), + )?; + + entry.update_cache(out_dir)?; } Ok(()) @@ -270,8 +334,9 @@ fn main() -> Result<()> { &contracts_dir, )?; - invoke_build(tmp_dir_path)?; - write_output(tmp_dir_path, &out_dir, entries)?; + invoke_wasm_build(tmp_dir_path)?; + invoke_riscv_build(tmp_dir_path)?; + write_output(tmp_dir_path, &out_dir, entries)?; Ok(()) } diff --git a/substrate/frame/contracts/fixtures/build/Cargo.toml b/substrate/frame/contracts/fixtures/build/Cargo.toml new file mode 100644 index 000000000000..7fd42b887e72 --- /dev/null +++ b/substrate/frame/contracts/fixtures/build/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "contracts" +version = "0.1.0" +edition = "2021" + +# Binary targets are injected dynamically by the build script. +[[bin]] + +# local path are injected dynamically by the build script. +[dependencies] +uapi = { package = 'pallet-contracts-uapi', path = "", default-features = false } +common = { package = 'pallet-contracts-fixtures-common', path = "" } +polkavm-derive = '0.2.0' + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 diff --git a/substrate/frame/contracts/fixtures/build/riscv_memory_layout.ld b/substrate/frame/contracts/fixtures/build/riscv_memory_layout.ld new file mode 100644 index 000000000000..89084263adaa --- /dev/null +++ b/substrate/frame/contracts/fixtures/build/riscv_memory_layout.ld @@ -0,0 +1,4 @@ +SECTIONS { + .text : { KEEP(*(.text.polkavm_export)) } +} + diff --git a/substrate/frame/contracts/fixtures/contracts/call.rs b/substrate/frame/contracts/fixtures/contracts/call.rs index 396b71d5e969..0e5f4fbd2ddd 100644 --- a/substrate/frame/contracts/fixtures/contracts/call.rs +++ b/substrate/frame/contracts/fixtures/contracts/call.rs @@ -23,9 +23,11 @@ extern crate common; use uapi::{CallFlags, HostFn, HostFnImpl as api}; #[no_mangle] +#[polkavm_derive::polkavm_export] pub extern "C" fn deploy() {} #[no_mangle] +#[polkavm_derive::polkavm_export] pub extern "C" fn call() { let mut buffer = [0u8; 40]; let callee_input = 0..4; diff --git a/substrate/frame/contracts/fixtures/contracts/common/Cargo.toml b/substrate/frame/contracts/fixtures/contracts/common/Cargo.toml index 377e8bc9dd58..127bb575088d 100644 --- a/substrate/frame/contracts/fixtures/contracts/common/Cargo.toml +++ b/substrate/frame/contracts/fixtures/contracts/common/Cargo.toml @@ -6,6 +6,3 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Common utilities for pallet-contracts-fixtures." - -[lints] -workspace = true diff --git a/substrate/frame/contracts/fixtures/contracts/dummy.rs b/substrate/frame/contracts/fixtures/contracts/dummy.rs index 98b9d494bbc6..bde0d15e2f66 100644 --- a/substrate/frame/contracts/fixtures/contracts/dummy.rs +++ b/substrate/frame/contracts/fixtures/contracts/dummy.rs @@ -20,7 +20,9 @@ extern crate common; #[no_mangle] +#[polkavm_derive::polkavm_export] pub extern "C" fn deploy() {} #[no_mangle] +#[polkavm_derive::polkavm_export] pub extern "C" fn call() {} diff --git a/substrate/frame/contracts/uapi/Cargo.toml b/substrate/frame/contracts/uapi/Cargo.toml index f29014272829..d3f6fc062693 100644 --- a/substrate/frame/contracts/uapi/Cargo.toml +++ b/substrate/frame/contracts/uapi/Cargo.toml @@ -8,8 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Exposes all the host functions that a contract can import." -[lints] -workspace = true +# [lints] +# TODO: uncomment when rve toolchain is updated to rust 1.74 or greater. +# workspace = true [dependencies] paste = { version = "1.0", default-features = false } @@ -20,6 +21,9 @@ scale = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ], optional = true } +[target.'cfg(target_arch = "riscv32")'.dependencies] +polkavm-derive = '0.2.0' + [features] default = ["scale"] scale = ["dep:scale", "scale-info"] diff --git a/substrate/frame/contracts/uapi/src/host.rs b/substrate/frame/contracts/uapi/src/host.rs index f8b55d3822e9..8f76b21ec9f0 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::{CallFlags, Result, ReturnFlags, SENTINEL}; +use crate::{CallFlags, Result, ReturnFlags}; use paste::paste; #[cfg(target_arch = "wasm32")] @@ -36,23 +36,27 @@ macro_rules! hash_fn { }; } +// TODO remove cfg once used by all targets +#[cfg(target_arch = "wasm32")] fn extract_from_slice(output: &mut &mut [u8], new_len: usize) { debug_assert!(new_len <= output.len()); let tmp = core::mem::take(output); *output = &mut tmp[..new_len]; } +#[cfg(target_arch = "wasm32")] fn ptr_len_or_sentinel(data: &mut Option<&mut [u8]>) -> (*mut u8, u32) { match data { Some(ref mut data) => (data.as_mut_ptr(), data.len() as _), - None => (SENTINEL as _, 0), + None => (crate::SENTINEL as _, 0), } } +#[cfg(target_arch = "wasm32")] fn ptr_or_sentinel(data: &Option<&[u8]>) -> *const u8 { match data { Some(ref data) => data.as_ptr(), - None => SENTINEL as _, + None => crate::SENTINEL as _, } } @@ -201,12 +205,13 @@ pub trait HostFn { /// /// - `func_id`: The function id of the chain extension. /// - `input`: The input data buffer. - /// - `output`: A reference to the output data buffer to write the output data. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. /// /// # Return /// /// The chain extension returned value, if executed successfully. - fn call_chain_extension(func_id: u32, input: &[u8], output: &mut &mut [u8]) -> u32; + fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut [u8]>) -> u32; /// Call some dispatchable of the runtime. /// @@ -324,6 +329,25 @@ pub trait HostFn { /// Returns the size of the pre-existing value at the specified key if any. fn contains_storage_v1(key: &[u8]) -> Option; + /// Emit a custom debug message. + /// + /// No newlines are added to the supplied message. + /// Specifying invalid UTF-8 just drops the message with no trap. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. The message is interpreted as UTF-8 and + /// appended to the debug buffer which is then supplied to the calling RPC client. + /// + /// # Note + /// + /// Even though no action is taken when debug message recording is disabled there is still + /// a non trivial overhead (and weight cost) associated with calling this function. Contract + /// languages should remove calls to this function (either at runtime or compile time) when + /// not being executed as an RPC. For example, they could allow users to disable logging + /// through compile time flags (cargo features) for on-chain deployment. Additionally, the + /// return value of this function can be cached in order to prevent further calls at runtime. + fn debug_message(str: &[u8]) -> Result; + /// Execute code in the context (storage, caller, value) of the current contract. /// /// Reentrancy protection is always disabled since the callee is allowed @@ -789,5 +813,5 @@ pub trait HostFn { #[deprecated( note = "Unstable function. Behaviour can change without further notice. Use only for testing." )] - fn xcm_send(dest: &[u8], msg: &[u8], output: &mut &mut [u8]) -> Result; + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; } diff --git a/substrate/frame/contracts/uapi/src/host/riscv32.rs b/substrate/frame/contracts/uapi/src/host/riscv32.rs index f58b8831f06d..4132768abc28 100644 --- a/substrate/frame/contracts/uapi/src/host/riscv32.rs +++ b/substrate/frame/contracts/uapi/src/host/riscv32.rs @@ -1,3 +1,4 @@ +#![allow(unused_variables, unused_mut)] // Copyright (C) Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,3 +13,295 @@ // See the License for the specific language governing permissions and // limitations under the License. // TODO: bring up to date with wasm32.rs + +use super::{CallFlags, HostFn, HostFnImpl, Result}; +use crate::ReturnFlags; + +/// A macro to implement all Host functions with a signature of `fn(&mut &mut [u8])`. +/// +/// Example: +/// ```nocompile +// impl_wrapper_for! { +// () => [gas_left], +// (v1) => [gas_left], +// } +// ``` +// +// Expands to: +// ```nocompile +// fn gas_left(output: &mut &mut [u8]) { +// unsafe { sys::gas_left(...); } +// } +// fn gas_left_v1(output: &mut &mut [u8]) { +// unsafe { sys::v1::gas_left(...); } +// } +// ``` +macro_rules! impl_wrapper_for { + (@impl_fn $( $mod:ident )::*, $suffix_sep: literal, $suffix:tt, $name:ident) => { + paste::paste! { + fn [<$name $suffix_sep $suffix>](output: &mut &mut [u8]) { + todo!() + } + } + }; + + () => {}; + + (($mod:ident) => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys::$mod, "_", $mod, $name);)* + impl_wrapper_for!($($tail)*); + }; + + (() => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys, "", "", $name);)* + impl_wrapper_for!($($tail)*); + }; +} + +/// A macro to implement all the hash functions Apis. +macro_rules! impl_hash_fn { + ( $name:ident, $bytes_result:literal ) => { + paste::item! { + fn [](input: &[u8], output: &mut [u8; $bytes_result]) { + todo!() + } + } + }; +} + +/// A macro to implement the get_storage functions. +macro_rules! impl_get_storage { + ($fn_name:ident, $sys_get_storage:path) => { + fn $fn_name(key: &[u8], output: &mut &mut [u8]) -> Result { + todo!() + } + }; +} + +impl HostFn for HostFnImpl { + fn instantiate_v1( + code_hash: &[u8], + gas: u64, + value: &[u8], + input: &[u8], + mut address: Option<&mut [u8]>, + mut output: Option<&mut [u8]>, + salt: &[u8], + ) -> Result { + todo!() + } + + fn instantiate_v2( + code_hash: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input: &[u8], + mut address: Option<&mut [u8]>, + mut output: Option<&mut [u8]>, + salt: &[u8], + ) -> Result { + todo!() + } + + fn call( + callee: &[u8], + gas: u64, + value: &[u8], + input_data: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn call_v1( + flags: CallFlags, + callee: &[u8], + gas: u64, + value: &[u8], + input_data: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn call_v2( + flags: CallFlags, + callee: &[u8], + ref_time_limit: u64, + proof_time_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input_data: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn caller_is_root() -> u32 { + todo!() + } + + fn delegate_call( + flags: CallFlags, + code_hash: &[u8], + input: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn transfer(account_id: &[u8], value: &[u8]) -> Result { + todo!() + } + + fn deposit_event(topics: &[u8], data: &[u8]) { + todo!() + } + + fn set_storage(key: &[u8], value: &[u8]) { + todo!() + } + + fn set_storage_v1(key: &[u8], encoded_value: &[u8]) -> Option { + todo!() + } + + fn set_storage_v2(key: &[u8], encoded_value: &[u8]) -> Option { + todo!() + } + + fn clear_storage(key: &[u8]) { + todo!() + } + + fn clear_storage_v1(key: &[u8]) -> Option { + todo!() + } + + impl_get_storage!(get_storage, sys::get_storage); + impl_get_storage!(get_storage_v1, sys::v1::get_storage); + + fn take_storage(key: &[u8], output: &mut &mut [u8]) -> Result { + todo!() + } + + fn contains_storage(key: &[u8]) -> Option { + todo!() + } + + fn contains_storage_v1(key: &[u8]) -> Option { + todo!() + } + + fn terminate(beneficiary: &[u8]) -> ! { + todo!() + } + + fn terminate_v1(beneficiary: &[u8]) -> ! { + todo!() + } + + fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut [u8]>) -> u32 { + todo!() + } + + fn input(output: &mut &mut [u8]) { + todo!() + } + + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { + todo!() + } + + fn call_runtime(call: &[u8]) -> Result { + todo!() + } + + fn debug_message(str: &[u8]) -> Result { + todo!() + } + + impl_wrapper_for! { + () => [caller, block_number, address, balance, gas_left, value_transferred, now, minimum_balance], + (v1) => [gas_left], + } + + fn weight_to_fee(gas: u64, output: &mut &mut [u8]) { + todo!() + } + + fn weight_to_fee_v1(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]) { + todo!() + } + + impl_hash_fn!(sha2_256, 32); + impl_hash_fn!(keccak_256, 32); + impl_hash_fn!(blake2_256, 32); + impl_hash_fn!(blake2_128, 16); + + fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result { + todo!() + } + + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { + todo!() + } + + fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result { + todo!() + } + + fn is_contract(account_id: &[u8]) -> bool { + todo!() + } + + fn caller_is_origin() -> bool { + todo!() + } + + fn set_code_hash(code_hash: &[u8]) -> Result { + todo!() + } + + fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result { + todo!() + } + + fn own_code_hash(output: &mut [u8]) { + todo!() + } + + fn account_reentrance_count(account: &[u8]) -> u32 { + todo!() + } + + fn add_delegate_dependency(code_hash: &[u8]) { + todo!() + } + + fn remove_delegate_dependency(code_hash: &[u8]) { + todo!() + } + + fn instantiation_nonce() -> u64 { + todo!() + } + + fn reentrance_count() -> u32 { + todo!() + } + + fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result { + todo!() + } + + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { + todo!() + } +} diff --git a/substrate/frame/contracts/uapi/src/host/wasm32.rs b/substrate/frame/contracts/uapi/src/host/wasm32.rs index d30058daf3df..29fa1706a842 100644 --- a/substrate/frame/contracts/uapi/src/host/wasm32.rs +++ b/substrate/frame/contracts/uapi/src/host/wasm32.rs @@ -69,6 +69,8 @@ mod sys { pub fn contains_storage(key_ptr: *const u8, key_len: u32) -> ReturnCode; + pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode; + pub fn delegate_call( flags: u32, code_hash_ptr: *const u8, @@ -97,7 +99,6 @@ mod sys { pub fn get_storage( key_ptr: *const u8, - key_len: u32, out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; @@ -130,12 +131,7 @@ mod sys { pub fn set_code_hash(code_hash_ptr: *const u8) -> ReturnCode; - pub fn set_storage( - key_ptr: *const u8, - key_len: u32, - value_ptr: *const u8, - value_len: u32, - ) -> ReturnCode; + pub fn set_storage(key_ptr: *const u8, value_ptr: *const u8, value_len: u32); pub fn sr25519_verify( signature_ptr: *const u8, @@ -219,7 +215,6 @@ mod sys { pub fn set_storage( key_ptr: *const u8, - key_len: u32, value_ptr: *const u8, value_len: u32, ) -> ReturnCode; @@ -280,29 +275,47 @@ mod sys { } /// A macro to implement all Host functions with a signature of `fn(&mut &mut [u8])`. +/// +/// Example: +/// ```nocompile +// impl_wrapper_for! { +// () => [gas_left], +// (v1) => [gas_left], +// } +// ``` +// +// Expands to: +// ```nocompile +// fn gas_left(output: &mut &mut [u8]) { +// unsafe { sys::gas_left(...); } +// } +// fn gas_left_v1(output: &mut &mut [u8]) { +// unsafe { sys::v1::gas_left(...); } +// } +// ``` macro_rules! impl_wrapper_for { - (@impl_fn $( $mod:ident )::*, $suffix:literal, $name:ident) => { - paste::paste! { - fn [<$name $suffix>](output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { - $( $mod )::*::$name(output.as_mut_ptr(), &mut output_len); - } - } - } - }; - - () => {}; - - (($mod:ident, $suffix:literal) => [$( $name:ident),*], $($tail:tt)*) => { - $(impl_wrapper_for!(@impl_fn sys::$mod, $suffix, $name);)* - impl_wrapper_for!($($tail)*); - }; - - (() => [$( $name:ident),*], $($tail:tt)*) => { - $(impl_wrapper_for!(@impl_fn sys, "", $name);)* - impl_wrapper_for!($($tail)*); - }; + (@impl_fn $( $mod:ident )::*, $suffix_sep: literal, $suffix:tt, $name:ident) => { + paste::paste! { + fn [<$name $suffix_sep $suffix>](output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { + $( $mod )::*::$name(output.as_mut_ptr(), &mut output_len); + } + } + } + }; + + () => {}; + + (($mod:ident) => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys::$mod, "_", $mod, $name);)* + impl_wrapper_for!($($tail)*); + }; + + (() => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys, "", "", $name);)* + impl_wrapper_for!($($tail)*); + }; } /// A macro to implement all the hash functions Apis. @@ -322,27 +335,6 @@ macro_rules! impl_hash_fn { }; } -/// A macro to implement the get_storage functions. -macro_rules! impl_get_storage { - ($fn_name:ident, $sys_get_storage:path) => { - fn $fn_name(key: &[u8], output: &mut &mut [u8]) -> Result { - let mut output_len = output.len() as u32; - let ret_code = { - unsafe { - $sys_get_storage( - key.as_ptr(), - key.len() as u32, - output.as_mut_ptr(), - &mut output_len, - ) - } - }; - extract_from_slice(output, output_len as usize); - ret_code.into() - } - }; -} - impl HostFn for HostFnImpl { fn instantiate_v1( code_hash: &[u8], @@ -579,19 +571,12 @@ impl HostFn for HostFnImpl { } fn set_storage(key: &[u8], value: &[u8]) { - unsafe { - sys::set_storage(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) - }; + unsafe { sys::set_storage(key.as_ptr(), value.as_ptr(), value.len() as u32) }; } fn set_storage_v1(key: &[u8], encoded_value: &[u8]) -> Option { let ret_code = unsafe { - sys::v1::set_storage( - key.as_ptr(), - key.len() as u32, - encoded_value.as_ptr(), - encoded_value.len() as u32, - ) + sys::v1::set_storage(key.as_ptr(), encoded_value.as_ptr(), encoded_value.len() as u32) }; ret_code.into() } @@ -617,8 +602,29 @@ impl HostFn for HostFnImpl { ret_code.into() } - impl_get_storage!(get_storage, sys::get_storage); - impl_get_storage!(get_storage_v1, sys::v1::get_storage); + fn get_storage(key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = + { unsafe { sys::get_storage(key.as_ptr(), output.as_mut_ptr(), &mut output_len) } }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } + + fn get_storage_v1(key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::v1::get_storage( + key.as_ptr(), + key.len() as u32, + output.as_mut_ptr(), + &mut output_len, + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } fn take_storage(key: &[u8], output: &mut &mut [u8]) -> Result { let mut output_len = output.len() as u32; @@ -636,6 +642,11 @@ impl HostFn for HostFnImpl { ret_code.into() } + fn debug_message(str: &[u8]) -> Result { + let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; + ret_code.into() + } + fn contains_storage(key: &[u8]) -> Option { let ret_code = unsafe { sys::contains_storage(key.as_ptr(), key.len() as u32) }; ret_code.into() @@ -654,20 +665,23 @@ impl HostFn for HostFnImpl { unsafe { sys::v1::terminate(beneficiary.as_ptr()) } } - fn call_chain_extension(func_id: u32, input: &[u8], output: &mut &mut [u8]) -> u32 { - let mut output_len = output.len() as u32; + fn call_chain_extension(func_id: u32, input: &[u8], mut output: Option<&mut [u8]>) -> u32 { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let ret_code = { unsafe { sys::call_chain_extension( func_id, input.as_ptr(), input.len() as u32, - output.as_mut_ptr(), + output_ptr, &mut output_len, ) } }; - extract_from_slice(output, output_len as usize); + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } ret_code.into_u32() } @@ -690,7 +704,7 @@ impl HostFn for HostFnImpl { impl_wrapper_for! { () => [caller, block_number, address, balance, gas_left, value_transferred, now, minimum_balance], - (v1, "_v1") => [gas_left], + (v1) => [gas_left], } fn weight_to_fee(gas: u64, output: &mut &mut [u8]) { @@ -802,7 +816,7 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn xcm_send(dest: &[u8], msg: &[u8], output: &mut &mut [u8]) -> Result { + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { let ret_code = unsafe { sys::xcm_send(dest.as_ptr(), msg.as_ptr(), msg.len() as _, output.as_mut_ptr()) }; diff --git a/substrate/frame/contracts/uapi/src/lib.rs b/substrate/frame/contracts/uapi/src/lib.rs index 3d384bbb85dd..99d77f504eda 100644 --- a/substrate/frame/contracts/uapi/src/lib.rs +++ b/substrate/frame/contracts/uapi/src/lib.rs @@ -35,7 +35,7 @@ macro_rules! define_error_codes { )* ) => { /// Every error that can be returned to a contract when it calls any of the host functions. - #[derive(Debug)] + #[derive(Debug, PartialEq, Eq)] #[repr(u32)] pub enum ReturnErrorCode { /// API call successful. @@ -49,7 +49,6 @@ macro_rules! define_error_codes { } impl From for Result { - #[inline] fn from(return_code: ReturnCode) -> Self { match return_code.0 { 0 => Ok(()),