diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 7d2cb1e9ff0686..59cddfc4612ec2 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -1216,30 +1216,26 @@ impl AptosVM { session = self.vm_impl.new_session(resolver, SessionId::txn(txn)); } - if let aptos_types::transaction::authenticator::TransactionAuthenticator::FeePayer { - .. - } = &txn.authenticator_ref() - { - if self + let txn_data = TransactionMetadata::new(txn); + if txn_data.fee_payer().is_some() + && self .vm_impl .get_features() .is_enabled(FeatureFlag::SPONSORED_AUTOMATIC_ACCOUNT_CREATION) - { - if let Err(err) = session.execute_function_bypass_visibility( - &ACCOUNT_MODULE, - CREATE_ACCOUNT_IF_DOES_NOT_EXIST, - vec![], - serialize_values(&vec![MoveValue::Address(txn.sender())]), - gas_meter, - ) { - return discard_error_vm_status(err.into()); - }; - } + { + if let Err(err) = session.execute_function_bypass_visibility( + &ACCOUNT_MODULE, + CREATE_ACCOUNT_IF_DOES_NOT_EXIST, + vec![], + serialize_values(&vec![MoveValue::Address(txn.sender())]), + gas_meter, + ) { + return discard_error_vm_status(err.into()); + }; } let storage_gas_params = unwrap_or_discard!(self.vm_impl.get_storage_gas_parameters(log_context)); - let txn_data = TransactionMetadata::new(txn); // We keep track of whether any newly published modules are loaded into the Vm's loader // cache as part of executing transactions. This would allow us to decide whether the cache diff --git a/aptos-move/aptos-vm/src/aptos_vm_impl.rs b/aptos-move/aptos-vm/src/aptos_vm_impl.rs index 8347bbac24484f..237a69d898b4e6 100644 --- a/aptos-move/aptos-vm/src/aptos_vm_impl.rs +++ b/aptos-move/aptos-vm/src/aptos_vm_impl.rs @@ -8,8 +8,8 @@ use crate::{ errors::{convert_epilogue_error, convert_prologue_error, expect_only_successful_execution}, move_vm_ext::{AptosMoveResolver, MoveVmExt, SessionExt, SessionId}, system_module_names::{ - EMIT_FEE_STATEMENT, MULTISIG_ACCOUNT_MODULE, TRANSACTION_FEE_MODULE, - VALIDATE_MULTISIG_TRANSACTION, + ACCOUNT_MODULE, CREATE_ACCOUNT_IF_DOES_NOT_EXIST, EMIT_FEE_STATEMENT, + MULTISIG_ACCOUNT_MODULE, TRANSACTION_FEE_MODULE, VALIDATE_MULTISIG_TRANSACTION, }, testing::{maybe_raise_injected_error, InjectedError}, transaction_metadata::TransactionMetadata, @@ -615,6 +615,31 @@ impl AptosVMImpl { txn_data: &TransactionMetadata, log_context: &AdapterLogSchema, ) -> Result<(), VMStatus> { + if txn_data.fee_payer().is_some() + && txn_data.sequence_number == 0 + && self + .get_features() + .is_enabled(FeatureFlag::SPONSORED_AUTOMATIC_ACCOUNT_CREATION) + { + session + .execute_function_bypass_visibility( + &ACCOUNT_MODULE, + CREATE_ACCOUNT_IF_DOES_NOT_EXIST, + vec![], + serialize_values(&vec![MoveValue::Address(txn_data.sender())]), + &mut UnmeteredGasMeter, + ) + .map(|_return_vals| ()) + .map_err(expect_no_verification_errors) + .or_else(|err| { + expect_only_successful_execution( + err, + &format!("{:?}::{}", ACCOUNT_MODULE, CREATE_ACCOUNT_IF_DOES_NOT_EXIST), + log_context, + ) + })?; + } + self.run_epilogue(session, gas_remaining, fee_statement, txn_data) .or_else(|e| { expect_only_successful_execution( diff --git a/aptos-move/e2e-move-tests/src/tests/fee_payer.rs b/aptos-move/e2e-move-tests/src/tests/fee_payer.rs index 227517146b57ca..fa65479499bb03 100644 --- a/aptos-move/e2e-move-tests/src/tests/fee_payer.rs +++ b/aptos-move/e2e-move-tests/src/tests/fee_payer.rs @@ -1,15 +1,17 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{assert_success, MoveHarness}; +use crate::{assert_abort, assert_success, MoveHarness}; use aptos_cached_packages::aptos_stdlib; use aptos_language_e2e_tests::{ account::{Account, TransactionBuilder}, transaction_status_eq, }; use aptos_types::{ - account_address::AccountAddress, account_config::CoinStoreResource, - on_chain_config::FeatureFlag, transaction::TransactionStatus, + account_address::AccountAddress, + account_config::CoinStoreResource, + on_chain_config::FeatureFlag, + transaction::{ExecutionStatus, TransactionStatus}, }; use move_core_types::{move_resource::MoveStructType, vm_status::StatusCode}; @@ -74,6 +76,79 @@ fn test_account_not_exist_with_fee_payer_create_account() { assert!(bob_start > bob_after); } +#[test] +fn test_account_not_exist_and_out_of_gas_with_fee_payer_create_account() { + // This should result in an aborted, yet committed transaction and an account created. + let mut h = MoveHarness::new_with_features(vec![FeatureFlag::GAS_PAYER_ENABLED], vec![]); + + let alice = Account::new(); + let bob = h.new_account_at(AccountAddress::from_hex_literal("0xb0b").unwrap()); + + let alice_start = + h.read_resource::(alice.address(), CoinStoreResource::struct_tag()); + assert!(alice_start.is_none()); + let bob_start = h.read_aptos_balance(bob.address()); + + let payload = aptos_stdlib::aptos_coin_transfer(*alice.address(), 1); + let transaction = TransactionBuilder::new(alice.clone()) + .fee_payer(bob.clone()) + .payload(payload) + .sequence_number(0) + .max_gas_amount(2) + .gas_unit_price(1) + .sign_fee_payer(); + + let output = h.run_raw(transaction); + // ECOIN_STORE_NOT_PUBLISHED + assert_eq!( + output.status(), + &TransactionStatus::Keep(ExecutionStatus::OutOfGas) + ); + + let alice_after = + h.read_resource::(alice.address(), CoinStoreResource::struct_tag()); + assert!(alice_after.is_none()); + let bob_after = h.read_aptos_balance(bob.address()); + + assert_eq!(h.sequence_number(alice.address()), 1); + assert!(bob_start > bob_after); +} + +#[test] +fn test_account_not_exist_and_abort_with_fee_payer_create_account() { + // This should result in an aborted, yet committed transaction and an account created. + let mut h = MoveHarness::new_with_features(vec![FeatureFlag::GAS_PAYER_ENABLED], vec![]); + + let alice = Account::new(); + let bob = h.new_account_at(AccountAddress::from_hex_literal("0xb0b").unwrap()); + + let alice_start = + h.read_resource::(alice.address(), CoinStoreResource::struct_tag()); + assert!(alice_start.is_none()); + let bob_start = h.read_aptos_balance(bob.address()); + + let payload = aptos_stdlib::aptos_coin_transfer(*alice.address(), 1); + let transaction = TransactionBuilder::new(alice.clone()) + .fee_payer(bob.clone()) + .payload(payload) + .sequence_number(0) + .max_gas_amount(4) + .gas_unit_price(1) + .sign_fee_payer(); + + let output = h.run_raw(transaction); + // ECOIN_STORE_NOT_PUBLISHED + assert_abort!(output.status(), 393221); + + let alice_after = + h.read_resource::(alice.address(), CoinStoreResource::struct_tag()); + assert!(alice_after.is_none()); + let bob_after = h.read_aptos_balance(bob.address()); + + assert_eq!(h.sequence_number(alice.address()), 1); + assert!(bob_start > bob_after); +} + #[test] fn test_account_not_exist_with_fee_payer_without_create_account() { let mut h = MoveHarness::new_with_features(vec![FeatureFlag::GAS_PAYER_ENABLED], vec![