FlashLoan: fee fixes (#693)
- Rename the new "swap fee" to "deposit fee" and let it apply to all
deposits, not just for Swap-type flash loans.
- But don't apply it to withdrawals (effectively giving rebates!)
Result of audit feedback
(cherry picked from commit 1d6ce550f3
)
This commit is contained in:
parent
f63163d737
commit
2d392c8fff
|
@ -35,10 +35,13 @@ Update this for each program release and mainnet deployment.
|
|||
Liquidators may be interested in performing actions in the same transaction
|
||||
as a flash loan swap.
|
||||
|
||||
- Flash loan swaps: The DAO can now charge a fee (#660)
|
||||
- Flash loan: The DAO can now charge a deposit fee (#660, #693)
|
||||
|
||||
The DAO can now configure a fee on flash loan based swaps. Previously flash
|
||||
loans that did not use more than the user's deposits were free.
|
||||
The DAO can now configure a fee on deposits that happen in flash loans. This
|
||||
could be used to apply a fee to flash loan swaps.
|
||||
|
||||
Previously flash loans that did not increase the user's token balance and did
|
||||
not borrow tokens were free.
|
||||
|
||||
- Stop loss: Respect net borrow limits and change low-health completion (#677)
|
||||
- Stop loss: Store helpful UI fields (#654, #667)
|
||||
|
|
|
@ -594,7 +594,7 @@
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRate",
|
||||
"name": "flashLoanDepositFeeRate",
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
|
@ -926,7 +926,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRateOpt",
|
||||
"name": "flashLoanDepositFeeRateOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
|
@ -6670,7 +6670,7 @@
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRate",
|
||||
"name": "flashLoanDepositFeeRate",
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
|
@ -8256,7 +8256,7 @@
|
|||
"type": "i128"
|
||||
},
|
||||
{
|
||||
"name": "swapFee",
|
||||
"name": "depositFee",
|
||||
"type": "i128"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -235,8 +235,8 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
// Verify that each mentioned vault has a bank in the health accounts
|
||||
let mut vaults_with_banks = vec![false; vaults.len()];
|
||||
|
||||
// Biggest flash_loan_swap_fee_rate over all involved banks
|
||||
let mut max_swap_fee_rate = 0.0f32;
|
||||
// Biggest flash_loan_deposit_fee_rate over all involved banks
|
||||
let mut max_deposit_fee_rate = 0.0f32;
|
||||
|
||||
// Loop over the banks, finding matching vaults
|
||||
// TODO: must be moved into health.rs, because it assumes something about the health accounts structure
|
||||
|
@ -295,7 +295,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
change += repay;
|
||||
}
|
||||
|
||||
max_swap_fee_rate = max_swap_fee_rate.max(bank.flash_loan_swap_fee_rate);
|
||||
max_deposit_fee_rate = max_deposit_fee_rate.max(bank.flash_loan_deposit_fee_rate);
|
||||
|
||||
changes.push(TokenVaultChange {
|
||||
token_index: bank.token_index,
|
||||
|
@ -364,14 +364,14 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
let loan_origination_fee = loan * bank.loan_origination_fee_rate;
|
||||
bank.collected_fees_native += loan_origination_fee;
|
||||
|
||||
let swap_fee = if flash_loan_type == FlashLoanType::Swap {
|
||||
change.amount * I80F48::from_num(max_swap_fee_rate)
|
||||
let deposit_fee = if change.amount > 0 {
|
||||
change.amount * I80F48::from_num(max_deposit_fee_rate)
|
||||
} else {
|
||||
I80F48::ZERO
|
||||
};
|
||||
bank.collected_fees_native += swap_fee;
|
||||
bank.collected_fees_native += deposit_fee;
|
||||
|
||||
let change_amount = change.amount - loan_origination_fee - swap_fee;
|
||||
let change_amount = change.amount - loan_origination_fee - deposit_fee;
|
||||
let native_after_change = native + change_amount;
|
||||
if bank.are_deposits_reduce_only() {
|
||||
require!(
|
||||
|
@ -415,7 +415,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
deposit_index: bank.deposit_index.to_bits(),
|
||||
borrow_index: bank.borrow_index.to_bits(),
|
||||
price: oracle_price.to_bits(),
|
||||
swap_fee: swap_fee.to_bits(),
|
||||
deposit_fee: deposit_fee.to_bits(),
|
||||
});
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
|
|
|
@ -41,7 +41,7 @@ pub fn token_edit(
|
|||
force_close_opt: Option<bool>,
|
||||
token_conditional_swap_taker_fee_rate_opt: Option<f32>,
|
||||
token_conditional_swap_maker_fee_rate_opt: Option<f32>,
|
||||
flash_loan_swap_fee_rate_opt: Option<f32>,
|
||||
flash_loan_deposit_fee_rate_opt: Option<f32>,
|
||||
) -> Result<()> {
|
||||
let group = ctx.accounts.group.load()?;
|
||||
|
||||
|
@ -308,35 +308,35 @@ pub fn token_edit(
|
|||
require_group_admin = true;
|
||||
};
|
||||
|
||||
if let Some(fee_fraction) = token_conditional_swap_taker_fee_rate_opt {
|
||||
if let Some(fee_rate) = token_conditional_swap_taker_fee_rate_opt {
|
||||
msg!(
|
||||
"Token conditional swap taker fee fraction old {:?}, new {:?}",
|
||||
bank.token_conditional_swap_taker_fee_rate,
|
||||
fee_fraction
|
||||
fee_rate
|
||||
);
|
||||
require_gte!(fee_fraction, 0.0); // values <0 are not currently supported
|
||||
bank.token_conditional_swap_taker_fee_rate = fee_fraction;
|
||||
require_gte!(fee_rate, 0.0); // values <0 are not currently supported
|
||||
bank.token_conditional_swap_taker_fee_rate = fee_rate;
|
||||
require_group_admin = true;
|
||||
}
|
||||
if let Some(fees_fraction) = token_conditional_swap_maker_fee_rate_opt {
|
||||
if let Some(fee_rate) = token_conditional_swap_maker_fee_rate_opt {
|
||||
msg!(
|
||||
"Token conditional swap maker fee fraction old {:?}, new {:?}",
|
||||
bank.token_conditional_swap_maker_fee_rate,
|
||||
fees_fraction
|
||||
fee_rate
|
||||
);
|
||||
require_gte!(fees_fraction, 0.0); // values <0 are not currently supported
|
||||
bank.token_conditional_swap_maker_fee_rate = fees_fraction;
|
||||
require_gte!(fee_rate, 0.0); // values <0 are not currently supported
|
||||
bank.token_conditional_swap_maker_fee_rate = fee_rate;
|
||||
require_group_admin = true;
|
||||
}
|
||||
|
||||
if let Some(fees_fraction) = flash_loan_swap_fee_rate_opt {
|
||||
if let Some(fee_rate) = flash_loan_deposit_fee_rate_opt {
|
||||
msg!(
|
||||
"Flash loan swap fee fraction old {:?}, new {:?}",
|
||||
bank.flash_loan_swap_fee_rate,
|
||||
fees_fraction
|
||||
bank.flash_loan_deposit_fee_rate,
|
||||
fee_rate
|
||||
);
|
||||
require_gte!(fees_fraction, 0.0); // values <0 are not currently supported
|
||||
bank.flash_loan_swap_fee_rate = fees_fraction;
|
||||
require_gte!(fee_rate, 0.0); // values <0 are not currently supported
|
||||
bank.flash_loan_deposit_fee_rate = fee_rate;
|
||||
require_group_admin = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ pub fn token_register(
|
|||
reduce_only: u8,
|
||||
token_conditional_swap_taker_fee_rate: f32,
|
||||
token_conditional_swap_maker_fee_rate: f32,
|
||||
flash_loan_swap_fee_rate: f32,
|
||||
flash_loan_deposit_fee_rate: f32,
|
||||
) -> Result<()> {
|
||||
// Require token 0 to be in the insurance token
|
||||
if token_index == INSURANCE_TOKEN_INDEX {
|
||||
|
@ -107,7 +107,7 @@ pub fn token_register(
|
|||
fees_withdrawn: 0,
|
||||
token_conditional_swap_taker_fee_rate,
|
||||
token_conditional_swap_maker_fee_rate,
|
||||
flash_loan_swap_fee_rate,
|
||||
flash_loan_deposit_fee_rate,
|
||||
reserved: [0; 2092],
|
||||
};
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ pub fn token_register_trustless(
|
|||
fees_withdrawn: 0,
|
||||
token_conditional_swap_taker_fee_rate: 0.0005,
|
||||
token_conditional_swap_maker_fee_rate: 0.0005,
|
||||
flash_loan_swap_fee_rate: 0.0005,
|
||||
flash_loan_deposit_fee_rate: 0.0005,
|
||||
reserved: [0; 2092],
|
||||
};
|
||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||
|
|
|
@ -145,7 +145,7 @@ pub mod mango_v4 {
|
|||
reduce_only: u8,
|
||||
token_conditional_swap_taker_fee_rate: f32,
|
||||
token_conditional_swap_maker_fee_rate: f32,
|
||||
flash_loan_swap_fee_rate: f32,
|
||||
flash_loan_deposit_fee_rate: f32,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::token_register(
|
||||
|
@ -172,7 +172,7 @@ pub mod mango_v4 {
|
|||
reduce_only,
|
||||
token_conditional_swap_taker_fee_rate,
|
||||
token_conditional_swap_maker_fee_rate,
|
||||
flash_loan_swap_fee_rate,
|
||||
flash_loan_deposit_fee_rate,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ pub mod mango_v4 {
|
|||
force_close_opt: Option<bool>,
|
||||
token_conditional_swap_taker_fee_rate_opt: Option<f32>,
|
||||
token_conditional_swap_maker_fee_rate_opt: Option<f32>,
|
||||
flash_loan_swap_fee_rate_opt: Option<f32>,
|
||||
flash_loan_deposit_fee_rate_opt: Option<f32>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::token_edit(
|
||||
|
@ -247,7 +247,7 @@ pub mod mango_v4 {
|
|||
force_close_opt,
|
||||
token_conditional_swap_taker_fee_rate_opt,
|
||||
token_conditional_swap_maker_fee_rate_opt,
|
||||
flash_loan_swap_fee_rate_opt,
|
||||
flash_loan_deposit_fee_rate_opt,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ pub struct FlashLoanTokenDetailV2 {
|
|||
pub deposit_index: i128,
|
||||
pub borrow_index: i128,
|
||||
pub price: i128,
|
||||
pub swap_fee: i128,
|
||||
pub deposit_fee: i128,
|
||||
}
|
||||
|
||||
#[event]
|
||||
|
|
|
@ -146,7 +146,7 @@ pub struct Bank {
|
|||
pub token_conditional_swap_taker_fee_rate: f32,
|
||||
pub token_conditional_swap_maker_fee_rate: f32,
|
||||
|
||||
pub flash_loan_swap_fee_rate: f32,
|
||||
pub flash_loan_deposit_fee_rate: f32,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 2092],
|
||||
|
@ -259,7 +259,7 @@ impl Bank {
|
|||
fees_withdrawn: 0,
|
||||
token_conditional_swap_taker_fee_rate: 0.0,
|
||||
token_conditional_swap_maker_fee_rate: 0.0,
|
||||
flash_loan_swap_fee_rate: 0.0,
|
||||
flash_loan_deposit_fee_rate: 0.0,
|
||||
reserved: [0; 2092],
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ impl Bank {
|
|||
require_gte!(2, self.reduce_only);
|
||||
require_gte!(self.token_conditional_swap_taker_fee_rate, 0.0);
|
||||
require_gte!(self.token_conditional_swap_maker_fee_rate, 0.0);
|
||||
require_gte!(self.flash_loan_swap_fee_rate, 0.0);
|
||||
require_gte!(self.flash_loan_deposit_fee_rate, 0.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -242,7 +242,7 @@ async fn test_margin_trade() -> Result<(), BanksClientError> {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_flash_loan_swap_fee() -> Result<(), BanksClientError> {
|
||||
async fn test_flash_loan_deposit_fee() -> Result<(), BanksClientError> {
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(100_000);
|
||||
let context = test_builder.start_default().await;
|
||||
|
@ -271,7 +271,7 @@ async fn test_flash_loan_swap_fee() -> Result<(), BanksClientError> {
|
|||
.create(solana)
|
||||
.await;
|
||||
|
||||
let swap_fee_fraction = 0.042f64;
|
||||
let deposit_fee_rate = 0.042f64;
|
||||
send_tx(
|
||||
solana,
|
||||
TokenEdit {
|
||||
|
@ -279,7 +279,7 @@ async fn test_flash_loan_swap_fee() -> Result<(), BanksClientError> {
|
|||
admin,
|
||||
mint: tokens[1].mint.pubkey,
|
||||
options: mango_v4::instruction::TokenEdit {
|
||||
flash_loan_swap_fee_rate_opt: Some(swap_fee_fraction as f32),
|
||||
flash_loan_deposit_fee_rate_opt: Some(deposit_fee_rate as f32),
|
||||
..token_edit_instruction_default()
|
||||
},
|
||||
},
|
||||
|
@ -412,10 +412,16 @@ async fn test_flash_loan_swap_fee() -> Result<(), BanksClientError> {
|
|||
provided_amount + deposit_amount
|
||||
);
|
||||
|
||||
let mango_withdraw_amount = account_position_f64(solana, account, tokens[0].bank).await;
|
||||
assert!(balance_f64eq(
|
||||
mango_withdraw_amount,
|
||||
(initial_deposit - withdraw_amount) as f64
|
||||
));
|
||||
|
||||
let mango_deposit_amount = account_position_f64(solana, account, tokens[1].bank).await;
|
||||
assert!(balance_f64eq(
|
||||
mango_deposit_amount,
|
||||
deposit_amount as f64 * (1.0 - swap_fee_fraction)
|
||||
deposit_amount as f64 * (1.0 - deposit_fee_rate)
|
||||
));
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -875,7 +875,7 @@ impl ClientInstruction for TokenRegisterInstruction {
|
|||
reduce_only: 0,
|
||||
token_conditional_swap_taker_fee_rate: 0.0,
|
||||
token_conditional_swap_maker_fee_rate: 0.0,
|
||||
flash_loan_swap_fee_rate: 0.0,
|
||||
flash_loan_deposit_fee_rate: 0.0,
|
||||
};
|
||||
|
||||
let bank = Pubkey::find_program_address(
|
||||
|
@ -1119,7 +1119,7 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
|
|||
force_close_opt: None,
|
||||
token_conditional_swap_taker_fee_rate_opt: None,
|
||||
token_conditional_swap_maker_fee_rate_opt: None,
|
||||
flash_loan_swap_fee_rate_opt: None,
|
||||
flash_loan_deposit_fee_rate_opt: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ async function tokenEdit(): Promise<void> {
|
|||
params.forceClose,
|
||||
params.tokenConditionalSwapTakerFeeRate,
|
||||
params.tokenConditionalSwapMakerFeeRate,
|
||||
params.flashLoanSwapFeeRate,
|
||||
params.flashLoanDepositFeeRate,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -124,7 +124,7 @@ export class Bank implements BankForHealth {
|
|||
feesWithdrawn: BN;
|
||||
tokenConditionalSwapTakerFeeRate: number;
|
||||
tokenConditionalSwapMakerFeeRate: number;
|
||||
flashLoanSwapFeeRate: number;
|
||||
flashLoanDepositFeeRate: number;
|
||||
},
|
||||
): Bank {
|
||||
return new Bank(
|
||||
|
@ -175,7 +175,7 @@ export class Bank implements BankForHealth {
|
|||
obj.feesWithdrawn,
|
||||
obj.tokenConditionalSwapTakerFeeRate,
|
||||
obj.tokenConditionalSwapMakerFeeRate,
|
||||
obj.flashLoanSwapFeeRate,
|
||||
obj.flashLoanDepositFeeRate,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ export class Bank implements BankForHealth {
|
|||
public feesWithdrawn: BN,
|
||||
public tokenConditionalSwapTakerFeeRate: number,
|
||||
public tokenConditionalSwapMakerFeeRate: number,
|
||||
public flashLoanSwapFeeRate: number,
|
||||
public flashLoanDepositFeeRate: number,
|
||||
) {
|
||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||
this.oracleConfig = {
|
||||
|
|
|
@ -404,7 +404,7 @@ export class MangoClient {
|
|||
params.reduceOnly,
|
||||
params.tokenConditionalSwapTakerFeeRate,
|
||||
params.tokenConditionalSwapMakerFeeRate,
|
||||
params.flashLoanSwapFeeRate,
|
||||
params.flashLoanDepositFeeRate,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -479,7 +479,7 @@ export class MangoClient {
|
|||
params.forceClose,
|
||||
params.tokenConditionalSwapTakerFeeRate,
|
||||
params.tokenConditionalSwapMakerFeeRate,
|
||||
params.flashLoanSwapFeeRate,
|
||||
params.flashLoanDepositFeeRate,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -24,7 +24,7 @@ export interface TokenRegisterParams {
|
|||
reduceOnly: number;
|
||||
tokenConditionalSwapTakerFeeRate: number;
|
||||
tokenConditionalSwapMakerFeeRate: number;
|
||||
flashLoanSwapFeeRate: number;
|
||||
flashLoanDepositFeeRate: number;
|
||||
}
|
||||
|
||||
export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
||||
|
@ -59,7 +59,7 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
|||
reduceOnly: 0,
|
||||
tokenConditionalSwapTakerFeeRate: 0.0005,
|
||||
tokenConditionalSwapMakerFeeRate: 0.0005,
|
||||
flashLoanSwapFeeRate: 0.0005,
|
||||
flashLoanDepositFeeRate: 0.0005,
|
||||
};
|
||||
|
||||
export interface TokenEditParams {
|
||||
|
@ -89,7 +89,7 @@ export interface TokenEditParams {
|
|||
forceClose: boolean | null;
|
||||
tokenConditionalSwapTakerFeeRate: number | null;
|
||||
tokenConditionalSwapMakerFeeRate: number | null;
|
||||
flashLoanSwapFeeRate: number | null;
|
||||
flashLoanDepositFeeRate: number | null;
|
||||
}
|
||||
|
||||
export const NullTokenEditParams: TokenEditParams = {
|
||||
|
@ -119,7 +119,7 @@ export const NullTokenEditParams: TokenEditParams = {
|
|||
forceClose: null,
|
||||
tokenConditionalSwapTakerFeeRate: null,
|
||||
tokenConditionalSwapMakerFeeRate: null,
|
||||
flashLoanSwapFeeRate: null,
|
||||
flashLoanDepositFeeRate: null,
|
||||
};
|
||||
|
||||
export interface PerpEditParams {
|
||||
|
|
|
@ -594,7 +594,7 @@ export type MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRate",
|
||||
"name": "flashLoanDepositFeeRate",
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
|
@ -926,7 +926,7 @@ export type MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRateOpt",
|
||||
"name": "flashLoanDepositFeeRateOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
|
@ -6670,7 +6670,7 @@ export type MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRate",
|
||||
"name": "flashLoanDepositFeeRate",
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
|
@ -8256,7 +8256,7 @@ export type MangoV4 = {
|
|||
"type": "i128"
|
||||
},
|
||||
{
|
||||
"name": "swapFee",
|
||||
"name": "depositFee",
|
||||
"type": "i128"
|
||||
}
|
||||
]
|
||||
|
@ -13131,7 +13131,7 @@ export const IDL: MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRate",
|
||||
"name": "flashLoanDepositFeeRate",
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
|
@ -13463,7 +13463,7 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRateOpt",
|
||||
"name": "flashLoanDepositFeeRateOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
|
@ -19207,7 +19207,7 @@ export const IDL: MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "flashLoanSwapFeeRate",
|
||||
"name": "flashLoanDepositFeeRate",
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
|
@ -20793,7 +20793,7 @@ export const IDL: MangoV4 = {
|
|||
"type": "i128"
|
||||
},
|
||||
{
|
||||
"name": "swapFee",
|
||||
"name": "depositFee",
|
||||
"type": "i128"
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue