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
16 changes: 8 additions & 8 deletions examples/timelock-controller/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ use stellar_access::access_control::{
};
use stellar_governance::timelock::{
cancel_operation, execute_operation, get_min_delay as timelock_get_min_delay,
get_operation_state, get_timestamp, hash_operation as timelock_hash_operation,
get_operation_ledger, get_operation_state, hash_operation as timelock_hash_operation,
is_operation_done, is_operation_pending, is_operation_ready, operation_exists,
schedule_operation, set_execute_operation, set_min_delay as timelock_set_min_delay, Operation,
OperationState, TimelockError,
Expand Down Expand Up @@ -218,7 +218,7 @@ impl TimelockController {
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `min_delay` - Initial minimum delay in seconds for operations.
/// * `min_delay` - Initial minimum delay in ledgers for operations.
/// * `proposers` - Accounts to be granted proposer and canceller roles.
/// * `executors` - Accounts to be granted executor role.
/// * `admin` - Optional account to be granted admin role for initial setup.
Expand Down Expand Up @@ -270,7 +270,7 @@ impl TimelockController {
/// * `predecessor` - The predecessor operation ID (use empty bytes for
/// none).
/// * `salt` - Salt for uniqueness (use empty bytes for default).
/// * `delay` - The delay in seconds before the operation can be executed.
/// * `delay` - The delay in ledgers before the operation can be executed.
/// * `proposer` - The address proposing the operation (must have proposer
/// role).
///
Expand Down Expand Up @@ -367,7 +367,7 @@ impl TimelockController {
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `new_delay` - The new minimum delay in seconds.
/// * `new_delay` - The new minimum delay in ledgers.
///
/// # Notes
///
Expand All @@ -379,7 +379,7 @@ impl TimelockController {
timelock_set_min_delay(e, new_delay);
}

/// Returns the minimum delay in seconds required for operations.
/// Returns the minimum delay in ledgers required for operations.
pub fn get_min_delay(e: &Env) -> u32 {
timelock_get_min_delay(e)
}
Expand All @@ -397,9 +397,9 @@ impl TimelockController {
timelock_hash_operation(e, &operation)
}

/// Returns the timestamp at which an operation becomes ready.
pub fn get_timestamp(e: &Env, operation_id: BytesN<32>) -> u64 {
get_timestamp(e, &operation_id)
/// Returns the ledger sequence number at which an operation becomes ready.
pub fn get_operation_ledger(e: &Env, operation_id: BytesN<32>) -> u32 {
get_operation_ledger(e, &operation_id)
}

/// Returns the current state of an operation.
Expand Down
6 changes: 3 additions & 3 deletions examples/timelock-controller/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fn schedule_and_execute_operation() {
assert!(!client.is_operation_ready(&operation_id));

// Advance ledgers to make operation ready
e.ledger().with_mut(|li| li.timestamp += 10);
e.ledger().with_mut(|li| li.sequence_number += 10);

assert!(client.is_operation_ready(&operation_id));

Expand Down Expand Up @@ -140,7 +140,7 @@ fn schedule_and_execute_operation_no_executors() {
assert!(client.is_operation_pending(&operation_id));
assert!(!client.is_operation_ready(&operation_id));

e.ledger().with_mut(|li| li.timestamp += 10);
e.ledger().with_mut(|li| li.sequence_number += 10);

assert!(client.is_operation_ready(&operation_id));

Expand Down Expand Up @@ -207,7 +207,7 @@ fn schedule_and_execute_self_admin_operation() {
assert!(client.is_operation_pending(&operation_id));
assert!(!client.is_operation_ready(&operation_id));

e.ledger().with_mut(|li| li.timestamp += 10);
e.ledger().with_mut(|li| li.sequence_number += 10);

assert!(client.is_operation_ready(&operation_id));

Expand Down
6 changes: 3 additions & 3 deletions packages/governance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ The `votes` module provides vote tracking functionality with delegation and hist

- **Voting Units**: The base unit of voting power, typically 1:1 with token balance
- **Delegation**: Accounts can delegate their voting power to another account (delegatee)
- **Checkpoints**: Historical snapshots of voting power at specific timestamps
- **Clock Mode**: Uses ledger timestamps (`e.ledger().timestamp()`) as the timepoint reference
- **Checkpoints**: Historical snapshots of voting power at specific ledger sequence numbers
- **Clock Mode**: Uses ledger sequence numbers (`e.ledger().sequence()`) as the timepoint reference

#### Key Features

- Track voting power per account with historical checkpoints
- Support delegation (an account can delegate its voting power to another account)
- Provide historical vote queries at any past timestamp
- Provide historical vote queries at any past ledger sequence number
- Explicit delegation required (accounts must self-delegate to use their own voting power)
- Non-delegated voting units are not counted as votes

Expand Down
8 changes: 4 additions & 4 deletions packages/governance/src/timelock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ mod test;
use soroban_sdk::{contracterror, contractevent, Address, BytesN, Env, Symbol, Val, Vec};

pub use crate::timelock::storage::{
cancel_operation, execute_operation, get_min_delay, get_operation_state, get_timestamp,
cancel_operation, execute_operation, get_min_delay, get_operation_ledger, get_operation_state,
hash_operation, is_operation_done, is_operation_pending, is_operation_ready, operation_exists,
schedule_operation, set_execute_operation, set_min_delay, Operation, OperationState,
TimelockStorageKey,
Expand Down Expand Up @@ -93,11 +93,11 @@ pub const TIMELOCK_EXTEND_AMOUNT: u32 = 30 * DAY_IN_LEDGERS;
pub const TIMELOCK_TTL_THRESHOLD: u32 = TIMELOCK_EXTEND_AMOUNT - DAY_IN_LEDGERS;

/// Sentinel value for an operation that has not been scheduled.
pub const UNSET_TIMESTAMP: u64 = 0;
pub const UNSET_LEDGER: u32 = 0;

/// Sentinel value used to mark an operation as done.
/// Using 1 instead of 0 to distinguish from unset operations.
pub const DONE_TIMESTAMP: u64 = 1;
pub const DONE_LEDGER: u32 = 1;
Comment thread
brozorec marked this conversation as resolved.

// ################## EVENTS ##################

Expand Down Expand Up @@ -146,7 +146,7 @@ pub struct OperationScheduled {
/// * `args` - The arguments to pass to the function.
/// * `predecessor` - The predecessor operation ID.
/// * `salt` - The salt for uniqueness.
/// * `delay` - The delay in seconds.
/// * `delay` - The delay in ledgers.
#[allow(clippy::too_many_arguments)]
pub fn emit_operation_scheduled(
e: &Env,
Expand Down
65 changes: 33 additions & 32 deletions packages/governance/src/timelock/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use soroban_sdk::{

use crate::timelock::{
emit_min_delay_changed, emit_operation_cancelled, emit_operation_executed,
emit_operation_scheduled, TimelockError, DONE_TIMESTAMP, TIMELOCK_EXTEND_AMOUNT,
TIMELOCK_TTL_THRESHOLD, UNSET_TIMESTAMP,
emit_operation_scheduled, TimelockError, DONE_LEDGER, TIMELOCK_EXTEND_AMOUNT,
TIMELOCK_TTL_THRESHOLD, UNSET_LEDGER,
};

// ################## TYPES ##################
Expand Down Expand Up @@ -50,25 +50,25 @@ pub enum OperationState {
#[derive(Clone)]
#[contracttype]
pub enum TimelockStorageKey {
/// Minimum delay in seconds for operations
/// Minimum delay in ledgers for operations
MinDelay,
/// Maps operation ID to the timestamp when it will be in a
/// Maps operation ID to the ledger sequence number when it will be in a
/// [`OperationState::Ready`] state (Note: value is 0 for
/// [`OperationState::Unset`], 1 for [`OperationState:Done`]).
Timestamp(BytesN<32>),
/// [`OperationState::Unset`], 1 for [`OperationState::Done`]).
OperationLedger(BytesN<32>),
}

// ################## QUERY STATE ##################

/// Returns the minimum delay in seconds required for operations.
/// Returns the minimum delay in ledgers required for operations.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
///
/// # Returns
///
/// The minimum delay in seconds.
/// The minimum delay in ledgers.
///
/// # Errors
///
Expand All @@ -80,7 +80,7 @@ pub fn get_min_delay(e: &Env) -> u32 {
.unwrap_or_else(|| panic_with_error!(e, TimelockError::MinDelayNotSet))
}

/// Returns the timestamp at which an operation becomes ready.
/// Returns the ledger sequence number at which an operation becomes ready.
///
/// # Arguments
///
Expand All @@ -89,16 +89,17 @@ pub fn get_min_delay(e: &Env) -> u32 {
///
/// # Returns
///
/// - `UNSET_TIMESTAMP` for unset operations
/// - `DONE_TIMESTAMP` for done operations
/// - Unix timestamp when the operation becomes ready for scheduled operations
pub fn get_timestamp(e: &Env, operation_id: &BytesN<32>) -> u64 {
let key = TimelockStorageKey::Timestamp(operation_id.clone());
if let Some(timestamp) = e.storage().persistent().get::<_, u64>(&key) {
/// - `UNSET_LEDGER` for unset operations
/// - `DONE_LEDGER` for done operations
/// - Ledger sequence number when the operation becomes ready for scheduled
/// operations
pub fn get_operation_ledger(e: &Env, operation_id: &BytesN<32>) -> u32 {
let key = TimelockStorageKey::OperationLedger(operation_id.clone());
if let Some(ready_ledger) = e.storage().persistent().get::<_, u32>(&key) {
e.storage().persistent().extend_ttl(&key, TIMELOCK_TTL_THRESHOLD, TIMELOCK_EXTEND_AMOUNT);
timestamp
ready_ledger
} else {
UNSET_TIMESTAMP
UNSET_LEDGER
}
}

Expand All @@ -113,13 +114,13 @@ pub fn get_timestamp(e: &Env, operation_id: &BytesN<32>) -> u64 {
///
/// The current [`OperationState`] of the operation.
pub fn get_operation_state(e: &Env, operation_id: &BytesN<32>) -> OperationState {
let ready_timestamp = get_timestamp(e, operation_id);
let current_timestamp = e.ledger().timestamp();
let ready_ledger = get_operation_ledger(e, operation_id);
let current_ledger = e.ledger().sequence();

match ready_timestamp {
UNSET_TIMESTAMP => OperationState::Unset,
DONE_TIMESTAMP => OperationState::Done,
ready if ready > current_timestamp => OperationState::Waiting,
match ready_ledger {
UNSET_LEDGER => OperationState::Unset,
DONE_LEDGER => OperationState::Done,
ready if ready > current_ledger => OperationState::Waiting,
_ => OperationState::Ready,
}
}
Expand Down Expand Up @@ -172,7 +173,7 @@ pub fn is_operation_done(e: &Env, operation_id: &BytesN<32>) -> bool {
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `min_delay` - The new minimum delay in seconds.
/// * `min_delay` - The new minimum delay in ledgers.
///
/// # Events
///
Expand All @@ -196,7 +197,7 @@ pub fn set_min_delay(e: &Env, min_delay: u32) {
///
/// * `e` - Access to Soroban environment.
/// * `operation` - The operation to schedule.
/// * `delay` - The delay in seconds before the operation can be executed.
/// * `delay` - The delay in ledgers before the operation can be executed.
///
/// # Returns
///
Expand Down Expand Up @@ -235,11 +236,11 @@ pub fn schedule_operation(e: &Env, operation: &Operation, delay: u32) -> BytesN<
panic_with_error!(e, TimelockError::InsufficientDelay);
}

let current_timestamp = e.ledger().timestamp();
let ready_timestamp = current_timestamp + (delay as u64);
let current_ledger = e.ledger().sequence();
let ready_ledger = current_ledger.saturating_add(delay);

let key = TimelockStorageKey::Timestamp(id.clone());
e.storage().persistent().set(&key, &ready_timestamp);
let key = TimelockStorageKey::OperationLedger(id.clone());
e.storage().persistent().set(&key, &ready_ledger);

emit_operation_scheduled(
e,
Expand Down Expand Up @@ -337,8 +338,8 @@ pub fn set_execute_operation(e: &Env, operation: &Operation) {
panic_with_error!(e, TimelockError::UnexecutedPredecessor);
}

let key = TimelockStorageKey::Timestamp(id.clone());
e.storage().persistent().set(&key, &DONE_TIMESTAMP);
let key = TimelockStorageKey::OperationLedger(id.clone());
e.storage().persistent().set(&key, &DONE_LEDGER);

emit_operation_executed(
e,
Expand Down Expand Up @@ -377,7 +378,7 @@ pub fn cancel_operation(e: &Env, operation_id: &BytesN<32>) {
panic_with_error!(e, TimelockError::InvalidOperationState);
}

let key = TimelockStorageKey::Timestamp(operation_id.clone());
let key = TimelockStorageKey::OperationLedger(operation_id.clone());
e.storage().persistent().remove(&key);

emit_operation_cancelled(e, operation_id);
Expand Down
Loading
Loading