Skip to content

Commit

Permalink
build: reorganize subcontracts into separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
chadoh committed Jul 22, 2024
1 parent 3ac25cd commit ef3551b
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 84 deletions.
72 changes: 48 additions & 24 deletions contracts/data-feed/src/data_feed.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use loam_sdk::{
soroban_sdk::{self, contracttype, env, vec, Address, Bytes, Lazy, Map, Vec},
soroban_sdk::{
self, contracttype, env, panic_with_error, vec, Address, Bytes, Env, Lazy, Map, Vec,
},
IntoKey,
};
use loam_subcontract_core::Core;

use crate::subcontract::{Asset, IsReflector, IsReflectorAdmin, IsSep40, PriceData};
use crate::sep40::{Asset, IsSep40, IsSep40Admin, PriceData};
use crate::u64_extensions::U64Extensions;
use crate::Contract;

#[contracttype]
Expand All @@ -17,7 +19,6 @@ pub struct DataFeed {
// prices: Map<(Asset, u32), Vec<PriceData>>,
resolution: u32,
last_timestamp: u64,
retention_period: u64,
// x_last_price: Map<(Asset, Asset), PriceData>,
// x_price: Map<(Asset, Asset, u64), PriceData>,
// x_prices: Map<(Asset, Asset, u32), Vec<PriceData>>,
Expand All @@ -34,8 +35,6 @@ impl DataFeed {
decimals: u32,
// The resolution of the prices.
resolution: u32,
// The retention period for the prices.
retention_period: u64,
) -> Self {
let mut asset_map = Map::new(env());
for asset in assets.into_iter() {
Expand All @@ -47,7 +46,6 @@ impl DataFeed {
decimals,
resolution,
last_timestamp: 0,
retention_period,
}
}
}
Expand All @@ -61,28 +59,14 @@ impl Default for DataFeed {
Asset::Stellar(env().current_contract_address()),
0,
0,
0,
)
}
}

impl IsReflectorAdmin for DataFeed {
fn reflector_init(
&self,
assets: Vec<Asset>,
base: Asset,
decimals: u32,
resolution: u32,
retention_period: u64,
) {
impl IsSep40Admin for DataFeed {
fn sep40_init(&self, assets: Vec<Asset>, base: Asset, decimals: u32, resolution: u32) {
Contract::require_auth();
DataFeed::set_lazy(DataFeed::new(
assets,
base,
decimals,
resolution,
retention_period,
));
DataFeed::set_lazy(DataFeed::new(assets, base, decimals, resolution));
}

fn add_assets(&mut self, assets: Vec<Asset>) {
Expand All @@ -91,4 +75,44 @@ impl IsReflectorAdmin for DataFeed {
self.assets.set(asset, ());
}
}

fn set_price(&mut self, updates: Vec<i128>, timestamp: u64) {
Contract::require_auth();
let updates_len = updates.len();
if updates_len == 0 || updates_len >= 256 {
panic!("The assets update length or prices update length is invalid");
}
let timeframe: u64 = self.resolution.into();
let ledger_timestamp = now();
if timestamp == 0
|| !timestamp.is_valid_timestamp(timeframe)
|| timestamp > ledger_timestamp
{
panic!("The prices timestamp is invalid");
}

// from reflector implementation
// let retention_period = e.get_retention_period();

// let ledgers_to_live: u32 = ((retention_period / 1000 / 5) + 1) as u32;

//iterate over the updates
for (i, price) in updates.iter().enumerate() {
//don't store zero prices
if price == 0 {
continue;
}
let asset = i as u8;
//store the new price
e.set_price(asset, price, timestamp, ledgers_to_live);
}
if timestamp > self.last_timestamp {
self.last_timestamp = timestamp;
}
}
}

/// Get the timestamp from env, converted to milliseconds
fn now() -> u64 {
env().ledger().timestamp() * 1000
}
9 changes: 6 additions & 3 deletions contracts/data-feed/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
#![no_std]
use loam_sdk::{derive_contract, soroban_sdk::Vec};
use loam_subcontract_core::{admin::Admin, Core};
use sep40::Asset;

pub mod data_feed;
pub mod subcontract;
pub mod reflector;
pub mod sep40;
pub mod u64_extensions;

use data_feed::DataFeed;
use subcontract::{Asset, PriceData, Reflector, ReflectorAdmin, Sep40};
use sep40::Sep40Admin;

#[derive_contract(
Core(Admin),
// Sep40(DataFeed),
// Reflector(DataFeed),
ReflectorAdmin(DataFeed)
Sep40Admin(DataFeed)
)]
pub struct Contract;

Expand Down
61 changes: 61 additions & 0 deletions contracts/data-feed/src/reflector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::sep40::{Asset, PriceData};
use loam_sdk::{
soroban_sdk::{Lazy, Vec},
subcontract,
};

#[subcontract]
/// SEP40 extension implemented by Reflector Network: https://reflector.network/docs/interface
pub trait IsReflector {
/// Get the most recent price update timestamp
fn last_timestamp(&self) -> u64;

/// Get the most recent cross price record for the pair of assets
fn x_last_price(&self, base_asset: Asset, quote_asset: Asset) -> Option<PriceData>;

/// Get the cross price for the pair of assets at specific timestamp
fn x_price(&self, base_asset: Asset, quote_asset: Asset, timestamp: u64) -> Option<PriceData>;

/// Get last N cross price records of for the pair of assets
fn x_prices(
&self,
base_asset: Asset,
quote_asset: Asset,
records: u32,
) -> Option<Vec<PriceData>>;

/// Get the time-weighted average price for the given asset over N recent records
fn twap(&self, asset: Asset, records: u32) -> Option<i128>;

/// Get the time-weighted average cross price for the given asset pair over N recent records
fn x_twap(&self, base_asset: Asset, quote_asset: Asset, records: u32) -> Option<i128>;

/// Get historical records retention period, in seconds
fn period(&self) -> Option<u64>;

// Part of Reflector Network implementation, but omitted here because this is implemented via
// the SmartDeploy / Loam Deploy Wasm package management infrastructure.
// /// Get contract protocol version
// fn version(&self, ) -> u32;

// Part of the Reflector Network implementation, but ommitted here because `get_admin` is
// already implemented by the `Core` subcontract.
// /// Get contract admin address
// fn admin(&self, ) -> Option<Address>;
}

#[subcontract]
/// The Reflector contract is a superset of SEP40, and needs some of its own data, such as
/// `retention_period`, in order to work.
pub trait IsReflectorAdmin {
/// Sets history retention period for the prices. Can be invoked only by the admin account.
///
/// # Arguments
///
/// * `period` - History retention period (in seconds)
///
/// # Panics
///
/// Panics if the caller doesn't match admin address, or if the period/version is invalid
fn set_retention_period(&mut self, period: u64);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use loam_sdk::{
soroban_sdk::{self, Address, Lazy, Vec},
soroban_sdk::{self, Lazy, Vec},
subcontract,
};

Expand Down Expand Up @@ -45,62 +45,22 @@ pub trait IsSep40 {
}

#[subcontract]
/// SEP40 extension implemented by Reflector Network: https://reflector.network/docs/interface
pub trait IsReflector {
/// Get the most recent price update timestamp
fn last_timestamp(&self) -> u64;

/// Get the most recent cross price record for the pair of assets
fn x_last_price(&self, base_asset: Asset, quote_asset: Asset) -> Option<PriceData>;

/// Get the cross price for the pair of assets at specific timestamp
fn x_price(&self, base_asset: Asset, quote_asset: Asset, timestamp: u64) -> Option<PriceData>;

/// Get last N cross price records of for the pair of assets
fn x_prices(
&self,
base_asset: Asset,
quote_asset: Asset,
records: u32,
) -> Option<Vec<PriceData>>;

/// Get the time-weighted average price for the given asset over N recent records
fn twap(&self, asset: Asset, records: u32) -> Option<i128>;

/// Get the time-weighted average cross price for the given asset pair over N recent records
fn x_twap(&self, base_asset: Asset, quote_asset: Asset, records: u32) -> Option<i128>;

/// Get historical records retention period, in seconds
fn period(&self) -> Option<u64>;

// Part of Reflector Network implementation, but omitted here because this is implemented via
// the SmartDeploy / Loam Deploy Wasm package management infrastructure.
// /// Get contract protocol version
// fn version(&self, ) -> u32;

// Part of the Reflector Network implementation, but ommitted here because `get_admin` is
// already implemented by the `Core` subcontract.
// /// Get contract admin address
// fn admin(&self, ) -> Option<Address>;
}

#[subcontract]
/// While not part of the official consumer-facing spec, every SEP40/Reflector contract will need
/// While not part of the official consumer-facing spec, every SEP40 contract will need
/// to provide a way for Oracles to update the contract with new prices. This is an interface for
/// that, and also for other administrative functions, like initializing the contract.
///
/// Since Reflector's contract is a superset of SEP40, this admin interface is suitable for use
/// with either one. It contains extra fields, such as `retention_period`, which are not useful to
/// a strict implementation of SEP40.
pub trait IsReflectorAdmin {
/// Initialize the contract with the given configuration.
pub trait IsSep40Admin {
/// Initialize the subcontract with the given configuration.
///
/// This assumes that you have already:
///
/// - instantiated the Core subcontract with `admin_set`
///
/// # Panics
///
/// - if contract has already been initialized
/// - if `sep40_init` has already been called
/// - if `admin_set` has not yet been called and there is therefore not yet an admin
/// - if admin did not sign the transaction envelope
fn reflector_init(
fn sep40_init(
&self,
// The assets supported by the contract.
assets: Vec<Asset>,
Expand All @@ -110,19 +70,24 @@ pub trait IsReflectorAdmin {
decimals: u32,
// The resolution of the prices.
resolution: u32,
// The retention period for the prices.
retention_period: u64,
);

/// Adds given assets to the contract quoted assets list. Can be invoked only by the admin account.
///
/// # Panics
///
/// Panics if the caller doesn't match admin, or if the assets are already added
fn add_assets(&mut self, assets: Vec<Asset>);

/// Record new price feed history snapshot. Can be invoked only by the admin account.
///
/// # Arguments
///
/// * `admin` - Admin account address
/// * `assets` - Assets to add
/// * `version` - Configuration protocol version
/// * `updates` - Price feed snapshot
/// * `timestamp` - History snapshot timestamp
///
/// # Panics
///
/// Panics if the caller doesn't match admin address, or if the assets are already added
fn add_assets(&mut self, assets: Vec<Asset>);
/// Panics if the caller doesn't match admin address, or if the price snapshot record is invalid
fn set_price(&mut self, updates: Vec<i128>, timestamp: u64);
}
17 changes: 17 additions & 0 deletions contracts/data-feed/src/u64_extensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pub trait U64Extensions {
fn get_normalized_timestamp(self, timeframe: u64) -> u64;
fn is_valid_timestamp(self, timeframe: u64) -> bool;
}

impl U64Extensions for u64 {
fn get_normalized_timestamp(self, timeframe: u64) -> u64 {
if (self == 0) || (timeframe == 0) {
return 0;
}
(self / timeframe) * timeframe
}

fn is_valid_timestamp(self, timeframe: u64) -> bool {
self == Self::get_normalized_timestamp(self, timeframe)
}
}

0 comments on commit ef3551b

Please sign in to comment.