mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Merge branch 'master' into fix/34
This commit is contained in:
commit
dbf1a0b9e7
|
@ -2,7 +2,7 @@ const accounts = require(`./test-wallets.js`).accounts;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
client: require('ganache-cli'),
|
client: require('ganache-cli'),
|
||||||
skipFiles: [],
|
skipFiles: ['./mocks', './interfaces'],
|
||||||
mocha: {
|
mocha: {
|
||||||
enableTimeouts: false,
|
enableTimeouts: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ usePlugin('buidler-typechain');
|
||||||
usePlugin('solidity-coverage');
|
usePlugin('solidity-coverage');
|
||||||
usePlugin('@nomiclabs/buidler-waffle');
|
usePlugin('@nomiclabs/buidler-waffle');
|
||||||
usePlugin('@nomiclabs/buidler-etherscan');
|
usePlugin('@nomiclabs/buidler-etherscan');
|
||||||
//usePlugin('buidler-gas-reporter');
|
usePlugin('buidler-gas-reporter');
|
||||||
|
|
||||||
const DEFAULT_BLOCK_GAS_LIMIT = 10000000;
|
const DEFAULT_BLOCK_GAS_LIMIT = 10000000;
|
||||||
const DEFAULT_GAS_PRICE = 10;
|
const DEFAULT_GAS_PRICE = 10;
|
||||||
|
@ -57,6 +57,9 @@ const config: any = {
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
coverage: {
|
||||||
|
url: 'http://localhost:8555',
|
||||||
|
},
|
||||||
kovan: getCommonNetworkConfig(eEthereumNetwork.kovan, 42),
|
kovan: getCommonNetworkConfig(eEthereumNetwork.kovan, 42),
|
||||||
ropsten: getCommonNetworkConfig(eEthereumNetwork.ropsten, 3),
|
ropsten: getCommonNetworkConfig(eEthereumNetwork.ropsten, 3),
|
||||||
main: getCommonNetworkConfig(eEthereumNetwork.main, 1),
|
main: getCommonNetworkConfig(eEthereumNetwork.main, 1),
|
||||||
|
|
|
@ -19,5 +19,4 @@ abstract contract FlashLoanReceiverBase is IFlashLoanReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
receive() external payable {}
|
receive() external payable {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ interface ILendingPool {
|
||||||
**/
|
**/
|
||||||
event Deposit(
|
event Deposit(
|
||||||
address indexed reserve,
|
address indexed reserve,
|
||||||
address indexed user,
|
address user,
|
||||||
|
address indexed onBehalfOf,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
uint16 indexed referral
|
uint16 indexed referral
|
||||||
);
|
);
|
||||||
|
@ -139,6 +140,7 @@ interface ILendingPool {
|
||||||
function deposit(
|
function deposit(
|
||||||
address reserve,
|
address reserve,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
|
address onBehalfOf,
|
||||||
uint16 referralCode
|
uint16 referralCode
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
|
@ -218,6 +220,27 @@ interface ILendingPool {
|
||||||
bool receiveAToken
|
bool receiveAToken
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev flashes the underlying collateral on an user to swap for the owed asset and repay
|
||||||
|
* - Both the owner of the position and other liquidators can execute it
|
||||||
|
* - The owner can repay with his collateral at any point, no matter the health factor
|
||||||
|
* - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below
|
||||||
|
* @param collateral The address of the collateral asset
|
||||||
|
* @param principal The address of the owed asset
|
||||||
|
* @param user Address of the borrower
|
||||||
|
* @param principalAmount Amount of the debt to repay. type(uint256).max to repay the maximum possible
|
||||||
|
* @param receiver Address of the contract receiving the collateral to swap
|
||||||
|
* @param params Variadic bytes param to pass with extra information to the receiver
|
||||||
|
**/
|
||||||
|
function repayWithCollateral(
|
||||||
|
address collateral,
|
||||||
|
address principal,
|
||||||
|
address user,
|
||||||
|
uint256 principalAmount,
|
||||||
|
address receiver,
|
||||||
|
bytes calldata params
|
||||||
|
) external;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev allows smartcontracts to access the liquidity of the pool within one transaction,
|
* @dev allows smartcontracts to access the liquidity of the pool within one transaction,
|
||||||
* as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
|
* as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
|
||||||
|
|
21
contracts/interfaces/ISwapAdapter.sol
Normal file
21
contracts/interfaces/ISwapAdapter.sol
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity ^0.6.8;
|
||||||
|
|
||||||
|
interface ISwapAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Swaps an `amountToSwap` of an asset to another, approving a `fundsDestination` to pull the funds
|
||||||
|
* @param assetToSwapFrom Origin asset
|
||||||
|
* @param assetToSwapTo Destination asset
|
||||||
|
* @param amountToSwap How much `assetToSwapFrom` needs to be swapped
|
||||||
|
* @param fundsDestination Address that will be pulling the swapped funds
|
||||||
|
* @param params Additional variadic field to include extra params
|
||||||
|
*/
|
||||||
|
function executeOperation(
|
||||||
|
address assetToSwapFrom,
|
||||||
|
address assetToSwapTo,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
address fundsDestination,
|
||||||
|
bytes calldata params
|
||||||
|
) external;
|
||||||
|
}
|
|
@ -53,6 +53,8 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
|
|
||||||
address[] internal _reservesList;
|
address[] internal _reservesList;
|
||||||
|
|
||||||
|
bool internal _flashLiquidationLocked;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev only lending pools configurator can use functions affected by this modifier
|
* @dev only lending pools configurator can use functions affected by this modifier
|
||||||
**/
|
**/
|
||||||
|
@ -91,6 +93,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
function deposit(
|
function deposit(
|
||||||
address asset,
|
address asset,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
|
address onBehalfOf,
|
||||||
uint16 referralCode
|
uint16 referralCode
|
||||||
) external override {
|
) external override {
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
@ -102,18 +105,17 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
reserve.updateCumulativeIndexesAndTimestamp();
|
reserve.updateCumulativeIndexesAndTimestamp();
|
||||||
reserve.updateInterestRates(asset, aToken, amount, 0);
|
reserve.updateInterestRates(asset, aToken, amount, 0);
|
||||||
|
|
||||||
bool isFirstDeposit = IAToken(aToken).balanceOf(msg.sender) == 0;
|
bool isFirstDeposit = IAToken(aToken).balanceOf(onBehalfOf) == 0;
|
||||||
if (isFirstDeposit) {
|
if (isFirstDeposit) {
|
||||||
_usersConfig[msg.sender].setUsingAsCollateral(reserve.index, true);
|
_usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//minting AToken to user 1:1 with the specific exchange rate
|
IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex);
|
||||||
IAToken(aToken).mint(msg.sender, amount);
|
|
||||||
|
|
||||||
//transfer to the aToken contract
|
//transfer to the aToken contract
|
||||||
IERC20(asset).safeTransferFrom(msg.sender, aToken, amount);
|
IERC20(asset).safeTransferFrom(msg.sender, aToken, amount);
|
||||||
|
|
||||||
emit Deposit(asset, msg.sender, amount, referralCode);
|
emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -151,10 +153,10 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw);
|
reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw);
|
||||||
|
|
||||||
if (amountToWithdraw == userBalance) {
|
if (amountToWithdraw == userBalance) {
|
||||||
_usersConfig[msg.sender].setUsingAsCollateral(reserve.index, false);
|
_usersConfig[msg.sender].setUsingAsCollateral(reserve.id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
IAToken(aToken).burn(msg.sender, msg.sender, amountToWithdraw);
|
IAToken(aToken).burn(msg.sender, msg.sender, amountToWithdraw, reserve.liquidityIndex);
|
||||||
|
|
||||||
emit Withdraw(asset, msg.sender, amount);
|
emit Withdraw(asset, msg.sender, amount);
|
||||||
}
|
}
|
||||||
|
@ -200,6 +202,16 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
uint256 rateMode,
|
uint256 rateMode,
|
||||||
address onBehalfOf
|
address onBehalfOf
|
||||||
) external override {
|
) external override {
|
||||||
|
_executeRepay(asset, msg.sender, amount, rateMode, onBehalfOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _executeRepay(
|
||||||
|
address asset,
|
||||||
|
address user,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 rateMode,
|
||||||
|
address onBehalfOf
|
||||||
|
) internal {
|
||||||
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
ReserveLogic.ReserveData storage reserve = _reserves[asset];
|
||||||
|
|
||||||
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve);
|
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve);
|
||||||
|
@ -240,12 +252,12 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
reserve.updateInterestRates(asset, aToken, paybackAmount, 0);
|
reserve.updateInterestRates(asset, aToken, paybackAmount, 0);
|
||||||
|
|
||||||
if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) {
|
if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) {
|
||||||
_usersConfig[onBehalfOf].setBorrowing(reserve.index, false);
|
_usersConfig[onBehalfOf].setBorrowing(reserve.id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount);
|
IERC20(asset).safeTransferFrom(user, aToken, paybackAmount);
|
||||||
|
|
||||||
emit Repay(asset, onBehalfOf, msg.sender, paybackAmount);
|
emit Repay(asset, onBehalfOf, user, paybackAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -360,7 +372,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
_addressesProvider.getPriceOracle()
|
_addressesProvider.getPriceOracle()
|
||||||
);
|
);
|
||||||
|
|
||||||
_usersConfig[msg.sender].setUsingAsCollateral(reserve.index, useAsCollateral);
|
_usersConfig[msg.sender].setUsingAsCollateral(reserve.id, useAsCollateral);
|
||||||
|
|
||||||
if (useAsCollateral) {
|
if (useAsCollateral) {
|
||||||
emit ReserveUsedAsCollateralEnabled(asset, msg.sender);
|
emit ReserveUsedAsCollateralEnabled(asset, msg.sender);
|
||||||
|
@ -422,7 +434,55 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev allows smart contracts to access the liquidity of the pool within one transaction,
|
* @dev flashes the underlying collateral on an user to swap for the owed asset and repay
|
||||||
|
* - Both the owner of the position and other liquidators can execute it
|
||||||
|
* - The owner can repay with his collateral at any point, no matter the health factor
|
||||||
|
* - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below
|
||||||
|
* @param collateral The address of the collateral asset
|
||||||
|
* @param principal The address of the owed asset
|
||||||
|
* @param user Address of the borrower
|
||||||
|
* @param principalAmount Amount of the debt to repay. type(uint256).max to repay the maximum possible
|
||||||
|
* @param receiver Address of the contract receiving the collateral to swap
|
||||||
|
* @param params Variadic bytes param to pass with extra information to the receiver
|
||||||
|
**/
|
||||||
|
function repayWithCollateral(
|
||||||
|
address collateral,
|
||||||
|
address principal,
|
||||||
|
address user,
|
||||||
|
uint256 principalAmount,
|
||||||
|
address receiver,
|
||||||
|
bytes calldata params
|
||||||
|
) external override {
|
||||||
|
require(!_flashLiquidationLocked, Errors.REENTRANCY_NOT_ALLOWED);
|
||||||
|
_flashLiquidationLocked = true;
|
||||||
|
|
||||||
|
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||||
|
|
||||||
|
//solium-disable-next-line
|
||||||
|
(bool success, bytes memory result) = liquidationManager.delegatecall(
|
||||||
|
abi.encodeWithSignature(
|
||||||
|
'repayWithCollateral(address,address,address,uint256,address,bytes)',
|
||||||
|
collateral,
|
||||||
|
principal,
|
||||||
|
user,
|
||||||
|
principalAmount,
|
||||||
|
receiver,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
);
|
||||||
|
require(success, Errors.FAILED_REPAY_WITH_COLLATERAL);
|
||||||
|
|
||||||
|
(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
|
||||||
|
|
||||||
|
if (returnCode != 0) {
|
||||||
|
revert(string(abi.encodePacked(returnMessage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_flashLiquidationLocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev allows smartcontracts to access the liquidity of the pool within one transaction,
|
||||||
* as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
|
* as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
|
||||||
* that must be kept into consideration. For further details please visit https://developers.aave.com
|
* that must be kept into consideration. For further details please visit https://developers.aave.com
|
||||||
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
|
||||||
|
@ -462,15 +522,13 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
vars.amountPlusPremium = amount.add(vars.premium);
|
vars.amountPlusPremium = amount.add(vars.premium);
|
||||||
|
|
||||||
if (debtMode == ReserveLogic.InterestRateMode.NONE) {
|
if (debtMode == ReserveLogic.InterestRateMode.NONE) {
|
||||||
|
|
||||||
IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium);
|
IERC20(asset).transferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium);
|
||||||
|
|
||||||
reserve.updateCumulativeIndexesAndTimestamp();
|
reserve.updateCumulativeIndexesAndTimestamp();
|
||||||
reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium);
|
reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium);
|
||||||
reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0);
|
reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0);
|
||||||
|
|
||||||
emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode);
|
|
||||||
|
|
||||||
|
emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode);
|
||||||
} else {
|
} else {
|
||||||
// If the transfer didn't succeed, the receiver either didn't return the funds, or didn't approve the transfer.
|
// If the transfer didn't succeed, the receiver either didn't return the funds, or didn't approve the transfer.
|
||||||
_executeBorrow(
|
_executeBorrow(
|
||||||
|
@ -571,8 +629,8 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
reserve.currentVariableBorrowRate,
|
reserve.currentVariableBorrowRate,
|
||||||
reserve.currentStableBorrowRate,
|
reserve.currentStableBorrowRate,
|
||||||
IStableDebtToken(reserve.stableDebtTokenAddress).getAverageStableRate(),
|
IStableDebtToken(reserve.stableDebtTokenAddress).getAverageStableRate(),
|
||||||
reserve.lastLiquidityIndex,
|
reserve.liquidityIndex,
|
||||||
reserve.lastVariableBorrowIndex,
|
reserve.variableBorrowIndex,
|
||||||
reserve.lastUpdateTimestamp
|
reserve.lastUpdateTimestamp
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -620,7 +678,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
uint256 currentStableDebt,
|
uint256 currentStableDebt,
|
||||||
uint256 currentVariableDebt,
|
uint256 currentVariableDebt,
|
||||||
uint256 principalStableDebt,
|
uint256 principalStableDebt,
|
||||||
uint256 principalVariableDebt,
|
uint256 variableBorrowIndex,
|
||||||
uint256 scaledVariableDebt,
|
uint256 scaledVariableDebt,
|
||||||
uint256 stableBorrowRate,
|
uint256 stableBorrowRate,
|
||||||
uint256 liquidityRate,
|
uint256 liquidityRate,
|
||||||
|
@ -639,7 +697,8 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
stableRateLastUpdated = IStableDebtToken(reserve.stableDebtTokenAddress).getUserLastUpdated(
|
stableRateLastUpdated = IStableDebtToken(reserve.stableDebtTokenAddress).getUserLastUpdated(
|
||||||
user
|
user
|
||||||
);
|
);
|
||||||
usageAsCollateralEnabled = _usersConfig[user].isUsingAsCollateral(reserve.index);
|
usageAsCollateralEnabled = _usersConfig[user].isUsingAsCollateral(reserve.id);
|
||||||
|
variableBorrowIndex = IVariableDebtToken(reserve.variableDebtTokenAddress).getUserIndex(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReserves() external override view returns (address[] memory) {
|
function getReserves() external override view returns (address[] memory) {
|
||||||
|
@ -743,11 +802,9 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
oracle
|
oracle
|
||||||
);
|
);
|
||||||
|
|
||||||
|
uint256 reserveId = reserve.id;
|
||||||
uint256 reserveIndex = reserve.index;
|
if (!userConfig.isBorrowing(reserveId)) {
|
||||||
|
userConfig.setBorrowing(reserveId, true);
|
||||||
if (!userConfig.isBorrowing(reserveIndex)) {
|
|
||||||
userConfig.setBorrowing(reserveIndex, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
address debtTokenAddress = ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE ?
|
address debtTokenAddress = ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE ?
|
||||||
|
@ -777,13 +834,17 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
IVariableDebtToken(debtTokenAddress).mint(vars.user, vars.amount);
|
IVariableDebtToken(debtTokenAddress).mint(vars.user, vars.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
reserve.updateInterestRates(vars.asset, vars.aTokenAddress, 0, vars.releaseUnderlying ? vars.amount : 0);
|
reserve.updateInterestRates(
|
||||||
|
vars.asset,
|
||||||
if(vars.releaseUnderlying){
|
vars.aTokenAddress,
|
||||||
IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount);
|
0,
|
||||||
|
vars.releaseUnderlying ? vars.amount : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (vars.releaseUnderlying) {
|
||||||
|
IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
emit Borrow(
|
emit Borrow(
|
||||||
vars.asset,
|
vars.asset,
|
||||||
msg.sender,
|
msg.sender,
|
||||||
|
@ -806,7 +867,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
||||||
reserveAlreadyAdded = true;
|
reserveAlreadyAdded = true;
|
||||||
}
|
}
|
||||||
if (!reserveAlreadyAdded) {
|
if (!reserveAlreadyAdded) {
|
||||||
_reserves[asset].index = uint8(_reservesList.length);
|
_reserves[asset].id = uint8(_reservesList.length);
|
||||||
_reservesList.push(asset);
|
_reservesList.push(asset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {Helpers} from '../libraries/helpers/Helpers.sol';
|
||||||
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
|
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
|
||||||
import {PercentageMath} from '../libraries/math/PercentageMath.sol';
|
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 {Errors} from '../libraries/helpers/Errors.sol';
|
import {Errors} from '../libraries/helpers/Errors.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,6 +37,9 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
using ReserveConfiguration for ReserveConfiguration.Map;
|
using ReserveConfiguration for ReserveConfiguration.Map;
|
||||||
using UserConfiguration for UserConfiguration.Map;
|
using UserConfiguration for UserConfiguration.Map;
|
||||||
|
|
||||||
|
// IMPORTANT The storage layout of the LendingPool is reproduced here because this contract
|
||||||
|
// is gonna be used through DELEGATECALL
|
||||||
|
|
||||||
LendingPoolAddressesProvider internal addressesProvider;
|
LendingPoolAddressesProvider internal addressesProvider;
|
||||||
|
|
||||||
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
||||||
|
@ -43,6 +47,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
|
|
||||||
address[] internal reservesList;
|
address[] internal reservesList;
|
||||||
|
|
||||||
|
bool internal _flashLiquidationLocked;
|
||||||
|
|
||||||
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
|
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +71,24 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
bool receiveAToken
|
bool receiveAToken
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
@dev emitted when a borrower/liquidator repays with the borrower's collateral
|
||||||
|
@param collateral the address of the collateral being swapped to repay
|
||||||
|
@param principal the address of the reserve of the debt
|
||||||
|
@param user the borrower's address
|
||||||
|
@param liquidator the address of the liquidator, same as the one of the borrower on self-repayment
|
||||||
|
@param principalAmount the amount of the debt finally covered
|
||||||
|
@param swappedCollateralAmount the amount of collateral finally swapped
|
||||||
|
*/
|
||||||
|
event RepaidWithCollateral(
|
||||||
|
address indexed collateral,
|
||||||
|
address indexed principal,
|
||||||
|
address indexed user,
|
||||||
|
address liquidator,
|
||||||
|
uint256 principalAmount,
|
||||||
|
uint256 swappedCollateralAmount
|
||||||
|
);
|
||||||
|
|
||||||
enum LiquidationErrors {
|
enum LiquidationErrors {
|
||||||
NO_ERROR,
|
NO_ERROR,
|
||||||
NO_COLLATERAL_AVAILABLE,
|
NO_COLLATERAL_AVAILABLE,
|
||||||
|
@ -142,7 +166,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
|
|
||||||
vars.isCollateralEnabled =
|
vars.isCollateralEnabled =
|
||||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||||
userConfig.isUsingAsCollateral(collateralReserve.index);
|
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||||
|
|
||||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||||
if (!vars.isCollateralEnabled) {
|
if (!vars.isCollateralEnabled) {
|
||||||
|
@ -264,7 +288,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
);
|
);
|
||||||
|
|
||||||
//burn the equivalent amount of atoken
|
//burn the equivalent amount of atoken
|
||||||
vars.collateralAtoken.burn(user, msg.sender, vars.maxCollateralToLiquidate);
|
vars.collateralAtoken.burn(user, msg.sender, vars.maxCollateralToLiquidate, collateralReserve.liquidityIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
//transfers the principal currency to the aToken
|
//transfers the principal currency to the aToken
|
||||||
|
@ -287,6 +311,158 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
||||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev flashes the underlying collateral on an user to swap for the owed asset and repay
|
||||||
|
* - Both the owner of the position and other liquidators can execute it.
|
||||||
|
* - The owner can repay with his collateral at any point, no matter the health factor.
|
||||||
|
* - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below.
|
||||||
|
* @param collateral The address of the collateral asset.
|
||||||
|
* @param principal The address of the owed asset.
|
||||||
|
* @param user Address of the borrower.
|
||||||
|
* @param principalAmount Amount of the debt to repay.
|
||||||
|
* @param receiver Address of the contract receiving the collateral to swap.
|
||||||
|
* @param params Variadic bytes param to pass with extra information to the receiver
|
||||||
|
**/
|
||||||
|
function repayWithCollateral(
|
||||||
|
address collateral,
|
||||||
|
address principal,
|
||||||
|
address user,
|
||||||
|
uint256 principalAmount,
|
||||||
|
address receiver,
|
||||||
|
bytes calldata params
|
||||||
|
) external returns (uint256, string memory) {
|
||||||
|
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
||||||
|
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||||
|
|
||||||
|
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||||
|
|
||||||
|
LiquidationCallLocalVars memory vars;
|
||||||
|
|
||||||
|
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
|
||||||
|
user,
|
||||||
|
reserves,
|
||||||
|
usersConfig[user],
|
||||||
|
reservesList,
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
||||||
|
return (
|
||||||
|
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||||
|
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
|
||||||
|
|
||||||
|
vars.actualAmountToLiquidate = principalAmount > vars.maxPrincipalAmountToLiquidate
|
||||||
|
? vars.maxPrincipalAmountToLiquidate
|
||||||
|
: principalAmount;
|
||||||
|
|
||||||
|
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
||||||
|
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
||||||
|
|
||||||
|
(
|
||||||
|
vars.maxCollateralToLiquidate,
|
||||||
|
vars.principalAmountNeeded
|
||||||
|
) = calculateAvailableCollateralToLiquidate(
|
||||||
|
collateralReserve,
|
||||||
|
debtReserve,
|
||||||
|
collateral,
|
||||||
|
principal,
|
||||||
|
vars.actualAmountToLiquidate,
|
||||||
|
vars.userCollateralBalance
|
||||||
|
);
|
||||||
|
|
||||||
|
//if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
|
||||||
|
//of collateral to cover the actual amount that is being liquidated, hence we liquidate
|
||||||
|
//a smaller amount
|
||||||
|
if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) {
|
||||||
|
vars.actualAmountToLiquidate = vars.principalAmountNeeded;
|
||||||
|
}
|
||||||
|
//updating collateral reserve indexes
|
||||||
|
collateralReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
|
|
||||||
|
vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate, collateralReserve.liquidityIndex);
|
||||||
|
|
||||||
|
if (vars.userCollateralBalance == vars.maxCollateralToLiquidate) {
|
||||||
|
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
address principalAToken = debtReserve.aTokenAddress;
|
||||||
|
|
||||||
|
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||||
|
ISwapAdapter(receiver).executeOperation(
|
||||||
|
collateral,
|
||||||
|
principal,
|
||||||
|
vars.maxCollateralToLiquidate,
|
||||||
|
address(this),
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
//updating debt reserve
|
||||||
|
debtReserve.updateCumulativeIndexesAndTimestamp();
|
||||||
|
debtReserve.updateInterestRates(principal, principalAToken, vars.actualAmountToLiquidate, 0);
|
||||||
|
IERC20(principal).transferFrom(receiver, principalAToken, vars.actualAmountToLiquidate);
|
||||||
|
|
||||||
|
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
||||||
|
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
|
||||||
|
user,
|
||||||
|
vars.actualAmountToLiquidate
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(user, vars.userVariableDebt);
|
||||||
|
IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(
|
||||||
|
user,
|
||||||
|
vars.actualAmountToLiquidate.sub(vars.userVariableDebt)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//updating collateral reserve
|
||||||
|
collateralReserve.updateInterestRates(
|
||||||
|
collateral,
|
||||||
|
address(vars.collateralAtoken),
|
||||||
|
0,
|
||||||
|
vars.maxCollateralToLiquidate
|
||||||
|
);
|
||||||
|
|
||||||
|
emit RepaidWithCollateral(
|
||||||
|
collateral,
|
||||||
|
principal,
|
||||||
|
user,
|
||||||
|
msg.sender,
|
||||||
|
vars.actualAmountToLiquidate,
|
||||||
|
vars.maxCollateralToLiquidate
|
||||||
|
);
|
||||||
|
|
||||||
|
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
struct AvailableCollateralToLiquidateLocalVars {
|
struct AvailableCollateralToLiquidateLocalVars {
|
||||||
uint256 userCompoundedBorrowBalance;
|
uint256 userCompoundedBorrowBalance;
|
||||||
uint256 liquidationBonus;
|
uint256 liquidationBonus;
|
||||||
|
|
|
@ -38,6 +38,8 @@ 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 FAILED_REPAY_WITH_COLLATERAL = '53';
|
||||||
|
|
||||||
// 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'
|
||||||
|
@ -46,8 +48,9 @@ library Errors {
|
||||||
string public constant TRANSFER_AMOUNT_NOT_GT_0 = '31'; // 'Transferred amount needs to be greater than zero'
|
string public constant TRANSFER_AMOUNT_NOT_GT_0 = '31'; // 'Transferred amount needs to be greater than zero'
|
||||||
string public constant INTEREST_ALREADY_REDIRECTED = '32'; // 'Interest is already redirected to the user'
|
string public constant INTEREST_ALREADY_REDIRECTED = '32'; // 'Interest is already redirected to the user'
|
||||||
string public constant NO_VALID_BALANCE_FOR_REDIRECTION = '33'; // 'Interest stream can only be redirected if there is a valid balance'
|
string public constant NO_VALID_BALANCE_FOR_REDIRECTION = '33'; // 'Interest stream can only be redirected if there is a valid balance'
|
||||||
|
string public constant INVALID_ATOKEN_BALANCE = '52'; // balance on burning is invalid
|
||||||
// require error messages - ReserveLogic
|
|
||||||
|
// require error messages - ReserveLogic
|
||||||
string public constant RESERVE_ALREADY_INITIALIZED = '34'; // 'Reserve has already been initialized'
|
string public constant RESERVE_ALREADY_INITIALIZED = '34'; // 'Reserve has already been initialized'
|
||||||
string public constant LIQUIDITY_INDEX_OVERFLOW = '47'; // Liquidity index overflows uint128
|
string public constant LIQUIDITY_INDEX_OVERFLOW = '47'; // Liquidity index overflows uint128
|
||||||
string public constant VARIABLE_BORROW_INDEX_OVERFLOW = '48'; // Variable borrow index overflows uint128
|
string public constant VARIABLE_BORROW_INDEX_OVERFLOW = '48'; // Variable borrow index overflows uint128
|
||||||
|
@ -70,7 +73,7 @@ library Errors {
|
||||||
string public constant NO_ERRORS = '42'; // 'No errors'
|
string public constant NO_ERRORS = '42'; // 'No errors'
|
||||||
|
|
||||||
//require error messages - Math libraries
|
//require error messages - Math libraries
|
||||||
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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ library GenericLogic {
|
||||||
using ReserveConfiguration for ReserveConfiguration.Map;
|
using ReserveConfiguration for ReserveConfiguration.Map;
|
||||||
using UserConfiguration for UserConfiguration.Map;
|
using UserConfiguration for UserConfiguration.Map;
|
||||||
|
|
||||||
uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
|
uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1 ether;
|
||||||
|
uint256 public constant HEALTH_FACTOR_CRITICAL_THRESHOLD = 0.98 ether;
|
||||||
|
|
||||||
struct balanceDecreaseAllowedLocalVars {
|
struct balanceDecreaseAllowedLocalVars {
|
||||||
uint256 decimals;
|
uint256 decimals;
|
||||||
|
@ -63,7 +64,7 @@ library GenericLogic {
|
||||||
) external view returns (bool) {
|
) external view returns (bool) {
|
||||||
if (
|
if (
|
||||||
!userConfig.isBorrowingAny() ||
|
!userConfig.isBorrowingAny() ||
|
||||||
!userConfig.isUsingAsCollateral(reservesData[asset].index)
|
!userConfig.isUsingAsCollateral(reservesData[asset].id)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,24 +51,27 @@ library ReserveLogic {
|
||||||
struct ReserveData {
|
struct ReserveData {
|
||||||
//stores the reserve configuration
|
//stores the reserve configuration
|
||||||
ReserveConfiguration.Map configuration;
|
ReserveConfiguration.Map configuration;
|
||||||
address aTokenAddress;
|
|
||||||
address stableDebtTokenAddress;
|
|
||||||
address variableDebtTokenAddress;
|
|
||||||
address interestRateStrategyAddress;
|
|
||||||
//the liquidity index. Expressed in ray
|
//the liquidity index. Expressed in ray
|
||||||
uint128 lastLiquidityIndex;
|
uint128 liquidityIndex;
|
||||||
|
//variable borrow index. Expressed in ray
|
||||||
|
uint128 variableBorrowIndex;
|
||||||
//the current supply rate. Expressed in ray
|
//the current supply rate. Expressed in ray
|
||||||
uint128 currentLiquidityRate;
|
uint128 currentLiquidityRate;
|
||||||
//the current variable borrow rate. Expressed in ray
|
//the current variable borrow rate. Expressed in ray
|
||||||
uint128 currentVariableBorrowRate;
|
uint128 currentVariableBorrowRate;
|
||||||
//the current stable borrow rate. Expressed in ray
|
//the current stable borrow rate. Expressed in ray
|
||||||
uint128 currentStableBorrowRate;
|
uint128 currentStableBorrowRate;
|
||||||
//variable borrow index. Expressed in ray
|
|
||||||
uint128 lastVariableBorrowIndex;
|
|
||||||
uint40 lastUpdateTimestamp;
|
uint40 lastUpdateTimestamp;
|
||||||
//the index of the reserve in the list of the active reserves
|
|
||||||
uint8 index;
|
|
||||||
|
|
||||||
|
//tokens addresses
|
||||||
|
address aTokenAddress;
|
||||||
|
address stableDebtTokenAddress;
|
||||||
|
address variableDebtTokenAddress;
|
||||||
|
|
||||||
|
address interestRateStrategyAddress;
|
||||||
|
|
||||||
|
//the id of the reserve. Represents the position in the list of the active reserves
|
||||||
|
uint8 id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,12 +87,12 @@ library ReserveLogic {
|
||||||
//solium-disable-next-line
|
//solium-disable-next-line
|
||||||
if (timestamp == uint40(block.timestamp)) {
|
if (timestamp == uint40(block.timestamp)) {
|
||||||
//if the index was updated in the same block, no need to perform any calculation
|
//if the index was updated in the same block, no need to perform any calculation
|
||||||
return reserve.lastLiquidityIndex;
|
return reserve.liquidityIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 cumulated = MathUtils
|
uint256 cumulated = MathUtils
|
||||||
.calculateLinearInterest(reserve.currentLiquidityRate, timestamp)
|
.calculateLinearInterest(reserve.currentLiquidityRate, timestamp)
|
||||||
.rayMul(reserve.lastLiquidityIndex);
|
.rayMul(reserve.liquidityIndex);
|
||||||
|
|
||||||
return cumulated;
|
return cumulated;
|
||||||
}
|
}
|
||||||
|
@ -107,12 +110,12 @@ library ReserveLogic {
|
||||||
//solium-disable-next-line
|
//solium-disable-next-line
|
||||||
if (timestamp == uint40(block.timestamp)) {
|
if (timestamp == uint40(block.timestamp)) {
|
||||||
//if the index was updated in the same block, no need to perform any calculation
|
//if the index was updated in the same block, no need to perform any calculation
|
||||||
return reserve.lastVariableBorrowIndex;
|
return reserve.variableBorrowIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 cumulated = MathUtils
|
uint256 cumulated = MathUtils
|
||||||
.calculateCompoundedInterest(reserve.currentVariableBorrowRate, timestamp)
|
.calculateCompoundedInterest(reserve.currentVariableBorrowRate, timestamp)
|
||||||
.rayMul(reserve.lastVariableBorrowIndex);
|
.rayMul(reserve.variableBorrowIndex);
|
||||||
|
|
||||||
return cumulated;
|
return cumulated;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +125,7 @@ library ReserveLogic {
|
||||||
* a formal specification.
|
* a formal specification.
|
||||||
* @param reserve the reserve object
|
* @param reserve the reserve object
|
||||||
**/
|
**/
|
||||||
function updateCumulativeIndexesAndTimestamp(ReserveData storage reserve) internal {
|
function updateCumulativeIndexesAndTimestamp(ReserveData storage reserve) internal {
|
||||||
uint256 currentLiquidityRate = reserve.currentLiquidityRate;
|
uint256 currentLiquidityRate = reserve.currentLiquidityRate;
|
||||||
|
|
||||||
//only cumulating if there is any income being produced
|
//only cumulating if there is any income being produced
|
||||||
|
@ -132,10 +135,10 @@ library ReserveLogic {
|
||||||
currentLiquidityRate,
|
currentLiquidityRate,
|
||||||
lastUpdateTimestamp
|
lastUpdateTimestamp
|
||||||
);
|
);
|
||||||
uint256 index = cumulatedLiquidityInterest.rayMul(reserve.lastLiquidityIndex);
|
uint256 index = cumulatedLiquidityInterest.rayMul(reserve.liquidityIndex);
|
||||||
require(index < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW);
|
require(index < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW);
|
||||||
|
|
||||||
reserve.lastLiquidityIndex = uint128(index);
|
reserve.liquidityIndex = uint128(index);
|
||||||
|
|
||||||
//as the liquidity rate might come only from stable rate loans, we need to ensure
|
//as the liquidity rate might come only from stable rate loans, we need to ensure
|
||||||
//that there is actual variable debt before accumulating
|
//that there is actual variable debt before accumulating
|
||||||
|
@ -144,11 +147,9 @@ library ReserveLogic {
|
||||||
reserve.currentVariableBorrowRate,
|
reserve.currentVariableBorrowRate,
|
||||||
lastUpdateTimestamp
|
lastUpdateTimestamp
|
||||||
);
|
);
|
||||||
index = cumulatedVariableBorrowInterest.rayMul(
|
index = cumulatedVariableBorrowInterest.rayMul(reserve.variableBorrowIndex);
|
||||||
reserve.lastVariableBorrowIndex
|
require(index < (1 << 128), Errors.VARIABLE_BORROW_INDEX_OVERFLOW);
|
||||||
);
|
reserve.variableBorrowIndex = uint128(index);
|
||||||
require(index < (1 << 128), Errors.VARIABLE_BORROW_INDEX_OVERFLOW);
|
|
||||||
reserve.lastVariableBorrowIndex = uint128(index);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,12 +173,10 @@ library ReserveLogic {
|
||||||
|
|
||||||
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
|
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
|
||||||
|
|
||||||
result = result.rayMul(
|
result = result.rayMul(reserve.liquidityIndex);
|
||||||
reserve.lastLiquidityIndex
|
|
||||||
);
|
|
||||||
require(result < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW);
|
require(result < (1 << 128), Errors.LIQUIDITY_INDEX_OVERFLOW);
|
||||||
|
|
||||||
reserve.lastLiquidityIndex = uint128(result);
|
reserve.liquidityIndex = uint128(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -194,13 +193,13 @@ library ReserveLogic {
|
||||||
address interestRateStrategyAddress
|
address interestRateStrategyAddress
|
||||||
) external {
|
) external {
|
||||||
require(reserve.aTokenAddress == address(0), Errors.RESERVE_ALREADY_INITIALIZED);
|
require(reserve.aTokenAddress == address(0), Errors.RESERVE_ALREADY_INITIALIZED);
|
||||||
if (reserve.lastLiquidityIndex == 0) {
|
if (reserve.liquidityIndex == 0) {
|
||||||
//if the reserve has not been initialized yet
|
//if the reserve has not been initialized yet
|
||||||
reserve.lastLiquidityIndex = uint128(WadRayMath.ray());
|
reserve.liquidityIndex = uint128(WadRayMath.ray());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reserve.lastVariableBorrowIndex == 0) {
|
if (reserve.variableBorrowIndex == 0) {
|
||||||
reserve.lastVariableBorrowIndex = uint128(WadRayMath.ray());
|
reserve.variableBorrowIndex = uint128(WadRayMath.ray());
|
||||||
}
|
}
|
||||||
|
|
||||||
reserve.aTokenAddress = aTokenAddress;
|
reserve.aTokenAddress = aTokenAddress;
|
||||||
|
@ -217,6 +216,7 @@ library ReserveLogic {
|
||||||
uint256 newStableRate;
|
uint256 newStableRate;
|
||||||
uint256 newVariableRate;
|
uint256 newVariableRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Updates the reserve current stable borrow rate Rf, the current variable borrow rate Rv and the current liquidity rate Rl.
|
* @dev Updates the reserve current stable borrow rate Rf, the current variable borrow rate Rv and the current liquidity rate Rl.
|
||||||
* Also updates the lastUpdateTimestamp value. Please refer to the whitepaper for further information.
|
* Also updates the lastUpdateTimestamp value. Please refer to the whitepaper for further information.
|
||||||
|
@ -234,7 +234,8 @@ library ReserveLogic {
|
||||||
UpdateInterestRatesLocalVars memory vars;
|
UpdateInterestRatesLocalVars memory vars;
|
||||||
|
|
||||||
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
|
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
|
||||||
vars.currentAvgStableRate = IStableDebtToken(vars.stableDebtTokenAddress).getAverageStableRate();
|
vars.currentAvgStableRate = IStableDebtToken(vars.stableDebtTokenAddress)
|
||||||
|
.getAverageStableRate();
|
||||||
vars.availableLiquidity = IERC20(reserveAddress).balanceOf(aTokenAddress);
|
vars.availableLiquidity = IERC20(reserveAddress).balanceOf(aTokenAddress);
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -249,9 +250,9 @@ library ReserveLogic {
|
||||||
vars.currentAvgStableRate,
|
vars.currentAvgStableRate,
|
||||||
reserve.configuration.getReserveFactor()
|
reserve.configuration.getReserveFactor()
|
||||||
);
|
);
|
||||||
require(vars.newLiquidityRate < (1 << 128), "ReserveLogic: Liquidity rate overflow");
|
require(vars.newLiquidityRate < (1 << 128), 'ReserveLogic: Liquidity rate overflow');
|
||||||
require(vars.newStableRate < (1 << 128), "ReserveLogic: Stable borrow rate overflow");
|
require(vars.newStableRate < (1 << 128), 'ReserveLogic: Stable borrow rate overflow');
|
||||||
require(vars.newVariableRate < (1 << 128), "ReserveLogic: Variable borrow rate overflow");
|
require(vars.newVariableRate < (1 << 128), 'ReserveLogic: Variable borrow rate overflow');
|
||||||
|
|
||||||
reserve.currentLiquidityRate = uint128(vars.newLiquidityRate);
|
reserve.currentLiquidityRate = uint128(vars.newLiquidityRate);
|
||||||
reserve.currentStableBorrowRate = uint128(vars.newStableRate);
|
reserve.currentStableBorrowRate = uint128(vars.newStableRate);
|
||||||
|
@ -263,8 +264,8 @@ library ReserveLogic {
|
||||||
vars.newStableRate,
|
vars.newStableRate,
|
||||||
vars.currentAvgStableRate,
|
vars.currentAvgStableRate,
|
||||||
vars.newVariableRate,
|
vars.newVariableRate,
|
||||||
reserve.lastLiquidityIndex,
|
reserve.liquidityIndex,
|
||||||
reserve.lastVariableBorrowIndex
|
reserve.variableBorrowIndex
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,7 +190,7 @@ library ValidationLogic {
|
||||||
require(vars.stableRateBorrowingEnabled, Errors.STABLE_BORROWING_NOT_ENABLED);
|
require(vars.stableRateBorrowingEnabled, Errors.STABLE_BORROWING_NOT_ENABLED);
|
||||||
|
|
||||||
require(
|
require(
|
||||||
!userConfig.isUsingAsCollateral(reserve.index) ||
|
!userConfig.isUsingAsCollateral(reserve.id) ||
|
||||||
reserve.configuration.getLtv() == 0 ||
|
reserve.configuration.getLtv() == 0 ||
|
||||||
amount > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
|
amount > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
|
||||||
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
|
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
|
||||||
|
@ -274,7 +274,7 @@ library ValidationLogic {
|
||||||
require(stableRateEnabled, Errors.STABLE_BORROWING_NOT_ENABLED);
|
require(stableRateEnabled, Errors.STABLE_BORROWING_NOT_ENABLED);
|
||||||
|
|
||||||
require(
|
require(
|
||||||
!userConfig.isUsingAsCollateral(reserve.index) ||
|
!userConfig.isUsingAsCollateral(reserve.id) ||
|
||||||
reserve.configuration.getLtv() == 0 ||
|
reserve.configuration.getLtv() == 0 ||
|
||||||
stableBorrowBalance.add(variableBorrowBalance) >
|
stableBorrowBalance.add(variableBorrowBalance) >
|
||||||
IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
|
IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
|
||||||
|
@ -321,10 +321,10 @@ library ValidationLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev validates a flashloan action
|
* @dev validates a flashloan action
|
||||||
* @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan)
|
* @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan)
|
||||||
* @param premium the premium paid on the flashloan
|
* @param premium the premium paid on the flashloan
|
||||||
**/
|
**/
|
||||||
function validateFlashloan(uint256 mode, uint256 premium) internal pure {
|
function validateFlashloan(uint256 mode, uint256 premium) internal pure {
|
||||||
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);
|
||||||
|
|
|
@ -55,17 +55,17 @@ library MathUtils {
|
||||||
return WadRayMath.ray();
|
return WadRayMath.ray();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 expMinusOne = exp-1;
|
uint256 expMinusOne = exp - 1;
|
||||||
|
|
||||||
uint256 expMinusTwo = exp > 2 ? exp-2 : 0;
|
uint256 expMinusTwo = exp > 2 ? exp - 2 : 0;
|
||||||
|
|
||||||
uint256 ratePerSecond = rate/SECONDS_PER_YEAR;
|
uint256 ratePerSecond = rate / SECONDS_PER_YEAR;
|
||||||
|
|
||||||
uint256 basePowerTwo = ratePerSecond.rayMul(ratePerSecond);
|
uint256 basePowerTwo = ratePerSecond.rayMul(ratePerSecond);
|
||||||
uint256 basePowerThree = basePowerTwo.rayMul(ratePerSecond);
|
uint256 basePowerThree = basePowerTwo.rayMul(ratePerSecond);
|
||||||
|
|
||||||
uint256 secondTerm = exp.mul(expMinusOne).mul(basePowerTwo)/2;
|
uint256 secondTerm = exp.mul(expMinusOne).mul(basePowerTwo) / 2;
|
||||||
uint256 thirdTerm = exp.mul(expMinusOne).mul(expMinusTwo).mul(basePowerThree)/6;
|
uint256 thirdTerm = exp.mul(expMinusOne).mul(expMinusTwo).mul(basePowerThree) / 6;
|
||||||
|
|
||||||
return WadRayMath.ray().add(ratePerSecond.mul(exp)).add(secondTerm).add(thirdTerm);
|
return WadRayMath.ray().add(ratePerSecond.mul(exp)).add(secondTerm).add(thirdTerm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// SPDX-License-Identifier: agpl-3.0
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
pragma solidity ^0.6.8;
|
pragma solidity ^0.6.8;
|
||||||
|
|
||||||
|
|
||||||
import {Errors} from '../helpers/Errors.sol';
|
import {Errors} from '../helpers/Errors.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,7 +12,6 @@ import {Errors} from '../helpers/Errors.sol';
|
||||||
**/
|
**/
|
||||||
|
|
||||||
library PercentageMath {
|
library PercentageMath {
|
||||||
|
|
||||||
uint256 constant PERCENTAGE_FACTOR = 1e4; //percentage plus two decimals
|
uint256 constant PERCENTAGE_FACTOR = 1e4; //percentage plus two decimals
|
||||||
uint256 constant HALF_PERCENT = PERCENTAGE_FACTOR / 2;
|
uint256 constant HALF_PERCENT = PERCENTAGE_FACTOR / 2;
|
||||||
|
|
||||||
|
@ -24,19 +22,19 @@ library PercentageMath {
|
||||||
* @return the percentage of value
|
* @return the percentage of value
|
||||||
**/
|
**/
|
||||||
function percentMul(uint256 value, uint256 percentage) internal pure returns (uint256) {
|
function percentMul(uint256 value, uint256 percentage) internal pure returns (uint256) {
|
||||||
if(value == 0){
|
if (value == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 result = value*percentage;
|
uint256 result = value * percentage;
|
||||||
|
|
||||||
require(result/value == percentage, Errors.MULTIPLICATION_OVERFLOW);
|
require(result / value == percentage, Errors.MULTIPLICATION_OVERFLOW);
|
||||||
|
|
||||||
result+=HALF_PERCENT;
|
result += HALF_PERCENT;
|
||||||
|
|
||||||
require(result >= HALF_PERCENT, Errors.ADDITION_OVERFLOW);
|
require(result >= HALF_PERCENT, Errors.ADDITION_OVERFLOW);
|
||||||
|
|
||||||
return result/PERCENTAGE_FACTOR;
|
return result / PERCENTAGE_FACTOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,15 +46,15 @@ library PercentageMath {
|
||||||
function percentDiv(uint256 value, uint256 percentage) internal pure returns (uint256) {
|
function percentDiv(uint256 value, uint256 percentage) internal pure returns (uint256) {
|
||||||
require(percentage != 0, Errors.DIVISION_BY_ZERO);
|
require(percentage != 0, Errors.DIVISION_BY_ZERO);
|
||||||
uint256 halfPercentage = percentage / 2;
|
uint256 halfPercentage = percentage / 2;
|
||||||
|
|
||||||
uint256 result = value*PERCENTAGE_FACTOR;
|
|
||||||
|
|
||||||
require(result/PERCENTAGE_FACTOR == value, Errors.MULTIPLICATION_OVERFLOW);
|
uint256 result = value * PERCENTAGE_FACTOR;
|
||||||
|
|
||||||
|
require(result / PERCENTAGE_FACTOR == value, Errors.MULTIPLICATION_OVERFLOW);
|
||||||
|
|
||||||
result += halfPercentage;
|
result += halfPercentage;
|
||||||
|
|
||||||
require(result >= halfPercentage, Errors.ADDITION_OVERFLOW);
|
require(result >= halfPercentage, Errors.ADDITION_OVERFLOW);
|
||||||
|
|
||||||
return result/percentage;
|
return result / percentage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,149 +15,149 @@ pragma solidity 0.6.8;
|
||||||
* class of bugs, so it's recommended to use it always.
|
* class of bugs, so it's recommended to use it always.
|
||||||
*/
|
*/
|
||||||
library SafeMath {
|
library SafeMath {
|
||||||
/**
|
/**
|
||||||
* @dev Returns the addition of two unsigned integers, reverting on
|
* @dev Returns the addition of two unsigned integers, reverting on
|
||||||
* overflow.
|
* overflow.
|
||||||
*
|
*
|
||||||
* Counterpart to Solidity's `+` operator.
|
* Counterpart to Solidity's `+` operator.
|
||||||
*
|
*
|
||||||
* Requirements:
|
* Requirements:
|
||||||
* - Addition cannot overflow.
|
* - Addition cannot overflow.
|
||||||
*/
|
*/
|
||||||
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
uint256 c = a + b;
|
uint256 c = a + b;
|
||||||
require(c >= a, 'SafeMath: addition overflow');
|
require(c >= a, 'SafeMath: addition overflow');
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the subtraction of two unsigned integers, reverting on
|
||||||
|
* overflow (when the result is negative).
|
||||||
|
*
|
||||||
|
* Counterpart to Solidity's `-` operator.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Subtraction cannot overflow.
|
||||||
|
*/
|
||||||
|
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
return sub(a, b, 'SafeMath: subtraction overflow');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
|
||||||
|
* overflow (when the result is negative).
|
||||||
|
*
|
||||||
|
* Counterpart to Solidity's `-` operator.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Subtraction cannot overflow.
|
||||||
|
*/
|
||||||
|
function sub(
|
||||||
|
uint256 a,
|
||||||
|
uint256 b,
|
||||||
|
string memory errorMessage
|
||||||
|
) internal pure returns (uint256) {
|
||||||
|
require(b <= a, errorMessage);
|
||||||
|
uint256 c = a - b;
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the multiplication of two unsigned integers, reverting on
|
||||||
|
* overflow.
|
||||||
|
*
|
||||||
|
* Counterpart to Solidity's `*` operator.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Multiplication cannot overflow.
|
||||||
|
*/
|
||||||
|
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
|
||||||
|
// benefit is lost if 'b' is also tested.
|
||||||
|
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
|
||||||
|
if (a == 0) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
uint256 c = a * b;
|
||||||
* @dev Returns the subtraction of two unsigned integers, reverting on
|
require(c / a == b, 'SafeMath: multiplication overflow');
|
||||||
* overflow (when the result is negative).
|
|
||||||
*
|
|
||||||
* Counterpart to Solidity's `-` operator.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - Subtraction cannot overflow.
|
|
||||||
*/
|
|
||||||
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
||||||
return sub(a, b, 'SafeMath: subtraction overflow');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return c;
|
||||||
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
|
}
|
||||||
* overflow (when the result is negative).
|
|
||||||
*
|
|
||||||
* Counterpart to Solidity's `-` operator.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - Subtraction cannot overflow.
|
|
||||||
*/
|
|
||||||
function sub(
|
|
||||||
uint256 a,
|
|
||||||
uint256 b,
|
|
||||||
string memory errorMessage
|
|
||||||
) internal pure returns (uint256) {
|
|
||||||
require(b <= a, errorMessage);
|
|
||||||
uint256 c = a - b;
|
|
||||||
|
|
||||||
return c;
|
/**
|
||||||
}
|
* @dev Returns the integer division of two unsigned integers. Reverts on
|
||||||
|
* division by zero. The result is rounded towards zero.
|
||||||
|
*
|
||||||
|
* Counterpart to Solidity's `/` operator. Note: this function uses a
|
||||||
|
* `revert` opcode (which leaves remaining gas untouched) while Solidity
|
||||||
|
* uses an invalid opcode to revert (consuming all remaining gas).
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - The divisor cannot be zero.
|
||||||
|
*/
|
||||||
|
function div(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
return div(a, b, 'SafeMath: division by zero');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Returns the multiplication of two unsigned integers, reverting on
|
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
|
||||||
* overflow.
|
* division by zero. The result is rounded towards zero.
|
||||||
*
|
*
|
||||||
* Counterpart to Solidity's `*` operator.
|
* Counterpart to Solidity's `/` operator. Note: this function uses a
|
||||||
*
|
* `revert` opcode (which leaves remaining gas untouched) while Solidity
|
||||||
* Requirements:
|
* uses an invalid opcode to revert (consuming all remaining gas).
|
||||||
* - Multiplication cannot overflow.
|
*
|
||||||
*/
|
* Requirements:
|
||||||
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
|
* - The divisor cannot be zero.
|
||||||
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
|
*/
|
||||||
// benefit is lost if 'b' is also tested.
|
function div(
|
||||||
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
|
uint256 a,
|
||||||
if (a == 0) {
|
uint256 b,
|
||||||
return 0;
|
string memory errorMessage
|
||||||
}
|
) internal pure returns (uint256) {
|
||||||
|
// Solidity only automatically asserts when dividing by 0
|
||||||
|
require(b > 0, errorMessage);
|
||||||
|
uint256 c = a / b;
|
||||||
|
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
|
||||||
|
|
||||||
uint256 c = a * b;
|
return c;
|
||||||
require(c / a == b, 'SafeMath: multiplication overflow');
|
}
|
||||||
|
|
||||||
return c;
|
/**
|
||||||
}
|
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
|
||||||
|
* Reverts when dividing by zero.
|
||||||
|
*
|
||||||
|
* Counterpart to Solidity's `%` operator. This function uses a `revert`
|
||||||
|
* opcode (which leaves remaining gas untouched) while Solidity uses an
|
||||||
|
* invalid opcode to revert (consuming all remaining gas).
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - The divisor cannot be zero.
|
||||||
|
*/
|
||||||
|
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
return mod(a, b, 'SafeMath: modulo by zero');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Returns the integer division of two unsigned integers. Reverts on
|
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
|
||||||
* division by zero. The result is rounded towards zero.
|
* Reverts with custom message when dividing by zero.
|
||||||
*
|
*
|
||||||
* Counterpart to Solidity's `/` operator. Note: this function uses a
|
* Counterpart to Solidity's `%` operator. This function uses a `revert`
|
||||||
* `revert` opcode (which leaves remaining gas untouched) while Solidity
|
* opcode (which leaves remaining gas untouched) while Solidity uses an
|
||||||
* uses an invalid opcode to revert (consuming all remaining gas).
|
* invalid opcode to revert (consuming all remaining gas).
|
||||||
*
|
*
|
||||||
* Requirements:
|
* Requirements:
|
||||||
* - The divisor cannot be zero.
|
* - The divisor cannot be zero.
|
||||||
*/
|
*/
|
||||||
function div(uint256 a, uint256 b) internal pure returns (uint256) {
|
function mod(
|
||||||
return div(a, b, 'SafeMath: division by zero');
|
uint256 a,
|
||||||
}
|
uint256 b,
|
||||||
|
string memory errorMessage
|
||||||
/**
|
) internal pure returns (uint256) {
|
||||||
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
|
require(b != 0, errorMessage);
|
||||||
* division by zero. The result is rounded towards zero.
|
return a % b;
|
||||||
*
|
}
|
||||||
* Counterpart to Solidity's `/` operator. Note: this function uses a
|
|
||||||
* `revert` opcode (which leaves remaining gas untouched) while Solidity
|
|
||||||
* uses an invalid opcode to revert (consuming all remaining gas).
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - The divisor cannot be zero.
|
|
||||||
*/
|
|
||||||
function div(
|
|
||||||
uint256 a,
|
|
||||||
uint256 b,
|
|
||||||
string memory errorMessage
|
|
||||||
) internal pure returns (uint256) {
|
|
||||||
// Solidity only automatically asserts when dividing by 0
|
|
||||||
require(b > 0, errorMessage);
|
|
||||||
uint256 c = a / b;
|
|
||||||
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
|
|
||||||
* Reverts when dividing by zero.
|
|
||||||
*
|
|
||||||
* Counterpart to Solidity's `%` operator. This function uses a `revert`
|
|
||||||
* opcode (which leaves remaining gas untouched) while Solidity uses an
|
|
||||||
* invalid opcode to revert (consuming all remaining gas).
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - The divisor cannot be zero.
|
|
||||||
*/
|
|
||||||
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
||||||
return mod(a, b, 'SafeMath: modulo by zero');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
|
|
||||||
* Reverts with custom message when dividing by zero.
|
|
||||||
*
|
|
||||||
* Counterpart to Solidity's `%` operator. This function uses a `revert`
|
|
||||||
* opcode (which leaves remaining gas untouched) while Solidity uses an
|
|
||||||
* invalid opcode to revert (consuming all remaining gas).
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
* - The divisor cannot be zero.
|
|
||||||
*/
|
|
||||||
function mod(
|
|
||||||
uint256 a,
|
|
||||||
uint256 b,
|
|
||||||
string memory errorMessage
|
|
||||||
) internal pure returns (uint256) {
|
|
||||||
require(b != 0, errorMessage);
|
|
||||||
return a % b;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {Errors} from '../helpers/Errors.sol';
|
||||||
**/
|
**/
|
||||||
|
|
||||||
library WadRayMath {
|
library WadRayMath {
|
||||||
|
|
||||||
uint256 internal constant WAD = 1e18;
|
uint256 internal constant WAD = 1e18;
|
||||||
uint256 internal constant halfWAD = WAD / 2;
|
uint256 internal constant halfWAD = WAD / 2;
|
||||||
|
|
||||||
|
@ -55,20 +54,19 @@ library WadRayMath {
|
||||||
* @return the result of a*b, in wad
|
* @return the result of a*b, in wad
|
||||||
**/
|
**/
|
||||||
function wadMul(uint256 a, uint256 b) internal pure returns (uint256) {
|
function wadMul(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
if (a == 0) {
|
||||||
if(a == 0){
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 result = a*b;
|
uint256 result = a * b;
|
||||||
|
|
||||||
require(result/a == b, Errors.MULTIPLICATION_OVERFLOW);
|
require(result / a == b, Errors.MULTIPLICATION_OVERFLOW);
|
||||||
|
|
||||||
result+=halfWAD;
|
result += halfWAD;
|
||||||
|
|
||||||
require(result >= halfWAD, Errors.ADDITION_OVERFLOW);
|
require(result >= halfWAD, Errors.ADDITION_OVERFLOW);
|
||||||
|
|
||||||
return result/WAD;
|
return result / WAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,15 +80,15 @@ library WadRayMath {
|
||||||
|
|
||||||
uint256 halfB = b / 2;
|
uint256 halfB = b / 2;
|
||||||
|
|
||||||
uint256 result = a*WAD;
|
uint256 result = a * WAD;
|
||||||
|
|
||||||
require(result/WAD == a, Errors.MULTIPLICATION_OVERFLOW);
|
require(result / WAD == a, Errors.MULTIPLICATION_OVERFLOW);
|
||||||
|
|
||||||
result += halfB;
|
result += halfB;
|
||||||
|
|
||||||
require(result >= halfB, Errors.ADDITION_OVERFLOW);
|
require(result >= halfB, Errors.ADDITION_OVERFLOW);
|
||||||
|
|
||||||
return result/b;
|
return result / b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,19 +98,19 @@ library WadRayMath {
|
||||||
* @return the result of a*b, in ray
|
* @return the result of a*b, in ray
|
||||||
**/
|
**/
|
||||||
function rayMul(uint256 a, uint256 b) internal pure returns (uint256) {
|
function rayMul(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
if(a == 0){
|
if (a == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 result = a*b;
|
uint256 result = a * b;
|
||||||
|
|
||||||
require(result/a == b, Errors.MULTIPLICATION_OVERFLOW);
|
require(result / a == b, Errors.MULTIPLICATION_OVERFLOW);
|
||||||
|
|
||||||
result+=halfRAY;
|
result += halfRAY;
|
||||||
|
|
||||||
require(result >= halfRAY, Errors.ADDITION_OVERFLOW);
|
require(result >= halfRAY, Errors.ADDITION_OVERFLOW);
|
||||||
|
|
||||||
return result/RAY;
|
return result / RAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,16 +124,15 @@ library WadRayMath {
|
||||||
|
|
||||||
uint256 halfB = b / 2;
|
uint256 halfB = b / 2;
|
||||||
|
|
||||||
uint256 result = a*RAY;
|
uint256 result = a * RAY;
|
||||||
|
|
||||||
require(result/RAY == a, Errors.MULTIPLICATION_OVERFLOW);
|
require(result / RAY == a, Errors.MULTIPLICATION_OVERFLOW);
|
||||||
|
|
||||||
result += halfB;
|
result += halfB;
|
||||||
|
|
||||||
require(result >= halfB, Errors.ADDITION_OVERFLOW);
|
require(result >= halfB, Errors.ADDITION_OVERFLOW);
|
||||||
|
|
||||||
return result/b;
|
return result / b;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,10 +142,10 @@ library WadRayMath {
|
||||||
**/
|
**/
|
||||||
function rayToWad(uint256 a) internal pure returns (uint256) {
|
function rayToWad(uint256 a) internal pure returns (uint256) {
|
||||||
uint256 halfRatio = WAD_RAY_RATIO / 2;
|
uint256 halfRatio = WAD_RAY_RATIO / 2;
|
||||||
uint256 result = halfRatio+a;
|
uint256 result = halfRatio + a;
|
||||||
require(result >= halfRatio, Errors.ADDITION_OVERFLOW);
|
require(result >= halfRatio, Errors.ADDITION_OVERFLOW);
|
||||||
|
|
||||||
return result/WAD_RAY_RATIO;
|
return result / WAD_RAY_RATIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,8 +154,8 @@ library WadRayMath {
|
||||||
* @return a converted in ray
|
* @return a converted in ray
|
||||||
**/
|
**/
|
||||||
function wadToRay(uint256 a) internal pure returns (uint256) {
|
function wadToRay(uint256 a) internal pure returns (uint256) {
|
||||||
uint256 result = a*WAD_RAY_RATIO;
|
uint256 result = a * WAD_RAY_RATIO;
|
||||||
require(result/WAD_RAY_RATIO == a, Errors.MULTIPLICATION_OVERFLOW);
|
require(result / WAD_RAY_RATIO == a, Errors.MULTIPLICATION_OVERFLOW);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,57 +5,57 @@ pragma solidity 0.6.8;
|
||||||
* @dev Collection of functions related to the address type
|
* @dev Collection of functions related to the address type
|
||||||
*/
|
*/
|
||||||
library Address {
|
library Address {
|
||||||
/**
|
/**
|
||||||
* @dev Returns true if `account` is a contract.
|
* @dev Returns true if `account` is a contract.
|
||||||
*
|
*
|
||||||
* [IMPORTANT]
|
* [IMPORTANT]
|
||||||
* ====
|
* ====
|
||||||
* It is unsafe to assume that an address for which this function returns
|
* It is unsafe to assume that an address for which this function returns
|
||||||
* false is an externally-owned account (EOA) and not a contract.
|
* false is an externally-owned account (EOA) and not a contract.
|
||||||
*
|
*
|
||||||
* Among others, `isContract` will return false for the following
|
* Among others, `isContract` will return false for the following
|
||||||
* types of addresses:
|
* types of addresses:
|
||||||
*
|
*
|
||||||
* - an externally-owned account
|
* - an externally-owned account
|
||||||
* - a contract in construction
|
* - a contract in construction
|
||||||
* - an address where a contract will be created
|
* - an address where a contract will be created
|
||||||
* - an address where a contract lived, but was destroyed
|
* - an address where a contract lived, but was destroyed
|
||||||
* ====
|
* ====
|
||||||
*/
|
*/
|
||||||
function isContract(address account) internal view returns (bool) {
|
function isContract(address account) internal view returns (bool) {
|
||||||
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
|
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
|
||||||
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
|
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
|
||||||
// for accounts without code, i.e. `keccak256('')`
|
// for accounts without code, i.e. `keccak256('')`
|
||||||
bytes32 codehash;
|
bytes32 codehash;
|
||||||
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
|
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
|
||||||
// solhint-disable-next-line no-inline-assembly
|
// solhint-disable-next-line no-inline-assembly
|
||||||
assembly {
|
assembly {
|
||||||
codehash := extcodehash(account)
|
codehash := extcodehash(account)
|
||||||
}
|
|
||||||
return (codehash != accountHash && codehash != 0x0);
|
|
||||||
}
|
}
|
||||||
|
return (codehash != accountHash && codehash != 0x0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
|
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
|
||||||
* `recipient`, forwarding all available gas and reverting on errors.
|
* `recipient`, forwarding all available gas and reverting on errors.
|
||||||
*
|
*
|
||||||
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
|
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
|
||||||
* of certain opcodes, possibly making contracts go over the 2300 gas limit
|
* of certain opcodes, possibly making contracts go over the 2300 gas limit
|
||||||
* imposed by `transfer`, making them unable to receive funds via
|
* imposed by `transfer`, making them unable to receive funds via
|
||||||
* `transfer`. {sendValue} removes this limitation.
|
* `transfer`. {sendValue} removes this limitation.
|
||||||
*
|
*
|
||||||
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
|
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
|
||||||
*
|
*
|
||||||
* IMPORTANT: because control is transferred to `recipient`, care must be
|
* IMPORTANT: because control is transferred to `recipient`, care must be
|
||||||
* taken to not create reentrancy vulnerabilities. Consider using
|
* taken to not create reentrancy vulnerabilities. Consider using
|
||||||
* {ReentrancyGuard} or the
|
* {ReentrancyGuard} or the
|
||||||
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
|
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
|
||||||
*/
|
*/
|
||||||
function sendValue(address payable recipient, uint256 amount) internal {
|
function sendValue(address payable recipient, uint256 amount) internal {
|
||||||
require(address(this).balance >= amount, 'Address: insufficient balance');
|
require(address(this).balance >= amount, 'Address: insufficient balance');
|
||||||
|
|
||||||
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
|
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
|
||||||
(bool success, ) = recipient.call{value: amount}('');
|
(bool success, ) = recipient.call{value: amount}('');
|
||||||
require(success, 'Address: unable to send value, recipient may have reverted');
|
require(success, 'Address: unable to send value, recipient may have reverted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ pragma solidity 0.6.8;
|
||||||
* This contract is only required for intermediate, library-like contracts.
|
* This contract is only required for intermediate, library-like contracts.
|
||||||
*/
|
*/
|
||||||
abstract contract Context {
|
abstract contract Context {
|
||||||
function _msgSender() internal virtual view returns (address payable) {
|
function _msgSender() internal virtual view returns (address payable) {
|
||||||
return msg.sender;
|
return msg.sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _msgData() internal virtual view returns (bytes memory) {
|
function _msgData() internal virtual view returns (bytes memory) {
|
||||||
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
|
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
|
||||||
return msg.data;
|
return msg.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
pragma solidity 0.6.8;
|
pragma solidity 0.6.8;
|
||||||
|
|
||||||
import {IERC20} from "../interfaces/IERC20.sol";
|
import {IERC20} from '../interfaces/IERC20.sol';
|
||||||
import {SafeMath} from "../libraries/math/SafeMath.sol";
|
import {SafeMath} from '../libraries/math/SafeMath.sol';
|
||||||
import {Address} from "./Address.sol";
|
import {Address} from './Address.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title SafeERC20
|
* @title SafeERC20
|
||||||
|
@ -16,34 +16,49 @@ import {Address} from "./Address.sol";
|
||||||
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
|
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
|
||||||
*/
|
*/
|
||||||
library SafeERC20 {
|
library SafeERC20 {
|
||||||
using SafeMath for uint256;
|
using SafeMath for uint256;
|
||||||
using Address for address;
|
using Address for address;
|
||||||
|
|
||||||
function safeTransfer(IERC20 token, address to, uint256 value) internal {
|
function safeTransfer(
|
||||||
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
|
IERC20 token,
|
||||||
}
|
address to,
|
||||||
|
uint256 value
|
||||||
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
|
) internal {
|
||||||
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
|
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeApprove(IERC20 token, address spender, uint256 value) internal {
|
function safeTransferFrom(
|
||||||
require((value == 0) || (token.allowance(address(this), spender) == 0),
|
IERC20 token,
|
||||||
"SafeERC20: approve from non-zero to non-zero allowance"
|
address from,
|
||||||
);
|
address to,
|
||||||
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
|
uint256 value
|
||||||
}
|
) internal {
|
||||||
|
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
|
||||||
function callOptionalReturn(IERC20 token, bytes memory data) private {
|
}
|
||||||
require(address(token).isContract(), "SafeERC20: call to non-contract");
|
|
||||||
|
function safeApprove(
|
||||||
// solhint-disable-next-line avoid-low-level-calls
|
IERC20 token,
|
||||||
(bool success, bytes memory returndata) = address(token).call(data);
|
address spender,
|
||||||
require(success, "SafeERC20: low-level call failed");
|
uint256 value
|
||||||
|
) internal {
|
||||||
if (returndata.length > 0) { // Return data is optional
|
require(
|
||||||
// solhint-disable-next-line max-line-length
|
(value == 0) || (token.allowance(address(this), spender) == 0),
|
||||||
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
|
'SafeERC20: approve from non-zero to non-zero allowance'
|
||||||
}
|
);
|
||||||
|
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function callOptionalReturn(IERC20 token, bytes memory data) private {
|
||||||
|
require(address(token).isContract(), 'SafeERC20: call to non-contract');
|
||||||
|
|
||||||
|
// solhint-disable-next-line avoid-low-level-calls
|
||||||
|
(bool success, bytes memory returndata) = address(token).call(data);
|
||||||
|
require(success, 'SafeERC20: low-level call failed');
|
||||||
|
|
||||||
|
if (returndata.length > 0) {
|
||||||
|
// Return data is optional
|
||||||
|
// solhint-disable-next-line max-line-length
|
||||||
|
require(abi.decode(returndata, (bool)), 'SafeERC20: ERC20 operation did not succeed');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
60
contracts/mocks/flashloan/MockSwapAdapter.sol
Normal file
60
contracts/mocks/flashloan/MockSwapAdapter.sol
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// SPDX-License-Identifier: agpl-3.0
|
||||||
|
pragma solidity ^0.6.8;
|
||||||
|
|
||||||
|
import {MintableERC20} from '../tokens/MintableERC20.sol';
|
||||||
|
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
|
||||||
|
import {ISwapAdapter} from '../../interfaces/ISwapAdapter.sol';
|
||||||
|
import {ILendingPool} from "../../interfaces/ILendingPool.sol";
|
||||||
|
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||||
|
|
||||||
|
contract MockSwapAdapter is ISwapAdapter {
|
||||||
|
|
||||||
|
uint256 internal _amountToReturn;
|
||||||
|
bool internal _tryReentrancy;
|
||||||
|
ILendingPoolAddressesProvider public addressesProvider;
|
||||||
|
|
||||||
|
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
|
||||||
|
|
||||||
|
constructor(ILendingPoolAddressesProvider provider) public {
|
||||||
|
addressesProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAmountToReturn(uint256 amount) public {
|
||||||
|
_amountToReturn = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTryReentrancy(bool tryReentrancy) public {
|
||||||
|
_tryReentrancy = tryReentrancy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeOperation(
|
||||||
|
address assetToSwapFrom,
|
||||||
|
address assetToSwapTo,
|
||||||
|
uint256 amountToSwap,
|
||||||
|
address fundsDestination,
|
||||||
|
bytes calldata params
|
||||||
|
) external override {
|
||||||
|
params;
|
||||||
|
IERC20(assetToSwapFrom).transfer(address(1), amountToSwap); // We don't want to keep funds here
|
||||||
|
MintableERC20(assetToSwapTo).mint(_amountToReturn);
|
||||||
|
IERC20(assetToSwapTo).approve(fundsDestination, _amountToReturn);
|
||||||
|
|
||||||
|
if (_tryReentrancy) {
|
||||||
|
ILendingPool(fundsDestination).repayWithCollateral(
|
||||||
|
assetToSwapFrom,
|
||||||
|
assetToSwapTo,
|
||||||
|
address(1), // Doesn't matter, we just want to test the reentrancy
|
||||||
|
1 ether, // Same
|
||||||
|
address(1), // Same
|
||||||
|
"0x"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, _amountToReturn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function burnAsset(IERC20 asset, uint256 amount) public {
|
||||||
|
uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount;
|
||||||
|
asset.transfer(address(0), amountToBurn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
|
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
|
||||||
import {IAToken} from './interfaces/IAToken.sol';
|
import {IAToken} from './interfaces/IAToken.sol';
|
||||||
import {IERC20} from '../interfaces/IERC20.sol';
|
import {IERC20} from '../interfaces/IERC20.sol';
|
||||||
import {SafeERC20} from "../misc/SafeERC20.sol";
|
import {SafeERC20} from '../misc/SafeERC20.sol';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title Aave ERC20 AToken
|
* @title Aave ERC20 AToken
|
||||||
|
@ -26,23 +26,15 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
|
|
||||||
address public immutable UNDERLYING_ASSET_ADDRESS;
|
address public immutable UNDERLYING_ASSET_ADDRESS;
|
||||||
address public immutable RESERVE_TREASURY_ADDRESS;
|
address public immutable RESERVE_TREASURY_ADDRESS;
|
||||||
|
LendingPool public immutable POOL;
|
||||||
|
|
||||||
mapping(address => uint256) private _userIndexes;
|
mapping(address => uint256) private _scaledRedirectedBalances;
|
||||||
mapping(address => address) private _interestRedirectionAddresses;
|
|
||||||
mapping(address => uint256) private _redirectedBalances;
|
|
||||||
mapping(address => address) private _interestRedirectionAllowances;
|
|
||||||
|
|
||||||
LendingPool private immutable _pool;
|
|
||||||
|
|
||||||
uint256 public constant ATOKEN_REVISION = 0x1;
|
uint256 public constant ATOKEN_REVISION = 0x1;
|
||||||
|
|
||||||
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);
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier whenTransferAllowed(address from, uint256 amount) {
|
|
||||||
require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED);
|
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +45,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
string memory tokenName,
|
string memory tokenName,
|
||||||
string memory tokenSymbol
|
string memory tokenSymbol
|
||||||
) public ERC20(tokenName, tokenSymbol, 18) {
|
) public ERC20(tokenName, tokenSymbol, 18) {
|
||||||
_pool = pool;
|
POOL = pool;
|
||||||
UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress;
|
UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress;
|
||||||
RESERVE_TREASURY_ADDRESS = reserveTreasuryAddress;
|
RESERVE_TREASURY_ADDRESS = reserveTreasuryAddress;
|
||||||
}
|
}
|
||||||
|
@ -72,56 +64,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
_setDecimals(underlyingAssetDecimals);
|
_setDecimals(underlyingAssetDecimals);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @notice ERC20 implementation internal function backing transfer() and transferFrom()
|
|
||||||
* @dev validates the transfer before allowing it. NOTE: This is not standard ERC20 behavior
|
|
||||||
**/
|
|
||||||
function _transfer(
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 amount
|
|
||||||
) internal override whenTransferAllowed(from, amount) {
|
|
||||||
_executeTransfer(from, to, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev redirects the interest generated to a target address.
|
|
||||||
* when the interest is redirected, the user balance is added to
|
|
||||||
* the recepient redirected balance.
|
|
||||||
* @param to the address to which the interest will be redirected
|
|
||||||
**/
|
|
||||||
function redirectInterestStream(address to) external override {
|
|
||||||
_redirectInterestStream(msg.sender, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev redirects the interest generated by from to a target address.
|
|
||||||
* when the interest is redirected, the user balance is added to
|
|
||||||
* the recepient redirected balance. The caller needs to have allowance on
|
|
||||||
* the interest redirection to be able to execute the function.
|
|
||||||
* @param from the address of the user whom interest is being redirected
|
|
||||||
* @param to the address to which the interest will be redirected
|
|
||||||
**/
|
|
||||||
function redirectInterestStreamOf(address from, address to) external override {
|
|
||||||
require(
|
|
||||||
msg.sender == _interestRedirectionAllowances[from],
|
|
||||||
Errors.INTEREST_REDIRECTION_NOT_ALLOWED
|
|
||||||
);
|
|
||||||
_redirectInterestStream(from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev gives allowance to an address to execute the interest redirection
|
|
||||||
* on behalf of the caller.
|
|
||||||
* @param to the address to which the interest will be redirected. Pass address(0) to reset
|
|
||||||
* the allowance.
|
|
||||||
**/
|
|
||||||
function allowInterestRedirectionTo(address to) external override {
|
|
||||||
require(to != msg.sender, Errors.CANNOT_GIVE_ALLOWANCE_TO_HIMSELF);
|
|
||||||
_interestRedirectionAllowances[msg.sender] = to;
|
|
||||||
emit InterestRedirectionAllowanceChanged(msg.sender, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev burns the aTokens and sends the equivalent amount of underlying to the target.
|
* @dev burns the aTokens and sends the equivalent amount of underlying to the target.
|
||||||
* only lending pools can call this function
|
* only lending pools can call this function
|
||||||
|
@ -129,37 +71,24 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
**/
|
**/
|
||||||
function burn(
|
function burn(
|
||||||
address user,
|
address user,
|
||||||
address underlyingTarget,
|
address receiverOfUnderlying,
|
||||||
uint256 amount
|
uint256 amount,
|
||||||
|
uint256 index
|
||||||
) external override onlyLendingPool {
|
) external override onlyLendingPool {
|
||||||
//cumulates the balance of the user
|
|
||||||
(, uint256 currentBalance, uint256 balanceIncrease) = _calculateBalanceIncrease(user);
|
|
||||||
|
|
||||||
//if the user is redirecting his interest towards someone else,
|
uint256 currentBalance = balanceOf(user);
|
||||||
//we update the redirected balance of the redirection address by adding the accrued interest,
|
|
||||||
//and removing the amount to redeem
|
|
||||||
_updateRedirectedBalanceOfRedirectionAddress(user, balanceIncrease, amount);
|
|
||||||
|
|
||||||
if (balanceIncrease > amount) {
|
require(amount <= currentBalance, Errors.INVALID_ATOKEN_BALANCE);
|
||||||
_mint(user, balanceIncrease.sub(amount));
|
|
||||||
} else {
|
|
||||||
_burn(user, amount.sub(balanceIncrease));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 userIndex = 0;
|
uint256 scaledAmount = amount.rayDiv(index);
|
||||||
|
|
||||||
//reset the user data if the remaining balance is 0
|
_burn(user, scaledAmount);
|
||||||
if (currentBalance.sub(amount) == 0) {
|
|
||||||
_resetDataOnZeroBalance(user);
|
|
||||||
} else {
|
|
||||||
//updates the user index
|
|
||||||
userIndex = _userIndexes[user] = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
//transfers the underlying to the target
|
//transfers the underlying to the target
|
||||||
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(underlyingTarget, amount);
|
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount);
|
||||||
|
|
||||||
emit Burn(msg.sender, underlyingTarget, amount, balanceIncrease, userIndex);
|
|
||||||
|
emit Burn(msg.sender, receiverOfUnderlying, amount, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,22 +97,15 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
* @param user the address receiving the minted tokens
|
* @param user the address receiving the minted tokens
|
||||||
* @param amount the amount of tokens to mint
|
* @param amount the amount of tokens to mint
|
||||||
*/
|
*/
|
||||||
function mint(address user, uint256 amount) external override onlyLendingPool {
|
function mint(address user, uint256 amount, uint256 index) external override onlyLendingPool {
|
||||||
//cumulates the balance of the user
|
|
||||||
(, , uint256 balanceIncrease) = _calculateBalanceIncrease(user);
|
|
||||||
|
|
||||||
//updates the user index
|
|
||||||
uint256 index = _userIndexes[user] = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
||||||
|
|
||||||
//if the user is redirecting his interest towards someone else,
|
|
||||||
//we update the redirected balance of the redirection address by adding the accrued interest
|
|
||||||
//and the amount deposited
|
|
||||||
_updateRedirectedBalanceOfRedirectionAddress(user, balanceIncrease.add(amount), 0);
|
|
||||||
|
|
||||||
|
uint256 scaledAmount = amount.rayDiv(index);
|
||||||
|
|
||||||
//mint an equivalent amount of tokens to cover the new deposit
|
//mint an equivalent amount of tokens to cover the new deposit
|
||||||
_mint(user, amount.add(balanceIncrease));
|
_mint(user,scaledAmount);
|
||||||
|
|
||||||
emit Mint(user, amount, balanceIncrease, index);
|
emit Mint(user, amount, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mintToReserve(uint256 amount) external override onlyLendingPool {
|
function mintToReserve(uint256 amount) external override onlyLendingPool {
|
||||||
|
@ -205,52 +127,26 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
) external override onlyLendingPool {
|
) external override onlyLendingPool {
|
||||||
//being a normal transfer, the Transfer() and BalanceTransfer() are emitted
|
//being a normal transfer, the Transfer() and BalanceTransfer() are emitted
|
||||||
//so no need to emit a specific event here
|
//so no need to emit a specific event here
|
||||||
_executeTransfer(from, to, value);
|
_transfer(from, to, value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev calculates the balance of the user, which is the
|
* @dev calculates the balance of the user, which is the
|
||||||
* principal balance + interest generated by the principal balance + interest generated by the redirected balance
|
* principal balance + interest generated by the principal balance
|
||||||
* @param user the user for which the balance is being calculated
|
* @param user the user for which the balance is being calculated
|
||||||
* @return the total balance of the user
|
* @return the total balance of the user
|
||||||
**/
|
**/
|
||||||
function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) {
|
function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) {
|
||||||
//current principal balance of the user
|
return super.balanceOf(user).rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS));
|
||||||
uint256 currentPrincipalBalance = super.balanceOf(user);
|
|
||||||
//balance redirected by other users to user for interest rate accrual
|
|
||||||
uint256 redirectedBalance = _redirectedBalances[user];
|
|
||||||
|
|
||||||
if (currentPrincipalBalance == 0 && redirectedBalance == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
//if the user is not redirecting the interest to anybody, accrues
|
|
||||||
//the interest for himself
|
|
||||||
|
|
||||||
if (_interestRedirectionAddresses[user] == address(0)) {
|
|
||||||
//accruing for himself means that both the principal balance and
|
|
||||||
//the redirected balance partecipate in the interest
|
|
||||||
return
|
|
||||||
_calculateCumulatedBalance(user, currentPrincipalBalance.add(redirectedBalance)).sub(
|
|
||||||
redirectedBalance
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
//if the user redirected the interest, then only the redirected
|
|
||||||
//balance generates interest. In that case, the interest generated
|
|
||||||
//by the redirected balance is added to the current principal balance.
|
|
||||||
return
|
|
||||||
currentPrincipalBalance.add(
|
|
||||||
_calculateCumulatedBalance(user, redirectedBalance).sub(redirectedBalance)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev returns the principal balance of the user. The principal balance is the last
|
* @dev returns the scaled balance of the user. The scaled balance is the sum of all the
|
||||||
* updated stored balance, which does not consider the perpetually accruing interest.
|
* updated stored balance divided the reserve index at the moment of the update
|
||||||
* @param user the address of the user
|
* @param user the address of the user
|
||||||
* @return the principal balance of the user
|
* @return the scaled balance of the user
|
||||||
**/
|
**/
|
||||||
function principalBalanceOf(address user) external override view returns (uint256) {
|
function scaledBalanceOf(address user) external override view returns (uint256) {
|
||||||
return super.balanceOf(user);
|
return super.balanceOf(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,17 +157,15 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
* @return the current total supply
|
* @return the current total supply
|
||||||
**/
|
**/
|
||||||
function totalSupply() public override(ERC20, IERC20) view returns (uint256) {
|
function totalSupply() public override(ERC20, IERC20) view returns (uint256) {
|
||||||
uint256 currentSupplyPrincipal = super.totalSupply();
|
uint256 currentSupplyScaled = super.totalSupply();
|
||||||
|
|
||||||
if (currentSupplyPrincipal == 0) {
|
if (currentSupplyScaled == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
currentSupplyPrincipal
|
currentSupplyScaled
|
||||||
.wadToRay()
|
.rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS));
|
||||||
.rayMul(_pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS))
|
|
||||||
.rayToWad();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -281,284 +175,16 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
* @return true if the user can transfer amount, false otherwise
|
* @return true if the user can transfer amount, false otherwise
|
||||||
**/
|
**/
|
||||||
function isTransferAllowed(address user, uint256 amount) public override view returns (bool) {
|
function isTransferAllowed(address user, uint256 amount) public override view returns (bool) {
|
||||||
return _pool.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount);
|
return POOL.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev returns the last index of the user, used to calculate the balance of the user
|
* @dev transfers the underlying asset to the target. Used by the lendingpool to transfer
|
||||||
* @param user address of the user
|
* assets in borrow(), redeem() and flashLoan()
|
||||||
* @return the last user index
|
* @param target the target of the transfer
|
||||||
**/
|
* @param amount the amount to transfer
|
||||||
function getUserIndex(address user) external override view returns (uint256) {
|
* @return the amount transferred
|
||||||
return _userIndexes[user];
|
**/
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev returns the address to which the interest is redirected
|
|
||||||
* @param user address of the user
|
|
||||||
* @return 0 if there is no redirection, an address otherwise
|
|
||||||
**/
|
|
||||||
function getInterestRedirectionAddress(address user) external override view returns (address) {
|
|
||||||
return _interestRedirectionAddresses[user];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev returns the redirected balance of the user. The redirected balance is the balance
|
|
||||||
* redirected by other accounts to the user, that is accrueing interest for him.
|
|
||||||
* @param user address of the user
|
|
||||||
* @return the total redirected balance
|
|
||||||
**/
|
|
||||||
function getRedirectedBalance(address user) external override view returns (uint256) {
|
|
||||||
return _redirectedBalances[user];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev calculates the increase in balance since the last user action
|
|
||||||
* @param user the address of the user
|
|
||||||
* @return the last user principal balance, the current balance and the balance increase
|
|
||||||
**/
|
|
||||||
function _calculateBalanceIncrease(address user)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (
|
|
||||||
uint256,
|
|
||||||
uint256,
|
|
||||||
uint256
|
|
||||||
)
|
|
||||||
{
|
|
||||||
uint256 currentBalance = balanceOf(user);
|
|
||||||
uint256 balanceIncrease = 0;
|
|
||||||
uint256 previousBalance = 0;
|
|
||||||
|
|
||||||
if (currentBalance != 0) {
|
|
||||||
previousBalance = super.balanceOf(user);
|
|
||||||
//calculate the accrued interest since the last accumulation
|
|
||||||
balanceIncrease = currentBalance.sub(previousBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (previousBalance, currentBalance, balanceIncrease);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev accumulates the accrued interest of the user to the principal balance
|
|
||||||
* @param user the address of the user for which the interest is being accumulated
|
|
||||||
* @return the previous principal balance, the new principal balance, the balance increase
|
|
||||||
* and the new user index
|
|
||||||
**/
|
|
||||||
function _cumulateBalance(address user)
|
|
||||||
internal
|
|
||||||
returns (
|
|
||||||
uint256,
|
|
||||||
uint256,
|
|
||||||
uint256,
|
|
||||||
uint256
|
|
||||||
)
|
|
||||||
{
|
|
||||||
(
|
|
||||||
uint256 previousBalance,
|
|
||||||
uint256 currentBalance,
|
|
||||||
uint256 balanceIncrease
|
|
||||||
) = _calculateBalanceIncrease(user);
|
|
||||||
|
|
||||||
_mint(user, balanceIncrease);
|
|
||||||
|
|
||||||
//updates the user index
|
|
||||||
uint256 index = _userIndexes[user] = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
||||||
|
|
||||||
return (previousBalance, currentBalance, balanceIncrease, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev updates the redirected balance of the user. If the user is not redirecting his
|
|
||||||
* interest, nothing is executed.
|
|
||||||
* @param user the address of the user for which the interest is being accumulated
|
|
||||||
* @param balanceToAdd the amount to add to the redirected balance
|
|
||||||
* @param balanceToRemove the amount to remove from the redirected balance
|
|
||||||
**/
|
|
||||||
function _updateRedirectedBalanceOfRedirectionAddress(
|
|
||||||
address user,
|
|
||||||
uint256 balanceToAdd,
|
|
||||||
uint256 balanceToRemove
|
|
||||||
) internal {
|
|
||||||
address redirectionAddress = _interestRedirectionAddresses[user];
|
|
||||||
//if there isn't any redirection, nothing to be done
|
|
||||||
if (redirectionAddress == address(0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//compound balances of the redirected address
|
|
||||||
(, , uint256 balanceIncrease, uint256 index) = _cumulateBalance(redirectionAddress);
|
|
||||||
|
|
||||||
//updating the redirected balance
|
|
||||||
_redirectedBalances[redirectionAddress] = _redirectedBalances[redirectionAddress]
|
|
||||||
.add(balanceToAdd)
|
|
||||||
.sub(balanceToRemove);
|
|
||||||
|
|
||||||
//if the interest of redirectionAddress is also being redirected, we need to update
|
|
||||||
//the redirected balance of the redirection target by adding the balance increase
|
|
||||||
address targetOfRedirectionAddress = _interestRedirectionAddresses[redirectionAddress];
|
|
||||||
|
|
||||||
// if the redirection address is also redirecting the interest, we accumulate his balance
|
|
||||||
// and update his chain of redirection
|
|
||||||
if (targetOfRedirectionAddress != address(0)) {
|
|
||||||
_updateRedirectedBalanceOfRedirectionAddress(redirectionAddress, balanceIncrease, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit RedirectedBalanceUpdated(
|
|
||||||
redirectionAddress,
|
|
||||||
balanceIncrease,
|
|
||||||
index,
|
|
||||||
balanceToAdd,
|
|
||||||
balanceToRemove
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev calculate the interest accrued by user on a specific balance
|
|
||||||
* @param user the address of the user for which the interest is being accumulated
|
|
||||||
* @param balance the balance on which the interest is calculated
|
|
||||||
* @return the interest rate accrued
|
|
||||||
**/
|
|
||||||
function _calculateCumulatedBalance(address user, uint256 balance)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
balance
|
|
||||||
.wadToRay()
|
|
||||||
.rayMul(_pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS))
|
|
||||||
.rayDiv(_userIndexes[user])
|
|
||||||
.rayToWad();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev executes the transfer of aTokens, invoked by both _transfer() and
|
|
||||||
* transferOnLiquidation()
|
|
||||||
* @param from the address from which transfer the aTokens
|
|
||||||
* @param to the destination address
|
|
||||||
* @param value the amount to transfer
|
|
||||||
**/
|
|
||||||
function _executeTransfer(
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 value
|
|
||||||
) internal {
|
|
||||||
require(value > 0, Errors.TRANSFER_AMOUNT_NOT_GT_0);
|
|
||||||
|
|
||||||
//cumulate the balance of the sender
|
|
||||||
(, uint256 fromBalance, uint256 fromBalanceIncrease, uint256 fromIndex) = _cumulateBalance(
|
|
||||||
from
|
|
||||||
);
|
|
||||||
|
|
||||||
//cumulate the balance of the receiver
|
|
||||||
(, , uint256 toBalanceIncrease, uint256 toIndex) = _cumulateBalance(to);
|
|
||||||
|
|
||||||
//if the sender is redirecting his interest towards someone else,
|
|
||||||
//adds to the redirected balance the accrued interest and removes the amount
|
|
||||||
//being transferred
|
|
||||||
_updateRedirectedBalanceOfRedirectionAddress(from, fromBalanceIncrease, value);
|
|
||||||
|
|
||||||
//if the receiver is redirecting his interest towards someone else,
|
|
||||||
//adds to the redirected balance the accrued interest and the amount
|
|
||||||
//being transferred
|
|
||||||
_updateRedirectedBalanceOfRedirectionAddress(to, toBalanceIncrease.add(value), 0);
|
|
||||||
|
|
||||||
//performs the transfer
|
|
||||||
super._transfer(from, to, value);
|
|
||||||
|
|
||||||
bool fromIndexReset = false;
|
|
||||||
//reset the user data if the remaining balance is 0
|
|
||||||
if (fromBalance.sub(value) == 0 && from != to) {
|
|
||||||
fromIndexReset = _resetDataOnZeroBalance(from);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit BalanceTransfer(
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
value,
|
|
||||||
fromBalanceIncrease,
|
|
||||||
toBalanceIncrease,
|
|
||||||
fromIndexReset ? 0 : fromIndex,
|
|
||||||
toIndex
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev executes the redirection of the interest from one address to another.
|
|
||||||
* immediately after redirection, the destination address will start to accrue interest.
|
|
||||||
* @param from the address from which transfer the aTokens
|
|
||||||
* @param to the destination address
|
|
||||||
**/
|
|
||||||
function _redirectInterestStream(address from, address to) internal {
|
|
||||||
address currentRedirectionAddress = _interestRedirectionAddresses[from];
|
|
||||||
|
|
||||||
require(to != currentRedirectionAddress, Errors.INTEREST_ALREADY_REDIRECTED);
|
|
||||||
|
|
||||||
//accumulates the accrued interest to the principal
|
|
||||||
(
|
|
||||||
uint256 previousPrincipalBalance,
|
|
||||||
uint256 fromBalance,
|
|
||||||
uint256 balanceIncrease,
|
|
||||||
uint256 fromIndex
|
|
||||||
) = _cumulateBalance(from);
|
|
||||||
|
|
||||||
require(fromBalance > 0, Errors.NO_VALID_BALANCE_FOR_REDIRECTION);
|
|
||||||
|
|
||||||
//if the user is already redirecting the interest to someone, before changing
|
|
||||||
//the redirection address we substract the redirected balance of the previous
|
|
||||||
//recipient
|
|
||||||
if (currentRedirectionAddress != address(0)) {
|
|
||||||
_updateRedirectedBalanceOfRedirectionAddress(from, 0, previousPrincipalBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if the user is redirecting the interest back to himself,
|
|
||||||
//we simply set to 0 the interest redirection address
|
|
||||||
if (to == from) {
|
|
||||||
_interestRedirectionAddresses[from] = address(0);
|
|
||||||
emit InterestStreamRedirected(from, address(0), fromBalance, balanceIncrease, fromIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//first set the redirection address to the new recipient
|
|
||||||
_interestRedirectionAddresses[from] = to;
|
|
||||||
|
|
||||||
//adds the user balance to the redirected balance of the destination
|
|
||||||
_updateRedirectedBalanceOfRedirectionAddress(from, fromBalance, 0);
|
|
||||||
|
|
||||||
emit InterestStreamRedirected(from, to, fromBalance, balanceIncrease, fromIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev function to reset the interest stream redirection and the user index, if the
|
|
||||||
* user has no balance left.
|
|
||||||
* @param user the address of the user
|
|
||||||
* @return true if the user index has also been reset, false otherwise. useful to emit the proper user index value
|
|
||||||
**/
|
|
||||||
function _resetDataOnZeroBalance(address user) internal returns (bool) {
|
|
||||||
//if the user has 0 principal balance, the interest stream redirection gets reset
|
|
||||||
_interestRedirectionAddresses[user] = address(0);
|
|
||||||
|
|
||||||
//emits a InterestStreamRedirected event to notify that the redirection has been reset
|
|
||||||
emit InterestStreamRedirected(user, address(0), 0, 0, 0);
|
|
||||||
|
|
||||||
//if the redirected balance is also 0, we clear up the user index
|
|
||||||
if (_redirectedBalances[user] == 0) {
|
|
||||||
_userIndexes[user] = 0;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev transfers the underlying asset to the target. Used by the lendingpool to transfer
|
|
||||||
* assets in borrow(), redeem() and flashLoan()
|
|
||||||
* @param target the target of the transfer
|
|
||||||
* @param amount the amount to transfer
|
|
||||||
* @return the amount transferred
|
|
||||||
**/
|
|
||||||
|
|
||||||
function transferUnderlyingTo(address target, uint256 amount)
|
function transferUnderlyingTo(address target, uint256 amount)
|
||||||
external
|
external
|
||||||
override
|
override
|
||||||
|
@ -569,6 +195,34 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _transfer(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount,
|
||||||
|
bool validate
|
||||||
|
) internal {
|
||||||
|
if(validate){
|
||||||
|
require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 index = POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
||||||
|
|
||||||
|
uint256 scaledAmount = amount.rayDiv(index);
|
||||||
|
|
||||||
|
super._transfer(from, to, scaledAmount);
|
||||||
|
|
||||||
|
emit BalanceTransfer(from, to, amount, index);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function _transfer(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 amount
|
||||||
|
) internal override {
|
||||||
|
|
||||||
|
_transfer(from, to, amount, true);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @dev aTokens should not receive ETH
|
* @dev aTokens should not receive ETH
|
||||||
**/
|
**/
|
||||||
|
|
|
@ -118,7 +118,7 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
|
||||||
.add(vars.amountInRay.rayMul(rate))
|
.add(vars.amountInRay.rayMul(rate))
|
||||||
.rayDiv(currentBalance.add(amount).wadToRay());
|
.rayDiv(currentBalance.add(amount).wadToRay());
|
||||||
|
|
||||||
require(vars.newStableRate < (1 << 128), "Debt token: stable rate overflow");
|
require(vars.newStableRate < (1 << 128), 'Debt token: stable rate overflow');
|
||||||
_usersData[user] = vars.newStableRate;
|
_usersData[user] = vars.newStableRate;
|
||||||
|
|
||||||
//solium-disable-next-line
|
//solium-disable-next-line
|
||||||
|
|
|
@ -60,7 +60,6 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
|
||||||
uint256 index = POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET);
|
uint256 index = POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET);
|
||||||
|
|
||||||
_mint(user, amount.rayDiv(index));
|
_mint(user, amount.rayDiv(index));
|
||||||
_userIndexes[user] = index;
|
|
||||||
emit MintDebt(user, amount, index);
|
emit MintDebt(user, amount, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,14 +76,6 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
|
||||||
emit BurnDebt(user, amount, index);
|
emit BurnDebt(user, amount, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Returns the principal debt balance of the user from
|
|
||||||
* @return The debt balance of the user since the last burn/mint action
|
|
||||||
**/
|
|
||||||
function principalBalanceOf(address user) public virtual override view returns (uint256) {
|
|
||||||
return super.balanceOf(user).rayMul(_userIndexes[user]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Returns the principal debt balance of the user from
|
* @dev Returns the principal debt balance of the user from
|
||||||
* @return The debt balance of the user since the last burn/mint action
|
* @return The debt balance of the user since the last burn/mint action
|
||||||
|
|
|
@ -5,7 +5,9 @@ import {Context} from '@openzeppelin/contracts/GSN/Context.sol';
|
||||||
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
|
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
|
||||||
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
|
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
|
||||||
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
|
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
|
||||||
import {VersionedInitializable} from '../../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
|
import {
|
||||||
|
VersionedInitializable
|
||||||
|
} from '../../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
|
||||||
import {ERC20} from '../ERC20.sol';
|
import {ERC20} from '../ERC20.sol';
|
||||||
import {Errors} from '../../libraries/helpers/Errors.sol';
|
import {Errors} from '../../libraries/helpers/Errors.sol';
|
||||||
|
|
||||||
|
@ -16,7 +18,6 @@ import {Errors} from '../../libraries/helpers/Errors.sol';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
abstract contract DebtTokenBase is ERC20, VersionedInitializable {
|
abstract contract DebtTokenBase is ERC20, VersionedInitializable {
|
||||||
|
|
||||||
address internal immutable UNDERLYING_ASSET;
|
address internal immutable UNDERLYING_ASSET;
|
||||||
ILendingPool internal immutable POOL;
|
ILendingPool internal immutable POOL;
|
||||||
mapping(address => uint256) internal _usersData;
|
mapping(address => uint256) internal _usersData;
|
||||||
|
@ -29,7 +30,7 @@ abstract contract DebtTokenBase is ERC20, VersionedInitializable {
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev The metadata of the token will be set on the proxy, that the reason of
|
* @dev The metadata of the token will be set on the proxy, that the reason of
|
||||||
* passing "NULL" and 0 as metadata
|
* passing "NULL" and 0 as metadata
|
||||||
*/
|
*/
|
||||||
|
@ -37,7 +38,7 @@ abstract contract DebtTokenBase is ERC20, VersionedInitializable {
|
||||||
address pool,
|
address pool,
|
||||||
address underlyingAssetAddress,
|
address underlyingAssetAddress,
|
||||||
string memory name,
|
string memory name,
|
||||||
string memory symbol
|
string memory symbol
|
||||||
) public ERC20(name, symbol, 18) {
|
) public ERC20(name, symbol, 18) {
|
||||||
POOL = ILendingPool(pool);
|
POOL = ILendingPool(pool);
|
||||||
UNDERLYING_ASSET = underlyingAssetAddress;
|
UNDERLYING_ASSET = underlyingAssetAddress;
|
||||||
|
@ -76,32 +77,59 @@ abstract contract DebtTokenBase is ERC20, VersionedInitializable {
|
||||||
* standard ERC20 functions for transfer and allowance.
|
* standard ERC20 functions for transfer and allowance.
|
||||||
**/
|
**/
|
||||||
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
|
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
|
||||||
recipient; amount;
|
recipient;
|
||||||
|
amount;
|
||||||
revert('TRANSFER_NOT_SUPPORTED');
|
revert('TRANSFER_NOT_SUPPORTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowance(address owner, address spender) public virtual override view returns (uint256) {
|
function allowance(address owner, address spender)
|
||||||
owner; spender;
|
public
|
||||||
|
virtual
|
||||||
|
override
|
||||||
|
view
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
owner;
|
||||||
|
spender;
|
||||||
revert('ALLOWANCE_NOT_SUPPORTED');
|
revert('ALLOWANCE_NOT_SUPPORTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
function approve(address spender, uint256 amount) public virtual override returns (bool) {
|
function approve(address spender, uint256 amount) public virtual override returns (bool) {
|
||||||
spender; amount;
|
spender;
|
||||||
|
amount;
|
||||||
revert('APPROVAL_NOT_SUPPORTED');
|
revert('APPROVAL_NOT_SUPPORTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
|
function transferFrom(
|
||||||
sender; recipient; amount;
|
address sender,
|
||||||
|
address recipient,
|
||||||
|
uint256 amount
|
||||||
|
) public virtual override returns (bool) {
|
||||||
|
sender;
|
||||||
|
recipient;
|
||||||
|
amount;
|
||||||
revert('TRANSFER_NOT_SUPPORTED');
|
revert('TRANSFER_NOT_SUPPORTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
function increaseAllowance(address spender, uint256 addedValue) public virtual override returns (bool) {
|
function increaseAllowance(address spender, uint256 addedValue)
|
||||||
spender; addedValue;
|
public
|
||||||
|
virtual
|
||||||
|
override
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
spender;
|
||||||
|
addedValue;
|
||||||
revert('ALLOWANCE_NOT_SUPPORTED');
|
revert('ALLOWANCE_NOT_SUPPORTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual override returns (bool) {
|
function decreaseAllowance(address spender, uint256 subtractedValue)
|
||||||
spender; subtractedValue;
|
public
|
||||||
|
virtual
|
||||||
|
override
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
spender;
|
||||||
|
subtractedValue;
|
||||||
revert('ALLOWANCE_NOT_SUPPORTED');
|
revert('ALLOWANCE_NOT_SUPPORTED');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,115 +8,48 @@ interface IAToken is IERC20 {
|
||||||
* @dev emitted after aTokens are burned
|
* @dev emitted after aTokens are burned
|
||||||
* @param from the address performing the redeem
|
* @param from the address performing the redeem
|
||||||
* @param value the amount to be redeemed
|
* @param value the amount to be redeemed
|
||||||
* @param fromBalanceIncrease the cumulated balance since the last update of the user
|
* @param index the last index of the reserve
|
||||||
* @param fromIndex the last index of the user
|
|
||||||
**/
|
**/
|
||||||
event Burn(
|
event Burn(
|
||||||
address indexed from,
|
address indexed from,
|
||||||
address indexed target,
|
address indexed target,
|
||||||
uint256 value,
|
uint256 value,
|
||||||
uint256 fromBalanceIncrease,
|
uint256 index
|
||||||
uint256 fromIndex
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev emitted after the mint action
|
* @dev emitted after the mint action
|
||||||
* @param from the address performing the mint
|
* @param from the address performing the mint
|
||||||
* @param value the amount to be minted
|
* @param value the amount to be minted
|
||||||
* @param fromBalanceIncrease the cumulated balance since the last update of the user
|
* @param index the last index of the reserve
|
||||||
* @param fromIndex the last index of the user
|
|
||||||
**/
|
**/
|
||||||
event Mint(address indexed from, uint256 value, uint256 fromBalanceIncrease, uint256 fromIndex);
|
event Mint(address indexed from, uint256 value, uint256 index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev emitted during the transfer action
|
* @dev emitted during the transfer action
|
||||||
* @param from the address from which the tokens are being transferred
|
* @param from the address from which the tokens are being transferred
|
||||||
* @param to the adress of the destination
|
* @param to the adress of the destination
|
||||||
* @param value the amount to be minted
|
* @param value the amount to be minted
|
||||||
* @param fromBalanceIncrease the cumulated balance since the last update of the user
|
* @param index the last index of the reserve
|
||||||
* @param toBalanceIncrease the cumulated balance since the last update of the destination
|
|
||||||
* @param fromIndex the last index of the user
|
|
||||||
* @param toIndex the last index of the liquidator
|
|
||||||
**/
|
**/
|
||||||
event BalanceTransfer(
|
event BalanceTransfer(
|
||||||
address indexed from,
|
address indexed from,
|
||||||
address indexed to,
|
address indexed to,
|
||||||
uint256 value,
|
uint256 value,
|
||||||
uint256 fromBalanceIncrease,
|
uint256 index
|
||||||
uint256 toBalanceIncrease,
|
|
||||||
uint256 fromIndex,
|
|
||||||
uint256 toIndex
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev emitted when the accumulation of the interest
|
|
||||||
* by an user is redirected to another user
|
|
||||||
* @param from the address from which the interest is being redirected
|
|
||||||
* @param to the adress of the destination
|
|
||||||
* @param fromBalanceIncrease the cumulated balance since the last update of the user
|
|
||||||
* @param fromIndex the last index of the user
|
|
||||||
**/
|
|
||||||
event InterestStreamRedirected(
|
|
||||||
address indexed from,
|
|
||||||
address indexed to,
|
|
||||||
uint256 redirectedBalance,
|
|
||||||
uint256 fromBalanceIncrease,
|
|
||||||
uint256 fromIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev emitted when the redirected balance of an user is being updated
|
|
||||||
* @param targetAddress the address of which the balance is being updated
|
|
||||||
* @param targetBalanceIncrease the cumulated balance since the last update of the target
|
|
||||||
* @param targetIndex the last index of the user
|
|
||||||
* @param redirectedBalanceAdded the redirected balance being added
|
|
||||||
* @param redirectedBalanceRemoved the redirected balance being removed
|
|
||||||
**/
|
|
||||||
event RedirectedBalanceUpdated(
|
|
||||||
address indexed targetAddress,
|
|
||||||
uint256 targetBalanceIncrease,
|
|
||||||
uint256 targetIndex,
|
|
||||||
uint256 redirectedBalanceAdded,
|
|
||||||
uint256 redirectedBalanceRemoved
|
|
||||||
);
|
|
||||||
|
|
||||||
event InterestRedirectionAllowanceChanged(address indexed from, address indexed to);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev redirects the interest generated to a target address.
|
|
||||||
* when the interest is redirected, the user balance is added to
|
|
||||||
* the recepient redirected balance.
|
|
||||||
* @param to the address to which the interest will be redirected
|
|
||||||
**/
|
|
||||||
function redirectInterestStream(address to) external;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev redirects the interest generated by from to a target address.
|
|
||||||
* when the interest is redirected, the user balance is added to
|
|
||||||
* the recepient redirected balance. The caller needs to have allowance on
|
|
||||||
* the interest redirection to be able to execute the function.
|
|
||||||
* @param from the address of the user whom interest is being redirected
|
|
||||||
* @param to the address to which the interest will be redirected
|
|
||||||
**/
|
|
||||||
function redirectInterestStreamOf(address from, address to) external;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev gives allowance to an address to execute the interest redirection
|
|
||||||
* on behalf of the caller.
|
|
||||||
* @param to the address to which the interest will be redirected. Pass address(0) to reset
|
|
||||||
* the allowance.
|
|
||||||
**/
|
|
||||||
function allowInterestRedirectionTo(address to) external;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev burns the aTokens and sends the equivalent amount of underlying to the target.
|
* @dev burns the aTokens and sends the equivalent amount of underlying to the target.
|
||||||
* only lending pools can call this function
|
* only lending pools can call this function
|
||||||
* @param amount the amount being burned
|
* @param amount the amount being burned
|
||||||
|
* @param index the liquidity index
|
||||||
**/
|
**/
|
||||||
function burn(
|
function burn(
|
||||||
address user,
|
address user,
|
||||||
address underlyingTarget,
|
address underlyingTarget,
|
||||||
uint256 amount
|
uint256 amount,
|
||||||
|
uint256 index
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,8 +57,9 @@ interface IAToken is IERC20 {
|
||||||
* only lending pools can call this function
|
* only lending pools can call this function
|
||||||
* @param user the address receiving the minted tokens
|
* @param user the address receiving the minted tokens
|
||||||
* @param amount the amount of tokens to mint
|
* @param amount the amount of tokens to mint
|
||||||
|
* @param index the liquidity index
|
||||||
*/
|
*/
|
||||||
function mint(address user, uint256 amount) external;
|
function mint(address user, uint256 amount, uint256 index) external;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev mints aTokens to reserve, based on the reserveFactor value
|
* @dev mints aTokens to reserve, based on the reserveFactor value
|
||||||
|
@ -154,7 +88,7 @@ interface IAToken is IERC20 {
|
||||||
* @param user the address of the user
|
* @param user the address of the user
|
||||||
* @return the principal balance of the user
|
* @return the principal balance of the user
|
||||||
**/
|
**/
|
||||||
function principalBalanceOf(address user) external view returns (uint256);
|
function scaledBalanceOf(address user) external view returns (uint256);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Used to validate transfers before actually executing them.
|
* @dev Used to validate transfers before actually executing them.
|
||||||
|
@ -165,34 +99,11 @@ interface IAToken is IERC20 {
|
||||||
function isTransferAllowed(address user, uint256 amount) external view returns (bool);
|
function isTransferAllowed(address user, uint256 amount) external view returns (bool);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev returns the last index of the user, used to calculate the balance of the user
|
* @dev transfer the amount of the underlying asset to the user
|
||||||
* @param user address of the user
|
* @param user address of the user
|
||||||
* @return the last user index
|
|
||||||
**/
|
|
||||||
function getUserIndex(address user) external view returns (uint256);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev returns the address to which the interest is redirected
|
|
||||||
* @param user address of the user
|
|
||||||
* @return 0 if there is no redirection, an address otherwise
|
|
||||||
**/
|
|
||||||
function getInterestRedirectionAddress(address user) external view returns (address);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev returns the redirected balance of the user. The redirected balance is the balance
|
|
||||||
* redirected by other accounts to the user, that is accrueing interest for him.
|
|
||||||
* @param user address of the user
|
|
||||||
* @return the total redirected balance
|
|
||||||
**/
|
|
||||||
function getRedirectedBalance(address user) external view returns (uint256);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev transfers the underlying asset to the target. Used by the lendingpool to transfer
|
|
||||||
* assets in borrow(), redeem() and flashLoan()
|
|
||||||
* @param target the target of the transfer
|
|
||||||
* @param amount the amount to transfer
|
* @param amount the amount to transfer
|
||||||
* @return the amount transferred
|
* @return the redirected balance index
|
||||||
**/
|
**/
|
||||||
|
|
||||||
function transferUnderlyingTo(address target, uint256 amount) external returns (uint256);
|
function transferUnderlyingTo(address user, uint256 amount) external returns (uint256);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xf8c6eB390cDc5C08717bC2268aa0c1169A9B5deE",
|
"address": "0x9Dc554694756dC303a087e04bA6918C333Bc26a7",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x4a716924Dad0c0d0E558844F304548814e7089F1",
|
"address": "0xAfC307938C1c0035942c141c31524504c89Aaa8B",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x798c5b4b62b1eA9D64955D6751B03075A003F123",
|
"address": "0x73DE1e0ab6A5C221258703bc546E0CAAcCc6EC87",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
"address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8"
|
"address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8"
|
"address": "0x65e0Cd5B8904A02f2e00BC6f58bf881998D54BDe"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"LendingPoolDataProvider": {
|
"LendingPoolDataProvider": {
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
"address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e"
|
"address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e"
|
"address": "0x5d12dDe3286D94E0d85F9D3B01B7099cfA0aBCf1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PriceOracle": {
|
"PriceOracle": {
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x1750499D05Ed1674d822430FB960d5F6731fDf64",
|
"address": "0xbeA90474c2F3C7c43bC7c36CaAf5272c927Af5a1",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xEC1C93A9f6a9e18E97784c76aC52053587FcDB89",
|
"address": "0x19E42cA990cF697D3dda0e59131215C43bB6989F",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x7B6C3e5486D9e6959441ab554A889099eed76290",
|
"address": "0xE30c3983E51bC9d6baE3E9437710a1459e21e81F",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xD83D2773a7873ae2b5f8Fb92097e20a8C64F691E",
|
"address": "0xDf69898e844197a24C658CcF9fD53dF15948dc8b",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x626FdE749F9d499d3777320CAf29484B624ab84a",
|
"address": "0xBe6d8642382C241c9B4B50c89574DbF3f4181E7D",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -169,16 +169,16 @@
|
||||||
"address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA"
|
"address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA"
|
"address": "0xAd49512dFBaD6fc13D67d3935283c0606812E962"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"WalletBalanceProvider": {
|
"WalletBalanceProvider": {
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2",
|
"address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2",
|
"address": "0xA29C2A7e59aa49C71aF084695337E3AA5e820758",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -188,7 +188,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x11df1AF606b85226Ab9a8B1FDa90395298e7494F",
|
"address": "0xbe66dC9DFEe580ED968403e35dF7b5159f873df8",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -198,7 +198,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x8f9A92c125FFEb83d8eC808Cd9f8cb80084c1E37",
|
"address": "0x93AfC6Df4bB8F62F2493B19e577f8382c0BA9EBC",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -208,7 +208,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xc4007844AE6bBe168cE8D692C86a7A4414FBcD26",
|
"address": "0x75Ded61646B5945BdDd0CD9a9Db7c8288DA6F810",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -218,7 +218,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xAb768C858C33DfcB6651d1174AFb750433a87Be0",
|
"address": "0xdE7c40e675bF1aA45c18cCbaEb9662B16b0Ddf7E",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -228,7 +228,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xA089557D64DAE4b4FcB65aB7C8A520AABb213e37",
|
"address": "0xDFbeeed692AA81E7f86E72F7ACbEA2A1C4d63544",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -238,7 +238,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x20FAE2042b362E3FaB2806820b9A43CC116e2846",
|
"address": "0x5191aA68c7dB195181Dd2441dBE23A48EA24b040",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -248,7 +248,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x8880F314112f15C2AfF674c3B27f9a44Ca86e4d0",
|
"address": "0x8F9422aa37215c8b3D1Ea1674138107F84D68F26",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -258,7 +258,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xDcb10C2e15110Db4B02C0a1df459768E680ce245",
|
"address": "0xa89E20284Bd638F31b0011D0fC754Fc9d2fa73e3",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -268,7 +268,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xfD408ec64Da574b1859814F810564f73ea2Ff003",
|
"address": "0xaA935993065F2dDB1d13623B1941C7AEE3A60F23",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -278,7 +278,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x0006F7c3542BEE76Dd887f54eD22405Ac4ae905a",
|
"address": "0x35A2624888e207e4B3434E9a9E250bF6Ee68FeA3",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -288,7 +288,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x6ca94a51c644eca3F9CA315bcC41CbA6940A66Eb",
|
"address": "0x1f569c307949a908A4b8Ff7453a88Ca0b8D8df13",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -298,7 +298,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x6765291Cab755B980F377445eFd0F9F945CDA6C4",
|
"address": "0x4301cb254CCc126B9eb9cbBE030C6FDA2FA16D4a",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -308,7 +308,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xa7dB4d25Fc525d19Fbda4E74AAF447B88420FbcB",
|
"address": "0x0766c9592a8686CAB0081b4f35449462c6e82F11",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -318,7 +318,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x273D60904A8DBa3Ae6B20505c59902644124fF0E",
|
"address": "0xaF6D34adD35E1A565be4539E4d1069c48A49C953",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -328,7 +328,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xfc37dE87C1Ee39cc856782BF96fEdcB6FA5c5A7f",
|
"address": "0x48bb3E35D2D6994374db457a6Bf61de2d9cC8E49",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -338,7 +338,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x049228dFFEdf91ff224e9F96247aEBA700e3590c",
|
"address": "0x1E59BA56B1F61c3Ee946D8c7e2994B4A9b0cA45C",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -348,7 +348,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xA410D1f3fEAF300842142Cd7AA1709D84944DCb7",
|
"address": "0x53813198c75959DDB604462831d8989C29152164",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -358,7 +358,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x835973768750b3ED2D5c3EF5AdcD5eDb44d12aD4",
|
"address": "0x0eD6115873ce6B807a03FE0df1f940387779b729",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -368,7 +368,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x1181FC27dbF04B5105243E60BB1936c002e9d5C8",
|
"address": "0xFFfDa24e7E3d5F89a24278f53d6f0F81B3bE0d6B",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -378,7 +378,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x6F96975e2a0e1380b6e2e406BB33Ae96e4b6DB65",
|
"address": "0x5889354f21A1C8D8D2f82669d778f6Dab778B519",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -388,7 +388,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xc032930653da193EDE295B4DcE3DD093a695c3b3",
|
"address": "0x09F7bF33B3F8922268B34103af3a8AF83148C9B1",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -398,7 +398,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xb3363f4349b1160DbA55ec4D82fDe874A4123A2a",
|
"address": "0x8f3966F7d53Fd5f12b701C8835e1e32541613869",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -408,16 +408,16 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xf8c6eB390cDc5C08717bC2268aa0c1169A9B5deE",
|
"address": "0x9Dc554694756dC303a087e04bA6918C333Bc26a7",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AaveProtocolTestHelpers": {
|
"AaveProtocolTestHelpers": {
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
"address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10"
|
"address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10"
|
"address": "0x9305d862ee95a899b83906Cd9CB666aC269E5f66"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"StableDebtToken": {
|
"StableDebtToken": {
|
||||||
|
@ -426,7 +426,7 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xB660Fdd109a95718cB9d20E3A89EE6cE342aDcB6",
|
"address": "0x02BB514187B830d6A2111197cd7D8cb60650B970",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -436,13 +436,13 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d",
|
"address": "0x6774Ce86Abf5EBB22E9F45b5f55daCbB4170aD7f",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AToken": {
|
"AToken": {
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xA0AB1cB92A4AF81f84dCd258155B5c25D247b54E",
|
"address": "0x007C1a44e85bDa8F562F916685A9DC8BdC6542bF",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
|
@ -452,11 +452,11 @@
|
||||||
},
|
},
|
||||||
"MockAToken": {
|
"MockAToken": {
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
"address": "0x3bDA11B584dDff7F66E0cFe1da1562c92B45db60",
|
"address": "0x392E5355a0e88Bd394F717227c752670fb3a8020",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x1203D1b97BF6E546c00C45Cda035D3010ACe1180",
|
"address": "0xFBdF1E93D0D88145e3CcA63bf8d513F83FB0903b",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -466,28 +466,36 @@
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x2cc20bE530F92865c2ed8CeD0b020a11bFe62Fe7",
|
"address": "0xEcb928A3c079a1696Aa5244779eEc3dE1717fACd",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MockStableDebtToken": {
|
"MockStableDebtToken": {
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
"address": "0x392E5355a0e88Bd394F717227c752670fb3a8020",
|
"address": "0x3b050AFb4ac4ACE646b31fF3639C1CD43aC31460",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0x8733AfE8174BA7c04c6CD694bD673294079b7E10",
|
"address": "0xE45fF4A0A8D0E9734C73874c034E03594E15ba28",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MockVariableDebtToken": {
|
"MockVariableDebtToken": {
|
||||||
"buidlerevm": {
|
"buidlerevm": {
|
||||||
"address": "0x3b050AFb4ac4ACE646b31fF3639C1CD43aC31460",
|
"address": "0xEBAB67ee3ef604D5c250A53b4b8fcbBC6ec3007C",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"address": "0xA8083d78B6ABC328b4d3B714F76F384eCC7147e1",
|
"address": "0x5cCC6Abc4c9F7262B9485797a848Ec6CC28A11dF",
|
||||||
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"MockSwapAdapter": {
|
||||||
|
"buidlerevm": {
|
||||||
|
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
|
||||||
|
},
|
||||||
|
"localhost": {
|
||||||
|
"address": "0x749258D38b0473d96FEcc14cC5e7DCE12d7Bd6f6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,10 +32,11 @@ import {Ierc20Detailed} from '../types/Ierc20Detailed';
|
||||||
import {StableDebtToken} from '../types/StableDebtToken';
|
import {StableDebtToken} from '../types/StableDebtToken';
|
||||||
import {VariableDebtToken} from '../types/VariableDebtToken';
|
import {VariableDebtToken} from '../types/VariableDebtToken';
|
||||||
import { ZERO_ADDRESS } from './constants';
|
import { ZERO_ADDRESS } from './constants';
|
||||||
|
import {MockSwapAdapter} from '../types/MockSwapAdapter';
|
||||||
|
|
||||||
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;
|
||||||
if (currentNetwork !== 'buidlerevm' && currentNetwork !== 'soliditycoverage') {
|
if (currentNetwork !== 'buidlerevm' && !currentNetwork.includes('coverage')) {
|
||||||
console.log(`*** ${contractId} ***\n`);
|
console.log(`*** ${contractId} ***\n`);
|
||||||
console.log(`Network: ${currentNetwork}`);
|
console.log(`Network: ${currentNetwork}`);
|
||||||
console.log(`tx: ${contractInstance.deployTransaction.hash}`);
|
console.log(`tx: ${contractInstance.deployTransaction.hash}`);
|
||||||
|
@ -213,6 +214,9 @@ export const deployMockFlashLoanReceiver = async (addressesProvider: tEthereumAd
|
||||||
addressesProvider,
|
addressesProvider,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const deployMockSwapAdapter = async (addressesProvider: tEthereumAddress) =>
|
||||||
|
await deployContract<MockSwapAdapter>(eContractid.MockSwapAdapter, [addressesProvider]);
|
||||||
|
|
||||||
export const deployWalletBalancerProvider = async (addressesProvider: tEthereumAddress) =>
|
export const deployWalletBalancerProvider = async (addressesProvider: tEthereumAddress) =>
|
||||||
await deployContract<WalletBalanceProvider>(eContractid.WalletBalanceProvider, [
|
await deployContract<WalletBalanceProvider>(eContractid.WalletBalanceProvider, [
|
||||||
addressesProvider,
|
addressesProvider,
|
||||||
|
@ -390,6 +394,14 @@ export const getMockFlashLoanReceiver = async (address?: tEthereumAddress) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMockSwapAdapter = async (address?: tEthereumAddress) => {
|
||||||
|
return await getContract<MockSwapAdapter>(
|
||||||
|
eContractid.MockSwapAdapter,
|
||||||
|
address ||
|
||||||
|
(await getDb().get(`${eContractid.MockSwapAdapter}.${BRE.network.name}`).value()).address
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getLendingRateOracle = async (address?: tEthereumAddress) => {
|
export const getLendingRateOracle = async (address?: tEthereumAddress) => {
|
||||||
return await getContract<LendingRateOracle>(
|
return await getContract<LendingRateOracle>(
|
||||||
eContractid.LendingRateOracle,
|
eContractid.LendingRateOracle,
|
||||||
|
|
|
@ -32,6 +32,7 @@ export enum eContractid {
|
||||||
LendingPoolLiquidationManager = 'LendingPoolLiquidationManager',
|
LendingPoolLiquidationManager = 'LendingPoolLiquidationManager',
|
||||||
InitializableAdminUpgradeabilityProxy = 'InitializableAdminUpgradeabilityProxy',
|
InitializableAdminUpgradeabilityProxy = 'InitializableAdminUpgradeabilityProxy',
|
||||||
MockFlashLoanReceiver = 'MockFlashLoanReceiver',
|
MockFlashLoanReceiver = 'MockFlashLoanReceiver',
|
||||||
|
MockSwapAdapter = 'MockSwapAdapter',
|
||||||
WalletBalanceProvider = 'WalletBalanceProvider',
|
WalletBalanceProvider = 'WalletBalanceProvider',
|
||||||
AToken = 'AToken',
|
AToken = 'AToken',
|
||||||
MockAToken = 'MockAToken',
|
MockAToken = 'MockAToken',
|
||||||
|
@ -111,7 +112,7 @@ export enum ProtocolErrors {
|
||||||
INVALID_REDIRECTION_ADDRESS = 'Invalid redirection address',
|
INVALID_REDIRECTION_ADDRESS = 'Invalid redirection address',
|
||||||
INVALID_HF = 'Invalid health factor',
|
INVALID_HF = 'Invalid health factor',
|
||||||
TRANSFER_AMOUNT_EXCEEDS_BALANCE = 'ERC20: transfer amount exceeds balance',
|
TRANSFER_AMOUNT_EXCEEDS_BALANCE = 'ERC20: transfer amount exceeds balance',
|
||||||
SAFEERC20_LOWLEVEL_CALL = 'SafeERC20: low-level call failed'
|
SAFEERC20_LOWLEVEL_CALL = 'SafeERC20: low-level call failed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type tEthereumAddress = string;
|
export type tEthereumAddress = string;
|
||||||
|
|
|
@ -13,8 +13,10 @@
|
||||||
"types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'",
|
"types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'",
|
||||||
"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",
|
||||||
|
"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-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts",
|
"test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts",
|
||||||
"dev:coverage": "buidler 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",
|
||||||
"dev:prettier": "prettier --write .",
|
"dev:prettier": "prettier --write .",
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
deployStableDebtToken,
|
deployStableDebtToken,
|
||||||
deployVariableDebtToken,
|
deployVariableDebtToken,
|
||||||
deployGenericAToken,
|
deployGenericAToken,
|
||||||
|
deployMockSwapAdapter,
|
||||||
} from '../helpers/contracts-helpers';
|
} from '../helpers/contracts-helpers';
|
||||||
import {LendingPoolAddressesProvider} from '../types/LendingPoolAddressesProvider';
|
import {LendingPoolAddressesProvider} from '../types/LendingPoolAddressesProvider';
|
||||||
import {ContractTransaction, Signer} from 'ethers';
|
import {ContractTransaction, Signer} from 'ethers';
|
||||||
|
@ -504,6 +505,9 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
|
||||||
const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address);
|
const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address);
|
||||||
await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address);
|
await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address);
|
||||||
|
|
||||||
|
const mockSwapAdapter = await deployMockSwapAdapter(addressesProvider.address);
|
||||||
|
await insertContractAddressInDb(eContractid.MockSwapAdapter, mockSwapAdapter.address);
|
||||||
|
|
||||||
await deployWalletBalancerProvider(addressesProvider.address);
|
await deployWalletBalancerProvider(addressesProvider.address);
|
||||||
|
|
||||||
const testHelpers = await deployAaveProtocolTestHelpers(addressesProvider.address);
|
const testHelpers = await deployAaveProtocolTestHelpers(addressesProvider.address);
|
||||||
|
|
|
@ -7,12 +7,12 @@ makeSuite('AToken: Modifiers', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
it('Tries to invoke mint not being the LendingPool', async () => {
|
it('Tries to invoke mint not being the LendingPool', async () => {
|
||||||
const {deployer, aDai} = testEnv;
|
const {deployer, aDai} = testEnv;
|
||||||
await expect(aDai.mint(deployer.address, '1')).to.be.revertedWith(CALLER_MUST_BE_LENDING_POOL);
|
await expect(aDai.mint(deployer.address, '1', '1')).to.be.revertedWith(CALLER_MUST_BE_LENDING_POOL);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Tries to invoke burn not being the LendingPool', async () => {
|
it('Tries to invoke burn not being the LendingPool', async () => {
|
||||||
const {deployer, aDai} = testEnv;
|
const {deployer, aDai} = testEnv;
|
||||||
await expect(aDai.burn(deployer.address, deployer.address, '1')).to.be.revertedWith(
|
await expect(aDai.burn(deployer.address, deployer.address, '1', '1')).to.be.revertedWith(
|
||||||
CALLER_MUST_BE_LENDING_POOL
|
CALLER_MUST_BE_LENDING_POOL
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,9 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
//user 1 deposits 1000 DAI
|
//user 1 deposits 1000 DAI
|
||||||
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
await pool.connect(users[0].signer).deposit(dai.address, amountDAItoDeposit, '0');
|
await pool
|
||||||
|
.connect(users[0].signer)
|
||||||
|
.deposit(dai.address, amountDAItoDeposit, users[0].address, '0');
|
||||||
|
|
||||||
await aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit);
|
await aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit);
|
||||||
|
|
||||||
|
@ -47,59 +49,18 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('User 1 redirects interest to user 2, transfers 500 DAI back to user 0', async () => {
|
|
||||||
const {users, aDai, dai} = testEnv;
|
|
||||||
await aDai.connect(users[1].signer).redirectInterestStream(users[2].address);
|
|
||||||
|
|
||||||
const aDAIRedirected = await convertToCurrencyDecimals(dai.address, '1000');
|
|
||||||
|
|
||||||
const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '500');
|
|
||||||
|
|
||||||
const user2RedirectedBalanceBefore = await aDai.getRedirectedBalance(users[2].address);
|
|
||||||
expect(user2RedirectedBalanceBefore.toString()).to.be.equal(
|
|
||||||
aDAIRedirected,
|
|
||||||
INVALID_REDIRECTED_BALANCE_BEFORE_TRANSFER
|
|
||||||
);
|
|
||||||
|
|
||||||
await aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer);
|
|
||||||
|
|
||||||
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
|
|
||||||
const user1RedirectionAddress = await aDai.getInterestRedirectionAddress(users[1].address);
|
|
||||||
|
|
||||||
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
|
|
||||||
aDAItoTransfer,
|
|
||||||
INVALID_REDIRECTED_BALANCE_BEFORE_TRANSFER
|
|
||||||
);
|
|
||||||
expect(user1RedirectionAddress.toString()).to.be.equal(
|
|
||||||
users[2].address,
|
|
||||||
INVALID_REDIRECTION_ADDRESS
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User 0 transfers back to user 1', async () => {
|
|
||||||
const {users, aDai, dai, weth} = testEnv;
|
|
||||||
const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '500');
|
|
||||||
|
|
||||||
await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer);
|
|
||||||
|
|
||||||
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
|
|
||||||
|
|
||||||
const user1BalanceAfter = await aDai.balanceOf(users[1].address);
|
|
||||||
|
|
||||||
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
|
|
||||||
user1BalanceAfter.toString(),
|
|
||||||
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User 0 deposits 1 WETH and user 1 tries to borrow, but the aTokens received as a transfer are not available as collateral (revert expected)', async () => {
|
it('User 0 deposits 1 WETH and user 1 tries to borrow, but the aTokens received as a transfer are not available as collateral (revert expected)', async () => {
|
||||||
const {users, pool, weth} = testEnv;
|
const {users, pool, weth} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
|
|
||||||
await weth.connect(users[0].signer).mint(await convertToCurrencyDecimals(weth.address, '1'));
|
await weth.connect(users[0].signer).mint(await convertToCurrencyDecimals(weth.address, '1'));
|
||||||
|
|
||||||
await weth.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await weth.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
await pool.connect(users[0].signer).deposit(weth.address, ethers.utils.parseEther('1.0'), '0');
|
await pool
|
||||||
|
.connect(users[0].signer)
|
||||||
|
.deposit(weth.address, ethers.utils.parseEther('1.0'), userAddress, '0');
|
||||||
await expect(
|
await expect(
|
||||||
pool
|
pool
|
||||||
.connect(users[1].signer)
|
.connect(users[1].signer)
|
||||||
|
@ -124,87 +85,4 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
|
||||||
).to.be.revertedWith(TRANSFER_NOT_ALLOWED);
|
).to.be.revertedWith(TRANSFER_NOT_ALLOWED);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('User 0 tries to transfer 0 balance (revert expected)', async () => {
|
|
||||||
const {users, pool, aDai, dai, weth} = testEnv;
|
|
||||||
await expect(
|
|
||||||
aDai.connect(users[0].signer).transfer(users[1].address, '0'),
|
|
||||||
TRANSFER_AMOUNT_NOT_GT_0
|
|
||||||
).to.be.revertedWith(TRANSFER_AMOUNT_NOT_GT_0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User 1 repays the borrow, transfers aDAI back to user 0', async () => {
|
|
||||||
const {users, pool, aDai, dai, weth} = testEnv;
|
|
||||||
|
|
||||||
await weth.connect(users[1].signer).mint(await convertToCurrencyDecimals(weth.address, '2'));
|
|
||||||
|
|
||||||
await weth.connect(users[1].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
|
||||||
|
|
||||||
await pool
|
|
||||||
.connect(users[1].signer)
|
|
||||||
.repay(weth.address, MAX_UINT_AMOUNT, RateMode.Stable, users[1].address);
|
|
||||||
|
|
||||||
const aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '1000');
|
|
||||||
|
|
||||||
await aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer);
|
|
||||||
|
|
||||||
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
|
|
||||||
|
|
||||||
const user1RedirectionAddress = await aDai.getInterestRedirectionAddress(users[1].address);
|
|
||||||
|
|
||||||
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
|
|
||||||
'0',
|
|
||||||
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(user1RedirectionAddress.toString()).to.be.equal(
|
|
||||||
ZERO_ADDRESS,
|
|
||||||
INVALID_REDIRECTION_ADDRESS
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User 0 redirects interest to user 2, transfers 500 aDAI to user 1. User 1 redirects to user 3. User 0 transfers another 100 aDAI', async () => {
|
|
||||||
const {users, pool, aDai, dai, weth} = testEnv;
|
|
||||||
|
|
||||||
let aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '500');
|
|
||||||
|
|
||||||
await aDai.connect(users[0].signer).redirectInterestStream(users[2].address);
|
|
||||||
|
|
||||||
await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer);
|
|
||||||
|
|
||||||
await aDai.connect(users[1].signer).redirectInterestStream(users[3].address);
|
|
||||||
|
|
||||||
aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '100');
|
|
||||||
|
|
||||||
await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer);
|
|
||||||
|
|
||||||
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
|
|
||||||
const user3RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[3].address);
|
|
||||||
|
|
||||||
const expectedUser2Redirected = await convertToCurrencyDecimals(aDai.address, '400');
|
|
||||||
const expectedUser3Redirected = await convertToCurrencyDecimals(aDai.address, '600');
|
|
||||||
|
|
||||||
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
|
|
||||||
expectedUser2Redirected,
|
|
||||||
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
|
|
||||||
);
|
|
||||||
expect(user3RedirectedBalanceAfter.toString()).to.be.equal(
|
|
||||||
expectedUser3Redirected,
|
|
||||||
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User 1 transfers the whole amount to himself', async () => {
|
|
||||||
const {users, pool, aDai, dai} = testEnv;
|
|
||||||
|
|
||||||
const user1BalanceBefore = await aDai.balanceOf(users[1].address);
|
|
||||||
|
|
||||||
await aDai.connect(users[1].signer).transfer(users[1].address, user1BalanceBefore);
|
|
||||||
|
|
||||||
const user1BalanceAfter = await aDai.balanceOf(users[1].address);
|
|
||||||
|
|
||||||
expect(user1BalanceAfter.toString()).to.be.equal(
|
|
||||||
user1BalanceBefore,
|
|
||||||
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -251,7 +251,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
it('Reverts when trying to disable the DAI reserve with liquidity on it', async () => {
|
it('Reverts when trying to disable the DAI reserve with liquidity on it', async () => {
|
||||||
const {dai, pool, configurator} = testEnv;
|
const {dai, pool, configurator} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
await dai.mint(await convertToCurrencyDecimals(dai.address, '1000'));
|
await dai.mint(await convertToCurrencyDecimals(dai.address, '1000'));
|
||||||
|
|
||||||
//approve protocol to access depositor wallet
|
//approve protocol to access depositor wallet
|
||||||
|
@ -259,7 +259,7 @@ makeSuite('LendingPoolConfigurator', (testEnv: TestEnv) => {
|
||||||
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
//user 1 deposits 1000 DAI
|
//user 1 deposits 1000 DAI
|
||||||
await pool.deposit(dai.address, amountDAItoDeposit, '0');
|
await pool.deposit(dai.address, amountDAItoDeposit, userAddress, '0');
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
configurator.deactivateReserve(dai.address),
|
configurator.deactivateReserve(dai.address),
|
||||||
|
|
916
test/flash-liquidation-with-collateral.spec.ts
Normal file
916
test/flash-liquidation-with-collateral.spec.ts
Normal file
|
@ -0,0 +1,916 @@
|
||||||
|
import {TestEnv, makeSuite} from './helpers/make-suite';
|
||||||
|
import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants';
|
||||||
|
import {ethers} from 'ethers';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import {
|
||||||
|
calcExpectedVariableDebtTokenBalance,
|
||||||
|
calcExpectedStableDebtTokenBalance,
|
||||||
|
} from './helpers/utils/calculations';
|
||||||
|
import {getContractsData} from './helpers/actions';
|
||||||
|
import {waitForTx} from './__setup.spec';
|
||||||
|
import {timeLatest, BRE, increaseTime} from '../helpers/misc-utils';
|
||||||
|
import {ProtocolErrors} from '../helpers/types';
|
||||||
|
import {convertToCurrencyDecimals} from '../helpers/contracts-helpers';
|
||||||
|
import {expectRepayWithCollateralEvent} from './repay-with-collateral.spec';
|
||||||
|
|
||||||
|
const {expect} = require('chai');
|
||||||
|
const {parseUnits, parseEther} = ethers.utils;
|
||||||
|
|
||||||
|
makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEnv) => {
|
||||||
|
const {INVALID_HF, COLLATERAL_CANNOT_BE_LIQUIDATED} = ProtocolErrors;
|
||||||
|
|
||||||
|
it('User 1 provides some liquidity for others to borrow', async () => {
|
||||||
|
const {pool, weth, dai, usdc, deployer} = testEnv;
|
||||||
|
|
||||||
|
await weth.mint(parseEther('200'));
|
||||||
|
await weth.approve(pool.address, parseEther('200'));
|
||||||
|
await pool.deposit(weth.address, parseEther('200'), deployer.address, 0);
|
||||||
|
await dai.mint(parseEther('20000'));
|
||||||
|
await dai.approve(pool.address, parseEther('20000'));
|
||||||
|
await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0);
|
||||||
|
await usdc.mint(parseEther('20000'));
|
||||||
|
await usdc.approve(pool.address, parseEther('20000'));
|
||||||
|
await pool.deposit(usdc.address, parseEther('20000'), deployer.address, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 5 liquidate User 3 collateral, all his variable debt and part of the stable', async () => {
|
||||||
|
const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
const liquidator = users[4];
|
||||||
|
const amountToDeposit = parseEther('20');
|
||||||
|
const amountToBorrow = parseUnits('40', 6);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDeposit);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).approve(pool.address, amountToDeposit);
|
||||||
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
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, 1, 0);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
// Set HF below 1
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(usdcPrice.toString()).multipliedBy(60).toFixed(0)
|
||||||
|
);
|
||||||
|
const userGlobalDataPrior = await pool.getUserAccountData(user.address);
|
||||||
|
expect(userGlobalDataPrior.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
|
||||||
|
|
||||||
|
const amountToRepay = parseUnits('80', 6);
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
const txReceipt = await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(usdc.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance(
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentStableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.gte(0)
|
||||||
|
? new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString()
|
||||||
|
: '0',
|
||||||
|
'INVALID_VARIABLE_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
const stableDebtRepaid = new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.abs();
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentStableDebt).to.be.bignumber.equal(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentStableDebt)
|
||||||
|
.minus(stableDebtRepaid)
|
||||||
|
.plus(expectedStableDebtIncrease)
|
||||||
|
.gte(0)
|
||||||
|
? new BigNumber(usdcUserDataBefore.currentStableDebt)
|
||||||
|
.minus(stableDebtRepaid)
|
||||||
|
.plus(expectedStableDebtIncrease)
|
||||||
|
.toString()
|
||||||
|
: '0',
|
||||||
|
'INVALID_STABLE_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
),
|
||||||
|
'INVALID_COLLATERAL_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
const eventsEmitted = txReceipt.events || [];
|
||||||
|
|
||||||
|
expectRepayWithCollateralEvent(
|
||||||
|
eventsEmitted,
|
||||||
|
pool.address,
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address
|
||||||
|
);
|
||||||
|
// Resets USDC Price
|
||||||
|
await oracle.setAssetPrice(usdc.address, usdcPrice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 3 deposits WETH and borrows USDC at Variable', async () => {
|
||||||
|
const {pool, weth, usdc, users, oracle} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
const amountToDeposit = parseEther('10');
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDeposit);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
const userGlobalData = await pool.getUserAccountData(user.address);
|
||||||
|
|
||||||
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const amountUSDCToBorrow = await convertToCurrencyDecimals(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(userGlobalData.availableBorrowsETH.toString())
|
||||||
|
.div(usdcPrice.toString())
|
||||||
|
.multipliedBy(0.95)
|
||||||
|
.toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 5 liquidates half the USDC loan of User 3 by swapping his WETH collateral', async () => {
|
||||||
|
const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
const liquidator = users[4];
|
||||||
|
// Sets USDC Price higher to decrease health factor below 1
|
||||||
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(usdcPrice.toString()).multipliedBy(1.15).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const userGlobalData = await pool.getUserAccountData(user.address);
|
||||||
|
|
||||||
|
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
const amountToRepay = usdcReserveDataBefore.totalBorrowsVariable.dividedBy(2).toFixed(0);
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(usdc.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString(),
|
||||||
|
'INVALID_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
),
|
||||||
|
'INVALID_COLLATERAL_POSITION'
|
||||||
|
);
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
|
||||||
|
|
||||||
|
// Resets USDC Price
|
||||||
|
await oracle.setAssetPrice(usdc.address, usdcPrice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Revert expected. User 5 tries to liquidate an User 3 collateral a currency he havent borrow', async () => {
|
||||||
|
const {pool, weth, dai, users, oracle, mockSwapAdapter, usdc} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
const liquidator = users[4];
|
||||||
|
|
||||||
|
const amountToRepay = parseUnits('10', 6);
|
||||||
|
|
||||||
|
// Sets USDC Price higher to decrease health factor below 1
|
||||||
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(usdcPrice.toString()).multipliedBy(6.4).toFixed(0)
|
||||||
|
);
|
||||||
|
const userGlobalData = await pool.getUserAccountData(user.address);
|
||||||
|
|
||||||
|
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('40');
|
||||||
|
|
||||||
|
await oracle.setAssetPrice(usdc.address, usdcPrice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 5 liquidates all the USDC loan of User 3 by swapping his WETH collateral', async () => {
|
||||||
|
const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
const liquidator = users[4];
|
||||||
|
// Sets USDC Price higher to decrease health factor below 1
|
||||||
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
usdc.address,
|
||||||
|
new BigNumber(usdcPrice.toString()).multipliedBy(1.35).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const userGlobalData = await pool.getUserAccountData(user.address);
|
||||||
|
|
||||||
|
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
const amountToRepay = usdcReserveDataBefore.totalBorrowsVariable.toFixed(0);
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(usdc.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString(),
|
||||||
|
'INVALID_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
),
|
||||||
|
'INVALID_COLLATERAL_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Resets USDC Price
|
||||||
|
await oracle.setAssetPrice(usdc.address, usdcPrice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 2 deposit WETH and borrows DAI at Variable', async () => {
|
||||||
|
const {pool, weth, dai, users, oracle} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
const amountToDeposit = ethers.utils.parseEther('2');
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDeposit);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
const userGlobalData = await pool.getUserAccountData(user.address);
|
||||||
|
|
||||||
|
const daiPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
|
||||||
|
const amountDAIToBorrow = await convertToCurrencyDecimals(
|
||||||
|
dai.address,
|
||||||
|
new BigNumber(userGlobalData.availableBorrowsETH.toString())
|
||||||
|
.div(daiPrice.toString())
|
||||||
|
.multipliedBy(0.9)
|
||||||
|
.toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
const liquidator = users[4];
|
||||||
|
|
||||||
|
// Sets DAI Price higher to decrease health factor below 1
|
||||||
|
const daiPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
dai.address,
|
||||||
|
new BigNumber(daiPrice.toString()).multipliedBy(1.4).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.toString();
|
||||||
|
|
||||||
|
await waitForTx(await mockSwapAdapter.setTryReentrancy(true));
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('53');
|
||||||
|
|
||||||
|
// Resets DAI Price
|
||||||
|
await oracle.setAssetPrice(dai.address, daiPrice);
|
||||||
|
// Resets mock
|
||||||
|
await waitForTx(await mockSwapAdapter.setTryReentrancy(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 5 tries to liquidate User 2 DAI Variable loan using his WETH collateral, with good HF', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
const liquidator = users[4];
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
// First half
|
||||||
|
const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.dividedBy(2).toString();
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('38');
|
||||||
|
});
|
||||||
|
it('User 5 liquidates User 2 DAI Variable loan using his WETH collateral, half the amount', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
const liquidator = users[4];
|
||||||
|
|
||||||
|
// Sets DAI Price higher to decrease health factor below 1
|
||||||
|
const daiPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
dai.address,
|
||||||
|
new BigNumber(daiPrice.toString()).multipliedBy(1.4).toFixed(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const userGlobalData = await pool.getUserAccountData(user.address);
|
||||||
|
|
||||||
|
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
// First half
|
||||||
|
const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.multipliedBy(0.6).toString();
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(dai.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
daiReserveDataBefore,
|
||||||
|
daiUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(daiUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(daiUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
|
||||||
|
|
||||||
|
// Resets DAI price
|
||||||
|
await oracle.setAssetPrice(dai.address, daiPrice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 2 tries to repay remaining DAI Variable loan using his WETH collateral', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
await increaseTime(1000);
|
||||||
|
// Repay the remaining DAI
|
||||||
|
const amountToRepay = daiReserveDataBefore.totalBorrowsVariable.toString();
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
const receipt = await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = (await BRE.ethers.provider.getBlock(receipt.blockNumber))
|
||||||
|
.timestamp;
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(dai.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
daiReserveDataBefore,
|
||||||
|
daiUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(daiUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(daiUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
)
|
||||||
|
).to.be.bignumber.equal(wethUserDataAfter.currentATokenBalance);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Liquidator tries to repay 4 user a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => {
|
||||||
|
const {pool, weth, dai, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[3];
|
||||||
|
const liquidator = users[5];
|
||||||
|
|
||||||
|
const amountToDepositWeth = parseEther('0.1');
|
||||||
|
const amountToDepositDAI = parseEther('500');
|
||||||
|
const amountToBorrowVariable = parseUnits('80', '6');
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDepositWeth);
|
||||||
|
await dai.connect(user.signer).mint(amountToDepositDAI);
|
||||||
|
await weth.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(weth.address, amountToDepositWeth, 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);
|
||||||
|
|
||||||
|
const amountToRepay = amountToBorrowVariable;
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
// Set HF below 1
|
||||||
|
const daiPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
await oracle.setAssetPrice(
|
||||||
|
dai.address,
|
||||||
|
new BigNumber(daiPrice.toString()).multipliedBy(0.1).toFixed(0)
|
||||||
|
);
|
||||||
|
const userGlobalDataPrior = await pool.getUserAccountData(user.address);
|
||||||
|
expect(userGlobalDataPrior.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
|
||||||
|
|
||||||
|
// Execute liquidation
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const collateralConfig = await pool.getReserveConfigurationData(weth.address);
|
||||||
|
|
||||||
|
const collateralDecimals = collateralConfig.decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(usdc.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const collateralLiquidationBonus = collateralConfig.liquidationBonus.toString();
|
||||||
|
|
||||||
|
const expectedDebtCovered = new BigNumber(collateralPrice.toString())
|
||||||
|
.times(new BigNumber(wethUserDataBefore.currentATokenBalance.toString()))
|
||||||
|
.times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
)
|
||||||
|
.div(new BigNumber(collateralLiquidationBonus).div(10000).toString())
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(expectedDebtCovered.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease),
|
||||||
|
'INVALID_VARIABLE_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0);
|
||||||
|
|
||||||
|
// Resets DAI Price
|
||||||
|
await oracle.setAssetPrice(dai.address, daiPrice);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 5 deposits WETH and DAI, then borrows USDC at Variable, then disables WETH as collateral', async () => {
|
||||||
|
const {pool, weth, dai, usdc, users} = testEnv;
|
||||||
|
const user = users[4];
|
||||||
|
const amountWETHToDeposit = parseEther('10');
|
||||||
|
const amountDAIToDeposit = parseEther('60');
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Liquidator tries to liquidates User 5 USDC loan by swapping his WETH collateral, should revert due WETH collateral disabled', async () => {
|
||||||
|
const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[4];
|
||||||
|
const liquidator = users[5];
|
||||||
|
|
||||||
|
const amountToRepay = parseUnits('65', 6);
|
||||||
|
|
||||||
|
// User 5 Disable WETH as collateral
|
||||||
|
await pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
expect(wethUserDataBefore.usageAsCollateralEnabled).to.be.false;
|
||||||
|
|
||||||
|
// Liquidator should NOT be able to liquidate himself with WETH, even if is disabled
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(liquidator.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith(COLLATERAL_CANNOT_BE_LIQUIDATED);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString(),
|
||||||
|
'INVALID_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,7 +21,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
REQUESTED_AMOUNT_TOO_SMALL,
|
REQUESTED_AMOUNT_TOO_SMALL,
|
||||||
TRANSFER_AMOUNT_EXCEEDS_BALANCE,
|
TRANSFER_AMOUNT_EXCEEDS_BALANCE,
|
||||||
INVALID_FLASHLOAN_MODE,
|
INVALID_FLASHLOAN_MODE,
|
||||||
SAFEERC20_LOWLEVEL_CALL
|
SAFEERC20_LOWLEVEL_CALL,
|
||||||
} = ProtocolErrors;
|
} = ProtocolErrors;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
@ -30,13 +30,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
it('Deposits ETH into the reserve', async () => {
|
it('Deposits ETH into the reserve', async () => {
|
||||||
const {pool, weth} = testEnv;
|
const {pool, weth} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
const amountToDeposit = ethers.utils.parseEther('1');
|
const amountToDeposit = ethers.utils.parseEther('1');
|
||||||
|
|
||||||
await weth.mint(amountToDeposit);
|
await weth.mint(amountToDeposit);
|
||||||
|
|
||||||
await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await weth.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
await pool.deposit(weth.address, amountToDeposit, '0');
|
await pool.deposit(weth.address, amountToDeposit, userAddress, '0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => {
|
it('Takes WETH flashloan with mode = 0, returns the funds correctly', async () => {
|
||||||
|
@ -143,7 +144,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0');
|
await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0');
|
||||||
|
|
||||||
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
|
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
|
||||||
|
|
||||||
|
@ -210,6 +211,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
it('Deposits USDC into the reserve', async () => {
|
it('Deposits USDC into the reserve', async () => {
|
||||||
const {usdc, pool} = testEnv;
|
const {usdc, pool} = testEnv;
|
||||||
|
const userAddress = await pool.signer.getAddress();
|
||||||
|
|
||||||
await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000'));
|
await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000'));
|
||||||
|
|
||||||
|
@ -217,7 +219,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
const amountToDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
|
const amountToDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
|
||||||
|
|
||||||
await pool.deposit(usdc.address, amountToDeposit, '0');
|
await pool.deposit(usdc.address, amountToDeposit, userAddress, '0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => {
|
it('Takes out a 500 USDC flashloan, returns the funds correctly', async () => {
|
||||||
|
@ -284,7 +286,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
const amountToDeposit = await convertToCurrencyDecimals(weth.address, '5');
|
const amountToDeposit = await convertToCurrencyDecimals(weth.address, '5');
|
||||||
|
|
||||||
await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, '0');
|
await pool.connect(caller.signer).deposit(weth.address, amountToDeposit, caller.address, '0');
|
||||||
|
|
||||||
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
|
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
|
||||||
|
|
||||||
|
@ -307,7 +309,6 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
it('Caller deposits 1000 DAI as collateral, Takes a WETH flashloan with mode = 0, does not approve the transfer of the funds', async () => {
|
it('Caller deposits 1000 DAI as collateral, Takes a WETH flashloan with mode = 0, does not approve the transfer of the funds', async () => {
|
||||||
const {dai, pool, weth, users} = testEnv;
|
const {dai, pool, weth, users} = testEnv;
|
||||||
|
|
||||||
const caller = users[3];
|
const caller = users[3];
|
||||||
|
|
||||||
await dai.connect(caller.signer).mint(await convertToCurrencyDecimals(dai.address, '1000'));
|
await dai.connect(caller.signer).mint(await convertToCurrencyDecimals(dai.address, '1000'));
|
||||||
|
@ -316,7 +317,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
|
|
||||||
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
const amountToDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, '0');
|
await pool.connect(caller.signer).deposit(dai.address, amountToDeposit, caller.address, '0');
|
||||||
|
|
||||||
const flashAmount = ethers.utils.parseEther('0.8');
|
const flashAmount = ethers.utils.parseEther('0.8');
|
||||||
|
|
||||||
|
@ -340,8 +341,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
|
await _mockFlashLoanReceiver.setFailExecutionTransfer(true);
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(caller.signer)
|
.connect(caller.signer)
|
||||||
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0');
|
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0');
|
||||||
|
|
||||||
const {stableDebtTokenAddress} = await pool.getReserveTokensAddresses(weth.address);
|
const {stableDebtTokenAddress} = await pool.getReserveTokensAddresses(weth.address);
|
||||||
|
|
||||||
|
@ -353,6 +354,5 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
||||||
const callerDebt = await wethDebtToken.balanceOf(caller.address);
|
const callerDebt = await wethDebtToken.balanceOf(caller.address);
|
||||||
|
|
||||||
expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt');
|
expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
calcExpectedUserDataAfterStableRateRebalance,
|
calcExpectedUserDataAfterStableRateRebalance,
|
||||||
calcExpectedUserDataAfterSwapRateMode,
|
calcExpectedUserDataAfterSwapRateMode,
|
||||||
calcExpectedUserDataAfterWithdraw,
|
calcExpectedUserDataAfterWithdraw,
|
||||||
calcExpectedUsersDataAfterRedirectInterest,
|
|
||||||
} from './utils/calculations';
|
} from './utils/calculations';
|
||||||
import {getReserveAddressFromSymbol, getReserveData, getUserData} from './utils/helpers';
|
import {getReserveAddressFromSymbol, getReserveData, getUserData} from './utils/helpers';
|
||||||
|
|
||||||
|
@ -49,23 +48,25 @@ const almostEqualOrEqual = function (
|
||||||
key === 'marketStableRate' ||
|
key === 'marketStableRate' ||
|
||||||
key === 'symbol' ||
|
key === 'symbol' ||
|
||||||
key === 'aTokenAddress' ||
|
key === 'aTokenAddress' ||
|
||||||
key === 'initialATokenExchangeRate' ||
|
|
||||||
key === 'decimals'
|
key === 'decimals'
|
||||||
) {
|
) {
|
||||||
// skipping consistency check on accessory data
|
// skipping consistency check on accessory data
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.assert(actual[key] != undefined, `Property ${key} is undefined in the actual data`);
|
this.assert(actual[key] != undefined, `Property ${key} is undefined in the actual data`);
|
||||||
expect(expected[key] != undefined, `Property ${key} is undefined in the expected data`);
|
expect(expected[key] != undefined, `Property ${key} is undefined in the expected data`);
|
||||||
|
|
||||||
|
if (expected[key] == null || actual[key] == null) {
|
||||||
|
console.log('Found a undefined value for Key ', key, ' value ', expected[key], actual[key]);
|
||||||
|
}
|
||||||
|
|
||||||
if (actual[key] instanceof BigNumber) {
|
if (actual[key] instanceof BigNumber) {
|
||||||
if (!expected[key]) {
|
|
||||||
console.log('Key ', key, ' value ', expected[key], actual[key]);
|
|
||||||
}
|
|
||||||
const actualValue = (<BigNumber>actual[key]).decimalPlaces(0, BigNumber.ROUND_DOWN);
|
const actualValue = (<BigNumber>actual[key]).decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
const expectedValue = (<BigNumber>expected[key]).decimalPlaces(0, BigNumber.ROUND_DOWN);
|
const expectedValue = (<BigNumber>expected[key]).decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
this.assert(
|
this.assert(
|
||||||
actualValue.eq(expectedValue) ||
|
actualValue.eq(expectedValue) ||
|
||||||
actualValue.plus(1).eq(expectedValue) ||
|
actualValue.plus(1).eq(expectedValue) ||
|
||||||
|
@ -133,7 +134,8 @@ export const approve = async (reserveSymbol: string, user: SignerWithAddress, te
|
||||||
export const deposit = async (
|
export const deposit = async (
|
||||||
reserveSymbol: string,
|
reserveSymbol: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
user: SignerWithAddress,
|
sender: SignerWithAddress,
|
||||||
|
onBehalfOf: tEthereumAddress,
|
||||||
sendValue: string,
|
sendValue: string,
|
||||||
expectedResult: string,
|
expectedResult: string,
|
||||||
testEnv: TestEnv,
|
testEnv: TestEnv,
|
||||||
|
@ -149,8 +151,9 @@ export const deposit = async (
|
||||||
|
|
||||||
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
|
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
|
||||||
reserve,
|
reserve,
|
||||||
user.address,
|
onBehalfOf,
|
||||||
testEnv
|
testEnv,
|
||||||
|
sender.address
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sendValue) {
|
if (sendValue) {
|
||||||
|
@ -158,14 +161,16 @@ export const deposit = async (
|
||||||
}
|
}
|
||||||
if (expectedResult === 'success') {
|
if (expectedResult === 'success') {
|
||||||
const txResult = await waitForTx(
|
const txResult = await waitForTx(
|
||||||
await await pool.connect(user.signer).deposit(reserve, amountToDeposit, '0', txOptions)
|
await pool
|
||||||
|
.connect(sender.signer)
|
||||||
|
.deposit(reserve, amountToDeposit, onBehalfOf, '0', txOptions)
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reserveData: reserveDataAfter,
|
reserveData: reserveDataAfter,
|
||||||
userData: userDataAfter,
|
userData: userDataAfter,
|
||||||
timestamp,
|
timestamp,
|
||||||
} = await getContractsData(reserve, user.address, testEnv);
|
} = await getContractsData(reserve, onBehalfOf, testEnv, sender.address);
|
||||||
|
|
||||||
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
||||||
|
|
||||||
|
@ -198,7 +203,7 @@ export const deposit = async (
|
||||||
// });
|
// });
|
||||||
} else if (expectedResult === 'revert') {
|
} else if (expectedResult === 'revert') {
|
||||||
await expect(
|
await expect(
|
||||||
pool.connect(user.signer).deposit(reserve, amountToDeposit, '0', txOptions),
|
pool.connect(sender.signer).deposit(reserve, amountToDeposit, onBehalfOf, '0', txOptions),
|
||||||
revertMessage
|
revertMessage
|
||||||
).to.be.reverted;
|
).to.be.reverted;
|
||||||
}
|
}
|
||||||
|
@ -650,153 +655,6 @@ export const rebalanceStableBorrowRate = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const redirectInterestStream = async (
|
|
||||||
reserveSymbol: string,
|
|
||||||
user: SignerWithAddress,
|
|
||||||
to: tEthereumAddress,
|
|
||||||
expectedResult: string,
|
|
||||||
testEnv: TestEnv,
|
|
||||||
revertMessage?: string
|
|
||||||
) => {
|
|
||||||
const {
|
|
||||||
aTokenInstance,
|
|
||||||
reserve,
|
|
||||||
userData: fromDataBefore,
|
|
||||||
reserveData: reserveDataBefore,
|
|
||||||
} = await getDataBeforeAction(reserveSymbol, user.address, testEnv);
|
|
||||||
|
|
||||||
const {userData: toDataBefore} = await getContractsData(reserve, to, testEnv);
|
|
||||||
|
|
||||||
if (expectedResult === 'success') {
|
|
||||||
const txResult = await waitForTx(
|
|
||||||
await aTokenInstance.connect(user.signer).redirectInterestStream(to)
|
|
||||||
);
|
|
||||||
|
|
||||||
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
|
||||||
|
|
||||||
const {userData: fromDataAfter} = await getContractsData(reserve, user.address, testEnv);
|
|
||||||
|
|
||||||
const {userData: toDataAfter} = await getContractsData(reserve, to, testEnv);
|
|
||||||
|
|
||||||
const [expectedFromData, expectedToData] = calcExpectedUsersDataAfterRedirectInterest(
|
|
||||||
reserveDataBefore,
|
|
||||||
fromDataBefore,
|
|
||||||
toDataBefore,
|
|
||||||
user.address,
|
|
||||||
to,
|
|
||||||
true,
|
|
||||||
txCost,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectEqual(fromDataAfter, expectedFromData);
|
|
||||||
expectEqual(toDataAfter, expectedToData);
|
|
||||||
|
|
||||||
// truffleAssert.eventEmitted(txResult, 'InterestStreamRedirected', (ev: any) => {
|
|
||||||
// const {_from, _to} = ev;
|
|
||||||
// return _from === user
|
|
||||||
// && _to === (to === user ? NIL_ADDRESS : to);
|
|
||||||
// });
|
|
||||||
} else if (expectedResult === 'revert') {
|
|
||||||
await expect(aTokenInstance.connect(user.signer).redirectInterestStream(to), revertMessage).to
|
|
||||||
.be.reverted;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const redirectInterestStreamOf = async (
|
|
||||||
reserveSymbol: string,
|
|
||||||
user: SignerWithAddress,
|
|
||||||
from: tEthereumAddress,
|
|
||||||
to: tEthereumAddress,
|
|
||||||
expectedResult: string,
|
|
||||||
testEnv: TestEnv,
|
|
||||||
revertMessage?: string
|
|
||||||
) => {
|
|
||||||
const {
|
|
||||||
aTokenInstance,
|
|
||||||
reserve,
|
|
||||||
userData: fromDataBefore,
|
|
||||||
reserveData: reserveDataBefore,
|
|
||||||
} = await getDataBeforeAction(reserveSymbol, from, testEnv);
|
|
||||||
|
|
||||||
const {userData: toDataBefore} = await getContractsData(reserve, user.address, testEnv);
|
|
||||||
|
|
||||||
if (expectedResult === 'success') {
|
|
||||||
const txResult = await waitForTx(
|
|
||||||
await aTokenInstance.connect(user.signer).redirectInterestStreamOf(from, to)
|
|
||||||
);
|
|
||||||
|
|
||||||
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
|
|
||||||
|
|
||||||
const {userData: fromDataAfter} = await getContractsData(reserve, from, testEnv);
|
|
||||||
|
|
||||||
const {userData: toDataAfter} = await getContractsData(reserve, to, testEnv);
|
|
||||||
|
|
||||||
const [expectedFromData, exptectedToData] = calcExpectedUsersDataAfterRedirectInterest(
|
|
||||||
reserveDataBefore,
|
|
||||||
fromDataBefore,
|
|
||||||
toDataBefore,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
from === user.address,
|
|
||||||
txCost,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectEqual(fromDataAfter, expectedFromData);
|
|
||||||
expectEqual(toDataAfter, exptectedToData);
|
|
||||||
|
|
||||||
// truffleAssert.eventEmitted(
|
|
||||||
// txResult,
|
|
||||||
// "InterestStreamRedirected",
|
|
||||||
// (ev: any) => {
|
|
||||||
// const {_from, _to} = ev;
|
|
||||||
// return (
|
|
||||||
// _from.toLowerCase() === from.toLowerCase() &&
|
|
||||||
// _to.toLowerCase() === to.toLowerCase()
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
} else if (expectedResult === 'revert') {
|
|
||||||
await expect(
|
|
||||||
aTokenInstance.connect(user.signer).redirectInterestStreamOf(from, to),
|
|
||||||
revertMessage
|
|
||||||
).to.be.reverted;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const allowInterestRedirectionTo = async (
|
|
||||||
reserveSymbol: string,
|
|
||||||
user: SignerWithAddress,
|
|
||||||
to: tEthereumAddress,
|
|
||||||
expectedResult: string,
|
|
||||||
testEnv: TestEnv,
|
|
||||||
revertMessage?: string
|
|
||||||
) => {
|
|
||||||
const {aTokenInstance} = await getDataBeforeAction(reserveSymbol, user.address, testEnv);
|
|
||||||
|
|
||||||
if (expectedResult === 'success') {
|
|
||||||
const txResult = await waitForTx(
|
|
||||||
await aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to)
|
|
||||||
);
|
|
||||||
|
|
||||||
// truffleAssert.eventEmitted(
|
|
||||||
// txResult,
|
|
||||||
// "InterestRedirectionAllowanceChanged",
|
|
||||||
// (ev: any) => {
|
|
||||||
// const {_from, _to} = ev;
|
|
||||||
// return (
|
|
||||||
// _from.toLowerCase() === user.toLowerCase() &&
|
|
||||||
// _to.toLowerCase() === to.toLowerCase()
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
} else if (expectedResult === 'revert') {
|
|
||||||
await expect(aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to), revertMessage)
|
|
||||||
.to.be.reverted;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const expectEqual = (
|
const expectEqual = (
|
||||||
actual: UserReserveData | ReserveData,
|
actual: UserReserveData | ReserveData,
|
||||||
expected: UserReserveData | ReserveData
|
expected: UserReserveData | ReserveData
|
||||||
|
@ -845,10 +703,15 @@ const getTxCostAndTimestamp = async (tx: ContractReceipt) => {
|
||||||
return {txCost, txTimestamp};
|
return {txCost, txTimestamp};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getContractsData = async (reserve: string, user: string, testEnv: TestEnv) => {
|
export const getContractsData = async (
|
||||||
|
reserve: string,
|
||||||
|
user: string,
|
||||||
|
testEnv: TestEnv,
|
||||||
|
sender?: string
|
||||||
|
) => {
|
||||||
const {pool} = testEnv;
|
const {pool} = testEnv;
|
||||||
const reserveData = await getReserveData(pool, reserve);
|
const reserveData = await getReserveData(pool, reserve);
|
||||||
const userData = await getUserData(pool, reserve, user);
|
const userData = await getUserData(pool, reserve, user, sender || user);
|
||||||
const timestamp = await timeLatest();
|
const timestamp = await timeLatest();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
getMintableErc20,
|
getMintableErc20,
|
||||||
getLendingPoolConfiguratorProxy,
|
getLendingPoolConfiguratorProxy,
|
||||||
getPriceOracle,
|
getPriceOracle,
|
||||||
|
getMockSwapAdapter,
|
||||||
} from '../../helpers/contracts-helpers';
|
} from '../../helpers/contracts-helpers';
|
||||||
import {tEthereumAddress} from '../../helpers/types';
|
import {tEthereumAddress} from '../../helpers/types';
|
||||||
import {LendingPool} from '../../types/LendingPool';
|
import {LendingPool} from '../../types/LendingPool';
|
||||||
|
@ -23,6 +24,7 @@ import bignumberChai from 'chai-bignumber';
|
||||||
import {almostEqual} from './almost-equal';
|
import {almostEqual} from './almost-equal';
|
||||||
import {PriceOracle} from '../../types/PriceOracle';
|
import {PriceOracle} from '../../types/PriceOracle';
|
||||||
import {LendingPoolAddressesProvider} from '../../types/LendingPoolAddressesProvider';
|
import {LendingPoolAddressesProvider} from '../../types/LendingPoolAddressesProvider';
|
||||||
|
import { MockSwapAdapter } from '../../types/MockSwapAdapter';
|
||||||
chai.use(bignumberChai());
|
chai.use(bignumberChai());
|
||||||
chai.use(almostEqual());
|
chai.use(almostEqual());
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ export interface TestEnv {
|
||||||
usdc: MintableErc20;
|
usdc: MintableErc20;
|
||||||
lend: MintableErc20;
|
lend: MintableErc20;
|
||||||
addressesProvider: LendingPoolAddressesProvider;
|
addressesProvider: LendingPoolAddressesProvider;
|
||||||
|
mockSwapAdapter: MockSwapAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
let buidlerevmSnapshotId: string = '0x1';
|
let buidlerevmSnapshotId: string = '0x1';
|
||||||
|
@ -67,6 +70,7 @@ const testEnv: TestEnv = {
|
||||||
usdc: {} as MintableErc20,
|
usdc: {} as MintableErc20,
|
||||||
lend: {} as MintableErc20,
|
lend: {} as MintableErc20,
|
||||||
addressesProvider: {} as LendingPoolAddressesProvider,
|
addressesProvider: {} as LendingPoolAddressesProvider,
|
||||||
|
mockSwapAdapter: {} as MockSwapAdapter
|
||||||
} as TestEnv;
|
} as TestEnv;
|
||||||
|
|
||||||
export async function initializeMakeSuite() {
|
export async function initializeMakeSuite() {
|
||||||
|
@ -125,6 +129,8 @@ export async function initializeMakeSuite() {
|
||||||
testEnv.usdc = await getMintableErc20(usdcAddress);
|
testEnv.usdc = await getMintableErc20(usdcAddress);
|
||||||
testEnv.lend = await getMintableErc20(lendAddress);
|
testEnv.lend = await getMintableErc20(lendAddress);
|
||||||
testEnv.weth = await getMintableErc20(wethAddress);
|
testEnv.weth = await getMintableErc20(wethAddress);
|
||||||
|
|
||||||
|
testEnv.mockSwapAdapter = await getMockSwapAdapter()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {
|
export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {
|
||||||
|
|
|
@ -8,10 +8,7 @@ import {
|
||||||
repay,
|
repay,
|
||||||
setUseAsCollateral,
|
setUseAsCollateral,
|
||||||
swapBorrowRateMode,
|
swapBorrowRateMode,
|
||||||
rebalanceStableBorrowRate,
|
rebalanceStableBorrowRate
|
||||||
redirectInterestStream,
|
|
||||||
redirectInterestStreamOf,
|
|
||||||
allowInterestRedirectionTo,
|
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import {RateMode} from '../../helpers/types';
|
import {RateMode} from '../../helpers/types';
|
||||||
|
|
||||||
|
@ -92,13 +89,25 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
|
|
||||||
case 'deposit':
|
case 'deposit':
|
||||||
{
|
{
|
||||||
const {amount, sendValue} = action.args;
|
const {amount, sendValue, onBehalfOf: onBehalfOfIndex} = action.args;
|
||||||
|
const onBehalfOf = onBehalfOfIndex
|
||||||
|
? users[parseInt(onBehalfOfIndex)].address
|
||||||
|
: user.address;
|
||||||
|
|
||||||
if (!amount || amount === '') {
|
if (!amount || amount === '') {
|
||||||
throw `Invalid amount to deposit into the ${reserve} reserve`;
|
throw `Invalid amount to deposit into the ${reserve} reserve`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await deposit(reserve, amount, user, sendValue, expected, testEnv, revertMessage);
|
await deposit(
|
||||||
|
reserve,
|
||||||
|
amount,
|
||||||
|
user,
|
||||||
|
onBehalfOf,
|
||||||
|
sendValue,
|
||||||
|
expected,
|
||||||
|
testEnv,
|
||||||
|
revertMessage
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -185,71 +194,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
|
||||||
await rebalanceStableBorrowRate(reserve, user, target, expected, testEnv, revertMessage);
|
await rebalanceStableBorrowRate(reserve, user, target, expected, testEnv, revertMessage);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'redirectInterestStream':
|
|
||||||
{
|
|
||||||
const {to: toIndex} = action.args;
|
|
||||||
|
|
||||||
if (!toIndex || toIndex === '') {
|
|
||||||
throw `A target must be selected when trying to redirect the interest`;
|
|
||||||
}
|
|
||||||
const toUser = users[parseInt(toIndex)];
|
|
||||||
|
|
||||||
await redirectInterestStream(
|
|
||||||
reserve,
|
|
||||||
user,
|
|
||||||
toUser.address,
|
|
||||||
expected,
|
|
||||||
testEnv,
|
|
||||||
revertMessage
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'redirectInterestStreamOf':
|
|
||||||
{
|
|
||||||
const {from: fromIndex, to: toIndex} = action.args;
|
|
||||||
|
|
||||||
if (!fromIndex || fromIndex === '') {
|
|
||||||
throw `A from address must be specified when trying to redirect the interest`;
|
|
||||||
}
|
|
||||||
if (!toIndex || toIndex === '') {
|
|
||||||
throw `A target must be selected when trying to redirect the interest`;
|
|
||||||
}
|
|
||||||
const toUser = users[parseInt(toIndex)];
|
|
||||||
const fromUser = users[parseInt(fromIndex)];
|
|
||||||
|
|
||||||
await redirectInterestStreamOf(
|
|
||||||
reserve,
|
|
||||||
user,
|
|
||||||
fromUser.address,
|
|
||||||
toUser.address,
|
|
||||||
expected,
|
|
||||||
testEnv,
|
|
||||||
revertMessage
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'allowInterestRedirectionTo':
|
|
||||||
{
|
|
||||||
const {to: toIndex} = action.args;
|
|
||||||
|
|
||||||
if (!toIndex || toIndex === '') {
|
|
||||||
throw `A target must be selected when trying to redirect the interest`;
|
|
||||||
}
|
|
||||||
const toUser = users[parseInt(toIndex)];
|
|
||||||
|
|
||||||
await allowInterestRedirectionTo(
|
|
||||||
reserve,
|
|
||||||
user,
|
|
||||||
toUser.address,
|
|
||||||
expected,
|
|
||||||
testEnv,
|
|
||||||
revertMessage
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw `Invalid action requested: ${name}`;
|
throw `Invalid action requested: ${name}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,7 +206,6 @@
|
||||||
"name": "deposit",
|
"name": "deposit",
|
||||||
"args": {
|
"args": {
|
||||||
"reserve": "WETH",
|
"reserve": "WETH",
|
||||||
|
|
||||||
"amount": "0",
|
"amount": "0",
|
||||||
"user": "1"
|
"user": "1"
|
||||||
},
|
},
|
||||||
|
@ -229,6 +228,40 @@
|
||||||
"revertMessage": "Amount must be greater than 0"
|
"revertMessage": "Amount must be greater than 0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "User 1 deposits 100 DAI on behalf of user 2, user 2 tries to borrow 0.1 WETH",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "mint",
|
||||||
|
"args": {
|
||||||
|
"reserve": "DAI",
|
||||||
|
"amount": "100",
|
||||||
|
"user": "1"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deposit",
|
||||||
|
"args": {
|
||||||
|
"reserve": "DAI",
|
||||||
|
"amount": "100",
|
||||||
|
"user": "1",
|
||||||
|
"onBehalfOf": "2"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "borrow",
|
||||||
|
"args": {
|
||||||
|
"reserve": "WETH",
|
||||||
|
"amount": "0.1",
|
||||||
|
"borrowRateMode": "variable",
|
||||||
|
"user": "2"
|
||||||
|
},
|
||||||
|
"expected": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
{
|
|
||||||
"title": "AToken: interest rate redirection negative test cases",
|
|
||||||
"description": "Test cases for the aToken interest rate redirection.",
|
|
||||||
"stories": [
|
|
||||||
{
|
|
||||||
"description": "User 0 deposits 1000 DAI, tries to give allowance to redirect interest to himself (revert expected)",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "mint",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approve",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "deposit",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "allowInterestRedirectionTo",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0",
|
|
||||||
"to": "0"
|
|
||||||
},
|
|
||||||
"expected": "revert",
|
|
||||||
"revertMessage": "User cannot give allowance to himself"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "User 1 tries to redirect the interest of user 0 without allowance (revert expected)",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStreamOf",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "1",
|
|
||||||
"from": "0",
|
|
||||||
"to": "2"
|
|
||||||
},
|
|
||||||
"expected": "revert",
|
|
||||||
"revertMessage": "Caller is not allowed to redirect the interest of the user"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "User 0 tries to redirect the interest to user 2 twice (revert expected)",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0",
|
|
||||||
"to": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0",
|
|
||||||
"to": "2"
|
|
||||||
},
|
|
||||||
"expected": "revert",
|
|
||||||
"revertMessage": "Interest is already redirected to the user"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "User 3 with 0 balance tries to redirect the interest to user 2 (revert expected)",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "3",
|
|
||||||
"to": "2"
|
|
||||||
},
|
|
||||||
"expected": "revert",
|
|
||||||
"revertMessage": "Interest stream can only be redirected if there is a valid balance"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,405 +0,0 @@
|
||||||
{
|
|
||||||
"title": "AToken: interest rate redirection",
|
|
||||||
"description": "Test cases for the aToken interest rate redirection.",
|
|
||||||
"stories": [
|
|
||||||
{
|
|
||||||
"description": "User 0 deposits 1000 DAI, redirects the interest to user 2",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "mint",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approve",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "deposit",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0",
|
|
||||||
"to": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "User 1 deposits 1 ETH, borrows 100 DAI, repays after one year. Users 0 deposits another 1000 DAI. Redirected balance of user 2 is updated",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "mint",
|
|
||||||
"args": {
|
|
||||||
"reserve": "WETH",
|
|
||||||
"amount": "2",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approve",
|
|
||||||
"args": {
|
|
||||||
"reserve": "WETH",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "deposit",
|
|
||||||
"args": {
|
|
||||||
"reserve": "WETH",
|
|
||||||
|
|
||||||
"amount": "2",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "borrow",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"borrowRateMode": "stable",
|
|
||||||
"user": "1",
|
|
||||||
"timeTravel": "365"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mint",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "deposit",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "User 1 borrows another 100 DAI, repay the whole amount. Users 0 and User 2 withdraw",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "borrow",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"borrowRateMode": "stable",
|
|
||||||
"user": "1",
|
|
||||||
"timeTravel": "365"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mint",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approve",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "repay",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "1",
|
|
||||||
"onBehalfOf": "1",
|
|
||||||
"borrowRateMode": "stable"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "User 0 deposits 1000 DAI, redirects interest to user 2, user 1 borrows 100 DAI. After one year, user 0 redirects interest back to himself, user 1 borrows another 100 DAI and after another year repays the whole amount. Users 0 and User 2 withdraw",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "deposit",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0",
|
|
||||||
"to": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "borrow",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"borrowRateMode": "stable",
|
|
||||||
"user": "1",
|
|
||||||
"timeTravel": "365"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0",
|
|
||||||
"to": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "borrow",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"borrowRateMode": "stable",
|
|
||||||
"user": "1",
|
|
||||||
"timeTravel": "365"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mint",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approve",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "repay",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "1",
|
|
||||||
"onBehalfOf": "1",
|
|
||||||
"borrowRateMode": "stable"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "User 0 deposits 1000 DAI, redirects interest to user 2, user 1 borrows 100 DAI. After one year, user 2 redirects interest to user 3. user 1 borrows another 100 DAI, user 0 deposits another 100 DAI. User 1 repays the whole amount. Users 0, 2 first 1 DAI, then everything. User 3 withdraws",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"name": "deposit",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1000",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "0",
|
|
||||||
"to": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "borrow",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"borrowRateMode": "stable",
|
|
||||||
"user": "1",
|
|
||||||
"timeTravel": "365"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "redirectInterestStream",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "2",
|
|
||||||
"to": "3"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "borrow",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"borrowRateMode": "stable",
|
|
||||||
"user": "1",
|
|
||||||
"timeTravel": "365"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "deposit",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mint",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "100",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approve",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"user": "1"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "repay",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "1",
|
|
||||||
"onBehalfOf": "1",
|
|
||||||
"borrowRateMode": "stable"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "1",
|
|
||||||
"user": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "0"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "2"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "withdraw",
|
|
||||||
"args": {
|
|
||||||
"reserve": "DAI",
|
|
||||||
"amount": "-1",
|
|
||||||
"user": "3"
|
|
||||||
},
|
|
||||||
"expected": "success"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -7,9 +7,9 @@ import {
|
||||||
EXCESS_UTILIZATION_RATE,
|
EXCESS_UTILIZATION_RATE,
|
||||||
ZERO_ADDRESS,
|
ZERO_ADDRESS,
|
||||||
} from '../../../helpers/constants';
|
} from '../../../helpers/constants';
|
||||||
import { IReserveParams, iAavePoolAssets, RateMode } from '../../../helpers/types';
|
import {IReserveParams, iAavePoolAssets, RateMode} from '../../../helpers/types';
|
||||||
import './math';
|
import './math';
|
||||||
import { ReserveData, UserReserveData } from './interfaces';
|
import {ReserveData, UserReserveData} from './interfaces';
|
||||||
|
|
||||||
export const strToBN = (amount: string): BigNumber => new BigNumber(amount);
|
export const strToBN = (amount: string): BigNumber => new BigNumber(amount);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export const calcExpectedUserDataAfterDeposit = (
|
||||||
txTimestamp
|
txTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
expectedUserData.principalATokenBalance = userDataBeforeAction.principalStableDebt;
|
expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt;
|
||||||
expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt;
|
expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt;
|
||||||
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
|
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
|
||||||
expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate;
|
expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate;
|
||||||
|
@ -49,38 +49,26 @@ export const calcExpectedUserDataAfterDeposit = (
|
||||||
|
|
||||||
expectedUserData.liquidityRate = reserveDataAfterAction.liquidityRate;
|
expectedUserData.liquidityRate = reserveDataAfterAction.liquidityRate;
|
||||||
|
|
||||||
expectedUserData.currentATokenBalance = userDataBeforeAction.currentATokenBalance.plus(
|
expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(
|
||||||
amountDeposited
|
userDataBeforeAction,
|
||||||
|
reserveDataAfterAction.liquidityIndex,
|
||||||
|
new BigNumber(amountDeposited),
|
||||||
|
new BigNumber(0)
|
||||||
);
|
);
|
||||||
|
expectedUserData.currentATokenBalance = calcExpectedATokenBalance(
|
||||||
if (userDataBeforeAction.currentATokenBalance.eq(0)) {
|
|
||||||
expectedUserData.usageAsCollateralEnabled = true;
|
|
||||||
} else {
|
|
||||||
//if the user is withdrawing everything, usageAsCollateralEnabled must be false
|
|
||||||
if (expectedUserData.currentATokenBalance.eq(0)) {
|
|
||||||
expectedUserData.usageAsCollateralEnabled = false;
|
|
||||||
} else {
|
|
||||||
expectedUserData.usageAsCollateralEnabled = userDataBeforeAction.usageAsCollateralEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
|
|
||||||
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited);
|
|
||||||
|
|
||||||
expectedUserData.principalATokenBalance = expectedUserData.currentATokenBalance = calcExpectedATokenBalance(
|
|
||||||
reserveDataBeforeAction,
|
reserveDataBeforeAction,
|
||||||
userDataBeforeAction,
|
userDataBeforeAction,
|
||||||
txTimestamp
|
txTimestamp
|
||||||
).plus(amountDeposited);
|
).plus(amountDeposited);
|
||||||
|
|
||||||
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
|
if (userDataBeforeAction.currentATokenBalance.eq(0)) {
|
||||||
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
|
expectedUserData.usageAsCollateralEnabled = true;
|
||||||
expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex(
|
} else {
|
||||||
reserveDataBeforeAction,
|
expectedUserData.usageAsCollateralEnabled = userDataBeforeAction.usageAsCollateralEnabled;
|
||||||
expectedUserData.currentATokenBalance,
|
}
|
||||||
expectedUserData.redirectedBalance,
|
|
||||||
txTimestamp
|
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
|
||||||
);
|
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited);
|
||||||
|
|
||||||
expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance(
|
expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance(
|
||||||
userDataBeforeAction,
|
userDataBeforeAction,
|
||||||
|
@ -93,14 +81,6 @@ export const calcExpectedUserDataAfterDeposit = (
|
||||||
txTimestamp
|
txTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance(
|
|
||||||
userDataBeforeAction,
|
|
||||||
expectedUserData,
|
|
||||||
userDataBeforeAction.redirectionAddressRedirectedBalance,
|
|
||||||
new BigNumber(amountDeposited),
|
|
||||||
new BigNumber(0)
|
|
||||||
);
|
|
||||||
|
|
||||||
return expectedUserData;
|
return expectedUserData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,23 +105,29 @@ export const calcExpectedUserDataAfterWithdraw = (
|
||||||
amountWithdrawn = aTokenBalance.toFixed(0);
|
amountWithdrawn = aTokenBalance.toFixed(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedUserData.principalATokenBalance = expectedUserData.currentATokenBalance = aTokenBalance.minus(
|
expectedUserData.scaledATokenBalance = calcExpectedScaledATokenBalance(
|
||||||
amountWithdrawn
|
userDataBeforeAction,
|
||||||
|
reserveDataAfterAction.liquidityIndex,
|
||||||
|
new BigNumber(0),
|
||||||
|
new BigNumber(amountWithdrawn)
|
||||||
);
|
);
|
||||||
|
|
||||||
expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance(
|
expectedUserData.currentATokenBalance = aTokenBalance.minus(amountWithdrawn);
|
||||||
|
|
||||||
|
expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt;
|
||||||
|
expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt;
|
||||||
|
|
||||||
|
expectedUserData.currentStableDebt = calcExpectedStableDebtTokenBalance(
|
||||||
userDataBeforeAction,
|
userDataBeforeAction,
|
||||||
txTimestamp
|
txTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
expectedUserData.currentVariableDebt = expectedUserData.principalStableDebt = calcExpectedVariableDebtTokenBalance(
|
expectedUserData.currentVariableDebt = calcExpectedVariableDebtTokenBalance(
|
||||||
reserveDataBeforeAction,
|
reserveDataBeforeAction,
|
||||||
userDataBeforeAction,
|
userDataBeforeAction,
|
||||||
txTimestamp
|
txTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
expectedUserData.principalStableDebt = userDataBeforeAction.principalStableDebt;
|
|
||||||
expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt;
|
|
||||||
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
|
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
|
||||||
expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate;
|
expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate;
|
||||||
expectedUserData.stableRateLastUpdated = userDataBeforeAction.stableRateLastUpdated;
|
expectedUserData.stableRateLastUpdated = userDataBeforeAction.stableRateLastUpdated;
|
||||||
|
@ -159,31 +145,8 @@ export const calcExpectedUserDataAfterWithdraw = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
|
|
||||||
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountWithdrawn);
|
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountWithdrawn);
|
||||||
|
|
||||||
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
|
|
||||||
|
|
||||||
if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.redirectedBalance.eq(0)) {
|
|
||||||
expectedUserData.interestRedirectionAddress = ZERO_ADDRESS;
|
|
||||||
} else {
|
|
||||||
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
|
|
||||||
}
|
|
||||||
expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
expectedUserData.currentATokenBalance,
|
|
||||||
expectedUserData.redirectedBalance,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance(
|
|
||||||
userDataBeforeAction,
|
|
||||||
expectedUserData,
|
|
||||||
userDataBeforeAction.redirectionAddressRedirectedBalance,
|
|
||||||
new BigNumber(0),
|
|
||||||
new BigNumber(amountWithdrawn)
|
|
||||||
);
|
|
||||||
|
|
||||||
return expectedUserData;
|
return expectedUserData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -575,13 +538,8 @@ export const calcExpectedUserDataAfterBorrow = (
|
||||||
userDataBeforeAction,
|
userDataBeforeAction,
|
||||||
currentTimestamp
|
currentTimestamp
|
||||||
);
|
);
|
||||||
expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance;
|
expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance;
|
||||||
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
|
|
||||||
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
|
|
||||||
expectedUserData.redirectionAddressRedirectedBalance =
|
|
||||||
userDataBeforeAction.redirectionAddressRedirectedBalance;
|
|
||||||
expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex;
|
|
||||||
|
|
||||||
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed);
|
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed);
|
||||||
|
|
||||||
return expectedUserData;
|
return expectedUserData;
|
||||||
|
@ -664,13 +622,8 @@ export const calcExpectedUserDataAfterRepay = (
|
||||||
userDataBeforeAction,
|
userDataBeforeAction,
|
||||||
txTimestamp
|
txTimestamp
|
||||||
);
|
);
|
||||||
expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance;
|
expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance;
|
||||||
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
|
|
||||||
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
|
|
||||||
expectedUserData.redirectionAddressRedirectedBalance =
|
|
||||||
userDataBeforeAction.redirectionAddressRedirectedBalance;
|
|
||||||
expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex;
|
|
||||||
|
|
||||||
if (user === onBehalfOf) {
|
if (user === onBehalfOf) {
|
||||||
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(totalRepaid);
|
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(totalRepaid);
|
||||||
} else {
|
} else {
|
||||||
|
@ -687,7 +640,7 @@ export const calcExpectedUserDataAfterSetUseAsCollateral = (
|
||||||
userDataBeforeAction: UserReserveData,
|
userDataBeforeAction: UserReserveData,
|
||||||
txCost: BigNumber
|
txCost: BigNumber
|
||||||
): UserReserveData => {
|
): UserReserveData => {
|
||||||
const expectedUserData = { ...userDataBeforeAction };
|
const expectedUserData = {...userDataBeforeAction};
|
||||||
|
|
||||||
expectedUserData.usageAsCollateralEnabled = useAsCollateral;
|
expectedUserData.usageAsCollateralEnabled = useAsCollateral;
|
||||||
|
|
||||||
|
@ -793,7 +746,7 @@ export const calcExpectedUserDataAfterSwapRateMode = (
|
||||||
txCost: BigNumber,
|
txCost: BigNumber,
|
||||||
txTimestamp: BigNumber
|
txTimestamp: BigNumber
|
||||||
): UserReserveData => {
|
): UserReserveData => {
|
||||||
const expectedUserData = { ...userDataBeforeAction };
|
const expectedUserData = {...userDataBeforeAction};
|
||||||
|
|
||||||
const variableBorrowBalance = calcExpectedVariableDebtTokenBalance(
|
const variableBorrowBalance = calcExpectedVariableDebtTokenBalance(
|
||||||
reserveDataBeforeAction,
|
reserveDataBeforeAction,
|
||||||
|
@ -809,18 +762,6 @@ export const calcExpectedUserDataAfterSwapRateMode = (
|
||||||
txTimestamp
|
txTimestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance;
|
|
||||||
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
|
|
||||||
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
|
|
||||||
expectedUserData.redirectionAddressRedirectedBalance =
|
|
||||||
userDataBeforeAction.redirectionAddressRedirectedBalance;
|
|
||||||
expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
expectedUserData.currentATokenBalance,
|
|
||||||
expectedUserData.redirectedBalance,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rateMode === RateMode.Stable) {
|
if (rateMode === RateMode.Stable) {
|
||||||
// swap to variable
|
// swap to variable
|
||||||
expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = new BigNumber(0);
|
expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = new BigNumber(0);
|
||||||
|
@ -912,6 +853,7 @@ export const calcExpectedReserveDataAfterStableRateRebalance = (
|
||||||
expectedReserveData.totalBorrowsVariable,
|
expectedReserveData.totalBorrowsVariable,
|
||||||
expectedReserveData.averageStableBorrowRate
|
expectedReserveData.averageStableBorrowRate
|
||||||
);
|
);
|
||||||
|
|
||||||
expectedReserveData.liquidityRate = rates[0];
|
expectedReserveData.liquidityRate = rates[0];
|
||||||
|
|
||||||
expectedReserveData.stableBorrowRate = rates[1];
|
expectedReserveData.stableBorrowRate = rates[1];
|
||||||
|
@ -937,7 +879,7 @@ export const calcExpectedUserDataAfterStableRateRebalance = (
|
||||||
txCost: BigNumber,
|
txCost: BigNumber,
|
||||||
txTimestamp: BigNumber
|
txTimestamp: BigNumber
|
||||||
): UserReserveData => {
|
): UserReserveData => {
|
||||||
const expectedUserData = { ...userDataBeforeAction };
|
const expectedUserData = {...userDataBeforeAction};
|
||||||
|
|
||||||
expectedUserData.principalVariableDebt = calcExpectedVariableDebtTokenBalance(
|
expectedUserData.principalVariableDebt = calcExpectedVariableDebtTokenBalance(
|
||||||
reserveDataBeforeAction,
|
reserveDataBeforeAction,
|
||||||
|
@ -953,10 +895,6 @@ export const calcExpectedUserDataAfterStableRateRebalance = (
|
||||||
|
|
||||||
expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt;
|
expectedUserData.principalVariableDebt = userDataBeforeAction.principalVariableDebt;
|
||||||
|
|
||||||
const debtAccrued = expectedUserData.currentStableDebt.minus(
|
|
||||||
userDataBeforeAction.principalStableDebt
|
|
||||||
);
|
|
||||||
|
|
||||||
expectedUserData.stableBorrowRate = reserveDataBeforeAction.stableBorrowRate;
|
expectedUserData.stableBorrowRate = reserveDataBeforeAction.stableBorrowRate;
|
||||||
|
|
||||||
expectedUserData.liquidityRate = expectedDataAfterAction.liquidityRate;
|
expectedUserData.liquidityRate = expectedDataAfterAction.liquidityRate;
|
||||||
|
@ -966,114 +904,19 @@ export const calcExpectedUserDataAfterStableRateRebalance = (
|
||||||
userDataBeforeAction,
|
userDataBeforeAction,
|
||||||
txTimestamp
|
txTimestamp
|
||||||
);
|
);
|
||||||
expectedUserData.principalATokenBalance = userDataBeforeAction.principalATokenBalance;
|
|
||||||
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
|
|
||||||
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
|
|
||||||
expectedUserData.redirectionAddressRedirectedBalance =
|
|
||||||
userDataBeforeAction.redirectionAddressRedirectedBalance;
|
|
||||||
|
|
||||||
expectedUserData.currentATokenUserIndex = calcExpectedATokenUserIndex(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
expectedUserData.currentATokenBalance,
|
|
||||||
expectedUserData.redirectedBalance,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
return expectedUserData;
|
return expectedUserData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const calcExpectedUsersDataAfterRedirectInterest = (
|
const calcExpectedScaledATokenBalance = (
|
||||||
reserveDataBeforeAction: ReserveData,
|
userDataBeforeAction: UserReserveData,
|
||||||
fromDataBeforeAction: UserReserveData,
|
index: BigNumber,
|
||||||
toDataBeforeAction: UserReserveData,
|
amountAdded: BigNumber,
|
||||||
fromAddress: string,
|
amountTaken: BigNumber
|
||||||
toAddress: string,
|
|
||||||
isFromExecutingTx: boolean,
|
|
||||||
txCost: BigNumber,
|
|
||||||
txTimestamp: BigNumber
|
|
||||||
): UserReserveData[] => {
|
|
||||||
const expectedFromData = { ...fromDataBeforeAction };
|
|
||||||
const expectedToData = { ...toDataBeforeAction };
|
|
||||||
|
|
||||||
expectedFromData.currentStableDebt = calcExpectedStableDebtTokenBalance(
|
|
||||||
fromDataBeforeAction,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectedToData.currentVariableDebt = calcExpectedVariableDebtTokenBalance(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
toDataBeforeAction,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectedFromData.variableBorrowIndex = fromDataBeforeAction.variableBorrowIndex;
|
|
||||||
expectedToData.variableBorrowIndex = toDataBeforeAction.variableBorrowIndex;
|
|
||||||
|
|
||||||
expectedFromData.stableBorrowRate = fromDataBeforeAction.stableBorrowRate;
|
|
||||||
expectedToData.stableBorrowRate = toDataBeforeAction.stableBorrowRate;
|
|
||||||
|
|
||||||
expectedFromData.principalATokenBalance = expectedFromData.currentATokenBalance = calcExpectedATokenBalance(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
fromDataBeforeAction,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectedToData.principalATokenBalance = expectedToData.currentATokenBalance = calcExpectedATokenBalance(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
toDataBeforeAction,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectedToData.redirectedBalance = toDataBeforeAction.redirectedBalance.plus(
|
|
||||||
expectedFromData.currentATokenBalance
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fromAddress === toAddress) {
|
|
||||||
expectedFromData.interestRedirectionAddress = ZERO_ADDRESS;
|
|
||||||
expectedFromData.redirectedBalance = new BigNumber(0);
|
|
||||||
expectedFromData.redirectionAddressRedirectedBalance = new BigNumber(0);
|
|
||||||
expectedToData.interestRedirectionAddress = ZERO_ADDRESS;
|
|
||||||
expectedToData.redirectedBalance = new BigNumber(0);
|
|
||||||
expectedToData.redirectionAddressRedirectedBalance = new BigNumber(0);
|
|
||||||
} else {
|
|
||||||
expectedFromData.interestRedirectionAddress = toAddress;
|
|
||||||
|
|
||||||
expectedFromData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance(
|
|
||||||
toDataBeforeAction,
|
|
||||||
expectedFromData,
|
|
||||||
toDataBeforeAction.redirectedBalance,
|
|
||||||
expectedFromData.currentATokenBalance,
|
|
||||||
new BigNumber(0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedFromData.currentATokenUserIndex = calcExpectedATokenUserIndex(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
expectedFromData.currentATokenBalance,
|
|
||||||
expectedFromData.redirectedBalance,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
expectedToData.currentATokenUserIndex = calcExpectedATokenUserIndex(
|
|
||||||
reserveDataBeforeAction,
|
|
||||||
expectedToData.currentATokenBalance,
|
|
||||||
expectedToData.redirectedBalance,
|
|
||||||
txTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
return [expectedFromData, expectedToData];
|
|
||||||
};
|
|
||||||
|
|
||||||
const calcExpectedATokenUserIndex = (
|
|
||||||
reserveDataBeforeAction: ReserveData,
|
|
||||||
expectedUserBalanceAfterAction: BigNumber,
|
|
||||||
expectedUserRedirectedBalanceAterAction: BigNumber,
|
|
||||||
currentTimestamp: BigNumber
|
|
||||||
) => {
|
) => {
|
||||||
if (expectedUserBalanceAfterAction.eq(0) && expectedUserRedirectedBalanceAterAction.eq(0)) {
|
return userDataBeforeAction.scaledATokenBalance
|
||||||
return new BigNumber(0);
|
.plus(amountAdded.rayDiv(index))
|
||||||
}
|
.minus(amountTaken.rayDiv(index));
|
||||||
return calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const calcExpectedATokenBalance = (
|
const calcExpectedATokenBalance = (
|
||||||
|
@ -1081,51 +924,24 @@ const calcExpectedATokenBalance = (
|
||||||
userDataBeforeAction: UserReserveData,
|
userDataBeforeAction: UserReserveData,
|
||||||
currentTimestamp: BigNumber
|
currentTimestamp: BigNumber
|
||||||
) => {
|
) => {
|
||||||
const income = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp);
|
const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
interestRedirectionAddress,
|
scaledATokenBalance: scaledBalanceBeforeAction,
|
||||||
currentATokenUserIndex: userIndexBeforeAction,
|
|
||||||
redirectedBalance,
|
|
||||||
principalATokenBalance: principalBalanceBeforeAction,
|
|
||||||
} = userDataBeforeAction;
|
} = userDataBeforeAction;
|
||||||
|
|
||||||
if (userIndexBeforeAction.eq(0)) {
|
return scaledBalanceBeforeAction.rayMul(index);
|
||||||
return principalBalanceBeforeAction;
|
|
||||||
}
|
|
||||||
if (interestRedirectionAddress === ZERO_ADDRESS) {
|
|
||||||
return principalBalanceBeforeAction
|
|
||||||
.plus(redirectedBalance)
|
|
||||||
.wadToRay()
|
|
||||||
.rayMul(income)
|
|
||||||
.rayDiv(userIndexBeforeAction)
|
|
||||||
.rayToWad()
|
|
||||||
.minus(redirectedBalance);
|
|
||||||
} else {
|
|
||||||
return principalBalanceBeforeAction.plus(
|
|
||||||
redirectedBalance
|
|
||||||
.wadToRay()
|
|
||||||
.rayMul(income)
|
|
||||||
.rayDiv(userIndexBeforeAction)
|
|
||||||
.rayToWad()
|
|
||||||
.minus(redirectedBalance)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const calcExpectedRedirectedBalance = (
|
const calcExpectedRedirectedBalance = (
|
||||||
userDataBeforeAction: UserReserveData,
|
|
||||||
expectedUserDataAfterAction: UserReserveData,
|
expectedUserDataAfterAction: UserReserveData,
|
||||||
|
index: BigNumber,
|
||||||
redirectedBalanceBefore: BigNumber,
|
redirectedBalanceBefore: BigNumber,
|
||||||
amountToAdd: BigNumber,
|
amountToAdd: BigNumber,
|
||||||
amountToSubstract: BigNumber
|
amountToSubstract: BigNumber
|
||||||
): BigNumber => {
|
): BigNumber => {
|
||||||
const balanceIncrease = userDataBeforeAction.currentATokenBalance.minus(
|
|
||||||
userDataBeforeAction.principalATokenBalance
|
|
||||||
);
|
|
||||||
|
|
||||||
return expectedUserDataAfterAction.interestRedirectionAddress !== ZERO_ADDRESS
|
return expectedUserDataAfterAction.interestRedirectionAddress !== ZERO_ADDRESS
|
||||||
? redirectedBalanceBefore.plus(balanceIncrease).plus(amountToAdd).minus(amountToSubstract)
|
? redirectedBalanceBefore.plus(amountToAdd.rayDiv(index)).minus(amountToSubstract.rayDiv(index))
|
||||||
: new BigNumber('0');
|
: new BigNumber('0');
|
||||||
};
|
};
|
||||||
const calcExpectedAverageStableBorrowRate = (
|
const calcExpectedAverageStableBorrowRate = (
|
||||||
|
@ -1174,7 +990,7 @@ export const calcExpectedStableDebtTokenBalance = (
|
||||||
userDataBeforeAction: UserReserveData,
|
userDataBeforeAction: UserReserveData,
|
||||||
currentTimestamp: BigNumber
|
currentTimestamp: BigNumber
|
||||||
) => {
|
) => {
|
||||||
const { principalStableDebt, stableBorrowRate, stableRateLastUpdated } = userDataBeforeAction;
|
const {principalStableDebt, stableBorrowRate, stableRateLastUpdated} = userDataBeforeAction;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
stableBorrowRate.eq(0) ||
|
stableBorrowRate.eq(0) ||
|
||||||
|
@ -1247,7 +1063,7 @@ const calcExpectedInterestRates = (
|
||||||
totalBorrowsVariable: BigNumber,
|
totalBorrowsVariable: BigNumber,
|
||||||
averageStableBorrowRate: BigNumber
|
averageStableBorrowRate: BigNumber
|
||||||
): BigNumber[] => {
|
): BigNumber[] => {
|
||||||
const { reservesParams } = configuration;
|
const {reservesParams} = configuration;
|
||||||
|
|
||||||
const reserveIndex = Object.keys(reservesParams).findIndex((value) => value === reserveSymbol);
|
const reserveIndex = Object.keys(reservesParams).findIndex((value) => value === reserveSymbol);
|
||||||
const [, reserveConfiguration] = (Object.entries(reservesParams) as [string, IReserveParams][])[
|
const [, reserveConfiguration] = (Object.entries(reservesParams) as [string, IReserveParams][])[
|
||||||
|
@ -1337,7 +1153,7 @@ const calcExpectedReserveNormalizedIncome = (
|
||||||
reserveData: ReserveData,
|
reserveData: ReserveData,
|
||||||
currentTimestamp: BigNumber
|
currentTimestamp: BigNumber
|
||||||
) => {
|
) => {
|
||||||
const { liquidityRate, liquidityIndex, lastUpdateTimestamp } = reserveData;
|
const {liquidityRate, liquidityIndex, lastUpdateTimestamp} = reserveData;
|
||||||
|
|
||||||
//if utilization rate is 0, nothing to compound
|
//if utilization rate is 0, nothing to compound
|
||||||
if (liquidityRate.eq('0')) {
|
if (liquidityRate.eq('0')) {
|
||||||
|
@ -1359,7 +1175,7 @@ const calcExpectedReserveNormalizedDebt = (
|
||||||
reserveData: ReserveData,
|
reserveData: ReserveData,
|
||||||
currentTimestamp: BigNumber
|
currentTimestamp: BigNumber
|
||||||
) => {
|
) => {
|
||||||
const { variableBorrowRate, variableBorrowIndex, lastUpdateTimestamp } = reserveData;
|
const {variableBorrowRate, variableBorrowIndex, lastUpdateTimestamp} = reserveData;
|
||||||
|
|
||||||
//if utilization rate is 0, nothing to compound
|
//if utilization rate is 0, nothing to compound
|
||||||
if (variableBorrowRate.eq('0')) {
|
if (variableBorrowRate.eq('0')) {
|
||||||
|
|
|
@ -61,30 +61,20 @@ export const getReserveData = async (
|
||||||
export const getUserData = async (
|
export const getUserData = async (
|
||||||
pool: LendingPool,
|
pool: LendingPool,
|
||||||
reserve: string,
|
reserve: string,
|
||||||
user: string
|
user: tEthereumAddress,
|
||||||
|
sender?: tEthereumAddress
|
||||||
): Promise<UserReserveData> => {
|
): Promise<UserReserveData> => {
|
||||||
const [userData, aTokenData] = await Promise.all([
|
const [userData, scaledATokenBalance] = await Promise.all([
|
||||||
pool.getUserReserveData(reserve, user),
|
pool.getUserReserveData(reserve, user),
|
||||||
getATokenUserData(reserve, user, pool),
|
getATokenUserData(reserve, user, pool),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [
|
|
||||||
userIndex,
|
|
||||||
redirectedBalance,
|
|
||||||
principalATokenBalance,
|
|
||||||
redirectionAddressRedirectedBalance,
|
|
||||||
interestRedirectionAddress,
|
|
||||||
] = aTokenData;
|
|
||||||
|
|
||||||
const token = await getMintableErc20(reserve);
|
const token = await getMintableErc20(reserve);
|
||||||
const walletBalance = new BigNumber((await token.balanceOf(user)).toString());
|
const walletBalance = new BigNumber((await token.balanceOf(sender || user)).toString());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
principalATokenBalance: new BigNumber(principalATokenBalance),
|
scaledATokenBalance: new BigNumber(scaledATokenBalance),
|
||||||
interestRedirectionAddress,
|
|
||||||
redirectionAddressRedirectedBalance: new BigNumber(redirectionAddressRedirectedBalance),
|
|
||||||
redirectedBalance: new BigNumber(redirectedBalance),
|
|
||||||
currentATokenUserIndex: new BigNumber(userIndex),
|
|
||||||
currentATokenBalance: new BigNumber(userData.currentATokenBalance.toString()),
|
currentATokenBalance: new BigNumber(userData.currentATokenBalance.toString()),
|
||||||
currentStableDebt: new BigNumber(userData.currentStableDebt.toString()),
|
currentStableDebt: new BigNumber(userData.currentStableDebt.toString()),
|
||||||
currentVariableDebt: new BigNumber(userData.currentVariableDebt.toString()),
|
currentVariableDebt: new BigNumber(userData.currentVariableDebt.toString()),
|
||||||
|
@ -114,28 +104,8 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo
|
||||||
const aTokenAddress: string = (await pool.getReserveTokensAddresses(reserve)).aTokenAddress;
|
const aTokenAddress: string = (await pool.getReserveTokensAddresses(reserve)).aTokenAddress;
|
||||||
|
|
||||||
const aToken = await getAToken(aTokenAddress);
|
const aToken = await getAToken(aTokenAddress);
|
||||||
const [
|
|
||||||
userIndex,
|
|
||||||
interestRedirectionAddress,
|
|
||||||
redirectedBalance,
|
|
||||||
principalTokenBalance,
|
|
||||||
] = await Promise.all([
|
|
||||||
aToken.getUserIndex(user),
|
|
||||||
aToken.getInterestRedirectionAddress(user),
|
|
||||||
aToken.getRedirectedBalance(user),
|
|
||||||
aToken.principalBalanceOf(user),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const redirectionAddressRedirectedBalance =
|
const scaledBalance = await aToken.scaledBalanceOf(user);
|
||||||
interestRedirectionAddress !== ZERO_ADDRESS
|
return scaledBalance.toString();
|
||||||
? new BigNumber((await aToken.getRedirectedBalance(interestRedirectionAddress)).toString())
|
|
||||||
: new BigNumber('0');
|
|
||||||
|
|
||||||
return [
|
|
||||||
userIndex.toString(),
|
|
||||||
redirectedBalance.toString(),
|
|
||||||
principalTokenBalance.toString(),
|
|
||||||
redirectionAddressRedirectedBalance.toString(),
|
|
||||||
interestRedirectionAddress,
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
export interface UserReserveData {
|
export interface UserReserveData {
|
||||||
principalATokenBalance: BigNumber;
|
scaledATokenBalance: BigNumber;
|
||||||
currentATokenBalance: BigNumber;
|
currentATokenBalance: BigNumber;
|
||||||
currentATokenUserIndex: BigNumber;
|
|
||||||
interestRedirectionAddress: string;
|
|
||||||
redirectionAddressRedirectedBalance: BigNumber;
|
|
||||||
redirectedBalance: BigNumber;
|
|
||||||
currentStableDebt: BigNumber;
|
currentStableDebt: BigNumber;
|
||||||
currentVariableDebt: BigNumber;
|
currentVariableDebt: BigNumber;
|
||||||
principalStableDebt: BigNumber;
|
principalStableDebt: BigNumber;
|
||||||
|
|
|
@ -32,7 +32,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
|
|
||||||
//user 1 deposits 1000 DAI
|
//user 1 deposits 1000 DAI
|
||||||
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
await pool.connect(depositor.signer).deposit(dai.address, amountDAItoDeposit, '0');
|
await pool
|
||||||
|
.connect(depositor.signer)
|
||||||
|
.deposit(dai.address, amountDAItoDeposit, depositor.address, '0');
|
||||||
|
|
||||||
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
||||||
|
|
||||||
|
@ -43,7 +45,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
//user 2 deposits 1 WETH
|
//user 2 deposits 1 WETH
|
||||||
await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0');
|
await pool
|
||||||
|
.connect(borrower.signer)
|
||||||
|
.deposit(weth.address, amountETHtoDeposit, borrower.address, '0');
|
||||||
|
|
||||||
//user 2 borrows
|
//user 2 borrows
|
||||||
const userGlobalData = await pool.getUserAccountData(borrower.address);
|
const userGlobalData = await pool.getUserAccountData(borrower.address);
|
||||||
|
@ -192,7 +196,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
//the liquidity index of the principal reserve needs to be bigger than the index before
|
//the liquidity index of the principal reserve needs to be bigger than the index before
|
||||||
expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt(
|
expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte(
|
||||||
daiReserveDataBefore.liquidityIndex.toString(),
|
daiReserveDataBefore.liquidityIndex.toString(),
|
||||||
'Invalid liquidity index'
|
'Invalid liquidity index'
|
||||||
);
|
);
|
||||||
|
@ -213,6 +217,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
const {users, pool, usdc, oracle, weth} = testEnv;
|
const {users, pool, usdc, oracle, weth} = testEnv;
|
||||||
const depositor = users[3];
|
const depositor = users[3];
|
||||||
const borrower = users[4];
|
const borrower = users[4];
|
||||||
|
|
||||||
//mints USDC to depositor
|
//mints USDC to depositor
|
||||||
await usdc
|
await usdc
|
||||||
.connect(depositor.signer)
|
.connect(depositor.signer)
|
||||||
|
@ -224,7 +229,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
//user 3 deposits 1000 USDC
|
//user 3 deposits 1000 USDC
|
||||||
const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
|
const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
|
||||||
|
|
||||||
await pool.connect(depositor.signer).deposit(usdc.address, amountUSDCtoDeposit, '0');
|
await pool
|
||||||
|
.connect(depositor.signer)
|
||||||
|
.deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0');
|
||||||
|
|
||||||
//user 4 deposits 1 ETH
|
//user 4 deposits 1 ETH
|
||||||
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
||||||
|
@ -235,7 +242,9 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
//approve protocol to access borrower wallet
|
//approve protocol to access borrower wallet
|
||||||
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0');
|
await pool
|
||||||
|
.connect(borrower.signer)
|
||||||
|
.deposit(weth.address, amountETHtoDeposit, borrower.address, '0');
|
||||||
|
|
||||||
//user 4 borrows
|
//user 4 borrows
|
||||||
const userGlobalData = await pool.getUserAccountData(borrower.address);
|
const userGlobalData = await pool.getUserAccountData(borrower.address);
|
||||||
|
@ -246,7 +255,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
usdc.address,
|
usdc.address,
|
||||||
new BigNumber(userGlobalData.availableBorrowsETH.toString())
|
new BigNumber(userGlobalData.availableBorrowsETH.toString())
|
||||||
.div(usdcPrice.toString())
|
.div(usdcPrice.toString())
|
||||||
.multipliedBy(0.95)
|
.multipliedBy(0.9502)
|
||||||
.toFixed(0)
|
.toFixed(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -274,7 +283,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
const ethReserveDataBefore = await pool.getReserveData(weth.address);
|
const ethReserveDataBefore = await pool.getReserveData(weth.address);
|
||||||
|
|
||||||
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString())
|
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString())
|
||||||
.div(2)
|
.multipliedBy(0.5)
|
||||||
.toFixed(0);
|
.toFixed(0);
|
||||||
|
|
||||||
await pool.liquidationCall(
|
await pool.liquidationCall(
|
||||||
|
@ -328,7 +337,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
//the liquidity index of the principal reserve needs to be bigger than the index before
|
//the liquidity index of the principal reserve needs to be bigger than the index before
|
||||||
expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt(
|
expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte(
|
||||||
usdcReserveDataBefore.liquidityIndex.toString(),
|
usdcReserveDataBefore.liquidityIndex.toString(),
|
||||||
'Invalid liquidity index'
|
'Invalid liquidity index'
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import {BRE} from '../helpers/misc-utils';
|
import {BRE, increaseTime} from '../helpers/misc-utils';
|
||||||
import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants';
|
import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants';
|
||||||
import {convertToCurrencyDecimals} from '../helpers/contracts-helpers';
|
import {convertToCurrencyDecimals} from '../helpers/contracts-helpers';
|
||||||
import {makeSuite} from './helpers/make-suite';
|
import {makeSuite} from './helpers/make-suite';
|
||||||
|
@ -15,6 +15,14 @@ const {expect} = chai;
|
||||||
makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', (testEnv) => {
|
makeSuite('LendingPool liquidation - liquidator receiving the underlying asset', (testEnv) => {
|
||||||
const {INVALID_HF} = ProtocolErrors;
|
const {INVALID_HF} = ProtocolErrors;
|
||||||
|
|
||||||
|
before('Before LendingPool liquidation: set config', () => {
|
||||||
|
BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN});
|
||||||
|
});
|
||||||
|
|
||||||
|
after('After LendingPool liquidation: reset config', () => {
|
||||||
|
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
||||||
|
});
|
||||||
|
|
||||||
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];
|
||||||
|
@ -29,7 +37,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
//user 1 deposits 1000 DAI
|
//user 1 deposits 1000 DAI
|
||||||
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000');
|
||||||
|
|
||||||
await pool.connect(depositor.signer).deposit(dai.address, amountDAItoDeposit, '0');
|
await pool
|
||||||
|
.connect(depositor.signer)
|
||||||
|
.deposit(dai.address, amountDAItoDeposit, depositor.address, '0');
|
||||||
//user 2 deposits 1 ETH
|
//user 2 deposits 1 ETH
|
||||||
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
||||||
|
|
||||||
|
@ -39,7 +49,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
//approve protocol to access the borrower wallet
|
//approve protocol to access the borrower wallet
|
||||||
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0');
|
await pool
|
||||||
|
.connect(borrower.signer)
|
||||||
|
.deposit(weth.address, amountETHtoDeposit, borrower.address, '0');
|
||||||
|
|
||||||
//user 2 borrows
|
//user 2 borrows
|
||||||
|
|
||||||
|
@ -103,6 +115,8 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
|
|
||||||
const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2).toFixed(0);
|
const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2).toFixed(0);
|
||||||
|
|
||||||
|
await increaseTime(100);
|
||||||
|
|
||||||
const tx = await pool
|
const tx = await pool
|
||||||
.connect(liquidator.signer)
|
.connect(liquidator.signer)
|
||||||
.liquidationCall(weth.address, dai.address, borrower.address, amountToLiquidate, false);
|
.liquidationCall(weth.address, dai.address, borrower.address, amountToLiquidate, false);
|
||||||
|
@ -150,7 +164,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
);
|
);
|
||||||
|
|
||||||
//the liquidity index of the principal reserve needs to be bigger than the index before
|
//the liquidity index of the principal reserve needs to be bigger than the index before
|
||||||
expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt(
|
expect(daiReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte(
|
||||||
daiReserveDataBefore.liquidityIndex.toString(),
|
daiReserveDataBefore.liquidityIndex.toString(),
|
||||||
'Invalid liquidity index'
|
'Invalid liquidity index'
|
||||||
);
|
);
|
||||||
|
@ -194,7 +208,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
//depositor deposits 1000 USDC
|
//depositor deposits 1000 USDC
|
||||||
const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
|
const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000');
|
||||||
|
|
||||||
await pool.connect(depositor.signer).deposit(usdc.address, amountUSDCtoDeposit, '0');
|
await pool
|
||||||
|
.connect(depositor.signer)
|
||||||
|
.deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0');
|
||||||
|
|
||||||
//borrower deposits 1 ETH
|
//borrower deposits 1 ETH
|
||||||
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1');
|
||||||
|
@ -205,7 +221,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
//approve protocol to access the borrower wallet
|
//approve protocol to access the borrower wallet
|
||||||
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
await pool.connect(borrower.signer).deposit(weth.address, amountETHtoDeposit, '0');
|
await pool
|
||||||
|
.connect(borrower.signer)
|
||||||
|
.deposit(weth.address, amountETHtoDeposit, borrower.address, '0');
|
||||||
|
|
||||||
//borrower borrows
|
//borrower borrows
|
||||||
const userGlobalData = await pool.getUserAccountData(borrower.address);
|
const userGlobalData = await pool.getUserAccountData(borrower.address);
|
||||||
|
@ -216,7 +234,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
usdc.address,
|
usdc.address,
|
||||||
new BigNumber(userGlobalData.availableBorrowsETH.toString())
|
new BigNumber(userGlobalData.availableBorrowsETH.toString())
|
||||||
.div(usdcPrice.toString())
|
.div(usdcPrice.toString())
|
||||||
.multipliedBy(0.95)
|
.multipliedBy(0.9502)
|
||||||
.toFixed(0)
|
.toFixed(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -244,10 +262,11 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
const usdcReserveDataBefore = await pool.getReserveData(usdc.address);
|
const usdcReserveDataBefore = await pool.getReserveData(usdc.address);
|
||||||
const ethReserveDataBefore = await pool.getReserveData(weth.address);
|
const ethReserveDataBefore = await pool.getReserveData(weth.address);
|
||||||
|
|
||||||
const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString())
|
const amountToLiquidate = BRE.ethers.BigNumber.from(
|
||||||
|
userReserveDataBefore.currentStableDebt.toString()
|
||||||
|
)
|
||||||
.div(2)
|
.div(2)
|
||||||
.decimalPlaces(0, BigNumber.ROUND_DOWN)
|
.toString();
|
||||||
.toFixed(0);
|
|
||||||
|
|
||||||
await pool
|
await pool
|
||||||
.connect(liquidator.signer)
|
.connect(liquidator.signer)
|
||||||
|
@ -292,7 +311,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
);
|
);
|
||||||
|
|
||||||
//the liquidity index of the principal reserve needs to be bigger than the index before
|
//the liquidity index of the principal reserve needs to be bigger than the index before
|
||||||
expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gt(
|
expect(usdcReserveDataAfter.liquidityIndex.toString()).to.be.bignumber.gte(
|
||||||
usdcReserveDataBefore.liquidityIndex.toString(),
|
usdcReserveDataBefore.liquidityIndex.toString(),
|
||||||
'Invalid liquidity index'
|
'Invalid liquidity index'
|
||||||
);
|
);
|
||||||
|
@ -334,7 +353,9 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
||||||
//borrower deposits 1000 LEND
|
//borrower deposits 1000 LEND
|
||||||
const amountLENDtoDeposit = await convertToCurrencyDecimals(lend.address, '1000');
|
const amountLENDtoDeposit = await convertToCurrencyDecimals(lend.address, '1000');
|
||||||
|
|
||||||
await pool.connect(borrower.signer).deposit(lend.address, amountLENDtoDeposit, '0');
|
await pool
|
||||||
|
.connect(borrower.signer)
|
||||||
|
.deposit(lend.address, amountLENDtoDeposit, borrower.address, '0');
|
||||||
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
const usdcPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
//drops HF below 1
|
//drops HF below 1
|
||||||
|
|
640
test/repay-with-collateral.spec.ts
Normal file
640
test/repay-with-collateral.spec.ts
Normal file
|
@ -0,0 +1,640 @@
|
||||||
|
import {TestEnv, makeSuite} from './helpers/make-suite';
|
||||||
|
import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants';
|
||||||
|
import {ethers} from 'ethers';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import {
|
||||||
|
calcExpectedVariableDebtTokenBalance,
|
||||||
|
calcExpectedStableDebtTokenBalance,
|
||||||
|
} from './helpers/utils/calculations';
|
||||||
|
import {getContractsData} from './helpers/actions';
|
||||||
|
import {waitForTx} from './__setup.spec';
|
||||||
|
import {timeLatest} from '../helpers/misc-utils';
|
||||||
|
import {tEthereumAddress} from '../helpers/types';
|
||||||
|
import {parse} from 'path';
|
||||||
|
|
||||||
|
const {expect} = require('chai');
|
||||||
|
const {parseUnits, parseEther} = ethers.utils;
|
||||||
|
|
||||||
|
export const expectRepayWithCollateralEvent = (
|
||||||
|
events: ethers.Event[],
|
||||||
|
pool: tEthereumAddress,
|
||||||
|
collateral: tEthereumAddress,
|
||||||
|
borrowing: tEthereumAddress,
|
||||||
|
user: tEthereumAddress
|
||||||
|
) => {
|
||||||
|
if (!events || events.length < 16) {
|
||||||
|
expect(false, 'INVALID_EVENTS_LENGTH_ON_REPAY_COLLATERAL');
|
||||||
|
}
|
||||||
|
|
||||||
|
const repayWithCollateralEvent = events[15];
|
||||||
|
|
||||||
|
expect(repayWithCollateralEvent.address).to.be.equal(pool);
|
||||||
|
expect(`0x${repayWithCollateralEvent.topics[1].slice(26)}`.toLowerCase()).to.be.equal(
|
||||||
|
collateral.toLowerCase()
|
||||||
|
);
|
||||||
|
expect(`0x${repayWithCollateralEvent.topics[2].slice(26)}`).to.be.equal(borrowing.toLowerCase());
|
||||||
|
expect(`0x${repayWithCollateralEvent.topics[3].slice(26)}`.toLowerCase()).to.be.equal(
|
||||||
|
user.toLowerCase()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||||
|
it('User 1 provides some liquidity for others to borrow', async () => {
|
||||||
|
const {pool, weth, dai, usdc, deployer} = testEnv;
|
||||||
|
|
||||||
|
await weth.mint(parseEther('200'));
|
||||||
|
await weth.approve(pool.address, parseEther('200'));
|
||||||
|
await pool.deposit(weth.address, parseEther('200'), deployer.address, 0);
|
||||||
|
await dai.mint(parseEther('20000'));
|
||||||
|
await dai.approve(pool.address, parseEther('20000'));
|
||||||
|
await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0);
|
||||||
|
await usdc.mint(parseEther('20000'));
|
||||||
|
await usdc.approve(pool.address, parseEther('20000'));
|
||||||
|
await pool.deposit(usdc.address, parseEther('20000'), deployer.address, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 2 deposit WETH and borrows DAI at Variable', async () => {
|
||||||
|
const {pool, weth, dai, users} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
const amountToDeposit = ethers.utils.parseEther('1');
|
||||||
|
const amountToBorrow = ethers.utils.parseEther('20');
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDeposit);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
|
||||||
|
const amountToRepay = parseEther('10');
|
||||||
|
|
||||||
|
await waitForTx(await mockSwapAdapter.setTryReentrancy(true));
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('53');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 2 tries to repay his DAI Variable loan using his WETH collateral. First half the amount, after that, the rest', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[1];
|
||||||
|
|
||||||
|
const amountToRepay = parseEther('10');
|
||||||
|
|
||||||
|
await waitForTx(await mockSwapAdapter.setTryReentrancy(false));
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(dai.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
daiReserveDataBefore,
|
||||||
|
daiUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(daiUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(daiUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 3 deposits WETH and borrows USDC at Variable', async () => {
|
||||||
|
const {pool, weth, usdc, users} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
const amountToDeposit = parseEther('10');
|
||||||
|
const amountToBorrow = parseUnits('40', 6);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDeposit);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||||
|
|
||||||
|
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
|
||||||
|
|
||||||
|
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => {
|
||||||
|
const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
|
||||||
|
const amountToRepay = parseUnits('10', 6);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(usdc.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString(),
|
||||||
|
'INVALID_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
),
|
||||||
|
'INVALID_COLLATERAL_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Revert expected. User 3 tries to repay with his collateral a currency he havent borrow', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
|
||||||
|
const amountToRepay = parseUnits('10', 6);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
).to.be.revertedWith('40');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 3 tries to repay with his collateral all his variable debt and part of the stable', async () => {
|
||||||
|
const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[2];
|
||||||
|
|
||||||
|
const amountToDeposit = parseEther('20');
|
||||||
|
const amountToBorrowStable = parseUnits('40', 6);
|
||||||
|
const amountToBorrowVariable = parseUnits('40', 6);
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDeposit);
|
||||||
|
|
||||||
|
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, amountToBorrowStable, 1, 0);
|
||||||
|
|
||||||
|
const amountToRepay = parseUnits('80', 6);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
const txReceipt = await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(usdc.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance(
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentStableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.gte(0)
|
||||||
|
? new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString()
|
||||||
|
: '0',
|
||||||
|
'INVALID_VARIABLE_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
const stableDebtRepaid = new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.abs();
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentStableDebt).to.be.bignumber.equal(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentStableDebt)
|
||||||
|
.minus(stableDebtRepaid)
|
||||||
|
.plus(expectedStableDebtIncrease)
|
||||||
|
.gte(0)
|
||||||
|
? new BigNumber(usdcUserDataBefore.currentStableDebt)
|
||||||
|
.minus(stableDebtRepaid)
|
||||||
|
.plus(expectedStableDebtIncrease)
|
||||||
|
.toString()
|
||||||
|
: '0',
|
||||||
|
'INVALID_STABLE_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
),
|
||||||
|
'INVALID_COLLATERAL_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
const eventsEmitted = txReceipt.events || [];
|
||||||
|
|
||||||
|
expectRepayWithCollateralEvent(
|
||||||
|
eventsEmitted,
|
||||||
|
pool.address,
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 4 tries to repay a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => {
|
||||||
|
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[3];
|
||||||
|
|
||||||
|
const amountToDepositWeth = parseEther('0.1');
|
||||||
|
const amountToDepositDAI = parseEther('500');
|
||||||
|
const amountToBorrowVariable = parseEther('80');
|
||||||
|
|
||||||
|
await weth.connect(user.signer).mint(amountToDepositWeth);
|
||||||
|
await dai.connect(user.signer).mint(amountToDepositDAI);
|
||||||
|
await weth.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(weth.address, amountToDepositWeth, 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);
|
||||||
|
|
||||||
|
const amountToRepay = parseEther('80');
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
await waitForTx(
|
||||||
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
dai.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(dai.address);
|
||||||
|
|
||||||
|
const collateralConfig = await pool.getReserveConfigurationData(weth.address);
|
||||||
|
|
||||||
|
const collateralDecimals = collateralConfig.decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(dai.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const collateralLiquidationBonus = collateralConfig.liquidationBonus.toString();
|
||||||
|
|
||||||
|
const expectedDebtCovered = new BigNumber(collateralPrice.toString())
|
||||||
|
.times(new BigNumber(wethUserDataBefore.currentATokenBalance.toString()))
|
||||||
|
.times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
)
|
||||||
|
.div(new BigNumber(collateralLiquidationBonus).div(10000).toString())
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
daiReserveDataBefore,
|
||||||
|
daiUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(daiUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.equal(
|
||||||
|
new BigNumber(daiUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(expectedDebtCovered.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease),
|
||||||
|
'INVALID_VARIABLE_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 5 deposits WETH and DAI, then borrows USDC at Variable, then disables WETH as collateral', async () => {
|
||||||
|
const {pool, weth, dai, usdc, users} = testEnv;
|
||||||
|
const user = users[4];
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('User 5 tries to repay his USDC loan by swapping his WETH collateral, should not revert even with WETH collateral disabled', async () => {
|
||||||
|
const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv;
|
||||||
|
const user = users[4];
|
||||||
|
|
||||||
|
const amountToRepay = parseUnits('65', 6);
|
||||||
|
|
||||||
|
// Disable WETH as collateral
|
||||||
|
await pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false);
|
||||||
|
|
||||||
|
const {userData: wethUserDataBefore} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
reserveData: usdcReserveDataBefore,
|
||||||
|
userData: usdcUserDataBefore,
|
||||||
|
} = await getContractsData(usdc.address, user.address, testEnv);
|
||||||
|
|
||||||
|
expect(wethUserDataBefore.usageAsCollateralEnabled).to.be.false;
|
||||||
|
|
||||||
|
// User 5 should be able to liquidate himself with WETH, even if is disabled
|
||||||
|
await mockSwapAdapter.setAmountToReturn(amountToRepay);
|
||||||
|
expect(
|
||||||
|
await pool
|
||||||
|
.connect(user.signer)
|
||||||
|
.repayWithCollateral(
|
||||||
|
weth.address,
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
amountToRepay,
|
||||||
|
mockSwapAdapter.address,
|
||||||
|
'0x'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const repayWithCollateralTimestamp = await timeLatest();
|
||||||
|
|
||||||
|
const {userData: wethUserDataAfter} = await getContractsData(
|
||||||
|
weth.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const {userData: usdcUserDataAfter} = await getContractsData(
|
||||||
|
usdc.address,
|
||||||
|
user.address,
|
||||||
|
testEnv
|
||||||
|
);
|
||||||
|
|
||||||
|
const collateralPrice = await oracle.getAssetPrice(weth.address);
|
||||||
|
const principalPrice = await oracle.getAssetPrice(usdc.address);
|
||||||
|
|
||||||
|
const collateralDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(weth.address)
|
||||||
|
).decimals.toString();
|
||||||
|
const principalDecimals = (
|
||||||
|
await pool.getReserveConfigurationData(usdc.address)
|
||||||
|
).decimals.toString();
|
||||||
|
|
||||||
|
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
|
||||||
|
.times(new BigNumber(amountToRepay.toString()).times(105))
|
||||||
|
.times(new BigNumber(10).pow(collateralDecimals))
|
||||||
|
.div(
|
||||||
|
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
|
||||||
|
)
|
||||||
|
.div(100)
|
||||||
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
||||||
|
|
||||||
|
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
|
||||||
|
usdcReserveDataBefore,
|
||||||
|
usdcUserDataBefore,
|
||||||
|
new BigNumber(repayWithCollateralTimestamp)
|
||||||
|
).minus(usdcUserDataBefore.currentVariableDebt);
|
||||||
|
|
||||||
|
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
|
||||||
|
new BigNumber(usdcUserDataBefore.currentVariableDebt)
|
||||||
|
.minus(amountToRepay.toString())
|
||||||
|
.plus(expectedVariableDebtIncrease)
|
||||||
|
.toString(),
|
||||||
|
'INVALID_DEBT_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
|
||||||
|
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
|
||||||
|
expectedCollateralLiquidated.toString()
|
||||||
|
),
|
||||||
|
'INVALID_COLLATERAL_POSITION'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,8 +8,6 @@ import {getReservesConfigByPool} from '../helpers/constants';
|
||||||
import {AavePools, iAavePoolAssets, IReserveParams} from '../helpers/types';
|
import {AavePools, iAavePoolAssets, IReserveParams} from '../helpers/types';
|
||||||
import {executeStory} from './helpers/scenario-engine';
|
import {executeStory} from './helpers/scenario-engine';
|
||||||
|
|
||||||
BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN});
|
|
||||||
|
|
||||||
const scenarioFolder = './test/helpers/scenarios/';
|
const scenarioFolder = './test/helpers/scenarios/';
|
||||||
|
|
||||||
const selectedScenarios: string[] = [''];
|
const selectedScenarios: string[] = [''];
|
||||||
|
@ -21,12 +19,19 @@ fs.readdirSync(scenarioFolder).forEach((file) => {
|
||||||
|
|
||||||
makeSuite(scenario.title, async (testEnv) => {
|
makeSuite(scenario.title, async (testEnv) => {
|
||||||
before('Initializing configuration', async () => {
|
before('Initializing configuration', async () => {
|
||||||
|
// Sets BigNumber for this suite, instead of globally
|
||||||
|
BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN});
|
||||||
|
|
||||||
actionsConfiguration.skipIntegrityCheck = false; //set this to true to execute solidity-coverage
|
actionsConfiguration.skipIntegrityCheck = false; //set this to true to execute solidity-coverage
|
||||||
|
|
||||||
calculationsConfiguration.reservesParams = <iAavePoolAssets<IReserveParams>>(
|
calculationsConfiguration.reservesParams = <iAavePoolAssets<IReserveParams>>(
|
||||||
getReservesConfigByPool(AavePools.proto)
|
getReservesConfigByPool(AavePools.proto)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
after('Reset', () => {
|
||||||
|
// Reset BigNumber
|
||||||
|
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
||||||
|
});
|
||||||
|
|
||||||
for (const story of scenario.stories) {
|
for (const story of scenario.stories) {
|
||||||
it(story.description, async () => {
|
it(story.description, async () => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user