From e339b575a757580a41bdf62d007fa8d133608d40 Mon Sep 17 00:00:00 2001 From: Lukas Date: Mon, 9 Sep 2024 18:54:15 +0200 Subject: [PATCH] feat: use permissionless rescuable (#20) --- lib/solidity-utils | 2 +- .../procedures/AaveV3HelpersProcedureTwo.sol | 3 +- .../ERC4626StataTokenUpgradeable.sol | 2 +- .../contracts/static-a-token/StataTokenV2.sol | 29 +++++++++--- .../static-a-token/interfaces/IAToken.sol | 2 + .../static-a-token/StataTokenV2Rescuable.sol | 44 +++++++++++-------- tests/periphery/static-a-token/TestBase.sol | 2 +- 7 files changed, 55 insertions(+), 29 deletions(-) diff --git a/lib/solidity-utils b/lib/solidity-utils index 58c52433..a842c363 160000 --- a/lib/solidity-utils +++ b/lib/solidity-utils @@ -1 +1 @@ -Subproject commit 58c52433220656344c3c44f63a5ba38b5edeacec +Subproject commit a842c36308e76b8202a46962a6c2d59daceb640a diff --git a/src/deployments/contracts/procedures/AaveV3HelpersProcedureTwo.sol b/src/deployments/contracts/procedures/AaveV3HelpersProcedureTwo.sol index e8d40b76..742bc971 100644 --- a/src/deployments/contracts/procedures/AaveV3HelpersProcedureTwo.sol +++ b/src/deployments/contracts/procedures/AaveV3HelpersProcedureTwo.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.0; import '../../interfaces/IMarketReportTypes.sol'; -import {TransparentProxyFactory, ITransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {ITransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; import {StataTokenV2} from 'aave-v3-periphery/contracts/static-a-token/StataTokenV2.sol'; import {StataTokenFactory} from 'aave-v3-periphery/contracts/static-a-token/StataTokenFactory.sol'; import {IErrors} from '../../interfaces/IErrors.sol'; diff --git a/src/periphery/contracts/static-a-token/ERC4626StataTokenUpgradeable.sol b/src/periphery/contracts/static-a-token/ERC4626StataTokenUpgradeable.sol index d6be297a..91603154 100644 --- a/src/periphery/contracts/static-a-token/ERC4626StataTokenUpgradeable.sol +++ b/src/periphery/contracts/static-a-token/ERC4626StataTokenUpgradeable.sol @@ -115,7 +115,7 @@ abstract contract ERC4626StataTokenUpgradeable is ERC4626Upgradeable, IERC4626St } ///@inheritdoc IERC4626StataToken - function aToken() external view returns (IERC20) { + function aToken() public view returns (IERC20) { ERC4626StataTokenStorage storage $ = _getERC4626StataTokenStorage(); return $._aToken; } diff --git a/src/periphery/contracts/static-a-token/StataTokenV2.sol b/src/periphery/contracts/static-a-token/StataTokenV2.sol index e984b2b6..cab1c6ba 100644 --- a/src/periphery/contracts/static-a-token/StataTokenV2.sol +++ b/src/periphery/contracts/static-a-token/StataTokenV2.sol @@ -3,12 +3,14 @@ pragma solidity ^0.8.0; import {ERC20Upgradeable, ERC20PermitUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol'; import {PausableUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol'; -import {IRescuable, Rescuable} from 'solidity-utils/contracts/utils/Rescuable.sol'; +import {IPermissionlessRescuable, PermissionlessRescuable} from 'solidity-utils/contracts/utils/PermissionlessRescuable.sol'; +import {IRescuableBase, RescuableBase} from 'solidity-utils/contracts/utils/RescuableBase.sol'; import {IACLManager} from '../../../core/contracts/interfaces/IACLManager.sol'; -import {ERC4626Upgradeable, ERC4626StataTokenUpgradeable, IPool} from './ERC4626StataTokenUpgradeable.sol'; +import {ERC4626Upgradeable, ERC4626StataTokenUpgradeable, IPool, Math, IERC20} from './ERC4626StataTokenUpgradeable.sol'; import {ERC20AaveLMUpgradeable, IRewardsController} from './ERC20AaveLMUpgradeable.sol'; import {IStataTokenV2} from './interfaces/IStataTokenV2.sol'; +import {IAToken} from './interfaces/IAToken.sol'; /** * @title StataTokenV2 @@ -20,9 +22,11 @@ contract StataTokenV2 is ERC20AaveLMUpgradeable, ERC4626StataTokenUpgradeable, PausableUpgradeable, - Rescuable, + PermissionlessRescuable, IStataTokenV2 { + using Math for uint256; + constructor( IPool pool, IRewardsController rewardsController @@ -53,9 +57,22 @@ contract StataTokenV2 is else _unpause(); } - /// @inheritdoc IRescuable - function whoCanRescue() public view override returns (address) { - return POOL_ADDRESSES_PROVIDER.getACLAdmin(); + /// @inheritdoc IPermissionlessRescuable + function whoShouldReceiveFunds() public view override returns (address) { + return IAToken(address(aToken())).RESERVE_TREASURY_ADDRESS(); + } + + /// @inheritdoc IRescuableBase + function maxRescue( + address asset + ) public view override(IRescuableBase, RescuableBase) returns (uint256) { + IERC20 cachedAToken = aToken(); + if (asset == address(cachedAToken)) { + uint256 requiredBacking = _convertToAssets(totalSupply(), Math.Rounding.Ceil); + uint256 balance = cachedAToken.balanceOf(address(this)); + return balance > requiredBacking ? balance - requiredBacking : 0; + } + return type(uint256).max; } ///@inheritdoc IStataTokenV2 diff --git a/src/periphery/contracts/static-a-token/interfaces/IAToken.sol b/src/periphery/contracts/static-a-token/interfaces/IAToken.sol index 31e9a805..7d58f563 100644 --- a/src/periphery/contracts/static-a-token/interfaces/IAToken.sol +++ b/src/periphery/contracts/static-a-token/interfaces/IAToken.sol @@ -8,6 +8,8 @@ interface IAToken { function UNDERLYING_ASSET_ADDRESS() external view returns (address); + function RESERVE_TREASURY_ADDRESS() external view returns (address); + /** * @notice Returns the scaled total supply of the scaled balance token. Represents sum(debt/index) * @return The scaled total supply diff --git a/tests/periphery/static-a-token/StataTokenV2Rescuable.sol b/tests/periphery/static-a-token/StataTokenV2Rescuable.sol index e43b14d8..9dd59f90 100644 --- a/tests/periphery/static-a-token/StataTokenV2Rescuable.sol +++ b/tests/periphery/static-a-token/StataTokenV2Rescuable.sol @@ -1,31 +1,37 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.10; -import {IRescuable} from 'solidity-utils/contracts/utils/Rescuable.sol'; +import {IAToken} from '../../../src/periphery/contracts/static-a-token/StataTokenV2.sol'; import {BaseTest} from './TestBase.sol'; contract StataTokenV2RescuableTest is BaseTest { - function test_whoCanRescue() external view { - assertEq(IRescuable(address(stataTokenV2)).whoCanRescue(), poolAdmin); - } + event ERC20Rescued( + address indexed caller, + address indexed token, + address indexed to, + uint256 amount + ); - function test_rescuable_shouldRevertForInvalidCaller() external { + function test_rescuable_shouldTransferAssetsToCollector() external { deal(tokenList.usdx, address(stataTokenV2), 1 ether); - vm.expectRevert('ONLY_RESCUE_GUARDIAN'); - IRescuable(address(stataTokenV2)).emergencyTokenTransfer( - tokenList.usdx, - address(this), - 1 ether - ); + stataTokenV2.emergencyTokenTransfer(tokenList.usdx, 1 ether); } - function test_rescuable_shouldSuceedForOwner() external { - deal(tokenList.usdx, address(stataTokenV2), 1 ether); - vm.startPrank(poolAdmin); - IRescuable(address(stataTokenV2)).emergencyTokenTransfer( - tokenList.usdx, - address(this), - 1 ether - ); + function test_rescuable_shouldWorkForAToken() external { + _fundAToken(1 ether, address(stataTokenV2)); + stataTokenV2.emergencyTokenTransfer(aToken, 1 ether); + } + + function test_rescuable_shouldNotCauseInsolvency(uint256 donation, uint256 stake) external { + vm.assume(donation != 0 && donation <= type(uint96).max); + vm.assume(stake != 0 && stake <= type(uint96).max); + _fundAToken(donation, address(stataTokenV2)); + _fund4626(stake, address(this)); + + address treasury = IAToken(aToken).RESERVE_TREASURY_ADDRESS(); + + vm.expectEmit(true, true, true, true); + emit ERC20Rescued(address(this), aToken, treasury, donation); + stataTokenV2.emergencyTokenTransfer(aToken, donation + stake); } } diff --git a/tests/periphery/static-a-token/TestBase.sol b/tests/periphery/static-a-token/TestBase.sol index 0365fb21..55deaa3d 100644 --- a/tests/periphery/static-a-token/TestBase.sol +++ b/tests/periphery/static-a-token/TestBase.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.10; import {IERC20Metadata, IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol'; -import {ITransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import {ITransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol'; import {StataTokenFactory} from '../../../src/periphery/contracts/static-a-token/StataTokenFactory.sol'; import {StataTokenV2} from '../../../src/periphery/contracts/static-a-token/StataTokenV2.sol'; import {IERC20AaveLM} from '../../../src/periphery/contracts/static-a-token/interfaces/IERC20AaveLM.sol';