194 lines
7.2 KiB
Solidity
194 lines
7.2 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import {
|
|
IAavePoolLike,
|
|
IAaveFlashLoanReceiver
|
|
} from "./AaveQuotePushFlashReceiver.sol";
|
|
|
|
interface IUniswapV2Router02Minimal {
|
|
function swapExactTokensForTokens(
|
|
uint256 amountIn,
|
|
uint256 amountOutMin,
|
|
address[] calldata path,
|
|
address to,
|
|
uint256 deadline
|
|
) external returns (uint256[] memory amounts);
|
|
|
|
function removeLiquidity(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint256 liquidity,
|
|
uint256 amountAMin,
|
|
uint256 amountBMin,
|
|
address to,
|
|
uint256 deadline
|
|
) external returns (uint256 amountA, uint256 amountB);
|
|
}
|
|
|
|
/**
|
|
* @title AaveUniV2CwStableRebalanceFlashReceiver
|
|
* @notice Same-block Aave V3 USDC flash loan to quote-side rebalance a skewed cW/stable UniV2 pair,
|
|
* remove owned LP, sell cW* back into the pair for USDC, and repay Aave.
|
|
*
|
|
* @dev LP tokens must be on this contract before `runRebalanceRemove` (transfer or `pullLpFrom`).
|
|
*/
|
|
contract AaveUniV2CwStableRebalanceFlashReceiver is IAaveFlashLoanReceiver, Ownable {
|
|
using SafeERC20 for IERC20;
|
|
|
|
address public immutable pool;
|
|
|
|
struct RebalanceRemoveParams {
|
|
address router;
|
|
address pair;
|
|
address cwToken;
|
|
address stableToken;
|
|
uint256 lpAmount;
|
|
uint256 rebalanceStableIn;
|
|
uint256 minCwFromRebalance;
|
|
uint256 minStableFromRemove;
|
|
uint256 minCwFromRemove;
|
|
uint256 cwToSellForRepay;
|
|
uint256 minStableFromRepaySwap;
|
|
address recipient;
|
|
}
|
|
|
|
error UntrustedPool();
|
|
error UntrustedInitiator();
|
|
error BadParams();
|
|
error InsufficientToRepay();
|
|
error NothingToSweep();
|
|
|
|
event RebalanceRemoveExecuted(
|
|
address indexed pair,
|
|
address indexed stableToken,
|
|
uint256 borrowedAmount,
|
|
uint256 premium,
|
|
uint256 lpRemoved,
|
|
uint256 stableSurplus
|
|
);
|
|
event TokenSwept(address indexed token, address indexed to, uint256 amount);
|
|
|
|
constructor(address pool_, address initialOwner) Ownable(initialOwner) {
|
|
if (pool_ == address(0) || initialOwner == address(0)) revert BadParams();
|
|
pool = pool_;
|
|
}
|
|
|
|
function pullLpFrom(address pair, address from, uint256 amount) external onlyOwner {
|
|
if (pair == address(0) || from == address(0) || amount == 0) revert BadParams();
|
|
IERC20(pair).safeTransferFrom(from, address(this), amount);
|
|
}
|
|
|
|
function runRebalanceRemove(address stableToken, uint256 amount, RebalanceRemoveParams calldata params)
|
|
external
|
|
onlyOwner
|
|
{
|
|
if (stableToken == address(0) || amount == 0) revert BadParams();
|
|
if (params.lpAmount == 0 || params.rebalanceStableIn == 0 || params.recipient == address(0)) {
|
|
revert BadParams();
|
|
}
|
|
if (IERC20(params.pair).balanceOf(address(this)) < params.lpAmount) revert BadParams();
|
|
|
|
address[] memory assets = new address[](1);
|
|
uint256[] memory amts = new uint256[](1);
|
|
uint256[] memory modes = new uint256[](1);
|
|
assets[0] = stableToken;
|
|
amts[0] = amount;
|
|
modes[0] = 0;
|
|
IAavePoolLike(pool).flashLoan(
|
|
address(this), assets, amts, modes, address(this), abi.encode(address(this), params), 0
|
|
);
|
|
}
|
|
|
|
function executeOperation(
|
|
address[] calldata assets,
|
|
uint256[] calldata amounts,
|
|
uint256[] calldata premiums,
|
|
address initiator,
|
|
bytes calldata params
|
|
) external override returns (bool) {
|
|
if (msg.sender != pool) revert UntrustedPool();
|
|
if (assets.length != 1 || amounts.length != 1 || premiums.length != 1) revert BadParams();
|
|
_executeRebalanceRemove(assets[0], amounts[0], premiums[0], initiator, params);
|
|
return true;
|
|
}
|
|
|
|
function sweepToken(address token, address to, uint256 amount) external onlyOwner {
|
|
if (token == address(0) || to == address(0) || amount == 0) revert BadParams();
|
|
IERC20(token).safeTransfer(to, amount);
|
|
emit TokenSwept(token, to, amount);
|
|
}
|
|
|
|
function _executeRebalanceRemove(
|
|
address stableToken,
|
|
uint256 amount,
|
|
uint256 premium,
|
|
address initiator,
|
|
bytes calldata params
|
|
) internal {
|
|
(address expectedInitiator, RebalanceRemoveParams memory p) =
|
|
abi.decode(params, (address, RebalanceRemoveParams));
|
|
if (initiator != expectedInitiator) revert UntrustedInitiator();
|
|
if (
|
|
p.router == address(0) || p.pair == address(0) || p.cwToken == address(0)
|
|
|| p.stableToken != stableToken
|
|
) revert BadParams();
|
|
if (p.rebalanceStableIn != amount) revert BadParams();
|
|
|
|
uint256 deadline = block.timestamp;
|
|
address[] memory stableToCw = _path(p.stableToken, p.cwToken);
|
|
address[] memory cwToStable = _path(p.cwToken, p.stableToken);
|
|
|
|
IERC20(p.stableToken).forceApprove(p.router, p.rebalanceStableIn);
|
|
IUniswapV2Router02Minimal(p.router).swapExactTokensForTokens(
|
|
p.rebalanceStableIn, p.minCwFromRebalance, stableToCw, address(this), deadline
|
|
);
|
|
|
|
IERC20(p.pair).forceApprove(p.router, p.lpAmount);
|
|
(address tokenA, address tokenB) = _sortTokens(p.cwToken, p.stableToken);
|
|
(uint256 minA, uint256 minB) = p.cwToken < p.stableToken
|
|
? (p.minCwFromRemove, p.minStableFromRemove)
|
|
: (p.minStableFromRemove, p.minCwFromRemove);
|
|
IUniswapV2Router02Minimal(p.router).removeLiquidity(
|
|
tokenA, tokenB, p.lpAmount, minA, minB, address(this), deadline
|
|
);
|
|
|
|
uint256 repayNeed = amount + premium;
|
|
uint256 stableBal = IERC20(p.stableToken).balanceOf(address(this));
|
|
if (stableBal < repayNeed && p.cwToSellForRepay > 0) {
|
|
IERC20(p.cwToken).forceApprove(p.router, p.cwToSellForRepay);
|
|
IUniswapV2Router02Minimal(p.router).swapExactTokensForTokens(
|
|
p.cwToSellForRepay, p.minStableFromRepaySwap, cwToStable, address(this), deadline
|
|
);
|
|
stableBal = IERC20(p.stableToken).balanceOf(address(this));
|
|
}
|
|
|
|
if (stableBal < repayNeed) revert InsufficientToRepay();
|
|
uint256 surplus = stableBal - repayNeed;
|
|
IERC20(p.stableToken).forceApprove(pool, repayNeed);
|
|
if (surplus > 0) {
|
|
IERC20(p.stableToken).safeTransfer(p.recipient, surplus);
|
|
}
|
|
|
|
uint256 cwLeft = IERC20(p.cwToken).balanceOf(address(this));
|
|
if (cwLeft > 0) {
|
|
IERC20(p.cwToken).safeTransfer(p.recipient, cwLeft);
|
|
}
|
|
|
|
emit RebalanceRemoveExecuted(p.pair, p.stableToken, amount, premium, p.lpAmount, surplus);
|
|
}
|
|
|
|
function _path(address tokenIn, address tokenOut) internal pure returns (address[] memory path) {
|
|
path = new address[](2);
|
|
path[0] = tokenIn;
|
|
path[1] = tokenOut;
|
|
}
|
|
|
|
function _sortTokens(address tokenA, address tokenB) internal pure returns (address, address) {
|
|
return tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
|
|
}
|
|
}
|