diff --git a/buidler.config.ts b/buidler.config.ts index 03f42645..47eba1e2 100644 --- a/buidler.config.ts +++ b/buidler.config.ts @@ -10,7 +10,6 @@ usePlugin('solidity-coverage'); usePlugin('@nomiclabs/buidler-waffle'); usePlugin('@nomiclabs/buidler-etherscan'); //usePlugin('buidler-gas-reporter'); - const DEFAULT_BLOCK_GAS_LIMIT = 10000000; const DEFAULT_GAS_PRICE = 10; const HARDFORK = 'istanbul'; diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 41b13f07..85746888 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -137,6 +137,15 @@ interface ILendingPool { address liquidator, bool receiveAToken ); + /** + * @dev Emitted when the pause is triggered. + */ + event Paused(); + + /** + * @dev Emitted when the pause is lifted. + */ + event Unpaused(); /** * @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens) @@ -420,4 +429,15 @@ interface ILendingPool { ) external view returns (bool); function getReserves() external view returns (address[] memory); + + /** + * @dev Set the _pause state + * @param val the boolean value to set the current pause state of LendingPool + */ + function setPause(bool val) external; + + /** + * @dev Returns if the LendingPool is paused + */ + function paused() external view returns(bool); } diff --git a/contracts/interfaces/ISwapAdapter.sol b/contracts/interfaces/ISwapAdapter.sol index 8f261d81..ed91f95f 100644 --- a/contracts/interfaces/ISwapAdapter.sol +++ b/contracts/interfaces/ISwapAdapter.sol @@ -2,20 +2,19 @@ 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; + /** + * @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; } diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index ba5e7894..69a92b6f 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -55,16 +55,27 @@ contract LendingPool is VersionedInitializable, ILendingPool { address[] internal _reservesList; bool internal _flashLiquidationLocked; + bool internal _paused; /** * @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 ); - _; + } + + /** + * @dev Function to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + function whenNotPaused() internal view { + require(!_paused, Errors.IS_PAUSED); } uint256 public constant UINT_MAX_VALUE = uint256(-1); @@ -97,6 +108,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { address onBehalfOf, uint16 referralCode ) external override { + whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateDeposit(reserve, amount); @@ -125,6 +137,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param amount the underlying amount to be redeemed **/ function withdraw(address asset, uint256 amount) external override { + whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; address aToken = reserve.aTokenAddress; @@ -185,6 +198,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 interestRateMode, uint256 amount ) external override { + whenNotPaused(); address debtToken = _reserves[asset].getDebtTokenAddress(interestRateMode); _borrowAllowance[debtToken][msg.sender][user] = amount; @@ -207,6 +221,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint16 referralCode, address onBehalfOf ) external override { + whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; if (onBehalfOf != msg.sender) { @@ -246,6 +261,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 rateMode, address onBehalfOf ) external override { + whenNotPaused(); _executeRepay(asset, msg.sender, amount, rateMode, onBehalfOf); } @@ -307,6 +323,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param rateMode the rate mode that the user wants to swap **/ function swapBorrowRateMode(address asset, uint256 rateMode) external override { + whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve); @@ -350,6 +367,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param user the address of the user to be rebalanced **/ function rebalanceStableBorrowRate(address asset, address user) external override { + whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; IStableDebtToken stableDebtToken = IStableDebtToken(reserve.stableDebtTokenAddress); @@ -395,6 +413,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise. **/ function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override { + whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateSetUseReserveAsCollateral( @@ -431,7 +450,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 purchaseAmount, bool receiveAToken ) external override { - + whenNotPaused(); address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager(); //solium-disable-next-line @@ -488,6 +507,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { address receiver, bytes calldata params ) external override { + whenNotPaused(); require(!_flashLiquidationLocked, Errors.REENTRANCY_NOT_ALLOWED); _flashLiquidationLocked = true; @@ -535,6 +555,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { bytes calldata params, uint16 referralCode ) external override { + whenNotPaused(); ReserveLogic.ReserveData storage reserve = _reserves[asset]; FlashLoanLocalVars memory vars; @@ -596,6 +617,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 amountToSwap, bytes calldata params ) external override { + whenNotPaused(); address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager(); //solium-disable-next-line @@ -791,7 +813,8 @@ contract LendingPool is VersionedInitializable, ILendingPool { address stableDebtAddress, address variableDebtAddress, address interestRateStrategyAddress - ) external override onlyLendingPoolConfigurator { + ) external override { + onlyLendingPoolConfigurator(); _reserves[asset].init( aTokenAddress, stableDebtAddress, @@ -810,16 +833,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; } @@ -970,6 +990,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { address user, uint256 amount ) external override view returns (bool) { + whenNotPaused(); return GenericLogic.balanceDecreaseAllowed( asset, @@ -995,4 +1016,26 @@ contract LendingPool is VersionedInitializable, ILendingPool { function getAddressesProvider() external view returns (ILendingPoolAddressesProvider) { return _addressesProvider; } + + /** + * @dev Set the _pause state + * @param val the boolean value to set the current pause state of LendingPool + */ + function setPause(bool val) external override { + onlyLendingPoolConfigurator(); + + _paused = val; + if (_paused) { + emit Paused(); + } else { + emit Unpaused(); + } + } + + /** + * @dev Returns if the LendingPool is paused + */ + function paused() external view override returns(bool) { + return _paused; + } } diff --git a/contracts/lendingpool/LendingPoolConfigurator.sol b/contracts/lendingpool/LendingPoolConfigurator.sol index e9c0a292..790eca96 100644 --- a/contracts/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/lendingpool/LendingPoolConfigurator.sol @@ -582,4 +582,12 @@ contract LendingPoolConfigurator is VersionedInitializable { proxy.upgradeToAndCall(implementation, params); } + + /** + * @dev pauses or unpauses LendingPool actions + * @param val the boolean value to set the current pause state of LendingPool + **/ + function setPoolPause(bool val) external onlyLendingPoolManager { + pool.setPause(val); + } } diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 664e32f4..6adc18d9 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -27,6 +27,7 @@ import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol'; * @title LendingPoolLiquidationManager contract * @author Aave * @notice Implements the liquidation function. + * @dev LendingPoolLiquidationManager inherits Pausable from OpenZeppelin to have the same storage layout as LendingPool **/ contract LendingPoolLiquidationManager is VersionedInitializable { using SafeERC20 for IERC20; @@ -49,6 +50,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable { address[] internal reservesList; bool internal _flashLiquidationLocked; + bool public _paused; uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000; diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index 3ab451b1..48c53ba6 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -49,8 +49,8 @@ library Errors { string public constant CANNOT_GIVE_ALLOWANCE_TO_HIMSELF = '30'; // 'User cannot give allowance to himself' string public constant TRANSFER_AMOUNT_NOT_GT_0 = '31'; // 'Transferred amount needs to be greater than zero' 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 LIQUIDITY_INDEX_OVERFLOW = '47'; // Liquidity index overflows uint128 string public constant VARIABLE_BORROW_INDEX_OVERFLOW = '48'; // Variable borrow index overflows uint128 @@ -77,6 +77,8 @@ library Errors { string public constant ADDITION_OVERFLOW = '45'; string public constant DIVISION_BY_ZERO = '46'; + // pausable error message + string public constant IS_PAUSED = '58'; // 'Pool is paused' enum LiquidationErrors { NO_ERROR, NO_COLLATERAL_AVAILABLE, diff --git a/contracts/libraries/logic/ReserveLogic.sol b/contracts/libraries/logic/ReserveLogic.sol index 32e16599..f8ce2220 100644 --- a/contracts/libraries/logic/ReserveLogic.sol +++ b/contracts/libraries/logic/ReserveLogic.sol @@ -62,14 +62,11 @@ library ReserveLogic { //the current stable borrow rate. Expressed in ray uint128 currentStableBorrowRate; uint40 lastUpdateTimestamp; - //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; } diff --git a/contracts/mocks/flashloan/MockSwapAdapter.sol b/contracts/mocks/flashloan/MockSwapAdapter.sol index 7fdd6d35..08658d4b 100644 --- a/contracts/mocks/flashloan/MockSwapAdapter.sol +++ b/contracts/mocks/flashloan/MockSwapAdapter.sol @@ -4,57 +4,56 @@ 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 {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; - uint256 internal _amountToReturn; - bool internal _tryReentrancy; - ILendingPoolAddressesProvider public addressesProvider; + event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); - event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); + constructor(ILendingPoolAddressesProvider provider) public { + addressesProvider = provider; + } - 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' + ); } - function setAmountToReturn(uint256 amount) public { - _amountToReturn = amount; - } + emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, _amountToReturn); + } - 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); - } + function burnAsset(IERC20 asset, uint256 amount) public { + uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount; + asset.transfer(address(0), amountToBurn); + } } diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index b829350c..3bb54b49 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -27,14 +27,18 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { LendingPool public immutable POOL; /// @dev owner => next valid nonce to submit with permit() - mapping (address => uint256) public _nonces; + mapping(address => uint256) public _nonces; uint256 public constant ATOKEN_REVISION = 0x1; - + bytes32 public DOMAIN_SEPARATOR; - bytes public constant EIP712_REVISION = bytes("1"); - bytes32 internal constant EIP712_DOMAIN = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = keccak256( + 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' + ); + bytes32 public constant PERMIT_TYPEHASH = keccak256( + 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)' + ); modifier onlyLendingPool { require(msg.sender == address(POOL), Errors.CALLER_MUST_BE_LENDING_POOL); @@ -65,16 +69,18 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { //solium-disable-next-line assembly { - chainId := chainid() + chainId := chainid() } - - DOMAIN_SEPARATOR = keccak256(abi.encode( + + DOMAIN_SEPARATOR = keccak256( + abi.encode( EIP712_DOMAIN, keccak256(bytes(tokenName)), keccak256(EIP712_REVISION), chainId, address(this) - )); + ) + ); _setName(tokenName); _setSymbol(tokenSymbol); @@ -92,7 +98,6 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { uint256 amount, uint256 index ) external override onlyLendingPool { - uint256 currentBalance = balanceOf(user); require(amount <= currentBalance, Errors.INVALID_ATOKEN_BALANCE); @@ -104,6 +109,9 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { //transfers the underlying to the target IncentivizedERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount); + //transfer event to track balances + emit Transfer(user, address(0), amount); + emit Burn(msg.sender, receiverOfUnderlying, amount, index); } @@ -113,14 +121,18 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { * @param user the address receiving the minted tokens * @param amount the amount of tokens to mint */ - function mint(address user, uint256 amount, uint256 index) external override onlyLendingPool { - - + function mint( + address user, + uint256 amount, + uint256 index + ) external override onlyLendingPool { uint256 scaledAmount = amount.rayDiv(index); - - //mint an equivalent amount of tokens to cover the new deposit - _mint(user,scaledAmount); + //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); } @@ -208,12 +220,12 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { } /** - * @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 - **/ + * @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) external override @@ -225,39 +237,38 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { } /** - * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md - * @param owner the owner of the funds - * @param spender the spender - * @param value the amount - * @param deadline the deadline timestamp, type(uint256).max for max deadline - * @param v signature param - * @param s signature param - * @param r signature param - */ + * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner the owner of the funds + * @param spender the spender + * @param value the amount + * @param deadline the deadline timestamp, type(uint256).max for max deadline + * @param v signature param + * @param s signature param + * @param r signature param + */ function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s ) external { - require(owner != address(0), "INVALID_OWNER"); - //solium-disable-next-line - require(block.timestamp <= deadline, "INVALID_EXPIRATION"); - uint256 currentValidNonce = _nonces[owner]; - bytes32 digest = keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256( - abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) - ) - ); - require(owner == ecrecover(digest, v, r, s), "INVALID_SIGNATURE"); - _nonces[owner] = currentValidNonce.add(1); - _approve(owner, spender, value); + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + _nonces[owner] = currentValidNonce.add(1); + _approve(owner, spender, value); } function _transfer( @@ -265,8 +276,8 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { address to, uint256 amount, bool validate - ) internal { - if(validate){ + ) internal { + if (validate) { require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED); } @@ -277,17 +288,16 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { 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); + ) internal override { + _transfer(from, to, amount, true); } + /** * @dev aTokens should not receive ETH **/ diff --git a/contracts/tokenization/IncentivizedERC20.sol b/contracts/tokenization/IncentivizedERC20.sol index 0d3aa311..02f9eac3 100644 --- a/contracts/tokenization/IncentivizedERC20.sol +++ b/contracts/tokenization/IncentivizedERC20.sol @@ -10,16 +10,17 @@ import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController /** * @title ERC20 * @notice Basic ERC20 implementation - * @author Aave + * @author Aave, inspired by the Openzeppelin ERC20 implementation **/ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { using SafeMath for uint256; IAaveIncentivesController internal immutable _incentivesController; - 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; @@ -79,6 +80,7 @@ contract IncentivizedERC20 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; } @@ -126,6 +128,7 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { _msgSender(), _allowances[sender][_msgSender()].sub(amount, 'ERC20: transfer amount exceeds allowance') ); + emit Transfer(sender, recipient, amount); return true; } @@ -184,7 +187,6 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { _incentivesController.handleAction(recipient, totalSupply, oldRecipientBalance); } } - emit Transfer(sender, recipient, amount); } function _mint(address account, uint256 amount) internal virtual { @@ -201,8 +203,6 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { if (address(_incentivesController) != address(0)) { _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); } - - emit Transfer(address(0), account, amount); } function _burn(address account, uint256 amount) internal virtual { @@ -219,8 +219,6 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed { if (address(_incentivesController) != address(0)) { _incentivesController.handleAction(account, oldTotalSupply, oldAccountBalance); } - - emit Transfer(account, address(0), amount); } function _approve( diff --git a/contracts/tokenization/StableDebtToken.sol b/contracts/tokenization/StableDebtToken.sol index fd2affca..547052e4 100644 --- a/contracts/tokenization/StableDebtToken.sol +++ b/contracts/tokenization/StableDebtToken.sol @@ -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); } } diff --git a/contracts/tokenization/VariableDebtToken.sol b/contracts/tokenization/VariableDebtToken.sol index 879ffc17..7ec1d8e3 100644 --- a/contracts/tokenization/VariableDebtToken.sol +++ b/contracts/tokenization/VariableDebtToken.sol @@ -80,6 +80,7 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken { require(newUserIndex < (1 << 128), 'Debt token: Index overflow'); _usersData[user] = newUserIndex; + emit Transfer(address(0), user, amount); emit MintDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex); } @@ -109,6 +110,8 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken { } _usersData[user] = newUserIndex; + // transfer event to track the balances + emit Transfer(user, address(0), amount); emit BurnDebt(user, amount, previousBalance, currentBalance, balanceIncrease, newUserIndex); } } diff --git a/deployed-contracts.json b/deployed-contracts.json index 00c4ef4f..9981b17c 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -495,4 +495,4 @@ "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2" } } -} \ No newline at end of file +} diff --git a/helpers/types.ts b/helpers/types.ts index 4d8ed0fa..966180d4 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -5,7 +5,7 @@ export enum eEthereumNetwork { kovan = 'kovan', ropsten = 'ropsten', main = 'main', - coverage = 'coverage' + coverage = 'coverage', } export enum AavePools { @@ -42,7 +42,7 @@ export enum eContractid { AaveProtocolTestHelpers = 'AaveProtocolTestHelpers', IERC20Detailed = 'IERC20Detailed', StableDebtToken = 'StableDebtToken', - VariableDebtToken = 'VariableDebtToken' + VariableDebtToken = 'VariableDebtToken', } export enum ProtocolErrors { @@ -100,6 +100,8 @@ export enum ProtocolErrors { NO_ERRORS = '42', // 'No errors' INVALID_FLASHLOAN_MODE = '43', //Invalid flashloan mode + IS_PAUSED = '58', // Pool is paused + // old INVALID_FROM_BALANCE_AFTER_TRANSFER = 'Invalid from balance after transfer', diff --git a/package-lock.json b/package-lock.json index 80de030a..23687190 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5138,12 +5138,14 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "ansi-styles": { "version": "3.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -5151,7 +5153,8 @@ }, "bindings": { "version": "1.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, "requires": { "file-uri-to-path": "1.0.0" @@ -5159,7 +5162,8 @@ }, "bip66": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -5167,17 +5171,20 @@ }, "bn.js": { "version": "4.11.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true }, "brorand": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, "browserify-aes": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { "buffer-xor": "^1.0.3", @@ -5190,22 +5197,26 @@ }, "buffer-from": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, "buffer-xor": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, "camelcase": { "version": "5.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "cipher-base": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -5214,7 +5225,8 @@ }, "cliui": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { "string-width": "^3.1.0", @@ -5224,7 +5236,8 @@ }, "color-convert": { "version": "1.9.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" @@ -5232,12 +5245,14 @@ }, "color-name": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "create-hash": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { "cipher-base": "^1.0.1", @@ -5249,7 +5264,8 @@ }, "create-hmac": { "version": "1.1.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { "cipher-base": "^1.0.3", @@ -5262,7 +5278,8 @@ }, "cross-spawn": { "version": "6.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -5274,12 +5291,14 @@ }, "decamelize": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "drbg.js": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", "dev": true, "requires": { "browserify-aes": "^1.0.6", @@ -5289,7 +5308,8 @@ }, "elliptic": { "version": "6.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -5303,12 +5323,14 @@ }, "emoji-regex": { "version": "7.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "end-of-stream": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -5316,7 +5338,8 @@ }, "ethereumjs-util": { "version": "6.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==", "dev": true, "requires": { "bn.js": "^4.11.0", @@ -5330,7 +5353,8 @@ }, "ethjs-util": { "version": "0.1.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", "dev": true, "requires": { "is-hex-prefixed": "1.0.0", @@ -5339,7 +5363,8 @@ }, "evp_bytestokey": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { "md5.js": "^1.3.4", @@ -5348,7 +5373,8 @@ }, "execa": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { "cross-spawn": "^6.0.0", @@ -5362,12 +5388,14 @@ }, "file-uri-to-path": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "dev": true }, "find-up": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { "locate-path": "^3.0.0" @@ -5375,12 +5403,14 @@ }, "get-caller-file": { "version": "2.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-stream": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { "pump": "^3.0.0" @@ -5388,7 +5418,8 @@ }, "hash-base": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -5397,7 +5428,8 @@ }, "hash.js": { "version": "1.1.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -5406,7 +5438,8 @@ }, "hmac-drbg": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { "hash.js": "^1.0.3", @@ -5416,37 +5449,44 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "invert-kv": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-hex-prefixed": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=", "dev": true }, "is-stream": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, "isexe": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "keccak": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw==", "dev": true, "requires": { "bindings": "^1.2.1", @@ -5457,7 +5497,8 @@ }, "lcid": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { "invert-kv": "^2.0.0" @@ -5465,7 +5506,8 @@ }, "locate-path": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { "p-locate": "^3.0.0", @@ -5474,7 +5516,8 @@ }, "map-age-cleaner": { "version": "0.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { "p-defer": "^1.0.0" @@ -5482,7 +5525,8 @@ }, "md5.js": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { "hash-base": "^3.0.0", @@ -5492,7 +5536,8 @@ }, "mem": { "version": "4.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", @@ -5502,32 +5547,38 @@ }, "mimic-fn": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "minimalistic-assert": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, "minimalistic-crypto-utils": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", "dev": true }, "nan": { "version": "2.14.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true }, "nice-try": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "npm-run-path": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { "path-key": "^2.0.0" @@ -5535,7 +5586,8 @@ }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -5543,7 +5595,8 @@ }, "os-locale": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { "execa": "^1.0.0", @@ -5553,22 +5606,26 @@ }, "p-defer": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, "p-finally": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-is-promise": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, "p-limit": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -5576,7 +5633,8 @@ }, "p-locate": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { "p-limit": "^2.0.0" @@ -5584,22 +5642,26 @@ }, "p-try": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "path-exists": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-key": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "pump": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -5608,17 +5670,20 @@ }, "require-directory": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, "ripemd160": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { "hash-base": "^3.0.0", @@ -5627,7 +5692,8 @@ }, "rlp": { "version": "2.2.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-l6YVrI7+d2vpW6D6rS05x2Xrmq8oW7v3pieZOJKBEdjuTF4Kz/iwk55Zyh1Zaz+KOB2kC8+2jZlp2u9L4tTzCQ==", "dev": true, "requires": { "bn.js": "^4.11.1", @@ -5636,12 +5702,14 @@ }, "safe-buffer": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", "dev": true }, "secp256k1": { "version": "3.7.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-1cf8sbnRreXrQFdH6qsg2H71Xw91fCCS9Yp021GnUNJzWJS/py96fS4lHbnTnouLp08Xj6jBoBB6V78Tdbdu5g==", "dev": true, "requires": { "bindings": "^1.5.0", @@ -5656,17 +5724,20 @@ }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "sha.js": { "version": "2.4.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -5675,7 +5746,8 @@ }, "shebang-command": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -5683,22 +5755,26 @@ }, "shebang-regex": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "source-map": { "version": "0.6.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-support": { "version": "0.5.12", - "bundled": true, + "resolved": false, + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -5707,7 +5783,8 @@ }, "string-width": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { "emoji-regex": "^7.0.1", @@ -5717,7 +5794,8 @@ }, "strip-ansi": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -5725,12 +5803,14 @@ }, "strip-eof": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, "strip-hex-prefix": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", "dev": true, "requires": { "is-hex-prefixed": "1.0.0" @@ -5738,7 +5818,8 @@ }, "which": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -5746,12 +5827,14 @@ }, "which-module": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "wrap-ansi": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { "ansi-styles": "^3.2.0", @@ -5761,17 +5844,20 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "y18n": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yargs": { "version": "13.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", "dev": true, "requires": { "cliui": "^5.0.0", @@ -5789,7 +5875,8 @@ }, "yargs-parser": { "version": "13.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/package.json b/package.json index bfb663df..91c4504e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ "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-transfers": "buidler test test/__setup.spec.ts test/atoken-transfer.spec.ts", "test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts", + "test-liquidate": "buidler test test/__setup.spec.ts test/liquidation-atoken.spec.ts", + "test-pausable": "buidler test test/__setup.spec.ts test/pausable-functions.spec.ts", "test-permit": "buidler test test/__setup.spec.ts test/atoken-permit.spec.ts", "dev:coverage": "buidler coverage --network coverage", "dev:deployment": "buidler dev-deployment", diff --git a/test/atoken-transfer.spec.ts b/test/atoken-transfer.spec.ts index a5c13989..87c1af73 100644 --- a/test/atoken-transfer.spec.ts +++ b/test/atoken-transfer.spec.ts @@ -17,6 +17,7 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { // ZERO_COLLATERAL, COLLATERAL_BALANCE_IS_0, TRANSFER_NOT_ALLOWED, + IS_PAUSED, } = ProtocolErrors; it('User 0 deposits 1000 DAI, transfers to user 1', async () => { @@ -45,7 +46,6 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { ); }); - 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 userAddress = await pool.signer.getAddress(); @@ -92,5 +92,4 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { TRANSFER_NOT_ALLOWED ).to.be.revertedWith(TRANSFER_NOT_ALLOWED); }); - }); diff --git a/test/flash-liquidation-with-collateral.spec.ts b/test/flash-liquidation-with-collateral.spec.ts index 0dcc7f1e..d30787c9 100644 --- a/test/flash-liquidation-with-collateral.spec.ts +++ b/test/flash-liquidation-with-collateral.spec.ts @@ -17,7 +17,7 @@ const {expect} = require('chai'); const {parseUnits, parseEther} = ethers.utils; makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEnv) => { - const {INVALID_HF, COLLATERAL_CANNOT_BE_LIQUIDATED} = ProtocolErrors; + const {INVALID_HF, COLLATERAL_CANNOT_BE_LIQUIDATED, IS_PAUSED} = ProtocolErrors; it('User 1 provides some liquidity for others to borrow', async () => { const {pool, weth, dai, usdc, deployer} = testEnv; @@ -736,7 +736,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0'); await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0'); - await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address); + await pool + .connect(user.signer) + .borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address); const amountToRepay = amountToBorrowVariable; diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 85eec131..036a30fd 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -23,6 +23,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { TRANSFER_AMOUNT_EXCEEDS_BALANCE, INVALID_FLASHLOAN_MODE, SAFEERC20_LOWLEVEL_CALL, + IS_PAUSED, } = ProtocolErrors; before(async () => { diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 2f3200e0..3e60955d 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -54,7 +54,6 @@ const almostEqualOrEqual = function ( return; } - 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`); @@ -63,10 +62,9 @@ const almostEqualOrEqual = function ( } if (actual[key] instanceof BigNumber) { - const actualValue = (actual[key]).decimalPlaces(0, BigNumber.ROUND_DOWN); const expectedValue = (expected[key]).decimalPlaces(0, BigNumber.ROUND_DOWN); - + this.assert( actualValue.eq(expectedValue) || actualValue.plus(1).eq(expectedValue) || diff --git a/test/helpers/scenario-engine.ts b/test/helpers/scenario-engine.ts index 6a03a4d1..bb4f82f8 100644 --- a/test/helpers/scenario-engine.ts +++ b/test/helpers/scenario-engine.ts @@ -230,7 +230,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv await rebalanceStableBorrowRate(reserve, user, target, expected, testEnv, revertMessage); } break; - + default: throw `Invalid action requested: ${name}`; } diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index 575c7653..98431d36 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -539,7 +539,7 @@ export const calcExpectedUserDataAfterBorrow = ( currentTimestamp ); expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; - + expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed); return expectedUserData; @@ -623,7 +623,7 @@ export const calcExpectedUserDataAfterRepay = ( txTimestamp ); expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance; - + if (user === onBehalfOf) { expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(totalRepaid); } else { @@ -926,9 +926,7 @@ export const calcExpectedATokenBalance = ( ) => { const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp); - const { - scaledATokenBalance: scaledBalanceBeforeAction, - } = userDataBeforeAction; + const {scaledATokenBalance: scaledBalanceBeforeAction} = userDataBeforeAction; return scaledBalanceBeforeAction.rayMul(index); }; diff --git a/test/liquidation-atoken.spec.ts b/test/liquidation-atoken.spec.ts index 15f3d12d..97d9507d 100644 --- a/test/liquidation-atoken.spec.ts +++ b/test/liquidation-atoken.spec.ts @@ -17,6 +17,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) => INVALID_HF, SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER, COLLATERAL_CANNOT_BE_LIQUIDATED, + IS_PAUSED, } = ProtocolErrors; it('LIQUIDATION - Deposits WETH, borrows DAI/Check liquidation fails because health factor is above 1', async () => { diff --git a/test/pausable-functions.spec.ts b/test/pausable-functions.spec.ts new file mode 100644 index 00000000..3403491c --- /dev/null +++ b/test/pausable-functions.spec.ts @@ -0,0 +1,378 @@ +import {makeSuite, TestEnv} from './helpers/make-suite'; +import {ProtocolErrors, RateMode} from '../helpers/types'; +import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants'; +import {convertToCurrencyDecimals, getMockFlashLoanReceiver} from '../helpers/contracts-helpers'; +import {parseEther, parseUnits} from 'ethers/lib/utils'; +import {BigNumber} from 'bignumber.js'; +import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver'; + +const {expect} = require('chai'); + +makeSuite('Pausable Pool', (testEnv: TestEnv) => { + let _mockFlashLoanReceiver = {} as MockFlashLoanReceiver; + + const { + IS_PAUSED, + TRANSFER_NOT_ALLOWED, + INVALID_FROM_BALANCE_AFTER_TRANSFER, + INVALID_TO_BALANCE_AFTER_TRANSFER, + } = ProtocolErrors; + + before(async () => { + _mockFlashLoanReceiver = await getMockFlashLoanReceiver(); + }); + + it('User 0 deposits 1000 DAI. Configurator pauses pool. Transfers to user 1 reverts. Configurator unpauses the network and next transfer succees', async () => { + const {users, pool, dai, aDai, configurator} = testEnv; + + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await dai.connect(users[0].signer).mint(amountDAItoDeposit); + + // user 0 deposits 1000 DAI + await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool + .connect(users[0].signer) + .deposit(dai.address, amountDAItoDeposit, users[0].address, '0'); + + const user0Balance = await aDai.balanceOf(users[0].address); + const user1Balance = await aDai.balanceOf(users[1].address); + + // Configurator pauses the pool + await configurator.setPoolPause(true); + + // User 0 tries the transfer to User 1 + await expect( + aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit) + ).to.revertedWith(IS_PAUSED); + + const pausedFromBalance = await aDai.balanceOf(users[0].address); + const pausedToBalance = await aDai.balanceOf(users[1].address); + + expect(pausedFromBalance).to.be.equal( + user0Balance.toString(), + INVALID_TO_BALANCE_AFTER_TRANSFER + ); + expect(pausedToBalance.toString()).to.be.equal( + user1Balance.toString(), + INVALID_FROM_BALANCE_AFTER_TRANSFER + ); + + // Configurator unpauses the pool + await configurator.setPoolPause(false); + + // User 0 succeeds transfer to User 1 + await aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit); + + const fromBalance = await aDai.balanceOf(users[0].address); + const toBalance = await aDai.balanceOf(users[1].address); + + expect(fromBalance.toString()).to.be.equal( + user0Balance.sub(amountDAItoDeposit), + INVALID_FROM_BALANCE_AFTER_TRANSFER + ); + expect(toBalance.toString()).to.be.equal( + user1Balance.add(amountDAItoDeposit), + INVALID_TO_BALANCE_AFTER_TRANSFER + ); + }); + + it('Deposit', async () => { + const {users, pool, dai, aDai, configurator} = testEnv; + + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await dai.connect(users[0].signer).mint(amountDAItoDeposit); + + // user 0 deposits 1000 DAI + await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + // Configurator pauses the pool + await configurator.setPoolPause(true); + await expect( + pool.connect(users[0].signer).deposit(dai.address, amountDAItoDeposit, users[0].address, '0') + ).to.revertedWith(IS_PAUSED); + + // Configurator unpauses the pool + await configurator.setPoolPause(false); + }); + + it('Withdraw', async () => { + const {users, pool, dai, aDai, configurator} = testEnv; + + const amountDAItoDeposit = await convertToCurrencyDecimals(dai.address, '1000'); + + await dai.connect(users[0].signer).mint(amountDAItoDeposit); + + // user 0 deposits 1000 DAI + await dai.connect(users[0].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool + .connect(users[0].signer) + .deposit(dai.address, amountDAItoDeposit, users[0].address, '0'); + + // Configurator pauses the pool + await configurator.setPoolPause(true); + + // user tries to burn + await expect( + pool.connect(users[0].signer).withdraw(dai.address, amountDAItoDeposit) + ).to.revertedWith(IS_PAUSED); + + // Configurator unpauses the pool + await configurator.setPoolPause(false); + }); + + it('DelegateBorrowAllowance', async () => { + const {pool, dai, users, configurator} = testEnv; + + const user = users[1]; + const toUser = users[2]; + // Pause the pool + await configurator.setPoolPause(true); + + // Try to execute liquidation + await expect( + pool.connect(user.signer).delegateBorrowAllowance(dai.address, toUser.address, '1', '1') + ).revertedWith(IS_PAUSED); + + // Unpause the pool + await configurator.setPoolPause(false); + }); + + it('Borrow', async () => { + const {pool, dai, users, configurator} = testEnv; + + const user = users[1]; + // Pause the pool + await configurator.setPoolPause(true); + + // Try to execute liquidation + await expect( + pool.connect(user.signer).borrow(dai.address, '1', '1', '0', user.address) + ).revertedWith(IS_PAUSED); + + // Unpause the pool + await configurator.setPoolPause(false); + }); + + it('Swap liquidity', async () => { + const {pool, dai, weth, users, configurator} = testEnv; + + const user = users[1]; + // Pause the pool + await configurator.setPoolPause(true); + + // Try to execute liquidation + await expect( + pool.connect(user.signer).swapLiquidity(user.address, dai.address, weth.address, '1', '0x') + ).revertedWith(IS_PAUSED); + + // Unpause the pool + await configurator.setPoolPause(false); + }); + + it('Repay', async () => { + const {pool, dai, users, configurator} = testEnv; + + const user = users[1]; + // Pause the pool + await configurator.setPoolPause(true); + + // Try to execute liquidation + await expect(pool.connect(user.signer).repay(dai.address, '1', '1', user.address)).revertedWith( + IS_PAUSED + ); + + // Unpause the pool + await configurator.setPoolPause(false); + }); + + it('Repay with collateral', async () => { + const {pool, weth, dai, usdc, users, mockSwapAdapter, oracle, configurator} = testEnv; + const user = users[6]; + const liquidator = users[5]; + + // Pause the pool + await configurator.setPoolPause(true); + + // Try to execute liquidation + await expect( + pool + .connect(liquidator.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + '1', + mockSwapAdapter.address, + '0x' + ) + ).revertedWith(IS_PAUSED); + + // Unpause the pool + await configurator.setPoolPause(false); + }); + + it('Flash loan', async () => { + const {dai, pool, weth, users, configurator} = testEnv; + + const caller = users[3]; + + const flashAmount = parseEther('0.8'); + + await _mockFlashLoanReceiver.setFailExecutionTransfer(true); + + // Pause pool + await configurator.setPoolPause(true); + + await expect( + pool + .connect(caller.signer) + .flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0') + ).revertedWith(IS_PAUSED); + + // Unpause pool + await configurator.setPoolPause(false); + }); + + it('Liquidation call', async () => { + const {users, pool, usdc, oracle, weth, configurator} = testEnv; + const depositor = users[3]; + const borrower = users[4]; + + //mints USDC to depositor + await usdc + .connect(depositor.signer) + .mint(await convertToCurrencyDecimals(usdc.address, '1000')); + + //approve protocol to access depositor wallet + await usdc.connect(depositor.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + //user 3 deposits 1000 USDC + const amountUSDCtoDeposit = await convertToCurrencyDecimals(usdc.address, '1000'); + + await pool + .connect(depositor.signer) + .deposit(usdc.address, amountUSDCtoDeposit, depositor.address, '0'); + + //user 4 deposits 1 ETH + const amountETHtoDeposit = await convertToCurrencyDecimals(weth.address, '1'); + + //mints WETH to borrower + await weth.connect(borrower.signer).mint(amountETHtoDeposit); + + //approve protocol to access borrower wallet + await weth.connect(borrower.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool + .connect(borrower.signer) + .deposit(weth.address, amountETHtoDeposit, borrower.address, '0'); + + //user 4 borrows + const userGlobalData = await pool.getUserAccountData(borrower.address); + + const usdcPrice = await oracle.getAssetPrice(usdc.address); + + const amountUSDCToBorrow = await convertToCurrencyDecimals( + usdc.address, + new BigNumber(userGlobalData.availableBorrowsETH.toString()) + .div(usdcPrice.toString()) + .multipliedBy(0.9502) + .toFixed(0) + ); + + await pool + .connect(borrower.signer) + .borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address); + + // Drops HF below 1 + await oracle.setAssetPrice( + usdc.address, + new BigNumber(usdcPrice.toString()).multipliedBy(1.2).toFixed(0) + ); + + //mints dai to the liquidator + await usdc.mint(await convertToCurrencyDecimals(usdc.address, '1000')); + await usdc.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + const userReserveDataBefore = await pool.getUserReserveData(usdc.address, borrower.address); + + const amountToLiquidate = new BigNumber(userReserveDataBefore.currentStableDebt.toString()) + .multipliedBy(0.5) + .toFixed(0); + + // Pause pool + await configurator.setPoolPause(true); + + // Do liquidation + expect( + pool.liquidationCall(weth.address, usdc.address, borrower.address, amountToLiquidate, true) + ).revertedWith(IS_PAUSED); + + // Unpause pool + await configurator.setPoolPause(false); + }); + + it('SwapBorrowRateMode', async () => { + const {pool, weth, dai, usdc, users, configurator, mockSwapAdapter} = testEnv; + const user = users[1]; + const amountWETHToDeposit = parseEther('10'); + const amountDAIToDeposit = parseEther('120'); + const amountToBorrow = parseUnits('65', 6); + + await weth.connect(user.signer).mint(amountWETHToDeposit); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0'); + + await dai.connect(user.signer).mint(amountDAIToDeposit); + await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address); + + // Pause pool + await configurator.setPoolPause(true); + + // Try to repay + await expect( + pool.connect(user.signer).swapBorrowRateMode(usdc.address, RateMode.Stable) + ).revertedWith(IS_PAUSED); + + // Unpause pool + await configurator.setPoolPause(false); + }); + + it('RebalanceStableBorrowRate', async () => { + const {pool, dai, users, configurator} = testEnv; + const user = users[1]; + // Pause pool + await configurator.setPoolPause(true); + + await expect( + pool.connect(user.signer).rebalanceStableBorrowRate(dai.address, user.address) + ).revertedWith(IS_PAUSED); + + // Unpause pool + await configurator.setPoolPause(false); + }); + + it('setUserUseReserveAsCollateral', async () => { + const {pool, weth, users, configurator} = testEnv; + const user = users[1]; + + const amountWETHToDeposit = parseEther('1'); + await weth.connect(user.signer).mint(amountWETHToDeposit); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0'); + + // Pause pool + await configurator.setPoolPause(true); + + await expect( + pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false) + ).revertedWith(IS_PAUSED); + + // Unpause pool + await configurator.setPoolPause(false); + }); +}); diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts index abe7b680..41e9060f 100644 --- a/test/repay-with-collateral.spec.ts +++ b/test/repay-with-collateral.spec.ts @@ -9,7 +9,7 @@ import { import {getContractsData} from './helpers/actions'; import {waitForTx} from './__setup.spec'; import {timeLatest} from '../helpers/misc-utils'; -import {tEthereumAddress} from '../helpers/types'; +import {ProtocolErrors, tEthereumAddress} from '../helpers/types'; import {parse} from 'path'; const {expect} = require('chai'); @@ -39,6 +39,7 @@ export const expectRepayWithCollateralEvent = ( }; makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { + const {IS_PAUSED} = ProtocolErrors; it("It's not possible to repayWithCollateral() on a non-active collateral or a non active principal", async () => { const {configurator, weth, pool, users, dai, mockSwapAdapter} = testEnv; const user = users[1]; @@ -347,7 +348,9 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0'); - await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address); + await pool + .connect(user.signer) + .borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address); await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0, user.address);