Skip to content

Commit

Permalink
feat: support getLPFeeFromTotalFee in protocolFeeController (#222)
Browse files Browse the repository at this point in the history
* feat: support getLPFeeFromTotalFee in protocolFeeController

* fix: adjust the test error tolerance to 0.05%
  • Loading branch information
chefburger authored Dec 2, 2024
1 parent 913ffac commit d48491a
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
32499
32487
Original file line number Diff line number Diff line change
@@ -1 +1 @@
31956
31968
25 changes: 22 additions & 3 deletions src/ProtocolFeeController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,31 @@ contract ProtocolFeeController is IProtocolFeeController, Ownable2Step {
emit ProtocolFeeSplitRatioUpdated(oldProtocolFeeSplitRatio, newProtocolFeeSplitRatio);
}

/// @notice Get the protocol fee for a pool given the conditions of this contract
/// @return protocolFee The pool's protocol fee, expressed in hundredths of a bip. The upper 12 bits are for 1->0
/// @notice Get the LP fee based on protocolFeeSplitRatio and total fee. This is useful for FE to calculate the LP fee
/// based on user's input when initializing a static fee pool
/// warning: if protocolFee is over 0.4% based on the totalFee, then it will be capped at 0.4% which means
/// lpFee in this case will charge more lpFee than expected i.e more than "1 - protocolFeeSplitRatio"
/// @param totalFee The total fee (including lpFee and protocolFee) for the pool, expressed in hundredths of a bip
/// @return lpFee The LP fee that can be passed in as poolKey.fee, expressed in hundredths of a bip
function getLPFeeFromTotalFee(uint24 totalFee) external view returns (uint24) {
/// @dev the formula is derived from the following equation:
/// poolKey.fee = lpFee = (totalFee - protocolFee) / (1 - protocolFee)
uint256 oneDirectionProtocolFee = totalFee * protocolFeeSplitRatio / ONE_HUNDRED_PERCENT_RATIO;
if (oneDirectionProtocolFee > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) {
oneDirectionProtocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE;
}

return uint24(
(totalFee - oneDirectionProtocolFee) * ONE_HUNDRED_PERCENT_RATIO
/ (ONE_HUNDRED_PERCENT_RATIO - oneDirectionProtocolFee)
);
}

/// @inheritdoc IProtocolFeeController
function protocolFeeForPool(PoolKey memory poolKey) external view override returns (uint24 protocolFee) {
if (address(poolKey.poolManager) != poolManager) revert InvalidPoolManager();

// otherwise, calculate the protocol fee based on the predefined rule
// calculate the protocol fee based on the predefined rule
uint256 lpFee = poolKey.fee;
if (lpFee == LPFeeLibrary.DYNAMIC_FEE_FLAG) {
/// @notice for dynamic fee pools, the default protocol fee is 0
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IProtocolFeeController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface IProtocolFeeController {
/// @param poolKey The pool key to identify the pool. The controller may want to use attributes on the pool
/// to determine the protocol fee, hence the entire key is needed.
/// @return protocolFee The pool's protocol fee, expressed in hundredths of a bip. The upper 12 bits are for 1->0
/// and the lower 12 are for 0->1. The maximum is 1000 - meaning the maximum protocol fee is 0.1%.
/// and the lower 12 are for 0->1. The maximum is 4000 - meaning the maximum protocol fee is 0.4%.
/// the protocolFee is taken from the input first, then the lpFee is taken from the remaining input
function protocolFeeForPool(PoolKey memory poolKey) external view returns (uint24 protocolFee);
}
40 changes: 38 additions & 2 deletions test/ProtocolFeeController.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,42 @@ contract ProtocolFeeControllerTest is Test, BinTestHelper, TokenFixture {
}
}

function testGetLPFeeFromTotalFee(uint24 totalFee, uint24 splitRatio) public {
totalFee = uint24(bound(totalFee, 0, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE));
ProtocolFeeController controller = new ProtocolFeeController(address(clPoolManager));
splitRatio = uint24(bound(splitRatio, 0, controller.ONE_HUNDRED_PERCENT_RATIO()));
controller.setProtocolFeeSplitRatio(splitRatio);

// try to simulate the calculation the process of FE initialization pool

// step1: calculate lpFee from totalFee
uint24 lpFee = controller.getLPFeeFromTotalFee(totalFee);

assertGe(lpFee, 0);
assertLe(lpFee, totalFee);

// step2: prepare the poolKey
PoolKey memory key = PoolKey({
currency0: currency0,
currency1: currency1,
hooks: IHooks(address(0)),
poolManager: clPoolManager,
fee: lpFee,
parameters: bytes32(0).setTickSpacing(10)
});
uint24 protocolFee = controller.protocolFeeForPool(key);
uint16 protocolFeeZeroForOne = protocolFee.getZeroForOneFee();

// verify the totalFee expected to be equal to protocolFee + (1 - protocolFee) * lpFee
assertApproxEqAbs(
totalFee,
protocolFeeZeroForOne.calculateSwapFee(lpFee),
// keeping the error within 0.01% (can't avoid due to precision loss)
100,
"totalFee should be equal to protocolFee + (1 - protocolFee) * lpFee"
);
}

function testProtocolFeeForPool(uint24 lpFee, uint256 protocolFeeRatio) public {
lpFee = uint24(bound(lpFee, 0, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE));
ProtocolFeeController controller = new ProtocolFeeController(address(clPoolManager));
Expand Down Expand Up @@ -223,8 +259,8 @@ contract ProtocolFeeControllerTest is Test, BinTestHelper, TokenFixture {
assertApproxEqAbs(
totalFee * controller.protocolFeeSplitRatio() / controller.ONE_HUNDRED_PERCENT_RATIO(),
protocolFeeZeroForOne,
// keeping the error within 0.01% (can't avoid due to precision loss)
100
// keeping the error within 0.05% (can't avoid due to precision loss)
500
);
}
}
Expand Down

0 comments on commit d48491a

Please sign in to comment.