# 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