diff --git a/Cargo.lock b/Cargo.lock index 41e96f8db..e05fe290c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1827,6 +1827,19 @@ dependencies = [ "fvm_shared 4.0.0", ] +[[package]] +name = "fil_custom_syscall_actor" +version = "0.1.0" +dependencies = [ + "cid 0.10.1", + "fvm_ipld_encoding 0.4.0", + "fvm_sdk 4.0.0", + "fvm_shared 4.0.0", + "num-traits", + "serde", + "serde_tuple", +] + [[package]] name = "fil_events_actor" version = "0.1.0" @@ -2445,6 +2458,7 @@ dependencies = [ name = "fvm_integration_tests" version = "4.0.0" dependencies = [ + "ambassador", "anyhow", "blake2b_simd", "bls-signatures", @@ -2456,6 +2470,7 @@ dependencies = [ "fvm_ipld_blockstore 0.2.0", "fvm_ipld_car 0.7.1", "fvm_ipld_encoding 0.4.0", + "fvm_sdk 4.0.0", "fvm_shared 4.0.0", "fvm_test_actors", "hex", diff --git a/Cargo.toml b/Cargo.toml index b18dfa44b..3ade4472f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ ahash = "0.8.3" itertools = "0.11.0" once_cell = "1.18.0" unsigned-varint = "0.7.2" +ambassador = "0.3.5" # dev/tools/tests criterion = "0.5.1" diff --git a/fvm/Cargo.toml b/fvm/Cargo.toml index 55b247af2..2faa140f1 100644 --- a/fvm/Cargo.toml +++ b/fvm/Cargo.toml @@ -36,6 +36,7 @@ once_cell = { workspace = true } minstant = { workspace = true } blake2b_simd = { workspace = true } byteorder = { workspace = true } +ambassador = { workspace = true } derive_more = "0.99.17" replace_with = "0.1.7" filecoin-proofs-api = { version = "16", default-features = false } @@ -44,7 +45,6 @@ num_cpus = "1.15.0" fvm-wasm-instrument = "0.4.0" yastl = "0.1.2" static_assertions = "1.1.0" -ambassador = "0.3.5" [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/testing/integration/Cargo.toml b/testing/integration/Cargo.toml index 37c80364c..e54b968e7 100644 --- a/testing/integration/Cargo.toml +++ b/testing/integration/Cargo.toml @@ -13,6 +13,7 @@ fvm_shared = { workspace = true, features = ["testing"] } fvm_ipld_car = { workspace = true } fvm_ipld_blockstore = { workspace = true } fvm_ipld_encoding = { workspace = true } +fvm_sdk = { workspace = true } anyhow = { workspace = true } cid = { workspace = true } @@ -26,6 +27,7 @@ rand_chacha = { workspace = true } serde = { workspace = true } serde_tuple = { workspace = true } thiserror = { workspace = true } +ambassador = { workspace = true } wasmtime = { workspace = true, default-features = false, features = ["cranelift", "parallel-compilation"] } [dev-dependencies] diff --git a/testing/integration/src/custom_kernel/mod.rs b/testing/integration/src/custom_kernel/mod.rs new file mode 100644 index 000000000..d84f1ce23 --- /dev/null +++ b/testing/integration/src/custom_kernel/mod.rs @@ -0,0 +1,201 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +use fvm::call_manager::CallManager; +use fvm::gas::Gas; +use fvm::kernel::filecoin::{DefaultFilecoinKernel, FilecoinKernel}; +use fvm::kernel::prelude::*; +use fvm::kernel::Result; +use fvm::kernel::{ + ActorOps, CryptoOps, DebugOps, EventOps, IpldBlockOps, MessageOps, NetworkOps, RandomnessOps, + SelfOps, SendOps, SyscallHandler, UpgradeOps, +}; +use fvm::syscalls::Linker; +use fvm_shared::clock::ChainEpoch; +use fvm_shared::consensus::ConsensusFault; +use fvm_shared::piece::PieceInfo; +use fvm_shared::randomness::RANDOMNESS_LENGTH; +use fvm_shared::sector::{ + AggregateSealVerifyProofAndInfos, RegisteredSealProof, ReplicaUpdateInfo, SealVerifyInfo, +}; +use fvm_shared::sys::out::network::NetworkContext; +use fvm_shared::sys::out::vm::MessageContext; +use fvm_shared::{address::Address, econ::TokenAmount, ActorID, MethodNum}; + +use ambassador::Delegate; +use cid::Cid; + +// we define a single custom syscall which simply doubles the input +pub trait CustomKernel: Kernel { + fn my_custom_syscall(&self, doubleme: i32) -> Result; +} + +// our custom kernel extends the filecoin kernel +#[derive(Delegate)] +#[delegate(IpldBlockOps, where = "C: CallManager")] +#[delegate(ActorOps, where = "C: CallManager")] +#[delegate(CryptoOps, where = "C: CallManager")] +#[delegate(DebugOps, where = "C: CallManager")] +#[delegate(EventOps, where = "C: CallManager")] +#[delegate(MessageOps, where = "C: CallManager")] +#[delegate(NetworkOps, where = "C: CallManager")] +#[delegate(RandomnessOps, where = "C: CallManager")] +#[delegate(SelfOps, where = "C: CallManager")] +#[delegate(SendOps, generics = "K", where = "K: CustomKernel")] +#[delegate(UpgradeOps, generics = "K", where = "K: CustomKernel")] +pub struct DefaultCustomKernel(pub DefaultFilecoinKernel); + +impl CustomKernel for DefaultCustomKernel +where + C: CallManager, + DefaultCustomKernel: Kernel, +{ + fn my_custom_syscall(&self, doubleme: i32) -> Result { + Ok(doubleme * 2) + } +} + +impl DefaultCustomKernel +where + C: CallManager, +{ + fn price_list(&self) -> &PriceList { + (self.0).0.call_manager.price_list() + } +} + +impl Kernel for DefaultCustomKernel +where + C: CallManager, +{ + type CallManager = C; + type Limiter = as Kernel>::Limiter; + + fn into_inner(self) -> (Self::CallManager, BlockRegistry) + where + Self: Sized, + { + self.0.into_inner() + } + + fn new( + mgr: C, + blocks: BlockRegistry, + caller: ActorID, + actor_id: ActorID, + method: MethodNum, + value_received: TokenAmount, + read_only: bool, + ) -> Self { + DefaultCustomKernel(DefaultFilecoinKernel::new( + mgr, + blocks, + caller, + actor_id, + method, + value_received, + read_only, + )) + } + + fn machine(&self) -> &::Machine { + self.0.machine() + } + + fn limiter_mut(&mut self) -> &mut Self::Limiter { + self.0.limiter_mut() + } + + fn gas_available(&self) -> Gas { + self.0.gas_available() + } + + fn charge_gas(&self, name: &str, compute: Gas) -> Result { + self.0.charge_gas(name, compute) + } +} + +impl FilecoinKernel for DefaultCustomKernel +where + C: CallManager, +{ + fn compute_unsealed_sector_cid( + &self, + proof_type: RegisteredSealProof, + pieces: &[PieceInfo], + ) -> Result { + self.0.compute_unsealed_sector_cid(proof_type, pieces) + } + + fn verify_post(&self, verify_info: &fvm_shared::sector::WindowPoStVerifyInfo) -> Result { + self.0.verify_post(verify_info) + } + + // NOT forwarded + fn batch_verify_seals(&self, vis: &[SealVerifyInfo]) -> Result> { + Ok(vec![true; vis.len()]) + } + + // NOT forwarded + fn verify_consensus_fault( + &self, + h1: &[u8], + h2: &[u8], + extra: &[u8], + ) -> Result> { + let charge = self + .price_list() + .on_verify_consensus_fault(h1.len(), h2.len(), extra.len()); + let _ = self.charge_gas(&charge.name, charge.total())?; + Ok(None) + } + + // NOT forwarded + fn verify_aggregate_seals(&self, agg: &AggregateSealVerifyProofAndInfos) -> Result { + let charge = self.price_list().on_verify_aggregate_seals(agg); + let _ = self.charge_gas(&charge.name, charge.total())?; + Ok(true) + } + + // NOT forwarded + fn verify_replica_update(&self, rep: &ReplicaUpdateInfo) -> Result { + let charge = self.price_list().on_verify_replica_update(rep); + let _ = self.charge_gas(&charge.name, charge.total())?; + Ok(true) + } + + fn total_fil_circ_supply(&self) -> Result { + self.0.total_fil_circ_supply() + } +} + +impl SyscallHandler for DefaultCustomKernel +where + K: CustomKernel + + FilecoinKernel + + ActorOps + + SendOps + + UpgradeOps + + IpldBlockOps + + CryptoOps + + DebugOps + + EventOps + + MessageOps + + NetworkOps + + RandomnessOps + + SelfOps, +{ + fn link_syscalls(linker: &mut Linker) -> anyhow::Result<()> { + DefaultFilecoinKernel::::link_syscalls(linker)?; + + linker.link_syscall("my_custom_kernel", "my_custom_syscall", my_custom_syscall)?; + + Ok(()) + } +} + +pub fn my_custom_syscall( + context: fvm::syscalls::Context<'_, impl CustomKernel>, + doubleme: i32, +) -> Result { + context.kernel.my_custom_syscall(doubleme) +} diff --git a/testing/integration/src/lib.rs b/testing/integration/src/lib.rs index 706162647..835a5e32e 100644 --- a/testing/integration/src/lib.rs +++ b/testing/integration/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT mod builtin; pub mod bundle; +pub mod custom_kernel; pub mod dummy; pub mod error; pub mod tester; diff --git a/testing/integration/src/tester.rs b/testing/integration/src/tester.rs index af5b229ab..bb4f28f56 100644 --- a/testing/integration/src/tester.rs +++ b/testing/integration/src/tester.rs @@ -6,7 +6,6 @@ use fvm::call_manager::DefaultCallManager; use fvm::engine::EnginePool; use fvm::executor::DefaultExecutor; use fvm::externs::Externs; -use fvm::kernel::filecoin::DefaultFilecoinKernel; use fvm::machine::{DefaultMachine, Machine, MachineContext, NetworkConfig}; use fvm::state_tree::{ActorState, StateTree}; use fvm::{init_actor, system_actor}; @@ -24,6 +23,7 @@ use multihash::Code; use crate::builtin::{ fetch_builtin_code_cid, set_burnt_funds_account, set_eam_actor, set_init_actor, set_sys_actor, }; +use crate::custom_kernel::DefaultCustomKernel; use crate::dummy::DummyExterns; use crate::error::Error::{FailedToFlushTree, NoManifestInformation}; @@ -36,7 +36,7 @@ lazy_static! { pub trait Store: Blockstore + Sized + 'static {} pub type IntegrationExecutor = - DefaultExecutor>>>; + DefaultExecutor>>>; pub type Account = (ActorID, Address); @@ -298,7 +298,7 @@ where let machine = DefaultMachine::new(&mc, blockstore, externs)?; let executor = DefaultExecutor::< - DefaultFilecoinKernel>>, + DefaultCustomKernel>>, >::new(engine, machine)?; self.executor = Some(executor); diff --git a/testing/integration/tests/main.rs b/testing/integration/tests/main.rs index d2345f5ec..0b83f0a5d 100644 --- a/testing/integration/tests/main.rs +++ b/testing/integration/tests/main.rs @@ -20,9 +20,9 @@ use fvm_shared::message::Message; use fvm_shared::state::StateTreeVersion; use fvm_shared::version::NetworkVersion; use fvm_test_actors::wasm_bin::{ - ADDRESS_ACTOR_BINARY, CREATE_ACTOR_BINARY, EXIT_DATA_ACTOR_BINARY, HELLO_WORLD_ACTOR_BINARY, - IPLD_ACTOR_BINARY, OOM_ACTOR_BINARY, READONLY_ACTOR_BINARY, SSELF_ACTOR_BINARY, - STACK_OVERFLOW_ACTOR_BINARY, SYSCALL_ACTOR_BINARY, UPGRADE_ACTOR_BINARY, + ADDRESS_ACTOR_BINARY, CREATE_ACTOR_BINARY, CUSTOM_SYSCALL_ACTOR_BINARY, EXIT_DATA_ACTOR_BINARY, + HELLO_WORLD_ACTOR_BINARY, IPLD_ACTOR_BINARY, OOM_ACTOR_BINARY, READONLY_ACTOR_BINARY, + SSELF_ACTOR_BINARY, STACK_OVERFLOW_ACTOR_BINARY, SYSCALL_ACTOR_BINARY, UPGRADE_ACTOR_BINARY, UPGRADE_RECEIVE_ACTOR_BINARY, }; use num_traits::Zero; @@ -1108,6 +1108,57 @@ fn readonly_actor_tests() { assert!(res.msg_receipt.events_root.is_none()); } +#[test] +fn custom_syscall() { + // Instantiate tester + let mut tester = new_tester( + NetworkVersion::V21, + StateTreeVersion::V5, + MemoryBlockstore::default(), + ) + .unwrap(); + + let [(_sender_id, sender_address)] = tester.create_accounts().unwrap(); + + let wasm_bin = CUSTOM_SYSCALL_ACTOR_BINARY; + + // Set actor state + let actor_state = [(); 0]; + let state_cid = tester.set_state(&actor_state).unwrap(); + + // Set actor + let actor_address = Address::new_id(10000); + + tester + .set_actor_from_bin(wasm_bin, state_cid, actor_address, TokenAmount::zero()) + .unwrap(); + + // Instantiate machine + tester.instantiate_machine(DummyExterns).unwrap(); + + let executor = tester.executor.as_mut().unwrap(); + + let message = Message { + from: sender_address, + to: actor_address, + gas_limit: 1000000000, + method_num: 1, + sequence: 0, + value: TokenAmount::from_atto(100), + ..Message::default() + }; + + let res = executor + .execute_message(message, ApplyKind::Explicit, 100) + .unwrap(); + + assert!( + res.msg_receipt.exit_code.is_success(), + "{:?}", + res.failure_info + ); +} + #[test] fn upgrade_actor_test() { // inline function to calculate cid from address diff --git a/testing/test_actors/actors/fil-custom-syscall/Cargo.toml b/testing/test_actors/actors/fil-custom-syscall/Cargo.toml new file mode 100644 index 000000000..ffd683dfd --- /dev/null +++ b/testing/test_actors/actors/fil-custom-syscall/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "fil_custom_syscall_actor" +version = "0.1.0" +edition = "2021" +publish = false + +[target.'cfg(target_arch = "wasm32")'.dependencies] +fvm_sdk = { workspace = true } +fvm_shared = { workspace = true } +fvm_ipld_encoding = { workspace = true } +cid = { workspace = true } +serde = { workspace = true } +serde_tuple = { workspace = true } +num-traits = { workspace = true } + +[lib] +crate-type = ["cdylib"] ## cdylib is necessary for Wasm build diff --git a/testing/test_actors/actors/fil-custom-syscall/src/actor.rs b/testing/test_actors/actors/fil-custom-syscall/src/actor.rs new file mode 100644 index 000000000..be08a02b1 --- /dev/null +++ b/testing/test_actors/actors/fil-custom-syscall/src/actor.rs @@ -0,0 +1,18 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +fvm_sdk::sys::fvm_syscalls! { + module = "my_custom_kernel"; + pub fn my_custom_syscall(doubleme: i32) -> Result; +} + +#[no_mangle] +pub fn invoke(_: u32) -> u32 { + fvm_sdk::initialize(); + + unsafe { + let value = my_custom_syscall(11).unwrap(); + assert_eq!(value, 22, "expected 22, got {}", value); + } + + 0 +} diff --git a/testing/test_actors/actors/fil-custom-syscall/src/lib.rs b/testing/test_actors/actors/fil-custom-syscall/src/lib.rs new file mode 100644 index 000000000..1d4daedef --- /dev/null +++ b/testing/test_actors/actors/fil-custom-syscall/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +#[cfg(target_arch = "wasm32")] +mod actor; diff --git a/testing/test_actors/build.rs b/testing/test_actors/build.rs index a3f118cb7..d5fdfa71f 100644 --- a/testing/test_actors/build.rs +++ b/testing/test_actors/build.rs @@ -33,6 +33,7 @@ const ACTORS: &[(&str, &str)] = &[ ("SSELF_ACTOR_BINARY", "fil_sself_actor"), ("UPGRADE_ACTOR_BINARY", "fil_upgrade_actor"), ("UPGRADE_RECEIVE_ACTOR_BINARY", "fil_upgrade_receive_actor"), + ("CUSTOM_SYSCALL_ACTOR_BINARY", "fil_custom_syscall_actor"), ]; const WASM_TARGET: &str = "wasm32-unknown-unknown";