From bbc11eb092eb97505e59f4bde94f6ceee03a0ffe Mon Sep 17 00:00:00 2001 From: David Racero Date: Mon, 14 Sep 2020 13:03:39 +0200 Subject: [PATCH] Added pausable to Pool actions and aTokens at transfer, triggered by LendingPoolConfigurator. Added basic test to aToken transfer. --- contracts/interfaces/ILendingPool.sol | 15 +++++ contracts/lendingpool/LendingPool.sol | 51 +++++++++++++---- .../lendingpool/LendingPoolConfigurator.sol | 14 +++++ contracts/tokenization/AToken.sol | 47 ++++++++-------- package.json | 1 + test/atoken-transfer.spec.ts | 55 ++++++++++++++++++- 6 files changed, 145 insertions(+), 38 deletions(-) diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index b14f5cb8..bf03e296 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -129,6 +129,15 @@ interface ILendingPool { address liquidator, bool receiveAToken ); + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); /** * @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens) @@ -374,4 +383,10 @@ interface ILendingPool { ) external view returns (bool); function getReserves() external view returns (address[] memory); + + function pause() external; + + function unpause() external; + + function isPaused() external view returns (bool); } diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 814230ef..3c7b63bd 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -4,6 +4,7 @@ pragma experimental ABIEncoderV2; import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol'; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {Pausable} from '@openzeppelin/contracts/utils/Pausable.sol'; import { VersionedInitializable } from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol'; @@ -31,7 +32,7 @@ import {ILendingPool} from '../interfaces/ILendingPool.sol'; * @author Aave **/ -contract LendingPool is VersionedInitializable, ILendingPool { +contract LendingPool is VersionedInitializable, Pausable, ILendingPool { using SafeMath for uint256; using WadRayMath for uint256; using ReserveLogic for ReserveLogic.ReserveData; @@ -93,7 +94,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 amount, address onBehalfOf, uint16 referralCode - ) external override { + ) external override whenNotPaused { ReserveLogic.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateDeposit(reserve, amount); @@ -121,7 +122,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param amount the underlying amount to be redeemed **/ - function withdraw(address asset, uint256 amount) external override { + function withdraw(address asset, uint256 amount) external override whenNotPaused { ReserveLogic.ReserveData storage reserve = _reserves[asset]; address aToken = reserve.aTokenAddress; @@ -172,7 +173,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 amount, uint256 interestRateMode, uint16 referralCode - ) external override { + ) external override whenNotPaused { _executeBorrow( ExecuteBorrowParams( asset, @@ -199,7 +200,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 amount, uint256 rateMode, address onBehalfOf - ) external override { + ) external override whenNotPaused { _executeRepay(asset, msg.sender, amount, rateMode, onBehalfOf); } @@ -260,7 +261,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param asset the address of the reserve on which the user borrowed * @param rateMode the rate mode that the user wants to swap **/ - function swapBorrowRateMode(address asset, uint256 rateMode) external override { + function swapBorrowRateMode(address asset, uint256 rateMode) external override whenNotPaused { ReserveLogic.ReserveData storage reserve = _reserves[asset]; (uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve); @@ -303,7 +304,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param user the address of the user to be rebalanced **/ - function rebalanceStableBorrowRate(address asset, address user) external override { + function rebalanceStableBorrowRate(address asset, address user) external override whenNotPaused { ReserveLogic.ReserveData storage reserve = _reserves[asset]; IStableDebtToken stableDebtToken = IStableDebtToken(reserve.stableDebtTokenAddress); @@ -348,7 +349,11 @@ contract LendingPool is VersionedInitializable, ILendingPool { * @param asset the address of the reserve * @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise. **/ - function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override { + function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) + external + override + whenNotPaused + { ReserveLogic.ReserveData storage reserve = _reserves[asset]; ValidationLogic.validateSetUseReserveAsCollateral( @@ -384,7 +389,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { address user, uint256 purchaseAmount, bool receiveAToken - ) external override { + ) external override whenNotPaused { address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager(); //solium-disable-next-line @@ -440,7 +445,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 principalAmount, address receiver, bytes calldata params - ) external override { + ) external override whenNotPaused { require(!_flashLiquidationLocked, Errors.REENTRANCY_NOT_ALLOWED); _flashLiquidationLocked = true; @@ -487,7 +492,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { uint256 mode, bytes calldata params, uint16 referralCode - ) external override { + ) external override whenNotPaused { ReserveLogic.ReserveData storage reserve = _reserves[asset]; FlashLoanLocalVars memory vars; @@ -706,7 +711,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { address stableDebtAddress, address variableDebtAddress, address interestRateStrategyAddress - ) external override onlyLendingPoolConfigurator { + ) external override onlyLendingPoolConfigurator whenNotPaused { _reserves[asset].init( aTokenAddress, stableDebtAddress, @@ -726,6 +731,7 @@ contract LendingPool is VersionedInitializable, ILendingPool { external override onlyLendingPoolConfigurator + whenNotPaused { _reserves[asset].interestRateStrategyAddress = rateStrategyAddress; } @@ -908,4 +914,25 @@ contract LendingPool is VersionedInitializable, ILendingPool { function getAddressesProvider() external view returns (ILendingPoolAddressesProvider) { return _addressesProvider; } + + /** + * @dev pause all the Lending Pool actions + */ + function pause() external override onlyLendingPoolConfigurator { + _pause(); + } + + /** + * @dev unpause all the Lending Pool actions + */ + function unpause() external override onlyLendingPoolConfigurator { + _unpause(); + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function isPaused() public override view returns (bool) { + return Pausable.paused(); + } } diff --git a/contracts/lendingpool/LendingPoolConfigurator.sol b/contracts/lendingpool/LendingPoolConfigurator.sol index e9c0a292..4eaf3237 100644 --- a/contracts/lendingpool/LendingPoolConfigurator.sol +++ b/contracts/lendingpool/LendingPoolConfigurator.sol @@ -582,4 +582,18 @@ contract LendingPoolConfigurator is VersionedInitializable { proxy.upgradeToAndCall(implementation, params); } + + /** + * @dev pauses LendingPool actions + **/ + function pausePool() external onlyLendingPoolManager { + pool.pause(); + } + + /** + * @dev unpauses LendingPool actions + **/ + function unpausePool() external onlyLendingPoolManager { + pool.unpause(); + } } diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index d4558836..758da2e4 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -67,7 +67,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { uint256 amount, uint256 index ) external override onlyLendingPool { - uint256 currentBalance = balanceOf(user); require(amount <= currentBalance, Errors.INVALID_ATOKEN_BALANCE); @@ -79,7 +78,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken { //transfers the underlying to the target ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount); - emit Burn(msg.sender, receiverOfUnderlying, amount, index); } @@ -89,13 +87,15 @@ contract AToken is VersionedInitializable, ERC20, 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(user, scaledAmount); emit Mint(user, amount, index); } @@ -119,7 +119,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { /** * @dev calculates the balance of the user, which is the - * principal balance + interest generated by the principal balance + * principal balance + interest generated by the principal balance * @param user the user for which the balance is being calculated * @return the total balance of the user **/ @@ -150,9 +150,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken { return 0; } - return - currentSupplyScaled - .rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)); + return currentSupplyScaled.rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS)); } /** @@ -162,16 +160,16 @@ contract AToken is VersionedInitializable, ERC20, IAToken { * @return true if the user can transfer amount, false otherwise **/ function isTransferAllowed(address user, uint256 amount) public override view returns (bool) { - return POOL.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount); + return !POOL.isPaused() && POOL.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount); } /** - * @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 @@ -187,8 +185,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken { address to, uint256 amount, bool validate - ) internal { - if(validate){ + ) internal { + if (validate) { require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED); } @@ -199,17 +197,16 @@ contract AToken is VersionedInitializable, ERC20, 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/package.json b/package.json index 3da86f23..220a21b4 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "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", "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 73c299ad..5634ae9e 100644 --- a/test/atoken-transfer.spec.ts +++ b/test/atoken-transfer.spec.ts @@ -45,7 +45,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(); @@ -81,4 +80,58 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => { ).to.be.revertedWith(TRANSFER_NOT_ALLOWED); }); + 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.pausePool(); + + // User 0 tries the transfer to User 1 + await expect( + aDai.connect(users[0].signer).transfer(users[1].address, amountDAItoDeposit) + ).to.revertedWith(TRANSFER_NOT_ALLOWED); + + 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.unpausePool(); + + // 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 + ); + }); });