diff --git a/contracts/router-access/src/lib.rs b/contracts/router-access/src/lib.rs index 16f48b13..f5b81821 100644 --- a/contracts/router-access/src/lib.rs +++ b/contracts/router-access/src/lib.rs @@ -843,26 +843,6 @@ impl RouterAccess { None => u64::MAX, }; - env.storage() - .instance() - .set(&DataKey::HasRole(role.clone(), account.clone()), &true); - - // Increment RoleMemberCount when the account transitions from inactive to active. - // This covers two cases: - // 1. Brand-new grant (no prior assignment). - // 2. Re-grant of a previously expired role (raw assignment exists but was inactive). - // An expiry update on a live role must NOT increment to avoid double-counting. - if !currently_active { - let count: u32 = env - .storage() - .instance() - .get::(&DataKey::RoleMemberCount(role.clone())) - .unwrap_or(0); - env.storage() - .instance() - .set(&DataKey::RoleMemberCount(role.clone()), &(count + 1)); - } - let mut members: Vec
= env .storage() .instance() @@ -885,6 +865,26 @@ impl RouterAccess { .instance() .set(&DataKey::RoleMembers(role.clone()), &members); + env.storage() + .instance() + .set(&DataKey::HasRole(role.clone(), account.clone()), &true); + + // Increment RoleMemberCount when the account transitions from inactive to active. + // This covers two cases: + // 1. Brand-new grant (no prior assignment). + // 2. Re-grant of a previously expired role (raw assignment exists but was inactive). + // An expiry update on a live role must NOT increment to avoid double-counting. + if !currently_active { + let count: u32 = env + .storage() + .instance() + .get::(&DataKey::RoleMemberCount(role.clone())) + .unwrap_or(0); + env.storage() + .instance() + .set(&DataKey::RoleMemberCount(role.clone()), &(count + 1)); + } + let mut roles: Vec = env .storage() .instance() diff --git a/contracts/router-quote/src/lib.rs b/contracts/router-quote/src/lib.rs index 03fc3b7a..3b621520 100644 --- a/contracts/router-quote/src/lib.rs +++ b/contracts/router-quote/src/lib.rs @@ -340,9 +340,15 @@ impl RouterQuote { let mut configured_routes = Vec::new(&env); for route in routes { - if let Ok(fee) = Self::get_route_fee(env.clone(), route.clone()) { - configured_routes.push_back((route, fee)); - } + let tiers = Self::get_route_fee_tiers(env.clone(), route.clone()); + let fee = if let Some(lowest) = tiers.get(0) { + lowest.fee_bps + } else if let Ok(fee) = Self::get_route_fee(env.clone(), route.clone()) { + fee + } else { + continue; + }; + configured_routes.push_back((route, fee)); } configured_routes } diff --git a/contracts/router-timelock/src/lib.rs b/contracts/router-timelock/src/lib.rs index d56dd5a1..66f2d0e8 100644 --- a/contracts/router-timelock/src/lib.rs +++ b/contracts/router-timelock/src/lib.rs @@ -221,11 +221,14 @@ impl RouterTimelock { return Err(TimelockError::AlreadyQueued); } - // Validate no circular dependencies + // Validate dependencies: no circular references, dependency must exist, depth within limits for dep_id in deps.iter() { if dep_id == op_id { return Err(TimelockError::CircularDependency); } + if !env.storage().instance().has(&DataKey::Op(dep_id.clone())) { + return Err(TimelockError::NotFound); + } Self::check_dependency_depth(&env, dep_id.clone(), 0)?; } @@ -1635,6 +1638,75 @@ mod tests { ); } + #[test] + fn test_get_operations_by_status_returns_matching_ops() { + let (env, admin, client) = setup(); + let target = Address::generate(&env); + let deps = Vec::new(&env); + + let op1 = client.queue( + &admin, + &String::from_str(&env, "op1"), + &target, + &3600, + &GRACE, + &deps, + ); + let op2 = client.queue( + &admin, + &String::from_str(&env, "op2"), + &target, + &3600, + &GRACE, + &deps, + ); + + let queued = client.get_operations_by_status(&OperationStatus::Queued); + assert_eq!(queued.len(), 2); + assert_eq!(queued.get(0).unwrap().0, op1); + assert_eq!(queued.get(1).unwrap().0, op2); + + let ready = client.get_operations_by_status(&OperationStatus::Ready); + assert_eq!(ready.len(), 0); + + // Advance past ETA — both become Ready + env.ledger().with_mut(|l| l.timestamp += 3601); + + let queued = client.get_operations_by_status(&OperationStatus::Queued); + assert_eq!(queued.len(), 0); + + let ready = client.get_operations_by_status(&OperationStatus::Ready); + assert_eq!(ready.len(), 2); + assert_eq!(ready.get(0).unwrap().0, op1); + assert_eq!(ready.get(1).unwrap().0, op2); + } + + #[test] + fn test_get_operations_by_status_expired() { + let (env, admin, client) = setup(); + let target = Address::generate(&env); + let deps: Vec = Vec::new(&env); + let grace: u64 = 3600; + + let op_id = client.queue( + &admin, + &String::from_str(&env, "expires"), + &target, + &3600, + &grace, + &deps, + ); + + // Jump past grace period + env.ledger().with_mut(|l| l.timestamp += 3600 + grace + 1); + let expired = client.get_operations_by_status(&OperationStatus::Expired); + assert_eq!(expired.len(), 1); + assert_eq!(expired.get(0).unwrap().0, op_id); + + let ready = client.get_operations_by_status(&OperationStatus::Ready); + assert_eq!(ready.len(), 0); + } + #[test] fn test_pending_ops_index_excludes_expired_from_pending() { let (env, admin, client) = setup();