diff --git a/Cargo.lock b/Cargo.lock index a5938f1..54d31b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -208,7 +208,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.8", + "rustix 0.37.11", "slab", "socket2", "waker-fn", @@ -263,14 +263,14 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] name = "atomic-waker" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -317,7 +317,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "basic_test_actor" +name = "basic-target-actor" +version = "0.1.0" +dependencies = [ + "frc42_dispatch", + "fvm_ipld_encoding 0.3.3", + "fvm_sdk 3.0.0", + "fvm_shared 3.1.0", + "serde", + "thiserror", +] + +[[package]] +name = "basic-test-actor" version = "0.1.0" dependencies = [ "frc42_dispatch", @@ -458,9 +470,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -468,6 +480,7 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] @@ -529,7 +542,7 @@ dependencies = [ ] [[package]] -name = "builtin_test_actor" +name = "builtins-test-actor" version = "0.1.0" dependencies = [ "fil_actors_runtime_v10", @@ -603,6 +616,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cheatcodes-actor" +version = "0.1.0" +dependencies = [ + "frc42_dispatch", + "fvm_ipld_encoding 0.3.3", + "fvm_sdk 3.0.0", + "fvm_shared 3.1.0", + "serde", +] + +[[package]] +name = "cheatcodes-test-actor" +version = "0.1.0" +dependencies = [ + "fil_actors_runtime_v10", + "frc42_dispatch", + "fvm_ipld_encoding 0.3.3", + "fvm_sdk 3.0.0", + "fvm_shared 3.1.0", + "paste", + "serde", +] + [[package]] name = "cid" version = "0.8.6" @@ -690,7 +727,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -736,9 +773,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] @@ -770,7 +807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" [[package]] -name = "constructor_setup_test_actor" +name = "constructor-setup-test-actor" version = "0.1.0" dependencies = [ "cid", @@ -939,9 +976,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1096,6 +1133,36 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "demo-target-actor" +version = "0.1.0" +dependencies = [ + "cid", + "frc42_dispatch", + "fvm_ipld_blockstore", + "fvm_ipld_encoding 0.3.3", + "fvm_sdk 3.0.0", + "fvm_shared 3.1.0", + "serde", + "serde_tuple", + "thiserror", +] + +[[package]] +name = "demo-test-actor" +version = "0.1.0" +dependencies = [ + "cid", + "fil_actors_runtime_v10", + "frc42_dispatch", + "fvm_ipld_blockstore", + "fvm_ipld_encoding 0.3.3", + "fvm_sdk 3.0.0", + "fvm_shared 3.1.0", + "paste", + "serde", +] + [[package]] name = "derive-getters" version = "0.2.0" @@ -1248,13 +1315,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1310,15 +1377,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba569491c70ec8471e34aa7e9c0b9e82bb5d2464c0398442d17d3c4af814e5a" -[[package]] -name = "failed_test_actor" -version = "0.1.0" -dependencies = [ - "frc42_dispatch", - "fvm_sdk 3.0.0", - "fvm_shared 3.1.0", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -2287,9 +2345,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -2308,7 +2366,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -2970,7 +3028,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes 1.0.10", - "rustix 0.37.8", + "rustix 0.37.11", "windows-sys 0.48.0", ] @@ -3025,6 +3083,10 @@ dependencies = [ "log", ] +[[package]] +name = "kythera-actors" +version = "0.1.0" + [[package]] name = "kythera-cli" version = "0.1.0-alpha.2" @@ -3035,8 +3097,8 @@ dependencies = [ "clap 4.2.1", "colored", "env_logger", + "kythera-actors", "kythera-lib", - "kythera_test_actors", "log", "optional_struct", "path-clean", @@ -3072,6 +3134,7 @@ dependencies = [ "fvm_ipld_blockstore", "fvm_ipld_encoding 0.3.3", "fvm_shared 3.1.0", + "kythera-common", "multihash", ] @@ -3101,9 +3164,9 @@ dependencies = [ "fvm_ipld_car", "fvm_ipld_encoding 0.3.3", "fvm_shared 3.1.0", + "kythera-actors", "kythera-common", "kythera-fvm", - "kythera_test_actors", "libsecp256k1", "log", "rand", @@ -3112,10 +3175,6 @@ dependencies = [ "wat", ] -[[package]] -name = "kythera_test_actors" -version = "0.1.0" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3268,7 +3327,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix 0.37.8", + "rustix 0.37.11", ] [[package]] @@ -3608,9 +3667,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "paste" @@ -3650,9 +3709,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "polling" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" +checksum = "4be1c66a6add46bff50935c313dae30a5030cf8385c5206e8a95e9e9def974aa" dependencies = [ "autocfg", "bitflags", @@ -3661,7 +3720,7 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3958,12 +4017,12 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.8" +version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ "bitflags", - "errno 0.3.0", + "errno 0.3.1", "io-lifetimes 1.0.10", "libc", "linux-raw-sys 0.3.1", @@ -4005,9 +4064,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -4032,13 +4091,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -4072,7 +4131,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -4384,9 +4443,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5" dependencies = [ "proc-macro2", "quote", @@ -4426,7 +4485,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix 0.37.8", + "rustix 0.37.11", "windows-sys 0.45.0", ] @@ -4468,7 +4527,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -5232,5 +5291,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] diff --git a/Cargo.toml b/Cargo.toml index 16fe620..f69e909 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,9 @@ members = [ "lib", "fvm", "common", - "test_actors", - "test_actors/actors/*", + "actors", + "actors/actors/*", + "actors/test_actors/*", ] diff --git a/test_actors/Cargo.toml b/actors/Cargo.toml similarity index 73% rename from test_actors/Cargo.toml rename to actors/Cargo.toml index 7205173..1c6a978 100644 --- a/test_actors/Cargo.toml +++ b/actors/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "kythera_test_actors" +name = "kythera-actors" description = "Kythera FVM WASM actor builder" version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Polyphene"] publish = false + +[features] +default=[] +testing=[] diff --git a/test_actors/actors/failed_test_actor/Cargo.toml b/actors/actors/cheatcodes_actor/Cargo.toml similarity index 59% rename from test_actors/actors/failed_test_actor/Cargo.toml rename to actors/actors/cheatcodes_actor/Cargo.toml index 4bd51d3..d36944d 100644 --- a/test_actors/actors/failed_test_actor/Cargo.toml +++ b/actors/actors/cheatcodes_actor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "failed_test_actor" +name = "cheatcodes-actor" version = "0.1.0" edition = "2021" publish=false @@ -8,6 +8,8 @@ publish=false frc42_dispatch = "3.1.0" fvm_sdk = { version = "3.0.0" } fvm_shared = { version = "3.1.0" } +fvm_ipld_encoding = { version = "0.3.3" } +serde = { version = "1.0.136", features = ["derive"] } [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/actors/actors/cheatcodes_actor/README.md b/actors/actors/cheatcodes_actor/README.md new file mode 100644 index 0000000..607adc1 --- /dev/null +++ b/actors/actors/cheatcodes_actor/README.md @@ -0,0 +1,13 @@ +## Cheatcodes Test Actor + +This is the actor embedded in our `kythera-fvm` to expose the cheatcodes interface. + +### Cheatcodes + +The following cheatcodes are exposed through the actor: +- `Epoch`: Set the `NetworkContext::epoch` +- `Warp`: Set the `NetworkContext::timestamp` +- `Fee`: Set the `NetworkContext::fee` +- `ChaindId`: Set the `NetworkContext::chain_id` +- `Prank`: Sets the **next implicit message**'s `MessageContext::caller` to be the input address +- `Trick`: Sets the **next implicit message and its sub-implicit messages**' `MessageContext::origin` to be the input address \ No newline at end of file diff --git a/actors/actors/cheatcodes_actor/src/actor.rs b/actors/actors/cheatcodes_actor/src/actor.rs new file mode 100644 index 0000000..a8b5af6 --- /dev/null +++ b/actors/actors/cheatcodes_actor/src/actor.rs @@ -0,0 +1,114 @@ +// Copyright 2023 Polyphene. +// SPDX-License-Identifier: Apache-2.0, MIT + +use frc42_dispatch::match_method; +use fvm_ipld_encoding::{de::DeserializeOwned, RawBytes}; +use fvm_sdk::NO_DATA_BLOCK_ID; +use fvm_shared::address::Address; +use fvm_shared::error::ExitCode; + +/// Deserialize message parameters into given struct. +pub fn deserialize_params(params: u32) -> D { + let params = fvm_sdk::message::params_raw(params) + .expect("Could not get message parameters") + .expect("Expected message parameters but got none"); + + let params = RawBytes::new(params.data); + + params + .deserialize() + .expect("Should be able to deserialize message params into arguments of called method") +} + +#[no_mangle] +fn invoke(input: u32) -> u32 { + let method_num = fvm_sdk::message::method_number(); + match_method!( + method_num, + { + "Warp" => { + // Ensure that the message params can be deserialized. + let new_timestamp: u64 = deserialize_params(input); + + Warp(new_timestamp); + + NO_DATA_BLOCK_ID + }, + "Epoch" => { + // Ensure that the message params can be deserialized. + let new_epoch: i64 = deserialize_params(input); + + Epoch(new_epoch); + + NO_DATA_BLOCK_ID + }, + "Fee" => { + // Ensure that the message params can be deserialized. + let (lo, hi): (u64, u64) = deserialize_params(input); + + Fee( + fvm_shared::sys::TokenAmount { + lo, + hi + } + ); + + NO_DATA_BLOCK_ID + }, + "ChainId" => { + // Ensure that the message params can be deserialized. + let new_chain_id: u64 = deserialize_params(input); + + ChainId(new_chain_id); + + NO_DATA_BLOCK_ID + }, + "Prank" => { + // Ensure that the message params can be deserialized. + let new_caller: Address = deserialize_params(input); + + Prank(new_caller); + + NO_DATA_BLOCK_ID + }, + "Trick" => { + // Ensure that the message params can be deserialized. + let new_origin: Address = deserialize_params(input); + + Trick(new_origin); + + NO_DATA_BLOCK_ID + }, + _ => { + fvm_sdk::vm::abort( + ExitCode::USR_UNHANDLED_MESSAGE.value(), + Some("Unknown method number"), + ); + } + } + ) +} + +/// Warp the machine context to a given timestamp. +#[allow(non_snake_case)] +fn Warp(_new_timestamp: u64) {} + +/// Update the machine context to a given epoch. +#[allow(non_snake_case)] +fn Epoch(_new_epoch: i64) {} + +/// Update the base fee that's in effect when the machine runs. +#[allow(non_snake_case)] +fn Fee(_new_fee: fvm_shared::sys::TokenAmount) {} + +/// Set a new chain Id in the machine context. +#[allow(non_snake_case)] +fn ChainId(_new_chain_id: u64) {} + +/// Prank the call manager to set a pre-determined caller for the next message sent. +#[allow(non_snake_case)] +fn Prank(_new_caller: Address) {} + +/// Trick the call manager to set a pre-determined origin for the next message sent. +#[allow(non_snake_case)] +fn Trick(_new_origin: Address) {} diff --git a/test_actors/actors/basic_test_actor/src/lib.rs b/actors/actors/cheatcodes_actor/src/lib.rs similarity index 100% rename from test_actors/actors/basic_test_actor/src/lib.rs rename to actors/actors/cheatcodes_actor/src/lib.rs diff --git a/test_actors/build.rs b/actors/build.rs similarity index 76% rename from test_actors/build.rs rename to actors/build.rs index d3a4ec0..059086a 100644 --- a/test_actors/build.rs +++ b/actors/build.rs @@ -6,13 +6,19 @@ use std::path::Path; use std::process::{Command, Stdio}; use std::thread; -const ACTORS: &[&str] = &[ - "basic_test_actor", - "builtin_test_actor", - "constructor_setup_test_actor", - "failed_test_actor", +const ACTORS: &[&str] = &["cheatcodes-actor"]; + +#[cfg(feature = "testing")] +const TEST_ACTORS: &[&str] = &[ + "basic-test-actor", + "basic-target-actor", + "builtins-test-actor", + "cheatcodes-test-actor", + "constructor-setup-test-actor", ]; +const FILES_TO_WATCH: &[&str] = &["Cargo.toml", "src", "actors"]; + fn main() -> Result<(), Box> { // Cargo executable location. let cargo = std::env::var_os("CARGO").expect("no CARGO env var"); @@ -28,14 +34,26 @@ fn main() -> Result<(), Box> { Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR unset")) .join("Cargo.toml"); - for file in ["Cargo.toml", "src", "actors"] { + let mut files_to_watch = FILES_TO_WATCH.to_vec(); + + if cfg!(feature = "testing") { + files_to_watch = [files_to_watch, vec!["test_actors"]].concat(); + } + + for file in files_to_watch { println!("cargo:rerun-if-changed={}", file); } - // Cargo build command for all actors at once. + let mut actors = ACTORS.to_vec(); + + if cfg!(feature = "testing") { + actors = [ACTORS, TEST_ACTORS].concat(); + } + + // Cargo build command for all test_actors at once. let mut cmd = Command::new(cargo); cmd.arg("build") - .args(ACTORS.iter().map(|pkg| "-p=".to_owned() + pkg)) + .args(actors.iter().map(|pkg| "-p=".to_owned() + pkg)) .arg("--target=wasm32-unknown-unknown") .arg("--profile=wasm") .arg("--locked") diff --git a/test_actors/src/README.md b/actors/src/README.md similarity index 69% rename from test_actors/src/README.md rename to actors/src/README.md index a9916a3..3fb5e27 100644 --- a/test_actors/src/README.md +++ b/actors/src/README.md @@ -1,4 +1,4 @@ -This crate contains the necessary logic to build some testing actors for our Kythera toolset. +This crate contains the necessary logic to build actors for our Kythera toolset. It was heavily inspired and copied from the implementation [over the `ref-fvm`](https://github.com/filecoin-project/ref-fvm/tree/37643fc02f0342256afecff5158c43693b5ee4f0/testing/test_actors) done by @fridrik01. \ No newline at end of file diff --git a/test_actors/src/lib.rs b/actors/src/lib.rs similarity index 100% rename from test_actors/src/lib.rs rename to actors/src/lib.rs diff --git a/actors/src/wasm_bin/mod.rs b/actors/src/wasm_bin/mod.rs new file mode 100644 index 0000000..6582fed --- /dev/null +++ b/actors/src/wasm_bin/mod.rs @@ -0,0 +1,28 @@ +// Copyright 2023 Polyphene. +// SPDX-License-Identifier: Apache-2.0, MIT +// Constants for wasm build artifacts. + +macro_rules! wasm_bin { + ($x: expr) => { + concat!( + env!("OUT_DIR"), + "/bundle/wasm32-unknown-unknown/wasm/", + $x, + ".wasm" + ) + }; +} + +pub const CHEATCODES_ACTOR_BINARY: &[u8] = include_bytes!(wasm_bin!("cheatcodes_actor")); + +#[cfg(feature = "testing")] +pub mod test_actors { + // Integration tests actors. + pub const BASIC_TEST_ACTOR_BINARY: &[u8] = include_bytes!(wasm_bin!("basic_test_actor")); + pub const BASIC_TARGET_ACTOR_BINARY: &[u8] = include_bytes!(wasm_bin!("basic_target_actor")); + pub const BUILTINS_TEST_ACTOR_BINARY: &[u8] = include_bytes!(wasm_bin!("builtins_test_actor")); + pub const CHEATCODES_TEST_ACTOR_BINARY: &[u8] = + include_bytes!(wasm_bin!("cheatcodes_test_actor")); + pub const CONSTRUCTOR_SETUP_TEST_ACTOR_BINARY: &[u8] = + include_bytes!(wasm_bin!("constructor_setup_test_actor")); +} diff --git a/actors/test_actors/basic_target_actor/Cargo.toml b/actors/test_actors/basic_target_actor/Cargo.toml new file mode 100644 index 0000000..e2e0f01 --- /dev/null +++ b/actors/test_actors/basic_target_actor/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "basic-target-actor" +version = "0.1.0" +edition = "2021" +publish=false + +[target.'cfg(target_arch = "wasm32")'.dependencies] +frc42_dispatch = "3.1.0" +fvm_sdk = { version = "3.0.0" } +fvm_shared = { version = "3.1.0" } +fvm_ipld_encoding = { version = "0.3.3" } +serde = { version = "1.0.136", features = ["derive"] } +thiserror = { version = "1.0.31" } + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/actors/test_actors/basic_target_actor/README.md b/actors/test_actors/basic_target_actor/README.md new file mode 100644 index 0000000..3d1f31f --- /dev/null +++ b/actors/test_actors/basic_target_actor/README.md @@ -0,0 +1,6 @@ +## Basic Target Actor + +This is a basic actor that serves as a target actor in some of our tests for Kythera. It is the target actor used against +our `cheatcodes_test_actor` for example. Its entrypoints are: +- `Caller`: Method that returns the value of the `MessageContext.caller` +- `Origin`: Method that returns the value of the `MessageContext.origin` diff --git a/actors/test_actors/basic_target_actor/src/actor.rs b/actors/test_actors/basic_target_actor/src/actor.rs new file mode 100644 index 0000000..d4643dd --- /dev/null +++ b/actors/test_actors/basic_target_actor/src/actor.rs @@ -0,0 +1,52 @@ +// Copyright 2023 Polyphene. +// SPDX-License-Identifier: Apache-2.0, MIT + +use frc42_dispatch::match_method; +use fvm_ipld_encoding::DAG_CBOR; +use fvm_sdk as sdk; +use fvm_shared::error::ExitCode; +use sdk::sys::ErrorNumber; +use serde::ser; +use thiserror::Error; + +#[derive(Error, Debug)] +enum IpldError { + #[error("ipld encoding error: {0}")] + Encoding(#[from] fvm_ipld_encoding::Error), + #[error("ipld blockstore error: {0}")] + Blockstore(#[from] ErrorNumber), +} + +fn return_ipld(value: &T) -> std::result::Result +where + T: ser::Serialize + ?Sized, +{ + let bytes = fvm_ipld_encoding::to_vec(value)?; + Ok(sdk::ipld::put_block(DAG_CBOR, bytes.as_slice())?) +} + +#[no_mangle] +fn invoke(_input: u32) -> u32 { + let method_num = fvm_sdk::message::method_number(); + match_method!( + method_num, + { + "Caller" => { + let mc_caller: u64 = unsafe { fvm_sdk::sys::vm::message_context().unwrap().caller }; + + return_ipld(&mc_caller).unwrap() + }, + "Origin" => { + let mc_origin: u64 = unsafe { fvm_sdk::sys::vm::message_context().unwrap().origin }; + + return_ipld(&mc_origin).unwrap() + }, + _ => { + fvm_sdk::vm::abort( + ExitCode::USR_UNHANDLED_MESSAGE.value(), + Some("Unknown method number"), + ); + } + } + ) +} diff --git a/test_actors/actors/builtins_test_actor/src/lib.rs b/actors/test_actors/basic_target_actor/src/lib.rs similarity index 100% rename from test_actors/actors/builtins_test_actor/src/lib.rs rename to actors/test_actors/basic_target_actor/src/lib.rs diff --git a/test_actors/actors/basic_test_actor/Cargo.toml b/actors/test_actors/basic_test_actor/Cargo.toml similarity index 93% rename from test_actors/actors/basic_test_actor/Cargo.toml rename to actors/test_actors/basic_test_actor/Cargo.toml index 9b8f15e..35421eb 100644 --- a/test_actors/actors/basic_test_actor/Cargo.toml +++ b/actors/test_actors/basic_test_actor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "basic_test_actor" +name = "basic-test-actor" version = "0.1.0" edition = "2021" publish=false diff --git a/test_actors/actors/basic_test_actor/README.md b/actors/test_actors/basic_test_actor/README.md similarity index 100% rename from test_actors/actors/basic_test_actor/README.md rename to actors/test_actors/basic_test_actor/README.md diff --git a/test_actors/actors/basic_test_actor/src/actor.rs b/actors/test_actors/basic_test_actor/src/actor.rs similarity index 76% rename from test_actors/actors/basic_test_actor/src/actor.rs rename to actors/test_actors/basic_test_actor/src/actor.rs index d8f069f..75ff1dc 100644 --- a/test_actors/actors/basic_test_actor/src/actor.rs +++ b/actors/test_actors/basic_test_actor/src/actor.rs @@ -4,6 +4,7 @@ use frc42_dispatch::match_method; use fvm_ipld_encoding::DAG_CBOR; use fvm_sdk as sdk; +use fvm_sdk::NO_DATA_BLOCK_ID; use fvm_shared::error::ExitCode; use sdk::sys::ErrorNumber; use serde::ser; @@ -27,12 +28,25 @@ where #[no_mangle] fn invoke(_input: u32) -> u32 { + std::panic::set_hook(Box::new(|info| { + sdk::vm::exit( + ExitCode::USR_ASSERTION_FAILED.value(), + None, + Some(&format!("{info}")), + ) + })); + let method_num = sdk::message::method_number(); match_method!( method_num, { "TestOne" => return_ipld(TestOne()).unwrap(), "TestTwo" => return_ipld(TestTwo()).unwrap(), + "TestFailed" => { + TestFailed(); + + NO_DATA_BLOCK_ID + }, _ => { sdk::vm::abort( ExitCode::USR_UNHANDLED_MESSAGE.value(), @@ -52,3 +66,8 @@ fn TestOne() -> &'static str { fn TestTwo() -> &'static str { "TestTwo" } + +#[allow(non_snake_case)] +fn TestFailed() { + assert_eq!(1 + 1, 3); +} diff --git a/test_actors/actors/constructor_setup_test_actor/src/lib.rs b/actors/test_actors/basic_test_actor/src/lib.rs similarity index 100% rename from test_actors/actors/constructor_setup_test_actor/src/lib.rs rename to actors/test_actors/basic_test_actor/src/lib.rs diff --git a/test_actors/actors/builtins_test_actor/Cargo.toml b/actors/test_actors/builtins_test_actor/Cargo.toml similarity index 92% rename from test_actors/actors/builtins_test_actor/Cargo.toml rename to actors/test_actors/builtins_test_actor/Cargo.toml index a4a77c8..d069be6 100644 --- a/test_actors/actors/builtins_test_actor/Cargo.toml +++ b/actors/test_actors/builtins_test_actor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "builtin_test_actor" +name = "builtins-test-actor" version = "0.1.0" edition = "2021" publish=false diff --git a/test_actors/actors/builtins_test_actor/README.md b/actors/test_actors/builtins_test_actor/README.md similarity index 100% rename from test_actors/actors/builtins_test_actor/README.md rename to actors/test_actors/builtins_test_actor/README.md diff --git a/test_actors/actors/builtins_test_actor/src/actor.rs b/actors/test_actors/builtins_test_actor/src/actor.rs similarity index 100% rename from test_actors/actors/builtins_test_actor/src/actor.rs rename to actors/test_actors/builtins_test_actor/src/actor.rs diff --git a/test_actors/actors/failed_test_actor/src/lib.rs b/actors/test_actors/builtins_test_actor/src/lib.rs similarity index 100% rename from test_actors/actors/failed_test_actor/src/lib.rs rename to actors/test_actors/builtins_test_actor/src/lib.rs diff --git a/actors/test_actors/cheatcodes_test_actor/Cargo.toml b/actors/test_actors/cheatcodes_test_actor/Cargo.toml new file mode 100644 index 0000000..2358e10 --- /dev/null +++ b/actors/test_actors/cheatcodes_test_actor/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cheatcodes-test-actor" +version = "0.1.0" +edition = "2021" +publish=false + +[target.'cfg(target_arch = "wasm32")'.dependencies] +fil_actors_runtime_v10 = "1.0.0" +frc42_dispatch = "3.1.0" +fvm_sdk = { version = "3.0.0" } +fvm_shared = { version = "3.1.0" } +fvm_ipld_encoding = { version = "0.3.3" } +paste = "1.0.12" +serde = { version = "1.0.136", features = ["derive"] } + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/actors/test_actors/cheatcodes_test_actor/README.md b/actors/test_actors/cheatcodes_test_actor/README.md new file mode 100644 index 0000000..e148049 --- /dev/null +++ b/actors/test_actors/cheatcodes_test_actor/README.md @@ -0,0 +1,13 @@ +## Cheatcodes Test Actor + +This is an actor to test Kythera cheatcodes implementation. + +### Cheatcodes + +The following cheatcodes are tested through the actor: +- `Epoch`: Set the `NetworkContext::epoch` +- `Warp`: Set the `NetworkContext::timestamp` +- `Fee`: Set the `NetworkContext::fee` +- `ChaindId`: Set the `NetworkContext::chain_id` +- `Prank`: Sets the **next call**'s `NetworkContext::caller` to be the input address +- `Trick`: Sets the **next call**'s `NetworkContext::origin` to be the input address \ No newline at end of file diff --git a/actors/test_actors/cheatcodes_test_actor/src/actor.rs b/actors/test_actors/cheatcodes_test_actor/src/actor.rs new file mode 100644 index 0000000..eba69b3 --- /dev/null +++ b/actors/test_actors/cheatcodes_test_actor/src/actor.rs @@ -0,0 +1,349 @@ +// Copyright 2023 Polyphene. +// SPDX-License-Identifier: Apache-2.0, MIT + +use frc42_dispatch::{match_method, method_hash}; +use fvm_ipld_encoding::ipld_block::IpldBlock; +use fvm_ipld_encoding::DAG_CBOR; +use fvm_ipld_encoding::{de::DeserializeOwned, RawBytes}; +use fvm_sdk as sdk; +use fvm_sdk::NO_DATA_BLOCK_ID; +use fvm_shared::address::Address; +use fvm_shared::bigint::Zero; +use fvm_shared::econ::TokenAmount; +use fvm_shared::error::ExitCode; +use fvm_shared::sys::SendFlags; +use paste::paste; + +macro_rules! declare_match_method { + ( + $input:expr, $($name:literal => $func:path,)* + ) => { + let method_num = sdk::message::method_number(); + match_method! { + method_num, + { + $($name => { + $func($input); + NO_DATA_BLOCK_ID + }),* + _ => { + sdk::vm::abort( + ExitCode::USR_UNHANDLED_MESSAGE.value(), + Some("Unknown method number"), + ); + }, + } + } + }; +} + +macro_rules! declare_tests_fail { + ($($method:literal),*) => { + $( + paste! { + #[allow(non_snake_case)] + fn [](_input: u32) { + let new_timestamp = String::from("timestamp"); + + fvm_sdk::send::send( + &Address::new_id(98), + method_hash!($method), + Some(IpldBlock::serialize(DAG_CBOR, &new_timestamp).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + } + #[allow(non_snake_case)] + fn [](_input: u32) { + fvm_sdk::send::send( + &Address::new_id(98), + method_hash!($method), + None, + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + } + } + )* + }; +} + +/// Deserialize message parameters into given struct. +pub fn deserialize_params(params: u32) -> D { + let params = fvm_sdk::message::params_raw(params) + .expect("Could not get message parameters") + .expect("Expected message parameters but got none"); + + let params = RawBytes::new(params.data); + + params + .deserialize() + .expect("Should be able to deserialize message params into arguments of called method") +} + +#[no_mangle] +fn invoke(input: u32) -> u32 { + std::panic::set_hook(Box::new(|info| { + sdk::vm::abort( + ExitCode::USR_ASSERTION_FAILED.value(), + Some(&format!("{info}")), + ) + })); + + declare_match_method! { + input, + "TestFailDeserializationWarp" => TestFailDeserializationWarp, + "TestFailNoParametersWarp" => TestFailNoParametersWarp, + "TestWarp" => TestWarp, + "TestFailDeserializationEpoch" => TestFailDeserializationEpoch, + "TestFailNoParametersEpoch" => TestFailNoParametersEpoch, + "TestEpoch" => TestEpoch, + "TestFailDeserializationFee" => TestFailDeserializationFee, + "TestFailNoParametersFee" => TestFailNoParametersFee, + "TestFee" => TestFee, + "TestFailDeserializationChainId" => TestFailDeserializationChainId, + "TestFailNoParametersChainId" => TestFailNoParametersChainId, + "TestChainId" => TestChainId, + "TestFailDeserializationPrank" => TestFailDeserializationPrank, + "TestFailNoParametersPrank" => TestFailNoParametersPrank, + "TestFailAddressTypePrank" => TestFailAddressTypePrank, + "TestPrank" => TestPrank, + "TestFailDeserializationTrick" => TestFailDeserializationTrick, + "TestFailNoParametersTrick" => TestFailNoParametersTrick, + "TestFailAddressTypeTrick" => TestFailAddressTypeTrick, + "TestTrick" => TestTrick, + } +} + +// Checks Warp cheatcode happy path. +#[allow(non_snake_case)] +fn TestWarp(_input: u32) { + let timestamp = fvm_sdk::network::tipset_timestamp(); + + assert_eq!(timestamp, 0u64); + + let new_timestamp = 10000u64; + + let res = fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("Warp"), + Some(IpldBlock::serialize(DAG_CBOR, &new_timestamp).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let nc_timestamp = unsafe { fvm_sdk::sys::network::context().unwrap().timestamp }; + + assert_eq!(new_timestamp, nc_timestamp); +} + +// Checks Epoch cheatcode happy path. +#[allow(non_snake_case)] +fn TestEpoch(_input: u32) { + let epoch = fvm_sdk::network::curr_epoch(); + + assert_eq!(epoch, 0i64); + + let new_epoch = 10000i64; + + let res = fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("Epoch"), + Some(IpldBlock::serialize(DAG_CBOR, &new_epoch).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let nc_epoch = unsafe { fvm_sdk::sys::network::context().unwrap().epoch }; + + assert_eq!(new_epoch, nc_epoch); +} + +// Checks Fee cheatcode happy path. +#[allow(non_snake_case)] +fn TestFee(_input: u32) { + let base_fee_sys = + fvm_shared::sys::TokenAmount::try_from(fvm_sdk::network::base_fee()).unwrap(); + let lo = base_fee_sys.lo; + let hi = base_fee_sys.hi; + + assert_eq!(lo, 100u64); + assert_eq!(hi, 0u64); + + let new_base_fee = (200u64, 200u64); + + let res = fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("Fee"), + Some(IpldBlock::serialize(DAG_CBOR, &new_base_fee).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let nc_base_fee = unsafe { + let base_fee = fvm_sdk::sys::network::context().unwrap().base_fee; + (base_fee.lo, base_fee.hi) + }; + + assert_eq!(new_base_fee, nc_base_fee); +} + +// Checks ChainId cheatcode happy path. +#[allow(non_snake_case)] +fn TestChainId(_input: u32) { + let chain_id = fvm_sdk::network::chain_id(); + + assert_eq!(u64::from(chain_id), 1312u64); + + let new_chain_id = 1500u64; + + let res = fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("ChainId"), + Some(IpldBlock::serialize(DAG_CBOR, &new_chain_id).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let nc_chain_id = unsafe { fvm_sdk::sys::network::context().unwrap().chain_id }; + + assert_eq!(new_chain_id, nc_chain_id); +} + +// Checks Prank cheatcode happy path. +#[allow(non_snake_case)] +fn TestPrank(input: u32) { + let target_actor_id: u64 = deserialize_params(input); + + let new_caller = Address::new_id(1); + + let res = fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("Prank"), + Some(IpldBlock::serialize(DAG_CBOR, &new_caller).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let res = fvm_sdk::send::send( + &Address::new_id(target_actor_id), + method_hash!("Caller"), + None, + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let caller: u64 = RawBytes::new( + res.return_data + .expect("Should be able to get Caller from target actor") + .data, + ) + .deserialize() + .unwrap(); + + assert_eq!(new_caller.id().unwrap(), caller); +} + +// Checks Prank with a wrong address type. +#[allow(non_snake_case)] +fn TestFailAddressTypePrank(_input: u32) { + let new_caller = Address::new_actor(b"WrongType"); + + fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("Prank"), + Some(IpldBlock::serialize(DAG_CBOR, &new_caller).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); +} + +// Checks Trick cheatcode happy path. +#[allow(non_snake_case)] +fn TestTrick(input: u32) { + let target_actor_id: u64 = deserialize_params(input); + + let new_origin = Address::new_id(1); + + let res = fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("Trick"), + Some(IpldBlock::serialize(DAG_CBOR, &new_origin).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let res = fvm_sdk::send::send( + &Address::new_id(target_actor_id), + method_hash!("Origin"), + None, + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); + + assert_eq!(res.exit_code, ExitCode::OK); + + let origin: u64 = RawBytes::new( + res.return_data + .expect("Should be able to get Origin from target actor") + .data, + ) + .deserialize() + .unwrap(); + + assert_eq!(new_origin.id().unwrap(), origin); +} + +// Checks Trick with a wrong address type. +#[allow(non_snake_case)] +fn TestFailAddressTypeTrick(_input: u32) { + let new_origin = Address::new_actor(b"WrongType"); + + fvm_sdk::send::send( + &Address::new_id(98), + method_hash!("Trick"), + Some(IpldBlock::serialize(DAG_CBOR, &new_origin).unwrap()), + TokenAmount::zero(), + None, + SendFlags::empty(), + ) + .unwrap(); +} + +declare_tests_fail!("Warp", "Epoch", "Fee", "ChainId", "Prank", "Trick"); diff --git a/actors/test_actors/cheatcodes_test_actor/src/lib.rs b/actors/test_actors/cheatcodes_test_actor/src/lib.rs new file mode 100644 index 0000000..6662b42 --- /dev/null +++ b/actors/test_actors/cheatcodes_test_actor/src/lib.rs @@ -0,0 +1,5 @@ +// Copyright 2023 Polyphene. +// SPDX-License-Identifier: Apache-2.0, MIT + +#[cfg(target_arch = "wasm32")] +mod actor; diff --git a/test_actors/actors/constructor_setup_test_actor/Cargo.toml b/actors/test_actors/constructor_setup_test_actor/Cargo.toml similarity index 92% rename from test_actors/actors/constructor_setup_test_actor/Cargo.toml rename to actors/test_actors/constructor_setup_test_actor/Cargo.toml index 9bfaf52..e0468a2 100644 --- a/test_actors/actors/constructor_setup_test_actor/Cargo.toml +++ b/actors/test_actors/constructor_setup_test_actor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "constructor_setup_test_actor" +name = "constructor-setup-test-actor" version = "0.1.0" edition = "2021" publish=false diff --git a/test_actors/actors/constructor_setup_test_actor/README.md b/actors/test_actors/constructor_setup_test_actor/README.md similarity index 100% rename from test_actors/actors/constructor_setup_test_actor/README.md rename to actors/test_actors/constructor_setup_test_actor/README.md diff --git a/test_actors/actors/constructor_setup_test_actor/src/actor.rs b/actors/test_actors/constructor_setup_test_actor/src/actor.rs similarity index 100% rename from test_actors/actors/constructor_setup_test_actor/src/actor.rs rename to actors/test_actors/constructor_setup_test_actor/src/actor.rs diff --git a/actors/test_actors/constructor_setup_test_actor/src/lib.rs b/actors/test_actors/constructor_setup_test_actor/src/lib.rs new file mode 100644 index 0000000..6662b42 --- /dev/null +++ b/actors/test_actors/constructor_setup_test_actor/src/lib.rs @@ -0,0 +1,5 @@ +// Copyright 2023 Polyphene. +// SPDX-License-Identifier: Apache-2.0, MIT + +#[cfg(target_arch = "wasm32")] +mod actor; diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d7dcf99..d92da92 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -36,4 +36,4 @@ assert_fs = "1.0.12" predicates = "3.0.2" tempfile = "3.4.0" wat = "1.0.61" -kythera_test_actors = { path = "../test_actors" } +kythera-actors = { path = "../actors", features = ["testing"] } diff --git a/cli/src/commands/test/mod.rs b/cli/src/commands/test/mod.rs index 18364e0..81ffaa4 100644 --- a/cli/src/commands/test/mod.rs +++ b/cli/src/commands/test/mod.rs @@ -140,8 +140,8 @@ mod tests { use std::{fs::File, io::Write}; use assert_cmd::Command; + use kythera_actors::wasm_bin::test_actors::BASIC_TEST_ACTOR_BINARY; use kythera_lib::{to_vec, Abi, Method}; - use kythera_test_actors::wasm_bin::{BASIC_TEST_ACTOR_BINARY, FAILED_TEST_ACTOR_BINARY}; use predicates::str::contains; use tempfile::{tempdir, TempDir}; @@ -248,7 +248,7 @@ mod tests { #[test] fn outputs_failed_tests() { let dir = create_target_and_test_actors( - FAILED_TEST_ACTOR_BINARY, + BASIC_TEST_ACTOR_BINARY, &Abi { constructor: None, set_up: None, @@ -271,7 +271,7 @@ mod tests { )) .stdout(contains("left: `2`,")) .stdout(contains( - "right: `3`\', test_actors/actors/failed_test_actor/src/actor.rs:41:5 (24)", + "right: `3`\', actors/test_actors/basic_test_actor/src/actor.rs:72:5 (24)", )) .stdout(contains("test result: FAILED. 0 passed; 1 failed")); } diff --git a/cli/src/utils/search.rs b/cli/src/utils/search.rs index baa4e06..fb292ee 100644 --- a/cli/src/utils/search.rs +++ b/cli/src/utils/search.rs @@ -64,7 +64,7 @@ fn read_actor>(binary_path: P) -> anyhow::Result { /// Gather the target Actor file and its test files. /// The rules for reading Actor files and it's matching tests are: -/// - All .wasm files that are at the root of the kythera input dir are actors. +/// - All .wasm files that are at the root of the kythera input dir are target actors. /// - All .t.wasm files that are at the root of the kythera wasm dir are test actors. /// - All .wasm files that are in .t dirs are test actors. pub fn search_files>(path: P) -> anyhow::Result> { diff --git a/common/src/abi/mod.rs b/common/src/abi/mod.rs index 00bc7c1..672d475 100644 --- a/common/src/abi/mod.rs +++ b/common/src/abi/mod.rs @@ -41,7 +41,7 @@ pub fn pascal_case_split(s: &str) -> Vec<&str> { /// `Abi` is the structure we use internally to deal with Actor Binary Interface. It contains all /// exposed [`Method`] from a given Actor. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Abi { pub constructor: Option, pub set_up: Option, diff --git a/fvm/Cargo.toml b/fvm/Cargo.toml index ebe3d9b..715c7e6 100644 --- a/fvm/Cargo.toml +++ b/fvm/Cargo.toml @@ -13,3 +13,5 @@ fvm_ipld_blockstore = "0.1.1" fvm_ipld_encoding = { version = "0.3.3" } fvm_shared = { version = "3.1.0" } multihash = { version = "0.16.1", default-features = false } + +kythera-common = { path = "../common" } diff --git a/fvm/src/call_manager.rs b/fvm/src/call_manager.rs new file mode 100644 index 0000000..796f756 --- /dev/null +++ b/fvm/src/call_manager.rs @@ -0,0 +1,315 @@ +use crate::kernel::KytheraKernel; +use crate::machine::KytheraMachine; +use crate::utils::{CHAIN_ID_NUM, EPOCH_NUM, FEE_NUM, PRANK_NUM, TRICK_NUM, WARP_NUM}; +use anyhow::anyhow; +use cid::Cid; +use fvm::call_manager::{CallManager, DefaultCallManager, FinishRet, InvocationResult}; +use fvm::engine::Engine; +use fvm::gas::{Gas, GasTracker}; +use fvm::kernel::{Block, ExecutionError}; +use fvm::machine::Machine; +use fvm::state_tree::ActorState; +use fvm::Kernel; +use fvm_ipld_encoding::from_slice; +use fvm_shared::address::Address; +use fvm_shared::econ::TokenAmount; +use fvm_shared::event::StampedEvent; +use fvm_shared::{ActorID, MethodNum}; + +#[repr(transparent)] +pub struct KytheraCallManager>(pub C); + +impl KytheraCallManager +where + M: Machine, + C: CallManager>, +{ + fn handle_cheatcode( + &mut self, + method: MethodNum, + params: Option, + ) -> fvm::kernel::Result<()> { + match method { + WARP_NUM => { + let new_timestamp: u64 = from_slice( + params + .ok_or(ExecutionError::Fatal(anyhow!( + "No parameters provided for Warp cheatcode" + )))? + .data(), + ) + .map_err(|err| { + ExecutionError::Fatal(anyhow!(format!( + "Could not deserialize parameters for Warp cheatcode: {}", + err + ))) + })?; + self.machine_mut().override_context.timestamp = Some(new_timestamp); + } + EPOCH_NUM => { + let new_epoch: i64 = from_slice( + params + .ok_or(ExecutionError::Fatal(anyhow!( + "No parameters provided for Epoch cheatcode" + )))? + .data(), + ) + .map_err(|err| { + ExecutionError::Fatal(anyhow!(format!( + "Could not deserialize parameters for Epoch cheatcode: {}", + err + ))) + })?; + self.machine_mut().override_context.epoch = Some(new_epoch); + } + FEE_NUM => { + let (lo, hi): (u64, u64) = from_slice( + params + .ok_or(ExecutionError::Fatal(anyhow!( + "No parameters provided for Fee cheatcode" + )))? + .data(), + ) + .map_err(|err| { + ExecutionError::Fatal(anyhow!(format!( + "Could not deserialize parameters for Fee cheatcode: {}", + err + ))) + })?; + + self.machine_mut().override_context.base_fee = + Some(fvm_shared::sys::TokenAmount { lo, hi }); + } + CHAIN_ID_NUM => { + let chain_id: u64 = from_slice( + params + .ok_or(ExecutionError::Fatal(anyhow!( + "No parameters provided for ChainId cheatcode" + )))? + .data(), + ) + .map_err(|err| { + ExecutionError::Fatal(anyhow!(format!( + "Could not deserialize parameters for ChainId cheatcode: {}", + err + ))) + })?; + + self.machine_mut().override_context.chain_id = Some(chain_id); + } + PRANK_NUM => { + let new_caller: Address = from_slice( + params + .ok_or(ExecutionError::Fatal(anyhow!( + "No parameters provided for Prank cheatcode" + )))? + .data(), + ) + .map_err(|err| { + ExecutionError::Fatal(anyhow!(format!( + "Could not deserialize parameters for Prank cheatcode: {}", + err + ))) + })?; + + let new_caller_id = match new_caller.id() { + Ok(id) => id, + Err(err) => { + return Err(ExecutionError::Fatal(anyhow!(format!( + "Address parameter for Prank should have a valid ActorID: {}", + err + )))) + } + }; + + self.machine_mut().override_context.caller = Some(new_caller_id); + } + TRICK_NUM => { + let new_origin: Address = from_slice( + params + .ok_or(ExecutionError::Fatal(anyhow!( + "No parameters provided for Trick cheatcode" + )))? + .data(), + ) + .map_err(|err| { + ExecutionError::Fatal(anyhow!(format!( + "Could not deserialize parameters for Trick cheatcode: {}", + err + ))) + })?; + + let new_origin_id = match new_origin.id() { + Ok(id) => id, + Err(err) => { + return Err(ExecutionError::Fatal(anyhow!(format!( + "Address parameter for Trick should have a valid ActorID: {}", + err + )))) + } + }; + + self.machine_mut().override_context.origin = Some(new_origin_id); + } + _ => return Err(ExecutionError::Fatal(anyhow!("Call to unknown cheatcode"))), + } + + Ok(()) + } +} + +impl CallManager for KytheraCallManager +where + M: Machine, + C: CallManager>, +{ + type Machine = C::Machine; + + fn new( + machine: Self::Machine, + engine: Engine, + gas_limit: u64, + origin: ActorID, + origin_address: Address, + receiver: Option, + receiver_address: Address, + nonce: u64, + gas_premium: TokenAmount, + ) -> Self { + Self(C::new( + machine, + engine, + gas_limit, + origin, + origin_address, + receiver, + receiver_address, + nonce, + gas_premium, + )) + } + + fn send>( + &mut self, + from: ActorID, + to: Address, + method: MethodNum, + params: Option, + value: &TokenAmount, + gas_limit: Option, + read_only: bool, + ) -> fvm::kernel::Result { + // If cheatcode actor then we proceed as usual + if to == Address::new_id(98) { + self.handle_cheatcode(method, params.clone())?; + + self.0 + .send::>(from, to, method, params, value, gas_limit, read_only) + } + // If any other actor, check if override caller + else { + let caller = self.machine().override_context().caller.unwrap_or(from); + self.machine_mut().override_context.caller = None; + self.0 + .send::>(caller, to, method, params, value, gas_limit, read_only) + } + } + + fn with_transaction( + &mut self, + f: impl FnOnce(&mut Self) -> fvm::kernel::Result, + ) -> fvm::kernel::Result { + // This transmute is _safe_ because this type is "repr transparent". + let inner_ptr = &mut self.0 as *mut C; + self.0.with_transaction(|inner: &mut C| unsafe { + // Make sure that we've got the right pointer. Otherwise, this cast definitely isn't + // safe. + assert_eq!(inner_ptr, inner as *mut C); + + // Ok, we got the pointer we expected, casting back to the interceptor is safe. + f(&mut *(inner as *mut C as *mut Self)) + }) + } + + fn finish(self) -> (fvm::kernel::Result, Self::Machine) { + self.0.finish() + } + + fn machine(&self) -> &Self::Machine { + self.0.machine() + } + + fn machine_mut(&mut self) -> &mut Self::Machine { + self.0.machine_mut() + } + + fn engine(&self) -> &Engine { + self.0.engine() + } + + fn gas_tracker(&self) -> &GasTracker { + self.0.gas_tracker() + } + + fn gas_premium(&self) -> &TokenAmount { + self.0.gas_premium() + } + + fn origin(&self) -> ActorID { + self.0.origin() + } + + fn next_actor_address(&self) -> Address { + self.0.next_actor_address() + } + + fn create_actor( + &mut self, + code_id: Cid, + actor_id: ActorID, + delegated_address: Option
, + ) -> fvm::kernel::Result<()> { + self.0.create_actor(code_id, actor_id, delegated_address) + } + + fn resolve_address(&self, address: &Address) -> fvm::kernel::Result> { + self.0.resolve_address(address) + } + + fn set_actor(&mut self, id: ActorID, state: ActorState) -> fvm::kernel::Result<()> { + self.0.set_actor(id, state) + } + + fn get_actor(&self, id: ActorID) -> fvm::kernel::Result> { + self.0.get_actor(id) + } + + fn delete_actor(&mut self, id: ActorID) -> fvm::kernel::Result<()> { + self.0.delete_actor(id) + } + + fn transfer( + &mut self, + from: ActorID, + to: ActorID, + value: &TokenAmount, + ) -> fvm::kernel::Result<()> { + self.0.transfer(from, to, value) + } + + fn nonce(&self) -> u64 { + self.0.nonce() + } + + fn invocation_count(&self) -> u64 { + self.0.invocation_count() + } + + fn limiter_mut(&mut self) -> &mut ::Limiter { + self.0.limiter_mut() + } + + fn append_event(&mut self, evt: StampedEvent) { + self.0.append_event(evt) + } +} diff --git a/fvm/src/context.rs b/fvm/src/context.rs new file mode 100644 index 0000000..6d9a9a2 --- /dev/null +++ b/fvm/src/context.rs @@ -0,0 +1,51 @@ +use fvm_shared::clock::ChainEpoch; +use fvm_shared::sys::out::network::NetworkContext; +use fvm_shared::sys::out::vm::MessageContext; +use fvm_shared::sys::TokenAmount; +use fvm_shared::ActorID; + +pub trait Override { + fn override_with_context(&self, context: &S) -> Self; +} + +#[derive(Default, Debug, Clone)] +pub struct OverrideContext { + /// The Chain ID of the network. + pub chain_id: Option, + + /// The current epoch. + pub epoch: Option, + + /// The UNIX timestamp (in seconds) of the current tipset. + pub timestamp: Option, + + /// The base fee that's in effect when the Machine runs. + pub base_fee: Option, + + /// The current call's origin actor ID. + pub origin: Option, + + /// The caller's actor ID. + pub caller: Option, +} + +impl Override for NetworkContext { + fn override_with_context(&self, context: &OverrideContext) -> NetworkContext { + NetworkContext { + chain_id: context.chain_id.unwrap_or(self.chain_id), + epoch: context.epoch.unwrap_or(self.epoch), + timestamp: context.timestamp.unwrap_or(self.timestamp), + base_fee: context.base_fee.unwrap_or(self.base_fee), + ..*self + } + } +} + +impl Override for MessageContext { + fn override_with_context(&self, context: &OverrideContext) -> MessageContext { + MessageContext { + origin: context.origin.unwrap_or(self.origin), + ..*self + } + } +} diff --git a/fvm/src/executor.rs b/fvm/src/executor.rs index 0101f9a..a24006e 100644 --- a/fvm/src/executor.rs +++ b/fvm/src/executor.rs @@ -4,18 +4,18 @@ use crate::externs::FakeExterns; use crate::machine::KytheraMachine; use cid::Cid; -use fvm::call_manager::DefaultCallManager; use fvm::engine::EnginePool; use fvm::executor::DefaultExecutor; -use fvm::DefaultKernel; +use crate::kernel::KytheraKernel; +use crate::utils::KYTHERA_NETWORK_ID; +pub use fvm::executor::Executor as _; pub use fvm::executor::{ApplyFailure, ApplyKind, ApplyRet}; - -use fvm::executor::Executor as _; -use fvm::machine::{Machine, NetworkConfig}; -use fvm_ipld_blockstore::MemoryBlockstore; +use fvm::machine::{DefaultMachine, Machine, NetworkConfig}; +use fvm_ipld_blockstore::{Buffered, MemoryBlockstore}; use fvm_ipld_encoding::RawBytes; use fvm_shared::address::Address; +use fvm_shared::chainid::ChainID; use fvm_shared::econ::TokenAmount; use fvm_shared::message::Message; use fvm_shared::version::NetworkVersion; @@ -26,9 +26,7 @@ const DEFAULT_BASE_FEE: u64 = 100; /// Wrapper around `fvm` Executor with sane defaults. pub struct KytheraExecutor { - inner: DefaultExecutor< - DefaultKernel>>, - >, + inner: DefaultExecutor, account_address: Address, test_address: Address, target_actor_id: RawBytes, @@ -47,6 +45,10 @@ impl KytheraExecutor { let mut nc = NetworkConfig::new(NETWORK_VERSION); nc.override_actors(builtin_actors); nc.enable_actor_debugging(); + // If chain Id is 0 (invalid value) we set our default + if nc.chain_id == ChainID::from(0) { + nc.chain_id = ChainID::from(KYTHERA_NETWORK_ID) + } let mut mc = nc.for_epoch(0, 0, state_root); mc.set_base_fee(TokenAmount::from_atto(DEFAULT_BASE_FEE)) @@ -61,8 +63,12 @@ impl KytheraExecutor { .preload(&blockstore, &code_cids) .expect("Should be able to preload Executor"); - let machine = KytheraMachine::new(&mc, blockstore, FakeExterns::new()) - .expect("Should be able to start KytheraMachine"); + let machine = KytheraMachine::>::new( + mc, + blockstore, + FakeExterns::new(), + ) + .expect("Should be able to start KytheraMachine"); Self { inner: DefaultExecutor::new(engine, machine).expect("Should be able to start Executor"), @@ -83,7 +89,7 @@ impl KytheraExecutor { to: self.test_address, gas_limit: 1000000000, method_num, - params: self.target_actor_id.clone().into(), + params: self.target_actor_id.clone(), sequence, ..Message::default() }; @@ -99,12 +105,20 @@ impl KytheraExecutor { .flush() .expect("Should be able to flush Executor"); - let blockstore = self + let mut machine: KytheraMachine = self .inner .into_machine() - .expect("Machine should exist at this point") - .into_store() - .into_inner(); - (root, blockstore) + .expect("Machine should exist at this point"); + + machine.state_tree_mut(); + + let buff_blockstore = machine.into_store(); + buff_blockstore + .flush(&root) + .expect("Should be able to flush Buffered Blockstore"); + + let memory_blockstore = buff_blockstore.into_inner(); + + (root, memory_blockstore) } } diff --git a/fvm/src/kernel.rs b/fvm/src/kernel.rs new file mode 100644 index 0000000..ce3e9bc --- /dev/null +++ b/fvm/src/kernel.rs @@ -0,0 +1,402 @@ +use crate::call_manager::KytheraCallManager; +use crate::context::Override; +use crate::machine::KytheraMachine; +use cid::Cid; +use fvm::call_manager::CallManager; +use fvm::gas::{Gas, GasTimer, PriceList}; +use fvm::kernel::{ + ActorOps, BlockId, BlockRegistry, BlockStat, CircSupplyOps, CryptoOps, DebugOps, EventOps, + GasOps, IpldBlockOps, LimiterOps, MessageOps, NetworkOps, RandomnessOps, SelfOps, SendOps, + SendResult, +}; +use fvm::machine::Machine; +use fvm::{DefaultKernel, Kernel}; +use fvm_shared::address::Address; +use fvm_shared::clock::ChainEpoch; +use fvm_shared::consensus::ConsensusFault; +use fvm_shared::crypto::signature::{ + SignatureType, SECP_PUB_LEN, SECP_SIG_LEN, SECP_SIG_MESSAGE_HASH_SIZE, +}; +use fvm_shared::econ::TokenAmount; +use fvm_shared::piece::PieceInfo; +use fvm_shared::randomness::RANDOMNESS_LENGTH; +use fvm_shared::sector::{ + AggregateSealVerifyProofAndInfos, RegisteredSealProof, ReplicaUpdateInfo, SealVerifyInfo, + WindowPoStVerifyInfo, +}; +use fvm_shared::sys::out::network::NetworkContext; +use fvm_shared::sys::out::vm::MessageContext; +use fvm_shared::sys::SendFlags; +use fvm_shared::{ActorID, MethodNum}; + +pub struct KytheraKernel> { + inner: K, +} + +impl Kernel for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + type CallManager = C; + + fn into_inner(self) -> (Self::CallManager, BlockRegistry) + where + Self: Sized, + { + let (kythera_cm, br) = self.inner.into_inner(); + (kythera_cm.0, br) + } + + fn new( + mgr: Self::CallManager, + blocks: BlockRegistry, + caller: ActorID, + actor_id: ActorID, + method: MethodNum, + value_received: TokenAmount, + read_only: bool, + ) -> Self + where + Self: Sized, + { + Self { + inner: K::new( + KytheraCallManager(mgr), + blocks, + caller, + actor_id, + method, + value_received, + read_only, + ), + } + } + + fn machine(&self) -> &::Machine { + self.inner.machine() + } +} + +impl ActorOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn resolve_address(&self, address: &Address) -> fvm::kernel::Result { + self.inner.resolve_address(address) + } + + fn lookup_delegated_address(&self, actor_id: ActorID) -> fvm::kernel::Result> { + self.inner.lookup_delegated_address(actor_id) + } + + fn get_actor_code_cid(&self, id: ActorID) -> fvm::kernel::Result { + self.inner.get_actor_code_cid(id) + } + + fn next_actor_address(&self) -> fvm::kernel::Result
{ + self.inner.next_actor_address() + } + + fn create_actor( + &mut self, + code_cid: Cid, + actor_id: ActorID, + delegated_address: Option
, + ) -> fvm::kernel::Result<()> { + self.inner + .create_actor(code_cid, actor_id, delegated_address) + } + + fn get_builtin_actor_type(&self, code_cid: &Cid) -> fvm::kernel::Result { + self.inner.get_builtin_actor_type(code_cid) + } + + fn get_code_cid_for_type(&self, typ: u32) -> fvm::kernel::Result { + self.inner.get_code_cid_for_type(typ) + } + + fn balance_of(&self, actor_id: ActorID) -> fvm::kernel::Result { + self.inner.balance_of(actor_id) + } +} + +impl IpldBlockOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn block_open(&mut self, cid: &Cid) -> fvm::kernel::Result<(BlockId, BlockStat)> { + self.inner.block_open(cid) + } + + fn block_create(&mut self, codec: u64, data: &[u8]) -> fvm::kernel::Result { + self.inner.block_create(codec, data) + } + + fn block_link( + &mut self, + id: BlockId, + hash_fun: u64, + hash_len: u32, + ) -> fvm::kernel::Result { + self.inner.block_link(id, hash_fun, hash_len) + } + + fn block_read(&self, id: BlockId, offset: u32, buf: &mut [u8]) -> fvm::kernel::Result { + self.inner.block_read(id, offset, buf) + } + + fn block_stat(&self, id: BlockId) -> fvm::kernel::Result { + self.inner.block_stat(id) + } +} + +impl CircSupplyOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn total_fil_circ_supply(&self) -> fvm::kernel::Result { + self.inner.total_fil_circ_supply() + } +} + +impl CryptoOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn verify_signature( + &self, + sig_type: SignatureType, + signature: &[u8], + signer: &Address, + plaintext: &[u8], + ) -> fvm::kernel::Result { + self.inner + .verify_signature(sig_type, signature, signer, plaintext) + } + + fn recover_secp_public_key( + &self, + hash: &[u8; SECP_SIG_MESSAGE_HASH_SIZE], + signature: &[u8; SECP_SIG_LEN], + ) -> fvm::kernel::Result<[u8; SECP_PUB_LEN]> { + self.inner.recover_secp_public_key(hash, signature) + } + + fn hash(&self, code: u64, data: &[u8]) -> fvm::kernel::Result { + self.inner.hash(code, data) + } + + fn compute_unsealed_sector_cid( + &self, + proof_type: RegisteredSealProof, + pieces: &[PieceInfo], + ) -> fvm::kernel::Result { + self.inner.compute_unsealed_sector_cid(proof_type, pieces) + } + + fn verify_seal(&self, vi: &SealVerifyInfo) -> fvm::kernel::Result { + self.inner.verify_seal(vi) + } + + fn verify_post(&self, verify_info: &WindowPoStVerifyInfo) -> fvm::kernel::Result { + self.inner.verify_post(verify_info) + } + + fn verify_consensus_fault( + &self, + h1: &[u8], + h2: &[u8], + extra: &[u8], + ) -> fvm::kernel::Result> { + self.inner.verify_consensus_fault(h1, h2, extra) + } + + fn batch_verify_seals(&self, vis: &[SealVerifyInfo]) -> fvm::kernel::Result> { + self.inner.batch_verify_seals(vis) + } + + fn verify_aggregate_seals( + &self, + aggregate: &AggregateSealVerifyProofAndInfos, + ) -> fvm::kernel::Result { + self.inner.verify_aggregate_seals(aggregate) + } + + fn verify_replica_update(&self, replica: &ReplicaUpdateInfo) -> fvm::kernel::Result { + self.inner.verify_replica_update(replica) + } +} + +impl DebugOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn log(&self, msg: String) { + self.inner.log(msg) + } + + fn debug_enabled(&self) -> bool { + self.inner.debug_enabled() + } + + fn store_artifact(&self, name: &str, data: &[u8]) -> fvm::kernel::Result<()> { + self.inner.store_artifact(name, data) + } +} + +impl EventOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn emit_event(&mut self, raw_evt: &[u8]) -> fvm::kernel::Result<()> { + self.inner.emit_event(raw_evt) + } +} + +impl GasOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn gas_used(&self) -> Gas { + self.inner.gas_used() + } + + fn gas_available(&self) -> Gas { + self.inner.gas_available() + } + + fn charge_gas(&self, name: &str, compute: Gas) -> fvm::kernel::Result { + self.inner.charge_gas(name, compute) + } + + fn price_list(&self) -> &PriceList { + self.inner.price_list() + } +} + +impl MessageOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn msg_context(&self) -> fvm::kernel::Result { + self.inner + .msg_context() + .map(|mc| mc.override_with_context(self.machine().override_context())) + } +} + +impl NetworkOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn network_context(&self) -> fvm::kernel::Result { + self.inner + .network_context() + .map(|nc| nc.override_with_context(self.machine().override_context())) + } + + fn tipset_cid(&self, epoch: ChainEpoch) -> fvm::kernel::Result { + self.inner.tipset_cid(epoch) + } +} + +impl RandomnessOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn get_randomness_from_tickets( + &self, + personalization: i64, + rand_epoch: ChainEpoch, + entropy: &[u8], + ) -> fvm::kernel::Result<[u8; RANDOMNESS_LENGTH]> { + self.inner + .get_randomness_from_tickets(personalization, rand_epoch, entropy) + } + + fn get_randomness_from_beacon( + &self, + personalization: i64, + rand_epoch: ChainEpoch, + entropy: &[u8], + ) -> fvm::kernel::Result<[u8; RANDOMNESS_LENGTH]> { + self.inner + .get_randomness_from_beacon(personalization, rand_epoch, entropy) + } +} + +impl SelfOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn root(&self) -> fvm::kernel::Result { + self.inner.root() + } + + fn set_root(&mut self, root: Cid) -> fvm::kernel::Result<()> { + self.inner.set_root(root) + } + + fn current_balance(&self) -> fvm::kernel::Result { + self.inner.current_balance() + } + + fn self_destruct(&mut self, beneficiary: &Address) -> fvm::kernel::Result<()> { + self.inner.self_destruct(beneficiary) + } +} + +impl SendOps for KytheraKernel +where + M: Machine, + C: CallManager>, + K: Kernel>, +{ + fn send( + &mut self, + recipient: &Address, + method: u64, + params: BlockId, + value: &TokenAmount, + gas_limit: Option, + flags: SendFlags, + ) -> fvm::kernel::Result { + self.inner + .send(recipient, method, params, value, gas_limit, flags) + } +} + +impl LimiterOps for KytheraKernel +where + K: LimiterOps, +{ + type Limiter = K::Limiter; + + fn limiter_mut(&mut self) -> &mut Self::Limiter { + self.inner.limiter_mut() + } +} diff --git a/fvm/src/lib.rs b/fvm/src/lib.rs index d34853d..6aea913 100644 --- a/fvm/src/lib.rs +++ b/fvm/src/lib.rs @@ -10,10 +10,6 @@ pub mod engine { } pub mod executor; -pub mod machine { - pub use fvm::machine::{DefaultMachine as KytheraMachine, Machine, Manifest, NetworkConfig}; -} - pub mod state_tree { pub use fvm::state_tree::ActorState; pub use fvm::state_tree::StateTree; @@ -24,4 +20,9 @@ pub mod trace { } pub type Account = (ActorID, Address); +mod call_manager; +mod context; pub mod externs; +mod kernel; +pub mod machine; +pub(crate) mod utils; diff --git a/fvm/src/machine.rs b/fvm/src/machine.rs new file mode 100644 index 0000000..8ff421d --- /dev/null +++ b/fvm/src/machine.rs @@ -0,0 +1,80 @@ +use crate::context::OverrideContext; +use crate::externs::FakeExterns; +use fvm::machine::MachineContext; +pub use fvm::machine::{DefaultMachine, Machine, Manifest, NetworkConfig}; +use fvm::state_tree::StateTree; +use fvm_ipld_blockstore::MemoryBlockstore; + +pub struct KytheraMachine> { + inner: M, + // We store OverrideContext here as the Kernel is instantiated at every new implicit message + // and the CallManager needs to have only one inner value for the unsafe code in `with_transaction()` + // to work. + pub(crate) override_context: OverrideContext, +} + +impl KytheraMachine +where + M: Machine, +{ + pub fn new( + context: MachineContext, + blockstore: MemoryBlockstore, + externs: FakeExterns, + ) -> anyhow::Result>> { + let machine = DefaultMachine::new(&context, blockstore, externs)?; + Ok(KytheraMachine { + inner: machine, + override_context: OverrideContext::default(), + }) + } + + pub fn override_context(&self) -> &OverrideContext { + &self.override_context + } +} + +impl Machine for KytheraMachine +where + M: Machine, +{ + type Blockstore = M::Blockstore; + type Externs = M::Externs; + type Limiter = M::Limiter; + + fn blockstore(&self) -> &Self::Blockstore { + self.inner.blockstore() + } + + fn context(&self) -> &MachineContext { + self.inner.context() + } + + fn externs(&self) -> &Self::Externs { + self.inner.externs() + } + + fn builtin_actors(&self) -> &Manifest { + self.inner.builtin_actors() + } + + fn state_tree(&self) -> &StateTree { + self.inner.state_tree() + } + + fn state_tree_mut(&mut self) -> &mut StateTree { + self.inner.state_tree_mut() + } + + fn into_store(self) -> Self::Blockstore { + self.inner.into_store() + } + + fn machine_id(&self) -> &str { + self.inner.machine_id() + } + + fn new_limiter(&self) -> Self::Limiter { + self.inner.new_limiter() + } +} diff --git a/fvm/src/utils.rs b/fvm/src/utils.rs new file mode 100644 index 0000000..5438934 --- /dev/null +++ b/fvm/src/utils.rs @@ -0,0 +1,32 @@ +pub const KYTHERA_NETWORK_ID: u64 = 1312; + +pub(crate) const WARP_NUM: u64 = 112632689; +pub(crate) const EPOCH_NUM: u64 = 1015545011; +pub(crate) const FEE_NUM: u64 = 1307676284; +pub(crate) const CHAIN_ID_NUM: u64 = 2832802136; +pub(crate) const PRANK_NUM: u64 = 3950310035; +pub(crate) const TRICK_NUM: u64 = 4270775027; + +#[cfg(test)] +mod test { + use super::*; + use kythera_common::abi::derive_method_num; + + // Cheatcodes methods names + pub(crate) const WARP_METHOD: &str = "Warp"; + pub(crate) const EPOCH_METHOD: &str = "Epoch"; + pub(crate) const FEE_METHOD: &str = "Fee"; + pub(crate) const CHAIN_ID_METHOD: &str = "ChainId"; + pub(crate) const PRANK_METHOD: &str = "Prank"; + pub(crate) const TRICK_METHOD: &str = "Trick"; + + #[test] + fn test_cheatcodes_number() { + assert_eq!(WARP_NUM, derive_method_num(WARP_METHOD).unwrap()); + assert_eq!(EPOCH_NUM, derive_method_num(EPOCH_METHOD).unwrap()); + assert_eq!(FEE_NUM, derive_method_num(FEE_METHOD).unwrap()); + assert_eq!(CHAIN_ID_NUM, derive_method_num(CHAIN_ID_METHOD).unwrap()); + assert_eq!(PRANK_NUM, derive_method_num(PRANK_METHOD).unwrap()); + assert_eq!(TRICK_NUM, derive_method_num(TRICK_METHOD).unwrap()); + } +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index f429db9..a338570 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -32,6 +32,7 @@ fvm_ipld_car = { version = "0.6.0" } fvm_ipld_encoding = { version = "0.3.3" } fvm_shared = { version = "3.1.0", features = ["testing"] } +kythera-actors = { path = "../actors" } kythera-fvm = { path = "../fvm" } kythera-common = { path = "../common" } @@ -44,6 +45,9 @@ rand_chacha = "0.3.1" thiserror = "1.0.39" colored = { version = "2.0.0", optional = true } +[dev-dependencies.kythera-actors] +path = "../actors" +features = ["testing"] + [dev-dependencies] wat = "1.0.51" -kythera_test_actors = { path = "../test_actors" } diff --git a/lib/src/error.rs b/lib/src/error.rs index 548f7f3..bf1c822 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -6,12 +6,12 @@ #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Constructor exit code was not ok")] - ConstructorError { + Constructor { #[source] source: Option>, }, #[error("Setup exit code was not ok")] - SetupError { + Setup { #[source] source: Option>, }, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 84e7405..751ed7c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -8,13 +8,17 @@ pub use kythera_common::{ abi::{pascal_case_split, Abi, Method, MethodType}, from_slice, to_vec, }; -pub use kythera_fvm::executor::ApplyRet; -pub use kythera_fvm::trace::ExecutionEvent; + +pub use kythera_fvm::{ + executor::{ApplyRet, KytheraExecutor}, + trace::ExecutionEvent, + Account, +}; use core::fmt; -use kythera_fvm::{executor::KytheraExecutor, Account}; use std::sync::mpsc::Sender; +use fvm_ipld_encoding::RawBytes; use fvm_shared::{address::Address, bigint::Zero, econ::TokenAmount, error::ExitCode}; use error::Error; @@ -148,6 +152,7 @@ impl Tester { let mut state_tree = StateTree::new(); let builtin_actors = state_tree.load_builtin_actors(); + state_tree.load_kythera_actors(); let account = state_tree.create_account(*builtin_actors.manifest.get_account_code()); Self { @@ -192,7 +197,9 @@ impl Tester { .ok_or(Error::MissingActor)?; let target_id = match target.address.id() { - Ok(id) => id.to_ne_bytes().to_vec(), + Ok(id) => { + RawBytes::new(to_vec(&id).expect("Should be able to serialize target actor ID")) + } Err(_) => panic!("Actor Id should be valid"), }; @@ -226,7 +233,7 @@ impl Tester { self.builtin_actors.root, self.account.1, test_address, - target_id.clone().into(), + target_id.clone(), ); // Run the constructor if it exists. @@ -237,14 +244,14 @@ impl Tester { let source = apply_ret.failure_info.map(|f| f.to_string().into()); return TestActorResults { test_actor, - results: Err(Error::ConstructorError { source }), + results: Err(Error::Constructor { source }), }; } } Err(err) => { return TestActorResults { test_actor, - results: Err(Error::ConstructorError { + results: Err(Error::Constructor { source: Some(err.into()), }), } @@ -260,14 +267,14 @@ impl Tester { let source = apply_ret.failure_info.map(|f| f.to_string().into()); return TestActorResults { test_actor, - results: Err(Error::SetupError { source }), + results: Err(Error::Setup { source }), }; } } Err(err) => { return TestActorResults { test_actor, - results: Err(Error::SetupError { + results: Err(Error::Setup { source: Some(err.into()), }), } @@ -306,7 +313,7 @@ impl Tester { self.builtin_actors.root, self.account.1, test_address, - target_id.clone().into(), + target_id.clone(), ); log::debug!( @@ -360,9 +367,8 @@ impl Default for Tester { #[cfg(test)] mod tests { - use fvm_ipld_blockstore::Blockstore; - use super::*; + use fvm_ipld_blockstore::Blockstore; #[test] fn test_tester_instantiation() { diff --git a/lib/src/state_tree.rs b/lib/src/state_tree.rs index 22945ab..68c3bca 100644 --- a/lib/src/state_tree.rs +++ b/lib/src/state_tree.rs @@ -27,6 +27,8 @@ use fil_actors_runtime_v10::{ }; use fvm_shared::bigint::Zero; use fvm_shared::sector::StoragePower; +use kythera_actors::wasm_bin::CHEATCODES_ACTOR_BINARY; +use kythera_common::abi::Abi; const STATE_TREE_VERSION: StateTreeVersion = StateTreeVersion::V5; @@ -88,6 +90,7 @@ impl StateTree { log::trace!("Setting Actor {} on the BlockStore", name); self.inner.set_actor(id, actor_state); + Ok(()) } @@ -325,6 +328,23 @@ impl StateTree { } } + /// Load Kythera utilities' actors. + pub fn load_kythera_actors(&mut self) { + // Deploy cheatcodes actor. + let cheatcodes_actor = WasmActor::new( + String::from("Cheatcodes"), + CHEATCODES_ACTOR_BINARY.to_vec(), + Abi::default(), + ); + + self.deploy_actor_from_bin_at_address( + &Address::new_id(98u64), + &cheatcodes_actor, + TokenAmount::zero(), + ) + .expect("Should be able to load cheatcodes actor"); + } + /// Creates new accounts in the testing context /// Inserts the account in the state tree, all with the provided balance, returning it and its public key address. pub fn create_account(&mut self, accounts_code_cid: Cid) -> Account { @@ -363,16 +383,14 @@ impl StateTree { (assigned_addr, pub_key_addr) } - /// Deploy a new Actor at a given address, provided with a given token balance - /// and returns the CodeCID of the installed actor - pub fn deploy_actor_from_bin( + /// Deploy a new Actor at a given address, provided with a given token balance and returns the + /// CodeCID of the installed actor. + fn deploy_actor_from_bin_at_address( &mut self, + address: &Address, actor: &WasmActor, balance: TokenAmount, - ) -> Result { - let actor_id = rand::random(); - let actor_address = Address::new_id(actor_id); - + ) -> Result<(), Error> { // Put the WASM code into the blockstore. log::debug!("Deploying Actor {} code", actor.name); let code_cid = self @@ -388,8 +406,32 @@ impl StateTree { .setting_err(&actor.name)?; // Set the Actor State on the `BlockStore`. - self.set_actor(&actor.name, [(); 0], code_cid, actor_id, 0, balance)?; + self.set_actor( + &actor.name, + [(); 0], + code_cid, + address + .id() + .expect("Should be able to get actor Id from address"), + 0, + balance, + ) + } - Ok(actor_address) + /// Deploy a new Actor provided with a given token balance and returns the CodeCID of the + /// installed actor. + pub fn deploy_actor_from_bin( + &mut self, + actor: &WasmActor, + balance: TokenAmount, + ) -> Result { + let actor_address = Address::new_actor(actor.name.as_bytes()); + let actor_id = self + .inner + .register_new_address(&actor_address) + .expect("Should be able to register verified registry multisig root address"); + let actor_address_id = Address::new_id(actor_id); + self.deploy_actor_from_bin_at_address(&actor_address_id, actor, balance)?; + Ok(actor_address_id) } } diff --git a/lib/tests/lib.rs b/lib/tests/lib.rs index 18b1ebb..e8b7eec 100644 --- a/lib/tests/lib.rs +++ b/lib/tests/lib.rs @@ -1,9 +1,12 @@ use fvm_shared::error::ExitCode; +use kythera_actors::wasm_bin::test_actors::{ + BASIC_TARGET_ACTOR_BINARY, BASIC_TEST_ACTOR_BINARY, BUILTINS_TEST_ACTOR_BINARY, + CHEATCODES_TEST_ACTOR_BINARY, CONSTRUCTOR_SETUP_TEST_ACTOR_BINARY, +}; use kythera_common::abi::{Abi, Method, MethodType}; +use kythera_fvm::executor::ApplyFailure::MessageBacktrace; use kythera_lib::{TestResultType, Tester, WasmActor}; -use kythera_test_actors::wasm_bin::{ - BASIC_TEST_ACTOR_BINARY, BUILTIN_TEST_ACTOR_BINARY, CONSTRUCTOR_SETUP_TEST_ACTOR_BINARY, -}; + const TARGET_WAT: &str = r#" ;; Mock invoke function (module @@ -102,7 +105,7 @@ fn test_builtin_deployed() { ); // Set test actor - let test_wasm_bin: Vec = Vec::from(BUILTIN_TEST_ACTOR_BINARY); + let test_wasm_bin: Vec = Vec::from(BUILTINS_TEST_ACTOR_BINARY); let test_abi = Abi { constructor: None, set_up: None, @@ -145,7 +148,6 @@ fn test_constructor_and_set_up_called() { let mut tester = Tester::new(); // Set target actor - set_target_actor( &mut tester, String::from("Target"), @@ -190,3 +192,117 @@ fn test_constructor_and_set_up_called() { } } } + +macro_rules! generate_match_assert { + ($apply_failure:expr, $result:expr, $($test_name:expr => $test_message:expr),*) => {{ + match $apply_failure { + MessageBacktrace(backtrace) => { + match $result.method().name() { + $($test_name => { + assert!(backtrace.to_string().contains($test_message)); + })* + _ => {} + } + }, + _ => { + panic!("Failure should be a MessageBacktrace variant for failing tests"); + } + } + }}; +} + +#[test] +fn test_cheatcodes() { + // Instantiate tester + let mut tester = Tester::new(); + + // Set target actor + set_target_actor( + &mut tester, + String::from("Target"), + Vec::from(BASIC_TARGET_ACTOR_BINARY), + Abi { + constructor: None, + set_up: None, + methods: vec![ + Method::new_from_name("Caller").unwrap(), + Method::new_from_name("Origin").unwrap(), + ], + }, + ); + + // Set test actor + let test_wasm_bin: Vec = Vec::from(CHEATCODES_TEST_ACTOR_BINARY); + let test_abi = Abi { + constructor: None, + set_up: None, + methods: vec![ + Method::new_from_name("TestWarp").unwrap(), + Method::new_from_name("TestFailDeserializationWarp").unwrap(), + Method::new_from_name("TestFailNoParametersWarp").unwrap(), + Method::new_from_name("TestEpoch").unwrap(), + Method::new_from_name("TestFailDeserializationEpoch").unwrap(), + Method::new_from_name("TestFailNoParametersEpoch").unwrap(), + Method::new_from_name("TestFee").unwrap(), + Method::new_from_name("TestFailDeserializationFee").unwrap(), + Method::new_from_name("TestFailNoParametersFee").unwrap(), + Method::new_from_name("TestChainId").unwrap(), + Method::new_from_name("TestFailDeserializationChainId").unwrap(), + Method::new_from_name("TestFailNoParametersChainId").unwrap(), + Method::new_from_name("TestPrank").unwrap(), + Method::new_from_name("TestFailDeserializationPrank").unwrap(), + Method::new_from_name("TestFailNoParametersPrank").unwrap(), + Method::new_from_name("TestFailAddressTypePrank").unwrap(), + Method::new_from_name("TestTrick").unwrap(), + Method::new_from_name("TestFailDeserializationTrick").unwrap(), + Method::new_from_name("TestFailNoParametersTrick").unwrap(), + Method::new_from_name("TestFailAddressTypeTrick").unwrap(), + ], + }; + let test_actor = WasmActor::new(String::from("Cheatcodes Tests"), test_wasm_bin, test_abi); + + match tester.test(&[test_actor.clone()], None) { + Err(_) => { + panic!("Could not run test when testing Tester") + } + Ok(test_res) => test_res[0] + .results + .as_ref() + .unwrap() + .iter() + .for_each(|result| match (result.method().r#type(), result.ret()) { + (MethodType::TestFail, TestResultType::Failed(apply_ret)) => { + assert_eq!( + apply_ret.msg_receipt.exit_code, + ExitCode::SYS_ASSERTION_FAILED + ); + let apply_failure = apply_ret.failure_info.clone().unwrap(); + + generate_match_assert!( + apply_failure, + result, + "TestFailDeserializationWarp" => "Could not deserialize parameters for Warp cheatcode", + "TestFailNoParametersWarp" => "No parameters provided for Warp cheatcode", + "TestFailDeserializationEpoch" => "Could not deserialize parameters for Epoch cheatcode", + "TestFailNoParametersEpoch" => "No parameters provided for Epoch cheatcode", + "TestFailDeserializationFee" => "Could not deserialize parameters for Fee cheatcode", + "TestFailNoParametersFee" => "No parameters provided for Fee cheatcode", + "TestFailDeserializationChainId" => "Could not deserialize parameters for ChainId cheatcode", + "TestFailNoParametersChainId" => "No parameters provided for ChainId cheatcode", + "TestFailDeserializationPrank" => "Could not deserialize parameters for Prank cheatcode", + "TestFailNoParametersPrank" => "No parameters provided for Prank cheatcode", + "TestFailAddressTypePrank" => "Address parameter for Prank should have a valid ActorID", + "TestFailDeserializationTrick" => "Could not deserialize parameters for Trick cheatcode", + "TestFailNoParametersTrick" => "No parameters provided for Trick cheatcode", + "TestFailAddressTypeTrick" => "Address parameter for Trick should have a valid ActorID" + ); + } + (MethodType::Test, TestResultType::Passed(apply_ret)) => { + assert_eq!(apply_ret.msg_receipt.exit_code, ExitCode::OK); + } + apply_ret => { + panic!("test against cheatcodes test actor should be valid: {apply_ret:?}") + } + }), + } +} diff --git a/test_actors/actors/failed_test_actor/README.md b/test_actors/actors/failed_test_actor/README.md deleted file mode 100644 index 671747a..0000000 --- a/test_actors/actors/failed_test_actor/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## Failed test Actor - -This is an actor that fails a test assertion, used to test the CLI output. diff --git a/test_actors/actors/failed_test_actor/src/actor.rs b/test_actors/actors/failed_test_actor/src/actor.rs deleted file mode 100644 index 5f86aab..0000000 --- a/test_actors/actors/failed_test_actor/src/actor.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2023 Polyphene. -// SPDX-License-Identifier: Apache-2.0, MIT - -use frc42_dispatch::match_method; -use fvm_sdk as sdk; -use fvm_sdk::NO_DATA_BLOCK_ID; -use fvm_shared::error::ExitCode; - -#[no_mangle] -fn invoke(_input: u32) -> u32 { - std::panic::set_hook(Box::new(|info| { - sdk::vm::exit( - ExitCode::USR_ASSERTION_FAILED.value(), - None, - Some(&format!("{info}")), - ) - })); - - let method_num = sdk::message::method_number(); - match_method!( - method_num, - { - "TestFailed" => { - TestFailed(); - - NO_DATA_BLOCK_ID - }, - _ => { - sdk::vm::abort( - ExitCode::USR_UNHANDLED_MESSAGE.value(), - Some("Unknown method number"), - ); - } - } - ) -} - -// Checks that all relevant builtins are deployed at a correct actor Id in Kythera -#[allow(non_snake_case)] -fn TestFailed() { - assert_eq!(1 + 1, 3); -} diff --git a/test_actors/src/wasm_bin/mod.rs b/test_actors/src/wasm_bin/mod.rs deleted file mode 100644 index b4bbd9d..0000000 --- a/test_actors/src/wasm_bin/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023 Polyphene. -// SPDX-License-Identifier: Apache-2.0, MIT -// Constants for wasm build artifacts. - -macro_rules! wasm_bin { - ($x: expr) => { - concat!( - env!("OUT_DIR"), - "/bundle/wasm32-unknown-unknown/wasm/", - $x, - ".wasm" - ) - }; -} - -// Integration test actors. -pub const BASIC_TEST_ACTOR_BINARY: &[u8] = include_bytes!(wasm_bin!("basic_test_actor")); -pub const CONSTRUCTOR_SETUP_TEST_ACTOR_BINARY: &[u8] = - include_bytes!(wasm_bin!("constructor_setup_test_actor")); -pub const BUILTIN_TEST_ACTOR_BINARY: &[u8] = include_bytes!(wasm_bin!("builtin_test_actor")); -pub const FAILED_TEST_ACTOR_BINARY: &[u8] = include_bytes!(wasm_bin!("failed_test_actor"));