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
53 changes: 52 additions & 1 deletion contracts/price-oracle/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum DataKey {
Admin,
Provider(Address),
ProviderWeight(Address),
ProviderLastLedger(Address),
IsPaused,
ActiveRelayers,
CommunityCouncil,
Expand Down Expand Up @@ -134,13 +135,47 @@ pub fn _is_provider(env: &Env, addr: &Address) -> bool {
.unwrap_or(false)
}

/// Panics if the caller is not a whitelisted provider.
/// Panic if the caller is not a whitelisted provider.
pub fn _require_provider(env: &Env, caller: &Address) {
if !_is_provider(env, caller) {
panic!("Unauthorised: caller is not a whitelisted provider");
}
}

/// Prune inactive relayers from the whitelist.
///
/// Iterates through the active relayers list and removes any provider that has not
/// submitted a price update within `max_inactive_ledgers` ledgers.
///
/// # Arguments
/// * `env` - The contract environment
/// * `max_inactive_ledgers` - The maximum number of ledgers a provider can be inactive
///
/// # Panics
/// Panics if the caller is not an authorized admin.
pub fn _prune_inactive_relayers(env: &Env, caller: &Address, max_inactive_ledgers: u32) -> u32 {
// Require admin authorization
_require_authorized(env, caller);

let current_ledger = env.ledger().sequence();
let relayers = _get_active_relayers(env);
let mut pruned_count: u32 = 0;

for relayer in relayers.iter() {
let last_ledger = _get_provider_last_ledger(env, &relayer);

// If provider has never submitted (last_ledger = 0) or has been inactive too long
let inactive_ledgers = current_ledger.saturating_sub(last_ledger);

if last_ledger == 0 || inactive_ledgers > max_inactive_ledgers {
_remove_provider(env, &relayer);
pruned_count += 1;
}
}

pruned_count
}

pub fn _set_provider_weight(env: &Env, provider: &Address, weight: u32) {
env.storage()
.instance()
Expand All @@ -154,6 +189,22 @@ pub fn _get_provider_weight(env: &Env, provider: &Address) -> u32 {
.unwrap_or(0)
}

/// Set the last ledger sequence when a provider submitted a price update.
pub fn _set_provider_last_ledger(env: &Env, provider: &Address, ledger: u32) {
env.storage()
.instance()
.set(&DataKey::ProviderLastLedger(provider.clone()), &ledger);
}

/// Get the last ledger sequence when a provider submitted a price update.
/// Returns 0 if the provider has never submitted a price.
pub fn _get_provider_last_ledger(env: &Env, provider: &Address) -> u32 {
env.storage()
.instance()
.get::<DataKey, u32>(&DataKey::ProviderLastLedger(provider.clone()))
.unwrap_or(0)
}

/// Get the list of all active relayers (whitelisted providers).
pub fn _get_active_relayers(env: &Env) -> Vec<Address> {
env.storage()
Expand Down
36 changes: 36 additions & 0 deletions contracts/price-oracle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ pub trait StellarFlowTrait {
///
/// Returns true if the contract is frozen, false otherwise.
fn is_frozen(env: Env) -> bool;

/// Prune inactive relayers from the whitelist.
///
/// This is a "Pull" mechanism (not automated) that allows admins to remove
/// providers that have not submitted a price update within `max_inactive_ledgers`.
///
/// # Arguments
/// * `admin` - The admin address authorizing this action
/// * `max_inactive_ledgers` - The maximum number of ledgers a provider can be inactive
///
/// # Returns
/// The number of providers that were pruned.
fn prune_inactive_relayers(env: Env, admin: Address, max_inactive_ledgers: u32) -> u32;
}

#[contractclient(name = "TokenContractClient")]
Expand Down Expand Up @@ -1213,6 +1226,10 @@ impl PriceOracle {
}
}

// Track this provider's activity
let current_ledger = env.ledger().sequence();
crate::auth::_set_provider_last_ledger(&env, &source, current_ledger);

// Add the normalized price entry to the buffer
let entry = PriceBufferEntry {
price: normalized,
Expand Down Expand Up @@ -1723,6 +1740,25 @@ impl PriceOracle {
pub fn get_price_update_subscribers(env: Env) -> soroban_sdk::Vec<Address> {
callbacks::get_subscribers(&env)
}

/// Prune inactive relayers from the whitelist.
///
/// This is a "Pull" mechanism (not automated) that allows admins to remove
/// providers that have not submitted a price update within `max_inactive_ledgers`.
///
/// # Arguments
/// * `admin` - The admin address authorizing this action
/// * `max_inactive_ledgers` - The maximum number of ledgers a provider can be inactive
///
/// # Returns
/// The number of providers that were pruned.
pub fn prune_inactive_relayers(env: Env, admin: Address, max_inactive_ledgers: u32) -> u32 {
_require_not_destroyed(&env);
crate::auth::_require_not_frozen(&env);
admin.require_auth();

crate::auth::_prune_inactive_relayers(&env, &admin, max_inactive_ledgers)
}
}

mod asset_symbol;
Expand Down