Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions src/ProtocolFeeController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ contract ProtocolFeeController is IProtocolFeeController, Ownable2Step {
/// @notice throw when the pool manager saved does not match the pool manager from the pool key
error InvalidPoolManager();

/// @notice throw when the default protocol fee for dynamic fee pool is invalid i.e. greater than 0.4%
error InvalidDefaultProtocolFeeForDynamicFeePool();

/// @notice throw when the protocol fee split ratio is invalid i.e. greater than 100%
error InvalidProtocolFeeSplitRatio();

Expand All @@ -30,6 +33,15 @@ contract ProtocolFeeController is IProtocolFeeController, Ownable2Step {

address public immutable poolManager;

/// @notice the default protocol fee for dynamic fee pool,
/// every newly created dynamic fee pool will have this default protocol fee
/// @dev 1000 = 0.1%, the initial setting is 0.03% i.e. 3bps
uint24 public defaultProtocolFeeForDynamicFeePool = 300;

event DefaultProtocolFeeForDynamicFeePoolUpdated(
uint24 oldDefaultProtocolFeeForDynamicFeePool, uint24 newDefaultProtocolFeeForDynamicFeePool
);

event ProtocolFeeSplitRatioUpdated(uint256 oldProtocolFeeSplitRatio, uint256 newProtocolFeeSplitRatio);

/// @notice emit when the protocol fee is collected
Expand All @@ -39,6 +51,23 @@ contract ProtocolFeeController is IProtocolFeeController, Ownable2Step {
poolManager = _poolManager;
}

/// @notice Set the default protocol fee for dynamic fee pool, this will only impact the newly created dynamic fee pool
/// all those existing dynamic fee pools will not be affected, they will keep using the default protocol fee set at the time of creation
/// @param newDefaultProtocolFeeForDynamicFeePool 1000 = 0.1%, the initial setting is 0.03% i.e. 3bps
function setDefaultProtocolFeeForDynamicFeePool(uint24 newDefaultProtocolFeeForDynamicFeePool) external onlyOwner {
// cap the protocol fee at 0.4%, if it's over the limit we revert the tx
if (newDefaultProtocolFeeForDynamicFeePool > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) {
revert InvalidDefaultProtocolFeeForDynamicFeePool();
}

uint24 oldDefaultProtocolFeeForDynamicFeePool = defaultProtocolFeeForDynamicFeePool;
defaultProtocolFeeForDynamicFeePool = newDefaultProtocolFeeForDynamicFeePool;

emit DefaultProtocolFeeForDynamicFeePoolUpdated(
oldDefaultProtocolFeeForDynamicFeePool, newDefaultProtocolFeeForDynamicFeePool
);
}

/// @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 {
Expand Down Expand Up @@ -77,8 +106,8 @@ contract ProtocolFeeController is IProtocolFeeController, Ownable2Step {
// 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);
/// @notice for dynamic fee pools, the default protocol fee is set separately
return _buildProtocolFee(defaultProtocolFeeForDynamicFeePool);
} else if (protocolFeeSplitRatio == 0) {
return _buildProtocolFee(0);
} else if (protocolFeeSplitRatio == ONE_HUNDRED_PERCENT_RATIO) {
Expand Down
132 changes: 132 additions & 0 deletions test/ProtocolFeeController.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {BalanceDelta} from "../src/types/BalanceDelta.sol";
import {BinTestHelper} from "./pool-bin/helpers/BinTestHelper.sol";
import {BinSwapHelper} from "./pool-bin/helpers/BinSwapHelper.sol";
import {BinLiquidityHelper} from "./pool-bin/helpers/BinLiquidityHelper.sol";
import {HooksContract} from "./libraries/Hooks/HooksContract.sol";

contract ProtocolFeeControllerTest is Test, BinTestHelper, TokenFixture {
using CLPoolParametersHelper for bytes32;
Expand All @@ -36,13 +37,18 @@ contract ProtocolFeeControllerTest is Test, BinTestHelper, TokenFixture {
/// @notice 100% in hundredths of a bip
uint256 private constant ONE_HUNDRED_PERCENT_RATIO = 1e6;

/// @dev the initial setting of the default protocol fee for dynamic fee pool is 0.03% i.e. 3bps
uint24 private constant DEFAULT_PROTOCOL_FEE_FOR_DYNAMIC_FEE_POOL = 300;

Vault vault;
CLPoolManager clPoolManager;
BinPoolManager binPoolManager;

BinSwapHelper public binSwapHelper;
BinLiquidityHelper public binLiquidityHelper;

HooksContract public hooksContract;

function setUp() public {
initializeTokens();

Expand All @@ -58,6 +64,8 @@ contract ProtocolFeeControllerTest is Test, BinTestHelper, TokenFixture {
IERC20(Currency.unwrap(currency1)).approve(address(binSwapHelper), 1000 ether);
IERC20(Currency.unwrap(currency0)).approve(address(binLiquidityHelper), 1000 ether);
IERC20(Currency.unwrap(currency1)).approve(address(binLiquidityHelper), 1000 ether);

hooksContract = new HooksContract(0);
}

function testOwnerTransfer() public {
Expand Down Expand Up @@ -90,6 +98,32 @@ contract ProtocolFeeControllerTest is Test, BinTestHelper, TokenFixture {
assertEq(controller.owner(), makeAddr("newOwner"));
}

function testSetDefaultProtocolFeeForDynamicFeePool(uint24 newDefaultProtocolFeeForDynamicFeePool) public {
ProtocolFeeController controller = new ProtocolFeeController(address(clPoolManager));

// it should start with 0.03% as default
assertEq(controller.defaultProtocolFeeForDynamicFeePool(), DEFAULT_PROTOCOL_FEE_FOR_DYNAMIC_FEE_POOL);

{
// must from owner
vm.prank(makeAddr("someone"));
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, makeAddr("someone")));
controller.setDefaultProtocolFeeForDynamicFeePool(newDefaultProtocolFeeForDynamicFeePool);
}

if (newDefaultProtocolFeeForDynamicFeePool > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) {
vm.expectRevert(ProtocolFeeController.InvalidDefaultProtocolFeeForDynamicFeePool.selector);
controller.setDefaultProtocolFeeForDynamicFeePool(newDefaultProtocolFeeForDynamicFeePool);
} else {
vm.expectEmit(true, true, true, true);
emit ProtocolFeeController.DefaultProtocolFeeForDynamicFeePoolUpdated(
DEFAULT_PROTOCOL_FEE_FOR_DYNAMIC_FEE_POOL, newDefaultProtocolFeeForDynamicFeePool
);
controller.setDefaultProtocolFeeForDynamicFeePool(newDefaultProtocolFeeForDynamicFeePool);
assertEq(controller.defaultProtocolFeeForDynamicFeePool(), newDefaultProtocolFeeForDynamicFeePool);
}
}

function testSetProcotolFeeSplitRatio(uint256 newProtocolFeeSplitRatio) public {
ProtocolFeeController controller = new ProtocolFeeController(address(clPoolManager));

Expand Down Expand Up @@ -363,6 +397,104 @@ contract ProtocolFeeControllerTest is Test, BinTestHelper, TokenFixture {
}
}

function testCLDynamicPoolInitWithProtolFeeControllerFuzz(uint24 newDefaultProtocolFeeForDynamicFeePool) public {
ProtocolFeeController controller = new ProtocolFeeController(address(clPoolManager));
newDefaultProtocolFeeForDynamicFeePool =
uint24(bound(newDefaultProtocolFeeForDynamicFeePool, 0, ProtocolFeeLibrary.MAX_PROTOCOL_FEE));

clPoolManager.setProtocolFeeController(controller);

PoolKey memory key = PoolKey({
currency0: currency0,
currency1: currency1,
hooks: hooksContract,
poolManager: clPoolManager,
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
parameters: bytes32(0).setTickSpacing(10)
});
clPoolManager.initialize(key, Constants.SQRT_RATIO_1_1);

(,, uint24 actualProtocolFee,) = clPoolManager.getSlot0(key.toId());

// under default rule protocol fee must be equal for both directions
uint16 protocolFeeZeroForOne = actualProtocolFee.getZeroForOneFee();
uint16 protocolFeeOneForZero = actualProtocolFee.getOneForZeroFee();
assertEq(protocolFeeOneForZero, protocolFeeZeroForOne);
assertEq(protocolFeeOneForZero, DEFAULT_PROTOCOL_FEE_FOR_DYNAMIC_FEE_POOL);

controller.setDefaultProtocolFeeForDynamicFeePool(newDefaultProtocolFeeForDynamicFeePool);

key.parameters = bytes32(0).setTickSpacing(30);
clPoolManager.initialize(key, Constants.SQRT_RATIO_1_1);

(,, actualProtocolFee,) = clPoolManager.getSlot0(key.toId());
// under default rule protocol fee must be equal for both directions
protocolFeeZeroForOne = actualProtocolFee.getZeroForOneFee();
protocolFeeOneForZero = actualProtocolFee.getOneForZeroFee();
assertEq(protocolFeeOneForZero, protocolFeeZeroForOne);
assertEq(protocolFeeOneForZero, newDefaultProtocolFeeForDynamicFeePool);

// verify the original pool is not affected
{
key.parameters = bytes32(0).setTickSpacing(10);

(,, actualProtocolFee,) = clPoolManager.getSlot0(key.toId());
// under default rule protocol fee must be equal for both directions
protocolFeeZeroForOne = actualProtocolFee.getZeroForOneFee();
protocolFeeOneForZero = actualProtocolFee.getOneForZeroFee();
assertEq(protocolFeeOneForZero, protocolFeeZeroForOne);
}
}

function testBinDynamicPoolInitWithProtolFeeControllerFuzz(uint24 newDefaultProtocolFeeForDynamicFeePool) public {
ProtocolFeeController controller = new ProtocolFeeController(address(binPoolManager));
newDefaultProtocolFeeForDynamicFeePool =
uint24(bound(newDefaultProtocolFeeForDynamicFeePool, 0, ProtocolFeeLibrary.MAX_PROTOCOL_FEE));

binPoolManager.setProtocolFeeController(controller);

PoolKey memory key = PoolKey({
currency0: currency0,
currency1: currency1,
hooks: hooksContract,
poolManager: binPoolManager,
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
parameters: bytes32(0).setBinStep(1)
});
binPoolManager.initialize(key, ID_ONE);

(, uint24 actualProtocolFee,) = binPoolManager.getSlot0(key.toId());

// under default rule protocol fee must be equal for both directions
uint16 protocolFeeZeroForOne = actualProtocolFee.getZeroForOneFee();
uint16 protocolFeeOneForZero = actualProtocolFee.getOneForZeroFee();
assertEq(protocolFeeOneForZero, protocolFeeZeroForOne);
assertEq(protocolFeeOneForZero, DEFAULT_PROTOCOL_FEE_FOR_DYNAMIC_FEE_POOL);

controller.setDefaultProtocolFeeForDynamicFeePool(newDefaultProtocolFeeForDynamicFeePool);

key.parameters = bytes32(0).setBinStep(5);
binPoolManager.initialize(key, ID_ONE);

(, actualProtocolFee,) = binPoolManager.getSlot0(key.toId());
// under default rule protocol fee must be equal for both directions
protocolFeeZeroForOne = actualProtocolFee.getZeroForOneFee();
protocolFeeOneForZero = actualProtocolFee.getOneForZeroFee();
assertEq(protocolFeeOneForZero, protocolFeeZeroForOne);
assertEq(protocolFeeOneForZero, newDefaultProtocolFeeForDynamicFeePool);

// verify the original pool is not affected
{
key.parameters = bytes32(0).setBinStep(1);

(, actualProtocolFee,) = binPoolManager.getSlot0(key.toId());
// under default rule protocol fee must be equal for both directions
protocolFeeZeroForOne = actualProtocolFee.getZeroForOneFee();
protocolFeeOneForZero = actualProtocolFee.getOneForZeroFee();
assertEq(protocolFeeOneForZero, protocolFeeZeroForOne);
}
}

function testSetProtocolFeeForCLPool(uint24 newProtocolFee) public {
ProtocolFeeController controller = new ProtocolFeeController(address(clPoolManager));
clPoolManager.setProtocolFeeController(controller);
Expand Down