Skip to content

Commit

Permalink
Transfer integration test (#95)
Browse files Browse the repository at this point in the history
* Token transfer integration test

* Add Transfer method to token actor

* Add transfer actor to dev dependencies for testing

* Add comment explaining the transfer actor

* Add asserts to the transfer_tokens test to make it check balances after each operation
  • Loading branch information
abright authored Sep 14, 2022
1 parent 344a773 commit 8f95e9f
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 5 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ members = [
"frc42_dispatch/hasher",
"frc42_dispatch/macros",
"frc42_dispatch/macros/example",
"frcxx_nft",
"fvm_dispatch_tools",
"fil_fungible_token",
"testing/fil_token_integration",
"testing/fil_token_integration/actors/basic_token_actor",
"testing/fil_token_integration/actors/basic_receiving_actor",
"testing/fil_token_integration/actors/basic_nft_actor",
"frcxx_nft"
"testing/fil_token_integration/actors/basic_transfer_actor"
]
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ test-coverage: install-toolchain
--exclude fil_token_integration_tests \
--exclude basic_token_actor \
--exclude basic_receiving_actor \
--exclude basic_nft_actor
--exclude basic_nft_actor \
--exclude basic_transfer_actor

# separate actor testing stage to run from CI without coverage support
test-actors: install-toolchain
Expand Down
9 changes: 6 additions & 3 deletions testing/fil_token_integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ fvm_integration_tests = "2.0.0-alpha.1"
fvm_ipld_blockstore = "0.1.1"
fvm_ipld_encoding = "0.2.2"
fvm_shared = { version = "2.0.0-alpha.2" }
serde = { version = "1.0", features = ["derive"] }
serde_tuple = { version = "0.5.0" }

[dev-dependencies]
basic_token_actor = {path = "actors/basic_token_actor"}
basic_receiving_actor = {path = "actors/basic_receiving_actor"}
actors-v10 = { package = "fil_builtin_actors_bundle", git = "https://github.com/filecoin-project/builtin-actors", branch = "next", features = ["m2-native"] }
basic_nft_actor = {path = "actors/basic_nft_actor"}
actors-v10 = { package = "fil_builtin_actors_bundle", git = "https://github.com/filecoin-project/builtin-actors", branch = "next", features = ["m2-native"] }
basic_receiving_actor = { path = "actors/basic_receiving_actor" }
basic_token_actor = { path = "actors/basic_token_actor" }
basic_transfer_actor = { path = "actors/basic_transfer_actor" }
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,14 @@ pub fn invoke(params: u32) -> u32 {
401872942 => {
// 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "basic_transfer_actor"
version = "0.1.0"
edition = "2021"

[dependencies]
cid = { version = "0.8.5", default-features = false }
fil_fungible_token = { version = "0.1.0", path = "../../../../fil_fungible_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.136", features = ["derive"] }
serde_tuple = { version = "0.5.0" }

[build-dependencies]
wasm-builder = "3.0"
12 changes: 12 additions & 0 deletions testing/fil_token_integration/actors/basic_transfer_actor/build.rs
Original file line number Diff line number Diff line change
@@ -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()
}
146 changes: 146 additions & 0 deletions testing/fil_token_integration/actors/basic_transfer_actor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use cid::{multihash::Code, Cid};
use fil_fungible_token::{
receiver::types::{FRC46TokenReceived, UniversalReceiverParams, FRC46_TOKEN_TYPE},
token::types::TransferParams,
};
use frc42_dispatch::{match_method, method_hash};
use fvm_ipld_blockstore::Block;
use fvm_ipld_encoding::tuple::{Deserialize_tuple, Serialize_tuple};
use fvm_ipld_encoding::{de::DeserializeOwned, RawBytes, DAG_CBOR};
use fvm_sdk as sdk;
use fvm_shared::{address::Address, bigint::Zero, econ::TokenAmount, error::ExitCode};
use sdk::NO_DATA_BLOCK_ID;

/// Grab the incoming parameters and convert from RawBytes to deserialized struct
pub fn deserialize_params<O: DeserializeOwned>(params: u32) -> O {
let params = sdk::message::params_raw(params).unwrap().1;
let params = RawBytes::new(params);
params.deserialize().unwrap()
}

#[derive(Serialize_tuple, Deserialize_tuple)]
struct TransferActorState {
operator_address: Option<Address>,
token_address: Option<Address>,
}

impl TransferActorState {
fn load(cid: &Cid) -> Self {
let data = sdk::ipld::get(cid).unwrap();
fvm_ipld_encoding::from_slice::<Self>(&data).unwrap()
}

fn save(&self) -> Cid {
let serialized = fvm_ipld_encoding::to_vec(self).unwrap();
let block = Block { codec: DAG_CBOR, data: serialized };
sdk::ipld::put(Code::Blake2b256.into(), 32, block.codec, block.data.as_ref()).unwrap()
}
}

/// Implements a simple actor that can hold and transfer tokens
///
/// First operator to send it tokens will be saved and tokens from other operators will be rejected
///
/// Address of the token actor is also saved as this identifies the token type
///
/// After receiving some tokens, it does nothing until the Forward method is called by the initial operator
/// When Forward method is invoked, it will transfer the entire balance it holds to a given address
///
/// Forward requires the same operator to initiate transfer and will abort if the operator address doesn't match,
/// or if the receiver hook rejects the transfer
#[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" => {
let initial_state = TransferActorState { operator_address: None, token_address: None };
let cid = initial_state.save();
sdk::sself::set_root(&cid).unwrap();

NO_DATA_BLOCK_ID
},
"Receive" => {
let mut state = TransferActorState::load(&sdk::sself::root().unwrap());
// Received is passed a TokenReceivedParams
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();

// check the address, we'll remember the first operator and reject others later
match state.operator_address {
Some(operator) => {
let actor_id = sdk::actor::resolve_address(&operator).unwrap();
if actor_id != token_params.operator {
panic!("cannot accept from this operator");
}
}
None => {
state.operator_address = Some(Address::new_id(token_params.operator));
state.token_address = Some(Address::new_id(sdk::message::caller()));
let cid = state.save();
sdk::sself::set_root(&cid).unwrap();
}
}

// all good, don't need to return anything
NO_DATA_BLOCK_ID
},
"Forward" => {
let state = TransferActorState::load(&sdk::sself::root().unwrap());

let target: Address = deserialize_params(input);

// match sender address to the one who operated the last transfer
// if there's no address set, abort because we're expecting a transfer first
match state.operator_address {
Some(operator) => {
let actor_id = sdk::actor::resolve_address(&operator).unwrap();
if actor_id != sdk::message::caller() {
panic!("cannot accept from this operator");
}
}
None => panic!("no operator id set"),
}

// get our balance
let self_address = Address::new_id(sdk::message::receiver());
let balance_receipt = sdk::send::send(&state.token_address.unwrap(), method_hash!("BalanceOf"), RawBytes::serialize(self_address).unwrap(), TokenAmount::zero()).unwrap();
if !balance_receipt.exit_code.is_success() {
panic!("unable to get balance");
}
let balance = balance_receipt.return_data.deserialize::<TokenAmount>().unwrap();

// transfer to target address
let params = TransferParams {
to: target,
amount: balance, // send everything
operator_data: RawBytes::default(),
};
let transfer_receipt = sdk::send::send(&state.token_address.unwrap(), method_hash!("Transfer"), RawBytes::serialize(&params).unwrap(), TokenAmount::zero()).unwrap();
if !transfer_receipt.exit_code.is_success() {
panic!("transfer call failed");
}

// we could return the balance sent or something like that
// but the test we run from is checking that already so no need to do it here
NO_DATA_BLOCK_ID
}
_ => {
sdk::vm::abort(
ExitCode::USR_UNHANDLED_MESSAGE.value(),
Some("Unknown method number"),
);
}
})
}
Loading

0 comments on commit 8f95e9f

Please sign in to comment.