fix: resolve issues #420, #409, #411, #408 — time skew, balance-delta… - #457
Merged
Conversation
…col#409, stellar-vortex-protocol#411, stellar-vortex-protocol#408 — time skew, balance-delta, scoped auth, chain onboarding ## Issue stellar-vortex-protocol#420 — Ledger close-time skew: slash grace period and named boundary helpers Closes stellar-vortex-protocol#420 Stellar ledger close times are validator-agreed and can drift from wall-clock time by up to ~7 s (two typical 5 s ledger intervals). A solver whose fill_intent transaction was broadcast before the deadline can land in a ledger whose close time is past the deadline and be slashed unfairly. Changes: - Added SLASH_GRACE_SECS = 10 (2× worst-case ledger gap) constant. slash_solver now requires now >= deadline + SLASH_GRACE_SECS before executing a slash, giving solvers a timing buffer without widening the fill window itself. - Extracted all time-boundary comparisons into four named helper functions: fill_window_open(now, deadline) -> bool // now < deadline (exclusive) slash_eligible(now, deadline) -> bool // now >= deadline + SLASH_GRACE_SECS dispute_window_open(now, ddl) -> bool // now < dispute_deadline arbiter_timeout_reached(now, r) -> bool // now >= raised_at + ARBITER_WINDOW - fill_intent_inner now calls check_fill_guards (which uses fill_window_open) instead of a raw inline comparison, ensuring fill_intent and is_intent_fillable can never silently diverge (issue stellar-vortex-protocol#259). - slash_solver calls slash_eligible instead of a raw now >= deadline check. - Added docs/420-ledger-time-skew-analysis.md: full analysis of Stellar close-time semantics, worst-case drift table, per-boundary grace rationale, boundary test matrix, and evaluation of ledger-sequence-based deadlines (deferred — ABI-breaking with negligible security gain at typical drift). ## Issue stellar-vortex-protocol#409 — Balance-delta accounting for fee-on-transfer tokens Closes stellar-vortex-protocol#409 SEP-41 does not forbid transfer fees. Recording the requested amount rather than the actually-received amount creates phantom liabilities: the contract owes more than it holds, and the last withdrawer cannot be paid. This is a well-known DeFi insolvency vector. Changes: - Added pull_exact(env, token, from, amount) -> i128 helper. It reads balance_before, executes the transfer, reads balance_after, and returns after - before — the actual amount received regardless of token behaviour. - register_solver_inner uses pull_exact for the bond deposit. If a fee-on-transfer bond token delivers less than requested, the stored bond is corrected to the actual received delta so the solver cannot accumulate phantom bond credit. - begin_fill uses pull_exact for the dst_token escrow deposit. The stored fill_amount is corrected to the real escrowed value so release_fill / resolve_dispute pay out exactly what arrived. - open_dispute uses pull_exact for the dispute bond and rejects (panics with Error::BondTokenFeeOnTransfer = 39) if received != requested, because the bond token is admin-configured USDC which must not levy a fee; a discrepancy indicates misconfiguration and we refuse to under-collateralise the dispute. - fill_intent direct solver→user transfers are explicitly exempt: the contract is never the receiver, so balance-delta measurement is neither possible nor needed. The exemption and its rationale are documented in fill_intent_inner and in SECURITY.md §Fee-on-transfer tokens. - SECURITY.md: added §Fee-on-transfer tokens documenting the pull_exact pattern, the per-path decision (accept delta vs. reject), the direct-fill exemption, and the rebasing-token non-support note for bond tokens. ## Issue stellar-vortex-protocol#411 — Scope solver authorization with require_auth_for_args Closes stellar-vortex-protocol#411 With relayers and gasless flows, auth entries get passed around. An unscoped require_auth() on fill_intent lets a delegating invoker redirect a solver's signature to a different intent or a different fill amount — a concrete relay-reuse attack vector. Changes: - fill_intent: upgraded from solver.require_auth() to solver.require_auth_for_args((solver, intent_id, fill_amount)). This is the highest-value call site: the solver authorises an outgoing token transfer. Scoping to (solver, intent_id, fill_amount) ensures a signed auth entry cannot be replayed for a different intent or amount. The solver address is included so the tuple is globally unique across contracts. - accept_intent: require_auth_for_args((intent_id,)) — prevents redirecting the solver's accept signature to a different intent. - accept_intent_with_bond: require_auth_for_args((intent_id, bond_token)) — same, plus pins the bond denomination. - begin_fill: require_auth_for_args((solver, intent_id, fill_amount)) — tokens move into escrow, same rationale as fill_intent. - batch_accept_intent: require_auth_for_args((intent_ids,)) — solver's sig covers exactly this ordered set of intent IDs. - batch_fill_intent: require_auth_for_args((fills,)) — covers both the intent IDs and fill amounts; a signature for one fill list cannot be replayed for a different list. - docs/auth-audit.md: updated the Upgraded table to include all seven scoped entrypoints with their args and rationale. Updated Integration impact section with the new payload shapes solver bots must sign. ## Issue stellar-vortex-protocol#408 — Atomic timelocked source-chain onboarding proposal Closes stellar-vortex-protocol#408 Adding a new source chain previously required multiple separate admin operations across two contracts: add_allowed_src_chain, set_authorized_emitter on proof_registry, Axelar source config, and token-format rules. A partial configuration leaves the chain half-enabled — intents can be submitted but every fill fails with a proof error and the intents are slashed or expire, causing real user harm. Changes (intent_settlement/src/lib.rs): - Added ChainConfig struct: { name, wormhole_id, axelar_name, emitter, axelar_source, token_format } — the complete per-chain configuration bundle. - Added DataKey::PendingChainOnboarding(String) and DataKey::ChainConfig(String) storage keys. - Added Error::NoPendingChainOnboarding = 36, ChainAlreadyConfigured = 37, ChainNotConfigured = 38. - propose_chain_onboarding(config: ChainConfig): admin-only, queues the config under a 48-hour timelock. A new proposal for the same chain overwrites any prior pending one (and resets the clock). Emits chain_onboarding_proposed. - execute_chain_onboarding(name): callable after timelock elapses. Atomically: 1. Adds name to the src-chain allowlist. 2. Calls proof_registry.configure_chain(wormhole_id, emitter) in the same transaction so both contracts are configured together. 3. Writes a live ChainConfig entry for get_chain_config. Panics with NoPendingChainOnboarding, TimelockNotElapsed, or ChainAlreadyConfigured as appropriate. Emits chain_onboarding_executed. - execute_chain_offboarding(name): admin-only, immediate (no timelock). Removes the chain from the allowlist and calls proof_registry.remove_chain in one call. In-flight intents already submitted are unaffected — src_chain is validated at submit_intent time only, not at fill time. Existing proof records remain readable. Emits chain_offboarding_executed. - get_chain_config(name): view returning the live ChainConfig or None. Changes (proof_registry/src/lib.rs): - Added ProofKey::Configurator storage key. - set_configurator(configurator): admin-only, grants the given address (typically intent_settlement) the narrow configurator role. - get_configurator(): view. - configure_chain(chain_id, emitter): configurator-only, registers the authorized Wormhole emitter for chain_id. Called by execute_chain_onboarding. - remove_chain(chain_id): configurator-only, removes the authorized emitter for chain_id. Called by execute_chain_offboarding. Existing stored proofs are unaffected. - Added Error::ConfiguratorNotSet = 11. Documentation: - docs/132-supported-chains.md §6: atomic onboarding flow (propose + execute), offboarding behaviour (§6.2 — in-flight intents unaffected), and manual step-by-step reference (§6.3). ## Code quality - Removed all duplicate const definitions (DISPUTE_WINDOW, ARBITER_WINDOW, MAX_EXTENSION_DURATION, DEFAULT_MIN_BOND/FILL_WINDOW/INTENT_EXPIRY/ PROTOCOL_FEE_BPS, SLASH_COOLDOWN, CANCEL_COOLDOWN, MAX_BATCH_SIZE) that had been left in the file from earlier merge conflicts. - Removed duplicate placeholder implementations of begin_fill, open_dispute, resolve_dispute, release_fill, batch_fill_intent, batch_cancel_intent, and get_pending_admin that had been superseded by the full implementations. - Removed placeholder validate_proof stub that was superseded by the real cross-contract validation implementation.
|
@whiteghost0001 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! 🚀 |
…ce-delta-scoped-auth-chain-onboarding
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.
…, scoped auth, chain onboarding
Issue #420 — Ledger close-time skew: slash grace period and named boundary helpers Closes #420
Stellar ledger close times are validator-agreed and can drift from wall-clock time by up to ~7 s (two typical 5 s ledger intervals). A solver whose fill_intent transaction was broadcast before the deadline can land in a ledger whose close time is past the deadline and be slashed unfairly.
Changes:
fill_window_open(now, deadline) -> bool // now < deadline (exclusive)
slash_eligible(now, deadline) -> bool // now >= deadline + SLASH_GRACE_SECS
dispute_window_open(now, ddl) -> bool // now < dispute_deadline
arbiter_timeout_reached(now, r) -> bool // now >= raised_at + ARBITER_WINDOW
instead of a raw inline comparison, ensuring fill_intent and is_intent_fillable
can never silently diverge (issue [High] Add an
is_intent_fillableconvenience view #259).close-time semantics, worst-case drift table, per-boundary grace rationale,
boundary test matrix, and evaluation of ledger-sequence-based deadlines
(deferred — ABI-breaking with negligible security gain at typical drift).
Issue #409 — Balance-delta accounting for fee-on-transfer tokens Closes #409
SEP-41 does not forbid transfer fees. Recording the requested amount rather than the actually-received amount creates phantom liabilities: the contract owes more than it holds, and the last withdrawer cannot be paid. This is a well-known DeFi insolvency vector.
Changes:
Issue #411 — Scope solver authorization with require_auth_for_args Closes #411
With relayers and gasless flows, auth entries get passed around. An unscoped require_auth() on fill_intent lets a delegating invoker redirect a solver's signature to a different intent or a different fill amount — a concrete relay-reuse attack vector.
Changes:
Issue #408 — Atomic timelocked source-chain onboarding proposal Closes #408
Adding a new source chain previously required multiple separate admin operations across two contracts: add_allowed_src_chain, set_authorized_emitter on proof_registry, Axelar source config, and token-format rules. A partial configuration leaves the chain half-enabled — intents can be submitted but every fill fails with a proof error and the intents are slashed or expire, causing real user harm.
Changes (intent_settlement/src/lib.rs):
Changes (proof_registry/src/lib.rs):
Documentation:
Code quality
Summary
Related issue
Type of change
Component
vortex-contract)vortex-backend)vortex-frontend)Checklist
Screenshots / notes