Skip to content

Commit

Permalink
[Aptos Framework] Add batch versions of aptos_coin::transfer and tran…
Browse files Browse the repository at this point in the history
…sfer_coins
  • Loading branch information
movekevin committed Dec 16, 2022
1 parent 77e7a38 commit c81284c
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 2 deletions.
87 changes: 87 additions & 0 deletions aptos-move/framework/aptos-framework/doc/aptos_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
- [Struct `DirectCoinTransferConfigUpdatedEvent`](#0x1_aptos_account_DirectCoinTransferConfigUpdatedEvent)
- [Constants](#@Constants_0)
- [Function `create_account`](#0x1_aptos_account_create_account)
- [Function `batch_transfer`](#0x1_aptos_account_batch_transfer)
- [Function `transfer`](#0x1_aptos_account_transfer)
- [Function `batch_transfer_coins`](#0x1_aptos_account_batch_transfer_coins)
- [Function `transfer_coins`](#0x1_aptos_account_transfer_coins)
- [Function `deposit_coins`](#0x1_aptos_account_deposit_coins)
- [Function `assert_account_exists`](#0x1_aptos_account_assert_account_exists)
Expand Down Expand Up @@ -142,6 +144,16 @@ Account is not registered to receive APT.



<a name="0x1_aptos_account_EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH"></a>

The lengths of the recipients and amounts lists don't match.


<pre><code><b>const</b> <a href="aptos_account.md#0x1_aptos_account_EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH">EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH</a>: u64 = 5;
</code></pre>



<a name="0x1_aptos_account_create_account"></a>

## Function `create_account`
Expand All @@ -166,6 +178,43 @@ Basic account creation methods.



</details>

<a name="0x1_aptos_account_batch_transfer"></a>

## Function `batch_transfer`

Batch version of APT transfer.


<pre><code><b>public</b> entry <b>fun</b> <a href="aptos_account.md#0x1_aptos_account_batch_transfer">batch_transfer</a>(source: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>, recipients: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;, amounts: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u64&gt;)
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> entry <b>fun</b> <a href="aptos_account.md#0x1_aptos_account_batch_transfer">batch_transfer</a>(source: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>, recipients: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;, amounts: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u64&gt;) {
<b>let</b> recipients_len = <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_length">vector::length</a>(&recipients);
<b>assert</b>!(
recipients_len == <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_length">vector::length</a>(&amounts),
<a href="../../aptos-stdlib/../move-stdlib/doc/error.md#0x1_error_invalid_argument">error::invalid_argument</a>(<a href="aptos_account.md#0x1_aptos_account_EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH">EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH</a>),
);

<b>let</b> i = 0;
<b>while</b> (i &lt; recipients_len) {
<b>let</b> <b>to</b> = *<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_borrow">vector::borrow</a>(&recipients, i);
<b>let</b> amount = *<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_borrow">vector::borrow</a>(&amounts, i);
<a href="aptos_account.md#0x1_aptos_account_transfer">transfer</a>(source, <b>to</b>, amount);
i = i + 1;
};
}
</code></pre>



</details>

<a name="0x1_aptos_account_transfer"></a>
Expand Down Expand Up @@ -195,6 +244,44 @@ This would create the recipient account first, which also registers it to receiv



</details>

<a name="0x1_aptos_account_batch_transfer_coins"></a>

## Function `batch_transfer_coins`

Batch version of transfer_coins.


<pre><code><b>public</b> entry <b>fun</b> <a href="aptos_account.md#0x1_aptos_account_batch_transfer_coins">batch_transfer_coins</a>&lt;CoinType&gt;(from: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>, recipients: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;, amounts: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u64&gt;)
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> entry <b>fun</b> <a href="aptos_account.md#0x1_aptos_account_batch_transfer_coins">batch_transfer_coins</a>&lt;CoinType&gt;(
from: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>, recipients: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;, amounts: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u64&gt;) <b>acquires</b> <a href="aptos_account.md#0x1_aptos_account_DirectTransferConfig">DirectTransferConfig</a> {
<b>let</b> recipients_len = <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_length">vector::length</a>(&recipients);
<b>assert</b>!(
recipients_len == <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_length">vector::length</a>(&amounts),
<a href="../../aptos-stdlib/../move-stdlib/doc/error.md#0x1_error_invalid_argument">error::invalid_argument</a>(<a href="aptos_account.md#0x1_aptos_account_EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH">EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH</a>),
);

<b>let</b> i = 0;
<b>while</b> (i &lt; recipients_len) {
<b>let</b> <b>to</b> = *<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_borrow">vector::borrow</a>(&recipients, i);
<b>let</b> amount = *<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector_borrow">vector::borrow</a>(&amounts, i);
<a href="aptos_account.md#0x1_aptos_account_transfer_coins">transfer_coins</a>&lt;CoinType&gt;(from, <b>to</b>, amount);
i = i + 1;
};
}
</code></pre>



</details>

<a name="0x1_aptos_account_transfer_coins"></a>
Expand Down
90 changes: 88 additions & 2 deletions aptos-move/framework/aptos-framework/sources/aptos_account.move
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module aptos_framework::aptos_account {
use aptos_framework::account::{Self, new_event_handle};
use aptos_framework::aptos_coin::AptosCoin;
use aptos_framework::coin;
use aptos_framework::coin::{Self, Coin};
use aptos_framework::event::{EventHandle, emit_event};
use std::error;
use std::signer;
use std::vector;

friend aptos_framework::genesis;
friend aptos_framework::resource_account;
Expand All @@ -17,6 +18,8 @@ module aptos_framework::aptos_account {
const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_COIN_TRANSFERS: u64 = 3;
/// Account opted out of directly receiving NFT tokens.
const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_TOKEN_TRANSFERS: u64 = 4;
/// The lengths of the recipients and amounts lists don't match.
const EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH: u64 = 5;

/// Configuration for whether an account can receive direct transfers of coins that they have not registered.
///
Expand All @@ -40,6 +43,23 @@ module aptos_framework::aptos_account {
coin::register<AptosCoin>(&signer);
}

/// Batch version of APT transfer.
public entry fun batch_transfer(source: &signer, recipients: vector<address>, amounts: vector<u64>) {
let recipients_len = vector::length(&recipients);
assert!(
recipients_len == vector::length(&amounts),
error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
);

let i = 0;
while (i < recipients_len) {
let to = *vector::borrow(&recipients, i);
let amount = *vector::borrow(&amounts, i);
transfer(source, to, amount);
i = i + 1;
};
}

/// Convenient function to transfer APT to a recipient account that might not exist.
/// This would create the recipient account first, which also registers it to receive APT, before transferring.
public entry fun transfer(source: &signer, to: address, amount: u64) {
Expand All @@ -49,6 +69,24 @@ module aptos_framework::aptos_account {
coin::transfer<AptosCoin>(source, to, amount)
}

/// Batch version of transfer_coins.
public entry fun batch_transfer_coins<CoinType>(
from: &signer, recipients: vector<address>, amounts: vector<u64>) acquires DirectTransferConfig {
let recipients_len = vector::length(&recipients);
assert!(
recipients_len == vector::length(&amounts),
error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
);

let i = 0;
while (i < recipients_len) {
let to = *vector::borrow(&recipients, i);
let amount = *vector::borrow(&amounts, i);
transfer_coins<CoinType>(from, to, amount);
i = i + 1;
};
}

/// Convenient function to transfer a custom CoinType to a recipient account that might not exist.
/// This would create the recipient account first and register it to receive the CoinType, before transferring.
public entry fun transfer_coins<CoinType>(from: &signer, to: address, amount: u64) acquires DirectTransferConfig {
Expand Down Expand Up @@ -119,7 +157,6 @@ module aptos_framework::aptos_account {
use aptos_std::from_bcs;
#[test_only]
use std::string::utf8;
use aptos_framework::coin::Coin;
#[test_only]
use aptos_framework::account::create_account_for_test;

Expand All @@ -145,6 +182,26 @@ module aptos_framework::aptos_account {
coin::destroy_mint_cap(mint_cap);
}

#[test(from = @0x123, core = @0x1, recipient_1 = @0x124, recipient_2 = @0x125)]
public fun test_batch_transfer(from: &signer, core: &signer, recipient_1: &signer, recipient_2: &signer) {
let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(core);
create_account(signer::address_of(from));
let recipient_1_addr = signer::address_of(recipient_1);
let recipient_2_addr = signer::address_of(recipient_2);
create_account(recipient_1_addr);
create_account(recipient_2_addr);
coin::deposit(signer::address_of(from), coin::mint(10000, &mint_cap));
batch_transfer(
from,
vector[recipient_1_addr, recipient_2_addr],
vector[100, 500],
);
assert!(coin::balance<AptosCoin>(recipient_1_addr) == 100, 0);
assert!(coin::balance<AptosCoin>(recipient_2_addr) == 500, 1);
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}

#[test(from = @0x1, to = @0x12)]
public fun test_direct_coin_transfers(from: &signer, to: &signer) acquires DirectTransferConfig {
let (burn_cap, freeze_cap, mint_cap) = coin::initialize<FakeCoin>(
Expand All @@ -167,6 +224,35 @@ module aptos_framework::aptos_account {
coin::destroy_freeze_cap(freeze_cap);
}

#[test(from = @0x1, recipient_1 = @0x124, recipient_2 = @0x125)]
public fun test_batch_transfer_coins(
from: &signer, recipient_1: &signer, recipient_2: &signer) acquires DirectTransferConfig {
let (burn_cap, freeze_cap, mint_cap) = coin::initialize<FakeCoin>(
from,
utf8(b"FC"),
utf8(b"FC"),
10,
true,
);
create_account_for_test(signer::address_of(from));
let recipient_1_addr = signer::address_of(recipient_1);
let recipient_2_addr = signer::address_of(recipient_2);
create_account_for_test(recipient_1_addr);
create_account_for_test(recipient_2_addr);
deposit_coins(signer::address_of(from), coin::mint(1000, &mint_cap));
batch_transfer_coins<FakeCoin>(
from,
vector[recipient_1_addr, recipient_2_addr],
vector[100, 500],
);
assert!(coin::balance<FakeCoin>(recipient_1_addr) == 100, 0);
assert!(coin::balance<FakeCoin>(recipient_2_addr) == 500, 1);

coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
coin::destroy_freeze_cap(freeze_cap);
}

#[test(from = @0x1, to = @0x12)]
public fun test_direct_coin_transfers_with_explicit_direct_coin_transfer_config(
from: &signer, to: &signer) acquires DirectTransferConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ pub enum EntryFunctionCall {
cap_update_table: Vec<u8>,
},

/// Batch version of APT transfer.
AptosAccountBatchTransfer {
recipients: Vec<AccountAddress>,
amounts: Vec<u64>,
},

/// Batch version of transfer_coins.
AptosAccountBatchTransferCoins {
coin_type: TypeTag,
recipients: Vec<AccountAddress>,
amounts: Vec<u64>,
},

/// Basic account creation methods.
AptosAccountCreateAccount {
auth_key: AccountAddress,
Expand Down Expand Up @@ -522,6 +535,15 @@ impl EntryFunctionCall {
cap_rotate_key,
cap_update_table,
),
AptosAccountBatchTransfer {
recipients,
amounts,
} => aptos_account_batch_transfer(recipients, amounts),
AptosAccountBatchTransferCoins {
coin_type,
recipients,
amounts,
} => aptos_account_batch_transfer_coins(coin_type, recipients, amounts),
AptosAccountCreateAccount { auth_key } => aptos_account_create_account(auth_key),
AptosAccountSetAllowDirectCoinTransfers { allow } => {
aptos_account_set_allow_direct_coin_transfers(allow)
Expand Down Expand Up @@ -881,6 +903,51 @@ pub fn account_rotate_authentication_key(
))
}

/// Batch version of APT transfer.
pub fn aptos_account_batch_transfer(
recipients: Vec<AccountAddress>,
amounts: Vec<u64>,
) -> TransactionPayload {
TransactionPayload::EntryFunction(EntryFunction::new(
ModuleId::new(
AccountAddress::new([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1,
]),
ident_str!("aptos_account").to_owned(),
),
ident_str!("batch_transfer").to_owned(),
vec![],
vec![
bcs::to_bytes(&recipients).unwrap(),
bcs::to_bytes(&amounts).unwrap(),
],
))
}

/// Batch version of transfer_coins.
pub fn aptos_account_batch_transfer_coins(
coin_type: TypeTag,
recipients: Vec<AccountAddress>,
amounts: Vec<u64>,
) -> TransactionPayload {
TransactionPayload::EntryFunction(EntryFunction::new(
ModuleId::new(
AccountAddress::new([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1,
]),
ident_str!("aptos_account").to_owned(),
),
ident_str!("batch_transfer_coins").to_owned(),
vec![coin_type],
vec![
bcs::to_bytes(&recipients).unwrap(),
bcs::to_bytes(&amounts).unwrap(),
],
))
}

/// Basic account creation methods.
pub fn aptos_account_create_account(auth_key: AccountAddress) -> TransactionPayload {
TransactionPayload::EntryFunction(EntryFunction::new(
Expand Down Expand Up @@ -2253,6 +2320,31 @@ mod decoder {
}
}

pub fn aptos_account_batch_transfer(payload: &TransactionPayload) -> Option<EntryFunctionCall> {
if let TransactionPayload::EntryFunction(script) = payload {
Some(EntryFunctionCall::AptosAccountBatchTransfer {
recipients: bcs::from_bytes(script.args().get(0)?).ok()?,
amounts: bcs::from_bytes(script.args().get(1)?).ok()?,
})
} else {
None
}
}

pub fn aptos_account_batch_transfer_coins(
payload: &TransactionPayload,
) -> Option<EntryFunctionCall> {
if let TransactionPayload::EntryFunction(script) = payload {
Some(EntryFunctionCall::AptosAccountBatchTransferCoins {
coin_type: script.ty_args().get(0)?.clone(),
recipients: bcs::from_bytes(script.args().get(0)?).ok()?,
amounts: bcs::from_bytes(script.args().get(1)?).ok()?,
})
} else {
None
}
}

pub fn aptos_account_create_account(payload: &TransactionPayload) -> Option<EntryFunctionCall> {
if let TransactionPayload::EntryFunction(script) = payload {
Some(EntryFunctionCall::AptosAccountCreateAccount {
Expand Down Expand Up @@ -3061,6 +3153,14 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy<EntryFunctionDecoderMa
"account_rotate_authentication_key".to_string(),
Box::new(decoder::account_rotate_authentication_key),
);
map.insert(
"aptos_account_batch_transfer".to_string(),
Box::new(decoder::aptos_account_batch_transfer),
);
map.insert(
"aptos_account_batch_transfer_coins".to_string(),
Box::new(decoder::aptos_account_batch_transfer_coins),
);
map.insert(
"aptos_account_create_account".to_string(),
Box::new(decoder::aptos_account_create_account),
Expand Down

0 comments on commit c81284c

Please sign in to comment.