From e3784564c942d32de286893ec8256135819ada3b Mon Sep 17 00:00:00 2001
From: Martin Domajnko <35891136+martines3000@users.noreply.github.com>
Date: Tue, 13 Aug 2024 08:46:08 +0200
Subject: [PATCH] feat: add `update_price_feeds_if_necessary` to market
contract (#13)
---
Cargo.lock | 1 +
abis/market_abi/src/abi.sw | 19 +-
abis/market_abi/src/structs.sw | 9 +
contracts/market/src/main.sw | 48 +-
.../tests/local_tests/functions/pause.rs | 37 +-
.../market/tests/local_tests/main_test_uni.rs | 61 ++-
.../main_test_uni_no_debug_mode.rs | 60 ++-
contracts/pyth-mock/src/main.sw | 15 +
libs/market_sdk/Cargo.toml | 1 +
libs/market_sdk/src/market_utils.rs | 422 ++++++++++++++----
libs/pyth_mock_sdk/src/lib.rs | 17 +-
11 files changed, 541 insertions(+), 149 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 5a5cec59..81294b95 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3657,6 +3657,7 @@ dependencies = [
"anyhow",
"fuels",
"market",
+ "pyth_sdk",
"rand",
"serde",
"serde_json",
diff --git a/abis/market_abi/src/abi.sw b/abis/market_abi/src/abi.sw
index 7d04c273..4eff261a 100644
--- a/abis/market_abi/src/abi.sw
+++ b/abis/market_abi/src/abi.sw
@@ -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;
@@ -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);
@@ -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
);
+ #[payable, storage(read, write)]
+ fn absorb(accounts: Vec, 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;
@@ -85,6 +85,7 @@ abi Market {
// ## 7. Reserves management
#[storage(read)]
fn get_reserves() -> I256;
+
#[storage(read)]
fn withdraw_reserves(to: Address, amount: u256);
@@ -128,8 +129,8 @@ abi Market {
fn update_fee(update_data: Vec) -> u64;
#[payable, storage(read)]
- fn update_price_feeds(update_fee: u64, update_data: Vec);
-
+ fn update_price_feeds_if_necessary(price_data_update: PriceDataUpdate);
+
// ## 11. Changing market configuration
#[storage(write, read)]
fn update_market_configuration(configuration: MarketConfiguration);
diff --git a/abis/market_abi/src/structs.sw b/abis/market_abi/src/structs.sw
index 92f705b5..aba22999 100644
--- a/abis/market_abi/src/structs.sw
+++ b/abis/market_abi/src/structs.sw
@@ -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
@@ -126,6 +128,13 @@ impl MarketBasics {
}
}
+pub struct PriceDataUpdate {
+ pub update_fee: u64,
+ pub publish_times: Vec,
+ pub price_feed_ids: Vec,
+ pub update_data: Vec
+}
+
pub enum Error {
AlreadyInitialized: (),
Paused: (),
diff --git a/contracts/market/src/main.sw b/contracts/market/src/main.sw
index 58aaa0c9..1e03ffda 100644
--- a/contracts/market/src/main.sw
+++ b/contracts/market/src/main.sw
@@ -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
@@ -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;
@@ -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);
@@ -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);
@@ -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);
}
@@ -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) {
+ #[payable, storage(read, write)]
+ fn absorb(accounts: Vec, 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() {
@@ -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();
@@ -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);
@@ -774,8 +790,8 @@ impl Market for Contract {
}
#[payable, storage(read)]
- fn update_price_feeds(update_fee: u64, update_data: Vec) {
- 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
@@ -822,17 +838,17 @@ fn update_fee_internal(update_data: Vec) -> u64 {
}
#[payable, storage(read)]
-fn update_price_feeds_internal(update_fee: u64, update_data: Vec) {
+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:
diff --git a/contracts/market/tests/local_tests/functions/pause.rs b/contracts/market/tests/local_tests/functions/pause.rs
index 882df9e2..8ed9eed4 100644
--- a/contracts/market/tests/local_tests/functions/pause.rs
+++ b/contracts/market/tests/local_tests/functions/pause.rs
@@ -1,4 +1,5 @@
use market::PauseConfiguration;
+use market::PriceDataUpdate;
use token_sdk::{TokenAsset, TokenContract};
use crate::utils::init_wallets;
@@ -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;
@@ -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 🧛
@@ -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();
@@ -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);
@@ -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();
@@ -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 🗿
@@ -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,
@@ -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);
@@ -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);
@@ -461,6 +479,7 @@ async fn pause_test() {
uni.bits256,
1,
bob_address,
+ &price_data_update,
)
.await
.is_err();
diff --git a/contracts/market/tests/local_tests/main_test_uni.rs b/contracts/market/tests/local_tests/main_test_uni.rs
index 26c44111..0dc4abb2 100644
--- a/contracts/market/tests/local_tests/main_test_uni.rs
+++ b/contracts/market/tests/local_tests/main_test_uni.rs
@@ -1,6 +1,7 @@
use chrono::Utc;
use fuels::prelude::ViewOnlyAccount;
use fuels::types::{Address, Bits256, ContractId};
+use market::PriceDataUpdate;
use market_sdk::{get_market_config, parse_units, MarketContract};
use pyth_mock_sdk::PythMockContract;
use token_sdk::{TokenAsset, TokenContract};
@@ -82,6 +83,7 @@ async fn main_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;
@@ -94,11 +96,13 @@ async fn main_test() {
))
}
- 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;
+ price_feed_ids.push(asset.1.price_feed_id);
+
println!(
"Price for {} = {}",
asset.1.symbol,
@@ -106,6 +110,13 @@ async fn main_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 🧛
@@ -190,7 +201,7 @@ async fn main_test() {
.with_account(&alice)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount)
+ .withdraw_base(&[&oracle.instance], amount, &price_data_update)
.await
.unwrap();
@@ -292,6 +303,7 @@ async fn main_test() {
(amount - u128::from(parse_units(1, usdc.decimals)))
.try_into()
.unwrap(),
+ &price_data_update,
)
.await
.unwrap();
@@ -309,7 +321,11 @@ async fn main_test() {
.with_account(alice)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], parse_units(2, usdc.decimals))
+ .withdraw_base(
+ &[&oracle.instance],
+ parse_units(2, usdc.decimals),
+ &price_data_update,
+ )
.await
.is_err();
assert!(res);
@@ -343,7 +359,16 @@ async fn main_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(),
+ };
println!(
"🔻 UNI price drops: ${} -> ${}",
@@ -376,7 +401,7 @@ async fn main_test() {
.with_account(bob)
.await
.unwrap()
- .absorb(&[&oracle.instance], vec![alice_address])
+ .absorb(&[&oracle.instance], vec![alice_address], &price_data_update)
.await
.unwrap();
@@ -443,6 +468,7 @@ async fn main_test() {
uni.bits256,
1,
bob_address,
+ &price_data_update,
)
.await
.unwrap();
@@ -469,7 +495,11 @@ async fn main_test() {
.with_account(bob)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount.try_into().unwrap())
+ .withdraw_base(
+ &[&oracle.instance],
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
@@ -498,7 +528,11 @@ async fn main_test() {
.with_account(chad)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount.try_into().unwrap())
+ .withdraw_base(
+ &[&oracle.instance],
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
@@ -527,7 +561,11 @@ async fn main_test() {
.with_account(alice)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount.try_into().unwrap())
+ .withdraw_base(
+ &[&oracle.instance],
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
@@ -556,7 +594,12 @@ async fn main_test() {
.with_account(chad)
.await
.unwrap()
- .withdraw_collateral(&[&oracle.instance], uni.bits256, amount.try_into().unwrap())
+ .withdraw_collateral(
+ &[&oracle.instance],
+ uni.bits256,
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
diff --git a/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs b/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs
index b18aadc9..88e995ba 100644
--- a/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs
+++ b/contracts/market/tests/local_tests/main_test_uni_no_debug_mode.rs
@@ -2,6 +2,7 @@ use crate::utils::{init_wallets, print_case_title};
use chrono::Utc;
use fuels::prelude::ViewOnlyAccount;
use fuels::types::{Address, Bits256, ContractId};
+use market::PriceDataUpdate;
use market_sdk::{get_market_config, parse_units, MarketContract};
use pyth_mock_sdk::PythMockContract;
use token_sdk::{TokenAsset, TokenContract};
@@ -79,6 +80,7 @@ async fn main_test_no_debug() {
// ==================== 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;
@@ -91,11 +93,13 @@ async fn main_test_no_debug() {
))
}
- 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;
+ price_feed_ids.push(asset.1.price_feed_id);
+
println!(
"Price for {} = {}",
asset.1.symbol,
@@ -103,6 +107,13 @@ async fn main_test_no_debug() {
);
}
+ 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 🧛
@@ -186,7 +197,7 @@ async fn main_test_no_debug() {
.with_account(&alice)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount)
+ .withdraw_base(&[&oracle.instance], amount, &price_data_update)
.await
.unwrap();
@@ -285,6 +296,7 @@ async fn main_test_no_debug() {
(amount - u128::from(parse_units(1, usdc.decimals)))
.try_into()
.unwrap(),
+ &price_data_update,
)
.await
.unwrap();
@@ -301,7 +313,11 @@ async fn main_test_no_debug() {
.with_account(alice)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], parse_units(2, usdc.decimals))
+ .withdraw_base(
+ &[&oracle.instance],
+ parse_units(2, usdc.decimals),
+ &price_data_update,
+ )
.await
.is_err();
assert!(res);
@@ -334,7 +350,15 @@ async fn main_test_no_debug() {
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(),
+ };
println!(
"🔻 UNI price drops: ${} -> ${}",
@@ -365,7 +389,7 @@ async fn main_test_no_debug() {
.with_account(bob)
.await
.unwrap()
- .absorb(&[&oracle.instance], vec![alice_address])
+ .absorb(&[&oracle.instance], vec![alice_address], &price_data_update)
.await
.unwrap();
@@ -431,6 +455,7 @@ async fn main_test_no_debug() {
uni.bits256,
1,
bob_address,
+ &price_data_update,
)
.await
.unwrap();
@@ -456,7 +481,11 @@ async fn main_test_no_debug() {
.with_account(bob)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount.try_into().unwrap())
+ .withdraw_base(
+ &[&oracle.instance],
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
@@ -484,7 +513,11 @@ async fn main_test_no_debug() {
.with_account(chad)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount.try_into().unwrap())
+ .withdraw_base(
+ &[&oracle.instance],
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
@@ -512,7 +545,11 @@ async fn main_test_no_debug() {
.with_account(alice)
.await
.unwrap()
- .withdraw_base(&[&oracle.instance], amount.try_into().unwrap())
+ .withdraw_base(
+ &[&oracle.instance],
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
@@ -540,7 +577,12 @@ async fn main_test_no_debug() {
.with_account(chad)
.await
.unwrap()
- .withdraw_collateral(&[&oracle.instance], uni.bits256, amount.try_into().unwrap())
+ .withdraw_collateral(
+ &[&oracle.instance],
+ uni.bits256,
+ amount.try_into().unwrap(),
+ &price_data_update,
+ )
.await
.unwrap();
diff --git a/contracts/pyth-mock/src/main.sw b/contracts/pyth-mock/src/main.sw
index bec0f4ea..d5e36fe6 100644
--- a/contracts/pyth-mock/src/main.sw
+++ b/contracts/pyth-mock/src/main.sw
@@ -99,7 +99,22 @@ impl PythCore for Contract {
publish_times: Vec,
update_data: Vec,
) {
+ // Currently implementation is the same as `update_price_feeds`
let mut index = 0;
+
+ // Bytes structure
+ // 0: price_feed_id -> b256
+ // 1: price -> u64
+ // 2: exponent -> u32
+ // 3: publish_time -> u64
+ // 4: confidence -> u64
+
+ while index < update_data.len() {
+ let payload = update_data.get(index).unwrap();
+ let (price_feed_id, price) = decode_bytes(payload);
+ storage.latest_price_feed.insert(price_feed_id, price);
+ index += 1;
+ }
}
#[storage(read)]
diff --git a/libs/market_sdk/Cargo.toml b/libs/market_sdk/Cargo.toml
index 77dd0102..2086dcc5 100644
--- a/libs/market_sdk/Cargo.toml
+++ b/libs/market_sdk/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
license = "Apache-2.0"
[dependencies]
+pyth_sdk = { workspace = true }
market = { workspace = true }
token_sdk = { workspace = true }
fuels = { workspace = true }
diff --git a/libs/market_sdk/src/market_utils.rs b/libs/market_sdk/src/market_utils.rs
index c2f84a6f..8904cff3 100644
--- a/libs/market_sdk/src/market_utils.rs
+++ b/libs/market_sdk/src/market_utils.rs
@@ -12,7 +12,8 @@ use fuels::{
},
types::{
bech32::Bech32ContractId, transaction::TxPolicies,
- transaction_builders::VariableOutputPolicy, Address, AssetId, Bits256, Bytes32, ContractId,
+ transaction_builders::VariableOutputPolicy, Address, AssetId, Bits256, Bytes, Bytes32,
+ ContractId,
},
};
use rand::Rng;
@@ -150,10 +151,20 @@ impl MarketContract {
}
// Contract methods
- pub async fn activate_contract(&self, market_configuration: MarketConfiguration) -> anyhow::Result> {
- Ok(self.instance.methods().activate_contract(market_configuration).call().await?)
+ // # 0. Activate contract
+ pub async fn activate_contract(
+ &self,
+ market_configuration: MarketConfiguration,
+ ) -> anyhow::Result> {
+ Ok(self
+ .instance
+ .methods()
+ .activate_contract(market_configuration)
+ .call()
+ .await?)
}
+ // # 1. Debug functionality (for testing purposes)
pub async fn debug_increment_timestamp(&self) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
@@ -166,78 +177,83 @@ impl MarketContract {
.await?)
}
- pub async fn supply_base(
+ // # 2. Collateral asset management
+ pub async fn add_collateral_asset(
&self,
- base_asset_id: AssetId,
- amount: u64,
+ config: &CollateralConfiguration,
) -> anyhow::Result> {
- let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
- let call_params = CallParameters::default()
- .with_amount(amount)
- .with_asset_id(base_asset_id);
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .supply_base()
+ .add_collateral_asset(config.clone())
.with_tx_policies(tx_policies)
- .call_params(call_params)?
.call()
.await?)
}
- pub async fn add_collateral_asset(
+ pub async fn puase_collateral_asset(
&self,
- config: &CollateralConfiguration,
+ asset_id: AssetId,
) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .add_collateral_asset(config.clone())
+ .pause_collateral_asset(asset_id.into())
.with_tx_policies(tx_policies)
.call()
.await?)
}
- pub async fn withdraw_base(
+ pub async fn resume_collateral_asset(
&self,
- contract_ids: &[&dyn ContractDependency],
- amount: u64,
+ asset_id: AssetId,
) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .withdraw_base(amount.into())
- .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
- .with_contracts(contract_ids)
+ .resume_collateral_asset(asset_id.into())
.with_tx_policies(tx_policies)
.call()
.await?)
}
- pub async fn withdraw_collateral(
+ pub async fn update_collateral_asset(
&self,
- contract_ids: &[&dyn ContractDependency],
- asset_id: Bits256,
- amount: u64,
+ asset_id: AssetId,
+ config: &CollateralConfiguration,
) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .withdraw_collateral(asset_id, amount.into())
+ .update_collateral_asset(asset_id.into(), config.clone())
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?)
+ }
+
+ pub async fn get_collateral_configurations(
+ &self,
+ ) -> anyhow::Result>> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ Ok(self
+ .instance
+ .methods()
+ .get_collateral_configurations()
.with_tx_policies(tx_policies)
- .with_contracts(contract_ids)
- .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?)
}
+ // # 3. Collateral asset management (Supply and Withdrawal)
pub async fn supply_collateral(
&self,
asset_id: AssetId,
@@ -259,38 +275,38 @@ impl MarketContract {
.await?)
}
- pub async fn get_user_collateral(
+ pub async fn withdraw_collateral(
&self,
- address: Address,
+ contract_ids: &[&dyn ContractDependency],
asset_id: Bits256,
- ) -> anyhow::Result {
+ amount: u64,
+ price_data_update: &PriceDataUpdate,
+ ) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
- let res = self
+ Ok(self
.instance
.methods()
- .get_user_collateral(address, asset_id)
+ .withdraw_collateral(asset_id, amount.into(), price_data_update.clone())
.with_tx_policies(tx_policies)
+ .with_contracts(contract_ids)
+ .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
- .await?
- .value;
-
- Ok(convert_u256_to_u128(res))
+ .await?)
}
- pub async fn available_to_borrow(
+ pub async fn get_user_collateral(
&self,
- contract_ids: &[&dyn ContractDependency],
address: Address,
+ asset_id: Bits256,
) -> anyhow::Result {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
let res = self
.instance
.methods()
- .available_to_borrow(address)
+ .get_user_collateral(address, asset_id)
.with_tx_policies(tx_policies)
- .with_contracts(contract_ids)
.call()
.await?
.value;
@@ -298,97 +314,168 @@ impl MarketContract {
Ok(convert_u256_to_u128(res))
}
- pub async fn get_user_supply_borrow(&self, address: Address) -> anyhow::Result<(u128, u128)> {
+ pub async fn totals_collateral(&self, asset_id: Bits256) -> anyhow::Result {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
- let (supply, borrow) = self
+ let res = self
.instance
.methods()
- .get_user_supply_borrow(address)
+ .totals_collateral(asset_id)
.with_tx_policies(tx_policies)
.call()
.await?
.value;
- Ok((convert_u256_to_u128(supply), convert_u256_to_u128(borrow)))
+ Ok(convert_u256_to_u128(res))
}
- pub async fn get_user_basic(
+ // # 4. Base asset management (Supply and Withdrawal)
+ pub async fn supply_base(
&self,
- address: Address,
- ) -> anyhow::Result> {
- let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+ base_asset_id: AssetId,
+ amount: u64,
+ ) -> anyhow::Result> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
+ let call_params = CallParameters::default()
+ .with_amount(amount)
+ .with_asset_id(base_asset_id);
Ok(self
.instance
.methods()
- .get_user_basic(address)
+ .supply_base()
.with_tx_policies(tx_policies)
+ .call_params(call_params)?
.call()
.await?)
}
- pub async fn get_market_basics(&self) -> anyhow::Result> {
+ pub async fn withdraw_base(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ amount: u64,
+ price_data_update: &PriceDataUpdate,
+ ) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+ let call_params = CallParameters::default().with_amount(price_data_update.update_fee);
Ok(self
.instance
.methods()
- .get_market_basics()
+ .withdraw_base(amount.into(), price_data_update.clone())
+ .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
+ .with_contracts(contract_ids)
.with_tx_policies(tx_policies)
+ .call_params(call_params)?
.call()
.await?)
}
- pub async fn totals_collateral(&self, bits256: Bits256) -> anyhow::Result {
+ pub async fn get_user_supply_borrow(&self, address: Address) -> anyhow::Result<(u128, u128)> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
- let res = self
+ let (supply, borrow) = self
.instance
.methods()
- .totals_collateral(bits256)
+ .get_user_supply_borrow(address)
.with_tx_policies(tx_policies)
.call()
.await?
.value;
- Ok(convert_u256_to_u128(res))
+ Ok((convert_u256_to_u128(supply), convert_u256_to_u128(borrow)))
}
- pub async fn get_utilization(&self) -> anyhow::Result {
+ pub async fn available_to_borrow(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ address: Address,
+ ) -> anyhow::Result {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
let res = self
.instance
.methods()
- .get_utilization()
+ .available_to_borrow(address)
.with_tx_policies(tx_policies)
+ .with_contracts(contract_ids)
.call()
.await?
.value;
+
Ok(convert_u256_to_u128(res))
}
- pub async fn balance_of(&self, bits256: Bits256) -> anyhow::Result> {
+ // # 5. Liquidation management
+ pub async fn absorb(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ addresses: Vec,
+ price_data_update: &PriceDataUpdate,
+ ) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+ let call_params = CallParameters::default().with_amount(price_data_update.update_fee);
Ok(self
.instance
.methods()
- .balance_of(bits256)
+ .absorb(addresses, price_data_update.clone())
.with_tx_policies(tx_policies)
+ .with_contracts(contract_ids)
+ .call_params(call_params)?
.call()
.await?)
}
- pub async fn pause(&self, config: &PauseConfiguration) -> anyhow::Result> {
+ pub async fn is_liquidatable(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ address: Address,
+ ) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .pause(config.clone())
+ .is_liquidatable(address)
.with_tx_policies(tx_policies)
+ .with_contracts(contract_ids)
+ .call()
+ .await?)
+ }
+
+ // # 6. Protocol collateral management
+ pub async fn buy_collateral(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ base_asset_id: AssetId,
+ amount: u64,
+ asset_id: Bits256,
+ min_amount: u64,
+ recipient: Address,
+ price_data_update: &PriceDataUpdate,
+ ) -> anyhow::Result> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ let call_params_base_asset = CallParameters::default()
+ .with_amount(amount)
+ .with_asset_id(base_asset_id); // Buy collateral with base asset
+
+ let call_params = CallParameters::default().with_amount(price_data_update.update_fee); // Fee for price update
+
+ Ok(self
+ .instance
+ .methods()
+ .buy_collateral(
+ asset_id,
+ min_amount.into(),
+ recipient,
+ price_data_update.clone(),
+ )
+ .with_tx_policies(tx_policies)
+ .with_contracts(contract_ids)
+ .call_params(call_params_base_asset)?
+ .with_variable_output_policy(VariableOutputPolicy::Exactly(2))
.call()
.await?)
}
@@ -414,63 +501,52 @@ impl MarketContract {
Ok(convert_u256_to_u128(res))
}
- pub async fn buy_collateral(
+ pub async fn quote_collateral(
&self,
contract_ids: &[&dyn ContractDependency],
- base_asset_id: AssetId,
- amount: u64,
- asset_id: Bits256,
- min_amount: u64,
- recipient: Address,
- ) -> anyhow::Result> {
+ asset_id: AssetId,
+ base_amount: u64,
+ ) -> anyhow::Result {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
- let call_params = CallParameters::default()
- .with_amount(amount)
- .with_asset_id(base_asset_id);
-
- Ok(self
+ let res = self
.instance
.methods()
- .buy_collateral(asset_id, min_amount.into(), recipient)
+ .quote_collateral(asset_id.into(), base_amount.into())
.with_tx_policies(tx_policies)
.with_contracts(contract_ids)
- .call_params(call_params)?
- .with_variable_output_policy(VariableOutputPolicy::Exactly(2))
.call()
- .await?)
+ .await?
+ .value;
+
+ Ok(convert_u256_to_u128(res))
}
- pub async fn absorb(
- &self,
- contract_ids: &[&dyn ContractDependency],
- addresses: Vec,
- ) -> anyhow::Result> {
+ // # 7. Reserves management
+ pub async fn get_reserves(&self) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .absorb(addresses)
+ .get_reserves()
.with_tx_policies(tx_policies)
- .with_contracts(contract_ids)
.call()
.await?)
}
- pub async fn is_liquidatable(
+ pub async fn withdraw_reserves(
&self,
- contract_ids: &[&dyn ContractDependency],
- address: Address,
- ) -> anyhow::Result> {
+ to: Address,
+ amount: u64,
+ ) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .is_liquidatable(address)
+ .withdraw_reserves(to, amount.into())
.with_tx_policies(tx_policies)
- .with_contracts(contract_ids)
.call()
.await?)
}
@@ -490,18 +566,118 @@ impl MarketContract {
.await?)
}
- pub async fn get_reserves(&self) -> anyhow::Result> {
+ // # 9. Pause management
+ pub async fn pause(&self, config: &PauseConfiguration) -> anyhow::Result> {
let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
Ok(self
.instance
.methods()
- .get_reserves()
+ .pause(config.clone())
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?)
+ }
+
+ // # 10. Getters
+ pub async fn get_market_configuration(
+ &self,
+ ) -> anyhow::Result> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ Ok(self
+ .instance
+ .methods()
+ .get_market_configuration()
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?)
+ }
+
+ pub async fn get_market_basics(&self) -> anyhow::Result> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ Ok(self
+ .instance
+ .methods()
+ .get_market_basics()
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?)
+ }
+
+ pub async fn get_user_basic(
+ &self,
+ address: Address,
+ ) -> anyhow::Result> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ Ok(self
+ .instance
+ .methods()
+ .get_user_basic(address)
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?)
+ }
+
+ pub async fn get_utilization(&self) -> anyhow::Result {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ let res = self
+ .instance
+ .methods()
+ .get_utilization()
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?
+ .value;
+ Ok(convert_u256_to_u128(res))
+ }
+
+ pub async fn balance_of(&self, bits256: Bits256) -> anyhow::Result> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ Ok(self
+ .instance
+ .methods()
+ .balance_of(bits256)
.with_tx_policies(tx_policies)
.call()
.await?)
}
+ pub async fn get_supply_rate(&self, utilization: u64) -> anyhow::Result {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ let value = self
+ .instance
+ .methods()
+ .get_supply_rate(utilization.into())
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?
+ .value;
+
+ Ok(convert_u256_to_u128(value))
+ }
+
+ pub async fn get_borrow_rate(&self, utilization: u64) -> anyhow::Result {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ let value = self
+ .instance
+ .methods()
+ .get_borrow_rate(utilization.into())
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?
+ .value;
+
+ Ok(convert_u256_to_u128(value))
+ }
+
+ // # 10. Pyth Oracle management
pub async fn set_pyth_contract_id(
&self,
contract_id: ContractId,
@@ -517,6 +693,66 @@ impl MarketContract {
.await?)
}
+ pub async fn get_price(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ price_feed_id: Bits256,
+ ) -> anyhow::Result {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ let price = self
+ .instance
+ .methods()
+ .get_price(price_feed_id)
+ .with_contracts(contract_ids)
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?
+ .value;
+
+ Ok(price)
+ }
+
+ pub async fn update_fee(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ update_data: Vec,
+ ) -> anyhow::Result {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ let value = self
+ .instance
+ .methods()
+ .update_fee(update_data)
+ .with_contracts(contract_ids)
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?
+ .value;
+
+ Ok(value)
+ }
+
+ pub async fn update_price_feeds_if_necessary(
+ &self,
+ contract_ids: &[&dyn ContractDependency],
+ price_data_update: &PriceDataUpdate,
+ ) -> anyhow::Result<()> {
+ let tx_policies = TxPolicies::default().with_script_gas_limit(DEFAULT_GAS_LIMIT);
+
+ self.instance
+ .methods()
+ .update_price_feeds_if_necessary(price_data_update.clone())
+ .with_contracts(contract_ids)
+ .with_tx_policies(tx_policies)
+ .call()
+ .await?;
+
+ Ok(())
+ }
+
+ // # 11. Changing market configuration
+
pub async fn print_debug_state(
&self,
wallets: &Vec,
diff --git a/libs/pyth_mock_sdk/src/lib.rs b/libs/pyth_mock_sdk/src/lib.rs
index c68ddba0..17937c37 100644
--- a/libs/pyth_mock_sdk/src/lib.rs
+++ b/libs/pyth_mock_sdk/src/lib.rs
@@ -62,11 +62,10 @@ impl PythMockContract {
.await?)
}
- // Vec<(PriceFeedId, Price)>
- pub async fn update_prices(
+ pub async fn create_update_data(
&self,
- prices: Vec<(Bits256, (u64, u32, u64, u64))>,
- ) -> anyhow::Result> {
+ prices: &Vec<(Bits256, (u64, u32, u64, u64))>,
+ ) -> anyhow::Result> {
let mut update_data: Vec = Vec::new();
for (price_feed_id, (price, exponent, publish_time, confidence)) in prices {
@@ -88,6 +87,16 @@ impl PythMockContract {
});
}
+ Ok(update_data)
+ }
+
+ // Vec<(PriceFeedId, Price)>
+ pub async fn update_prices(
+ &self,
+ prices: &Vec<(Bits256, (u64, u32, u64, u64))>,
+ ) -> anyhow::Result> {
+ let update_data: Vec = self.create_update_data(prices).await?;
+
Ok(self
.instance
.methods()