diff --git a/api/doc/spec.json b/api/doc/spec.json index 6e210370533dc..bf944fc8e7000 100644 --- a/api/doc/spec.json +++ b/api/doc/spec.json @@ -13968,6 +13968,9 @@ }, { "$ref": "#/components/schemas/AccountSignature_MultiKeySignature" + }, + { + "$ref": "#/components/schemas/AccountSignature_NoAccountSignature" } ], "discriminator": { @@ -13976,7 +13979,8 @@ "ed25519_signature": "#/components/schemas/AccountSignature_Ed25519Signature", "multi_ed25519_signature": "#/components/schemas/AccountSignature_MultiEd25519Signature", "single_key_signature": "#/components/schemas/AccountSignature_SingleKeySignature", - "multi_key_signature": "#/components/schemas/AccountSignature_MultiKeySignature" + "multi_key_signature": "#/components/schemas/AccountSignature_MultiKeySignature", + "no_account_signature": "#/components/schemas/AccountSignature_NoAccountSignature" } } }, @@ -14046,6 +14050,28 @@ } ] }, + "AccountSignature_NoAccountSignature": { + "allOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "no_account_signature" + ], + "example": "no_account_signature" + } + } + }, + { + "$ref": "#/components/schemas/NoAccountSignature" + } + ] + }, "AccountSignature_SingleKeySignature": { "allOf": [ { @@ -15582,6 +15608,10 @@ } ] }, + "NoAccountSignature": { + "type": "object", + "description": "A placeholder to represent the absence of account signature" + }, "PendingTransaction": { "type": "object", "description": "A transaction waiting in mempool", diff --git a/api/doc/spec.yaml b/api/doc/spec.yaml index 2988cbcf24212..a515d33940b19 100644 --- a/api/doc/spec.yaml +++ b/api/doc/spec.yaml @@ -10453,6 +10453,7 @@ components: - $ref: '#/components/schemas/AccountSignature_MultiEd25519Signature' - $ref: '#/components/schemas/AccountSignature_SingleKeySignature' - $ref: '#/components/schemas/AccountSignature_MultiKeySignature' + - $ref: '#/components/schemas/AccountSignature_NoAccountSignature' discriminator: propertyName: type mapping: @@ -10460,6 +10461,7 @@ components: multi_ed25519_signature: '#/components/schemas/AccountSignature_MultiEd25519Signature' single_key_signature: '#/components/schemas/AccountSignature_SingleKeySignature' multi_key_signature: '#/components/schemas/AccountSignature_MultiKeySignature' + no_account_signature: '#/components/schemas/AccountSignature_NoAccountSignature' AccountSignature_Ed25519Signature: allOf: - type: object @@ -10496,6 +10498,18 @@ components: - multi_key_signature example: multi_key_signature - $ref: '#/components/schemas/MultiKeySignature' + AccountSignature_NoAccountSignature: + allOf: + - type: object + required: + - type + properties: + type: + type: string + enum: + - no_account_signature + example: no_account_signature + - $ref: '#/components/schemas/NoAccountSignature' AccountSignature_SingleKeySignature: allOf: - type: object @@ -11702,6 +11716,9 @@ components: - entry_function_payload example: entry_function_payload - $ref: '#/components/schemas/EntryFunctionPayload' + NoAccountSignature: + type: object + description: A placeholder to represent the absence of account signature PendingTransaction: type: object description: A transaction waiting in mempool diff --git a/api/src/tests/multisig_transactions_test.rs b/api/src/tests/multisig_transactions_test.rs index eb6e132ecb277..9cd59d00e41ee 100644 --- a/api/src/tests/multisig_transactions_test.rs +++ b/api/src/tests/multisig_transactions_test.rs @@ -403,6 +403,54 @@ async fn test_multisig_transaction_with_mismatching_payload() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_multisig_transaction_simulation() { + let mut context = new_test_context(current_function_name!()); + let owner_account_1 = &mut context.create_account().await; + let owner_account_2 = &mut context.create_account().await; + let owner_account_3 = &mut context.create_account().await; + let multisig_account = context + .create_multisig_account( + owner_account_1, + vec![owner_account_2.address(), owner_account_3.address()], + 1, /* 1-of-3 */ + 1000, /* initial balance */ + ) + .await; + + let multisig_payload = construct_multisig_txn_transfer_payload(owner_account_1.address(), 1000); + context + .create_multisig_transaction(owner_account_1, multisig_account, multisig_payload.clone()) + .await; + + // Simulate the multisig tx + let simulation_resp = context + .simulate_multisig_transaction( + owner_account_1, + multisig_account, + "0x1::aptos_account::transfer", + &[], + &[&owner_account_1.address().to_hex_literal(), "1000"], + 200, + ) + .await; + // Validate that the simulation did successfully execute a transfer of 1000 coins from the + // multisig account. + let simulation_resp = &simulation_resp.as_array().unwrap()[0]; + assert!(simulation_resp["success"].as_bool().unwrap()); + let withdraw_event = &simulation_resp["events"].as_array().unwrap()[0]; + assert_eq!( + withdraw_event["type"].as_str().unwrap(), + "0x1::coin::CoinWithdraw" + ); + let withdraw_from_account = + AccountAddress::from_hex_literal(withdraw_event["data"]["account"].as_str().unwrap()) + .unwrap(); + let withdrawn_amount = withdraw_event["data"]["amount"].as_str().unwrap(); + assert_eq!(withdraw_from_account, multisig_account); + assert_eq!(withdrawn_amount, "1000"); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_multisig_transaction_simulation_2_of_3() { let mut context = new_test_context(current_function_name!()); let owner_account_1 = &mut context.create_account().await; let owner_account_2 = &mut context.create_account().await; @@ -416,8 +464,16 @@ async fn test_multisig_transaction_simulation() { ) .await; - // Should be able to simulate the multisig tx without having enough approvals or the transaction - // created. + let multisig_payload = construct_multisig_txn_transfer_payload(owner_account_1.address(), 1000); + context + .create_multisig_transaction(owner_account_1, multisig_account, multisig_payload.clone()) + .await; + + context + .approve_multisig_transaction(owner_account_2, multisig_account, 1) + .await; + + // Simulate the multisig transaction let simulation_resp = context .simulate_multisig_transaction( owner_account_1, @@ -443,6 +499,27 @@ async fn test_multisig_transaction_simulation() { let withdrawn_amount = withdraw_event["data"]["amount"].as_str().unwrap(); assert_eq!(withdraw_from_account, multisig_account); assert_eq!(withdrawn_amount, "1000"); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_multisig_transaction_simulation_fail() { + let mut context = new_test_context(current_function_name!()); + let owner_account_1 = &mut context.create_account().await; + let owner_account_2 = &mut context.create_account().await; + let owner_account_3 = &mut context.create_account().await; + let multisig_account = context + .create_multisig_account( + owner_account_1, + vec![owner_account_2.address(), owner_account_3.address()], + 1, /* 1-of-3 */ + 1000, /* initial balance */ + ) + .await; + + let multisig_payload = construct_multisig_txn_transfer_payload(owner_account_1.address(), 2000); + context + .create_multisig_transaction(owner_account_1, multisig_account, multisig_payload.clone()) + .await; // Simulating transferring more than what the multisig account has should fail. let simulation_resp = context @@ -456,7 +533,56 @@ async fn test_multisig_transaction_simulation() { ) .await; let simulation_resp = &simulation_resp.as_array().unwrap()[0]; + let transaction_failed = &simulation_resp["events"] + .as_array() + .unwrap() + .iter() + .any(|event| { + event["type"] + .as_str() + .unwrap() + .contains("TransactionExecutionFailed") + }); + assert!(transaction_failed); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_multisig_transaction_simulation_fail_2_of_3_insufficient_approvals() { + let mut context = new_test_context(current_function_name!()); + let owner_account_1 = &mut context.create_account().await; + let owner_account_2 = &mut context.create_account().await; + let owner_account_3 = &mut context.create_account().await; + let multisig_account = context + .create_multisig_account( + owner_account_1, + vec![owner_account_2.address(), owner_account_3.address()], + 2, /* 2-of-3 */ + 1000, /* initial balance */ + ) + .await; + + let multisig_payload = construct_multisig_txn_transfer_payload(owner_account_1.address(), 2000); + context + .create_multisig_transaction(owner_account_1, multisig_account, multisig_payload.clone()) + .await; + + // Simulating without sufficient approvals has should fail. + let simulation_resp = context + .simulate_multisig_transaction( + owner_account_1, + multisig_account, + "0x1::aptos_account::transfer", + &[], + &[&owner_account_1.address().to_hex_literal(), "1000"], + 200, + ) + .await; + let simulation_resp = &simulation_resp.as_array().unwrap()[0]; assert!(!simulation_resp["success"].as_bool().unwrap()); + assert!(simulation_resp["vm_status"] + .as_str() + .unwrap() + .contains("MULTISIG_TRANSACTION_INSUFFICIENT_APPROVALS")); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -473,6 +599,11 @@ async fn test_simulate_multisig_transaction_should_charge_gas_against_sender() { .await; assert_eq!(10, context.get_apt_balance(multisig_account).await); + let multisig_payload = construct_multisig_txn_transfer_payload(owner_account.address(), 10); + context + .create_multisig_transaction(owner_account, multisig_account, multisig_payload.clone()) + .await; + // This simulation should succeed because gas should be paid out of the sender account (owner), // not the multisig account itself. let simulation_resp = context diff --git a/api/src/tests/simulation_test.rs b/api/src/tests/simulation_test.rs index 3e60ecb9af937..7f2623e1f028e 100644 --- a/api/src/tests/simulation_test.rs +++ b/api/src/tests/simulation_test.rs @@ -2,10 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use super::new_test_context; -use aptos_api_test_context::{current_function_name, TestContext}; +use aptos_api_test_context::{current_function_name, pretty, TestContext}; use aptos_crypto::ed25519::Ed25519Signature; -use aptos_types::transaction::{ - authenticator::TransactionAuthenticator, EntryFunction, TransactionPayload, +use aptos_types::{ + account_address::AccountAddress, + transaction::{ + authenticator::{AccountAuthenticator, TransactionAuthenticator}, + EntryFunction, RawTransaction, SignedTransaction, TransactionPayload, + }, }; use move_core_types::{ident_str, language_storage::ModuleId}; use serde_json::json; @@ -153,3 +157,272 @@ async fn test_simulate_txn_with_aggregator() { unreachable!("Simulation uses Ed25519 authenticator."); } } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bcs_simulate_simple() { + let transfer_amount: u64 = SMALL_TRANSFER_AMOUNT; + + let mut context = new_test_context(current_function_name!()); + let alice = &mut context.gen_account(); + let bob = &mut context.gen_account(); + let txn = context.mint_user_account(alice).await; + context.commit_block(&vec![txn]).await; + + let txn = context.account_transfer_to(alice, bob.address(), transfer_amount); + let body = bcs::to_bytes(&txn).unwrap(); + + // expected to fail due to using a valid signature. + let _resp = context + .expect_status_code(400) + .post_bcs_txn("/transactions/simulate", body) + .await; + + if let TransactionAuthenticator::Ed25519 { + public_key, + signature: _, + } = txn.authenticator_ref() + { + let txn = SignedTransaction::new_signed_transaction( + txn.clone().into_raw_transaction(), + TransactionAuthenticator::Ed25519 { + public_key: public_key.clone(), + signature: Ed25519Signature::dummy_signature(), + }, + ); + + let body = bcs::to_bytes(&txn).unwrap(); + + // expected to succeed + let resp = context + .expect_status_code(200) + .post_bcs_txn("/transactions/simulate", body) + .await; + + assert!(resp[0]["success"].as_bool().unwrap(), "{}", pretty(&resp)); + } else { + unreachable!("Simulation uses Ed25519 authenticator."); + } +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bcs_simulate_without_auth_key_check() { + let transfer_amount: u64 = SMALL_TRANSFER_AMOUNT; + + let mut context = new_test_context(current_function_name!()); + let alice = &mut context.gen_account(); + let bob = &mut context.gen_account(); + let txn = context.mint_user_account(alice).await; + context.commit_block(&vec![txn]).await; + + // Construct a signed transaction. + let txn = context.account_transfer_to(alice, bob.address(), transfer_amount); + // Replace the authenticator with a NoAccountAuthenticator in the transaction. + let txn = SignedTransaction::new_signed_transaction( + txn.clone().into_raw_transaction(), + TransactionAuthenticator::SingleSender { + sender: AccountAuthenticator::NoAccountAuthenticator, + }, + ); + + let body = bcs::to_bytes(&txn).unwrap(); + + // expected to succeed + let resp = context + .expect_status_code(200) + .post_bcs_txn("/transactions/simulate", body) + .await; + assert!(resp[0]["success"].as_bool().unwrap(), "{}", pretty(&resp)); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bcs_simulate_fee_payer_transaction_without_gas_fee_check() { + let mut context = new_test_context(current_function_name!()); + let alice = &mut context.gen_account(); + let bob = &mut context.gen_account(); + let txn = context.mint_user_account(alice).await; + context.commit_block(&vec![txn]).await; + + let transfer_amount: u64 = SMALL_TRANSFER_AMOUNT; + let txn = context.account_transfer_to(alice, bob.address(), transfer_amount); + let raw_txn = RawTransaction::new( + txn.sender(), + txn.sequence_number(), + txn.payload().clone(), + txn.max_gas_amount(), + 100, + txn.expiration_timestamp_secs(), + txn.chain_id(), + ); + let txn = SignedTransaction::new_signed_transaction( + raw_txn.clone(), + TransactionAuthenticator::FeePayer { + sender: AccountAuthenticator::NoAccountAuthenticator, + secondary_signer_addresses: vec![], + secondary_signers: vec![], + fee_payer_address: AccountAddress::ONE, + fee_payer_signer: AccountAuthenticator::NoAccountAuthenticator, + }, + ); + let body = bcs::to_bytes(&txn).unwrap(); + let resp = context + .expect_status_code(200) + .post_bcs_txn("/transactions/simulate", body) + .await; + assert!(!resp[0]["success"].as_bool().unwrap(), "{}", pretty(&resp)); + assert!( + resp[0]["vm_status"] + .as_str() + .unwrap() + .contains("INSUFFICIENT_BALANCE_FOR_TRANSACTION_FEE"), + "{}", + pretty(&resp) + ); + + let txn = SignedTransaction::new_signed_transaction( + raw_txn.clone(), + TransactionAuthenticator::FeePayer { + sender: AccountAuthenticator::NoAccountAuthenticator, + secondary_signer_addresses: vec![], + secondary_signers: vec![], + fee_payer_address: AccountAddress::ZERO, + fee_payer_signer: AccountAuthenticator::NoAccountAuthenticator, + }, + ); + let body = bcs::to_bytes(&txn).unwrap(); + let resp = context + .expect_status_code(200) + .post_bcs_txn("/transactions/simulate", body) + .await; + assert!(resp[0]["success"].as_bool().unwrap(), "{}", pretty(&resp)); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bcs_simulate_automated_account_creation() { + let mut context = new_test_context(current_function_name!()); + let alice = &mut context.gen_account(); + let bob = &mut context.gen_account(); + + let transfer_amount: u64 = 0; + let txn = context.account_transfer_to(alice, bob.address(), transfer_amount); + let raw_txn = RawTransaction::new( + txn.sender(), + txn.sequence_number(), + txn.payload().clone(), + txn.max_gas_amount(), + 100, + txn.expiration_timestamp_secs(), + txn.chain_id(), + ); + // Replace the authenticator with a NoAccountAuthenticator in the transaction. + let txn = SignedTransaction::new_signed_transaction( + raw_txn.clone(), + TransactionAuthenticator::SingleSender { + sender: AccountAuthenticator::NoAccountAuthenticator, + }, + ); + + let body = bcs::to_bytes(&txn).unwrap(); + + let resp = context + .expect_status_code(200) + .post_bcs_txn("/transactions/simulate", body) + .await; + assert!(!resp[0]["success"].as_bool().unwrap(), "{}", pretty(&resp)); + assert!( + resp[0]["vm_status"] + .as_str() + .unwrap() + .contains("SENDING_ACCOUNT_DOES_NOT_EXIST"), + "{}", + pretty(&resp) + ); + + let txn = + SignedTransaction::new_signed_transaction(raw_txn, TransactionAuthenticator::FeePayer { + sender: AccountAuthenticator::NoAccountAuthenticator, + secondary_signer_addresses: vec![], + secondary_signers: vec![], + fee_payer_address: AccountAddress::ZERO, + fee_payer_signer: AccountAuthenticator::NoAccountAuthenticator, + }); + let body = bcs::to_bytes(&txn).unwrap(); + let resp = context + .expect_status_code(200) + .post_bcs_txn("/transactions/simulate", body) + .await; + assert!(resp[0]["success"].as_bool().unwrap(), "{}", pretty(&resp)); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bcs_execute_simple_no_authenticator_fail() { + let transfer_amount: u64 = SMALL_TRANSFER_AMOUNT; + + let mut context = new_test_context(current_function_name!()); + let alice = &mut context.gen_account(); + let bob = &mut context.gen_account(); + let txn = context.mint_user_account(alice).await; + context.commit_block(&vec![txn]).await; + + // Construct a signed transaction. + let txn = context.account_transfer_to(alice, bob.address(), transfer_amount); + // Replace the authenticator with a NoAccountAuthenticator in the transaction. + let txn = SignedTransaction::new_signed_transaction( + txn.clone().into_raw_transaction(), + TransactionAuthenticator::SingleSender { + sender: AccountAuthenticator::NoAccountAuthenticator, + }, + ); + + let body = bcs::to_bytes(&txn).unwrap(); + + // expected to fail due to the use of NoAccountAuthenticator in an actual execution + let resp = context + .expect_status_code(400) + .post_bcs_txn("/transactions", body) + .await; + assert!(resp["message"] + .as_str() + .unwrap() + .contains("INVALID_SIGNATURE")); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bcs_execute_fee_payer_transaction_no_authenticator_fail() { + let mut context = new_test_context(current_function_name!()); + let alice = &mut context.gen_account(); + let bob = &mut context.gen_account(); + let txn = context.mint_user_account(alice).await; + context.commit_block(&vec![txn]).await; + + let transfer_amount: u64 = SMALL_TRANSFER_AMOUNT; + let txn = context.account_transfer_to(alice, bob.address(), transfer_amount); + let raw_txn = RawTransaction::new( + txn.sender(), + txn.sequence_number(), + txn.payload().clone(), + txn.max_gas_amount(), + 100, + txn.expiration_timestamp_secs(), + txn.chain_id(), + ); + + let txn = SignedTransaction::new_signed_transaction( + raw_txn.clone(), + TransactionAuthenticator::FeePayer { + sender: AccountAuthenticator::NoAccountAuthenticator, + secondary_signer_addresses: vec![], + secondary_signers: vec![], + fee_payer_address: AccountAddress::ZERO, + fee_payer_signer: AccountAuthenticator::NoAccountAuthenticator, + }, + ); + let body = bcs::to_bytes(&txn).unwrap(); + let resp = context + .expect_status_code(400) + .post_bcs_txn("/transactions", body) + .await; + assert!(resp["message"] + .as_str() + .unwrap() + .contains("INVALID_SIGNATURE")); +} diff --git a/api/types/src/lib.rs b/api/types/src/lib.rs index ccb1da50d0a8c..e7ab206cde38a 100644 --- a/api/types/src/lib.rs +++ b/api/types/src/lib.rs @@ -49,9 +49,9 @@ pub use transaction::{ DirectWriteSet, Ed25519Signature, EncodeSubmissionRequest, EntryFunctionPayload, Event, FeePayerSignature, GasEstimation, GasEstimationBcs, GenesisPayload, GenesisTransaction, MultiAgentSignature, MultiEd25519Signature, MultiKeySignature, MultisigPayload, - MultisigTransactionPayload, PendingTransaction, PublicKey, ScriptPayload, ScriptWriteSet, - Signature, SingleKeySignature, SubmitTransactionRequest, Transaction, TransactionData, - TransactionId, TransactionInfo, TransactionOnChainData, TransactionPayload, + MultisigTransactionPayload, NoAccountSignature, PendingTransaction, PublicKey, ScriptPayload, + ScriptWriteSet, Signature, SingleKeySignature, SubmitTransactionRequest, Transaction, + TransactionData, TransactionId, TransactionInfo, TransactionOnChainData, TransactionPayload, TransactionSignature, TransactionSigningMessage, TransactionsBatchSingleSubmissionFailure, TransactionsBatchSubmissionResult, UserCreateSigningMessageRequest, UserTransaction, UserTransactionRequest, VersionedEvent, WriteModule, WriteResource, WriteSet, WriteSetChange, diff --git a/api/types/src/transaction.rs b/api/types/src/transaction.rs index 40ff6a6e1f253..4af7dae0b843e 100755 --- a/api/types/src/transaction.rs +++ b/api/types/src/transaction.rs @@ -1834,6 +1834,33 @@ impl TryFrom for AccountAuthenticator { } } +/// A placeholder to represent the absence of account signature +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Object)] +pub struct NoAccountSignature; + +impl VerifyInput for NoAccountSignature { + fn verify(&self) -> anyhow::Result<()> { + Ok(()) + } +} + +impl TryFrom for TransactionAuthenticator { + type Error = anyhow::Error; + + fn try_from(signature: NoAccountSignature) -> Result { + let account_auth = signature.try_into()?; + Ok(TransactionAuthenticator::single_sender(account_auth)) + } +} + +impl TryFrom for AccountAuthenticator { + type Error = anyhow::Error; + + fn try_from(_value: NoAccountSignature) -> Result { + Ok(AccountAuthenticator::NoAccountAuthenticator) + } +} + /// Account signature scheme /// /// The account signature scheme allows you to have two types of accounts: @@ -1849,6 +1876,7 @@ pub enum AccountSignature { MultiEd25519Signature(MultiEd25519Signature), SingleKeySignature(SingleKeySignature), MultiKeySignature(MultiKeySignature), + NoAccountSignature(NoAccountSignature), } impl VerifyInput for AccountSignature { @@ -1858,6 +1886,7 @@ impl VerifyInput for AccountSignature { AccountSignature::MultiEd25519Signature(inner) => inner.verify(), AccountSignature::SingleKeySignature(inner) => inner.verify(), AccountSignature::MultiKeySignature(inner) => inner.verify(), + AccountSignature::NoAccountSignature(inner) => inner.verify(), } } } @@ -1871,6 +1900,7 @@ impl TryFrom for AccountAuthenticator { AccountSignature::MultiEd25519Signature(s) => s.try_into()?, AccountSignature::SingleKeySignature(s) => s.try_into()?, AccountSignature::MultiKeySignature(s) => s.try_into()?, + AccountSignature::NoAccountSignature(s) => s.try_into()?, }) } } @@ -2031,6 +2061,7 @@ impl From<&AccountAuthenticator> for AccountSignature { signatures_required: public_keys.signatures_required(), }) }, + NoAccountAuthenticator => AccountSignature::NoAccountSignature(NoAccountSignature), } } } 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 1813da08cd5f6..2c921f8a16dae 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -128,6 +128,7 @@ pub enum FeatureFlag { EnableResourceAccessControl, RejectUnstableBytecodeForScript, FederatedKeyless, + TransactionSimulationEnhancement, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -338,6 +339,9 @@ impl From for AptosFeatureFlag { AptosFeatureFlag::REJECT_UNSTABLE_BYTECODE_FOR_SCRIPT }, FeatureFlag::FederatedKeyless => AptosFeatureFlag::FEDERATED_KEYLESS, + FeatureFlag::TransactionSimulationEnhancement => { + AptosFeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT + }, } } } @@ -477,6 +481,9 @@ impl From for FeatureFlag { FeatureFlag::RejectUnstableBytecodeForScript }, AptosFeatureFlag::FEDERATED_KEYLESS => FeatureFlag::FederatedKeyless, + AptosFeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT => { + FeatureFlag::TransactionSimulationEnhancement + }, } } } diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 00050681bff9c..0fd1831d88d6b 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -637,6 +637,7 @@ impl AptosVM { txn_data, log_context, traversal_context, + self.is_simulation, ) })?; @@ -682,6 +683,7 @@ impl AptosVM { txn_data, log_context, traversal_context, + self.is_simulation, ) })?; let output = epilogue_session.finish( @@ -1233,7 +1235,13 @@ impl AptosVM { new_published_modules_loaded: &mut bool, change_set_configs: &ChangeSetConfigs, ) -> Result<(VMStatus, VMOutput), VMStatus> { - if self.is_simulation { + // Once `simulation_enhancement` is enabled, we use `execute_multisig_transaction` for simulation, + // deprecating `simulate_multisig_transaction`. + if self.is_simulation + && !self + .features() + .is_transaction_simulation_enhancement_enabled() + { self.simulate_multisig_transaction( resolver, session, @@ -2354,8 +2362,10 @@ impl AptosVM { transaction_validation::run_script_prologue( session, txn_data, + self.features(), log_context, traversal_context, + self.is_simulation, ) }, TransactionPayload::Multisig(multisig_payload) => { @@ -2365,13 +2375,18 @@ impl AptosVM { transaction_validation::run_script_prologue( session, txn_data, + self.features(), log_context, traversal_context, + self.is_simulation, )?; - // Skip validation if this is part of tx simulation. - // This allows simulating multisig txs without having to first create the multisig - // tx. - if !self.is_simulation { + // Once "simulation_enhancement" is enabled, the simulation path also validates the + // multisig transaction by running the multisig prologue. + if !self.is_simulation + || self + .features() + .is_transaction_simulation_enhancement_enabled() + { transaction_validation::run_multisig_prologue( session, txn_data, diff --git a/aptos-move/aptos-vm/src/transaction_metadata.rs b/aptos-move/aptos-vm/src/transaction_metadata.rs index e73d53acc19c0..65358aee33e82 100644 --- a/aptos-move/aptos-vm/src/transaction_metadata.rs +++ b/aptos-move/aptos-vm/src/transaction_metadata.rs @@ -37,20 +37,29 @@ impl TransactionMetadata { pub fn new(txn: &SignedTransaction) -> Self { Self { sender: txn.sender(), - authentication_key: txn.authenticator().sender().authentication_key().to_vec(), + authentication_key: txn + .authenticator() + .sender() + .authentication_key() + .map_or_else(Vec::new, |auth_key| auth_key.to_vec()), secondary_signers: txn.authenticator().secondary_signer_addresses(), secondary_authentication_keys: txn .authenticator() .secondary_signers() .iter() - .map(|account_auth| account_auth.authentication_key().to_vec()) + .map(|account_auth| { + account_auth + .authentication_key() + .map_or_else(Vec::new, |auth_key| auth_key.to_vec()) + }) .collect(), sequence_number: txn.sequence_number(), fee_payer: txn.authenticator_ref().fee_payer_address(), - fee_payer_authentication_key: txn - .authenticator() - .fee_payer_signer() - .map(|signer| signer.authentication_key().to_vec()), + fee_payer_authentication_key: txn.authenticator().fee_payer_signer().map(|signer| { + signer + .authentication_key() + .map_or_else(Vec::new, |auth_key| auth_key.to_vec()) + }), max_gas_amount: txn.max_gas_amount().into(), gas_unit_price: txn.gas_unit_price().into(), transaction_size: (txn.raw_txn_bytes_len() as u64).into(), diff --git a/aptos-move/aptos-vm/src/transaction_validation.rs b/aptos-move/aptos-vm/src/transaction_validation.rs index c90f7bd8c43e0..229833b71f01b 100644 --- a/aptos-move/aptos-vm/src/transaction_validation.rs +++ b/aptos-move/aptos-vm/src/transaction_validation.rs @@ -40,6 +40,14 @@ pub static APTOS_TRANSACTION_VALIDATION: Lazy = multi_agent_prologue_name: Identifier::new("multi_agent_script_prologue").unwrap(), user_epilogue_name: Identifier::new("epilogue").unwrap(), user_epilogue_gas_payer_name: Identifier::new("epilogue_gas_payer").unwrap(), + fee_payer_prologue_extended_name: Identifier::new("fee_payer_script_prologue_extended") + .unwrap(), + script_prologue_extended_name: Identifier::new("script_prologue_extended").unwrap(), + multi_agent_prologue_extended_name: Identifier::new("multi_agent_script_prologue_extended") + .unwrap(), + user_epilogue_extended_name: Identifier::new("epilogue_extended").unwrap(), + user_epilogue_gas_payer_extended_name: Identifier::new("epilogue_gas_payer_extended") + .unwrap(), }); /// On-chain functions used to validate transactions @@ -52,6 +60,11 @@ pub struct TransactionValidation { pub multi_agent_prologue_name: Identifier, pub user_epilogue_name: Identifier, pub user_epilogue_gas_payer_name: Identifier, + pub fee_payer_prologue_extended_name: Identifier, + pub script_prologue_extended_name: Identifier, + pub multi_agent_prologue_extended_name: Identifier, + pub user_epilogue_extended_name: Identifier, + pub user_epilogue_gas_payer_extended_name: Identifier, } impl TransactionValidation { @@ -72,8 +85,10 @@ impl TransactionValidation { pub(crate) fn run_script_prologue( session: &mut SessionExt, txn_data: &TransactionMetadata, + features: &Features, log_context: &AdapterLogSchema, traversal_context: &mut TraversalContext, + is_simulation: bool, ) -> Result<(), VMStatus> { let txn_sequence_number = txn_data.sequence_number(); let txn_authentication_key = txn_data.authentication_key().to_vec(); @@ -91,48 +106,107 @@ pub(crate) fn run_script_prologue( txn_data.fee_payer(), txn_data.fee_payer_authentication_key.as_ref(), ) { - let args = vec![ - MoveValue::Signer(txn_data.sender), - MoveValue::U64(txn_sequence_number), - MoveValue::vector_u8(txn_authentication_key), - MoveValue::vector_address(txn_data.secondary_signers()), - MoveValue::Vector(secondary_auth_keys), - MoveValue::Address(fee_payer), - MoveValue::vector_u8(fee_payer_auth_key.to_vec()), - MoveValue::U64(txn_gas_price.into()), - MoveValue::U64(txn_max_gas_units.into()), - MoveValue::U64(txn_expiration_timestamp_secs), - MoveValue::U8(chain_id.id()), - ]; - (&APTOS_TRANSACTION_VALIDATION.fee_payer_prologue_name, args) + if features.is_transaction_simulation_enhancement_enabled() { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(txn_sequence_number), + MoveValue::vector_u8(txn_authentication_key), + MoveValue::vector_address(txn_data.secondary_signers()), + MoveValue::Vector(secondary_auth_keys), + MoveValue::Address(fee_payer), + MoveValue::vector_u8(fee_payer_auth_key.to_vec()), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(txn_expiration_timestamp_secs), + MoveValue::U8(chain_id.id()), + MoveValue::Bool(is_simulation), + ]; + ( + &APTOS_TRANSACTION_VALIDATION.fee_payer_prologue_extended_name, + args, + ) + } else { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(txn_sequence_number), + MoveValue::vector_u8(txn_authentication_key), + MoveValue::vector_address(txn_data.secondary_signers()), + MoveValue::Vector(secondary_auth_keys), + MoveValue::Address(fee_payer), + MoveValue::vector_u8(fee_payer_auth_key.to_vec()), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(txn_expiration_timestamp_secs), + MoveValue::U8(chain_id.id()), + ]; + (&APTOS_TRANSACTION_VALIDATION.fee_payer_prologue_name, args) + } } else if txn_data.is_multi_agent() { - let args = vec![ - MoveValue::Signer(txn_data.sender), - MoveValue::U64(txn_sequence_number), - MoveValue::vector_u8(txn_authentication_key), - MoveValue::vector_address(txn_data.secondary_signers()), - MoveValue::Vector(secondary_auth_keys), - MoveValue::U64(txn_gas_price.into()), - MoveValue::U64(txn_max_gas_units.into()), - MoveValue::U64(txn_expiration_timestamp_secs), - MoveValue::U8(chain_id.id()), - ]; - ( - &APTOS_TRANSACTION_VALIDATION.multi_agent_prologue_name, - args, - ) + if features.is_transaction_simulation_enhancement_enabled() { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(txn_sequence_number), + MoveValue::vector_u8(txn_authentication_key), + MoveValue::vector_address(txn_data.secondary_signers()), + MoveValue::Vector(secondary_auth_keys), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(txn_expiration_timestamp_secs), + MoveValue::U8(chain_id.id()), + MoveValue::Bool(is_simulation), + ]; + ( + &APTOS_TRANSACTION_VALIDATION.multi_agent_prologue_extended_name, + args, + ) + } else { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(txn_sequence_number), + MoveValue::vector_u8(txn_authentication_key), + MoveValue::vector_address(txn_data.secondary_signers()), + MoveValue::Vector(secondary_auth_keys), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(txn_expiration_timestamp_secs), + MoveValue::U8(chain_id.id()), + ]; + ( + &APTOS_TRANSACTION_VALIDATION.multi_agent_prologue_name, + args, + ) + } } else { - let args = vec![ - MoveValue::Signer(txn_data.sender), - MoveValue::U64(txn_sequence_number), - MoveValue::vector_u8(txn_authentication_key), - MoveValue::U64(txn_gas_price.into()), - MoveValue::U64(txn_max_gas_units.into()), - MoveValue::U64(txn_expiration_timestamp_secs), - MoveValue::U8(chain_id.id()), - MoveValue::vector_u8(txn_data.script_hash.clone()), - ]; - (&APTOS_TRANSACTION_VALIDATION.script_prologue_name, args) + #[allow(clippy::collapsible_else_if)] + if features.is_transaction_simulation_enhancement_enabled() { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(txn_sequence_number), + MoveValue::vector_u8(txn_authentication_key), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(txn_expiration_timestamp_secs), + MoveValue::U8(chain_id.id()), + MoveValue::vector_u8(txn_data.script_hash.clone()), + MoveValue::Bool(is_simulation), + ]; + ( + &APTOS_TRANSACTION_VALIDATION.script_prologue_extended_name, + args, + ) + } else { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(txn_sequence_number), + MoveValue::vector_u8(txn_authentication_key), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(txn_expiration_timestamp_secs), + MoveValue::U8(chain_id.id()), + MoveValue::vector_u8(txn_data.script_hash.clone()), + ]; + (&APTOS_TRANSACTION_VALIDATION.script_prologue_name, args) + } }; session .execute_function_bypass_visibility( @@ -198,6 +272,7 @@ fn run_epilogue( txn_data: &TransactionMetadata, features: &Features, traversal_context: &mut TraversalContext, + is_simulation: bool, ) -> VMResult<()> { let txn_gas_price = txn_data.gas_unit_price(); let txn_max_gas_units = txn_data.max_gas_amount(); @@ -206,18 +281,34 @@ fn run_epilogue( // accepted it, in which case the gas payer feature is enabled. if let Some(fee_payer) = txn_data.fee_payer() { let (func_name, args) = { - let args = vec![ - MoveValue::Signer(txn_data.sender), - MoveValue::Address(fee_payer), - MoveValue::U64(fee_statement.storage_fee_refund()), - MoveValue::U64(txn_gas_price.into()), - MoveValue::U64(txn_max_gas_units.into()), - MoveValue::U64(gas_remaining.into()), - ]; - ( - &APTOS_TRANSACTION_VALIDATION.user_epilogue_gas_payer_name, - args, - ) + if features.is_transaction_simulation_enhancement_enabled() { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::Address(fee_payer), + MoveValue::U64(fee_statement.storage_fee_refund()), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(gas_remaining.into()), + MoveValue::Bool(is_simulation), + ]; + ( + &APTOS_TRANSACTION_VALIDATION.user_epilogue_gas_payer_extended_name, + args, + ) + } else { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::Address(fee_payer), + MoveValue::U64(fee_statement.storage_fee_refund()), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(gas_remaining.into()), + ]; + ( + &APTOS_TRANSACTION_VALIDATION.user_epilogue_gas_payer_name, + args, + ) + } }; session.execute_function_bypass_visibility( &APTOS_TRANSACTION_VALIDATION.module_id(), @@ -230,14 +321,29 @@ fn run_epilogue( } else { // Regular tx, run the normal epilogue let (func_name, args) = { - let args = vec![ - MoveValue::Signer(txn_data.sender), - MoveValue::U64(fee_statement.storage_fee_refund()), - MoveValue::U64(txn_gas_price.into()), - MoveValue::U64(txn_max_gas_units.into()), - MoveValue::U64(gas_remaining.into()), - ]; - (&APTOS_TRANSACTION_VALIDATION.user_epilogue_name, args) + if features.is_transaction_simulation_enhancement_enabled() { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(fee_statement.storage_fee_refund()), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(gas_remaining.into()), + MoveValue::Bool(is_simulation), + ]; + ( + &APTOS_TRANSACTION_VALIDATION.user_epilogue_extended_name, + args, + ) + } else { + let args = vec![ + MoveValue::Signer(txn_data.sender), + MoveValue::U64(fee_statement.storage_fee_refund()), + MoveValue::U64(txn_gas_price.into()), + MoveValue::U64(txn_max_gas_units.into()), + MoveValue::U64(gas_remaining.into()), + ]; + (&APTOS_TRANSACTION_VALIDATION.user_epilogue_name, args) + } }; session.execute_function_bypass_visibility( &APTOS_TRANSACTION_VALIDATION.module_id(), @@ -288,6 +394,7 @@ pub(crate) fn run_success_epilogue( txn_data: &TransactionMetadata, log_context: &AdapterLogSchema, traversal_context: &mut TraversalContext, + is_simulation: bool, ) -> Result<(), VMStatus> { fail_point!("move_adapter::run_success_epilogue", |_| { Err(VMStatus::error( @@ -303,6 +410,7 @@ pub(crate) fn run_success_epilogue( txn_data, features, traversal_context, + is_simulation, ) .or_else(|err| convert_epilogue_error(err, log_context)) } @@ -317,6 +425,7 @@ pub(crate) fn run_failure_epilogue( txn_data: &TransactionMetadata, log_context: &AdapterLogSchema, traversal_context: &mut TraversalContext, + is_simulation: bool, ) -> Result<(), VMStatus> { run_epilogue( session, @@ -325,6 +434,7 @@ pub(crate) fn run_failure_epilogue( txn_data, features, traversal_context, + is_simulation, ) .or_else(|e| { expect_only_successful_execution( diff --git a/aptos-move/framework/aptos-framework/doc/transaction_validation.md b/aptos-move/framework/aptos-framework/doc/transaction_validation.md index 8f1a41d972c96..b21495159a7c3 100644 --- a/aptos-move/framework/aptos-framework/doc/transaction_validation.md +++ b/aptos-move/framework/aptos-framework/doc/transaction_validation.md @@ -10,22 +10,34 @@ - [Function `initialize`](#0x1_transaction_validation_initialize) - [Function `prologue_common`](#0x1_transaction_validation_prologue_common) - [Function `script_prologue`](#0x1_transaction_validation_script_prologue) +- [Function `script_prologue_extended`](#0x1_transaction_validation_script_prologue_extended) - [Function `multi_agent_script_prologue`](#0x1_transaction_validation_multi_agent_script_prologue) +- [Function `multi_agent_script_prologue_extended`](#0x1_transaction_validation_multi_agent_script_prologue_extended) - [Function `multi_agent_common_prologue`](#0x1_transaction_validation_multi_agent_common_prologue) - [Function `fee_payer_script_prologue`](#0x1_transaction_validation_fee_payer_script_prologue) +- [Function `fee_payer_script_prologue_extended`](#0x1_transaction_validation_fee_payer_script_prologue_extended) - [Function `epilogue`](#0x1_transaction_validation_epilogue) +- [Function `epilogue_extended`](#0x1_transaction_validation_epilogue_extended) - [Function `epilogue_gas_payer`](#0x1_transaction_validation_epilogue_gas_payer) +- [Function `epilogue_gas_payer_extended`](#0x1_transaction_validation_epilogue_gas_payer_extended) +- [Function `skip_auth_key_check`](#0x1_transaction_validation_skip_auth_key_check) +- [Function `skip_gas_payment`](#0x1_transaction_validation_skip_gas_payment) - [Specification](#@Specification_1) - [High-level Requirements](#high-level-req) - [Module-level Specification](#module-level-spec) - [Function `initialize`](#@Specification_1_initialize) - [Function `prologue_common`](#@Specification_1_prologue_common) - [Function `script_prologue`](#@Specification_1_script_prologue) + - [Function `script_prologue_extended`](#@Specification_1_script_prologue_extended) - [Function `multi_agent_script_prologue`](#@Specification_1_multi_agent_script_prologue) + - [Function `multi_agent_script_prologue_extended`](#@Specification_1_multi_agent_script_prologue_extended) - [Function `multi_agent_common_prologue`](#@Specification_1_multi_agent_common_prologue) - [Function `fee_payer_script_prologue`](#@Specification_1_fee_payer_script_prologue) + - [Function `fee_payer_script_prologue_extended`](#@Specification_1_fee_payer_script_prologue_extended) - [Function `epilogue`](#@Specification_1_epilogue) + - [Function `epilogue_extended`](#@Specification_1_epilogue_extended) - [Function `epilogue_gas_payer`](#@Specification_1_epilogue_gas_payer) + - [Function `epilogue_gas_payer_extended`](#@Specification_1_epilogue_gas_payer_extended)
use 0x1::account;
@@ -40,6 +52,7 @@
 use 0x1::system_addresses;
 use 0x1::timestamp;
 use 0x1::transaction_fee;
+use 0x1::vector;
 
@@ -269,7 +282,7 @@ Only called during genesis to initialize system resources for this module. -
fun prologue_common(sender: signer, gas_payer: address, txn_sequence_number: u64, txn_authentication_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
fun prologue_common(sender: signer, gas_payer: address, txn_sequence_number: u64, txn_authentication_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool)
 
@@ -287,6 +300,7 @@ Only called during genesis to initialize system resources for this module. txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, + is_simulation: bool, ) { assert!( timestamp::now_seconds() < txn_expiration_time, @@ -303,10 +317,13 @@ Only called during genesis to initialize system resources for this module. || txn_sequence_number > 0 ) { assert!(account::exists_at(transaction_sender), error::invalid_argument(PROLOGUE_EACCOUNT_DOES_NOT_EXIST)); - assert!( - txn_authentication_key == account::get_authentication_key(transaction_sender), - error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), - ); + if (!features::transaction_simulation_enhancement_enabled() || + !skip_auth_key_check(is_simulation, &txn_authentication_key)) { + assert!( + txn_authentication_key == account::get_authentication_key(transaction_sender), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ) + }; let account_sequence_number = account::get_sequence_number(transaction_sender); assert!( @@ -331,24 +348,29 @@ Only called during genesis to initialize system resources for this module. error::invalid_argument(PROLOGUE_ESEQUENCE_NUMBER_TOO_NEW) ); - assert!( - txn_authentication_key == bcs::to_bytes(&transaction_sender), - error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), - ); + if (!features::transaction_simulation_enhancement_enabled() || + !skip_auth_key_check(is_simulation, &txn_authentication_key)) { + assert!( + txn_authentication_key == bcs::to_bytes(&transaction_sender), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ); + } }; let max_transaction_fee = txn_gas_price * txn_max_gas_units; - if (features::operations_default_to_fa_apt_store_enabled()) { - assert!( - aptos_account::is_fungible_balance_at_least(gas_payer, max_transaction_fee), - error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) - ); - } else { - assert!( - coin::is_balance_at_least<AptosCoin>(gas_payer, max_transaction_fee), - error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) - ); + if (!features::transaction_simulation_enhancement_enabled() || !skip_gas_payment(is_simulation, gas_payer)) { + if (features::operations_default_to_fa_apt_store_enabled()) { + assert!( + aptos_account::is_fungible_balance_at_least(gas_payer, max_transaction_fee), + error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) + ); + } else { + assert!( + coin::is_balance_at_least<AptosCoin>(gas_payer, max_transaction_fee), + error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) + ); + } } }
@@ -383,6 +405,7 @@ Only called during genesis to initialize system resources for this module. _script_hash: vector<u8>, ) { let gas_payer = signer::address_of(&sender); + // prologue_common with is_simulation set to false behaves identically to the original script_prologue function. prologue_common( sender, gas_payer, @@ -391,7 +414,53 @@ Only called during genesis to initialize system resources for this module. txn_gas_price, txn_max_gas_units, txn_expiration_time, - chain_id + chain_id, + false, + ) +} + + + + + + + + +## Function `script_prologue_extended` + + + +
fun script_prologue_extended(sender: signer, txn_sequence_number: u64, txn_public_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, _script_hash: vector<u8>, is_simulation: bool)
+
+ + + +
+Implementation + + +
fun script_prologue_extended(
+    sender: signer,
+    txn_sequence_number: u64,
+    txn_public_key: vector<u8>,
+    txn_gas_price: u64,
+    txn_max_gas_units: u64,
+    txn_expiration_time: u64,
+    chain_id: u8,
+    _script_hash: vector<u8>,
+    is_simulation: bool,
+) {
+    let gas_payer = signer::address_of(&sender);
+    prologue_common(
+        sender,
+        gas_payer,
+        txn_sequence_number,
+        txn_public_key,
+        txn_gas_price,
+        txn_max_gas_units,
+        txn_expiration_time,
+        chain_id,
+        is_simulation,
     )
 }
 
@@ -427,6 +496,8 @@ Only called during genesis to initialize system resources for this module. chain_id: u8, ) { let sender_addr = signer::address_of(&sender); + // prologue_common and multi_agent_common_prologue with is_simulation set to false behaves identically to the + // original multi_agent_script_prologue function. prologue_common( sender, sender_addr, @@ -436,8 +507,56 @@ Only called during genesis to initialize system resources for this module. txn_max_gas_units, txn_expiration_time, chain_id, + false, ); - multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, false); +} + + + + +
+ + + +## Function `multi_agent_script_prologue_extended` + + + +
fun multi_agent_script_prologue_extended(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool)
+
+ + + +
+Implementation + + +
fun multi_agent_script_prologue_extended(
+    sender: signer,
+    txn_sequence_number: u64,
+    txn_sender_public_key: vector<u8>,
+    secondary_signer_addresses: vector<address>,
+    secondary_signer_public_key_hashes: vector<vector<u8>>,
+    txn_gas_price: u64,
+    txn_max_gas_units: u64,
+    txn_expiration_time: u64,
+    chain_id: u8,
+    is_simulation: bool,
+) {
+    let sender_addr = signer::address_of(&sender);
+    prologue_common(
+        sender,
+        sender_addr,
+        txn_sequence_number,
+        txn_sender_public_key,
+        txn_gas_price,
+        txn_max_gas_units,
+        txn_expiration_time,
+        chain_id,
+        is_simulation,
+    );
+    multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation);
 }
 
@@ -451,7 +570,7 @@ Only called during genesis to initialize system resources for this module. -
fun multi_agent_common_prologue(secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>)
+
fun multi_agent_common_prologue(secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, is_simulation: bool)
 
@@ -463,6 +582,7 @@ Only called during genesis to initialize system resources for this module.
fun multi_agent_common_prologue(
     secondary_signer_addresses: vector<address>,
     secondary_signer_public_key_hashes: vector<vector<u8>>,
+    is_simulation: bool,
 ) {
     let num_secondary_signers = vector::length(&secondary_signer_addresses);
     assert!(
@@ -475,9 +595,10 @@ Only called during genesis to initialize system resources for this module.
         spec {
             invariant i <= num_secondary_signers;
             invariant forall j in 0..i:
-                account::exists_at(secondary_signer_addresses[j])
-                    && secondary_signer_public_key_hashes[j]
-                    == account::get_authentication_key(secondary_signer_addresses[j]);
+                account::exists_at(secondary_signer_addresses[j]);
+            invariant forall j in 0..i:
+                secondary_signer_public_key_hashes[j] == account::get_authentication_key(secondary_signer_addresses[j]) ||
+                    (features::spec_simulation_enhancement_enabled() && is_simulation && vector::is_empty(secondary_signer_public_key_hashes[j]));
         };
         (i < num_secondary_signers)
     }) {
@@ -485,10 +606,13 @@ Only called during genesis to initialize system resources for this module.
         assert!(account::exists_at(secondary_address), error::invalid_argument(PROLOGUE_EACCOUNT_DOES_NOT_EXIST));
 
         let signer_public_key_hash = *vector::borrow(&secondary_signer_public_key_hashes, i);
-        assert!(
-            signer_public_key_hash == account::get_authentication_key(secondary_address),
-            error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
-        );
+        if (!features::transaction_simulation_enhancement_enabled() ||
+                !skip_auth_key_check(is_simulation, &signer_public_key_hash)) {
+            assert!(
+                signer_public_key_hash == account::get_authentication_key(secondary_address),
+                error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+            )
+        };
         i = i + 1;
     }
 }
@@ -527,6 +651,8 @@ Only called during genesis to initialize system resources for this module.
     chain_id: u8,
 ) {
     assert!(features::fee_payer_enabled(), error::invalid_state(PROLOGUE_EFEE_PAYER_NOT_ENABLED));
+    // prologue_common and multi_agent_common_prologue with is_simulation set to false behaves identically to the
+    // original fee_payer_script_prologue function.
     prologue_common(
         sender,
         fee_payer_address,
@@ -536,8 +662,9 @@ Only called during genesis to initialize system resources for this module.
         txn_max_gas_units,
         txn_expiration_time,
         chain_id,
+        false,
     );
-    multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes);
+    multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, false);
     assert!(
         fee_payer_public_key_hash == account::get_authentication_key(fee_payer_address),
         error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
@@ -547,6 +674,62 @@ Only called during genesis to initialize system resources for this module.
 
 
 
+
+ + + +## Function `fee_payer_script_prologue_extended` + + + +
fun fee_payer_script_prologue_extended(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, fee_payer_address: address, fee_payer_public_key_hash: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool)
+
+ + + +
+Implementation + + +
fun fee_payer_script_prologue_extended(
+    sender: signer,
+    txn_sequence_number: u64,
+    txn_sender_public_key: vector<u8>,
+    secondary_signer_addresses: vector<address>,
+    secondary_signer_public_key_hashes: vector<vector<u8>>,
+    fee_payer_address: address,
+    fee_payer_public_key_hash: vector<u8>,
+    txn_gas_price: u64,
+    txn_max_gas_units: u64,
+    txn_expiration_time: u64,
+    chain_id: u8,
+    is_simulation: bool,
+) {
+    assert!(features::fee_payer_enabled(), error::invalid_state(PROLOGUE_EFEE_PAYER_NOT_ENABLED));
+    prologue_common(
+        sender,
+        fee_payer_address,
+        txn_sequence_number,
+        txn_sender_public_key,
+        txn_gas_price,
+        txn_max_gas_units,
+        txn_expiration_time,
+        chain_id,
+        is_simulation,
+    );
+    multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation);
+    if (!features::transaction_simulation_enhancement_enabled() ||
+        !skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) {
+        assert!(
+            fee_payer_public_key_hash == account::get_authentication_key(fee_payer_address),
+            error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY),
+        )
+    }
+}
+
+ + +
@@ -571,7 +754,7 @@ Called by the Adapter storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, - gas_units_remaining: u64 + gas_units_remaining: u64, ) { let addr = signer::address_of(&account); epilogue_gas_payer(account, addr, storage_fee_refunded, txn_gas_price, txn_max_gas_units, gas_units_remaining); @@ -580,6 +763,38 @@ Called by the Adapter + + + + +## Function `epilogue_extended` + + + +
fun epilogue_extended(account: signer, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64, is_simulation: bool)
+
+ + + +
+Implementation + + +
fun epilogue_extended(
+    account: signer,
+    storage_fee_refunded: u64,
+    txn_gas_price: u64,
+    txn_max_gas_units: u64,
+    gas_units_remaining: u64,
+    is_simulation: bool,
+) {
+    let addr = signer::address_of(&account);
+    epilogue_gas_payer_extended(account, addr, storage_fee_refunded, txn_gas_price, txn_max_gas_units, gas_units_remaining, is_simulation);
+}
+
+ + +
@@ -605,7 +820,49 @@ Called by the Adapter storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, - gas_units_remaining: u64 + gas_units_remaining: u64, +) { + // epilogue_gas_payer_extended with is_simulation set to false behaves identically to the original + // epilogue_gas_payer function. + epilogue_gas_payer_extended( + account, + gas_payer, + storage_fee_refunded, + txn_gas_price, + txn_max_gas_units, + gas_units_remaining, + false, + ); +} +
+ + + + + + + +## Function `epilogue_gas_payer_extended` + + + +
fun epilogue_gas_payer_extended(account: signer, gas_payer: address, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64, is_simulation: bool)
+
+ + + +
+Implementation + + +
fun epilogue_gas_payer_extended(
+    account: signer,
+    gas_payer: address,
+    storage_fee_refunded: u64,
+    txn_gas_price: u64,
+    txn_max_gas_units: u64,
+    gas_units_remaining: u64,
+    is_simulation: bool,
 ) {
     assert!(txn_max_gas_units >= gas_units_remaining, error::invalid_argument(EOUT_OF_GAS));
     let gas_used = txn_max_gas_units - gas_units_remaining;
@@ -618,39 +875,41 @@ Called by the Adapter
 
     // it's important to maintain the error code consistent with vm
     // to do failed transaction cleanup.
-    if (features::operations_default_to_fa_apt_store_enabled()) {
-        assert!(
-            aptos_account::is_fungible_balance_at_least(gas_payer, transaction_fee_amount),
-            error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT),
-        );
-    } else {
-        assert!(
-            coin::is_balance_at_least<AptosCoin>(gas_payer, transaction_fee_amount),
-            error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT),
-        );
-    };
-
-    let amount_to_burn = if (features::collect_and_distribute_gas_fees()) {
-        // TODO(gas): We might want to distinguish the refundable part of the charge and burn it or track
-        // it separately, so that we don't increase the total supply by refunding.
+    if (!features::transaction_simulation_enhancement_enabled() || !skip_gas_payment(is_simulation, gas_payer)) {
+        if (features::operations_default_to_fa_apt_store_enabled()) {
+            assert!(
+                aptos_account::is_fungible_balance_at_least(gas_payer, transaction_fee_amount),
+                error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT),
+            );
+        } else {
+            assert!(
+                coin::is_balance_at_least<AptosCoin>(gas_payer, transaction_fee_amount),
+                error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT),
+            );
+        };
 
-        // If transaction fees are redistributed to validators, collect them here for
-        // later redistribution.
-        transaction_fee::collect_fee(gas_payer, transaction_fee_amount);
-        0
-    } else {
-        // Otherwise, just burn the fee.
-        // TODO: this branch should be removed completely when transaction fee collection
-        // is tested and is fully proven to work well.
-        transaction_fee_amount
-    };
+        let amount_to_burn = if (features::collect_and_distribute_gas_fees()) {
+            // TODO(gas): We might want to distinguish the refundable part of the charge and burn it or track
+            // it separately, so that we don't increase the total supply by refunding.
+
+            // If transaction fees are redistributed to validators, collect them here for
+            // later redistribution.
+            transaction_fee::collect_fee(gas_payer, transaction_fee_amount);
+            0
+        } else {
+            // Otherwise, just burn the fee.
+            // TODO: this branch should be removed completely when transaction fee collection
+            // is tested and is fully proven to work well.
+            transaction_fee_amount
+        };
 
-    if (amount_to_burn > storage_fee_refunded) {
-        let burn_amount = amount_to_burn - storage_fee_refunded;
-        transaction_fee::burn_fee(gas_payer, burn_amount);
-    } else if (amount_to_burn < storage_fee_refunded) {
-        let mint_amount = storage_fee_refunded - amount_to_burn;
-        transaction_fee::mint_and_refund(gas_payer, mint_amount)
+        if (amount_to_burn > storage_fee_refunded) {
+            let burn_amount = amount_to_burn - storage_fee_refunded;
+            transaction_fee::burn_fee(gas_payer, burn_amount);
+        } else if (amount_to_burn < storage_fee_refunded) {
+            let mint_amount = storage_fee_refunded - amount_to_burn;
+            transaction_fee::mint_and_refund(gas_payer, mint_amount)
+        };
     };
 
     // Increment sequence number
@@ -661,6 +920,54 @@ Called by the Adapter
 
 
 
+
+ + + +## Function `skip_auth_key_check` + + + +
fun skip_auth_key_check(is_simulation: bool, auth_key: &vector<u8>): bool
+
+ + + +
+Implementation + + +
inline fun skip_auth_key_check(is_simulation: bool, auth_key: &vector<u8>): bool {
+    is_simulation && vector::is_empty(auth_key)
+}
+
+ + + +
+ + + +## Function `skip_gas_payment` + + + +
fun skip_gas_payment(is_simulation: bool, gas_payer: address): bool
+
+ + + +
+Implementation + + +
inline fun skip_gas_payment(is_simulation: bool, gas_payer: address): bool {
+    is_simulation && gas_payer == @0x0
+}
+
+ + +
@@ -792,7 +1099,7 @@ Give some constraints that may abort according to the conditions. ### Function `prologue_common` -
fun prologue_common(sender: signer, gas_payer: address, txn_sequence_number: u64, txn_authentication_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8)
+
fun prologue_common(sender: signer, gas_payer: address, txn_sequence_number: u64, txn_authentication_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool)
 
@@ -816,10 +1123,6 @@ Give some constraints that may abort according to the conditions.
pragma verify = false;
-include PrologueCommonAbortsIf {
-    gas_payer: signer::address_of(sender),
-    txn_authentication_key: txn_public_key
-};
 
@@ -831,22 +1134,57 @@ Give some constraints that may abort according to the conditions.
schema MultiAgentPrologueCommonAbortsIf {
     secondary_signer_addresses: vector<address>;
     secondary_signer_public_key_hashes: vector<vector<u8>>;
+    is_simulation: bool;
     let num_secondary_signers = len(secondary_signer_addresses);
     aborts_if len(secondary_signer_public_key_hashes) != num_secondary_signers;
     // This enforces high-level requirement 2:
     aborts_if exists i in 0..num_secondary_signers:
-        !account::exists_at(secondary_signer_addresses[i])
-            || secondary_signer_public_key_hashes[i] !=
-            account::get_authentication_key(secondary_signer_addresses[i]);
+        !account::exists_at(secondary_signer_addresses[i]);
+    aborts_if exists i in 0..num_secondary_signers:
+        !can_skip(features::spec_simulation_enhancement_enabled(), is_simulation, secondary_signer_public_key_hashes[i]) &&
+            secondary_signer_public_key_hashes[i] !=
+                account::get_authentication_key(secondary_signer_addresses[i]);
     ensures forall i in 0..num_secondary_signers:
-        account::exists_at(secondary_signer_addresses[i])
-            && secondary_signer_public_key_hashes[i] ==
-            account::get_authentication_key(secondary_signer_addresses[i]);
+        account::exists_at(secondary_signer_addresses[i]);
+    ensures forall i in 0..num_secondary_signers:
+        secondary_signer_public_key_hashes[i] == account::get_authentication_key(secondary_signer_addresses[i])
+            || can_skip(features::spec_simulation_enhancement_enabled(), is_simulation, secondary_signer_public_key_hashes[i]);
 }
 
+ + + + +
fun can_skip(feature_flag: bool, is_simulation: bool, auth_key: vector<u8>): bool {
+   features::spec_simulation_enhancement_enabled() && is_simulation && vector::is_empty(auth_key)
+}
+
+ + + + + +### Function `script_prologue_extended` + + +
fun script_prologue_extended(sender: signer, txn_sequence_number: u64, txn_public_key: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, _script_hash: vector<u8>, is_simulation: bool)
+
+ + + + +
pragma verify = false;
+include PrologueCommonAbortsIf {
+    gas_payer: signer::address_of(sender),
+    txn_authentication_key: txn_public_key
+};
+
+ + + ### Function `multi_agent_script_prologue` @@ -856,6 +1194,22 @@ Give some constraints that may abort according to the conditions.
+ + +
pragma verify = false;
+
+ + + + + +### Function `multi_agent_script_prologue_extended` + + +
fun multi_agent_script_prologue_extended(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool)
+
+ + Aborts if length of public key hashed vector not equal the number of singers. @@ -871,6 +1225,7 @@ not equal the number of singers. include MultiAgentPrologueCommonAbortsIf { secondary_signer_addresses, secondary_signer_public_key_hashes, + is_simulation, };
@@ -881,7 +1236,7 @@ not equal the number of singers. ### Function `multi_agent_common_prologue` -
fun multi_agent_common_prologue(secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>)
+
fun multi_agent_common_prologue(secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, is_simulation: bool)
 
@@ -890,6 +1245,7 @@ not equal the number of singers.
include MultiAgentPrologueCommonAbortsIf {
     secondary_signer_addresses,
     secondary_signer_public_key_hashes,
+    is_simulation,
 };
 
@@ -906,6 +1262,22 @@ not equal the number of singers. +
pragma verify = false;
+
+ + + + + +### Function `fee_payer_script_prologue_extended` + + +
fun fee_payer_script_prologue_extended(sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector<u8>, secondary_signer_addresses: vector<address>, secondary_signer_public_key_hashes: vector<vector<u8>>, fee_payer_address: address, fee_payer_public_key_hash: vector<u8>, txn_gas_price: u64, txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, is_simulation: bool)
+
+ + + +
pragma verify_duration_estimate = 120;
 aborts_if !features::spec_is_enabled(features::FEE_PAYER_ENABLED);
 let gas_payer = fee_payer_address;
@@ -917,6 +1289,7 @@ not equal the number of singers.
 include MultiAgentPrologueCommonAbortsIf {
     secondary_signer_addresses,
     secondary_signer_public_key_hashes,
+    is_simulation,
 };
 aborts_if !account::exists_at(gas_payer);
 aborts_if !(fee_payer_public_key_hash == account::get_authentication_key(gas_payer));
@@ -934,6 +1307,22 @@ not equal the number of singers.
 
+ + +
pragma verify = false;
+
+ + + + + +### Function `epilogue_extended` + + +
fun epilogue_extended(account: signer, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64, is_simulation: bool)
+
+ + Abort according to the conditions. AptosCoinCapabilities and CoinInfo should exists. Skip transaction_fee::burn_fee verification. @@ -954,13 +1343,9 @@ Skip transaction_fee::burn_fee verification.
-Abort according to the conditions. -AptosCoinCapabilities and CoinInfo should exist. -Skip transaction_fee::burn_fee verification.
pragma verify = false;
-include EpilogueGasPayerAbortsIf;
 
@@ -1030,4 +1415,24 @@ Skip transaction_fee::burn_fee verification.
+ + + +### Function `epilogue_gas_payer_extended` + + +
fun epilogue_gas_payer_extended(account: signer, gas_payer: address, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, gas_units_remaining: u64, is_simulation: bool)
+
+ + +Abort according to the conditions. +AptosCoinCapabilities and CoinInfo should exist. +Skip transaction_fee::burn_fee verification. + + +
pragma verify = false;
+include EpilogueGasPayerAbortsIf;
+
+ + [move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/sources/transaction_validation.move b/aptos-move/framework/aptos-framework/sources/transaction_validation.move index 5949c93bf18e0..c3496215972e6 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_validation.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_validation.move @@ -79,6 +79,7 @@ module aptos_framework::transaction_validation { txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, + is_simulation: bool, ) { assert!( timestamp::now_seconds() < txn_expiration_time, @@ -95,10 +96,13 @@ module aptos_framework::transaction_validation { || txn_sequence_number > 0 ) { assert!(account::exists_at(transaction_sender), error::invalid_argument(PROLOGUE_EACCOUNT_DOES_NOT_EXIST)); - assert!( - txn_authentication_key == account::get_authentication_key(transaction_sender), - error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), - ); + if (!features::transaction_simulation_enhancement_enabled() || + !skip_auth_key_check(is_simulation, &txn_authentication_key)) { + assert!( + txn_authentication_key == account::get_authentication_key(transaction_sender), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ) + }; let account_sequence_number = account::get_sequence_number(transaction_sender); assert!( @@ -123,24 +127,29 @@ module aptos_framework::transaction_validation { error::invalid_argument(PROLOGUE_ESEQUENCE_NUMBER_TOO_NEW) ); - assert!( - txn_authentication_key == bcs::to_bytes(&transaction_sender), - error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), - ); + if (!features::transaction_simulation_enhancement_enabled() || + !skip_auth_key_check(is_simulation, &txn_authentication_key)) { + assert!( + txn_authentication_key == bcs::to_bytes(&transaction_sender), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ); + } }; let max_transaction_fee = txn_gas_price * txn_max_gas_units; - if (features::operations_default_to_fa_apt_store_enabled()) { - assert!( - aptos_account::is_fungible_balance_at_least(gas_payer, max_transaction_fee), - error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) - ); - } else { - assert!( - coin::is_balance_at_least(gas_payer, max_transaction_fee), - error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) - ); + if (!features::transaction_simulation_enhancement_enabled() || !skip_gas_payment(is_simulation, gas_payer)) { + if (features::operations_default_to_fa_apt_store_enabled()) { + assert!( + aptos_account::is_fungible_balance_at_least(gas_payer, max_transaction_fee), + error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) + ); + } else { + assert!( + coin::is_balance_at_least(gas_payer, max_transaction_fee), + error::invalid_argument(PROLOGUE_ECANT_PAY_GAS_DEPOSIT) + ); + } } } @@ -155,6 +164,7 @@ module aptos_framework::transaction_validation { _script_hash: vector, ) { let gas_payer = signer::address_of(&sender); + // prologue_common with is_simulation set to false behaves identically to the original script_prologue function. prologue_common( sender, gas_payer, @@ -163,7 +173,36 @@ module aptos_framework::transaction_validation { txn_gas_price, txn_max_gas_units, txn_expiration_time, - chain_id + chain_id, + false, + ) + } + + // This function extends the script_prologue by adding a parameter to indicate simulation mode. + // Once the transaction_simulation_enhancement feature is enabled, the Aptos VM will invoke this function instead. + // Eventually, this function will be consolidated with the original function once the feature is fully enabled. + fun script_prologue_extended( + sender: signer, + txn_sequence_number: u64, + txn_public_key: vector, + txn_gas_price: u64, + txn_max_gas_units: u64, + txn_expiration_time: u64, + chain_id: u8, + _script_hash: vector, + is_simulation: bool, + ) { + let gas_payer = signer::address_of(&sender); + prologue_common( + sender, + gas_payer, + txn_sequence_number, + txn_public_key, + txn_gas_price, + txn_max_gas_units, + txn_expiration_time, + chain_id, + is_simulation, ) } @@ -179,6 +218,8 @@ module aptos_framework::transaction_validation { chain_id: u8, ) { let sender_addr = signer::address_of(&sender); + // prologue_common and multi_agent_common_prologue with is_simulation set to false behaves identically to the + // original multi_agent_script_prologue function. prologue_common( sender, sender_addr, @@ -188,13 +229,45 @@ module aptos_framework::transaction_validation { txn_max_gas_units, txn_expiration_time, chain_id, + false, ); - multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, false); + } + + // This function extends the multi_agent_script_prologue by adding a parameter to indicate simulation mode. + // Once the transaction_simulation_enhancement feature is enabled, the Aptos VM will invoke this function instead. + // Eventually, this function will be consolidated with the original function once the feature is fully enabled. + fun multi_agent_script_prologue_extended( + sender: signer, + txn_sequence_number: u64, + txn_sender_public_key: vector, + 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, + ) { + let sender_addr = signer::address_of(&sender); + prologue_common( + sender, + sender_addr, + txn_sequence_number, + txn_sender_public_key, + txn_gas_price, + txn_max_gas_units, + txn_expiration_time, + chain_id, + is_simulation, + ); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); } fun multi_agent_common_prologue( secondary_signer_addresses: vector
, secondary_signer_public_key_hashes: vector>, + is_simulation: bool, ) { let num_secondary_signers = vector::length(&secondary_signer_addresses); assert!( @@ -207,9 +280,10 @@ module aptos_framework::transaction_validation { spec { invariant i <= num_secondary_signers; invariant forall j in 0..i: - account::exists_at(secondary_signer_addresses[j]) - && secondary_signer_public_key_hashes[j] - == account::get_authentication_key(secondary_signer_addresses[j]); + account::exists_at(secondary_signer_addresses[j]); + invariant forall j in 0..i: + secondary_signer_public_key_hashes[j] == account::get_authentication_key(secondary_signer_addresses[j]) || + (features::spec_simulation_enhancement_enabled() && is_simulation && vector::is_empty(secondary_signer_public_key_hashes[j])); }; (i < num_secondary_signers) }) { @@ -217,10 +291,13 @@ module aptos_framework::transaction_validation { assert!(account::exists_at(secondary_address), error::invalid_argument(PROLOGUE_EACCOUNT_DOES_NOT_EXIST)); let signer_public_key_hash = *vector::borrow(&secondary_signer_public_key_hashes, i); - assert!( - signer_public_key_hash == account::get_authentication_key(secondary_address), - error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), - ); + if (!features::transaction_simulation_enhancement_enabled() || + !skip_auth_key_check(is_simulation, &signer_public_key_hash)) { + assert!( + signer_public_key_hash == account::get_authentication_key(secondary_address), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ) + }; i = i + 1; } } @@ -239,6 +316,8 @@ module aptos_framework::transaction_validation { chain_id: u8, ) { assert!(features::fee_payer_enabled(), error::invalid_state(PROLOGUE_EFEE_PAYER_NOT_ENABLED)); + // prologue_common and multi_agent_common_prologue with is_simulation set to false behaves identically to the + // original fee_payer_script_prologue function. prologue_common( sender, fee_payer_address, @@ -248,14 +327,54 @@ module aptos_framework::transaction_validation { txn_max_gas_units, txn_expiration_time, chain_id, + false, ); - multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, false); assert!( fee_payer_public_key_hash == account::get_authentication_key(fee_payer_address), error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), ); } + // This function extends the fee_payer_script_prologue by adding a parameter to indicate simulation mode. + // Once the transaction_simulation_enhancement feature is enabled, the Aptos VM will invoke this function instead. + // Eventually, this function will be consolidated with the original function once the feature is fully enabled. + fun fee_payer_script_prologue_extended( + sender: signer, + txn_sequence_number: u64, + txn_sender_public_key: vector, + secondary_signer_addresses: vector
, + secondary_signer_public_key_hashes: vector>, + fee_payer_address: address, + fee_payer_public_key_hash: vector, + txn_gas_price: u64, + txn_max_gas_units: u64, + txn_expiration_time: u64, + chain_id: u8, + is_simulation: bool, + ) { + assert!(features::fee_payer_enabled(), error::invalid_state(PROLOGUE_EFEE_PAYER_NOT_ENABLED)); + prologue_common( + sender, + fee_payer_address, + txn_sequence_number, + txn_sender_public_key, + txn_gas_price, + txn_max_gas_units, + txn_expiration_time, + chain_id, + is_simulation, + ); + multi_agent_common_prologue(secondary_signer_addresses, secondary_signer_public_key_hashes, is_simulation); + if (!features::transaction_simulation_enhancement_enabled() || + !skip_auth_key_check(is_simulation, &fee_payer_public_key_hash)) { + assert!( + fee_payer_public_key_hash == account::get_authentication_key(fee_payer_address), + error::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), + ) + } + } + /// Epilogue function is run after a transaction is successfully executed. /// Called by the Adapter fun epilogue( @@ -263,12 +382,27 @@ module aptos_framework::transaction_validation { storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, - gas_units_remaining: u64 + gas_units_remaining: u64, ) { let addr = signer::address_of(&account); epilogue_gas_payer(account, addr, storage_fee_refunded, txn_gas_price, txn_max_gas_units, gas_units_remaining); } + // This function extends the epilogue by adding a parameter to indicate simulation mode. + // Once the transaction_simulation_enhancement feature is enabled, the Aptos VM will invoke this function instead. + // Eventually, this function will be consolidated with the original function once the feature is fully enabled. + fun epilogue_extended( + account: signer, + storage_fee_refunded: u64, + txn_gas_price: u64, + txn_max_gas_units: u64, + gas_units_remaining: u64, + is_simulation: bool, + ) { + let addr = signer::address_of(&account); + epilogue_gas_payer_extended(account, addr, storage_fee_refunded, txn_gas_price, txn_max_gas_units, gas_units_remaining, is_simulation); + } + /// Epilogue function with explicit gas payer specified, is run after a transaction is successfully executed. /// Called by the Adapter fun epilogue_gas_payer( @@ -277,7 +411,32 @@ module aptos_framework::transaction_validation { storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, - gas_units_remaining: u64 + gas_units_remaining: u64, + ) { + // epilogue_gas_payer_extended with is_simulation set to false behaves identically to the original + // epilogue_gas_payer function. + epilogue_gas_payer_extended( + account, + gas_payer, + storage_fee_refunded, + txn_gas_price, + txn_max_gas_units, + gas_units_remaining, + false, + ); + } + + // This function extends the epilogue_gas_payer by adding a parameter to indicate simulation mode. + // Once the transaction_simulation_enhancement feature is enabled, the Aptos VM will invoke this function instead. + // Eventually, this function will be consolidated with the original function once the feature is fully enabled. + fun epilogue_gas_payer_extended( + account: signer, + gas_payer: address, + storage_fee_refunded: u64, + txn_gas_price: u64, + txn_max_gas_units: u64, + gas_units_remaining: u64, + is_simulation: bool, ) { assert!(txn_max_gas_units >= gas_units_remaining, error::invalid_argument(EOUT_OF_GAS)); let gas_used = txn_max_gas_units - gas_units_remaining; @@ -290,43 +449,53 @@ module aptos_framework::transaction_validation { // it's important to maintain the error code consistent with vm // to do failed transaction cleanup. - if (features::operations_default_to_fa_apt_store_enabled()) { - assert!( - aptos_account::is_fungible_balance_at_least(gas_payer, transaction_fee_amount), - error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT), - ); - } else { - assert!( - coin::is_balance_at_least(gas_payer, transaction_fee_amount), - error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT), - ); - }; + if (!features::transaction_simulation_enhancement_enabled() || !skip_gas_payment(is_simulation, gas_payer)) { + if (features::operations_default_to_fa_apt_store_enabled()) { + assert!( + aptos_account::is_fungible_balance_at_least(gas_payer, transaction_fee_amount), + error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT), + ); + } else { + assert!( + coin::is_balance_at_least(gas_payer, transaction_fee_amount), + error::out_of_range(PROLOGUE_ECANT_PAY_GAS_DEPOSIT), + ); + }; - let amount_to_burn = if (features::collect_and_distribute_gas_fees()) { - // TODO(gas): We might want to distinguish the refundable part of the charge and burn it or track - // it separately, so that we don't increase the total supply by refunding. + let amount_to_burn = if (features::collect_and_distribute_gas_fees()) { + // TODO(gas): We might want to distinguish the refundable part of the charge and burn it or track + // it separately, so that we don't increase the total supply by refunding. - // If transaction fees are redistributed to validators, collect them here for - // later redistribution. - transaction_fee::collect_fee(gas_payer, transaction_fee_amount); - 0 - } else { - // Otherwise, just burn the fee. - // TODO: this branch should be removed completely when transaction fee collection - // is tested and is fully proven to work well. - transaction_fee_amount - }; + // If transaction fees are redistributed to validators, collect them here for + // later redistribution. + transaction_fee::collect_fee(gas_payer, transaction_fee_amount); + 0 + } else { + // Otherwise, just burn the fee. + // TODO: this branch should be removed completely when transaction fee collection + // is tested and is fully proven to work well. + transaction_fee_amount + }; - if (amount_to_burn > storage_fee_refunded) { - let burn_amount = amount_to_burn - storage_fee_refunded; - transaction_fee::burn_fee(gas_payer, burn_amount); - } else if (amount_to_burn < storage_fee_refunded) { - let mint_amount = storage_fee_refunded - amount_to_burn; - transaction_fee::mint_and_refund(gas_payer, mint_amount) + if (amount_to_burn > storage_fee_refunded) { + let burn_amount = amount_to_burn - storage_fee_refunded; + transaction_fee::burn_fee(gas_payer, burn_amount); + } else if (amount_to_burn < storage_fee_refunded) { + let mint_amount = storage_fee_refunded - amount_to_burn; + transaction_fee::mint_and_refund(gas_payer, mint_amount) + }; }; // Increment sequence number let addr = signer::address_of(&account); account::increment_sequence_number(addr); } + + inline fun skip_auth_key_check(is_simulation: bool, auth_key: &vector): bool { + is_simulation && vector::is_empty(auth_key) + } + + inline fun skip_gas_payment(is_simulation: bool, gas_payer: address): bool { + is_simulation && gas_payer == @0x0 + } } 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 c0349586e5aef..799e71e414204 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move @@ -106,13 +106,14 @@ spec aptos_framework::transaction_validation { txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, + is_simulation: bool, ) { // TODO(fa_migration) pragma verify = false; include PrologueCommonAbortsIf; } - spec script_prologue( + spec script_prologue_extended( sender: signer, txn_sequence_number: u64, txn_public_key: vector, @@ -121,6 +122,7 @@ spec aptos_framework::transaction_validation { txn_expiration_time: u64, chain_id: u8, _script_hash: vector, + is_simulation: bool, ) { // TODO(fa_migration) pragma verify = false; @@ -130,9 +132,24 @@ spec aptos_framework::transaction_validation { }; } + spec script_prologue( + sender: signer, + txn_sequence_number: u64, + txn_public_key: vector, + txn_gas_price: u64, + txn_max_gas_units: u64, + txn_expiration_time: u64, + chain_id: u8, + _script_hash: vector, + ) { + // TODO: temporary mockup + pragma verify = false; + } + spec schema MultiAgentPrologueCommonAbortsIf { secondary_signer_addresses: vector
; secondary_signer_public_key_hashes: vector>; + is_simulation: bool; // Vectors to be `zipped with` should be of equal length. let num_secondary_signers = len(secondary_signer_addresses); @@ -142,30 +159,38 @@ spec aptos_framework::transaction_validation { // property 2: All secondary signer addresses are verified to be authentic through a validation process. /// [high-level-req-2] aborts_if exists i in 0..num_secondary_signers: - !account::exists_at(secondary_signer_addresses[i]) - || secondary_signer_public_key_hashes[i] != - account::get_authentication_key(secondary_signer_addresses[i]); - + !account::exists_at(secondary_signer_addresses[i]); + aborts_if exists i in 0..num_secondary_signers: + !can_skip(features::spec_simulation_enhancement_enabled(), is_simulation, secondary_signer_public_key_hashes[i]) && + secondary_signer_public_key_hashes[i] != + account::get_authentication_key(secondary_signer_addresses[i]); // By the end, all secondary signers account should exist and public key hash should match. ensures forall i in 0..num_secondary_signers: - account::exists_at(secondary_signer_addresses[i]) - && secondary_signer_public_key_hashes[i] == - account::get_authentication_key(secondary_signer_addresses[i]); + account::exists_at(secondary_signer_addresses[i]); + ensures forall i in 0..num_secondary_signers: + secondary_signer_public_key_hashes[i] == account::get_authentication_key(secondary_signer_addresses[i]) + || can_skip(features::spec_simulation_enhancement_enabled(), is_simulation, secondary_signer_public_key_hashes[i]); + } + + spec fun can_skip(feature_flag: bool, is_simulation: bool, auth_key: vector): bool { + features::spec_simulation_enhancement_enabled() && is_simulation && vector::is_empty(auth_key) } spec multi_agent_common_prologue( secondary_signer_addresses: vector
, secondary_signer_public_key_hashes: vector>, + is_simulation: bool, ) { include MultiAgentPrologueCommonAbortsIf { secondary_signer_addresses, secondary_signer_public_key_hashes, + is_simulation, }; } /// Aborts if length of public key hashed vector /// not equal the number of singers. - spec multi_agent_script_prologue ( + spec multi_agent_script_prologue_extended( sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector, @@ -175,6 +200,7 @@ spec aptos_framework::transaction_validation { txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, + is_simulation: bool, ) { pragma verify_duration_estimate = 120; let gas_payer = signer::address_of(sender); @@ -188,10 +214,26 @@ spec aptos_framework::transaction_validation { include MultiAgentPrologueCommonAbortsIf { secondary_signer_addresses, secondary_signer_public_key_hashes, + is_simulation, }; } - spec fee_payer_script_prologue( + spec multi_agent_script_prologue( + sender: signer, + txn_sequence_number: u64, + txn_sender_public_key: vector, + 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, + ) { + // TODO: temporary mockup + pragma verify = false; + } + + spec fee_payer_script_prologue_extended( sender: signer, txn_sequence_number: u64, txn_sender_public_key: vector, @@ -203,6 +245,7 @@ spec aptos_framework::transaction_validation { txn_max_gas_units: u64, txn_expiration_time: u64, chain_id: u8, + is_simulation: bool, ) { pragma verify_duration_estimate = 120; @@ -216,6 +259,7 @@ spec aptos_framework::transaction_validation { include MultiAgentPrologueCommonAbortsIf { secondary_signer_addresses, secondary_signer_public_key_hashes, + is_simulation, }; aborts_if !account::exists_at(gas_payer); @@ -223,37 +267,79 @@ spec aptos_framework::transaction_validation { aborts_if !features::spec_fee_payer_enabled(); } + spec fee_payer_script_prologue( + sender: signer, + txn_sequence_number: u64, + txn_sender_public_key: vector, + secondary_signer_addresses: vector
, + secondary_signer_public_key_hashes: vector>, + fee_payer_address: address, + fee_payer_public_key_hash: vector, + txn_gas_price: u64, + txn_max_gas_units: u64, + txn_expiration_time: u64, + chain_id: u8, + ) { + // TODO: temporary mockup + pragma verify = false; + } + /// Abort according to the conditions. /// `AptosCoinCapabilities` and `CoinInfo` should exists. /// Skip transaction_fee::burn_fee verification. - spec epilogue( + spec epilogue_extended( account: signer, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, - gas_units_remaining: u64 + gas_units_remaining: u64, + is_simulation: bool, ) { // TODO(fa_migration) pragma verify = false; include EpilogueGasPayerAbortsIf { gas_payer: signer::address_of(account) }; } + spec epilogue( + account: signer, + storage_fee_refunded: u64, + txn_gas_price: u64, + txn_max_gas_units: u64, + gas_units_remaining: u64, + ) { + // TODO: temporary mockup + pragma verify = false; + } + /// Abort according to the conditions. /// `AptosCoinCapabilities` and `CoinInfo` should exist. /// Skip transaction_fee::burn_fee verification. - spec epilogue_gas_payer( + spec epilogue_gas_payer_extended( account: signer, gas_payer: address, storage_fee_refunded: u64, txn_gas_price: u64, txn_max_gas_units: u64, - gas_units_remaining: u64 + gas_units_remaining: u64, + is_simulation: bool, ) { // TODO(fa_migration) pragma verify = false; include EpilogueGasPayerAbortsIf; } + spec epilogue_gas_payer( + account: signer, + gas_payer: address, + storage_fee_refunded: u64, + txn_gas_price: u64, + txn_max_gas_units: u64, + gas_units_remaining: u64, + ) { + // TODO: temporary mockup + pragma verify = false; + } + spec schema EpilogueGasPayerAbortsIf { use std::option; use aptos_std::type_info; diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index d35bb996c55c8..be36f1c502b96 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -129,6 +129,8 @@ return true. - [Function `default_to_concurrent_fungible_balance_enabled`](#0x1_features_default_to_concurrent_fungible_balance_enabled) - [Function `get_abort_if_multisig_payload_mismatch_feature`](#0x1_features_get_abort_if_multisig_payload_mismatch_feature) - [Function `abort_if_multisig_payload_mismatch_enabled`](#0x1_features_abort_if_multisig_payload_mismatch_enabled) +- [Function `get_transaction_simulation_enhancement_feature`](#0x1_features_get_transaction_simulation_enhancement_feature) +- [Function `transaction_simulation_enhancement_enabled`](#0x1_features_transaction_simulation_enhancement_enabled) - [Function `change_feature_flags`](#0x1_features_change_feature_flags) - [Function `change_feature_flags_internal`](#0x1_features_change_feature_flags_internal) - [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch) @@ -867,6 +869,20 @@ Lifetime: transient + + +Whether the simulation enhancement is enabled. This enables the simulation without an authentication check, +the sponsored transaction simulation when the fee payer is set to 0x0, and the multisig transaction +simulation consistnet with the execution. + +Lifetime: transient + + +
const TRANSACTION_SIMULATION_ENHANCEMENT: u64 = 78;
+
+ + + Whether during upgrade compatibility checking, friend functions should be treated similar like @@ -3152,6 +3168,52 @@ Lifetime: transient + + + + +## Function `get_transaction_simulation_enhancement_feature` + + + +
public fun get_transaction_simulation_enhancement_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_transaction_simulation_enhancement_feature(): u64 { TRANSACTION_SIMULATION_ENHANCEMENT }
+
+ + + +
+ + + +## Function `transaction_simulation_enhancement_enabled` + + + +
public fun transaction_simulation_enhancement_enabled(): bool
+
+ + + +
+Implementation + + +
public fun transaction_simulation_enhancement_enabled(): bool acquires Features {
+    is_enabled(TRANSACTION_SIMULATION_ENHANCEMENT)
+}
+
+ + +
@@ -3604,6 +3666,17 @@ Helper to check whether a feature flag is enabled. + + + + +
fun spec_simulation_enhancement_enabled(): bool {
+   spec_is_enabled(TRANSACTION_SIMULATION_ENHANCEMENT)
+}
+
+ + + ### Function `abort_if_multisig_payload_mismatch_enabled` diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index a1e55fc0653d0..44c21927c8aef 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -582,6 +582,19 @@ module std::features { is_enabled(ABORT_IF_MULTISIG_PAYLOAD_MISMATCH) } + /// Whether the simulation enhancement is enabled. This enables the simulation without an authentication check, + /// the sponsored transaction simulation when the fee payer is set to 0x0, and the multisig transaction + /// simulation consistnet with the execution. + /// + /// Lifetime: transient + const TRANSACTION_SIMULATION_ENHANCEMENT: u64 = 78; + + public fun get_transaction_simulation_enhancement_feature(): u64 { TRANSACTION_SIMULATION_ENHANCEMENT } + + public fun transaction_simulation_enhancement_enabled(): bool acquires Features { + is_enabled(TRANSACTION_SIMULATION_ENHANCEMENT) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.spec.move b/aptos-move/framework/move-stdlib/sources/configs/features.spec.move index 2823108154016..e8daa8950e396 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.spec.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.spec.move @@ -100,6 +100,10 @@ spec std::features { spec_is_enabled(ABORT_IF_MULTISIG_PAYLOAD_MISMATCH) } + spec fun spec_simulation_enhancement_enabled(): bool { + spec_is_enabled(TRANSACTION_SIMULATION_ENHANCEMENT) + } + spec abort_if_multisig_payload_mismatch_enabled { pragma opaque; aborts_if [abstract] false; diff --git a/crates/indexer/src/models/signatures.rs b/crates/indexer/src/models/signatures.rs index 8b8d56bebb9f8..f2bc121df6652 100644 --- a/crates/indexer/src/models/signatures.rs +++ b/crates/indexer/src/models/signatures.rs @@ -8,7 +8,8 @@ use aptos_api_types::{ AccountSignature as APIAccountSignature, Ed25519Signature as APIEd25519Signature, FeePayerSignature as APIFeePayerSignature, MultiAgentSignature as APIMultiAgentSignature, MultiEd25519Signature as APIMultiEd25519Signature, MultiKeySignature as APIMultiKeySignature, - SingleKeySignature as APISingleKeySignature, TransactionSignature as APITransactionSignature, + NoAccountSignature as APINoAccountSignature, SingleKeySignature as APISingleKeySignature, + TransactionSignature as APITransactionSignature, }; use aptos_bitvec::BitVec; use field_count::FieldCount; @@ -301,6 +302,15 @@ impl Signature { multi_agent_index, override_address, )], + APIAccountSignature::NoAccountSignature(sig) => vec![Self::parse_no_account_signature( + sig, + sender, + transaction_version, + transaction_block_height, + is_sender_primary, + multi_agent_index, + override_address, + )], } } @@ -353,4 +363,29 @@ impl Signature { multi_sig_index: 0, } } + + fn parse_no_account_signature( + _s: &APINoAccountSignature, + sender: &String, + transaction_version: i64, + transaction_block_height: i64, + is_sender_primary: bool, + multi_agent_index: i64, + override_address: Option<&String>, + ) -> Self { + let signer = standardize_address(override_address.unwrap_or(sender)); + Self { + transaction_version, + transaction_block_height, + signer, + is_sender_primary, + type_: String::from("no_account_signature"), + public_key: "Not implemented".into(), + threshold: 1, + public_key_indices: serde_json::Value::Array(vec![]), + signature: "Not implemented".into(), + multi_agent_index, + multi_sig_index: 0, + } + } } diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/convert.rs b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/convert.rs index 30366de9a4edd..5c8c0ab670bbe 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/convert.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/convert.rs @@ -670,6 +670,11 @@ pub fn convert_account_signature( convert_multi_key_signature(s), ), ), + AccountSignature::NoAccountSignature(_) => { + unreachable!( + "[Indexer Fullnode] Indexer should never see transactions with NoAccountSignature" + ) + }, }; transaction::AccountSignature { diff --git a/ecosystem/typescript/sdk/examples/typescript/multisig_account.ts b/ecosystem/typescript/sdk/examples/typescript/multisig_account.ts index b9fc100518c37..6626156fac4ed 100644 --- a/ecosystem/typescript/sdk/examples/typescript/multisig_account.ts +++ b/ecosystem/typescript/sdk/examples/typescript/multisig_account.ts @@ -83,12 +83,6 @@ const { AccountAddress, EntryFunction, MultiSig, MultiSigTransactionPayload, Tra const multisigTxExecution = new TransactionPayloadMultisig( new MultiSig(AccountAddress.fromHex(multisigAddress), transferTxPayload), ); - // We can simulate the transaction to see if it will succeed without having to create it on chain. - const [simulationResp] = await client.simulateTransaction( - owner2, - await client.generateRawTransaction(owner2.address(), multisigTxExecution), - ); - assert(simulationResp.success); // Create the multisig tx on chain. const createMultisigTx = await client.generateTransaction(owner2.address(), { diff --git a/execution/executor/src/components/chunk_output.rs b/execution/executor/src/components/chunk_output.rs index e31b71197ae46..7de6d39417a0d 100644 --- a/execution/executor/src/components/chunk_output.rs +++ b/execution/executor/src/components/chunk_output.rs @@ -396,6 +396,11 @@ pub fn update_counters_for_processed_chunk( .inc(); } }, + AccountAuthenticator::NoAccountAuthenticator => { + metrics::APTOS_PROCESSED_TXNS_AUTHENTICATOR + .with_label_values(&[process_type, "NoAccountAuthenticator"]) + .inc(); + }, }; } diff --git a/scripts/authenticator_regenerate.sh b/scripts/authenticator_regenerate.sh index 8524ec16f8561..6a2501cb651ad 100755 --- a/scripts/authenticator_regenerate.sh +++ b/scripts/authenticator_regenerate.sh @@ -37,18 +37,19 @@ cd $repodir cargo run -p aptos-openapi-spec-generator -- -f json -o api/doc/spec.json ) -( - echo - echo "Regenerating Typescript SDK (in `pwd`)" - # Typescript SDK client files - cd ecosystem/typescript/sdk - pnpm install - pnpm generate-client - - # Typescript SDK docs - pnpm generate-ts-docs - cd .. -) +## Disabled due to errors, and it's for the V1 which is going to be deprecated. +# ( +# echo +# echo "Regenerating Typescript SDK (in `pwd`)" +# # Typescript SDK client files +# cd ecosystem/typescript/sdk +# pnpm install +# pnpm generate-client +# +# # Typescript SDK docs +# pnpm generate-ts-docs +# cd .. +# ) echo echo "WARNING: If you are adding a new transaction authenticator..." diff --git a/testsuite/generate-format/tests/staged/api.yaml b/testsuite/generate-format/tests/staged/api.yaml index f79e996c3f475..72e38f150ea12 100644 --- a/testsuite/generate-format/tests/staged/api.yaml +++ b/testsuite/generate-format/tests/staged/api.yaml @@ -47,6 +47,8 @@ AccountAuthenticator: STRUCT: - authenticator: TYPENAME: MultiKeyAuthenticator + 4: + NoAccountAuthenticator: UNIT AggregateSignature: STRUCT: - validator_bitmask: diff --git a/testsuite/generate-format/tests/staged/aptos.yaml b/testsuite/generate-format/tests/staged/aptos.yaml index d32331a7b09ee..5e5c9c0e80697 100644 --- a/testsuite/generate-format/tests/staged/aptos.yaml +++ b/testsuite/generate-format/tests/staged/aptos.yaml @@ -35,6 +35,8 @@ AccountAuthenticator: STRUCT: - authenticator: TYPENAME: MultiKeyAuthenticator + 4: + NoAccountAuthenticator: UNIT AggregateSignature: STRUCT: - validator_bitmask: diff --git a/testsuite/generate-format/tests/staged/consensus.yaml b/testsuite/generate-format/tests/staged/consensus.yaml index 78e83931c185e..9548f063c237d 100644 --- a/testsuite/generate-format/tests/staged/consensus.yaml +++ b/testsuite/generate-format/tests/staged/consensus.yaml @@ -35,6 +35,8 @@ AccountAuthenticator: STRUCT: - authenticator: TYPENAME: MultiKeyAuthenticator + 4: + NoAccountAuthenticator: UNIT AggregateSignature: STRUCT: - validator_bitmask: diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index ec459b6cc7ff8..5522a395335b3 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -93,6 +93,7 @@ pub enum FeatureFlag { ENABLE_RESOURCE_ACCESS_CONTROL = 75, REJECT_UNSTABLE_BYTECODE_FOR_SCRIPT = 76, FEDERATED_KEYLESS = 77, + TRANSACTION_SIMULATION_ENHANCEMENT = 78, } impl FeatureFlag { @@ -169,6 +170,7 @@ impl FeatureFlag { FeatureFlag::ENABLE_ENUM_TYPES, FeatureFlag::ENABLE_RESOURCE_ACCESS_CONTROL, FeatureFlag::REJECT_UNSTABLE_BYTECODE_FOR_SCRIPT, + FeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT, ] } } @@ -310,6 +312,10 @@ impl Features { self.is_enabled(FeatureFlag::ABORT_IF_MULTISIG_PAYLOAD_MISMATCH) } + pub fn is_transaction_simulation_enhancement_enabled(&self) -> bool { + self.is_enabled(FeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT) + } + pub fn get_max_identifier_size(&self) -> u64 { if self.is_enabled(FeatureFlag::LIMIT_MAX_IDENTIFIER_LENGTH) { IDENTIFIER_SIZE_MAX diff --git a/types/src/transaction/authenticator.rs b/types/src/transaction/authenticator.rs index 87bd294632a19..20f6474fb2824 100644 --- a/types/src/transaction/authenticator.rs +++ b/types/src/transaction/authenticator.rs @@ -361,6 +361,9 @@ impl TransactionAuthenticator { AccountAuthenticator::MultiKey { authenticator } => { single_key_authenticators.extend(authenticator.to_single_key_authenticators()?); }, + AccountAuthenticator::NoAccountAuthenticator => { + // This case adds no single key authenticators to the vector. + }, }; } Ok(single_key_authenticators) @@ -452,6 +455,7 @@ pub enum Scheme { MultiEd25519 = 1, SingleKey = 2, MultiKey = 3, + NoScheme = 250, /// Scheme identifier used to derive addresses (not the authentication key) of objects and /// resources accounts. This application serves to domain separate hashes. Without such /// separation, an adversary could create (and get a signer for) a these accounts @@ -471,6 +475,7 @@ impl fmt::Display for Scheme { Scheme::MultiEd25519 => "MultiEd25519", Scheme::SingleKey => "SingleKey", Scheme::MultiKey => "MultiKey", + Scheme::NoScheme => "NoScheme", Scheme::DeriveAuid => "DeriveAuid", Scheme::DeriveObjectAddressFromObject => "DeriveObjectAddressFromObject", Scheme::DeriveObjectAddressFromGuid => "DeriveObjectAddressFromGuid", @@ -505,6 +510,7 @@ pub enum AccountAuthenticator { MultiKey { authenticator: MultiKeyAuthenticator, }, + NoAccountAuthenticator, // ... add more schemes here } @@ -516,6 +522,7 @@ impl AccountAuthenticator { Self::MultiEd25519 { .. } => Scheme::MultiEd25519, Self::SingleKey { .. } => Scheme::SingleKey, Self::MultiKey { .. } => Scheme::MultiKey, + Self::NoAccountAuthenticator => Scheme::NoScheme, } } @@ -561,6 +568,7 @@ impl AccountAuthenticator { } => signature.verify(message, public_key), Self::SingleKey { authenticator } => authenticator.verify(message), Self::MultiKey { authenticator } => authenticator.verify(message), + Self::NoAccountAuthenticator => bail!("No signature to verify."), } } @@ -571,6 +579,7 @@ impl AccountAuthenticator { Self::MultiEd25519 { public_key, .. } => public_key.to_bytes().to_vec(), Self::SingleKey { authenticator } => authenticator.public_key_bytes(), Self::MultiKey { authenticator } => authenticator.public_key_bytes(), + Self::NoAccountAuthenticator => vec![], } } @@ -581,12 +590,20 @@ impl AccountAuthenticator { Self::MultiEd25519 { signature, .. } => signature.to_bytes().to_vec(), Self::SingleKey { authenticator } => authenticator.signature_bytes(), Self::MultiKey { authenticator } => authenticator.signature_bytes(), + Self::NoAccountAuthenticator => vec![], } } /// Return an authentication key derived from `self`'s public key and scheme id - pub fn authentication_key(&self) -> AuthenticationKey { - AuthenticationKey::from_preimage(self.public_key_bytes(), self.scheme()) + pub fn authentication_key(&self) -> Option { + if let Self::NoAccountAuthenticator = self { + None + } else { + Some(AuthenticationKey::from_preimage( + self.public_key_bytes(), + self.scheme(), + )) + } } /// Return the number of signatures included in this account authenticator. @@ -596,6 +613,7 @@ impl AccountAuthenticator { Self::MultiEd25519 { signature, .. } => signature.signatures().len(), Self::SingleKey { .. } => 1, Self::MultiKey { authenticator } => authenticator.signatures.len(), + Self::NoAccountAuthenticator => 0, } } }