From 2e0ace8fbfb21502020580679c95cf3296408a30 Mon Sep 17 00:00:00 2001 From: ZenGround0 <5515260+ZenGround0@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:29:35 -0400 Subject: [PATCH] Finish datacap tests (#747) * Auto allowance on mint * transfer[_from] restriction * destroy Co-authored-by: zenground0 --- actors/datacap/src/lib.rs | 2 +- actors/datacap/tests/datacap_actor_test.rs | 106 ++++++++++++++++++++- actors/datacap/tests/harness/mod.rs | 85 ++++++++++++++++- 3 files changed, 188 insertions(+), 5 deletions(-) diff --git a/actors/datacap/src/lib.rs b/actors/datacap/src/lib.rs index 9674f8544..4a1891f76 100644 --- a/actors/datacap/src/lib.rs +++ b/actors/datacap/src/lib.rs @@ -40,7 +40,7 @@ pub const DATACAP_GRANULARITY: u64 = TOKEN_PRECISION as u64; lazy_static! { // > 800 EiB - static ref INFINITE_ALLOWANCE: TokenAmount = TokenAmount::from_atto( + pub static ref INFINITE_ALLOWANCE: TokenAmount = TokenAmount::from_atto( BigInt::from(TOKEN_PRECISION) * BigInt::from(1_000_000_000_000_000_000_000_i128) ); diff --git a/actors/datacap/tests/datacap_actor_test.rs b/actors/datacap/tests/datacap_actor_test.rs index b33272e04..76e485a5c 100644 --- a/actors/datacap/tests/datacap_actor_test.rs +++ b/actors/datacap/tests/datacap_actor_test.rs @@ -32,10 +32,12 @@ mod mint { use fvm_shared::error::ExitCode; use fvm_shared::MethodNum; - use fil_actor_datacap::{Actor, Method, MintParams}; + use fil_actor_datacap::{Actor, Method, MintParams, INFINITE_ALLOWANCE}; use fil_actors_runtime::cbor::serialize; use fil_actors_runtime::test_utils::{expect_abort_contains_message, MARKET_ACTOR_CODE_ID}; use fil_actors_runtime::{STORAGE_MARKET_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR}; + use fvm_ipld_encoding::RawBytes; + use std::ops::Sub; use crate::*; @@ -87,12 +89,40 @@ mod mint { ); h.check_state(&rt); } + + #[test] + fn auto_allowance_on_mint() { + let (mut rt, h) = make_harness(); + let amt = TokenAmount::from_whole(42); + h.mint(&mut rt, &*ALICE, &amt, vec![*BOB]).unwrap(); + let allowance = h.get_allowance_between(&rt, &*ALICE, &*BOB); + assert!(allowance.eq(&INFINITE_ALLOWANCE)); + + // mint again + h.mint(&mut rt, &*ALICE, &amt, vec![*BOB]).unwrap(); + let allowance2 = h.get_allowance_between(&rt, &*ALICE, &*BOB); + assert!(allowance2.eq(&INFINITE_ALLOWANCE)); + + // transfer of an allowance *does* deduct allowance even though it is too small to matter in practice + let operator_data = RawBytes::new(vec![1, 2, 3, 4]); + h.transfer_from(&mut rt, &*BOB, &*ALICE, &h.governor, &(2 * amt.clone()), operator_data) + .unwrap(); + let allowance3 = h.get_allowance_between(&rt, &*ALICE, &*BOB); + assert!(allowance3.eq(&INFINITE_ALLOWANCE.clone().sub(2 * amt.clone()))); + + // minting any amount to this address at the same operator resets at infinite + h.mint(&mut rt, &*ALICE, &TokenAmount::from_whole(1), vec![*BOB]).unwrap(); + let allowance = h.get_allowance_between(&rt, &*ALICE, &*BOB); + assert!(allowance.eq(&INFINITE_ALLOWANCE)); + + h.check_state(&rt); + } } mod transfer { // Tests for the specific transfer restrictions of the datacap token. - use crate::{make_harness, ALICE, BOB}; + use crate::{make_harness, ALICE, BOB, CARLA}; use fil_actors_runtime::test_utils::expect_abort_contains_message; use fvm_ipld_encoding::RawBytes; use fvm_shared::econ::TokenAmount; @@ -119,6 +149,78 @@ mod transfer { // The governor can transfer out. h.transfer(&mut rt, &h.governor, &*BOB, &amt, operator_data).unwrap(); } + + #[test] + fn transfer_from_restricted() { + let (mut rt, h) = make_harness(); + let operator_data = RawBytes::new(vec![1, 2, 3, 4]); + + let amt = TokenAmount::from_whole(1); + h.mint(&mut rt, &*ALICE, &amt, vec![*BOB]).unwrap(); + + // operator can't transfer out to third address + expect_abort_contains_message( + ExitCode::USR_FORBIDDEN, + "transfer not allowed", + h.transfer_from(&mut rt, &*BOB, &*ALICE, &*CARLA, &amt, operator_data.clone()), + ); + rt.reset(); + + // operator can't transfer out to self + expect_abort_contains_message( + ExitCode::USR_FORBIDDEN, + "transfer not allowed", + h.transfer_from(&mut rt, &*BOB, &*ALICE, &*BOB, &amt, operator_data.clone()), + ); + rt.reset(); + // even if governor has a delegate operator and enough tokens, delegated transfer + // cannot send to non governor + h.mint(&mut rt, &h.governor, &amt, vec![*BOB]).unwrap(); + expect_abort_contains_message( + ExitCode::USR_FORBIDDEN, + "transfer not allowed", + h.transfer_from(&mut rt, &*BOB, &h.governor, &*ALICE, &amt, operator_data), + ); + rt.reset(); + } +} + +mod destroy { + use crate::{make_harness, ALICE, BOB}; + use fil_actor_datacap::DestroyParams; + use fil_actors_runtime::test_utils::{expect_abort_contains_message, ACCOUNT_ACTOR_CODE_ID}; + use fil_actors_runtime::VERIFIED_REGISTRY_ACTOR_ADDR; + use fvm_shared::econ::TokenAmount; + use fvm_shared::MethodNum; + + use fil_actor_datacap::{Actor, Method}; + use fil_actors_runtime::cbor::serialize; + use fvm_shared::error::ExitCode; + + #[test] + fn only_governor_allowed() { + let (mut rt, h) = make_harness(); + + let amt = TokenAmount::from_whole(1); + h.mint(&mut rt, &*ALICE, &(2 * amt.clone()), vec![*BOB]).unwrap(); + + // destroying from operator does not work + let params = DestroyParams { owner: *ALICE, amount: amt.clone() }; + + rt.expect_validate_caller_addr(vec![VERIFIED_REGISTRY_ACTOR_ADDR]); + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, *BOB); + expect_abort_contains_message( + ExitCode::USR_FORBIDDEN, + "caller address", + rt.call::(Method::Destroy as MethodNum, &serialize(¶ms, "params").unwrap()), + ); + + // Destroying from 0 allowance having governor works + assert!(h.get_allowance_between(&rt, &*ALICE, &h.governor).is_zero()); + let ret = h.destroy(&mut rt, &*ALICE, &amt).unwrap(); + assert_eq!(ret.balance, amt); // burned 2 amt - amt = amt + h.check_state(&rt) + } } fn make_harness() -> (MockRuntime, Harness) { diff --git a/actors/datacap/tests/harness/mod.rs b/actors/datacap/tests/harness/mod.rs index f4f5432c5..0ca77a71d 100644 --- a/actors/datacap/tests/harness/mod.rs +++ b/actors/datacap/tests/harness/mod.rs @@ -1,5 +1,7 @@ use frc46_token::receiver::types::{FRC46TokenReceived, UniversalReceiverParams, FRC46_TOKEN_TYPE}; -use frc46_token::token::types::{MintReturn, TransferParams, TransferReturn}; +use frc46_token::token::types::{ + BurnReturn, MintReturn, TransferFromParams, TransferFromReturn, TransferParams, TransferReturn, +}; use fvm_ipld_encoding::RawBytes; use fvm_shared::address::Address; use fvm_shared::econ::TokenAmount; @@ -8,7 +10,7 @@ use fvm_shared::MethodNum; use num_traits::Zero; use fil_actor_datacap::testing::check_state_invariants; -use fil_actor_datacap::{Actor as DataCapActor, Method, MintParams, State}; +use fil_actor_datacap::{Actor as DataCapActor, DestroyParams, Method, MintParams, State}; use fil_actors_runtime::cbor::serialize; use fil_actors_runtime::runtime::Runtime; use fil_actors_runtime::test_utils::*; @@ -97,6 +99,24 @@ impl Harness { Ok(ret.deserialize().unwrap()) } + pub fn destroy( + &self, + rt: &mut MockRuntime, + owner: &Address, + amount: &TokenAmount, + ) -> Result { + rt.expect_validate_caller_addr(vec![VERIFIED_REGISTRY_ACTOR_ADDR]); + + let params = DestroyParams { owner: *owner, amount: amount.clone() }; + + rt.set_caller(*VERIFREG_ACTOR_CODE_ID, VERIFIED_REGISTRY_ACTOR_ADDR); + let ret = + rt.call::(Method::Destroy as MethodNum, &serialize(¶ms, "params")?)?; + + rt.verify(); + Ok(ret.deserialize().unwrap()) + } + pub fn transfer( &self, rt: &mut MockRuntime, @@ -141,6 +161,54 @@ impl Harness { Ok(ret.deserialize().unwrap()) } + pub fn transfer_from( + &self, + rt: &mut MockRuntime, + operator: &Address, + from: &Address, + to: &Address, + amount: &TokenAmount, + operator_data: RawBytes, + ) -> Result { + rt.expect_validate_caller_any(); + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, *operator); + + // Expect the token receiver hook to be called. + let hook_params = UniversalReceiverParams { + type_: FRC46_TOKEN_TYPE, + payload: serialize( + &FRC46TokenReceived { + from: from.id().unwrap(), + to: to.id().unwrap(), + operator: operator.id().unwrap(), + amount: amount.clone(), + operator_data: operator_data.clone(), + token_data: Default::default(), + }, + "hook payload", + )?, + }; + // UniversalReceiverParams + rt.expect_send( + *to, + frc42_dispatch::method_hash!("Receive"), + serialize(&hook_params, "hook params")?, + TokenAmount::zero(), + RawBytes::default(), + ExitCode::OK, + ); + + let params = + TransferFromParams { to: *to, from: *from, amount: amount.clone(), operator_data }; + let ret = rt.call::( + Method::TransferFrom as MethodNum, + &serialize(¶ms, "params")?, + )?; + + rt.verify(); + Ok(ret.deserialize().unwrap()) + } + // Reads the total supply from state directly. pub fn get_supply(&self, rt: &MockRuntime) -> TokenAmount { rt.get_state::().token.supply @@ -151,6 +219,19 @@ impl Harness { rt.get_state::().token.get_balance(rt.store(), address.id().unwrap()).unwrap() } + // Reads allowance from state directly + pub fn get_allowance_between( + &self, + rt: &MockRuntime, + owner: &Address, + operator: &Address, + ) -> TokenAmount { + rt.get_state::() + .token + .get_allowance_between(rt.store(), owner.id().unwrap(), operator.id().unwrap()) + .unwrap() + } + pub fn check_state(&self, rt: &MockRuntime) { let (_, acc) = check_state_invariants(&rt.get_state(), rt.store()); acc.assert_empty();