Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aggregated fee sweeper #1254

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cec7ad7
refactor: remove "all tokens" sweeper option, as it's unlikely to be …
EndymionJkb Jan 22, 2025
8626104
feat: add limit and deadline to the burner interface
EndymionJkb Jan 22, 2025
31933d4
feat: add limit and deadline to the sweeper interface and call to burn
EndymionJkb Jan 22, 2025
ba730cc
test: update tests to use the new interface; add tests for new features
EndymionJkb Jan 22, 2025
8169d5b
fix: need some fees to trigger the deadline
EndymionJkb Jan 22, 2025
89cf0cd
Merge branch 'main' into sweeper-refactor
EndymionJkb Jan 22, 2025
40204ef
refactor: remove the single burner and replace with a mapping + burne…
EndymionJkb Jan 22, 2025
886aaab
refactor: add fee burner to sweep call, and allowlist for protocol fe…
EndymionJkb Jan 22, 2025
a705fad
test: adjust existing tests to new interface
EndymionJkb Jan 22, 2025
7090bcd
refactor: add/remove permissioned; disallow invalid fee burner
EndymionJkb Jan 23, 2025
af44582
test: add tests for multiple fee burners
EndymionJkb Jan 23, 2025
db8a9ed
docs: improve documentation
EndymionJkb Jan 23, 2025
b180455
test: add test for unsupported burner
EndymionJkb Jan 23, 2025
ad89276
refactor: adjust burner interface for aggregate sweeping
EndymionJkb Jan 23, 2025
b2ec059
refactor: adjust to aggregate sweeping (remove pool, don't withdraw)
EndymionJkb Jan 23, 2025
a7c3faa
test: adjust and add tests for the aggregate interface
EndymionJkb Jan 23, 2025
7a8edde
docs: clarify param names
EndymionJkb Jan 23, 2025
923b427
Merge branch 'sweeper-refactor' into multi-burner
EndymionJkb Jan 23, 2025
244809d
Merge branch 'multi-burner' into aggregate-burner
EndymionJkb Jan 23, 2025
6bc4362
Merge branch 'main' into sweeper-refactor
EndymionJkb Jan 23, 2025
7e5f0ea
Merge branch 'sweeper-refactor' into multi-burner
EndymionJkb Jan 23, 2025
0328119
Merge branch 'multi-burner' into aggregate-burner
EndymionJkb Jan 23, 2025
0967650
refactor: rename some parameters for clarity
EndymionJkb Jan 27, 2025
26b5f58
Merge branch 'multi-burner' into aggregate-burner
EndymionJkb Jan 27, 2025
f389a05
Merge branch 'main' into sweeper-refactor
EndymionJkb Jan 28, 2025
50f60f2
refactor: collect aggregate fees in addition to withdrawing
EndymionJkb Jan 28, 2025
8893fc3
test: adjust tests for internal fee collection
EndymionJkb Jan 28, 2025
50bfda5
Merge branch 'sweeper-refactor' into multi-burner
EndymionJkb Jan 29, 2025
7a96de2
Merge branch 'multi-burner' into aggregate-burner
EndymionJkb Jan 29, 2025
9a978aa
Add support for multiple Protocol Fee Burners (#1253)
EndymionJkb Jan 29, 2025
1acae07
Merge branch 'sweeper-refactor' into aggregate-burner
EndymionJkb Jan 29, 2025
d1d6e40
Merge branch 'main' into aggregate-burner
EndymionJkb Jan 29, 2025
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
36 changes: 28 additions & 8 deletions pkg/interfaces/contracts/standalone-utils/IProtocolFeeBurner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,49 @@ pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IProtocolFeeBurner {
/**
* @notice A protocol fee token has been "burned" (i.e., swapped for the desired target token).
* @param feeToken The token in which the fee was originally collected
* @param exactFeeTokenAmountIn The number of feeTokens collected
* @param targetToken The preferred token for fee collection (e.g., USDC)
* @param targetTokenAmountOut The number of target tokens actually received
* @param recipient The address where the target tokens were sent
*/
event ProtocolFeeBurned(
address indexed pool,
IERC20 indexed feeToken,
uint256 feeTokenAmount,
uint256 exactFeeTokenAmountIn,
IERC20 indexed targetToken,
uint256 targetTokenAmount,
uint256 targetTokenAmountOut,
address recipient
);

/**
* @notice The actual amount out is below the minimum limit specified for the operation.
* @param tokenOut The outgoing token
* @param amountOut The total BPT amount out
* @param minAmountOut The amount of the limit that has been exceeded
*/
error AmountOutBelowMin(IERC20 tokenOut, uint256 amountOut, uint256 minAmountOut);

/// @notice The swap transaction was not validated before the specified deadline timestamp.
error SwapDeadline();

/**
* @notice Swap an exact amount of `feeToken` for the `targetToken`, and send proceeds to the `recipient`.
* @dev Assumes the sweeper has transferred the tokens to the burner prior to the call.
* @param pool The pool the fees came from (only used for documentation in the event)
* @param feeToken The feeToken collected from the pool
* @param feeTokenAmount The number of fee tokens collected
* @param exactFeeTokenAmountIn The number of fee tokens collected
* @param targetToken The desired target token (token out of the swap)
* @param minTargetTokenAmountOut The minimum amount out for the swap
* @param recipient The recipient of the swap proceeds
* @param deadline Deadline for the burn operation (i.e., swap), after which it will revert
*/
function burn(
address pool,
IERC20 feeToken,
uint256 feeTokenAmount,
uint256 exactFeeTokenAmountIn,
IERC20 targetToken,
address recipient
uint256 minTargetTokenAmountOut,
address recipient,
uint256 deadline
) external;
}
124 changes: 83 additions & 41 deletions pkg/interfaces/contracts/standalone-utils/IProtocolFeeSweeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,55 +20,89 @@ interface IProtocolFeeSweeper {
*/
event FeeRecipientSet(address indexed feeRecipient);

/**
* @notice Emitted when the protocol fee burner contract is set or updated.
* @param protocolFeeBurner The contract used to "burn" protocol fees (i.e., convert them to the target token)
*/
event ProtocolFeeBurnerSet(address indexed protocolFeeBurner);

/**
* @notice Emitted when a fee token is transferred directly, vs. calling the burner.
* @dev This can happen if no target token or burner contract was specified, or the fee token is the target token.
* @param pool The pool on which the fee was collected
* @param feeToken The token the fee was collected in (also the target token in this case; no swap necessary)
* @param feeTokenAmount The number of feeTokens
* @param recipient The recipient of the fee tokens
*/
event ProtocolFeeSwept(address indexed pool, IERC20 indexed feeToken, uint256 feeTokenAmount, address recipient);
event ProtocolFeeSwept(IERC20 indexed feeToken, uint256 feeTokenAmount, address recipient);

/**
* @notice Emitted when a burner is added to the protocol fee burner allowlist.
* @dev `sweepProtocolFeesForToken` can only be called with approved protocol fee burner addresses.
* @param protocolFeeBurner The address of the approved protocol fee burner that was added
*/
event ProtocolFeeBurnerAdded(address indexed protocolFeeBurner);

/**
* @notice Emitted when a burner is removed from the protocol fee burner allowlist.
* @dev `sweepProtocolFeesForToken` can only be called with approved protocol fee burner addresses.
* @param protocolFeeBurner The address of the approved protocol fee burner that was removed
*/
event ProtocolFeeBurnerRemoved(address indexed protocolFeeBurner);

/// @notice The fee recipient is invalid.
error InvalidFeeRecipient();

/// @notice The target token is invalid.
error InvalidTargetToken();

/// @notice The protocol fee burner to be added is invalid.
error InvalidProtocolFeeBurner();

/**
* @notice Withdraw, convert, and forward protocol fees for a given pool and token.
* @dev This will withdraw the fee token from the controller to this contract, and attempt to convert and forward
* the proceeds to the fee recipient. Note that this requires governance to grant this contract permission to call
* `withdrawProtocolFeesForToken` on the `ProtocolFeeController`. Since the general idea is to sweep when the token
* value crosses a certain threshold, we expect that this might be the most commonly used sweeping function.
*
* This is a permissioned call, since it involves a swap and a permissionless sweep could be triggered at times
* disadvantageous to the protocol (e.g., flash crashes).
*
* @param pool The pool that incurred the fees we're withdrawing
* @param feeToken The fee token in the pool
* @notice The specified fee burner has not been approved.
* @param protocolFeeBurner The address of the unsupported fee burner
*/
error UnsupportedProtocolFeeBurner(address protocolFeeBurner);

/**
* @notice Protocol fee burners can only be added to the allowlist once.
* @param protocolFeeBurner The address of an approved protocol fee burner
*/
error ProtocolFeeBurnerAlreadyAdded(address protocolFeeBurner);

/**
* @notice Protocol fee burners must be added to the allowlist before being removed.
* @param protocolFeeBurner The address of a protocol fee burner to be removed from the allowlist
*/
error ProtocolFeeBurnerNotAdded(address protocolFeeBurner);

/**
* @notice Cannot request burning more than the balance of the contract.
* @param feeToken The feeToken being burned
EndymionJkb marked this conversation as resolved.
Show resolved Hide resolved
* @param requiredBalance The exact amount to be burned
* @param actualBalance The actual balance of the token (will be less than the required balance)
*/
function sweepProtocolFeesForToken(address pool, IERC20 feeToken) external;
error InsufficientBalance(IERC20 feeToken, uint256 requiredBalance, uint256 actualBalance);

/**
* @notice Withdraw, convert, and forward protocol fees for a given pool.
* @dev This will withdraw all fee tokens from the controller to this contract, and attempt to convert and forward
* the proceeds to the fee recipient. Note that this requires governance to grant this contract permission to call
* `withdrawProtocolFees` on the `ProtocolFeeController`.
* @notice Convert, and forward withdrawn protocol fees for a given pool and token.
* @dev This presumes protocol fees have been withdrawn externally to this contract, and attempts to convert a
* given amount of fee tokens to the target, and forward the proceeds to the fee recipient.
*
* This is a permissioned call, since it involves a swap and a permissionless sweep could be triggered at times
* disadvantageous to the protocol (e.g., flash crashes).
* disadvantageous to the protocol (e.g., flash crashes). The general design is for an off-chain process to
* periodically collect fees from the Vault and withdraw them to this contract. This can be done for many pools,
* such that this contracts receives the aggregate total protocol fees from all of them. When a token balances
* exceeds an externally determined threshold value, the off-chain process calls the sweeper with an exact amount
* of tokens.
*
* @param pool The pool that incurred the fees we're withdrawing
* @param feeToken The fee token in the pool
* @param exactFeeTokenAmountIn The amount of fee tokens we want to burn, or zero for the full balance
* @param minTargetTokenAmountOut The minimum number of target tokens to be sent to the recipient
* @param deadline Deadline for the burn operation (swap), after which it will revert
* @param feeBurner The protocol fee burner to be used (or the zero address to fall back on direct transfer)
*/
function sweepProtocolFees(address pool) external;
function sweepProtocolFeesForToken(
IERC20 feeToken,
uint256 exactFeeTokenAmountIn,
uint256 minTargetTokenAmountOut,
uint256 deadline,
IProtocolFeeBurner feeBurner
) external;

/**
* @notice Return the address of the current `ProtocolFeeController` from the Vault.
Expand All @@ -92,11 +126,11 @@ interface IProtocolFeeSweeper {
function getFeeRecipient() external view returns (address);

/**
* @notice Getter for the current protocol fee burner.
* @dev Can be changed by `setProtocolFeeBurner`.
* @return protocolFeeBurner The currently active protocol fee burner
* @notice Check whether a given address corresponds to an approved protocol fee burner.
* @param protocolFeeBurner The address to be checked
* @return isApproved True if the given address is on the approved protocol fee burner allowlist
*/
function getProtocolFeeBurner() external view returns (IProtocolFeeBurner);
function isApprovedProtocolFeeBurner(address protocolFeeBurner) external view returns (bool);

/**
* @notice Update the fee recipient address.
Expand All @@ -105,23 +139,31 @@ interface IProtocolFeeSweeper {
*/
function setFeeRecipient(address feeRecipient) external;

/**
* @notice Update the address of the protocol fee burner, used to convert protocol fees to a target token.
* @dev This is a permissioned function. If it is not set, the contract will fall back to forwarding all fee tokens
* directly to the fee recipient. Note that if this function is called, `setTargetToken` must be called as well,
* or any sweep operations using the burner will revert with `InvalidTargetToken`.
*
* @param protocolFeeBurner The address of the current protocol fee burner
*/
function setProtocolFeeBurner(IProtocolFeeBurner protocolFeeBurner) external;

/**
* @notice Update the address of the target token.
* @dev This is the token for which the burner will attempt to swap all collected fee tokens.
* @param targetToken The address of the target token
*/
function setTargetToken(IERC20 targetToken) external;

/**
* @notice Add an approved fee burner to the allowlist.
* @dev This is a permissioned call. `sweepProtocolFeesForToken` can only be called with approved protocol
* fee burners.
*
* @param protocolFeeBurner The address of an approved protocol fee burner to be added
*/
function addProtocolFeeBurner(IProtocolFeeBurner protocolFeeBurner) external;

/**
* @notice Remove a fee burner from the allowlist.
* @dev This is a permissioned call. `sweepProtocolFeesForToken` can only be called with approved protocol
* fee burners.
*
* @param protocolFeeBurner The address of a protocol fee burner on the allowlist to be removed
*/
function removeProtocolFeeBurner(IProtocolFeeBurner protocolFeeBurner) external;

/**
* @notice Retrieve any tokens "stuck" in this contract (e.g., dust, or failed conversions).
* @dev It will recover the full balance of all the tokens. This can only be called by the `feeRecipient`.
Expand Down
Loading