Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions contracts/router-access/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, u32>(&DataKey::RoleMemberCount(role.clone()))
.unwrap_or(0);
env.storage()
.instance()
.set(&DataKey::RoleMemberCount(role.clone()), &(count + 1));
}

let mut members: Vec<Address> = env
.storage()
.instance()
Expand All @@ -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, u32>(&DataKey::RoleMemberCount(role.clone()))
.unwrap_or(0);
env.storage()
.instance()
.set(&DataKey::RoleMemberCount(role.clone()), &(count + 1));
}

let mut roles: Vec<String> = env
.storage()
.instance()
Expand Down
12 changes: 9 additions & 3 deletions contracts/router-quote/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
74 changes: 73 additions & 1 deletion contracts/router-timelock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
}

Expand Down Expand Up @@ -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<Bytes> = 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();
Expand Down
Loading