Files
smom-dbis-138/contracts/flash/AaveUniV2CwStableRebalanceFlashReceiver.sol

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);
}
}