Fixed liquidation tests

This commit is contained in:
The3D 2020-07-08 17:26:50 +02:00
parent 395e4aa3a7
commit f1743a5eac
26 changed files with 221 additions and 266 deletions

View File

@ -731,6 +731,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable {
external
view
returns (
uint256 decimals,
uint256 ltv,
uint256 liquidationThreshold,
uint256 liquidationBonus,
@ -746,6 +747,7 @@ contract LendingPool is ReentrancyGuard, VersionedInitializable {
CoreLibrary.ReserveData storage reserve = reserves[_reserve];
return (
reserve.decimals,
reserve.baseLTVasCollateral,
reserve.liquidationThreshold,
reserve.liquidationBonus,

View File

@ -10,10 +10,11 @@ import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol";
import "../configuration/LendingPoolAddressesProvider.sol";
import "../tokenization/AToken.sol";
import "../tokenization/interfaces/IStableDebtToken.sol";
import "../tokenization/interfaces/IVariableDebtToken.sol";
import "../libraries/CoreLibrary.sol";
import "../libraries/WadRayMath.sol";
import "../interfaces/IPriceOracleGetter.sol";
import {IFeeProvider} from "../interfaces/IFeeProvider.sol";
import "../libraries/EthAddressLib.sol";
import "../libraries/GenericLogic.sol";
import "../libraries/UserLogic.sol";
@ -120,13 +121,12 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl
) external payable returns (uint256, string memory) {
CoreLibrary.ReserveData storage principalReserve = reserves[_reserve];
CoreLibrary.ReserveData storage collateralReserve = reserves[_collateral];
CoreLibrary.UserReserveData storage userCollateral = usersReserveData[msg
.sender][_collateral];
CoreLibrary.UserReserveData storage userCollateral = usersReserveData[_user][_collateral];
LiquidationCallLocalVars memory vars;
(, , , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
msg.sender,
_user,
reserves,
usersReserveData,
reservesList,
@ -140,7 +140,7 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl
);
}
vars.userCollateralBalance = IERC20(_collateral).balanceOf(_user);
vars.userCollateralBalance = IERC20(collateralReserve.aTokenAddress).balanceOf(_user);
//if _user hasn't deposited this specific collateral, nothing can be liquidated
if (vars.userCollateralBalance == 0) {
@ -213,6 +213,8 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl
}
}
console.log("Balance before liquidation is %s", IERC20(principalReserve.stableDebtTokenAddress).balanceOf(_user));
//TODO Burn debt tokens
if(vars.userVariableDebt >= vars.actualAmountToLiquidate){
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn(_user, vars.actualAmountToLiquidate);
@ -222,6 +224,8 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl
IStableDebtToken(principalReserve.stableDebtTokenAddress).burn(_user, vars.actualAmountToLiquidate.sub(vars.userVariableDebt));
}
console.log("Balance after liquidation is %s", IERC20(principalReserve.stableDebtTokenAddress).balanceOf(_user));
vars.collateralAtoken = AToken(collateralReserve.aTokenAddress);
//if liquidator reclaims the aToken, he receives the equivalent atoken amount
@ -235,9 +239,13 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl
//otherwise receives the underlying asset
//burn the equivalent amount of atoken
vars.collateralAtoken.burnOnLiquidation(_user, vars.maxCollateralToLiquidate);
console.log("Burned %s collateral tokens, collateral %s",vars.maxCollateralToLiquidate, _collateral);
IERC20(_collateral).universalTransfer(msg.sender, vars.maxCollateralToLiquidate);
}
console.log("Transferring principal, amount %s",vars.actualAmountToLiquidate);
//transfers the principal currency to the pool
IERC20(_reserve).universalTransferFromSenderToThis(vars.actualAmountToLiquidate, true);

View File

@ -11,6 +11,7 @@ import {WadRayMath} from "./WadRayMath.sol";
import "../interfaces/IPriceOracleGetter.sol";
import {IFeeProvider} from "../interfaces/IFeeProvider.sol";
import '@nomiclabs/buidler/console.sol';
/**
* @title GenericLogic library
@ -150,7 +151,7 @@ library GenericLogic {
CalculateUserAccountDataVars memory vars;
for (vars.i = 0; vars.i < _reserves.length; vars.i++) {
vars.currentReserveAddress = _reserves[vars.i];
CoreLibrary.ReserveData storage currentReserve = _reservesData[vars
@ -236,7 +237,10 @@ library GenericLogic {
uint256 borrowBalanceETH,
uint256 totalFeesETH,
uint256 liquidationThreshold
) internal pure returns (uint256) {
) internal view returns (uint256) {
console.log("Borrow balance ETH is %s", borrowBalanceETH);
if (borrowBalanceETH == 0) return uint256(-1);
return

View File

@ -4,6 +4,7 @@ pragma solidity ^0.6.8;
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@nomiclabs/buidler/console.sol';
/**
* @title UniversalERC20 library
@ -37,8 +38,13 @@ library UniversalERC20 {
}
if (isETH(token)) {
console.log("transfer of ETH, value %s", amount);
console.log("Balance is %s", address(this).balance);
(bool result, ) = payable(to).call{value: amount, gas: DEFAULT_TRANSFER_GAS}('');
console.log("Transfer done, result %s", result);
require(result, 'ETH_TRANSFER_FAILED');
} else {
token.safeTransfer(to, amount);
}
@ -166,6 +172,6 @@ library UniversalERC20 {
* @return boolean
**/
function isETH(IERC20 token) internal pure returns (bool) {
return (address(token) == address(ZERO_ADDRESS) || address(token) == address(ETH_ADDRESS));
return (address(token) == address(ETH_ADDRESS));
}
}

View File

@ -36,7 +36,7 @@ contract AaveProtocolTestHelpers {
address[] memory reserves = pool.getReserves();
TokenData[] memory aTokens = new TokenData[](reserves.length);
for (uint256 i = 0; i < reserves.length; i++) {
(,,,,address aTokenAddress,,,,,) = pool.getReserveConfigurationData(reserves[i]);
(,,,,,address aTokenAddress,,,,,) = pool.getReserveConfigurationData(reserves[i]);
aTokens[i] = TokenData({
symbol: AToken(aTokenAddress).symbol(),
tokenAddress: aTokenAddress

View File

@ -92,7 +92,7 @@ contract WalletBalanceProvider {
uint256[] memory balances = new uint256[](reserves.length);
for (uint256 j = 0; j < reserves.length; j++) {
(, , , , , , , , bool isActive,) = pool.getReserveConfigurationData(reserves[j]);
(, , , , , , , , , bool isActive,) = pool.getReserveConfigurationData(reserves[j]);
if (!isActive) {
balances[j] = 0;

6
package-lock.json generated
View File

@ -2162,6 +2162,12 @@
"integrity": "sha512-SubOtaSI2AILWTWe2j0c6i2yFT/f9J6UBjeVGDuwDiPLkF/U5+/eTWUE3sbCZ1KgcPF6UJsDVYbIxaYA097MQA==",
"dev": true
},
"chai-bn": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/chai-bn/-/chai-bn-0.2.1.tgz",
"integrity": "sha512-01jt2gSXAw7UYFPT5K8d7HYjdXj2vyeIuE+0T/34FWzlNcVbs1JkPxRu7rYMfQnJhrHT8Nr6qjSf5ZwwLU2EYg==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",

View File

@ -36,6 +36,7 @@
"buidler-typechain": "0.1.1",
"chai": "4.2.0",
"chai-bignumber": "3.0.0",
"chai-bn": "^0.2.1",
"ethereum-waffle": "2.5.1",
"ethers": "4.0.47",
"husky": "^4.2.5",

View File

@ -270,8 +270,6 @@ const initReserves = async (
]
)
console.log(`Debt tokens for ${assetSymbol}: stable ${stableDebtToken.address} variable ${variableDebtToken.address}`)
if (process.env.POOL === AavePools.secondary) {
if (assetSymbol.search("UNI") === -1) {
assetSymbol = `Uni${assetSymbol}`;
@ -525,7 +523,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
fallbackOracle.address,
]);
await waitForTx(
await addressesProvider.setPriceOracle(chainlinkProxyPriceProvider.address)
await addressesProvider.setPriceOracle(fallbackOracle.address)
);
const lendingRateOracle = await deployLendingRateOracle();

View File

@ -175,7 +175,7 @@ makeSuite("AToken: Transfer", (testEnv: TestEnv) => {
const {users, pool, aDai, dai} = testEnv;
await pool
.connect(users[1].signer)
.repay(MOCK_ETH_ADDRESS, MAX_UINT_AMOUNT, users[1].address, users[1].address, {
.repay(MOCK_ETH_ADDRESS, MAX_UINT_AMOUNT, RateMode.Stable, users[1].address, {
value: ethers.utils.parseEther("1"),
});

View File

@ -326,8 +326,6 @@ export const calcExpectedReserveDataAfterBorrow = (
): ReserveData => {
const expectedReserveData = <ReserveData>{};
console.log('Computing borrow, amountBorrowed: ', amountBorrowed, ' Rate mode: ', borrowRateMode);
expectedReserveData.address = reserveDataBeforeAction.address;
const amountBorrowedBN = new BigNumber(amountBorrowed);
@ -419,8 +417,7 @@ export const calcExpectedReserveDataAfterRepay = (
txTimestamp: BigNumber,
currentTimestamp: BigNumber
): ReserveData => {
console.log('Computing repay, amount repaid: ', amountRepaid, ' Rate mode: ', borrowRateMode);
const expectedReserveData: ReserveData = <ReserveData>{};
expectedReserveData.address = reserveDataBeforeAction.address;
@ -766,7 +763,6 @@ export const calcExpectedReserveDataAfterSwapRateMode = (
rateMode: string,
txTimestamp: BigNumber
): ReserveData => {
console.log('Computing swap, Rate mode: ', rateMode);
const expectedReserveData: ReserveData = <ReserveData>{};
@ -1246,7 +1242,7 @@ const calcExpectedVariableDebtUserIndex = (
return calcExpectedReserveNormalizedDebt(reserveDataBeforeAction, currentTimestamp);
};
const calcExpectedVariableDebtTokenBalance = (
export const calcExpectedVariableDebtTokenBalance = (
reserveDataBeforeAction: ReserveData,
userDataBeforeAction: UserReserveData,
currentTimestamp: BigNumber
@ -1266,7 +1262,7 @@ const calcExpectedVariableDebtTokenBalance = (
.rayToWad();
};
const calcExpectedStableDebtTokenBalance = (
export const calcExpectedStableDebtTokenBalance = (
userDataBeforeAction: UserReserveData,
currentTimestamp: BigNumber
) => {

View File

@ -5,8 +5,12 @@ import {APPROVAL_AMOUNT_LENDING_POOL, MOCK_ETH_ADDRESS, oneEther} from '../helpe
import {convertToCurrencyDecimals} from '../helpers/contracts-helpers';
import {makeSuite} from './helpers/make-suite';
import {ProtocolErrors, RateMode} from '../helpers/types';
import { calcExpectedVariableDebtTokenBalance } from './helpers/utils/calculations';
import { getUserData, getReserveData } from './helpers/utils/helpers';
const {expect} = require('chai');
const chai = require('chai');
chai.use(require('chai-bignumber')());
const {expect} = chai;
makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => {
const {
@ -42,7 +46,6 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
});
//user 2 borrows
const userGlobalData = await pool.getUserAccountData(borrower.address);
const daiPrice = await oracle.getAssetPrice(dai.address);
@ -59,7 +62,6 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
.borrow(dai.address, amountDAIToBorrow, RateMode.Variable, '0');
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
console.log('userGlobalDataAfter.healthFactor', userGlobalDataAfter.healthFactor.toString());
expect(userGlobalDataAfter.currentLiquidationThreshold).to.be.bignumber.equal(
'80',
@ -78,18 +80,14 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
const daiPrice = await oracle.getAssetPrice(dai.address);
//halving the price of ETH - means doubling the DAIETH exchange rate
console.log('DAI price before', daiPrice.toString());
await oracle.setAssetPrice(
dai.address,
new BigNumber(daiPrice.toString()).multipliedBy(1.15).toFixed(0)
);
console.log('DAI price after', (await oracle.getAssetPrice(dai.address)).toString());
const userGlobalData = await pool.getUserAccountData(borrower.address);
expect(userGlobalData.healthFactor).to.be.bignumber.lt(oneEther.toFixed(0), INVALID_HF);
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
});
it('LIQUIDATION - Tries to liquidate a different currency than the loan principal', async () => {
@ -107,20 +105,17 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
).revertedWith(USER_DID_NOT_BORROW_SPECIFIED);
});
it(
'LIQUIDATION - Tries to liquidate a different ' + 'collateral than the borrower collateral',
async () => {
const {pool, dai, users} = testEnv;
const borrower = users[1];
it('LIQUIDATION - Tries to liquidate a different collateral than the borrower collateral', async () => {
const {pool, dai, users} = testEnv;
const borrower = users[1];
await expect(
pool.liquidationCall(dai.address, dai.address, borrower.address, oneEther.toString(), true)
).revertedWith(INVALID_COLLATERAL_TO_LIQUIDATE);
}
);
await expect(
pool.liquidationCall(dai.address, dai.address, borrower.address, oneEther.toString(), true)
).revertedWith(INVALID_COLLATERAL_TO_LIQUIDATE);
});
it('LIQUIDATION - Liquidates the borrow', async () => {
const {pool, dai, users, addressesProvider, oracle} = testEnv;
const {pool, dai, users, oracle} = testEnv;
const borrower = users[1];
//mints dai to the caller
@ -130,17 +125,16 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
//approve protocol to access depositor wallet
await dai.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const userReserveDataBefore = await pool.getUserReserveData(dai.address, borrower.address);
const daiReserveDataBefore = await pool.getReserveData(dai.address);
const daiReserveDataBefore = await getReserveData(pool, dai.address);
const ethReserveDataBefore = await pool.getReserveData(MOCK_ETH_ADDRESS);
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString())
.plus(userReserveDataBefore.currentVariableDebt.toString())
const userReserveDataBefore = await getUserData(pool, dai.address, borrower.address);
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentVariableDebt.toString())
.div(2)
.toFixed(0);
await pool.liquidationCall(
const tx = await pool.liquidationCall(
MOCK_ETH_ADDRESS,
dai.address,
borrower.address,
@ -155,15 +149,15 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
const daiReserveDataAfter = await pool.getReserveData(dai.address);
const ethReserveDataAfter = await pool.getReserveData(MOCK_ETH_ADDRESS);
const feeAddress = await addressesProvider.getTokenDistributor();
const feeAddressBalance = await BRE.ethers.provider.getBalance(feeAddress);
const collateralPrice = (await oracle.getAssetPrice(MOCK_ETH_ADDRESS)).toString();
const principalPrice = (await oracle.getAssetPrice(dai.address)).toString();
const collateralDecimals = (await pool.getReserveDecimals(MOCK_ETH_ADDRESS)).toString();
const principalDecimals = (await pool.getReserveDecimals(dai.address)).toString();
const collateralDecimals = (
await pool.getReserveConfigurationData(MOCK_ETH_ADDRESS)
).decimals.toString();
const principalDecimals = (
await pool.getReserveConfigurationData(dai.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice)
.times(new BigNumber(amountToLiquidate).times(105))
@ -171,15 +165,26 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
.div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals)))
.decimalPlaces(0, BigNumber.ROUND_DOWN);
expect(userGlobalDataAfter.healthFactor).to.be.bignumber.gt(
if(!tx.blockNumber){
expect(false, "Invalid block number");
return;
}
const txTimestamp = new BigNumber((await BRE.ethers.provider.getBlock(tx.blockNumber)).timestamp);
const variableDebtBeforeTx = calcExpectedVariableDebtTokenBalance(
daiReserveDataBefore,
userReserveDataBefore,
txTimestamp
);
expect(userGlobalDataAfter.healthFactor.toString()).to.be.bignumber.gt(
oneEther.toFixed(0),
'Invalid health factor'
);
expect(feeAddressBalance).to.be.bignumber.gt('0');
expect(userReserveDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentVariableDebt.toString())
new BigNumber(variableDebtBeforeTx)
.minus(amountToLiquidate)
.toFixed(0),
'Invalid user borrow balance after liquidation'
@ -199,8 +204,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
});
it(
'User 3 deposits 1000 USDC, user 4 1 ETH,' +
' user 4 borrows - drops HF, liquidates the borrow',
'User 3 deposits 1000 USDC, user 4 1 ETH, user 4 borrows - drops HF, liquidates the borrow',
async () => {
const {users, pool, usdc, oracle, addressesProvider} = testEnv;
const depositor = users[3];
@ -262,7 +266,6 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
const ethReserveDataBefore = await pool.getReserveData(MOCK_ETH_ADDRESS);
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString())
.plus(userReserveDataBefore.currentStableDebt.toString())
.div(2)
.toFixed(0);
@ -281,15 +284,15 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
const usdcReserveDataAfter = await pool.getReserveData(usdc.address);
const ethReserveDataAfter = await pool.getReserveData(MOCK_ETH_ADDRESS);
const feeAddress = await addressesProvider.getTokenDistributor();
const feeAddressBalance = await BRE.ethers.provider.getBalance(feeAddress);
const collateralPrice = (await oracle.getAssetPrice(MOCK_ETH_ADDRESS)).toString();
const principalPrice = (await oracle.getAssetPrice(usdc.address)).toString();
const collateralDecimals = (await pool.getReserveDecimals(MOCK_ETH_ADDRESS)).toString();
const principalDecimals = (await pool.getReserveDecimals(usdc.address)).toString();
const collateralDecimals = (
await pool.getReserveConfigurationData(MOCK_ETH_ADDRESS)
).decimals.toString();
const principalDecimals = (
await pool.getReserveConfigurationData(usdc.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice)
.times(new BigNumber(amountToLiquidate).times(105))
@ -297,21 +300,19 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
.div(new BigNumber(collateralPrice).times(new BigNumber(10).pow(principalDecimals)))
.decimalPlaces(0, BigNumber.ROUND_DOWN);
expect(userGlobalDataAfter.healthFactor).to.be.bignumber.gt(
expect(userGlobalDataAfter.healthFactor.toString()).to.be.bignumber.gt(
oneEther.toFixed(0),
'Invalid health factor'
);
expect(feeAddressBalance).to.be.bignumber.gt('0');
expect(userReserveDataAfter.principalBorrowBalance).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentBorrowBalance.toString())
expect(userReserveDataAfter.currentStableDebt.toString()).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentStableDebt.toString())
.minus(amountToLiquidate)
.toFixed(0),
'Invalid user borrow balance after liquidation'
);
expect(usdcReserveDataAfter.availableLiquidity).to.be.bignumber.almostEqual(
expect(usdcReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual(
new BigNumber(usdcReserveDataBefore.availableLiquidity.toString())
.plus(amountToLiquidate)
.toFixed(0),

View File

@ -6,6 +6,8 @@ import {convertToCurrencyDecimals} from '../helpers/contracts-helpers';
import {makeSuite} from './helpers/make-suite';
import {ProtocolErrors, RateMode} from '../helpers/types';
import {borrow} from './helpers/actions';
import {calcExpectedStableDebtTokenBalance} from './helpers/utils/calculations';
import { getUserData } from './helpers/utils/helpers';
const chai = require('chai');
chai.use(require('chai-bignumber')());
@ -115,34 +117,31 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
});
it('LIQUIDATION - Liquidates the borrow', async () => {
const {dai, users, pool, oracle, addressesProvider} = testEnv;
const {dai, users, pool, oracle} = testEnv;
const liquidator = users[3];
const borrower = users[1];
//mints dai to the caller
await dai.mint(await convertToCurrencyDecimals(dai.address, '1000'));
//mints dai to the liquidator
await dai.connect(liquidator.signer).mint(await convertToCurrencyDecimals(dai.address, '1000'));
//approve protocol to access depositor wallet
//approve protocol to access the liquidator wallet
await dai.connect(liquidator.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const userReserveDataBefore: any = await pool.getUserReserveData(dai.address, borrower.address);
const daiReserveDataBefore: any = await pool.getReserveData(dai.address);
const ethReserveDataBefore: any = await pool.getReserveData(MOCK_ETH_ADDRESS);
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentBorrowBalance)
const userReserveDataBefore: any = await getUserData(pool, dai.address, borrower.address);
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt)
.div(2)
.toFixed(0);
await pool.liquidationCall(
MOCK_ETH_ADDRESS,
dai.address,
borrower.address,
amountToLiquidate,
false
);
const tx = await pool
.connect(liquidator.signer)
.liquidationCall(MOCK_ETH_ADDRESS, dai.address, borrower.address, amountToLiquidate, false);
const userReserveDataAfter: any = await pool.getUserReserveData(dai.address, borrower.address);
const userReserveDataAfter: any = await getUserData(pool, dai.address, borrower.address);
const daiReserveDataAfter: any = await pool.getReserveData(dai.address);
const ethReserveDataAfter: any = await pool.getReserveData(MOCK_ETH_ADDRESS);
@ -150,8 +149,12 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
const collateralPrice = await oracle.getAssetPrice(MOCK_ETH_ADDRESS);
const principalPrice = await oracle.getAssetPrice(dai.address);
const collateralDecimals = await pool.getReserveDecimals(MOCK_ETH_ADDRESS);
const principalDecimals = await pool.getReserveDecimals(dai.address);
const collateralDecimals = (
await pool.getReserveConfigurationData(MOCK_ETH_ADDRESS)
).decimals.toString();
const principalDecimals = (
await pool.getReserveConfigurationData(dai.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToLiquidate).times(105))
@ -162,29 +165,24 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedFeeLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(userReserveDataBefore.originationFee).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const feeAddress = await addressesProvider.getTokenDistributor();
const feeAddressBalance = await BRE.ethers.provider.getBalance(feeAddress);
expect(userReserveDataAfter.originationFee.toString()).to.be.bignumber.eq(
'0',
'Origination fee should be repaid'
if (!tx.blockNumber) {
expect(false, 'Invalid block number');
return;
}
const txTimestamp = new BigNumber(
(await BRE.ethers.provider.getBlock(tx.blockNumber)).timestamp
);
expect(feeAddressBalance).to.be.bignumber.gt('0');
const stableDebtBeforeTx = calcExpectedStableDebtTokenBalance(
userReserveDataBefore,
txTimestamp.plus(2)
);
expect(userReserveDataAfter.principalBorrowBalance.toString()).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentBorrowBalance).minus(amountToLiquidate).toFixed(0),
'Invalid user borrow balance after liquidation'
console.log("debt: ", stableDebtBeforeTx.toFixed(), userReserveDataBefore.currentStableDebt.toFixed(), userReserveDataAfter.currentStableDebt.toString())
expect(userReserveDataAfter.currentStableDebt.toString()).to.be.bignumber.almostEqual(
new BigNumber(stableDebtBeforeTx).minus(amountToLiquidate).toFixed(0),
'Invalid user debt after liquidation'
);
expect(daiReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual(
@ -192,9 +190,10 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
'Invalid principal available liquidity'
);
console.log('eth liquidity: ', daiReserveDataAfter.availableLiquidity.toString());
expect(ethReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual(
new BigNumber(ethReserveDataBefore.availableLiquidity)
.minus(expectedFeeLiquidated)
.minus(expectedCollateralLiquidated)
.toFixed(0),
'Invalid collateral available liquidity'
@ -208,7 +207,6 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
const borrower = users[4];
const liquidator = users[5];
//mints USDC to depositor
await usdc
.connect(depositor.signer)
@ -236,32 +234,47 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
const amountUSDCToBorrow = await convertToCurrencyDecimals(
usdc.address,
new BigNumber(userGlobalData.availableBorrowsETH).div(usdcPrice.toString()).multipliedBy(0.95).toFixed(0)
new BigNumber(userGlobalData.availableBorrowsETH)
.div(usdcPrice.toString())
.multipliedBy(0.95)
.toFixed(0)
);
await pool.connect(borrower.signer).borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
await pool
.connect(borrower.signer)
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
//drops HF below 1
await oracle.setAssetPrice(usdc.address, new BigNumber(usdcPrice.toString()).multipliedBy(1.2).toFixed(0));
await oracle.setAssetPrice(
usdc.address,
new BigNumber(usdcPrice.toString()).multipliedBy(1.2).toFixed(0)
);
//mints dai to the liquidator
await usdc.connect(liquidator.signer).mint(await convertToCurrencyDecimals(usdc.address, '1000'));
await usdc
.connect(liquidator.signer)
.mint(await convertToCurrencyDecimals(usdc.address, '1000'));
//approve protocol to access depositor wallet
await usdc.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await usdc.connect(liquidator.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const userReserveDataBefore: any = await pool.getUserReserveData(usdc.address, borrower.address);
const userReserveDataBefore: any = await pool.getUserReserveData(
usdc.address,
borrower.address
);
const usdcReserveDataBefore: any = await pool.getReserveData(usdc.address);
const ethReserveDataBefore: any = await pool.getReserveData(MOCK_ETH_ADDRESS);
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentBorrowBalance)
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt)
.div(2)
.decimalPlaces(0, BigNumber.ROUND_DOWN)
.toFixed(0);
await pool.connect(liquidator.signer).liquidationCall(MOCK_ETH_ADDRESS, usdc.address, borrower.address, amountToLiquidate, false);
await pool
.connect(liquidator.signer)
.liquidationCall(MOCK_ETH_ADDRESS, usdc.address, borrower.address, amountToLiquidate, false);
const userReserveDataAfter: any = await pool.getUserReserveData(usdc.address, borrower.address);
@ -270,27 +283,18 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
const usdcReserveDataAfter: any = await pool.getReserveData(usdc.address);
const ethReserveDataAfter: any = await pool.getReserveData(MOCK_ETH_ADDRESS);
const feeAddress = await addressesProvider.getTokenDistributor();
const feeAddressBalance = await BRE.ethers.provider.getBalance(feeAddress);
const collateralPrice = await oracle.getAssetPrice(MOCK_ETH_ADDRESS);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralDecimals = await pool.getReserveDecimals(MOCK_ETH_ADDRESS);
const principalDecimals = await pool.getReserveDecimals(usdc.address);
const collateralDecimals = (await pool.getReserveConfigurationData(MOCK_ETH_ADDRESS)).decimals.toString();
const principalDecimals = (await pool.getReserveConfigurationData(usdc.address)).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToLiquidate).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)))
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedFeeLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(userReserveDataBefore.originationFee).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
@ -299,15 +303,16 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
'Invalid health factor'
);
expect(userReserveDataAfter.originationFee.toString()).to.be.bignumber.eq(
'0',
'Origination fee should be repaid'
console.log(
'Debt: ',
userReserveDataAfter.currentStableDebt.toString(),
new BigNumber(userReserveDataBefore.currentStableDebt.toString())
.minus(amountToLiquidate)
.toFixed(0)
);
expect(feeAddressBalance.toString()).to.be.bignumber.gt('0');
expect(userReserveDataAfter.principalBorrowBalance.toString()).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentBorrowBalance.toString())
expect(userReserveDataAfter.currentStableDebt.toString()).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentStableDebt.toString())
.minus(amountToLiquidate)
.toFixed(0),
'Invalid user borrow balance after liquidation'
@ -318,9 +323,16 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
'Invalid principal available liquidity'
);
console.log(
'Debt: ',
usdcReserveDataAfter.availableLiquidity.toString(),
new BigNumber(usdcReserveDataBefore.availableLiquidity.toString())
.plus(amountToLiquidate)
.toFixed(0)
);
expect(ethReserveDataAfter.availableLiquidity.toString()).to.be.bignumber.almostEqual(
new BigNumber(ethReserveDataBefore.availableLiquidity)
.minus(expectedFeeLiquidated)
.minus(expectedCollateralLiquidated)
.toFixed(0),
'Invalid collateral available liquidity'
@ -328,7 +340,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
});
it('User 4 deposits 1000 LEND - drops HF, liquidates the LEND, which results on a lower amount being liquidated', async () => {
const {lend, usdc, users, pool, oracle, addressesProvider} = testEnv;
const {lend, usdc, users, pool, oracle} = testEnv;
const depositor = users[3];
const borrower = users[4];
@ -347,20 +359,26 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
const usdcPrice = await oracle.getAssetPrice(usdc.address);
//drops HF below 1
await oracle.setAssetPrice(usdc.address, new BigNumber(usdcPrice.toString()).multipliedBy(1.1).toFixed(0));
await oracle.setAssetPrice(
usdc.address,
new BigNumber(usdcPrice.toString()).multipliedBy(1.12).toFixed(0)
);
//mints usdc to the liquidator
await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000'));
await usdc.connect(liquidator.signer).mint(await convertToCurrencyDecimals(usdc.address, '1000'));
//approve protocol to access depositor wallet
await usdc.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await usdc.connect(liquidator.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
const userReserveDataBefore: any = await pool.getUserReserveData(usdc.address, borrower.address);
const userReserveDataBefore: any = await pool.getUserReserveData(
usdc.address,
borrower.address
);
const usdcReserveDataBefore: any = await pool.getReserveData(usdc.address);
const lendReserveDataBefore: any = await pool.getReserveData(lend.address);
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentBorrowBalance)
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt)
.div(2)
.decimalPlaces(0, BigNumber.ROUND_DOWN)
.toFixed(0);
@ -368,13 +386,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
const collateralPrice = await oracle.getAssetPrice(lend.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
await pool.connect(liquidator.signer).liquidationCall(
lend.address,
usdc.address,
borrower.address,
amountToLiquidate,
false
);
await pool
.connect(liquidator.signer)
.liquidationCall(lend.address, usdc.address, borrower.address, amountToLiquidate, false);
const userReserveDataAfter: any = await pool.getUserReserveData(usdc.address, borrower.address);
@ -383,17 +397,19 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
const usdcReserveDataAfter: any = await pool.getReserveData(usdc.address);
const lendReserveDataAfter: any = await pool.getReserveData(lend.address);
const collateralDecimals = await pool.getReserveDecimals(lend.address);
const principalDecimals = await pool.getReserveDecimals(usdc.address);
const collateralDecimals = (await pool.getReserveConfigurationData(lend.address)).decimals.toString();
const principalDecimals = (await pool.getReserveConfigurationData(usdc.address)).decimals.toString();
const expectedCollateralLiquidated = oneEther.multipliedBy('1000');
const liquidationBonus = await pool.getReserveLiquidationBonus(lend.address);
const liquidationBonus = (await pool.getReserveConfigurationData(lend.address)).liquidationBonus.toString();
const expectedPrincipal = new BigNumber(collateralPrice.toString())
.times(expectedCollateralLiquidated)
.times(new BigNumber(10).pow(principalDecimals))
.div(new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)))
.div(
new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals))
)
.times(100)
.div(liquidationBonus.toString())
.decimalPlaces(0, BigNumber.ROUND_DOWN);
@ -403,13 +419,8 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
'Invalid health factor'
);
expect(userReserveDataAfter.originationFee.toString()).to.be.bignumber.eq(
'0',
'Origination fee should be repaid'
);
expect(userReserveDataAfter.principalBorrowBalance.toString()).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentBorrowBalance).minus(expectedPrincipal).toFixed(0),
expect(userReserveDataAfter.currentStableDebt.toString()).to.be.bignumber.almostEqual(
new BigNumber(userReserveDataBefore.currentStableDebt).minus(expectedPrincipal).toFixed(0),
'Invalid user borrow balance after liquidation'
);

View File

@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN});
const scenarioFolder = './test/helpers/scenarios/';
const selectedScenarios: string[] = [];
const selectedScenarios: string[] = ['borrow-repay-variable.json'];
fs.readdirSync(scenarioFolder).forEach((file) => {
if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -385,6 +385,7 @@ export class LendingPool extends Contract {
getReserveConfigurationData(
_reserve: string
): Promise<{
decimals: BigNumber;
ltv: BigNumber;
liquidationThreshold: BigNumber;
liquidationBonus: BigNumber;
@ -398,13 +399,14 @@ export class LendingPool extends Contract {
0: BigNumber;
1: BigNumber;
2: BigNumber;
3: string;
3: BigNumber;
4: string;
5: boolean;
5: string;
6: boolean;
7: boolean;
8: boolean;
9: boolean;
10: boolean;
}>;
getReserveData(
@ -650,6 +652,7 @@ export class LendingPool extends Contract {
getReserveConfigurationData(
_reserve: string
): Promise<{
decimals: BigNumber;
ltv: BigNumber;
liquidationThreshold: BigNumber;
liquidationBonus: BigNumber;
@ -663,13 +666,14 @@ export class LendingPool extends Contract {
0: BigNumber;
1: BigNumber;
2: BigNumber;
3: string;
3: BigNumber;
4: string;
5: boolean;
5: string;
6: boolean;
7: boolean;
8: boolean;
9: boolean;
10: boolean;
}>;
getReserveData(

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -37,7 +37,6 @@ interface LendingPoolLiquidationManagerInterface extends Interface {
_user,
_purchaseAmount,
_liquidatedCollateralAmount,
_accruedBorrowInterest,
_liquidator,
_receiveAToken,
_timestamp
@ -49,25 +48,6 @@ interface LendingPoolLiquidationManagerInterface extends Interface {
null,
null,
null,
null,
null
]): string[];
}>;
OriginationFeeLiquidated: TypedEventDescription<{
encodeTopics([
_collateral,
_reserve,
_user,
_feeLiquidated,
_liquidatedCollateralForFee,
_timestamp
]: [
string | null,
string | null,
string | null,
null,
null,
null
]): string[];
}>;
@ -138,20 +118,10 @@ export class LendingPoolLiquidationManager extends Contract {
_user: string | null,
_purchaseAmount: null,
_liquidatedCollateralAmount: null,
_accruedBorrowInterest: null,
_liquidator: null,
_receiveAToken: null,
_timestamp: null
): EventFilter;
OriginationFeeLiquidated(
_collateral: string | null,
_reserve: string | null,
_user: string | null,
_feeLiquidated: null,
_liquidatedCollateralForFee: null,
_timestamp: null
): EventFilter;
};
estimate: {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -134,4 +134,4 @@ const _abi = [
];
const _bytecode =
"0x608060405234801561001057600080fd5b5060405161099e38038061099e8339818101604052602081101561003357600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055610939806100656000396000f3fe6080604052600436106100385760003560e01c80639e3c930914610083578063b59b28ef1461014f578063f7888aec146102d35761007e565b3661007e5761004633610320565b61007c576040805162461bcd60e51b8152602060048201526002602482015261191960f11b604482015290519081900360640190fd5b005b600080fd5b34801561008f57600080fd5b506100b6600480360360208110156100a657600080fd5b50356001600160a01b031661035c565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156100fa5781810151838201526020016100e2565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015610139578181015183820152602001610121565b5050505090500194505050505060405180910390f35b34801561015b57600080fd5b506102836004803603604081101561017257600080fd5b81019060208101813564010000000081111561018d57600080fd5b82018360208201111561019f57600080fd5b803590602001918460208302840111640100000000831117156101c157600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561021157600080fd5b82018360208201111561022357600080fd5b8035906020019184602083028401116401000000008311171561024557600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506106a9945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156102bf5781810151838201526020016102a7565b505050509050019250505060405180910390f35b3480156102df57600080fd5b5061030e600480360360408110156102f657600080fd5b506001600160a01b0381358116916020013516610841565b60408051918252519081900360200190f35b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061035457508115155b949350505050565b60608060008060009054906101000a90046001600160a01b03166001600160a01b0316630261bf8b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156103ae57600080fd5b505afa1580156103c2573d6000803e3d6000fd5b505050506040513d60208110156103d857600080fd5b505160408051630240bc6b60e21b815290519192506060916001600160a01b03841691630902f1ac916004808301926000929190829003018186803b15801561042057600080fd5b505afa158015610434573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561045d57600080fd5b810190808051604051939291908464010000000082111561047d57600080fd5b90830190602082018581111561049257600080fd5b82518660208202830111640100000000821117156104af57600080fd5b82525081516020918201928201910280838360005b838110156104dc5781810151838201526020016104c4565b5050505090500160405250505090506060815167ffffffffffffffff8111801561050557600080fd5b5060405190808252806020026020018201604052801561052f578160200160208202803683370190505b50905060005b825181101561069d576000846001600160a01b0316633e15014185848151811061055b57fe5b60200260200101516040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b031681526020019150506101406040518083038186803b1580156105aa57600080fd5b505afa1580156105be573d6000803e3d6000fd5b505050506040513d6101408110156105d557600080fd5b5061010001519050806106025760008383815181106105f057fe5b60200260200101818152505050610695565b61060a6108eb565b6001600160a01b031684838151811061061f57fe5b60200260200101516001600160a01b03161461066f576106528885848151811061064557fe5b6020026020010151610841565b83838151811061065e57fe5b602002602001018181525050610693565b876001600160a01b03163183838151811061068657fe5b6020026020010181815250505b505b600101610535565b50909350915050915091565b606080825184510267ffffffffffffffff811180156106c757600080fd5b506040519080825280602002602001820160405280156106f1578160200160208202803683370190505b50905060005b84518110156108375760005b845181101561082e57845182026107186108eb565b6001600160a01b031686838151811061072d57fe5b60200260200101516001600160a01b031614156107815786838151811061075057fe5b60200260200101516001600160a01b031631848383018151811061077057fe5b602002602001018181525050610825565b6107a686838151811061079057fe5b60200260200101516001600160a01b0316610320565b6107e7576040805162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22faa27a5a2a760991b604482015290519081900360640190fd5b61080a8784815181106107f657fe5b602002602001015187848151811061064557fe5b848383018151811061081857fe5b6020026020010181815250505b50600101610703565b506001016106f7565b5090505b92915050565b6000610855826001600160a01b0316610320565b156108e357816001600160a01b03166370a08231846040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156108b057600080fd5b505afa1580156108c4573d6000803e3d6000fd5b505050506040513d60208110156108da57600080fd5b5051905061083b565b50600061083b565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee9056fea2646970667358221220690f7ecb9ab912f052e8d44a9a4f27007b3b6f0119cdc6270033c06dbc83a4e664736f6c63430006080033";
"0x608060405234801561001057600080fd5b5060405161099e38038061099e8339818101604052602081101561003357600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055610939806100656000396000f3fe6080604052600436106100385760003560e01c80639e3c930914610083578063b59b28ef1461014f578063f7888aec146102d35761007e565b3661007e5761004633610320565b61007c576040805162461bcd60e51b8152602060048201526002602482015261191960f11b604482015290519081900360640190fd5b005b600080fd5b34801561008f57600080fd5b506100b6600480360360208110156100a657600080fd5b50356001600160a01b031661035c565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156100fa5781810151838201526020016100e2565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015610139578181015183820152602001610121565b5050505090500194505050505060405180910390f35b34801561015b57600080fd5b506102836004803603604081101561017257600080fd5b81019060208101813564010000000081111561018d57600080fd5b82018360208201111561019f57600080fd5b803590602001918460208302840111640100000000831117156101c157600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561021157600080fd5b82018360208201111561022357600080fd5b8035906020019184602083028401116401000000008311171561024557600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506106a9945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156102bf5781810151838201526020016102a7565b505050509050019250505060405180910390f35b3480156102df57600080fd5b5061030e600480360360408110156102f657600080fd5b506001600160a01b0381358116916020013516610841565b60408051918252519081900360200190f35b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061035457508115155b949350505050565b60608060008060009054906101000a90046001600160a01b03166001600160a01b0316630261bf8b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156103ae57600080fd5b505afa1580156103c2573d6000803e3d6000fd5b505050506040513d60208110156103d857600080fd5b505160408051630240bc6b60e21b815290519192506060916001600160a01b03841691630902f1ac916004808301926000929190829003018186803b15801561042057600080fd5b505afa158015610434573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561045d57600080fd5b810190808051604051939291908464010000000082111561047d57600080fd5b90830190602082018581111561049257600080fd5b82518660208202830111640100000000821117156104af57600080fd5b82525081516020918201928201910280838360005b838110156104dc5781810151838201526020016104c4565b5050505090500160405250505090506060815167ffffffffffffffff8111801561050557600080fd5b5060405190808252806020026020018201604052801561052f578160200160208202803683370190505b50905060005b825181101561069d576000846001600160a01b0316633e15014185848151811061055b57fe5b60200260200101516040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b031681526020019150506101606040518083038186803b1580156105aa57600080fd5b505afa1580156105be573d6000803e3d6000fd5b505050506040513d6101608110156105d557600080fd5b5061012001519050806106025760008383815181106105f057fe5b60200260200101818152505050610695565b61060a6108eb565b6001600160a01b031684838151811061061f57fe5b60200260200101516001600160a01b03161461066f576106528885848151811061064557fe5b6020026020010151610841565b83838151811061065e57fe5b602002602001018181525050610693565b876001600160a01b03163183838151811061068657fe5b6020026020010181815250505b505b600101610535565b50909350915050915091565b606080825184510267ffffffffffffffff811180156106c757600080fd5b506040519080825280602002602001820160405280156106f1578160200160208202803683370190505b50905060005b84518110156108375760005b845181101561082e57845182026107186108eb565b6001600160a01b031686838151811061072d57fe5b60200260200101516001600160a01b031614156107815786838151811061075057fe5b60200260200101516001600160a01b031631848383018151811061077057fe5b602002602001018181525050610825565b6107a686838151811061079057fe5b60200260200101516001600160a01b0316610320565b6107e7576040805162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22faa27a5a2a760991b604482015290519081900360640190fd5b61080a8784815181106107f657fe5b602002602001015187848151811061064557fe5b848383018151811061081857fe5b6020026020010181815250505b50600101610703565b506001016106f7565b5090505b92915050565b6000610855826001600160a01b0316610320565b156108e357816001600160a01b03166370a08231846040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156108b057600080fd5b505afa1580156108c4573d6000803e3d6000fd5b505050506040513d60208110156108da57600080fd5b5051905061083b565b50600061083b565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee9056fea2646970667358221220fa706074cbd7cd86464474ec2c51c52dff3f0f34b17906e75605b4357a7d4dbe64736f6c63430006080033";