Skip to content

feat: frontend data provider #571

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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 src/core/sentinel.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod sentinel {

// Helper constant to set the starting index for iterating over the
// yangs in the order they were added
const LOOP_START: u64 = 1;
const LOOP_START: u32 = 1;

//
// Storage
Expand All @@ -42,10 +42,10 @@ pub mod sentinel {
// mapping between a yang address and our deployed Gate
yang_to_gate: LegacyMap::<ContractAddress, IGateDispatcher>,
// length of the yang_addresses array
yang_addresses_count: u64,
yang_addresses_count: u32,
// array of yang addresses added to the Shrine via this Sentinel
// starts from index 1
yang_addresses: LegacyMap::<u64, ContractAddress>,
yang_addresses: LegacyMap::<u32, ContractAddress>,
// The Shrine associated with this Sentinel
shrine: IShrineDispatcher,
// mapping between a yang address and the cap on the yang's asset in the
Expand Down Expand Up @@ -119,8 +119,8 @@ pub mod sentinel {
}

fn get_yang_addresses(self: @ContractState) -> Span<ContractAddress> {
let mut idx: u64 = LOOP_START;
let loop_end: u64 = self.yang_addresses_count.read() + LOOP_START;
let mut idx: u32 = LOOP_START;
let loop_end: u32 = self.yang_addresses_count.read() + LOOP_START;
let mut addresses: Array<ContractAddress> = ArrayTrait::new();
loop {
if idx == loop_end {
Expand All @@ -131,11 +131,11 @@ pub mod sentinel {
}
}

fn get_yang_addresses_count(self: @ContractState) -> u64 {
fn get_yang_addresses_count(self: @ContractState) -> u32 {
self.yang_addresses_count.read()
}

fn get_yang(self: @ContractState, idx: u64) -> ContractAddress {
fn get_yang(self: @ContractState, idx: u32) -> ContractAddress {
self.yang_addresses.read(idx)
}

Expand Down Expand Up @@ -190,7 +190,7 @@ pub mod sentinel {
let gate = IGateDispatcher { contract_address: gate };
assert(gate.get_asset() == yang, 'SE: Asset of gate is not yang');

let index: u64 = self.yang_addresses_count.read() + 1;
let index: u32 = self.yang_addresses_count.read() + 1;
self.yang_addresses_count.write(index);
self.yang_addresses.write(index, yang);
self.yang_to_gate.write(yang, gate);
Expand Down
80 changes: 40 additions & 40 deletions src/core/shrine.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,46 @@ pub mod shrine {
self.deposits.read((yang_id, trove_id))
}

// Returns an ordered array of the `YangBalance` struct for a trove's deposits.
// Starts from yang ID 1.
// Note that zero values are added to the return array because downstream
// computation assumes the full array of yangs.
fn get_trove_deposits(self: @ContractState, trove_id: u64) -> Span<YangBalance> {
let mut yang_balances: Array<YangBalance> = ArrayTrait::new();

let mut current_yang_id: u32 = START_YANG_IDX;
let loop_end: u32 = self.yangs_count.read() + START_YANG_IDX;
loop {
if current_yang_id == loop_end {
break yang_balances.span();
}

let deposited: Wad = self.deposits.read((current_yang_id, trove_id));
yang_balances.append(YangBalance { yang_id: current_yang_id, amount: deposited });

current_yang_id += 1;
}
}

// Returns an ordered array of the `YangBalance` struct for the total deposited yangs in the Shrine.
// Starts from yang ID 1.
fn get_shrine_deposits(self: @ContractState) -> Span<YangBalance> {
let mut yang_balances: Array<YangBalance> = ArrayTrait::new();

let mut current_yang_id: u32 = START_YANG_IDX;
let loop_end: u32 = self.yangs_count.read() + START_YANG_IDX;
loop {
if current_yang_id == loop_end {
break yang_balances.span();
}

let yang_total: Wad = self.yang_total.read(current_yang_id);
yang_balances.append(YangBalance { yang_id: current_yang_id, amount: yang_total });

current_yang_id += 1;
}
}

fn get_budget(self: @ContractState) -> SignedWad {
self.budget.read()
}
Expand Down Expand Up @@ -1394,46 +1434,6 @@ pub mod shrine {
}
}

// Returns an ordered array of the `YangBalance` struct for a trove's deposits.
// Starts from yang ID 1.
// Note that zero values are added to the return array because downstream
// computation assumes the full array of yangs.
fn get_trove_deposits(self: @ContractState, trove_id: u64) -> Span<YangBalance> {
let mut yang_balances: Array<YangBalance> = ArrayTrait::new();

let mut current_yang_id: u32 = START_YANG_IDX;
let loop_end: u32 = self.yangs_count.read() + START_YANG_IDX;
loop {
if current_yang_id == loop_end {
break yang_balances.span();
}

let deposited: Wad = self.deposits.read((current_yang_id, trove_id));
yang_balances.append(YangBalance { yang_id: current_yang_id, amount: deposited });

current_yang_id += 1;
}
}

// Returns an ordered array of the `YangBalance` struct for the total deposited yangs in the Shrine.
// Starts from yang ID 1.
fn get_shrine_deposits(self: @ContractState) -> Span<YangBalance> {
let mut yang_balances: Array<YangBalance> = ArrayTrait::new();

let mut current_yang_id: u32 = START_YANG_IDX;
let loop_end: u32 = self.yangs_count.read() + START_YANG_IDX;
loop {
if current_yang_id == loop_end {
break yang_balances.span();
}

let yang_total: Wad = self.yang_total.read(current_yang_id);
yang_balances.append(YangBalance { yang_id: current_yang_id, amount: yang_total });

current_yang_id += 1;
}
}

// Returns a tuple of:
// 1. the custom threshold (maximum LTV before liquidation)
// 2. the total value of the yangs, at a given interval
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/ISentinel.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ pub trait ISentinel<TContractState> {
fn get_gate_address(self: @TContractState, yang: ContractAddress) -> ContractAddress;
fn get_gate_live(self: @TContractState, yang: ContractAddress) -> bool;
fn get_yang_addresses(self: @TContractState) -> Span<ContractAddress>;
fn get_yang_addresses_count(self: @TContractState) -> u64;
fn get_yang(self: @TContractState, idx: u64) -> ContractAddress;
fn get_yang_addresses_count(self: @TContractState) -> u32;
fn get_yang(self: @TContractState, idx: u32) -> ContractAddress;
fn get_yang_asset_max(self: @TContractState, yang: ContractAddress) -> u128;
fn get_asset_amt_per_yang(self: @TContractState, yang: ContractAddress) -> Wad;
// external
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/IShrine.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub trait IShrine<TContractState> {
fn get_protocol_owned_troves_debt(self: @TContractState) -> Wad;
fn get_yangs_count(self: @TContractState) -> u32;
fn get_deposit(self: @TContractState, yang: ContractAddress, trove_id: u64) -> Wad;
fn get_trove_deposits(self: @TContractState, trove_id: u64) -> Span<YangBalance>;
fn get_shrine_deposits(self: @TContractState) -> Span<YangBalance>;
fn get_budget(self: @TContractState) -> SignedWad;
fn get_yang_price(self: @TContractState, yang: ContractAddress, interval: u64) -> (Wad, Wad);
fn get_yang_rate(self: @TContractState, yang: ContractAddress, rate_era: u64) -> Ray;
Expand Down
7 changes: 7 additions & 0 deletions src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ mod utils {
pub mod exp;
pub mod math;
pub mod reentrancy_guard;
pub mod upgradeable;
}

mod periphery {
pub mod frontend_data_provider;
pub mod interfaces;
pub mod roles;
}

// mock used for local devnet deployment
Expand Down
210 changes: 210 additions & 0 deletions src/periphery/frontend_data_provider.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#[starknet::contract]
pub mod frontend_data_provider {
use access_control::access_control_component;
use opus::interfaces::IGate::{IGateDispatcher, IGateDispatcherTrait};
use opus::interfaces::ISentinel::{ISentinelDispatcher, ISentinelDispatcherTrait};
use opus::interfaces::IShrine::{IShrineDispatcher, IShrineDispatcherTrait};
use opus::periphery::interfaces::IFrontendDataProvider;
use opus::periphery::roles::frontend_data_provider_roles;
use opus::types::{Health, RecoveryModeInfo, ShrineAssetInfo, TroveAssetInfo, YangBalance, YinInfo};
use opus::utils::upgradeable::{IUpgradeable, upgradeable_component};
use starknet::{ClassHash, ContractAddress};
use wadray::{Ray, Wad};

//
// Components
//

component!(path: access_control_component, storage: access_control, event: AccessControlEvent);

#[abi(embed_v0)]
impl AccessControlPublic = access_control_component::AccessControl<ContractState>;
impl AccessControlHelpers = access_control_component::AccessControlHelpers<ContractState>;

component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent);

impl UpgradeableHelpers = upgradeable_component::UpgradeableHelpers<ContractState>;

//
// Storage
//

#[storage]
struct Storage {
// components
#[substorage(v0)]
access_control: access_control_component::Storage,
#[substorage(v0)]
upgradeable: upgradeable_component::Storage,
// Sentinel associated with the Shrine
sentinel: ISentinelDispatcher,
shrine: IShrineDispatcher,
}

//
// Events
//

#[event]
#[derive(Copy, Drop, starknet::Event, PartialEq)]
pub enum Event {
AccessControlEvent: access_control_component::Event,
UpgradeableEvent: upgradeable_component::Event,
}

//
// Constructor
//

#[constructor]
fn constructor(ref self: ContractState, shrine: ContractAddress, sentinel: ContractAddress) {
self.shrine.write(IShrineDispatcher { contract_address: shrine });
self.sentinel.write(ISentinelDispatcher { contract_address: sentinel });
}

//
// Upgradeable
//

#[abi(embed_v0)]
impl IUpgradeableImpl of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
self.access_control.assert_has_role(frontend_data_provider_roles::UPGRADE);
self.upgradeable.upgrade(new_class_hash);
}
}

//
// External functions
//

#[abi(embed_v0)]
impl IFrontendDataProviderImpl of IFrontendDataProvider<ContractState> {
fn get_yin_info(self: @ContractState) -> YinInfo {
let shrine: IShrineDispatcher = self.shrine.read();

YinInfo {
yin_spot_price: shrine.get_yin_spot_price(),
yin_total_supply: shrine.get_total_yin(),
yin_ceiling: shrine.get_debt_ceiling(),
}
}

fn get_recovery_mode_info(self: @ContractState) -> RecoveryModeInfo {
let shrine: IShrineDispatcher = self.shrine.read();
let shrine_health: Health = shrine.get_shrine_health();

let target_factor: Ray = shrine.get_recovery_mode_target_factor();
let buffer_factor: Ray = shrine.get_recovery_mode_buffer_factor();
let target_ltv: Ray = target_factor * shrine_health.threshold;
let buffer_ltv: Ray = (target_factor + buffer_factor) * shrine_health.threshold;

RecoveryModeInfo { is_recovery_mode: shrine.is_recovery_mode(), target_ltv, buffer_ltv }
}

// Returns an ordered array of TroveAssetInfo struct for a trove
fn get_trove_assets_info(self: @ContractState, trove_id: u64) -> Span<TroveAssetInfo> {
let shrine: IShrineDispatcher = self.shrine.read();
let sentinel: ISentinelDispatcher = self.sentinel.read();

let mut trove_yang_balances: Span<YangBalance> = shrine.get_trove_deposits(trove_id);
let mut yang_addresses: Span<ContractAddress> = sentinel.get_yang_addresses();

assert(trove_yang_balances.len() == yang_addresses.len(), 'FDP: Length mismatch');

let mut yang_infos: Array<TroveAssetInfo> = ArrayTrait::new();
let current_rate_era: u64 = shrine.get_current_rate_era();
loop {
match trove_yang_balances.pop_front() {
Option::Some(yang_balance) => {
let yang: ContractAddress = *yang_addresses.pop_front().unwrap();
assert(sentinel.get_yang(*yang_balance.yang_id) == yang, 'FDP: Address mismatch');

let (shrine_asset_info, yang_price) = self
.get_shrine_yang_info_helper(
shrine, sentinel, yang, *yang_balance.amount, current_rate_era
);

let asset_amt: u128 = sentinel.convert_to_assets(yang, *yang_balance.amount);
let trove_yang_info = TroveAssetInfo {
shrine_asset_info, amount: asset_amt, value: *yang_balance.amount * yang_price,
};
yang_infos.append(trove_yang_info);
},
Option::None => { break yang_infos.span(); }
}
}
}

// Returns an ordered array of ShrineAssetInfo struct for the Shrine
fn get_shrine_assets_info(self: @ContractState) -> Span<ShrineAssetInfo> {
let shrine: IShrineDispatcher = self.shrine.read();
let sentinel: ISentinelDispatcher = self.sentinel.read();

let mut shrine_yang_balances: Span<YangBalance> = shrine.get_shrine_deposits();
let mut yang_addresses: Span<ContractAddress> = sentinel.get_yang_addresses();

assert(shrine_yang_balances.len() == yang_addresses.len(), 'FDP: Length mismatch');

let mut yang_infos: Array<ShrineAssetInfo> = ArrayTrait::new();
let current_rate_era: u64 = shrine.get_current_rate_era();
loop {
match shrine_yang_balances.pop_front() {
Option::Some(yang_balance) => {
let yang: ContractAddress = *yang_addresses.pop_front().unwrap();
assert(sentinel.get_yang(*yang_balance.yang_id) == yang, 'FDP: Address mismatch');

let (shrine_yang_info, _) = self
.get_shrine_yang_info_helper(
shrine, sentinel, yang, *yang_balance.amount, current_rate_era
);
yang_infos.append(shrine_yang_info);
},
Option::None => { break yang_infos.span(); }
}
}
}
}

//
// Internal functions
//

#[generate_trait]
impl FrontendDataProviderHelpers of FrontendDataProviderHelpersTrait {
// Helper function to generate a ShrineAssetInfo struct for a yang.
// Returns a tuple of a ShrineAssetInfo struct and the yang price
fn get_shrine_yang_info_helper(
self: @ContractState,
shrine: IShrineDispatcher,
sentinel: ISentinelDispatcher,
yang: ContractAddress,
yang_amt: Wad,
current_rate_era: u64
) -> (ShrineAssetInfo, Wad) {
let gate = IGateDispatcher { contract_address: sentinel.get_gate_address(yang) };
let deposited: u128 = gate.get_total_assets();

let (yang_price, _, _) = shrine.get_current_yang_price(yang);
let yang_value: Wad = yang_amt * yang_price;
let asset_price: Wad = yang_value / deposited.into();

let threshold: Ray = shrine.get_yang_threshold(yang);
let base_rate: Ray = shrine.get_yang_rate(yang, current_rate_era);
let ceiling: u128 = sentinel.get_yang_asset_max(yang);

(
ShrineAssetInfo {
address: yang,
price: asset_price,
threshold,
base_rate,
deposited,
ceiling,
deposited_value: yang_value
},
yang_price
)
}
}
}
Loading