Repository navigation
Per-market fee attribution for shared quote tokens - #1093
Merged
IbrahimIjai merged 14 commits intoOct 21, 2026
Merged
IbrahimIjai merged 14 commits into
IbrahimIjai merged 14 commits into
Conversation
main did not compile, so no gate step could run: - tests/Cargo.toml: two [[test]] headers were missing (duplicate keys) - settlement_invariants/rollback_harness: depended on nonexistent `gmx_types`/`gmx_math` package names; fixed unused-arg/while-let/clippy issues, a nonexistent `soroban_sdk::Status`, a u32/u64 mismatch, an invalid `Env::new()` call, and a test asserting on a nonexistent field - gmx-types event_schema: `[u8; 32]` is not a contracttype; use BytesN<32> - order_cleanup: u32/usize comparison in the batch limit check - cargo fmt on the touched modules Compile fixes only; no behaviour changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Protocol fees could be paid to any address the FEE_KEEPER passed to
claim_fees / claim_all_fees. The destination is now trusted state only.
- Protocol recipient (instance storage) set by `set_fee_receiver`, and an
optional per-market override (persistent storage) set/cleared by
`set_market_fee_receiver`; both stored-admin only.
- Every change emits `FeeReceiverSet { market, previous, receiver }`
(topic `fee_rcv`) so the full history is auditable.
- BREAKING: `claim_fees(keeper, market, token)` and
`claim_all_fees(fee_handler, keeper, markets, tokens)` drop the
`receiver` argument; `BatchFeesClaimed` drops its `receiver` field.
- Reverts with FeeReceiverNotSet (SO4-Markets#7) when nothing is configured, and
InvalidFeeReceiver (SO4-Markets#8) for the market pool or the handler itself.
- New views: get_fee_receiver, get_protocol_fee_receiver,
get_market_fee_receiver. v1 is single-recipient; no splits.
- Accrual paths are unchanged: settlement credits
claimable_fee_amount(market, token); no order parameter is consulted.
Tests: unit tests for routing, fallback, admin auth (positive/negative),
invalid recipients, events; sweeper per-market routing; e2e test showing
an order's `receiver` cannot redirect settled fees; fee scenarios added
to the resource bench. Docs: security.md, deployment.md, auth-audit.md;
deploy.sh sets the protocol recipient after initialize.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pre-existing test code that never compiled or that current stable clippy (what CI installs) rejects; no production behaviour changes: - order_handler tests from SO4-Markets#1086 (rollback_atomicity, settlement_invariants): add the missing settlement_invariants/rollback_harness dev-dependencies, drop an extra `initialize` argument, fix a u32/u64 comparison and a use-after-move, remove unused imports, rustfmt - tests/clob_model.rs (SO4-Markets#1082): type alias for a complex return type, needless borrows, `div_ceil` - oracle: turn an assertion on a constant into a compile-time check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picks up the new fee-recipient entrypoints, the `receiver`-less claim_fees / claim_all_fees signatures, and errors SO4-Markets#7/SO4-Markets#8. Generated with stellar-cli 28.1.0. The package.json bump to @stellar/stellar-sdk ^16 that this CLI version emits was left out on purpose (major frontend upgrade, out of scope); other contracts' bindings are stale only because of SO4-Markets#1085's gmx-types additions and are untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
execute_{deposit,withdrawal}_reverts_on_stale_price advanced the ledger by
one and expected a panic, but oracle#1205 (53abcfb) deliberately accepts
prices for PRICE_FRESHNESS_WINDOW (20) ledgers, so the calls succeed and
the #[should_panic] tests fail. Step past the window so the tests check
what they describe again. Test-only change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds fees/set_fee_receiver, fees/set_market_fee_receiver and fees/claim_fees. Every existing scenario is unchanged versus the previous baseline. Recorded from CI run 39335378079 (ubuntu, soroban-sdk 25.3.1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`cargo build --target wasm32v1-none --release` with no -p list builds every workspace member, including test-only crates that enable soroban-sdk `testutils`, which does not compile for wasm. Use `make build-wasm` (the $(CONTRACTS) list), as ci.yml already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The FIFO property test picks resting orders at random to cancel or reduce,
and reduces to rng.range(1, qty - 1). Once an order has been reduced to 1,
choosing it again calls range(1, 0) and underflows ("attempt to subtract
with overflow"). Cancel it instead in that case. Generator bug in the test
only; this test never ran before because tests/Cargo.toml was missing its
[[test]] header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fees stay market-scoped in both custody (each market_token pool) and
ledger (claimable_fee_amount(market, token) backed by
pool_amount(market, token)), so markets sharing a quote token never
overwrite each other and no token-level ledger records the same tokens
twice. Token-level reads and withdrawals aggregate those entries:
- fee_handler.claimable_fees_by_token(token, markets) -> TokenFeeAttribution
{ token, total, markets: [MarketFeeAmount { market, amount }] }
- fee_handler.claim_token_fees(keeper, token, markets): FEE_KEEPER; claims
each market exactly as claim_fees (own pool, own pool_amount, own
recipient, own FeeClaimed), then emits TokenFeesClaimed. Atomic.
- Both reject duplicate markets (DuplicateMarket SO4-Markets#10), are bounded by
MAX_TOKEN_FEE_MARKETS = 20 (TooManyMarkets SO4-Markets#9) and use checked sums
(FeeOverflow SO4-Markets#11).
- reader.get_protocol_stats rejects duplicate markets (DuplicateMarket SO4-Markets#6),
which previously counted a market's fees, pool value and OI twice.
claim_fees now shares the per-market claim path; its behaviour and
resource cost are unchanged (bench baseline identical).
Tests: fee_handler unit tests (separate ledgers, attribution, per-market
recipients and events, duplicates, bounds, auth, atomic revert, overflow),
reader duplicate test, and tests/shared_quote_token_fees.rs e2e with two
markets settling shorts in the same quote token. Bench scenario
fees/claim_token_fees added; docs/security.md, auth audit and fee-handler
bindings updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Markets#1090 deleted SO4-Markets#1090 (280f268) deleted 765 files from main, leaving only libs/clob_settlement_core. This merge keeps this branch's full tree, which restores every file SO4-Markets#1090 deleted, and takes the files main added since 5436392 (SO4-Markets#1090, SO4-Markets#1083, SO4-Markets#1089, SO4-Markets#1084): - libs/clob_settlement_core/src/lib.rs - contracts/reader/src/clob_reads.rs - docs/REENTRANCY_ANALYSIS.md, docs/clob-read-api-specification.md, docs/market-data-streaming.md - tests/reentrancy_token.rs None of these are compiled yet: clob_settlement_core has no Cargo.toml, clob_reads.rs is not declared as a module, and reentrancy_token.rs is not registered in tests/Cargo.toml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Markets#1090 deleted) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@toluwalasedaniel459-cmyk Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
1 task
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 #970
Summary
Several markets can accrue protocol fees in the same token (e.g. every
*/USDCmarket). This PR keeps fees market-scoped for both custody and ledger, and adds token-level reads and withdrawals that aggregate those per-market entries without double counting.Topology:
market_tokenpool. Markets never share a custody balance.claimable_fee_amount(market, token)and are backed bypool_amount(market, token). Both keys include the market, so two markets sharing a quote token write different entries and cannot overwrite each other.New
fee_handlerentrypoints:claimable_fees_by_token(token, markets)TokenFeeAttribution { token, total, markets: [MarketFeeAmount { market, amount }] }claim_token_fees(keeper, token, markets)FEE_KEEPERclaim_token_feesclaims each market exactly asclaim_feesdoes: from that market's own pool, against its ownpool_amount, to its own trusted recipient (#969), with its ownFeeClaimedevent. It then emitstkfee_clmTokenFeesClaimed { token, total, market_count }. The call is atomic: one failing market reverts every transfer.Guards (both calls):
DuplicateMarket(#10 — Add upgrade entrypoint toorder_handler#10): a market listed twice is rejected, so it cannot be counted twice.TooManyMarkets(#9 — Add upgrade entrypoint towithdrawal_handler#9): at mostMAX_TOKEN_FEE_MARKETS = 20markets. This bounds iteration, including the pairwise duplicate check.FeeOverflow(#11 — Add upgrade entrypoint toliquidation_handler#11): totals use checkedu128addition.Reader fix:
reader::get_protocol_statsnow rejects duplicate markets (DuplicateMarket, #6). Before, listing a market twice counted its fees, pool value and open interest twice.Refactor:
claim_feesnow shares the internal per-market claim path. Its behaviour and resource cost are unchanged: every existing benchmark scenario is identical.Topology assumption: #970 says to implement the topology selected in #11/#19. I couldn't find that decision recorded in the repo, so this PR implements the existing market-scoped model. Please confirm that's the selected topology.
Tests
fee_handlerunit tests:pool_amountand custody untouched.claim_token_feespays each market to its own recipient, with the exact event sequence.reader: duplicate markets are rejected inget_protocol_stats.tests/shared_quote_token_fees.rs(e2e):pool_amountby its own fee.tests/resource_bench.rs: new scenariofees/claim_token_fees.MAX_TOKEN_FEE_MARKETSis added to the baseline's bounded constants.Verification (local, Windows host with soroban-sdk 25.3.1)
cargo fmt --all -- --check--all-targets -D warnings(fee-handler,reader, integration tests)fee-handlertestsreaderprotocol_statstestsshared_quote_token_fees,fee_receiver_routing,resource_benchfee-handler,reader,fee-batch-sweeper)claim_token_feesauth-bearing, PASSNot run: the full workspace
cargo test. It stops at the pre-existingtests/funding_rate.rsfailures described in #1092.After the merge with
main(92e9e1c, fork CI runs 39450574755 and 39450581857): rustfmt, clippy, the WASM build and Resource Benchmarks pass.cargo testfails only on the 6 knownfunding_ratetests.Docs and types
docs/security.mddocuments shared-quote-token attribution, the new entrypoints and the new errors.docs/auth-audit.mdis regenerated.bindings/fee-handleris regenerated.bindings/readeris left stale, as are the other bindings already stale since feat: typed error model, market lifecycle states, and versioned event schema #1085. Regenerating it would pull in about 600 lines of unrelatedgmx-typesadditions.🤖 Generated with Claude Code