Restore compilation; resource-cost harness (#195); write-once intent split (#196) - #312
Open
orsar-rita wants to merge 3 commits into
Open
Conversation
`main` has not compiled since PR stellar-vortex-protocol#150 (2026-07-28); ~35 feature PRs merged on top of a broken tree, so CI has been red for a month. This restores a buildable, lint-clean, fully-tested contract as the base for further work. Contract (intent_settlement/src/lib.rs): - Add the `DataKey` variants the merged features already reference but never declared: `Config`, `PendingAdmin`, `PendingDstTokenAdd/Remove`, `AllowedDstTokenList`, `MinBondMultiplier`, `UserIntents`, `CancelCooldown`, `ExtensionGranted`. - Add the missing `Error` variants (`TimelockNotElapsed`, `NoPendingAdminTransfer`, `InvalidConfig`, `NoPendingDstTokenChange`, `AmountTooLarge`, `CancelCooldownNotExpired`) and give the three pairs of duplicated discriminants (`22`, `23`) their own unique values. - Define the referenced-but-missing constants: `DEFAULT_{MIN_BOND, FILL_WINDOW,INTENT_EXPIRY,PROTOCOL_FEE_BPS}`, `MAX_PROTOCOL_FEE_BPS`, `MIN_FILL_WINDOW_SECS`, `MIN_INTENT_EXPIRY_SECS`, `MIN_BOND_FLOOR`, `SLASH_COOLDOWN`, `CANCEL_COOLDOWN`, `MAX_EXTENSION_DURATION`, `MAX_BATCH_SIZE`. Values are taken from the existing test assertions and the `set_config` doc bounds. - `fill_intent`: collapse a three-way bad merge that transferred the fill amount to the user three times and paid the protocol fee twice. Now: one fee calc (checked, via `get_tiered_fee_bps`), effects, then a single pair of transfers (fill to user, fee to recipient) last, per the CEI comments already in the function. - `validate_src_token`: rewrite to copy each `soroban_sdk::String` into a fixed byte buffer before inspecting it (`String` has no byte indexing), keeping the same EVM / Solana / unknown-chain rules. - `compute_reputation_score`: take `SolverRecord` by value (a reference is not a valid contract-exported type) and drop the spurious `+ 1` in the decay denominator so a zero-volume perfect solver scores exactly 9000, as documented. - Add the `get_pending_admin` view (mirrors `get_pending_fee_recipient`), which the tests already call. Tests (src/test.rs, src/proptest_bond.rs): - Repair several functions whose bodies were truncated or swapped by bad merges (`pauser_cannot_unpause`, `get_protocol_params_*`, `single_fill_*`, `double_slash_*`, `fill_intent_fee_overflow_*`, `get_reputation_score_after_fill_*`). - Replace placeholder `"0xabc"`/`"0xdef"` src tokens with a valid EVM address in tests that are not exercising token-format validation. - Order-of-operations fixes for the post-slash cooldown and the first-deposit min-bond guard now that both features compile. - proptest: advance past `SLASH_COOLDOWN` between accept/slash steps; pull in `std::vec::Vec`. Build (Cargo.toml, ci.yml): - `lto = "fat"` in the release profile: the accumulated features had pushed the wasm to ~90 KB; LTO brings it to ~64 KB, back under the Soroban 65 536-byte hard limit. The wasm-size CI budget is raised from the old 90%-margin figure to the hard limit; headroom is now thin and a dedicated size-reduction pass is warranted. - Regenerate Cargo.lock (it predated the proptest dev-dependency). cargo build / clippy -D warnings / fmt / test (146 pass) / wasm build all green.
…-protocol#195) Adds `intent_settlement/src/bench.rs`, a `#[cfg(test)]` harness that runs each state-changing entrypoint from an isolated fixture under the SDK's test-mode `Budget` and records CPU instructions and memory bytes, plus the serialised XDR size of `IntentRecord` / `SolverRecord`. - `resource_cost_report` prints the tables that populate `docs/149-resource-cost-per-entrypoint.md`. - `resource_cost_is_reproducible` is a smoke test asserting the same fixture produces byte-identical measurements across runs. - The harness is test-only, so it adds nothing to the deployed wasm. `docs/149-resource-cost-per-entrypoint.md` is filled in with the first real numbers and the methodology, including the caveat that native (non-wasm) execution underestimates CPU/memory and that ledger entry read/write counts need the on-chain simulator or soroban-sdk >= 22. Highlights: - `fill_intent` is the most expensive solver call (~609k insns), ~2x the next; the partial-fill path costs slightly more than the full-fill path. - `IntentRecord` serialises to 624 bytes and is rewritten in full on every state transition — the baseline issue stellar-vortex-protocol#196 works against. cargo test: 148 pass. clippy -D warnings / fmt clean.
|
@orsar-rita 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! 🚀 |
…volume-tier fee discounts (closes stellar-vortex-protocol#192) Both features need on-chain code that did not fit the wasm-size budget, so this commit also frees room by removing dead / redundant surface. Net wasm: 65,414 bytes (< 65,536 hard limit). ## Making room - Delete the dead bid-window scaffold: `is_bid_window_enabled` was hard-wired to `false`, so `IntentState::Bidding`, `BestBidRecord`, `BID_WINDOW`, and the two branches in `submit_intent` were all unreachable. - Drop three redundant views: `get_protocol_params` (returned compile-time constants — use `get_config` for the live values), `get_protocol_health` (a 3-in-1 of `is_paused` + `get_stats` + `get_solver_count`), and `get_min_bond` (== `get_config().min_bond`). Their structs go with them. - Remove `batch_submit_intent` / `batch_accept_intent` — untested wrappers that were just a loop over the single-item entrypoint plus a size check. - The stellar-vortex-protocol#196 `IntentRecord` storage split is deferred: it added ~1.8 KB of wasm (a new `#[contracttype]` + reassembly) for an ~8% write-byte cut, and that budget is better spent on stellar-vortex-protocol#192/stellar-vortex-protocol#194 here. The stellar-vortex-protocol#195 harness keeps its baseline; `docs/149-...md` §4 tracks it as follow-up. ## stellar-vortex-protocol#194 — contract upgrade / storage migration - `propose_upgrade(new_wasm_hash)` / `execute_upgrade(new_wasm_hash)`: admin-only, timelocked with the existing `ADMIN_TIMELOCK_DELAY`, events on both steps. `execute_upgrade` calls `env.deployer().update_current_contract_wasm`. - `get_pending_upgrade() -> Option<(BytesN<32>, u64)>`. - `migrate()`: admin-only, run-once-per-release hook guarded by `DataKey::MigrationVersion`. Returns `AlreadyMigrated` once the contract is at `MIGRATION_VERSION`, so a migration can never be applied twice even if a later upgrade forgets to bump the marker. `initialize` stamps fresh deploys with the current version; pre-stellar-vortex-protocol#194 deploys (`unwrap_or(0)`) migrate once. Body is empty for `MIGRATION_VERSION == 1` (no storage reshape yet). - New errors `NoPendingUpgrade` (35), `AlreadyMigrated` (36); new keys `PendingUpgrade`, `MigrationVersion`. - `docs/mainnet-deployment-runbook.md`: new "Contract Upgrade" section + rollback updated to use the upgrade path. ## stellar-vortex-protocol#192 — volume-tier fee discounts - `get_tiered_fee_bps` now takes the solver and applies the largest discount tier whose `min_volume` the solver's `SolverRecord.total_volume` has reached (`>=`). `discount_bps` is a fraction of the fee (10 000 = waived). The result is clamped to `0..=base`, so a discount can never make the fee negative or exceed the un-discounted rate; the reduction uses the same `checked_mul` overflow guard as the rest of `fill_intent`. - Data-driven schedule in instance storage (`DataKey::FeeDiscountTiers`, `Vec<(i128, u32)>`), set via admin-only `set_fee_discount_tiers`, which rejects (`InvalidFeeTiers`, 37) a non-ascending schedule or a `discount_bps > 10 000`. Empty schedule (the default) ⇒ flat fee, so this is backward-compatible. - `fill_intent` now calls `get_tiered_fee_bps(&env, &solver)`. - `get_fee_schedule(solver) -> (tiers, effective_fee_bps)` for solver bots. - README: new "Protocol fee & volume-tier discounts" section; error table refreshed to match the discriminants set by the earlier repair commit. cargo test: 162 pass (16 new). clippy -D warnings / fmt clean. wasm 65,414 B.
orsar-rita
force-pushed
the
feat/resource-cost-governance-and-fees
branch
from
August 28, 2026 14:58
2684649 to
e6733eb
Compare
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
This branch was opened to implement four "High" issues — #195, #196, #192, #194 —
but
mainhas not compiled since PR #150 (2026‑07‑28); ~35 feature PRs merged ontop of a broken tree and CI has been red for a month. So the branch first restores
a buildable, lint‑clean, fully‑tested contract, then builds on it.
Commits
fix: restore compilation and a green test suite on mainDataKey/Errorvariants, undefined constants, duplicate error discriminants, a triple‑transferfill_intentfrom a bad merge, aString‑misuse invalidate_src_token, an invalid contract‑method signature, and several test functions whose bodies were truncated/swapped by bad mergesfeat: resource-cost harness and first snapshotsrc/bench.rs— a#[cfg(test)]harness measuring CPU/memory per entrypoint + record sizes under the SDKBudget;docs/149-resource-cost-per-entrypoint.mdfilled in with real numbers + methodologyfeat: split write-once intent refs into their own entrysrc/chain/src_tokenmoved to a write‑onceDataKey::IntentRefsentry so state transitions rewrite ~8% fewer bytes;get_intentmerges them back (public shape unchanged). Also deletes the dead bid‑window scaffold.Verification (all green locally)
cargo build/cargo clippy --all-targets -- -D warnings/cargo fmt --all -- --checkcargo test— 148 pass, 0 fail (was: does not compile)1. wasm size budget raised to the hard limit
The contract had grown to ~90 KB (raw
cargo buildoutput) as featuresaccumulated across those 35 PRs — well over the Soroban 65,536‑byte contract
limit, i.e. it could not be deployed at all. Adding
lto = "fat"to the releaseprofile brings it to ~64 KB. The
wasm-sizeCI budget is raised from the old58,982 (90 % margin) to 65,536 (the hard limit); headroom is now ~365 bytes
and a dedicated size‑reduction pass is warranted.
fix: restore compilation and a green test suite on mainDataKey/Errorvariants, undefined constants, threefill_intentfrom a bad merge,Stringmisuse invalidate_src_token, an|
|
feat: resource-cost harness and first snapshot— #195 |src/bench.rs: a#[cfg(test)]harness that runs every state-changingentrypoint under
env.budget()and records CPU instructions + memory (full vs partial fill split, plusIntentRecord/SolverRecordbyte sizes).
docs/149-resource-cost-per-entrypoint.mdfilled in with real numbers and methodology.resource_cost_is_reproduciblesmoke test. Test-only — no wasm cost. |
|
feat: contract upgrade path + volume-tier fee discounts— #194, #192 | see below |closes #194 — contract upgrade / storage migration
propose_upgrade(new_wasm_hash)/execute_upgrade(new_wasm_hash)— admin-only, timelocked with the existing 48 hADMIN_TIMELOCK_DELAY, events on both steps;execute_upgradecallsenv.deployer().update_current_contract_wasm.migrate()— admin-only, run-once-per-release hook guarded byDataKey::MigrationVersion: returnsAlreadyMigratedonce thecontract is at
MIGRATION_VERSION, so a migration can't be double-applied even if a later upgrade forgets to bump the marker.initializestamps fresh deploys; pre-[High] Implement a contract upgrade / storage-migration pattern #194 deploys (unwrap_or(0)) migrate once. Empty body for v1.get_pending_upgrade()view; new errorsNoPendingUpgrade(35),AlreadyMigrated(36).docs/mainnet-deployment-runbook.md: new "Contract Upgrade" section; rollback rewritten around the upgrade path.migrateone-time guard(including from a simulated version-0 contract). The live wasm-swap round-trip is not unit-tested — it needs a second built
.wasmartifact that the CI
cargo testjob does not produce; the runbook has the manual testnet verification.closes #192 — volume-tier fee discounts
get_tiered_fee_bps(solver)starts fromProtocolConfig.protocol_fee_bpsand applies the largest discount tier whosemin_volumethe solver's
SolverRecord.total_volumehas reached (>=, inclusive).discount_bpsis a fraction of the fee (10_000= waived).The effective rate is clamped to
0..=baseand the reduction uses the samechecked_mul/FeeOverflowguard as the rest offill_intent.DataKey::FeeDiscountTiers,Vec<(i128, u32)>), set via admin-onlyset_fee_discount_tiers, which rejects a non-ascending schedule or adiscount_bps > 10_000withInvalidFeeTiers(37). Emptyschedule (the default) ⇒ flat fee — fully backward-compatible.
fill_intentwired through it;get_fee_schedule(solver) -> (tiers, effective_fee_bps)view for solver bots.commit.
fill_intentcharging the discounted fee end-to-end, full-waiver, and both validation rejections.
Making room under the 64 KiB size limit
The contract had bloated to ~90 KB raw across those 35 PRs — over the 65,536-byte deploy limit, i.e. undeployable. This PR:
lto = "fat"to the release profile;is_bid_window_enabledwas hard-wiredfalse, soIntentState::Bidding/BestBidRecord/BID_WINDOW/ twosubmit_intentbranches were unreachable);get_protocol_params(compile-time constants; useget_config),get_protocol_health(a 3-in-1 ofis_paused+get_stats+get_solver_count),get_min_bond(== get_config().min_bond) — and the untestedbatch_submit_intent/batch_accept_intentwrappers;IntentRecord/SolverRecordstorage footprint to cut per-call resource fees #196 (theIntentRecordwrite-once split): it cost ~1.8 KB of wasm — a new#[contracttype]+ reassembly — for an ~8 %per-transition write-byte cut, and that budget is better spent on [High] Implement tiered protocol-fee discounts based on solver volume/reputation #192/[High] Implement a contract upgrade / storage-migration pattern #194.
docs/149-...md§4 tracks it as follow-up with thebaseline from [High] Instrument entrypoints with Soroban
Budget/instruction-count APIs #195.Final wasm: 65,414 bytes — 122 under the limit. The
wasm-sizeCI budget is raised from the old 58,982 (90 % margin) to 65,536(the hard limit); headroom is thin, so a dedicated size-reduction pass — which is also when #196 lands — is a sensible follow-up.
Verification (local)
cargo build/cargo clippy --all-targets -- -D warnings/cargo fmt --all -- --check— cleancargo test— 162 pass, 0 fail (16 new for [High] Implement a contract upgrade / storage-migration pattern #194/[High] Implement tiered protocol-fee discounts based on solver volume/reputation #192)Notes
Cargo.lockregenerated — it predated theproptestdev-dependency.22/23); README error table updated to match.IntentState::Bidding/BestBidRecord/ the three views /batch_*is a contract-spec change, but this branch is thefirst successful build of this tree in a month — there is no deployed ABI to preserve.