Skip to content

Commit

Permalink
Allow the transfer_to function to transfer any native asset (FuelLabs…
Browse files Browse the repository at this point in the history
…#1935)

* refactor: rename transfer_to to transfer

* fix: add new param asset_id to transfer()

* refactor: rename recipient & destination to to

* docs: update wording to reflect param name changes

* test: update test contract

* test: stub out new test for transfer

* test: add test for transfer to address

* test: fix old tests to use new sdk features

* test: add test for transfer to contract

* docs: cleanup and improve docstrings

* docs: fixup

* style: format comments

* Update sway-lib-std/src/token.sw

Co-authored-by: John Adler <[email protected]>

* Update sway-lib-std/src/token.sw

Co-authored-by: John Adler <[email protected]>

* Update sway-lib-std/src/token.sw

Co-authored-by: John Adler <[email protected]>

* style: formatting docs

Co-authored-by: John Adler <[email protected]>
  • Loading branch information
nfurfaro and adlerjohn authored Jun 14, 2022
1 parent f1632e5 commit 1cc7e96
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 47 deletions.
72 changes: 47 additions & 25 deletions sway-lib-std/src/token.sw
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Functionality for performing common operations with tokens.
library token;
//! Functionality for performing common operations on tokens.

use ::address::Address;
use ::contract_id::ContractId;
Expand All @@ -8,24 +8,32 @@ use ::tx::*;
use ::context::call_frames::contract_id;
use ::identity::Identity;

/// Mint `amount` coins of the current contract's `asset_id` and transfer them to `destination` by calling either force_transfer_to_contract() or transfer_to_output(), depending on the type of `Identity`.
pub fn mint_to(amount: u64, recipient: Identity) {
/// Mint `amount` coins of the current contract's `asset_id` and transfer them
/// to `to` by calling either force_transfer_to_contract() or
/// transfer_to_output(), depending on the type of `Identity`.
pub fn mint_to(amount: u64, to: Identity) {
mint(amount);
transfer_to(amount, recipient);
transfer(amount, contract_id(), to);
}

/// Mint `amount` coins of the current contract's `asset_id` and send them (!!! UNCONDITIONALLY !!!) to the contract at `destination`.
/// This will allow the transfer of coins even if there is no way to retrieve them !!!
/// Use of this function can lead to irretrievable loss of coins if not used with caution.
pub fn mint_to_contract(amount: u64, destination: ContractId) {
/// Mint `amount` coins of the current contract's `asset_id` and send them
/// UNCONDITIONALLY to the contract at `to`.
///
/// CAUTION !!!
///
/// This will transfer coins to a contract even with no way to retrieve them
/// (i.e: no withdrawal functionality on the receiving contract), possibly leading to
/// the PERMANENT LOSS OF COINS if not used with care.
pub fn mint_to_contract(amount: u64, to: ContractId) {
mint(amount);
force_transfer_to_contract(amount, contract_id(), destination);
force_transfer_to_contract(amount, contract_id(), to);
}

/// Mint `amount` coins of the current contract's `asset_id` and send them to the Address `recipient`.
pub fn mint_to_address(amount: u64, recipient: Address) {
/// Mint `amount` coins of the current contract's `asset_id` and send them to
/// the Address `to`.
pub fn mint_to_address(amount: u64, to: Address) {
mint(amount);
transfer_to_output(amount, contract_id(), recipient);
transfer_to_output(amount, contract_id(), to);
}

/// Mint `amount` coins of the current contract's `asset_id`.
Expand All @@ -42,29 +50,43 @@ pub fn burn(amount: u64) {
}
}

/// Transfer `amount` coins of the current contract's `asset_id` and send them to `destination` by calling either force_transfer_to_contract() or transfer_to_output(), depending on the type of `Identity`.
pub fn transfer_to(amount: u64, recipient: Identity) {
match recipient {
/// Transfer `amount` coins of the current contract's `asset_id` and send them
/// to `to` by calling either force_transfer_to_contract() or
/// transfer_to_output(), depending on the type of `Identity`.
///
/// CAUTION !!!
///
/// This may transfer coins to a contract even with no way to retrieve them
/// (i.e. no withdrawal functionality on receiving contract), possibly leading
/// to the PERMANENT LOSS OF COINS if not used with care.
pub fn transfer(amount: u64, asset_id: ContractId, to: Identity) {
match to {
Identity::Address(addr) => {
transfer_to_output(amount, contract_id(), addr);
transfer_to_output(amount, asset_id, addr);
},
Identity::ContractId(id) => {
force_transfer_to_contract(amount, contract_id(), id);
force_transfer_to_contract(amount, asset_id, id);
},
}
}

/// !!! UNCONDITIONAL transfer of `amount` coins of type `asset_id` to contract at `destination`.
/// This will allow the transfer of coins even if there is no way to retrieve them !!!
/// Use of this function can lead to irretrievable loss of coins if not used with caution.
pub fn force_transfer_to_contract(amount: u64, asset_id: ContractId, destination: ContractId) {
asm(r1: amount, r2: asset_id.value, r3: destination.value) {
/// UNCONDITIONAL transfer of `amount` coins of type `asset_id` to
/// the contract at `to`.
///
/// CAUTION !!!
///
/// This will transfer coins to a contract even with no way to retrieve them
/// (i.e. no withdrawal functionality on receiving contract), possibly leading
/// to the PERMANENT LOSS OF COINS if not used with care.
pub fn force_transfer_to_contract(amount: u64, asset_id: ContractId, to: ContractId) {
asm(r1: amount, r2: asset_id.value, r3: to.value) {
tr r3 r1 r2;
}
}

/// Transfer `amount` coins of tof type `asset_id` and send them to the address `recipient`.
pub fn transfer_to_output(amount: u64, asset_id: ContractId, recipient: Address) {
/// Transfer `amount` coins of type `asset_id` and send them to
/// the address `to`.
pub fn transfer_to_output(amount: u64, asset_id: ContractId, to: Address) {
const OUTPUT_VARIABLE_TYPE: u8 = 4;

// maintain a manual index as we only have `while` loops in sway atm:
Expand All @@ -90,7 +112,7 @@ pub fn transfer_to_output(amount: u64, asset_id: ContractId, recipient: Address)
if !output_found {
revert(0);
} else {
asm(r1: recipient.value, r2: output_index, r3: amount, r4: asset_id.value) {
asm(r1: to.value, r2: output_index, r3: amount, r4: asset_id.value) {
tro r1 r2 r3 r4;
};
}
Expand Down
78 changes: 71 additions & 7 deletions test/src/sdk-harness/test_projects/token_ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,18 +178,16 @@ async fn can_mint_and_send_to_address() {
}

#[tokio::test]
async fn call_mint_to_with_address() {
async fn can_perform_generic_mint_to_with_address() {
let wallet = launch_provider_and_get_single_wallet().await;
let (fuelcoin_instance, fuelcoin_id) = get_fuelcoin_instance(wallet.clone()).await;
let amount = 55u64;

let amount = 55u64;
let asset_id_array: [u8; 32] = fuelcoin_id.into();

let address = wallet.address();
let recipient = address.clone();

fuelcoin_instance
.mint_to_using_an_address(amount, recipient)
.generic_mint_to(amount, Identity::Address(address))
.append_variable_outputs(1)
.call()
.await
Expand All @@ -206,7 +204,7 @@ async fn call_mint_to_with_address() {
}

#[tokio::test]
async fn call_mint_to_with_contract_id() {
async fn can_perform_generic_mint_to_with_contract_id() {
let num_wallets = 1;
let coins_per_wallet = 1;
let amount_per_coin = 1_000_000;
Expand All @@ -225,7 +223,7 @@ async fn call_mint_to_with_contract_id() {
let target = balance_id.clone();

fuelcoin_instance
.mint_to_using_a_contract_id(amount, target)
.generic_mint_to(amount, Identity::ContractId(target))
.set_contracts(&[balance_id])
.call()
.await
Expand All @@ -241,6 +239,72 @@ async fn call_mint_to_with_contract_id() {
assert_eq!(result.value, amount)
}

#[tokio::test]
async fn can_perform_generic_transfer_to_address() {
let wallet = launch_provider_and_get_single_wallet().await;
let (fuelcoin_instance, fuelcoin_id) = get_fuelcoin_instance(wallet.clone()).await;

let amount = 33u64;
let asset_id_array: [u8; 32] = fuelcoin_id.into();
let address = wallet.address();

fuelcoin_instance.mint_coins(amount).call().await.unwrap();

fuelcoin_instance
.generic_transfer(amount, fuelcoin_id, Identity::Address(address))
.append_variable_outputs(1)
.call()
.await
.unwrap();

assert_eq!(
wallet
.get_spendable_coins(&AssetId::from(asset_id_array), 1)
.await
.unwrap()[0]
.amount,
amount.into()
);
}

#[tokio::test]
async fn can_perform_generic_transfer_to_contract() {
let num_wallets = 1;
let coins_per_wallet = 1;
let amount_per_coin = 1_000_000;

let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
let wallets = launch_provider_and_get_wallets(config).await;

let (fuelcoin_instance, fuelcoin_id) = get_fuelcoin_instance(wallets[0].clone()).await;
let balance_id = get_balance_contract_id(wallets[0].clone()).await;

let amount = 44u64;
let to = balance_id.clone();

fuelcoin_instance.mint_coins(amount).call().await.unwrap();

fuelcoin_instance
.generic_transfer(amount, fuelcoin_id, Identity::ContractId(to))
.set_contracts(&[balance_id])
.call()
.await
.unwrap();

let result = fuelcoin_instance
.get_balance(fuelcoin_id, to)
.set_contracts(&[balance_id])
.call()
.await
.unwrap();

assert_eq!(result.value, amount)
}

async fn get_fuelcoin_instance(wallet: Wallet) -> (TestFuelCoinContract, ContractId) {
let fuelcoin_id = Contract::deploy(
"test_projects/token_ops/out/debug/token_ops.bin",
Expand Down
30 changes: 15 additions & 15 deletions test/src/sdk-harness/test_projects/token_ops/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ abi TestFuelCoin {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn force_transfer_coins(coins: u64, asset_id: ContractId, target: ContractId);
fn transfer_coins_to_output(coins: u64, asset_id: ContractId, recipient: Address);
fn transfer_coins_to_output(coins: u64, asset_id: ContractId, to: Address);
fn get_balance(target: ContractId, asset_id: ContractId) -> u64;
fn mint_and_send_to_contract(amount: u64, destination: ContractId);
fn mint_and_send_to_address(amount: u64, recipient: Address);
fn mint_to_using_an_address(amount: u64, recipient: Address);
fn mint_to_using_a_contract_id(amount: u64, recipient: ContractId);
fn mint_and_send_to_contract(amount: u64, to: ContractId);
fn mint_and_send_to_address(amount: u64, to: Address);
fn generic_mint_to(amount: u64, to: Identity);
fn generic_transfer(amount: u64, asset_id: ContractId, to: Identity);
}

impl TestFuelCoin for Contract {
Expand All @@ -27,27 +27,27 @@ impl TestFuelCoin for Contract {
force_transfer_to_contract(coins, asset_id, target);
}

fn transfer_coins_to_output(coins: u64, asset_id: ContractId, recipient: Address) {
transfer_to_output(coins, asset_id, recipient);
fn transfer_coins_to_output(coins: u64, asset_id: ContractId, to: Address) {
transfer_to_output(coins, asset_id, to);
}

fn get_balance(target: ContractId, asset_id: ContractId) -> u64 {
balance_of(target, asset_id)
}

fn mint_and_send_to_contract(amount: u64, destination: ContractId) {
mint_to_contract(amount, destination);
fn mint_and_send_to_contract(amount: u64, to: ContractId) {
mint_to_contract(amount, to);
}

fn mint_and_send_to_address(amount: u64, recipient: Address) {
mint_to_address(amount, recipient);
fn mint_and_send_to_address(amount: u64, to: Address) {
mint_to_address(amount, to);
}

fn mint_to_using_an_address(amount: u64, recipient: Address) {
mint_to(amount, Identity::Address(recipient));
fn generic_mint_to(amount: u64, to: Identity) {
mint_to(amount, to);
}

fn mint_to_using_a_contract_id(amount: u64, recipient: ContractId) {
mint_to(amount, Identity::ContractId(recipient));
fn generic_transfer(amount: u64, asset_id: ContractId, to: Identity) {
transfer(amount, asset_id, to)
}
}

0 comments on commit 1cc7e96

Please sign in to comment.