@@ -125,6 +125,7 @@ impl Pool {
125125 locked_amount : u64 ,
126126 collateral_custody : & Custody ,
127127 ) -> Result < u64 > {
128+ // The "optimal" algorithm is always used to compute the fee for entering a position.
128129 // entry_fee = custody.fees.open_position * utilization_fee * size
129130 // where utilization_fee = 1 + custody.fees.utilization_mult * (new_utilization - optimal_utilization) / (1 - optimal_utilization);
130131
@@ -957,9 +958,37 @@ impl Pool {
957958 require ! ( !custody. is_virtual, PerpetualsError :: InstructionNotAllowed ) ;
958959
959960 if custody. fees . mode == FeesMode :: Fixed {
960- return Self :: get_fee_amount ( base_fee, std:: cmp:: max ( amount_add, amount_remove) ) ;
961+ Self :: get_fee_amount ( base_fee, std:: cmp:: max ( amount_add, amount_remove) )
962+ } else if custody. fees . mode == FeesMode :: Linear {
963+ self . get_fee_linear (
964+ token_id,
965+ base_fee,
966+ amount_add,
967+ amount_remove,
968+ custody,
969+ token_price,
970+ )
971+ } else {
972+ self . get_fee_optimal (
973+ token_id,
974+ base_fee,
975+ amount_add,
976+ amount_remove,
977+ custody,
978+ token_price,
979+ )
961980 }
981+ }
962982
983+ fn get_fee_linear (
984+ & self ,
985+ token_id : usize ,
986+ base_fee : u64 ,
987+ amount_add : u64 ,
988+ amount_remove : u64 ,
989+ custody : & Custody ,
990+ token_price : & OraclePrice ,
991+ ) -> Result < u64 > {
963992 // if token ratio is improved:
964993 // fee = base_fee / ratio_fee
965994 // otherwise:
@@ -1035,6 +1064,63 @@ impl Pool {
10351064 std:: cmp:: max ( amount_add, amount_remove) ,
10361065 )
10371066 }
1067+
1068+ fn get_fee_optimal (
1069+ & self ,
1070+ token_id : usize ,
1071+ base_fee : u64 ,
1072+ amount_add : u64 ,
1073+ amount_remove : u64 ,
1074+ custody : & Custody ,
1075+ token_price : & OraclePrice ,
1076+ ) -> Result < u64 > {
1077+ // Fee calculations must temporarily be in i64 because of negative slope.
1078+ let fee_max: i64 = custody. fees . fee_max as i64 ;
1079+ let fee_optimal: i64 = custody. fees . fee_optimal as i64 ;
1080+
1081+ let target_ratio: i64 = self . ratios [ token_id] . target as i64 ;
1082+ let min_ratio: i64 = self . ratios [ token_id] . min as i64 ;
1083+ let max_ratio: i64 = self . ratios [ token_id] . max as i64 ;
1084+ let post_lp_ratio: i64 =
1085+ self . get_new_ratio ( amount_add, amount_remove, custody, token_price) ? as i64 ;
1086+
1087+ let base_fee: i64 = base_fee as i64 ;
1088+
1089+ let slope_denominator: i64 = if post_lp_ratio > target_ratio {
1090+ math:: checked_sub ( max_ratio, target_ratio) ?
1091+ } else {
1092+ math:: checked_sub ( target_ratio, min_ratio) ?
1093+ } ;
1094+
1095+ let slope_numerator: i64 = if amount_add != 0 {
1096+ if post_lp_ratio > max_ratio {
1097+ return err ! ( PerpetualsError :: TokenRatioOutOfRange ) ;
1098+ }
1099+ fee_max - fee_optimal
1100+ } else {
1101+ if post_lp_ratio < min_ratio {
1102+ return err ! ( PerpetualsError :: TokenRatioOutOfRange ) ;
1103+ }
1104+ fee_optimal - fee_max
1105+ } ;
1106+
1107+ // Delay applying slope_denominator until the very end to avoid losing precision.
1108+ // b = fee_optimal - target_ratio * slope
1109+ // lp_fee = slope * post_lp_ratio + b
1110+ let b: i64 = math:: checked_sub (
1111+ math:: checked_mul ( fee_optimal, slope_denominator) ?,
1112+ math:: checked_mul ( target_ratio, slope_numerator) ?,
1113+ ) ?;
1114+ let lp_fee: i64 = math:: checked_div (
1115+ math:: checked_add ( math:: checked_mul ( slope_numerator, post_lp_ratio) ?, b) ?,
1116+ slope_denominator,
1117+ ) ?;
1118+
1119+ Self :: get_fee_amount (
1120+ math:: checked_as_u64 ( math:: checked_add ( lp_fee, base_fee) ?) ?,
1121+ std:: cmp:: max ( amount_add, amount_remove) ,
1122+ )
1123+ }
10381124}
10391125
10401126#[ cfg( test) ]
@@ -1096,12 +1182,14 @@ mod test {
10961182 swap_out : 100 ,
10971183 stable_swap_in : 100 ,
10981184 stable_swap_out : 100 ,
1099- add_liquidity : 200 ,
1100- remove_liquidity : 300 ,
1185+ add_liquidity : 0 ,
1186+ remove_liquidity : 0 ,
11011187 open_position : 100 ,
11021188 close_position : 0 ,
11031189 liquidation : 50 ,
11041190 protocol_share : 25 ,
1191+ fee_max : 0 ,
1192+ fee_optimal : 0 ,
11051193 } ;
11061194
11071195 let custody = Custody {
@@ -1570,6 +1658,83 @@ mod test {
15701658 )
15711659 . unwrap( )
15721660 ) ;
1661+
1662+ // Test Optimal fees
1663+ custody. fees . mode = FeesMode :: Optimal ;
1664+ custody. fees . fee_max = 250 ; // 0.025
1665+ custody. fees . fee_optimal = 10 ; // 0.001
1666+ assert_eq ! (
1667+ 18_000_000 , /* 0.1% fee because we approach target ratio. */
1668+ pool. get_fee(
1669+ 0 ,
1670+ custody. fees. add_liquidity,
1671+ scale( 18 , custody. decimals) ,
1672+ 0 ,
1673+ & custody,
1674+ & token_price,
1675+ )
1676+ . unwrap( )
1677+ ) ;
1678+ assert_eq ! (
1679+ 4_014_000_000 , /* 2.23% fee because we exceed target ratio, nearing max ratio. */
1680+ pool. get_fee(
1681+ 0 ,
1682+ custody. fees. add_liquidity,
1683+ scale( 180 , custody. decimals) ,
1684+ 0 ,
1685+ & custody,
1686+ & token_price,
1687+ )
1688+ . unwrap( )
1689+ ) ;
1690+ assert_eq ! (
1691+ 13_100_000 , /* 1.31% fee for removing a little liquidity. */
1692+ pool. get_fee(
1693+ 0 ,
1694+ custody. fees. remove_liquidity,
1695+ 0 ,
1696+ scale( 1 , custody. decimals) ,
1697+ & custody,
1698+ & token_price,
1699+ )
1700+ . unwrap( )
1701+ ) ;
1702+ assert_eq ! (
1703+ 231_000_000 , /* 2.31% fee because almost all liquidity is removed. */
1704+ pool. get_fee(
1705+ 0 ,
1706+ custody. fees. remove_liquidity,
1707+ 0 ,
1708+ scale( 10 , custody. decimals) ,
1709+ & custody,
1710+ & token_price,
1711+ )
1712+ . unwrap( )
1713+ ) ;
1714+ // Removing too much liquidity takes the token ratio out of range.
1715+ assert_eq ! (
1716+ err!( PerpetualsError :: TokenRatioOutOfRange ) ,
1717+ pool. get_fee(
1718+ 0 ,
1719+ custody. fees. remove_liquidity,
1720+ 0 ,
1721+ scale( 15 , custody. decimals) ,
1722+ & custody,
1723+ & token_price,
1724+ )
1725+ ) ;
1726+ // Adding too much liquidity takes the token ratio out of range.
1727+ assert_eq ! (
1728+ err!( PerpetualsError :: TokenRatioOutOfRange ) ,
1729+ pool. get_fee(
1730+ 0 ,
1731+ custody. fees. add_liquidity,
1732+ scale( 1800 , custody. decimals) ,
1733+ 0 ,
1734+ & custody,
1735+ & token_price,
1736+ )
1737+ ) ;
15731738 }
15741739
15751740 #[ test]
0 commit comments