The upgrade mechanism is already implemented in /contracts/src/upgrade/lib.rs and is ready to be integrated into any Soroban contract in the Traqora ecosystem.
/contracts/src/upgrade/lib.rs- Main upgrade mechanism module/contracts/src/upgrade/integration_examples.rs- Integration examples and patterns/contracts/tests/upgrade_mechanism_test.rs- Comprehensive test suite (38 test cases)
/UPGRADE_MECHANISM.md- Complete API reference and best practicesIMPLEMENTATION_GUIDE.md- This file
The upgrade module is already exposed in the main library. Verify in /contracts/src/lib.rs:
#[path = "upgrade/lib.rs"]
pub mod upgrade;For each contract you want to make upgradeable, add these functions to the #[contractimpl] block:
use soroban_sdk::{Address, BytesN, Env};
use crate::upgrade::{UpgradeContract, UpgradeStorage, ScheduledUpgrade};
#[contractimpl]
impl MyContract {
// ... existing contract functions ...
// ===== UPGRADE FUNCTIONS =====
/// Schedule a contract upgrade (admin only)
pub fn schedule_upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) {
UpgradeContract::schedule_upgrade(env, admin, new_wasm_hash);
}
/// Execute a scheduled upgrade after timelock (admin only)
pub fn execute_upgrade(env: Env, admin: Address) {
UpgradeContract::execute_upgrade(env, admin);
}
/// Cancel a pending upgrade (owner only)
pub fn cancel_upgrade(env: Env, owner: Address) {
UpgradeContract::cancel_upgrade(env, owner);
}
/// Set custom timelock duration in seconds (owner only)
pub fn set_timelock_duration(env: Env, owner: Address, duration: u64) {
UpgradeContract::set_timelock_duration(env, owner, duration);
}
// ===== READ-ONLY FUNCTIONS =====
/// Get currently scheduled upgrade (if any)
pub fn get_scheduled_upgrade(env: Env) -> Option<ScheduledUpgrade> {
UpgradeContract::get_scheduled_upgrade(env)
}
/// Get current timelock duration in seconds
pub fn get_timelock_duration(env: Env) -> u64 {
UpgradeContract::get_timelock_duration(env)
}
/// Get seconds remaining until upgrade can be executed
pub fn get_upgrade_timelock_remaining(env: Env) -> u64 {
UpgradeContract::get_upgrade_timelock_remaining(env)
}
}Here's how to add upgrade capability to the TRQ token contract:
File: /contracts/src/token/lib.rs
use soroban_sdk::{
contract, contractimpl, contracttype, symbol_short, Address, Env, String, Symbol, BytesN,
};
use crate::access::{AccessControl, Role};
use crate::upgrade::{UpgradeContract, UpgradeStorage, ScheduledUpgrade}; // ADD THIS
// ... existing TokenMetadata, Allowance, TokenStorage ...
#[contract]
pub struct TRQTokenContract;
#[contractimpl]
impl TRQTokenContract {
// ... existing token functions (init_token, mint, transfer, etc.) ...
// ===== NEW: UPGRADE FUNCTIONS =====
pub fn schedule_upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) {
UpgradeContract::schedule_upgrade(env, admin, new_wasm_hash);
}
pub fn execute_upgrade(env: Env, admin: Address) {
UpgradeContract::execute_upgrade(env, admin);
}
pub fn cancel_upgrade(env: Env, owner: Address) {
UpgradeContract::cancel_upgrade(env, owner);
}
pub fn set_timelock_duration(env: Env, owner: Address, duration: u64) {
UpgradeContract::set_timelock_duration(env, owner, duration);
}
pub fn get_scheduled_upgrade(env: Env) -> Option<ScheduledUpgrade> {
UpgradeContract::get_scheduled_upgrade(env)
}
pub fn get_timelock_duration(env: Env) -> u64 {
UpgradeContract::get_timelock_duration(env)
}
pub fn get_upgrade_timelock_remaining(env: Env) -> u64 {
UpgradeContract::get_upgrade_timelock_remaining(env)
}
}The upgrade mechanism has a comprehensive test suite with 38 test cases covering:
Run all upgrade tests:
cd contracts
cargo test upgrade_mechanism -- --nocaptureRun specific test categories:
# Core functionality
cargo test upgrade_mechanism_tests::test_schedule_upgrade_basic
# Authorization checks
cargo test upgrade_mechanism_tests::test_schedule_upgrade_requires_admin
# Timelock validation
cargo test upgrade_mechanism_tests::test_execute_upgrade_rejects_premature_execution
# State management
cargo test upgrade_mechanism_tests::test_cannot_schedule_multiple_pending_upgrades
# Full flow
cargo test upgrade_mechanism_tests::test_upgrade_flow_complete_scenario- ✅
test_schedule_upgrade_basic- Basic scheduling works - ✅
test_schedule_upgrade_stores_timestamp- Timestamp is recorded - ✅
test_get_scheduled_upgrade_read_only- Read-only access works - ✅
test_execute_upgrade_success_after_timelock- Execution succeeds after timelock
- ✅
test_schedule_upgrade_requires_admin- Only admin can schedule - ✅
test_execute_upgrade_requires_admin- Only admin can execute - ✅
test_set_timelock_requires_owner- Only owner can set timelock - ✅
test_cancel_upgrade_requires_owner- Only owner can cancel
- ✅
test_execute_upgrade_rejects_premature_execution- Rejects early execution ⭐ - ✅
test_execute_upgrade_succeeds_at_exact_timelock_boundary- Exact boundary works - ✅
test_execute_upgrade_succeeds_after_timelock- Post-timelock works - ✅
test_cannot_set_zero_timelock- Prevents invalid duration
- ✅
test_execute_upgrade_with_no_scheduled_upgrade- Rejects when none scheduled - ✅
test_execute_upgrade_cannot_execute_twice- Prevents double execution - ✅
test_cannot_schedule_multiple_pending_upgrades- Prevents concurrent upgrades - ✅
test_can_schedule_upgrade_after_previous_executed- Allows sequential upgrades
- ✅
test_default_timelock_is_48_hours- Default is 172800 seconds - ✅
test_set_custom_timelock_duration- Custom duration works - ✅
test_execute_upgrade_uses_custom_timelock- Custom duration is enforced
- ✅
test_get_upgrade_timelock_remaining_no_upgrade- Returns 0 if none scheduled - ✅
test_get_upgrade_timelock_remaining_early_stage- Correct remainder calculation - ✅
test_get_upgrade_timelock_remaining_after_expiry- Returns 0 after expiry - ✅
test_get_upgrade_timelock_remaining_after_execution- Returns 0 after execution
- ✅
test_cancel_upgrade_clears_scheduled_upgrade- Cancellation works - ✅
test_cannot_cancel_executed_upgrade- Prevents canceling executed upgrades
- ✅
test_upgrade_flow_complete_scenario- Full workflow works - ✅
test_multiple_sequential_upgrades- Multiple upgrades sequence correctly - ✅
test_cancel_and_reschedule- Cancel and reschedule works
All upgrades require a 48-hour waiting period by default.
// Schedule upgrade
let new_hash = BytesN::from_array(&env, &[/*...*/]);
UpgradeContract::schedule_upgrade(env.clone(), admin.clone(), new_hash);
// Must wait 48 hours (172800 seconds) before execution
env.ledger().set_timestamp(env.ledger().timestamp() + 172800);
// Now execution succeeds
UpgradeContract::execute_upgrade(env, admin);Owners can adjust timelock for different security profiles.
// Emergency scenario: reduce timelock to 1 hour
UpgradeContract::set_timelock_duration(env, owner, 3600);
// Critical security: increase timelock to 7 days
UpgradeContract::set_timelock_duration(env, owner, 604800);The system rejects execution attempts before timelock elapses.
// Schedule upgrade at time T
env.ledger().set_timestamp(1000);
UpgradeContract::schedule_upgrade(env.clone(), admin.clone(), hash);
// Try to execute 1 second later - FAILS
env.ledger().set_timestamp(1001);
UpgradeContract::execute_upgrade(env.clone(), admin.clone()); // Panics: "Timelock period not yet elapsed"Once an upgrade is executed, it cannot be executed again.
UpgradeContract::execute_upgrade(env.clone(), admin.clone());
UpgradeContract::execute_upgrade(env, admin); // Panics: "Upgrade already executed"All state changes emit events for monitoring.
Events emitted:
- UpgradeScheduled(new_wasm_hash, scheduled_at, scheduled_by)
- UpgradeExecuted(new_wasm_hash, executed_at, executed_by)
- UpgradeCancelled(new_wasm_hash, cancelled_by)
- TimelockUpdated(new_duration, updated_by)
schedule_upgrade() → Admin role required
execute_upgrade() → Admin role required
cancel_upgrade() → Owner only
set_timelock() → Owner only
get_*() → Public (read-only)
- Minimum wait: 48 hours by default (172800 seconds)
- Configurable by owner for different scenarios
- Enforced at execution time
- Prevents flash upgrades and malicious code injection
- Only one upgrade can be pending at a time
- Executed upgrades cannot be re-executed
- Previous upgrades can be scheduled after execution
- Cancellation only works on pending upgrades
Add upgrade functions to existing contract:
pub fn schedule_upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) {
UpgradeContract::schedule_upgrade(env, admin, new_wasm_hash);
}
pub fn execute_upgrade(env: Env, admin: Address) {
UpgradeContract::execute_upgrade(env, admin);
}DAO proposes and votes on upgrades:
pub fn dao_propose_upgrade(env: Env, proposer: Address, new_hash: BytesN<32>) {
// Verify proposer has voting power
// Create governance proposal
// Start voting period
}
pub fn dao_execute_upgrade(env: Env, proposal_id: u64) {
// Verify proposal passed
// Verify voting ended
UpgradeContract::schedule_upgrade(env, admin, new_hash);
}
pub fn finalize_upgrade_after_timelock(env: Env, admin: Address) {
// Check timelock passed
UpgradeContract::execute_upgrade(env, admin);
}Validate upgrade before scheduling:
pub fn schedule_validated_upgrade(
env: Env,
admin: Address,
new_hash: BytesN<32>,
new_version: u32,
) {
// Verify compatibility
assert!(is_compatible(&env, new_version), "Incompatible version");
// Schedule upgrade
UpgradeContract::schedule_upgrade(env, admin, new_hash);
}Listen for upgrade events:
// JavaScript example
contract.events.on('UpgradeScheduled', (event) => {
console.log('Upgrade scheduled:', event.new_wasm_hash);
console.log('Scheduled at:', new Date(event.scheduled_at * 1000));
console.log('By:', event.scheduled_by);
// Alert stakeholders
notifySlack(`Upgrade scheduled: ${event.new_wasm_hash}`);
});
contract.events.on('UpgradeExecuted', (event) => {
console.log('Upgrade executed:', event.new_wasm_hash);
notifySlack(`Upgrade executed: ${event.new_wasm_hash}`);
});Query upgrade status anytime:
// Check if upgrade pending
let upgrade = contract.get_scheduled_upgrade();
match upgrade {
Some(u) => println!("Pending upgrade: {:?}", u.new_wasm_hash),
None => println!("No upgrades scheduled"),
}
// Check time remaining
let remaining = contract.get_upgrade_timelock_remaining();
println!("Time remaining: {} seconds", remaining);Verify upgrade mechanism health:
pub fn verify_upgrade_health(env: &Env) -> bool {
// Check timelock is reasonable
let timelock = UpgradeContract::get_timelock_duration(env.clone());
assert!(timelock > 3600 && timelock < 2592000, "Timelock out of range");
// Check no stuck upgrades
if let Some(u) = UpgradeContract::get_scheduled_upgrade(env.clone()) {
let age = env.ledger().timestamp() - u.scheduled_at;
assert!(age < 2592000, "Upgrade scheduled too long ago");
}
true
}Cause: Trying to execute before 48 hours have passed
Solution:
- Wait for timelock to expire, or
- Reduce timelock:
set_timelock_duration(env, owner, shorter_duration), or - Cancel and reschedule with adjusted time
Cause: Trying to schedule while another upgrade is pending
Solution:
- Wait for existing upgrade to execute, or
- Cancel existing upgrade:
cancel_upgrade(env, owner), then schedule new one
Cause: Caller doesn't have Admin role
Solution:
- Use proper admin account, or
- Grant Admin role:
AccessControl::set_role(&env, &admin, &address, Role::Admin, true)
- Implementation Version: 1.0.0
- Default Timelock: 48 hours (172800 seconds)
- Minimum Timelock: 1 second (can be configured)
- Maximum WASM Hash: 32 bytes (256 bits)
- Supported Soroban SDK: 22.0.0
- Review the upgrade mechanism: UPGRADE_MECHANISM.md
- Run tests to verify functionality:
cargo test upgrade_mechanism - Integrate into your contracts using examples in this guide
- Deploy to testnet and verify events
- Document upgrade procedures for your DAO/governance