diff --git a/contracts/router-quote/src/lib.rs b/contracts/router-quote/src/lib.rs index 91d88643..03fc3b7a 100644 --- a/contracts/router-quote/src/lib.rs +++ b/contracts/router-quote/src/lib.rs @@ -236,6 +236,21 @@ impl RouterQuote { /// The tiers are sorted by `min_amount` ascending and are used to select /// the highest matching tier for a quote. When no tier matches, the flat /// route fee (if any) or the default fee is used. + /// + /// # Arguments + /// * `env` - The Soroban environment. + /// * `caller` - The address initiating the call; must be the admin. + /// * `route` - The route name to configure. + /// * `tiers` - The fee tiers to set; each `FeeTier.min_amount` must be + /// non-negative and each `FeeTier.fee_bps` must be <= 10000. + /// + /// # Returns + /// `Ok(())` on success. + /// + /// # Errors + /// * [`QuoteError::Unauthorized`] — if caller is not the admin. + /// * [`QuoteError::InvalidFeeTier`] — if any tier has a negative `min_amount`. + /// * [`QuoteError::InvalidFeeBps`] — if any tier's `fee_bps` > 10000. pub fn set_route_fee_tiers( env: Env, caller: Address, @@ -1191,6 +1206,24 @@ mod tests { assert_eq!(result, Err(Ok(QuoteError::Unauthorized))); } + #[test] + fn test_set_route_fee_tiers_rejects_negative_min_amount() { + let (env, admin, client) = setup(); + let route = String::from_str(&env, "uniswap"); + let tiers = vec![&env, FeeTier { min_amount: -1, fee_bps: 50 }]; + let result = client.try_set_route_fee_tiers(&admin, &route, &tiers); + assert_eq!(result, Err(Ok(QuoteError::InvalidFeeTier))); + } + + #[test] + fn test_set_route_fee_tiers_rejects_fee_bps_over_max() { + let (env, admin, client) = setup(); + let route = String::from_str(&env, "uniswap"); + let tiers = vec![&env, FeeTier { min_amount: 0, fee_bps: 10001 }]; + let result = client.try_set_route_fee_tiers(&admin, &route, &tiers); + assert_eq!(result, Err(Ok(QuoteError::InvalidFeeBps))); + } + #[test] fn test_unauthorized_transfer_admin_fails() { let (env, _admin, client) = setup(); diff --git a/contracts/router-timelock/src/lib.rs b/contracts/router-timelock/src/lib.rs index 65d22639..d56dd5a1 100644 --- a/contracts/router-timelock/src/lib.rs +++ b/contracts/router-timelock/src/lib.rs @@ -427,7 +427,9 @@ impl RouterTimelock { .checked_add(op.grace_period_seconds) .map_or(true, |expiry| now > expiry); if is_expired || op.executed || op.cancelled { - // It is expired or finalized! + // It is expired or finalized! Remove the underlying storage entries. + env.storage().instance().remove(&DataKey::Op(op_id.clone())); + env.storage().instance().remove(&DataKey::Deps(op_id.clone())); cleaned_count += 1; } else { new_pending.push_back(op_id); diff --git a/integration-tests/tests/quote_execution_multicall_pipeline.rs b/integration-tests/tests/quote_execution_multicall_pipeline.rs index 5a73e04f..d1e3f205 100644 --- a/integration-tests/tests/quote_execution_multicall_pipeline.rs +++ b/integration-tests/tests/quote_execution_multicall_pipeline.rs @@ -198,8 +198,7 @@ fn test_pipeline_quote_calculation() { amount_in, }; - let quote = s.quote.get_quote("e_request) - .expect("Failed to get quote"); + let quote = s.quote.get_quote("e_request); // Verify quote response assert_eq!(quote.amount_in, amount_in); @@ -266,8 +265,7 @@ fn test_pipeline_execution_swap() { }; // Execute (with mocked auth, this should succeed) - let result = s.execution.execute(&s.user, &exec_request) - .expect("Execution should succeed"); + let result = s.execution.execute(&s.user, &exec_request); // Verify execution result assert_eq!(result.target, mock_target); @@ -370,8 +368,7 @@ fn test_quote_to_execution_to_multicall_pipeline() { amount_in, }; - let quote = s.quote.get_quote("e_request) - .expect("Quote should succeed"); + let quote = s.quote.get_quote("e_request); println!(" ✓ Quote obtained: amount_out = {}", quote.amount_out); // ── Phase 2: Middleware Check ────────────────────────────────────── @@ -396,8 +393,7 @@ fn test_quote_to_execution_to_multicall_pipeline() { amount: 1_000_000, }; - let exec_result = s.execution.execute(&s.user, &exec_request) - .expect("Execution should succeed"); + let exec_result = s.execution.execute(&s.user, &exec_request); assert_eq!(exec_result.success, true); println!(" ✓ Single swap executed successfully");