mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Fixed conflicts and merge changes
This commit is contained in:
commit
6cbcf39454
|
@ -1,10 +1,10 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import {usePlugin} from '@nomiclabs/buidler/config';
|
import {usePlugin} from '@nomiclabs/buidler/config';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import {accounts} from './test-wallets.js';
|
import {accounts} from './test-wallets.js';
|
||||||
import {eEthereumNetwork} from './helpers/types';
|
import {eEthereumNetwork} from './helpers/types';
|
||||||
|
import {BUIDLEREVM_CHAINID, COVERAGE_CHAINID} from './helpers/constants';
|
||||||
|
|
||||||
usePlugin('@nomiclabs/buidler-ethers');
|
usePlugin('@nomiclabs/buidler-ethers');
|
||||||
usePlugin('buidler-typechain');
|
usePlugin('buidler-typechain');
|
||||||
|
@ -14,6 +14,7 @@ usePlugin('@nomiclabs/buidler-etherscan');
|
||||||
//usePlugin('buidler-gas-reporter');
|
//usePlugin('buidler-gas-reporter');
|
||||||
|
|
||||||
const SKIP_LOAD = process.env.SKIP_LOAD === 'true';
|
const SKIP_LOAD = process.env.SKIP_LOAD === 'true';
|
||||||
|
|
||||||
const DEFAULT_BLOCK_GAS_LIMIT = 10000000;
|
const DEFAULT_BLOCK_GAS_LIMIT = 10000000;
|
||||||
const DEFAULT_GAS_PRICE = 10;
|
const DEFAULT_GAS_PRICE = 10;
|
||||||
const HARDFORK = 'istanbul';
|
const HARDFORK = 'istanbul';
|
||||||
|
@ -73,6 +74,7 @@ const buidlerConfig: any = {
|
||||||
networks: {
|
networks: {
|
||||||
coverage: {
|
coverage: {
|
||||||
url: 'http://localhost:8555',
|
url: 'http://localhost:8555',
|
||||||
|
chainId: COVERAGE_CHAINID,
|
||||||
},
|
},
|
||||||
kovan: getCommonNetworkConfig(eEthereumNetwork.kovan, 42),
|
kovan: getCommonNetworkConfig(eEthereumNetwork.kovan, 42),
|
||||||
ropsten: getCommonNetworkConfig(eEthereumNetwork.ropsten, 3),
|
ropsten: getCommonNetworkConfig(eEthereumNetwork.ropsten, 3),
|
||||||
|
@ -82,7 +84,7 @@ const buidlerConfig: any = {
|
||||||
blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
|
blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
|
||||||
gas: DEFAULT_BLOCK_GAS_LIMIT,
|
gas: DEFAULT_BLOCK_GAS_LIMIT,
|
||||||
gasPrice: 8000000000,
|
gasPrice: 8000000000,
|
||||||
chainId: 31337,
|
chainId: BUIDLEREVM_CHAINID,
|
||||||
throwOnTransactionFailures: true,
|
throwOnTransactionFailures: true,
|
||||||
throwOnCallFailures: true,
|
throwOnCallFailures: true,
|
||||||
accounts: accounts.map(({secretKey, balance}: {secretKey: string; balance: string}) => ({
|
accounts: accounts.map(({secretKey, balance}: {secretKey: string; balance: string}) => ({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import {oneRay} from '../helpers/constants';
|
import {oneRay} from '../helpers/constants';
|
||||||
import {IAaveConfiguration, EthereumNetwork} from '../helpers/types';
|
import {IAaveConfiguration, EthereumNetwork, eEthereumNetwork} from '../helpers/types';
|
||||||
|
|
||||||
import {CommonsConfig} from './commons';
|
import {CommonsConfig} from './commons';
|
||||||
|
|
||||||
|
@ -255,6 +255,8 @@ export const AaveConfig: IAaveConfiguration = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ReserveAssets: {
|
ReserveAssets: {
|
||||||
|
[eEthereumNetwork.buidlerevm]: {},
|
||||||
|
[eEthereumNetwork.coverage]: {},
|
||||||
[EthereumNetwork.kovan]: {
|
[EthereumNetwork.kovan]: {
|
||||||
WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
|
WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
|
||||||
DAI: '0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD',
|
DAI: '0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import {oneEther, oneRay, RAY} from '../helpers/constants';
|
import {oneEther, oneRay, RAY} from '../helpers/constants';
|
||||||
import {ICommonConfiguration, EthereumNetwork} from '../helpers/types';
|
import {ICommonConfiguration, EthereumNetwork, eEthereumNetwork} from '../helpers/types';
|
||||||
|
|
||||||
const MOCK_CHAINLINK_AGGREGATORS_PRICES = {
|
const MOCK_CHAINLINK_AGGREGATORS_PRICES = {
|
||||||
DAI: oneEther.multipliedBy('0.00369068412860').toFixed(),
|
DAI: oneEther.multipliedBy('0.00369068412860').toFixed(),
|
||||||
|
@ -120,37 +120,51 @@ export const CommonsConfig: ICommonConfiguration = {
|
||||||
|
|
||||||
// If lendingPoolManagerAddress is set, will take priority over lendingPoolManagerAddressIndex
|
// If lendingPoolManagerAddress is set, will take priority over lendingPoolManagerAddressIndex
|
||||||
LendingPoolManagerAddress: {
|
LendingPoolManagerAddress: {
|
||||||
[EthereumNetwork.kovan]: undefined,
|
[eEthereumNetwork.coverage]: undefined,
|
||||||
[EthereumNetwork.ropsten]: undefined,
|
[eEthereumNetwork.buidlerevm]: undefined,
|
||||||
[EthereumNetwork.main]: undefined,
|
[eEthereumNetwork.kovan]: undefined,
|
||||||
|
[eEthereumNetwork.ropsten]: undefined,
|
||||||
|
[eEthereumNetwork.main]: undefined,
|
||||||
},
|
},
|
||||||
LendingPoolManagerAddressIndex: 0,
|
LendingPoolManagerAddressIndex: 0,
|
||||||
ProviderRegistry: {
|
ProviderRegistry: {
|
||||||
[EthereumNetwork.kovan]: undefined,
|
[eEthereumNetwork.kovan]: '',
|
||||||
[EthereumNetwork.ropsten]: undefined,
|
[eEthereumNetwork.ropsten]: '',
|
||||||
[EthereumNetwork.main]: undefined,
|
[eEthereumNetwork.main]: '',
|
||||||
|
[eEthereumNetwork.coverage]: '',
|
||||||
|
[eEthereumNetwork.buidlerevm]: '',
|
||||||
},
|
},
|
||||||
LendingRateOracle: {
|
LendingRateOracle: {
|
||||||
[EthereumNetwork.kovan]: '0xdcde9bb6a49e37fa433990832ab541ae2d4feb4a',
|
[eEthereumNetwork.coverage]: '',
|
||||||
[EthereumNetwork.ropsten]: '0x05dcca805a6562c1bdd0423768754acb6993241b',
|
[eEthereumNetwork.buidlerevm]: '',
|
||||||
[EthereumNetwork.main]: '0x4d728a4496e4de35f218d5a214366bde3a62b51c',
|
[eEthereumNetwork.kovan]: '0xdcde9bb6a49e37fa433990832ab541ae2d4feb4a',
|
||||||
|
[eEthereumNetwork.ropsten]: '0x05dcca805a6562c1bdd0423768754acb6993241b',
|
||||||
|
[eEthereumNetwork.main]: '0x4d728a4496e4de35f218d5a214366bde3a62b51c',
|
||||||
},
|
},
|
||||||
TokenDistributor: {
|
TokenDistributor: {
|
||||||
|
[eEthereumNetwork.coverage]: '',
|
||||||
|
[eEthereumNetwork.buidlerevm]: '',
|
||||||
[EthereumNetwork.kovan]: '0x971efe90088f21dc6a36f610ffed77fc19710708',
|
[EthereumNetwork.kovan]: '0x971efe90088f21dc6a36f610ffed77fc19710708',
|
||||||
[EthereumNetwork.ropsten]: '0xeba2ea67942b8250d870b12750b594696d02fc9c',
|
[EthereumNetwork.ropsten]: '0xeba2ea67942b8250d870b12750b594696d02fc9c',
|
||||||
[EthereumNetwork.main]: '0xe3d9988f676457123c5fd01297605efdd0cba1ae',
|
[EthereumNetwork.main]: '0xe3d9988f676457123c5fd01297605efdd0cba1ae',
|
||||||
},
|
},
|
||||||
ChainlinkProxyPriceProvider: {
|
ChainlinkProxyPriceProvider: {
|
||||||
|
[eEthereumNetwork.coverage]: '',
|
||||||
|
[eEthereumNetwork.buidlerevm]: '',
|
||||||
[EthereumNetwork.kovan]: '0x276C4793F2EE3D5Bf18C5b879529dD4270BA4814',
|
[EthereumNetwork.kovan]: '0x276C4793F2EE3D5Bf18C5b879529dD4270BA4814',
|
||||||
[EthereumNetwork.ropsten]: '0x657372A559c30d236F011239fF9fbB6D76718271',
|
[EthereumNetwork.ropsten]: '0x657372A559c30d236F011239fF9fbB6D76718271',
|
||||||
[EthereumNetwork.main]: '0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4',
|
[EthereumNetwork.main]: '0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4',
|
||||||
},
|
},
|
||||||
FallbackOracle: {
|
FallbackOracle: {
|
||||||
|
[eEthereumNetwork.coverage]: '',
|
||||||
|
[eEthereumNetwork.buidlerevm]: '',
|
||||||
[EthereumNetwork.kovan]: '0x50913E8E1c650E790F8a1E741FF9B1B1bB251dfe',
|
[EthereumNetwork.kovan]: '0x50913E8E1c650E790F8a1E741FF9B1B1bB251dfe',
|
||||||
[EthereumNetwork.ropsten]: '0xAD1a978cdbb8175b2eaeC47B01404f8AEC5f4F0d',
|
[EthereumNetwork.ropsten]: '0xAD1a978cdbb8175b2eaeC47B01404f8AEC5f4F0d',
|
||||||
[EthereumNetwork.main]: '0xf67a8b0e3e0ee303422f78b4c5b8da60df80a59c',
|
[EthereumNetwork.main]: '0xf67a8b0e3e0ee303422f78b4c5b8da60df80a59c',
|
||||||
},
|
},
|
||||||
ChainlinkAggregator: {
|
ChainlinkAggregator: {
|
||||||
|
[eEthereumNetwork.coverage]: {},
|
||||||
|
[eEthereumNetwork.buidlerevm]: {},
|
||||||
[EthereumNetwork.kovan]: {
|
[EthereumNetwork.kovan]: {
|
||||||
DAI: '0x6F47077D3B6645Cb6fb7A29D280277EC1e5fFD90',
|
DAI: '0x6F47077D3B6645Cb6fb7A29D280277EC1e5fFD90',
|
||||||
TUSD: '0x02424c54D78D48179Fd12ebFfB11c16f9CA984Ad',
|
TUSD: '0x02424c54D78D48179Fd12ebFfB11c16f9CA984Ad',
|
||||||
|
@ -228,9 +242,20 @@ export const CommonsConfig: ICommonConfiguration = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ReserveAssets: {
|
ReserveAssets: {
|
||||||
|
[eEthereumNetwork.coverage]: {},
|
||||||
|
[eEthereumNetwork.buidlerevm]: {},
|
||||||
[EthereumNetwork.main]: {},
|
[EthereumNetwork.main]: {},
|
||||||
[EthereumNetwork.kovan]: {},
|
[EthereumNetwork.kovan]: {},
|
||||||
[EthereumNetwork.ropsten]: {},
|
[EthereumNetwork.ropsten]: {},
|
||||||
},
|
},
|
||||||
ReservesConfig: {},
|
ReservesConfig: {},
|
||||||
|
ATokenDomainSeparator: {
|
||||||
|
[eEthereumNetwork.coverage]:
|
||||||
|
'0x95b73a72c6ecf4ccbbba5178800023260bad8e75cdccdb8e4827a2977a37c820',
|
||||||
|
[eEthereumNetwork.buidlerevm]:
|
||||||
|
'0x76cbbf8aa4b11a7c207dd79ccf8c394f59475301598c9a083f8258b4fafcfa86',
|
||||||
|
[eEthereumNetwork.kovan]: '',
|
||||||
|
[eEthereumNetwork.ropsten]: '',
|
||||||
|
[eEthereumNetwork.main]: '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import {EthereumNetwork, IUniswapConfiguration} from '../helpers/types';
|
import {eEthereumNetwork, EthereumNetwork, IUniswapConfiguration} from '../helpers/types';
|
||||||
import {oneRay} from '../helpers/constants';
|
import {oneRay} from '../helpers/constants';
|
||||||
|
|
||||||
import {CommonsConfig} from './commons';
|
import {CommonsConfig} from './commons';
|
||||||
|
@ -157,6 +157,8 @@ export const UniswapConfig: IUniswapConfiguration = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ChainlinkAggregator: {
|
ChainlinkAggregator: {
|
||||||
|
[eEthereumNetwork.buidlerevm]: {},
|
||||||
|
[eEthereumNetwork.coverage]: {},
|
||||||
[EthereumNetwork.kovan]: {
|
[EthereumNetwork.kovan]: {
|
||||||
DAI: '0x6F47077D3B6645Cb6fb7A29D280277EC1e5fFD90',
|
DAI: '0x6F47077D3B6645Cb6fb7A29D280277EC1e5fFD90',
|
||||||
USDC: '0x672c1C0d1130912D83664011E7960a42E8cA05D5',
|
USDC: '0x672c1C0d1130912D83664011E7960a42E8cA05D5',
|
||||||
|
@ -192,6 +194,8 @@ export const UniswapConfig: IUniswapConfiguration = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ReserveAssets: {
|
ReserveAssets: {
|
||||||
|
[eEthereumNetwork.buidlerevm]: {},
|
||||||
|
[eEthereumNetwork.coverage]: {},
|
||||||
[EthereumNetwork.kovan]: {
|
[EthereumNetwork.kovan]: {
|
||||||
WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
|
WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
|
||||||
DAI: '0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD',
|
DAI: '0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD',
|
||||||
|
|
|
@ -29,6 +29,13 @@ interface ILendingPool {
|
||||||
**/
|
**/
|
||||||
event Withdraw(address indexed reserve, address indexed user, uint256 amount);
|
event Withdraw(address indexed reserve, address indexed user, uint256 amount);
|
||||||
|
|
||||||
|
event BorrowAllowanceDelegated(
|
||||||
|
address indexed asset,
|
||||||
|
address indexed fromUser,
|
||||||
|
address indexed toUser,
|
||||||
|
uint256 interestRateMode,
|
||||||
|
uint256 amount
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* @dev emitted on borrow
|
* @dev emitted on borrow
|
||||||
* @param reserve the address of the reserve
|
* @param reserve the address of the reserve
|
||||||
|
@ -40,7 +47,8 @@ interface ILendingPool {
|
||||||
**/
|
**/
|
||||||
event Borrow(
|
event Borrow(
|
||||||
address indexed reserve,
|
address indexed reserve,
|
||||||
address indexed user,
|
address user,
|
||||||
|
address indexed onBehalfOf,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 borrowRateMode,
|
uint256 borrowRateMode,
|
||||||
uint256 borrowRate,
|
uint256 borrowRate,
|
||||||
|
@ -129,6 +137,15 @@ interface ILendingPool {
|
||||||
address liquidator,
|
address liquidator,
|
||||||
bool receiveAToken
|
bool receiveAToken
|
||||||
);
|
);
|
||||||
|
/**
|
||||||
|
* @dev Emitted when the pause is triggered.
|
||||||
|
*/
|
||||||
|
event Paused();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Emitted when the pause is lifted.
|
||||||
|
*/
|
||||||
|
event Unpaused();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens)
|
* @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens)
|
||||||
|
@ -151,6 +168,27 @@ interface ILendingPool {
|
||||||
**/
|
**/
|
||||||
function withdraw(address reserve, uint256 amount) external;
|
function withdraw(address reserve, uint256 amount) external;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets allowance to borrow on a certain type of debt asset for a certain user address
|
||||||
|
* @param asset The underlying asset of the debt token
|
||||||
|
* @param user The user to give allowance to
|
||||||
|
* @param interestRateMode Type of debt: 1 for stable, 2 for variable
|
||||||
|
* @param amount Allowance amount to borrow
|
||||||
|
**/
|
||||||
|
function delegateBorrowAllowance(
|
||||||
|
address asset,
|
||||||
|
address user,
|
||||||
|
uint256 interestRateMode,
|
||||||
|
uint256 amount
|
||||||
|
) external;
|
||||||
|
|
||||||
|
function getBorrowAllowance(
|
||||||
|
address fromUser,
|
||||||
|
address toUser,
|
||||||
|
address asset,
|
||||||
|
uint256 interestRateMode
|
||||||
|
) external view returns (uint256);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
||||||
* already deposited enough collateral.
|
* already deposited enough collateral.
|
||||||
|
@ -162,7 +200,8 @@ interface ILendingPool {
|
||||||
address reserve,
|
address reserve,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 interestRateMode,
|
uint256 interestRateMode,
|
||||||
uint16 referralCode
|
uint16 referralCode,
|
||||||
|
address onBehalfOf
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -260,6 +299,22 @@ interface ILendingPool {
|
||||||
uint16 referralCode
|
uint16 referralCode
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||||
|
* - It's not possible to release one asset to swap for the same
|
||||||
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||||
|
* @param fromAsset Asset to swap from
|
||||||
|
* @param toAsset Asset to swap to
|
||||||
|
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||||
|
**/
|
||||||
|
function swapLiquidity(
|
||||||
|
address receiverAddress,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
bytes calldata params
|
||||||
|
) external;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev accessory functions to fetch data from the core contract
|
* @dev accessory functions to fetch data from the core contract
|
||||||
**/
|
**/
|
||||||
|
@ -374,4 +429,15 @@ interface ILendingPool {
|
||||||
) external view returns (bool);
|
) external view returns (bool);
|
||||||
|
|
||||||
function getReserves() external view returns (address[] memory);
|
function getReserves() external view returns (address[] memory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Set the _pause state
|
||||||
|
* @param val the boolean value to set the current pause state of LendingPool
|
||||||
|
*/
|
||||||
|
function setPause(bool val) external;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns if the LendingPool is paused
|
||||||
|
*/
|
||||||
|
function paused() external view returns (bool);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {UserConfiguration} from '../libraries/configuration/UserConfiguration.so
|
||||||
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
|
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
|
||||||
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
|
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
|
||||||
import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
|
import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
|
||||||
|
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
||||||
import {LendingPoolLiquidationManager} from './LendingPoolLiquidationManager.sol';
|
import {LendingPoolLiquidationManager} from './LendingPoolLiquidationManager.sol';
|
||||||
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
||||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||||
|
@ -48,20 +49,33 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
|
|
||||||
mapping(address => ReserveLogic.ReserveData) internal _reserves;
|
mapping(address => ReserveLogic.ReserveData) internal _reserves;
|
||||||
mapping(address => UserConfiguration.Map) internal _usersConfig;
|
mapping(address => UserConfiguration.Map) internal _usersConfig;
|
||||||
|
// debt token address => user who gives allowance => user who receives allowance => amount
|
||||||
|
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
|
||||||
|
|
||||||
address[] internal _reservesList;
|
address[] internal _reservesList;
|
||||||
|
|
||||||
bool internal _flashLiquidationLocked;
|
bool internal _flashLiquidationLocked;
|
||||||
|
bool internal _paused;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev only lending pools configurator can use functions affected by this modifier
|
* @dev only lending pools configurator can use functions affected by this modifier
|
||||||
**/
|
**/
|
||||||
modifier onlyLendingPoolConfigurator {
|
function onlyLendingPoolConfigurator() internal view {
|
||||||
require(
|
require(
|
||||||
_addressesProvider.getLendingPoolConfigurator() == msg.sender,
|
_addressesProvider.getLendingPoolConfigurator() == msg.sender,
|
||||||
Errors.CALLER_NOT_LENDING_POOL_CONFIGURATOR
|
Errors.CALLER_NOT_LENDING_POOL_CONFIGURATOR
|
||||||
);
|
);
|
||||||
_;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Function to make a function callable only when the contract is not paused.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - The contract must not be paused.
|
||||||
|
*/
|
||||||
|
function whenNotPaused() internal view {
|
||||||
|
require(!_paused, Errors.IS_PAUSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 public constant UINT_MAX_VALUE = uint256(-1);
|
uint256 public constant UINT_MAX_VALUE = uint256(-1);
|
||||||
|
@ -94,6 +108,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
address onBehalfOf,
|
address onBehalfOf,
|
||||||
uint16 referralCode
|
uint16 referralCode
|
||||||
) external override {
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
|
||||||
ValidationLogic.validateDeposit(reserve, amount);
|
ValidationLogic.validateDeposit(reserve, amount);
|
||||||
|
@ -122,6 +137,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
* @param amount the underlying amount to be redeemed
|
* @param amount the underlying amount to be redeemed
|
||||||
**/
|
**/
|
||||||
function withdraw(address asset, uint256 amount) external override {
|
function withdraw(address asset, uint256 amount) external override {
|
||||||
|
whenNotPaused();
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
|
||||||
address aToken = reserve.aTokenAddress;
|
address aToken = reserve.aTokenAddress;
|
||||||
|
@ -159,6 +175,36 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
emit Withdraw(asset, msg.sender, amount);
|
emit Withdraw(asset, msg.sender, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBorrowAllowance(
|
||||||
|
address fromUser,
|
||||||
|
address toUser,
|
||||||
|
address asset,
|
||||||
|
uint256 interestRateMode
|
||||||
|
) external override view returns (uint256) {
|
||||||
|
return
|
||||||
|
_borrowAllowance[_reserves[asset].getDebtTokenAddress(interestRateMode)][fromUser][toUser];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets allowance to borrow on a certain type of debt asset for a certain user address
|
||||||
|
* @param asset The underlying asset of the debt token
|
||||||
|
* @param user The user to give allowance to
|
||||||
|
* @param interestRateMode Type of debt: 1 for stable, 2 for variable
|
||||||
|
* @param amount Allowance amount to borrow
|
||||||
|
**/
|
||||||
|
function delegateBorrowAllowance(
|
||||||
|
address asset,
|
||||||
|
address user,
|
||||||
|
uint256 interestRateMode,
|
||||||
|
uint256 amount
|
||||||
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
|
address debtToken = _reserves[asset].getDebtTokenAddress(interestRateMode);
|
||||||
|
|
||||||
|
_borrowAllowance[debtToken][msg.sender][user] = amount;
|
||||||
|
emit BorrowAllowanceDelegated(asset, msg.sender, user, interestRateMode, amount);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
|
||||||
* already deposited enough collateral.
|
* already deposited enough collateral.
|
||||||
|
@ -166,20 +212,35 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
* @param amount the amount to be borrowed
|
* @param amount the amount to be borrowed
|
||||||
* @param interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE)
|
* @param interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE)
|
||||||
* @param referralCode a referral code for integrators
|
* @param referralCode a referral code for integrators
|
||||||
|
* @param onBehalfOf address of the user who will receive the debt
|
||||||
**/
|
**/
|
||||||
function borrow(
|
function borrow(
|
||||||
address asset,
|
address asset,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 interestRateMode,
|
uint256 interestRateMode,
|
||||||
uint16 referralCode
|
uint16 referralCode,
|
||||||
|
address onBehalfOf
|
||||||
) external override {
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
|
||||||
|
if (onBehalfOf != msg.sender) {
|
||||||
|
address debtToken = reserve.getDebtTokenAddress(interestRateMode);
|
||||||
|
|
||||||
|
_borrowAllowance[debtToken][onBehalfOf][msg
|
||||||
|
.sender] = _borrowAllowance[debtToken][onBehalfOf][msg.sender].sub(
|
||||||
|
amount,
|
||||||
|
Errors.BORROW_ALLOWANCE_ARE_NOT_ENOUGH
|
||||||
|
);
|
||||||
|
}
|
||||||
_executeBorrow(
|
_executeBorrow(
|
||||||
ExecuteBorrowParams(
|
ExecuteBorrowParams(
|
||||||
asset,
|
asset,
|
||||||
msg.sender,
|
msg.sender,
|
||||||
|
onBehalfOf,
|
||||||
amount,
|
amount,
|
||||||
interestRateMode,
|
interestRateMode,
|
||||||
_reserves[asset].aTokenAddress,
|
reserve.aTokenAddress,
|
||||||
referralCode,
|
referralCode,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
@ -200,6 +261,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
uint256 rateMode,
|
uint256 rateMode,
|
||||||
address onBehalfOf
|
address onBehalfOf
|
||||||
) external override {
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
_executeRepay(asset, msg.sender, amount, rateMode, onBehalfOf);
|
_executeRepay(asset, msg.sender, amount, rateMode, onBehalfOf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,6 +323,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
* @param rateMode the rate mode that the user wants to swap
|
* @param rateMode the rate mode that the user wants to swap
|
||||||
**/
|
**/
|
||||||
function swapBorrowRateMode(address asset, uint256 rateMode) external override {
|
function swapBorrowRateMode(address asset, uint256 rateMode) external override {
|
||||||
|
whenNotPaused();
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
|
||||||
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve);
|
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve);
|
||||||
|
@ -304,6 +367,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
* @param user the address of the user to be rebalanced
|
* @param user the address of the user to be rebalanced
|
||||||
**/
|
**/
|
||||||
function rebalanceStableBorrowRate(address asset, address user) external override {
|
function rebalanceStableBorrowRate(address asset, address user) external override {
|
||||||
|
whenNotPaused();
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
|
||||||
IStableDebtToken stableDebtToken = IStableDebtToken(reserve.stableDebtTokenAddress);
|
IStableDebtToken stableDebtToken = IStableDebtToken(reserve.stableDebtTokenAddress);
|
||||||
|
@ -349,6 +413,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
* @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise.
|
* @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise.
|
||||||
**/
|
**/
|
||||||
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override {
|
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override {
|
||||||
|
whenNotPaused();
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
|
||||||
ValidationLogic.validateSetUseReserveAsCollateral(
|
ValidationLogic.validateSetUseReserveAsCollateral(
|
||||||
|
@ -385,6 +450,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
uint256 purchaseAmount,
|
uint256 purchaseAmount,
|
||||||
bool receiveAToken
|
bool receiveAToken
|
||||||
) external override {
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||||
|
|
||||||
//solium-disable-next-line
|
//solium-disable-next-line
|
||||||
|
@ -441,6 +507,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
address receiver,
|
address receiver,
|
||||||
bytes calldata params
|
bytes calldata params
|
||||||
) external override {
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
require(!_flashLiquidationLocked, Errors.REENTRANCY_NOT_ALLOWED);
|
require(!_flashLiquidationLocked, Errors.REENTRANCY_NOT_ALLOWED);
|
||||||
_flashLiquidationLocked = true;
|
_flashLiquidationLocked = true;
|
||||||
|
|
||||||
|
@ -488,6 +555,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
bytes calldata params,
|
bytes calldata params,
|
||||||
uint16 referralCode
|
uint16 referralCode
|
||||||
) external override {
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
FlashLoanLocalVars memory vars;
|
FlashLoanLocalVars memory vars;
|
||||||
|
|
||||||
|
@ -523,6 +591,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
ExecuteBorrowParams(
|
ExecuteBorrowParams(
|
||||||
asset,
|
asset,
|
||||||
msg.sender,
|
msg.sender,
|
||||||
|
msg.sender,
|
||||||
vars.amountPlusPremium.sub(vars.availableBalance),
|
vars.amountPlusPremium.sub(vars.availableBalance),
|
||||||
mode,
|
mode,
|
||||||
vars.aTokenAddress,
|
vars.aTokenAddress,
|
||||||
|
@ -533,6 +602,44 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||||
|
* - It's not possible to release one asset to swap for the same
|
||||||
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||||
|
* @param fromAsset Asset to swap from
|
||||||
|
* @param toAsset Asset to swap to
|
||||||
|
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||||
|
**/
|
||||||
|
function swapLiquidity(
|
||||||
|
address receiverAddress,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
bytes calldata params
|
||||||
|
) external override {
|
||||||
|
whenNotPaused();
|
||||||
|
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||||
|
|
||||||
|
//solium-disable-next-line
|
||||||
|
(bool success, bytes memory result) = liquidationManager.delegatecall(
|
||||||
|
abi.encodeWithSignature(
|
||||||
|
'swapLiquidity(address,address,address,uint256,bytes)',
|
||||||
|
receiverAddress,
|
||||||
|
fromAsset,
|
||||||
|
toAsset,
|
||||||
|
amountToSwap,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
);
|
||||||
|
require(success, Errors.FAILED_COLLATERAL_SWAP);
|
||||||
|
|
||||||
|
(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
|
||||||
|
|
||||||
|
if (returnCode != 0) {
|
||||||
|
revert(string(abi.encodePacked(returnMessage)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev accessory functions to fetch data from the core contract
|
* @dev accessory functions to fetch data from the core contract
|
||||||
**/
|
**/
|
||||||
|
@ -706,7 +813,8 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
address stableDebtAddress,
|
address stableDebtAddress,
|
||||||
address variableDebtAddress,
|
address variableDebtAddress,
|
||||||
address interestRateStrategyAddress
|
address interestRateStrategyAddress
|
||||||
) external override onlyLendingPoolConfigurator {
|
) external override {
|
||||||
|
onlyLendingPoolConfigurator();
|
||||||
_reserves[asset].init(
|
_reserves[asset].init(
|
||||||
aTokenAddress,
|
aTokenAddress,
|
||||||
stableDebtAddress,
|
stableDebtAddress,
|
||||||
|
@ -725,16 +833,13 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress)
|
function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress)
|
||||||
external
|
external
|
||||||
override
|
override
|
||||||
onlyLendingPoolConfigurator
|
|
||||||
{
|
{
|
||||||
|
onlyLendingPoolConfigurator();
|
||||||
_reserves[asset].interestRateStrategyAddress = rateStrategyAddress;
|
_reserves[asset].interestRateStrategyAddress = rateStrategyAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConfiguration(address asset, uint256 configuration)
|
function setConfiguration(address asset, uint256 configuration) external override {
|
||||||
external
|
onlyLendingPoolConfigurator();
|
||||||
override
|
|
||||||
onlyLendingPoolConfigurator
|
|
||||||
{
|
|
||||||
_reserves[asset].configuration.data = configuration;
|
_reserves[asset].configuration.data = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -752,6 +857,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
struct ExecuteBorrowParams {
|
struct ExecuteBorrowParams {
|
||||||
address asset;
|
address asset;
|
||||||
address user;
|
address user;
|
||||||
|
address onBehalfOf;
|
||||||
uint256 amount;
|
uint256 amount;
|
||||||
uint256 interestRateMode;
|
uint256 interestRateMode;
|
||||||
address aTokenAddress;
|
address aTokenAddress;
|
||||||
|
@ -765,7 +871,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
**/
|
**/
|
||||||
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
|
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[vars.asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[vars.asset];
|
||||||
UserConfiguration.Map storage userConfig = _usersConfig[msg.sender];
|
UserConfiguration.Map storage userConfig = _usersConfig[vars.onBehalfOf];
|
||||||
|
|
||||||
address oracle = _addressesProvider.getPriceOracle();
|
address oracle = _addressesProvider.getPriceOracle();
|
||||||
|
|
||||||
|
@ -775,7 +881,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
|
|
||||||
ValidationLogic.validateBorrow(
|
ValidationLogic.validateBorrow(
|
||||||
reserve,
|
reserve,
|
||||||
vars.asset,
|
vars.onBehalfOf,
|
||||||
vars.amount,
|
vars.amount,
|
||||||
amountInETH,
|
amountInETH,
|
||||||
vars.interestRateMode,
|
vars.interestRateMode,
|
||||||
|
@ -802,12 +908,12 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
currentStableRate = reserve.currentStableBorrowRate;
|
currentStableRate = reserve.currentStableBorrowRate;
|
||||||
|
|
||||||
IStableDebtToken(reserve.stableDebtTokenAddress).mint(
|
IStableDebtToken(reserve.stableDebtTokenAddress).mint(
|
||||||
vars.user,
|
vars.onBehalfOf,
|
||||||
vars.amount,
|
vars.amount,
|
||||||
currentStableRate
|
currentStableRate
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.user, vars.amount);
|
IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.onBehalfOf, vars.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
reserve.updateInterestRates(
|
reserve.updateInterestRates(
|
||||||
|
@ -818,12 +924,13 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (vars.releaseUnderlying) {
|
if (vars.releaseUnderlying) {
|
||||||
IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount);
|
IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit Borrow(
|
emit Borrow(
|
||||||
vars.asset,
|
vars.asset,
|
||||||
msg.sender,
|
vars.user,
|
||||||
|
vars.onBehalfOf,
|
||||||
vars.amount,
|
vars.amount,
|
||||||
vars.interestRateMode,
|
vars.interestRateMode,
|
||||||
ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE
|
ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE
|
||||||
|
@ -883,6 +990,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
address user,
|
address user,
|
||||||
uint256 amount
|
uint256 amount
|
||||||
) external override view returns (bool) {
|
) external override view returns (bool) {
|
||||||
|
whenNotPaused();
|
||||||
return
|
return
|
||||||
GenericLogic.balanceDecreaseAllowed(
|
GenericLogic.balanceDecreaseAllowed(
|
||||||
asset,
|
asset,
|
||||||
|
@ -908,4 +1016,26 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
function getAddressesProvider() external view returns (ILendingPoolAddressesProvider) {
|
function getAddressesProvider() external view returns (ILendingPoolAddressesProvider) {
|
||||||
return _addressesProvider;
|
return _addressesProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Set the _pause state
|
||||||
|
* @param val the boolean value to set the current pause state of LendingPool
|
||||||
|
*/
|
||||||
|
function setPause(bool val) external override {
|
||||||
|
onlyLendingPoolConfigurator();
|
||||||
|
|
||||||
|
_paused = val;
|
||||||
|
if (_paused) {
|
||||||
|
emit Paused();
|
||||||
|
} else {
|
||||||
|
emit Unpaused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns if the LendingPool is paused
|
||||||
|
*/
|
||||||
|
function paused() external override view returns (bool) {
|
||||||
|
return _paused;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -582,4 +582,12 @@ contract LendingPoolConfigurator is VersionedInitializable {
|
||||||
|
|
||||||
proxy.upgradeToAndCall(implementation, params);
|
proxy.upgradeToAndCall(implementation, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev pauses or unpauses LendingPool actions
|
||||||
|
* @param val the boolean value to set the current pause state of LendingPool
|
||||||
|
**/
|
||||||
|
function setPoolPause(bool val) external onlyLendingPoolManager {
|
||||||
|
pool.setPause(val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,13 @@ import {PercentageMath} from '../libraries/math/PercentageMath.sol';
|
||||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||||
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
||||||
import {Errors} from '../libraries/helpers/Errors.sol';
|
import {Errors} from '../libraries/helpers/Errors.sol';
|
||||||
|
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title LendingPoolLiquidationManager contract
|
* @title LendingPoolLiquidationManager contract
|
||||||
* @author Aave
|
* @author Aave
|
||||||
* @notice Implements the liquidation function.
|
* @notice Implements the liquidation function.
|
||||||
|
* @dev LendingPoolLiquidationManager inherits Pausable from OpenZeppelin to have the same storage layout as LendingPool
|
||||||
**/
|
**/
|
||||||
contract LendingPoolLiquidationManager is VersionedInitializable {
|
contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
using SafeERC20 for IERC20;
|
using SafeERC20 for IERC20;
|
||||||
|
@ -43,10 +45,12 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
|
|
||||||
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
||||||
mapping(address => UserConfiguration.Map) internal usersConfig;
|
mapping(address => UserConfiguration.Map) internal usersConfig;
|
||||||
|
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
|
||||||
|
|
||||||
address[] internal reservesList;
|
address[] internal reservesList;
|
||||||
|
|
||||||
bool internal _flashLiquidationLocked;
|
bool internal _flashLiquidationLocked;
|
||||||
|
bool public _paused;
|
||||||
|
|
||||||
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
|
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
|
||||||
|
|
||||||
|
@ -88,15 +92,6 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
uint256 swappedCollateralAmount
|
uint256 swappedCollateralAmount
|
||||||
);
|
);
|
||||||
|
|
||||||
enum LiquidationErrors {
|
|
||||||
NO_ERROR,
|
|
||||||
NO_COLLATERAL_AVAILABLE,
|
|
||||||
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
|
||||||
CURRRENCY_NOT_BORROWED,
|
|
||||||
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
|
||||||
NOT_ENOUGH_LIQUIDITY
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LiquidationCallLocalVars {
|
struct LiquidationCallLocalVars {
|
||||||
uint256 userCollateralBalance;
|
uint256 userCollateralBalance;
|
||||||
uint256 userStableDebt;
|
uint256 userStableDebt;
|
||||||
|
@ -112,6 +107,29 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
uint256 healthFactor;
|
uint256 healthFactor;
|
||||||
IAToken collateralAtoken;
|
IAToken collateralAtoken;
|
||||||
bool isCollateralEnabled;
|
bool isCollateralEnabled;
|
||||||
|
address principalAToken;
|
||||||
|
uint256 errorCode;
|
||||||
|
string errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwapLiquidityLocalVars {
|
||||||
|
uint256 healthFactor;
|
||||||
|
uint256 amountToReceive;
|
||||||
|
uint256 userBalanceBefore;
|
||||||
|
IAToken fromReserveAToken;
|
||||||
|
IAToken toReserveAToken;
|
||||||
|
uint256 errorCode;
|
||||||
|
string errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AvailableCollateralToLiquidateLocalVars {
|
||||||
|
uint256 userCompoundedBorrowBalance;
|
||||||
|
uint256 liquidationBonus;
|
||||||
|
uint256 collateralPrice;
|
||||||
|
uint256 principalCurrencyPrice;
|
||||||
|
uint256 maxAmountCollateralToLiquidate;
|
||||||
|
uint256 principalDecimals;
|
||||||
|
uint256 collateralDecimals;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,8 +156,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
uint256 purchaseAmount,
|
uint256 purchaseAmount,
|
||||||
bool receiveAToken
|
bool receiveAToken
|
||||||
) external returns (uint256, string memory) {
|
) external returns (uint256, string memory) {
|
||||||
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
|
||||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||||
|
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
||||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||||
|
|
||||||
LiquidationCallLocalVars memory vars;
|
LiquidationCallLocalVars memory vars;
|
||||||
|
@ -152,43 +170,29 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
addressesProvider.getPriceOracle()
|
addressesProvider.getPriceOracle()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
|
||||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
|
||||||
|
|
||||||
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
|
||||||
|
|
||||||
vars.isCollateralEnabled =
|
|
||||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
|
||||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
|
||||||
|
|
||||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
|
||||||
if (!vars.isCollateralEnabled) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
|
||||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
|
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
|
||||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
|
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
|
||||||
user,
|
user,
|
||||||
principalReserve
|
principalReserve
|
||||||
);
|
);
|
||||||
|
|
||||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
|
||||||
return (
|
collateralReserve,
|
||||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
principalReserve,
|
||||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
userConfig,
|
||||||
);
|
vars.healthFactor,
|
||||||
|
vars.userStableDebt,
|
||||||
|
vars.userVariableDebt
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||||
|
return (vars.errorCode, vars.errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
//all clear - calculate the max principal amount that can be liquidated
|
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
||||||
|
|
||||||
|
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
||||||
|
|
||||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
||||||
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
||||||
);
|
);
|
||||||
|
@ -224,7 +228,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
);
|
);
|
||||||
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
|
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
|
||||||
return (
|
return (
|
||||||
uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
uint256(Errors.LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
||||||
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
|
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -296,7 +300,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
receiveAToken
|
receiveAToken
|
||||||
);
|
);
|
||||||
|
|
||||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -319,9 +323,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
address receiver,
|
address receiver,
|
||||||
bytes calldata params
|
bytes calldata params
|
||||||
) external returns (uint256, string memory) {
|
) external returns (uint256, string memory) {
|
||||||
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
|
||||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||||
|
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
||||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||||
|
|
||||||
LiquidationCallLocalVars memory vars;
|
LiquidationCallLocalVars memory vars;
|
||||||
|
@ -334,36 +337,20 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
addressesProvider.getPriceOracle()
|
addressesProvider.getPriceOracle()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
|
||||||
msg.sender != user && vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
|
||||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.sender != user) {
|
|
||||||
vars.isCollateralEnabled =
|
|
||||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
|
||||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
|
||||||
|
|
||||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
|
||||||
if (!vars.isCollateralEnabled) {
|
|
||||||
return (
|
|
||||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
|
||||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
|
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
|
||||||
|
|
||||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateRepayWithCollateral(
|
||||||
return (
|
collateralReserve,
|
||||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
debtReserve,
|
||||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
userConfig,
|
||||||
);
|
user,
|
||||||
|
vars.healthFactor,
|
||||||
|
vars.userStableDebt,
|
||||||
|
vars.userVariableDebt
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||||
|
return (vars.errorCode, vars.errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
|
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
|
||||||
|
@ -407,7 +394,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
address principalAToken = debtReserve.aTokenAddress;
|
vars.principalAToken = debtReserve.aTokenAddress;
|
||||||
|
|
||||||
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||||
ISwapAdapter(receiver).executeOperation(
|
ISwapAdapter(receiver).executeOperation(
|
||||||
|
@ -420,8 +407,13 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
|
|
||||||
//updating debt reserve
|
//updating debt reserve
|
||||||
debtReserve.updateCumulativeIndexesAndTimestamp();
|
debtReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
debtReserve.updateInterestRates(principal, principalAToken, vars.actualAmountToLiquidate, 0);
|
debtReserve.updateInterestRates(
|
||||||
IERC20(principal).transferFrom(receiver, principalAToken, vars.actualAmountToLiquidate);
|
principal,
|
||||||
|
vars.principalAToken,
|
||||||
|
vars.actualAmountToLiquidate,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
IERC20(principal).transferFrom(receiver, vars.principalAToken, vars.actualAmountToLiquidate);
|
||||||
|
|
||||||
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
||||||
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
|
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
|
||||||
|
@ -453,17 +445,100 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
vars.maxCollateralToLiquidate
|
vars.maxCollateralToLiquidate
|
||||||
);
|
);
|
||||||
|
|
||||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AvailableCollateralToLiquidateLocalVars {
|
/**
|
||||||
uint256 userCompoundedBorrowBalance;
|
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||||
uint256 liquidationBonus;
|
* - It's not possible to release one asset to swap for the same
|
||||||
uint256 collateralPrice;
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||||
uint256 principalCurrencyPrice;
|
* @param fromAsset Asset to swap from
|
||||||
uint256 maxAmountCollateralToLiquidate;
|
* @param toAsset Asset to swap to
|
||||||
uint256 principalDecimals;
|
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||||
uint256 collateralDecimals;
|
**/
|
||||||
|
function swapLiquidity(
|
||||||
|
address receiverAddress,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
bytes calldata params
|
||||||
|
) external returns (uint256, string memory) {
|
||||||
|
ReserveLogic.ReserveData storage fromReserve = reserves[fromAsset];
|
||||||
|
ReserveLogic.ReserveData storage toReserve = reserves[toAsset];
|
||||||
|
|
||||||
|
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
|
||||||
|
SwapLiquidityLocalVars memory vars;
|
||||||
|
|
||||||
|
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateSwapLiquidity(
|
||||||
|
fromReserve,
|
||||||
|
toReserve,
|
||||||
|
fromAsset,
|
||||||
|
toAsset
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||||
|
return (vars.errorCode, vars.errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
vars.fromReserveAToken = IAToken(fromReserve.aTokenAddress);
|
||||||
|
vars.toReserveAToken = IAToken(toReserve.aTokenAddress);
|
||||||
|
|
||||||
|
fromReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
|
toReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
|
|
||||||
|
if (vars.fromReserveAToken.balanceOf(msg.sender) == amountToSwap) {
|
||||||
|
usersConfig[msg.sender].setUsingAsCollateral(fromReserve.id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromReserve.updateInterestRates(fromAsset, address(vars.fromReserveAToken), 0, amountToSwap);
|
||||||
|
|
||||||
|
vars.fromReserveAToken.burn(
|
||||||
|
msg.sender,
|
||||||
|
receiverAddress,
|
||||||
|
amountToSwap,
|
||||||
|
fromReserve.liquidityIndex
|
||||||
|
);
|
||||||
|
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||||
|
ISwapAdapter(receiverAddress).executeOperation(
|
||||||
|
fromAsset,
|
||||||
|
toAsset,
|
||||||
|
amountToSwap,
|
||||||
|
address(this),
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
vars.amountToReceive = IERC20(toAsset).balanceOf(receiverAddress);
|
||||||
|
if (vars.amountToReceive != 0) {
|
||||||
|
IERC20(toAsset).transferFrom(
|
||||||
|
receiverAddress,
|
||||||
|
address(vars.toReserveAToken),
|
||||||
|
vars.amountToReceive
|
||||||
|
);
|
||||||
|
vars.toReserveAToken.mint(msg.sender, vars.amountToReceive, toReserve.liquidityIndex);
|
||||||
|
toReserve.updateInterestRates(
|
||||||
|
toAsset,
|
||||||
|
address(vars.toReserveAToken),
|
||||||
|
vars.amountToReceive,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
|
||||||
|
msg.sender,
|
||||||
|
reserves,
|
||||||
|
usersConfig[msg.sender],
|
||||||
|
reservesList,
|
||||||
|
addressesProvider.getPriceOracle()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (vars.healthFactor < GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD),
|
||||||
|
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,8 +38,11 @@ library Errors {
|
||||||
string public constant INCONSISTENT_PROTOCOL_ACTUAL_BALANCE = '26'; // 'The actual balance of the protocol is inconsistent'
|
string public constant INCONSISTENT_PROTOCOL_ACTUAL_BALANCE = '26'; // 'The actual balance of the protocol is inconsistent'
|
||||||
string public constant CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27'; // 'The actual balance of the protocol is inconsistent'
|
string public constant CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27'; // 'The actual balance of the protocol is inconsistent'
|
||||||
string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected
|
string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected
|
||||||
string public constant REENTRANCY_NOT_ALLOWED = '52';
|
string public constant BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54'; // User borrows on behalf, but allowance are too small
|
||||||
|
string public constant REENTRANCY_NOT_ALLOWED = '57';
|
||||||
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
||||||
|
string public constant FAILED_COLLATERAL_SWAP = '55';
|
||||||
|
string public constant INVALID_EQUAL_ASSETS_TO_SWAP = '56';
|
||||||
|
|
||||||
// require error messages - aToken
|
// require error messages - aToken
|
||||||
string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool'
|
string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool'
|
||||||
|
@ -73,4 +76,18 @@ library Errors {
|
||||||
string public constant MULTIPLICATION_OVERFLOW = '44';
|
string public constant MULTIPLICATION_OVERFLOW = '44';
|
||||||
string public constant ADDITION_OVERFLOW = '45';
|
string public constant ADDITION_OVERFLOW = '45';
|
||||||
string public constant DIVISION_BY_ZERO = '46';
|
string public constant DIVISION_BY_ZERO = '46';
|
||||||
|
|
||||||
|
// pausable error message
|
||||||
|
string public constant IS_PAUSED = '58'; // 'Pool is paused'
|
||||||
|
enum LiquidationErrors {
|
||||||
|
NO_ERROR,
|
||||||
|
NO_COLLATERAL_AVAILABLE,
|
||||||
|
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
||||||
|
CURRRENCY_NOT_BORROWED,
|
||||||
|
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
||||||
|
NOT_ENOUGH_LIQUIDITY,
|
||||||
|
NO_ACTIVE_RESERVE,
|
||||||
|
HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD,
|
||||||
|
INVALID_EQUAL_ASSETS_TO_SWAP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,28 @@ library ReserveLogic {
|
||||||
return cumulated;
|
return cumulated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev returns an address of the debt token used for particular interest rate mode on asset.
|
||||||
|
* @param reserve the reserve object
|
||||||
|
* @param interestRateMode - STABLE or VARIABLE from ReserveLogic.InterestRateMode enum
|
||||||
|
* @return an address of the corresponding debt token from reserve configuration
|
||||||
|
**/
|
||||||
|
function getDebtTokenAddress(ReserveLogic.ReserveData storage reserve, uint256 interestRateMode)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (address)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
ReserveLogic.InterestRateMode.STABLE == ReserveLogic.InterestRateMode(interestRateMode) ||
|
||||||
|
ReserveLogic.InterestRateMode.VARIABLE == ReserveLogic.InterestRateMode(interestRateMode),
|
||||||
|
Errors.INVALID_INTEREST_RATE_MODE_SELECTED
|
||||||
|
);
|
||||||
|
return
|
||||||
|
ReserveLogic.InterestRateMode.STABLE == ReserveLogic.InterestRateMode(interestRateMode)
|
||||||
|
? reserve.stableDebtTokenAddress
|
||||||
|
: reserve.variableDebtTokenAddress;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
|
* @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
|
||||||
* a formal specification.
|
* a formal specification.
|
||||||
|
@ -165,7 +187,7 @@ library ReserveLogic {
|
||||||
ReserveData storage reserve,
|
ReserveData storage reserve,
|
||||||
uint256 totalLiquidity,
|
uint256 totalLiquidity,
|
||||||
uint256 amount
|
uint256 amount
|
||||||
) internal {
|
) external {
|
||||||
uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay());
|
uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay());
|
||||||
|
|
||||||
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
|
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
|
||||||
|
@ -227,7 +249,7 @@ library ReserveLogic {
|
||||||
address aTokenAddress,
|
address aTokenAddress,
|
||||||
uint256 liquidityAdded,
|
uint256 liquidityAdded,
|
||||||
uint256 liquidityTaken
|
uint256 liquidityTaken
|
||||||
) internal {
|
) external {
|
||||||
UpdateInterestRatesLocalVars memory vars;
|
UpdateInterestRatesLocalVars memory vars;
|
||||||
|
|
||||||
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
|
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
|
||||||
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
|
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
|
||||||
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
||||||
import {Errors} from '../helpers/Errors.sol';
|
import {Errors} from '../helpers/Errors.sol';
|
||||||
|
import {Helpers} from '../helpers/Helpers.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title ReserveLogic library
|
* @title ReserveLogic library
|
||||||
|
@ -100,7 +101,7 @@ library ValidationLogic {
|
||||||
/**
|
/**
|
||||||
* @dev validates a borrow.
|
* @dev validates a borrow.
|
||||||
* @param reserve the reserve state from which the user is borrowing
|
* @param reserve the reserve state from which the user is borrowing
|
||||||
* @param reserveAddress the address of the reserve
|
* @param userAddress the address of the user
|
||||||
* @param amount the amount to be borrowed
|
* @param amount the amount to be borrowed
|
||||||
* @param amountInETH the amount to be borrowed, in ETH
|
* @param amountInETH the amount to be borrowed, in ETH
|
||||||
* @param interestRateMode the interest rate mode at which the user is borrowing
|
* @param interestRateMode the interest rate mode at which the user is borrowing
|
||||||
|
@ -113,7 +114,7 @@ library ValidationLogic {
|
||||||
|
|
||||||
function validateBorrow(
|
function validateBorrow(
|
||||||
ReserveLogic.ReserveData storage reserve,
|
ReserveLogic.ReserveData storage reserve,
|
||||||
address reserveAddress,
|
address userAddress,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint256 amountInETH,
|
uint256 amountInETH,
|
||||||
uint256 interestRateMode,
|
uint256 interestRateMode,
|
||||||
|
@ -151,7 +152,7 @@ library ValidationLogic {
|
||||||
vars.currentLiquidationThreshold,
|
vars.currentLiquidationThreshold,
|
||||||
vars.healthFactor
|
vars.healthFactor
|
||||||
) = GenericLogic.calculateUserAccountData(
|
) = GenericLogic.calculateUserAccountData(
|
||||||
msg.sender,
|
userAddress,
|
||||||
reservesData,
|
reservesData,
|
||||||
userConfig,
|
userConfig,
|
||||||
reserves,
|
reserves,
|
||||||
|
@ -192,7 +193,7 @@ library ValidationLogic {
|
||||||
require(
|
require(
|
||||||
!userConfig.isUsingAsCollateral(reserve.id) ||
|
!userConfig.isUsingAsCollateral(reserve.id) ||
|
||||||
reserve.configuration.getLtv() == 0 ||
|
reserve.configuration.getLtv() == 0 ||
|
||||||
amount > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
|
amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress),
|
||||||
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
|
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -329,4 +330,139 @@ library ValidationLogic {
|
||||||
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
|
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
|
||||||
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
|
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Validates the liquidationCall() action
|
||||||
|
* @param collateralReserve The reserve data of the collateral
|
||||||
|
* @param principalReserve The reserve data of the principal
|
||||||
|
* @param userConfig The user configuration
|
||||||
|
* @param userHealthFactor The user's health factor
|
||||||
|
* @param userStableDebt Total stable debt balance of the user
|
||||||
|
* @param userVariableDebt Total variable debt balance of the user
|
||||||
|
**/
|
||||||
|
function validateLiquidationCall(
|
||||||
|
ReserveLogic.ReserveData storage collateralReserve,
|
||||||
|
ReserveLogic.ReserveData storage principalReserve,
|
||||||
|
UserConfiguration.Map storage userConfig,
|
||||||
|
uint256 userHealthFactor,
|
||||||
|
uint256 userStableDebt,
|
||||||
|
uint256 userVariableDebt
|
||||||
|
) internal view returns (uint256, string memory) {
|
||||||
|
if (
|
||||||
|
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
|
||||||
|
) {
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||||
|
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||||
|
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||||
|
|
||||||
|
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||||
|
if (!isCollateralEnabled) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||||
|
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||||
|
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Validates the repayWithCollateral() action
|
||||||
|
* @param collateralReserve The reserve data of the collateral
|
||||||
|
* @param principalReserve The reserve data of the principal
|
||||||
|
* @param userConfig The user configuration
|
||||||
|
* @param user The address of the user
|
||||||
|
* @param userHealthFactor The user's health factor
|
||||||
|
* @param userStableDebt Total stable debt balance of the user
|
||||||
|
* @param userVariableDebt Total variable debt balance of the user
|
||||||
|
**/
|
||||||
|
function validateRepayWithCollateral(
|
||||||
|
ReserveLogic.ReserveData storage collateralReserve,
|
||||||
|
ReserveLogic.ReserveData storage principalReserve,
|
||||||
|
UserConfiguration.Map storage userConfig,
|
||||||
|
address user,
|
||||||
|
uint256 userHealthFactor,
|
||||||
|
uint256 userStableDebt,
|
||||||
|
uint256 userVariableDebt
|
||||||
|
) internal view returns (uint256, string memory) {
|
||||||
|
if (
|
||||||
|
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
|
||||||
|
) {
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
msg.sender != user && userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||||
|
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.sender != user) {
|
||||||
|
bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||||
|
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||||
|
|
||||||
|
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||||
|
if (!isCollateralEnabled) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||||
|
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||||
|
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Validates the swapLiquidity() action
|
||||||
|
* @param fromReserve The reserve data of the asset to swap from
|
||||||
|
* @param toReserve The reserve data of the asset to swap to
|
||||||
|
* @param fromAsset Address of the asset to swap from
|
||||||
|
* @param toAsset Address of the asset to swap to
|
||||||
|
**/
|
||||||
|
function validateSwapLiquidity(
|
||||||
|
ReserveLogic.ReserveData storage fromReserve,
|
||||||
|
ReserveLogic.ReserveData storage toReserve,
|
||||||
|
address fromAsset,
|
||||||
|
address toAsset
|
||||||
|
) internal view returns (uint256, string memory) {
|
||||||
|
if (!fromReserve.configuration.getActive() || !toReserve.configuration.getActive()) {
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromAsset == toAsset) {
|
||||||
|
return (
|
||||||
|
uint256(Errors.LiquidationErrors.INVALID_EQUAL_ASSETS_TO_SWAP),
|
||||||
|
Errors.INVALID_EQUAL_ASSETS_TO_SWAP
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,22 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
|
|
||||||
uint256 public constant UINT_MAX_VALUE = uint256(-1);
|
uint256 public constant UINT_MAX_VALUE = uint256(-1);
|
||||||
address public immutable UNDERLYING_ASSET_ADDRESS;
|
address public immutable UNDERLYING_ASSET_ADDRESS;
|
||||||
uint256 public constant ATOKEN_REVISION = 0x1;
|
|
||||||
LendingPool public immutable POOL;
|
LendingPool public immutable POOL;
|
||||||
|
|
||||||
|
/// @dev owner => next valid nonce to submit with permit()
|
||||||
|
mapping(address => uint256) public _nonces;
|
||||||
|
|
||||||
|
uint256 public constant ATOKEN_REVISION = 0x1;
|
||||||
|
|
||||||
|
bytes32 public DOMAIN_SEPARATOR;
|
||||||
|
bytes public constant EIP712_REVISION = bytes('1');
|
||||||
|
bytes32 internal constant EIP712_DOMAIN = keccak256(
|
||||||
|
'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
|
||||||
|
);
|
||||||
|
bytes32 public constant PERMIT_TYPEHASH = keccak256(
|
||||||
|
'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'
|
||||||
|
);
|
||||||
|
|
||||||
modifier onlyLendingPool {
|
modifier onlyLendingPool {
|
||||||
require(msg.sender == address(POOL), Errors.CALLER_MUST_BE_LENDING_POOL);
|
require(msg.sender == address(POOL), Errors.CALLER_MUST_BE_LENDING_POOL);
|
||||||
_;
|
_;
|
||||||
|
@ -51,6 +64,23 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
string calldata tokenName,
|
string calldata tokenName,
|
||||||
string calldata tokenSymbol
|
string calldata tokenSymbol
|
||||||
) external virtual initializer {
|
) external virtual initializer {
|
||||||
|
uint256 chainId;
|
||||||
|
|
||||||
|
//solium-disable-next-line
|
||||||
|
assembly {
|
||||||
|
chainId := chainid()
|
||||||
|
}
|
||||||
|
|
||||||
|
DOMAIN_SEPARATOR = keccak256(
|
||||||
|
abi.encode(
|
||||||
|
EIP712_DOMAIN,
|
||||||
|
keccak256(bytes(tokenName)),
|
||||||
|
keccak256(EIP712_REVISION),
|
||||||
|
chainId,
|
||||||
|
address(this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
_setName(tokenName);
|
_setName(tokenName);
|
||||||
_setSymbol(tokenSymbol);
|
_setSymbol(tokenSymbol);
|
||||||
_setDecimals(underlyingAssetDecimals);
|
_setDecimals(underlyingAssetDecimals);
|
||||||
|
@ -78,6 +108,9 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
//transfers the underlying to the target
|
//transfers the underlying to the target
|
||||||
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount);
|
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount);
|
||||||
|
|
||||||
|
//transfer event to track balances
|
||||||
|
emit Transfer(user, address(0), amount);
|
||||||
|
|
||||||
emit Burn(msg.sender, receiverOfUnderlying, amount, index);
|
emit Burn(msg.sender, receiverOfUnderlying, amount, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +130,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
//mint an equivalent amount of tokens to cover the new deposit
|
//mint an equivalent amount of tokens to cover the new deposit
|
||||||
_mint(user, scaledAmount);
|
_mint(user, scaledAmount);
|
||||||
|
|
||||||
|
//transfer event to track balances
|
||||||
|
emit Transfer(address(0), user, amount);
|
||||||
emit Mint(user, amount, index);
|
emit Mint(user, amount, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +215,41 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
|
||||||
|
* @param owner the owner of the funds
|
||||||
|
* @param spender the spender
|
||||||
|
* @param value the amount
|
||||||
|
* @param deadline the deadline timestamp, type(uint256).max for max deadline
|
||||||
|
* @param v signature param
|
||||||
|
* @param s signature param
|
||||||
|
* @param r signature param
|
||||||
|
*/
|
||||||
|
function permit(
|
||||||
|
address owner,
|
||||||
|
address spender,
|
||||||
|
uint256 value,
|
||||||
|
uint256 deadline,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s
|
||||||
|
) external {
|
||||||
|
require(owner != address(0), 'INVALID_OWNER');
|
||||||
|
//solium-disable-next-line
|
||||||
|
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
|
||||||
|
uint256 currentValidNonce = _nonces[owner];
|
||||||
|
bytes32 digest = keccak256(
|
||||||
|
abi.encodePacked(
|
||||||
|
'\x19\x01',
|
||||||
|
DOMAIN_SEPARATOR,
|
||||||
|
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE');
|
||||||
|
_nonces[owner] = currentValidNonce.add(1);
|
||||||
|
_approve(owner, spender, value);
|
||||||
|
}
|
||||||
|
|
||||||
function _transfer(
|
function _transfer(
|
||||||
address from,
|
address from,
|
||||||
address to,
|
address to,
|
||||||
|
|
|
@ -9,14 +9,14 @@ import {SafeMath} from '../libraries/math/SafeMath.sol';
|
||||||
/**
|
/**
|
||||||
* @title ERC20
|
* @title ERC20
|
||||||
* @notice Basic ERC20 implementation
|
* @notice Basic ERC20 implementation
|
||||||
* @author Aave
|
* @author Aave, inspired by the Openzeppelin ERC20 implementation
|
||||||
**/
|
**/
|
||||||
contract ERC20 is Context, IERC20, IERC20Detailed {
|
contract ERC20 is Context, IERC20, IERC20Detailed {
|
||||||
using SafeMath for uint256;
|
using SafeMath for uint256;
|
||||||
|
|
||||||
mapping(address => uint256) private _balances;
|
mapping(address => uint256) internal _balances;
|
||||||
mapping(address => mapping(address => uint256)) private _allowances;
|
mapping(address => mapping(address => uint256)) private _allowances;
|
||||||
uint256 private _totalSupply;
|
uint256 internal _totalSupply;
|
||||||
string private _name;
|
string private _name;
|
||||||
string private _symbol;
|
string private _symbol;
|
||||||
uint8 private _decimals;
|
uint8 private _decimals;
|
||||||
|
@ -74,6 +74,7 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
||||||
**/
|
**/
|
||||||
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
|
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
|
||||||
_transfer(_msgSender(), recipient, amount);
|
_transfer(_msgSender(), recipient, amount);
|
||||||
|
emit Transfer(msg.sender, recipient, amount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +122,7 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
||||||
_msgSender(),
|
_msgSender(),
|
||||||
_allowances[sender][_msgSender()].sub(amount, 'ERC20: transfer amount exceeds allowance')
|
_allowances[sender][_msgSender()].sub(amount, 'ERC20: transfer amount exceeds allowance')
|
||||||
);
|
);
|
||||||
|
emit Transfer(sender, recipient, amount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +171,6 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
||||||
|
|
||||||
_balances[sender] = _balances[sender].sub(amount, 'ERC20: transfer amount exceeds balance');
|
_balances[sender] = _balances[sender].sub(amount, 'ERC20: transfer amount exceeds balance');
|
||||||
_balances[recipient] = _balances[recipient].add(amount);
|
_balances[recipient] = _balances[recipient].add(amount);
|
||||||
emit Transfer(sender, recipient, amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _mint(address account, uint256 amount) internal virtual {
|
function _mint(address account, uint256 amount) internal virtual {
|
||||||
|
@ -179,7 +180,6 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
||||||
|
|
||||||
_totalSupply = _totalSupply.add(amount);
|
_totalSupply = _totalSupply.add(amount);
|
||||||
_balances[account] = _balances[account].add(amount);
|
_balances[account] = _balances[account].add(amount);
|
||||||
emit Transfer(address(0), account, amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _burn(address account, uint256 amount) internal virtual {
|
function _burn(address account, uint256 amount) internal virtual {
|
||||||
|
@ -189,7 +189,6 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
||||||
|
|
||||||
_balances[account] = _balances[account].sub(amount, 'ERC20: burn amount exceeds balance');
|
_balances[account] = _balances[account].sub(amount, 'ERC20: burn amount exceeds balance');
|
||||||
_totalSupply = _totalSupply.sub(amount);
|
_totalSupply = _totalSupply.sub(amount);
|
||||||
emit Transfer(account, address(0), amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _approve(
|
function _approve(
|
||||||
|
|
|
@ -132,6 +132,9 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
|
||||||
|
|
||||||
_mint(user, amount.add(balanceIncrease));
|
_mint(user, amount.add(balanceIncrease));
|
||||||
|
|
||||||
|
// transfer event to track balances
|
||||||
|
emit Transfer(address(0), user, amount);
|
||||||
|
|
||||||
emit MintDebt(
|
emit MintDebt(
|
||||||
user,
|
user,
|
||||||
amount,
|
amount,
|
||||||
|
@ -180,6 +183,9 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
|
||||||
_burn(user, amount.sub(balanceIncrease));
|
_burn(user, amount.sub(balanceIncrease));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transfer event to track balances
|
||||||
|
emit Transfer(user, address(0), amount);
|
||||||
|
|
||||||
emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease);
|
emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
|
||||||
require(newUserIndex < (1 << 128), 'Debt token: Index overflow');
|
require(newUserIndex < (1 << 128), 'Debt token: Index overflow');
|
||||||
_usersData[user] = newUserIndex;
|
_usersData[user] = newUserIndex;
|
||||||
|
|
||||||
|
emit Transfer(address(0), user, amount);
|
||||||
emit MintDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex);
|
emit MintDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +109,8 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
|
||||||
}
|
}
|
||||||
_usersData[user] = newUserIndex;
|
_usersData[user] = newUserIndex;
|
||||||
|
|
||||||
|
// transfer event to track the balances
|
||||||
|
emit Transfer(user, address(0), amount);
|
||||||
emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex);
|
emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -493,9 +493,6 @@
|
||||||
"MockSwapAdapter": {
|
"MockSwapAdapter": {
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
|
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
|
||||||
},
|
|
||||||
"localhost": {
|
|
||||||
"address": "0x749258D38b0473d96FEcc14cC5e7DCE12d7Bd6f6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,18 @@ import {
|
||||||
iMultiPoolsAssets,
|
iMultiPoolsAssets,
|
||||||
IReserveParams,
|
IReserveParams,
|
||||||
PoolConfiguration,
|
PoolConfiguration,
|
||||||
tEthereumAddress,
|
|
||||||
iBasicDistributionParams,
|
iBasicDistributionParams,
|
||||||
|
ICommonConfiguration,
|
||||||
|
eEthereumNetwork,
|
||||||
} from './types';
|
} from './types';
|
||||||
import {getParamPerPool} from './contracts-helpers';
|
import {getParamPerPool} from './contracts-helpers';
|
||||||
import {AaveConfig} from '../config/aave';
|
import {AaveConfig} from '../config/aave';
|
||||||
import {UniswapConfig} from '../config/uniswap';
|
import {UniswapConfig} from '../config/uniswap';
|
||||||
|
import {CommonsConfig} from '../config/commons';
|
||||||
import {ZERO_ADDRESS} from './constants';
|
import {ZERO_ADDRESS} from './constants';
|
||||||
|
import {BRE} from './misc-utils';
|
||||||
|
import {tEthereumAddress} from './types';
|
||||||
|
import {getParamPerNetwork} from './contracts-helpers';
|
||||||
|
|
||||||
export enum ConfigNames {
|
export enum ConfigNames {
|
||||||
Commons = 'Commons',
|
Commons = 'Commons',
|
||||||
|
@ -23,6 +28,8 @@ export const loadPoolConfig = (configName: ConfigNames): PoolConfiguration => {
|
||||||
return AaveConfig;
|
return AaveConfig;
|
||||||
case ConfigNames.Uniswap:
|
case ConfigNames.Uniswap:
|
||||||
return UniswapConfig;
|
return UniswapConfig;
|
||||||
|
case ConfigNames.Commons:
|
||||||
|
return CommonsConfig;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported pool configuration: ${Object.values(ConfigNames)}`);
|
throw new Error(`Unsupported pool configuration: ${Object.values(ConfigNames)}`);
|
||||||
}
|
}
|
||||||
|
@ -55,3 +62,24 @@ export const getFeeDistributionParamsCommon = (
|
||||||
percentages,
|
percentages,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGenesisLendingPoolManagerAddress = async (config: ICommonConfiguration) => {
|
||||||
|
const currentNetwork = BRE.network.name;
|
||||||
|
const targetAddress = getParamPerNetwork(
|
||||||
|
config.LendingPoolManagerAddress,
|
||||||
|
<eEthereumNetwork>currentNetwork
|
||||||
|
);
|
||||||
|
if (targetAddress) {
|
||||||
|
return targetAddress;
|
||||||
|
}
|
||||||
|
const addressList = await Promise.all(
|
||||||
|
(await BRE.ethers.getSigners()).map((signer) => signer.getAddress())
|
||||||
|
);
|
||||||
|
const addressIndex = config.LendingPoolManagerAddressIndex;
|
||||||
|
return addressList[addressIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getATokenDomainSeparatorPerNetwork = (
|
||||||
|
network: eEthereumNetwork,
|
||||||
|
config: ICommonConfiguration
|
||||||
|
): tEthereumAddress => getParamPerNetwork<tEthereumAddress>(config.ATokenDomainSeparator, network);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
|
export const BUIDLEREVM_CHAINID = 31337;
|
||||||
|
export const COVERAGE_CHAINID = 1337;
|
||||||
export const TEST_SNAPSHOT_ID = '0x1';
|
export const TEST_SNAPSHOT_ID = '0x1';
|
||||||
|
|
||||||
export const WAD = Math.pow(10, 18).toString();
|
export const WAD = Math.pow(10, 18).toString();
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import {BRE} from './misc-utils';
|
|
||||||
import {ICommonConfiguration, eEthereumNetwork} from './types';
|
|
||||||
import {getParamPerNetwork} from './contracts-helpers';
|
|
||||||
|
|
||||||
export const getGenesisLendingPoolManagerAddress = async (config: ICommonConfiguration) => {
|
|
||||||
const currentNetwork = BRE.network.name;
|
|
||||||
const targetAddress = getParamPerNetwork(
|
|
||||||
config.LendingPoolManagerAddress,
|
|
||||||
<eEthereumNetwork>currentNetwork
|
|
||||||
);
|
|
||||||
if (targetAddress) {
|
|
||||||
return targetAddress;
|
|
||||||
}
|
|
||||||
const addressList = await Promise.all(
|
|
||||||
(await BRE.ethers.getSigners()).map((signer) => signer.getAddress())
|
|
||||||
);
|
|
||||||
const addressIndex = config.LendingPoolManagerAddressIndex;
|
|
||||||
return addressList[addressIndex];
|
|
||||||
};
|
|
|
@ -47,6 +47,8 @@ const {
|
||||||
|
|
||||||
export type MockTokenMap = {[symbol: string]: MintableERC20};
|
export type MockTokenMap = {[symbol: string]: MintableERC20};
|
||||||
import {MockSwapAdapter} from '../types/MockSwapAdapter';
|
import {MockSwapAdapter} from '../types/MockSwapAdapter';
|
||||||
|
import {signTypedData_v4, TypedData} from 'eth-sig-util';
|
||||||
|
import {fromRpcSig, ECDSASignature} from 'ethereumjs-util';
|
||||||
|
|
||||||
export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => {
|
export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => {
|
||||||
const currentNetwork = BRE.network.name;
|
const currentNetwork = BRE.network.name;
|
||||||
|
@ -553,10 +555,14 @@ const linkBytecode = (artifact: Artifact, libraries: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getParamPerNetwork = <T>(
|
export const getParamPerNetwork = <T>(
|
||||||
{kovan, ropsten, main}: iParamsPerNetwork<T>,
|
{kovan, ropsten, main, buidlerevm, coverage}: iParamsPerNetwork<T>,
|
||||||
network: eEthereumNetwork
|
network: eEthereumNetwork
|
||||||
) => {
|
) => {
|
||||||
switch (network) {
|
switch (network) {
|
||||||
|
case eEthereumNetwork.coverage:
|
||||||
|
return coverage;
|
||||||
|
case eEthereumNetwork.buidlerevm:
|
||||||
|
return buidlerevm;
|
||||||
case eEthereumNetwork.kovan:
|
case eEthereumNetwork.kovan:
|
||||||
return kovan;
|
return kovan;
|
||||||
case eEthereumNetwork.ropsten:
|
case eEthereumNetwork.ropsten:
|
||||||
|
@ -834,3 +840,55 @@ export const getLendingPoolAddressesProviderRegistry = async (address?: tEthereu
|
||||||
).address
|
).address
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildPermitParams = (
|
||||||
|
chainId: number,
|
||||||
|
token: tEthereumAddress,
|
||||||
|
revision: string,
|
||||||
|
tokenName: string,
|
||||||
|
owner: tEthereumAddress,
|
||||||
|
spender: tEthereumAddress,
|
||||||
|
nonce: number,
|
||||||
|
deadline: string,
|
||||||
|
value: tStringTokenSmallUnits
|
||||||
|
) => ({
|
||||||
|
types: {
|
||||||
|
EIP712Domain: [
|
||||||
|
{name: 'name', type: 'string'},
|
||||||
|
{name: 'version', type: 'string'},
|
||||||
|
{name: 'chainId', type: 'uint256'},
|
||||||
|
{name: 'verifyingContract', type: 'address'},
|
||||||
|
],
|
||||||
|
Permit: [
|
||||||
|
{name: 'owner', type: 'address'},
|
||||||
|
{name: 'spender', type: 'address'},
|
||||||
|
{name: 'value', type: 'uint256'},
|
||||||
|
{name: 'nonce', type: 'uint256'},
|
||||||
|
{name: 'deadline', type: 'uint256'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
primaryType: 'Permit' as const,
|
||||||
|
domain: {
|
||||||
|
name: tokenName,
|
||||||
|
version: revision,
|
||||||
|
chainId: chainId,
|
||||||
|
verifyingContract: token,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
owner,
|
||||||
|
spender,
|
||||||
|
value,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getSignatureFromTypedData = (
|
||||||
|
privateKey: string,
|
||||||
|
typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it
|
||||||
|
): ECDSASignature => {
|
||||||
|
const signature = signTypedData_v4(Buffer.from(privateKey.substring(2, 66), 'hex'), {
|
||||||
|
data: typedData,
|
||||||
|
});
|
||||||
|
return fromRpcSig(signature);
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {exit} from 'process';
|
||||||
import {BRE} from './misc-utils';
|
import {BRE} from './misc-utils';
|
||||||
|
|
||||||
export const SUPPORTED_ETHERSCAN_NETWORKS = ['main', 'ropsten', 'kovan'];
|
export const SUPPORTED_ETHERSCAN_NETWORKS = ['main', 'ropsten', 'kovan'];
|
||||||
|
@ -83,14 +84,17 @@ export const runTaskWithRetry = async (
|
||||||
export const checkVerification = () => {
|
export const checkVerification = () => {
|
||||||
const currentNetwork = BRE.network.name;
|
const currentNetwork = BRE.network.name;
|
||||||
if (!process.env.ETHERSCAN_KEY) {
|
if (!process.env.ETHERSCAN_KEY) {
|
||||||
throw Error('Missing process.env.ETHERSCAN_KEY.');
|
console.error('Missing process.env.ETHERSCAN_KEY.');
|
||||||
|
exit(3);
|
||||||
}
|
}
|
||||||
if (!process.env.ETHERSCAN_NETWORK) {
|
if (!process.env.ETHERSCAN_NETWORK) {
|
||||||
throw Error('Missing process.env.ETHERSCAN_NETWORK');
|
console.error('Missing process.env.ETHERSCAN_NETWORK');
|
||||||
|
exit(4);
|
||||||
}
|
}
|
||||||
if (!SUPPORTED_ETHERSCAN_NETWORKS.includes(currentNetwork)) {
|
if (!SUPPORTED_ETHERSCAN_NETWORKS.includes(currentNetwork)) {
|
||||||
throw Error(
|
console.error(
|
||||||
`Current network ${currentNetwork} not supported. Please change to one of the next networks: ${SUPPORTED_ETHERSCAN_NETWORKS.toString()}`
|
`Current network ${currentNetwork} not supported. Please change to one of the next networks: ${SUPPORTED_ETHERSCAN_NETWORKS.toString()}`
|
||||||
);
|
);
|
||||||
|
exit(5);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,13 @@ export enum eEthereumNetwork {
|
||||||
kovan = 'kovan',
|
kovan = 'kovan',
|
||||||
ropsten = 'ropsten',
|
ropsten = 'ropsten',
|
||||||
main = 'main',
|
main = 'main',
|
||||||
|
coverage = 'coverage',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EthereumNetworkNames {
|
||||||
|
kovan = 'kovan',
|
||||||
|
ropsten = 'ropsten',
|
||||||
|
main = 'main',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AavePools {
|
export enum AavePools {
|
||||||
|
@ -106,6 +113,8 @@ export enum ProtocolErrors {
|
||||||
NO_ERRORS = '42', // 'No errors'
|
NO_ERRORS = '42', // 'No errors'
|
||||||
INVALID_FLASHLOAN_MODE = '43', //Invalid flashloan mode
|
INVALID_FLASHLOAN_MODE = '43', //Invalid flashloan mode
|
||||||
|
|
||||||
|
IS_PAUSED = '58', // Pool is paused
|
||||||
|
|
||||||
// old
|
// old
|
||||||
|
|
||||||
INVALID_FROM_BALANCE_AFTER_TRANSFER = 'Invalid from balance after transfer',
|
INVALID_FROM_BALANCE_AFTER_TRANSFER = 'Invalid from balance after transfer',
|
||||||
|
@ -254,6 +263,8 @@ export interface IMarketRates {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface iParamsPerNetwork<T> {
|
export interface iParamsPerNetwork<T> {
|
||||||
|
[eEthereumNetwork.coverage]: T;
|
||||||
|
[eEthereumNetwork.buidlerevm]: T;
|
||||||
[eEthereumNetwork.kovan]: T;
|
[eEthereumNetwork.kovan]: T;
|
||||||
[eEthereumNetwork.ropsten]: T;
|
[eEthereumNetwork.ropsten]: T;
|
||||||
[eEthereumNetwork.main]: T;
|
[eEthereumNetwork.main]: T;
|
||||||
|
@ -330,6 +341,7 @@ export interface ICommonConfiguration {
|
||||||
LendingPoolManagerAddressIndex: number;
|
LendingPoolManagerAddressIndex: number;
|
||||||
ReserveAssets: iParamsPerNetwork<SymbolMap<tEthereumAddress>>;
|
ReserveAssets: iParamsPerNetwork<SymbolMap<tEthereumAddress>>;
|
||||||
ReservesConfig: iMultiPoolsAssets<IReserveParams>;
|
ReservesConfig: iMultiPoolsAssets<IReserveParams>;
|
||||||
|
ATokenDomainSeparator: iParamsPerNetwork<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAaveConfiguration extends ICommonConfiguration {
|
export interface IAaveConfiguration extends ICommonConfiguration {
|
||||||
|
@ -344,4 +356,4 @@ export interface ITokenAddress {
|
||||||
[token: string]: tEthereumAddress;
|
[token: string]: tEthereumAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PoolConfiguration = IAaveConfiguration | IUniswapConfiguration;
|
export type PoolConfiguration = ICommonConfiguration | IAaveConfiguration | IUniswapConfiguration;
|
||||||
|
|
9502
package-lock.json
generated
9502
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -14,7 +14,7 @@
|
||||||
"test": "buidler test",
|
"test": "buidler test",
|
||||||
"test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts",
|
"test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts",
|
||||||
"aave:evm:dev:migration": "buidler aave:dev",
|
"aave:evm:dev:migration": "buidler aave:dev",
|
||||||
"aave:evm:full:migration": "buidler aave:full",
|
"aave:evm:full:migration": "buidler aave:full --verify",
|
||||||
"aave:kovan:dev:migration": "npm run buidler:kovan -- aave:dev --verify",
|
"aave:kovan:dev:migration": "npm run buidler:kovan -- aave:dev --verify",
|
||||||
"aave:kovan:full:migration": "npm run buidler:kovan -- aave:full --verify",
|
"aave:kovan:full:migration": "npm run buidler:kovan -- aave:full --verify",
|
||||||
"aave:ropsten:dev:migration": "npm run buidler:ropsten -- aave:dev --verify",
|
"aave:ropsten:dev:migration": "npm run buidler:ropsten -- aave:dev --verify",
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
"aave:main:dev:migration": "npm run buidler:main -- aave:dev --verify",
|
"aave:main:dev:migration": "npm run buidler:main -- aave:dev --verify",
|
||||||
"aave:main:full:migration": "npm run buidler:main -- aave:full --verify",
|
"aave:main:full:migration": "npm run buidler:main -- aave:full --verify",
|
||||||
"uniswap:evm:dev:migration": "buidler uniswap:dev",
|
"uniswap:evm:dev:migration": "buidler uniswap:dev",
|
||||||
"uniswap:evm:full:migration": "buidler uniswap:full",
|
"uniswap:evm:full:migration": "buidler uniswap:full --verify",
|
||||||
"uniswap:kovan:dev:migration": "npm run buidler:kovan -- uniswap:dev --verify",
|
"uniswap:kovan:dev:migration": "npm run buidler:kovan -- uniswap:dev --verify",
|
||||||
"uniswap:kovan:full:migration": "npm run buidler:kovan -- uniswap:full --verify",
|
"uniswap:kovan:full:migration": "npm run buidler:kovan -- uniswap:full --verify",
|
||||||
"uniswap:ropsten:dev:migration": "npm run buidler:ropsten -- uniswap:dev --verify",
|
"uniswap:ropsten:dev:migration": "npm run buidler:ropsten -- uniswap:dev --verify",
|
||||||
|
@ -31,7 +31,11 @@
|
||||||
"uniswap:main:full:migration": "npm run buidler:main -- uniswap:full --verify",
|
"uniswap:main:full:migration": "npm run buidler:main -- uniswap:full --verify",
|
||||||
"test-repay-with-collateral": "buidler test test/__setup.spec.ts test/repay-with-collateral.spec.ts",
|
"test-repay-with-collateral": "buidler test test/__setup.spec.ts test/repay-with-collateral.spec.ts",
|
||||||
"test-liquidate-with-collateral": "buidler test test/__setup.spec.ts test/flash-liquidation-with-collateral.spec.ts",
|
"test-liquidate-with-collateral": "buidler test test/__setup.spec.ts test/flash-liquidation-with-collateral.spec.ts",
|
||||||
|
"test-transfers": "buidler test test/__setup.spec.ts test/atoken-transfer.spec.ts",
|
||||||
"test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts",
|
"test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts",
|
||||||
|
"test-liquidate": "buidler test test/__setup.spec.ts test/liquidation-atoken.spec.ts",
|
||||||
|
"test-pausable": "buidler test test/__setup.spec.ts test/pausable-functions.spec.ts",
|
||||||
|
"test-permit": "buidler test test/__setup.spec.ts test/atoken-permit.spec.ts",
|
||||||
"dev:coverage": "buidler coverage --network coverage",
|
"dev:coverage": "buidler coverage --network coverage",
|
||||||
"dev:deployment": "buidler dev-deployment",
|
"dev:deployment": "buidler dev-deployment",
|
||||||
"dev:deployExample": "buidler deploy-Example",
|
"dev:deployExample": "buidler deploy-Example",
|
||||||
|
@ -74,7 +78,9 @@
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"tslint-plugin-prettier": "^2.3.0",
|
"tslint-plugin-prettier": "^2.3.0",
|
||||||
"typechain": "2.0.0",
|
"typechain": "2.0.0",
|
||||||
"typescript": "3.9.3"
|
"typescript": "3.9.3",
|
||||||
|
"eth-sig-util": "2.5.3",
|
||||||
|
"ethereumjs-util": "7.0.2"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
|
|
@ -6,8 +6,11 @@ import {
|
||||||
getLendingPoolAddressesProviderRegistry,
|
getLendingPoolAddressesProviderRegistry,
|
||||||
} from '../../helpers/contracts-helpers';
|
} from '../../helpers/contracts-helpers';
|
||||||
import {waitForTx} from '../../helpers/misc-utils';
|
import {waitForTx} from '../../helpers/misc-utils';
|
||||||
import {ConfigNames, loadPoolConfig} from '../../helpers/configuration';
|
import {
|
||||||
import {getGenesisLendingPoolManagerAddress} from '../../helpers/contracts-getters';
|
ConfigNames,
|
||||||
|
loadPoolConfig,
|
||||||
|
getGenesisLendingPoolManagerAddress,
|
||||||
|
} from '../../helpers/configuration';
|
||||||
import {eEthereumNetwork} from '../../helpers/types';
|
import {eEthereumNetwork} from '../../helpers/types';
|
||||||
|
|
||||||
task(
|
task(
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import {task} from '@nomiclabs/buidler/config';
|
import {task} from '@nomiclabs/buidler/config';
|
||||||
import {checkVerification} from '../../helpers/etherscan-verification';
|
import {checkVerification} from '../../helpers/etherscan-verification';
|
||||||
import {ConfigNames} from '../../helpers/configuration';
|
import {ConfigNames} from '../../helpers/configuration';
|
||||||
|
import {EthereumNetworkNames} from '../../helpers/types';
|
||||||
|
|
||||||
task('aave:full', 'Deploy development enviroment')
|
task('aave:full', 'Deploy development enviroment')
|
||||||
.addOptionalParam('verify', 'Verify contracts at Etherscan')
|
.addFlag('verify', 'Verify contracts at Etherscan')
|
||||||
.setAction(async ({verify}, localBRE) => {
|
.setAction(async ({verify}, localBRE) => {
|
||||||
const POOL_NAME = ConfigNames.Aave;
|
const POOL_NAME = ConfigNames.Aave;
|
||||||
|
const network = <EthereumNetworkNames>localBRE.network.name;
|
||||||
|
|
||||||
await localBRE.run('set-bre');
|
await localBRE.run('set-bre');
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {checkVerification} from '../../helpers/etherscan-verification';
|
||||||
import {ConfigNames} from '../../helpers/configuration';
|
import {ConfigNames} from '../../helpers/configuration';
|
||||||
|
|
||||||
task('uniswap:full', 'Deploy development enviroment')
|
task('uniswap:full', 'Deploy development enviroment')
|
||||||
.addOptionalParam('verify', 'Verify contracts at Etherscan')
|
.addFlag('verify', 'Verify contracts at Etherscan')
|
||||||
.setAction(async ({verify}, localBRE) => {
|
.setAction(async ({verify}, localBRE) => {
|
||||||
const POOL_NAME = ConfigNames.Uniswap;
|
const POOL_NAME = ConfigNames.Uniswap;
|
||||||
|
|
||||||
|
|
315
test/atoken-permit.spec.ts
Normal file
315
test/atoken-permit.spec.ts
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
import {MAX_UINT_AMOUNT, ZERO_ADDRESS, BUIDLEREVM_CHAINID} from '../helpers/constants';
|
||||||
|
import {buildPermitParams, getSignatureFromTypedData} from '../helpers/contracts-helpers';
|
||||||
|
import {expect} from 'chai';
|
||||||
|
import {ethers} from 'ethers';
|
||||||
|
import {eEthereumNetwork} from '../helpers/types';
|
||||||
|
import {makeSuite, TestEnv} from './helpers/make-suite';
|
||||||
|
import {BRE} from '../helpers/misc-utils';
|
||||||
|
import {
|
||||||
|
ConfigNames,
|
||||||
|
getATokenDomainSeparatorPerNetwork,
|
||||||
|
loadPoolConfig,
|
||||||
|
} from '../helpers/configuration';
|
||||||
|
import {waitForTx} from '../helpers/misc-utils';
|
||||||
|
|
||||||
|
const {parseEther} = ethers.utils;
|
||||||
|
|
||||||
|
makeSuite('AToken: Permit', (testEnv: TestEnv) => {
|
||||||
|
const poolConfig = loadPoolConfig(ConfigNames.Commons);
|
||||||
|
|
||||||
|
it('Checks the domain separator', async () => {
|
||||||
|
const DOMAIN_SEPARATOR_ENCODED = getATokenDomainSeparatorPerNetwork(
|
||||||
|
eEthereumNetwork.buidlerevm,
|
||||||
|
poolConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
const {aDai} = testEnv;
|
||||||
|
|
||||||
|
const separator = await aDai.DOMAIN_SEPARATOR();
|
||||||
|
|
||||||
|
expect(separator).to.be.equal(DOMAIN_SEPARATOR_ENCODED, 'Invalid domain separator');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Get aDAI for tests', async () => {
|
||||||
|
const {dai, deployer, pool} = testEnv;
|
||||||
|
|
||||||
|
await dai.mint(parseEther('20000'));
|
||||||
|
await dai.approve(pool.address, parseEther('20000'));
|
||||||
|
await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Reverts submitting a permit with 0 expiration', async () => {
|
||||||
|
const {aDai, deployer, users} = testEnv;
|
||||||
|
const owner = deployer;
|
||||||
|
const spender = users[1];
|
||||||
|
|
||||||
|
const tokenName = await aDai.name();
|
||||||
|
|
||||||
|
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||||
|
const expiration = 0;
|
||||||
|
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||||
|
const permitAmount = ethers.utils.parseEther('2').toString();
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
aDai.address,
|
||||||
|
'1',
|
||||||
|
tokenName,
|
||||||
|
owner.address,
|
||||||
|
spender.address,
|
||||||
|
nonce,
|
||||||
|
permitAmount,
|
||||||
|
expiration.toFixed()
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||||
|
'0',
|
||||||
|
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||||
|
);
|
||||||
|
|
||||||
|
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
aDai
|
||||||
|
.connect(spender.signer)
|
||||||
|
.permit(owner.address, spender.address, permitAmount, expiration, v, r, s)
|
||||||
|
).to.be.revertedWith('INVALID_EXPIRATION');
|
||||||
|
|
||||||
|
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||||
|
'0',
|
||||||
|
'INVALID_ALLOWANCE_AFTER_PERMIT'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Submits a permit with maximum expiration length', async () => {
|
||||||
|
const {aDai, deployer, users} = testEnv;
|
||||||
|
const owner = deployer;
|
||||||
|
const spender = users[1];
|
||||||
|
|
||||||
|
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||||
|
const deadline = MAX_UINT_AMOUNT;
|
||||||
|
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||||
|
const permitAmount = parseEther('2').toString();
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
aDai.address,
|
||||||
|
'1',
|
||||||
|
await aDai.name(),
|
||||||
|
owner.address,
|
||||||
|
spender.address,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||||
|
'0',
|
||||||
|
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||||
|
);
|
||||||
|
|
||||||
|
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await waitForTx(
|
||||||
|
await aDai
|
||||||
|
.connect(spender.signer)
|
||||||
|
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect((await aDai._nonces(owner.address)).toNumber()).to.be.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Cancels the previous permit', async () => {
|
||||||
|
const {aDai, deployer, users} = testEnv;
|
||||||
|
const owner = deployer;
|
||||||
|
const spender = users[1];
|
||||||
|
|
||||||
|
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||||
|
const deadline = MAX_UINT_AMOUNT;
|
||||||
|
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||||
|
const permitAmount = '0';
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
aDai.address,
|
||||||
|
'1',
|
||||||
|
await aDai.name(),
|
||||||
|
owner.address,
|
||||||
|
spender.address,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||||
|
ethers.utils.parseEther('2'),
|
||||||
|
'INVALID_ALLOWANCE_BEFORE_PERMIT'
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitForTx(
|
||||||
|
await aDai
|
||||||
|
.connect(spender.signer)
|
||||||
|
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
|
||||||
|
);
|
||||||
|
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
|
||||||
|
permitAmount,
|
||||||
|
'INVALID_ALLOWANCE_AFTER_PERMIT'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect((await aDai._nonces(owner.address)).toNumber()).to.be.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to submit a permit with invalid nonce', async () => {
|
||||||
|
const {aDai, deployer, users} = testEnv;
|
||||||
|
const owner = deployer;
|
||||||
|
const spender = users[1];
|
||||||
|
|
||||||
|
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||||
|
const deadline = MAX_UINT_AMOUNT;
|
||||||
|
const nonce = 1000;
|
||||||
|
const permitAmount = '0';
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
aDai.address,
|
||||||
|
'1',
|
||||||
|
await aDai.name(),
|
||||||
|
owner.address,
|
||||||
|
spender.address,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
aDai
|
||||||
|
.connect(spender.signer)
|
||||||
|
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
|
||||||
|
).to.be.revertedWith('INVALID_SIGNATURE');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to submit a permit with invalid expiration (previous to the current block)', async () => {
|
||||||
|
const {aDai, deployer, users} = testEnv;
|
||||||
|
const owner = deployer;
|
||||||
|
const spender = users[1];
|
||||||
|
|
||||||
|
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||||
|
const expiration = '1';
|
||||||
|
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||||
|
const permitAmount = '0';
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
aDai.address,
|
||||||
|
'1',
|
||||||
|
await aDai.name(),
|
||||||
|
owner.address,
|
||||||
|
spender.address,
|
||||||
|
nonce,
|
||||||
|
expiration,
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
aDai
|
||||||
|
.connect(spender.signer)
|
||||||
|
.permit(owner.address, spender.address, expiration, permitAmount, v, r, s)
|
||||||
|
).to.be.revertedWith('INVALID_EXPIRATION');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to submit a permit with invalid signature', async () => {
|
||||||
|
const {aDai, deployer, users} = testEnv;
|
||||||
|
const owner = deployer;
|
||||||
|
const spender = users[1];
|
||||||
|
|
||||||
|
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||||
|
const deadline = MAX_UINT_AMOUNT;
|
||||||
|
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||||
|
const permitAmount = '0';
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
aDai.address,
|
||||||
|
'1',
|
||||||
|
await aDai.name(),
|
||||||
|
owner.address,
|
||||||
|
spender.address,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
aDai
|
||||||
|
.connect(spender.signer)
|
||||||
|
.permit(owner.address, ZERO_ADDRESS, permitAmount, deadline, v, r, s)
|
||||||
|
).to.be.revertedWith('INVALID_SIGNATURE');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to submit a permit with invalid owner', async () => {
|
||||||
|
const {aDai, deployer, users} = testEnv;
|
||||||
|
const owner = deployer;
|
||||||
|
const spender = users[1];
|
||||||
|
|
||||||
|
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
|
||||||
|
const expiration = MAX_UINT_AMOUNT;
|
||||||
|
const nonce = (await aDai._nonces(owner.address)).toNumber();
|
||||||
|
const permitAmount = '0';
|
||||||
|
const msgParams = buildPermitParams(
|
||||||
|
chainId,
|
||||||
|
aDai.address,
|
||||||
|
'1',
|
||||||
|
await aDai.name(),
|
||||||
|
owner.address,
|
||||||
|
spender.address,
|
||||||
|
nonce,
|
||||||
|
expiration,
|
||||||
|
permitAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
|
||||||
|
if (!ownerPrivateKey) {
|
||||||
|
throw new Error('INVALID_OWNER_PK');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
aDai
|
||||||
|
.connect(spender.signer)
|
||||||
|
.permit(ZERO_ADDRESS, spender.address, expiration, permitAmount, v, r, s)
|
||||||
|
).to.be.revertedWith('INVALID_OWNER');
|
||||||
|
});
|
||||||
|
});
|
|
@ -17,6 +17,7 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
// ZERO_COLLATERAL,
|
// ZERO_COLLATERAL,
|
||||||
COLLATERAL_BALANCE_IS_0,
|
COLLATERAL_BALANCE_IS_0,
|
||||||
TRANSFER_NOT_ALLOWED,
|
TRANSFER_NOT_ALLOWED,
|
||||||
|
IS_PAUSED,
|
||||||
} = ProtocolErrors;
|
} = ProtocolErrors;
|
||||||
|
|
||||||
it('User 0 deposits 1000 DAI, transfers to user 1', async () => {
|
it('User 0 deposits 1000 DAI, transfers to user 1', async () => {
|
||||||
|
@ -59,7 +60,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
await expect(
|
await expect(
|
||||||
pool
|
pool
|
||||||
.connect(users[1].signer)
|
.connect(users[1].signer)
|
||||||
.borrow(weth.address, ethers.utils.parseEther('0.1'), RateMode.Stable, AAVE_REFERRAL),
|
.borrow(
|
||||||
|
weth.address,
|
||||||
|
ethers.utils.parseEther('0.1'),
|
||||||
|
RateMode.Stable,
|
||||||
|
AAVE_REFERRAL,
|
||||||
|
users[1].address
|
||||||
|
),
|
||||||
COLLATERAL_BALANCE_IS_0
|
COLLATERAL_BALANCE_IS_0
|
||||||
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
|
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
|
||||||
});
|
});
|
||||||
|
@ -72,7 +79,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(users[1].signer)
|
.connect(users[1].signer)
|
||||||
.borrow(weth.address, ethers.utils.parseEther('0.1'), RateMode.Stable, AAVE_REFERRAL);
|
.borrow(
|
||||||
|
weth.address,
|
||||||
|
ethers.utils.parseEther('0.1'),
|
||||||
|
RateMode.Stable,
|
||||||
|
AAVE_REFERRAL,
|
||||||
|
users[1].address
|
||||||
|
);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer),
|
aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer),
|
||||||
|
|
198
test/collateral-swap.spec.ts
Normal file
198
test/collateral-swap.spec.ts
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
import {makeSuite, TestEnv} from './helpers/make-suite';
|
||||||
|
import {MockSwapAdapter} from '../types/MockSwapAdapter';
|
||||||
|
import {getMockSwapAdapter} from '../helpers/contracts-helpers';
|
||||||
|
import {ProtocolErrors} from '../helpers/types';
|
||||||
|
import {ethers} from 'ethers';
|
||||||
|
import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants';
|
||||||
|
import {getContractsData, getTxCostAndTimestamp} from './helpers/actions';
|
||||||
|
import {calcExpectedATokenBalance} from './helpers/utils/calculations';
|
||||||
|
import {waitForTx} from '../helpers/misc-utils';
|
||||||
|
import {advanceBlock, timeLatest} from '../helpers/misc-utils';
|
||||||
|
|
||||||
|
const {expect} = require('chai');
|
||||||
|
|
||||||
|
makeSuite('LendingPool SwapDeposit function', (testEnv: TestEnv) => {
|
||||||
|
let _mockSwapAdapter = {} as MockSwapAdapter;
|
||||||
|
const {HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD} = ProtocolErrors;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
_mockSwapAdapter = await getMockSwapAdapter();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposits WETH into the reserve', async () => {
|
||||||
|
const {pool, weth, users} = testEnv;
|
||||||
|
const amountToDeposit = ethers.utils.parseEther('1');
|
||||||
|
|
||||||
|
for (const signer of [weth.signer, users[2].signer]) {
|
||||||
|
const connectedWETH = weth.connect(signer);
|
||||||
|
await connectedWETH.mint(amountToDeposit);
|
||||||
|
await connectedWETH.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
await pool
|
||||||
|
.connect(signer)
|
||||||
|
.deposit(weth.address, amountToDeposit, await signer.getAddress(), '0');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('User tries to swap more then he can, revert expected', async () => {
|
||||||
|
const {pool, weth, dai} = testEnv;
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
ethers.utils.parseEther('1.1'),
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('55');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to swap asset on equal asset, revert expected', async () => {
|
||||||
|
const {pool, weth} = testEnv;
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
weth.address,
|
||||||
|
ethers.utils.parseEther('0.1'),
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('56');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to swap more then available on the reserve', async () => {
|
||||||
|
const {pool, weth, dai, users, aEth, deployer} = testEnv;
|
||||||
|
|
||||||
|
await pool.borrow(weth.address, ethers.utils.parseEther('0.1'), 1, 0, deployer.address);
|
||||||
|
await pool.connect(users[2].signer).withdraw(weth.address, ethers.utils.parseEther('1'));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
ethers.utils.parseEther('1'),
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('55');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to swap correct amount', async () => {
|
||||||
|
const {pool, weth, dai, aEth, aDai} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
|
const amountToSwap = ethers.utils.parseEther('0.25');
|
||||||
|
|
||||||
|
const amountToReturn = ethers.utils.parseEther('0.5');
|
||||||
|
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: wethReserveDataBefore,
|
||||||
|
userData: wethUserDataBefore,
|
||||||
|
} = await getContractsData(weth.address, userAddress, testEnv);
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
userAddress,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const reserveBalanceWETHBefore = await weth.balanceOf(aEth.address);
|
||||||
|
const reserveBalanceDAIBefore = await dai.balanceOf(aDai.address);
|
||||||
|
|
||||||
|
const txReceipt = await waitForTx(
|
||||||
|
await pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
amountToSwap,
|
||||||
|
'0x10'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const {txTimestamp} = await getTxCostAndTimestamp(txReceipt);
|
||||||
|
const userATokenBalanceWETHAfter = await aEth.balanceOf(userAddress);
|
||||||
|
const userATokenBalanceDAIAfter = await aDai.balanceOf(userAddress);
|
||||||
|
|
||||||
|
const reserveBalanceWETHAfter = await weth.balanceOf(aEth.address);
|
||||||
|
const reserveBalanceDAIAfter = await dai.balanceOf(aDai.address);
|
||||||
|
|
||||||
|
expect(userATokenBalanceWETHAfter.toString()).to.be.equal(
|
||||||
|
calcExpectedATokenBalance(wethReserveDataBefore, wethUserDataBefore, txTimestamp)
|
||||||
|
.minus(amountToSwap.toString())
|
||||||
|
.toString(),
|
||||||
|
'was burned incorrect amount of user funds'
|
||||||
|
);
|
||||||
|
expect(userATokenBalanceDAIAfter.toString()).to.be.equal(
|
||||||
|
calcExpectedATokenBalance(daiReserveDataBefore, daiUserDataBefore, txTimestamp)
|
||||||
|
.plus(amountToReturn.toString())
|
||||||
|
.toString(),
|
||||||
|
'was minted incorrect amount of user funds'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reserveBalanceWETHAfter.toString()).to.be.equal(
|
||||||
|
reserveBalanceWETHBefore.sub(amountToSwap).toString(),
|
||||||
|
'was sent incorrect amount if reserve funds'
|
||||||
|
);
|
||||||
|
expect(reserveBalanceDAIAfter.toString()).to.be.equal(
|
||||||
|
reserveBalanceDAIBefore.add(amountToReturn).toString(),
|
||||||
|
'was received incorrect amount if reserve funds'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User tries to drop HF below one', async () => {
|
||||||
|
const {pool, weth, dai, deployer} = testEnv;
|
||||||
|
const amountToSwap = ethers.utils.parseEther('0.3');
|
||||||
|
|
||||||
|
const amountToReturn = ethers.utils.parseEther('0.5');
|
||||||
|
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
|
||||||
|
|
||||||
|
await pool.borrow(weth.address, ethers.utils.parseEther('0.3'), 1, 0, deployer.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.swapLiquidity(_mockSwapAdapter.address, weth.address, dai.address, amountToSwap, '0x10')
|
||||||
|
).to.be.revertedWith(HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set usage as collateral to false if no leftovers after swap', async () => {
|
||||||
|
const {pool, weth, dai, aEth, users} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
|
|
||||||
|
// add more liquidity to allow user 0 to swap everything he has
|
||||||
|
await weth.connect(users[2].signer).mint(ethers.utils.parseEther('1'));
|
||||||
|
await pool
|
||||||
|
.connect(users[2].signer)
|
||||||
|
.deposit(weth.address, ethers.utils.parseEther('1'), users[2].address, '0');
|
||||||
|
|
||||||
|
// cleanup borrowings, to be abe to swap whole weth
|
||||||
|
const amountToRepay = ethers.utils.parseEther('0.5');
|
||||||
|
await weth.mint(amountToRepay);
|
||||||
|
await pool.repay(weth.address, amountToRepay, '1', userAddress);
|
||||||
|
const txTimestamp = (await timeLatest()).plus(100);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: wethReserveDataBefore,
|
||||||
|
userData: wethUserDataBefore,
|
||||||
|
} = await getContractsData(weth.address, userAddress, testEnv);
|
||||||
|
const amountToSwap = calcExpectedATokenBalance(
|
||||||
|
wethReserveDataBefore,
|
||||||
|
wethUserDataBefore,
|
||||||
|
txTimestamp.plus('1')
|
||||||
|
);
|
||||||
|
|
||||||
|
await advanceBlock(txTimestamp.toNumber());
|
||||||
|
|
||||||
|
await pool.swapLiquidity(
|
||||||
|
_mockSwapAdapter.address,
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
amountToSwap.toString(),
|
||||||
|
'0x10'
|
||||||
|
);
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
userAddress,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.equal(
|
||||||
|
false,
|
||||||
|
'usageAsCollateralEnabled are not set to false'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -16,7 +16,7 @@ const {expect} = require('chai');
|
||||||
const {parseUnits, parseEther} = ethers.utils;
|
const {parseUnits, parseEther} = ethers.utils;
|
||||||
|
|
||||||
makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEnv) => {
|
makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEnv) => {
|
||||||
const {INVALID_HF, COLLATERAL_CANNOT_BE_LIQUIDATED} = ProtocolErrors;
|
const {INVALID_HF, COLLATERAL_CANNOT_BE_LIQUIDATED, IS_PAUSED} = ProtocolErrors;
|
||||||
|
|
||||||
it('User 1 provides some liquidity for others to borrow', async () => {
|
it('User 1 provides some liquidity for others to borrow', async () => {
|
||||||
const {pool, weth, dai, usdc, deployer} = testEnv;
|
const {pool, weth, dai, usdc, deployer} = testEnv;
|
||||||
|
@ -46,9 +46,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
||||||
|
|
||||||
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0);
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0, user.address);
|
||||||
|
|
||||||
const {userData: wethUserDataBefore} = await getContractsData(
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
weth.address,
|
weth.address,
|
||||||
|
@ -202,7 +202,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
||||||
.toFixed(0)
|
.toFixed(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 0);
|
await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 0, user.address);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('User 5 liquidates half the USDC loan of User 3 by swapping his WETH collateral', async () => {
|
it('User 5 liquidates half the USDC loan of User 3 by swapping his WETH collateral', async () => {
|
||||||
|
@ -463,7 +463,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
||||||
.toFixed(0)
|
.toFixed(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0);
|
await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0, user.address);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
||||||
|
@ -735,7 +735,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
|
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
|
||||||
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
|
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0);
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address);
|
||||||
|
|
||||||
const amountToRepay = amountToBorrowVariable;
|
const amountToRepay = amountToBorrowVariable;
|
||||||
|
|
||||||
|
@ -843,7 +845,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
|
||||||
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
|
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Liquidator tries to liquidates User 5 USDC loan by swapping his WETH collateral, should revert due WETH collateral disabled', async () => {
|
it('Liquidator tries to liquidates User 5 USDC loan by swapping his WETH collateral, should revert due WETH collateral disabled', async () => {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import {TestEnv, makeSuite} from './helpers/make-suite';
|
import {TestEnv, makeSuite} from './helpers/make-suite';
|
||||||
import {APPROVAL_AMOUNT_LENDING_POOL, oneRay} from '../helpers/constants';
|
import {APPROVAL_AMOUNT_LENDING_POOL, oneRay} from '../helpers/constants';
|
||||||
import {
|
import {
|
||||||
|
@ -8,7 +10,6 @@ import {
|
||||||
import {ethers} from 'ethers';
|
import {ethers} from 'ethers';
|
||||||
import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver';
|
import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver';
|
||||||
import {ProtocolErrors, eContractid} from '../helpers/types';
|
import {ProtocolErrors, eContractid} from '../helpers/types';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import {VariableDebtToken} from '../types/VariableDebtToken';
|
import {VariableDebtToken} from '../types/VariableDebtToken';
|
||||||
import {StableDebtToken} from '../types/StableDebtToken';
|
import {StableDebtToken} from '../types/StableDebtToken';
|
||||||
|
|
||||||
|
@ -22,13 +23,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
TRANSFER_AMOUNT_EXCEEDS_BALANCE,
|
TRANSFER_AMOUNT_EXCEEDS_BALANCE,
|
||||||
INVALID_FLASHLOAN_MODE,
|
INVALID_FLASHLOAN_MODE,
|
||||||
SAFEERC20_LOWLEVEL_CALL,
|
SAFEERC20_LOWLEVEL_CALL,
|
||||||
|
IS_PAUSED,
|
||||||
} = ProtocolErrors;
|
} = ProtocolErrors;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
|
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Deposits ETH into the reserve', async () => {
|
it('Deposits WETH into the reserve', async () => {
|
||||||
const {pool, weth} = testEnv;
|
const {pool, weth} = testEnv;
|
||||||
const userAddress = await pool.signer.getAddress();
|
const userAddress = await pool.signer.getAddress();
|
||||||
const amountToDeposit = ethers.utils.parseEther('1');
|
const amountToDeposit = ethers.utils.parseEther('1');
|
||||||
|
|
|
@ -280,11 +280,41 @@ export const withdraw = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const delegateBorrowAllowance = async (
|
||||||
|
reserveSymbol: string,
|
||||||
|
amount: string,
|
||||||
|
interestRateMode: string,
|
||||||
|
user: SignerWithAddress,
|
||||||
|
receiver: tEthereumAddress,
|
||||||
|
expectedResult: string,
|
||||||
|
testEnv: TestEnv,
|
||||||
|
revertMessage?: string
|
||||||
|
) => {
|
||||||
|
const {pool} = testEnv;
|
||||||
|
|
||||||
|
const reserve = await getReserveAddressFromSymbol(reserveSymbol);
|
||||||
|
const amountToDelegate = await convertToCurrencyDecimals(reserve, amount);
|
||||||
|
|
||||||
|
const delegateAllowancePromise = pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.delegateBorrowAllowance(reserve, receiver, interestRateMode, amountToDelegate.toString());
|
||||||
|
if (expectedResult === 'revert') {
|
||||||
|
await expect(delegateAllowancePromise, revertMessage).to.be.reverted;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
await delegateAllowancePromise;
|
||||||
|
expect(
|
||||||
|
(await pool.getBorrowAllowance(user.address, receiver, reserve, interestRateMode)).toString()
|
||||||
|
).to.be.equal(amountToDelegate.toString(), 'borrowAllowance are set incorrectly');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const borrow = async (
|
export const borrow = async (
|
||||||
reserveSymbol: string,
|
reserveSymbol: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
interestRateMode: string,
|
interestRateMode: string,
|
||||||
user: SignerWithAddress,
|
user: SignerWithAddress,
|
||||||
|
onBehalfOf: tEthereumAddress,
|
||||||
timeTravel: string,
|
timeTravel: string,
|
||||||
expectedResult: string,
|
expectedResult: string,
|
||||||
testEnv: TestEnv,
|
testEnv: TestEnv,
|
||||||
|
@ -296,15 +326,18 @@ export const borrow = async (
|
||||||
|
|
||||||
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
|
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
|
||||||
reserve,
|
reserve,
|
||||||
user.address,
|
onBehalfOf,
|
||||||
testEnv
|
testEnv,
|
||||||
|
user.address
|
||||||
);
|
);
|
||||||
|
|
||||||
const amountToBorrow = await convertToCurrencyDecimals(reserve, amount);
|
const amountToBorrow = await convertToCurrencyDecimals(reserve, amount);
|
||||||
|
|
||||||
if (expectedResult === 'success') {
|
if (expectedResult === 'success') {
|
||||||
const txResult = await waitForTx(
|
const txResult = await waitForTx(
|
||||||
await pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0')
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.borrow(reserve, amountToBorrow, interestRateMode, '0', onBehalfOf)
|
||||||
);
|
);
|
||||||
|
|
||||||
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
||||||
|
@ -319,7 +352,7 @@ export const borrow = async (
|
||||||
reserveData: reserveDataAfter,
|
reserveData: reserveDataAfter,
|
||||||
userData: userDataAfter,
|
userData: userDataAfter,
|
||||||
timestamp,
|
timestamp,
|
||||||
} = await getContractsData(reserve, user.address, testEnv);
|
} = await getContractsData(reserve, onBehalfOf, testEnv, user.address);
|
||||||
|
|
||||||
const expectedReserveData = calcExpectedReserveDataAfterBorrow(
|
const expectedReserveData = calcExpectedReserveDataAfterBorrow(
|
||||||
amountToBorrow.toString(),
|
amountToBorrow.toString(),
|
||||||
|
@ -366,7 +399,7 @@ export const borrow = async (
|
||||||
// });
|
// });
|
||||||
} else if (expectedResult === 'revert') {
|
} else if (expectedResult === 'revert') {
|
||||||
await expect(
|
await expect(
|
||||||
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0'),
|
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0', onBehalfOf),
|
||||||
revertMessage
|
revertMessage
|
||||||
).to.be.reverted;
|
).to.be.reverted;
|
||||||
}
|
}
|
||||||
|
@ -686,7 +719,7 @@ const getDataBeforeAction = async (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTxCostAndTimestamp = async (tx: ContractReceipt) => {
|
export const getTxCostAndTimestamp = async (tx: ContractReceipt) => {
|
||||||
if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) {
|
if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) {
|
||||||
throw new Error('No tx blocknumber');
|
throw new Error('No tx blocknumber');
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
setUseAsCollateral,
|
setUseAsCollateral,
|
||||||
swapBorrowRateMode,
|
swapBorrowRateMode,
|
||||||
rebalanceStableBorrowRate,
|
rebalanceStableBorrowRate,
|
||||||
|
delegateBorrowAllowance,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import {RateMode} from '../../helpers/types';
|
import {RateMode} from '../../helpers/types';
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
|
|
||||||
if (borrowRateMode) {
|
if (borrowRateMode) {
|
||||||
if (borrowRateMode === 'none') {
|
if (borrowRateMode === 'none') {
|
||||||
RateMode.None;
|
rateMode = RateMode.None;
|
||||||
} else if (borrowRateMode === 'stable') {
|
} else if (borrowRateMode === 'stable') {
|
||||||
rateMode = RateMode.Stable;
|
rateMode = RateMode.Stable;
|
||||||
} else if (borrowRateMode === 'variable') {
|
} else if (borrowRateMode === 'variable') {
|
||||||
|
@ -111,6 +112,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'delegateBorrowAllowance':
|
||||||
|
{
|
||||||
|
const {amount, toUser: toUserIndex} = action.args;
|
||||||
|
const toUser = users[parseInt(toUserIndex, 10)].address;
|
||||||
|
if (!amount || amount === '') {
|
||||||
|
throw `Invalid amount to deposit into the ${reserve} reserve`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await delegateBorrowAllowance(
|
||||||
|
reserve,
|
||||||
|
amount,
|
||||||
|
rateMode,
|
||||||
|
user,
|
||||||
|
toUser,
|
||||||
|
expected,
|
||||||
|
testEnv,
|
||||||
|
revertMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'withdraw':
|
case 'withdraw':
|
||||||
{
|
{
|
||||||
const {amount} = action.args;
|
const {amount} = action.args;
|
||||||
|
@ -124,13 +146,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
break;
|
break;
|
||||||
case 'borrow':
|
case 'borrow':
|
||||||
{
|
{
|
||||||
const {amount, timeTravel} = action.args;
|
const {amount, timeTravel, onBehalfOf: onBehalfOfIndex} = action.args;
|
||||||
|
|
||||||
|
const onBehalfOf = onBehalfOfIndex
|
||||||
|
? users[parseInt(onBehalfOfIndex)].address
|
||||||
|
: user.address;
|
||||||
|
|
||||||
if (!amount || amount === '') {
|
if (!amount || amount === '') {
|
||||||
throw `Invalid amount to borrow from the ${reserve} reserve`;
|
throw `Invalid amount to borrow from the ${reserve} reserve`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await borrow(reserve, amount, rateMode, user, timeTravel, expected, testEnv, revertMessage);
|
await borrow(
|
||||||
|
reserve,
|
||||||
|
amount,
|
||||||
|
rateMode,
|
||||||
|
user,
|
||||||
|
onBehalfOf,
|
||||||
|
timeTravel,
|
||||||
|
expected,
|
||||||
|
testEnv,
|
||||||
|
revertMessage
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
148
test/helpers/scenarios/credit-delegation.json
Normal file
148
test/helpers/scenarios/credit-delegation.json
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
{
|
||||||
|
"title": "LendingPool: credit delegation",
|
||||||
|
"description": "Test cases for the credit delegation related functions.",
|
||||||
|
"stories": [
|
||||||
|
{
|
||||||
|
"description": "User 0 deposits 1000 DAI, user 0 delegates borrowing of 1 WETH on variable to user 4, user 4 borrows 1 WETH variable on behalf of user 0",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "mint",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1000",
|
||||||
|
"user": "0"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "approve",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"user": "0"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deposit",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1000",
|
||||||
|
"user": "0"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "delegateBorrowAllowance",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "2",
|
||||||
|
"user": "0",
|
||||||
|
"borrowRateMode": "variable",
|
||||||
|
"toUser": "4"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "borrow",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1",
|
||||||
|
"user": "4",
|
||||||
|
"onBehalfOf": "0",
|
||||||
|
"borrowRateMode": "variable"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "User 4 trying to borrow 1 WETH stable on behalf of user 0, revert expected",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "borrow",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1",
|
||||||
|
"user": "4",
|
||||||
|
"onBehalfOf": "0",
|
||||||
|
"borrowRateMode": "stable"
|
||||||
|
},
|
||||||
|
"expected": "revert",
|
||||||
|
"revertMessage": "54"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "User 0 delegates borrowing of 1 WETH to user 4, user 4 borrows 3 WETH variable on behalf of user 0, revert expected",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "delegateBorrowAllowance",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1",
|
||||||
|
"user": "0",
|
||||||
|
"borrowRateMode": "variable",
|
||||||
|
"toUser": "4"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "borrow",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "3",
|
||||||
|
"user": "4",
|
||||||
|
"onBehalfOf": "0",
|
||||||
|
"borrowRateMode": "variable"
|
||||||
|
},
|
||||||
|
"expected": "revert",
|
||||||
|
"revertMessage": "54"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "User 0 delegates borrowing of 1 WETH on stable to user 2, user 2 borrows 1 WETH stable on behalf of user 0",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "delegateBorrowAllowance",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1",
|
||||||
|
"user": "0",
|
||||||
|
"borrowRateMode": "stable",
|
||||||
|
"toUser": "2"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "borrow",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1",
|
||||||
|
"user": "2",
|
||||||
|
"onBehalfOf": "0",
|
||||||
|
"borrowRateMode": "stable"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "User 0 delegates borrowing of 1 WETH to user 2 with wrong borrowRateMode, revert expected",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "delegateBorrowAllowance",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "1",
|
||||||
|
"user": "0",
|
||||||
|
"borrowRateMode": "random",
|
||||||
|
"toUser": "2"
|
||||||
|
},
|
||||||
|
"expected": "revert",
|
||||||
|
"revertMessage": "8"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -919,7 +919,7 @@ const calcExpectedScaledATokenBalance = (
|
||||||
.minus(amountTaken.rayDiv(index));
|
.minus(amountTaken.rayDiv(index));
|
||||||
};
|
};
|
||||||
|
|
||||||
const calcExpectedATokenBalance = (
|
export const calcExpectedATokenBalance = (
|
||||||
reserveDataBeforeAction: ReserveData,
|
reserveDataBeforeAction: ReserveData,
|
||||||
userDataBeforeAction: UserReserveData,
|
userDataBeforeAction: UserReserveData,
|
||||||
currentTimestamp: BigNumber
|
currentTimestamp: BigNumber
|
||||||
|
|
|
@ -21,6 +21,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
INVALID_HF,
|
INVALID_HF,
|
||||||
SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER,
|
SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER,
|
||||||
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
||||||
|
IS_PAUSED,
|
||||||
} = ProtocolErrors;
|
} = ProtocolErrors;
|
||||||
|
|
||||||
it('LIQUIDATION - Deposits WETH, borrows DAI/Check liquidation fails because health factor is above 1', async () => {
|
it('LIQUIDATION - Deposits WETH, borrows DAI/Check liquidation fails because health factor is above 1', async () => {
|
||||||
|
@ -67,7 +68,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(borrower.signer)
|
.connect(borrower.signer)
|
||||||
.borrow(dai.address, amountDAIToBorrow, RateMode.Variable, '0');
|
.borrow(dai.address, amountDAIToBorrow, RateMode.Variable, '0', borrower.address);
|
||||||
|
|
||||||
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
||||||
|
|
||||||
|
@ -265,7 +266,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(borrower.signer)
|
.connect(borrower.signer)
|
||||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
|
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
|
||||||
|
|
||||||
//drops HF below 1
|
//drops HF below 1
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {CommonsConfig} from '../config/commons';
|
||||||
|
|
||||||
const APPROVAL_AMOUNT_LENDING_POOL =
|
const APPROVAL_AMOUNT_LENDING_POOL =
|
||||||
CommonsConfig.ProtocolGlobalParams.ApprovalAmountLendingPoolCore;
|
CommonsConfig.ProtocolGlobalParams.ApprovalAmountLendingPoolCore;
|
||||||
|
import {parseEther} from 'ethers/lib/utils';
|
||||||
|
|
||||||
const chai = require('chai');
|
const chai = require('chai');
|
||||||
|
|
||||||
|
@ -27,6 +28,26 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("It's not possible to liquidate on a non-active collateral or a non active principal", async () => {
|
||||||
|
const {configurator, weth, pool, users, dai} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
await configurator.deactivateReserve(weth.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(weth.address);
|
||||||
|
|
||||||
|
await configurator.deactivateReserve(dai.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(dai.address);
|
||||||
|
});
|
||||||
|
|
||||||
it('LIQUIDATION - Deposits WETH, borrows DAI', async () => {
|
it('LIQUIDATION - Deposits WETH, borrows DAI', async () => {
|
||||||
const {dai, weth, users, pool, oracle} = testEnv;
|
const {dai, weth, users, pool, oracle} = testEnv;
|
||||||
const depositor = users[0];
|
const depositor = users[0];
|
||||||
|
@ -72,7 +93,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(borrower.signer)
|
.connect(borrower.signer)
|
||||||
.borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0');
|
.borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0', borrower.address);
|
||||||
|
|
||||||
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
|
||||||
|
|
||||||
|
@ -244,7 +265,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(borrower.signer)
|
.connect(borrower.signer)
|
||||||
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
|
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
|
||||||
|
|
||||||
//drops HF below 1
|
//drops HF below 1
|
||||||
await oracle.setAssetPrice(
|
await oracle.setAssetPrice(
|
||||||
|
|
378
test/pausable-functions.spec.ts
Normal file
378
test/pausable-functions.spec.ts
Normal file
|
@ -0,0 +1,378 @@
|
||||||
|
import {makeSuite, TestEnv} from './helpers/make-suite';
|
||||||
|
import {ProtocolErrors, RateMode} from '../helpers/types';
|
||||||
|
import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants';
|
||||||
|
import {convertToCurrencyDecimals, getMockFlashLoanReceiver} from '../helpers/contracts-helpers';
|
||||||
|
import {parseEther, parseUnits} from 'ethers/lib/utils';
|
||||||
|
import {BigNumber} from 'bignumber.js';
|
||||||
|
import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver';
|
||||||
|
|
||||||
|
const {expect} = require('chai');
|
||||||
|
|
||||||
|
makeSuite('Pausable Pool', (testEnv: TestEnv) => {
|
||||||
|
let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver;
|
||||||
|
|
||||||
|
const {
|
||||||
|
IS_PAUSED,
|
||||||
|
TRANSFER_NOT_ALLOWED,
|
||||||
|
INVALID_FROM_BALANCE_AFTER_TRANSFER,
|
||||||
|
INVALID_TO_BALANCE_AFTER_TRANSFER,
|
||||||
|
} = ProtocolErrors;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 0 deposits 1000 DAI. Configurator pauses pool. Transfers to user 1 reverts. Configurator unpauses the network and next transfer succees', async () => {
|
||||||
|
const {users, pool, dai, aDai, configurator} = testEnv;
|
||||||
|
|
||||||
|
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
|
await dai.connect(users[0].signer).mint(amountDAItoDeposit);
|
||||||
|
|
||||||
|
// user 0 deposits 1000 DAI
|
||||||
|
await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
await pool
|
||||||
|
.connect(users[0].signer)
|
||||||
|
.deposit(dai.address, amountDAItoDeposit, users[0].address, '0');
|
||||||
|
|
||||||
|
const user0Balance = await aDai.balanceOf(users[0].address);
|
||||||
|
const user1Balance = await aDai.balanceOf(users[1].address);
|
||||||
|
|
||||||
|
// Configurator pauses the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// User 0 tries the transfer to User 1
|
||||||
|
await expect(
|
||||||
|
aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit)
|
||||||
|
).to.revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
const pausedFromBalance = await aDai.balanceOf(users[0].address);
|
||||||
|
const pausedToBalance = await aDai.balanceOf(users[1].address);
|
||||||
|
|
||||||
|
expect(pausedFromBalance).to.be.equal(
|
||||||
|
user0Balance.toString(),
|
||||||
|
INVALID_TO_BALANCE_AFTER_TRANSFER
|
||||||
|
);
|
||||||
|
expect(pausedToBalance.toString()).to.be.equal(
|
||||||
|
user1Balance.toString(),
|
||||||
|
INVALID_FROM_BALANCE_AFTER_TRANSFER
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configurator unpauses the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
|
||||||
|
// User 0 succeeds transfer to User 1
|
||||||
|
await aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit);
|
||||||
|
|
||||||
|
const fromBalance = await aDai.balanceOf(users[0].address);
|
||||||
|
const toBalance = await aDai.balanceOf(users[1].address);
|
||||||
|
|
||||||
|
expect(fromBalance.toString()).to.be.equal(
|
||||||
|
user0Balance.sub(amountDAItoDeposit),
|
||||||
|
INVALID_FROM_BALANCE_AFTER_TRANSFER
|
||||||
|
);
|
||||||
|
expect(toBalance.toString()).to.be.equal(
|
||||||
|
user1Balance.add(amountDAItoDeposit),
|
||||||
|
INVALID_TO_BALANCE_AFTER_TRANSFER
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deposit', async () => {
|
||||||
|
const {users, pool, dai, aDai, configurator} = testEnv;
|
||||||
|
|
||||||
|
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
|
await dai.connect(users[0].signer).mint(amountDAItoDeposit);
|
||||||
|
|
||||||
|
// user 0 deposits 1000 DAI
|
||||||
|
await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
// Configurator pauses the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
await expect(
|
||||||
|
pool.connect(users[0].signer).deposit(dai.address, amountDAItoDeposit, users[0].address, '0')
|
||||||
|
).to.revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Configurator unpauses the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Withdraw', async () => {
|
||||||
|
const {users, pool, dai, aDai, configurator} = testEnv;
|
||||||
|
|
||||||
|
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
|
await dai.connect(users[0].signer).mint(amountDAItoDeposit);
|
||||||
|
|
||||||
|
// user 0 deposits 1000 DAI
|
||||||
|
await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
await pool
|
||||||
|
.connect(users[0].signer)
|
||||||
|
.deposit(dai.address, amountDAItoDeposit, users[0].address, '0');
|
||||||
|
|
||||||
|
// Configurator pauses the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// user tries to burn
|
||||||
|
await expect(
|
||||||
|
pool.connect(users[0].signer).withdraw(dai.address, amountDAItoDeposit)
|
||||||
|
).to.revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Configurator unpauses the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DelegateBorrowAllowance', async () => {
|
||||||
|
const {pool, dai, users, configurator} = testEnv;
|
||||||
|
|
||||||
|
const user = users[1];
|
||||||
|
const toUser = users[2];
|
||||||
|
// Pause the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// Try to execute liquidation
|
||||||
|
await expect(
|
||||||
|
pool.connect(user.signer).delegateBorrowAllowance(dai.address, toUser.address, '1', '1')
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Borrow', async () => {
|
||||||
|
const {pool, dai, users, configurator} = testEnv;
|
||||||
|
|
||||||
|
const user = users[1];
|
||||||
|
// Pause the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// Try to execute liquidation
|
||||||
|
await expect(
|
||||||
|
pool.connect(user.signer).borrow(dai.address, '1', '1', '0', user.address)
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Swap liquidity', async () => {
|
||||||
|
const {pool, dai, weth, users, configurator} = testEnv;
|
||||||
|
|
||||||
|
const user = users[1];
|
||||||
|
// Pause the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// Try to execute liquidation
|
||||||
|
await expect(
|
||||||
|
pool.connect(user.signer).swapLiquidity(user.address, dai.address, weth.address, '1', '0x')
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Repay', async () => {
|
||||||
|
const {pool, dai, users, configurator} = testEnv;
|
||||||
|
|
||||||
|
const user = users[1];
|
||||||
|
// Pause the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// Try to execute liquidation
|
||||||
|
await expect(pool.connect(user.signer).repay(dai.address, '1', '1', user.address)).revertedWith(
|
||||||
|
IS_PAUSED
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unpause the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Repay with collateral', async () => {
|
||||||
|
const {pool, weth, dai, usdc, users, mockSwapAdapter, oracle, configurator} = testEnv;
|
||||||
|
const user = users[6];
|
||||||
|
const liquidator = users[5];
|
||||||
|
|
||||||
|
// Pause the pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// Try to execute liquidation
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
'1',
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause the pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Flash loan', async () => {
|
||||||
|
const {dai, pool, weth, users, configurator} = testEnv;
|
||||||
|
|
||||||
|
const caller = users[3];
|
||||||
|
|
||||||
|
const flashAmount = parseEther('0.8');
|
||||||
|
|
||||||
|
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
|
||||||
|
|
||||||
|
// Pause pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(caller.signer)
|
||||||
|
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0')
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Liquidation call', async () => {
|
||||||
|
const {users, pool, usdc, oracle, weth, configurator} = testEnv;
|
||||||
|
const depositor = users[3];
|
||||||
|
const borrower = users[4];
|
||||||
|
|
||||||
|
//mints USDC to depositor
|
||||||
|
await usdc
|
||||||
|
.connect(depositor.signer)
|
||||||
|
.mint(await convertToCurrencyDecimals(usdc.address, '1000'));
|
||||||
|
|
||||||
|
//approve protocol to access depositor wallet
|
||||||
|
await usdc.connect(depositor.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
//user 3 deposits 1000 USDC
|
||||||
|
const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
|
||||||
|
|
||||||
|
await pool
|
||||||
|
.connect(depositor.signer)
|
||||||
|
.deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0');
|
||||||
|
|
||||||
|
//user 4 deposits 1 ETH
|
||||||
|
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
||||||
|
|
||||||
|
//mints WETH to borrower
|
||||||
|
await weth.connect(borrower.signer).mint(amountETHtoDeposit);
|
||||||
|
|
||||||
|
//approve protocol to access borrower wallet
|
||||||
|
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
await pool
|
||||||
|
.connect(borrower.signer)
|
||||||
|
.deposit(weth.address, amountETHtoDeposit, borrower.address, '0');
|
||||||
|
|
||||||
|
//user 4 borrows
|
||||||
|
const userGlobalData = await pool.getUserAccountData(borrower.address);
|
||||||
|
|
||||||
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const amountUSDCToBorrow = await convertToCurrencyDecimals(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(userGlobalData.availableBorrowsETH.toString())
|
||||||
|
.div(usdcPrice.toString())
|
||||||
|
.multipliedBy(0.9502)
|
||||||
|
.toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
await pool
|
||||||
|
.connect(borrower.signer)
|
||||||
|
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
|
||||||
|
|
||||||
|
// Drops HF below 1
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(usdcPrice.toString()).multipliedBy(1.2).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
//mints dai to the liquidator
|
||||||
|
await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000'));
|
||||||
|
await usdc.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
const userReserveDataBefore = await pool.getUserReserveData(usdc.address, borrower.address);
|
||||||
|
|
||||||
|
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString())
|
||||||
|
.multipliedBy(0.5)
|
||||||
|
.toFixed(0);
|
||||||
|
|
||||||
|
// Pause pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// Do liquidation
|
||||||
|
expect(
|
||||||
|
pool.liquidationCall(weth.address, usdc.address, borrower.address, amountToLiquidate, true)
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('SwapBorrowRateMode', async () => {
|
||||||
|
const {pool, weth, dai, usdc, users, configurator, mockSwapAdapter} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
const amountWETHToDeposit = parseEther('10');
|
||||||
|
const amountDAIToDeposit = parseEther('120');
|
||||||
|
const amountToBorrow = parseUnits('65', 6);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountWETHToDeposit);
|
||||||
|
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
await dai.connect(user.signer).mint(amountDAIToDeposit);
|
||||||
|
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||||
|
|
||||||
|
// Pause pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
// Try to repay
|
||||||
|
await expect(
|
||||||
|
pool.connect(user.signer).swapBorrowRateMode(usdc.address, RateMode.Stable)
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('RebalanceStableBorrowRate', async () => {
|
||||||
|
const {pool, dai, users, configurator} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
// Pause pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.connect(user.signer).rebalanceStableBorrowRate(dai.address, user.address)
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setUserUseReserveAsCollateral', async () => {
|
||||||
|
const {pool, weth, users, configurator} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
|
||||||
|
const amountWETHToDeposit = parseEther('1');
|
||||||
|
await weth.connect(user.signer).mint(amountWETHToDeposit);
|
||||||
|
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
// Pause pool
|
||||||
|
await configurator.setPoolPause(true);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false)
|
||||||
|
).revertedWith(IS_PAUSED);
|
||||||
|
|
||||||
|
// Unpause pool
|
||||||
|
await configurator.setPoolPause(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,8 +8,7 @@ import {
|
||||||
} from './helpers/utils/calculations';
|
} from './helpers/utils/calculations';
|
||||||
import {getContractsData} from './helpers/actions';
|
import {getContractsData} from './helpers/actions';
|
||||||
import {timeLatest, waitForTx} from '../helpers/misc-utils';
|
import {timeLatest, waitForTx} from '../helpers/misc-utils';
|
||||||
import {tEthereumAddress} from '../helpers/types';
|
import {ProtocolErrors, tEthereumAddress} from '../helpers/types';
|
||||||
import {parse} from 'path';
|
|
||||||
|
|
||||||
const {expect} = require('chai');
|
const {expect} = require('chai');
|
||||||
const {parseUnits, parseEther} = ethers.utils;
|
const {parseUnits, parseEther} = ethers.utils;
|
||||||
|
@ -38,6 +37,45 @@ export const expectRepayWithCollateralEvent = (
|
||||||
};
|
};
|
||||||
|
|
||||||
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
const {IS_PAUSED} = ProtocolErrors;
|
||||||
|
it("It's not possible to repayWithCollateral() on a non-active collateral or a non active principal", async () => {
|
||||||
|
const {configurator, weth, pool, users, dai, mockSwapAdapter} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
await configurator.deactivateReserve(weth.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
parseEther('100'),
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(weth.address);
|
||||||
|
|
||||||
|
await configurator.deactivateReserve(dai.address);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
parseEther('100'),
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('2');
|
||||||
|
|
||||||
|
await configurator.activateReserve(dai.address);
|
||||||
|
});
|
||||||
|
|
||||||
it('User 1 provides some liquidity for others to borrow', async () => {
|
it('User 1 provides some liquidity for others to borrow', async () => {
|
||||||
const {pool, weth, dai, usdc, deployer} = testEnv;
|
const {pool, weth, dai, usdc, deployer} = testEnv;
|
||||||
|
|
||||||
|
@ -64,7 +102,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0);
|
await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0, user.address);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
||||||
|
@ -186,7 +224,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => {
|
it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => {
|
||||||
|
@ -308,9 +346,11 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0);
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address);
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0);
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0, user.address);
|
||||||
|
|
||||||
const amountToRepay = parseUnits('80', 6);
|
const amountToRepay = parseUnits('80', 6);
|
||||||
|
|
||||||
|
@ -449,7 +489,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
|
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
|
||||||
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
|
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0);
|
await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0, user.address);
|
||||||
|
|
||||||
const amountToRepay = parseEther('80');
|
const amountToRepay = parseEther('80');
|
||||||
|
|
||||||
|
@ -541,7 +581,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
|
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
|
||||||
|
|
||||||
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('User 5 tries to repay his USDC loan by swapping his WETH collateral, should not revert even with WETH collateral disabled', async () => {
|
it('User 5 tries to repay his USDC loan by swapping his WETH collateral, should not revert even with WETH collateral disabled', async () => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user