Summary
contracts/risk-pool/src/lib.rs implements a two-step admin transfer via propose_new_admin() (line 663) and accept_admin() (line 670). However, propose_new_admin has two issues:
- No event emitted — the pending admin proposal is stored silently in instance storage with no on-chain notification. Off-chain monitoring cannot detect an impending admin change.
- No expiry — the pending admin proposal has no timeout. A compromised admin can propose a malicious address and wait indefinitely for an opportunity to force-accept it.
Code
pub fn propose_new_admin(env: Env, admin: Address, new_admin: Address) {
Self::require_admin(&env, &admin);
env.storage().instance().set(&StorageKey::PendingAdmin, &new_admin);
// ← no event emitted
// ← no expiry timestamp stored
}
accept_admin (line 670) correctly requires the proposed admin to authorize, but without an event from propose_new_admin, observers have no way to know a proposal is pending until accept_admin is called.
Impact
- LPs who monitor contract events to detect admin changes will have no warning before the admin handover completes
- If the admin key is compromised, the attacker can silently propose a malicious address and wait for a low-attention period to accept it
- A stale pending admin proposal that was never cancelled stays valid forever — a forgotten proposal becomes an unintended attack surface
Fix
pub fn propose_new_admin(env: Env, admin: Address, new_admin: Address) {
Self::require_admin(&env, &admin);
let now = env.ledger().timestamp();
let proposal = PendingAdminProposal {
new_admin: new_admin.clone(),
proposed_at: now,
expires_at: now + 7 * 24 * 60 * 60, // 7-day window
};
env.storage().instance().set(&StorageKey::PendingAdmin, &proposal);
env.events().publish(
(Symbol::new(&env, "admin_proposed"),),
AdminProposed { current_admin: admin, new_admin },
);
}
Severity: Medium
Summary
contracts/risk-pool/src/lib.rsimplements a two-step admin transfer viapropose_new_admin()(line 663) andaccept_admin()(line 670). However,propose_new_adminhas two issues:Code
accept_admin(line 670) correctly requires the proposed admin to authorize, but without an event frompropose_new_admin, observers have no way to know a proposal is pending untilaccept_adminis called.Impact
Fix
Severity: Medium