From aa4eaf5ae098ad690cbe1017d58214e5d9f2cbec Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Thu, 8 Sep 2022 10:15:09 +1000 Subject: [PATCH 01/32] Add configurable test actor --- Cargo.toml | 1 + Makefile | 3 +- testing/fil_token_integration/Cargo.toml | 1 + .../actors/test_actor/Cargo.toml | 18 ++ .../actors/test_actor/build.rs | 12 ++ .../actors/test_actor/src/lib.rs | 167 ++++++++++++++++++ 6 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 testing/fil_token_integration/actors/test_actor/Cargo.toml create mode 100644 testing/fil_token_integration/actors/test_actor/build.rs create mode 100644 testing/fil_token_integration/actors/test_actor/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e1da20f4..034d92f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ members = [ "testing/fil_token_integration/actors/basic_receiving_actor", "testing/fil_token_integration/actors/basic_nft_actor", "testing/fil_token_integration/actors/basic_transfer_actor", + "testing/fil_token_integration/actors/test_actor" ] \ No newline at end of file diff --git a/Makefile b/Makefile index b4f2d638..2593f738 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,8 @@ test-coverage: install-toolchain --exclude basic_token_actor \ --exclude basic_receiving_actor \ --exclude basic_nft_actor \ - --exclude basic_transfer_actor + --exclude basic_transfer_actor \ + --exclude test_actor # separate actor testing stage to run from CI without coverage support test-actors: install-toolchain diff --git a/testing/fil_token_integration/Cargo.toml b/testing/fil_token_integration/Cargo.toml index e7328545..65aa78e2 100644 --- a/testing/fil_token_integration/Cargo.toml +++ b/testing/fil_token_integration/Cargo.toml @@ -24,3 +24,4 @@ basic_nft_actor = {path = "actors/basic_nft_actor"} basic_receiving_actor = { path = "actors/basic_receiving_actor" } basic_token_actor = { path = "actors/basic_token_actor" } basic_transfer_actor = { path = "actors/basic_transfer_actor" } +test_actor = { path = "actors/test_actor" } diff --git a/testing/fil_token_integration/actors/test_actor/Cargo.toml b/testing/fil_token_integration/actors/test_actor/Cargo.toml new file mode 100644 index 00000000..98c0516c --- /dev/null +++ b/testing/fil_token_integration/actors/test_actor/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "test_actor" +version = "0.1.0" +edition = "2021" + +[dependencies] +cid = { version = "0.8.5", default-features = false } +frc46_token = { version = "0.1.0", path = "../../../../frc46_token" } +frc42_dispatch = { path = "../../../../frc42_dispatch" } +fvm_ipld_blockstore = { version = "0.1.1" } +fvm_ipld_encoding = { version = "0.2.2" } +fvm_sdk = { version = "2.0.0-alpha.2" } +fvm_shared = { version = "2.0.0-alpha.2" } +serde = { version = "1.0", features = ["derive"] } +serde_tuple = { version = "0.5.0" } + +[build-dependencies] +wasm-builder = "3.0" \ No newline at end of file diff --git a/testing/fil_token_integration/actors/test_actor/build.rs b/testing/fil_token_integration/actors/test_actor/build.rs new file mode 100644 index 00000000..0f2aa8a5 --- /dev/null +++ b/testing/fil_token_integration/actors/test_actor/build.rs @@ -0,0 +1,12 @@ +fn main() { + use wasm_builder::WasmBuilder; + WasmBuilder::new() + .with_current_project() + .import_memory() + .append_to_rust_flags("-Ctarget-feature=+crt-static") + .append_to_rust_flags("-Cpanic=abort") + .append_to_rust_flags("-Coverflow-checks=true") + .append_to_rust_flags("-Clto=true") + .append_to_rust_flags("-Copt-level=z") + .build() +} diff --git a/testing/fil_token_integration/actors/test_actor/src/lib.rs b/testing/fil_token_integration/actors/test_actor/src/lib.rs new file mode 100644 index 00000000..1412e37e --- /dev/null +++ b/testing/fil_token_integration/actors/test_actor/src/lib.rs @@ -0,0 +1,167 @@ +use frc42_dispatch::{match_method, method_hash}; +use frc46_token::{ + receiver::types::{FRC46TokenReceived, UniversalReceiverParams, FRC46_TOKEN_TYPE}, + token::types::{BurnParams, TransferParams}, +}; +use fvm_ipld_encoding::{ + de::DeserializeOwned, + tuple::{Deserialize_tuple, Serialize_tuple}, + RawBytes, +}; +use fvm_sdk as sdk; +use fvm_shared::{address::Address, bigint::Zero, econ::TokenAmount, error::ExitCode}; +use sdk::NO_DATA_BLOCK_ID; +use serde::{Deserialize, Serialize}; + +/// Grab the incoming parameters and convert from RawBytes to deserialized struct +pub fn deserialize_params(params: u32) -> O { + let params = sdk::message::params_raw(params).unwrap().1; + let params = RawBytes::new(params); + params.deserialize().unwrap() +} + +/// Action to take in receiver hook or Action method +/// This gets serialized and sent along as operator_data +#[derive(Serialize, Deserialize, Debug)] +pub enum TestAction { + /// Accept the tokens + Accept, + /// Reject the tokens (hook aborts) + Reject, + /// Transfer to another address (with operator_data that can provide further instructions) + Transfer(Address, RawBytes), + /// Burn incoming tokens + Burn, +} + +/// Params for Action method call +#[derive(Serialize_tuple, Deserialize_tuple, Debug)] +pub struct ActionParams { + /// Address of the token actor + token_address: Address, + /// Action to take with our token balance. Only Transfer and Burn actions apply here. + action: TestAction, +} + +#[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))) + })); + + let method_num = sdk::message::method_number(); + match_method!(method_num, { + "Constructor" => { + NO_DATA_BLOCK_ID + }, + "Receive" => { + // Received is passed a UniversalReceiverParams + let params: UniversalReceiverParams = deserialize_params(input); + + // reject if not an FRC46 token + // we don't know how to inspect other payloads here + if params.type_ != FRC46_TOKEN_TYPE { + panic!("invalid token type, rejecting transfer"); + } + + // get token transfer data + let token_params: FRC46TokenReceived = params.payload.deserialize().unwrap(); + + // todo: examine the operator_data to determine our next move + let action: TestAction = token_params.operator_data.deserialize().unwrap(); + match action { + TestAction::Accept => { + // do nothing, return success + } + TestAction::Reject => { + // abort to reject transfer + sdk::vm::abort( + ExitCode::USR_FORBIDDEN.value(), + Some("rejecting transfer"), + ); + } + TestAction::Transfer(to, operator_data) => { + // transfer to a target address + let transfer_params = TransferParams { + to, + amount: token_params.amount, + operator_data, + }; + let receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); + if !receipt.exit_code.is_success() { + panic!("transfer call failed"); + } + } + TestAction::Burn => { + // burn the tokens + let burn_params = BurnParams { + amount: token_params.amount, + }; + let receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Burn"), RawBytes::serialize(&burn_params).unwrap(), TokenAmount::zero()).unwrap(); + if !receipt.exit_code.is_success() { + panic!("burn call failed"); + } + } + } + + // all good, don't need to return anything + NO_DATA_BLOCK_ID + }, + "Action" => { + // take action independent of the receiver hook + let params: ActionParams = deserialize_params(input); + + // get our balance + let get_balance = || { + let self_address = Address::new_id(sdk::message::receiver()); + let balance_receipt = sdk::send::send(¶ms.token_address, method_hash!("BalanceOf"), RawBytes::serialize(self_address).unwrap(), TokenAmount::zero()).unwrap(); + if !balance_receipt.exit_code.is_success() { + panic!("unable to get balance"); + } + balance_receipt.return_data.deserialize::().unwrap() + }; + + match params.action { + TestAction::Accept => { + // nothing to do here + } + TestAction::Reject => { + // nothing to do here + } + TestAction::Transfer(to, operator_data) => { + // transfer to a target address + let balance = get_balance(); + let transfer_params = TransferParams { + to, + amount: balance, + operator_data, + }; + let receipt = sdk::send::send(¶ms.token_address, method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); + if !receipt.exit_code.is_success() { + panic!("transfer call failed"); + } + } + TestAction::Burn => { + // burn the tokens + let balance = get_balance(); + let burn_params = BurnParams { + amount: balance, + }; + let receipt = sdk::send::send(¶ms.token_address, method_hash!("Burn"), RawBytes::serialize(&burn_params).unwrap(), TokenAmount::zero()).unwrap(); + if !receipt.exit_code.is_success() { + panic!("burn call failed"); + } + } + } + + // all good, don't need to return anything + NO_DATA_BLOCK_ID + } + _ => { + sdk::vm::abort( + ExitCode::USR_UNHANDLED_MESSAGE.value(), + Some("Unknown method number"), + ); + } + }) +} From 23293ac03cc47e1a2f4a80400c84dc625bcb86e2 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Fri, 9 Sep 2022 10:39:45 +1000 Subject: [PATCH 02/32] WIP configurable actor tests --- testing/fil_token_integration/tests/common.rs | 121 ++++++++++++++++++ .../fil_token_integration/tests/more_tests.rs | 100 +++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 testing/fil_token_integration/tests/common.rs create mode 100644 testing/fil_token_integration/tests/more_tests.rs diff --git a/testing/fil_token_integration/tests/common.rs b/testing/fil_token_integration/tests/common.rs new file mode 100644 index 00000000..157bcbcd --- /dev/null +++ b/testing/fil_token_integration/tests/common.rs @@ -0,0 +1,121 @@ +use std::env; + +use basic_token_actor::MintParams; +use cid::Cid; +use fil_fungible_token::token::{state::TokenState, types::MintReturn}; +use frc42_dispatch::method_hash; +use fvm::{ + executor::{ApplyKind, ApplyRet, Executor}, + externs::Externs, +}; +use fvm_integration_tests::{ + bundle, + dummy::DummyExterns, + tester::{Account, Tester}, +}; +use fvm_ipld_blockstore::{Blockstore, MemoryBlockstore}; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::address::Address; +use fvm_shared::bigint::Zero; +use fvm_shared::econ::TokenAmount; +use fvm_shared::message::Message; +use fvm_shared::state::StateTreeVersion; +use fvm_shared::version::NetworkVersion; +use serde::{Deserialize, Serialize}; + +pub fn load_actor_wasm(path: &str) -> Vec { + let wasm_path = env::current_dir().unwrap().join(path).canonicalize().unwrap(); + + std::fs::read(wasm_path).expect("unable to read actor file") +} + +pub trait TestHelpers { + /// Call a method on an actor + fn call_method( + &mut self, + from: Address, + to: Address, + method_num: u64, + params: Option, + ) -> ApplyRet; + + /// Get balance from token actor for a given address + /// This is a very common thing to check during tests + fn get_balance( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + ) -> TokenAmount; + + /// Install an actor with initial state and ID + /// Returns the actor's address + fn install_actor_with_state( + &mut self, + path: &str, + actor_id: u64, + state: S, + ) -> Address; + + /// Install an actor with no initial state + /// Takes ID and returns the new actor's address + fn install_actor_stateless(&mut self, path: &str, actor_id: u64) -> Address; +} + +impl TestHelpers for Tester { + fn call_method( + &mut self, + from: Address, + to: Address, + method_num: u64, + params: Option, + ) -> ApplyRet { + static mut SEQUENCE: u64 = 0u64; + let message = Message { + from, + to, + gas_limit: 99999999, + method_num, + sequence: unsafe { SEQUENCE }, + params: if let Some(params) = params { params } else { RawBytes::default() }, + ..Message::default() + }; + unsafe { + SEQUENCE += 1; + } + self.executor.as_mut().unwrap().execute_message(message, ApplyKind::Explicit, 100).unwrap() + } + + fn get_balance( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + ) -> TokenAmount { + let params = RawBytes::serialize(target).unwrap(); + let ret_val = + self.call_method(operator, token_actor, method_hash!("BalanceOf"), Some(params)); + println!("balance return data {:#?}", &ret_val); + ret_val.msg_receipt.return_data.deserialize::().unwrap() + } + + fn install_actor_with_state( + &mut self, + path: &str, + actor_id: u64, + state: S, + ) -> Address { + let code = load_actor_wasm(path); + let address = Address::new_id(actor_id); + let state_cid = self.set_state(&state).unwrap(); + self.set_actor_from_bin(&code, state_cid, address, TokenAmount::zero()).unwrap(); + address + } + + fn install_actor_stateless(&mut self, path: &str, actor_id: u64) -> Address { + let code = load_actor_wasm(path); + let address = Address::new_id(actor_id); + self.set_actor_from_bin(&code, Cid::default(), address, TokenAmount::zero()).unwrap(); + address + } +} diff --git a/testing/fil_token_integration/tests/more_tests.rs b/testing/fil_token_integration/tests/more_tests.rs new file mode 100644 index 00000000..1d97ba31 --- /dev/null +++ b/testing/fil_token_integration/tests/more_tests.rs @@ -0,0 +1,100 @@ +use basic_token_actor::MintParams; +use frc42_dispatch::method_hash; +use frc46_token::token::{state::TokenState, types::MintReturn}; +use fvm_integration_tests::{ + bundle, + dummy::DummyExterns, + tester::{Account, Tester}, +}; +use fvm_ipld_blockstore::MemoryBlockstore; +use fvm_ipld_encoding::{ + tuple::{Deserialize_tuple, Serialize_tuple}, + RawBytes, +}; +use fvm_shared::{ + address::Address, econ::TokenAmount, state::StateTreeVersion, version::NetworkVersion, +}; +use serde::{Deserialize, Serialize}; + +mod common; +use common::TestHelpers; + +const BASIC_TOKEN_ACTOR_WASM: &str = + "../../target/debug/wbuild/basic_token_actor/basic_token_actor.compact.wasm"; +const TEST_ACTOR_WASM: &str = "../../target/debug/wbuild/test_actor/test_actor.compact.wasm"; + +/// Action to take in receiver hook or Action method +/// This gets serialized and sent along as operator_data +#[derive(Serialize, Deserialize, Debug)] +pub enum TestAction { + /// Accept the tokens + Accept, + /// Reject the tokens (hook aborts) + Reject, + /// Transfer to another address (with operator_data that can provide further instructions) + Transfer(Address, RawBytes), + /// Burn incoming tokens + Burn, +} + +/// Params for Action method call +/// This gives us a way to supply the token address, since we won't get it as a sender like we do for hook calls +#[derive(Serialize_tuple, Deserialize_tuple, Debug)] +pub struct ActionParams { + /// Address of the token actor + token_address: Address, + /// Action to take with our token balance. Only Transfer and Burn actions apply here. + action: TestAction, +} + +/// Helper for nesting calls to create action sequences +/// eg. transfer and then the receiver hook rejects: +/// action(TestAction::Transfer( +/// some_address, +/// action(TestAction::Reject), +/// ), +/// ) +fn action(action: TestAction) -> RawBytes { + RawBytes::serialize(action).unwrap() +} + +#[test] +fn more_tests() { + let blockstore = MemoryBlockstore::default(); + let bundle_root = bundle::import_bundle(&blockstore, actors_v10::BUNDLE_CAR).unwrap(); + let mut tester = + Tester::new(NetworkVersion::V15, StateTreeVersion::V4, bundle_root, blockstore.clone()) + .unwrap(); + + let operator: [Account; 1] = tester.create_accounts().unwrap(); + + let initial_token_state = TokenState::new(&blockstore).unwrap(); + + // install actors required for our test: a token actor and one instance of the test actor + let token_actor = + tester.install_actor_with_state(BASIC_TOKEN_ACTOR_WASM, 10000, initial_token_state); + let test_actor = tester.install_actor_stateless(TEST_ACTOR_WASM, 10010); + + // Instantiate machine + tester.instantiate_machine(DummyExterns).unwrap(); + + // construct actors + let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Constructor"), None); + assert!(ret_val.msg_receipt.exit_code.is_success()); + let ret_val = tester.call_method(operator[0].1, test_actor, method_hash!("Constructor"), None); + assert!(ret_val.msg_receipt.exit_code.is_success()); + + // mint some tokens + // TODO: add operator data to MintParams + let mint_params = MintParams { initial_owner: test_actor, amount: TokenAmount::from_atto(100) }; + let params = RawBytes::serialize(mint_params).unwrap(); + let ret_val = + tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); + println!("minting return data {:#?}", &ret_val); + let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); + println!("minted - total supply: {:?}", &mint_result.supply); + + // check balance of transfer actor + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + println!("balance held by transfer actor: {:?}", balance); +} From 0424763f99dca9b94f37753c752c5268d2169fd0 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 12 Sep 2022 13:55:24 +1000 Subject: [PATCH 03/32] Add operator_data to MintParams in the basic token actor --- .../actors/basic_token_actor/src/lib.rs | 3 ++- testing/fil_token_integration/tests/frc46_tokens.rs | 2 +- testing/fil_token_integration/tests/more_tests.rs | 5 ++--- testing/fil_token_integration/tests/transfer_tokens.rs | 7 +++++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index 2f097520..99b09fdc 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -141,6 +141,7 @@ impl FRC46Token for BasicToken<'_> { pub struct MintParams { pub initial_owner: Address, pub amount: TokenAmount, + pub operator_data: RawBytes, } impl Cbor for MintParams {} @@ -151,7 +152,7 @@ impl BasicToken<'_> { &caller_address(), ¶ms.initial_owner, ¶ms.amount, - Default::default(), + params.operator_data, Default::default(), )?; diff --git a/testing/fil_token_integration/tests/frc46_tokens.rs b/testing/fil_token_integration/tests/frc46_tokens.rs index bc43bf82..5202f6e2 100644 --- a/testing/fil_token_integration/tests/frc46_tokens.rs +++ b/testing/fil_token_integration/tests/frc46_tokens.rs @@ -84,7 +84,7 @@ fn it_mints_tokens() { // Mint some tokens let mint_params = - MintParams { initial_owner: receive_address, amount: TokenAmount::from_atto(100) }; + MintParams { initial_owner: receive_address, amount: TokenAmount::from_atto(100), operator_data: RawBytes::default() }; let params = RawBytes::serialize(mint_params).unwrap(); let ret_val = call_method(minter[0].1, actor_address, method_hash!("Mint"), Some(params)); println!("mint return data {:#?}", &ret_val); diff --git a/testing/fil_token_integration/tests/more_tests.rs b/testing/fil_token_integration/tests/more_tests.rs index 1d97ba31..880e905e 100644 --- a/testing/fil_token_integration/tests/more_tests.rs +++ b/testing/fil_token_integration/tests/more_tests.rs @@ -84,9 +84,8 @@ fn more_tests() { let ret_val = tester.call_method(operator[0].1, test_actor, method_hash!("Constructor"), None); assert!(ret_val.msg_receipt.exit_code.is_success()); - // mint some tokens - // TODO: add operator data to MintParams - let mint_params = MintParams { initial_owner: test_actor, amount: TokenAmount::from_atto(100) }; + // mint some tokens, hook will reject + let mint_params = MintParams { initial_owner: test_actor, amount: TokenAmount::from_atto(100), operator_data: action(TestAction::Reject) }; let params = RawBytes::serialize(mint_params).unwrap(); let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); diff --git a/testing/fil_token_integration/tests/transfer_tokens.rs b/testing/fil_token_integration/tests/transfer_tokens.rs index 553aae11..3976c0fa 100644 --- a/testing/fil_token_integration/tests/transfer_tokens.rs +++ b/testing/fil_token_integration/tests/transfer_tokens.rs @@ -149,8 +149,11 @@ fn transfer_tokens() { println!("receiving actor constructor return data: {:#?}", &ret_val); // mint some tokens - let mint_params = - MintParams { initial_owner: transfer_address, amount: TokenAmount::from_atto(100) }; + let mint_params = MintParams { + initial_owner: transfer_address, + amount: TokenAmount::from_atto(100), + operator_data: RawBytes::default(), + }; let params = RawBytes::serialize(mint_params).unwrap(); let ret_val = tester.call_method(operator[0].1, token_address, method_hash!("Mint"), Some(params)); From 4c550fbf8d14a3445b9806202a6240cf5d84d816 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 12 Sep 2022 13:57:49 +1000 Subject: [PATCH 04/32] Don't blindly flush state after minting Doing this can unintentionally revert to the pre-hook state if the receiver hook carried out further operations on the balance received --- .../fil_token_integration/actors/basic_token_actor/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index 99b09fdc..06ac46a3 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -290,8 +290,6 @@ pub fn invoke(params: u32) -> u32 { let params = deserialize_params(params); let res = token_actor.mint(params).unwrap(); - let cid = token_actor.util.flush().unwrap(); - sdk::sself::set_root(&cid).unwrap(); return_ipld(&res).unwrap() } _ => { From 13bd598b5d4ba89dbdfe8a1c7934b459d95979b5 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 12 Sep 2022 14:49:24 +1000 Subject: [PATCH 05/32] test: receiver hook burns incoming tokens after mint --- .../fil_token_integration/tests/more_tests.rs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/testing/fil_token_integration/tests/more_tests.rs b/testing/fil_token_integration/tests/more_tests.rs index 880e905e..b0aa6b8a 100644 --- a/testing/fil_token_integration/tests/more_tests.rs +++ b/testing/fil_token_integration/tests/more_tests.rs @@ -90,10 +90,31 @@ fn more_tests() { let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); println!("minting return data {:#?}", &ret_val); + if ret_val.msg_receipt.exit_code.is_success() { + let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); + println!("minted - total supply: {:?}", &mint_result.supply); + assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); + } + + // check balance of test actor + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + println!("balance held by test actor: {:?}", balance); + assert_eq!(balance, TokenAmount::from_atto(0)); + + // mint again, hook will burn + let mint_params = MintParams { initial_owner: test_actor, amount: TokenAmount::from_atto(100), operator_data: action(TestAction::Burn) }; + let params = RawBytes::serialize(mint_params).unwrap(); + let ret_val = + tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); + println!("minting return data {:#?}", &ret_val); let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); + // TOOD: mint result doesn't know the tokens were burned + // it needs some awareness of post-hook state println!("minted - total supply: {:?}", &mint_result.supply); + assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); - // check balance of transfer actor + // check balance of test actor let balance = tester.get_balance(operator[0].1, token_actor, test_actor); - println!("balance held by transfer actor: {:?}", balance); + println!("balance held by test actor: {:?}", balance); + assert_eq!(balance, TokenAmount::from_atto(100)); } From e3f563982c73595c501c268afa20642737132f59 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 12 Sep 2022 18:30:46 +1000 Subject: [PATCH 06/32] MintParams formatting --- testing/fil_token_integration/tests/frc46_tokens.rs | 7 +++++-- testing/fil_token_integration/tests/more_tests.rs | 12 ++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/testing/fil_token_integration/tests/frc46_tokens.rs b/testing/fil_token_integration/tests/frc46_tokens.rs index 5202f6e2..135052e0 100644 --- a/testing/fil_token_integration/tests/frc46_tokens.rs +++ b/testing/fil_token_integration/tests/frc46_tokens.rs @@ -83,8 +83,11 @@ fn it_mints_tokens() { println!("receiving actor constructor return data: {:#?}", &ret_val); // Mint some tokens - let mint_params = - MintParams { initial_owner: receive_address, amount: TokenAmount::from_atto(100), operator_data: RawBytes::default() }; + let mint_params = MintParams { + initial_owner: receive_address, + amount: TokenAmount::from_atto(100), + operator_data: RawBytes::default(), + }; let params = RawBytes::serialize(mint_params).unwrap(); let ret_val = call_method(minter[0].1, actor_address, method_hash!("Mint"), Some(params)); println!("mint return data {:#?}", &ret_val); diff --git a/testing/fil_token_integration/tests/more_tests.rs b/testing/fil_token_integration/tests/more_tests.rs index b0aa6b8a..d2fc0903 100644 --- a/testing/fil_token_integration/tests/more_tests.rs +++ b/testing/fil_token_integration/tests/more_tests.rs @@ -85,7 +85,11 @@ fn more_tests() { assert!(ret_val.msg_receipt.exit_code.is_success()); // mint some tokens, hook will reject - let mint_params = MintParams { initial_owner: test_actor, amount: TokenAmount::from_atto(100), operator_data: action(TestAction::Reject) }; + let mint_params = MintParams { + initial_owner: test_actor, + amount: TokenAmount::from_atto(100), + operator_data: action(TestAction::Reject), + }; let params = RawBytes::serialize(mint_params).unwrap(); let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); @@ -102,7 +106,11 @@ fn more_tests() { assert_eq!(balance, TokenAmount::from_atto(0)); // mint again, hook will burn - let mint_params = MintParams { initial_owner: test_actor, amount: TokenAmount::from_atto(100), operator_data: action(TestAction::Burn) }; + let mint_params = MintParams { + initial_owner: test_actor, + amount: TokenAmount::from_atto(100), + operator_data: action(TestAction::Burn), + }; let params = RawBytes::serialize(mint_params).unwrap(); let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); From a5a4988a51441cda803b693875b75491b1d38814 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 12 Sep 2022 18:31:46 +1000 Subject: [PATCH 07/32] wip make minting aware of changed state after receiver hook returns --- .../actors/basic_token_actor/Cargo.toml | 1 + .../actors/basic_token_actor/src/lib.rs | 31 +++++++++++++++---- .../fil_token_integration/tests/more_tests.rs | 9 +++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml b/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml index e31c9af5..15d30d2a 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml +++ b/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml @@ -5,6 +5,7 @@ repository = "https://github.com/helix-collective/filecoin" edition = "2021" [dependencies] +cid = { version = "0.8.5", default-features = false } fvm_actor_utils = { version = "0.1.0", path = "../../../../fvm_actor_utils" } fvm_ipld_blockstore = { version = "0.1.1" } fvm_ipld_encoding = { version = "0.2.2" } diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index 06ac46a3..4fe68212 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -1,5 +1,6 @@ mod util; +use cid::Cid; use frc46_token::token::types::{ AllowanceReturn, BalanceReturn, BurnFromReturn, BurnParams, BurnReturn, DecreaseAllowanceParams, FRC46Token, GetAllowanceParams, GranularityReturn, @@ -147,7 +148,7 @@ pub struct MintParams { impl Cbor for MintParams {} impl BasicToken<'_> { - fn mint(&mut self, params: MintParams) -> Result { + fn mint(&mut self, params: MintParams) -> Result<(Cid, MintReturn), RuntimeError> { let mut hook = self.util.mint( &caller_address(), ¶ms.initial_owner, @@ -161,7 +162,7 @@ impl BasicToken<'_> { let ret = hook.call(self.util.msg())?; - Ok(ret) + Ok((cid, ret)) } } @@ -199,6 +200,7 @@ pub fn invoke(params: u32) -> u32 { let root_cid = sdk::sself::root().unwrap(); let bs = Blockstore::default(); + // might want these to be done inside each handler as there's times we'll want to discard and reload it let mut token_state = Token::<_, FvmMessenger>::load_state(&bs, &root_cid).unwrap(); let mut token_actor = @@ -287,10 +289,27 @@ pub fn invoke(params: u32) -> u32 { // FRC46 Token standard 3839021839 => { // Mint - let params = deserialize_params(params); - let res = token_actor.mint(params).unwrap(); - - return_ipld(&res).unwrap() + let params: MintParams = deserialize_params(params); + let owner = params.initial_owner; + let (cid, res) = token_actor.mint(params).unwrap(); + + // TODO: we need to know if the cid changed. having mint return it kinda works but is messy + // but we also can't reload state inside those calls + let new_cid = sdk::sself::root().unwrap(); + if cid == new_cid { + return_ipld(&res).unwrap() + } else { + token_state = Token::<_, FvmMessenger>::load_state(&bs, &root_cid).unwrap(); + token_actor = BasicToken { + util: Token::wrap(bs, FvmMessenger::default(), 1, &mut token_state), + }; + + return_ipld(&MintReturn { + balance: token_actor.balance_of(owner).unwrap(), + supply: token_actor.total_supply(), + }) + .unwrap() + } } _ => { sdk::vm::abort( diff --git a/testing/fil_token_integration/tests/more_tests.rs b/testing/fil_token_integration/tests/more_tests.rs index d2fc0903..07687ff5 100644 --- a/testing/fil_token_integration/tests/more_tests.rs +++ b/testing/fil_token_integration/tests/more_tests.rs @@ -116,13 +116,12 @@ fn more_tests() { tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); println!("minting return data {:#?}", &ret_val); let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); - // TOOD: mint result doesn't know the tokens were burned - // it needs some awareness of post-hook state + // tokens were burned so supply reduces back to zero println!("minted - total supply: {:?}", &mint_result.supply); - assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); + assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); - // check balance of test actor + // check balance of test actor, should also be zero let balance = tester.get_balance(operator[0].1, token_actor, test_actor); println!("balance held by test actor: {:?}", balance); - assert_eq!(balance, TokenAmount::from_atto(100)); + assert_eq!(balance, TokenAmount::from_atto(0)); } From 3071ff1005c0585931529fbb2e43a3017aa3d664 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 13 Sep 2022 12:57:35 +1000 Subject: [PATCH 08/32] wip better state reloading mechanism --- frc46_token/src/token/mod.rs | 7 ++++ .../actors/basic_token_actor/src/lib.rs | 40 ++++++++----------- .../fil_token_integration/tests/more_tests.rs | 5 ++- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/frc46_token/src/token/mod.rs b/frc46_token/src/token/mod.rs index b6f09458..a46f0354 100644 --- a/frc46_token/src/token/mod.rs +++ b/frc46_token/src/token/mod.rs @@ -105,6 +105,13 @@ where self.state = state; } + /// Loads a fresh copy of the state from a blockstore from a given cid, replacing existing state + /// The old state is returned to enable comparisons and the like but can be safely dropped otherwise + pub fn load_replace(&mut self, cid: &Cid) -> Result { + let new_state = TokenState::load(&self.bs, cid)?; + Ok(std::mem::replace(self.state, new_state)) + } + /// Opens an atomic transaction on TokenState which allows a closure to make multiple /// modifications to the state tree. /// diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index 4fe68212..7698dd83 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -1,6 +1,5 @@ mod util; -use cid::Cid; use frc46_token::token::types::{ AllowanceReturn, BalanceReturn, BurnFromReturn, BurnParams, BurnReturn, DecreaseAllowanceParams, FRC46Token, GetAllowanceParams, GranularityReturn, @@ -148,7 +147,8 @@ pub struct MintParams { impl Cbor for MintParams {} impl BasicToken<'_> { - fn mint(&mut self, params: MintParams) -> Result<(Cid, MintReturn), RuntimeError> { + fn mint(&mut self, params: MintParams) -> Result { + let owner = params.initial_owner; let mut hook = self.util.mint( &caller_address(), ¶ms.initial_owner, @@ -162,7 +162,19 @@ impl BasicToken<'_> { let ret = hook.call(self.util.msg())?; - Ok((cid, ret)) + let new_cid = sdk::sself::root().unwrap(); + let ret = if cid == new_cid { + ret + } else { + self.util.load_replace(&new_cid).unwrap(); + MintReturn { + balance: self.balance_of(owner).unwrap(), + supply: self.total_supply(), + recipient_data: ret.recipient_data, + } + }; + + Ok(ret) } } @@ -290,26 +302,8 @@ pub fn invoke(params: u32) -> u32 { 3839021839 => { // Mint let params: MintParams = deserialize_params(params); - let owner = params.initial_owner; - let (cid, res) = token_actor.mint(params).unwrap(); - - // TODO: we need to know if the cid changed. having mint return it kinda works but is messy - // but we also can't reload state inside those calls - let new_cid = sdk::sself::root().unwrap(); - if cid == new_cid { - return_ipld(&res).unwrap() - } else { - token_state = Token::<_, FvmMessenger>::load_state(&bs, &root_cid).unwrap(); - token_actor = BasicToken { - util: Token::wrap(bs, FvmMessenger::default(), 1, &mut token_state), - }; - - return_ipld(&MintReturn { - balance: token_actor.balance_of(owner).unwrap(), - supply: token_actor.total_supply(), - }) - .unwrap() - } + let res = token_actor.mint(params).unwrap(); + return_ipld(&res).unwrap() } _ => { sdk::vm::abort( diff --git a/testing/fil_token_integration/tests/more_tests.rs b/testing/fil_token_integration/tests/more_tests.rs index 07687ff5..8c183485 100644 --- a/testing/fil_token_integration/tests/more_tests.rs +++ b/testing/fil_token_integration/tests/more_tests.rs @@ -117,7 +117,10 @@ fn more_tests() { println!("minting return data {:#?}", &ret_val); let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); // tokens were burned so supply reduces back to zero - println!("minted - total supply: {:?}", &mint_result.supply); + println!( + "minted - total supply: {:?}, balance: {:?}", + &mint_result.supply, &mint_result.balance + ); assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); // check balance of test actor, should also be zero From e2557f6162124990a7b771d90750bc9c52d82c47 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 19 Sep 2022 10:42:48 +1000 Subject: [PATCH 09/32] more common stuff --- testing/fil_token_integration/tests/common.rs | 100 +++++++++++------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/testing/fil_token_integration/tests/common.rs b/testing/fil_token_integration/tests/common.rs index 157bcbcd..ed22e295 100644 --- a/testing/fil_token_integration/tests/common.rs +++ b/testing/fil_token_integration/tests/common.rs @@ -2,26 +2,19 @@ use std::env; use basic_token_actor::MintParams; use cid::Cid; -use fil_fungible_token::token::{state::TokenState, types::MintReturn}; use frc42_dispatch::method_hash; use fvm::{ executor::{ApplyKind, ApplyRet, Executor}, externs::Externs, }; -use fvm_integration_tests::{ - bundle, - dummy::DummyExterns, - tester::{Account, Tester}, -}; -use fvm_ipld_blockstore::{Blockstore, MemoryBlockstore}; +use fvm_integration_tests::{bundle, tester::Tester}; +use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::RawBytes; -use fvm_shared::address::Address; -use fvm_shared::bigint::Zero; -use fvm_shared::econ::TokenAmount; -use fvm_shared::message::Message; -use fvm_shared::state::StateTreeVersion; -use fvm_shared::version::NetworkVersion; -use serde::{Deserialize, Serialize}; +use fvm_shared::{ + address::Address, bigint::Zero, econ::TokenAmount, message::Message, state::StateTreeVersion, + version::NetworkVersion, +}; +use serde::Serialize; pub fn load_actor_wasm(path: &str) -> Vec { let wasm_path = env::current_dir().unwrap().join(path).canonicalize().unwrap(); @@ -29,6 +22,15 @@ pub fn load_actor_wasm(path: &str) -> Vec { std::fs::read(wasm_path).expect("unable to read actor file") } +/// Construct a Tester with the provided blockstore +/// mainly cuts down on noise with importing the built-in actor bundle and network/state tree versions +pub fn construct_tester(blockstore: &BS) -> Tester { + let bundle_root = bundle::import_bundle(&blockstore, actors_v10::BUNDLE_CAR).unwrap(); + + Tester::new(NetworkVersion::V15, StateTreeVersion::V4, bundle_root, blockstore.clone()).unwrap() +} + +/// Helper routines to simplify common operations with a Tester pub trait TestHelpers { /// Call a method on an actor fn call_method( @@ -39,15 +41,6 @@ pub trait TestHelpers { params: Option, ) -> ApplyRet; - /// Get balance from token actor for a given address - /// This is a very common thing to check during tests - fn get_balance( - &mut self, - operator: Address, - token_actor: Address, - target: Address, - ) -> TokenAmount; - /// Install an actor with initial state and ID /// Returns the actor's address fn install_actor_with_state( @@ -62,6 +55,27 @@ pub trait TestHelpers { fn install_actor_stateless(&mut self, path: &str, actor_id: u64) -> Address; } +/// Helper routines to simplify common token operations +pub trait TokenHelpers { + /// Get balance from token actor for a given address + /// This is a very common thing to check during tests + fn get_balance( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + ) -> TokenAmount; + + fn mint_tokens( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + amount: TokenAmount, + operator_data: RawBytes, + ) -> ApplyRet; +} + impl TestHelpers for Tester { fn call_method( &mut self, @@ -86,19 +100,6 @@ impl TestHelpers for Tester { self.executor.as_mut().unwrap().execute_message(message, ApplyKind::Explicit, 100).unwrap() } - fn get_balance( - &mut self, - operator: Address, - token_actor: Address, - target: Address, - ) -> TokenAmount { - let params = RawBytes::serialize(target).unwrap(); - let ret_val = - self.call_method(operator, token_actor, method_hash!("BalanceOf"), Some(params)); - println!("balance return data {:#?}", &ret_val); - ret_val.msg_receipt.return_data.deserialize::().unwrap() - } - fn install_actor_with_state( &mut self, path: &str, @@ -119,3 +120,30 @@ impl TestHelpers for Tester { address } } + +impl TokenHelpers for Tester { + fn get_balance( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + ) -> TokenAmount { + let params = RawBytes::serialize(target).unwrap(); + let ret_val = + self.call_method(operator, token_actor, method_hash!("BalanceOf"), Some(params)); + ret_val.msg_receipt.return_data.deserialize::().unwrap() + } + + fn mint_tokens( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + amount: TokenAmount, + operator_data: RawBytes, + ) -> ApplyRet { + let mint_params = MintParams { initial_owner: target, amount, operator_data }; + let params = RawBytes::serialize(mint_params).unwrap(); + self.call_method(operator, token_actor, method_hash!("Mint"), Some(params)) + } +} From 08137717b587102d679f96b1a4f9b29eb50c271d Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 19 Sep 2022 10:44:17 +1000 Subject: [PATCH 10/32] single-actor tests --- .../tests/frc46_single_actor_tests.rs | 180 ++++++++++++++++++ .../fil_token_integration/tests/more_tests.rs | 130 ------------- 2 files changed, 180 insertions(+), 130 deletions(-) create mode 100644 testing/fil_token_integration/tests/frc46_single_actor_tests.rs delete mode 100644 testing/fil_token_integration/tests/more_tests.rs diff --git a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs new file mode 100644 index 00000000..01216788 --- /dev/null +++ b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs @@ -0,0 +1,180 @@ +use frc42_dispatch::method_hash; +use frc46_token::token::{state::TokenState, types::MintReturn}; +use fvm_integration_tests::{dummy::DummyExterns, tester::Account}; +use fvm_ipld_blockstore::MemoryBlockstore; +use fvm_ipld_encoding::{ + tuple::{Deserialize_tuple, Serialize_tuple}, + RawBytes, +}; +use fvm_shared::{address::Address, econ::TokenAmount}; +use serde::{Deserialize, Serialize}; + +mod common; +use common::{construct_tester, TestHelpers, TokenHelpers}; + +const BASIC_TOKEN_ACTOR_WASM: &str = + "../../target/debug/wbuild/basic_token_actor/basic_token_actor.compact.wasm"; +const TEST_ACTOR_WASM: &str = "../../target/debug/wbuild/test_actor/test_actor.compact.wasm"; + +/// Action to take in receiver hook or Action method +/// This gets serialized and sent along as operator_data +#[derive(Serialize, Deserialize, Debug)] +pub enum TestAction { + /// Accept the tokens + Accept, + /// Reject the tokens (hook aborts) + Reject, + /// Transfer to another address (with operator_data that can provide further instructions) + Transfer(Address, RawBytes), + /// Burn incoming tokens + Burn, +} + +/// Params for Action method call +/// This gives us a way to supply the token address, since we won't get it as a sender like we do for hook calls +#[derive(Serialize_tuple, Deserialize_tuple, Debug)] +pub struct ActionParams { + /// Address of the token actor + token_address: Address, + /// Action to take with our token balance. Only Transfer and Burn actions apply here. + action: TestAction, +} + +/// Helper for nesting calls to create action sequences +/// eg. transfer and then the receiver hook rejects: +/// action(TestAction::Transfer( +/// some_address, +/// action(TestAction::Reject), +/// ), +/// ) +fn action(action: TestAction) -> RawBytes { + RawBytes::serialize(action).unwrap() +} + +/// This covers several simpler tests, which all involve a single receiving actor +/// They're combined because these integration tests take a long time to build and run +/// Test cases covered: +/// - mint to test actor who rejects in receiver hook +/// - mint to self (token actor - should be rejected) +/// - mint to test actor who burns tokens upon receipt (calling Burn from within the hook) +/// - test actor transfers back to token actor (should be rejected) +/// - test actor transfers to self (zero amount) +/// - test actor transfers to self (non-zero amount) +/// - test actor transfers to self and rejects +#[test] +fn frc46_single_actor_tests() { + let blockstore = MemoryBlockstore::default(); + let mut tester = construct_tester(&blockstore); + + let operator: [Account; 1] = tester.create_accounts().unwrap(); + + let initial_token_state = TokenState::new(&blockstore).unwrap(); + + // install actors required for our test: a token actor and one instance of the test actor + let token_actor = + tester.install_actor_with_state(BASIC_TOKEN_ACTOR_WASM, 10000, initial_token_state); + let test_actor = tester.install_actor_stateless(TEST_ACTOR_WASM, 10010); + + // Instantiate machine + tester.instantiate_machine(DummyExterns).unwrap(); + + // construct actors + let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Constructor"), None); + assert!(ret_val.msg_receipt.exit_code.is_success()); + let ret_val = tester.call_method(operator[0].1, test_actor, method_hash!("Constructor"), None); + assert!(ret_val.msg_receipt.exit_code.is_success()); + + // TEST: mint to test actor who rejects hook + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + action(TestAction::Reject), + ); + assert!(!ret_val.msg_receipt.exit_code.is_success()); + + // check balance of test actor, should be zero + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(0)); + + // TEST: mint to self (token actor), should be rejected + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + token_actor, + TokenAmount::from_atto(100), + action(TestAction::Reject), + ); + // should fail because the token actor has no receiver hook + assert!(!ret_val.msg_receipt.exit_code.is_success()); + + // TEST: mint to test actor, hook burns tokens immediately + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + action(TestAction::Burn), + ); + let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); + // tokens were burned so supply reduces back to zero + assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); + + // check balance of test actor, should also be zero + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(0)); + + // TEST: test actor transfers to self (zero amount) + let test_action = ActionParams { + token_address: token_actor, + action: TestAction::Transfer(test_actor, action(TestAction::Accept)), + }; + let params = RawBytes::serialize(test_action).unwrap(); + let ret_val = + tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + + // balance should remain zero + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(0)); + + // SETUP: we need a balance on the test actor for the next few tests + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + action(TestAction::Accept), + ); + let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); + assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(100)); + + // TEST: test actor transfers back to token actor (rejected, token actor has no hook) + let test_action = ActionParams { + token_address: token_actor, + action: TestAction::Transfer(token_actor, RawBytes::default()), + }; + let params = RawBytes::serialize(test_action).unwrap(); + let ret_val = + tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + assert!(!ret_val.msg_receipt.exit_code.is_success()); + // check that our test actor balance hasn't changed + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(100)); + + // TEST: test actor transfers to self (non-zero amount) + let test_action = ActionParams { + token_address: token_actor, + action: TestAction::Transfer(test_actor, action(TestAction::Accept)), + }; + let params = RawBytes::serialize(test_action).unwrap(); + let ret_val = + tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check that our test actor balance hasn't changed + let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(100)); +} diff --git a/testing/fil_token_integration/tests/more_tests.rs b/testing/fil_token_integration/tests/more_tests.rs deleted file mode 100644 index 8c183485..00000000 --- a/testing/fil_token_integration/tests/more_tests.rs +++ /dev/null @@ -1,130 +0,0 @@ -use basic_token_actor::MintParams; -use frc42_dispatch::method_hash; -use frc46_token::token::{state::TokenState, types::MintReturn}; -use fvm_integration_tests::{ - bundle, - dummy::DummyExterns, - tester::{Account, Tester}, -}; -use fvm_ipld_blockstore::MemoryBlockstore; -use fvm_ipld_encoding::{ - tuple::{Deserialize_tuple, Serialize_tuple}, - RawBytes, -}; -use fvm_shared::{ - address::Address, econ::TokenAmount, state::StateTreeVersion, version::NetworkVersion, -}; -use serde::{Deserialize, Serialize}; - -mod common; -use common::TestHelpers; - -const BASIC_TOKEN_ACTOR_WASM: &str = - "../../target/debug/wbuild/basic_token_actor/basic_token_actor.compact.wasm"; -const TEST_ACTOR_WASM: &str = "../../target/debug/wbuild/test_actor/test_actor.compact.wasm"; - -/// Action to take in receiver hook or Action method -/// This gets serialized and sent along as operator_data -#[derive(Serialize, Deserialize, Debug)] -pub enum TestAction { - /// Accept the tokens - Accept, - /// Reject the tokens (hook aborts) - Reject, - /// Transfer to another address (with operator_data that can provide further instructions) - Transfer(Address, RawBytes), - /// Burn incoming tokens - Burn, -} - -/// Params for Action method call -/// This gives us a way to supply the token address, since we won't get it as a sender like we do for hook calls -#[derive(Serialize_tuple, Deserialize_tuple, Debug)] -pub struct ActionParams { - /// Address of the token actor - token_address: Address, - /// Action to take with our token balance. Only Transfer and Burn actions apply here. - action: TestAction, -} - -/// Helper for nesting calls to create action sequences -/// eg. transfer and then the receiver hook rejects: -/// action(TestAction::Transfer( -/// some_address, -/// action(TestAction::Reject), -/// ), -/// ) -fn action(action: TestAction) -> RawBytes { - RawBytes::serialize(action).unwrap() -} - -#[test] -fn more_tests() { - let blockstore = MemoryBlockstore::default(); - let bundle_root = bundle::import_bundle(&blockstore, actors_v10::BUNDLE_CAR).unwrap(); - let mut tester = - Tester::new(NetworkVersion::V15, StateTreeVersion::V4, bundle_root, blockstore.clone()) - .unwrap(); - - let operator: [Account; 1] = tester.create_accounts().unwrap(); - - let initial_token_state = TokenState::new(&blockstore).unwrap(); - - // install actors required for our test: a token actor and one instance of the test actor - let token_actor = - tester.install_actor_with_state(BASIC_TOKEN_ACTOR_WASM, 10000, initial_token_state); - let test_actor = tester.install_actor_stateless(TEST_ACTOR_WASM, 10010); - - // Instantiate machine - tester.instantiate_machine(DummyExterns).unwrap(); - - // construct actors - let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Constructor"), None); - assert!(ret_val.msg_receipt.exit_code.is_success()); - let ret_val = tester.call_method(operator[0].1, test_actor, method_hash!("Constructor"), None); - assert!(ret_val.msg_receipt.exit_code.is_success()); - - // mint some tokens, hook will reject - let mint_params = MintParams { - initial_owner: test_actor, - amount: TokenAmount::from_atto(100), - operator_data: action(TestAction::Reject), - }; - let params = RawBytes::serialize(mint_params).unwrap(); - let ret_val = - tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); - println!("minting return data {:#?}", &ret_val); - if ret_val.msg_receipt.exit_code.is_success() { - let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); - println!("minted - total supply: {:?}", &mint_result.supply); - assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); - } - - // check balance of test actor - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); - println!("balance held by test actor: {:?}", balance); - assert_eq!(balance, TokenAmount::from_atto(0)); - - // mint again, hook will burn - let mint_params = MintParams { - initial_owner: test_actor, - amount: TokenAmount::from_atto(100), - operator_data: action(TestAction::Burn), - }; - let params = RawBytes::serialize(mint_params).unwrap(); - let ret_val = - tester.call_method(operator[0].1, token_actor, method_hash!("Mint"), Some(params)); - println!("minting return data {:#?}", &ret_val); - let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); - // tokens were burned so supply reduces back to zero - println!( - "minted - total supply: {:?}, balance: {:?}", - &mint_result.supply, &mint_result.balance - ); - assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); - - // check balance of test actor, should also be zero - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); - println!("balance held by test actor: {:?}", balance); - assert_eq!(balance, TokenAmount::from_atto(0)); -} From a8cec0bbf6cf9fea074d63e1994a58394c71626c Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 19 Sep 2022 13:37:19 +1000 Subject: [PATCH 11/32] keep TestAction and ActionParams as part of test_actor --- .../actors/test_actor/src/lib.rs | 16 ++++++- .../tests/frc46_single_actor_tests.rs | 44 ++----------------- 2 files changed, 17 insertions(+), 43 deletions(-) diff --git a/testing/fil_token_integration/actors/test_actor/src/lib.rs b/testing/fil_token_integration/actors/test_actor/src/lib.rs index 1412e37e..29743bb8 100644 --- a/testing/fil_token_integration/actors/test_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/test_actor/src/lib.rs @@ -35,12 +35,24 @@ pub enum TestAction { } /// Params for Action method call +/// This gives us a way to supply the token address, since we won't get it as a sender like we do for hook calls #[derive(Serialize_tuple, Deserialize_tuple, Debug)] pub struct ActionParams { /// Address of the token actor - token_address: Address, + pub token_address: Address, /// Action to take with our token balance. Only Transfer and Burn actions apply here. - action: TestAction, + pub action: TestAction, +} + +/// Helper for nesting calls to create action sequences +/// eg. transfer and then the receiver hook rejects: +/// action(TestAction::Transfer( +/// some_address, +/// action(TestAction::Reject), +/// ), +/// ) +pub fn action(action: TestAction) -> RawBytes { + RawBytes::serialize(action).unwrap() } #[no_mangle] diff --git a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs index 01216788..6c43b242 100644 --- a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs @@ -2,55 +2,17 @@ use frc42_dispatch::method_hash; use frc46_token::token::{state::TokenState, types::MintReturn}; use fvm_integration_tests::{dummy::DummyExterns, tester::Account}; use fvm_ipld_blockstore::MemoryBlockstore; -use fvm_ipld_encoding::{ - tuple::{Deserialize_tuple, Serialize_tuple}, - RawBytes, -}; -use fvm_shared::{address::Address, econ::TokenAmount}; -use serde::{Deserialize, Serialize}; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::econ::TokenAmount; mod common; use common::{construct_tester, TestHelpers, TokenHelpers}; +use test_actor::{action, ActionParams, TestAction}; const BASIC_TOKEN_ACTOR_WASM: &str = "../../target/debug/wbuild/basic_token_actor/basic_token_actor.compact.wasm"; const TEST_ACTOR_WASM: &str = "../../target/debug/wbuild/test_actor/test_actor.compact.wasm"; -/// Action to take in receiver hook or Action method -/// This gets serialized and sent along as operator_data -#[derive(Serialize, Deserialize, Debug)] -pub enum TestAction { - /// Accept the tokens - Accept, - /// Reject the tokens (hook aborts) - Reject, - /// Transfer to another address (with operator_data that can provide further instructions) - Transfer(Address, RawBytes), - /// Burn incoming tokens - Burn, -} - -/// Params for Action method call -/// This gives us a way to supply the token address, since we won't get it as a sender like we do for hook calls -#[derive(Serialize_tuple, Deserialize_tuple, Debug)] -pub struct ActionParams { - /// Address of the token actor - token_address: Address, - /// Action to take with our token balance. Only Transfer and Burn actions apply here. - action: TestAction, -} - -/// Helper for nesting calls to create action sequences -/// eg. transfer and then the receiver hook rejects: -/// action(TestAction::Transfer( -/// some_address, -/// action(TestAction::Reject), -/// ), -/// ) -fn action(action: TestAction) -> RawBytes { - RawBytes::serialize(action).unwrap() -} - /// This covers several simpler tests, which all involve a single receiving actor /// They're combined because these integration tests take a long time to build and run /// Test cases covered: From fc5a45ac0a49127717e3d2bc7636248465fd8d37 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Mon, 19 Sep 2022 16:00:06 +1000 Subject: [PATCH 12/32] duplicate MintParams in common to avoid linking in the token actor everywhere can only link against one actor for shared structs, because of the invoke methods using un-mangled names (causing collisions at link time if more than one is included) --- testing/fil_token_integration/tests/common.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/testing/fil_token_integration/tests/common.rs b/testing/fil_token_integration/tests/common.rs index ed22e295..6c4cceb9 100644 --- a/testing/fil_token_integration/tests/common.rs +++ b/testing/fil_token_integration/tests/common.rs @@ -1,6 +1,5 @@ use std::env; -use basic_token_actor::MintParams; use cid::Cid; use frc42_dispatch::method_hash; use fvm::{ @@ -9,13 +8,28 @@ use fvm::{ }; use fvm_integration_tests::{bundle, tester::Tester}; use fvm_ipld_blockstore::Blockstore; -use fvm_ipld_encoding::RawBytes; +use fvm_ipld_encoding::{ + tuple::{Deserialize_tuple, Serialize_tuple}, + Cbor, RawBytes, +}; use fvm_shared::{ address::Address, bigint::Zero, econ::TokenAmount, message::Message, state::StateTreeVersion, version::NetworkVersion, }; use serde::Serialize; +// this is here so we don't need to link every test against the basic_token_actor +// otherwise we can't link against test_actor or any other test/example actors, +// because the invoke() functions will conflict at link time +#[derive(Serialize_tuple, Deserialize_tuple, Clone, Debug)] +pub struct MintParams { + pub initial_owner: Address, + pub amount: TokenAmount, + pub operator_data: RawBytes, +} + +impl Cbor for MintParams {} + pub fn load_actor_wasm(path: &str) -> Vec { let wasm_path = env::current_dir().unwrap().join(path).canonicalize().unwrap(); From 9f530df5a85f6ac288e79bf44a020dc107711a9f Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 20 Sep 2022 10:55:01 +1000 Subject: [PATCH 13/32] wip multi-actor tests --- .../tests/frc46_multi_actor_tests.rs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 testing/fil_token_integration/tests/frc46_multi_actor_tests.rs diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs new file mode 100644 index 00000000..c384a8dd --- /dev/null +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -0,0 +1,91 @@ +use frc42_dispatch::method_hash; +use frc46_token::token::state::TokenState; +use fvm_integration_tests::{dummy::DummyExterns, tester::Account}; +use fvm_ipld_blockstore::MemoryBlockstore; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::{address::Address, econ::TokenAmount}; + +mod common; +use common::{construct_tester, TestHelpers, TokenHelpers}; +use test_actor::{action, ActionParams, TestAction}; + +const BASIC_TOKEN_ACTOR_WASM: &str = + "../../target/debug/wbuild/basic_token_actor/basic_token_actor.compact.wasm"; +const TEST_ACTOR_WASM: &str = "../../target/debug/wbuild/test_actor/test_actor.compact.wasm"; + +fn action_params(token_address: Address, action: TestAction) -> RawBytes { + RawBytes::serialize(ActionParams { token_address, action }).unwrap() +} + +#[test] +fn frc46_multi_actor_tests() { + let blockstore = MemoryBlockstore::default(); + let mut tester = construct_tester(&blockstore); + + let operator: [Account; 1] = tester.create_accounts().unwrap(); + + let initial_token_state = TokenState::new(&blockstore).unwrap(); + + let token_actor = + tester.install_actor_with_state(BASIC_TOKEN_ACTOR_WASM, 10000, initial_token_state); + // we'll use up to four actors for some of these tests, though most use only two + let alice = tester.install_actor_stateless(TEST_ACTOR_WASM, 10010); + let bob = tester.install_actor_stateless(TEST_ACTOR_WASM, 10011); + let carol = tester.install_actor_stateless(TEST_ACTOR_WASM, 10012); + let dave = tester.install_actor_stateless(TEST_ACTOR_WASM, 10013); + + // Instantiate machine + tester.instantiate_machine(DummyExterns).unwrap(); + + // construct actors + for actor in [token_actor, alice, bob, carol, dave] { + let ret_val = tester.call_method(operator[0].1, actor, method_hash!("Constructor"), None); + assert!(ret_val.msg_receipt.exit_code.is_success()); + } + + // TEST: alice sends bob a transfer of zero amount (rejecting first time and then accepting) + // first, tell bob to reject it + let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); + let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + // we told bob to reject, so transfer should fail + assert!(!ret_val.msg_receipt.exit_code.is_success()); + + // this time tell bob to accept it + let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); + let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + // we told bob to accept this time, so transfer should succeed + assert!(ret_val.msg_receipt.exit_code.is_success()); + + // balance should remain zero + let balance = tester.get_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.get_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(0)); + + // TEST: alice sends bob a transfer of a non-zero amounnt. As before, we'll reject it the first time then accept + // mint some tokens to alice first + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Accept), + ); + assert!(ret_val.msg_receipt.exit_code.is_success()); + let balance = tester.get_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(100)); + // now send to bob, who will reject them + let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); + let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(!ret_val.msg_receipt.exit_code.is_success()); + + // transfer to bob who will accept it this time + let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); + let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check balances + let balance = tester.get_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.get_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(100)); +} From 21cacacae1c0b9a5eaa22f7a20b764f034a41cb4 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 20 Sep 2022 12:03:14 +1000 Subject: [PATCH 14/32] don't explode the test actor if transfer within receiver hook fails original recipient just keeps the tokens in this case, instead of rejecting the transfer they originally received --- testing/fil_token_integration/actors/test_actor/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/testing/fil_token_integration/actors/test_actor/src/lib.rs b/testing/fil_token_integration/actors/test_actor/src/lib.rs index 29743bb8..7dca5b14 100644 --- a/testing/fil_token_integration/actors/test_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/test_actor/src/lib.rs @@ -99,10 +99,8 @@ fn invoke(input: u32) -> u32 { amount: token_params.amount, operator_data, }; - let receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); - if !receipt.exit_code.is_success() { - panic!("transfer call failed"); - } + let _receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); + // transfer failures are ignored - we just keep the tokens here } TestAction::Burn => { // burn the tokens From 7c48437978e2a891c359cd9190db6903d7540410 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 20 Sep 2022 12:14:16 +1000 Subject: [PATCH 15/32] add test to mint tokens and then transfer inside the receiver hook --- .../tests/frc46_multi_actor_tests.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index c384a8dd..4704d8eb 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -88,4 +88,37 @@ fn frc46_multi_actor_tests() { assert_eq!(balance, TokenAmount::from_atto(0)); let balance = tester.get_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(100)); + + // TEST: mint to alice who transfers to bob inside receiver hook, bob accepts + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Transfer(bob, action(TestAction::Accept))), + ); + assert!(ret_val.msg_receipt.exit_code.is_success()); + let balance = tester.get_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.get_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + + // TEST: mint to alice who transfers to bob inside receiver hook, bob rejects + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Transfer(bob, action(TestAction::Reject))), + ); + // mint succeeds but the transfer inside the receiver hook would have failed + assert!(ret_val.msg_receipt.exit_code.is_success()); + // alice should keep tokens in this case + let balance = tester.get_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(100)); + // bob's balance should remain unchanged + let balance = tester.get_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + + } From 30c0dfbd8828dce537182793de0824c0123adbb2 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 20 Sep 2022 15:42:23 +1000 Subject: [PATCH 16/32] update Transfer and TransferFrom to have the same post-hook state handling as Mint does --- .../actors/basic_token_actor/src/lib.rs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index 7698dd83..ad0f1ba2 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -52,6 +52,7 @@ impl FRC46Token for BasicToken<'_> { fn transfer(&mut self, params: TransferParams) -> Result { let operator = caller_address(); + let to = params.to; let mut hook = self.util.transfer( &operator, ¶ms.to, @@ -65,6 +66,19 @@ impl FRC46Token for BasicToken<'_> { let ret = hook.call(self.util.msg())?; + let new_cid = sdk::sself::root().unwrap(); + let ret = if cid == new_cid { + ret + } else { + // state has changed, update return data with new balances + self.util.load_replace(&new_cid)?; + TransferReturn { + from_balance: self.balance_of(operator)?, + to_balance: self.balance_of(to)?, + recipient_data: ret.recipient_data, + } + }; + Ok(ret) } @@ -73,6 +87,8 @@ impl FRC46Token for BasicToken<'_> { params: frc46_token::token::types::TransferFromParams, ) -> Result { let operator = caller_address(); + let from = params.from; + let to = params.to; let mut hook = self.util.transfer_from( &operator, ¶ms.from, @@ -87,6 +103,20 @@ impl FRC46Token for BasicToken<'_> { let ret = hook.call(self.util.msg())?; + let new_cid = sdk::sself::root().unwrap(); + let ret = if cid == new_cid { + ret + } else { + // state has changed, update return data with new balances + self.util.load_replace(&new_cid)?; + TransferFromReturn { + from_balance: self.balance_of(from)?, + to_balance: self.balance_of(to)?, + allowance: ret.allowance, // allowance remains unchanged? + recipient_data: ret.recipient_data, + } + }; + Ok(ret) } @@ -166,9 +196,10 @@ impl BasicToken<'_> { let ret = if cid == new_cid { ret } else { - self.util.load_replace(&new_cid).unwrap(); + // state has changed, update return data with new amounts + self.util.load_replace(&new_cid)?; MintReturn { - balance: self.balance_of(owner).unwrap(), + balance: self.balance_of(owner)?, supply: self.total_supply(), recipient_data: ret.recipient_data, } @@ -284,16 +315,12 @@ pub fn invoke(params: u32) -> u32 { // TransferFrom let params = deserialize_params(params); let res = token_actor.transfer_from(params).unwrap(); - let cid = token_actor.util.flush().unwrap(); - sdk::sself::set_root(&cid).unwrap(); return_ipld(&res).unwrap() } 1303003700 => { // Transfer let params = deserialize_params(params); let res = token_actor.transfer(params).unwrap(); - let cid = token_actor.util.flush().unwrap(); - sdk::sself::set_root(&cid).unwrap(); return_ipld(&res).unwrap() } From e81c3add5844d124a08ee4157bf9a2a99ac4924e Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 20 Sep 2022 15:47:31 +1000 Subject: [PATCH 17/32] return Transfer call result in test actor the caller can check for success or failure of the transfer and also has the TransferReturn data available for use --- .../actors/test_actor/src/lib.rs | 31 ++++++++++++------- .../tests/frc46_multi_actor_tests.rs | 14 ++++++--- .../tests/frc46_single_actor_tests.rs | 8 +++-- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/testing/fil_token_integration/actors/test_actor/src/lib.rs b/testing/fil_token_integration/actors/test_actor/src/lib.rs index 7dca5b14..2991bead 100644 --- a/testing/fil_token_integration/actors/test_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/test_actor/src/lib.rs @@ -6,7 +6,7 @@ use frc46_token::{ use fvm_ipld_encoding::{ de::DeserializeOwned, tuple::{Deserialize_tuple, Serialize_tuple}, - RawBytes, + RawBytes, DAG_CBOR, }; use fvm_sdk as sdk; use fvm_shared::{address::Address, bigint::Zero, econ::TokenAmount, error::ExitCode}; @@ -20,6 +20,14 @@ pub fn deserialize_params(params: u32) -> O { params.deserialize().unwrap() } +fn return_ipld(value: &T) -> u32 +where + T: Serialize + ?Sized, +{ + let bytes = fvm_ipld_encoding::to_vec(value).unwrap(); + sdk::ipld::put_block(DAG_CBOR, &bytes).unwrap() +} + /// Action to take in receiver hook or Action method /// This gets serialized and sent along as operator_data #[derive(Serialize, Deserialize, Debug)] @@ -84,6 +92,7 @@ fn invoke(input: u32) -> u32 { match action { TestAction::Accept => { // do nothing, return success + NO_DATA_BLOCK_ID } TestAction::Reject => { // abort to reject transfer @@ -99,8 +108,9 @@ fn invoke(input: u32) -> u32 { amount: token_params.amount, operator_data, }; - let _receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); - // transfer failures are ignored - we just keep the tokens here + let receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); + // ignore failures at this level and return the transfer call receipt so caller can decide what to do + return_ipld(&receipt) } TestAction::Burn => { // burn the tokens @@ -111,11 +121,9 @@ fn invoke(input: u32) -> u32 { if !receipt.exit_code.is_success() { panic!("burn call failed"); } + NO_DATA_BLOCK_ID } } - - // all good, don't need to return anything - NO_DATA_BLOCK_ID }, "Action" => { // take action independent of the receiver hook @@ -134,9 +142,11 @@ fn invoke(input: u32) -> u32 { match params.action { TestAction::Accept => { // nothing to do here + NO_DATA_BLOCK_ID } TestAction::Reject => { // nothing to do here + NO_DATA_BLOCK_ID } TestAction::Transfer(to, operator_data) => { // transfer to a target address @@ -147,9 +157,8 @@ fn invoke(input: u32) -> u32 { operator_data, }; let receipt = sdk::send::send(¶ms.token_address, method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); - if !receipt.exit_code.is_success() { - panic!("transfer call failed"); - } + // ignore failures at this level and return the transfer call receipt so caller can decide what to do + return_ipld(&receipt) } TestAction::Burn => { // burn the tokens @@ -161,11 +170,9 @@ fn invoke(input: u32) -> u32 { if !receipt.exit_code.is_success() { panic!("burn call failed"); } + NO_DATA_BLOCK_ID } } - - // all good, don't need to return anything - NO_DATA_BLOCK_ID } _ => { sdk::vm::abort( diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index 4704d8eb..8c269f88 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -3,7 +3,7 @@ use frc46_token::token::state::TokenState; use fvm_integration_tests::{dummy::DummyExterns, tester::Account}; use fvm_ipld_blockstore::MemoryBlockstore; use fvm_ipld_encoding::RawBytes; -use fvm_shared::{address::Address, econ::TokenAmount}; +use fvm_shared::{address::Address, econ::TokenAmount, receipt::Receipt}; mod common; use common::{construct_tester, TestHelpers, TokenHelpers}; @@ -47,8 +47,11 @@ fn frc46_multi_actor_tests() { // first, tell bob to reject it let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - // we told bob to reject, so transfer should fail - assert!(!ret_val.msg_receipt.exit_code.is_success()); + // we told bob to reject, so the action call should return success but give us the error result as return data + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check the receipt we got in return data + let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); + assert!(!receipt.exit_code.is_success()); // this time tell bob to accept it let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); @@ -77,7 +80,10 @@ fn frc46_multi_actor_tests() { // now send to bob, who will reject them let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(!ret_val.msg_receipt.exit_code.is_success()); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check the receipt we got in return data + let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); + assert!(!receipt.exit_code.is_success()); // transfer to bob who will accept it this time let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); diff --git a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs index 6c43b242..abd6e349 100644 --- a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs @@ -3,7 +3,7 @@ use frc46_token::token::{state::TokenState, types::MintReturn}; use fvm_integration_tests::{dummy::DummyExterns, tester::Account}; use fvm_ipld_blockstore::MemoryBlockstore; use fvm_ipld_encoding::RawBytes; -use fvm_shared::econ::TokenAmount; +use fvm_shared::{econ::TokenAmount, receipt::Receipt}; mod common; use common::{construct_tester, TestHelpers, TokenHelpers}; @@ -122,7 +122,11 @@ fn frc46_single_actor_tests() { let params = RawBytes::serialize(test_action).unwrap(); let ret_val = tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); - assert!(!ret_val.msg_receipt.exit_code.is_success()); + // action call should succeed, we'll dig into the return data to see the transfer call failure + assert!(ret_val.msg_receipt.exit_code.is_success()); + // return data is the Receipt from calling Transfer, which should show failure + let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); + assert!(!receipt.exit_code.is_success()); // check that our test actor balance hasn't changed let balance = tester.get_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(100)); From c494ff99b2fb4e7d4e479ee071141c2f15449544 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 20 Sep 2022 15:49:55 +1000 Subject: [PATCH 18/32] add transfer->hook transfer->accept/burn tests --- .../tests/frc46_multi_actor_tests.rs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index 8c269f88..17215c42 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -126,5 +126,46 @@ fn frc46_multi_actor_tests() { let balance = tester.get_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(200)); + // TEST: alice transfers to bob, bob transfers to carol (from hook), carol accepts + let params = action_params( + token_actor, + TestAction::Transfer(bob, action(TestAction::Transfer(carol, action(TestAction::Accept)))), + ); + let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check balances - alice should be empty, bob should keep 200, carol sitting on 100 + let balance = tester.get_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.get_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + let balance = tester.get_balance(operator[0].1, token_actor, carol); + assert_eq!(balance, TokenAmount::from_atto(100)); + // TEST: alice transfers to bob, bob transfers to carol (from hook), carol burns (from hook) + // mint a bit to alice first + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Accept), + ); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // now transfer alice->bob->carol and have carol burn the incoming balance + let params = action_params( + token_actor, + TestAction::Transfer(bob, action(TestAction::Transfer(carol, action(TestAction::Burn)))), + ); + let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check the receipt we got in return data + let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); + assert!(receipt.exit_code.is_success()); + // check balances - alice should be empty, bob should keep 200, carol sitting on 100 + let balance = tester.get_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.get_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + let balance = tester.get_balance(operator[0].1, token_actor, carol); + assert_eq!(balance, TokenAmount::from_atto(100)); } From ea6793cdbd525a03b8d39ccc850901d55592f705 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Tue, 20 Sep 2022 17:28:21 +1000 Subject: [PATCH 19/32] update transfer_tokens test to use the common helpers --- .../tests/transfer_tokens.rs | 148 +++--------------- 1 file changed, 24 insertions(+), 124 deletions(-) diff --git a/testing/fil_token_integration/tests/transfer_tokens.rs b/testing/fil_token_integration/tests/transfer_tokens.rs index 3976c0fa..b669f983 100644 --- a/testing/fil_token_integration/tests/transfer_tokens.rs +++ b/testing/fil_token_integration/tests/transfer_tokens.rs @@ -1,29 +1,16 @@ -use std::env; - -use basic_token_actor::MintParams; -use cid::Cid; use frc42_dispatch::method_hash; use frc46_token::token::{state::TokenState, types::MintReturn}; -use fvm::{ - executor::{ApplyKind, ApplyRet, Executor}, - externs::Externs, -}; -use fvm_integration_tests::{ - bundle, - dummy::DummyExterns, - tester::{Account, Tester}, -}; -use fvm_ipld_blockstore::{Blockstore, MemoryBlockstore}; +use fvm_integration_tests::{dummy::DummyExterns, tester::Account}; +use fvm_ipld_blockstore::MemoryBlockstore; use fvm_ipld_encoding::{ tuple::{Deserialize_tuple, Serialize_tuple}, RawBytes, }; use fvm_shared::address::Address; -use fvm_shared::bigint::Zero; use fvm_shared::econ::TokenAmount; -use fvm_shared::message::Message; -use fvm_shared::state::StateTreeVersion; -use fvm_shared::version::NetworkVersion; + +mod common; +use common::{construct_tester, TestHelpers, TokenHelpers}; const BASIC_TOKEN_ACTOR_WASM: &str = "../../target/debug/wbuild/basic_token_actor/basic_token_actor.compact.wasm"; @@ -38,132 +25,45 @@ struct TransferActorState { token_address: Option
, } -fn load_actor_wasm(path: &str) -> Vec { - let wasm_path = env::current_dir().unwrap().join(path).canonicalize().unwrap(); - - std::fs::read(wasm_path).expect("unable to read actor file") -} - -trait TestHelpers { - fn call_method( - &mut self, - from: Address, - to: Address, - method_num: u64, - params: Option, - ) -> ApplyRet; - fn check_balance( - &mut self, - operator: Address, - token_actor: Address, - target: Address, - ) -> TokenAmount; -} -impl TestHelpers for Tester { - fn call_method( - &mut self, - from: Address, - to: Address, - method_num: u64, - params: Option, - ) -> ApplyRet { - static mut SEQUENCE: u64 = 0u64; - let message = Message { - from, - to, - gas_limit: 99999999, - method_num, - sequence: unsafe { SEQUENCE }, - params: if let Some(params) = params { params } else { RawBytes::default() }, - ..Message::default() - }; - unsafe { - SEQUENCE += 1; - } - self.executor.as_mut().unwrap().execute_message(message, ApplyKind::Explicit, 100).unwrap() - } - - fn check_balance( - &mut self, - operator: Address, - token_actor: Address, - target: Address, - ) -> TokenAmount { - let params = RawBytes::serialize(target).unwrap(); - let ret_val = - self.call_method(operator, token_actor, method_hash!("BalanceOf"), Some(params)); - println!("balance return data {:#?}", &ret_val); - ret_val.msg_receipt.return_data.deserialize::().unwrap() - } -} - #[test] fn transfer_tokens() { let blockstore = MemoryBlockstore::default(); - let bundle_root = bundle::import_bundle(&blockstore, actors_v10::BUNDLE_CAR).unwrap(); - let mut tester = - Tester::new(NetworkVersion::V15, StateTreeVersion::V4, bundle_root, blockstore.clone()) - .unwrap(); + let mut tester = construct_tester(&blockstore); let operator: [Account; 1] = tester.create_accounts().unwrap(); - // token actor - let token_bin = load_actor_wasm(BASIC_TOKEN_ACTOR_WASM); - // transfer actor - let transfer_bin = load_actor_wasm(BASIC_TRANSFER_ACTOR_WASM); - // account actors - let receiver_bin = load_actor_wasm(BASIC_RECEIVER_ACTOR_WASM); - let token_state = TokenState::new(&blockstore).unwrap(); - let token_cid = tester.set_state(&token_state).unwrap(); - - // transfer actor state let transfer_state = TransferActorState { operator_address: None, token_address: None }; - let transfer_cid = tester.set_state(&transfer_state).unwrap(); - let token_address = Address::new_id(10000); - let transfer_address = Address::new_id(10010); - let receiver_address = Address::new_id(10020); - tester.set_actor_from_bin(&token_bin, token_cid, token_address, TokenAmount::zero()).unwrap(); - tester - .set_actor_from_bin(&transfer_bin, transfer_cid, transfer_address, TokenAmount::zero()) - .unwrap(); - tester - .set_actor_from_bin(&receiver_bin, Cid::default(), receiver_address, TokenAmount::zero()) - .unwrap(); + let token_address = tester.install_actor_with_state(BASIC_TOKEN_ACTOR_WASM, 10000, token_state); + let transfer_address = + tester.install_actor_with_state(BASIC_TRANSFER_ACTOR_WASM, 10010, transfer_state); + let receiver_address = tester.install_actor_stateless(BASIC_RECEIVER_ACTOR_WASM, 10020); // Instantiate machine tester.instantiate_machine(DummyExterns).unwrap(); // construct actors - let ret_val = - tester.call_method(operator[0].1, token_address, method_hash!("Constructor"), None); - println!("token actor constructor return data: {:#?}", &ret_val); - - let ret_val = - tester.call_method(operator[0].1, transfer_address, method_hash!("Constructor"), None); - println!("transfer actor constructor return data: {:#?}", &ret_val); - - let ret_val = - tester.call_method(operator[0].1, receiver_address, method_hash!("Constructor"), None); - println!("receiving actor constructor return data: {:#?}", &ret_val); + for actor in [token_address, transfer_address, receiver_address] { + let ret_val = tester.call_method(operator[0].1, actor, method_hash!("Constructor"), None); + assert!(ret_val.msg_receipt.exit_code.is_success()); + } // mint some tokens - let mint_params = MintParams { - initial_owner: transfer_address, - amount: TokenAmount::from_atto(100), - operator_data: RawBytes::default(), - }; - let params = RawBytes::serialize(mint_params).unwrap(); - let ret_val = - tester.call_method(operator[0].1, token_address, method_hash!("Mint"), Some(params)); + let ret_val = tester.mint_tokens( + operator[0].1, + token_address, + transfer_address, + TokenAmount::from_atto(100), + RawBytes::default(), + ); println!("minting return data {:#?}", &ret_val); let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); println!("minted - total supply: {:?}", &mint_result.supply); assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); // check balance of transfer actor - let balance = tester.check_balance(operator[0].1, token_address, transfer_address); + let balance = tester.get_balance(operator[0].1, token_address, transfer_address); println!("balance held by transfer actor: {:?}", balance); assert_eq!(balance, TokenAmount::from_atto(100)); @@ -174,11 +74,11 @@ fn transfer_tokens() { println!("forwarding return data {:#?}", &ret_val); // check balance of receiver actor - let balance = tester.check_balance(operator[0].1, token_address, transfer_address); + let balance = tester.get_balance(operator[0].1, token_address, transfer_address); println!("balance held by transfer actor: {:?}", balance); assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.check_balance(operator[0].1, token_address, receiver_address); + let balance = tester.get_balance(operator[0].1, token_address, receiver_address); println!("balance held by receiver actor: {:?}", balance); assert_eq!(balance, TokenAmount::from_atto(100)); } From 3fbc19b418b856787a885ee197e06aa99c08077c Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 12:21:00 +1000 Subject: [PATCH 20/32] improve Token::replace and make public --- frc46_token/src/token/mod.rs | 41 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/frc46_token/src/token/mod.rs b/frc46_token/src/token/mod.rs index a46f0354..b5d41156 100644 --- a/frc46_token/src/token/mod.rs +++ b/frc46_token/src/token/mod.rs @@ -76,11 +76,24 @@ where Self { bs, msg, granularity, state } } + /// Replace the current state with another + /// The previous state is returned and can be safely dropped + pub fn replace(&mut self, state: TokenState) -> TokenState { + std::mem::replace(self.state, state) + } + /// For an already initialised state tree, loads the state tree from the blockstore at a Cid pub fn load_state(bs: &BS, state_cid: &Cid) -> Result { Ok(TokenState::load(bs, state_cid)?) } + /// Loads a fresh copy of the state from a blockstore from a given cid, replacing existing state + /// The old state is returned to enable comparisons and the like but can be safely dropped otherwise + pub fn load_replace(&mut self, cid: &Cid) -> Result { + let new_state = TokenState::load(&self.bs, cid)?; + Ok(std::mem::replace(self.state, new_state)) + } + /// Flush state and return Cid for root pub fn flush(&mut self) -> Result { Ok(self.state.save(&self.bs)?) @@ -96,22 +109,6 @@ where &self.msg } - /// Replace the current state reference with another - /// This is intended for unit tests only (and enforced by the config and visibility limits) - /// The replacement state needs the same lifetime as the original so cloning it inside - /// actor method calls generally wouldn't work. - #[cfg(test)] - pub(in crate::token) fn replace(&mut self, state: &'st mut TokenState) { - self.state = state; - } - - /// Loads a fresh copy of the state from a blockstore from a given cid, replacing existing state - /// The old state is returned to enable comparisons and the like but can be safely dropped otherwise - pub fn load_replace(&mut self, cid: &Cid) -> Result { - let new_state = TokenState::load(&self.bs, cid)?; - Ok(std::mem::replace(self.state, new_state)) - } - /// Opens an atomic transaction on TokenState which allows a closure to make multiple /// modifications to the state tree. /// @@ -1109,7 +1106,7 @@ mod test { // force hook to abort token.msg.abort_next_send(); - let mut original_state = token.state().clone(); + let original_state = token.state().clone(); let mut hook = token .mint( TOKEN_ACTOR, @@ -1131,7 +1128,7 @@ mod test { assert_eq!(amount, TokenAmount::from_atto(1_000_000)); // restore original pre-mint state // in actor code, we'd just abort and let the VM handle this - token.replace(&mut original_state); + token.replace(original_state); } _ => panic!("expected receiver hook error"), }; @@ -1570,7 +1567,7 @@ mod test { // transfer 60 from owner -> receiver, but simulate receiver aborting the hook token.msg.abort_next_send(); - let mut pre_transfer_state = token.state().clone(); + let pre_transfer_state = token.state().clone(); let mut hook = token .transfer( ALICE, @@ -1592,7 +1589,7 @@ mod test { assert_eq!(amount, TokenAmount::from_atto(60)); // revert to pre-transfer state // in actor code, we'd just abort and let the VM handle this - token.replace(&mut pre_transfer_state); + token.replace(pre_transfer_state); } _ => panic!("expected receiver hook error"), }; @@ -1603,7 +1600,7 @@ mod test { // transfer 60 from owner -> self, simulate receiver aborting the hook token.msg.abort_next_send(); - let mut pre_transfer_state = token.state().clone(); + let pre_transfer_state = token.state().clone(); let mut hook = token .transfer( ALICE, @@ -1625,7 +1622,7 @@ mod test { assert_eq!(amount, TokenAmount::from_atto(60)); // revert to pre-transfer state // in actor code, we'd just abort and let the VM handle this - token.replace(&mut pre_transfer_state); + token.replace(pre_transfer_state); } _ => panic!("expected receiver hook error"), }; From cc49735e6d12a997cd6df007dfaf668cd5f0af64 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 13:46:49 +1000 Subject: [PATCH 21/32] abort with an error instead of ignoring calls to Accept or Reject in an Action --- .../actors/test_actor/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/testing/fil_token_integration/actors/test_actor/src/lib.rs b/testing/fil_token_integration/actors/test_actor/src/lib.rs index 2991bead..691ec2fc 100644 --- a/testing/fil_token_integration/actors/test_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/test_actor/src/lib.rs @@ -141,12 +141,16 @@ fn invoke(input: u32) -> u32 { match params.action { TestAction::Accept => { - // nothing to do here - NO_DATA_BLOCK_ID + sdk::vm::abort( + ExitCode::USR_ILLEGAL_ARGUMENT.value(), + Some("invalid argument"), + ); } TestAction::Reject => { - // nothing to do here - NO_DATA_BLOCK_ID + sdk::vm::abort( + ExitCode::USR_ILLEGAL_ARGUMENT.value(), + Some("invalid argument"), + ); } TestAction::Transfer(to, operator_data) => { // transfer to a target address From 37df1ebf87622e3ad684d7a8c207558367795465 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 14:04:09 +1000 Subject: [PATCH 22/32] rename get_balance to token_balance to avoid confusion with native token balance --- testing/fil_token_integration/tests/common.rs | 4 +-- .../tests/frc46_multi_actor_tests.rs | 30 +++++++++---------- .../tests/frc46_single_actor_tests.rs | 12 ++++---- .../tests/transfer_tokens.rs | 6 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/testing/fil_token_integration/tests/common.rs b/testing/fil_token_integration/tests/common.rs index 6c4cceb9..ddc330f9 100644 --- a/testing/fil_token_integration/tests/common.rs +++ b/testing/fil_token_integration/tests/common.rs @@ -73,7 +73,7 @@ pub trait TestHelpers { pub trait TokenHelpers { /// Get balance from token actor for a given address /// This is a very common thing to check during tests - fn get_balance( + fn token_balance( &mut self, operator: Address, token_actor: Address, @@ -136,7 +136,7 @@ impl TestHelpers for Tester { } impl TokenHelpers for Tester { - fn get_balance( + fn token_balance( &mut self, operator: Address, token_actor: Address, diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index 17215c42..05e3c32a 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -60,9 +60,9 @@ fn frc46_multi_actor_tests() { assert!(ret_val.msg_receipt.exit_code.is_success()); // balance should remain zero - let balance = tester.get_balance(operator[0].1, token_actor, alice); + let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.get_balance(operator[0].1, token_actor, bob); + let balance = tester.token_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(0)); // TEST: alice sends bob a transfer of a non-zero amounnt. As before, we'll reject it the first time then accept @@ -75,7 +75,7 @@ fn frc46_multi_actor_tests() { action(TestAction::Accept), ); assert!(ret_val.msg_receipt.exit_code.is_success()); - let balance = tester.get_balance(operator[0].1, token_actor, alice); + let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(100)); // now send to bob, who will reject them let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); @@ -90,9 +90,9 @@ fn frc46_multi_actor_tests() { let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); assert!(ret_val.msg_receipt.exit_code.is_success()); // check balances - let balance = tester.get_balance(operator[0].1, token_actor, alice); + let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.get_balance(operator[0].1, token_actor, bob); + let balance = tester.token_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(100)); // TEST: mint to alice who transfers to bob inside receiver hook, bob accepts @@ -104,9 +104,9 @@ fn frc46_multi_actor_tests() { action(TestAction::Transfer(bob, action(TestAction::Accept))), ); assert!(ret_val.msg_receipt.exit_code.is_success()); - let balance = tester.get_balance(operator[0].1, token_actor, alice); + let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.get_balance(operator[0].1, token_actor, bob); + let balance = tester.token_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(200)); // TEST: mint to alice who transfers to bob inside receiver hook, bob rejects @@ -120,10 +120,10 @@ fn frc46_multi_actor_tests() { // mint succeeds but the transfer inside the receiver hook would have failed assert!(ret_val.msg_receipt.exit_code.is_success()); // alice should keep tokens in this case - let balance = tester.get_balance(operator[0].1, token_actor, alice); + let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(100)); // bob's balance should remain unchanged - let balance = tester.get_balance(operator[0].1, token_actor, bob); + let balance = tester.token_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(200)); // TEST: alice transfers to bob, bob transfers to carol (from hook), carol accepts @@ -134,11 +134,11 @@ fn frc46_multi_actor_tests() { let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); assert!(ret_val.msg_receipt.exit_code.is_success()); // check balances - alice should be empty, bob should keep 200, carol sitting on 100 - let balance = tester.get_balance(operator[0].1, token_actor, alice); + let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.get_balance(operator[0].1, token_actor, bob); + let balance = tester.token_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(200)); - let balance = tester.get_balance(operator[0].1, token_actor, carol); + let balance = tester.token_balance(operator[0].1, token_actor, carol); assert_eq!(balance, TokenAmount::from_atto(100)); // TEST: alice transfers to bob, bob transfers to carol (from hook), carol burns (from hook) @@ -162,10 +162,10 @@ fn frc46_multi_actor_tests() { let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); assert!(receipt.exit_code.is_success()); // check balances - alice should be empty, bob should keep 200, carol sitting on 100 - let balance = tester.get_balance(operator[0].1, token_actor, alice); + let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.get_balance(operator[0].1, token_actor, bob); + let balance = tester.token_balance(operator[0].1, token_actor, bob); assert_eq!(balance, TokenAmount::from_atto(200)); - let balance = tester.get_balance(operator[0].1, token_actor, carol); + let balance = tester.token_balance(operator[0].1, token_actor, carol); assert_eq!(balance, TokenAmount::from_atto(100)); } diff --git a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs index abd6e349..a8c1cea3 100644 --- a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs @@ -57,7 +57,7 @@ fn frc46_single_actor_tests() { assert!(!ret_val.msg_receipt.exit_code.is_success()); // check balance of test actor, should be zero - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(0)); // TEST: mint to self (token actor), should be rejected @@ -84,7 +84,7 @@ fn frc46_single_actor_tests() { assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); // check balance of test actor, should also be zero - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(0)); // TEST: test actor transfers to self (zero amount) @@ -98,7 +98,7 @@ fn frc46_single_actor_tests() { assert!(ret_val.msg_receipt.exit_code.is_success()); // balance should remain zero - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(0)); // SETUP: we need a balance on the test actor for the next few tests @@ -111,7 +111,7 @@ fn frc46_single_actor_tests() { ); let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(100)); // TEST: test actor transfers back to token actor (rejected, token actor has no hook) @@ -128,7 +128,7 @@ fn frc46_single_actor_tests() { let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); assert!(!receipt.exit_code.is_success()); // check that our test actor balance hasn't changed - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(100)); // TEST: test actor transfers to self (non-zero amount) @@ -141,6 +141,6 @@ fn frc46_single_actor_tests() { tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); assert!(ret_val.msg_receipt.exit_code.is_success()); // check that our test actor balance hasn't changed - let balance = tester.get_balance(operator[0].1, token_actor, test_actor); + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(100)); } diff --git a/testing/fil_token_integration/tests/transfer_tokens.rs b/testing/fil_token_integration/tests/transfer_tokens.rs index b669f983..e3f57617 100644 --- a/testing/fil_token_integration/tests/transfer_tokens.rs +++ b/testing/fil_token_integration/tests/transfer_tokens.rs @@ -63,7 +63,7 @@ fn transfer_tokens() { assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); // check balance of transfer actor - let balance = tester.get_balance(operator[0].1, token_address, transfer_address); + let balance = tester.token_balance(operator[0].1, token_address, transfer_address); println!("balance held by transfer actor: {:?}", balance); assert_eq!(balance, TokenAmount::from_atto(100)); @@ -74,11 +74,11 @@ fn transfer_tokens() { println!("forwarding return data {:#?}", &ret_val); // check balance of receiver actor - let balance = tester.get_balance(operator[0].1, token_address, transfer_address); + let balance = tester.token_balance(operator[0].1, token_address, transfer_address); println!("balance held by transfer actor: {:?}", balance); assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.get_balance(operator[0].1, token_address, receiver_address); + let balance = tester.token_balance(operator[0].1, token_address, receiver_address); println!("balance held by receiver actor: {:?}", balance); assert_eq!(balance, TokenAmount::from_atto(100)); } From 2acef81eb8c0901b459ca5a7f91507ecf30ba7f6 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 14:26:58 +1000 Subject: [PATCH 23/32] separate individual frc46 tests into blocks --- .../tests/frc46_multi_actor_tests.rs | 257 ++++++++++-------- .../tests/frc46_single_actor_tests.rs | 190 +++++++------ 2 files changed, 245 insertions(+), 202 deletions(-) diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index 05e3c32a..411a9813 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -44,128 +44,157 @@ fn frc46_multi_actor_tests() { } // TEST: alice sends bob a transfer of zero amount (rejecting first time and then accepting) - // first, tell bob to reject it - let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); - let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - // we told bob to reject, so the action call should return success but give us the error result as return data - assert!(ret_val.msg_receipt.exit_code.is_success()); - // check the receipt we got in return data - let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); - assert!(!receipt.exit_code.is_success()); - - // this time tell bob to accept it - let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); - let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - // we told bob to accept this time, so transfer should succeed - assert!(ret_val.msg_receipt.exit_code.is_success()); - - // balance should remain zero - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(0)); + { + // first, tell bob to reject it + let params = + action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); + let ret_val = + tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + // we told bob to reject, so the action call should return success but give us the error result as return data + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check the receipt we got in return data + let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); + assert!(!receipt.exit_code.is_success()); + } + { + // this time tell bob to accept it + let params = + action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); + let ret_val = + tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + // we told bob to accept this time, so transfer should succeed + assert!(ret_val.msg_receipt.exit_code.is_success()); + // balance should remain zero + let balance = tester.token_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.token_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(0)); + } // TEST: alice sends bob a transfer of a non-zero amounnt. As before, we'll reject it the first time then accept - // mint some tokens to alice first - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - alice, - TokenAmount::from_atto(100), - action(TestAction::Accept), - ); - assert!(ret_val.msg_receipt.exit_code.is_success()); - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(100)); - // now send to bob, who will reject them - let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); - let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); - // check the receipt we got in return data - let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); - assert!(!receipt.exit_code.is_success()); - - // transfer to bob who will accept it this time - let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); - let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); - // check balances - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(100)); + { + // mint some tokens to alice first + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Accept), + ); + assert!(ret_val.msg_receipt.exit_code.is_success()); + let balance = tester.token_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(100)); + // now send to bob, who will reject them + let params = + action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); + let ret_val = + tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check the receipt we got in return data + let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); + assert!(!receipt.exit_code.is_success()); + } + { + // transfer to bob who will accept it this time + let params = + action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); + let ret_val = + tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check balances + let balance = tester.token_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.token_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(100)); + } // TEST: mint to alice who transfers to bob inside receiver hook, bob accepts - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - alice, - TokenAmount::from_atto(100), - action(TestAction::Transfer(bob, action(TestAction::Accept))), - ); - assert!(ret_val.msg_receipt.exit_code.is_success()); - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); + { + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Transfer(bob, action(TestAction::Accept))), + ); + assert!(ret_val.msg_receipt.exit_code.is_success()); + let balance = tester.token_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.token_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + } // TEST: mint to alice who transfers to bob inside receiver hook, bob rejects - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - alice, - TokenAmount::from_atto(100), - action(TestAction::Transfer(bob, action(TestAction::Reject))), - ); - // mint succeeds but the transfer inside the receiver hook would have failed - assert!(ret_val.msg_receipt.exit_code.is_success()); - // alice should keep tokens in this case - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(100)); - // bob's balance should remain unchanged - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); + { + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Transfer(bob, action(TestAction::Reject))), + ); + // mint succeeds but the transfer inside the receiver hook would have failed + assert!(ret_val.msg_receipt.exit_code.is_success()); + // alice should keep tokens in this case + let balance = tester.token_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(100)); + // bob's balance should remain unchanged + let balance = tester.token_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + } // TEST: alice transfers to bob, bob transfers to carol (from hook), carol accepts - let params = action_params( - token_actor, - TestAction::Transfer(bob, action(TestAction::Transfer(carol, action(TestAction::Accept)))), - ); - let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); - // check balances - alice should be empty, bob should keep 200, carol sitting on 100 - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); - let balance = tester.token_balance(operator[0].1, token_actor, carol); - assert_eq!(balance, TokenAmount::from_atto(100)); + { + let params = action_params( + token_actor, + TestAction::Transfer( + bob, + action(TestAction::Transfer(carol, action(TestAction::Accept))), + ), + ); + let ret_val = + tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check balances - alice should be empty, bob should keep 200, carol sitting on 100 + let balance = tester.token_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.token_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + let balance = tester.token_balance(operator[0].1, token_actor, carol); + assert_eq!(balance, TokenAmount::from_atto(100)); + } // TEST: alice transfers to bob, bob transfers to carol (from hook), carol burns (from hook) - // mint a bit to alice first - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - alice, - TokenAmount::from_atto(100), - action(TestAction::Accept), - ); - assert!(ret_val.msg_receipt.exit_code.is_success()); - // now transfer alice->bob->carol and have carol burn the incoming balance - let params = action_params( - token_actor, - TestAction::Transfer(bob, action(TestAction::Transfer(carol, action(TestAction::Burn)))), - ); - let ret_val = tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); - // check the receipt we got in return data - let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); - assert!(receipt.exit_code.is_success()); - // check balances - alice should be empty, bob should keep 200, carol sitting on 100 - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); - let balance = tester.token_balance(operator[0].1, token_actor, carol); - assert_eq!(balance, TokenAmount::from_atto(100)); + { + // mint a bit to alice first + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + alice, + TokenAmount::from_atto(100), + action(TestAction::Accept), + ); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // now transfer alice->bob->carol and have carol burn the incoming balance + let params = action_params( + token_actor, + TestAction::Transfer( + bob, + action(TestAction::Transfer(carol, action(TestAction::Burn))), + ), + ); + let ret_val = + tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check the receipt we got in return data + let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); + assert!(receipt.exit_code.is_success()); + // check balances - alice should be empty, bob should keep 200, carol sitting on 100 + let balance = tester.token_balance(operator[0].1, token_actor, alice); + assert_eq!(balance, TokenAmount::from_atto(0)); + let balance = tester.token_balance(operator[0].1, token_actor, bob); + assert_eq!(balance, TokenAmount::from_atto(200)); + let balance = tester.token_balance(operator[0].1, token_actor, carol); + assert_eq!(balance, TokenAmount::from_atto(100)); + } } diff --git a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs index a8c1cea3..4adb3519 100644 --- a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs @@ -41,106 +41,120 @@ fn frc46_single_actor_tests() { tester.instantiate_machine(DummyExterns).unwrap(); // construct actors - let ret_val = tester.call_method(operator[0].1, token_actor, method_hash!("Constructor"), None); - assert!(ret_val.msg_receipt.exit_code.is_success()); - let ret_val = tester.call_method(operator[0].1, test_actor, method_hash!("Constructor"), None); - assert!(ret_val.msg_receipt.exit_code.is_success()); + for actor in [token_actor, test_actor] { + let ret_val = tester.call_method(operator[0].1, actor, method_hash!("Constructor"), None); + assert!(ret_val.msg_receipt.exit_code.is_success()); + } // TEST: mint to test actor who rejects hook - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - test_actor, - TokenAmount::from_atto(100), - action(TestAction::Reject), - ); - assert!(!ret_val.msg_receipt.exit_code.is_success()); - - // check balance of test actor, should be zero - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(0)); + { + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + action(TestAction::Reject), + ); + assert!(!ret_val.msg_receipt.exit_code.is_success()); + + // check balance of test actor, should be zero + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(0)); + } // TEST: mint to self (token actor), should be rejected - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - token_actor, - TokenAmount::from_atto(100), - action(TestAction::Reject), - ); - // should fail because the token actor has no receiver hook - assert!(!ret_val.msg_receipt.exit_code.is_success()); + { + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + token_actor, + TokenAmount::from_atto(100), + action(TestAction::Reject), + ); + // should fail because the token actor has no receiver hook + assert!(!ret_val.msg_receipt.exit_code.is_success()); + } // TEST: mint to test actor, hook burns tokens immediately - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - test_actor, - TokenAmount::from_atto(100), - action(TestAction::Burn), - ); - let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); - // tokens were burned so supply reduces back to zero - assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); - - // check balance of test actor, should also be zero - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(0)); + { + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + action(TestAction::Burn), + ); + let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); + // tokens were burned so supply reduces back to zero + assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); + + // check balance of test actor, should also be zero + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(0)); + } // TEST: test actor transfers to self (zero amount) - let test_action = ActionParams { - token_address: token_actor, - action: TestAction::Transfer(test_actor, action(TestAction::Accept)), - }; - let params = RawBytes::serialize(test_action).unwrap(); - let ret_val = - tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); - - // balance should remain zero - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(0)); + { + let test_action = ActionParams { + token_address: token_actor, + action: TestAction::Transfer(test_actor, action(TestAction::Accept)), + }; + let params = RawBytes::serialize(test_action).unwrap(); + let ret_val = + tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + + // balance should remain zero + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(0)); + } // SETUP: we need a balance on the test actor for the next few tests - let ret_val = tester.mint_tokens( - operator[0].1, - token_actor, - test_actor, - TokenAmount::from_atto(100), - action(TestAction::Accept), - ); - let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); - assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(100)); + { + let ret_val = tester.mint_tokens( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + action(TestAction::Accept), + ); + let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); + assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(100)); + } // TEST: test actor transfers back to token actor (rejected, token actor has no hook) - let test_action = ActionParams { - token_address: token_actor, - action: TestAction::Transfer(token_actor, RawBytes::default()), - }; - let params = RawBytes::serialize(test_action).unwrap(); - let ret_val = - tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); - // action call should succeed, we'll dig into the return data to see the transfer call failure - assert!(ret_val.msg_receipt.exit_code.is_success()); - // return data is the Receipt from calling Transfer, which should show failure - let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); - assert!(!receipt.exit_code.is_success()); - // check that our test actor balance hasn't changed - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(100)); + { + let test_action = ActionParams { + token_address: token_actor, + action: TestAction::Transfer(token_actor, RawBytes::default()), + }; + let params = RawBytes::serialize(test_action).unwrap(); + let ret_val = + tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + // action call should succeed, we'll dig into the return data to see the transfer call failure + assert!(ret_val.msg_receipt.exit_code.is_success()); + // return data is the Receipt from calling Transfer, which should show failure + let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); + assert!(!receipt.exit_code.is_success()); + // check that our test actor balance hasn't changed + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(100)); + } // TEST: test actor transfers to self (non-zero amount) - let test_action = ActionParams { - token_address: token_actor, - action: TestAction::Transfer(test_actor, action(TestAction::Accept)), - }; - let params = RawBytes::serialize(test_action).unwrap(); - let ret_val = - tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); - // check that our test actor balance hasn't changed - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(100)); + { + let test_action = ActionParams { + token_address: token_actor, + action: TestAction::Transfer(test_actor, action(TestAction::Accept)), + }; + let params = RawBytes::serialize(test_action).unwrap(); + let ret_val = + tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + assert!(ret_val.msg_receipt.exit_code.is_success()); + // check that our test actor balance hasn't changed + let balance = tester.token_balance(operator[0].1, token_actor, test_actor); + assert_eq!(balance, TokenAmount::from_atto(100)); + } } From 517abcf786e509fb220ae5618eaea4626e51f34c Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 15:12:25 +1000 Subject: [PATCH 24/32] add call_method_ok and mint_tokens_ok helpers to assert success and reduce test case size --- testing/fil_token_integration/tests/common.rs | 45 +++++++++++++++++++ .../tests/frc46_multi_actor_tests.rs | 34 +++++--------- .../tests/frc46_single_actor_tests.rs | 17 +++---- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/testing/fil_token_integration/tests/common.rs b/testing/fil_token_integration/tests/common.rs index ddc330f9..9e360241 100644 --- a/testing/fil_token_integration/tests/common.rs +++ b/testing/fil_token_integration/tests/common.rs @@ -55,6 +55,15 @@ pub trait TestHelpers { params: Option, ) -> ApplyRet; + /// Call a method on an actor and assert a successful result + fn call_method_ok( + &mut self, + from: Address, + to: Address, + method_num: u64, + params: Option, + ) -> ApplyRet; + /// Install an actor with initial state and ID /// Returns the actor's address fn install_actor_with_state( @@ -80,6 +89,7 @@ pub trait TokenHelpers { target: Address, ) -> TokenAmount; + /// Mint tokens from token_actor to target address fn mint_tokens( &mut self, operator: Address, @@ -88,6 +98,16 @@ pub trait TokenHelpers { amount: TokenAmount, operator_data: RawBytes, ) -> ApplyRet; + + /// Mint tokens from token_actor to target address and assert a successful result + fn mint_tokens_ok( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + amount: TokenAmount, + operator_data: RawBytes, + ) -> ApplyRet; } impl TestHelpers for Tester { @@ -114,6 +134,18 @@ impl TestHelpers for Tester { self.executor.as_mut().unwrap().execute_message(message, ApplyKind::Explicit, 100).unwrap() } + fn call_method_ok( + &mut self, + from: Address, + to: Address, + method_num: u64, + params: Option, + ) -> ApplyRet { + let ret = self.call_method(from, to, method_num, params); + assert!(ret.msg_receipt.exit_code.is_success()); + ret + } + fn install_actor_with_state( &mut self, path: &str, @@ -160,4 +192,17 @@ impl TokenHelpers for Tester { let params = RawBytes::serialize(mint_params).unwrap(); self.call_method(operator, token_actor, method_hash!("Mint"), Some(params)) } + + fn mint_tokens_ok( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + amount: TokenAmount, + operator_data: RawBytes, + ) -> ApplyRet { + let ret = self.mint_tokens(operator, token_actor, target, amount, operator_data); + assert!(ret.msg_receipt.exit_code.is_success()); + ret + } } diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index 411a9813..2a62b98b 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -49,9 +49,8 @@ fn frc46_multi_actor_tests() { let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); let ret_val = - tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); + tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // we told bob to reject, so the action call should return success but give us the error result as return data - assert!(ret_val.msg_receipt.exit_code.is_success()); // check the receipt we got in return data let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); assert!(!receipt.exit_code.is_success()); @@ -60,10 +59,7 @@ fn frc46_multi_actor_tests() { // this time tell bob to accept it let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); - let ret_val = - tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - // we told bob to accept this time, so transfer should succeed - assert!(ret_val.msg_receipt.exit_code.is_success()); + tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // balance should remain zero let balance = tester.token_balance(operator[0].1, token_actor, alice); @@ -74,22 +70,20 @@ fn frc46_multi_actor_tests() { // TEST: alice sends bob a transfer of a non-zero amounnt. As before, we'll reject it the first time then accept { // mint some tokens to alice first - let ret_val = tester.mint_tokens( + let _ = tester.mint_tokens_ok( operator[0].1, token_actor, alice, TokenAmount::from_atto(100), action(TestAction::Accept), ); - assert!(ret_val.msg_receipt.exit_code.is_success()); let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(100)); // now send to bob, who will reject them let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); let ret_val = - tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); + tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // check the receipt we got in return data let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); assert!(!receipt.exit_code.is_success()); @@ -98,9 +92,7 @@ fn frc46_multi_actor_tests() { // transfer to bob who will accept it this time let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); - let ret_val = - tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); + tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // check balances let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); @@ -110,14 +102,13 @@ fn frc46_multi_actor_tests() { // TEST: mint to alice who transfers to bob inside receiver hook, bob accepts { - let ret_val = tester.mint_tokens( + tester.mint_tokens_ok( operator[0].1, token_actor, alice, TokenAmount::from_atto(100), action(TestAction::Transfer(bob, action(TestAction::Accept))), ); - assert!(ret_val.msg_receipt.exit_code.is_success()); let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); let balance = tester.token_balance(operator[0].1, token_actor, bob); @@ -126,7 +117,7 @@ fn frc46_multi_actor_tests() { // TEST: mint to alice who transfers to bob inside receiver hook, bob rejects { - let ret_val = tester.mint_tokens( + tester.mint_tokens_ok( operator[0].1, token_actor, alice, @@ -134,7 +125,6 @@ fn frc46_multi_actor_tests() { action(TestAction::Transfer(bob, action(TestAction::Reject))), ); // mint succeeds but the transfer inside the receiver hook would have failed - assert!(ret_val.msg_receipt.exit_code.is_success()); // alice should keep tokens in this case let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(100)); @@ -152,9 +142,7 @@ fn frc46_multi_actor_tests() { action(TestAction::Transfer(carol, action(TestAction::Accept))), ), ); - let ret_val = - tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); + tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // check balances - alice should be empty, bob should keep 200, carol sitting on 100 let balance = tester.token_balance(operator[0].1, token_actor, alice); assert_eq!(balance, TokenAmount::from_atto(0)); @@ -167,14 +155,13 @@ fn frc46_multi_actor_tests() { // TEST: alice transfers to bob, bob transfers to carol (from hook), carol burns (from hook) { // mint a bit to alice first - let ret_val = tester.mint_tokens( + tester.mint_tokens_ok( operator[0].1, token_actor, alice, TokenAmount::from_atto(100), action(TestAction::Accept), ); - assert!(ret_val.msg_receipt.exit_code.is_success()); // now transfer alice->bob->carol and have carol burn the incoming balance let params = action_params( token_actor, @@ -184,8 +171,7 @@ fn frc46_multi_actor_tests() { ), ); let ret_val = - tester.call_method(operator[0].1, alice, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); + tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // check the receipt we got in return data let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); assert!(receipt.exit_code.is_success()); diff --git a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs index 4adb3519..1c94071d 100644 --- a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs @@ -77,7 +77,7 @@ fn frc46_single_actor_tests() { // TEST: mint to test actor, hook burns tokens immediately { - let ret_val = tester.mint_tokens( + let ret_val = tester.mint_tokens_ok( operator[0].1, token_actor, test_actor, @@ -100,9 +100,7 @@ fn frc46_single_actor_tests() { action: TestAction::Transfer(test_actor, action(TestAction::Accept)), }; let params = RawBytes::serialize(test_action).unwrap(); - let ret_val = - tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); + tester.call_method_ok(operator[0].1, test_actor, method_hash!("Action"), Some(params)); // balance should remain zero let balance = tester.token_balance(operator[0].1, token_actor, test_actor); @@ -111,7 +109,7 @@ fn frc46_single_actor_tests() { // SETUP: we need a balance on the test actor for the next few tests { - let ret_val = tester.mint_tokens( + let ret_val = tester.mint_tokens_ok( operator[0].1, token_actor, test_actor, @@ -132,9 +130,9 @@ fn frc46_single_actor_tests() { }; let params = RawBytes::serialize(test_action).unwrap(); let ret_val = - tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + tester.call_method_ok(operator[0].1, test_actor, method_hash!("Action"), Some(params)); // action call should succeed, we'll dig into the return data to see the transfer call failure - assert!(ret_val.msg_receipt.exit_code.is_success()); + // return data is the Receipt from calling Transfer, which should show failure let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); assert!(!receipt.exit_code.is_success()); @@ -150,9 +148,8 @@ fn frc46_single_actor_tests() { action: TestAction::Transfer(test_actor, action(TestAction::Accept)), }; let params = RawBytes::serialize(test_action).unwrap(); - let ret_val = - tester.call_method(operator[0].1, test_actor, method_hash!("Action"), Some(params)); - assert!(ret_val.msg_receipt.exit_code.is_success()); + tester.call_method_ok(operator[0].1, test_actor, method_hash!("Action"), Some(params)); + // check that our test actor balance hasn't changed let balance = tester.token_balance(operator[0].1, token_actor, test_actor); assert_eq!(balance, TokenAmount::from_atto(100)); From d503d76b396d22236caad56f0923b1dd35373f65 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 15:40:17 +1000 Subject: [PATCH 25/32] add token balance assertion helpers to further compress test case code --- testing/fil_token_integration/tests/common.rs | 37 +++++++++++++++ .../tests/frc46_multi_actor_tests.rs | 45 +++++++------------ .../tests/frc46_single_actor_tests.rs | 33 +++++++++----- 3 files changed, 73 insertions(+), 42 deletions(-) diff --git a/testing/fil_token_integration/tests/common.rs b/testing/fil_token_integration/tests/common.rs index 9e360241..7d9c7c98 100644 --- a/testing/fil_token_integration/tests/common.rs +++ b/testing/fil_token_integration/tests/common.rs @@ -108,6 +108,22 @@ pub trait TokenHelpers { amount: TokenAmount, operator_data: RawBytes, ) -> ApplyRet; + + /// Check token balance, asserting that balance matches the provided amount + fn assert_token_balance( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + amount: TokenAmount, + ); + /// Check token balance, asserting a zero balance + fn assert_token_balance_zero( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + ); } impl TestHelpers for Tester { @@ -205,4 +221,25 @@ impl TokenHelpers for Tester { assert!(ret.msg_receipt.exit_code.is_success()); ret } + + fn assert_token_balance( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + amount: TokenAmount, + ) { + let balance = self.token_balance(operator, token_actor, target); + assert_eq!(balance, amount); + } + + fn assert_token_balance_zero( + &mut self, + operator: Address, + token_actor: Address, + target: Address, + ) { + let balance = self.token_balance(operator, token_actor, target); + assert_eq!(balance, TokenAmount::zero()); + } } diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index 2a62b98b..f6316e46 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -62,10 +62,8 @@ fn frc46_multi_actor_tests() { tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // balance should remain zero - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(0)); + tester.assert_token_balance_zero(operator[0].1, token_actor, alice); + tester.assert_token_balance_zero(operator[0].1, token_actor, bob); } // TEST: alice sends bob a transfer of a non-zero amounnt. As before, we'll reject it the first time then accept { @@ -77,8 +75,7 @@ fn frc46_multi_actor_tests() { TokenAmount::from_atto(100), action(TestAction::Accept), ); - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance(operator[0].1, token_actor, alice, TokenAmount::from_atto(100)); // now send to bob, who will reject them let params = action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Reject))); @@ -94,10 +91,8 @@ fn frc46_multi_actor_tests() { action_params(token_actor, TestAction::Transfer(bob, action(TestAction::Accept))); tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // check balances - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance_zero(operator[0].1, token_actor, alice); + tester.assert_token_balance(operator[0].1, token_actor, bob, TokenAmount::from_atto(100)); } // TEST: mint to alice who transfers to bob inside receiver hook, bob accepts @@ -109,10 +104,8 @@ fn frc46_multi_actor_tests() { TokenAmount::from_atto(100), action(TestAction::Transfer(bob, action(TestAction::Accept))), ); - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); + tester.assert_token_balance_zero(operator[0].1, token_actor, alice); + tester.assert_token_balance(operator[0].1, token_actor, bob, TokenAmount::from_atto(200)); } // TEST: mint to alice who transfers to bob inside receiver hook, bob rejects @@ -126,11 +119,9 @@ fn frc46_multi_actor_tests() { ); // mint succeeds but the transfer inside the receiver hook would have failed // alice should keep tokens in this case - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance(operator[0].1, token_actor, alice, TokenAmount::from_atto(100)); // bob's balance should remain unchanged - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); + tester.assert_token_balance(operator[0].1, token_actor, bob, TokenAmount::from_atto(200)); } // TEST: alice transfers to bob, bob transfers to carol (from hook), carol accepts @@ -144,12 +135,9 @@ fn frc46_multi_actor_tests() { ); tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); // check balances - alice should be empty, bob should keep 200, carol sitting on 100 - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); - let balance = tester.token_balance(operator[0].1, token_actor, carol); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance_zero(operator[0].1, token_actor, alice); + tester.assert_token_balance(operator[0].1, token_actor, bob, TokenAmount::from_atto(200)); + tester.assert_token_balance(operator[0].1, token_actor, carol, TokenAmount::from_atto(100)); } // TEST: alice transfers to bob, bob transfers to carol (from hook), carol burns (from hook) @@ -176,11 +164,8 @@ fn frc46_multi_actor_tests() { let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); assert!(receipt.exit_code.is_success()); // check balances - alice should be empty, bob should keep 200, carol sitting on 100 - let balance = tester.token_balance(operator[0].1, token_actor, alice); - assert_eq!(balance, TokenAmount::from_atto(0)); - let balance = tester.token_balance(operator[0].1, token_actor, bob); - assert_eq!(balance, TokenAmount::from_atto(200)); - let balance = tester.token_balance(operator[0].1, token_actor, carol); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance_zero(operator[0].1, token_actor, alice); + tester.assert_token_balance(operator[0].1, token_actor, bob, TokenAmount::from_atto(200)); + tester.assert_token_balance(operator[0].1, token_actor, carol, TokenAmount::from_atto(100)); } } diff --git a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs index 1c94071d..53845f81 100644 --- a/testing/fil_token_integration/tests/frc46_single_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_single_actor_tests.rs @@ -58,8 +58,7 @@ fn frc46_single_actor_tests() { assert!(!ret_val.msg_receipt.exit_code.is_success()); // check balance of test actor, should be zero - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(0)); + tester.assert_token_balance_zero(operator[0].1, token_actor, test_actor); } // TEST: mint to self (token actor), should be rejected @@ -89,8 +88,7 @@ fn frc46_single_actor_tests() { assert_eq!(mint_result.supply, TokenAmount::from_atto(0)); // check balance of test actor, should also be zero - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(0)); + tester.assert_token_balance_zero(operator[0].1, token_actor, test_actor); } // TEST: test actor transfers to self (zero amount) @@ -103,8 +101,7 @@ fn frc46_single_actor_tests() { tester.call_method_ok(operator[0].1, test_actor, method_hash!("Action"), Some(params)); // balance should remain zero - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(0)); + tester.assert_token_balance_zero(operator[0].1, token_actor, test_actor); } // SETUP: we need a balance on the test actor for the next few tests @@ -118,8 +115,12 @@ fn frc46_single_actor_tests() { ); let mint_result: MintReturn = ret_val.msg_receipt.return_data.deserialize().unwrap(); assert_eq!(mint_result.supply, TokenAmount::from_atto(100)); - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + ); } // TEST: test actor transfers back to token actor (rejected, token actor has no hook) @@ -137,8 +138,12 @@ fn frc46_single_actor_tests() { let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); assert!(!receipt.exit_code.is_success()); // check that our test actor balance hasn't changed - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + ); } // TEST: test actor transfers to self (non-zero amount) @@ -151,7 +156,11 @@ fn frc46_single_actor_tests() { tester.call_method_ok(operator[0].1, test_actor, method_hash!("Action"), Some(params)); // check that our test actor balance hasn't changed - let balance = tester.token_balance(operator[0].1, token_actor, test_actor); - assert_eq!(balance, TokenAmount::from_atto(100)); + tester.assert_token_balance( + operator[0].1, + token_actor, + test_actor, + TokenAmount::from_atto(100), + ); } } From f9286e5873ac421d1eb1b5b2fdc8b7b5da8aef53 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 17:27:01 +1000 Subject: [PATCH 26/32] add more balance asserts and check the more complex transfer results --- .../tests/frc46_multi_actor_tests.rs | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs index f6316e46..02c5c8e5 100644 --- a/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs +++ b/testing/fil_token_integration/tests/frc46_multi_actor_tests.rs @@ -1,9 +1,9 @@ use frc42_dispatch::method_hash; -use frc46_token::token::state::TokenState; +use frc46_token::token::{state::TokenState, types::TransferReturn}; use fvm_integration_tests::{dummy::DummyExterns, tester::Account}; use fvm_ipld_blockstore::MemoryBlockstore; use fvm_ipld_encoding::RawBytes; -use fvm_shared::{address::Address, econ::TokenAmount, receipt::Receipt}; +use fvm_shared::{address::Address, bigint::Zero, econ::TokenAmount, receipt::Receipt}; mod common; use common::{construct_tester, TestHelpers, TokenHelpers}; @@ -54,6 +54,8 @@ fn frc46_multi_actor_tests() { // check the receipt we got in return data let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); assert!(!receipt.exit_code.is_success()); + tester.assert_token_balance_zero(operator[0].1, token_actor, alice); + tester.assert_token_balance_zero(operator[0].1, token_actor, bob); } { // this time tell bob to accept it @@ -84,6 +86,9 @@ fn frc46_multi_actor_tests() { // check the receipt we got in return data let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); assert!(!receipt.exit_code.is_success()); + // alice should keep the tokens, while bob has nothing + tester.assert_token_balance(operator[0].1, token_actor, alice, TokenAmount::from_atto(100)); + tester.assert_token_balance_zero(operator[0].1, token_actor, bob); } { // transfer to bob who will accept it this time @@ -133,7 +138,21 @@ fn frc46_multi_actor_tests() { action(TestAction::Transfer(carol, action(TestAction::Accept))), ), ); - tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); + let ret_val = + tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); + // check the receipt we got in return data + let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); + assert!(receipt.exit_code.is_success()); + // check the transfer result (from alice to bob) + let bob_transfer: TransferReturn = receipt.return_data.deserialize().unwrap(); + assert_eq!(bob_transfer.from_balance, TokenAmount::zero()); + assert_eq!(bob_transfer.to_balance, TokenAmount::from_atto(200)); + // now extract the bob->carol receipt and transfer data contained within + let bob_receipt: Receipt = bob_transfer.recipient_data.deserialize().unwrap(); + let carol_transfer: TransferReturn = bob_receipt.return_data.deserialize().unwrap(); + assert_eq!(carol_transfer.from_balance, TokenAmount::from_atto(200)); + assert_eq!(carol_transfer.to_balance, TokenAmount::from_atto(100)); + // check balances - alice should be empty, bob should keep 200, carol sitting on 100 tester.assert_token_balance_zero(operator[0].1, token_actor, alice); tester.assert_token_balance(operator[0].1, token_actor, bob, TokenAmount::from_atto(200)); @@ -142,7 +161,7 @@ fn frc46_multi_actor_tests() { // TEST: alice transfers to bob, bob transfers to carol (from hook), carol burns (from hook) { - // mint a bit to alice first + // mint some more to alice first tester.mint_tokens_ok( operator[0].1, token_actor, @@ -160,9 +179,20 @@ fn frc46_multi_actor_tests() { ); let ret_val = tester.call_method_ok(operator[0].1, alice, method_hash!("Action"), Some(params)); + // check the receipt we got in return data - let receipt = ret_val.msg_receipt.return_data.deserialize::().unwrap(); + let receipt: Receipt = ret_val.msg_receipt.return_data.deserialize().unwrap(); assert!(receipt.exit_code.is_success()); + // check the transfer result (from alice to bob) + let bob_transfer: TransferReturn = receipt.return_data.deserialize().unwrap(); + assert_eq!(bob_transfer.from_balance, TokenAmount::zero()); + assert_eq!(bob_transfer.to_balance, TokenAmount::from_atto(200)); + // now extract the bob->carol receipt and transfer data contained within + let bob_receipt: Receipt = bob_transfer.recipient_data.deserialize().unwrap(); + let carol_transfer: TransferReturn = bob_receipt.return_data.deserialize().unwrap(); + assert_eq!(carol_transfer.from_balance, TokenAmount::from_atto(200)); + assert_eq!(carol_transfer.to_balance, TokenAmount::from_atto(100)); + // check balances - alice should be empty, bob should keep 200, carol sitting on 100 tester.assert_token_balance_zero(operator[0].1, token_actor, alice); tester.assert_token_balance(operator[0].1, token_actor, bob, TokenAmount::from_atto(200)); From 00e6c98e6eead5e9f54d1f708f59b2eff5e18149 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Wed, 21 Sep 2022 20:59:31 +1000 Subject: [PATCH 27/32] remove unnecessary comment --- .../fil_token_integration/actors/basic_token_actor/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index ad0f1ba2..9c01b87a 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -243,7 +243,6 @@ pub fn invoke(params: u32) -> u32 { let root_cid = sdk::sself::root().unwrap(); let bs = Blockstore::default(); - // might want these to be done inside each handler as there's times we'll want to discard and reload it let mut token_state = Token::<_, FvmMessenger>::load_state(&bs, &root_cid).unwrap(); let mut token_actor = From ad9ac07693ef8e6ad037bda4743f8a2a71c39ef9 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Thu, 22 Sep 2022 01:08:31 +1000 Subject: [PATCH 28/32] return intermediate data through hook call and construct MintReturn afterwards --- frc46_token/src/token/mod.rs | 33 ++++++++++++++----- frc46_token/src/token/types.rs | 12 ++++++- .../actors/basic_token_actor/src/lib.rs | 27 ++++++++------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/frc46_token/src/token/mod.rs b/frc46_token/src/token/mod.rs index b5d41156..ff488c13 100644 --- a/frc46_token/src/token/mod.rs +++ b/frc46_token/src/token/mod.rs @@ -16,7 +16,7 @@ use self::types::BurnReturn; use self::types::TransferFromReturn; use self::types::TransferReturn; use crate::receiver::{types::FRC46TokenReceived, ReceiverHook}; -use crate::token::types::MintReturn; +use crate::token::types::{MintIntermediate, MintReturn}; use crate::token::TokenError::InvalidGranularity; mod error; @@ -147,6 +147,9 @@ where /// Returns a ReceiverHook to call the owner's token receiver hook, /// and the owner's new balance. /// ReceiverHook must be called or it will panic and abort the transaction. + /// + /// The hook call will return a MintIntermediate struct which must be passed to mint_return + /// to get the final return data pub fn mint( &mut self, operator: &Address, @@ -154,7 +157,7 @@ where amount: &TokenAmount, operator_data: RawBytes, token_data: RawBytes, - ) -> Result> { + ) -> Result> { let amount = validate_amount_with_granularity(amount, "mint", self.granularity)?; // init the operator account so that its actor ID can be referenced in the receiver hook let operator_id = self.msg.resolve_or_init(operator)?; @@ -163,9 +166,9 @@ where // Increase the balance of the actor and increase total supply let result = self.transaction(|state, bs| { - let balance = state.change_balance_by(&bs, owner_id, amount)?; - let supply = state.change_supply_by(amount)?; - Ok(MintReturn { balance, supply: supply.clone(), recipient_data: RawBytes::default() }) + state.change_balance_by(&bs, owner_id, amount)?; + state.change_supply_by(amount)?; + Ok(MintIntermediate { recipient: *initial_owner, recipient_data: RawBytes::default() }) })?; // return the params we'll send to the receiver hook @@ -181,6 +184,17 @@ where Ok(ReceiverHook::new(*initial_owner, params, result)) } + /// Finalise return data from MintIntermediate data returned by calling receiver hook after minting + /// This is done to allow reloading the state if it changed as a result of the hook call + /// so we can return an accurate balance even if the receiver transferred or burned tokens upon receipt + pub fn mint_return(&self, intermediate: MintIntermediate) -> Result { + Ok(MintReturn { + balance: self.balance_of(&intermediate.recipient)?, + supply: self.total_supply(), + recipient_data: intermediate.recipient_data, + }) + } + /// Gets the total number of tokens in existence /// /// This equals the sum of `balance_of` called on all addresses. This equals sum of all @@ -899,7 +913,8 @@ mod test { ) .unwrap(); token.flush().unwrap(); - let result = hook.call(token.msg()).unwrap(); + let hook_ret = hook.call(token.msg()).unwrap(); + let result = token.mint_return(hook_ret).unwrap(); assert_eq!(TokenAmount::from_atto(1_000_000), result.balance); assert_eq!(TokenAmount::from_atto(1_000_000), result.supply); @@ -957,7 +972,8 @@ mod test { ) .unwrap(); token.flush().unwrap(); - let result = hook.call(token.msg()).unwrap(); + let hook_ret = hook.call(token.msg()).unwrap(); + let result = token.mint_return(hook_ret).unwrap(); assert_eq!(TokenAmount::from_atto(2_000_000), result.balance); assert_eq!(TokenAmount::from_atto(2_000_000), result.supply); @@ -989,7 +1005,8 @@ mod test { ) .unwrap(); token.flush().unwrap(); - let result = hook.call(token.msg()).unwrap(); + let hook_ret = hook.call(token.msg()).unwrap(); + let result = token.mint_return(hook_ret).unwrap(); assert_eq!(TokenAmount::from_atto(1_000_000), result.balance); assert_eq!(TokenAmount::from_atto(3_000_000), result.supply); diff --git a/frc46_token/src/token/types.rs b/frc46_token/src/token/types.rs index 3732bb1b..2202b7e5 100644 --- a/frc46_token/src/token/types.rs +++ b/frc46_token/src/token/types.rs @@ -129,7 +129,17 @@ pub struct MintReturn { } impl Cbor for MintReturn {} -impl RecipientData for MintReturn { + +/// Intermediate data used by mint_return to construct the return data +#[derive(Debug)] +pub struct MintIntermediate { + /// Recipient address to use for querying balance + pub recipient: Address, + /// (Optional) data returned from receiver hook + pub recipient_data: RawBytes, +} + +impl RecipientData for MintIntermediate { fn set_recipient_data(&mut self, data: RawBytes) { self.recipient_data = data; } diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index 9c01b87a..a77f30f5 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -1,5 +1,6 @@ mod util; +use cid::Cid; use frc46_token::token::types::{ AllowanceReturn, BalanceReturn, BurnFromReturn, BurnParams, BurnReturn, DecreaseAllowanceParams, FRC46Token, GetAllowanceParams, GranularityReturn, @@ -177,8 +178,16 @@ pub struct MintParams { impl Cbor for MintParams {} impl BasicToken<'_> { + fn reload(&mut self, initial_cid: &Cid) -> Result<(), RuntimeError> { + // todo: revise error type here so it plays nice with the result and doesn't need unwrap + let new_cid = sdk::sself::root().unwrap(); + if new_cid != *initial_cid { + self.util.load_replace(&new_cid)?; + } + Ok(()) + } + fn mint(&mut self, params: MintParams) -> Result { - let owner = params.initial_owner; let mut hook = self.util.mint( &caller_address(), ¶ms.initial_owner, @@ -190,20 +199,10 @@ impl BasicToken<'_> { let cid = self.util.flush()?; sdk::sself::set_root(&cid).unwrap(); - let ret = hook.call(self.util.msg())?; + let hook_ret = hook.call(self.util.msg())?; - let new_cid = sdk::sself::root().unwrap(); - let ret = if cid == new_cid { - ret - } else { - // state has changed, update return data with new amounts - self.util.load_replace(&new_cid)?; - MintReturn { - balance: self.balance_of(owner)?, - supply: self.total_supply(), - recipient_data: ret.recipient_data, - } - }; + self.reload(&cid)?; + let ret = self.util.mint_return(hook_ret)?; Ok(ret) } From 1755a967551497a6658af2a064097cbdaf877be9 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Thu, 22 Sep 2022 01:32:45 +1000 Subject: [PATCH 29/32] return intermediate data through hook call and construct TransferFromReturn afterwards --- frc46_token/src/token/mod.rs | 56 ++++++++++++------- frc46_token/src/token/types.rs | 13 ++++- .../actors/basic_token_actor/src/lib.rs | 19 +------ 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/frc46_token/src/token/mod.rs b/frc46_token/src/token/mod.rs index ff488c13..d397cb21 100644 --- a/frc46_token/src/token/mod.rs +++ b/frc46_token/src/token/mod.rs @@ -13,8 +13,7 @@ use num_traits::Zero; use self::state::{StateError as TokenStateError, TokenState}; use self::types::BurnFromReturn; use self::types::BurnReturn; -use self::types::TransferFromReturn; -use self::types::TransferReturn; +use self::types::{TransferFromIntermediate, TransferFromReturn, TransferReturn}; use crate::receiver::{types::FRC46TokenReceived, ReceiverHook}; use crate::token::types::{MintIntermediate, MintReturn}; use crate::token::TokenError::InvalidGranularity; @@ -510,8 +509,11 @@ where /// - The owner-operator allowance decreases by the requested value /// /// Returns a ReceiverHook to call the recipient's token receiver hook, - /// and the updated allowance and balances. + /// and a TransferFromIntermediate struct. /// ReceiverHook must be called or it will panic and abort the transaction. + /// + /// Return data from the hook should be passed to transfer_from_return which will generate + /// the TransferFromReturn struct pub fn transfer_from( &mut self, operator: &Address, @@ -520,7 +522,7 @@ where amount: &TokenAmount, operator_data: RawBytes, token_data: RawBytes, - ) -> Result> { + ) -> Result> { let amount = validate_amount_with_granularity(amount, "transfer", self.granularity)?; if self.msg.same_address(operator, from) { return Err(TokenError::InvalidOperator(*operator)); @@ -543,7 +545,7 @@ where }; // the owner must exist to have specified a non-zero allowance - let from = match self.msg.resolve_id(from) { + let from_id = match self.msg.resolve_id(from) { Ok(id) => id, Err(MessagingError::AddressNotResolved(from)) => { return Err(TokenError::TokenState(TokenStateError::InsufficientAllowance { @@ -561,32 +563,31 @@ where // update token state let ret = self.transaction(|state, bs| { - let remaining_allowance = - state.attempt_use_allowance(&bs, operator_id, from, amount)?; + state.attempt_use_allowance(&bs, operator_id, from_id, amount)?; // don't change balance if to == from, but must check that the transfer doesn't exceed balance - if to_id == from { - let balance = state.get_balance(&bs, from)?; + if to_id == from_id { + let balance = state.get_balance(&bs, from_id)?; if balance.lt(amount) { return Err(TokenStateError::InsufficientBalance { - owner: from, + owner: from_id, balance, delta: amount.clone().neg(), } .into()); } - Ok(TransferFromReturn { - from_balance: balance.clone(), - to_balance: balance, - allowance: remaining_allowance, + Ok(TransferFromIntermediate { + operator: *operator, + from: *from, + to: *to, recipient_data: RawBytes::default(), }) } else { - let to_balance = state.change_balance_by(&bs, to_id, amount)?; - let from_balance = state.change_balance_by(&bs, from, &amount.neg())?; - Ok(TransferFromReturn { - from_balance, - to_balance, - allowance: remaining_allowance, + state.change_balance_by(&bs, to_id, amount)?; + state.change_balance_by(&bs, from_id, &amount.neg())?; + Ok(TransferFromIntermediate { + operator: *operator, + from: *from, + to: *to, recipient_data: RawBytes::default(), }) } @@ -594,7 +595,7 @@ where let params = FRC46TokenReceived { operator: operator_id, - from, + from: from_id, to: to_id, amount: amount.clone(), operator_data, @@ -604,6 +605,19 @@ where Ok(ReceiverHook::new(*to, params, ret)) } + /// Generate TransferReturn from the intermediate data returned by a receiver hook call + pub fn transfer_from_return( + &self, + intermediate: TransferFromIntermediate, + ) -> Result { + Ok(TransferFromReturn { + from_balance: self.balance_of(&intermediate.from)?, + to_balance: self.balance_of(&intermediate.to)?, + allowance: self.allowance(&intermediate.from, &intermediate.operator)?, // allowance remains unchanged? + recipient_data: intermediate.recipient_data, + }) + } + /// Sets the balance of an account to a specific amount /// /// Using this library method obeys granularity and sign checks but does not invoke the receiver diff --git a/frc46_token/src/token/types.rs b/frc46_token/src/token/types.rs index 2202b7e5..614017f1 100644 --- a/frc46_token/src/token/types.rs +++ b/frc46_token/src/token/types.rs @@ -200,7 +200,18 @@ pub struct TransferFromReturn { impl Cbor for TransferFromParams {} impl Cbor for TransferFromReturn {} -impl RecipientData for TransferFromReturn { + +/// Intermediate data used by transfer_from_return to construct the return data +#[derive(Debug)] +pub struct TransferFromIntermediate { + pub operator: Address, + pub from: Address, + pub to: Address, + /// (Optional) data returned from receiver hook + pub recipient_data: RawBytes, +} + +impl RecipientData for TransferFromIntermediate { fn set_recipient_data(&mut self, data: RawBytes) { self.recipient_data = data; } diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index a77f30f5..eae6aa19 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -88,8 +88,6 @@ impl FRC46Token for BasicToken<'_> { params: frc46_token::token::types::TransferFromParams, ) -> Result { let operator = caller_address(); - let from = params.from; - let to = params.to; let mut hook = self.util.transfer_from( &operator, ¶ms.from, @@ -102,21 +100,10 @@ impl FRC46Token for BasicToken<'_> { let cid = self.util.flush()?; sdk::sself::set_root(&cid).unwrap(); - let ret = hook.call(self.util.msg())?; + let hook_ret = hook.call(self.util.msg())?; - let new_cid = sdk::sself::root().unwrap(); - let ret = if cid == new_cid { - ret - } else { - // state has changed, update return data with new balances - self.util.load_replace(&new_cid)?; - TransferFromReturn { - from_balance: self.balance_of(from)?, - to_balance: self.balance_of(to)?, - allowance: ret.allowance, // allowance remains unchanged? - recipient_data: ret.recipient_data, - } - }; + self.reload(&cid)?; + let ret = self.util.transfer_from_return(hook_ret)?; Ok(ret) } From 15afad78a33dfd6dfbe5627ee6b71326fdd46710 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Thu, 22 Sep 2022 01:41:51 +1000 Subject: [PATCH 30/32] return intermediate data through hook call and construct TransferReturn afterwards --- frc46_token/src/token/mod.rs | 48 +++++++++++++------ frc46_token/src/token/types.rs | 12 ++++- .../actors/basic_token_actor/src/lib.rs | 17 ++----- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/frc46_token/src/token/mod.rs b/frc46_token/src/token/mod.rs index d397cb21..54467758 100644 --- a/frc46_token/src/token/mod.rs +++ b/frc46_token/src/token/mod.rs @@ -13,7 +13,9 @@ use num_traits::Zero; use self::state::{StateError as TokenStateError, TokenState}; use self::types::BurnFromReturn; use self::types::BurnReturn; -use self::types::{TransferFromIntermediate, TransferFromReturn, TransferReturn}; +use self::types::{ + TransferFromIntermediate, TransferFromReturn, TransferIntermediate, TransferReturn, +}; use crate::receiver::{types::FRC46TokenReceived, ReceiverHook}; use crate::token::types::{MintIntermediate, MintReturn}; use crate::token::TokenError::InvalidGranularity; @@ -442,8 +444,11 @@ where /// - The to balance increases by the requested value /// /// Returns a ReceiverHook to call the recipient's token receiver hook, - /// and the updated balances. + /// and a TransferIntermediate struct /// ReceiverHook must be called or it will panic and abort the transaction. + /// + /// Return data from the hook should be passed to transfer_return which will generate + /// the Transfereturn struct pub fn transfer( &mut self, from: &Address, @@ -451,40 +456,44 @@ where amount: &TokenAmount, operator_data: RawBytes, token_data: RawBytes, - ) -> Result> { + ) -> Result> { let amount = validate_amount_with_granularity(amount, "transfer", self.granularity)?; // owner-initiated transfer - let from = self.msg.resolve_or_init(from)?; + let from_id = self.msg.resolve_or_init(from)?; let to_id = self.msg.resolve_or_init(to)?; // skip allowance check for self-managed transfers let res = self.transaction(|state, bs| { // don't change balance if to == from, but must check that the transfer doesn't exceed balance - if to_id == from { - let balance = state.get_balance(&bs, from)?; + if to_id == from_id { + let balance = state.get_balance(&bs, from_id)?; if balance.lt(amount) { return Err(TokenStateError::InsufficientBalance { - owner: from, + owner: from_id, balance, delta: amount.clone().neg(), } .into()); } - Ok(TransferReturn { - from_balance: balance.clone(), - to_balance: balance, + Ok(TransferIntermediate { + from: *from, + to: *to, recipient_data: RawBytes::default(), }) } else { - let to_balance = state.change_balance_by(&bs, to_id, amount)?; - let from_balance = state.change_balance_by(&bs, from, &amount.neg())?; - Ok(TransferReturn { from_balance, to_balance, recipient_data: RawBytes::default() }) + state.change_balance_by(&bs, to_id, amount)?; + state.change_balance_by(&bs, from_id, &amount.neg())?; + Ok(TransferIntermediate { + from: *from, + to: *to, + recipient_data: RawBytes::default(), + }) } })?; let params = FRC46TokenReceived { - operator: from, - from, + operator: from_id, + from: from_id, to: to_id, amount: amount.clone(), operator_data, @@ -494,6 +503,15 @@ where Ok(ReceiverHook::new(*to, params, res)) } + /// Generate TransferReturn from the intermediate data returned by a receiver hook call + pub fn transfer_return(&self, intermediate: TransferIntermediate) -> Result { + Ok(TransferReturn { + from_balance: self.balance_of(&intermediate.from)?, + to_balance: self.balance_of(&intermediate.to)?, + recipient_data: intermediate.recipient_data, + }) + } + /// Transfers an amount from one address to another /// /// - The requested value MUST be non-negative diff --git a/frc46_token/src/token/types.rs b/frc46_token/src/token/types.rs index 614017f1..07bdc384 100644 --- a/frc46_token/src/token/types.rs +++ b/frc46_token/src/token/types.rs @@ -168,7 +168,17 @@ pub struct TransferReturn { impl Cbor for TransferParams {} impl Cbor for TransferReturn {} -impl RecipientData for TransferReturn { + +/// Intermediate data used by transfer_return to construct the return data +#[derive(Debug)] +pub struct TransferIntermediate { + pub from: Address, + pub to: Address, + /// (Optional) data returned from receiver hook + pub recipient_data: RawBytes, +} + +impl RecipientData for TransferIntermediate { fn set_recipient_data(&mut self, data: RawBytes) { self.recipient_data = data; } diff --git a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs index eae6aa19..84778f1d 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/basic_token_actor/src/lib.rs @@ -53,7 +53,6 @@ impl FRC46Token for BasicToken<'_> { fn transfer(&mut self, params: TransferParams) -> Result { let operator = caller_address(); - let to = params.to; let mut hook = self.util.transfer( &operator, ¶ms.to, @@ -65,20 +64,10 @@ impl FRC46Token for BasicToken<'_> { let cid = self.util.flush()?; sdk::sself::set_root(&cid).unwrap(); - let ret = hook.call(self.util.msg())?; + let hook_ret = hook.call(self.util.msg())?; - let new_cid = sdk::sself::root().unwrap(); - let ret = if cid == new_cid { - ret - } else { - // state has changed, update return data with new balances - self.util.load_replace(&new_cid)?; - TransferReturn { - from_balance: self.balance_of(operator)?, - to_balance: self.balance_of(to)?, - recipient_data: ret.recipient_data, - } - }; + self.reload(&cid)?; + let ret = self.util.transfer_return(hook_ret)?; Ok(ret) } From 6d6fdb9a06fcbab527644d04aa51140f9261ae44 Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Fri, 23 Sep 2022 02:07:28 +1000 Subject: [PATCH 31/32] move transfer and burn actions in the test actor into separate functions --- .../actors/test_actor/src/lib.rs | 74 +++++++++---------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/testing/fil_token_integration/actors/test_actor/src/lib.rs b/testing/fil_token_integration/actors/test_actor/src/lib.rs index 691ec2fc..22bc13bb 100644 --- a/testing/fil_token_integration/actors/test_actor/src/lib.rs +++ b/testing/fil_token_integration/actors/test_actor/src/lib.rs @@ -63,6 +63,36 @@ pub fn action(action: TestAction) -> RawBytes { RawBytes::serialize(action).unwrap() } +/// Execute the Transfer action +fn transfer(token: Address, to: Address, amount: TokenAmount, operator_data: RawBytes) -> u32 { + let transfer_params = TransferParams { to, amount, operator_data }; + let receipt = sdk::send::send( + &token, + method_hash!("Transfer"), + RawBytes::serialize(&transfer_params).unwrap(), + TokenAmount::zero(), + ) + .unwrap(); + // ignore failures at this level and return the transfer call receipt so caller can decide what to do + return_ipld(&receipt) +} + +/// Execute the Burn action +fn burn(token: Address, amount: TokenAmount) -> u32 { + let burn_params = BurnParams { amount }; + let receipt = sdk::send::send( + &token, + method_hash!("Burn"), + RawBytes::serialize(&burn_params).unwrap(), + TokenAmount::zero(), + ) + .unwrap(); + if !receipt.exit_code.is_success() { + panic!("burn call failed"); + } + NO_DATA_BLOCK_ID +} + #[no_mangle] fn invoke(input: u32) -> u32 { std::panic::set_hook(Box::new(|info| { @@ -103,25 +133,11 @@ fn invoke(input: u32) -> u32 { } TestAction::Transfer(to, operator_data) => { // transfer to a target address - let transfer_params = TransferParams { - to, - amount: token_params.amount, - operator_data, - }; - let receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); - // ignore failures at this level and return the transfer call receipt so caller can decide what to do - return_ipld(&receipt) + transfer(Address::new_id(sdk::message::caller()), to, token_params.amount, operator_data) } TestAction::Burn => { // burn the tokens - let burn_params = BurnParams { - amount: token_params.amount, - }; - let receipt = sdk::send::send(&Address::new_id(sdk::message::caller()), method_hash!("Burn"), RawBytes::serialize(&burn_params).unwrap(), TokenAmount::zero()).unwrap(); - if !receipt.exit_code.is_success() { - panic!("burn call failed"); - } - NO_DATA_BLOCK_ID + burn(Address::new_id(sdk::message::caller()), token_params.amount) } } }, @@ -140,13 +156,7 @@ fn invoke(input: u32) -> u32 { }; match params.action { - TestAction::Accept => { - sdk::vm::abort( - ExitCode::USR_ILLEGAL_ARGUMENT.value(), - Some("invalid argument"), - ); - } - TestAction::Reject => { + TestAction::Accept | TestAction::Reject => { sdk::vm::abort( ExitCode::USR_ILLEGAL_ARGUMENT.value(), Some("invalid argument"), @@ -155,26 +165,12 @@ fn invoke(input: u32) -> u32 { TestAction::Transfer(to, operator_data) => { // transfer to a target address let balance = get_balance(); - let transfer_params = TransferParams { - to, - amount: balance, - operator_data, - }; - let receipt = sdk::send::send(¶ms.token_address, method_hash!("Transfer"), RawBytes::serialize(&transfer_params).unwrap(), TokenAmount::zero()).unwrap(); - // ignore failures at this level and return the transfer call receipt so caller can decide what to do - return_ipld(&receipt) + transfer(params.token_address, to, balance, operator_data) } TestAction::Burn => { // burn the tokens let balance = get_balance(); - let burn_params = BurnParams { - amount: balance, - }; - let receipt = sdk::send::send(¶ms.token_address, method_hash!("Burn"), RawBytes::serialize(&burn_params).unwrap(), TokenAmount::zero()).unwrap(); - if !receipt.exit_code.is_success() { - panic!("burn call failed"); - } - NO_DATA_BLOCK_ID + burn(params.token_address, balance) } } } From 61355fdd934ba212a7fd09b8d2558d8ff7eb045e Mon Sep 17 00:00:00 2001 From: Andrew Bright Date: Fri, 23 Sep 2022 02:12:35 +1000 Subject: [PATCH 32/32] bump frc46_token version to 0.2.0 --- frc46_token/Cargo.toml | 2 +- testing/fil_token_integration/Cargo.toml | 2 +- .../actors/basic_receiving_actor/Cargo.toml | 2 +- .../fil_token_integration/actors/basic_token_actor/Cargo.toml | 2 +- .../actors/basic_transfer_actor/Cargo.toml | 2 +- testing/fil_token_integration/actors/test_actor/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frc46_token/Cargo.toml b/frc46_token/Cargo.toml index a812fb05..82d7e9d2 100644 --- a/frc46_token/Cargo.toml +++ b/frc46_token/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frc46_token" description = "Filecoin FRC-0046 fungible token reference implementation" -version = "0.1.0" +version = "0.2.0" license = "MIT OR Apache-2.0" keywords = ["filecoin", "fvm", "token", "frc-0046"] repository = "https://github.com/helix-onchain/filecoin/" diff --git a/testing/fil_token_integration/Cargo.toml b/testing/fil_token_integration/Cargo.toml index 65aa78e2..c3fbed27 100644 --- a/testing/fil_token_integration/Cargo.toml +++ b/testing/fil_token_integration/Cargo.toml @@ -10,7 +10,7 @@ cid = { version = "0.8.5", default-features = false } fvm = { version = "2.0.0-alpha.2", default-features = false } frcxx_nft = { path = "../../frcxx_nft" } frc42_dispatch = { path = "../../frc42_dispatch" } -frc46_token = { version = "0.1.0", path = "../../frc46_token" } +frc46_token = { version = "0.2.0", path = "../../frc46_token" } fvm_integration_tests = "2.0.0-alpha.1" fvm_ipld_blockstore = "0.1.1" fvm_ipld_encoding = "0.2.2" diff --git a/testing/fil_token_integration/actors/basic_receiving_actor/Cargo.toml b/testing/fil_token_integration/actors/basic_receiving_actor/Cargo.toml index 65b26bd1..aa0db601 100644 --- a/testing/fil_token_integration/actors/basic_receiving_actor/Cargo.toml +++ b/testing/fil_token_integration/actors/basic_receiving_actor/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -frc46_token = { version = "0.1.0", path = "../../../../frc46_token" } +frc46_token = { version = "0.2.0", path = "../../../../frc46_token" } frc42_dispatch = { path = "../../../../frc42_dispatch" } fvm_ipld_blockstore = "0.1.1" fvm_ipld_encoding = "0.2.2" diff --git a/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml b/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml index 15d30d2a..2605ecb8 100644 --- a/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml +++ b/testing/fil_token_integration/actors/basic_token_actor/Cargo.toml @@ -11,7 +11,7 @@ fvm_ipld_blockstore = { version = "0.1.1" } fvm_ipld_encoding = { version = "0.2.2" } fvm_sdk = { version = "2.0.0-alpha.2" } fvm_shared = { version = "2.0.0-alpha.2" } -frc46_token = { version = "0.1.0", path = "../../../../frc46_token" } +frc46_token = { version = "0.2.0", path = "../../../../frc46_token" } num-traits = { version = "0.2.15" } serde = { version = "1.0.136", features = ["derive"] } serde_tuple = { version = "0.5.0" } diff --git a/testing/fil_token_integration/actors/basic_transfer_actor/Cargo.toml b/testing/fil_token_integration/actors/basic_transfer_actor/Cargo.toml index 102b51c1..24297843 100644 --- a/testing/fil_token_integration/actors/basic_transfer_actor/Cargo.toml +++ b/testing/fil_token_integration/actors/basic_transfer_actor/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] cid = { version = "0.8.5", default-features = false } -frc46_token = { version = "0.1.0", path = "../../../../frc46_token" } +frc46_token = { version = "0.2.0", path = "../../../../frc46_token" } frc42_dispatch = { path = "../../../../frc42_dispatch" } fvm_ipld_blockstore = { version = "0.1.1" } fvm_ipld_encoding = { version = "0.2.2" } diff --git a/testing/fil_token_integration/actors/test_actor/Cargo.toml b/testing/fil_token_integration/actors/test_actor/Cargo.toml index 98c0516c..d9d11ed4 100644 --- a/testing/fil_token_integration/actors/test_actor/Cargo.toml +++ b/testing/fil_token_integration/actors/test_actor/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] cid = { version = "0.8.5", default-features = false } -frc46_token = { version = "0.1.0", path = "../../../../frc46_token" } +frc46_token = { version = "0.2.0", path = "../../../../frc46_token" } frc42_dispatch = { path = "../../../../frc42_dispatch" } fvm_ipld_blockstore = { version = "0.1.1" } fvm_ipld_encoding = { version = "0.2.2" }