Thank you for this feedback. With this feedback and the added context that this code will be separately audited, we have made a few alterations to the API3DAORevenueIncinerator.sol file. Because we cannot attach a .txt or .sol, we can paste the entire file below.
We removed the SolDAO FixedPointMathLib library of optimised division function and reverted to normal in-line operands. The original implementation was intended for gas optimisation, but understanding this complicates external audits (especially considering very few of the library functions were actually used in the code), we eschewed the library import for the normal * and / operands. swapFractionUSDCToAPI3AndLpWithETHPair()
has been retained to allow lower-slippage calls and user options.
One of our developers noted that we could also save even more gas and reduce contract size by (1) updating the compiler version to 0.8.12 and (2) changing the visibility of the constant state variables (API3_TOKEN_ADDR, LP_TOKEN_ADDR, UNI_ROUTER_ADDR, USDC_TOKEN_ADDR, WETH_TOKEN_ADDR, and YEAR_IN_SECONDS) to βinternalβ rather than public. Because these constant variables are verifiable once the contract itself is deployed and verified, and they are of separately-verifiable contract addresses and a constant uint256 of the number of seconds in a year, there is no substantial difference in user experience or security, but a noticeable difference in contract size/gas. In fact, the new contract should save ~ 76087 gas and 283 bytes of contract size.
Should this updated version be preferable to the deployed contract, we would be happy to deploy and verify the code below following the audit. Please excuse any formatting misalignment from the pasting into this forum.
//SPDX-License-Identifier: MIT
/*****
***** this code and all instances and deployments of this code are provided as-is;
***** no guarantee, representation nor warranty (express or implied) is given as to the safety or any attribute of the code or smart contracts
***** users should proceed with caution and at their own risk.
/\
/__\
/\ /\
/__\/__\
/\ /\ /\
/__\/__\/__\
/\ /\ /\ /\
/__\/__\/__\/__\
/\ /\ /\ /\ /\
/__\/__\/ \/__\/__\
/\ /\ / \ /\ /\
/__\/__\/______\/__\/__\
/\ /\ /\ /\ /\ /\ /\
/__\/__\/__\/__\/__\/__\/__\
/\ /\ /\ /\ /\ /\ /\ /\
/__\/__\/__\/__\/__\/__\/__\/__\
β β β β β β β β β β β β
API3DAO REVENUE INCINERATOR
β β β β β β β β β β β β
*/
pragma solidity >=0.8.12;
/// @title API3DAO Revenue Incinerator
/** @notice immutable burn of excess USDC revenue by buying and burning API3 tokens per API3DAO whitepaper:
*** swaps USDC sent to this contract for API3 tokens and ETH to LP in UniV2; each LP is then queued chronologically.
*** LP position is redeemable to this contract after one year by any caller, which is swapped to API3 tokens via UniV2 and burned via the API3 token contract.
*** ETH sent directly to this contract is auto-swapped to API3 tokens, which are then burned via the API3 token contract,
*** providing an optional method for automated ETH revenue burning */
/** @dev be advised there is inherent slippage risk for large amounts,
*** so it is recommended to call swapUSDCToAPI3AndLpWithETHPair() often, perhaps by bot;
*** swapFractionUSDCToAPI3AndLpWithETHPair() may also be used to swap and LP less than the
*** total balance of USDC at a time and further avoid such risks. */
/// β β β /// INTERFACES /// β β β ///
interface IUniswapV2Router02 {
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
)
external
payable
returns (
uint256 amountToken,
uint256 amountETH,
uint256 liquidity
);
function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountETH);
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
}
interface IAPI3 {
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function burn(uint256 amount) external;
function updateBurnerStatus(bool burnerStatus) external;
}
interface IERC20 {
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
/// β β β /// CONTRACT /// β β β ///
contract API3DAORevenueIncinerator {
struct Liquidity {
uint256 withdrawTime;
uint256 amount;
}
address internal constant API3_TOKEN_ADDR =
0x0b38210ea11411557c13457D4dA7dC6ea731B88a;
address internal constant LP_TOKEN_ADDR =
0x4Dd26482738bE6C06C31467a19dcdA9AD781e8C4;
address internal constant UNI_ROUTER_ADDR =
0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address internal constant USDC_TOKEN_ADDR =
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address internal constant WETH_TOKEN_ADDR =
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
uint256 internal constant YEAR_IN_SECONDS = 31557600;
IUniswapV2Router02 public immutable router;
IAPI3 public immutable iAPI3Token;
uint256 public lpAddIndex;
uint256 public lpRedeemIndex;
mapping(uint256 => Liquidity) public liquidityAdds;
/// β β β /// ERRORS /// β β β ///
error NoAPI3Tokens();
error NoRedeemableLPTokens();
error NoUSDCTokens();
error ZeroDivisor();
/// βββ /// EVENTS /// βββ ///
event API3Burned(uint256 amountBurned);
event LiquidityProvided(uint256 liquidityAdded, uint256 indexed lpIndex, uint256 indexed timestamp);
event LiquidityRemoved(uint256 liquidityRemoved, uint256 indexed lpIndex, uint256 indexed timestamp);
/// β β β /// FUNCTIONS /// β β β ///
/// @notice set interfaces, API3 token burner status for address(this) and approve Uniswap router for address(this) for API3, USDC, and API3/ETH LP tokens
constructor() payable {
address uniRouter = UNI_ROUTER_ADDR;
router = IUniswapV2Router02(uniRouter);
iAPI3Token = IAPI3(API3_TOKEN_ADDR);
IAPI3(API3_TOKEN_ADDR).updateBurnerStatus(true);
IAPI3(API3_TOKEN_ADDR).approve(uniRouter, type(uint256).max);
IERC20(USDC_TOKEN_ADDR).approve(uniRouter, type(uint256).max);
IERC20(LP_TOKEN_ADDR).approve(uniRouter, type(uint256).max);
}
/// @notice receives ETH sent to address(this), and if msg.sender != router, swaps for API3 tokens, and calls _burnAPI3()
/// also useful for burning any leftover/dust API3 tokens held by or sent to this contract. Small amounts advisable to avoid MEV trouble.
receive() external payable {
if (msg.sender != UNI_ROUTER_ADDR) {
router.swapExactETHForTokens{value: msg.value}(
1,
_getPathForETHtoAPI3(),
address(this),
block.timestamp
);
_burnAPI3();
} else {}
}
/// @notice swaps all USDC in this contract for ETH, then swaps 1/2 of the ETH for API3 tokens, and LPs API3/ETH
/*** @dev no authorisation restriction, callable by anyone. More favourable to call frequently in order to avoid
*** substantial slippage / MEV risks */
function swapUSDCToAPI3AndLpWithETHPair() external {
uint256 _usdcBal = IERC20(USDC_TOKEN_ADDR).balanceOf(address(this));
if (_usdcBal == 0) revert NoUSDCTokens();
// swap all USDC for ETH
router.swapExactTokensForETH(
_usdcBal,
1,
_getPathForUSDCtoETH(),
payable(address(this)),
block.timestamp
);
// swap half of ETH for API3
router.swapExactETHForTokens{
value: address(this).balance / 2
}(0, _getPathForETHtoAPI3(), address(this), block.timestamp);
// LP
_lpApi3Eth();
}
/// @notice swaps this address's USDC divided by _divisor for ETH, then swaps 1/2 of the ETH for API3 tokens, and LPs API3/ETH
/** @dev no authorisation restriction, callable by anyone. Implemented to further avoid substantial slippage risks
** by permitting < 100% of this contract's USDC be swapped at a time */
/// @param _divisor divisor for division operation of this contract's USDC balance to then swap and LP, which must be > 0.
/// for example, if _divisor = 2, 50% of USDC will be swapped, if _divisor = 4, 25%, etc.
function swapFractionUSDCToAPI3AndLpWithETHPair(uint256 _divisor) external {
if (_divisor == 0) revert ZeroDivisor();
uint256 _swapAmount = IERC20(USDC_TOKEN_ADDR).balanceOf(address(this)) / _divisor;
if (_swapAmount == 0) revert NoUSDCTokens();
// swap USDC/_divisor for ETH
router.swapExactTokensForETH(
_swapAmount,
1,
_getPathForUSDCtoETH(),
payable(address(this)),
block.timestamp
);
// swap half of ETH for API3
router.swapExactETHForTokens{
value: address(this).balance / 2
}(0, _getPathForETHtoAPI3(), address(this), block.timestamp);
// LP
_lpApi3Eth();
}
/** @dev checks earliest Liquidity struct to see if any LP tokens are redeemable,
** then redeems that amount of liquidity to this address (which is entirely converted and burned in API3 tokens via _burnAPI3()),
** then deletes that mapped struct in liquidityAdds[] and increments the lpRedeemIndex */
/// @notice redeems the earliest available liquidity; redeemed API3 tokens are burned and redeemed ETH is converted to API3 tokens and burned
function redeemLP() external {
if (liquidityAdds[lpRedeemIndex].withdrawTime > block.timestamp)
revert NoRedeemableLPTokens();
uint256 _redeemableLpTokens = liquidityAdds[lpRedeemIndex].amount;
// if lpRedeemIndex returns zero, increment the index; otherwise call _redeemLP and then increment
if (_redeemableLpTokens == 0) {
delete liquidityAdds[lpRedeemIndex];
unchecked {
++lpRedeemIndex;
}
} else {
_redeemLP(_redeemableLpTokens, lpRedeemIndex);
unchecked {
++lpRedeemIndex;
}
}
}
/** @dev checks applicable Liquidity struct to see if any LP tokens are redeemable,
** then redeems that amount of liquidity to this address (which is entirely converted and burned in API3 tokens via _burnAPI3()),
** then deletes that mapped struct in liquidityAdds[]. Implemented in case of lpAddIndex--lpRedeemIndex mismatch */
/// @notice redeems specifically indexed liquidity; redeemed API3 tokens are burned and redeemed ETH is converted to API3 tokens and burned
/// @param _lpRedeemIndex: index of liquidity in liquidityAdds[] mapping to be redeemed
function redeemSpecificLP(uint256 _lpRedeemIndex) external {
if (liquidityAdds[_lpRedeemIndex].withdrawTime > block.timestamp)
revert NoRedeemableLPTokens();
uint256 _redeemableLpTokens = liquidityAdds[_lpRedeemIndex].amount;
// if _lpRedeemIndex returns zero, delete mapping; otherwise call _redeemLP. Do not increment the global lpRedeemIndex
if (_redeemableLpTokens == 0) {
delete liquidityAdds[_lpRedeemIndex];
} else {
_redeemLP(_redeemableLpTokens, _lpRedeemIndex);
}
}
/// @notice burns all API3 tokens held by this contract
function _burnAPI3() internal {
uint256 _api3Bal = iAPI3Token.balanceOf(address(this));
if (_api3Bal == 0) revert NoAPI3Tokens();
iAPI3Token.burn(_api3Bal);
emit API3Burned(_api3Bal);
}
/// @notice LPs ETH and API3 tokens to UNIv2's API3/ETH pair
/// @dev LP has 10% buffer. Liquidity locked for one year and queued chronologically.
function _lpApi3Eth() internal {
uint256 _api3Bal = iAPI3Token.balanceOf(address(this));
if (_api3Bal == 0) revert NoAPI3Tokens();
uint256 _ethBal = address(this).balance;
(, , uint256 liquidity) = router.addLiquidityETH{value: _ethBal}(
API3_TOKEN_ADDR,
_api3Bal,
(_api3Bal * 9) / 10, // 90% of the api3Bal
(_ethBal * 9) / 10, // 90% of the ethBal
payable(address(this)),
block.timestamp
);
emit LiquidityProvided(liquidity, lpAddIndex, block.timestamp);
unchecked {
liquidityAdds[lpAddIndex] = Liquidity(
block.timestamp + YEAR_IN_SECONDS, // will not overflow on human timelines
liquidity
);
++lpAddIndex;
}
}
/// @notice redeems the LP for the corresponding lpRedeemIndex, swaps redeemed ETH for API3 tokens, and burns all API3 tokens
/// @param _redeemableLpTokens amount of LP tokens available to redeem
/// @param _lpRedeemIndex LP index being redeemed
function _redeemLP(uint256 _redeemableLpTokens, uint256 _lpRedeemIndex)
internal
{
router.removeLiquidityETH(
API3_TOKEN_ADDR,
_redeemableLpTokens,
1,
1,
payable(address(this)),
block.timestamp
);
delete liquidityAdds[_lpRedeemIndex];
emit LiquidityRemoved(_redeemableLpTokens, _lpRedeemIndex, block.timestamp);
// removed liquidity is 50/50 ETH and API3; swap all newly received ETH for API3, then burn all the API3 tokens
router.swapExactETHForTokens{value: address(this).balance}(
1,
_getPathForETHtoAPI3(),
address(this),
block.timestamp
);
_burnAPI3();
}
/// @return path: the router path for ETH/API3 swap
function _getPathForETHtoAPI3() internal pure returns (address[] memory) {
address[] memory path = new address[](2);
path[0] = WETH_TOKEN_ADDR;
path[1] = API3_TOKEN_ADDR;
return path;
}
/// @return path: the router path for USDC/ETH swap
function _getPathForUSDCtoETH() internal pure returns (address[] memory) {
address[] memory path = new address[](2);
path[0] = USDC_TOKEN_ADDR;
path[1] = WETH_TOKEN_ADDR;
return path;
}
}