# 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