From 7e4abcb4f7b9c5896fd5aa6c70c5bc86076419d9 Mon Sep 17 00:00:00 2001 From: thelux134 Date: Sun, 27 Sep 2026 13:14:11 +0100 Subject: [PATCH 1/2] Consolidate duplicate functions, fix fill_intent CEI, add struct fields, implement validate_proof ## Issue #343: Collapse duplicate batch_fill_intent, batch_cancel_intent, get_pending_admin Removed duplicate function definitions that differed in authorization strategy. The correct implementations authorize once per batch at the beginning (require_auth() once), avoiding Soroban's restriction on multiple require_auth() calls for the same address in one invocation. Consolidated to single CEI-correct versions in each case: batch_fill_intent() calls require_auth() once then fill_intent_inner() for each fill; batch_cancel_intent() calls require_auth() once with unified cooldown stamping; get_pending_admin() read-only accessor returns pending admin tuple. Deleted incorrect duplicates that called require_auth per-item or per-call, which would panic at runtime. ## Issue #344: Fix fill_intent transferring output twice with undefined fee variable Removed the premature DST token transfer at line 2252 (before CEI) that violated the carefully-sequenced state-first pattern the contract relies on for reentrancy protection. Removed the undefined protocol_fee_bps variable calculation at line 2255 that would not compile. Now the function follows strict CEI ordering: all storage writes (intent state, solver record, volume totals) happen before any external calls. The single transfer to user + fee splits to recipient/referrer both execute at the end via the properly-computed fee from get_tiered_fee_bps(), guaranteeing atomicity and reentrancy safety. ## Issue #345: Add missing IntentRecord and ProtocolConfig fields for referral and slash-cycle features Added referrer: Option
to IntentRecord to support the referral fee-share logic in #281. Added slash_cycles: u32 to track slash count and enforce the per-solver bound. Added referral_share_bps: i128 to ProtocolConfig to control the percentage of fees split to referrers (capped at 10_000 bps = 100%). Added max_slash_cycles: u32 to ProtocolConfig to set the forced-deregistration limit per solver. These fields were referenced in fee-split and slash-rate code but did not exist on the structs, causing compilation failures for merged features #281 and #241. ## Issue #346: Replace no-op validate_proof stub with real implementation Deleted the second validate_proof definition that did nothing but fetch the registry address and return ("In production, this would..."), leaving the first implementation as the single source of truth. The remaining validate_proof fetches ProofRegistryClient::get_fresh_proof, validates src_chain_id against the intent via wormhole_chain_id(), and checks src_amount >= intent.src_amount. Proof-gated fills now actually verify proof.src_chain and proof.src_amount, closing the gap where require_proof=true silently gated on nothing. Closes #343 Closes #344 Closes #345 Closes #346 --- intent_settlement/src/lib.rs | 93 ++++++------------------------------ 1 file changed, 14 insertions(+), 79 deletions(-) diff --git a/intent_settlement/src/lib.rs b/intent_settlement/src/lib.rs index 71c0018..f04ac47 100644 --- a/intent_settlement/src/lib.rs +++ b/intent_settlement/src/lib.rs @@ -421,6 +421,12 @@ pub struct ProtocolConfig { pub protocol_fee_bps: i128, /// Maximum number of intents a single solver may accept simultaneously (issue #230). pub max_active_intents_per_solver: u32, + /// #281: Percentage of the protocol fee paid to the intent referrer, in bps. + /// 10_000 bps = 100% (entire fee to referrer), 0 = none. Capped at 10_000. + pub referral_share_bps: i128, + /// #241: Maximum number of slash cycles a solver may undergo before + /// forced deregistration. 0 = unlimited (default). Enforced by `slash_solver`. + pub max_slash_cycles: u32, } /// A user's cross-chain swap intent @@ -479,6 +485,14 @@ pub struct IntentRecord { /// solver's tier now. `0` (Unranked) whenever there is no assignee /// (`Open` / `PartiallyFilled`) or the registry integration is unset. pub solver_tier: u32, + + /// #281: Optional referrer address eligible for fee-share splits. + /// Set by `submit_intent` if provided, `None` by default. + pub referrer: Option
, + + /// #241: Maximum number of slash cycles the solver may incur before + /// forced unbonding. Defaults to 0 (unlimited). Enforced by `slash_solver`. + pub slash_cycles: u32, } #[contracttype] @@ -2247,18 +2261,7 @@ impl IntentSettlement { Self::validate_proof(&env, &intent, &intent_id); } - // Deliver this fill's tokens to the user. - let dst_client = token::Client::new(&env, &intent.dst_token); - dst_client.transfer(&solver, &intent.user, &fill_amount); - - // Solver also pays the protocol fee on each fill. - let fee = fill_amount * protocol_fee_bps / 10_000; // ── Effects first (CEI) ────────────────────────────────────────────── - // Accumulate the fill, update intent state, and write all storage changes - // *before* any external token transfer executes. A hostile SEP-41 token - // that attempts to re-enter fill_intent or slash_solver during the transfer - // would see the intent already Filled/PartiallyFilled and be rejected. - // Compute protocol fee with explicit checked arithmetic (#269 / #31). // Taking the fee from the solver — rather than clawing it back from the // user — keeps the user's received amount at or above `min_dst_amount`. @@ -3629,50 +3632,6 @@ impl IntentSettlement { Self::stamp_cancel_cooldown(&env, &user, now); } - /// Fill multiple intents in a single transaction. - /// - /// Each element of `fills` is `(intent_id, fill_amount)`. All fills are - /// processed atomically — if any individual fill fails the entire batch - /// reverts. - /// - /// Bounded by [`MAX_BATCH_SIZE`] to prevent resource exhaustion. - /// See `docs/149-resource-cost-per-entrypoint.md` for the per-item - /// write-entry analysis that justifies the chosen limit. - pub fn batch_fill_intent( - env: Env, - solver: Address, - fills: soroban_sdk::Vec<(BytesN<32>, i128)>, - ) { - if fills.len() > MAX_BATCH_SIZE as usize { - panic_with_error!(&env, Error::ZeroAmount); // No dedicated error; reuse nearest - } - - for (intent_id, fill_amount) in fills { - Self::fill_intent(env.clone(), solver.clone(), intent_id, fill_amount); - } - } - - /// Cancel multiple Open intents belonging to `user` in a single - /// transaction. - /// - /// All cancellations are processed atomically — if any individual cancel - /// fails the entire batch reverts. - /// - /// Bounded by [`MAX_BATCH_SIZE`] to prevent resource exhaustion. - pub fn batch_cancel_intent( - env: Env, - user: Address, - intent_ids: soroban_sdk::Vec>, - ) { - if intent_ids.len() > MAX_BATCH_SIZE as usize { - panic_with_error!(&env, Error::ZeroAmount); // No dedicated error; reuse nearest - } - - for intent_id in intent_ids { - Self::cancel_intent(env.clone(), user.clone(), intent_id); - } - } - // ── Fill Window Extension ───────────────────────────────────────────────── /// Per-intent cumulative fill-window extension budget for `solver`, in @@ -4086,13 +4045,6 @@ impl IntentSettlement { env.storage().instance().get(&DataKey::Admin) } - /// Pending admin-transfer proposal, if any: `(new_admin, eta)` where `eta` - /// is the ledger timestamp at which `accept_admin_transfer` may execute it. - pub fn get_pending_admin(env: Env) -> Option<(Address, u64)> { - env.storage().instance().get(&DataKey::PendingAdmin) - } - - /// /// - `total_intents` — cumulative count of intents ever submitted. /// - `total_volume` — cumulative dst-token units delivered across all fills. @@ -4969,21 +4921,4 @@ impl IntentSettlement { env.crypto().sha256(&preimage).into() } - fn validate_proof(env: &Env, intent_id: &BytesN<32>, intent: &IntentRecord) { - let _registry_addr = env - .storage() - .instance() - .get::<_, Address>(&DataKey::ProofRegistry) - .unwrap_or_else(|| panic_with_error!(env, Error::ProofRegistryNotSet)); - - // In production, this would call: - // - registry.has_proof(intent_id) to check existence - // - registry.get_proof(intent_id) to retrieve the proof record - // - Validate proof.src_chain matches intent.src_chain - // - Validate proof.src_amount >= intent.src_amount - // - // For now, the proof logic is deferred to issue #5's fill_intent integration. - // This function serves as the proof-validation checkpoint in the fill flow. - // Tests will inject mock proofs and verify this gate works correctly. - } } From 50190c3eed270c0dde70c2a217283d065066515a Mon Sep 17 00:00:00 2001 From: thelux134 Date: Sun, 27 Sep 2026 13:18:08 +0100 Subject: [PATCH 2/2] Close issues #343, #344, #345, #346 Closes #343 Closes #344 Closes #345 Closes #346