Add missing validation on four admin-only config setters - #673
Merged
abayomicornelius merged 4 commits intoAug 24, 2026
Merged
Conversation
…beat_timeout timeout_ledgers = 0 made check_keeper_heartbeat treat a keeper as stale the instant a single ledger elapsed since its last recorded activity, since is_stale = ledgers_since_last_activity > timeout. That false signal could lead an admin to revoke a perfectly healthy keeper's role based on a misconfigured (fat-fingered, or seconds/ledgers unit-mixed-up) value rather than real inactivity. Reject timeout_ledgers == 0 with a new InvalidHeartbeatTimeout error before it's written to data_store. Closes SO4-Markets#634
order_handler::liquidate_position caps the keeper fee transfer at the position's full collateral, not at some smaller fraction of it, so any configured liquidation_execution_fee at or above a position's collateral value resulted in the entire remaining collateral being paid to the liquidating keeper, zeroing out the position owner's remaining collateral. require_controller already gates the setter, but nothing on-chain caught a fat-fingered or decimal-scale-mixed-up value before it took effect. Add MAX_LIQUIDATION_EXECUTION_FEE (1,000 tokens at 7-decimal precision) and reject fees above it with a new LiquidationExecutionFeeTooHigh error. Closes SO4-Markets#633
…dress update_withdrawal_handler accepted any address with no validation, so a copy-paste mistake (e.g. pointing it at order_handler's or fee_handler's address instead of the real withdrawal handler) would silently misroute every create_withdrawal/execute_withdrawal call to a contract that doesn't implement the expected interface, surfacing only as a confusing cross-contract-call failure on the first user withdrawal. Reject new_handler == the router's own address or any of its other registered handler addresses (deposit_handler, order_handler, fee_handler) with a new InvalidWithdrawalHandler error. Closes SO4-Markets#635
Every other contract's initialize in this workspace (fee_handler, oracle, liquidation_handler, adl_handler, order_handler, deposit_handler, withdrawal_handler, market_factory, referral_storage, role_store) calls admin.require_auth() before setting up state. market_token::initialize was the sole outlier: it stored the passed-in admin address without ever requiring that (or any) address to authorize the call. Not exploitable via the current sole call path (market_factory::create_market deploys and initializes atomically), but a real gap in this contract's own defenses against any future/alternate deployment path that separates deploy from init. Add admin.require_auth() as the first line, matching every sibling contract. Closes SO4-Markets#632
|
@chonilius 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! 🚀 |
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.
Summary
Four small, independent config-input validation gaps across four contracts, each flagged in its own issue. All are admin/controller-gated setters that accepted values with no sanity checking, so a fat-fingered value (rather than any actual privilege-escalation bug) could cause real damage. Each fix is a minimal, targeted guard plus a dedicated error variant — no unrelated refactors.
order_handler::set_keeper_heartbeat_timeout— rejecttimeout_ledgers == 0check_keeper_heartbeatcomputesis_stale = ledgers_since_last_activity > timeout. Atimeout_ledgers = 0misconfiguration made every keeper on that role look permanently stale after a single ledger, which could lead an admin to revoke a perfectly healthy keeper's role based on a false signal. Now rejected withError::InvalidHeartbeatTimeout.data_store::set_liquidation_execution_fee— cap at a sane ceilingorder_handler::liquidate_positioncaps the transferred keeper fee at the position's entire collateral, not some smaller fraction of it. Any configured fee at or above a position's collateral value resulted in the whole remaining collateral being paid out to the liquidating keeper. AddedMAX_LIQUIDATION_EXECUTION_FEE(1,000 tokens at Stellar's 7-decimal precision — well above any realistic keeper reimbursement) and reject fees above it withError::LiquidationExecutionFeeTooHigh.exchange_router::update_withdrawal_handler— reject self/other-handler addressesAccepted any address with zero validation. A copy-paste mistake (e.g. pointing it at
order_handler's orfee_handler's address) would silently misroute every withdrawal call to a contract with a completely different interface, surfacing only as a confusing cross-contract-call error on the first user's withdrawal. Now rejectsnew_handlerequal to the router's own address or any of its other registered handler addresses (deposit_handler,order_handler,fee_handler) withError::InvalidWithdrawalHandler.market_token::initialize— require admin authEvery other contract's
initializein this workspace (fee_handler,oracle,liquidation_handler,adl_handler,order_handler,deposit_handler,withdrawal_handler,market_factory,referral_storage,role_store) callsadmin.require_auth()before setting up state.market_token::initializewas the sole outlier — it stored the passed-inadminaddress without ever requiring that (or any) address to authorize the call. Not exploitable via the current sole call path (market_factory::create_marketdeploys and initializes atomically in one invocation), but it's a real gap in this contract's own defenses against any future/alternate deployment path that separates deploy from init. Addedadmin.require_auth()as the first line, matching every sibling contract.Why these are safe, minimal fixes
All four are validation-only additions on admin/controller-gated entry points — no changes to any existing authorized behavior, no new state, no changes to any read path.
cargo build -p order-handler -p data-store -p exchange-router -p market-tokenpasses clean.Closes
Closes #632
Closes #633
Closes #634
Closes #635
Test plan
cargo buildacross all four touched crates (order_handler, data_store, exchange_router, market_token) — passes clean