Skip to content

Commit 156d79e

Browse files
authored
Merge pull request #1581 from o1-labs/dw/coinbase-tests-tx
Add tests for account creation in transaction logic
2 parents 16f0fcd + 9ee62ef commit 156d79e

File tree

3 files changed

+244
-1
lines changed

3 files changed

+244
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7474
`mina-node-native` ([#1549](https://github.com/o1-labs/mina-rust/pull/1549))
7575
- **CI**: add a step in tests to run the unit/integration tests of the package
7676
`mina-node-native` ([#1549](https://github.com/o1-labs/mina-rust/pull/1549))
77+
- **Tests**: add account creation test cases for payment and coinbase
78+
transactions, verifying correct handling of account creation fees during
79+
the first pass of transaction application
80+
([#1581](https://github.com/o1-labs/mina-rust/pull/1581))
7781
- **tools**: remove stack allocation from tools
7882
([#1576](https://github.com/o1-labs/mina-rust/pull/1576))
7983

ledger/tests/test_transaction_logic_first_pass.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//!
55
//! Tests the first pass of two-phase transaction application, covering:
66
//! - Successful payment transactions
7+
//! - Payment creating receiver account
78
//! - Insufficient balance errors
89
//! - Invalid nonce errors
910
//! - Nonexistent fee payer errors
@@ -439,3 +440,126 @@ fn test_apply_payment_nonexistent_fee_payer() {
439440
"Alice's account should still not exist after transaction error"
440441
);
441442
}
443+
444+
/// Test payment that creates a new receiver account.
445+
///
446+
/// When the receiver account doesn't exist, a new account is created
447+
/// automatically. The account creation fee is deducted from the payment amount,
448+
/// not from the sender's balance.
449+
///
450+
/// Ledger state: Sender's balance decreased by amount + fee, receiver account
451+
/// created with balance = amount - account_creation_fee.
452+
#[test]
453+
fn test_apply_payment_creates_receiver_account() {
454+
let db = Database::create(15);
455+
let mut ledger = Mask::new_root(db);
456+
457+
let alice_pk = mina_signer::PubKey::from_address(
458+
"B62qmnY6m4c6bdgSPnQGZriSaj9vuSjsfh6qkveGTsFX3yGA5ywRaja",
459+
)
460+
.unwrap()
461+
.into_compressed();
462+
let bob_pk = mina_signer::PubKey::from_address(
463+
"B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS",
464+
)
465+
.unwrap()
466+
.into_compressed();
467+
468+
// Create only Alice's account
469+
let alice_id = AccountId::new(alice_pk.clone(), Default::default());
470+
let alice_account = Account::create_with(alice_id.clone(), Balance::from_u64(5_000_000_000));
471+
ledger
472+
.get_or_create_account(alice_id.clone(), alice_account)
473+
.unwrap();
474+
475+
let bob_id = AccountId::new(bob_pk.clone(), Default::default());
476+
477+
// Verify Bob's account does not exist before the transaction
478+
assert!(
479+
ledger.location_of_account(&bob_id).is_none(),
480+
"Bob's account should not exist before transaction"
481+
);
482+
483+
// Record initial state
484+
let alice_location = ledger.location_of_account(&alice_id).unwrap();
485+
let alice_before = ledger.get(alice_location).unwrap();
486+
let initial_alice_balance = alice_before.balance;
487+
let initial_alice_nonce = alice_before.nonce;
488+
let initial_alice_receipt_hash = alice_before.receipt_chain_hash;
489+
490+
let amount = 2_000_000_000; // 2 MINA
491+
let fee = 10_000_000; // 0.01 MINA
492+
let nonce = 0;
493+
let payment = create_payment(&alice_pk, &bob_pk, amount, fee, nonce);
494+
495+
let constraint_constants = &test_constraint_constants();
496+
let account_creation_fee = constraint_constants.account_creation_fee; // 1 MINA
497+
498+
let state_view = ProtocolStateView {
499+
snarked_ledger_hash: Fp::zero(),
500+
blockchain_length: Length::from_u32(0),
501+
min_window_density: Length::from_u32(0),
502+
total_currency: Amount::zero(),
503+
global_slot_since_genesis: Slot::from_u32(0),
504+
staking_epoch_data: dummy_epoch_data(),
505+
next_epoch_data: dummy_epoch_data(),
506+
};
507+
let result = apply_transaction_first_pass(
508+
constraint_constants,
509+
Slot::from_u32(0),
510+
&state_view,
511+
&mut ledger,
512+
&Transaction::Command(UserCommand::SignedCommand(Box::new(payment))),
513+
);
514+
515+
assert!(result.is_ok());
516+
517+
// Verify Alice's balance decreased by fee + payment amount
518+
let alice_location = ledger.location_of_account(&alice_id).unwrap();
519+
let alice_after = ledger.get(alice_location).unwrap();
520+
let expected_alice_balance = initial_alice_balance
521+
.sub_amount(Amount::from_u64(fee))
522+
.unwrap()
523+
.sub_amount(Amount::from_u64(amount))
524+
.unwrap();
525+
assert_eq!(
526+
alice_after.balance, expected_alice_balance,
527+
"Alice's balance should decrease by fee + payment amount"
528+
);
529+
530+
// Verify Alice's nonce incremented
531+
assert_eq!(
532+
alice_after.nonce,
533+
initial_alice_nonce.incr(),
534+
"Alice's nonce should be incremented"
535+
);
536+
537+
// Verify Alice's receipt chain hash updated
538+
assert_ne!(
539+
alice_after.receipt_chain_hash, initial_alice_receipt_hash,
540+
"Alice's receipt chain hash should be updated"
541+
);
542+
543+
// Verify Bob's account was created
544+
let bob_location = ledger.location_of_account(&bob_id);
545+
assert!(
546+
bob_location.is_some(),
547+
"Bob's account should now exist after transaction"
548+
);
549+
550+
// Verify Bob's balance is payment amount minus account creation fee
551+
let bob_location = bob_location.unwrap();
552+
let bob_after = ledger.get(bob_location).unwrap();
553+
let expected_bob_balance = Balance::from_u64(amount - account_creation_fee);
554+
assert_eq!(
555+
bob_after.balance, expected_bob_balance,
556+
"Bob's balance should be payment amount minus account creation fee"
557+
);
558+
559+
// Verify Bob's nonce is 0 (new account)
560+
assert_eq!(
561+
bob_after.nonce,
562+
Nonce::zero(),
563+
"Bob's nonce should be 0 for new account"
564+
);
565+
}

ledger/tests/test_transaction_logic_first_pass_coinbase.rs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! Tests for apply_transaction_first_pass with coinbase transactions
22
//!
3-
//! Run with: cargo test --test test_transaction_logic_first_pass_coinbase
3+
//! Run with: cargo test --test test_transaction_logic_first_pass_coinbase --release
44
//!
55
//! Tests the first pass of two-phase transaction application for coinbase
66
//! rewards, covering:
77
//! - Successful coinbase without fee transfer
88
//! - Successful coinbase with fee transfer to different account
9+
//! - Coinbase with fee transfer to nonexistent account (creates account)
910
//! - Coinbase with fee transfer to same account (fee transfer should be
1011
//! removed)
1112
//! - Coinbase creating a new account
@@ -228,6 +229,120 @@ fn test_apply_coinbase_with_fee_transfer() {
228229
);
229230
}
230231

232+
/// Test coinbase with fee transfer to a nonexistent account.
233+
///
234+
/// The coinbase receiver exists, but the fee transfer receiver doesn't exist.
235+
/// The fee transfer should create the receiver account, deducting the account
236+
/// creation fee from the fee transfer amount.
237+
///
238+
/// Ledger state:
239+
/// - Coinbase receiver gets coinbase_amount - fee_transfer_amount
240+
/// - Fee transfer receiver account created with fee_transfer_amount -
241+
/// account_creation_fee
242+
#[test]
243+
fn test_apply_coinbase_with_fee_transfer_creates_account() {
244+
let mut ledger = create_test_ledger();
245+
246+
let alice_pk = mina_signer::PubKey::from_address(
247+
"B62qmnY6m4c6bdgSPnQGZriSaj9vuSjsfh6qkveGTsFX3yGA5ywRaja",
248+
)
249+
.unwrap()
250+
.into_compressed();
251+
let bob_pk = mina_signer::PubKey::from_address(
252+
"B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS",
253+
)
254+
.unwrap()
255+
.into_compressed();
256+
257+
let alice_id = AccountId::new(alice_pk.clone(), Default::default());
258+
let bob_id = AccountId::new(bob_pk.clone(), Default::default());
259+
260+
// Verify Bob's account does not exist before the transaction
261+
assert!(
262+
ledger.location_of_account(&bob_id).is_none(),
263+
"Bob's account should not exist before transaction"
264+
);
265+
266+
// Record Alice's initial state
267+
let alice_location = ledger.location_of_account(&alice_id).unwrap();
268+
let alice_before = ledger.get(alice_location).unwrap();
269+
let initial_alice_balance = alice_before.balance;
270+
271+
// Create a coinbase of 720 MINA to Alice with a 10 MINA fee transfer to Bob
272+
// (who doesn't exist yet)
273+
let coinbase_amount = Amount::from_u64(720_000_000_000);
274+
let fee_transfer_amount = Fee::from_u64(10_000_000_000);
275+
let fee_transfer = CoinbaseFeeTransfer::create(bob_pk.clone(), fee_transfer_amount);
276+
let coinbase = Coinbase::create(coinbase_amount, alice_pk.clone(), Some(fee_transfer)).unwrap();
277+
278+
let constraint_constants = &test_constraint_constants();
279+
let state_view = ProtocolStateView {
280+
snarked_ledger_hash: Fp::zero(),
281+
blockchain_length: Length::from_u32(0),
282+
min_window_density: Length::from_u32(0),
283+
total_currency: Amount::zero(),
284+
global_slot_since_genesis: Slot::from_u32(0),
285+
staking_epoch_data: dummy_epoch_data(),
286+
next_epoch_data: dummy_epoch_data(),
287+
};
288+
let result = apply_transaction_first_pass(
289+
constraint_constants,
290+
Slot::from_u32(0),
291+
&state_view,
292+
&mut ledger,
293+
&Transaction::Coinbase(coinbase),
294+
);
295+
296+
assert!(result.is_ok());
297+
298+
// Verify Bob's account was created
299+
let bob_location = ledger.location_of_account(&bob_id);
300+
assert!(
301+
bob_location.is_some(),
302+
"Bob's account should exist after transaction"
303+
);
304+
305+
// Verify ledger state changes
306+
let alice_location = ledger.location_of_account(&alice_id).unwrap();
307+
let alice_after = ledger.get(alice_location).unwrap();
308+
let bob_account = ledger.get(bob_location.unwrap()).unwrap();
309+
310+
// Verify Alice's balance increased by (coinbase amount - fee transfer amount)
311+
let coinbase_after_fee_transfer = coinbase_amount
312+
.checked_sub(&Amount::of_fee(&fee_transfer_amount))
313+
.unwrap();
314+
let expected_alice_balance = initial_alice_balance
315+
.add_amount(coinbase_after_fee_transfer)
316+
.unwrap();
317+
assert_eq!(
318+
alice_after.balance, expected_alice_balance,
319+
"Alice's balance should increase by coinbase minus fee transfer"
320+
);
321+
322+
// Verify Bob's balance equals fee transfer amount minus account creation fee
323+
let account_creation_fee = constraint_constants.account_creation_fee;
324+
let expected_bob_balance = Balance::from_u64(
325+
Amount::of_fee(&fee_transfer_amount)
326+
.as_u64()
327+
.saturating_sub(account_creation_fee),
328+
);
329+
assert_eq!(
330+
bob_account.balance, expected_bob_balance,
331+
"Bob's balance should equal fee transfer minus account creation fee"
332+
);
333+
334+
// Verify nonces
335+
assert_eq!(
336+
alice_after.nonce, alice_before.nonce,
337+
"Alice's nonce should remain unchanged"
338+
);
339+
assert_eq!(
340+
bob_account.nonce,
341+
Nonce::zero(),
342+
"Bob's nonce should be 0 for new account"
343+
);
344+
}
345+
231346
/// Test coinbase with fee transfer to the same account.
232347
///
233348
/// When the coinbase receiver and fee transfer receiver are the same, the fee

0 commit comments

Comments
 (0)