From c83674a1d3736d1dba9d8e38c032e7606dc57de3 Mon Sep 17 00:00:00 2001 From: Alex <445306+anorth@users.noreply.github.com> Date: Thu, 18 Aug 2022 08:20:46 +1000 Subject: [PATCH] Implement could-be-standard token receiver hook in account and multisig (#555) --- actors/account/src/lib.rs | 22 +++- actors/account/tests/account_actor_test.rs | 131 ++++++++++--------- actors/multisig/src/lib.rs | 21 ++- actors/multisig/tests/multisig_actor_test.rs | 21 ++- runtime/src/builtin/shared.rs | 6 + 5 files changed, 136 insertions(+), 65 deletions(-) diff --git a/actors/account/src/lib.rs b/actors/account/src/lib.rs index fc7643cf69..c8fed8c98c 100644 --- a/actors/account/src/lib.rs +++ b/actors/account/src/lib.rs @@ -9,9 +9,9 @@ use num_derive::FromPrimitive; use num_traits::FromPrimitive; use fil_actors_runtime::builtin::singletons::SYSTEM_ACTOR_ADDR; -use fil_actors_runtime::cbor; use fil_actors_runtime::runtime::{ActorCode, Runtime}; use fil_actors_runtime::{actor_error, ActorError}; +use fil_actors_runtime::{cbor, FUNGIBLE_TOKEN_RECEIVER_HOOK_METHOD_NUM}; pub use self::state::State; @@ -21,14 +21,13 @@ pub mod testing; #[cfg(feature = "fil-actor")] fil_actors_runtime::wasm_trampoline!(Actor); -// * Updated to specs-actors commit: 845089a6d2580e46055c24415a6c32ee688e5186 (v3.0.0) - /// Account actor methods available #[derive(FromPrimitive)] #[repr(u64)] pub enum Method { Constructor = METHOD_CONSTRUCTOR, PubkeyAddress = 2, + FungibleTokenReceiverHook = FUNGIBLE_TOKEN_RECEIVER_HOOK_METHOD_NUM, } /// Account Actor @@ -62,6 +61,19 @@ impl Actor { let st: State = rt.state()?; Ok(st.address) } + + // Always succeeds, accepting any token transfers. + pub fn fungible_token_receiver_hook( + rt: &mut RT, + _params: &RawBytes, + ) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + Ok(()) + } } impl ActorCode for Actor { @@ -83,6 +95,10 @@ impl ActorCode for Actor { let addr = Self::pubkey_address(rt)?; Ok(RawBytes::serialize(addr)?) } + Some(Method::FungibleTokenReceiverHook) => { + Self::fungible_token_receiver_hook(rt, params)?; + Ok(RawBytes::default()) + } None => Err(actor_error!(unhandled_message; "Invalid method")), } } diff --git a/actors/account/tests/account_actor_test.rs b/actors/account/tests/account_actor_test.rs index cef0a073b7..a7357265da 100644 --- a/actors/account/tests/account_actor_test.rs +++ b/actors/account/tests/account_actor_test.rs @@ -1,76 +1,87 @@ // Copyright 2019-2022 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use fil_actor_account::{testing::check_state_invariants, Actor as AccountActor, State}; -use fil_actors_runtime::builtin::SYSTEM_ACTOR_ADDR; -use fil_actors_runtime::test_utils::*; use fvm_ipld_encoding::RawBytes; use fvm_shared::address::Address; use fvm_shared::error::ExitCode; +use fvm_shared::MethodNum; -fn check_state(rt: &MockRuntime) { - let test_address = Address::new_id(1000); - let (_, acc) = check_state_invariants(&rt.get_state(), &test_address); - acc.assert_empty(); -} +use fil_actor_account::{testing::check_state_invariants, Actor as AccountActor, Method, State}; +use fil_actors_runtime::builtin::SYSTEM_ACTOR_ADDR; +use fil_actors_runtime::test_utils::*; + +#[test] +fn construction() { + fn construct(addr: Address, exit_code: ExitCode) { + let mut rt = MockRuntime { + receiver: Address::new_id(100), + caller: *SYSTEM_ACTOR_ADDR, + caller_type: *SYSTEM_ACTOR_CODE_ID, + ..Default::default() + }; + rt.expect_validate_caller_addr(vec![*SYSTEM_ACTOR_ADDR]); -macro_rules! account_tests { - ($($name:ident: $value:expr,)*) => { - $( - #[test] - fn $name() { - let (addr, exit_code) = $value; + if exit_code.is_success() { + rt.call::( + Method::Constructor as MethodNum, + &RawBytes::serialize(addr).unwrap(), + ) + .unwrap(); - let mut rt = MockRuntime { - receiver: fvm_shared::address::Address::new_id(100), - caller: SYSTEM_ACTOR_ADDR.clone(), - caller_type: SYSTEM_ACTOR_CODE_ID.clone(), - ..Default::default() - }; - rt.expect_validate_caller_addr(vec![*SYSTEM_ACTOR_ADDR]); + let state: State = rt.get_state(); + assert_eq!(state.address, addr); + rt.expect_validate_caller_any(); - if exit_code.is_success() { - rt.call::(1, &RawBytes::serialize(addr).unwrap()).unwrap(); + let pk: Address = rt + .call::(Method::PubkeyAddress as MethodNum, &RawBytes::default()) + .unwrap() + .deserialize() + .unwrap(); + assert_eq!(pk, addr); + check_state(&rt); + } else { + expect_abort(exit_code, rt.call::(1, &RawBytes::serialize(addr).unwrap())) + } + rt.verify(); + } - let state: State = rt.get_state(); - assert_eq!(state.address, addr); - rt.expect_validate_caller_any(); + construct( + Address::new_secp256k1(&[2; fvm_shared::address::SECP_PUB_LEN]).unwrap(), + ExitCode::OK, + ); + construct(Address::new_bls(&[1; fvm_shared::address::BLS_PUB_LEN]).unwrap(), ExitCode::OK); + construct(Address::new_id(1), ExitCode::USR_ILLEGAL_ARGUMENT); + construct(Address::new_actor(&[1, 2, 3]), ExitCode::USR_ILLEGAL_ARGUMENT); +} - let pk: Address = rt - .call::(2, &RawBytes::default()) - .unwrap() - .deserialize() - .unwrap(); - assert_eq!(pk, addr); +#[test] +fn token_receiver() { + let mut rt = MockRuntime { + receiver: Address::new_id(100), + caller: *SYSTEM_ACTOR_ADDR, + caller_type: *SYSTEM_ACTOR_CODE_ID, + ..Default::default() + }; + rt.expect_validate_caller_addr(vec![*SYSTEM_ACTOR_ADDR]); - check_state(&rt); - } else { - expect_abort( - exit_code, - rt.call::(1,&RawBytes::serialize(addr).unwrap()) - ) - } - rt.verify(); - } - )* - } + let param = Address::new_secp256k1(&[2; fvm_shared::address::SECP_PUB_LEN]).unwrap(); + rt.call::( + Method::Constructor as MethodNum, + &RawBytes::serialize(¶m).unwrap(), + ) + .unwrap(); + + rt.expect_validate_caller_any(); + let ret = rt.call::( + Method::FungibleTokenReceiverHook as MethodNum, + &RawBytes::new(vec![1, 2, 3]), + ); + assert!(ret.is_ok()); + assert_eq!(RawBytes::default(), ret.unwrap()); } -account_tests! { - happy_construct_secp256k1_address: ( - Address::new_secp256k1(&[2; fvm_shared::address::SECP_PUB_LEN]).unwrap(), - ExitCode::OK - ), - happy_construct_bls_address: ( - Address::new_bls(&[1; fvm_shared::address::BLS_PUB_LEN]).unwrap(), - ExitCode::OK - ), - fail_construct_id_address: ( - Address::new_id(1), - ExitCode::USR_ILLEGAL_ARGUMENT - ), - fail_construct_actor_address: ( - Address::new_actor(&[1, 2, 3]), - ExitCode::USR_ILLEGAL_ARGUMENT - ), +fn check_state(rt: &MockRuntime) { + let test_address = Address::new_id(1000); + let (_, acc) = check_state_invariants(&rt.get_state(), &test_address); + acc.assert_empty(); } diff --git a/actors/multisig/src/lib.rs b/actors/multisig/src/lib.rs index 7c06c15208..b23353e5f3 100644 --- a/actors/multisig/src/lib.rs +++ b/actors/multisig/src/lib.rs @@ -7,7 +7,8 @@ use fil_actors_runtime::cbor::serialize_vec; use fil_actors_runtime::runtime::{ActorCode, Primitives, Runtime}; use fil_actors_runtime::{ actor_error, cbor, make_empty_map, make_map_with_root, resolve_to_id_addr, ActorDowncast, - ActorError, Map, CALLER_TYPES_SIGNABLE, INIT_ACTOR_ADDR, + ActorError, Map, CALLER_TYPES_SIGNABLE, FUNGIBLE_TOKEN_RECEIVER_HOOK_METHOD_NUM, + INIT_ACTOR_ADDR, }; use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::RawBytes; @@ -44,6 +45,7 @@ pub enum Method { SwapSigner = 7, ChangeNumApprovalsThreshold = 8, LockBalance = 9, + FungibleTokenReceiverHook = FUNGIBLE_TOKEN_RECEIVER_HOOK_METHOD_NUM, } /// Multisig Actor @@ -541,6 +543,19 @@ impl Actor { execute_transaction_if_approved(rt, &st, tx_id, &txn) } + + // Always succeeds, accepting any token transfers. + pub fn fungible_token_receiver_hook( + rt: &mut RT, + _params: &RawBytes, + ) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + Ok(()) + } } fn execute_transaction_if_approved( @@ -700,6 +715,10 @@ impl ActorCode for Actor { Self::lock_balance(rt, cbor::deserialize_params(params)?)?; Ok(RawBytes::default()) } + Some(Method::FungibleTokenReceiverHook) => { + Self::fungible_token_receiver_hook(rt, params)?; + Ok(RawBytes::default()) + } None => Err(actor_error!(unhandled_message, "Invalid method")), } } diff --git a/actors/multisig/tests/multisig_actor_test.rs b/actors/multisig/tests/multisig_actor_test.rs index 645c818247..af6b267a11 100644 --- a/actors/multisig/tests/multisig_actor_test.rs +++ b/actors/multisig/tests/multisig_actor_test.rs @@ -15,7 +15,7 @@ use fvm_shared::bigint::Zero; use fvm_shared::clock::ChainEpoch; use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; -use fvm_shared::METHOD_SEND; +use fvm_shared::{MethodNum, METHOD_SEND}; mod util; @@ -2399,3 +2399,22 @@ mod lock_balance_tests { check_state(&rt); } } + +#[test] +fn token_receiver() { + let msig = Address::new_id(1000); + let anne = Address::new_id(101); + let bob = Address::new_id(102); + + let mut rt = construct_runtime(msig); + let h = util::ActorHarness::new(); + h.construct_and_verify(&mut rt, 2, 0, 0, vec![anne, bob]); + + rt.expect_validate_caller_any(); + let ret = rt.call::( + Method::FungibleTokenReceiverHook as MethodNum, + &RawBytes::new(vec![1, 2, 3]), + ); + assert!(ret.is_ok()); + assert_eq!(RawBytes::default(), ret.unwrap()); +} diff --git a/runtime/src/builtin/shared.rs b/runtime/src/builtin/shared.rs index e2ee7a39fd..95df7b3ec4 100644 --- a/runtime/src/builtin/shared.rs +++ b/runtime/src/builtin/shared.rs @@ -15,6 +15,12 @@ pub const HAMT_BIT_WIDTH: u32 = 5; /// user-programmable actors. pub const CALLER_TYPES_SIGNABLE: &[Type] = &[Type::Account, Type::Multisig]; +// TODO: use the fvm_dispatch crate here when we can depend on a published crate. +// This method number comes from taking the name as "TokensReceived" and applying +// the transformation described in https://github.com/filecoin-project/FIPs/pull/399. +// This matches the value in the fungible token library used by the datacap token actor. +pub const FUNGIBLE_TOKEN_RECEIVER_HOOK_METHOD_NUM: u64 = 1361519036; + /// ResolveToIDAddr resolves the given address to it's ID address form. /// If an ID address for the given address dosen't exist yet, it tries to create one by sending /// a zero balance to the given address.