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
2 changes: 2 additions & 0 deletions multisig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name = "zenith-multisig"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
build = "../common/build_meta.rs"

[lib]
# rlib alongside cdylib so other contracts (options_market) can depend on
Expand Down
5 changes: 3 additions & 2 deletions multisig/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ pub enum DataKey {
pub fn approval_key(env: &Env, class: &Symbol, action_id: &BytesN<32>) -> BytesN<32> {
let mut buf = Bytes::new(env);
buf.extend_from_slice(APPROVAL_DOMAIN);
buf.append(&Bytes::from_array(env, &env.current_contract_address().to_string().into_bytes()));
buf.append(&Bytes::from_array(env, &class.to_string().into_bytes()));
use soroban_sdk::xdr::ToXdr;
buf.append(&env.current_contract_address().to_xdr(env));
buf.append(&class.to_xdr(env));
buf.append(&Bytes::from_array(env, &env.ledger().network_id().to_array()));
buf.append(&Bytes::from_array(env, &action_id.to_array()));
env.crypto().sha256(&buf).into()
Expand Down
28 changes: 28 additions & 0 deletions price_oracle/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ pub fn feeder_removed(env: &Env, feeder: Address) {
.publish((Symbol::new(env, "feeder_removed"),), feeder);
}

pub fn feeder_rotated(env: &Env, old_feeder: Address, new_feeder: Address) {
env.events().publish(
(Symbol::new(env, "feeder_rotated"),),
(old_feeder, new_feeder),
);
}

pub fn symbol_feeder_added(env: &Env, symbol: Symbol, feeder: Address) {
env.events().publish(
(Symbol::new(env, "symbol_feeder_added"), symbol),
feeder,
);
}

pub fn symbol_feeder_removed(env: &Env, symbol: Symbol, feeder: Address) {
env.events().publish(
(Symbol::new(env, "symbol_feeder_removed"), symbol),
feeder,
);
}

pub fn symbol_feeder_rotated(env: &Env, symbol: Symbol, old_feeder: Address, new_feeder: Address) {
env.events().publish(
(Symbol::new(env, "symbol_feeder_rotated"), symbol),
(old_feeder, new_feeder),
);
}

pub fn price_reported(env: &Env, feeder: Address, symbol: Symbol, price: i128) {
env.events()
.publish((Symbol::new(env, "price_reported"), feeder, symbol), price);
Expand Down
156 changes: 152 additions & 4 deletions price_oracle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ enum Auth {
}

fn admin(env: &Env) -> Address {
zenith_common::get_admin(env, &DataKey::Admin)
zenith_common::get_admin(env, &DataKey::Admin, Error::InternalInvariant).unwrap_or_else(|e| fail(env, e))
}

fn fail(env: &Env, err: Error) -> ! {
Expand Down Expand Up @@ -94,7 +94,9 @@ fn require_auth(env: &Env, auth: &Auth) {
}

fn require_not_paused(env: &Env) {
zenith_common::require_not_paused(env, &DataKey::Paused, Error::ContractPaused);
if let Err(e) = zenith_common::require_not_paused(env, &DataKey::Paused, Error::ContractPaused) {
fail(env, e);
}
}

fn max_staleness(env: &Env) -> u64 {
Expand Down Expand Up @@ -136,7 +138,7 @@ fn set_min_reports_inner(env: &Env, auth: Auth, count: u32) {
fn transfer_admin_inner(env: &Env, auth: Auth, new_admin: Address) {
migrations::require_current(env);
require_auth(env, &auth);
let old = zenith_common::set_admin(env, &DataKey::Admin, &new_admin);
let old = zenith_common::set_admin(env, &DataKey::Admin, &new_admin, Error::InternalInvariant).unwrap_or_else(|e| fail(env, e));
events::admin_transferred(env, old, new_admin);
}

Expand Down Expand Up @@ -175,11 +177,88 @@ fn remove_feeder_inner(env: &Env, auth: Auth, feeder: Address) {
events::feeder_removed(env, feeder);
}

fn rotate_feeder_inner(env: &Env, auth: Auth, old_feeder: Address, new_feeder: Address) {
migrations::require_current(env);
require_not_paused(env);
require_auth(env, &auth);
if old_feeder == new_feeder {
fail(env, Error::FeederAlreadyAdded);
}
if !storage::is_feeder(env, &old_feeder) {
fail(env, Error::FeederNotFound);
}
if storage::is_feeder(env, &new_feeder) {
fail(env, Error::FeederAlreadyAdded);
}
storage::replace_feeder(env, &old_feeder, &new_feeder);
events::feeder_rotated(env, old_feeder, new_feeder);
}

fn add_feeder_for_symbol_inner(env: &Env, auth: Auth, symbol: Symbol, feeder: Address) {
migrations::require_current(env);
require_not_paused(env);
require_auth(env, &auth);
if oracle_adapter::symbol_precision(env, &symbol).is_none() {
fail(env, Error::UnknownSymbol);
}
if storage::is_feeder_for_symbol(env, &symbol, &feeder) {
fail(env, Error::FeederAlreadyAdded);
}
if storage::symbol_feeder_list(env, &symbol).len() >= MAX_FEEDERS {
fail(env, Error::TooManyFeeders);
}
storage::add_symbol_feeder(env, &symbol, &feeder);
events::symbol_feeder_added(env, symbol, feeder);
}

fn remove_feeder_for_symbol_inner(env: &Env, auth: Auth, symbol: Symbol, feeder: Address) {
migrations::require_current(env);
require_not_paused(env);
require_auth(env, &auth);
if oracle_adapter::symbol_precision(env, &symbol).is_none() {
fail(env, Error::UnknownSymbol);
}
if !storage::is_feeder_for_symbol(env, &symbol, &feeder) {
fail(env, Error::FeederNotFound);
}
if storage::symbol_feeder_list(env, &symbol).len() <= min_reports(env) {
fail(env, Error::BelowQuorum);
}
storage::remove_symbol_feeder(env, &symbol, &feeder);
events::symbol_feeder_removed(env, symbol, feeder);
}

fn rotate_feeder_for_symbol_inner(
env: &Env,
auth: Auth,
symbol: Symbol,
old_feeder: Address,
new_feeder: Address,
) {
migrations::require_current(env);
require_not_paused(env);
require_auth(env, &auth);
if oracle_adapter::symbol_precision(env, &symbol).is_none() {
fail(env, Error::UnknownSymbol);
}
if old_feeder == new_feeder {
fail(env, Error::FeederAlreadyAdded);
}
if !storage::is_feeder_for_symbol(env, &symbol, &old_feeder) {
fail(env, Error::FeederNotFound);
}
if storage::is_feeder_for_symbol(env, &symbol, &new_feeder) {
fail(env, Error::FeederAlreadyAdded);
}
storage::replace_symbol_feeder(env, &symbol, &old_feeder, &new_feeder);
events::symbol_feeder_rotated(env, symbol, old_feeder, new_feeder);
}

fn report_inner(env: &Env, report: FeederReport) {
migrations::require_current(env);
require_not_paused(env);
report.feeder.require_auth();
if !storage::is_feeder(env, &report.feeder) {
if !storage::is_effective_feeder(env, &report.symbol, &report.feeder) {
fail(env, Error::NotAFeeder);
}
if let Err(e) = oracle_adapter::ingest(env, report, max_staleness(env)) {
Expand Down Expand Up @@ -427,6 +506,70 @@ impl PriceOracle {
storage::feeder_list(&env)
}

/// Admin rotates/replaces an existing feeder with a new address.
pub fn rotate_feeder(env: Env, old_feeder: Address, new_feeder: Address) {
ttl::extend_instance(&env);
rotate_feeder_inner(&env, Auth::Admin, old_feeder, new_feeder);
}

/// Multisig twin of `rotate_feeder`.
pub fn rotate_feeder_via_multisig(
env: Env,
multisig_contract: Address,
action_id: BytesN<32>,
old_feeder: Address,
new_feeder: Address,
) {
ttl::extend_instance(&env);
rotate_feeder_inner(
&env,
Auth::Multisig(multisig_contract, action_id),
old_feeder,
new_feeder,
);
}

/// Admin registers a feeder authorized specifically for `symbol`.
pub fn add_feeder_for_symbol(env: Env, symbol: Symbol, feeder: Address) {
ttl::extend_instance(&env);
add_feeder_for_symbol_inner(&env, Auth::Admin, symbol, feeder);
}


/// Admin revokes a feeder authorized for `symbol`.
pub fn remove_feeder_for_symbol(env: Env, symbol: Symbol, feeder: Address) {
ttl::extend_instance(&env);
remove_feeder_for_symbol_inner(&env, Auth::Admin, symbol, feeder);
}


/// Admin rotates/replaces a feeder authorized specifically for `symbol`.
pub fn rotate_feeder_for_symbol(
env: Env,
symbol: Symbol,
old_feeder: Address,
new_feeder: Address,
) {
ttl::extend_instance(&env);
rotate_feeder_for_symbol_inner(&env, Auth::Admin, symbol, old_feeder, new_feeder);
}


pub fn is_feeder_for_symbol(env: Env, symbol: Symbol, address: Address) -> bool {
ttl::extend_instance(&env);
storage::is_feeder_for_symbol(&env, &symbol, &address)
}

pub fn get_feeder_count_for_symbol(env: Env, symbol: Symbol) -> u32 {
ttl::extend_instance(&env);
storage::symbol_feeder_list(&env, &symbol).len()
}

pub fn get_feeders_for_symbol(env: Env, symbol: Symbol) -> Vec<Address> {
ttl::extend_instance(&env);
storage::symbol_feeder_list(&env, &symbol)
}

// ── reports ─────────────────────────────────────────────────────────────

/// A feeder reports `symbol`'s current price at the oracle's 7-decimal
Expand Down Expand Up @@ -493,4 +636,9 @@ impl PriceOracle {
pub fn decimals(_env: Env) -> u32 {
PRICE_DECIMALS
}

pub fn schema_version(env: Env) -> u32 {
ttl::extend_instance(&env);
zenith_common::migrations::schema_version(&env)
}
}
4 changes: 2 additions & 2 deletions price_oracle/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,8 +512,8 @@ mod proptests {
let hi = rescale(v, down, 0, Rounding::Ceil).unwrap();
let f = pow10(down).unwrap();
prop_assert!(hi - lo <= 1);
prop_assert!(lo.checked_mul(f).map_or(true, |x| x <= v));
prop_assert!(hi.checked_mul(f).map_or(true, |x| x >= v));
prop_assert!(lo.checked_mul(f).is_none_or(|x| x <= v));
prop_assert!(hi.checked_mul(f).is_none_or(|x| x >= v));
}

#[test]
Expand Down
Loading