Skip to content
Open
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ This repository contains smart contracts for the StellarFlow Network with a time
- **Admin-Only Operations**: Only contract administrators can propose and execute upgrades
- **Upgrade Cancellation**: Ability to cancel pending upgrades before execution
- **Timelock Monitoring**: Functions to check remaining timelock time
- **State Expiration Management**: Automatic TTL extension to prevent data deletion

## State Expiration Management

To ensure contract data is never deleted due to Soroban's state expiration mechanism, the contract uses persistent storage and automatically extends TTL (Time-To-Live) for all storage entries. TTL is extended to approximately 1 year (535,000 ledgers) whenever data is accessed or modified.

### Implementation Details

- **Persistent Storage**: All contract state is stored in persistent storage to survive contract upgrades
- **Automatic TTL Extension**: `env.storage().persistent().extend_ttl()` is called in all update functions
- **TTL Parameters**:
- Minimum ledgers: 535,000 (≈1 year)
- Maximum ledgers: 1,000,000 (≈2 years)

## Architecture

Expand Down Expand Up @@ -90,6 +103,7 @@ cargo test
✅ **Pending State Storage**: New WASM hash stored in pending state before commitment
✅ **48-Hour Delay**: Enforced delay between proposal and execution
✅ **Flash Upgrade Prevention**: Complete protection against immediate upgrades
✅ **State Expiration Management**: Uses `env.storage().persistent().extend_ttl()` to keep data alive

## Build and Deploy

Expand Down
38 changes: 26 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const DATA_KEY: Symbol = Symbol::short("DATA");
const PENDING_UPGRADE_KEY: Symbol = Symbol::short("PENDING");
const UPGRADE_DELAY_SECONDS: u64 = 48 * 60 * 60; // 48 hours in seconds

// TTL extension parameters (approximately 1 year in ledgers)
const MIN_LEDGERS_TTL: u32 = 535_000;
const MAX_LEDGERS_TTL: u32 = 1_000_000;

#[contracttype]
pub struct PendingUpgrade {
pub new_wasm_hash: BytesN<32>,
Expand All @@ -26,7 +30,7 @@ pub struct TimeLockedUpgradeContract;
impl TimeLockedUpgradeContract {
/// Initialize the contract with an admin address
pub fn initialize(env: Env, admin: Address) {
if env.storage().instance().has(&DATA_KEY) {
if env.storage().persistent().has(&DATA_KEY) {
panic!("contract already initialized");
}

Expand All @@ -37,15 +41,18 @@ impl TimeLockedUpgradeContract {
value: 0,
};

env.storage().instance().set(&DATA_KEY, &data);
env.storage().persistent().set(&DATA_KEY, &data);
env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &DATA_KEY);
}

/// Get the current contract data
pub fn get_data(env: Env) -> ContractData {
env.storage()
.instance()
let data = env.storage()
.persistent()
.get(&DATA_KEY)
.unwrap_or_else(|| panic!("contract not initialized"))
.unwrap_or_else(|| panic!("contract not initialized"));
env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &DATA_KEY);
data
}

/// Propose an upgrade with a new WASM hash
Expand All @@ -68,7 +75,8 @@ impl TimeLockedUpgradeContract {
proposer: proposer.clone(),
};

env.storage().instance().set(&PENDING_UPGRADE_KEY, &pending_upgrade);
env.storage().persistent().set(&PENDING_UPGRADE_KEY, &pending_upgrade);
env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &PENDING_UPGRADE_KEY);
}

/// Execute a pending upgrade if the timelock period has passed
Expand All @@ -84,7 +92,7 @@ impl TimeLockedUpgradeContract {

let pending_upgrade: PendingUpgrade = env
.storage()
.instance()
.persistent()
.get(&PENDING_UPGRADE_KEY)
.unwrap_or_else(|| panic!("no pending upgrade"));

Expand All @@ -104,7 +112,7 @@ impl TimeLockedUpgradeContract {
.update_current_contract(pending_upgrade.new_wasm_hash);

// Clear the pending upgrade
env.storage().instance().remove(&PENDING_UPGRADE_KEY);
env.storage().persistent().remove(&PENDING_UPGRADE_KEY);
}

/// Cancel a pending upgrade
Expand All @@ -118,16 +126,21 @@ impl TimeLockedUpgradeContract {

canceller.require_auth();

if !env.storage().instance().has(&PENDING_UPGRADE_KEY) {
if !env.storage().persistent().has(&PENDING_UPGRADE_KEY) {
panic!("no pending upgrade to cancel");
}

env.storage().instance().remove(&PENDING_UPGRADE_KEY);
env.storage().persistent().remove(&PENDING_UPGRADE_KEY);
}

/// Get the current pending upgrade information
pub fn get_pending_upgrade(env: Env) -> Option<PendingUpgrade> {
env.storage().instance().get(&PENDING_UPGRADE_KEY)
if let Some(pending) = env.storage().persistent().get(&PENDING_UPGRADE_KEY) {
env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &PENDING_UPGRADE_KEY);
Some(pending)
} else {
None
}
}

/// Get the remaining time before an upgrade can be executed
Expand Down Expand Up @@ -158,7 +171,8 @@ impl TimeLockedUpgradeContract {
setter.require_auth();

data.value = value;
env.storage().instance().set(&DATA_KEY, &data);
env.storage().persistent().set(&DATA_KEY, &data);
env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &DATA_KEY);
}
}

Expand Down