diff --git a/script/DeployPuffETH.s.sol b/script/DeployPuffETH.s.sol index 007edba..f1702bc 100644 --- a/script/DeployPuffETH.s.sol +++ b/script/DeployPuffETH.s.sol @@ -1,25 +1,27 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { ERC1967Proxy } from "openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; -import { BaseScript } from "script/BaseScript.s.sol"; import { stdJson } from "forge-std/StdJson.sol"; +import { ERC1967Proxy } from "openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; +import { BaseScript } from "./BaseScript.s.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; -import { PufferDepositor } from "src/PufferDepositor.sol"; -import { PufferOracle } from "src/PufferOracle.sol"; -import { PufferVault } from "src/PufferVault.sol"; -import { Timelock } from "src/Timelock.sol"; -import { NoImplementation } from "src/NoImplementation.sol"; -import { PufferDeployment } from "src/structs/PufferDeployment.sol"; -import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; -import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; -import { IStETH } from "src/interface/Lido/IStETH.sol"; -import { ILidoWithdrawalQueue } from "src/interface/Lido/ILidoWithdrawalQueue.sol"; -import { stETHMock } from "test/mocks/stETHMock.sol"; -import { LidoWithdrawalQueueMock } from "test/mocks/LidoWithdrawalQueueMock.sol"; -import { stETHStrategyMock } from "test/mocks/stETHStrategyMock.sol"; -import { EigenLayerManagerMock } from "test/mocks/EigenLayerManagerMock.sol"; +import { PufferDepositor } from "../src/PufferDepositor.sol"; +import { PufferVault } from "../src/PufferVault.sol"; +import { Timelock } from "../src/Timelock.sol"; +import { NoImplementation } from "../src/NoImplementation.sol"; +import { PufferDeployment } from "../src/structs/PufferDeployment.sol"; +import { IEigenLayer } from "../src/interface/EigenLayer/IEigenLayer.sol"; +import { IStrategy } from "../src/interface/EigenLayer/IStrategy.sol"; +import { IStETH } from "../src/interface/Lido/IStETH.sol"; +import { ILidoWithdrawalQueue } from "../src/interface/Lido/ILidoWithdrawalQueue.sol"; +import { stETHMock } from "../test/mocks/stETHMock.sol"; +import { LidoWithdrawalQueueMock } from "../test/mocks/LidoWithdrawalQueueMock.sol"; +import { stETHStrategyMock } from "../test/mocks/stETHStrategyMock.sol"; +import { EigenLayerManagerMock } from "../test/mocks/EigenLayerManagerMock.sol"; import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { IWETH } from "../src/interface/Other/IWETH.sol"; +import { WETH9 } from "../test/mocks/WETH9.sol"; +import { ROLE_ID_UPGRADER, ROLE_ID_OPERATIONS } from "./Roles.sol"; /** * @title DeployPuffer @@ -38,15 +40,13 @@ import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils * PK=${deployer_pk} forge script script/DeployPuffETH.s.sol:DeployPuffETH -vvvv --rpc-url=... --broadcast */ contract DeployPuffETH is BaseScript { - uint64 constant ROLE_ID_UPGRADER = 1; - uint64 constant ROLE_ID_OPERATIONS = 22; - /** * @dev Ethereum Mainnet addresses */ IStrategy internal constant _EIGEN_STETH_STRATEGY = IStrategy(0x93c4b944D05dfe6df7645A86cd2206016c51564D); IEigenLayer internal constant _EIGEN_STRATEGY_MANAGER = IEigenLayer(0x858646372CC42E1A627fcE94aa7A7033e7CF075A); IStETH internal constant _ST_ETH = IStETH(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); + IWETH internal constant _WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); ILidoWithdrawalQueue internal constant _LIDO_WITHDRAWAL_QUEUE = ILidoWithdrawalQueue(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1); @@ -55,7 +55,6 @@ contract DeployPuffETH is BaseScript { PufferDepositor pufferDepositor; PufferDepositor pufferDepositorImplementation; - PufferOracle pufferOracle; Timelock timelock; ERC1967Proxy depositorProxy; @@ -64,6 +63,7 @@ contract DeployPuffETH is BaseScript { AccessManager accessManager; address stETHAddress; + address wethAddress; address operationsMultisig = vm.envOr("OPERATIONS_MULTISIG", makeAddr("operationsMultisig")); address pauserMultisig = vm.envOr("PAUSER_MULTISIG", makeAddr("pauserMultisig")); @@ -87,7 +87,6 @@ contract DeployPuffETH is BaseScript { vaultProxy = new ERC1967Proxy{ salt: pufferVaultSalt }(noImpl, ""); vm.label(address(vaultProxy), "PufferVault"); - // Deploy mock Puffer oracle timelock = new Timelock({ accessManager: address(accessManager), communityMultisig: communityMultisig, @@ -99,12 +98,14 @@ contract DeployPuffETH is BaseScript { { ( IStETH stETH, + IWETH weth, ILidoWithdrawalQueue lidoWithdrawalQueue, IStrategy stETHStrategy, IEigenLayer eigenStrategyManager ) = _getArgs(); stETHAddress = address(stETH); + wethAddress = address(weth); // Deploy implementation contracts pufferVaultImplementation = @@ -142,6 +143,7 @@ contract DeployPuffETH is BaseScript { pufferVaultImplementation: address(pufferVaultImplementation), pufferOracle: address(0), stETH: stETHAddress, + weth: wethAddress, timelock: address(timelock) }); } @@ -184,7 +186,7 @@ contract DeployPuffETH is BaseScript { // Grant roles to operations & community // Operations Multisig has 7 day delay - uint256 delayInSeconds = 604800; // 7 days + uint256 delayInSeconds = 0; // 7 days //@todo this is for testing, real deployment has 7 days delay calldatas[2] = abi.encodeWithSelector( AccessManager.grantRole.selector, ROLE_ID_UPGRADER, operationsMultisig, delayInSeconds ); @@ -245,6 +247,7 @@ contract DeployPuffETH is BaseScript { internal returns ( IStETH stETH, + IWETH weth, ILidoWithdrawalQueue lidoWithdrawalQueue, IStrategy stETHStrategy, IEigenLayer eigenStrategyManager @@ -252,11 +255,13 @@ contract DeployPuffETH is BaseScript { { if (isMainnet()) { stETH = _ST_ETH; + weth = _WETH; lidoWithdrawalQueue = _LIDO_WITHDRAWAL_QUEUE; stETHStrategy = _EIGEN_STETH_STRATEGY; eigenStrategyManager = _EIGEN_STRATEGY_MANAGER; } else { stETH = IStETH(address(new stETHMock())); + weth = new WETH9(); lidoWithdrawalQueue = new LidoWithdrawalQueueMock(); stETHStrategy = new stETHStrategyMock(); eigenStrategyManager = new EigenLayerManagerMock(); diff --git a/script/Roles.sol b/script/Roles.sol new file mode 100644 index 0000000..810fdab --- /dev/null +++ b/script/Roles.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +// Operations & Community multisig have this role +// Operations with 7 day delay, Community 0 +uint64 constant ROLE_ID_UPGRADER = 1; +// Role assigned to Operations Multisig +uint64 constant ROLE_ID_OPERATIONS = 22; + +// Role assigned to the Puffer Protocol +uint64 constant ROLE_ID_PUFFER_PROTOCOL = 1234; +uint64 constant ROLE_ID_DAO = 77; +uint64 constant ROLE_ID_GUARDIANS = 88; +uint64 constant ROLE_ID_PAUSER = 999; diff --git a/script/UpgradePuffETH.s.sol b/script/UpgradePuffETH.s.sol new file mode 100644 index 0000000..fab1056 --- /dev/null +++ b/script/UpgradePuffETH.s.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { stdJson } from "forge-std/StdJson.sol"; +import { BaseScript } from ".//BaseScript.s.sol"; +import { PufferVault } from "../src/PufferVault.sol"; +import { PufferVaultMainnet } from "../src/PufferVaultMainnet.sol"; +import { IEigenLayer } from "../src/interface/EigenLayer/IEigenLayer.sol"; +import { IStrategy } from "../src/interface/EigenLayer/IStrategy.sol"; +import { IStETH } from "../src/interface/Lido/IStETH.sol"; +import { ILidoWithdrawalQueue } from "../src/interface/Lido/ILidoWithdrawalQueue.sol"; +import { stETHMock } from "../test/mocks/stETHMock.sol"; +import { LidoWithdrawalQueueMock } from "../test/mocks/LidoWithdrawalQueueMock.sol"; +import { stETHStrategyMock } from "../test/mocks/stETHStrategyMock.sol"; +import { EigenLayerManagerMock } from "../test/mocks/EigenLayerManagerMock.sol"; +import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { IWETH } from "../src/interface/Other/IWETH.sol"; +import { IPufferOracle } from "../src/interface/IPufferOracle.sol"; +import { WETH9 } from "../test/mocks/WETH9.sol"; +import { Initializable } from "openzeppelin/proxy/utils/Initializable.sol"; +import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; + +/** + * @title UpgradePufETH + * @author Puffer Finance + * @notice Upgrades PufETH + * @dev + * + * + * NOTE: + * + * If you ran the deployment script, but did not `--broadcast` the transaction, it will still update your local chainId-deployment.json file. + * Other scripts will fail because addresses will be updated in deployments file, but the deployment never happened. + * + * BaseScript.sol holds the private key logic, if you don't have `PK` ENV variable, it will use the default one PK from `makeAddr("pufferDeployer")` + * + * PK=${deployer_pk} forge script script/UpgradePuffETH.s.sol:UpgradePuffETH --sig 'run(address)' "VAULTADDRESS" -vvvv --rpc-url=... --broadcast + */ +contract UpgradePuffETH is BaseScript { + /** + * @dev Ethereum Mainnet addresses + */ + IStrategy internal constant _EIGEN_STETH_STRATEGY = IStrategy(0x93c4b944D05dfe6df7645A86cd2206016c51564D); + IEigenLayer internal constant _EIGEN_STRATEGY_MANAGER = IEigenLayer(0x858646372CC42E1A627fcE94aa7A7033e7CF075A); + IStETH internal constant _ST_ETH = IStETH(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); + IWETH internal constant _WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + ILidoWithdrawalQueue internal constant _LIDO_WITHDRAWAL_QUEUE = + ILidoWithdrawalQueue(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1); + + function run(address pufferVault, address accessManager, address pufferOracle) public broadcast { + ( + IStETH stETH, + IWETH weth, + ILidoWithdrawalQueue lidoWithdrawalQueue, + IStrategy stETHStrategy, + IEigenLayer eigenStrategyManager + ) = _getArgs(); + + //@todo this is for tests only + AccessManager(accessManager).grantRole(1, _broadcaster, 0); + + PufferVaultMainnet newImplementation = new PufferVaultMainnet( + stETH, weth, lidoWithdrawalQueue, stETHStrategy, eigenStrategyManager, IPufferOracle(pufferOracle) + ); + + vm.expectEmit(true, true, true, true); + emit Initializable.Initialized(2); + UUPSUpgradeable(pufferVault).upgradeToAndCall( + address(newImplementation), abi.encodeCall(PufferVaultMainnet.initialize, ()) + ); + } + + function _getArgs() + internal + returns ( + IStETH stETH, + IWETH weth, + ILidoWithdrawalQueue lidoWithdrawalQueue, + IStrategy stETHStrategy, + IEigenLayer eigenStrategyManager + ) + { + if (isMainnet()) { + stETH = _ST_ETH; + weth = _WETH; + lidoWithdrawalQueue = _LIDO_WITHDRAWAL_QUEUE; + stETHStrategy = _EIGEN_STETH_STRATEGY; + eigenStrategyManager = _EIGEN_STRATEGY_MANAGER; + } else { + stETH = IStETH(address(new stETHMock())); + weth = new WETH9(); + lidoWithdrawalQueue = new LidoWithdrawalQueueMock(); + stETHStrategy = new stETHStrategyMock(); + eigenStrategyManager = new EigenLayerManagerMock(); + } + } +} diff --git a/src/PufferDepositor.sol b/src/PufferDepositor.sol index 1abe1d4..98112b8 100644 --- a/src/PufferDepositor.sol +++ b/src/PufferDepositor.sol @@ -6,13 +6,13 @@ import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; import { AccessManagedUpgradeable } from "@openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { IStETH } from "src/interface/Lido/IStETH.sol"; -import { IWstETH } from "src/interface/Lido/IWstETH.sol"; -import { PufferVault } from "src/PufferVault.sol"; -import { PufferDepositorStorage } from "src/PufferDepositorStorage.sol"; +import { IStETH } from "./interface/Lido/IStETH.sol"; +import { IWstETH } from "./interface/Lido/IWstETH.sol"; +import { PufferVault } from "./PufferVault.sol"; +import { PufferDepositorStorage } from "./PufferDepositorStorage.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ISushiRouter } from "src/interface/Other/ISushiRouter.sol"; -import { IPufferDepositor } from "src/interface/IPufferDepositor.sol"; +import { ISushiRouter } from "./interface/Other/ISushiRouter.sol"; +import { IPufferDepositor } from "./interface/IPufferDepositor.sol"; /** * @title PufferDepositor diff --git a/src/PufferOracle.sol b/src/PufferOracle.sol deleted file mode 100644 index 8bbd512..0000000 --- a/src/PufferOracle.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.0 <0.9.0; - -/** - * @title PufferOracle - * @author Puffer Finance - * @dev Stores the reported values - * @custom:security-contact security@puffer.fi - */ -contract PufferOracle { - /** - * @notice Emitted when the Guardians update state of the protocol - * @param ethAmount is the ETH amount that is not locked in Beacon chain - * @param lockedETH is the locked ETH amount in Beacon chain - * @param pufETHTotalSupply is the total supply of the pufETH - */ - event BackingUpdated(uint256 ethAmount, uint256 lockedETH, uint256 pufETHTotalSupply, uint256 blockNumber); - - /** - * @dev Number of blocks - */ - // slither-disable-next-line unused-state - uint256 internal constant _UPDATE_INTERVAL = 1; - - /** - * @dev Unlocked ETH amount - * Slot 0 - */ - uint256 ethAmount; - /** - * @dev Locked ETH amount in Beacon Chain - * Slot 1 - */ - uint256 lockedETH; - /** - * @dev pufETH total token supply - * Slot 2 - */ - uint256 pufETHTotalSupply; - /** - * @dev Block number for when the values were updated - * Slot 3 - */ - uint256 lastUpdate; - - /** - * @notice Simulate proofOfReservers from the guardians - */ - function proofOfReserve( - uint256 newEthAmountValue, - uint256 newLockedEthValue, - uint256 pufETHTotalSupplyValue, // @todo what to do with this? - uint256 blockNumber, - uint256 numberOfActiveValidators, - bytes[] calldata guardianSignatures - ) external { - // Check the signatures (reverts if invalid) - // GUARDIAN_MODULE.validateProofOfReserve({ - // ethAmount: ethAmount, - // lockedETH: lockedETH, - // pufETHTotalSupply: pufETHTotalSupply, - // blockNumber: blockNumber, - // numberOfActiveValidators: numberOfActiveValidators, - // guardianSignatures: guardianSignatures - // }); - - // if ((block.number - lastUpdate) < _UPDATE_INTERVAL) { - // revert OutsideUpdateWindow(); - // } - - ethAmount = newEthAmountValue; - lockedETH = newLockedEthValue; - pufETHTotalSupply = pufETHTotalSupply; - lastUpdate = blockNumber; - - emit BackingUpdated(newEthAmountValue, newLockedEthValue, pufETHTotalSupplyValue, blockNumber); - } -} diff --git a/src/PufferVault.sol b/src/PufferVault.sol index b8c7b54..f565aef 100644 --- a/src/PufferVault.sol +++ b/src/PufferVault.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { IPufferVault } from "src/interface/IPufferVault.sol"; +import { IPufferVault } from "./interface/IPufferVault.sol"; import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; -import { IStETH } from "src/interface/Lido/IStETH.sol"; -import { ILidoWithdrawalQueue } from "src/interface/Lido/ILidoWithdrawalQueue.sol"; -import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; -import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; -import { PufferVaultStorage } from "src/PufferVaultStorage.sol"; +import { IStETH } from "./interface/Lido/IStETH.sol"; +import { ILidoWithdrawalQueue } from "./interface/Lido/ILidoWithdrawalQueue.sol"; +import { IEigenLayer } from "./interface/EigenLayer/IEigenLayer.sol"; +import { IStrategy } from "./interface/EigenLayer/IStrategy.sol"; +import { PufferVaultStorage } from "./PufferVaultStorage.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/src/PufferVaultMainnet.sol b/src/PufferVaultMainnet.sol index 4bac789..dd83a8b 100644 --- a/src/PufferVaultMainnet.sol +++ b/src/PufferVaultMainnet.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { PufferVault } from "src/PufferVault.sol"; -import { IStETH } from "src/interface/Lido/IStETH.sol"; -import { ILidoWithdrawalQueue } from "src/interface/Lido/ILidoWithdrawalQueue.sol"; -import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; -import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; -import { IWETH } from "src/interface/Other/IWETH.sol"; +import { PufferVault } from "./PufferVault.sol"; +import { IStETH } from "./interface/Lido/IStETH.sol"; +import { ILidoWithdrawalQueue } from "./interface/Lido/ILidoWithdrawalQueue.sol"; +import { IEigenLayer } from "./interface/EigenLayer/IEigenLayer.sol"; +import { IStrategy } from "./interface/EigenLayer/IStrategy.sol"; +import { IWETH } from "./interface/Other/IWETH.sol"; +import { IPufferOracle } from "./interface/IPufferOracle.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; /** * @title PufferVault @@ -14,20 +16,34 @@ import { IWETH } from "src/interface/Other/IWETH.sol"; * @custom:security-contact security@puffer.fi */ contract PufferVaultMainnet is PufferVault { - // slither-disable-next-line unused-state - IWETH internal constant _WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + error ETHTransferFailed(); + + /** + * Emitted when the daily withdrawal limit is set + * @dev Signature: 0x8d5f7487ce1fd25059bd15204a55ea2c293160362b849a6f9244aec7d5a3700b + */ + event DailyWithdrawalLimitSet(uint96 oldLimit, uint96 newLimit); + + /** + * @dev The Wrapped Ethereum ERC20 token + */ + IWETH internal immutable _WETH; + + /** + * @dev The PufferOracle contract + */ + IPufferOracle public immutable PUFFER_ORACLE; constructor( IStETH stETH, + IWETH weth, ILidoWithdrawalQueue lidoWithdrawalQueue, IStrategy stETHStrategy, - IEigenLayer eigenStrategyManager + IEigenLayer eigenStrategyManager, + IPufferOracle oracle ) PufferVault(stETH, lidoWithdrawalQueue, stETHStrategy, eigenStrategyManager) { - _ST_ETH = stETH; - _LIDO_WITHDRAWAL_QUEUE = lidoWithdrawalQueue; - _EIGEN_STETH_STRATEGY = stETHStrategy; - _EIGEN_STRATEGY_MANAGER = eigenStrategyManager; - // Approve stETH to Lido && EL + _WETH = weth; + PUFFER_ORACLE = oracle; _disableInitializers(); } @@ -35,8 +51,12 @@ contract PufferVaultMainnet is PufferVault { * @notice Changes token from stETH to WETH */ function initialize() public reinitializer(2) { - ERC4626Storage storage $ = _getERC4626StorageInternal(); - $._asset = _WETH; + // In this initialization, we swap out the underlying stETH with WETH + ERC4626Storage storage erc4626Storage = _getERC4626StorageInternal(); + erc4626Storage._asset = _WETH; + + _setDailyWithdrawalLimit(100 ether); + _updateDailyWithdrawals(0); } /** @@ -44,17 +64,50 @@ contract PufferVaultMainnet is PufferVault { * Eventually, stETH will not exist anymore, and the Vault will represent shares of total ETH holdings * ETH to stETH is always 1:1 (stETH is rebasing token) * Sum of EL assets + Vault Assets + * + * NOTE on the native ETH deposit: + * When dealing with NATIVE ETH deposits, we need to deduct callvalue from the balance. + * The contract calculates the amount of shares(pufETH) to mint based on the total assets. + * When a user sends ETH, the msg.value is immediately added to address(this).balance, and address(this.balance) is included in the total assets. + * Because of that we must deduct the callvalue from the balance to avoid the user getting more shares than he should. + * We can't use msg.value in a view function, so we use assembly to get the callvalue. */ function totalAssets() public view virtual override returns (uint256) { + uint256 callValue; + assembly { + callValue := callvalue() + } return _ST_ETH.balanceOf(address(this)) + getELBackingEthAmount() + _WETH.balanceOf(address(this)) - + address(this).balance; //@todo when you add oracle pufferOracle.getLockedEthAmount() + + (address(this).balance - callValue) + PUFFER_ORACLE.getLockedEthAmount(); } - //@todo weth wrapping and unwrapping logic, native ETH deposit method + /** + * @notice Calculates the maximum amount of assets that can be withdrawn by the `owner`. + * @dev This function considers both the remaining daily withdrawal limit and the `owner`'s balance. + * @param owner The address of the owner for which the maximum withdrawal amount is calculated. + * @return maxAssets The maximum amount of assets that can be withdrawn by the `owner`. + */ + function maxWithdraw(address owner) public view virtual override returns (uint256 maxAssets) { + uint256 remainingAssets = getRemainingAssetsDailyWithdrawalLimit(); + uint256 maxUserAssets = previewRedeem(balanceOf(owner)); + return remainingAssets < maxUserAssets ? remainingAssets : maxUserAssets; + } + + /** + * @notice Calculates the maximum amount of shares that can be redeemed by the `owner`. + * @dev This function considers both the remaining daily withdrawal limit in terms of assets and converts it to shares, and the `owner`'s share balance. + * @param owner The address of the owner for which the maximum redeemable shares are calculated. + * @return maxShares The maximum amount of shares that can be redeemed by the `owner`. + */ + function maxRedeem(address owner) public view virtual override returns (uint256 maxShares) { + uint256 remainingShares = previewWithdraw(getRemainingAssetsDailyWithdrawalLimit()); + uint256 userShares = balanceOf(owner); + return remainingShares < userShares ? remainingShares : userShares; + } /** * @notice Withdrawals are allowed an the asset out is WETH - * Copied the original ERC4626 code back to override `PufferVault` + * Copied the original ERC4626 code back to override `PufferVault` + wrap ETH logic */ function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { uint256 maxAssets = maxWithdraw(owner); @@ -62,6 +115,10 @@ contract PufferVaultMainnet is PufferVault { revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); } + _updateDailyWithdrawals(assets); + + _wrapETH(assets); + uint256 shares = previewWithdraw(assets); // solhint-disable-next-line func-named-parameters _withdraw(_msgSender(), receiver, owner, assets, shares); @@ -71,7 +128,7 @@ contract PufferVaultMainnet is PufferVault { /** * @notice Withdrawals are allowed an the asset out is WETH - * Copied the original ERC4626 code back to override `PufferVault` + * Copied the original ERC4626 code back to override `PufferVault` + wrap ETH logic */ function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { uint256 maxShares = maxRedeem(owner); @@ -80,20 +137,111 @@ contract PufferVaultMainnet is PufferVault { } uint256 assets = previewRedeem(shares); + + _updateDailyWithdrawals(assets); + + _wrapETH(assets); + // solhint-disable-next-line func-named-parameters _withdraw(_msgSender(), receiver, owner, assets, shares); return assets; } - // slither-disable-next-line dead-code - function _wrap(uint256 amount) internal { - _WETH.deposit{ value: amount }(); + /** + * @notice Deposits native ETH + */ + function depositETH(address receiver) public payable virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (msg.value > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, msg.value, maxAssets); + } + + uint256 shares = previewDeposit(msg.value); + _mint(receiver, shares); + emit Deposit(_msgSender(), receiver, msg.value, shares); + + return shares; + } + + /** + * @notice Transfers ETH to a specified address + * @dev Restricted to PufferProtocol + * We use it to transfer ETH to PufferModule + * @param to The address to transfer ETH to + * @param ethAmount The amount of ETH to transfer + */ + function transferETH(address to, uint256 ethAmount) external restricted { + // Our Vault holds ETH & WETH + // If we don't have enough ETH for the transfer, unwrap WETH + uint256 ethBalance = address(this).balance; + if (ethBalance < ethAmount) { + _WETH.withdraw(ethAmount - ethBalance); + } + + // slither-disable-next-line arbitrary-send-eth + (bool success,) = to.call{ value: ethAmount }(""); + + if (!success) { + revert ETHTransferFailed(); + } + } + + /** + * @notice Allows the `msg.sender` to burn his shares + * @param shares The amount of shares to burn + */ + function burn(uint256 shares) public restricted { + _burn(msg.sender, shares); + } + + /** + * @notice Sets a new daily withdrawal limit + * @dev Restricted to DAO + * @param newLimit The new daily limit to be set + */ + function setDailyWithdrawalLimit(uint96 newLimit) external restricted { + _setDailyWithdrawalLimit(newLimit); + } + + function getRemainingAssetsDailyWithdrawalLimit() public view virtual returns (uint96) { + VaultStorage storage $ = _getPufferVaultStorage(); + uint96 dailyAssetsWithdrawalLimit = $.dailyAssetsWithdrawalLimit; + uint96 assetsWithdrawnToday = $.assetsWithdrawnToday; + + if (dailyAssetsWithdrawalLimit < assetsWithdrawnToday) { + return 0; + } + return dailyAssetsWithdrawalLimit - assetsWithdrawnToday; + } + + function _wrapETH(uint256 assets) internal { + uint256 wethBalance = _WETH.balanceOf(address(this)); + + if (wethBalance < assets) { + _WETH.deposit{ value: assets - wethBalance }(); + } + } + + /** + * @param withdrawalAmount is the assets amount, not shares + */ + function _updateDailyWithdrawals(uint256 withdrawalAmount) internal { + VaultStorage storage $ = _getPufferVaultStorage(); + + // Check if it's a new day to reset the withdrawal count + if ($.lastWithdrawalDay < block.timestamp / 1 days) { + $.lastWithdrawalDay = uint64(block.timestamp / 1 days); + $.assetsWithdrawnToday = 0; + } + + $.assetsWithdrawnToday += uint96(withdrawalAmount); } - // slither-disable-next-line dead-code - function _unwrap(uint256 amount) internal { - _WETH.withdraw(amount); + function _setDailyWithdrawalLimit(uint96 newLimit) internal { + VaultStorage storage $ = _getPufferVaultStorage(); + emit DailyWithdrawalLimitSet($.dailyAssetsWithdrawalLimit, newLimit); + $.dailyAssetsWithdrawalLimit = newLimit; } function _authorizeUpgrade(address newImplementation) internal virtual override restricted { } diff --git a/src/PufferVaultStorage.sol b/src/PufferVaultStorage.sol index 41781ba..dcb2c3a 100644 --- a/src/PufferVaultStorage.sol +++ b/src/PufferVaultStorage.sol @@ -18,11 +18,16 @@ abstract contract PufferVaultStorage { * +-----------------------------------------------------------+ */ struct VaultStorage { + // 5 Slots for Redemption logic uint256 lidoLockedETH; uint256 eigenLayerPendingWithdrawalSharesAmount; bool isLidoWithdrawal; EnumerableSet.UintSet lidoWithdrawals; EnumerableSet.Bytes32Set eigenLayerWithdrawals; + // 1 Slot for daily withdrawal limits + uint96 dailyAssetsWithdrawalLimit; + uint96 assetsWithdrawnToday; + uint64 lastWithdrawalDay; } // keccak256(abi.encode(uint256(keccak256("puffervault.storage")) - 1)) & ~bytes32(uint256(0xff)) diff --git a/src/interface/EigenLayer/IEigenLayer.sol b/src/interface/EigenLayer/IEigenLayer.sol index 1a349e3..9cce2d0 100644 --- a/src/interface/EigenLayer/IEigenLayer.sol +++ b/src/interface/EigenLayer/IEigenLayer.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0 <0.9.0; import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; -import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; +import { IStrategy } from "./IStrategy.sol"; interface IEigenLayer { /** diff --git a/src/interface/IPufferOracle.sol b/src/interface/IPufferOracle.sol new file mode 100644 index 0000000..9f9bdf0 --- /dev/null +++ b/src/interface/IPufferOracle.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +/** + * @title IPufferOracle + * @author Puffer Finance + * @custom:security-contact security@puffer.fi + */ +interface IPufferOracle { + /** + * @notice Thrown if Guardians try to re-submit the backing data + * @dev Signature "0xf93417f7" + */ + error OutsideUpdateWindow(); + + /** + * @notice Emitted when the Guardians update state of the protocol + * @param blockNumber is the block number of the update + * @param lockedETH is the locked ETH amount in Beacon chain + */ + event BackingUpdated(uint256 indexed blockNumber, uint256 lockedETH); + + /** + * @notice Emitted when the price to mint VT is updated + */ + event ValidatorTicketMintPriceUpdated(uint256 oldPrice, uint256 newPrice); + + /** + * @notice Increases the `lockedETH` amount on the Oracle by 32 ETH + * It is called when the Beacon chain receives a new deposit from PufferProtocol + * The PufferVault balance is decreased by the same amount + */ + function provisionNode() external; + + /** + * @notice Retrieves the current mint price for minting one Validator Ticket + * @return pricePerVT The current mint price + */ + function getValidatorTicketPrice() external view returns (uint256 pricePerVT); + + /** + * @notice Returns the locked ETH amount + * @return lockedEthAmount The amount of ETH locked in Beacon chain + */ + function getLockedEthAmount() external view returns (uint256 lockedEthAmount); + + /** + * @notice Returns true if the number of active Puffer Validators is over the burst threshold + */ + function isOverBurstThreshold() external view returns (bool); +} diff --git a/src/structs/PufferDeployment.sol b/src/structs/PufferDeployment.sol index 53c71cf..e9d7b55 100644 --- a/src/structs/PufferDeployment.sol +++ b/src/structs/PufferDeployment.sol @@ -9,5 +9,6 @@ struct PufferDeployment { address pufferVaultImplementation; address pufferOracle; address stETH; + address weth; address timelock; } diff --git a/test/Integration/PufferTest.integration.t.sol b/test/Integration/PufferTest.integration.t.sol index d7530e8..cc72870 100644 --- a/test/Integration/PufferTest.integration.t.sol +++ b/test/Integration/PufferTest.integration.t.sol @@ -5,9 +5,9 @@ import { Test } from "forge-std/Test.sol"; import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; import { PufferDepositor } from "../../src/PufferDepositor.sol"; import { PufferVaultMainnet } from "../../src/PufferVaultMainnet.sol"; -import { PufferOracle } from "../../src/PufferOracle.sol"; import { IStETH } from "../../src/interface/Lido/IStETH.sol"; import { IPufferDepositor } from "../../src/interface/IPufferDepositor.sol"; +import { MockPufferOracle } from "../mocks/MockPufferOracle.sol"; import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; import { IPufferVault } from "src/interface/IPufferVault.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -20,10 +20,12 @@ import { PufferVault } from "src/PufferVault.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; import { IStETH } from "src/interface/Lido/IStETH.sol"; import { IWstETH } from "src/interface/Lido/IWstETH.sol"; +import { IPufferOracle } from "src/interface/IPufferOracle.sol"; import { ILidoWithdrawalQueue } from "src/interface/Lido/ILidoWithdrawalQueue.sol"; import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; import { Timelock } from "src/Timelock.sol"; +import { IWETH } from "src/interface/Other/IWETH.sol"; contract PufferTest is Test { /** @@ -32,6 +34,7 @@ contract PufferTest is Test { IStrategy internal constant _EIGEN_STETH_STRATEGY = IStrategy(0x93c4b944D05dfe6df7645A86cd2206016c51564D); IEigenLayer internal constant _EIGEN_STRATEGY_MANAGER = IEigenLayer(0x858646372CC42E1A627fcE94aa7A7033e7CF075A); IStETH internal constant _ST_ETH = IStETH(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); + IWETH internal constant _WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IWstETH internal constant _WST_ETH = IWstETH(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0); ILidoWithdrawalQueue internal constant _LIDO_WITHDRAWAL_QUEUE = ILidoWithdrawalQueue(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1); @@ -57,12 +60,10 @@ contract PufferTest is Test { PufferDepositor public pufferDepositor; PufferVault public pufferVault; AccessManager public accessManager; - PufferOracle public pufferOracle; Timelock public timelock; // Lido contract (stETH) IStETH stETH = IStETH(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); - IERC20 internal constant _WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // EL Strategy Manager IEigenLayer eigenStrategyManager = IEigenLayer(0x858646372CC42E1A627fcE94aa7A7033e7CF075A); @@ -112,7 +113,6 @@ contract PufferTest is Test { pufferDepositor = PufferDepositor(payable(deployment.pufferDepositor)); pufferVault = PufferVault(payable(deployment.pufferVault)); accessManager = AccessManager(payable(deployment.accessManager)); - pufferOracle = PufferOracle(payable(deployment.pufferOracle)); timelock = Timelock(payable(deployment.timelock)); COMMUNITY_MULTISIG = timelock.COMMUNITY_MULTISIG(); @@ -194,10 +194,13 @@ contract PufferTest is Test { } function _upgradeToMainnetPuffer() internal { + MockPufferOracle mockOracle = new MockPufferOracle(); + // Simulate that our deployed oracle becomes active and starts posting results of Puffer staking // At this time, we stop accepting stETH, and we accept only native ETH - PufferVaultMainnet newImplementation = - new PufferVaultMainnet(_ST_ETH, _LIDO_WITHDRAWAL_QUEUE, _EIGEN_STETH_STRATEGY, _EIGEN_STRATEGY_MANAGER); + PufferVaultMainnet newImplementation = new PufferVaultMainnet( + _ST_ETH, _WETH, _LIDO_WITHDRAWAL_QUEUE, _EIGEN_STETH_STRATEGY, _EIGEN_STRATEGY_MANAGER, mockOracle + ); // Community multisig can do thing instantly vm.startPrank(COMMUNITY_MULTISIG); @@ -356,40 +359,6 @@ contract PufferTest is Test { assertEq(minted, 0, "got 0 back"); } - function test_upgrade_from_operations_multisig() public { - PufferVaultMainnet newImplementation = - new PufferVaultMainnet(_ST_ETH, _LIDO_WITHDRAWAL_QUEUE, _EIGEN_STETH_STRATEGY, _EIGEN_STRATEGY_MANAGER); - - // Community multisig can do thing instantly, this one has a delay - vm.startPrank(OPERATIONS_MULTISIG); - - bytes memory initializerCallData = abi.encodeCall(PufferVaultMainnet.initialize, ()); - - // It is not allowed to execute before the timelock - vm.expectRevert(); - accessManager.execute( - address(pufferVault), - abi.encodeCall(UUPSUpgradeable.upgradeToAndCall, (address(newImplementation), initializerCallData)) - ); - - // 1. Schedule the upgrade - accessManager.schedule( - address(pufferVault), - abi.encodeCall(UUPSUpgradeable.upgradeToAndCall, (address(newImplementation), initializerCallData)), - 0 - ); - - vm.warp(block.timestamp + 7 days); - - vm.expectEmit(true, true, true, true); - emit Initializable.Initialized(2); - // 2. Execute the upgrade - accessManager.execute( - address(pufferVault), - abi.encodeCall(UUPSUpgradeable.upgradeToAndCall, (address(newImplementation), initializerCallData)) - ); - } - function test_upgrade_to_mainnet() public giveToken(MAKER_VAULT, address(_WETH), eve, 100 ether) { // Test pre-mainnet version test_minting_and_lido_rebasing(); diff --git a/test/Integration/PufferVault.fork.t.sol b/test/Integration/PufferVault.fork.t.sol index 9f9ffb7..75011bf 100644 --- a/test/Integration/PufferVault.fork.t.sol +++ b/test/Integration/PufferVault.fork.t.sol @@ -32,7 +32,7 @@ contract PufferMainnetTest is Test { address OPERATIONS_MULTISIG; function setUp() public { - vm.createSelectFork(vm.rpcUrl("mainnet")); + vm.createSelectFork(vm.rpcUrl("mainnet"), 19185685); _setupContracts(); } diff --git a/test/mocks/EigenLayerManagerMock.sol b/test/mocks/EigenLayerManagerMock.sol index 0064b76..e67267d 100644 --- a/test/mocks/EigenLayerManagerMock.sol +++ b/test/mocks/EigenLayerManagerMock.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; +import { IEigenLayer } from "../../src/interface/EigenLayer/IEigenLayer.sol"; import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; -import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; +import { IStrategy } from "../../src/interface/EigenLayer/IStrategy.sol"; contract EigenLayerManagerMock is IEigenLayer { function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount) external returns (uint256 shares) { } diff --git a/test/mocks/LidoWithdrawalQueueMock.sol b/test/mocks/LidoWithdrawalQueueMock.sol index d5cc9d4..89a27b1 100644 --- a/test/mocks/LidoWithdrawalQueueMock.sol +++ b/test/mocks/LidoWithdrawalQueueMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { ILidoWithdrawalQueue } from "src/interface/Lido/ILidoWithdrawalQueue.sol"; +import { ILidoWithdrawalQueue } from "../../src/interface/Lido/ILidoWithdrawalQueue.sol"; contract LidoWithdrawalQueueMock is ILidoWithdrawalQueue { function requestWithdrawals(uint256[] calldata _amounts, address _owner) diff --git a/test/mocks/MockPufferOracle.sol b/test/mocks/MockPufferOracle.sol new file mode 100644 index 0000000..3f9ffe1 --- /dev/null +++ b/test/mocks/MockPufferOracle.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { IPufferOracle } from "../../src/interface/IPufferOracle.sol"; + +/** + * @title MockPufferOracle + * @author Puffer Finance + * @custom:security-contact security@puffer.fi + */ +contract MockPufferOracle is IPufferOracle { + /** + * @dev Number of blocks + */ + // slither-disable-next-line unused-state + uint256 internal constant _UPDATE_INTERVAL = 1; + + /** + * @dev Locked ETH amount in Beacon Chain + * Slot 1 + */ + uint256 public lockedETH; + /** + * @dev Block number for when the values were updated + * Slot 2 + */ + uint256 public lastUpdate; + + uint256 public numberOfActiveValidators; + + /** + * @notice Simulate proofOfReservers from the guardians + */ + function proofOfReserve( + uint256 newLockedEthValue, + uint256 blockNumber, + uint256 newNumberOfActiveValidators, + bytes[] calldata guardianSignatures + ) external { + if ((block.number - lastUpdate) < _UPDATE_INTERVAL) { + revert OutsideUpdateWindow(); + } + + lockedETH = newLockedEthValue; + lastUpdate = blockNumber; + numberOfActiveValidators = newNumberOfActiveValidators; + + emit BackingUpdated(newLockedEthValue, blockNumber); + } + + function provisionNode() external { } + + function getValidatorTicketPrice() external view returns (uint256 pricePerVT) { } + + function getLockedEthAmount() external view returns (uint256 lockedEthAmount) { } + + function isOverBurstThreshold() external view returns (bool) { } +} diff --git a/test/mocks/WETH9.sol b/test/mocks/WETH9.sol new file mode 100644 index 0000000..22e90a3 --- /dev/null +++ b/test/mocks/WETH9.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { IWETH } from "../../src/interface/Other/IWETH.sol"; + +contract WETH9 is IWETH { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + receive() external payable { + deposit(); + } + + fallback() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/test/mocks/stETHStrategyMock.sol b/test/mocks/stETHStrategyMock.sol index 0634363..1ac62d1 100644 --- a/test/mocks/stETHStrategyMock.sol +++ b/test/mocks/stETHStrategyMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; +import { IStrategy } from "../../src/interface/EigenLayer/IStrategy.sol"; contract stETHStrategyMock is IStrategy { /** diff --git a/test/unit/PufETH.t.sol b/test/unit/PufETH.t.sol index 7b82cfa..1bb441a 100644 --- a/test/unit/PufETH.t.sol +++ b/test/unit/PufETH.t.sol @@ -2,22 +2,20 @@ pragma solidity >=0.8.0 <0.9.0; import "erc4626-tests/ERC4626.test.sol"; -import { IStETH } from "src/interface/Lido/IStETH.sol"; -import { IPufferVault } from "src/interface/IPufferVault.sol"; +import { IStETH } from "../../src/interface/Lido/IStETH.sol"; +import { IPufferVault } from "../../src/interface/IPufferVault.sol"; import { IAccessManaged } from "openzeppelin/access/manager/IAccessManaged.sol"; -import { PufferDepositor } from "src/PufferDepositor.sol"; -import { PufferOracle } from "src/PufferOracle.sol"; -import { PufferVault } from "src/PufferVault.sol"; +import { PufferDepositor } from "../../src/PufferDepositor.sol"; +import { PufferVault } from "../../src/PufferVault.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; import { stETHMock } from "../mocks/stETHMock.sol"; -import { PufferDeployment } from "src/structs/PufferDeployment.sol"; +import { PufferDeployment } from "../../src/structs/PufferDeployment.sol"; import { DeployPuffETH } from "script/DeployPuffETH.s.sol"; contract PufETHTest is ERC4626Test { PufferDepositor public pufferDepositor; PufferVault public pufferVault; AccessManager public accessManager; - PufferOracle public pufferOracle; IStETH public stETH; address operationsMultisig = makeAddr("operations"); @@ -29,7 +27,6 @@ contract PufETHTest is ERC4626Test { pufferDepositor = PufferDepositor(payable(deployment.pufferDepositor)); pufferVault = PufferVault(payable(deployment.pufferVault)); accessManager = AccessManager(payable(deployment.accessManager)); - pufferOracle = PufferOracle(payable(deployment.pufferOracle)); stETH = IStETH(payable(deployment.stETH)); _underlying_ = address(stETH); diff --git a/test/unit/Timelock.t.sol b/test/unit/Timelock.t.sol index 087d9e5..20ca64a 100644 --- a/test/unit/Timelock.t.sol +++ b/test/unit/Timelock.t.sol @@ -4,7 +4,6 @@ pragma solidity >=0.8.0 <0.9.0; import { Test } from "forge-std/Test.sol"; import { PufferDepositor } from "src/PufferDepositor.sol"; import { Timelock } from "src/Timelock.sol"; -import { PufferOracle } from "src/PufferOracle.sol"; import { PufferVault } from "src/PufferVault.sol"; import { stETHMock } from "test/mocks/stETHMock.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; @@ -16,7 +15,6 @@ contract TimelockTest is Test { PufferDepositor public pufferDepositor; PufferVault public pufferVault; AccessManager public accessManager; - PufferOracle public pufferOracle; stETHMock public stETH; Timelock public timelock; @@ -26,7 +24,6 @@ contract TimelockTest is Test { pufferDepositor = PufferDepositor(payable(deployment.pufferDepositor)); pufferVault = PufferVault(payable(deployment.pufferVault)); accessManager = AccessManager(payable(deployment.accessManager)); - pufferOracle = PufferOracle(payable(deployment.pufferOracle)); stETH = stETHMock(payable(deployment.stETH)); timelock = Timelock(payable(deployment.timelock)); }