Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`mina-node-native` ([#1549](https://github.com/o1-labs/mina-rust/pull/1549))
- **CI**: add a step in tests to run the unit/integration tests of the package
`mina-node-native` ([#1549](https://github.com/o1-labs/mina-rust/pull/1549))
- **Tests**: add account creation test cases for payment and coinbase
transactions, verifying correct handling of account creation fees during
the first pass of transaction application
([#1581](https://github.com/o1-labs/mina-rust/pull/1581))
- **tools**: remove stack allocation from tools
([#1576](https://github.com/o1-labs/mina-rust/pull/1576))

Expand Down
124 changes: 124 additions & 0 deletions ledger/tests/test_transaction_logic_first_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//!
//! Tests the first pass of two-phase transaction application, covering:
//! - Successful payment transactions
//! - Payment creating receiver account
//! - Insufficient balance errors
//! - Invalid nonce errors
//! - Nonexistent fee payer errors
Expand Down Expand Up @@ -439,3 +440,126 @@ fn test_apply_payment_nonexistent_fee_payer() {
"Alice's account should still not exist after transaction error"
);
}

/// Test payment that creates a new receiver account.
///
/// When the receiver account doesn't exist, a new account is created
/// automatically. The account creation fee is deducted from the payment amount,
/// not from the sender's balance.
///
/// Ledger state: Sender's balance decreased by amount + fee, receiver account
/// created with balance = amount - account_creation_fee.
#[test]
fn test_apply_payment_creates_receiver_account() {
let db = Database::create(15);
let mut ledger = Mask::new_root(db);

let alice_pk = mina_signer::PubKey::from_address(
"B62qmnY6m4c6bdgSPnQGZriSaj9vuSjsfh6qkveGTsFX3yGA5ywRaja",
)
.unwrap()
.into_compressed();
let bob_pk = mina_signer::PubKey::from_address(
"B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS",
)
.unwrap()
.into_compressed();

// Create only Alice's account
let alice_id = AccountId::new(alice_pk.clone(), Default::default());
let alice_account = Account::create_with(alice_id.clone(), Balance::from_u64(5_000_000_000));
ledger
.get_or_create_account(alice_id.clone(), alice_account)
.unwrap();

let bob_id = AccountId::new(bob_pk.clone(), Default::default());

// Verify Bob's account does not exist before the transaction
assert!(
ledger.location_of_account(&bob_id).is_none(),
"Bob's account should not exist before transaction"
);

// Record initial state
let alice_location = ledger.location_of_account(&alice_id).unwrap();
let alice_before = ledger.get(alice_location).unwrap();
let initial_alice_balance = alice_before.balance;
let initial_alice_nonce = alice_before.nonce;
let initial_alice_receipt_hash = alice_before.receipt_chain_hash;

let amount = 2_000_000_000; // 2 MINA
let fee = 10_000_000; // 0.01 MINA
let nonce = 0;
let payment = create_payment(&alice_pk, &bob_pk, amount, fee, nonce);

let constraint_constants = &test_constraint_constants();
let account_creation_fee = constraint_constants.account_creation_fee; // 1 MINA

let state_view = ProtocolStateView {
snarked_ledger_hash: Fp::zero(),
blockchain_length: Length::from_u32(0),
min_window_density: Length::from_u32(0),
total_currency: Amount::zero(),
global_slot_since_genesis: Slot::from_u32(0),
staking_epoch_data: dummy_epoch_data(),
next_epoch_data: dummy_epoch_data(),
};
let result = apply_transaction_first_pass(
constraint_constants,
Slot::from_u32(0),
&state_view,
&mut ledger,
&Transaction::Command(UserCommand::SignedCommand(Box::new(payment))),
);

assert!(result.is_ok());

// Verify Alice's balance decreased by fee + payment amount
let alice_location = ledger.location_of_account(&alice_id).unwrap();
let alice_after = ledger.get(alice_location).unwrap();
let expected_alice_balance = initial_alice_balance
.sub_amount(Amount::from_u64(fee))
.unwrap()
.sub_amount(Amount::from_u64(amount))
.unwrap();
assert_eq!(
alice_after.balance, expected_alice_balance,
"Alice's balance should decrease by fee + payment amount"
);

// Verify Alice's nonce incremented
assert_eq!(
alice_after.nonce,
initial_alice_nonce.incr(),
"Alice's nonce should be incremented"
);

// Verify Alice's receipt chain hash updated
assert_ne!(
alice_after.receipt_chain_hash, initial_alice_receipt_hash,
"Alice's receipt chain hash should be updated"
);

// Verify Bob's account was created
let bob_location = ledger.location_of_account(&bob_id);
assert!(
bob_location.is_some(),
"Bob's account should now exist after transaction"
);

// Verify Bob's balance is payment amount minus account creation fee
let bob_location = bob_location.unwrap();
let bob_after = ledger.get(bob_location).unwrap();
let expected_bob_balance = Balance::from_u64(amount - account_creation_fee);
assert_eq!(
bob_after.balance, expected_bob_balance,
"Bob's balance should be payment amount minus account creation fee"
);

// Verify Bob's nonce is 0 (new account)
assert_eq!(
bob_after.nonce,
Nonce::zero(),
"Bob's nonce should be 0 for new account"
);
}
117 changes: 116 additions & 1 deletion ledger/tests/test_transaction_logic_first_pass_coinbase.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Tests for apply_transaction_first_pass with coinbase transactions
//!
//! Run with: cargo test --test test_transaction_logic_first_pass_coinbase
//! Run with: cargo test --test test_transaction_logic_first_pass_coinbase --release
//!
//! Tests the first pass of two-phase transaction application for coinbase
//! rewards, covering:
//! - Successful coinbase without fee transfer
//! - Successful coinbase with fee transfer to different account
//! - Coinbase with fee transfer to nonexistent account (creates account)
//! - Coinbase with fee transfer to same account (fee transfer should be
//! removed)
//! - Coinbase creating a new account
Expand Down Expand Up @@ -228,6 +229,120 @@ fn test_apply_coinbase_with_fee_transfer() {
);
}

/// Test coinbase with fee transfer to a nonexistent account.
///
/// The coinbase receiver exists, but the fee transfer receiver doesn't exist.
/// The fee transfer should create the receiver account, deducting the account
/// creation fee from the fee transfer amount.
///
/// Ledger state:
/// - Coinbase receiver gets coinbase_amount - fee_transfer_amount
/// - Fee transfer receiver account created with fee_transfer_amount -
/// account_creation_fee
#[test]
fn test_apply_coinbase_with_fee_transfer_creates_account() {
let mut ledger = create_test_ledger();

let alice_pk = mina_signer::PubKey::from_address(
"B62qmnY6m4c6bdgSPnQGZriSaj9vuSjsfh6qkveGTsFX3yGA5ywRaja",
)
.unwrap()
.into_compressed();
let bob_pk = mina_signer::PubKey::from_address(
"B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS",
)
.unwrap()
.into_compressed();

let alice_id = AccountId::new(alice_pk.clone(), Default::default());
let bob_id = AccountId::new(bob_pk.clone(), Default::default());

// Verify Bob's account does not exist before the transaction
assert!(
ledger.location_of_account(&bob_id).is_none(),
"Bob's account should not exist before transaction"
);

// Record Alice's initial state
let alice_location = ledger.location_of_account(&alice_id).unwrap();
let alice_before = ledger.get(alice_location).unwrap();
let initial_alice_balance = alice_before.balance;

// Create a coinbase of 720 MINA to Alice with a 10 MINA fee transfer to Bob
// (who doesn't exist yet)
let coinbase_amount = Amount::from_u64(720_000_000_000);
let fee_transfer_amount = Fee::from_u64(10_000_000_000);
let fee_transfer = CoinbaseFeeTransfer::create(bob_pk.clone(), fee_transfer_amount);
let coinbase = Coinbase::create(coinbase_amount, alice_pk.clone(), Some(fee_transfer)).unwrap();

let constraint_constants = &test_constraint_constants();
let state_view = ProtocolStateView {
snarked_ledger_hash: Fp::zero(),
blockchain_length: Length::from_u32(0),
min_window_density: Length::from_u32(0),
total_currency: Amount::zero(),
global_slot_since_genesis: Slot::from_u32(0),
staking_epoch_data: dummy_epoch_data(),
next_epoch_data: dummy_epoch_data(),
};
let result = apply_transaction_first_pass(
constraint_constants,
Slot::from_u32(0),
&state_view,
&mut ledger,
&Transaction::Coinbase(coinbase),
);

assert!(result.is_ok());

// Verify Bob's account was created
let bob_location = ledger.location_of_account(&bob_id);
assert!(
bob_location.is_some(),
"Bob's account should exist after transaction"
);

// Verify ledger state changes
let alice_location = ledger.location_of_account(&alice_id).unwrap();
let alice_after = ledger.get(alice_location).unwrap();
let bob_account = ledger.get(bob_location.unwrap()).unwrap();

// Verify Alice's balance increased by (coinbase amount - fee transfer amount)
let coinbase_after_fee_transfer = coinbase_amount
.checked_sub(&Amount::of_fee(&fee_transfer_amount))
.unwrap();
let expected_alice_balance = initial_alice_balance
.add_amount(coinbase_after_fee_transfer)
.unwrap();
assert_eq!(
alice_after.balance, expected_alice_balance,
"Alice's balance should increase by coinbase minus fee transfer"
);

// Verify Bob's balance equals fee transfer amount minus account creation fee
let account_creation_fee = constraint_constants.account_creation_fee;
let expected_bob_balance = Balance::from_u64(
Amount::of_fee(&fee_transfer_amount)
.as_u64()
.saturating_sub(account_creation_fee),
);
assert_eq!(
bob_account.balance, expected_bob_balance,
"Bob's balance should equal fee transfer minus account creation fee"
);

// Verify nonces
assert_eq!(
alice_after.nonce, alice_before.nonce,
"Alice's nonce should remain unchanged"
);
assert_eq!(
bob_account.nonce,
Nonce::zero(),
"Bob's nonce should be 0 for new account"
);
}

/// Test coinbase with fee transfer to the same account.
///
/// When the coinbase receiver and fee transfer receiver are the same, the fee
Expand Down
Loading