From c81284c4f69ad0c26d7212b3d87d1360b79522b7 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 16 Dec 2022 15:05:32 +0700 Subject: [PATCH] [Aptos Framework] Add batch versions of aptos_coin::transfer and transfer_coins --- .../aptos-framework/doc/aptos_account.md | 87 +++++++++++++++ .../sources/aptos_account.move | 90 +++++++++++++++- .../src/aptos_framework_sdk_builder.rs | 100 ++++++++++++++++++ 3 files changed, 275 insertions(+), 2 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/aptos_account.md b/aptos-move/framework/aptos-framework/doc/aptos_account.md index 1a8adbf36c008..b814ca1c9515c 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_account.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_account.md @@ -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) @@ -142,6 +144,16 @@ Account is not registered to receive APT. + + +The lengths of the recipients and amounts lists don't match. + + +
const EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH: u64 = 5;
+
+ + + ## Function `create_account` @@ -166,6 +178,43 @@ Basic account creation methods. + + + + +## Function `batch_transfer` + +Batch version of APT transfer. + + +
public entry fun batch_transfer(source: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+ + + +
+Implementation + + +
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;
+    };
+}
+
+ + +
@@ -195,6 +244,44 @@ This would create the recipient account first, which also registers it to receiv + + + + +## Function `batch_transfer_coins` + +Batch version of transfer_coins. + + +
public entry fun batch_transfer_coins<CoinType>(from: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+ + + +
+Implementation + + +
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;
+    };
+}
+
+ + +
diff --git a/aptos-move/framework/aptos-framework/sources/aptos_account.move b/aptos-move/framework/aptos-framework/sources/aptos_account.move index 6d1d8b9020c5b..079e8742c982e 100644 --- a/aptos-move/framework/aptos-framework/sources/aptos_account.move +++ b/aptos-move/framework/aptos-framework/sources/aptos_account.move @@ -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; @@ -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. /// @@ -40,6 +43,23 @@ module aptos_framework::aptos_account { coin::register(&signer); } + /// Batch version of APT transfer. + public entry fun batch_transfer(source: &signer, recipients: vector
, amounts: vector) { + 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) { @@ -49,6 +69,24 @@ module aptos_framework::aptos_account { coin::transfer(source, to, amount) } + /// Batch version of transfer_coins. + public entry fun batch_transfer_coins( + from: &signer, recipients: vector
, amounts: vector) 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(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(from: &signer, to: address, amount: u64) acquires DirectTransferConfig { @@ -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; @@ -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(recipient_1_addr) == 100, 0); + assert!(coin::balance(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( @@ -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( + 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( + from, + vector[recipient_1_addr, recipient_2_addr], + vector[100, 500], + ); + assert!(coin::balance(recipient_1_addr) == 100, 0); + assert!(coin::balance(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 { diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs index 21425d0980e4a..c1445729588bd 100644 --- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs +++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs @@ -76,6 +76,19 @@ pub enum EntryFunctionCall { cap_update_table: Vec, }, + /// Batch version of APT transfer. + AptosAccountBatchTransfer { + recipients: Vec, + amounts: Vec, + }, + + /// Batch version of transfer_coins. + AptosAccountBatchTransferCoins { + coin_type: TypeTag, + recipients: Vec, + amounts: Vec, + }, + /// Basic account creation methods. AptosAccountCreateAccount { auth_key: AccountAddress, @@ -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) @@ -881,6 +903,51 @@ pub fn account_rotate_authentication_key( )) } +/// Batch version of APT transfer. +pub fn aptos_account_batch_transfer( + recipients: Vec, + amounts: Vec, +) -> 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, + amounts: Vec, +) -> 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( @@ -2253,6 +2320,31 @@ mod decoder { } } + pub fn aptos_account_batch_transfer(payload: &TransactionPayload) -> Option { + 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 { + 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 { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::AptosAccountCreateAccount { @@ -3061,6 +3153,14 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy