# Solidity Code Review ## Defi MOOC Lab2: uniswap ### Target User Information `lendingPool.getUserAccountData(target_user);` * HF = 0.993100607795995422(993100607795995422) * Total ETH debt = 8093.660057205842670564(8093660057205842670564) * Total ETH collateral = 10630.629178819327381450(10630629178819327381450) * Total available borrows of ETH = 0 * LTV = 70.82%(7082) * LTV: the maximum amount of assets that can be borrowed with a specific collateral * Liquidity Threshold = 75.61%(7561) * LT: the percentage at which a position is defined as undercollateralised > Note: > 1. Total debt / Total collateral ~= 76.14% (有0.53% debt是undercollateralised) => debt * 0.53% = 42.8963983032 in ETH > 2. Collateral * LT = 8037.818722105293433 in ETH > => debt - Collateral * LT = 55.8413351005 in ETH > => The debt is greater than the borrowing capacity. * Liquidation Bonus of WBTC: 6.5% [[Risk Parameters aave V2](https://docs.aave.com/risk/v/aave-v2/asset-risk/risk-parameters#dynamic-risk-parameter-by-gauntlet)] * currentStableDebt = 0 * currentVariableDebt = 88.52410904 in WBTC (8852410904) `AaveProtocolDataProvider.getUserReserveData(address asset, address user);` * aWBTC = 94.27338222 in WBTC (9427338222) => 也是當時清算的[最大值](https://etherscan.io/tx/0xac7df37a43fab1b130318bbb761861b8357650db2e2c6493b73d6da3d9581077) > Calculate the amount of collateral that can be `debtToCover = (userStableDebt + userVariableDebt) * LiquidationCloseFactorPercent` > Caluclate the maximum collateral bonus you can receive = >`collateral balance * liquidation bonus * the collateral asset's price in ETH ` ### Contract Head ```solidity //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.7; // print logging messages and contract variables // calling console.log() from your Solidity code. import "hardhat/console.sol"; ``` ### Interface #### [Aave](https://docs.aave.com/developers/the-core-protocol/lendingpool/ilendingpool) ```solidity interface ILendingPool { /** * Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1 * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of theliquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param user The address of the borrower getting liquidated * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants * to receive the underlying collateral asset directly **/ function liquidationCall( address collateralAsset, address debtAsset, address user, uint256 debtToCover, bool receiveAToken ) external; /** * 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 ); } ``` ```solidity interface ILendingPoolAddressesProvider { function getLendingPool() external view returns (address); function getLendingPoolCore() external view returns (address payable); } ``` #### UniswapV2 * https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IERC20.sol * https://docs.uniswap.org/protocol/V2/reference/smart-contracts/Pair-ERC-20 ```solidity interface IERC20 { // Returns the account balance of another account with address _owner. function balanceOf(address owner) external view returns (uint256); /** * Allows _spender to withdraw from your account multiple times, up to the _value amount. * If this function is called again it overwrites the current allowance with _value. * Lets msg.sender set their allowance for a spender. **/ function approve(address spender, uint256 value) external; // return type is deleted to be compatible with USDT /** * Transfers _value amount of tokens to address _to, and MUST fire the Transfer event. * The function SHOULD throw if the message caller’s account balance does not have enough tokens to spend. * Lets msg.sender send pool tokens to an address. **/ function transfer(address to, uint256 value) external returns (bool); } ``` * https://github.com/Uniswap/v2-periphery/blob/master/contracts/interfaces/IWETH.sol ```solidity interface IWETH is IERC20 { // Convert the wrapped token back to Ether. function withdraw(uint256) external; } ``` * https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IUniswapV2Callee.sol ```solidity // The flash loan liquidator we plan to implement this time should be a UniswapV2 Callee interface IUniswapV2Callee { function uniswapV2Call( address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } ``` * https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IUniswapV2Factory.sol * https://docs.uniswap.org/protocol/V2/reference/smart-contracts/factory ```solidity interface IUniswapV2Factory { // Returns the address of the pair for tokenA and tokenB, if it has been created, else address(0). function getPair(address tokenA, address tokenB) external view returns (address pair); } ``` * https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IUniswapV2Pair.sol * https://docs.uniswap.org/protocol/V2/reference/smart-contracts/pair * [Flash Swaps](https://docs.uniswap.org/protocol/V2/concepts/core-concepts/flash-swaps) * 只要`data.length>0`, 就會使用flash swap。 * [See Pricing](https://docs.uniswap.org/protocol/V2/concepts/advanced-topics/pricing) ```solidity interface IUniswapV2Pair { function swap( uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data ) external; /* Returns the reserves of token0 and token1 used to price trades and distribute liquidity. * Also returns the block.timestamp (mod 2**32) of the last block during which an interaction occured for the pair. */ function getReserves() external view returns ( uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); } ``` * Swap 完整的code * L14會檢查data長度決定是否為flash swap,如果是會call `uniswapV2Call` * 將自己的清算邏輯以及最後付還借來的token實作在`uniswapV2Call` * L18-19 檢查餘額是否足夠償還,若不能`amountIn` == 0,在L20只要有(==0)會被revert * L24 檢查是否維持常數k ```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); } ``` * https://docs.uniswap.org/protocol/V2/reference/smart-contracts/router-02 ````solidity interface IUniswapV2Router02 { function WETH() external returns (address); function swapExactTokensForETH( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); function swapTokensForExactTokens( uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); } ``` ### Main contract ```solidity contract LiquidationOperator is IUniswapV2Callee { uint8 public constant health_factor_decimals = 18; // TODO: define constants used in the contract including ERC-20 tokens, Uniswap Pairs, Aave lending pools, etc. */ // END TODO // some helper function, it is totally fine if you can finish the lab without using these function // https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset // safe mul is not necessary since https://docs.soliditylang.org/en/v0.8.9/080-breaking-changes.html 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; } // some helper function, it is totally fine if you can finish the lab without using these function // given an output amount of an asset and pair reserves, returns a required input amount of the other asset // safe mul is not necessary since https://docs.soliditylang.org/en/v0.8.9/080-breaking-changes.html 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; } 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 *** // END TODO // required by the testing script, entry for your liquidation call function operate() external { // TODO: implement your liquidation logic // 0. security checks and initializing variables // *** Your code here *** // 1. get the target user account data & make sure it is liquidatable // *** Your code here *** // 2. call flash swap to liquidate the target user // based on https://etherscan.io/tx/0xac7df37a43fab1b130318bbb761861b8357650db2e2c6493b73d6da3d9581077 // we know that the target user borrowed USDT with WBTC as collateral // we should borrow USDT, liquidate the target user and get the WBTC, then swap WBTC to repay uniswap // (please feel free to develop other workflows as long as they liquidate the target user successfully) // *** Your code here *** // 3. Convert the profit into ETH and send back to sender // *** Your code here *** // END TODO } // required by the swap function uniswapV2Call( address, uint256, uint256 amount1, bytes calldata ) external override { // TODO: implement your liquidation logic // 2.0. security checks and initializing variables // *** Your code here *** // 2.1 liquidate the target user // *** Your code here *** // 2.2 swap WBTC for other things or repay directly // *** Your code here *** // 2.3 repay // *** Your code here *** // END TODO } } ``` ## Examples ### [matevz: 43.829133318134877689 ETH](https://github.com/matevz/defi-mooc-lab2/blob/main/contracts/LiquidationOperator.sol) ##### [IUniswapV2Router02](/https://github.com/Uniswap/v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router02.sol) * Router defines how trades are being carried over different pair contracts (pools). [[ref.](https://stackoverflow.com/questions/72837482/whats-the-different-with-uniswap-router-and-uniswap-factory)] ```solidity interface IUniswapV2Router02 { function WETH() external returns (address); function swapExactTokensForETH( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); function swapTokensForExactTokens( uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); } ``` #### Main contract ##### Constant Variables Used * [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_num` * [當時交易的區塊時間](https://etherscan.io/tx/0xac7df37a43fab1b130318bbb761861b8357650db2e2c6493b73d6da3d9581077)`Block Height: 12489620`/`timestamp: 1621760059`/ * 上一塊`Block Height: 12489619`/`timestamp: 1621760056` * `block_num` 至少要`1621760056`才能執行`swapExactTokensForETH` ```solidity uint8 public constant health_factor_decimals = 18; address liquidation_user = 0x59CE4a2AC5bC3f5F225439B2993b86B42f6d3e9F; uint64 block_num = 1621761058; // 2021/05/23 09:10:58 ``` ```solidity /*Uniswap*/ IUniswapV2Router02 router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); IUniswapV2Factory factory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); IUniswapV2Pair pair_WETH_USDT = IUniswapV2Pair(factory.getPair(address(WETH), address(USDT))); ``` ```solidity /*ERC20 Tokens*/ IWETH WETH = IWETH(router.WETH()); IERC20 WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); IERC20 USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ``` ```solidity /*Aave*/ ILendingPool p = ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); ``` * some helper function [`getAmountOut`](https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol#L43) * given an input amount of an asset and pair reserves * returns the maximum output amount of the other asset. ```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; } ``` * some helper function [`getAmountIn`](https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol#L53) * given an output amount of an asset and pair reserves * returns a required input amount of the other asset ```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; } ``` ```solidity constructor() {} receive() external payable {} ``` * `function operate() external {...}` ```solidity // 0. security checks and initializing variables uint256 healthFactor; // 1. get the target user account data & make sure it is liquidatable (, , , , , healthFactor) = p.getUserAccountData(liquidation_user); require(healthFactor < 10**18, "Health factor is not < 1"); // Fine-tuned value. Should be greater than closing factor, but not too much... uint256 debtToCoverUSDT = 1756163782216; ``` ```solidity // 2. call flash swap to liquidate the target user // we know that the target user borrowed USDT with WBTC as collateral // we should borrow USDT, liquidate the target user and get the WBTC, then swap WBTC to repay uniswap pair_WETH_USDT.swap(0, debtToCoverUSDT, address(this), "_"); uint256 balance = WBTC.balanceOf(address(this)); address[] memory path = new address[](2); path[0] = address(WBTC); path[1] = address(WETH); router.swapExactTokensForETH(balance, 0, path, msg.sender, block_num); ``` ```solidity // 3. Convert the profit into ETH and send back to sender uint256 balanceWETH = WETH.balanceOf(address(this)); WETH.withdraw(balanceWETH); payable(msg.sender).transfer(balanceWETH); (, , , , , healthFactor) = p.getUserAccountData(liquidation_user); require(healthFactor > 10**18, "Health factor must be > 1 after liquidation" ); ``` * `function uniswapV2Call( address,uint256, uint256 amount1, bytes calldata) external override {...}` * `msg.sender` : UniswapV2Pair ```solidity USDT.approve(address(p), 2**256 - 1); (uint112 r0, uint112 r1, ) = IUniswapV2Pair(msg.sender).getReserves(); p.liquidationCall( address(WBTC), address(USDT), liquidation_user, amount1, false ); WBTC.approve(address(router), 2**256 - 1); address[] memory path = new address[](2); path[0] = address(WBTC); path[1] = address(WETH); uint256 amountIn = getAmountIn(amount1, r0, r1); router.swapTokensForExactTokens( amountIn, 2**256 - 1, path, msg.sender, block_num ); ``` ### [RadNi](https://github.com/RadNi/defi-mooc-lab2/blob/main/contracts/LiquidationOperator.sol): 42.833137388471944147 #### Library ```solidity library DataTypes { // refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties. struct ReserveData { //stores the reserve configuration ReserveConfigurationMap configuration; //the liquidity index. Expressed in ray uint128 liquidityIndex; //variable borrow index. Expressed in ray uint128 variableBorrowIndex; //the current supply rate. Expressed in ray uint128 currentLiquidityRate; //the current variable borrow rate. Expressed in ray uint128 currentVariableBorrowRate; //the current stable borrow rate. Expressed in ray uint128 currentStableBorrowRate; uint40 lastUpdateTimestamp; //tokens addresses address aTokenAddress; address stableDebtTokenAddress; address variableDebtTokenAddress; //address of the interest rate strategy address interestRateStrategyAddress; //the id of the reserve. Represents the position in the list of the active reserves uint8 id; } struct ReserveConfigurationMap { //bit 0-15: LTV //bit 16-31: Liq. threshold //bit 32-47: Liq. bonus //bit 48-55: Decimals //bit 56: Reserve is active //bit 57: reserve is frozen //bit 58: borrowing is enabled //bit 59: stable rate borrowing enabled //bit 60-63: reserved //bit 64-79: reserve factor uint256 data; } struct UserConfigurationMap { uint256 data; } enum InterestRateMode { NONE, STABLE, VARIABLE } } ``` #### Aave ```solidity interface ILendingPool { function getUserConfiguration(address user) external view returns (DataTypes.UserConfigurationMap memory); } ``` #### Events ```solidity event Log(bytes); event UserStatus( uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor ); event UserConf(DataTypes.UserConfigurationMap); //operate() emit UserStatus( totalCollateralETH, totalDebtETH, availableBorrowsETH, currentLiquidationThreshold, ltv, healthFactor ); emit UserConf(conf); emit Log("amad2"); emit Log(abi.encodePacked(balance)); ``` ```solidity uint256 debtToCover = 2009163782216;//2,009,163.782216 ``` ### [amanbthakkar](https://github.com/amanbthakkar/defi-mooc-lab2/blob/main/contracts/LiquidationOperator.sol): 43.830429506519857889 ETH ```solidity uint256 debtToCoverUSDT = 1750000000000;//1,750,000.000000 ``` ### [清算與當時數量一致](https://etherscan.io/tx/0xac7df37a43fab1b130318bbb761861b8357650db2e2c6493b73d6da3d9581077) :24.11903787514428899 ETH ```solidity uint256 debtToCoverUSDT = 2916378221684;//2,916,378.221684 ``` ### [DrShymaa2022](https://github.com/DrShymaa2022/defi-mooc-lab2/blame/main/running_2step_lqs/LiquidationOperator.sol): 43.802557787156995831 ETH * 第一次:119011111111 * 第二次:167098888889 * 清算出: 272344998 WBTC (941397681 + ) ```solidity uint256 debtToCoverUSDT = 1790000000000;//1,790,000.000000 ``` USDT in wei: 0.000488945000000000 WBTC in wei: 16.638242604905507000