Skip to content

test: add integration test for same-ledger buy and sell without state corruption - #705

Merged
Chucks1093 merged 2 commits into
accesslayerorg:mainfrom
stableprogrammer:test/same-ledger-buy-sell-699
Aug 23, 2026
Merged

test: add integration test for same-ledger buy and sell without state corruption#705
Chucks1093 merged 2 commits into
accesslayerorg:mainfrom
stableprogrammer:test/same-ledger-buy-sell-699

Conversation

@stableprogrammer

Copy link
Copy Markdown
Contributor

Closes #699

What was changed and why

Issue #699 requires an integration test proving that a buy and an immediate sell in the same ledger do not corrupt contract state. Specifically: the sell must observe supply after the buy (not the pre-buy value), the final supply must equal the pre-buy supply (net-zero change), and the holder count must return to its pre-buy value.

The test file creator-keys/tests/same_ledger_buy_sell.rs covers all four acceptance criteria in 12 focused tests.


Why same ledger

The Soroban test harness processes each contract invocation within the same ledger sequence number unless env.ledger().set(...) is called explicitly to advance it. This test does not bump the ledger between the buy and the sell, so both operations share the same env.ledger().sequence(). The contract must apply the buy first and expose that updated state to the sell — this is the same-ledger sequential ordering guarantee the issue is testing.


Implementation detail: event log behaviour

Soroban's env.events().all() exposes only the events from the most recent completed invocation. This is why the event-related tests read the log immediately after each individual buy_key or sell_key call and accumulate counts in a loop, rather than reading once after all 5+5 operations. The approach is consistent with how existing event tests in this repo work (e.g. buy_sell_event_topics_distinct.rs, sell_event_fields.rs).


Tests (12 total, all passing)

Supply invariants (2 tests)

Test What it checks
test_final_supply_equals_pre_buy_supply_after_net_zero_buy_sell After buying 5 keys and selling 5 keys, get_total_key_supply returns the pre-buy value (0) — acceptance criterion 1
test_supply_transitions_correctly_through_buy_and_sell Each buy_key return value increments by 1 (1→5); each sell_key return value decrements by 1 (4→0); final sell returns pre-buy supply

Holder count invariants (2 tests)

Test What it checks
test_holder_count_returns_to_pre_buy_value_after_full_exit get_creator_holder_count returns 0 after the sole holder sells all 5 keys — acceptance criterion 2
test_holder_count_unchanged_after_partial_sell Holder count stays 1 when the trader still holds 2 keys after a partial sell of 3

No intermediate state observable (3 tests)

Test What it checks
test_sell_quote_observes_post_buy_supply The sell quote price equals the buy price at step 5; if the sell read stale pre-buy supply, the price would be lower — acceptance criterion 3
test_trader_balance_is_zero_after_full_exit get_key_balance is 0 after selling all 5 keys (no ghost balance)
test_supply_equals_sum_of_holder_balances_after_net_zero_trade With a bystander holding 3 keys throughout, total supply equals the sum of all holder balances after the trader's round-trip (no double-counting or state leak)

Event consistency (4 tests)

Test What it checks
test_buy_and_sell_return_values_form_consistent_supply_sequence buy_key returns strictly increasing supply (1,2,3,4,5); sell_key returns strictly decreasing supply (4,3,2,1,0); final value equals pre-buy supply — acceptance criterion 4
test_buy_and_sell_events_both_emitted_and_correctly_tagged Exactly 1 BUY_EVENT_NAME event per buy_key call and exactly 1 SELL_EVENT_NAME event per sell_key call (5 of each accumulated)
test_buy_events_carry_correct_addresses Each buy event has the correct buyer, creator_id, and quantity = 1
test_sell_events_carry_correct_addresses Each sell event has the correct seller, creator_id, and quantity = 1

Bystander isolation (1 test)

Test What it checks
test_bystander_unaffected_by_same_ledger_buy_sell A holder who bought 2 keys before the trader's round-trip sees no change to their balance or to total supply after the round-trip completes

How it was tested

cargo test --test same_ledger_buy_sell
running 12 tests
test test_buy_and_sell_events_both_emitted_and_correctly_tagged ... ok
test test_buy_and_sell_return_values_form_consistent_supply_sequence ... ok
test test_buy_events_carry_correct_addresses ... ok
test test_bystander_unaffected_by_same_ledger_buy_sell ... ok
test test_final_supply_equals_pre_buy_supply_after_net_zero_buy_sell ... ok
test test_holder_count_returns_to_pre_buy_value_after_full_exit ... ok
test test_holder_count_unchanged_after_partial_sell ... ok
test test_sell_events_carry_correct_addresses ... ok
test test_sell_quote_observes_post_buy_supply ... ok
test test_supply_equals_sum_of_holder_balances_after_net_zero_trade ... ok
test test_supply_transitions_correctly_through_buy_and_sell ... ok
test test_trader_balance_is_zero_after_full_exit ... ok

test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

… corruption (accesslayerorg#699)

Verifies that a buy of 5 keys followed by a sell of 5 keys in the same ledger
produces a net-zero change, that the sell observes post-buy state (no stale
reads), and that no state corruption occurs.

12 tests across four categories:

Supply invariants (2):
- Final supply equals pre-buy supply after net-zero buy+sell
- Supply transitions correctly: each buy_key return value increments by 1,
  each sell_key return value decrements by 1, last sell returns pre-buy supply

Holder count invariants (2):
- Holder count returns to pre-buy value after the trader fully exits
- Holder count is not decremented on partial sell (trader still holds keys)

No intermediate state observable (3):
- Sell quote references supply after all buys (post-buy state), not pre-buy supply
- Trader balance is zero after selling all keys (no ghost balances)
- Total supply equals sum of holder balances after the round-trip (no double-counting)

Event consistency (4):
- buy_key and sell_key return values form a consistent ascending/descending
  supply sequence (acceptance criterion: new_supply values consistent with sequence)
- Exactly 1 buy event per buy_key call and 1 sell event per sell_key call
  (accumulated to 5 of each over the full sequence)
- Each buy event carries the correct buyer and creator addresses
- Each sell event carries the correct seller and creator addresses

Bystander isolation (1):
- A holder who bought keys before the round-trip sees no change to their
  balance or to total supply after the trader's buy+sell completes
@Chucks1093
Chucks1093 merged commit bb1b66a into accesslayerorg:main Aug 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add integration test for the contract correctly handling a buy and immediate sell in the same ledger without state corruption

2 participants