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
2 changes: 2 additions & 0 deletions src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ pub struct StagedUpgrade {
pub new_wasm_hash: BytesN<32>,
pub proposer: Address,
pub staged_at: u64,
/// Earliest ledger timestamp at which the replacement may execute.
pub execute_at: u64,
}

#[contracttype]
Expand Down
7 changes: 3 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ pub enum ContractError {
pub(crate) const DATA_KEY: Symbol = symbol_short!("DATA");
pub(crate) const SIGNERS_KEY: Symbol = symbol_short!("SIGNERS");
const PENDING_UPGRADE_KEY: Symbol = symbol_short!("PENDING");
pub(crate) const UPGRADE_DELAY_SECONDS: u64 = 48 * 60 * 60;
pub(crate) use crate::upgrades::timelock::WASM_UPGRADE_DELAY_SECONDS as UPGRADE_DELAY_SECONDS;
pub(crate) const STAKE_REGISTRY_KEY: Symbol = symbol_short!("STAKES");
pub(crate) const TOTAL_STAKED_KEY: Symbol = symbol_short!("TOTAL");
const HEARTBEAT_KEY: Symbol = symbol_short!("HBEAT");
Expand Down Expand Up @@ -504,7 +504,7 @@ impl TimeLockedUpgradeContract {
.instance()
.get(&PENDING_UPGRADE_KEY)
.ok_or(ContractError::NoPendingUpgrade)?;
if !verify_staged_delay(pending.staged_at, env.ledger().timestamp(), UPGRADE_DELAY_SECONDS) {
if !crate::upgrades::timelock::is_ready(pending.execute_at, env.ledger().timestamp()) {
return Err(ContractError::UpgradeTimelockNotSatisfied);
}
// Store pre-upgrade contract data snapshot for health check validation
Expand Down Expand Up @@ -558,8 +558,7 @@ impl TimeLockedUpgradeContract {

pub fn get_upgrade_timelock_remaining(env: Env) -> Option<u64> {
env.storage().instance().get(&PENDING_UPGRADE_KEY).map(|staged: StagedUpgrade| {
let elapsed = env.ledger().timestamp().saturating_sub(staged.staged_at);
UPGRADE_DELAY_SECONDS.saturating_sub(elapsed)
staged.execute_at.saturating_sub(env.ledger().timestamp())
})
}

Expand Down
1 change: 1 addition & 0 deletions src/upgrades/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod migration;
pub mod timelock;
32 changes: 32 additions & 0 deletions src/upgrades/timelock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! Timelock policy for WASM replacements.

/// Minimum delay between registering a WASM hash and replacing the code.
pub const WASM_UPGRADE_DELAY_SECONDS: u64 = 48 * 60 * 60;

/// Return the earliest ledger timestamp at which an upgrade may execute.
pub fn execution_timestamp(proposed_at: u64) -> Option<u64> {
proposed_at.checked_add(WASM_UPGRADE_DELAY_SECONDS)
}

/// Check whether the mandatory delay has elapsed.
pub fn is_ready(execute_at: u64, current_timestamp: u64) -> bool {
current_timestamp >= execute_at
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn requires_the_full_delay() {
let execute_at = execution_timestamp(1_000).unwrap();

assert!(!is_ready(execute_at, execute_at - 1));
assert!(is_ready(execute_at, execute_at));
}

#[test]
fn rejects_deadline_overflow() {
assert_eq!(execution_timestamp(u64::MAX), None);
}
}