# Defi MOOC Lab2 ## contract LiquidationOperator ```solidity contract LiquidationOperator is IUniswapV2Callee{...} ``` * Flash Swap會調用[`UniswapV2Pair.sol`](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol) 中的函式[`swap`](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L172)。 * 其中`swap`會需要一個合約接收閃電貸,並且將閃電貸如何使用寫在函式`uniswapV2Call`中。 ```solidity if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); ``` * 本次作業會建立`LiquidationOperator`合約,繼承[`IUniswapV2Callee`](https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IUniswapV2Callee.sol),並實現介面中的函式 `uniswapV2Call` ```solidity //Lab: L85-L92 interface IUniswapV2Callee { function uniswapV2Call( address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } ``` ## Define constants used in the contract ```solidity= uint8 public constant health_factor_decimals = 18; address liquidation_user = 0x59CE4a2AC5bC3f5F225439B2993b86B42f6d3e9F; uint64 time_stamp = 1621761058; ``` 1. 定義health factor的位數從第幾位開始 2. 被清算者的地址([original tx](https://etherscan.io/tx/0xac7df37a43fab1b130318bbb761861b8357650db2e2c6493b73d6da3d9581077): Block Height=12489620, timestamp=1621760059) 3. 規定要介在上一塊到下一塊之間完成交易 ([Unixtime Calculator](https://www.vultr.com/resources/unix-time-calculator/?convert=epoch&epoch_timestamp=1621761058&epoch_tz=UTC&epoch_format=Y-m-d+H%3Ai%3As)) * 上一塊Block Height=12489619, timestamp=1621760056 ```solidity= IUniswapV2Router02 router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); IUniswapV2Factory factory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); IUniswapV2Pair pair_WETH_USDT = IUniswapV2Pair(factory.getPair(address(WETH), address(USDT))); ``` 1. [`UniswapV2Router02`](https://docs.uniswap.org/protocol/V2/reference/smart-contracts/router-02) :會使用到`swapTokensForExactTokens`、 `swapExactTokensForETH`,計算如何換tokens。 2. [`UniswapV2Factory`](https://docs.uniswap.org/protocol/V2/reference/smart-contracts/factory#getpair):使用到`getPair`,找到對應的token對。 3. [`UniswapV2Pair`](https://docs.uniswap.org/protocol/V2/reference/smart-contracts/pair#swap-1): 找到token pair,執行flash swap(`swap`) - [ ] 為什麼要用WETH&USDT? 如果是為了借出USDT,是不是也可以用USDT對其他token的pair呢? * [Interact with other contracts using contract Interfaces](https://soliditytips.com/articles/solidity-interface-interact-with-other-contracts/) ```solidity /*Aave*/ ILendingPool lending_pool = ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); ``` * [`LendingPool`](https://docs.aave.com/developers/v/2.0/the-core-protocol/lendingpool#liquidationcall): 使用[`liquidationcall()`](https://docs.aave.com/developers/v/2.0/the-core-protocol/lendingpool#liquidationcall)執行清算。此外也能夠用[`getUserAccountData()`](https://docs.aave.com/developers/v/2.0/the-core-protocol/lendingpool#getuseraccountdata) 取得目標用戶的資訊。 ## Some helper function ```solidity= function getAmountOut( uint256 amountIn, uint256 reserveIn, uint256 reserveOut ) internal pure returns (uint256 amountOut) { require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT"); require( reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY" ); uint256 amountInWithFee = amountIn * 997; uint256 numerator = amountInWithFee * reserveOut; uint256 denominator = reserveIn * 1000 + amountInWithFee; amountOut = numerator / denominator; } ``` * `getAmountOut()`:根據使用者輸入**要放入的tokenA數量**來計算可**換出多少tokenB的數量** * L6:檢查欲換入的tokenA數量是否大於0 * L7:檢查流動池中的token對(tokenA和tokenB)是否數量都大於0 * 可以用[`getReserves()`](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L38)查詢token對的數量 * L11-L14:計算最多換出多少tokenB(已扣除手續費約0.3%): * [扣除手續費後的amountOut如何計算](https://ethereum.stackexchange.com/questions/89980/why-does-uniswap-computes-amountout-of-a-token-this-way) ```solidity= function getAmountIn( uint256 amountOut, uint256 reserveIn, uint256 reserveOut ) internal pure returns (uint256 amountIn) { require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT"); require( reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY" ); uint256 numerator = reserveIn * amountOut * 1000; uint256 denominator = (reserveOut - amountOut) * 997; amountIn = (numerator / denominator) + 1; } ``` * `getAmountIn()`: 根據使用者輸入**要換出的tokenB數量**來計算要**放入多少的tokenA的數量** ```solidity= constructor() { // TODO: (optional) initialize your contract // *** Your code here *** // END TODO } // TODO: add a `receive` function so that you can withdraw your WETH // *** Your code here *** receive() external payable {} // END TODO ``` * L9:When the sender sends ETH to your contract address and ***doesn't specify any function*** (i.e. the data field of the tx is empty), the `receive()` gets executed.[[ref.](https://stackoverflow.com/questions/69178874/solidity-v0-6-0-fallback-functions-what-are-they-needed-for)] ## Required by the testing script, entry for your liquidation call * Implement your liquidation logic on `function operate(){...}` ### 0. security checks and initializing variables ```solidity= ``` ### 1. get the target user account data & make sure it is liquidatable ```solidity= (uint256 collateral_ETH, uint256 debt_ETH, , uint256 liquidation_threshold, uint256 loan_to_value, uint256 health_factor) = lending_pool.getUserAccountData(liquidation_user); require(health_factor < 1e18, "health factor should be < 1 before liquidation"); ``` * [`getUserAccountData()`](https://docs.aave.com/developers/v/2.0/the-core-protocol/lendingpool#getuseraccountdata) :取得Aave用戶的資料 ```solidity= /** * Returns the user account data across all the reserves * @param user The address of the user * @return totalCollateralETH the total collateral in ETH of the user * @return totalDebtETH the total debt in ETH of the user * @return availableBorrowsETH the borrowing power left of the user * @return currentLiquidationThreshold the liquidation threshold of the user * @return ltv the loan to value of the user * @return healthFactor the current health factor of the user **/ function getUserAccountData(address user) external view returns ( uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor ); ``` ### 2. call flash swap to liquidate the target user ```solidity= pair_WETH_USDT.swap(0, debtToCoverUSDT, address(this), "_"); ``` * [`swap()`](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159): 在UniswapV2Pair.sol中負責swap tokens,如果要使用*flash swap*,param中的`data`要放東西,讓`data.length` > 0。 * 根據[Uniswap Doc](https://docs.uniswap.org/protocol/V2/concepts/core-concepts/flash-swaps):允許 *flash swap* 在使用者會償還借款加上手續費的情況下,可以將ERC20所有的reserve提取。 ```solidity= function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY'); uint balance0; uint balance1; { // scope for _token{0,1}, avoids stack too deep errors address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); } _update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } ``` * ### 3. Convert the profit into ETH and send back to sender ## Required by the swap