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 4134ac10928..959c8787e0b 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -163,6 +163,7 @@ pub enum FeatureFlag { PublicStructEnumArgs, MultisigScript, TransactionLimits, + VersionedTransactionValidation, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -425,6 +426,9 @@ impl From for AptosFeatureFlag { FeatureFlag::PublicStructEnumArgs => AptosFeatureFlag::PUBLIC_STRUCT_ENUM_ARGS, FeatureFlag::MultisigScript => AptosFeatureFlag::MULTISIG_SCRIPT, FeatureFlag::TransactionLimits => AptosFeatureFlag::TRANSACTION_LIMITS, + FeatureFlag::VersionedTransactionValidation => { + AptosFeatureFlag::VERSIONED_TRANSACTION_VALIDATION + }, } } } @@ -614,6 +618,9 @@ impl From for FeatureFlag { AptosFeatureFlag::PUBLIC_STRUCT_ENUM_ARGS => FeatureFlag::PublicStructEnumArgs, AptosFeatureFlag::MULTISIG_SCRIPT => FeatureFlag::MultisigScript, AptosFeatureFlag::TRANSACTION_LIMITS => FeatureFlag::TransactionLimits, + AptosFeatureFlag::VERSIONED_TRANSACTION_VALIDATION => { + FeatureFlag::VersionedTransactionValidation + }, } } } diff --git a/aptos-move/aptos-vm/src/lib.rs b/aptos-move/aptos-vm/src/lib.rs index 6255fc42fff..50d6d7457de 100644 --- a/aptos-move/aptos-vm/src/lib.rs +++ b/aptos-move/aptos-vm/src/lib.rs @@ -120,6 +120,7 @@ pub mod system_module_names; pub mod testing; pub mod transaction_metadata; mod transaction_validation; +mod transaction_validation_versioned; pub mod validator_txns; pub mod verifier; diff --git a/aptos-move/aptos-vm/src/system_module_names.rs b/aptos-move/aptos-vm/src/system_module_names.rs index ee464f91fd2..16cffd2de8d 100644 --- a/aptos-move/aptos-vm/src/system_module_names.rs +++ b/aptos-move/aptos-vm/src/system_module_names.rs @@ -80,3 +80,13 @@ pub static TRANSACTION_FEE_MODULE: Lazy = Lazy::new(|| { }); pub const EMIT_FEE_STATEMENT: &IdentStr = ident_str!("emit_fee_statement"); + +pub static TRANSACTION_VALIDATION_MODULE: Lazy = Lazy::new(|| { + ModuleId::new( + account_config::CORE_CODE_ADDRESS, + ident_str!("transaction_validation").to_owned(), + ) +}); + +pub const VERSIONED_PROLOGUE_NAME: &IdentStr = ident_str!("versioned_prologue"); +pub const VERSIONED_EPILOGUE_NAME: &IdentStr = ident_str!("versioned_epilogue"); diff --git a/aptos-move/aptos-vm/src/transaction_validation.rs b/aptos-move/aptos-vm/src/transaction_validation.rs index 434aef94b59..977e39c6c97 100644 --- a/aptos-move/aptos-vm/src/transaction_validation.rs +++ b/aptos-move/aptos-vm/src/transaction_validation.rs @@ -18,9 +18,7 @@ use aptos_types::{ fee_statement::FeeStatement, move_utils::as_move_value::AsMoveValue, on_chain_config::Features, - transaction::{ - MultisigTransactionPayload, ReplayProtector, TransactionExecutableRef, TxnLimitsRequest, - }, + transaction::{MultisigTransactionPayload, ReplayProtector, TransactionExecutableRef}, }; use aptos_vm_logging::log_schema::AdapterLogSchema; use fail::fail_point; @@ -64,11 +62,6 @@ pub static APTOS_TRANSACTION_VALIDATION: Lazy = unified_prologue_fee_payer_v2_name: Identifier::new("unified_prologue_fee_payer_v2") .unwrap(), unified_epilogue_v2_name: Identifier::new("unified_epilogue_v2").unwrap(), - - // V3 prologues support voting-power-based high-txn-limits. - unified_prologue_v3_name: Identifier::new("unified_prologue_v3").unwrap(), - unified_prologue_fee_payer_v3_name: Identifier::new("unified_prologue_fee_payer_v3") - .unwrap(), }); /// On-chain functions used to validate transactions @@ -94,10 +87,6 @@ pub struct TransactionValidation { pub unified_prologue_v2_name: Identifier, pub unified_prologue_fee_payer_v2_name: Identifier, pub unified_epilogue_v2_name: Identifier, - - // V3 prologues support voting-power-based high-txn-limits. - pub unified_prologue_v3_name: Identifier, - pub unified_prologue_fee_payer_v3_name: Identifier, } impl TransactionValidation { @@ -133,6 +122,18 @@ pub(crate) fn run_script_prologue( traversal_context: &mut TraversalContext, is_simulation: bool, ) -> Result<(), VMStatus> { + if features.is_versioned_transaction_validation_enabled() { + return crate::transaction_validation_versioned::run_prologue( + session, + module_storage, + serialized_signers, + txn_data, + log_context, + traversal_context, + is_simulation, + ); + } + let txn_replay_protector = txn_data.replay_protector(); let txn_authentication_key = txn_data.authentication_proof().optional_auth_key(); let txn_gas_price = txn_data.gas_unit_price(); @@ -166,103 +167,88 @@ pub(crate) fn run_script_prologue( } }; - let (prologue_function_name, mut serialized_args) = - if let (true, Some(fee_payer_auth_key)) = ( - txn_data.fee_payer().is_some(), - txn_data - .fee_payer_authentication_proof - .as_ref() - .map(|proof| proof.optional_auth_key()), - ) { - let serialized_args = vec![ - serialized_signers.sender(), - serialized_signers - .fee_payer() - .ok_or_else(|| VMStatus::error(StatusCode::UNREACHABLE, None))?, - txn_authentication_key - .as_move_value() - .simple_serialize() - .unwrap(), - fee_payer_auth_key - .as_move_value() - .simple_serialize() - .unwrap(), - replay_protector_move_value, - MoveValue::vector_address(txn_data.secondary_signers()) - .simple_serialize() - .unwrap(), - MoveValue::Vector(secondary_auth_keys) - .simple_serialize() - .unwrap(), - MoveValue::U64(txn_gas_price.into()) - .simple_serialize() - .unwrap(), - MoveValue::U64(txn_max_gas_units.into()) - .simple_serialize() - .unwrap(), - MoveValue::U64(txn_expiration_timestamp_secs) - .simple_serialize() - .unwrap(), - MoveValue::U8(chain_id.id()).simple_serialize().unwrap(), - MoveValue::Bool(is_simulation).simple_serialize().unwrap(), - ]; - ( - if features.is_transaction_limits_enabled() { - &APTOS_TRANSACTION_VALIDATION.unified_prologue_fee_payer_v3_name - } else if features.is_transaction_payload_v2_enabled() { - &APTOS_TRANSACTION_VALIDATION.unified_prologue_fee_payer_v2_name - } else { - &APTOS_TRANSACTION_VALIDATION.unified_prologue_fee_payer_name - }, - serialized_args, - ) - } else { - let serialized_args = vec![ - serialized_signers.sender(), - txn_authentication_key - .as_move_value() - .simple_serialize() - .unwrap(), - replay_protector_move_value, - MoveValue::vector_address(txn_data.secondary_signers()) - .simple_serialize() - .unwrap(), - MoveValue::Vector(secondary_auth_keys) - .simple_serialize() - .unwrap(), - MoveValue::U64(txn_gas_price.into()) - .simple_serialize() - .unwrap(), - MoveValue::U64(txn_max_gas_units.into()) - .simple_serialize() - .unwrap(), - MoveValue::U64(txn_expiration_timestamp_secs) - .simple_serialize() - .unwrap(), - MoveValue::U8(chain_id.id()).simple_serialize().unwrap(), - MoveValue::Bool(is_simulation).simple_serialize().unwrap(), - ]; - ( - if features.is_transaction_limits_enabled() { - &APTOS_TRANSACTION_VALIDATION.unified_prologue_v3_name - } else if features.is_transaction_payload_v2_enabled() { - &APTOS_TRANSACTION_VALIDATION.unified_prologue_v2_name - } else { - &APTOS_TRANSACTION_VALIDATION.unified_prologue_name - }, - serialized_args, - ) - }; - - // Append the user's staking-backed request when dispatching to v3 prologue. - // Governance scripts pass None (they don't need Move-side validation). - if features.is_transaction_limits_enabled() { - let user_request = txn_data.txn_limits.as_ref().and_then(|v| match v { - TxnLimitsRequest::Staking(req) => Some(req), - TxnLimitsRequest::ApprovedGovernanceScript => None, - }); - serialized_args.push(bcs::to_bytes(&user_request).unwrap()); - } + let (prologue_function_name, serialized_args) = if let (true, Some(fee_payer_auth_key)) = ( + txn_data.fee_payer().is_some(), + txn_data + .fee_payer_authentication_proof + .as_ref() + .map(|proof| proof.optional_auth_key()), + ) { + let serialized_args = vec![ + serialized_signers.sender(), + serialized_signers + .fee_payer() + .ok_or_else(|| VMStatus::error(StatusCode::UNREACHABLE, None))?, + txn_authentication_key + .as_move_value() + .simple_serialize() + .unwrap(), + fee_payer_auth_key + .as_move_value() + .simple_serialize() + .unwrap(), + replay_protector_move_value, + MoveValue::vector_address(txn_data.secondary_signers()) + .simple_serialize() + .unwrap(), + MoveValue::Vector(secondary_auth_keys) + .simple_serialize() + .unwrap(), + MoveValue::U64(txn_gas_price.into()) + .simple_serialize() + .unwrap(), + MoveValue::U64(txn_max_gas_units.into()) + .simple_serialize() + .unwrap(), + MoveValue::U64(txn_expiration_timestamp_secs) + .simple_serialize() + .unwrap(), + MoveValue::U8(chain_id.id()).simple_serialize().unwrap(), + MoveValue::Bool(is_simulation).simple_serialize().unwrap(), + ]; + ( + if features.is_transaction_payload_v2_enabled() { + &APTOS_TRANSACTION_VALIDATION.unified_prologue_fee_payer_v2_name + } else { + &APTOS_TRANSACTION_VALIDATION.unified_prologue_fee_payer_name + }, + serialized_args, + ) + } else { + let serialized_args = vec![ + serialized_signers.sender(), + txn_authentication_key + .as_move_value() + .simple_serialize() + .unwrap(), + replay_protector_move_value, + MoveValue::vector_address(txn_data.secondary_signers()) + .simple_serialize() + .unwrap(), + MoveValue::Vector(secondary_auth_keys) + .simple_serialize() + .unwrap(), + MoveValue::U64(txn_gas_price.into()) + .simple_serialize() + .unwrap(), + MoveValue::U64(txn_max_gas_units.into()) + .simple_serialize() + .unwrap(), + MoveValue::U64(txn_expiration_timestamp_secs) + .simple_serialize() + .unwrap(), + MoveValue::U8(chain_id.id()).simple_serialize().unwrap(), + MoveValue::Bool(is_simulation).simple_serialize().unwrap(), + ]; + ( + if features.is_transaction_payload_v2_enabled() { + &APTOS_TRANSACTION_VALIDATION.unified_prologue_v2_name + } else { + &APTOS_TRANSACTION_VALIDATION.unified_prologue_name + }, + serialized_args, + ) + }; session .execute_function_bypass_visibility( @@ -503,6 +489,19 @@ fn run_epilogue( traversal_context: &mut TraversalContext, is_simulation: bool, ) -> VMResult<()> { + if features.is_versioned_transaction_validation_enabled() { + return crate::transaction_validation_versioned::run_epilogue( + session, + module_storage, + serialized_signers, + gas_remaining, + fee_statement, + txn_data, + traversal_context, + is_simulation, + ); + } + let txn_gas_price = txn_data.gas_unit_price(); let txn_max_gas_units = txn_data.max_gas_amount(); let is_orderless_txn = txn_data.is_orderless(); diff --git a/aptos-move/aptos-vm/src/transaction_validation_versioned.rs b/aptos-move/aptos-vm/src/transaction_validation_versioned.rs new file mode 100644 index 00000000000..3a76e701c15 --- /dev/null +++ b/aptos-move/aptos-vm/src/transaction_validation_versioned.rs @@ -0,0 +1,234 @@ +// Copyright (c) Aptos Foundation +// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE + +use crate::{ + aptos_vm::SerializedSigners, + errors::convert_prologue_error, + move_vm_ext::{AptosMoveResolver, SessionExt}, + system_module_names::{ + TRANSACTION_VALIDATION_MODULE, VERSIONED_EPILOGUE_NAME, VERSIONED_PROLOGUE_NAME, + }, + testing::{maybe_raise_injected_error, InjectedError}, + transaction_metadata::TransactionMetadata, +}; +use aptos_gas_algebra::Gas; +use aptos_types::{ + fee_statement::FeeStatement, + transaction::{ReplayProtector, TxnLimitsRequest, UserTxnLimitsRequest}, +}; +use aptos_vm_logging::log_schema::AdapterLogSchema; +use move_binary_format::errors::VMResult; +use move_core_types::account_address::AccountAddress; +use move_vm_runtime::{ + logging::expect_no_verification_errors, module_traversal::TraversalContext, ModuleStorage, +}; +use move_vm_types::gas::UnmeteredGasMeter; +use serde::Serialize; + +/// Mirrors Move enum in `transaction_validation.move` and needs to have the +/// same BCS serialization. +#[derive(Serialize)] +enum PrologueArgs { + V1 { + txn_sender_public_key: Option>, + fee_payer_public_key_hash: Option>, + replay_protector: ReplayProtector, + secondary_signer_addresses: Vec, + secondary_signer_public_key_hashes: Vec>>, + txn_gas_price: u64, + txn_max_gas_units: u64, + txn_expiration_time: u64, + chain_id: u8, + is_simulation: bool, + txn_limits_request: Option, + }, +} + +/// Builder that collects prologue arguments and selects the appropriate enum +/// variant to build. +pub(crate) struct PrologueBuilder { + txn_sender_public_key: Option>, + fee_payer_public_key_hash: Option>, + replay_protector: ReplayProtector, + secondary_signer_addresses: Vec, + secondary_signer_public_key_hashes: Vec>>, + txn_gas_price: u64, + txn_max_gas_units: u64, + txn_expiration_time: u64, + chain_id: u8, + is_simulation: bool, + txn_limits_request: Option, +} + +impl PrologueBuilder { + pub fn new(txn_data: &TransactionMetadata, is_simulation: bool) -> Self { + Self { + txn_sender_public_key: txn_data.authentication_proof().optional_auth_key(), + fee_payer_public_key_hash: txn_data + .fee_payer_authentication_proof + .as_ref() + .and_then(|proof| proof.optional_auth_key()), + replay_protector: txn_data.replay_protector(), + secondary_signer_addresses: txn_data.secondary_signers(), + secondary_signer_public_key_hashes: txn_data + .secondary_authentication_proofs + .iter() + .map(|proof| proof.optional_auth_key()) + .collect(), + txn_gas_price: txn_data.gas_unit_price().into(), + txn_max_gas_units: txn_data.max_gas_amount().into(), + txn_expiration_time: txn_data.expiration_timestamp_secs(), + chain_id: txn_data.chain_id().id(), + is_simulation, + txn_limits_request: txn_data.txn_limits.as_ref().and_then(|v| match v { + TxnLimitsRequest::ApprovedGovernanceScript => None, + TxnLimitsRequest::Staking(req) => Some(req.clone()), + }), + } + } + + /// Selects the highest supported variant based on feature flags and BCS-serializes it. + /// Currently only V1 exists. + pub fn build(self) -> Vec { + let args = PrologueArgs::V1 { + txn_sender_public_key: self.txn_sender_public_key, + fee_payer_public_key_hash: self.fee_payer_public_key_hash, + replay_protector: self.replay_protector, + secondary_signer_addresses: self.secondary_signer_addresses, + secondary_signer_public_key_hashes: self.secondary_signer_public_key_hashes, + txn_gas_price: self.txn_gas_price, + txn_max_gas_units: self.txn_max_gas_units, + txn_expiration_time: self.txn_expiration_time, + chain_id: self.chain_id, + is_simulation: self.is_simulation, + txn_limits_request: self.txn_limits_request, + }; + bcs::to_bytes(&args).expect("Failed to serialize prologue arguments") + } +} + +pub(crate) fn run_prologue( + session: &mut SessionExt, + module_storage: &impl ModuleStorage, + serialized_signers: &SerializedSigners, + txn_data: &TransactionMetadata, + log_context: &AdapterLogSchema, + traversal_context: &mut TraversalContext, + is_simulation: bool, +) -> Result<(), move_core_types::vm_status::VMStatus> { + let builder = PrologueBuilder::new(txn_data, is_simulation); + let serialized_args = vec![ + serialized_signers.sender(), + serialized_signers + .fee_payer() + .unwrap_or(serialized_signers.sender()), + builder.build(), + ]; + session + .execute_function_bypass_visibility( + &TRANSACTION_VALIDATION_MODULE, + VERSIONED_PROLOGUE_NAME, + vec![], + serialized_args, + &mut UnmeteredGasMeter, + traversal_context, + module_storage, + ) + .map(|_return_vals| ()) + .map_err(expect_no_verification_errors) + .or_else(|err| convert_prologue_error(err, log_context)) +} + +/// Mirrors Move enum in `transaction_validation.move` and needs to have the +/// same BCS serialization. +#[derive(Serialize)] +enum EpilogueArgs { + V1 { + fee_statement: FeeStatement, + txn_gas_price: u64, + txn_max_gas_units: u64, + gas_units_remaining: u64, + is_simulation: bool, + is_orderless_txn: bool, + }, +} + +/// Builder that collects epilogue arguments and selects the appropriate enum +/// variant based on feature flags. +pub(crate) struct EpilogueBuilder { + fee_statement: FeeStatement, + txn_gas_price: u64, + txn_max_gas_units: u64, + gas_units_remaining: u64, + is_simulation: bool, + is_orderless_txn: bool, +} + +impl EpilogueBuilder { + pub fn new( + fee_statement: FeeStatement, + txn_data: &TransactionMetadata, + gas_remaining: Gas, + is_simulation: bool, + ) -> Self { + Self { + fee_statement, + txn_gas_price: txn_data.gas_unit_price().into(), + txn_max_gas_units: txn_data.max_gas_amount().into(), + gas_units_remaining: gas_remaining.into(), + is_simulation, + is_orderless_txn: txn_data.is_orderless(), + } + } + + /// Selects the highest supported variant based on feature flags and BCS-serializes it. + /// Currently only V1 exists. + pub fn build(self) -> Vec { + let args = EpilogueArgs::V1 { + fee_statement: self.fee_statement, + txn_gas_price: self.txn_gas_price, + txn_max_gas_units: self.txn_max_gas_units, + gas_units_remaining: self.gas_units_remaining, + is_simulation: self.is_simulation, + is_orderless_txn: self.is_orderless_txn, + }; + bcs::to_bytes(&args).expect("Failed to serialize epilogue arguments") + } +} + +pub(crate) fn run_epilogue( + session: &mut SessionExt, + module_storage: &impl ModuleStorage, + serialized_signers: &SerializedSigners, + gas_remaining: Gas, + fee_statement: FeeStatement, + txn_data: &TransactionMetadata, + traversal_context: &mut TraversalContext, + is_simulation: bool, +) -> VMResult<()> { + let builder = EpilogueBuilder::new(fee_statement, txn_data, gas_remaining, is_simulation); + let serialized_args = vec![ + serialized_signers.sender(), + serialized_signers + .fee_payer() + .unwrap_or(serialized_signers.sender()), + builder.build(), + ]; + + session + .execute_function_bypass_visibility( + &TRANSACTION_VALIDATION_MODULE, + VERSIONED_EPILOGUE_NAME, + vec![], + serialized_args, + &mut UnmeteredGasMeter, + traversal_context, + module_storage, + ) + .map(|_return_vals| ()) + .map_err(expect_no_verification_errors)?; + + maybe_raise_injected_error(InjectedError::EndOfRunEpilogue)?; + + Ok(()) +} diff --git a/aptos-move/framework/aptos-framework/doc/transaction_fee.md b/aptos-move/framework/aptos-framework/doc/transaction_fee.md index 4757ec75644..18071338ef1 100644 --- a/aptos-move/framework/aptos-framework/doc/transaction_fee.md +++ b/aptos-move/framework/aptos-framework/doc/transaction_fee.md @@ -5,10 +5,10 @@ -- [Resource `AptosCoinCapabilities`](#0x1_transaction_fee_AptosCoinCapabilities) - [Resource `AptosFABurnCapabilities`](#0x1_transaction_fee_AptosFABurnCapabilities) - [Resource `AptosCoinMintCapability`](#0x1_transaction_fee_AptosCoinMintCapability) - [Struct `FeeStatement`](#0x1_transaction_fee_FeeStatement) +- [Resource `AptosCoinCapabilities`](#0x1_transaction_fee_AptosCoinCapabilities) - [Resource `CollectedFeesPerBlock`](#0x1_transaction_fee_CollectedFeesPerBlock) - [Constants](#@Constants_0) - [Function `burn_fee`](#0x1_transaction_fee_burn_fee) @@ -16,6 +16,7 @@ - [Function `store_aptos_coin_burn_cap`](#0x1_transaction_fee_store_aptos_coin_burn_cap) - [Function `store_aptos_coin_mint_cap`](#0x1_transaction_fee_store_aptos_coin_mint_cap) - [Function `emit_fee_statement`](#0x1_transaction_fee_emit_fee_statement) +- [Function `storage_fee_refund_octas`](#0x1_transaction_fee_storage_fee_refund_octas) - [Function `initialize_fee_collection_and_distribution`](#0x1_transaction_fee_initialize_fee_collection_and_distribution) - [Function `upgrade_burn_percentage`](#0x1_transaction_fee_upgrade_burn_percentage) - [Function `initialize_storage_refund`](#0x1_transaction_fee_initialize_storage_refund) @@ -44,35 +45,6 @@ - - -## Resource `AptosCoinCapabilities` - -Stores burn capability to burn the gas fees. - - -
#[deprecated]
-struct AptosCoinCapabilities has key
-
- - - -
-Fields - - -
-
-burn_cap: coin::BurnCapability<aptos_coin::AptosCoin> -
-
- -
-
- - -
- ## Resource `AptosFABurnCapabilities` @@ -198,6 +170,35 @@ This is meant to emitted as a module event. + + + + +## Resource `AptosCoinCapabilities` + +Stores burn capability to burn the gas fees. + + +
#[deprecated]
+struct AptosCoinCapabilities has key
+
+ + + +
+Fields + + +
+
+burn_cap: coin::BurnCapability<aptos_coin::AptosCoin> +
+
+ +
+
+ +
@@ -294,11 +295,10 @@ Burn transaction fees in epilogue. Implementation -
public(friend) fun burn_fee(
+
friend fun burn_fee(
     account: address, fee: u64
 ) {
-    let burn_ref =
-        &borrow_global<AptosFABurnCapabilities>(@aptos_framework).burn_ref;
+    let burn_ref = &AptosFABurnCapabilities[@aptos_framework].burn_ref;
     aptos_account::burn_from_fungible_store_for_gas(burn_ref, account, fee);
 }
 
@@ -323,10 +323,10 @@ Mint refund in epilogue. Implementation -
public(friend) fun mint_and_refund(
+
friend fun mint_and_refund(
     account: address, refund: u64
-) acquires AptosCoinMintCapability {
-    let mint_cap = &borrow_global<AptosCoinMintCapability>(@aptos_framework).mint_cap;
+) {
+    let mint_cap = &AptosCoinMintCapability[@aptos_framework].mint_cap;
     let refund_coin = coin::mint(refund, mint_cap);
     coin::deposit_for_gas_fee(account, refund_coin);
 }
@@ -352,7 +352,7 @@ Only called during genesis.
 Implementation
 
 
-
public(friend) fun store_aptos_coin_burn_cap(
+
friend fun store_aptos_coin_burn_cap(
     aptos_framework: &signer, burn_cap: BurnCapability<AptosCoin>
 ) {
     system_addresses::assert_aptos_framework(aptos_framework);
@@ -382,7 +382,7 @@ Only called during genesis.
 Implementation
 
 
-
public(friend) fun store_aptos_coin_mint_cap(
+
friend fun store_aptos_coin_mint_cap(
     aptos_framework: &signer, mint_cap: MintCapability<AptosCoin>
 ) {
     system_addresses::assert_aptos_framework(aptos_framework);
@@ -398,9 +398,10 @@ Only called during genesis.
 
 ## Function `emit_fee_statement`
 
+Called by epilogue only.
 
 
-
fun emit_fee_statement(fee_statement: transaction_fee::FeeStatement)
+
public(friend) fun emit_fee_statement(fee_statement: transaction_fee::FeeStatement)
 
@@ -409,13 +410,37 @@ Only called during genesis. Implementation -
fun emit_fee_statement(fee_statement: FeeStatement) {
+
friend fun emit_fee_statement(fee_statement: FeeStatement) {
     event::emit(fee_statement)
 }
 
+ + + + +## Function `storage_fee_refund_octas` + + + +
public(friend) fun storage_fee_refund_octas(self: &transaction_fee::FeeStatement): u64
+
+ + + +
+Implementation + + +
friend fun storage_fee_refund_octas(self: &FeeStatement): u64 {
+    self.storage_fee_refund_octas
+}
+
+ + +
@@ -762,7 +787,7 @@ Aborts if emit_fee_statement(fee_statement: transaction_fee::FeeStatement) +
public(friend) fun emit_fee_statement(fee_statement: transaction_fee::FeeStatement)
 
diff --git a/aptos-move/framework/aptos-framework/doc/transaction_validation.md b/aptos-move/framework/aptos-framework/doc/transaction_validation.md index 2bb21a9019c..c2c89a57497 100644 --- a/aptos-move/framework/aptos-framework/doc/transaction_validation.md +++ b/aptos-move/framework/aptos-framework/doc/transaction_validation.md @@ -8,6 +8,8 @@ - [Enum `ReplayProtector`](#0x1_transaction_validation_ReplayProtector) - [Resource `TransactionValidation`](#0x1_transaction_validation_TransactionValidation) - [Struct `GasPermission`](#0x1_transaction_validation_GasPermission) +- [Enum `PrologueArgs`](#0x1_transaction_validation_PrologueArgs) +- [Enum `EpilogueArgs`](#0x1_transaction_validation_EpilogueArgs) - [Constants](#@Constants_0) - [Function `grant_gas_permission`](#0x1_transaction_validation_grant_gas_permission) - [Function `revoke_gas_permission`](#0x1_transaction_validation_revoke_gas_permission) @@ -35,8 +37,8 @@ - [Function `unified_prologue_v2`](#0x1_transaction_validation_unified_prologue_v2) - [Function `unified_prologue_fee_payer_v2`](#0x1_transaction_validation_unified_prologue_fee_payer_v2) - [Function `unified_epilogue_v2`](#0x1_transaction_validation_unified_epilogue_v2) -- [Function `unified_prologue_v3`](#0x1_transaction_validation_unified_prologue_v3) -- [Function `unified_prologue_fee_payer_v3`](#0x1_transaction_validation_unified_prologue_fee_payer_v3) +- [Function `versioned_prologue`](#0x1_transaction_validation_versioned_prologue) +- [Function `versioned_epilogue`](#0x1_transaction_validation_versioned_epilogue) - [Specification](#@Specification_1) - [High-level Requirements](#high-level-req) - [Module-level Specification](#module-level-spec) @@ -63,8 +65,6 @@ - [Function `unified_prologue_v2`](#@Specification_1_unified_prologue_v2) - [Function `unified_prologue_fee_payer_v2`](#@Specification_1_unified_prologue_fee_payer_v2) - [Function `unified_epilogue_v2`](#@Specification_1_unified_epilogue_v2) - - [Function `unified_prologue_v3`](#@Specification_1_unified_prologue_v3) - - [Function `unified_prologue_fee_payer_v3`](#@Specification_1_unified_prologue_fee_payer_v3)
use 0x1::account;
@@ -231,6 +231,185 @@ correct chain-specific prologue and epilogue functions
 
 
 
+
+
+
+
+## Enum `PrologueArgs`
+
+Versioned enum-based prologue and epilogue
+Arguments for versioned_prologue. A new field becomes a new enum variant.
+
+- Old variants are kept for compatibility; their on-chain layout must remain stable.
+- Only the most recent variant needs real handling here — old variants were executed
+against the framework version that shipped them and are not reached from this code.
+
+
+
enum PrologueArgs
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+txn_sender_public_key: option::Option<vector<u8>> +
+
+ +
+
+fee_payer_public_key_hash: option::Option<vector<u8>> +
+
+ +
+
+replay_protector: transaction_validation::ReplayProtector +
+
+ +
+
+secondary_signer_addresses: vector<address> +
+
+ +
+
+secondary_signer_public_key_hashes: vector<option::Option<vector<u8>>> +
+
+ +
+
+txn_gas_price: u64 +
+
+ +
+
+txn_max_gas_units: u64 +
+
+ +
+
+txn_expiration_time: u64 +
+
+ +
+
+chain_id: u8 +
+
+ +
+
+is_simulation: bool +
+
+ +
+
+txn_limits_request: option::Option<transaction_limits::UserTxnLimitsRequest> +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Enum `EpilogueArgs` + +Arguments for versioned_epilogue. A new field becomes a new enum variant. + +- Old variants are kept for compatibility; their on-chain layout must remain stable. +- Only the most recent variant needs real handling here — old variants were executed +against the framework version that shipped them and are not reached from this code. + + +
enum EpilogueArgs
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+fee_statement: transaction_fee::FeeStatement +
+
+ +
+
+txn_gas_price: u64 +
+
+ +
+
+txn_max_gas_units: u64 +
+
+ +
+
+gas_units_remaining: u64 +
+
+ +
+
+is_simulation: bool +
+
+ +
+
+is_orderless_txn: bool +
+
+ +
+
+ + +
+ +
+
@@ -1507,12 +1686,11 @@ new set of functions to support txn payload v2 format and orderless transactions chain_id: u8, is_simulation: bool, ) { - unified_prologue_v3( - sender, - txn_sender_public_key, + prologue_common( + &sender, + &sender, replay_protector, - secondary_signer_addresses, - secondary_signer_public_key_hashes, + txn_sender_public_key, txn_gas_price, txn_max_gas_units, txn_expiration_time, @@ -1520,6 +1698,7 @@ new set of functions to support txn payload v2 format and orderless transactions is_simulation, option::none(), ); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); }
@@ -1557,14 +1736,11 @@ If there is no fee_payer, fee_payer = sender chain_id: u8, is_simulation: bool, ) { - unified_prologue_fee_payer_v3( - sender, - fee_payer, - txn_sender_public_key, - fee_payer_public_key_hash, + prologue_common( + &sender, + &fee_payer, replay_protector, - secondary_signer_addresses, - secondary_signer_public_key_hashes, + txn_sender_public_key, txn_gas_price, txn_max_gas_units, txn_expiration_time, @@ -1572,6 +1748,21 @@ If there is no fee_payer, fee_payer = sender is_simulation, option::none(), ); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); + if (!skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) { + let fee_payer_address = signer::address_of(&fee_payer); + if (fee_payer_public_key_hash.is_some()) { + assert!( + fee_payer_public_key_hash == option::some(account::get_authentication_key(fee_payer_address)), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY) + ); + } else { + assert!( + allow_missing_txn_authentication_key(fee_payer_address), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY) + ) + }; + } }
@@ -1656,14 +1847,13 @@ If there is no fee_payer, fee_payer = sender - + -## Function `unified_prologue_v3` +## Function `versioned_prologue` -V3 functions: extend V2 with voting-power-based high-txn-limits -
fun unified_prologue_v3(sender: signer, txn_sender_public_key: option::Option<vector<u8>>, replay_protector: transaction_validation::ReplayProtector, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<option::Option<vector<u8>>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool, txn_limits_request: option::Option<transaction_limits::UserTxnLimitsRequest>)
+
fun versioned_prologue(sender: signer, fee_payer: signer, args: transaction_validation::PrologueArgs)
 
@@ -1672,32 +1862,58 @@ V3 functions: extend V2 with voting-power-based high-txn-limits Implementation -
fun unified_prologue_v3(
-    sender: signer,
-    txn_sender_public_key: Option<vector<u8>>,
-    replay_protector: ReplayProtector,
-    secondary_signer_addresses: vector<address>,
-    secondary_signer_public_key_hashes: vector<Option<vector<u8>>>,
-    txn_gas_price: u64,
-    txn_max_gas_units: u64,
-    txn_expiration_time: u64,
-    chain_id: u8,
-    is_simulation: bool,
-    txn_limits_request: Option<UserTxnLimitsRequest>,
-) {
-    prologue_common(
-        &sender,
-        &sender,
-        replay_protector,
-        txn_sender_public_key,
-        txn_gas_price,
-        txn_max_gas_units,
-        txn_expiration_time,
-        chain_id,
-        is_simulation,
-        txn_limits_request,
-    );
-    multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation);
+
fun versioned_prologue(sender: signer, fee_payer: signer, args: PrologueArgs) {
+    match (args) {
+        V1 {
+            txn_sender_public_key,
+            fee_payer_public_key_hash,
+            replay_protector,
+            secondary_signer_addresses,
+            secondary_signer_public_key_hashes,
+            txn_gas_price,
+            txn_max_gas_units,
+            txn_expiration_time,
+            chain_id,
+            is_simulation,
+            txn_limits_request,
+        } => {
+            prologue_common(
+                &sender,
+                &fee_payer,
+                replay_protector,
+                txn_sender_public_key,
+                txn_gas_price,
+                txn_max_gas_units,
+                txn_expiration_time,
+                chain_id,
+                is_simulation,
+                txn_limits_request,
+            );
+            multi_agent_common_prologue(
+                secondary_signer_addresses,
+                secondary_signer_public_key_hashes,
+                is_simulation,
+            );
+
+            let fee_payer_address = signer::address_of(&fee_payer);
+            if (fee_payer_address != signer::address_of(&sender)) {
+                if (!skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) {
+                    if (fee_payer_public_key_hash.is_some()) {
+                        let fee_payer_public_key_hash = fee_payer_public_key_hash.destroy_some();
+                        assert!(
+                            fee_payer_public_key_hash == account::get_authentication_key(fee_payer_address),
+                            error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+                        );
+                    } else {
+                        assert!(
+                            allow_missing_txn_authentication_key(fee_payer_address),
+                            error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+                        );
+                    };
+                };
+            };
+        },
+    }
 }
 
@@ -1705,13 +1921,13 @@ V3 functions: extend V2 with voting-power-based high-txn-limits - + -## Function `unified_prologue_fee_payer_v3` +## Function `versioned_epilogue` -
fun unified_prologue_fee_payer_v3(sender: signer, fee_payer: signer, txn_sender_public_key: option::Option<vector<u8>>, fee_payer_public_key_hash: option::Option<vector<u8>>, replay_protector: transaction_validation::ReplayProtector, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<option::Option<vector<u8>>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool, txn_limits_request: option::Option<transaction_limits::UserTxnLimitsRequest>)
+
fun versioned_epilogue(account: signer, fee_payer: signer, args: transaction_validation::EpilogueArgs)
 
@@ -1720,47 +1936,28 @@ V3 functions: extend V2 with voting-power-based high-txn-limits Implementation -
fun unified_prologue_fee_payer_v3(
-    sender: signer,
-    fee_payer: signer,
-    txn_sender_public_key: Option<vector<u8>>,
-    fee_payer_public_key_hash: Option<vector<u8>>,
-    replay_protector: ReplayProtector,
-    secondary_signer_addresses: vector<address>,
-    secondary_signer_public_key_hashes: vector<Option<vector<u8>>>,
-    txn_gas_price: u64,
-    txn_max_gas_units: u64,
-    txn_expiration_time: u64,
-    chain_id: u8,
-    is_simulation: bool,
-    txn_limits_request: Option<UserTxnLimitsRequest>,
-) {
-    prologue_common(
-        &sender,
-        &fee_payer,
-        replay_protector,
-        txn_sender_public_key,
-        txn_gas_price,
-        txn_max_gas_units,
-        txn_expiration_time,
-        chain_id,
-        is_simulation,
-        txn_limits_request,
-    );
-    multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation);
-    if (!skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) {
-        let fee_payer_address = signer::address_of(&fee_payer);
-        if (fee_payer_public_key_hash.is_some()) {
-            assert!(
-                fee_payer_public_key_hash == option::some(account::get_authentication_key(fee_payer_address)),
-                error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY)
+
fun versioned_epilogue(account: signer, fee_payer: signer, args: EpilogueArgs) {
+    match (args) {
+        V1 {
+            fee_statement,
+            txn_gas_price,
+            txn_max_gas_units,
+            gas_units_remaining,
+            is_simulation,
+            is_orderless_txn,
+        } => {
+            unified_epilogue_v2(
+                account,
+                fee_payer,
+                fee_statement.storage_fee_refund_octas(),
+                txn_gas_price,
+                txn_max_gas_units,
+                gas_units_remaining,
+                is_simulation,
+                is_orderless_txn,
             );
-        } else {
-            assert!(
-                allow_missing_txn_authentication_key(fee_payer_address),
-                error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY)
-            )
-        };
+            transaction_fee::emit_fee_statement(fee_statement);
+        },
     }
 }
 
@@ -2291,38 +2488,6 @@ Skip transaction_fee::burn_fee verification. -
pragma verify = false;
-
- - - - - -### Function `unified_prologue_v3` - - -
fun unified_prologue_v3(sender: signer, txn_sender_public_key: option::Option<vector<u8>>, replay_protector: transaction_validation::ReplayProtector, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<option::Option<vector<u8>>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool, txn_limits_request: option::Option<transaction_limits::UserTxnLimitsRequest>)
-
- - - - -
pragma verify = false;
-
- - - - - -### Function `unified_prologue_fee_payer_v3` - - -
fun unified_prologue_fee_payer_v3(sender: signer, fee_payer: signer, txn_sender_public_key: option::Option<vector<u8>>, fee_payer_public_key_hash: option::Option<vector<u8>>, replay_protector: transaction_validation::ReplayProtector, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<option::Option<vector<u8>>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool, txn_limits_request: option::Option<transaction_limits::UserTxnLimitsRequest>)
-
- - - -
pragma verify = false;
 
diff --git a/aptos-move/framework/aptos-framework/sources/transaction_fee.move b/aptos-move/framework/aptos-framework/sources/transaction_fee.move index 4c5f580e4e8..4c3b98dc207 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_fee.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_fee.move @@ -24,12 +24,6 @@ module aptos_framework::transaction_fee { /// No longer supported. const ENO_LONGER_SUPPORTED: u64 = 4; - #[deprecated] - /// Stores burn capability to burn the gas fees. - struct AptosCoinCapabilities has key { - burn_cap: BurnCapability - } - /// Stores burn capability to burn the gas fees. struct AptosFABurnCapabilities has key { burn_ref: BurnRef @@ -74,25 +68,24 @@ module aptos_framework::transaction_fee { } /// Burn transaction fees in epilogue. - public(friend) fun burn_fee( + friend fun burn_fee( account: address, fee: u64 ) { - let burn_ref = - &borrow_global(@aptos_framework).burn_ref; + let burn_ref = &AptosFABurnCapabilities[@aptos_framework].burn_ref; aptos_account::burn_from_fungible_store_for_gas(burn_ref, account, fee); } /// Mint refund in epilogue. - public(friend) fun mint_and_refund( + friend fun mint_and_refund( account: address, refund: u64 - ) acquires AptosCoinMintCapability { - let mint_cap = &borrow_global(@aptos_framework).mint_cap; + ) { + let mint_cap = &AptosCoinMintCapability[@aptos_framework].mint_cap; let refund_coin = coin::mint(refund, mint_cap); coin::deposit_for_gas_fee(account, refund_coin); } /// Only called during genesis. - public(friend) fun store_aptos_coin_burn_cap( + friend fun store_aptos_coin_burn_cap( aptos_framework: &signer, burn_cap: BurnCapability ) { system_addresses::assert_aptos_framework(aptos_framework); @@ -102,20 +95,30 @@ module aptos_framework::transaction_fee { } /// Only called during genesis. - public(friend) fun store_aptos_coin_mint_cap( + friend fun store_aptos_coin_mint_cap( aptos_framework: &signer, mint_cap: MintCapability ) { system_addresses::assert_aptos_framework(aptos_framework); move_to(aptos_framework, AptosCoinMintCapability { mint_cap }) } - // Called by the VM after epilogue. - fun emit_fee_statement(fee_statement: FeeStatement) { + /// Called by epilogue only. + friend fun emit_fee_statement(fee_statement: FeeStatement) { event::emit(fee_statement) } + friend fun storage_fee_refund_octas(self: &FeeStatement): u64 { + self.storage_fee_refund_octas + } + // DEPRECATED section: + #[deprecated] + /// Stores burn capability to burn the gas fees. + struct AptosCoinCapabilities has key { + burn_cap: BurnCapability + } + #[deprecated] /// DEPRECATED: Stores information about the block proposer and the amount of fees /// collected when executing the block. diff --git a/aptos-move/framework/aptos-framework/sources/transaction_validation.move b/aptos-move/framework/aptos-framework/sources/transaction_validation.move index a4199731136..4fd96f4a012 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_validation.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_validation.move @@ -13,6 +13,7 @@ module aptos_framework::transaction_validation { use aptos_framework::system_addresses; use aptos_framework::timestamp; use aptos_framework::transaction_fee; + use aptos_framework::transaction_fee::FeeStatement; use aptos_framework::transaction_limits; use aptos_framework::transaction_limits::UserTxnLimitsRequest; use aptos_framework::nonce_validation; @@ -739,12 +740,11 @@ module aptos_framework::transaction_validation { chain_id: u8, is_simulation: bool, ) { - unified_prologue_v3( - sender, - txn_sender_public_key, + prologue_common( + &sender, + &sender, replay_protector, - secondary_signer_addresses, - secondary_signer_public_key_hashes, + txn_sender_public_key, txn_gas_price, txn_max_gas_units, txn_expiration_time, @@ -752,6 +752,7 @@ module aptos_framework::transaction_validation { is_simulation, option::none(), ); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); } /// If there is no fee_payer, fee_payer = sender @@ -769,14 +770,11 @@ module aptos_framework::transaction_validation { chain_id: u8, is_simulation: bool, ) { - unified_prologue_fee_payer_v3( - sender, - fee_payer, - txn_sender_public_key, - fee_payer_public_key_hash, + prologue_common( + &sender, + &fee_payer, replay_protector, - secondary_signer_addresses, - secondary_signer_public_key_hashes, + txn_sender_public_key, txn_gas_price, txn_max_gas_units, txn_expiration_time, @@ -784,6 +782,21 @@ module aptos_framework::transaction_validation { is_simulation, option::none(), ); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); + if (!skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) { + let fee_payer_address = signer::address_of(&fee_payer); + if (fee_payer_public_key_hash.is_some()) { + assert!( + fee_payer_public_key_hash == option::some(account::get_authentication_key(fee_payer_address)), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY) + ); + } else { + assert!( + allow_missing_txn_authentication_key(fee_payer_address), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY) + ) + }; + } } fun unified_epilogue_v2( @@ -844,79 +857,122 @@ module aptos_framework::transaction_validation { } /////////////////////////////////////////////////////////// - /// V3 functions: extend V2 with voting-power-based high-txn-limits + /// Versioned enum-based prologue and epilogue /////////////////////////////////////////////////////////// - fun unified_prologue_v3( - sender: signer, - txn_sender_public_key: Option>, - replay_protector: ReplayProtector, - secondary_signer_addresses: vector
, - secondary_signer_public_key_hashes: vector>>, - txn_gas_price: u64, - txn_max_gas_units: u64, - txn_expiration_time: u64, - chain_id: u8, - is_simulation: bool, - txn_limits_request: Option, - ) { - prologue_common( - &sender, - &sender, - replay_protector, - txn_sender_public_key, - txn_gas_price, - txn_max_gas_units, - txn_expiration_time, - chain_id, - is_simulation, - txn_limits_request, - ); - multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); + /// Arguments for `versioned_prologue`. A new field becomes a new enum variant. + /// + /// - Old variants are kept for compatibility; their on-chain layout must remain stable. + /// - Only the most recent variant needs real handling here — old variants were executed + /// against the framework version that shipped them and are not reached from this code. + enum PrologueArgs { + V1 { + txn_sender_public_key: Option>, + fee_payer_public_key_hash: Option>, + replay_protector: ReplayProtector, + secondary_signer_addresses: vector
, + secondary_signer_public_key_hashes: vector>>, + txn_gas_price: u64, + txn_max_gas_units: u64, + txn_expiration_time: u64, + chain_id: u8, + is_simulation: bool, + txn_limits_request: Option, + }, } - fun unified_prologue_fee_payer_v3( - sender: signer, - fee_payer: signer, - txn_sender_public_key: Option>, - fee_payer_public_key_hash: Option>, - replay_protector: ReplayProtector, - secondary_signer_addresses: vector
, - secondary_signer_public_key_hashes: vector>>, - txn_gas_price: u64, - txn_max_gas_units: u64, - txn_expiration_time: u64, - chain_id: u8, - is_simulation: bool, - txn_limits_request: Option, - ) { - prologue_common( - &sender, - &fee_payer, - replay_protector, - txn_sender_public_key, - txn_gas_price, - txn_max_gas_units, - txn_expiration_time, - chain_id, - is_simulation, - txn_limits_request, - ); - multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); - if (!skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) { - let fee_payer_address = signer::address_of(&fee_payer); - if (fee_payer_public_key_hash.is_some()) { - assert!( - fee_payer_public_key_hash == option::some(account::get_authentication_key(fee_payer_address)), - error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY) + fun versioned_prologue(sender: signer, fee_payer: signer, args: PrologueArgs) { + match (args) { + V1 { + txn_sender_public_key, + fee_payer_public_key_hash, + replay_protector, + secondary_signer_addresses, + secondary_signer_public_key_hashes, + txn_gas_price, + txn_max_gas_units, + txn_expiration_time, + chain_id, + is_simulation, + txn_limits_request, + } => { + prologue_common( + &sender, + &fee_payer, + replay_protector, + txn_sender_public_key, + txn_gas_price, + txn_max_gas_units, + txn_expiration_time, + chain_id, + is_simulation, + txn_limits_request, ); - } else { - assert!( - allow_missing_txn_authentication_key(fee_payer_address), - error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY) - ) - }; + multi_agent_common_prologue( + secondary_signer_addresses, + secondary_signer_public_key_hashes, + is_simulation, + ); + + let fee_payer_address = signer::address_of(&fee_payer); + if (fee_payer_address != signer::address_of(&sender)) { + if (!skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) { + if (fee_payer_public_key_hash.is_some()) { + let fee_payer_public_key_hash = fee_payer_public_key_hash.destroy_some(); + assert!( + fee_payer_public_key_hash == account::get_authentication_key(fee_payer_address), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ); + } else { + assert!( + allow_missing_txn_authentication_key(fee_payer_address), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ); + }; + }; + }; + }, } } + /// Arguments for `versioned_epilogue`. A new field becomes a new enum variant. + /// + /// - Old variants are kept for compatibility; their on-chain layout must remain stable. + /// - Only the most recent variant needs real handling here — old variants were executed + /// against the framework version that shipped them and are not reached from this code. + enum EpilogueArgs { + V1 { + fee_statement: FeeStatement, + txn_gas_price: u64, + txn_max_gas_units: u64, + gas_units_remaining: u64, + is_simulation: bool, + is_orderless_txn: bool, + }, + } + + fun versioned_epilogue(account: signer, fee_payer: signer, args: EpilogueArgs) { + match (args) { + V1 { + fee_statement, + txn_gas_price, + txn_max_gas_units, + gas_units_remaining, + is_simulation, + is_orderless_txn, + } => { + unified_epilogue_v2( + account, + fee_payer, + fee_statement.storage_fee_refund_octas(), + txn_gas_price, + txn_max_gas_units, + gas_units_remaining, + is_simulation, + is_orderless_txn, + ); + transaction_fee::emit_fee_statement(fee_statement); + }, + } + } } diff --git a/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move b/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move index 50d489c14e2..58e3d7dd0ce 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move @@ -443,38 +443,12 @@ spec aptos_framework::transaction_validation { pragma verify = false; } - spec unified_prologue_v3( - sender: signer, - txn_sender_public_key: Option>, - replay_protector: ReplayProtector, - secondary_signer_addresses: vector
, - secondary_signer_public_key_hashes: vector>>, - txn_gas_price: u64, - txn_max_gas_units: u64, - txn_expiration_time: u64, - chain_id: u8, - is_simulation: bool, - txn_limits_request: Option, - ) { + spec versioned_prologue(sender: signer, fee_payer: signer, args: PrologueArgs) { // TODO: temporary mockup pragma verify = false; } - spec unified_prologue_fee_payer_v3( - sender: signer, - fee_payer: signer, - txn_sender_public_key: Option>, - fee_payer_public_key_hash: Option>, - replay_protector: ReplayProtector, - secondary_signer_addresses: vector
, - secondary_signer_public_key_hashes: vector>>, - txn_gas_price: u64, - txn_max_gas_units: u64, - txn_expiration_time: u64, - chain_id: u8, - is_simulation: bool, - txn_limits_request: Option, - ) { + spec versioned_epilogue(account: signer, fee_payer: signer, args: EpilogueArgs) { // TODO: temporary mockup pragma verify = false; } diff --git a/aptos-move/framework/cached-packages/src/head.mrb b/aptos-move/framework/cached-packages/src/head.mrb index a38fa614ff3..fecf17aa3a8 100644 Binary files a/aptos-move/framework/cached-packages/src/head.mrb and b/aptos-move/framework/cached-packages/src/head.mrb differ diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index b4c5b0efe73..c2266918c97 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -173,6 +173,8 @@ pub enum FeatureFlag { MULTISIG_SCRIPT = 110, /// Enables higher transaction execution/IO limits backed by staking voting power. TRANSACTION_LIMITS = 111, + /// Whether versioned enum-based transaction validation is enabled. + VERSIONED_TRANSACTION_VALIDATION = 112, } impl FeatureFlag { @@ -284,6 +286,7 @@ impl FeatureFlag { Self::PUBLIC_STRUCT_ENUM_ARGS, Self::MULTISIG_SCRIPT, Self::TRANSACTION_LIMITS, + Self::VERSIONED_TRANSACTION_VALIDATION, ] } } @@ -494,7 +497,14 @@ impl Features { } pub fn is_transaction_limits_enabled(&self) -> bool { + // Transaction limits are enforced only through the versioned prologue path + // (via PrologueArgs::V1.txn_limits_request), so both flags must be on. self.is_enabled(FeatureFlag::TRANSACTION_LIMITS) + && self.is_enabled(FeatureFlag::VERSIONED_TRANSACTION_VALIDATION) + } + + pub fn is_versioned_transaction_validation_enabled(&self) -> bool { + self.is_enabled(FeatureFlag::VERSIONED_TRANSACTION_VALIDATION) } pub fn get_max_identifier_size(&self) -> u64 {