Skip to content

Commit

Permalink
feat: conversion rate pricing impl
Browse files Browse the repository at this point in the history
  • Loading branch information
JordyRo1 committed Dec 2, 2024
1 parent d4480ce commit cb33ec5
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 6 deletions.
5 changes: 5 additions & 0 deletions pragma-oracle/src/entry/entry.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ mod Entry {
let value: u128 = entries_mean(entries);
value
},
AggregationMode::ConversionRate => {
panic_with_felt252('No agg for conversion rate');
// No aggregation needed for conversion rate
0
},
AggregationMode::Error(()) => {
panic_with_felt252('Wrong aggregation mode');
0
Expand Down
6 changes: 5 additions & 1 deletion pragma-oracle/src/entry/structs.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,11 @@ struct PragmaPricesResponse {
expiration_timestamp: Option<u64>,
}

#[derive(Serde, Drop, Copy, starknet::Store)]
#[derive(Serde, Drop, Copy, starknet::Store, PartialEq)]
enum AggregationMode {
Median: (),
Mean: (),
ConversionRate,
Error: (),
}

Expand Down Expand Up @@ -306,6 +307,7 @@ impl AggregationModeIntoU8 of TryInto<AggregationMode, u8> {
match self {
AggregationMode::Median(()) => Option::Some(0_u8),
AggregationMode::Mean(()) => Option::Some(1_u8),
AggregationMode::ConversionRate => Option::Some(2_u8),
AggregationMode::Error(()) => Option::None(()),
}
}
Expand All @@ -316,6 +318,8 @@ impl u8IntoAggregationMode of Into<u8, AggregationMode> {
AggregationMode::Median(())
} else if self == 1_u8 {
AggregationMode::Mean(())
} else if self == 2_u8 {
AggregationMode::ConversionRate
} else {
AggregationMode::Error(())
}
Expand Down
116 changes: 116 additions & 0 deletions pragma-oracle/src/erc4626/erc4626.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Forked from https://github.com/0xEniotna/ERC4626/blob/main/src/erc4626/interface.cairo
use starknet::ContractAddress;

#[starknet::interface]
trait IERC4626<TState> {
// ************************************
// * Metadata
// ************************************
fn name(self: @TState) -> felt252;
fn symbol(self: @TState) -> felt252;
fn decimals(self: @TState) -> u8;

// ************************************
// * snake_case
// ************************************
fn total_supply(self: @TState) -> u256;
fn balance_of(self: @TState, account: ContractAddress) -> u256;
fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;

// ************************************
// * camelCase
// ************************************
fn totalSupply(self: @TState) -> u256;
fn balanceOf(self: @TState, account: ContractAddress) -> u256;
fn transferFrom(
ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;

// ************************************
// * Additional functions
// ************************************
fn asset(self: @TState) -> starknet::ContractAddress;
fn convert_to_assets(self: @TState, shares: u256) -> u256;
fn convert_to_shares(self: @TState, assets: u256) -> u256;
fn deposit(ref self: TState, assets: u256, receiver: starknet::ContractAddress) -> u256;
fn max_deposit(self: @TState, address: starknet::ContractAddress) -> u256;
fn max_mint(self: @TState, receiver: starknet::ContractAddress) -> u256;
fn max_redeem(self: @TState, owner: starknet::ContractAddress) -> u256;
fn max_withdraw(self: @TState, owner: starknet::ContractAddress) -> u256;
fn mint(ref self: TState, shares: u256, receiver: starknet::ContractAddress) -> u256;
fn preview_deposit(self: @TState, assets: u256) -> u256;
fn preview_mint(self: @TState, shares: u256) -> u256;
fn preview_redeem(self: @TState, shares: u256) -> u256;
fn preview_withdraw(self: @TState, assets: u256) -> u256;
fn redeem(
ref self: TState,
shares: u256,
receiver: starknet::ContractAddress,
owner: starknet::ContractAddress
) -> u256;
fn total_assets(self: @TState) -> u256;
fn withdraw(
ref self: TState,
assets: u256,
receiver: starknet::ContractAddress,
owner: starknet::ContractAddress
) -> u256;
}


#[starknet::interface]
trait IERC4626Metadata<TState> {
fn name(self: @TState) -> felt252;
fn symbol(self: @TState) -> felt252;
fn decimals(self: @TState) -> u8;
}

#[starknet::interface]
trait IERC4626Camel<TState> {
fn totalSupply(self: @TState) -> u256;
fn balanceOf(self: @TState, account: ContractAddress) -> u256;
fn transferFrom(
ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
}

#[starknet::interface]
trait IERC4626Snake<TState> {
fn total_supply(self: @TState) -> u256;
fn balance_of(self: @TState, account: ContractAddress) -> u256;
fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;
}

#[starknet::interface]
trait IERC4626Additional<TState> {
fn asset(self: @TState) -> ContractAddress;
fn convert_to_assets(self: @TState, shares: u256) -> u256;
fn convert_to_shares(self: @TState, assets: u256) -> u256;
fn deposit(ref self: TState, assets: u256, receiver: ContractAddress) -> u256;
fn max_deposit(self: @TState, address: ContractAddress) -> u256;
fn max_mint(self: @TState, receiver: ContractAddress) -> u256;
fn max_redeem(self: @TState, owner: ContractAddress) -> u256;
fn max_withdraw(self: @TState, owner: ContractAddress) -> u256;
fn mint(ref self: TState, shares: u256, receiver: ContractAddress) -> u256;
fn preview_deposit(self: @TState, assets: u256) -> u256;
fn preview_mint(self: @TState, shares: u256) -> u256;
fn preview_redeem(self: @TState, shares: u256) -> u256;
fn preview_withdraw(self: @TState, assets: u256) -> u256;
fn redeem(
ref self: TState, shares: u256, receiver: ContractAddress, owner: ContractAddress
) -> u256;
fn total_assets(self: @TState) -> u256;
fn withdraw(
ref self: TState, assets: u256, receiver: ContractAddress, owner: ContractAddress
) -> u256;
}
3 changes: 3 additions & 0 deletions pragma-oracle/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ mod utils {
mod bitwise;
mod strings;
}
mod erc4626 {
mod erc4626;
}
mod operations {
mod sorting {
mod merge_sort;
Expand Down
70 changes: 65 additions & 5 deletions pragma-oracle/src/oracle/oracle.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ trait IOracleABI<TContractState> {
);
fn remove_source(ref self: TContractState, source: felt252, data_type: DataType) -> bool;
fn set_sources_threshold(ref self: TContractState, threshold: u32);
fn register_tokenized_vault(
ref self: TContractState, token: felt252, token_address: ContractAddress
);
fn upgrade(ref self: TContractState, impl_hash: ClassHash);
}

Expand Down Expand Up @@ -192,6 +195,7 @@ mod Oracle {
IPublisherRegistryABIDispatcher, IPublisherRegistryABIDispatcherTrait
};
use starknet::{get_block_timestamp, Felt252TryIntoContractAddress};
use pragma::erc4626::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait};

use cmp::{max, min};
use option::OptionTrait;
Expand Down Expand Up @@ -235,6 +239,8 @@ mod Oracle {
//oracle_checkpoint_index, legacyMap between (pair_id, (SPOT/FUTURES/OPTIONS), expiration_timestamp (0 for SPOT)) and the index of the last checkpoint
oracle_checkpoint_index: LegacyMap::<(felt252, felt252, u64, u8), u64>,
oracle_sources_threshold_storage: u32,
// registry containing registered tokenized vaults and the corresponding address
tokenized_vault: LegacyMap<(felt252, felt252), ContractAddress>
}


Expand Down Expand Up @@ -514,18 +520,56 @@ mod Oracle {
}

// @notice aggregate all the entries for a given data type, with a given aggregation mode
// @notice The aggregation mode `ConversionRate` is set for tokenized vault, with pricing made based on STRK price and associated conversion rate fetched
// @notice onchain.
// @param data_type: an enum of DataType (e.g : DataType::SpotEntry(ASSET_ID) or DataType::FutureEntry((ASSSET_ID, expiration_timestamp)))
// @param aggregation_mode: the aggregation method to be used (e.g. AggregationMode::Median(()))
// @returns a PragmaPricesResponse, a structure providing the main information for an asset (see entry/structs for details)
fn get_data(
self: @ContractState, data_type: DataType, aggregation_mode: AggregationMode
) -> PragmaPricesResponse {
let sources = IOracleABI::get_all_sources(self, data_type);
let prices_response: PragmaPricesResponse = IOracleABI::get_data_for_sources(
self, data_type, aggregation_mode, sources
);
if aggregation_mode == AggregationMode::ConversionRate {
// Query median for STRK/USD
let sources = IOracleABI::get_all_sources(self, DataType::SpotEntry('STRK/USD'));
let response: PragmaPricesResponse = IOracleABI::get_data_for_sources(
self, DataType::SpotEntry('STRK/USD'), AggregationMode::Median(()), sources
);

prices_response
// Extract base asset
let asset: felt252 = match data_type {
DataType::SpotEntry(asset) => asset,
DataType::FutureEntry(_) => panic_with_felt252('Set only for Spot entries'),
DataType::GenericEntry(_) => panic_with_felt252('Set only for Spot entries'),
};

// Get base currency and pool
let base_asset: felt252 = self.get_pair(asset).base_currency_id;
assert(base_asset != 0, 'Asset not registered');
let pool_address: ContractAddress = self.tokenized_vault.read((base_asset, 'STRK'));
assert(
pool_address != contract_address_const::<0>(), 'No pool address for given token'
);
let pool = IERC4626Dispatcher { contract_address: pool_address };

// Compute adjusted price
let price: u256 = response.price.into()
* pool.preview_mint(1000000000000000000)
/ 1000000000000000000;

// The conversion should not fail because we scaled the price to 8 decimals
let converted_price: u128 = price.try_into().expect('Conversion should not fail');
assert(converted_price != 0, 'Price conversion failed');
PragmaPricesResponse {
price: converted_price,
decimals: response.decimals,
last_updated_timestamp: response.last_updated_timestamp,
num_sources_aggregated: response.num_sources_aggregated,
expiration_timestamp: response.expiration_timestamp
}
} else {
let sources = IOracleABI::get_all_sources(self, data_type);
IOracleABI::get_data_for_sources(self, data_type, aggregation_mode, sources)
}
}

// @notice aggregate all the entries for a given data type and given sources, with a given aggregation mode
Expand Down Expand Up @@ -1771,6 +1815,22 @@ mod Oracle {
}
}

// @notice register a new tokenized vault into the regisry (priced with STRK)
// @dev Callable only by the owner
// @dev the token must be registered as currency and pair in the oracle registry
// @dev We reserve the owner of the contract the right to overwrite an existing token address
// @param token The token to register
// @param token_address Token address to register
fn register_tokenized_vault(
ref self: ContractState, token: felt252, token_address: ContractAddress
) {
OracleInternal::assert_only_admin();
assert(token != 0, 'Token cannot be 0');
assert(token_address != contract_address_const::<0>(), 'Token address cannot be 0');
self.tokenized_vault.write((token, 'STRK'), token_address)
}


// @notice set a new checkpoint for a given data type and and aggregation mode
// @param data_type: an enum of DataType (e.g : DataType::SpotEntry(ASSET_ID) or DataType::FutureEntry((ASSSET_ID, expiration_timestamp)))
// @param aggregation_mode: the aggregation method to be used
Expand Down

0 comments on commit cb33ec5

Please sign in to comment.