Skip to content

feat(intent_settlement): batch fill/cancel, paginated list_solvers, Solana e2e (+ compile-baseline fix) - #311

Open
ushpraise wants to merge 2 commits into
stellar-vortex-protocol:mainfrom
ushpraise:feat/batch-ops-solver-listing-solana-e2e
Open

feat(intent_settlement): batch fill/cancel, paginated list_solvers, Solana e2e (+ compile-baseline fix)#311
ushpraise wants to merge 2 commits into
stellar-vortex-protocol:mainfrom
ushpraise:feat/batch-ops-solver-listing-solana-e2e

Conversation

@ushpraise

@ushpraise ushpraise commented Aug 28, 2026

Copy link
Copy Markdown

Closes #201
Closes #199
Closes #198

Why this PR is larger than three features

intent_settlement did not compile on main (cargo build → 50 errors), so none of #198/#199/#201 could be built, tested, or land green on their own. Restoring a compiling, green baseline is the substance of the open issues #202 (undefined constants), #203 (Error enum), #204 (DataKey enum), so this PR closes those too. Commit 1 is the baseline restoration; commit 2 is the three features + docs.

Commit 1 — restore a compiling, green baseline (#202, #203, #204)

Area Problem on main Fix
Constants (#202) ~12 names used but never declared (MAX_BATCH_SIZE, SLASH_COOLDOWN, CANCEL_COOLDOWN, DEFAULT_*, MAX_PROTOCOL_FEE_BPS, MIN_FILL_WINDOW_SECS, MIN_INTENT_EXPIRY_SECS, MIN_BOND_FLOOR, MAX_EXTENSION_DURATION) Declared with rationale comments (table below)
Error enum (#203) discriminant 22 used ×3, 23 used ×2 (won't compile); 6 variants referenced but missing Renumbered sequentially to 36; added AmountTooLarge, InvalidConfig, TimelockNotElapsed, NoPendingAdminTransfer, NoPendingDstTokenChange, CancelCooldownNotExpired, BatchTooLarge, ExtensionAlreadyGranted; replaced the Error::ZeroAmount "reuse nearest" placeholders
DataKey enum (#204) 9 variants referenced but missing Added Config, PendingAdmin, PendingDstTokenAdd, PendingDstTokenRemove, AllowedDstTokenList, MinBondMultiplier, UserIntents, CancelCooldown, ExtensionGranted
validate_src_token called String::get, which soroban_sdk::String does not have Reads bytes via copy_into_slice into a fixed buffer; accepted formats unchanged (EVM 0x+40hex; Solana base58 32–44)
compute_reputation_score pub contract fn taking &SolverRecorderror: unsupported type pub(crate) (not an entrypoint)
fill_intent dst transfer + fee transfer written three times each (a real fund-loss bug) One CEI-ordered pass: state committed, then one user transfer + one fee transfer
get_pending_admin PendingAdmin had no reader Added the view
test.rs / proptest_bond.rs merge damage: unclosed fns, empty fn bodies, a duplicate test name, a stale duplicate import, a SolverRecord literal missing a field; several pre-#127 tests used "0xabc" placeholder src_tokens; get_stats() destructured as a 2-tuple Repaired; proptest advances past SLASH_COOLDOWN between accept+slash steps

Chosen constant values (please sanity-check per #202)

Constant Value Rationale
DEFAULT_MIN_BOND / DEFAULT_FILL_WINDOW / DEFAULT_INTENT_EXPIRY / DEFAULT_PROTOCOL_FEE_BPS = MIN_BOND / FILL_WINDOW / INTENT_EXPIRY / PROTOCOL_FEE_BPS (50 USDC / 300 s / 1800 s / 5 bps) Aliased to the historical hard-coded constants so initialize() behaviour is byte-for-byte unchanged
MAX_PROTOCOL_FEE_BPS 1_000 10% ceiling — matches the value already documented in set_config's doc comment
MIN_FILL_WINDOW_SECS 60 matches set_config doc
MIN_INTENT_EXPIRY_SECS 300 matches set_config doc
MIN_BOND_FLOOR 10_000_000 1 USDC (7 decimals) absolute floor
SLASH_COOLDOWN 3_600 (1 h) matches the existing slash_cooldown_expires_after_time_window test's pass_time(3600)
CANCEL_COOLDOWN 60 anti-spam gap between a user's cancels
MAX_BATCH_SIZE 20 covers realistic solver batching, well inside per-tx limits
MAX_EXTENSION_DURATION 300 same magnitude as FILL_WINDOW

There are upstream branches (fix/error-discriminant-collision, fix/restore-dst-token-not-allowed-variant) touching the same enums — happy to rebase onto whichever lands first.


Commit 2 — the three features

#199batch_fill_intent / batch_cancel_intent

  • batch_fill_intent(solver, fills: Vec<(BytesN<32>, i128)>), batch_cancel_intent(user, intent_ids: Vec<BytesN<32>>); both capped at MAX_BATCH_SIZE, whole-batch revert on any failure.
  • Auth bug also fixed for the existing batch_submit_intent / batch_accept_intent: require_auth() may only be called once per address per invocation, so calling the public single-item entrypoints in a loop failed with Auth, ExistingValue on item 2. All four batch fns now authorise the actor once and call un-gated *_inner bodies.
  • batch_cancel_intent checks + stamps CANCEL_COOLDOWN once for the call, so a user clears all their open intents in one tx.
  • Mixed full/partial fills in one batch keep OpenIntents / active_intents correct.

#198list_solvers(start, limit)

  • New DataKey::SolverList (instance storage, Vec<Address>) kept in sync by register_solver / deregister_solver using the add_to_/remove_from_dst_token_list pattern (Add a view listing all currently allowed dst_tokens #117), including the "already present" guard — deregister + re-register never duplicates.
  • limit clamped to MAX_BATCH_SIZE; empty page past the end.
  • Storage-cost trade-off documented on DataKey::SolverList in the style of the OpenIntents comment.
  • indexer/reference-indexer.js comment points at list_solvers as the alternative to full event replay.

#201 — Solana, end to end

  • validate_src_token's Solana branch (base58 alphabet excluding 0 O I l, 32–44 chars, no 0x prefix) is verified against the Solana SDK (Pubkey = 32-byte ed25519 key, bs58 Bitcoin alphabet) and the published SPL mint list — noted in docs/132-supported-chains.md §3.2.
  • Docs: 132-supported-chains.md §2 Solana → Supported; §3.2 rewritten (rules table, alphabet, decimals-vary warning, CLI example); new §4.8 SPL-mint table (USDC 6, USDT 6, wSOL 9, JitoSOL 9, BONK 5). solver-integration-guide.md gains a per-source-chain src_token/src_amount table incl. resolving a Solana mint and reading its non-uniform decimals. README: "planned" removed, Solana rows in the decimal table.
  • Also documented that avalanche/bsc src_tokens are not yet format-checked on-chain (only the 5 EVM chains + Solana are).

Tests & CI

  • 14 new tests: list_solvers register/deregister/dedupe/pagination boundaries; batch_fill mixed full+partial, atomic revert, size guard; batch_cancel one-cooldown, atomic revert, size guard; Solana end-to-end with the allowlist enabled, out-of-range length rejection, 0x-prefix rejection.
  • wasm-size CI job: the contract is ~93 KB raw before any of this. [profile.release] now sets lto = true (→ 68 KB raw) and the job installs binaryen and measures the wasm-opt -Oz artifact — the size that actually deploys — against a 63 000-byte budget (under Soroban's 64 KB hard limit). It measures 61 456 bytes. A real size-reduction pass (dead-code audit / splitting rarely-used entrypoints) is still owed and noted in the workflow + CHANGELOG.

Local run (test output)

$ cargo fmt --all -- --check      # clean
$ cargo clippy --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s)   # 0 warnings
$ cargo test
test result: ok. 158 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 99.45s
$ PROPTEST_CASES=256 cargo test --features testutils -- bond_conservation
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 157 filtered out; finished in 81.96s
$ cargo build --target wasm32-unknown-unknown --release && wasm-opt -Oz ...
raw=68149  optimized=61456  budget=63000

…vortex-protocol#202, stellar-vortex-protocol#203, stellar-vortex-protocol#204)

The crate did not compile on main: lib.rs referenced ~12 undefined
constants, 9 undefined DataKey variants and 6 undefined Error variants,
the Error enum had duplicate discriminants (22 x3, 23 x2),
validate_src_token called a non-existent String::get, compute_reputation_score
was a pub contract fn taking a non-ABI &SolverRecord, and fill_intent
transferred the fill amount and fee three times each. test.rs had also
been mangled by bad merges (unclosed fns, empty fn bodies, a duplicate
test name, a stale duplicate import, a SolverRecord literal missing a
field). The proptest crate never had its dev-deps written to Cargo.lock.

Constants (stellar-vortex-protocol#202) — declared with rationale comments matching the existing
style; DEFAULT_* alias the historical hard-coded values so initialize()
behaviour is unchanged:
  DEFAULT_MIN_BOND/FILL_WINDOW/INTENT_EXPIRY/PROTOCOL_FEE_BPS  = MIN_BOND / 300 / 1800 / 5
  MAX_PROTOCOL_FEE_BPS = 1000 (10% ceiling, matches set_config doc)
  MIN_FILL_WINDOW_SECS = 60,  MIN_INTENT_EXPIRY_SECS = 300
  MIN_BOND_FLOOR = 10_000_000 (1 USDC)
  SLASH_COOLDOWN = 3600 (matches slash_cooldown_expires_after_time_window)
  CANCEL_COOLDOWN = 60,  MAX_BATCH_SIZE = 20,  MAX_EXTENSION_DURATION = 300

Error enum (stellar-vortex-protocol#203) — removed the collisions, renumbered sequentially to 36,
added AmountTooLarge, InvalidConfig, TimelockNotElapsed,
NoPendingAdminTransfer, NoPendingDstTokenChange, CancelCooldownNotExpired,
BatchTooLarge, ExtensionAlreadyGranted. Replaced the "reuse nearest"
Error::ZeroAmount placeholders in the batch guards and request_extension.

DataKey enum (stellar-vortex-protocol#204) — added Config, PendingAdmin, PendingDstTokenAdd,
PendingDstTokenRemove, AllowedDstTokenList, MinBondMultiplier, UserIntents,
CancelCooldown, ExtensionGranted.

Other baseline fixes:
- validate_src_token: rewritten over a fixed byte buffer via
  String::copy_into_slice; same accepted formats (EVM 0x+40hex, Solana
  base58 32-44).
- fill_intent: collapsed the triplicated transfer/fee blocks into one
  CEI-ordered pass (state committed, then one user transfer + one fee
  transfer).
- compute_reputation_score is now pub(crate) (not a contract entrypoint).
- added get_pending_admin view (PendingAdmin had no reader).
- test.rs: repaired the mangled functions, fixed get_stats() destructuring
  (now a 3-tuple), Vec::get Option handling, and pre-stellar-vortex-protocol#127 tests that used
  "0xabc" placeholder src_tokens; proptest advances past SLASH_COOLDOWN
  between accept+slash steps.

cargo build / clippy --all-targets -D warnings / test: green (145 passed).
…olana e2e

Closes stellar-vortex-protocol#198
Closes stellar-vortex-protocol#199
Closes stellar-vortex-protocol#201

## stellar-vortex-protocol#199 — batch_fill_intent / batch_cancel_intent

- `batch_fill_intent(solver, fills: Vec<(BytesN<32>, i128)>)` and
  `batch_cancel_intent(user, intent_ids: Vec<BytesN<32>>)`, both capped at
  MAX_BATCH_SIZE and reverting the whole batch on any failure (Soroban
  whole-transaction atomicity), matching batch_submit_intent /
  batch_accept_intent.
- Mixed outcomes in one batch_fill are fine: some pairs complete an intent
  (Filled), others only advance it (PartiallyFilled + re-opened);
  OpenIntents / active_intents bookkeeping stays correct across the mix.
- batch_cancel_intent checks and stamps the per-user CANCEL_COOLDOWN once
  for the whole call (via new cancel_intent_core / check_/stamp_ helpers),
  so a user can clear all their open intents in one tx instead of one per
  cooldown window.
- Auth fix: require_auth() is one-shot per address per invocation, so the
  loop bodies now call un-gated `*_inner` functions and each batch
  authorises the actor exactly once. This also fixes the pre-existing
  batch_submit_intent / batch_accept_intent, which failed with
  `Auth, ExistingValue` for any batch of more than one item.
- Dedicated `Error::BatchTooLarge`, checked before any auth/state change.

## stellar-vortex-protocol#198 — Paginated, enumerable solver listing

- `list_solvers(start: u32, limit: u32) -> Vec<Address>`, limit clamped to
  MAX_BATCH_SIZE, backed by a new instance-storage `DataKey::SolverList`
  kept in sync by register_solver (append-if-absent) / deregister_solver
  (remove) — mirrors the add_to/remove_from_dst_token_list pattern (stellar-vortex-protocol#117),
  including the "already present" guard so re-registration never duplicates.
- Storage-cost trade-off documented on `DataKey::SolverList` in the style of
  the OpenIntents comment.
- `indexer/reference-indexer.js`: comment noting list_solvers as the
  on-chain alternative to full solver_registered/solver_deregistered replay.

## stellar-vortex-protocol#201 — Solana as a fully-supported source chain

- validate_src_token already enforces the Solana rules (base58 alphabet
  excluding 0/O/I/l, 32-44 chars, no 0x prefix) → InvalidSrcToken. No
  logic change needed beyond the baseline compile fix; this change is docs
  + tests + removing the "planned" markers.
- docs/132-supported-chains.md: Solana promoted to "Supported"; §3.2
  rewritten with the on-chain validation rules, base58 alphabet, the
  32-byte-pubkey / bs58 verification source, a CLI example, and a
  decimals-vary warning; new §4.8 SPL-mint table (USDC 6, wSOL 9, BONK 5).
  Also noted that avalanche/bsc src_tokens are not yet format-checked.
- docs/solver-integration-guide.md: per-source-chain table for interpreting
  src_token / src_amount, incl. resolving a Solana SPL mint and reading its
  non-uniform decimals.
- README: "planned" removed from the Supported Source Chains table; Solana
  rows added to the decimal-normalization table; MAX_AMOUNT overflow note
  corrected to Error::AmountTooLarge.

## CI

- `[profile.release]` gains `lto = true` (raw wasm 95 KB -> 68 KB).
- wasm-size job now installs binaryen and measures the `wasm-opt -Oz`
  artifact (~61.4 KB) — the size that actually deploys — against a 63 000
  byte budget, under Soroban's 64 KB hard limit. The contract has grown
  close to the limit; a dedicated size-reduction pass is still owed.

## Tests

14 new tests. list_solvers register/deregister/dedupe/pagination; batch
fill mixed full+partial, atomic revert, size guard; batch cancel
one-cooldown, atomic revert, size guard; Solana end-to-end with the
allowlist enabled (EVM + Solana), out-of-range length rejection, 0x-prefix
rejection.

Local: cargo fmt --check, clippy --all-targets -D warnings, cargo test
(158 passed), PROPTEST_CASES=256 bond_conservation (passed), wasm-opt size
61 456 <= 63 000.
@drips-wave

drips-wave Bot commented Aug 28, 2026

Copy link
Copy Markdown

@ushpraise 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! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant