Description
contracts/access_control/src/lib.rs lines 46–57 contain a storage-tier mismatch identical in structure to the treasury bug (issue #182):
pub fn initialize(env: Env, admin: Address) -> Result<(), KoraError> {
// Guard reads from PERSISTENT storage
if env.storage().persistent().has(&DataKey::Admin) {
return Err(KoraError::AlreadyInitialized);
}
// But the write goes to INSTANCE storage
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::Paused, &false);
// ...
}
Because Soroban's persistent() and instance() are separate namespaces, the guard persistent().has(&DataKey::Admin) will never be true after initialization (since admin was written to instance(), not persistent()). This means:
- The re-initialization guard is permanently bypassed —
initialize can be called multiple times.
- A second
initialize call can overwrite the admin address and reset the Paused flag to false, even on a live protocol.
- An attacker who can call
initialize on a deployed contract can silently take admin rights.
Fix
Use a consistent storage tier throughout. Since require_admin reads admin from instance(), use instance() for the guard too:
if env.storage().instance().has(&DataKey::Admin) {
return Err(KoraError::AlreadyInitialized);
}
Or, if persistent storage is preferred for archival safety, write admin to persistent() and update require_admin to read from there consistently.
Acceptance Criteria
Complexity: Low (75 points)
Description
contracts/access_control/src/lib.rslines 46–57 contain a storage-tier mismatch identical in structure to the treasury bug (issue #182):Because Soroban's
persistent()andinstance()are separate namespaces, the guardpersistent().has(&DataKey::Admin)will never be true after initialization (since admin was written toinstance(), notpersistent()). This means:initializecan be called multiple times.initializecall can overwrite the admin address and reset thePausedflag tofalse, even on a live protocol.initializeon a deployed contract can silently take admin rights.Fix
Use a consistent storage tier throughout. Since
require_adminreads admin frominstance(), useinstance()for the guard too:Or, if persistent storage is preferred for archival safety, write admin to
persistent()and updaterequire_adminto read from there consistently.Acceptance Criteria
initializetwice returnsKoraError::AlreadyInitializedon the second call.require_adminreads from the same tier the admin was written to.Complexity: Low (75 points)