-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add ProtocolFeeController implementation (#189)
* feat: add initial CLProtocolFeeController implementation * feat: complete fee controller per discussion * feat: resolve issues and add test cases per comment * feat: resolve issues and add test cases per comment * fix: corner cases when protocolFeeSplitRatio = 0 or 100% --------- Co-authored-by: chefburger <[email protected]>
- Loading branch information
1 parent
e41f381
commit 913ffac
Showing
2 changed files
with
595 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
// Copyright (C) 2024 PancakeSwap | ||
pragma solidity 0.8.26; | ||
|
||
import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol"; | ||
import {PoolKey} from "./types/PoolKey.sol"; | ||
import {PoolId} from "./types/PoolId.sol"; | ||
import {Currency} from "./types/Currency.sol"; | ||
import {LPFeeLibrary} from "./libraries/LPFeeLibrary.sol"; | ||
import {IProtocolFeeController} from "./interfaces/IProtocolFeeController.sol"; | ||
import {IProtocolFees} from "./interfaces/IProtocolFees.sol"; | ||
import {ProtocolFeeLibrary} from "./libraries/ProtocolFeeLibrary.sol"; | ||
|
||
/// @notice ProtocolFeeController for both Pool type | ||
contract ProtocolFeeController is IProtocolFeeController, Ownable2Step { | ||
using ProtocolFeeLibrary for uint24; | ||
|
||
/// @notice throw when the pool manager saved does not match the pool manager from the pool key | ||
error InvalidPoolManager(); | ||
|
||
/// @notice throw when the protocol fee split ratio is invalid i.e. greater than 100% | ||
error InvliadProtocolFeeSplitRatio(); | ||
|
||
/// @notice 100% in hundredths of a bip | ||
uint256 public constant ONE_HUNDRED_PERCENT_RATIO = 1e6; | ||
|
||
/// @notice The ratio of the protocol fee in the total fee, expressed in hundredths of a bip i.e. 1e4 is 1% | ||
/// @dev The default value is 33% i.e. protocol fee should be 33% of the total fee | ||
uint256 public protocolFeeSplitRatio = 33 * 1e4; | ||
|
||
address public immutable poolManager; | ||
|
||
event ProtocolFeeSplitRatioUpdated(uint256 oldProtocolFeeSplitRatio, uint256 newProtocolFeeSplitRatio); | ||
|
||
/// @notice emit when the protocol fee is collected | ||
event ProtocolFeeCollected(Currency indexed currency, uint256 amount); | ||
|
||
constructor(address _poolManager) Ownable(msg.sender) { | ||
poolManager = _poolManager; | ||
} | ||
|
||
/// @notice Set the ratio of the protocol fee in the total fee | ||
/// @param newProtocolFeeSplitRatio 30e4 would mean 30% of the total fee goes to protocol | ||
function setProtocolFeeSplitRatio(uint256 newProtocolFeeSplitRatio) external onlyOwner { | ||
if (newProtocolFeeSplitRatio > ONE_HUNDRED_PERCENT_RATIO) revert InvliadProtocolFeeSplitRatio(); | ||
|
||
uint256 oldProtocolFeeSplitRatio = protocolFeeSplitRatio; | ||
protocolFeeSplitRatio = newProtocolFeeSplitRatio; | ||
|
||
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 | ||
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 | ||
uint256 lpFee = poolKey.fee; | ||
if (lpFee == LPFeeLibrary.DYNAMIC_FEE_FLAG) { | ||
/// @notice for dynamic fee pools, the default protocol fee is 0 | ||
return _buildProtocolFee(0); | ||
} else if (protocolFeeSplitRatio == 0) { | ||
return _buildProtocolFee(0); | ||
} else if (protocolFeeSplitRatio == ONE_HUNDRED_PERCENT_RATIO) { | ||
return _buildProtocolFee(ProtocolFeeLibrary.MAX_PROTOCOL_FEE); | ||
} else { | ||
/// @notice for static fee pools, the protocol fee should be a portion of the total fee based on 'protocolFeeSplitRatio' | ||
/// @dev the formula is derived from the following equation: | ||
/// totalSwapFee = protocolFee + (1 - protocolFee) * lpFee = protocolFee / protocolFeeSplitRatio | ||
uint24 oneDirectionProtocolFee = uint24( | ||
lpFee * ONE_HUNDRED_PERCENT_RATIO | ||
/ ( | ||
lpFee + ONE_HUNDRED_PERCENT_RATIO * ONE_HUNDRED_PERCENT_RATIO / protocolFeeSplitRatio | ||
- ONE_HUNDRED_PERCENT_RATIO | ||
) | ||
); | ||
|
||
// cap the protocol fee at 0.4%, if it's over the limit we set it to the max | ||
if (oneDirectionProtocolFee > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) { | ||
oneDirectionProtocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE; | ||
} | ||
return _buildProtocolFee(oneDirectionProtocolFee); | ||
} | ||
} | ||
|
||
/// @param fee If 1000, the protocol fee is 0.1%, cap at 0.4% | ||
/// @return The protocol fee for both directions, the upper 12 bits are for 1->0 | ||
function _buildProtocolFee(uint24 fee) internal pure returns (uint24) { | ||
return fee + (fee << 12); | ||
} | ||
|
||
/// @notice Override the default protcool fee for the pool | ||
/// @dev this could be used for marketing campaign where PCS takes 0 protocol fee for a pool for a period | ||
/// @param newProtocolFee 1000 = 0.1%, and max at 4000 = 0.4%. If set at 0.1%, this means 0.1% of amountIn for each swap will go to protocol | ||
function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external onlyOwner { | ||
if (address(key.poolManager) != poolManager) revert InvalidPoolManager(); | ||
|
||
// no need to validate the protocol fee as it will be done in the pool manager | ||
IProtocolFees(address(key.poolManager)).setProtocolFee(key, newProtocolFee); | ||
} | ||
|
||
/// @notice Collect the protocol fee from the pool manager | ||
/// @param recipient The address to receive the protocol fee | ||
/// @param currency The currency of the protocol fee | ||
/// @param amount The amount of the protocol fee to collect, 0 means collect all | ||
function collectProtocolFee(address recipient, Currency currency, uint256 amount) external onlyOwner { | ||
IProtocolFees(poolManager).collectProtocolFees(recipient, currency, amount); | ||
|
||
emit ProtocolFeeCollected(currency, amount); | ||
} | ||
} |
Oops, something went wrong.