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
c6a3588792
|
@ -1,8 +1,7 @@
|
|||
import {usePlugin, BuidlerConfig} from '@nomiclabs/buidler/config';
|
||||
import {usePlugin} from '@nomiclabs/buidler/config';
|
||||
// @ts-ignore
|
||||
import {accounts} from './test-wallets.js';
|
||||
import {eEthereumNetwork} from './helpers/types';
|
||||
import { BUIDLEREVM_CHAINID, COVERAGE_CHAINID } from './helpers/constants';
|
||||
|
||||
usePlugin('@nomiclabs/buidler-ethers');
|
||||
usePlugin('buidler-typechain');
|
||||
|
@ -11,6 +10,8 @@ usePlugin('@nomiclabs/buidler-waffle');
|
|||
usePlugin('@nomiclabs/buidler-etherscan');
|
||||
//usePlugin('buidler-gas-reporter');
|
||||
|
||||
const BUIDLEREVM_CHAINID = 31337;
|
||||
const COVERAGE_CHAINID = 1337;
|
||||
const DEFAULT_BLOCK_GAS_LIMIT = 10000000;
|
||||
const DEFAULT_GAS_PRICE = 10;
|
||||
const HARDFORK = 'istanbul';
|
||||
|
|
|
@ -290,6 +290,22 @@ interface ILendingPool {
|
|||
uint16 referralCode
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||
* - It's not possible to release one asset to swap for the same
|
||||
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||
* @param fromAsset Asset to swap from
|
||||
* @param toAsset Asset to swap to
|
||||
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||
**/
|
||||
function swapLiquidity(
|
||||
address receiverAddress,
|
||||
address fromAsset,
|
||||
address toAsset,
|
||||
uint256 amountToSwap,
|
||||
bytes calldata params
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @dev accessory functions to fetch data from the core contract
|
||||
**/
|
||||
|
|
|
@ -18,4 +18,4 @@ interface ISwapAdapter {
|
|||
address fundsDestination,
|
||||
bytes calldata params
|
||||
) external;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol'
|
|||
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
|
||||
import {DebtTokenBase} from '../tokenization/base/DebtTokenBase.sol';
|
||||
import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
|
||||
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
||||
import {LendingPoolLiquidationManager} from './LendingPoolLiquidationManager.sol';
|
||||
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
||||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||
|
@ -62,12 +63,11 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
/**
|
||||
* @dev only lending pools configurator can use functions affected by this modifier
|
||||
**/
|
||||
modifier onlyLendingPoolConfigurator {
|
||||
function onlyLendingPoolConfigurator() internal view {
|
||||
require(
|
||||
_addressesProvider.getLendingPoolConfigurator() == msg.sender,
|
||||
Errors.CALLER_NOT_LENDING_POOL_CONFIGURATOR
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
function getRevision() internal override pure returns (uint256) {
|
||||
|
@ -420,7 +420,6 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
uint256 purchaseAmount,
|
||||
bool receiveAToken
|
||||
) external override {
|
||||
|
||||
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||
|
||||
//solium-disable-next-line
|
||||
|
@ -570,6 +569,43 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||
* - It's not possible to release one asset to swap for the same
|
||||
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||
* @param fromAsset Asset to swap from
|
||||
* @param toAsset Asset to swap to
|
||||
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||
**/
|
||||
function swapLiquidity(
|
||||
address receiverAddress,
|
||||
address fromAsset,
|
||||
address toAsset,
|
||||
uint256 amountToSwap,
|
||||
bytes calldata params
|
||||
) external override {
|
||||
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||
|
||||
//solium-disable-next-line
|
||||
(bool success, bytes memory result) = liquidationManager.delegatecall(
|
||||
abi.encodeWithSignature(
|
||||
'swapLiquidity(address,address,address,uint256,bytes)',
|
||||
receiverAddress,
|
||||
fromAsset,
|
||||
toAsset,
|
||||
amountToSwap,
|
||||
params
|
||||
)
|
||||
);
|
||||
require(success, Errors.FAILED_COLLATERAL_SWAP);
|
||||
|
||||
(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
|
||||
|
||||
if (returnCode != 0) {
|
||||
revert(string(abi.encodePacked(returnMessage)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev accessory functions to fetch data from the core contract
|
||||
**/
|
||||
|
@ -744,7 +780,8 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
address stableDebtAddress,
|
||||
address variableDebtAddress,
|
||||
address interestRateStrategyAddress
|
||||
) external override onlyLendingPoolConfigurator {
|
||||
) external override {
|
||||
onlyLendingPoolConfigurator();
|
||||
_reserves[asset].init(
|
||||
aTokenAddress,
|
||||
stableDebtAddress,
|
||||
|
@ -763,16 +800,13 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress)
|
||||
external
|
||||
override
|
||||
onlyLendingPoolConfigurator
|
||||
{
|
||||
onlyLendingPoolConfigurator();
|
||||
_reserves[asset].interestRateStrategyAddress = rateStrategyAddress;
|
||||
}
|
||||
|
||||
function setConfiguration(address asset, uint256 configuration)
|
||||
external
|
||||
override
|
||||
onlyLendingPoolConfigurator
|
||||
{
|
||||
function setConfiguration(address asset, uint256 configuration) external override {
|
||||
onlyLendingPoolConfigurator();
|
||||
_reserves[asset].configuration.data = configuration;
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,26 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
string errorMsg;
|
||||
}
|
||||
|
||||
struct SwapLiquidityLocalVars {
|
||||
uint256 healthFactor;
|
||||
uint256 amountToReceive;
|
||||
uint256 userBalanceBefore;
|
||||
IAToken fromReserveAToken;
|
||||
IAToken toReserveAToken;
|
||||
uint256 errorCode;
|
||||
string errorMsg;
|
||||
}
|
||||
|
||||
struct AvailableCollateralToLiquidateLocalVars {
|
||||
uint256 userCompoundedBorrowBalance;
|
||||
uint256 liquidationBonus;
|
||||
uint256 collateralPrice;
|
||||
uint256 principalCurrencyPrice;
|
||||
uint256 maxAmountCollateralToLiquidate;
|
||||
uint256 principalDecimals;
|
||||
uint256 collateralDecimals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev as the contract extends the VersionedInitializable contract to match the state
|
||||
* of the LendingPool contract, the getRevision() function is needed.
|
||||
|
@ -390,7 +410,12 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
|
||||
//updating debt reserve
|
||||
debtReserve.updateState();
|
||||
debtReserve.updateInterestRates(principal, vars.principalAToken, vars.actualAmountToLiquidate, 0);
|
||||
debtReserve.updateInterestRates(
|
||||
principal,
|
||||
vars.principalAToken,
|
||||
vars.actualAmountToLiquidate,
|
||||
0
|
||||
);
|
||||
IERC20(principal).transferFrom(receiver, vars.principalAToken, vars.actualAmountToLiquidate);
|
||||
|
||||
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
||||
|
@ -431,14 +456,97 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
struct AvailableCollateralToLiquidateLocalVars {
|
||||
uint256 userCompoundedBorrowBalance;
|
||||
uint256 liquidationBonus;
|
||||
uint256 collateralPrice;
|
||||
uint256 principalCurrencyPrice;
|
||||
uint256 maxAmountCollateralToLiquidate;
|
||||
uint256 principalDecimals;
|
||||
uint256 collateralDecimals;
|
||||
/**
|
||||
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
||||
* - It's not possible to release one asset to swap for the same
|
||||
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
||||
* @param fromAsset Asset to swap from
|
||||
* @param toAsset Asset to swap to
|
||||
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
||||
**/
|
||||
function swapLiquidity(
|
||||
address receiverAddress,
|
||||
address fromAsset,
|
||||
address toAsset,
|
||||
uint256 amountToSwap,
|
||||
bytes calldata params
|
||||
) external returns (uint256, string memory) {
|
||||
ReserveLogic.ReserveData storage fromReserve = reserves[fromAsset];
|
||||
ReserveLogic.ReserveData storage toReserve = reserves[toAsset];
|
||||
|
||||
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
|
||||
SwapLiquidityLocalVars memory vars;
|
||||
|
||||
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateSwapLiquidity(
|
||||
fromReserve,
|
||||
toReserve,
|
||||
fromAsset,
|
||||
toAsset
|
||||
);
|
||||
|
||||
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||
return (vars.errorCode, vars.errorMsg);
|
||||
}
|
||||
|
||||
vars.fromReserveAToken = IAToken(fromReserve.aTokenAddress);
|
||||
vars.toReserveAToken = IAToken(toReserve.aTokenAddress);
|
||||
|
||||
fromReserve.updateCumulativeIndexesAndTimestamp();
|
||||
toReserve.updateCumulativeIndexesAndTimestamp();
|
||||
|
||||
if (vars.fromReserveAToken.balanceOf(msg.sender) == amountToSwap) {
|
||||
usersConfig[msg.sender].setUsingAsCollateral(fromReserve.id, false);
|
||||
}
|
||||
|
||||
fromReserve.updateInterestRates(fromAsset, address(vars.fromReserveAToken), 0, amountToSwap);
|
||||
|
||||
vars.fromReserveAToken.burn(
|
||||
msg.sender,
|
||||
receiverAddress,
|
||||
amountToSwap,
|
||||
fromReserve.liquidityIndex
|
||||
);
|
||||
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||
ISwapAdapter(receiverAddress).executeOperation(
|
||||
fromAsset,
|
||||
toAsset,
|
||||
amountToSwap,
|
||||
address(this),
|
||||
params
|
||||
);
|
||||
|
||||
vars.amountToReceive = IERC20(toAsset).balanceOf(receiverAddress);
|
||||
if (vars.amountToReceive != 0) {
|
||||
IERC20(toAsset).transferFrom(
|
||||
receiverAddress,
|
||||
address(vars.toReserveAToken),
|
||||
vars.amountToReceive
|
||||
);
|
||||
vars.toReserveAToken.mint(msg.sender, vars.amountToReceive, toReserve.liquidityIndex);
|
||||
toReserve.updateInterestRates(
|
||||
toAsset,
|
||||
address(vars.toReserveAToken),
|
||||
vars.amountToReceive,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
|
||||
msg.sender,
|
||||
reserves,
|
||||
usersConfig[msg.sender],
|
||||
reservesList,
|
||||
addressesProvider.getPriceOracle()
|
||||
);
|
||||
|
||||
if (vars.healthFactor < GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,8 +39,10 @@ library Errors {
|
|||
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 BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54'; // User borrows on behalf, but allowance are too small
|
||||
string public constant REENTRANCY_NOT_ALLOWED = '52';
|
||||
string public constant REENTRANCY_NOT_ALLOWED = '57';
|
||||
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';
|
||||
string public constant FAILED_COLLATERAL_SWAP = '55';
|
||||
string public constant INVALID_EQUAL_ASSETS_TO_SWAP = '56';
|
||||
|
||||
// require error messages - aToken
|
||||
string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool'
|
||||
|
@ -82,6 +84,8 @@ library Errors {
|
|||
CURRRENCY_NOT_BORROWED,
|
||||
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
||||
NOT_ENOUGH_LIQUIDITY,
|
||||
NO_ACTIVE_RESERVE
|
||||
NO_ACTIVE_RESERVE,
|
||||
HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD,
|
||||
INVALID_EQUAL_ASSETS_TO_SWAP
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ library ReserveLogic {
|
|||
ReserveData storage reserve,
|
||||
uint256 totalLiquidity,
|
||||
uint256 amount
|
||||
) internal {
|
||||
) external {
|
||||
uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay());
|
||||
|
||||
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
|
||||
|
@ -232,7 +232,7 @@ library ReserveLogic {
|
|||
address aTokenAddress,
|
||||
uint256 liquidityAdded,
|
||||
uint256 liquidityTaken
|
||||
) internal {
|
||||
) external {
|
||||
UpdateInterestRatesLocalVars memory vars;
|
||||
|
||||
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
|
||||
|
|
|
@ -347,12 +347,11 @@ library ValidationLogic {
|
|||
uint256 userHealthFactor,
|
||||
uint256 userStableDebt,
|
||||
uint256 userVariableDebt
|
||||
) internal view returns(uint256, string memory) {
|
||||
if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE),
|
||||
Errors.NO_ACTIVE_RESERVE
|
||||
);
|
||||
) internal view returns (uint256, string memory) {
|
||||
if (
|
||||
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
|
||||
) {
|
||||
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||
}
|
||||
|
||||
if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||
|
@ -362,8 +361,7 @@ library ValidationLogic {
|
|||
);
|
||||
}
|
||||
|
||||
bool isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
|
@ -402,12 +400,11 @@ library ValidationLogic {
|
|||
uint256 userHealthFactor,
|
||||
uint256 userStableDebt,
|
||||
uint256 userVariableDebt
|
||||
) internal view returns(uint256, string memory) {
|
||||
if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE),
|
||||
Errors.NO_ACTIVE_RESERVE
|
||||
);
|
||||
) internal view returns (uint256, string memory) {
|
||||
if (
|
||||
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
|
||||
) {
|
||||
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -420,8 +417,7 @@ library ValidationLogic {
|
|||
}
|
||||
|
||||
if (msg.sender != user) {
|
||||
bool isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
|
@ -442,4 +438,31 @@ library ValidationLogic {
|
|||
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validates the swapLiquidity() action
|
||||
* @param fromReserve The reserve data of the asset to swap from
|
||||
* @param toReserve The reserve data of the asset to swap to
|
||||
* @param fromAsset Address of the asset to swap from
|
||||
* @param toAsset Address of the asset to swap to
|
||||
**/
|
||||
function validateSwapLiquidity(
|
||||
ReserveLogic.ReserveData storage fromReserve,
|
||||
ReserveLogic.ReserveData storage toReserve,
|
||||
address fromAsset,
|
||||
address toAsset
|
||||
) internal view returns (uint256, string memory) {
|
||||
if (!fromReserve.configuration.getActive() || !toReserve.configuration.getActive()) {
|
||||
return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
|
||||
}
|
||||
|
||||
if (fromAsset == toAsset) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.INVALID_EQUAL_ASSETS_TO_SWAP),
|
||||
Errors.INVALID_EQUAL_ASSETS_TO_SWAP
|
||||
);
|
||||
}
|
||||
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,4 +57,4 @@ contract MockSwapAdapter is ISwapAdapter {
|
|||
uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount;
|
||||
asset.transfer(address(0), amountToBurn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
|||
//transfers the underlying to the target
|
||||
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount);
|
||||
|
||||
//transfer event to track balances
|
||||
emit Transfer(user, address(0), amount);
|
||||
|
||||
emit Burn(msg.sender, receiverOfUnderlying, amount, index);
|
||||
}
|
||||
|
@ -124,6 +126,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
|
|||
//mint an equivalent amount of tokens to cover the new deposit
|
||||
_mint(user,scaledAmount);
|
||||
|
||||
//transfer event to track balances
|
||||
emit Transfer(address(0), user, amount);
|
||||
emit Mint(user, amount, index);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,14 +9,14 @@ import {SafeMath} from '../libraries/math/SafeMath.sol';
|
|||
/**
|
||||
* @title ERC20
|
||||
* @notice Basic ERC20 implementation
|
||||
* @author Aave
|
||||
* @author Aave, inspired by the Openzeppelin ERC20 implementation
|
||||
**/
|
||||
contract ERC20 is Context, IERC20, IERC20Detailed {
|
||||
using SafeMath for uint256;
|
||||
|
||||
mapping(address => uint256) private _balances;
|
||||
mapping(address => uint256) internal _balances;
|
||||
mapping(address => mapping(address => uint256)) private _allowances;
|
||||
uint256 private _totalSupply;
|
||||
uint256 internal _totalSupply;
|
||||
string private _name;
|
||||
string private _symbol;
|
||||
uint8 private _decimals;
|
||||
|
@ -74,6 +74,7 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
|||
**/
|
||||
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
|
||||
_transfer(_msgSender(), recipient, amount);
|
||||
emit Transfer(msg.sender, recipient, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -121,6 +122,7 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
|||
_msgSender(),
|
||||
_allowances[sender][_msgSender()].sub(amount, 'ERC20: transfer amount exceeds allowance')
|
||||
);
|
||||
emit Transfer(sender, recipient, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -169,7 +171,6 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
|||
|
||||
_balances[sender] = _balances[sender].sub(amount, 'ERC20: transfer amount exceeds balance');
|
||||
_balances[recipient] = _balances[recipient].add(amount);
|
||||
emit Transfer(sender, recipient, amount);
|
||||
}
|
||||
|
||||
function _mint(address account, uint256 amount) internal virtual {
|
||||
|
@ -179,7 +180,6 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
|||
|
||||
_totalSupply = _totalSupply.add(amount);
|
||||
_balances[account] = _balances[account].add(amount);
|
||||
emit Transfer(address(0), account, amount);
|
||||
}
|
||||
|
||||
function _burn(address account, uint256 amount) internal virtual {
|
||||
|
@ -189,7 +189,6 @@ contract ERC20 is Context, IERC20, IERC20Detailed {
|
|||
|
||||
_balances[account] = _balances[account].sub(amount, 'ERC20: burn amount exceeds balance');
|
||||
_totalSupply = _totalSupply.sub(amount);
|
||||
emit Transfer(account, address(0), amount);
|
||||
}
|
||||
|
||||
function _approve(
|
||||
|
|
|
@ -133,6 +133,9 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
|
|||
|
||||
_mint(user, amount.add(balanceIncrease));
|
||||
|
||||
// transfer event to track balances
|
||||
emit Transfer(address(0), user, amount);
|
||||
|
||||
emit MintDebt(
|
||||
user,
|
||||
amount,
|
||||
|
@ -181,6 +184,9 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
|
|||
_burn(user, amount.sub(balanceIncrease));
|
||||
}
|
||||
|
||||
// transfer event to track balances
|
||||
emit Transfer(user, address(0), amount);
|
||||
|
||||
emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
|
|||
|
||||
_mint(user, amount.rayDiv(index));
|
||||
|
||||
emit Transfer(address(0), user, amount);
|
||||
emit MintDebt(user, amount, index);
|
||||
}
|
||||
|
||||
|
@ -71,6 +72,8 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
|
|||
function burn(address user, uint256 amount, uint256 index) external override onlyLendingPool {
|
||||
_burn(user, amount.rayDiv(index));
|
||||
_userIndexes[user] = index;
|
||||
|
||||
emit Transfer(user, address(0), amount);
|
||||
emit BurnDebt(user, amount, index);
|
||||
}
|
||||
|
||||
|
|
|
@ -493,9 +493,6 @@
|
|||
"MockSwapAdapter": {
|
||||
"buidlerevm": {
|
||||
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
|
||||
},
|
||||
"localhost": {
|
||||
"address": "0x749258D38b0473d96FEcc14cC5e7DCE12d7Bd6f6"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ export enum eContractid {
|
|||
AaveProtocolTestHelpers = 'AaveProtocolTestHelpers',
|
||||
IERC20Detailed = 'IERC20Detailed',
|
||||
StableDebtToken = 'StableDebtToken',
|
||||
VariableDebtToken = 'VariableDebtToken',
|
||||
VariableDebtToken = 'VariableDebtToken'
|
||||
}
|
||||
|
||||
export enum ProtocolErrors {
|
||||
|
|
10681
package-lock.json
generated
10681
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
198
test/collateral-swap.spec.ts
Normal file
198
test/collateral-swap.spec.ts
Normal file
|
@ -0,0 +1,198 @@
|
|||
import {makeSuite, TestEnv} from './helpers/make-suite';
|
||||
import {MockSwapAdapter} from '../types/MockSwapAdapter';
|
||||
import {getMockSwapAdapter} from '../helpers/contracts-helpers';
|
||||
import {ProtocolErrors} from '../helpers/types';
|
||||
import {ethers} from 'ethers';
|
||||
import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants';
|
||||
import {getContractsData, getTxCostAndTimestamp} from './helpers/actions';
|
||||
import {calcExpectedATokenBalance} from './helpers/utils/calculations';
|
||||
import {waitForTx} from './__setup.spec';
|
||||
import {advanceBlock, timeLatest} from '../helpers/misc-utils';
|
||||
|
||||
const {expect} = require('chai');
|
||||
|
||||
makeSuite('LendingPool SwapDeposit function', (testEnv: TestEnv) => {
|
||||
let _mockSwapAdapter = {} as MockSwapAdapter;
|
||||
const {HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD} = ProtocolErrors;
|
||||
|
||||
before(async () => {
|
||||
_mockSwapAdapter = await getMockSwapAdapter();
|
||||
});
|
||||
|
||||
it('Deposits WETH into the reserve', async () => {
|
||||
const {pool, weth, users} = testEnv;
|
||||
const amountToDeposit = ethers.utils.parseEther('1');
|
||||
|
||||
for (const signer of [weth.signer, users[2].signer]) {
|
||||
const connectedWETH = weth.connect(signer);
|
||||
await connectedWETH.mint(amountToDeposit);
|
||||
await connectedWETH.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
|
||||
await pool
|
||||
.connect(signer)
|
||||
.deposit(weth.address, amountToDeposit, await signer.getAddress(), '0');
|
||||
}
|
||||
});
|
||||
it('User tries to swap more then he can, revert expected', async () => {
|
||||
const {pool, weth, dai} = testEnv;
|
||||
await expect(
|
||||
pool.swapLiquidity(
|
||||
_mockSwapAdapter.address,
|
||||
weth.address,
|
||||
dai.address,
|
||||
ethers.utils.parseEther('1.1'),
|
||||
'0x10'
|
||||
)
|
||||
).to.be.revertedWith('55');
|
||||
});
|
||||
|
||||
it('User tries to swap asset on equal asset, revert expected', async () => {
|
||||
const {pool, weth} = testEnv;
|
||||
await expect(
|
||||
pool.swapLiquidity(
|
||||
_mockSwapAdapter.address,
|
||||
weth.address,
|
||||
weth.address,
|
||||
ethers.utils.parseEther('0.1'),
|
||||
'0x10'
|
||||
)
|
||||
).to.be.revertedWith('56');
|
||||
});
|
||||
|
||||
it('User tries to swap more then available on the reserve', async () => {
|
||||
const {pool, weth, dai, users, aEth, deployer} = testEnv;
|
||||
|
||||
await pool.borrow(weth.address, ethers.utils.parseEther('0.1'), 1, 0, deployer.address);
|
||||
await pool.connect(users[2].signer).withdraw(weth.address, ethers.utils.parseEther('1'));
|
||||
|
||||
await expect(
|
||||
pool.swapLiquidity(
|
||||
_mockSwapAdapter.address,
|
||||
weth.address,
|
||||
dai.address,
|
||||
ethers.utils.parseEther('1'),
|
||||
'0x10'
|
||||
)
|
||||
).to.be.revertedWith('55');
|
||||
});
|
||||
|
||||
it('User tries to swap correct amount', async () => {
|
||||
const {pool, weth, dai, aEth, aDai} = testEnv;
|
||||
const userAddress = await pool.signer.getAddress();
|
||||
const amountToSwap = ethers.utils.parseEther('0.25');
|
||||
|
||||
const amountToReturn = ethers.utils.parseEther('0.5');
|
||||
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
|
||||
|
||||
const {
|
||||
reserveData: wethReserveDataBefore,
|
||||
userData: wethUserDataBefore,
|
||||
} = await getContractsData(weth.address, userAddress, testEnv);
|
||||
|
||||
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
|
||||
dai.address,
|
||||
userAddress,
|
||||
testEnv
|
||||
);
|
||||
|
||||
const reserveBalanceWETHBefore = await weth.balanceOf(aEth.address);
|
||||
const reserveBalanceDAIBefore = await dai.balanceOf(aDai.address);
|
||||
|
||||
const txReceipt = await waitForTx(
|
||||
await pool.swapLiquidity(
|
||||
_mockSwapAdapter.address,
|
||||
weth.address,
|
||||
dai.address,
|
||||
amountToSwap,
|
||||
'0x10'
|
||||
)
|
||||
);
|
||||
const {txTimestamp} = await getTxCostAndTimestamp(txReceipt);
|
||||
const userATokenBalanceWETHAfter = await aEth.balanceOf(userAddress);
|
||||
const userATokenBalanceDAIAfter = await aDai.balanceOf(userAddress);
|
||||
|
||||
const reserveBalanceWETHAfter = await weth.balanceOf(aEth.address);
|
||||
const reserveBalanceDAIAfter = await dai.balanceOf(aDai.address);
|
||||
|
||||
expect(userATokenBalanceWETHAfter.toString()).to.be.equal(
|
||||
calcExpectedATokenBalance(wethReserveDataBefore, wethUserDataBefore, txTimestamp)
|
||||
.minus(amountToSwap.toString())
|
||||
.toString(),
|
||||
'was burned incorrect amount of user funds'
|
||||
);
|
||||
expect(userATokenBalanceDAIAfter.toString()).to.be.equal(
|
||||
calcExpectedATokenBalance(daiReserveDataBefore, daiUserDataBefore, txTimestamp)
|
||||
.plus(amountToReturn.toString())
|
||||
.toString(),
|
||||
'was minted incorrect amount of user funds'
|
||||
);
|
||||
|
||||
expect(reserveBalanceWETHAfter.toString()).to.be.equal(
|
||||
reserveBalanceWETHBefore.sub(amountToSwap).toString(),
|
||||
'was sent incorrect amount if reserve funds'
|
||||
);
|
||||
expect(reserveBalanceDAIAfter.toString()).to.be.equal(
|
||||
reserveBalanceDAIBefore.add(amountToReturn).toString(),
|
||||
'was received incorrect amount if reserve funds'
|
||||
);
|
||||
});
|
||||
|
||||
it('User tries to drop HF below one', async () => {
|
||||
const {pool, weth, dai, deployer} = testEnv;
|
||||
const amountToSwap = ethers.utils.parseEther('0.3');
|
||||
|
||||
const amountToReturn = ethers.utils.parseEther('0.5');
|
||||
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
|
||||
|
||||
await pool.borrow(weth.address, ethers.utils.parseEther('0.3'), 1, 0, deployer.address);
|
||||
|
||||
await expect(
|
||||
pool.swapLiquidity(_mockSwapAdapter.address, weth.address, dai.address, amountToSwap, '0x10')
|
||||
).to.be.revertedWith(HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD);
|
||||
});
|
||||
|
||||
it('Should set usage as collateral to false if no leftovers after swap', async () => {
|
||||
const {pool, weth, dai, aEth, users} = testEnv;
|
||||
const userAddress = await pool.signer.getAddress();
|
||||
|
||||
// add more liquidity to allow user 0 to swap everything he has
|
||||
await weth.connect(users[2].signer).mint(ethers.utils.parseEther('1'));
|
||||
await pool
|
||||
.connect(users[2].signer)
|
||||
.deposit(weth.address, ethers.utils.parseEther('1'), users[2].address, '0');
|
||||
|
||||
// cleanup borrowings, to be abe to swap whole weth
|
||||
const amountToRepay = ethers.utils.parseEther('0.5');
|
||||
await weth.mint(amountToRepay);
|
||||
await pool.repay(weth.address, amountToRepay, '1', userAddress);
|
||||
const txTimestamp = (await timeLatest()).plus(100);
|
||||
|
||||
const {
|
||||
reserveData: wethReserveDataBefore,
|
||||
userData: wethUserDataBefore,
|
||||
} = await getContractsData(weth.address, userAddress, testEnv);
|
||||
const amountToSwap = calcExpectedATokenBalance(
|
||||
wethReserveDataBefore,
|
||||
wethUserDataBefore,
|
||||
txTimestamp.plus('1')
|
||||
);
|
||||
|
||||
await advanceBlock(txTimestamp.toNumber());
|
||||
|
||||
await pool.swapLiquidity(
|
||||
_mockSwapAdapter.address,
|
||||
weth.address,
|
||||
dai.address,
|
||||
amountToSwap.toString(),
|
||||
'0x10'
|
||||
);
|
||||
const {userData: wethUserDataAfter} = await getContractsData(
|
||||
weth.address,
|
||||
userAddress,
|
||||
testEnv
|
||||
);
|
||||
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.equal(
|
||||
false,
|
||||
'usageAsCollateralEnabled are not set to false'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,3 +1,5 @@
|
|||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import {TestEnv, makeSuite} from './helpers/make-suite';
|
||||
import {APPROVAL_AMOUNT_LENDING_POOL, oneRay} from '../helpers/constants';
|
||||
import {
|
||||
|
@ -8,7 +10,6 @@ import {
|
|||
import {ethers} from 'ethers';
|
||||
import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver';
|
||||
import {ProtocolErrors, eContractid} from '../helpers/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {VariableDebtToken} from '../types/VariableDebtToken';
|
||||
import {StableDebtToken} from '../types/StableDebtToken';
|
||||
|
||||
|
@ -28,7 +29,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
|
|||
_mockFlashLoanReceiver = await getMockFlashLoanReceiver();
|
||||
});
|
||||
|
||||
it('Deposits ETH into the reserve', async () => {
|
||||
it('Deposits WETH into the reserve', async () => {
|
||||
const {pool, weth} = testEnv;
|
||||
const userAddress = await pool.signer.getAddress();
|
||||
const amountToDeposit = ethers.utils.parseEther('1');
|
||||
|
|
|
@ -722,7 +722,7 @@ const getDataBeforeAction = async (
|
|||
};
|
||||
};
|
||||
|
||||
const getTxCostAndTimestamp = async (tx: ContractReceipt) => {
|
||||
export const getTxCostAndTimestamp = async (tx: ContractReceipt) => {
|
||||
if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) {
|
||||
throw new Error('No tx blocknumber');
|
||||
}
|
||||
|
|
|
@ -927,7 +927,7 @@ const calcExpectedScaledATokenBalance = (
|
|||
.minus(amountTaken.rayDiv(index));
|
||||
};
|
||||
|
||||
const calcExpectedATokenBalance = (
|
||||
export const calcExpectedATokenBalance = (
|
||||
reserveDataBeforeAction: ReserveData,
|
||||
userDataBeforeAction: UserReserveData,
|
||||
currentTimestamp: BigNumber
|
||||
|
|
Loading…
Reference in New Issue
Block a user