Skip to content

Commit

Permalink
feat: add update_price_feeds_if_necessary to market contract (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
martines3000 authored Aug 13, 2024
1 parent dd2b86d commit e378456
Show file tree
Hide file tree
Showing 11 changed files with 541 additions and 149 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions abis/market_abi/src/abi.sw
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ abi Market {
#[payable, storage(read, write)]
fn supply_collateral(); // Payment is required: any collateral asset

#[storage(read, write)]
fn withdraw_collateral(asset: b256, amount: u256);
#[payable, storage(read, write)]
fn withdraw_collateral(asset: b256, amount: u256, price_data_update: PriceDataUpdate);

#[storage(read)]
fn get_user_collateral(address: Address, asset_id: b256) -> u256;
Expand All @@ -55,8 +55,8 @@ abi Market {
#[payable, storage(read, write)]
fn supply_base(); // Payment is required: base asset (USDC)

#[storage(read, write)]
fn withdraw_base(amount: u256);
#[payable, storage(read, write)]
fn withdraw_base(amount: u256, price_data_update: PriceDataUpdate);

#[storage(read)]
fn get_user_supply_borrow(account: Address) -> (u256, u256);
Expand All @@ -66,15 +66,15 @@ abi Market {

// # 5. Liquidation management
// Liquidates the user if there is insufficient collateral for the borrowing.
#[storage(read, write)]
fn absorb(accounts: Vec<Address>);
#[payable, storage(read, write)]
fn absorb(accounts: Vec<Address>, price_data_update: PriceDataUpdate);

#[storage(read)]
fn is_liquidatable(account: Address) -> bool;

// # 6. Protocol collateral management
#[payable, storage(read)]
fn buy_collateral(asset_id: b256, min_amount: u256, recipient: Address); // Payment is required: base asset (USDC)
fn buy_collateral(asset_id: b256, min_amount: u256, recipient: Address, price_data_update: PriceDataUpdate); // Payment is required: base asset (USDC)

#[storage(read)]
fn collateral_value_to_sell(asset_id: b256, collateral_amount: u256) -> u256;
Expand All @@ -85,6 +85,7 @@ abi Market {
// ## 7. Reserves management
#[storage(read)]
fn get_reserves() -> I256;

#[storage(read)]
fn withdraw_reserves(to: Address, amount: u256);

Expand Down Expand Up @@ -128,8 +129,8 @@ abi Market {
fn update_fee(update_data: Vec<Bytes>) -> u64;

#[payable, storage(read)]
fn update_price_feeds(update_fee: u64, update_data: Vec<Bytes>);

fn update_price_feeds_if_necessary(price_data_update: PriceDataUpdate);
// ## 11. Changing market configuration
#[storage(write, read)]
fn update_market_configuration(configuration: MarketConfiguration);
Expand Down
9 changes: 9 additions & 0 deletions abis/market_abi/src/structs.sw
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ library;

use i256::I256;
use std::constants::ZERO_B256;
use std::bytes::Bytes;
use pyth_interface::{data_structures::price::{PriceFeedId}};

pub const BASE_ACCRUAL_SCALE: u256 = 1_000_000; // 1e6
pub const BASE_INDEX_SCALE_15: u256 = 1_000_000_000_000_000; // 1e15
Expand Down Expand Up @@ -126,6 +128,13 @@ impl MarketBasics {
}
}

pub struct PriceDataUpdate {
pub update_fee: u64,
pub publish_times: Vec<u64>,
pub price_feed_ids: Vec<PriceFeedId>,
pub update_data: Vec<Bytes>
}

pub enum Error {
AlreadyInitialized: (),
Paused: (),
Expand Down
48 changes: 32 additions & 16 deletions contracts/market/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ impl Market for Contract {
.collateral_configurations_keys
.push(configuration.asset_id);

log(CollateralAssetAdded { asset_id: configuration.asset_id, configuration }); // TODO[Martin]: What is this event?
log(CollateralAssetAdded { asset_id: configuration.asset_id, configuration });
}

// ## 2.2 Pause an existing collateral asset
Expand Down Expand Up @@ -264,8 +264,9 @@ impl Market for Contract {
// ### Parameters:
// - `asset_id`: The asset ID of the collateral asset to be withdrawn
// - `amount`: The amount of collateral to be withdrawn
#[storage(read, write)]
fn withdraw_collateral(asset_id: b256, amount: u256) {
// - `price_data_update`: The price data update struct to be used for updating the price feeds
#[payable, storage(read, write)]
fn withdraw_collateral(asset_id: b256, amount: u256, price_data_update: PriceDataUpdate) {
// Get the caller's address and calculate the new user and total collateral
let caller = msg_sender_address();
let user_collateral = storage.user_collateral.get((caller, asset_id)).try_read().unwrap_or(0) - amount;
Expand All @@ -277,7 +278,10 @@ impl Market for Contract {
.user_collateral
.insert((caller, asset_id), user_collateral);

// Note: no accrue interest, BorrowCF < LiquidationCF covers small changes
// Update price data
update_price_feeds_if_necessary_internal(price_data_update);

// Note: no accrue interest, BorrowCollateralFactor < LiquidationCollateralFactor covers small changes
// Check if the user is borrow collateralized
require(is_borrow_collateralized(caller), Error::NotCollateralized);

Expand Down Expand Up @@ -367,8 +371,9 @@ impl Market for Contract {
// ## 4.2 Withdraw base (borrowing if possible/necessary)
// ### Parameters:
// - `amount`: The amount of base asset to be withdrawn
#[storage(read, write)]
fn withdraw_base(amount: u256) {
// - `price_data_update`: The price data update struct to be used for updating the price feeds
#[payable, storage(read, write)]
fn withdraw_base(amount: u256, price_data_update: PriceDataUpdate) {
// Only allow withdrawing if paused flag is not set
require(!storage.pause_config.withdraw_paused.read(), Error::Paused);

Expand Down Expand Up @@ -419,6 +424,9 @@ impl Market for Contract {
Error::BorrowTooSmall,
);

// Update price data
update_price_feeds_if_necessary_internal(price_data_update);

// Check that the user is borrow collateralized
require(is_borrow_collateralized(caller), Error::NotCollateralized);
}
Expand Down Expand Up @@ -486,14 +494,17 @@ impl Market for Contract {
// - Absorb a list of underwater accounts onto the protocol balance sheet
// ### Parameters:
// - `accounts`: The list of underwater accounts to be absorbed
#[storage(read, write)]
fn absorb(accounts: Vec<Address>) {
#[payable, storage(read, write)]
fn absorb(accounts: Vec<Address>, price_data_update: PriceDataUpdate) {
// Check that the pause flag is not set
require(!storage.pause_config.absorb_paused.read(), Error::Paused);

// Accrue interest
accrue_internal();

// Update price data
update_price_feeds_if_necessary_internal(price_data_update);

let mut index = 0;
// Loop and absorb each account
while index < accounts.len() {
Expand Down Expand Up @@ -523,8 +534,9 @@ impl Market for Contract {
// - `asset_id`: The asset ID of the collateral asset to be bought
// - `min_amount`: The minimum amount of collateral to be bought
// - `recipient`: The address of the recipient of the collateral
// - `price_data_update`: The price data update struct to be used for updating the price feeds
#[payable, storage(read)]
fn buy_collateral(asset_id: b256, min_amount: u256, recipient: Address) {
fn buy_collateral(asset_id: b256, min_amount: u256, recipient: Address, price_data_update: PriceDataUpdate) {
// Only allow buying collateral if paused flag is not set
require(!storage.pause_config.buy_paused.read(), Error::Paused);
let payment_amount: u256 = msg_amount().into();
Expand All @@ -548,6 +560,10 @@ impl Market for Contract {
// Note: Re-entrancy can skip the reserves check above on a second buyCollateral call.
let reserves = get_collateral_reserves_internal(asset_id);

// Update price data
// FIXME[Martin]: After Fuel responds either remove this or adapt
// update_price_feeds_if_necessary_internal(price_data_update);

// Calculate the quote for a collateral asset in exchange for an amount of the base asset
let collateral_amount = quote_collateral_internal(asset_id, payment_amount);

Expand Down Expand Up @@ -774,8 +790,8 @@ impl Market for Contract {
}

#[payable, storage(read)]
fn update_price_feeds(update_fee: u64, update_data: Vec<Bytes>) {
update_price_feeds_internal(update_fee, update_data)
fn update_price_feeds_if_necessary(price_data_update: PriceDataUpdate) {
update_price_feeds_if_necessary_internal(price_data_update)
}

// # 11. Changing market configuration
Expand Down Expand Up @@ -822,17 +838,17 @@ fn update_fee_internal(update_data: Vec<Bytes>) -> u64 {
}

#[payable, storage(read)]
fn update_price_feeds_internal(update_fee: u64, update_data: Vec<Bytes>) {
fn update_price_feeds_if_necessary_internal(price_data_update: PriceDataUpdate) {
let contract_id = storage.pyth_contract_id.read();
require(contract_id != ZERO_B256, Error::OracleContractIdNotSet);

let oracle = abi(PythCore, contract_id);
oracle.update_price_feeds {
asset_id: FUEL_ETH_BASE_ASSET_ID, coins: update_fee
oracle.update_price_feeds_if_necessary {
asset_id: FUEL_ETH_BASE_ASSET_ID, coins: price_data_update.update_fee
}
(update_data);
(price_data_update.price_feed_ids, price_data_update.publish_times, price_data_update.update_data);
}


// ## Timestamp getter
// ### Description:
Expand Down
37 changes: 28 additions & 9 deletions contracts/market/tests/local_tests/functions/pause.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use market::PauseConfiguration;
use market::PriceDataUpdate;
use token_sdk::{TokenAsset, TokenContract};

use crate::utils::init_wallets;
Expand Down Expand Up @@ -74,19 +75,22 @@ async fn pause_test() {

// ==================== Set oracle prices ====================
let mut prices = Vec::new();
let mut price_feed_ids = Vec::new();
let publish_time: u64 = Utc::now().timestamp().try_into().unwrap();
let confidence = 0;

for asset in &assets {
let price = asset.1.default_price * 10u64.pow(asset.1.price_feed_decimals);

price_feed_ids.push(asset.1.price_feed_id);

prices.push((
asset.1.price_feed_id,
(price, asset.1.price_feed_decimals, publish_time, confidence),
))
}

oracle.update_prices(prices).await.unwrap();
oracle.update_prices(&prices).await.unwrap();

for asset in &assets {
let price = oracle.price(asset.1.price_feed_id).await.unwrap().value;
Expand All @@ -98,6 +102,13 @@ async fn pause_test() {
);
}

let price_data_update = PriceDataUpdate {
update_fee: 1,
price_feed_ids: price_feed_ids,
publish_times: vec![publish_time; assets.len()],
update_data: oracle.create_update_data(&prices).await.unwrap(),
};

// =================================================
// ==================== Step #0 ====================
// 👛 Wallet: Bob 🧛
Expand Down Expand Up @@ -173,7 +184,7 @@ async fn pause_test() {
.with_account(&alice)
.await
.unwrap()
.withdraw_base(&[&oracle.instance], amount)
.withdraw_base(&[&oracle.instance], amount, &price_data_update)
.await
.unwrap();

Expand All @@ -200,7 +211,15 @@ async fn pause_test() {
res.confidence,
),
)]);
oracle.update_prices(prices).await.unwrap();
oracle.update_prices(&prices).await.unwrap();

// New `price_data_update` that will be used in the next steps
let price_data_update = PriceDataUpdate {
update_fee: 1,
price_feed_ids: vec![uni.price_feed_id],
publish_times: vec![Utc::now().timestamp().try_into().unwrap()],
update_data: oracle.create_update_data(&prices).await.unwrap(),
};

let res = oracle.price(uni.price_feed_id).await.unwrap().value;
assert!(new_price == res.price);
Expand All @@ -225,7 +244,7 @@ async fn pause_test() {
.with_account(bob)
.await
.unwrap()
.absorb(&[&oracle.instance], vec![alice_address])
.absorb(&[&oracle.instance], vec![alice_address], &price_data_update)
.await
.unwrap();

Expand Down Expand Up @@ -288,14 +307,13 @@ async fn pause_test() {
uni.bits256,
1,
bob_address,
&price_data_update,
)
.await
.unwrap();

market.debug_increment_timestamp().await.unwrap();

// TODO claim_paused

// =================================================
// ==================== Step #6 ====================
// 👛 Wallet: Admin 🗿
Expand All @@ -304,7 +322,7 @@ async fn pause_test() {
let price = oracle.price(uni.price_feed_id).await.unwrap().value;
let amount = parse_units(5, uni.price_feed_decimals.into()); // 1 UNI = $5
oracle
.update_prices(Vec::from([(
.update_prices(&Vec::from([(
uni.price_feed_id,
(
amount,
Expand Down Expand Up @@ -404,7 +422,7 @@ async fn pause_test() {
.with_account(alice)
.await
.unwrap()
.withdraw_base(&[&oracle.instance], amount)
.withdraw_base(&[&oracle.instance], amount, &price_data_update)
.await
.is_err();
assert!(res);
Expand All @@ -419,7 +437,7 @@ async fn pause_test() {
.with_account(bob)
.await
.unwrap()
.absorb(&[&oracle.instance], vec![alice_address])
.absorb(&[&oracle.instance], vec![alice_address], &price_data_update)
.await
.is_err();
assert!(res);
Expand Down Expand Up @@ -461,6 +479,7 @@ async fn pause_test() {
uni.bits256,
1,
bob_address,
&price_data_update,
)
.await
.is_err();
Expand Down
Loading

0 comments on commit e378456

Please sign in to comment.