Skip to content

Commit

Permalink
Implement could-be-standard token receiver hook in account and multis…
Browse files Browse the repository at this point in the history
…ig (#555)
  • Loading branch information
anorth committed Aug 28, 2022
1 parent 4af8c63 commit a1519ac
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 65 deletions.
22 changes: 19 additions & 3 deletions actors/account/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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<BS, RT>(
rt: &mut RT,
_params: &RawBytes,
) -> Result<(), ActorError>
where
BS: Blockstore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_accept_any()?;
Ok(())
}
}

impl ActorCode for Actor {
Expand All @@ -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")),
}
}
Expand Down
131 changes: 71 additions & 60 deletions actors/account/tests/account_actor_test.rs
Original file line number Diff line number Diff line change
@@ -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::<AccountActor>(
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::<AccountActor>(1, &RawBytes::serialize(addr).unwrap()).unwrap();
let pk: Address = rt
.call::<AccountActor>(Method::PubkeyAddress as MethodNum, &RawBytes::default())
.unwrap()
.deserialize()
.unwrap();
assert_eq!(pk, addr);
check_state(&rt);
} else {
expect_abort(exit_code, rt.call::<AccountActor>(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::<AccountActor>(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::<AccountActor>(1,&RawBytes::serialize(addr).unwrap())
)
}
rt.verify();
}
)*
}
let param = Address::new_secp256k1(&[2; fvm_shared::address::SECP_PUB_LEN]).unwrap();
rt.call::<AccountActor>(
Method::Constructor as MethodNum,
&RawBytes::serialize(&param).unwrap(),
)
.unwrap();

rt.expect_validate_caller_any();
let ret = rt.call::<AccountActor>(
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();
}
21 changes: 20 additions & 1 deletion actors/multisig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,6 +45,7 @@ pub enum Method {
SwapSigner = 7,
ChangeNumApprovalsThreshold = 8,
LockBalance = 9,
FungibleTokenReceiverHook = FUNGIBLE_TOKEN_RECEIVER_HOOK_METHOD_NUM,
}

/// Multisig Actor
Expand Down Expand Up @@ -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<BS, RT>(
rt: &mut RT,
_params: &RawBytes,
) -> Result<(), ActorError>
where
BS: Blockstore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_accept_any()?;
Ok(())
}
}

fn execute_transaction_if_approved<BS, RT>(
Expand Down Expand Up @@ -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")),
}
}
Expand Down
21 changes: 20 additions & 1 deletion actors/multisig/tests/multisig_actor_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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::<MultisigActor>(
Method::FungibleTokenReceiverHook as MethodNum,
&RawBytes::new(vec![1, 2, 3]),
);
assert!(ret.is_ok());
assert_eq!(RawBytes::default(), ret.unwrap());
}
6 changes: 6 additions & 0 deletions runtime/src/builtin/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit a1519ac

Please sign in to comment.