Skip to content

Contract upgradeability with timelock #16

@samjay8

Description

@samjay8

Title: feat(contracts): Contract upgradeability with timelock

Labels: contracts, security

Description:
Smart contracts need an upgrade path for bug fixes and new features
without requiring full redeployment. This issue adds a two-step
upgrade pattern with a 48-hour timelock to all Soroban contracts,
ensuring upgrades are transparent and time-locked against rushed
or malicious changes.

What Needs to Be Done:

  • Add to each contract (invoice_registry, escrow, yield_oracle):
    UpgradeProposal struct { new_wasm_hash: BytesN<32>, proposed_at: u64 }
    propose_upgrade(admin, new_wasm_hash) — starts 48h timelock
    execute_upgrade(admin) — callable only after 48h timelock expires
    cancel_upgrade(admin) — cancels pending proposal
    get_upgrade_proposal() — view, returns pending proposal or None
  • Timelock: 48 hours = 172800 seconds
  • Events:
    ("upgrade", "proposed") → { new_wasm_hash, proposed_at, executable_after }
    ("upgrade", "executed") → { new_wasm_hash, executed_at }
    ("upgrade", "canceled") → { canceled_by, canceled_at }
  • DEVELOPMENT.md documentation:
    • Step-by-step upgrade process
    • How to verify the new WASM hash before proposing
    • Emergency cancel procedure

Key Files:

  • contracts/invoice_registry/src/lib.rs (update)
  • contracts/escrow/src/lib.rs (update)
  • contracts/yield_oracle/src/lib.rs (update)
  • DEVELOPMENT.md (new or update)

Acceptance Criteria:

  • propose_upgrade() requires admin.require_auth()
  • execute_upgrade() fails if called before 48h
  • execute_upgrade() fails if no proposal exists
  • cancel_upgrade() removes pending proposal
  • Events emitted on every action
  • DEVELOPMENT.md explains full upgrade process
  • cargo test passes with 95%+ coverage for upgrade paths
  • Tests cover: propose, execute after timelock, execute before
    timelock (should fail), cancel, unauthorized access

Security Notes:

  • Only admin can propose, execute, or cancel
  • Timelock cannot be shortened after proposal
  • Proposal stores exact executable_after timestamp

Branch: feat/contract-upgradeability

Commit: feat(contracts): add two-step upgrade pattern with 48h timelock across all contracts

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions