test: add integration test for same-ledger buy and sell without state corruption - #705
Merged
Chucks1093 merged 2 commits intoAug 23, 2026
Conversation
… 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
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.rscovers 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 sameenv.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 individualbuy_keyorsell_keycall 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_final_supply_equals_pre_buy_supply_after_net_zero_buy_sellget_total_key_supplyreturns the pre-buy value (0) — acceptance criterion 1test_supply_transitions_correctly_through_buy_and_sellbuy_keyreturn value increments by 1 (1→5); eachsell_keyreturn value decrements by 1 (4→0); final sell returns pre-buy supplyHolder count invariants (2 tests)
test_holder_count_returns_to_pre_buy_value_after_full_exitget_creator_holder_countreturns 0 after the sole holder sells all 5 keys — acceptance criterion 2test_holder_count_unchanged_after_partial_sellNo intermediate state observable (3 tests)
test_sell_quote_observes_post_buy_supplytest_trader_balance_is_zero_after_full_exitget_key_balanceis 0 after selling all 5 keys (no ghost balance)test_supply_equals_sum_of_holder_balances_after_net_zero_tradeEvent consistency (4 tests)
test_buy_and_sell_return_values_form_consistent_supply_sequencebuy_keyreturns strictly increasing supply (1,2,3,4,5);sell_keyreturns strictly decreasing supply (4,3,2,1,0); final value equals pre-buy supply — acceptance criterion 4test_buy_and_sell_events_both_emitted_and_correctly_taggedBUY_EVENT_NAMEevent perbuy_keycall and exactly 1SELL_EVENT_NAMEevent persell_keycall (5 of each accumulated)test_buy_events_carry_correct_addressesbuyer,creator_id, andquantity = 1test_sell_events_carry_correct_addressesseller,creator_id, andquantity = 1Bystander isolation (1 test)
test_bystander_unaffected_by_same_ledger_buy_sellHow it was tested