Skip to content

Commit b9373a3

Browse files
committed
feat: opt risk factor
1 parent 322de81 commit b9373a3

File tree

6 files changed

+121
-25
lines changed

6 files changed

+121
-25
lines changed

workspace/apps/perpetuals/contracts/src/core/components/assets/assets.cairo

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub mod AssetsComponent {
5454
};
5555
use starkware_utils::storage::utils::{AddToStorage, SubFromStorage};
5656
use starkware_utils::time::time::{Time, TimeDelta, Timestamp};
57+
use crate::core::types::asset::RiskConfig;
5758

5859
#[storage]
5960
pub struct Storage {
@@ -70,6 +71,8 @@ pub mod AssetsComponent {
7071
num_of_active_synthetic_assets: usize,
7172
#[rename("synthetic_config")]
7273
pub asset_config: Map<AssetId, Option<AssetConfig>>,
74+
pub risk_config: Map<AssetId, RiskConfig>,
75+
pub risk_factor_tiers_opt: Map<AssetId, Map<u64, RiskFactor>>,
7376
#[rename("synthetic_timely_data")]
7477
pub asset_timely_data: IterableMap<AssetId, AssetTimelyData>,
7578
pub risk_factor_tiers: Map<AssetId, Vec<RiskFactor>>,
@@ -151,6 +154,33 @@ pub mod AssetsComponent {
151154
self.emit(events::OracleAdded { asset_id, asset_name, oracle_public_key, oracle_name });
152155
}
153156

157+
fn migrate_risk(ref self: ComponentState<TContractState>) {
158+
for (asset_id, _) in self.asset_timely_data {
159+
// pub risk_factor_first_tier_boundary: u128,
160+
// /// - `risk_factor_tier_size` — 92-bit field stored in a `u128`.
161+
// pub risk_factor_tier_size: u128,
162+
// pub len: u32,
163+
let x = self.asset_config.read(asset_id).unwrap();
164+
165+
self
166+
.risk_config
167+
.write(
168+
asset_id,
169+
RiskConfig {
170+
risk_factor_first_tier_boundary: x.risk_factor_first_tier_boundary,
171+
risk_factor_tier_size: x.risk_factor_tier_size,
172+
len: self.risk_factor_tiers.entry(asset_id).len().try_into().unwrap(),
173+
},
174+
);
175+
let vec = self.risk_factor_tiers.entry(asset_id);
176+
for i in 0..vec.len() {
177+
let value = vec.at(i.into()).read();
178+
self.risk_factor_tiers_opt.entry(asset_id).write(i.into(), value);
179+
}
180+
}
181+
}
182+
183+
154184
/// Add asset is called by the app governer to add a new synthetic asset.
155185
///
156186
/// Validations:
@@ -548,26 +578,22 @@ pub mod AssetsComponent {
548578
balance: Balance,
549579
price: Price,
550580
) -> RiskFactor {
551-
if let Option::Some(asset_config) = self.asset_config.read(asset_id) {
552-
let asset_risk_factor_tiers = self.risk_factor_tiers.entry(asset_id);
553-
let synthetic_value: u128 = price.mul(rhs: balance).abs();
554-
let index = if synthetic_value < asset_config.risk_factor_first_tier_boundary {
555-
0_u128
556-
} else {
557-
let tier_size = asset_config.risk_factor_tier_size;
558-
let first_tier_offset = synthetic_value
559-
- asset_config.risk_factor_first_tier_boundary;
560-
min(
561-
1_u128 + (first_tier_offset / tier_size),
562-
asset_risk_factor_tiers.len().into() - 1,
563-
)
564-
};
565-
asset_risk_factor_tiers
566-
.at(index.try_into().expect('INDEX_SHOULD_NEVER_OVERFLOW'))
567-
.read()
581+
let risk_config = self.risk_config.read(asset_id);
582+
let synthetic_value: u128 = price.mul(rhs: balance).abs();
583+
let index = if synthetic_value < risk_config.risk_factor_first_tier_boundary {
584+
0_u128
568585
} else {
569-
panic_with_felt252(ASSET_NOT_EXISTS)
570-
}
586+
let first_tier_offset = synthetic_value
587+
- risk_config.risk_factor_first_tier_boundary;
588+
min(
589+
1_u128 + (first_tier_offset / risk_config.risk_factor_tier_size),
590+
risk_config.len.into() - 1,
591+
)
592+
};
593+
self
594+
.risk_factor_tiers_opt
595+
.entry(asset_id)
596+
.read(index.try_into().expect('INDEX_SHOULD_NEVER_OVERFLOW'))
571597
}
572598

573599
fn get_funding_index(

workspace/apps/perpetuals/contracts/src/core/components/assets/interface.cairo

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ pub trait IAssets<TContractState> {
5555
);
5656
fn update_asset_quorum(ref self: TContractState, asset_id: AssetId, quorum: u8);
5757

58+
fn migrate_risk(ref self: TContractState);
59+
5860
// View functions.
5961
fn get_collateral_token_contract(self: @TContractState) -> IERC20Dispatcher;
6062
fn get_collateral_quantum(self: @TContractState) -> u64;

workspace/apps/perpetuals/contracts/src/core/core.cairo

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,22 @@ pub mod Core {
10991099
fulfillment_entry.write(total_amount);
11001100
}
11011101

1102-
fn _validate_order(ref self: ContractState, order: Order) {
1102+
fn _validate_order(
1103+
ref self: ContractState,
1104+
order: Order,
1105+
now: Option<Timestamp>,
1106+
collateral_id: Option<AssetId>,
1107+
) -> (Timestamp, AssetId) {
1108+
let now = if now.is_none() {
1109+
Time::now()
1110+
} else {
1111+
now.unwrap()
1112+
};
1113+
let collateral_id = if collateral_id.is_none() {
1114+
self.assets.get_collateral_id()
1115+
} else {
1116+
collateral_id.unwrap()
1117+
};
11031118
// Verify that position is not fee position.
11041119
assert(order.position_id != FEE_POSITION, CANT_TRADE_WITH_FEE_POSITION);
11051120
// This is to make sure that the fee is relative to the quote amount.
@@ -1119,9 +1134,9 @@ pub mod Core {
11191134
assert(!have_same_sign(order.quote_amount, order.base_amount), INVALID_AMOUNT_SIGN);
11201135

11211136
// Validate asset ids.
1122-
let collateral_id = self.assets.get_collateral_id();
11231137
assert(order.quote_asset_id == collateral_id, ASSET_ID_NOT_COLLATERAL);
11241138
assert(order.fee_asset_id == collateral_id, ASSET_ID_NOT_COLLATERAL);
1139+
(now, collateral_id)
11251140
}
11261141

11271142
fn _validate_trade(
@@ -1135,12 +1150,20 @@ pub mod Core {
11351150
) {
11361151
// Base asset check.
11371152
assert(order_a.base_asset_id == order_b.base_asset_id, DIFFERENT_BASE_ASSET_IDS);
1138-
self.assets.validate_active_asset(asset_id: order_a.base_asset_id);
1153+
// self.assets.validate_active_asset(asset_id: order_a.base_asset_id);
11391154

11401155
assert(order_a.position_id != order_b.position_id, INVALID_SAME_POSITIONS);
11411156

1142-
self._validate_order(order: order_a);
1143-
self._validate_order(order: order_b);
1157+
let (now, collateral_id) = self
1158+
._validate_order(
1159+
order: order_a, now: Default::default(), collateral_id: Default::default(),
1160+
);
1161+
self
1162+
._validate_order(
1163+
order: order_b,
1164+
now: Option::Some(now),
1165+
collateral_id: Option::Some(collateral_id),
1166+
);
11441167

11451168
// Non-zero actual amount check.
11461169
assert(actual_amount_base_a.is_non_zero(), INVALID_ZERO_AMOUNT);

workspace/apps/perpetuals/contracts/src/core/types/asset.cairo

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
use core::num::traits::Pow;
12
use core::num::traits::zero::Zero;
23
use perpetuals::core::types::balance::Balance;
34
use perpetuals::core::types::funding::FundingIndex;
45
use perpetuals::core::types::price::Price;
56
use perpetuals::core::types::risk_factor::RiskFactor;
67
use starknet::storage::StoragePointer0Offset;
7-
use starknet::storage_access::storage_address_from_base_and_offset;
8+
use starknet::storage_access::{StorePacking, storage_address_from_base_and_offset};
89
use starknet::syscalls::storage_read_syscall;
910
use starknet::{ContractAddress, SyscallResultTrait};
1011
use starkware_utils::time::time::Timestamp;
12+
const TWO_POW_92: u128 = 2_u128.pow(92);
13+
const MASK_92: u128 = TWO_POW_92 - 1;
1114

1215
#[derive(Copy, Debug, Default, Drop, Hash, PartialEq, Serde, starknet::Store)]
1316
pub struct AssetId {
@@ -101,6 +104,42 @@ pub struct AssetConfig {
101104
pub asset_type: AssetType // V2
102105
}
103106

107+
108+
#[derive(Copy, Drop, Serde)]
109+
pub struct RiskConfig {
110+
/// - `risk_factor_first_tier_boundary` — 92-bit field stored in a `u128`.
111+
pub risk_factor_first_tier_boundary: u128,
112+
/// - `risk_factor_tier_size` — 92-bit field stored in a `u128`.
113+
pub risk_factor_tier_size: u128,
114+
pub len: u32,
115+
}
116+
117+
/// ┌──────────────────────────────────────────────────────────────┐
118+
/// │ 252-bit felt (interpreted as uint256) │
119+
/// ├─────────────────────────┬─────────────────┬──────────────────┤
120+
/// │ bitadd s 1–92 │ bits 93–124 │ bits 129–220 │
121+
/// │ risk_factor_first_tier_boundary (92 bits) │ len (32 bits) │
122+
/// │ risk_factor_tier_size (92 bits) │ │
123+
/// └─────────────────────────┴─────────────────┴──────────────────┘
124+
impl StorePackingRiskConfig of StorePacking<RiskConfig, felt252> {
125+
fn pack(value: RiskConfig) -> felt252 {
126+
let low = value.risk_factor_first_tier_boundary + (value.len.into()) * TWO_POW_92;
127+
let high = value.risk_factor_tier_size;
128+
let result = u256 { low, high };
129+
result.try_into().unwrap()
130+
}
131+
132+
/// Unpacks a storage representation back into the original type.
133+
fn unpack(value: felt252) -> RiskConfig {
134+
let u256 { low, high } = value.into();
135+
let risk_factor_first_tier_boundary = low & MASK_92;
136+
let risk_factor_tier_size = high;
137+
let len: u32 = (low / TWO_POW_92).try_into().unwrap();
138+
RiskConfig { risk_factor_first_tier_boundary, risk_factor_tier_size, len }
139+
}
140+
}
141+
142+
104143
#[derive(Copy, Drop, Serde, starknet::Store)]
105144
pub struct AssetTimelyData {
106145
version: u8,

workspace/apps/perpetuals/contracts/src/core/types/order.cairo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub impl OrderImpl of OrderTrait {
9494
let order_fee_to_quote_ratio = FractionTrait::new(
9595
numerator: (*self.fee_amount).into(), denominator: (*self.quote_amount).abs().into(),
9696
);
97+
9798
if (actual_fee_to_quote_ratio > order_fee_to_quote_ratio) {
9899
let err = @illegal_fee_to_quote_ratio_err(*self.position_id);
99100
panic_with_byte_array(:err);

workspace/apps/perpetuals/contracts/src/tests/performance_tests/performance_tests.cairo

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use perpetuals::core::components::assets::interface::{IAssetsDispatcher, IAssetsDispatcherTrait};
12
use perpetuals::core::core::Core::{InternalCoreFunctions, SNIP12MetadataImpl};
23
use perpetuals::core::interface::{ICoreDispatcher, ICoreDispatcherTrait, Settlement};
34
use perpetuals::core::types::order::Order;
@@ -13,6 +14,7 @@ use starkware_utils::storage::iterable_map::*;
1314
use starkware_utils::time::time::Timestamp;
1415
use starkware_utils_testing::test_utils::cheat_caller_address_once;
1516

17+
1618
// Performance test for Core contract multi-trade execution.
1719
//
1820
// This test file is designed to measure the gas usage of executing multiple
@@ -630,6 +632,9 @@ fn test_performance() {
630632
let dispatcher = ICoreDispatcher { contract_address: CONTRACT_ADDRESS };
631633
let trades = settlements();
632634

635+
let assetdispatcher = IAssetsDispatcher { contract_address: CONTRACT_ADDRESS };
636+
assetdispatcher.migrate_risk();
637+
633638
cheat_caller_address_once(contract_address: CONTRACT_ADDRESS, caller_address: OPERATOR_ADDRESS);
634639
dispatcher.multi_trade(operator_nonce: CURRENT_OPERATOR_NONCE, :trades);
635640
}

0 commit comments

Comments
 (0)