Skip to content

Commit

Permalink
Fix a bug with the cyclic fee/rewards quote calculation (#629)
Browse files Browse the repository at this point in the history
* Fix a bug with the cyclic fee/rewards quote calculation

* Tweak

* Format
  • Loading branch information
wjthieme authored Jan 3, 2025
1 parent b446521 commit 80dfe6a
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 7 deletions.
46 changes: 42 additions & 4 deletions rust-sdk/core/src/quote/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@ pub fn collect_fees_quote(
.wrapping_sub(fee_growth_below_b)
.wrapping_sub(fee_growth_above_b);

let fee_owed_delta_a: U256 = <U256>::from(fee_growth_inside_a)
.wrapping_sub(position.fee_growth_checkpoint_a.into())
let fee_growth_delta_a = fee_growth_inside_a.wrapping_sub(position.fee_growth_checkpoint_a);

let fee_growth_delta_b = fee_growth_inside_b.wrapping_sub(position.fee_growth_checkpoint_b);

let fee_owed_delta_a: U256 = <U256>::from(fee_growth_delta_a)
.checked_mul(position.liquidity.into())
.ok_or(ARITHMETIC_OVERFLOW)?
>> 64;

let fee_owed_delta_b: U256 = <U256>::from(fee_growth_inside_b)
.wrapping_sub(position.fee_growth_checkpoint_b.into())
let fee_owed_delta_b: U256 = <U256>::from(fee_growth_delta_b)
.checked_mul(position.liquidity.into())
.ok_or(ARITHMETIC_OVERFLOW)?
>> 64;
Expand Down Expand Up @@ -219,4 +221,40 @@ mod tests {
assert_eq!(result.fee_owed_a, 623);
assert_eq!(result.fee_owed_b, 560);
}

#[test]
fn test_cyclic_growth_checkpoint() {
let position = PositionFacade {
liquidity: 91354442895,
tick_lower_index: 15168,
tick_upper_index: 19648,
fee_growth_checkpoint_a: 340282366920938463463368367551765494643,
fee_growth_checkpoint_b: 340282366920938463463235752370561182038,
..PositionFacade::default()
};

let whirlpool = WhirlpoolFacade {
tick_current_index: 18158,
fee_growth_global_a: 388775621815491196,
fee_growth_global_b: 2114651338550574490,
..WhirlpoolFacade::default()
};

let tick_lower = TickFacade {
fee_growth_outside_a: 334295763697402279,
fee_growth_outside_b: 1816428862338027402,
..TickFacade::default()
};

let tick_upper = TickFacade {
fee_growth_outside_a: 48907059211668900,
fee_growth_outside_b: 369439434559592375,
..TickFacade::default()
};

let result =
collect_fees_quote(whirlpool, position, tick_lower, tick_upper, None, None).unwrap();
assert_eq!(result.fee_owed_a, 58500334);
assert_eq!(result.fee_owed_b, 334966494);
}
}
69 changes: 66 additions & 3 deletions rust-sdk/core/src/quote/rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,16 @@ pub fn collect_rewards_quote(
.wrapping_sub(reward_growth_below)
.wrapping_sub(reward_growth_above);

let reward_growth_delta: u64 = <U256>::from(reward_growth_inside)
.wrapping_sub(position.reward_infos[i].growth_inside_checkpoint.into())
let reward_growth_delta =
reward_growth_inside.wrapping_sub(position.reward_infos[i].growth_inside_checkpoint);

let reward_owed_delta: u64 = <U256>::from(reward_growth_delta)
.checked_mul(position.liquidity.into())
.ok_or(ARITHMETIC_OVERFLOW)?
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;

let withdrawable_reward = position.reward_infos[i].amount_owed + reward_growth_delta;
let withdrawable_reward = position.reward_infos[i].amount_owed + reward_owed_delta;
let rewards_owed =
try_apply_transfer_fee(withdrawable_reward, transfer_fees[i].unwrap_or_default())?;
reward_quotes[i] = CollectRewardQuote { rewards_owed }
Expand Down Expand Up @@ -243,4 +245,65 @@ mod tests {
assert_eq!(quote.map(|x| x.rewards[1].rewards_owed), Ok(22560));
assert_eq!(quote.map(|x| x.rewards[2].rewards_owed), Ok(22610));
}

#[test]
fn test_cyclic_growth_checkpoint() {
let position = PositionFacade {
liquidity: 91354442895,
tick_lower_index: 15168,
tick_upper_index: 19648,
reward_infos: [
PositionRewardInfoFacade {
growth_inside_checkpoint: 340282366920938463463374607431768211400,
amount_owed: 0,
},
PositionRewardInfoFacade {
growth_inside_checkpoint: 340282366920938463463374607431768211000,
amount_owed: 0,
},
PositionRewardInfoFacade {
growth_inside_checkpoint: 0,
amount_owed: 0,
},
],
..PositionFacade::default()
};

let whirlpool = WhirlpoolFacade {
tick_current_index: 18158,
reward_infos: [
WhirlpoolRewardInfoFacade {
growth_global_x64: 0,
emissions_per_second_x64: 0,
},
WhirlpoolRewardInfoFacade {
growth_global_x64: 0,
emissions_per_second_x64: 0,
},
WhirlpoolRewardInfoFacade {
growth_global_x64: 0,
emissions_per_second_x64: 0,
},
],
..WhirlpoolFacade::default()
};

let tick_lower = TickFacade {
reward_growths_outside: [0, 0, 0],
..TickFacade::default()
};

let tick_upper = TickFacade {
reward_growths_outside: [0, 0, 0],
..TickFacade::default()
};

let result = collect_rewards_quote(
whirlpool, position, tick_lower, tick_upper, 10, None, None, None,
)
.unwrap();
assert_eq!(result.rewards[0].rewards_owed, 5115848802120);
assert_eq!(result.rewards[1].rewards_owed, 41657625960120);
assert_eq!(result.rewards[2].rewards_owed, 0);
}
}

0 comments on commit 80dfe6a

Please sign in to comment.