From 20a162aab118c51579d8bc25208988a0a33ceff6 Mon Sep 17 00:00:00 2001 From: Junkil Park Date: Tue, 5 Mar 2024 18:06:48 -0800 Subject: [PATCH] Enhanced multisig v2 with batch operations and the max pending txn limit (#12241) - introduced and enforced `MAX_PENDING_TRANSACTIONS` - added entry functions for batch voting - added entry functions for batch executing rejected transactions --- .../src/components/feature_flags.rs | 7 + .../aptos-framework/doc/multisig_account.md | 166 +++++++++++++++++ .../sources/multisig_account.move | 158 +++++++++++++++- .../src/aptos_framework_sdk_builder.rs | 176 ++++++++++++++++++ .../framework/move-stdlib/doc/features.md | 60 ++++++ .../move-stdlib/sources/configs/features.move | 11 ++ types/src/on_chain_config/aptos_features.rs | 2 + 7 files changed, 579 insertions(+), 1 deletion(-) diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index efc8c03201f6f..66c7e6d28072c 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -105,6 +105,7 @@ pub enum FeatureFlag { ObjectCodeDeployment, MaxObjectNestingCheck, KeylessAccountsWithPasskeys, + TransactionContextExtension, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -270,6 +271,9 @@ impl From for AptosFeatureFlag { FeatureFlag::KeylessAccountsWithPasskeys => { AptosFeatureFlag::KEYLESS_ACCOUNTS_WITH_PASSKEYS }, + FeatureFlag::TransactionContextExtension => { + AptosFeatureFlag::TRANSACTION_CONTEXT_EXTENSION + }, } } } @@ -358,6 +362,9 @@ impl From for FeatureFlag { AptosFeatureFlag::KEYLESS_ACCOUNTS_WITH_PASSKEYS => { FeatureFlag::KeylessAccountsWithPasskeys }, + AptosFeatureFlag::TRANSACTION_CONTEXT_EXTENSION => { + FeatureFlag::TransactionContextExtension + }, } } } diff --git a/aptos-move/framework/aptos-framework/doc/multisig_account.md b/aptos-move/framework/aptos-framework/doc/multisig_account.md index b787024d32e31..bf59b1f081df0 100644 --- a/aptos-move/framework/aptos-framework/doc/multisig_account.md +++ b/aptos-move/framework/aptos-framework/doc/multisig_account.md @@ -69,6 +69,7 @@ and implement the governance voting logic on top. - [Function `last_resolved_sequence_number`](#0x1_multisig_account_last_resolved_sequence_number) - [Function `next_sequence_number`](#0x1_multisig_account_next_sequence_number) - [Function `vote`](#0x1_multisig_account_vote) +- [Function `available_transaction_queue_capacity`](#0x1_multisig_account_available_transaction_queue_capacity) - [Function `create_with_existing_account`](#0x1_multisig_account_create_with_existing_account) - [Function `create_with_existing_account_and_revoke_auth_key`](#0x1_multisig_account_create_with_existing_account_and_revoke_auth_key) - [Function `create`](#0x1_multisig_account_create) @@ -91,7 +92,10 @@ and implement the governance voting logic on top. - [Function `approve_transaction`](#0x1_multisig_account_approve_transaction) - [Function `reject_transaction`](#0x1_multisig_account_reject_transaction) - [Function `vote_transanction`](#0x1_multisig_account_vote_transanction) +- [Function `vote_transaction`](#0x1_multisig_account_vote_transaction) +- [Function `vote_transactions`](#0x1_multisig_account_vote_transactions) - [Function `execute_rejected_transaction`](#0x1_multisig_account_execute_rejected_transaction) +- [Function `execute_rejected_transactions`](#0x1_multisig_account_execute_rejected_transactions) - [Function `validate_multisig_transaction`](#0x1_multisig_account_validate_multisig_transaction) - [Function `successful_transaction_execution_cleanup`](#0x1_multisig_account_successful_transaction_execution_cleanup) - [Function `failed_transaction_execution_cleanup`](#0x1_multisig_account_failed_transaction_execution_cleanup) @@ -884,6 +888,16 @@ Number of signatures required must be more than zero and at most the total numbe + + +The number of pending transactions has exceeded the maximum allowed. + + +
const EMAX_PENDING_TRANSACTIONS_EXCEEDED: u64 = 19;
+
+ + + Multisig accounts has not been enabled on this current network yet. @@ -894,6 +908,16 @@ Multisig accounts has not been enabled on this current network yet. + + +The multisig v2 enhancement feature is not enabled. + + +
const EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED: u64 = 20;
+
+ + + Transaction has not received enough approvals to be executed. @@ -994,6 +1018,15 @@ Transaction with specified id cannot be found. + + + + +
const MAX_PENDING_TRANSACTIONS: u64 = 20;
+
+ + + ## Function `metadata` @@ -1361,6 +1394,37 @@ Return a bool tuple indicating whether an owner has voted and if so, whether the + + + + +## Function `available_transaction_queue_capacity` + + + +
#[view]
+public fun available_transaction_queue_capacity(multisig_account: address): u64
+
+ + + +
+Implementation + + +
public fun available_transaction_queue_capacity(multisig_account: address): u64 acquires MultisigAccount {
+    let multisig_account_resource = borrow_global_mut<MultisigAccount>(multisig_account);
+    let num_pending_transactions = multisig_account_resource.next_sequence_number - multisig_account_resource.last_executed_sequence_number - 1;
+    if (num_pending_transactions > MAX_PENDING_TRANSACTIONS) {
+        0
+    } else {
+        MAX_PENDING_TRANSACTIONS - num_pending_transactions
+    }
+}
+
+ + +
@@ -2229,6 +2293,8 @@ Reject a multisig transaction. ## Function `vote_transanction` Generic function that can be used to either approve or reject a multisig transaction +Retained for backward compatibility: the function with the typographical error in its name +will continue to be an accessible entry point.
public entry fun vote_transanction(owner: &signer, multisig_account: address, sequence_number: u64, approved: bool)
@@ -2273,6 +2339,64 @@ Generic function that can be used to either approve or reject a multisig transac
 
 
 
+
+
+
+
+## Function `vote_transaction`
+
+Generic function that can be used to either approve or reject a multisig transaction
+
+
+
public entry fun vote_transaction(owner: &signer, multisig_account: address, sequence_number: u64, approved: bool)
+
+ + + +
+Implementation + + +
public entry fun vote_transaction(
+    owner: &signer, multisig_account: address, sequence_number: u64, approved: bool) acquires MultisigAccount {
+    assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+    vote_transanction(owner, multisig_account, sequence_number, approved);
+}
+
+ + + +
+ + + +## Function `vote_transactions` + +Generic function that can be used to either approve or reject a batch of transactions within a specified range. + + +
public entry fun vote_transactions(owner: &signer, multisig_account: address, starting_sequence_number: u64, final_sequence_number: u64, approved: bool)
+
+ + + +
+Implementation + + +
public entry fun vote_transactions(
+    owner: &signer, multisig_account: address, starting_sequence_number: u64, final_sequence_number: u64, approved: bool) acquires MultisigAccount {
+    assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+    let sequence_number = starting_sequence_number;
+    while(sequence_number <= final_sequence_number) {
+        vote_transanction(owner, multisig_account, sequence_number, approved);
+        sequence_number = sequence_number + 1;
+    }
+}
+
+ + +
@@ -2322,6 +2446,40 @@ Remove the next transaction if it has sufficient owner rejections. + + + + +## Function `execute_rejected_transactions` + +Remove the next transactions until the final_sequence_number if they have sufficient owner rejections. + + +
public entry fun execute_rejected_transactions(owner: &signer, multisig_account: address, final_sequence_number: u64)
+
+ + + +
+Implementation + + +
public entry fun execute_rejected_transactions(
+    owner: &signer,
+    multisig_account: address,
+    final_sequence_number: u64,
+) acquires MultisigAccount {
+    assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+    assert!(last_resolved_sequence_number(multisig_account) < final_sequence_number, error::invalid_argument(EINVALID_SEQUENCE_NUMBER));
+    assert!(final_sequence_number < next_sequence_number(multisig_account), error::invalid_argument(EINVALID_SEQUENCE_NUMBER));
+    while(last_resolved_sequence_number(multisig_account) < final_sequence_number) {
+        execute_rejected_transaction(owner, multisig_account);
+    }
+}
+
+ + +
@@ -2501,6 +2659,14 @@ This function is private so no other code can call this beside the VM itself as
fun add_transaction(creator: address, multisig_account: &mut MultisigAccount, transaction: MultisigTransaction) {
+    if(features::multisig_v2_enhancement_feature_enabled()) {
+        let num_pending_transactions = multisig_account.next_sequence_number - (multisig_account.last_executed_sequence_number + 1);
+        assert!(
+            num_pending_transactions < MAX_PENDING_TRANSACTIONS,
+            error::invalid_state(EMAX_PENDING_TRANSACTIONS_EXCEEDED)
+        );
+    };
+
     // The transaction creator also automatically votes for the transaction.
     simple_map::add(&mut transaction.votes, creator, true);
 
diff --git a/aptos-move/framework/aptos-framework/sources/multisig_account.move b/aptos-move/framework/aptos-framework/sources/multisig_account.move
index ed657770abb61..7289fccc3ffc9 100644
--- a/aptos-move/framework/aptos-framework/sources/multisig_account.move
+++ b/aptos-move/framework/aptos-framework/sources/multisig_account.move
@@ -93,9 +93,16 @@ module aptos_framework::multisig_account {
     const EINVALID_SEQUENCE_NUMBER: u64 = 17;
     /// Provided owners to remove and new owners overlap.
     const EOWNERS_TO_REMOVE_NEW_OWNERS_OVERLAP: u64 = 18;
+    /// The number of pending transactions has exceeded the maximum allowed.
+    const EMAX_PENDING_TRANSACTIONS_EXCEEDED: u64 = 19;
+    /// The multisig v2 enhancement feature is not enabled.
+    const EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED: u64 = 20;
+
 
     const ZERO_AUTH_KEY: vector = x"0000000000000000000000000000000000000000000000000000000000000000";
 
+    const MAX_PENDING_TRANSACTIONS: u64 = 20;
+
     /// Represents a multisig account's configurations and transactions.
     /// This will be stored in the multisig account (created as a resource account separate from any owner accounts).
     struct MultisigAccount has key {
@@ -384,6 +391,17 @@ module aptos_framework::multisig_account {
         (voted, vote)
     }
 
+    #[view]
+    public fun available_transaction_queue_capacity(multisig_account: address): u64 acquires MultisigAccount {
+        let multisig_account_resource = borrow_global_mut(multisig_account);
+        let num_pending_transactions = multisig_account_resource.next_sequence_number - multisig_account_resource.last_executed_sequence_number - 1;
+        if (num_pending_transactions > MAX_PENDING_TRANSACTIONS) {
+            0
+        } else {
+            MAX_PENDING_TRANSACTIONS - num_pending_transactions
+        }
+    }
+
     ////////////////////////// Multisig account creation functions ///////////////////////////////
 
     /// Creates a new multisig account on top of an existing account.
@@ -832,6 +850,8 @@ module aptos_framework::multisig_account {
     }
 
     /// Generic function that can be used to either approve or reject a multisig transaction
+    /// Retained for backward compatibility: the function with the typographical error in its name
+    /// will continue to be an accessible entry point.
     public entry fun vote_transanction(
         owner: &signer, multisig_account: address, sequence_number: u64, approved: bool) acquires MultisigAccount {
         assert_multisig_account_exists(multisig_account);
@@ -862,6 +882,24 @@ module aptos_framework::multisig_account {
         );
     }
 
+    /// Generic function that can be used to either approve or reject a multisig transaction
+    public entry fun vote_transaction(
+        owner: &signer, multisig_account: address, sequence_number: u64, approved: bool) acquires MultisigAccount {
+        assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+        vote_transanction(owner, multisig_account, sequence_number, approved);
+    }
+
+    /// Generic function that can be used to either approve or reject a batch of transactions within a specified range.
+    public entry fun vote_transactions(
+        owner: &signer, multisig_account: address, starting_sequence_number: u64, final_sequence_number: u64, approved: bool) acquires MultisigAccount {
+        assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+        let sequence_number = starting_sequence_number;
+        while(sequence_number <= final_sequence_number) {
+            vote_transanction(owner, multisig_account, sequence_number, approved);
+            sequence_number = sequence_number + 1;
+        }
+    }
+
     /// Remove the next transaction if it has sufficient owner rejections.
     public entry fun execute_rejected_transaction(
         owner: &signer,
@@ -891,6 +929,20 @@ module aptos_framework::multisig_account {
         );
     }
 
+    /// Remove the next transactions until the final_sequence_number if they have sufficient owner rejections.
+    public entry fun execute_rejected_transactions(
+        owner: &signer,
+        multisig_account: address,
+        final_sequence_number: u64,
+    ) acquires MultisigAccount {
+        assert!(features::multisig_v2_enhancement_feature_enabled(), error::invalid_state(EMULTISIG_V2_ENHANCEMENT_NOT_ENABLED));
+        assert!(last_resolved_sequence_number(multisig_account) < final_sequence_number, error::invalid_argument(EINVALID_SEQUENCE_NUMBER));
+        assert!(final_sequence_number < next_sequence_number(multisig_account), error::invalid_argument(EINVALID_SEQUENCE_NUMBER));
+        while(last_resolved_sequence_number(multisig_account) < final_sequence_number) {
+            execute_rejected_transaction(owner, multisig_account);
+        }
+    }
+
     ////////////////////////// To be called by VM only ///////////////////////////////
 
     /// Called by the VM as part of transaction prologue, which is invoked during mempool transaction validation and as
@@ -978,6 +1030,14 @@ module aptos_framework::multisig_account {
     }
 
     fun add_transaction(creator: address, multisig_account: &mut MultisigAccount, transaction: MultisigTransaction) {
+        if(features::multisig_v2_enhancement_feature_enabled()) {
+            let num_pending_transactions = multisig_account.next_sequence_number - (multisig_account.last_executed_sequence_number + 1);
+            assert!(
+                num_pending_transactions < MAX_PENDING_TRANSACTIONS,
+                error::invalid_state(EMAX_PENDING_TRANSACTIONS_EXCEEDED)
+            );
+        };
+
         // The transaction creator also automatically votes for the transaction.
         simple_map::add(&mut transaction.votes, creator, true);
 
@@ -1170,7 +1230,7 @@ module aptos_framework::multisig_account {
     fun setup() {
         let framework_signer = &create_signer(@0x1);
         features::change_feature_flags(
-            framework_signer, vector[features::get_multisig_accounts_feature()], vector[]);
+            framework_signer, vector[features::get_multisig_accounts_feature(), features::get_multisig_v2_enhancement_feature()], vector[]);
         timestamp::set_time_has_started_for_testing(framework_signer);
         chain_id::initialize_for_test(framework_signer, 1);
     }
@@ -1888,4 +1948,100 @@ module aptos_framework::multisig_account {
             option::none()
         );
     }
+
+    #[test(owner_1 = @0x123, owner_2 = @0x124, owner_3 = @0x125)]
+    #[expected_failure(abort_code = 196627, location = Self)]
+    fun test_max_pending_transaction_limit_should_fail(
+        owner_1: &signer,
+        owner_2: &signer,
+        owner_3: &signer
+    ) acquires MultisigAccount {
+        setup();
+        let owner_1_addr = address_of(owner_1);
+        let owner_2_addr = address_of(owner_2);
+        let owner_3_addr = address_of(owner_3);
+        create_account(owner_1_addr);
+        let multisig_address = get_next_multisig_account_address(owner_1_addr);
+        create_with_owners(
+            owner_1,
+            vector[owner_2_addr, owner_3_addr],
+            2,
+            vector[],
+            vector[]
+        );
+
+        let remaining_iterations = MAX_PENDING_TRANSACTIONS + 1;
+        while (remaining_iterations > 0) {
+            create_transaction(owner_1, multisig_address, PAYLOAD);
+            remaining_iterations = remaining_iterations - 1;
+        }
+    }
+
+    #[test_only]
+    fun create_transaction_with_eviction(
+        owner: &signer,
+        multisig_account: address,
+        payload: vector,
+    ) acquires MultisigAccount {
+        while(available_transaction_queue_capacity(multisig_account) == 0) {
+            execute_rejected_transaction(owner, multisig_account)
+        };
+        create_transaction(owner, multisig_account, payload);
+    }
+
+    #[test_only]
+    fun vote_all_transactions(
+        owner: &signer, multisig_account: address, approved: bool) acquires MultisigAccount {
+        let starting_sequence_number = last_resolved_sequence_number(multisig_account) + 1;
+        let final_sequence_number = next_sequence_number(multisig_account) - 1;
+        vote_transactions(owner, multisig_account, starting_sequence_number, final_sequence_number, approved);
+    }
+
+    #[test(owner_1 = @0x123, owner_2 = @0x124, owner_3 = @0x125)]
+    fun test_dos_mitigation_end_to_end(
+        owner_1: &signer,
+        owner_2: &signer,
+        owner_3: &signer
+    ) acquires MultisigAccount {
+        setup();
+        let owner_1_addr = address_of(owner_1);
+        let owner_2_addr = address_of(owner_2);
+        let owner_3_addr = address_of(owner_3);
+        create_account(owner_1_addr);
+        let multisig_address = get_next_multisig_account_address(owner_1_addr);
+        create_with_owners(
+            owner_1,
+            vector[owner_2_addr, owner_3_addr],
+            2,
+            vector[],
+            vector[]
+        );
+
+        // owner_3 is compromised and creates a bunch of bogus transactions.
+        let remaining_iterations = MAX_PENDING_TRANSACTIONS;
+        while (remaining_iterations > 0) {
+            create_transaction(owner_3, multisig_address, PAYLOAD);
+            remaining_iterations = remaining_iterations - 1;
+        };
+
+        // No one can create a transaction anymore because the transaction queue is full.
+        assert!(available_transaction_queue_capacity(multisig_address) == 0, 0);
+
+        // owner_1 and owner_2 vote "no" on all transactions.
+        vote_all_transactions(owner_1, multisig_address, false);
+        vote_all_transactions(owner_2, multisig_address, false);
+
+        // owner_1 evicts a transaction and creates a transaction to remove the compromised owner.
+        // Note that `PAYLOAD` is a placeholder and is not actually executed in this unit test.
+        create_transaction_with_eviction(owner_1, multisig_address, PAYLOAD);
+
+        // owner_2 approves the eviction transaction.
+        approve_transaction(owner_2, multisig_address, 11);
+
+        // owner_1 flushes the transaction queue except for the eviction transaction.
+        execute_rejected_transactions(owner_1, multisig_address, 10);
+
+        // execute the eviction transaction to remove the compromised owner.
+        assert!(can_be_executed(multisig_address, 11), 0);
+    }
 }
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 c55f002469df4..21296b2c03c38 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
@@ -503,6 +503,12 @@ pub enum EntryFunctionCall {
         multisig_account: AccountAddress,
     },
 
+    /// Remove the next transactions until the final_sequence_number if they have sufficient owner rejections.
+    MultisigAccountExecuteRejectedTransactions {
+        multisig_account: AccountAddress,
+        final_sequence_number: u64,
+    },
+
     /// Reject a multisig transaction.
     MultisigAccountRejectTransaction {
         multisig_account: AccountAddress,
@@ -567,6 +573,23 @@ pub enum EntryFunctionCall {
     },
 
     /// Generic function that can be used to either approve or reject a multisig transaction
+    MultisigAccountVoteTransaction {
+        multisig_account: AccountAddress,
+        sequence_number: u64,
+        approved: bool,
+    },
+
+    /// Generic function that can be used to either approve or reject a batch of transactions within a specified range.
+    MultisigAccountVoteTransactions {
+        multisig_account: AccountAddress,
+        starting_sequence_number: u64,
+        final_sequence_number: u64,
+        approved: bool,
+    },
+
+    /// Generic function that can be used to either approve or reject a multisig transaction
+    /// Retained for backward compatibility: the function with the typographical error in its name
+    /// will continue to be an accessible entry point.
     MultisigAccountVoteTransanction {
         multisig_account: AccountAddress,
         sequence_number: u64,
@@ -1220,6 +1243,13 @@ impl EntryFunctionCall {
             MultisigAccountExecuteRejectedTransaction { multisig_account } => {
                 multisig_account_execute_rejected_transaction(multisig_account)
             },
+            MultisigAccountExecuteRejectedTransactions {
+                multisig_account,
+                final_sequence_number,
+            } => multisig_account_execute_rejected_transactions(
+                multisig_account,
+                final_sequence_number,
+            ),
             MultisigAccountRejectTransaction {
                 multisig_account,
                 sequence_number,
@@ -1253,6 +1283,22 @@ impl EntryFunctionCall {
             MultisigAccountUpdateSignaturesRequired {
                 new_num_signatures_required,
             } => multisig_account_update_signatures_required(new_num_signatures_required),
+            MultisigAccountVoteTransaction {
+                multisig_account,
+                sequence_number,
+                approved,
+            } => multisig_account_vote_transaction(multisig_account, sequence_number, approved),
+            MultisigAccountVoteTransactions {
+                multisig_account,
+                starting_sequence_number,
+                final_sequence_number,
+                approved,
+            } => multisig_account_vote_transactions(
+                multisig_account,
+                starting_sequence_number,
+                final_sequence_number,
+                approved,
+            ),
             MultisigAccountVoteTransanction {
                 multisig_account,
                 sequence_number,
@@ -2766,6 +2812,28 @@ pub fn multisig_account_execute_rejected_transaction(
     ))
 }
 
+/// Remove the next transactions until the final_sequence_number if they have sufficient owner rejections.
+pub fn multisig_account_execute_rejected_transactions(
+    multisig_account: AccountAddress,
+    final_sequence_number: 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!("multisig_account").to_owned(),
+        ),
+        ident_str!("execute_rejected_transactions").to_owned(),
+        vec![],
+        vec![
+            bcs::to_bytes(&multisig_account).unwrap(),
+            bcs::to_bytes(&final_sequence_number).unwrap(),
+        ],
+    ))
+}
+
 /// Reject a multisig transaction.
 pub fn multisig_account_reject_transaction(
     multisig_account: AccountAddress,
@@ -2946,6 +3014,58 @@ pub fn multisig_account_update_signatures_required(
 }
 
 /// Generic function that can be used to either approve or reject a multisig transaction
+pub fn multisig_account_vote_transaction(
+    multisig_account: AccountAddress,
+    sequence_number: u64,
+    approved: bool,
+) -> 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!("multisig_account").to_owned(),
+        ),
+        ident_str!("vote_transaction").to_owned(),
+        vec![],
+        vec![
+            bcs::to_bytes(&multisig_account).unwrap(),
+            bcs::to_bytes(&sequence_number).unwrap(),
+            bcs::to_bytes(&approved).unwrap(),
+        ],
+    ))
+}
+
+/// Generic function that can be used to either approve or reject a batch of transactions within a specified range.
+pub fn multisig_account_vote_transactions(
+    multisig_account: AccountAddress,
+    starting_sequence_number: u64,
+    final_sequence_number: u64,
+    approved: bool,
+) -> 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!("multisig_account").to_owned(),
+        ),
+        ident_str!("vote_transactions").to_owned(),
+        vec![],
+        vec![
+            bcs::to_bytes(&multisig_account).unwrap(),
+            bcs::to_bytes(&starting_sequence_number).unwrap(),
+            bcs::to_bytes(&final_sequence_number).unwrap(),
+            bcs::to_bytes(&approved).unwrap(),
+        ],
+    ))
+}
+
+/// Generic function that can be used to either approve or reject a multisig transaction
+/// Retained for backward compatibility: the function with the typographical error in its name
+/// will continue to be an accessible entry point.
 pub fn multisig_account_vote_transanction(
     multisig_account: AccountAddress,
     sequence_number: u64,
@@ -4836,6 +4956,21 @@ mod decoder {
         }
     }
 
+    pub fn multisig_account_execute_rejected_transactions(
+        payload: &TransactionPayload,
+    ) -> Option {
+        if let TransactionPayload::EntryFunction(script) = payload {
+            Some(
+                EntryFunctionCall::MultisigAccountExecuteRejectedTransactions {
+                    multisig_account: bcs::from_bytes(script.args().get(0)?).ok()?,
+                    final_sequence_number: bcs::from_bytes(script.args().get(1)?).ok()?,
+                },
+            )
+        } else {
+            None
+        }
+    }
+
     pub fn multisig_account_reject_transaction(
         payload: &TransactionPayload,
     ) -> Option {
@@ -4936,6 +5071,35 @@ mod decoder {
         }
     }
 
+    pub fn multisig_account_vote_transaction(
+        payload: &TransactionPayload,
+    ) -> Option {
+        if let TransactionPayload::EntryFunction(script) = payload {
+            Some(EntryFunctionCall::MultisigAccountVoteTransaction {
+                multisig_account: bcs::from_bytes(script.args().get(0)?).ok()?,
+                sequence_number: bcs::from_bytes(script.args().get(1)?).ok()?,
+                approved: bcs::from_bytes(script.args().get(2)?).ok()?,
+            })
+        } else {
+            None
+        }
+    }
+
+    pub fn multisig_account_vote_transactions(
+        payload: &TransactionPayload,
+    ) -> Option {
+        if let TransactionPayload::EntryFunction(script) = payload {
+            Some(EntryFunctionCall::MultisigAccountVoteTransactions {
+                multisig_account: bcs::from_bytes(script.args().get(0)?).ok()?,
+                starting_sequence_number: bcs::from_bytes(script.args().get(1)?).ok()?,
+                final_sequence_number: bcs::from_bytes(script.args().get(2)?).ok()?,
+                approved: bcs::from_bytes(script.args().get(3)?).ok()?,
+            })
+        } else {
+            None
+        }
+    }
+
     pub fn multisig_account_vote_transanction(
         payload: &TransactionPayload,
     ) -> Option {
@@ -5861,6 +6025,10 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy
+
+Whether the Multisig V2 enhancement feature is enabled.
+
+Lifetime: transient
+
+
+
const MULTISIG_V2_ENHANCEMENT: u64 = 55;
+
+ + + Whether the new aptos_stdlib::multi_ed25519::public_key_validate_internal_v2() native is enabled. @@ -2356,6 +2370,52 @@ Lifetime: transient + + + + +## Function `get_multisig_v2_enhancement_feature` + + + +
public fun get_multisig_v2_enhancement_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_multisig_v2_enhancement_feature(): u64 { MULTISIG_V2_ENHANCEMENT }
+
+ + + +
+ + + +## Function `multisig_v2_enhancement_feature_enabled` + + + +
public fun multisig_v2_enhancement_feature_enabled(): bool
+
+ + + +
+Implementation + + +
public fun multisig_v2_enhancement_feature_enabled(): bool acquires Features {
+    is_enabled(MULTISIG_V2_ENHANCEMENT)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index dacd668ca3342..a23db2d95dbe4 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -428,6 +428,17 @@ module std::features { is_enabled(KEYLESS_ACCOUNTS_WITH_PASSKEYS) } + /// Whether the Multisig V2 enhancement feature is enabled. + /// + /// Lifetime: transient + const MULTISIG_V2_ENHANCEMENT: u64 = 55; + + public fun get_multisig_v2_enhancement_feature(): u64 { MULTISIG_V2_ENHANCEMENT } + + public fun multisig_v2_enhancement_feature_enabled(): bool acquires Features { + is_enabled(MULTISIG_V2_ENHANCEMENT) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index 1e5919b82fb26..e531553ddf0c7 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -66,6 +66,7 @@ pub enum FeatureFlag { OBJECT_CODE_DEPLOYMENT = 52, MAX_OBJECT_NESTING_CHECK = 53, KEYLESS_ACCOUNTS_WITH_PASSKEYS = 54, + TRANSACTION_CONTEXT_EXTENSION = 55, } impl FeatureFlag { @@ -118,6 +119,7 @@ impl FeatureFlag { FeatureFlag::OBJECT_CODE_DEPLOYMENT, FeatureFlag::MAX_OBJECT_NESTING_CHECK, FeatureFlag::KEYLESS_ACCOUNTS_WITH_PASSKEYS, + FeatureFlag::TRANSACTION_CONTEXT_EXTENSION, ] } }