From 799a60aae0827d8a2b2f48d37cee3f04b61d6456 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Wed, 4 Sep 2024 18:09:04 -0500 Subject: [PATCH] Refactored the `verifyDeposit` and `verifyWithdrawal` functions (#1156) * Added a test suite for the stUSD instance * Added a deployment script * Updated the deployment configuration of the stUSD market * Updated the time stretch * Implemented a base instance test for Morpho Blue * Refactored the Morpho Blue instance tests * Added an integration test for the Morpho Blue wstETH/USDA market * Added a deployment script for Morpho Blue wstETH/USDA * Addressed FIXMEs * Updated the time stretch * Addressed review feedback from @mcclurejt * Fixed deployments scripts and linter * Added a deployment script for Morpho Blue wstETH/USDA * Refactored the `verifyDeposit` and `verifyWithdrawal` logic * Addressed review feedback from @mcclurejt * Addressed remaining review feedback from @mcclurejt * Deployed the new pools --- .../src/factory/HyperdriveCreate2Factory.sol | 56 --- contracts/src/libraries/Constants.sol | 2 +- deployments.json | 125 ++++-- hardhat.config.mainnet.ts | 4 +- .../config/mainnet/erc4626-coordinator.ts | 1 + .../mainnet/morpho-blue-wsteth-usda-182day.ts | 19 +- tasks/deploy/config/mainnet/stusd-182day.ts | 18 +- tasks/deploy/verify.ts | 7 +- test/instances/aave/AaveHyperdrive.t.sol | 195 +-------- test/instances/chainlink/ChainlinkTest.t.sol | 253 ++++-------- test/instances/eeth/EETHHyperdrive.t.sol | 291 +++++--------- .../ERC4626HyperdriveInstanceTest.t.sol | 223 +---------- test/instances/erc4626/SUSDe.t.sol | 133 ++++--- test/instances/erc4626/StUSD.t.sol | 115 +++--- test/instances/erc4626/sxDai.t.sol | 115 +++--- test/instances/ezETH/EzETHHyperdrive.t.sol | 271 +++++-------- .../ezeth-linea/EzETHLineaTest.t.sol | 247 ++++-------- test/instances/lseth/LsETHHyperdrive.t.sol | 253 +++++------- .../MorphoBlueHyperdriveInstanceTest.t.sol | 158 -------- .../MorphoBlue_USDe_DAI_Hyperdrive.t.sol | 5 +- .../MorphoBlue_sUSDe_DAI_Hyperdrive.t.sol | 5 +- .../MorphoBlue_wstETH_USDA_Hyperdrive.t.sol | 5 +- .../MorphoBlue_wstETH_USDC_Hyperdrive.t.sol | 5 +- test/instances/reth/RETHHyperdrive.t.sol | 285 +++++--------- .../rseth-linea/RsETHLineaHyperdrive.t.sol | 291 ++++---------- test/instances/steth/StETHHyperdrive.t.sol | 284 ++++---------- test/utils/InstanceTest.sol | 369 +++++++++++++++--- 27 files changed, 1344 insertions(+), 2391 deletions(-) delete mode 100644 contracts/src/factory/HyperdriveCreate2Factory.sol diff --git a/contracts/src/factory/HyperdriveCreate2Factory.sol b/contracts/src/factory/HyperdriveCreate2Factory.sol deleted file mode 100644 index 1a53f7c01..000000000 --- a/contracts/src/factory/HyperdriveCreate2Factory.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.22; - -import { Create2 } from "openzeppelin/utils/Create2.sol"; - -/// @author DELV -/// @title HyperdriveCreate2Factory -/// @notice Uses Create2 to deploy a contract to a precomputable address that -/// is only dependent on the contract's bytecode and the provided salt. -/// @custom:disclaimer The language used in this code is for coding convenience -/// only, and is not intended to, and does not, have any -/// particular legal or regulatory significance. -contract HyperdriveCreate2Factory { - /// @notice Deploy the contract with the provided creation code to a - /// precomputable address determined by only the salt and bytecode. - /// @param _salt Salt to use for the deployment. - /// @param _creationCode Creation code of the contract to deploy. - /// @return deployed Address of the deployed contract. - function deploy( - bytes32 _salt, - bytes memory _creationCode - ) external payable returns (address deployed) { - return Create2.deploy(msg.value, _salt, _creationCode); - } - - /// @notice Deploy the contract with the provided creation code to a - /// precomputable address determined by only the deployer address - /// and salt. - /// @param _salt Salt to use for the deployment. - /// @param _creationCode Creation code of the contract to deploy. - /// @param _initializationCode Encoded function data to be called on the - /// newly deployed contract. - /// @return deployed Address of the deployed contract. - function deploy( - bytes32 _salt, - bytes memory _creationCode, - bytes memory _initializationCode - ) external payable returns (address deployed) { - deployed = Create2.deploy(msg.value, _salt, _creationCode); - (bool success, ) = deployed.call(_initializationCode); - require(success, "FAILED_INITIALIZATION"); - return deployed; - } - - /// @notice Use the deployer address and salt to compute the address of a - /// contract deployed via Create2. - /// @param _salt Salt of the deployed contract. - /// @param _bytecodeHash Hash of the contract bytecode. - /// @return Address of the Create2 deployed contract. - function getDeployed( - bytes32 _salt, - bytes32 _bytecodeHash - ) external view returns (address) { - return Create2.computeAddress(_salt, _bytecodeHash); - } -} diff --git a/contracts/src/libraries/Constants.sol b/contracts/src/libraries/Constants.sol index a9f1ef648..33ab6ba8d 100644 --- a/contracts/src/libraries/Constants.sol +++ b/contracts/src/libraries/Constants.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @dev The version of the contracts. -string constant VERSION = "v1.0.18"; +string constant VERSION = "v1.0.19"; /// @dev The number of targets that must be deployed for a full deployment. uint256 constant NUM_TARGETS = 5; diff --git a/deployments.json b/deployments.json index 0404ecb3f..f797bdda5 100644 --- a/deployments.json +++ b/deployments.json @@ -527,36 +527,6 @@ "address": "0xdf5d682404b0611f46f2626d9d5a37eb6a6fd27d", "timestamp": "2024-06-27T04:20:40.013Z" }, - "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626HyperdriveCoreDeployer": { - "contract": "ERC4626HyperdriveCoreDeployer", - "address": "0xd9bdabb0f87e46af44063d251eb07edfe260d9f1", - "timestamp": "2024-06-27T04:20:49.416Z" - }, - "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target0Deployer": { - "contract": "ERC4626Target0Deployer", - "address": "0x2bbf0716fb1ac91169b0dbecff232c7bf1b7e701", - "timestamp": "2024-06-27T04:21:02.853Z" - }, - "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target1Deployer": { - "contract": "ERC4626Target1Deployer", - "address": "0x068d5258f54b462de4240547dca0f1fce6382426", - "timestamp": "2024-06-27T04:21:16.020Z" - }, - "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target2Deployer": { - "contract": "ERC4626Target2Deployer", - "address": "0x0fb305a458b1008faee03147b700b3975bea03fd", - "timestamp": "2024-06-27T04:21:29.398Z" - }, - "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target3Deployer": { - "contract": "ERC4626Target3Deployer", - "address": "0xd8ee8f39c42ee9b005582e4dc0d530c7de6f1471", - "timestamp": "2024-06-27T04:21:38.990Z" - }, - "ElementDAO ERC4626 Hyperdrive Deployer Coordinator": { - "contract": "ERC4626HyperdriveDeployerCoordinator", - "address": "0xb2f17302ed12fe72d883fcc2b66f1b844fbb7964", - "timestamp": "2024-06-27T04:21:52.756Z" - }, "ElementDAO stETH Hyperdrive Deployer Coordinator_StETHHyperdriveCoreDeployer": { "contract": "StETHHyperdriveCoreDeployer", "address": "0xe8dc5073b02d062388db8e50762a8c9a1c823540", @@ -866,6 +836,101 @@ "contract": "MorphoBlueHyperdrive", "address": "0xA29A771683b4857bBd16e1e4f27D5B6bfF53209B", "timestamp": "2024-08-14T19:30:51.584Z" + }, + "ElementDAO 182 Day Morpho Blue wstETH/USDA Hyperdrive_MorphoBlueTarget0": { + "contract": "MorphoBlueTarget0", + "address": "0xdE7F1e3F02018169ef24622400933b7EFF44aE2d", + "timestamp": "2024-09-03T20:36:40.568Z" + }, + "ElementDAO 182 Day Morpho Blue wstETH/USDA Hyperdrive_MorphoBlueTarget1": { + "contract": "MorphoBlueTarget1", + "address": "0xdDe9EAc01F5895055c9078c4044689c552f1E12F", + "timestamp": "2024-09-03T20:35:51.225Z" + }, + "ElementDAO 182 Day Morpho Blue wstETH/USDA Hyperdrive_MorphoBlueTarget2": { + "contract": "MorphoBlueTarget2", + "address": "0x13C486EC46Eb5cD39e98f9bDD870Fd0257527119", + "timestamp": "2024-09-03T20:36:04.337Z" + }, + "ElementDAO 182 Day Morpho Blue wstETH/USDA Hyperdrive_MorphoBlueTarget3": { + "contract": "MorphoBlueTarget3", + "address": "0x5E723b39CA597Ae3b08A6fa6836B3c605f37c3F3", + "timestamp": "2024-09-03T20:36:13.598Z" + }, + "ElementDAO 182 Day Morpho Blue wstETH/USDA Hyperdrive_MorphoBlueTarget4": { + "contract": "MorphoBlueTarget4", + "address": "0xE6AA85FaA591650Bb7Ec6b53D68B39b66bC74441", + "timestamp": "2024-09-03T20:36:26.884Z" + }, + "ElementDAO 182 Day Morpho Blue wstETH/USDA Hyperdrive": { + "contract": "MorphoBlueHyperdrive", + "address": "0x7548c4F665402BAb3a4298B88527824B7b18Fe27", + "timestamp": "2024-09-03T20:36:40.438Z" + }, + "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626HyperdriveCoreDeployer": { + "contract": "ERC4626HyperdriveCoreDeployer", + "address": "0x1dcac79c73ca892d5872e5d8cb3ff43db0c81289", + "timestamp": "2024-09-03T21:44:28.498Z" + }, + "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target0Deployer": { + "contract": "ERC4626Target0Deployer", + "address": "0x9e8af51810042156f4cdae3109523345cc768541", + "timestamp": "2024-09-03T21:44:41.585Z" + }, + "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target1Deployer": { + "contract": "ERC4626Target1Deployer", + "address": "0xf19359677a2a0d5e3e43b7cc2bc73bac892d4c04", + "timestamp": "2024-09-03T21:44:50.642Z" + }, + "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target2Deployer": { + "contract": "ERC4626Target2Deployer", + "address": "0x218077b6c774abe7f262f9f1fc0b995352a0b6f3", + "timestamp": "2024-09-03T21:45:03.729Z" + }, + "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target3Deployer": { + "contract": "ERC4626Target3Deployer", + "address": "0xe275a81e0223688eb7da761169eb121b4ef7c3ef", + "timestamp": "2024-09-03T21:45:16.862Z" + }, + "ElementDAO ERC4626 Hyperdrive Deployer Coordinator_ERC4626Target4Deployer": { + "contract": "ERC4626Target4Deployer", + "address": "0x0fb70c3de906eddab929b5d288b638adb5238ccb", + "timestamp": "2024-09-03T21:45:29.967Z" + }, + "ElementDAO ERC4626 Hyperdrive Deployer Coordinator": { + "contract": "ERC4626HyperdriveDeployerCoordinator", + "address": "0x50ea16a4050e65748eeb82d94e0c84ac233225df", + "timestamp": "2024-09-03T21:45:39.060Z" + }, + "ElementDAO 182 Day stUSD Hyperdrive_ERC4626Target0": { + "contract": "ERC4626Target0", + "address": "0x686ffbDd397De6B2fff855b1bed53947da673F4f", + "timestamp": "2024-09-03T21:47:17.115Z" + }, + "ElementDAO 182 Day stUSD Hyperdrive_ERC4626Target1": { + "contract": "ERC4626Target1", + "address": "0xCD51526c7169652f8380095b338aFC1557BDA155", + "timestamp": "2024-09-03T21:46:28.318Z" + }, + "ElementDAO 182 Day stUSD Hyperdrive_ERC4626Target2": { + "contract": "ERC4626Target2", + "address": "0x9a70E96309d62A7c1c16aD9ba1F36bdEe6823C89", + "timestamp": "2024-09-03T21:46:41.719Z" + }, + "ElementDAO 182 Day stUSD Hyperdrive_ERC4626Target3": { + "contract": "ERC4626Target3", + "address": "0x94Da7F7fEFDb53Daa5Abe4a430c6e43686807F80", + "timestamp": "2024-09-03T21:46:50.768Z" + }, + "ElementDAO 182 Day stUSD Hyperdrive_ERC4626Target4": { + "contract": "ERC4626Target4", + "address": "0xe30bf456cd0697365ea786729EEB082239dad64e", + "timestamp": "2024-09-03T21:47:03.893Z" + }, + "ElementDAO 182 Day stUSD Hyperdrive": { + "contract": "ERC4626Hyperdrive", + "address": "0xA4090183878d5B7b6Ad104863743dd7E58985321", + "timestamp": "2024-09-03T21:47:16.998Z" } }, "gnosis": { diff --git a/hardhat.config.mainnet.ts b/hardhat.config.mainnet.ts index bb0537ec5..2201e127f 100644 --- a/hardhat.config.mainnet.ts +++ b/hardhat.config.mainnet.ts @@ -7,6 +7,7 @@ import { HardhatUserConfig } from "hardhat/config"; import baseConfig from "./hardhat.config"; import "./tasks"; import { + MAINNET_ERC4626_COORDINATOR, MAINNET_EZETH_182DAY, MAINNET_EZETH_COORDINATOR, MAINNET_FACTORY, @@ -14,7 +15,6 @@ import { MAINNET_MORPHO_BLUE_SUSDE_DAI_182DAY, MAINNET_MORPHO_BLUE_USDE_DAI_182DAY, MAINNET_MORPHO_BLUE_WSTETH_USDA_182DAY, - MAINNET_MORPHO_BLUE_WSTETH_USDC_182DAY, MAINNET_RETH_182DAY, MAINNET_RETH_COORDINATOR, MAINNET_STUSD_182DAY, @@ -32,6 +32,7 @@ const config: HardhatUserConfig = { hyperdriveDeploy: { factories: [MAINNET_FACTORY], coordinators: [ + MAINNET_ERC4626_COORDINATOR, MAINNET_EZETH_COORDINATOR, MAINNET_RETH_COORDINATOR, MAINNET_MORPHO_BLUE_COORDINATOR, @@ -41,7 +42,6 @@ const config: HardhatUserConfig = { MAINNET_RETH_182DAY, MAINNET_MORPHO_BLUE_SUSDE_DAI_182DAY, MAINNET_MORPHO_BLUE_USDE_DAI_182DAY, - MAINNET_MORPHO_BLUE_WSTETH_USDC_182DAY, MAINNET_MORPHO_BLUE_WSTETH_USDA_182DAY, MAINNET_STUSD_182DAY, ], diff --git a/tasks/deploy/config/mainnet/erc4626-coordinator.ts b/tasks/deploy/config/mainnet/erc4626-coordinator.ts index 1125f34b1..d59247c4f 100644 --- a/tasks/deploy/config/mainnet/erc4626-coordinator.ts +++ b/tasks/deploy/config/mainnet/erc4626-coordinator.ts @@ -8,6 +8,7 @@ export const MAINNET_ERC4626_COORDINATOR: HyperdriveCoordinatorConfig<"ERC4626"> name: MAINNET_ERC4626_COORDINATOR_NAME, prefix: "ERC4626", targetCount: 5, + extraConstructorArgs: [], factoryAddress: async (hre) => hre.hyperdriveDeploy.deployments.byName(MAINNET_FACTORY_NAME) .address, diff --git a/tasks/deploy/config/mainnet/morpho-blue-wsteth-usda-182day.ts b/tasks/deploy/config/mainnet/morpho-blue-wsteth-usda-182day.ts index f453a1e45..ed4a6260c 100644 --- a/tasks/deploy/config/mainnet/morpho-blue-wsteth-usda-182day.ts +++ b/tasks/deploy/config/mainnet/morpho-blue-wsteth-usda-182day.ts @@ -3,6 +3,7 @@ import { encodeAbiParameters, keccak256, parseEther, + toBytes, zeroAddress, } from "viem"; import { @@ -12,7 +13,7 @@ import { getLinkerDetails, normalizeFee, parseDuration, - toBytes, + toBytes32, } from "../../lib"; import { MAINNET_FACTORY_NAME } from "./factory"; import { MAINNET_MORPHO_BLUE_COORDINATOR_NAME } from "./morpho-blue-coordinator"; @@ -100,6 +101,11 @@ export const MAINNET_MORPHO_BLUE_WSTETH_USDA_182DAY: HyperdriveInstanceConfig<"M await pc.waitForTransactionReceipt({ hash: tx }); }, poolDeployConfig: async (hre) => { + let factoryContract = await hre.viem.getContractAt( + "HyperdriveFactory", + hre.hyperdriveDeploy.deployments.byName(MAINNET_FACTORY_NAME) + .address, + ); return { baseToken: USDA_ADDRESS_MAINNET, vaultSharesToken: zeroAddress, @@ -109,12 +115,11 @@ export const MAINNET_MORPHO_BLUE_WSTETH_USDA_182DAY: HyperdriveInstanceConfig<"M positionDuration: parseDuration(SIX_MONTHS), checkpointDuration: parseDuration("1 day"), timeStretch: 0n, - governance: (await hre.getNamedAccounts())[ - "deployer" - ] as Address, - feeCollector: zeroAddress, - sweepCollector: zeroAddress, - checkpointRewarder: zeroAddress, + governance: await factoryContract.read.hyperdriveGovernance(), + feeCollector: await factoryContract.read.feeCollector(), + sweepCollector: await factoryContract.read.sweepCollector(), + checkpointRewarder: + await factoryContract.read.checkpointRewarder(), ...(await getLinkerDetails( hre, hre.hyperdriveDeploy.deployments.byName( diff --git a/tasks/deploy/config/mainnet/stusd-182day.ts b/tasks/deploy/config/mainnet/stusd-182day.ts index 92a82560b..411671ce3 100644 --- a/tasks/deploy/config/mainnet/stusd-182day.ts +++ b/tasks/deploy/config/mainnet/stusd-182day.ts @@ -1,4 +1,4 @@ -import { Address, keccak256, parseEther, toBytes, zeroAddress } from "viem"; +import { Address, keccak256, parseEther, toBytes } from "viem"; import { HyperdriveInstanceConfig, getLinkerDetails, @@ -31,7 +31,7 @@ export const MAINNET_STUSD_182DAY: HyperdriveInstanceConfig<"ERC4626"> = { salt: toBytes32("0x69420"), extraData: "0x", contribution: CONTRIBUTION, - fixedAPR: parseEther("0.0829"), + fixedAPR: parseEther("0.0666"), timestretchAPR: parseEther("0.075"), options: async (hre) => ({ extraData: "0x", @@ -54,6 +54,11 @@ export const MAINNET_STUSD_182DAY: HyperdriveInstanceConfig<"ERC4626"> = { await pc.waitForTransactionReceipt({ hash: tx }); }, poolDeployConfig: async (hre) => { + let factoryContract = await hre.viem.getContractAt( + "HyperdriveFactory", + hre.hyperdriveDeploy.deployments.byName(MAINNET_FACTORY_NAME) + .address, + ); return { baseToken: USDA_ADDRESS_MAINNET, vaultSharesToken: STUSD_ADDRESS_MAINNET, @@ -63,11 +68,10 @@ export const MAINNET_STUSD_182DAY: HyperdriveInstanceConfig<"ERC4626"> = { positionDuration: parseDuration(SIX_MONTHS), checkpointDuration: parseDuration("1 day"), timeStretch: 0n, - // TODO: Read from the factory. - governance: (await hre.getNamedAccounts())["deployer"] as Address, - feeCollector: zeroAddress, - sweepCollector: zeroAddress, - checkpointRewarder: zeroAddress, + governance: await factoryContract.read.hyperdriveGovernance(), + feeCollector: await factoryContract.read.feeCollector(), + sweepCollector: await factoryContract.read.sweepCollector(), + checkpointRewarder: await factoryContract.read.checkpointRewarder(), ...(await getLinkerDetails( hre, hre.hyperdriveDeploy.deployments.byName(MAINNET_FACTORY_NAME) diff --git a/tasks/deploy/verify.ts b/tasks/deploy/verify.ts index 2f6eafab1..5b43e197a 100644 --- a/tasks/deploy/verify.ts +++ b/tasks/deploy/verify.ts @@ -193,8 +193,8 @@ task( // form target constructor args let targetArgs: - | [typeof poolConfig] | [typeof poolConfig, `0x${string}`] + | [typeof poolConfig, `0x${string}`, `0x${string}`] | [ typeof poolConfig, { @@ -204,6 +204,7 @@ task( irm: Address; lltv: bigint; }, + `0x${string}`, ] = [poolConfig]; // add extra args if present @@ -215,7 +216,7 @@ task( let isMorpho = (await instanceContract.read.kind()) == "MorphoBlueHyperdrive"; if (extras) { - targetArgs = [poolConfig, ...extras]; + targetArgs = [poolConfig, factoryAddress, ...extras]; } else if (isMorpho) { let morphoInstanceContract = await hre.viem.getContractAt( "IMorphoBlueHyperdrive", @@ -231,6 +232,7 @@ task( irm: await morphoInstanceContract.read.irm(), lltv: await morphoInstanceContract.read.lltv(), }, + factoryAddress, ]; } @@ -265,6 +267,7 @@ task( let args = [ i.name, poolConfig, + factoryAddress, await ihyperdrive.read.target0(), await ihyperdrive.read.target1(), await ihyperdrive.read.target2(), diff --git a/test/instances/aave/AaveHyperdrive.t.sol b/test/instances/aave/AaveHyperdrive.t.sol index a3449800f..c3ad8fd11 100644 --- a/test/instances/aave/AaveHyperdrive.t.sol +++ b/test/instances/aave/AaveHyperdrive.t.sol @@ -91,7 +91,10 @@ contract AaveHyperdriveTest is InstanceTest { roundTripLongMaturityWithSharesTolerance: 1e5, roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, roundTripShortInstantaneousWithSharesTolerance: 1e5, - roundTripShortMaturityWithSharesTolerance: 1e5 + roundTripShortMaturityWithSharesTolerance: 1e5, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 }) ) {} @@ -168,196 +171,6 @@ contract AaveHyperdriveTest is InstanceTest { ); } - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256, // unused - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - if (asBase) { - // Ensure that the total supply increased by the base paid. - assertApproxEqAbs( - AWETH.totalSupply(), - totalBaseBefore + amountPaid, - 1 - ); - assertApproxEqAbs( - AWETH.scaledTotalSupply(), - convertToShares(totalBaseBefore + amountPaid), - 1 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the Hyperdrive instance's base balance doesn't change - // and that the trader's base balance decreased by the amount paid. - assertEq( - WETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance - ); - assertEq( - WETH.balanceOf(trader), - traderBalancesBefore.baseBalance - amountPaid - ); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - convertToShares(AWETH.balanceOf(address(hyperdrive))), - hyperdriveBalancesBefore.sharesBalance + - convertToShares(amountPaid), - 2 - ); - assertEq( - convertToShares(AWETH.balanceOf(trader)), - traderBalancesBefore.sharesBalance - ); - } else { - // Ensure that the total supply and scaled total supply stay the same. - assertEq(AWETH.totalSupply(), totalBaseBefore); - assertApproxEqAbs( - AWETH.scaledTotalSupply(), - convertToShares(totalBaseBefore), - 1 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - assertEq( - WETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance - ); - assertEq(WETH.balanceOf(trader), traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - convertToShares(AWETH.balanceOf(address(hyperdrive))), - hyperdriveBalancesBefore.sharesBalance + - convertToShares(amountPaid), - 2 - ); - assertApproxEqAbs( - convertToShares(AWETH.balanceOf(trader)), - traderBalancesBefore.sharesBalance - - convertToShares(amountPaid), - 2 - ); - } - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256, // unused - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - if (asBase) { - // Ensure that the total supply decreased by the base proceeds. - assertApproxEqAbs( - AWETH.totalSupply(), - totalBaseBefore - baseProceeds, - 1 - ); - assertApproxEqAbs( - AWETH.scaledTotalSupply(), - convertToShares(totalBaseBefore - baseProceeds), - 1 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances Hyperdrive base balance doesn't - // change and that the trader's base balance increased by the base - // proceeds. - assertApproxEqAbs( - WETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance, - 1 - ); - assertEq( - WETH.balanceOf(trader), - traderBalancesBefore.baseBalance + baseProceeds - ); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - convertToShares(AWETH.balanceOf(address(hyperdrive))), - hyperdriveBalancesBefore.sharesBalance - - convertToShares(baseProceeds), - 2 - ); - assertApproxEqAbs( - convertToShares(AWETH.balanceOf(trader)), - traderBalancesBefore.sharesBalance, - 1 - ); - } else { - // Ensure that the total supply and scaled total supply stay the same. - assertEq(AWETH.totalSupply(), totalBaseBefore); - assertApproxEqAbs( - AWETH.scaledTotalSupply(), - convertToShares(totalBaseBefore), - 1 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - assertApproxEqAbs( - WETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance, - 1 - ); - assertApproxEqAbs( - WETH.balanceOf(trader), - traderBalancesBefore.baseBalance, - 1 - ); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - convertToShares(AWETH.balanceOf(address(hyperdrive))), - hyperdriveBalancesBefore.sharesBalance - - convertToShares(baseProceeds), - 2 - ); - assertApproxEqAbs( - convertToShares(AWETH.balanceOf(trader)), - traderBalancesBefore.sharesBalance + - convertToShares(baseProceeds), - 2 - ); - } - } - /// Getters /// function test_getters() external view { diff --git a/test/instances/chainlink/ChainlinkTest.t.sol b/test/instances/chainlink/ChainlinkTest.t.sol index a08df11d6..2afb89c4c 100644 --- a/test/instances/chainlink/ChainlinkTest.t.sol +++ b/test/instances/chainlink/ChainlinkTest.t.sol @@ -33,83 +33,86 @@ contract ChainlinkHyperdriveTest is InstanceTest { using Lib for *; using stdStorage for StdStorage; - // Chainlink's proxy for the wstETH-ETH reference rate on Gnosis Chain. + /// @dev Chainlink's proxy for the wstETH-ETH reference rate on Gnosis Chain. IChainlinkAggregatorV3 internal constant CHAINLINK_AGGREGATOR_PROXY = IChainlinkAggregatorV3(0x0064AC007fF665CF8D0D3Af5E0AD1c26a3f853eA); - // The underlying aggregator used Chainlink's by Chainlink's proxy on Gnosis - // chain. + /// @dev The underlying aggregator used Chainlink's by Chainlink's proxy on + /// Gnosis chain. address internal constant CHAINLINK_AGGREGATOR = address(0x6dcF8CE1982Fc71E7128407c7c6Ce4B0C1722F55); - // The address of the wstETH token on Gnosis Chain. + /// @dev The address of the wstETH token on Gnosis Chain. IERC20 internal constant WSTETH = IERC20(0x6C76971f98945AE98dD7d4DFcA8711ebea946eA6); - // The wstETH Whale accounts. + /// @dev The wstETH Whale accounts. address internal constant WSTETH_WHALE = address(0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f); address[] internal vaultSharesTokenWhaleAccounts = [WSTETH_WHALE]; - // The configuration for the Instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "ChainlinkHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, - baseToken: IERC20(address(0)), - vaultSharesToken: WSTETH, - shareTolerance: 0, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: false, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - baseWithdrawError: abi.encodeWithSelector( - IHyperdrive.UnsupportedToken.selector - ), - isRebasing: false, - // NOTE: Base deposits and withdrawals are disabled, so the - // tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 1e5, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e5, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e5, - roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, - roundTripLongMaturityWithSharesTolerance: 1e5, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e5, - roundTripShortMaturityWithSharesTolerance: 1e4 - }); - - /// @dev Instantiates the Instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Instantiates the Instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "ChainlinkHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, + baseToken: IERC20(address(0)), + vaultSharesToken: WSTETH, + shareTolerance: 0, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: false, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + baseWithdrawError: abi.encodeWithSelector( + IHyperdrive.UnsupportedToken.selector + ), + isRebasing: false, + // NOTE: Base deposits and withdrawals are disabled, so the + // tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e5, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e5, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e5, + roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, + roundTripLongMaturityWithSharesTolerance: 1e5, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e5, + roundTripShortMaturityWithSharesTolerance: 1e4, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __gnosis_chain_fork(35336446) { // Invoke the Instance testing suite setup. super.setUp(); @@ -125,6 +128,8 @@ contract ChainlinkHyperdriveTest is InstanceTest { } /// @dev Converts base amount to the equivalent about in shares. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -136,6 +141,8 @@ contract ChainlinkHyperdriveTest is InstanceTest { } /// @dev Converts share amount to the equivalent amount in base. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( uint256 shareAmount ) internal view override returns (uint256) { @@ -148,6 +155,7 @@ contract ChainlinkHyperdriveTest is InstanceTest { /// @dev Deploys the Chainlink deployer coordinator contract. /// @param _factory The address of the Hyperdrive factory. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -155,7 +163,7 @@ contract ChainlinkHyperdriveTest is InstanceTest { return address( new ChainlinkHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address(new ChainlinkHyperdriveCoreDeployer()), address(new ChainlinkTarget0Deployer()), @@ -168,125 +176,25 @@ contract ChainlinkHyperdriveTest is InstanceTest { } /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { return (0, WSTETH.totalSupply()); } /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. function getTokenBalances( address account ) internal view override returns (uint256, uint256) { return (0, WSTETH.balanceOf(account)); } - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base deposits are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total shares amount stayed the same. - (uint256 totalBaseAfter, uint256 totalSharesAfter) = getSupply(); - assertEq(totalBaseAfter, totalBaseBefore); - assertEq(totalSharesAfter, totalSharesBefore); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that none of the base balances changed. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - (uint256 traderBaseAfter, uint256 traderSharesAfter) = getTokenBalances( - trader - ); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance + - hyperdrive.convertToShares(amountPaid), - 1 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance - - hyperdrive.convertToShares(amountPaid), - 1 - ); - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base withdrawals are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total supplies didn't change. - (uint256 totalBaseAfter, uint256 totalSharesAfter) = getSupply(); - assertEq(totalBaseAfter, totalBaseBefore); - assertEq(totalSharesAfter, totalSharesBefore); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances do not change. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - (uint256 traderBaseAfter, uint256 traderSharesAfter) = getTokenBalances( - trader - ); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance - - hyperdrive.convertToShares(baseProceeds), - 1 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance + - hyperdrive.convertToShares(baseProceeds), - 1 - ); - } - /// Getters /// + /// @dev Test the instances getters. function test_getters() external view { assertEq( address(IChainlinkHyperdrive(address(hyperdrive)).aggregator()), @@ -298,6 +206,9 @@ contract ChainlinkHyperdriveTest is InstanceTest { /// Helpers /// + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. function advanceTime( uint256 timeDelta, int256 variableRate diff --git a/test/instances/eeth/EETHHyperdrive.t.sol b/test/instances/eeth/EETHHyperdrive.t.sol index 07def1fdb..dcb3b00df 100644 --- a/test/instances/eeth/EETHHyperdrive.t.sol +++ b/test/instances/eeth/EETHHyperdrive.t.sol @@ -31,81 +31,84 @@ contract EETHHyperdriveTest is InstanceTest { using HyperdriveUtils for IHyperdrive; using Lib for *; - // The mainnet liquidity pool. + /// @dev The mainnet liquidity pool. ILiquidityPool internal constant POOL = ILiquidityPool(0x308861A430be4cce5502d0A12724771Fc6DaF216); - // The mainnet EETH token. + /// @dev The mainnet EETH token. IEETH internal constant EETH = IEETH(0x35fA164735182de50811E8e2E824cFb9B6118ac2); - // The mainnet address that has the ability to call the rebase function. + /// @dev The mainnet address that has the ability to call the rebase function. address internal constant MEMBERSHIP_MANAGER = 0x3d320286E014C3e1ce99Af6d6B00f0C1D63E3000; - // Whale accounts. + /// @dev Whale accounts. address internal EETH_TOKEN_WHALE = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee; address[] internal EETHTokenWhaleAccounts = [EETH_TOKEN_WHALE]; - // The configuration for the Instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "EETHHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: EETHTokenWhaleAccounts, - baseToken: IERC20(ETH), - vaultSharesToken: IERC20(address(EETH)), - shareTolerance: 1e5, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: true, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - baseWithdrawError: abi.encodeWithSelector( - IHyperdrive.UnsupportedToken.selector - ), - isRebasing: true, - // NOTE: Base withdrawals are disabled, so the tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 1e5, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e5, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e5, - roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, - roundTripLongMaturityWithSharesTolerance: 1e5, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e5, - roundTripShortMaturityWithSharesTolerance: 1e4 - }); - - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "EETHHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: EETHTokenWhaleAccounts, + baseToken: IERC20(ETH), + vaultSharesToken: IERC20(address(EETH)), + shareTolerance: 1e5, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: true, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + baseWithdrawError: abi.encodeWithSelector( + IHyperdrive.UnsupportedToken.selector + ), + isRebasing: true, + // NOTE: Base withdrawals are disabled, so the tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e5, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e5, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e5, + roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, + roundTripLongMaturityWithSharesTolerance: 1e5, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e5, + roundTripShortMaturityWithSharesTolerance: 1e4, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __mainnet_fork(20_362_343) { // Invoke the instance testing suite setup. super.setUp(); @@ -119,7 +122,9 @@ contract EETHHyperdriveTest is InstanceTest { return new bytes(0); } - /// @dev Converts base amount to the equivalent amount in shares. + /// @dev Converts base amount to the equivalent about in shares. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -132,6 +137,8 @@ contract EETHHyperdriveTest is InstanceTest { } /// @dev Converts share amount to the equivalent amount in base. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( uint256 shareAmount ) internal view override returns (uint256) { @@ -143,8 +150,9 @@ contract EETHHyperdriveTest is InstanceTest { ); } - /// @dev Deploys the Morpho Blue deployer coordinator contract. + /// @dev Deploys the EETH deployer coordinator contract. /// @param _factory The address of the Hyperdrive factory. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -152,7 +160,7 @@ contract EETHHyperdriveTest is InstanceTest { return address( new EETHHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address(new EETHHyperdriveCoreDeployer(POOL)), address(new EETHTarget0Deployer(POOL)), @@ -166,147 +174,25 @@ contract EETHHyperdriveTest is InstanceTest { } /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { - return (POOL.getTotalPooledEther(), EETH.totalShares()); + return (address(POOL).balance, EETH.totalShares()); } /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. function getTokenBalances( address account ) internal view override returns (uint256, uint256) { - return (EETH.balanceOf(account), EETH.shares(account)); - } - - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - if (asBase) { - // Ensure that the amount of pooled ether increased by the base paid. - assertEq(POOL.getTotalPooledEther(), totalBaseBefore + amountPaid); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance - amountPaid); - - // Ensure that the EETH balances were updated correctly. - assertApproxEqAbs( - EETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance + amountPaid, - 5 - ); - assertEq(EETH.balanceOf(trader), traderBalancesBefore.baseBalance); - - // Ensure that the EETH shares were updated correctly. - uint256 expectedShares = convertToShares(amountPaid); - assertEq(EETH.totalShares(), totalSharesBefore + expectedShares); - assertEq( - EETH.shares(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance + expectedShares - ); - assertEq(EETH.shares(bob), traderBalancesBefore.sharesBalance); - } else { - // Ensure that the amount of pooled ether stays the same. - assertEq(POOL.getTotalPooledEther(), totalBaseBefore); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the EETH balances were updated correctly. - assertApproxEqAbs( - EETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance + amountPaid, - 3 - ); - assertApproxEqAbs( - EETH.balanceOf(trader), - traderBalancesBefore.baseBalance - amountPaid, - 3 - ); - - // Ensure that the EETH shares were updated correctly. - uint256 expectedShares = convertToShares(amountPaid); - assertEq(EETH.totalShares(), totalSharesBefore); - assertApproxEqAbs( - EETH.shares(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance + expectedShares, - 3 - ); - assertApproxEqAbs( - EETH.shares(trader), - traderBalancesBefore.sharesBalance - expectedShares, - 3 - ); - } - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base withdraws are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total pooled ether and shares stays the same. - assertEq(POOL.getTotalPooledEther(), totalBaseBefore); - assertApproxEqAbs(EETH.totalShares(), totalSharesBefore, 1); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the EETH balances were updated correctly. - assertApproxEqAbs( - EETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance - baseProceeds, - 1e4 - ); - assertApproxEqAbs( - EETH.balanceOf(trader), - traderBalancesBefore.baseBalance + baseProceeds, - 1e4 - ); - - // Ensure that the EETH shares were updated correctly. - uint256 expectedShares = convertToShares(baseProceeds); - assertApproxEqAbs( - EETH.shares(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance - expectedShares, - 2 - ); - assertApproxEqAbs( - EETH.shares(trader), - traderBalancesBefore.sharesBalance + expectedShares, - 2 - ); + return (account.balance, EETH.shares(account)); } /// Getters /// + /// @dev Test the instances getters. function test_getters() external view { assertEq( IEETHHyperdrive(address(hyperdrive)).liquidityPool(), @@ -316,11 +202,17 @@ contract EETHHyperdriveTest is InstanceTest { /// Price Per Share /// + /// @dev Fuzz test that verifies that the vault share price is the price + /// that dictates the conversion between base and shares. + /// @param basePaid The fuzz parameter for the base paid. function test__pricePerVaultShare(uint256 basePaid) external { // Ensure that the share price is the expected value. - (uint256 totalSupplyAssets, uint256 totalSupplyShares) = getSupply(); + (, uint256 totalSupplyShares) = getSupply(); uint256 vaultSharePrice = hyperdrive.getPoolInfo().vaultSharePrice; - assertEq(vaultSharePrice, totalSupplyAssets.divDown(totalSupplyShares)); + assertEq( + vaultSharePrice, + POOL.getTotalPooledEther().divDown(totalSupplyShares) + ); // Ensure that the share price accurately predicts the amount of shares // that will be minted for depositing a given amount of shares. This will @@ -339,12 +231,15 @@ contract EETHHyperdriveTest is InstanceTest { assertApproxEqAbs( hyperdriveSharesAfter, hyperdriveSharesBefore + basePaid.divDown(vaultSharePrice), - __testConfig.shareTolerance + config.shareTolerance ); } /// Helpers /// + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. function advanceTime( uint256 timeDelta, int256 variableRate diff --git a/test/instances/erc4626/ERC4626HyperdriveInstanceTest.t.sol b/test/instances/erc4626/ERC4626HyperdriveInstanceTest.t.sol index fc9218628..fe5a50d24 100644 --- a/test/instances/erc4626/ERC4626HyperdriveInstanceTest.t.sol +++ b/test/instances/erc4626/ERC4626HyperdriveInstanceTest.t.sol @@ -33,6 +33,8 @@ abstract contract ERC4626HyperdriveInstanceTest is InstanceTest { } /// @dev Converts base amount to the equivalent about in shares. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -44,6 +46,8 @@ abstract contract ERC4626HyperdriveInstanceTest is InstanceTest { } /// @dev Converts share amount to the equivalent amount in base. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( uint256 shareAmount ) internal view override returns (uint256) { @@ -54,8 +58,9 @@ abstract contract ERC4626HyperdriveInstanceTest is InstanceTest { ); } - /// @dev Deploys the ERC4626 deployer coordinator contract. + /// @dev Deploys the rsETH Linea deployer coordinator contract. /// @param _factory The address of the Hyperdrive factory. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -76,6 +81,8 @@ abstract contract ERC4626HyperdriveInstanceTest is InstanceTest { } /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { return ( IERC4626(address(config.vaultSharesToken)).totalAssets(), @@ -84,6 +91,9 @@ abstract contract ERC4626HyperdriveInstanceTest is InstanceTest { } /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. function getTokenBalances( address account ) internal view override returns (uint256, uint256) { @@ -93,215 +103,6 @@ abstract contract ERC4626HyperdriveInstanceTest is InstanceTest { ); } - /// @dev Verifies that deposit accounting is correct when opening positions. - /// @param trader The trader that is depositing. - /// @param amountPaid The amount that was deposited. - /// @param asBase Whether the deposit was made with base or vault shares. - /// @param totalBaseBefore The total base before the deposit. - /// @param totalSharesBefore The total shares before the deposit. - /// @param traderBalancesBefore The trader balances before the deposit. - /// @param hyperdriveBalancesBefore The hyperdrive balances before the deposit. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - if (asBase) { - // Ensure that the total supply increased by the base paid. - (uint256 totalBase, uint256 totalShares) = getSupply(); - assertApproxEqAbs(totalBase, totalBaseBefore + amountPaid, 1); - assertApproxEqAbs( - totalShares, - totalSharesBefore + hyperdrive.convertToShares(amountPaid), - 1 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the Hyperdrive instance's base balance doesn't change - // and that the trader's base balance decreased by the amount paid. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - ( - uint256 traderBaseAfter, - uint256 traderSharesAfter - ) = getTokenBalances(address(trader)); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq( - traderBaseAfter, - traderBalancesBefore.baseBalance - amountPaid - ); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance + - hyperdrive.convertToShares(amountPaid), - 2 - ); - assertEq(traderSharesAfter, traderBalancesBefore.sharesBalance); - } else { - // Ensure that the total supply and scaled total supply stay the same. - (uint256 totalBase, uint256 totalShares) = getSupply(); - assertEq(totalBase, totalBaseBefore); - assertApproxEqAbs(totalShares, totalSharesBefore, 1); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - ( - uint256 traderBaseAfter, - uint256 traderSharesAfter - ) = getTokenBalances(address(trader)); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance + - convertToShares(amountPaid), - 2 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance - - convertToShares(amountPaid), - 2 - ); - } - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - /// @param trader The trader that is withdrawing. - /// @param baseProceeds The base proceeds of the deposit. - /// @param asBase Whether the withdrawal was made with base or vault shares. - /// @param totalBaseBefore The total base before the withdrawal. - /// @param totalSharesBefore The total shares before the withdrawal. - /// @param traderBalancesBefore The trader balances before the withdrawal. - /// @param hyperdriveBalancesBefore The hyperdrive balances before the withdrawal. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - if (asBase) { - // Ensure that the total supply decreased by the base proceeds. - (uint256 totalBase, uint256 totalShares) = getSupply(); - assertApproxEqAbs(totalBase, totalBaseBefore - baseProceeds, 1); - assertApproxEqAbs( - totalShares, - totalSharesBefore - convertToShares(baseProceeds), - 1 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances were updated correctly. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - ( - uint256 traderBaseAfter, - uint256 traderSharesAfter - ) = getTokenBalances(address(trader)); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq( - traderBaseAfter, - traderBalancesBefore.baseBalance + baseProceeds - ); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance - - convertToShares(baseProceeds), - 2 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance, - 1 - ); - } else { - // Ensure that the total supply stayed the same. - (uint256 totalBase, uint256 totalShares) = getSupply(); - assertApproxEqAbs(totalBase, totalBaseBefore, 1); - assertApproxEqAbs(totalShares, totalSharesBefore, 1); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - ( - uint256 traderBaseAfter, - uint256 traderSharesAfter - ) = getTokenBalances(address(trader)); - assertApproxEqAbs( - hyperdriveBaseAfter, - hyperdriveBalancesBefore.baseBalance, - 1 - ); - assertApproxEqAbs( - traderBaseAfter, - traderBalancesBefore.baseBalance, - 1 - ); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance - - convertToShares(baseProceeds), - 2 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance + - convertToShares(baseProceeds), - 2 - ); - } - } - /// Getters /// /// @dev Test for the additional getters. In the case of the ERC4626Hyperdrive @@ -316,7 +117,7 @@ abstract contract ERC4626HyperdriveInstanceTest is InstanceTest { /// @dev Fuzz test that verifies that the vault share price is the price /// that dictates the conversion between base and shares. - /// @param basePaid The fuzz parameter for the base paid. + /// @param basePaid the fuzz parameter for the base paid. function test__pricePerVaultShare(uint256 basePaid) external { // Ensure that the share price is the expected value. (uint256 totalBase, uint256 totalSupply) = getSupply(); diff --git a/test/instances/erc4626/SUSDe.t.sol b/test/instances/erc4626/SUSDe.t.sol index 54089d936..439962bdf 100644 --- a/test/instances/erc4626/SUSDe.t.sol +++ b/test/instances/erc4626/SUSDe.t.sol @@ -16,18 +16,18 @@ contract SUSDeHyperdriveTest is ERC4626HyperdriveInstanceTest { using Lib for *; using stdStorage for StdStorage; - /// The cooldown error thrown by SUSDe on withdraw. + /// @dev The cooldown error thrown by SUSDe on withdraw. error OperationNotAllowed(); - // The staked USDe contract. + /// @dev The staked USDe contract. IERC4626 internal constant SUSDE = IERC4626(0x9D39A5DE30e57443BfF2A8307A4256c8797A3497); - // The USDe contract. + /// @dev The USDe contract. IERC20 internal constant USDE = IERC20(0x4c9EDD5852cd905f086C759E8383e09bff1E68B3); - // Whale accounts. + /// @dev Whale accounts. address internal USDE_TOKEN_WHALE = address(0x42862F48eAdE25661558AFE0A630b132038553D0); address[] internal baseTokenWhaleAccounts = [USDE_TOKEN_WHALE]; @@ -35,68 +35,71 @@ contract SUSDeHyperdriveTest is ERC4626HyperdriveInstanceTest { address(0x4139cDC6345aFFbaC0692b43bed4D059Df3e6d65); address[] internal vaultSharesTokenWhaleAccounts = [SUSDE_TOKEN_WHALE]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "ERC4626Hyperdrive", - decimals: 18, - baseTokenWhaleAccounts: baseTokenWhaleAccounts, - vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, - baseToken: USDE, - vaultSharesToken: IERC20(address(SUSDE)), - shareTolerance: 1e3, - minimumShareReserves: 1e18, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: true, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - // NOTE: SUSDe currently has a cooldown on withdrawals which - // prevents users from withdrawing as base instantaneously. We still - // support withdrawing with base since the cooldown can be disabled - // in the future. - baseWithdrawError: abi.encodeWithSelector( - OperationNotAllowed.selector - ), - isRebasing: false, - // NOTE: Base withdrawals are disabled, so the tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 1e8, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e8, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e5, - roundTripLongMaturityWithSharesUpperBoundTolerance: 100, - roundTripLongMaturityWithSharesTolerance: 1e5, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e5, - roundTripShortMaturityWithSharesTolerance: 1e5 - }); + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "ERC4626Hyperdrive", + decimals: 18, + baseTokenWhaleAccounts: baseTokenWhaleAccounts, + vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, + baseToken: USDE, + vaultSharesToken: IERC20(address(SUSDE)), + shareTolerance: 1e3, + minimumShareReserves: 1e18, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: true, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + // NOTE: SUSDe currently has a cooldown on withdrawals which + // prevents users from withdrawing as base instantaneously. We still + // support withdrawing with base since the cooldown can be disabled + // in the future. + baseWithdrawError: abi.encodeWithSelector( + OperationNotAllowed.selector + ), + isRebasing: false, + // NOTE: Base withdrawals are disabled, so the tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e8, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e8, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e5, + roundTripLongMaturityWithSharesUpperBoundTolerance: 100, + roundTripLongMaturityWithSharesTolerance: 1e5, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e5, + roundTripShortMaturityWithSharesTolerance: 1e5, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __mainnet_fork(20_335_384) { // Invoke the Instance testing suite setup. super.setUp(); diff --git a/test/instances/erc4626/StUSD.t.sol b/test/instances/erc4626/StUSD.t.sol index 45c0c055e..527bd447d 100644 --- a/test/instances/erc4626/StUSD.t.sol +++ b/test/instances/erc4626/StUSD.t.sol @@ -24,15 +24,15 @@ contract stUSDHyperdriveTest is ERC4626HyperdriveInstanceTest { using Lib for *; using stdStorage for StdStorage; - // The USDA contract. + /// @dev The USDA contract. IERC20 internal constant USDA = IERC20(0x0000206329b97DB379d5E1Bf586BbDB969C63274); - // The stUSD contract. + /// @dev The stUSD contract. ISTUSD internal constant STUSD = ISTUSD(0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776); - // Whale accounts. + /// @dev Whale accounts. address internal USDA_TOKEN_WHALE = address(0xEc0B13b2271E212E1a74D55D51932BD52A002961); address[] internal baseTokenWhaleAccounts = [USDA_TOKEN_WHALE]; @@ -40,60 +40,63 @@ contract stUSDHyperdriveTest is ERC4626HyperdriveInstanceTest { address(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); address[] internal vaultSharesTokenWhaleAccounts = [STUSD_TOKEN_WHALE]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "ERC4626Hyperdrive", - decimals: 18, - baseTokenWhaleAccounts: baseTokenWhaleAccounts, - vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, - baseToken: USDA, - vaultSharesToken: STUSD, - shareTolerance: 1e3, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: true, - enableShareDeposits: true, - enableBaseWithdraws: true, - enableShareWithdraws: true, - baseWithdrawError: new bytes(0), - isRebasing: false, - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 1e5, - roundTripLpWithdrawalSharesWithBaseTolerance: 1e6, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithBaseTolerance: 1e5, - roundTripLongMaturityWithBaseUpperBoundTolerance: 1e3, - roundTripLongMaturityWithBaseTolerance: 1e5, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithBaseTolerance: 1e5, - roundTripShortMaturityWithBaseTolerance: 1e5, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 1e7, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e7, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e5, - roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, - roundTripLongMaturityWithSharesTolerance: 1e5, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e5, - roundTripShortMaturityWithSharesTolerance: 1e5 - }); + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "ERC4626Hyperdrive", + decimals: 18, + baseTokenWhaleAccounts: baseTokenWhaleAccounts, + vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, + baseToken: USDA, + vaultSharesToken: STUSD, + shareTolerance: 1e3, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: true, + enableShareDeposits: true, + enableBaseWithdraws: true, + enableShareWithdraws: true, + baseWithdrawError: new bytes(0), + isRebasing: false, + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 1e5, + roundTripLpWithdrawalSharesWithBaseTolerance: 1e6, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithBaseTolerance: 1e5, + roundTripLongMaturityWithBaseUpperBoundTolerance: 1e3, + roundTripLongMaturityWithBaseTolerance: 1e5, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithBaseTolerance: 1e5, + roundTripShortMaturityWithBaseTolerance: 1e5, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e7, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e7, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e5, + roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, + roundTripLongMaturityWithSharesTolerance: 1e5, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e5, + roundTripShortMaturityWithSharesTolerance: 1e5, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __mainnet_fork(20_643_578) { // Invoke the Instance testing suite setup. super.setUp(); diff --git a/test/instances/erc4626/sxDai.t.sol b/test/instances/erc4626/sxDai.t.sol index 69ca83288..377e89bfe 100644 --- a/test/instances/erc4626/sxDai.t.sol +++ b/test/instances/erc4626/sxDai.t.sol @@ -16,15 +16,15 @@ contract sxDaiHyperdriveTest is ERC4626HyperdriveInstanceTest { using Lib for *; using stdStorage for StdStorage; - // The wxDai contract. + /// @dev The wxDai contract. IERC20 internal constant WXDAI = IERC20(0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d); - // The sxDai contract. + /// @dev The sxDai contract. IERC4626 internal constant SXDAI = IERC4626(0xaf204776c7245bF4147c2612BF6e5972Ee483701); - // Whale accounts. + /// @dev Whale accounts. address internal WXDAI_TOKEN_WHALE = address(0xd0Dd6cEF72143E22cCED4867eb0d5F2328715533); address[] internal baseTokenWhaleAccounts = [WXDAI_TOKEN_WHALE]; @@ -32,60 +32,63 @@ contract sxDaiHyperdriveTest is ERC4626HyperdriveInstanceTest { address(0x7a5c3860a77a8DC1b225BD46d0fb2ac1C6D191BC); address[] internal vaultSharesTokenWhaleAccounts = [SXDAI_TOKEN_WHALE]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "ERC4626Hyperdrive", - decimals: 18, - baseTokenWhaleAccounts: baseTokenWhaleAccounts, - vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, - baseToken: WXDAI, - vaultSharesToken: SXDAI, - shareTolerance: 1e3, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: true, - enableShareDeposits: true, - enableBaseWithdraws: true, - enableShareWithdraws: true, - baseWithdrawError: new bytes(0), - isRebasing: false, - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 1e5, - roundTripLpWithdrawalSharesWithBaseTolerance: 1e5, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithBaseTolerance: 1e5, - roundTripLongMaturityWithBaseUpperBoundTolerance: 1e3, - roundTripLongMaturityWithBaseTolerance: 1e5, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithBaseTolerance: 1e5, - roundTripShortMaturityWithBaseTolerance: 1e5, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 1e7, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e7, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e5, - roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, - roundTripLongMaturityWithSharesTolerance: 1e5, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e5, - roundTripShortMaturityWithSharesTolerance: 1e5 - }); + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "ERC4626Hyperdrive", + decimals: 18, + baseTokenWhaleAccounts: baseTokenWhaleAccounts, + vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, + baseToken: WXDAI, + vaultSharesToken: SXDAI, + shareTolerance: 1e3, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: true, + enableShareDeposits: true, + enableBaseWithdraws: true, + enableShareWithdraws: true, + baseWithdrawError: new bytes(0), + isRebasing: false, + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 1e5, + roundTripLpWithdrawalSharesWithBaseTolerance: 1e5, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithBaseTolerance: 1e5, + roundTripLongMaturityWithBaseUpperBoundTolerance: 1e3, + roundTripLongMaturityWithBaseTolerance: 1e5, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithBaseTolerance: 1e5, + roundTripShortMaturityWithBaseTolerance: 1e5, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e7, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e7, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e5, + roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, + roundTripLongMaturityWithSharesTolerance: 1e5, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e5, + roundTripShortMaturityWithSharesTolerance: 1e5, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __gnosis_chain_fork(35_681_086) { // Invoke the Instance testing suite setup. super.setUp(); diff --git a/test/instances/ezETH/EzETHHyperdrive.t.sol b/test/instances/ezETH/EzETHHyperdrive.t.sol index f02178a17..cc29fc075 100644 --- a/test/instances/ezETH/EzETHHyperdrive.t.sol +++ b/test/instances/ezETH/EzETHHyperdrive.t.sol @@ -30,92 +30,95 @@ contract EzETHHyperdriveTest is InstanceTest { using Lib for *; using stdStorage for StdStorage; - // The Renzo main entrypoint contract to stake ETH and receive ezETH. + /// @dev The Renzo main entrypoint contract to stake ETH and receive ezETH. IRestakeManager internal constant RESTAKE_MANAGER = IRestakeManager(0x74a09653A083691711cF8215a6ab074BB4e99ef5); - // The Renzo Oracle contract. + /// @dev The Renzo Oracle contract. IRenzoOracle internal constant RENZO_ORACLE = IRenzoOracle(0x5a12796f7e7EBbbc8a402667d266d2e65A814042); - // The ezETH token contract. + /// @dev The ezETH token contract. IERC20 internal constant EZETH = IERC20(0xbf5495Efe5DB9ce00f80364C8B423567e58d2110); - // Renzo's DepositQueue contract called from RestakeManager. Used to - // simulate interest. + /// @dev Renzo's DepositQueue contract called from RestakeManager. Used to + /// simulate interest. IDepositQueue DEPOSIT_QUEUE = IDepositQueue(0xf2F305D14DCD8aaef887E0428B3c9534795D0d60); - // Renzo's restaking protocol was launch Dec, 2023 and their use of - // oracles makes it difficult to test on a mainnet fork without heavy - // mocking. To test with their deployed code we use a shorter position - // duration. + // @dev Renzo's restaking protocol was launched on Dec, 2023 and their use + /// of oracles makes it difficult to test on a mainnet fork without + /// heavy mocking. To test with their deployed code we use a shorter + /// position duration. uint256 internal constant POSITION_DURATION_15_DAYS = 15 days; uint256 internal constant STARTING_BLOCK = 19119544; - // Whale accounts. + /// @dev Whale accounts. address internal EZETH_WHALE = 0x40C0d1fbcB0A43A62ca7A241E7A42ca58EeF96eb; address[] internal whaleAccounts = [EZETH_WHALE]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "EzETHHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: whaleAccounts, - baseToken: IERC20(ETH), - vaultSharesToken: IERC20(EZETH), - shareTolerance: 1e6, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION_15_DAYS, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: false, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - baseWithdrawError: abi.encodeWithSelector( - IHyperdrive.UnsupportedToken.selector - ), - isRebasing: false, - // NOTE: Base withdrawals are disabled, so the tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 1e6, - closeShortWithSharesTolerance: 1e6, - roundTripLpInstantaneousWithSharesTolerance: 1e7, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e7, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e7, - roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, - roundTripLongMaturityWithSharesTolerance: 1e7, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e7, - roundTripShortMaturityWithSharesTolerance: 1e8 - }); - - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "EzETHHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: whaleAccounts, + baseToken: IERC20(ETH), + vaultSharesToken: IERC20(EZETH), + shareTolerance: 1e6, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION_15_DAYS, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: false, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + baseWithdrawError: abi.encodeWithSelector( + IHyperdrive.UnsupportedToken.selector + ), + isRebasing: false, + // NOTE: Base withdrawals are disabled, so the tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 1e6, + closeShortWithSharesTolerance: 1e6, + roundTripLpInstantaneousWithSharesTolerance: 1e7, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e7, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e7, + roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, + roundTripLongMaturityWithSharesTolerance: 1e7, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e7, + roundTripShortMaturityWithSharesTolerance: 1e8, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 1_000 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __mainnet_fork(STARTING_BLOCK) { // Giving the EzETH whale account more EzETH before the instance setup. vm.startPrank(EZETH_WHALE); @@ -136,6 +139,8 @@ contract EzETHHyperdriveTest is InstanceTest { } /// @dev Converts base amount to the equivalent about in EzETH. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -145,8 +150,10 @@ contract EzETHHyperdriveTest is InstanceTest { } /// @dev Converts share amount to the equivalent amount in ETH. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( - uint256 baseAmount + uint256 shareAmount ) internal view override returns (uint256) { // Get the total TVL priced in ETH from RestakeManager. (, , uint256 totalTVL) = RESTAKE_MANAGER.calculateTVLs(); @@ -156,7 +163,7 @@ contract EzETHHyperdriveTest is InstanceTest { return RENZO_ORACLE.calculateRedeemAmount( - baseAmount, + shareAmount, totalSupply, totalTVL ); @@ -164,6 +171,7 @@ contract EzETHHyperdriveTest is InstanceTest { /// @dev Deploys the EzETH deployer coordinator contract. /// @param _factory The address of the Hyperdrive factory contract. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -171,7 +179,7 @@ contract EzETHHyperdriveTest is InstanceTest { return address( new EzETHHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address(new EzETHHyperdriveCoreDeployer(RESTAKE_MANAGER)), address(new EzETHTarget0Deployer(RESTAKE_MANAGER)), @@ -184,105 +192,28 @@ contract EzETHHyperdriveTest is InstanceTest { ); } - /// @dev Fetches the token balance information of an account. - function getTokenBalances( - address account - ) internal view override returns (uint256, uint256) { - // EzETH does not have a convenient function for fetching base balance. - return (0, EZETH.balanceOf(account)); - } - /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { (, uint256 totalPooledEther, ) = getSharePrice(); return (totalPooledEther, EZETH.totalSupply()); } - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 basePaid, - bool asBase, - uint256 totalBaseSupplyBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base deposits are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the amount of pooled ether stays the same. - (, uint256 totalPooledEther, ) = getSharePrice(); - assertEq(totalPooledEther, totalBaseSupplyBefore); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the ezETH shares were updated correctly. - uint256 expectedShares = convertToShares(basePaid); - assertEq(EZETH.totalSupply(), totalSharesBefore); - assertApproxEqAbs( - EZETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance + expectedShares, - 2 // Higher tolerance due to rounding when converting back into shares. - ); - assertApproxEqAbs( - EZETH.balanceOf(trader), - traderBalancesBefore.sharesBalance - expectedShares, - 2 // Higher tolerance due to rounding when converting back into shares. - ); - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalPooledEtherBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base withdraws are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total pooled ether and shares stays the same. - (, uint256 totalPooledEther, ) = getSharePrice(); - assertEq(totalPooledEther, totalPooledEtherBefore); - assertApproxEqAbs(EZETH.totalSupply(), totalSharesBefore, 1); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the ezETH shares were updated correctly. - uint256 expectedShares = baseProceeds.mulDivDown( - totalSharesBefore, - totalPooledEtherBefore - ); - assertApproxEqAbs( - EZETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance - expectedShares, - 1 - ); - assertApproxEqAbs( - EZETH.balanceOf(trader), - traderBalancesBefore.sharesBalance + expectedShares, - 1 - ); + /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. + function getTokenBalances( + address account + ) internal view override returns (uint256, uint256) { + // EzETH does not have a convenient function for fetching base balance. + return (0, EZETH.balanceOf(account)); } + /// @dev A test that ensures that advance time works properly by advancing + /// time, accruing interest, and ensuring that the vault share price + /// increased by the expected amount. function test__ezeth_interest_and_advance_time() external { // hand calculated value sanity check uint256 positionAdjustedInterestRate = uint256(0.05e18).mulDivDown( @@ -303,6 +234,7 @@ contract EzETHHyperdriveTest is InstanceTest { /// Getters /// + /// @dev Test the instances getters. function test_getters() external view { assertEq( address(IEzETHHyperdriveRead(address(hyperdrive)).renzo()), @@ -318,6 +250,9 @@ contract EzETHHyperdriveTest is InstanceTest { /// Price Per Share /// + /// @dev Fuzz test that verifies that the vault share price is the price + /// that dictates the conversion between base and shares. + /// @param basePaid the fuzz parameter for the base paid. function test__pricePerVaultShare(uint256 basePaid) external { // Ensure that the share price is the expected value. @@ -355,6 +290,9 @@ contract EzETHHyperdriveTest is InstanceTest { /// Helpers /// + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. function advanceTime( uint256 timeDelta, // assume a position duration jump int256 variableRate // annual variable rate @@ -408,7 +346,10 @@ contract EzETHHyperdriveTest is InstanceTest { } } - // returns share price information. + // @dev Gets the vault share price and some other important quantities. + /// @return sharePrice The vault share price. + /// @return totalPooledEther The total pooled ether. + /// @return totalShares The total amount of vault shares. function getSharePrice() internal view @@ -434,6 +375,10 @@ contract EzETHHyperdriveTest is InstanceTest { return (sharePrice, totalTVL, totalSupply); } + /// @dev Convert a base amount to vault shares and approve Hyperdrive to + /// spend the vault shares amount. + /// @param basePaid The amount of base that will be paid. + /// @return sharesPaid The amount of shares that will be paid. function getAndApproveShares( uint256 basePaid ) internal returns (uint256 sharesPaid) { diff --git a/test/instances/ezeth-linea/EzETHLineaTest.t.sol b/test/instances/ezeth-linea/EzETHLineaTest.t.sol index 328d26c7f..fd766ccea 100644 --- a/test/instances/ezeth-linea/EzETHLineaTest.t.sol +++ b/test/instances/ezeth-linea/EzETHLineaTest.t.sol @@ -32,77 +32,80 @@ contract EzETHLineaHyperdriveTest is InstanceTest { using Lib for *; using stdStorage for StdStorage; - // The Linea xRenzoDeposit contract. + /// @dev The Linea xRenzoDeposit contract. IXRenzoDeposit internal constant X_RENZO_DEPOSIT = IXRenzoDeposit(0x4D7572040B84b41a6AA2efE4A93eFFF182388F88); - // The address of the ezETH on Linea. + /// @dev The address of the ezETH on Linea. IERC20 internal constant EZETH = IERC20(0x2416092f143378750bb29b79eD961ab195CcEea5); - // Whale accounts. + /// @dev Whale accounts. address internal EZETH_WHALE = address(0x0684FC172a0B8e6A65cF4684eDb2082272fe9050); address[] internal vaultSharesTokenWhaleAccounts = [EZETH_WHALE]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "EzETHLineaHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, - baseToken: IERC20(ETH), - vaultSharesToken: EZETH, - shareTolerance: 0, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: false, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - baseWithdrawError: abi.encodeWithSelector( - IHyperdrive.UnsupportedToken.selector - ), - isRebasing: false, - // NOTE: Base withdrawals are disabled, so the tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 100, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e3, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e4, - roundTripLongMaturityWithSharesUpperBoundTolerance: 100, - roundTripLongMaturityWithSharesTolerance: 3e3, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e3, - roundTripShortMaturityWithSharesTolerance: 1e3 - }); - - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "EzETHLineaHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, + baseToken: IERC20(ETH), + vaultSharesToken: EZETH, + shareTolerance: 0, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: false, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + baseWithdrawError: abi.encodeWithSelector( + IHyperdrive.UnsupportedToken.selector + ), + isRebasing: false, + // NOTE: Base withdrawals are disabled, so the tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 100, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e3, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e4, + roundTripLongMaturityWithSharesUpperBoundTolerance: 100, + roundTripLongMaturityWithSharesTolerance: 3e3, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e3, + roundTripShortMaturityWithSharesTolerance: 1e3, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __linea_fork(8_431_727) { // Invoke the instance testing suite setup. super.setUp(); @@ -119,6 +122,8 @@ contract EzETHLineaHyperdriveTest is InstanceTest { } /// @dev Converts base amount to the equivalent about in shares. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -127,6 +132,8 @@ contract EzETHLineaHyperdriveTest is InstanceTest { } /// @dev Converts share amount to the equivalent amount in base. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( uint256 shareAmount ) internal view override returns (uint256) { @@ -136,6 +143,7 @@ contract EzETHLineaHyperdriveTest is InstanceTest { /// @dev Deploys the ezETH Linea deployer coordinator contract. /// @param _factory The address of the Hyperdrive factory. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -143,7 +151,7 @@ contract EzETHLineaHyperdriveTest is InstanceTest { return address( new EzETHLineaHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address( new EzETHLineaHyperdriveCoreDeployer(X_RENZO_DEPOSIT) @@ -159,125 +167,25 @@ contract EzETHLineaHyperdriveTest is InstanceTest { } /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { return (0, EZETH.totalSupply()); } /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. function getTokenBalances( address account ) internal view override returns (uint256, uint256) { return (account.balance, EZETH.balanceOf(account)); } - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base deposits are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total supply stayed the same. - (uint256 totalBaseAfter, uint256 totalSharesAfter) = getSupply(); - assertEq(totalBaseAfter, totalBaseBefore); - assertEq(totalSharesAfter, totalSharesBefore); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - (uint256 traderBaseAfter, uint256 traderSharesAfter) = getTokenBalances( - trader - ); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance + - hyperdrive.convertToShares(amountPaid), - 2 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance - - hyperdrive.convertToShares(amountPaid), - 2 - ); - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base withdrawals are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total supply stayed the same. - (uint256 totalBaseAfter, uint256 totalSharesAfter) = getSupply(); - assertEq(totalBaseAfter, totalBaseBefore); - assertEq(totalSharesAfter, totalSharesBefore); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - (uint256 traderBaseAfter, uint256 traderSharesAfter) = getTokenBalances( - trader - ); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance - - hyperdrive.convertToShares(baseProceeds), - 2 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance + - hyperdrive.convertToShares(baseProceeds), - 2 - ); - } - /// Getters /// + /// @dev Test for the additional getters. function test_getters() external view { assertEq( address(IEzETHLineaHyperdrive(address(hyperdrive)).xRenzoDeposit()), @@ -289,6 +197,9 @@ contract EzETHLineaHyperdriveTest is InstanceTest { /// Helpers /// + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. function advanceTime( uint256 timeDelta, int256 variableRate diff --git a/test/instances/lseth/LsETHHyperdrive.t.sol b/test/instances/lseth/LsETHHyperdrive.t.sol index 8c736c820..0dc06fd4a 100644 --- a/test/instances/lseth/LsETHHyperdrive.t.sol +++ b/test/instances/lseth/LsETHHyperdrive.t.sol @@ -29,11 +29,11 @@ contract LsETHHyperdriveTest is InstanceTest { using Lib for *; using stdStorage for StdStorage; - // The LsETH token contract. + /// @dev The LsETH token contract. IRiverV1 internal constant RIVER = IRiverV1(0x8c1BEd5b9a0928467c9B1341Da1D7BD5e10b6549); - // Whale accounts. + /// @dev Whale accounts. address internal constant LSETH_WHALE = 0xF047ab4c75cebf0eB9ed34Ae2c186f3611aEAfa6; address internal constant LSETH_WHALE_2 = @@ -46,65 +46,67 @@ contract LsETHHyperdriveTest is InstanceTest { LSETH_WHALE_3 ]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "LsETHHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: whaleAccounts, - baseToken: IERC20(ETH), - vaultSharesToken: IERC20(RIVER), - shareTolerance: 1e5, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: false, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - baseWithdrawError: abi.encodeWithSelector( - IHyperdrive.UnsupportedToken.selector - ), - isRebasing: false, - // NOTE: Base withdrawals are disabled, so the tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 1e3, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e3, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e4, - roundTripLongMaturityWithSharesUpperBoundTolerance: 100, - roundTripLongMaturityWithSharesTolerance: 3e3, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e3, - roundTripShortMaturityWithSharesTolerance: 1e3 - }); - - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. - + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "LsETHHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: whaleAccounts, + baseToken: IERC20(ETH), + vaultSharesToken: IERC20(RIVER), + shareTolerance: 1e5, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: false, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + baseWithdrawError: abi.encodeWithSelector( + IHyperdrive.UnsupportedToken.selector + ), + isRebasing: false, + // NOTE: Base withdrawals are disabled, so the tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e3, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e3, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e4, + roundTripLongMaturityWithSharesUpperBoundTolerance: 100, + roundTripLongMaturityWithSharesTolerance: 3e3, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e3, + roundTripShortMaturityWithSharesTolerance: 1e3, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __mainnet_fork(19_429_100) { // Invoke the instance testing suite setup. super.setUp(); @@ -120,6 +122,7 @@ contract LsETHHyperdriveTest is InstanceTest { /// @dev Deploys the LsETH deployer coordinator contract. /// @param _factory The address of the Hyperdrive factory contract. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -127,7 +130,7 @@ contract LsETHHyperdriveTest is InstanceTest { return address( new LsETHHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address(new LsETHHyperdriveCoreDeployer()), address(new LsETHTarget0Deployer()), @@ -140,7 +143,9 @@ contract LsETHHyperdriveTest is InstanceTest { ); } - /// @dev Converts base amount to the equivalent amount in LsETH. + /// @dev Converts base amount to the equivalent about in shares. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -148,7 +153,9 @@ contract LsETHHyperdriveTest is InstanceTest { return RIVER.sharesFromUnderlyingBalance(baseAmount); } - /// @dev Converts base amount to the equivalent amount in ETH. + /// @dev Converts share amount to the equivalent amount in base. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( uint256 shareAmount ) internal view override returns (uint256) { @@ -156,111 +163,26 @@ contract LsETHHyperdriveTest is InstanceTest { return RIVER.underlyingBalanceFromShares(shareAmount); } - /// @dev Fetches the token balance information of an account. - function getTokenBalances( - address account - ) internal view override returns (uint256, uint256) { - return (RIVER.balanceOfUnderlying(account), RIVER.balanceOf(account)); - } - /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { - return (RIVER.totalUnderlyingSupply(), RIVER.totalSupply()); - } - - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 amount, - bool asBase, - uint totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Deposits as base is not supported for this instance. - if (asBase) { - revert IHyperdrive.NotPayable(); - } - - // Convert the amount in terms of shares. - amount = convertToShares(amount); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the LsETH balances were updated correctly. - assertApproxEqAbs( - RIVER.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance + amount, - 1 - ); - assertApproxEqAbs( - RIVER.balanceOf(trader), - traderBalancesBefore.sharesBalance - amount, - 1 - ); - - // Ensure the total base supply was updated correctly. - assertEq(RIVER.totalUnderlyingSupply(), totalBaseBefore); - - // Ensure the total supply was updated correctly. - assertEq(RIVER.totalSupply(), totalSharesBefore); + return (address(RIVER).balance, RIVER.totalSupply()); } - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base withdraws are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Convert baseProceeds to shares to verify accounting. - uint256 amount = convertToShares(baseProceeds); - - // Ensure the total amount of LsETH stays the same. - assertEq(RIVER.totalSupply(), totalSharesBefore); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure the LsETH balances were updated correctly. - assertApproxEqAbs( - RIVER.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance - amount, - 1 - ); - assertApproxEqAbs( - RIVER.balanceOf(address(trader)), - traderBalancesBefore.sharesBalance + amount, - 1 - ); - - // Ensure the total base supply was updated correctly. - assertEq(RIVER.totalUnderlyingSupply(), totalBaseBefore); - - // Ensure the total supply was updated correctly. - assertEq(RIVER.totalSupply(), totalSharesBefore); + /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. + function getTokenBalances( + address account + ) internal view override returns (uint256, uint256) { + return (account.balance, RIVER.balanceOf(account)); } /// Getters /// + /// @dev Test for the additional getters. function test_getters() external view { (, uint256 totalShares) = getTokenBalances(address(hyperdrive)); assertEq(hyperdrive.totalShares(), totalShares); @@ -268,6 +190,9 @@ contract LsETHHyperdriveTest is InstanceTest { /// Price Per Share /// + /// @dev Fuzz test that verifies that the vault share price is the price + /// that dictates the conversion between base and shares. + /// @param basePaid the fuzz parameter for the base paid. function test_pricePerVaultShare(uint256 basePaid) external { // Ensure the share prices are equal upon market inception. uint256 vaultSharePrice = hyperdrive.getPoolInfo().vaultSharePrice; @@ -292,6 +217,9 @@ contract LsETHHyperdriveTest is InstanceTest { /// Helpers /// + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. function advanceTime( uint256 timeDelta, int256 variableRate @@ -333,6 +261,9 @@ contract LsETHHyperdriveTest is InstanceTest { ); } + /// @dev Test that ensures that advance time works correctly by advancing + /// time and ensuring the the ending vault share price was updated + /// correctly. function test_advanced_time() external { vm.stopPrank(); diff --git a/test/instances/morpho-blue/MorphoBlueHyperdriveInstanceTest.t.sol b/test/instances/morpho-blue/MorphoBlueHyperdriveInstanceTest.t.sol index 599542cc2..7c401e686 100644 --- a/test/instances/morpho-blue/MorphoBlueHyperdriveInstanceTest.t.sol +++ b/test/instances/morpho-blue/MorphoBlueHyperdriveInstanceTest.t.sol @@ -159,164 +159,6 @@ abstract contract MorphoBlueHyperdriveInstanceTest is InstanceTest { ); } - /// @dev Verifies that deposit accounting is correct when opening positions. - /// @param trader The trader that is depositing. - /// @param amountPaid The amount that was deposited. - /// @param asBase Whether the deposit was made with base or vault shares. - /// @param totalBaseBefore The total base before the deposit. - /// @param totalSharesBefore The total shares before the deposit. - /// @param traderBalancesBefore The trader balances before the deposit. - /// @param hyperdriveBalancesBefore The hyperdrive balances before the deposit. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Vault shares deposits are not supported for this instance. - if (!asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total supply increased by the base paid. - ( - uint256 totalSupplyAssets, - uint256 totalSupplyShares, - , - - ) = morphoBlueParams.morpho.expectedMarketBalances( - MarketParams({ - loanToken: address(config.baseToken), - collateralToken: morphoBlueParams.collateralToken, - oracle: morphoBlueParams.oracle, - irm: morphoBlueParams.irm, - lltv: morphoBlueParams.lltv - }) - ); - assertApproxEqAbs(totalSupplyAssets, totalBaseBefore + amountPaid, 1); - assertApproxEqAbs( - totalSupplyShares, - totalSharesBefore + hyperdrive.convertToShares(amountPaid), - 1 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the Hyperdrive instance's base balance doesn't change - // and that the trader's base balance decreased by the amount paid. - assertEq( - config.baseToken.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance - ); - assertEq( - config.baseToken.balanceOf(trader), - traderBalancesBefore.baseBalance - amountPaid - ); - - // Ensure that the shares balances were updated correctly. - (, uint256 hyperdriveSharesAfter) = getTokenBalances( - address(hyperdrive) - ); - (, uint256 traderSharesAfter) = getTokenBalances(address(trader)); - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance + - hyperdrive.convertToShares(amountPaid), - 3 - ); - assertEq(traderSharesAfter, traderBalancesBefore.sharesBalance); - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - /// @param trader The trader that is withdrawing. - /// @param baseProceeds The base proceeds of the deposit. - /// @param asBase Whether the withdrawal was made with base or vault shares. - /// @param totalBaseBefore The total base before the withdrawal. - /// @param totalSharesBefore The total shares before the withdrawal. - /// @param traderBalancesBefore The trader balances before the withdrawal. - /// @param hyperdriveBalancesBefore The hyperdrive balances before the withdrawal. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Vault shares withdrawals are not supported for this instance. - if (!asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total supply decreased by the base proceeds. - ( - uint256 totalSupplyAssets, - uint256 totalSupplyShares, - , - - ) = morphoBlueParams.morpho.expectedMarketBalances( - MarketParams({ - loanToken: address(config.baseToken), - collateralToken: morphoBlueParams.collateralToken, - oracle: morphoBlueParams.oracle, - irm: morphoBlueParams.irm, - lltv: morphoBlueParams.lltv - }) - ); - assertApproxEqAbs(totalSupplyAssets, totalBaseBefore - baseProceeds, 1); - assertApproxEqAbs( - totalSupplyShares, - totalSharesBefore - hyperdrive.convertToShares(baseProceeds), - 3 - ); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances Hyperdrive base balance doesn't - // change and that the trader's base balance decreased by the amount - // paid. - assertApproxEqAbs( - config.baseToken.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance, - 1 - ); - assertEq( - config.baseToken.balanceOf(trader), - traderBalancesBefore.baseBalance + baseProceeds - ); - - // Ensure that the shares balances were updated correctly. - (, uint256 hyperdriveSharesAfter) = getTokenBalances( - address(hyperdrive) - ); - (, uint256 traderSharesAfter) = getTokenBalances(address(trader)); - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance - - hyperdrive.convertToShares(baseProceeds), - 3 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance, - 1 - ); - } - /// Getters /// /// @dev Test the instances getters. In the case of Morpho Blue, we need to diff --git a/test/instances/morpho-blue/MorphoBlue_USDe_DAI_Hyperdrive.t.sol b/test/instances/morpho-blue/MorphoBlue_USDe_DAI_Hyperdrive.t.sol index 7497af38c..a924373ee 100644 --- a/test/instances/morpho-blue/MorphoBlue_USDe_DAI_Hyperdrive.t.sol +++ b/test/instances/morpho-blue/MorphoBlue_USDe_DAI_Hyperdrive.t.sol @@ -83,7 +83,10 @@ contract MorphoBlue_USDe_DAI_HyperdriveTest is roundTripLongMaturityWithSharesTolerance: 0, roundTripShortInstantaneousWithSharesUpperBoundTolerance: 0, roundTripShortInstantaneousWithSharesTolerance: 0, - roundTripShortMaturityWithSharesTolerance: 0 + roundTripShortMaturityWithSharesTolerance: 0, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 }), IMorphoBlueHyperdrive.MorphoBlueParams({ // The mainnet Morpho Blue pool diff --git a/test/instances/morpho-blue/MorphoBlue_sUSDe_DAI_Hyperdrive.t.sol b/test/instances/morpho-blue/MorphoBlue_sUSDe_DAI_Hyperdrive.t.sol index 7cdffbfd7..1a0a229a6 100644 --- a/test/instances/morpho-blue/MorphoBlue_sUSDe_DAI_Hyperdrive.t.sol +++ b/test/instances/morpho-blue/MorphoBlue_sUSDe_DAI_Hyperdrive.t.sol @@ -83,7 +83,10 @@ contract MorphoBlue_sUSDe_DAI_HyperdriveTest is roundTripLongMaturityWithSharesTolerance: 0, roundTripShortInstantaneousWithSharesUpperBoundTolerance: 0, roundTripShortInstantaneousWithSharesTolerance: 0, - roundTripShortMaturityWithSharesTolerance: 0 + roundTripShortMaturityWithSharesTolerance: 0, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 }), IMorphoBlueHyperdrive.MorphoBlueParams({ // The mainnet Morpho Blue pool diff --git a/test/instances/morpho-blue/MorphoBlue_wstETH_USDA_Hyperdrive.t.sol b/test/instances/morpho-blue/MorphoBlue_wstETH_USDA_Hyperdrive.t.sol index b7ed16cfa..73070ba27 100644 --- a/test/instances/morpho-blue/MorphoBlue_wstETH_USDA_Hyperdrive.t.sol +++ b/test/instances/morpho-blue/MorphoBlue_wstETH_USDA_Hyperdrive.t.sol @@ -83,7 +83,10 @@ contract MorphoBlue_wstETH_USDA_HyperdriveTest is roundTripLongMaturityWithSharesTolerance: 0, roundTripShortInstantaneousWithSharesUpperBoundTolerance: 0, roundTripShortInstantaneousWithSharesTolerance: 0, - roundTripShortMaturityWithSharesTolerance: 0 + roundTripShortMaturityWithSharesTolerance: 0, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 }), IMorphoBlueHyperdrive.MorphoBlueParams({ // The mainnet Morpho Blue pool diff --git a/test/instances/morpho-blue/MorphoBlue_wstETH_USDC_Hyperdrive.t.sol b/test/instances/morpho-blue/MorphoBlue_wstETH_USDC_Hyperdrive.t.sol index 1d578f441..3953c5ad9 100644 --- a/test/instances/morpho-blue/MorphoBlue_wstETH_USDC_Hyperdrive.t.sol +++ b/test/instances/morpho-blue/MorphoBlue_wstETH_USDC_Hyperdrive.t.sol @@ -85,7 +85,10 @@ contract MorphoBlue_wstETH_USDC_HyperdriveTest is roundTripLongMaturityWithSharesTolerance: 0, roundTripShortInstantaneousWithSharesUpperBoundTolerance: 0, roundTripShortInstantaneousWithSharesTolerance: 0, - roundTripShortMaturityWithSharesTolerance: 0 + roundTripShortMaturityWithSharesTolerance: 0, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 3 }), IMorphoBlueHyperdrive.MorphoBlueParams({ // The mainnet Morpho Blue pool diff --git a/test/instances/reth/RETHHyperdrive.t.sol b/test/instances/reth/RETHHyperdrive.t.sol index aa16eaccf..44ac834ec 100644 --- a/test/instances/reth/RETHHyperdrive.t.sol +++ b/test/instances/reth/RETHHyperdrive.t.sol @@ -27,9 +27,9 @@ contract RETHHyperdriveTest is InstanceTest { using Lib for *; using stdStorage for StdStorage; - // Rocket Network contracts can be upgraded and addresses changed. - // We can safely assume these addresses are accurate because - // this testing suite is forked from block 19429100. + // @dev Rocket Network contracts can be upgraded and addresses changed. We + /// can safely assume these addresses are accurate because this testing + /// suite is forked from block 19429100. IRocketStorage internal constant ROCKET_STORAGE = IRocketStorage(0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46); IRocketTokenRETH internal constant rocketTokenRETH = @@ -38,65 +38,70 @@ contract RETHHyperdriveTest is InstanceTest { IRocketNetworkBalances(0x07FCaBCbe4ff0d80c2b1eb42855C0131b6cba2F4); IRocketDepositPool internal constant rocketDepositPool = IRocketDepositPool(0xDD3f50F8A6CafbE9b31a427582963f465E745AF8); + address internal constant rocketVault = + address(0x3bDC69C4E5e13E52A65f5583c23EFB9636b469d6); - // Whale accounts. + /// @dev Whale accounts. address internal RETH_WHALE = 0xCc9EE9483f662091a1de4795249E24aC0aC2630f; address[] internal whaleAccounts = [RETH_WHALE]; - // The configuration for the Instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "RETHHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: whaleAccounts, - baseToken: IERC20(ETH), - vaultSharesToken: IERC20(rocketTokenRETH), - shareTolerance: 1e5, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - enableBaseDeposits: false, - enableShareDeposits: true, - enableBaseWithdraws: true, - enableShareWithdraws: true, - baseWithdrawError: new bytes(0), - isRebasing: false, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 1e3, - roundTripLpWithdrawalSharesWithBaseTolerance: 1e3, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithBaseTolerance: 1e3, - roundTripLongMaturityWithBaseUpperBoundTolerance: 1e3, - roundTripLongMaturityWithBaseTolerance: 1e3, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithBaseTolerance: 1e3, - roundTripShortMaturityWithBaseTolerance: 1e3, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 2e3, - roundTripLpWithdrawalSharesWithSharesTolerance: 2e3, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e4, - roundTripLongMaturityWithSharesUpperBoundTolerance: 100, - roundTripLongMaturityWithSharesTolerance: 3e3, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e3, - roundTripShortMaturityWithSharesTolerance: 1e3 - }); - - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "RETHHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: whaleAccounts, + baseToken: IERC20(ETH), + vaultSharesToken: IERC20(rocketTokenRETH), + shareTolerance: 1e5, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + enableBaseDeposits: false, + enableShareDeposits: true, + enableBaseWithdraws: true, + enableShareWithdraws: true, + baseWithdrawError: new bytes(0), + isRebasing: false, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 1e3, + roundTripLpWithdrawalSharesWithBaseTolerance: 1e3, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithBaseTolerance: 1e3, + roundTripLongMaturityWithBaseUpperBoundTolerance: 1e3, + roundTripLongMaturityWithBaseTolerance: 1e3, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithBaseTolerance: 1e3, + roundTripShortMaturityWithBaseTolerance: 1e3, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 2e3, + roundTripLpWithdrawalSharesWithSharesTolerance: 2e3, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e4, + roundTripLongMaturityWithSharesUpperBoundTolerance: 100, + roundTripLongMaturityWithSharesTolerance: 3e3, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e3, + roundTripShortMaturityWithSharesTolerance: 1e3, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __mainnet_fork(19_429_100) { // Give the rETH contract ETH to mimic adequate withdrawable liquidity. vm.deal(address(rocketTokenRETH), 50_000e18); @@ -113,7 +118,9 @@ contract RETHHyperdriveTest is InstanceTest { return new bytes(0); } - /// @dev Converts base amount to the equivalent amount in rETH. + /// @dev Converts base amount to the equivalent about in rETH. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -122,14 +129,18 @@ contract RETHHyperdriveTest is InstanceTest { } /// @dev Converts share amount to the equivalent amount in ETH. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( - uint256 baseAmount + uint256 shareAmount ) internal view override returns (uint256) { // Rocket Pool has a built-in function for computing price in terms of base. - return rocketTokenRETH.getEthValue(baseAmount); + return rocketTokenRETH.getEthValue(shareAmount); } /// @dev Deploys the rETH deployer coordinator contract. + /// @param _factory The address of the Hyperdrive factory contract. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -137,7 +148,7 @@ contract RETHHyperdriveTest is InstanceTest { return address( new RETHHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address(new RETHHyperdriveCoreDeployer()), address(new RETHTarget0Deployer()), @@ -151,152 +162,28 @@ contract RETHHyperdriveTest is InstanceTest { } /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { return ( - rocketNetworkBalances.getTotalETHBalance(), - rocketNetworkBalances.getTotalRETHSupply() + rocketVault.balance + address(rocketTokenRETH).balance, + rocketTokenRETH.totalSupply() ); } /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. function getTokenBalances( address account ) internal view override returns (uint256, uint256) { - uint256 rethBalance = rocketTokenRETH.balanceOf(account); - return (rocketTokenRETH.getEthValue(rethBalance), rethBalance); - } - - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 amount, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Deposits as base is not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Convert the amount in terms of shares. - amount = convertToShares(amount); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the rETH balances were updated correctly. - assertApproxEqAbs( - rocketTokenRETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance + amount, - 1 - ); - assertApproxEqAbs( - rocketTokenRETH.balanceOf(trader), - traderBalancesBefore.sharesBalance - amount, - 1 - ); - - // Ensure the total base supply was updated correctly. - assertEq(rocketNetworkBalances.getTotalETHBalance(), totalBaseBefore); - - // Ensure the total supply was updated correctly. - assertEq(rocketTokenRETH.totalSupply(), totalSharesBefore); - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Convert baseProceeds to shares to verify accounting. - uint256 shareProceeds = rocketTokenRETH.getRethValue(baseProceeds); - - if (asBase) { - // Ensure the total amount of rETH were updated correctly. - assertApproxEqAbs( - rocketTokenRETH.totalSupply(), - totalSharesBefore - shareProceeds, - 1 - ); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq( - trader.balance, - traderBalancesBefore.ETHBalance + baseProceeds - ); - - // Ensure the rETH balances were updated correctly. - assertApproxEqAbs( - rocketTokenRETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance - shareProceeds, - 1 - ); - assertEq( - rocketTokenRETH.balanceOf(address(trader)), - traderBalancesBefore.sharesBalance - ); - - // Ensure the total base supply was updated correctly. - assertEq( - rocketNetworkBalances.getTotalETHBalance(), - totalBaseBefore - ); - - // Ensure the total supply was updated correctly. - assertApproxEqAbs( - rocketTokenRETH.totalSupply(), - totalSharesBefore - shareProceeds, - 1 - ); - } else { - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure the rETH balances were updated correctly. - assertApproxEqAbs( - rocketTokenRETH.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance - shareProceeds, - 1 - ); - assertApproxEqAbs( - rocketTokenRETH.balanceOf(address(trader)), - traderBalancesBefore.sharesBalance + shareProceeds, - 1 - ); - - // Ensure the total base supply was updated correctly. - assertEq( - rocketNetworkBalances.getTotalETHBalance(), - totalBaseBefore - ); - - // Ensure the total supply was updated correctly. - assertEq(rocketTokenRETH.totalSupply(), totalSharesBefore); - } + return (account.balance, rocketTokenRETH.balanceOf(account)); } /// Getters /// + /// @dev Test the instances getters. function test_getters() external view { (, uint256 totalShares) = getTokenBalances(address(hyperdrive)); assertEq(hyperdrive.totalShares(), totalShares); @@ -304,6 +191,9 @@ contract RETHHyperdriveTest is InstanceTest { /// Price Per Share /// + /// @dev Fuzz test that verifies that the vault share price is the price + /// that dictates the conversion between base and shares. + /// @param basePaid the fuzz parameter for the base paid. function test_pricePerVaultShare(uint256 basePaid) external { // Ensure the share prices are equal upon market inception. uint256 vaultSharePrice = hyperdrive.getPoolInfo().vaultSharePrice; @@ -330,6 +220,9 @@ contract RETHHyperdriveTest is InstanceTest { /// Helpers /// + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. function advanceTime( uint256 timeDelta, int256 variableRate @@ -352,6 +245,8 @@ contract RETHHyperdriveTest is InstanceTest { vm.stopPrank(); } + /// @dev Tests that advance time works correctly by advancing the time and + /// ensuring that the vault share price was updated correctly. function test_advanced_time() external { vm.stopPrank(); diff --git a/test/instances/rseth-linea/RsETHLineaHyperdrive.t.sol b/test/instances/rseth-linea/RsETHLineaHyperdrive.t.sol index a8f4a6a07..d0f25fa57 100644 --- a/test/instances/rseth-linea/RsETHLineaHyperdrive.t.sol +++ b/test/instances/rseth-linea/RsETHLineaHyperdrive.t.sol @@ -26,78 +26,81 @@ contract RsETHLineaHyperdriveTest is InstanceTest { using Lib for *; using stdStorage for StdStorage; - // The Kelp DAO deposit contract on Linea. The rsETH/ETH price is used as - // the vault share price. + /// @dev The Kelp DAO deposit contract on Linea. The rsETH/ETH price is used + /// as the vault share price. IRSETHPoolV2 internal constant RSETH_POOL = IRSETHPoolV2(0x057297e44A3364139EDCF3e1594d6917eD7688c2); - // The address of wrsETH on Linea. + /// @dev The address of wrsETH on Linea. IERC20 internal constant WRSETH = IERC20(0xD2671165570f41BBB3B0097893300b6EB6101E6C); - // Whale accounts. + /// @dev Whale accounts. address internal WRSETH_WHALE = address(0x4DCb388488622e47683EAd1a147947140a31e485); address[] internal vaultSharesTokenWhaleAccounts = [WRSETH_WHALE]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "RsETHLineaHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, - baseToken: IERC20(ETH), - vaultSharesToken: WRSETH, - shareTolerance: 0, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - enableBaseDeposits: true, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - baseWithdrawError: abi.encodeWithSelector( - IHyperdrive.UnsupportedToken.selector - ), - isRebasing: false, - // NOTE: Base withdrawals are disabled, so the tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 100, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 100, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e4, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e4, - roundTripLongMaturityWithSharesUpperBoundTolerance: 100, - roundTripLongMaturityWithSharesTolerance: 3e3, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e3, - roundTripShortMaturityWithSharesTolerance: 1e4 - }); - - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "RsETHLineaHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, + baseToken: IERC20(ETH), + vaultSharesToken: WRSETH, + shareTolerance: 0, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: true, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + baseWithdrawError: abi.encodeWithSelector( + IHyperdrive.UnsupportedToken.selector + ), + isRebasing: false, + // NOTE: Base withdrawals are disabled, so the tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 100, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 100, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e4, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e4, + roundTripLongMaturityWithSharesUpperBoundTolerance: 100, + roundTripLongMaturityWithSharesTolerance: 3e3, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e3, + roundTripShortMaturityWithSharesTolerance: 1e4, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __linea_fork(8_431_727) { // Invoke the instance testing suite setup. super.setUp(); @@ -141,7 +144,7 @@ contract RsETHLineaHyperdriveTest is InstanceTest { return address( new RsETHLineaHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address(new RsETHLineaHyperdriveCoreDeployer(RSETH_POOL)), address(new RsETHLineaTarget0Deployer(RSETH_POOL)), @@ -171,168 +174,6 @@ contract RsETHLineaHyperdriveTest is InstanceTest { return (account.balance, WRSETH.balanceOf(account)); } - /// @dev Verifies that deposit accounting is correct when opening positions. - /// @param trader The trader that is depositing. - /// @param amountPaid The amount that was deposited. - /// @param asBase Whether the deposit was made with base or vault shares. - /// @param totalBaseBefore The total base before the deposit. - /// @param totalSharesBefore The total shares before the deposit. - /// @param traderBalancesBefore The trader balances before the deposit. - /// @param hyperdriveBalancesBefore The hyperdrive balances before the deposit. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - if (asBase) { - // Ensure that the total supply increased the same. - (uint256 totalBaseAfter, uint256 totalSharesAfter) = getSupply(); - assertEq(totalBaseAfter, totalBaseBefore + amountPaid); - assertEq( - totalSharesAfter, - totalSharesBefore + hyperdrive.convertToShares(amountPaid) - ); - - // Ensure that the trader's ETH balance decreased and that - // Hyperdrive's stayed the same. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq( - trader.balance, - traderBalancesBefore.ETHBalance - amountPaid - ); - - // Ensure that the trader's base balance decreased and that - // Hyperdrive's stayed the same. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - ( - uint256 traderBaseAfter, - uint256 traderSharesAfter - ) = getTokenBalances(trader); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq( - traderBaseAfter, - traderBalancesBefore.baseBalance - amountPaid - ); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance + - hyperdrive.convertToShares(amountPaid), - 2 - ); - assertEq(traderSharesAfter, traderBalancesBefore.sharesBalance); - } else { - // Ensure that the total supply stayed the same. - (uint256 totalBaseAfter, uint256 totalSharesAfter) = getSupply(); - assertEq(totalBaseAfter, totalBaseBefore); - assertEq(totalSharesAfter, totalSharesBefore); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - ( - uint256 traderBaseAfter, - uint256 traderSharesAfter - ) = getTokenBalances(trader); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance + - hyperdrive.convertToShares(amountPaid), - 2 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance - - hyperdrive.convertToShares(amountPaid), - 2 - ); - } - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - /// @param trader The trader that is withdrawing. - /// @param baseProceeds The base proceeds of the deposit. - /// @param asBase Whether the withdrawal was made with base or vault shares. - /// @param totalBaseBefore The total base before the withdrawal. - /// @param totalSharesBefore The total shares before the withdrawal. - /// @param traderBalancesBefore The trader balances before the withdrawal. - /// @param hyperdriveBalancesBefore The hyperdrive balances before the withdrawal. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base withdrawals are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total supply stayed the same. - (uint256 totalBaseAfter, uint256 totalSharesAfter) = getSupply(); - assertEq(totalBaseAfter, totalBaseBefore); - assertEq(totalSharesAfter, totalSharesBefore); - - // Ensure that the ETH balances didn't change. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the base balances didn't change. - ( - uint256 hyperdriveBaseAfter, - uint256 hyperdriveSharesAfter - ) = getTokenBalances(address(hyperdrive)); - (uint256 traderBaseAfter, uint256 traderSharesAfter) = getTokenBalances( - trader - ); - assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); - assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); - - // Ensure that the shares balances were updated correctly. - assertApproxEqAbs( - hyperdriveSharesAfter, - hyperdriveBalancesBefore.sharesBalance - - hyperdrive.convertToShares(baseProceeds), - 2 - ); - assertApproxEqAbs( - traderSharesAfter, - traderBalancesBefore.sharesBalance + - hyperdrive.convertToShares(baseProceeds), - 2 - ); - } - /// Getters /// /// @dev Test the instances getters. diff --git a/test/instances/steth/StETHHyperdrive.t.sol b/test/instances/steth/StETHHyperdrive.t.sol index 8da00ce2b..b4b99acca 100644 --- a/test/instances/steth/StETHHyperdrive.t.sol +++ b/test/instances/steth/StETHHyperdrive.t.sol @@ -29,76 +29,80 @@ contract StETHHyperdriveTest is InstanceTest { using Lib for *; using stdStorage for StdStorage; - // The Lido storage location that tracks buffered ether reserves. We can - // simulate the accrual of interest by updating this value. + /// @dev The Lido storage location that tracks buffered ether reserves. We + /// can simulate the accrual of interest by updating this value. bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); + /// @dev The stETH contract. ILido internal constant LIDO = ILido(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); - // Whale accounts. + /// @dev Whale accounts. address internal STETH_WHALE = 0x1982b2F5814301d4e9a8b0201555376e62F82428; address[] internal whaleAccounts = [STETH_WHALE]; - // The configuration for the instance testing suite. - InstanceTestConfig internal __testConfig = - InstanceTestConfig({ - name: "Hyperdrive", - kind: "StETHHyperdrive", - decimals: 18, - baseTokenWhaleAccounts: new address[](0), - vaultSharesTokenWhaleAccounts: whaleAccounts, - baseToken: IERC20(ETH), - vaultSharesToken: IERC20(LIDO), - shareTolerance: 1e5, - minimumShareReserves: 1e15, - minimumTransactionAmount: 1e15, - positionDuration: POSITION_DURATION, - enableBaseDeposits: true, - enableShareDeposits: true, - enableBaseWithdraws: false, - enableShareWithdraws: true, - baseWithdrawError: abi.encodeWithSelector( - IHyperdrive.UnsupportedToken.selector - ), - isRebasing: true, - fees: IHyperdrive.Fees({ - curve: 0, - flat: 0, - governanceLP: 0, - governanceZombie: 0 - }), - // NOTE: Base withdrawals are disabled, so the tolerances are zero. - // - // The base test tolerances. - roundTripLpInstantaneousWithBaseTolerance: 0, - roundTripLpWithdrawalSharesWithBaseTolerance: 0, - roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripLongInstantaneousWithBaseTolerance: 0, - roundTripLongMaturityWithBaseUpperBoundTolerance: 0, - roundTripLongMaturityWithBaseTolerance: 0, - roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, - roundTripShortInstantaneousWithBaseTolerance: 0, - roundTripShortMaturityWithBaseTolerance: 0, - // The share test tolerances. - closeLongWithSharesTolerance: 20, - closeShortWithSharesTolerance: 100, - roundTripLpInstantaneousWithSharesTolerance: 1e5, - roundTripLpWithdrawalSharesWithSharesTolerance: 1e5, - roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripLongInstantaneousWithSharesTolerance: 1e4, - roundTripLongMaturityWithSharesUpperBoundTolerance: 100, - roundTripLongMaturityWithSharesTolerance: 3e3, - roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, - roundTripShortInstantaneousWithSharesTolerance: 1e3, - roundTripShortMaturityWithSharesTolerance: 1e3 - }); - - /// @dev Instantiates the instance testing suite with the configuration. - constructor() InstanceTest(__testConfig) {} - - /// @dev Forge function that is invoked to setup the testing environment. + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + InstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "StETHHyperdrive", + decimals: 18, + baseTokenWhaleAccounts: new address[](0), + vaultSharesTokenWhaleAccounts: whaleAccounts, + baseToken: IERC20(ETH), + vaultSharesToken: IERC20(LIDO), + shareTolerance: 1e5, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + enableBaseDeposits: true, + enableShareDeposits: true, + enableBaseWithdraws: false, + enableShareWithdraws: true, + baseWithdrawError: abi.encodeWithSelector( + IHyperdrive.UnsupportedToken.selector + ), + isRebasing: true, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + // NOTE: Base withdrawals are disabled, so the tolerances are zero. + // + // The base test tolerances. + roundTripLpInstantaneousWithBaseTolerance: 0, + roundTripLpWithdrawalSharesWithBaseTolerance: 0, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripLongInstantaneousWithBaseTolerance: 0, + roundTripLongMaturityWithBaseUpperBoundTolerance: 0, + roundTripLongMaturityWithBaseTolerance: 0, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 0, + roundTripShortInstantaneousWithBaseTolerance: 0, + roundTripShortMaturityWithBaseTolerance: 0, + // The share test tolerances. + closeLongWithSharesTolerance: 20, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e5, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e5, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e4, + roundTripLongMaturityWithSharesUpperBoundTolerance: 100, + roundTripLongMaturityWithSharesTolerance: 3e3, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e3, + roundTripShortMaturityWithSharesTolerance: 1e3, + // The verification tolerances. + verifyDepositTolerance: 2, + verifyWithdrawalTolerance: 2 + }) + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. function setUp() public override __mainnet_fork(17_376_154) { // Invoke the instance testing suite setup. super.setUp(); @@ -113,6 +117,8 @@ contract StETHHyperdriveTest is InstanceTest { } /// @dev Converts base amount to the equivalent about in stETH. + /// @param baseAmount The base amount. + /// @return The converted share amount. function convertToShares( uint256 baseAmount ) internal view override returns (uint256) { @@ -123,6 +129,8 @@ contract StETHHyperdriveTest is InstanceTest { } /// @dev Converts share amount to the equivalent amount in ETH. + /// @param shareAmount The share amount. + /// @return The converted base amount. function convertToBase( uint256 shareAmount ) internal view override returns (uint256) { @@ -132,6 +140,7 @@ contract StETHHyperdriveTest is InstanceTest { /// @dev Deploys the rETH deployer coordinator contract. /// @param _factory The address of the Hyperdrive factory. + /// @return The coordinator address. function deployCoordinator( address _factory ) internal override returns (address) { @@ -139,7 +148,7 @@ contract StETHHyperdriveTest is InstanceTest { return address( new StETHHyperdriveDeployerCoordinator( - string.concat(__testConfig.name, "DeployerCoordinator"), + string.concat(config.name, "DeployerCoordinator"), _factory, address(new StETHHyperdriveCoreDeployer()), address(new StETHTarget0Deployer()), @@ -153,156 +162,25 @@ contract StETHHyperdriveTest is InstanceTest { } /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. function getSupply() internal view override returns (uint256, uint256) { return (LIDO.getTotalPooledEther(), LIDO.getTotalShares()); } /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. function getTokenBalances( address account ) internal view override returns (uint256, uint256) { - return (LIDO.balanceOf(account), LIDO.sharesOf(account)); - } - - /// @dev Verifies that deposit accounting is correct when opening positions. - function verifyDeposit( - address trader, - uint256 amountPaid, - bool asBase, - uint256 totalBaseBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - if (asBase) { - // Ensure that the amount of pooled ether increased by the base paid. - assertEq(LIDO.getTotalPooledEther(), totalBaseBefore + amountPaid); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(bob.balance, traderBalancesBefore.ETHBalance - amountPaid); - - // Ensure that the stETH balances were updated correctly. - assertApproxEqAbs( - LIDO.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance + amountPaid, - 1 - ); - assertEq(LIDO.balanceOf(trader), traderBalancesBefore.baseBalance); - - // Ensure that the stETH shares were updated correctly. - uint256 expectedShares = amountPaid.mulDivDown( - totalSharesBefore, - totalBaseBefore - ); - assertEq(LIDO.getTotalShares(), totalSharesBefore + expectedShares); - assertEq( - LIDO.sharesOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance + expectedShares - ); - assertEq(LIDO.sharesOf(bob), traderBalancesBefore.sharesBalance); - } else { - // Ensure that the amount of pooled ether stays the same. - assertEq(LIDO.getTotalPooledEther(), totalBaseBefore); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the stETH balances were updated correctly. - assertApproxEqAbs( - LIDO.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance + amountPaid, - 1 - ); - assertApproxEqAbs( - LIDO.balanceOf(trader), - traderBalancesBefore.baseBalance - amountPaid, - 1 - ); - - // Ensure that the stETH shares were updated correctly. - uint256 expectedShares = amountPaid.mulDivDown( - totalSharesBefore, - totalBaseBefore - ); - assertEq(LIDO.getTotalShares(), totalSharesBefore); - assertApproxEqAbs( - LIDO.sharesOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance + expectedShares, - 1 - ); - assertApproxEqAbs( - LIDO.sharesOf(trader), - traderBalancesBefore.sharesBalance - expectedShares, - 1 - ); - } - } - - /// @dev Verifies that withdrawal accounting is correct when closing positions. - function verifyWithdrawal( - address trader, - uint256 baseProceeds, - bool asBase, - uint256 totalPooledEtherBefore, - uint256 totalSharesBefore, - AccountBalances memory traderBalancesBefore, - AccountBalances memory hyperdriveBalancesBefore - ) internal view override { - // Base withdraws are not supported for this instance. - if (asBase) { - revert IHyperdrive.UnsupportedToken(); - } - - // Ensure that the total pooled ether and shares stays the same. - assertEq(LIDO.getTotalPooledEther(), totalPooledEtherBefore); - assertApproxEqAbs(LIDO.getTotalShares(), totalSharesBefore, 1); - - // Ensure that the ETH balances were updated correctly. - assertEq( - address(hyperdrive).balance, - hyperdriveBalancesBefore.ETHBalance - ); - assertEq(trader.balance, traderBalancesBefore.ETHBalance); - - // Ensure that the stETH balances were updated correctly. - assertApproxEqAbs( - LIDO.balanceOf(address(hyperdrive)), - hyperdriveBalancesBefore.baseBalance - baseProceeds, - 1 - ); - assertApproxEqAbs( - LIDO.balanceOf(trader), - traderBalancesBefore.baseBalance + baseProceeds, - 1 - ); - - // Ensure that the stETH shares were updated correctly. - uint256 expectedShares = baseProceeds.mulDivDown( - totalSharesBefore, - totalPooledEtherBefore - ); - assertApproxEqAbs( - LIDO.sharesOf(address(hyperdrive)), - hyperdriveBalancesBefore.sharesBalance - expectedShares, - 1 - ); - assertApproxEqAbs( - LIDO.sharesOf(trader), - traderBalancesBefore.sharesBalance + expectedShares, - 1 - ); + return (account.balance, LIDO.sharesOf(account)); } /// Getters /// + /// @dev Test the instances getters. function test_getters() external view { (, uint256 totalShares) = getTokenBalances(address(hyperdrive)); assertEq(hyperdrive.totalShares(), totalShares); @@ -310,6 +188,9 @@ contract StETHHyperdriveTest is InstanceTest { /// Price Per Share /// + /// @dev Fuzz test that verifies that the vault share price is the price + /// that dictates the conversion between base and shares. + /// @param basePaid the fuzz parameter for the base paid. function test__pricePerVaultShare(uint256 basePaid) external { // Ensure that the share price is the expected value. uint256 totalPooledEther = LIDO.getTotalPooledEther(); @@ -336,6 +217,9 @@ contract StETHHyperdriveTest is InstanceTest { /// Helpers /// + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. function advanceTime( uint256 timeDelta, int256 variableRate diff --git a/test/utils/InstanceTest.sol b/test/utils/InstanceTest.sol index 307c062df..7e5e8f403 100644 --- a/test/utils/InstanceTest.sol +++ b/test/utils/InstanceTest.sol @@ -75,74 +75,82 @@ abstract contract InstanceTest is HyperdriveTest { /// token. If it is, we have to handle balances and approvals /// differently. bool isRebasing; - /// @dev The equality tolerance for the close long with shares test. + /// @dev The equality tolerance in wei for the close long with shares + /// test. uint256 closeLongWithSharesTolerance; - /// @dev The equality tolerance for the close short with shares test. + /// @dev The equality tolerance in wei for the close short with shares + /// test. uint256 closeShortWithSharesTolerance; - /// @dev The equality tolerance for the instantaneous LP with base test. + /// @dev The equality tolerance in wei for the instantaneous LP with + /// base test. uint256 roundTripLpInstantaneousWithBaseTolerance; - /// @dev The equality tolerance for the instantaneous LP with shares test. + /// @dev The equality tolerance in wei for the instantaneous LP with + /// shares test. uint256 roundTripLpInstantaneousWithSharesTolerance; - /// @dev The equality tolerance for the LP withdrawal shares with base - /// test. + /// @dev The equality tolerance in wei for the LP withdrawal shares with + /// base test. uint256 roundTripLpWithdrawalSharesWithBaseTolerance; - /// @dev The equality tolerance for the LP withdrawal shares with shares - /// test. + /// @dev The equality tolerance in wei for the LP withdrawal shares with + /// shares test. uint256 roundTripLpWithdrawalSharesWithSharesTolerance; - /// @dev The upper bound tolerance for the instantaneous long round trip - /// with base test. + /// @dev The upper bound tolerance in wei for the instantaneous long + /// round trip with base test. uint256 roundTripLongInstantaneousWithBaseUpperBoundTolerance; - /// @dev The equality tolerance for the instantaneous long round trip - /// with base test. + /// @dev The equality tolerance in wei for the instantaneous long round + /// trip with base test. uint256 roundTripLongInstantaneousWithBaseTolerance; - /// @dev The upper bound tolerance for the instantaneous long round trip - /// with shares test. + /// @dev The upper bound tolerance in wei for the instantaneous long + /// round trip with shares test. uint256 roundTripLongInstantaneousWithSharesUpperBoundTolerance; - /// @dev The equality tolerance for the instantaneous long round trip - /// with shares test. + /// @dev The equality tolerance in wei for the instantaneous long round + /// trip with shares test. uint256 roundTripLongInstantaneousWithSharesTolerance; - /// @dev The upper bound tolerance for the long at maturity round trip - /// with base test. + /// @dev The upper bound tolerance in wei for the long at maturity round + /// trip with base test. uint256 roundTripLongMaturityWithBaseUpperBoundTolerance; - /// @dev The equality tolerance for the long at maturity round trip - /// with base test. + /// @dev The equality tolerance in wei for the long at maturity round + /// trip with base test. uint256 roundTripLongMaturityWithBaseTolerance; - /// @dev The upper bound tolerance for the long at maturity round trip - /// with shares test. + /// @dev The upper bound tolerance in wei for the long at maturity round + /// trip with shares test. uint256 roundTripLongMaturityWithSharesUpperBoundTolerance; - /// @dev The equality tolerance for the long at maturity round trip - /// with shares test. + /// @dev The equality tolerance in wei for the long at maturity round + /// trip with shares test. uint256 roundTripLongMaturityWithSharesTolerance; - /// @dev The upper bound tolerance for the instantaneous short round trip - /// with base test. + /// @dev The upper bound tolerance in wei for the instantaneous short + /// round trip with base test. uint256 roundTripShortInstantaneousWithBaseUpperBoundTolerance; - /// @dev The equality tolerance for the instantaneous short round trip - /// with base test. + /// @dev The equality tolerance in wei for the instantaneous short round + /// trip with base test. uint256 roundTripShortInstantaneousWithBaseTolerance; - /// @dev The upper bound tolerance for the instantaneous short round trip - /// with shares test. + /// @dev The upper bound tolerance in wei for the instantaneous short + /// round trip with shares test. uint256 roundTripShortInstantaneousWithSharesUpperBoundTolerance; - /// @dev The equality tolerance for the instantaneous short round trip - /// with shares test. + /// @dev The equality tolerance in wei for the instantaneous short round + /// trip with shares test. uint256 roundTripShortInstantaneousWithSharesTolerance; - /// @dev The equality tolerance for the short at maturity round trip - /// with base test. + /// @dev The equality tolerance in wei for the short at maturity round + /// trip with base test. uint256 roundTripShortMaturityWithBaseTolerance; - /// @dev The equality tolerance for the short at maturity round trip - /// with shares test. + /// @dev The equality tolerance in wei for the short at maturity round + /// trip with shares test. uint256 roundTripShortMaturityWithSharesTolerance; + /// @dev The equality tolerance in wei for `verifyDeposit`. + uint256 verifyDepositTolerance; + /// @dev The equality tolerance in wei for `verifyWithdrawal`. + uint256 verifyWithdrawalTolerance; } - // Fixed rate used to configure market. + /// @dev Fixed rate used to configure market. uint256 internal constant FIXED_RATE = 0.05e18; - // Default deployment constants. + /// @dev Default deployment constants. bytes32 private constant DEFAULT_DEPLOYMENT_ID = bytes32(uint256(0xdeadbeef)); bytes32 private constant DEFAULT_DEPLOYMENT_SALT = bytes32(uint256(0xdeadbabe)); - // The configuration for the Instance testing suite. + /// @dev The configuration for the Instance testing suite. InstanceTestConfig internal config; /// @dev The configuration for the pool. This allows test authors to specify @@ -150,13 +158,13 @@ abstract contract InstanceTest is HyperdriveTest { /// parameters will be overridden by factory parameters. IHyperdrive.PoolDeployConfig internal poolConfig; - // The Hyperdrive factory. + /// @dev The Hyperdrive factory. IHyperdriveFactory internal factory; - // The address of the deployer coordinator contract. + /// @dev The address of the deployer coordinator contract. address internal deployerCoordinator; - // Flag for denoting if the base token is ETH. + /// @dev Flag for denoting if the base token is ETH. bool internal immutable isBaseETH; /// @dev Constructor for the Instance testing suite. @@ -515,34 +523,145 @@ abstract contract InstanceTest is HyperdriveTest { uint256 shareAmount ) internal view virtual returns (uint256 baseAmount); - /// @dev A virtual function that ensures the deposit accounting is correct - /// when opening positions. - /// @param trader The account opening the position. - /// @param basePaid The amount the position was opened with in terms of base. - /// @param asBase Flag to determine whether the position was opened with the base or share token. - /// @param totalBaseBefore Total supply of the base token before the trade. - /// @param totalSharesBefore Total supply of the share token before the trade. - /// @param traderBalancesBefore Balances of tokens of the trader before the trade. - /// @param hyperdriveBalancesBefore Balances of tokens of the Hyperdrive contract before the trade. + /// @dev Verifies that deposit accounting is correct when opening positions. + /// @param trader The trader that is depositing. + /// @param amountPaid The amount that was deposited. + /// @param asBase Whether the deposit was made with base or vault shares. + /// @param totalBaseBefore The total base before the deposit. + /// @param totalSharesBefore The total shares before the deposit. + /// @param traderBalancesBefore The trader balances before the deposit. + /// @param hyperdriveBalancesBefore The hyperdrive balances before the deposit. function verifyDeposit( address trader, - uint256 basePaid, + uint256 amountPaid, bool asBase, uint256 totalBaseBefore, uint256 totalSharesBefore, AccountBalances memory traderBalancesBefore, AccountBalances memory hyperdriveBalancesBefore - ) internal virtual; - - /// @dev A virtual function that ensures the withdrawal accounting is correct - /// when opening positions. - /// @param trader The account opening the position. - /// @param baseProceeds The amount the position was opened with in terms of base. - /// @param asBase Flag to determine whether the position was opened with the base or share token. - /// @param totalBaseBefore Total supply of the base token before the trade. - /// @param totalSharesBefore Total supply of the share token before the trade. - /// @param traderBalancesBefore Balances of tokens of the trader before the trade. - /// @param hyperdriveBalancesBefore Balances of tokens of the Hyperdrive contract before the trade. + ) internal view { + // If we're depositing with base, verify that base was pulled from the + // trader into Hyperdrive and correctly converted to vault shares. + if (asBase) { + // If base deposits aren't supported, we revert since this route + // shouldn't be called. + if (!config.enableBaseDeposits) { + revert IHyperdrive.UnsupportedToken(); + } + + // Ensure that the total supply increased by the base paid. + (uint256 totalBase, uint256 totalShares) = getSupply(); + assertApproxEqAbs(totalBase, totalBaseBefore + amountPaid, 1); + assertApproxEqAbs( + totalShares, + totalSharesBefore + hyperdrive.convertToShares(amountPaid), + config.verifyDepositTolerance + ); + + // If the base token isn't ETH, ensure that the ETH balances didn't + // change. + if (!isBaseETH) { + assertEq( + address(hyperdrive).balance, + hyperdriveBalancesBefore.ETHBalance + ); + assertEq(trader.balance, traderBalancesBefore.ETHBalance); + } + // Otherwise, the trader's ETH balance should be reduced by the + // amount paid. + else { + assertEq( + address(hyperdrive).balance, + hyperdriveBalancesBefore.ETHBalance + ); + assertEq( + trader.balance, + traderBalancesBefore.ETHBalance - amountPaid + ); + } + + // Ensure that the Hyperdrive instance's base balance doesn't change + // and that the trader's base balance decreased by the amount paid. + ( + uint256 hyperdriveBaseAfter, + uint256 hyperdriveSharesAfter + ) = getTokenBalances(address(hyperdrive)); + ( + uint256 traderBaseAfter, + uint256 traderSharesAfter + ) = getTokenBalances(address(trader)); + assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); + assertEq( + traderBaseAfter, + traderBalancesBefore.baseBalance - amountPaid + ); + + // Ensure that the shares balances were updated correctly. + assertApproxEqAbs( + hyperdriveSharesAfter, + hyperdriveBalancesBefore.sharesBalance + + hyperdrive.convertToShares(amountPaid), + config.verifyDepositTolerance + ); + assertEq(traderSharesAfter, traderBalancesBefore.sharesBalance); + } + // If we're depositing with vault shares, verify that the vault shares + // were pulled from the trader into Hyperdrive. + else { + // If vault share deposits aren't supported, we revert since this + // route shouldn't be called. + if (!config.enableShareDeposits) { + revert IHyperdrive.UnsupportedToken(); + } + + // Ensure that the total supply and scaled total supply stay the same. + (uint256 totalBase, uint256 totalShares) = getSupply(); + assertEq(totalBase, totalBaseBefore); + assertApproxEqAbs(totalShares, totalSharesBefore, 1); + + // Ensure that the ETH balances didn't change. + assertEq( + address(hyperdrive).balance, + hyperdriveBalancesBefore.ETHBalance + ); + assertEq(bob.balance, traderBalancesBefore.ETHBalance); + + // Ensure that the base balances didn't change. + ( + uint256 hyperdriveBaseAfter, + uint256 hyperdriveSharesAfter + ) = getTokenBalances(address(hyperdrive)); + ( + uint256 traderBaseAfter, + uint256 traderSharesAfter + ) = getTokenBalances(address(trader)); + assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); + assertEq(traderBaseAfter, traderBalancesBefore.baseBalance); + + // Ensure that the shares balances were updated correctly. + assertApproxEqAbs( + hyperdriveSharesAfter, + hyperdriveBalancesBefore.sharesBalance + + convertToShares(amountPaid), + config.verifyDepositTolerance + ); + assertApproxEqAbs( + traderSharesAfter, + traderBalancesBefore.sharesBalance - + convertToShares(amountPaid), + config.verifyDepositTolerance + ); + } + } + + /// @dev Verifies that withdrawal accounting is correct when closing positions. + /// @param trader The trader that is withdrawing. + /// @param baseProceeds The base proceeds of the deposit. + /// @param asBase Whether the withdrawal was made with base or vault shares. + /// @param totalBaseBefore The total base before the withdrawal. + /// @param totalSharesBefore The total shares before the withdrawal. + /// @param traderBalancesBefore The trader balances before the withdrawal. + /// @param hyperdriveBalancesBefore The hyperdrive balances before the withdrawal. function verifyWithdrawal( address trader, uint256 baseProceeds, @@ -551,7 +670,128 @@ abstract contract InstanceTest is HyperdriveTest { uint256 totalSharesBefore, AccountBalances memory traderBalancesBefore, AccountBalances memory hyperdriveBalancesBefore - ) internal virtual; + ) internal view { + // If we're withdrawing with base, ensure that Hyperdrive's vault shares + // were reduced, successfully converted into base, and distributed to + // the trader. + if (asBase) { + // If base withdrawals aren't supported, we revert since this + // route shouldn't be called. + if (!config.enableBaseWithdraws) { + revert IHyperdrive.UnsupportedToken(); + } + + // Ensure that the total supply decreased by the base proceeds. + (uint256 totalBase, uint256 totalShares) = getSupply(); + assertApproxEqAbs(totalBase, totalBaseBefore - baseProceeds, 1); + assertApproxEqAbs( + totalShares, + totalSharesBefore - convertToShares(baseProceeds), + config.verifyWithdrawalTolerance + ); + + // If the base token isn't ETH, ensure that the ETH balances didn't + // change. + if (!isBaseETH) { + assertEq( + address(hyperdrive).balance, + hyperdriveBalancesBefore.ETHBalance + ); + assertEq(bob.balance, traderBalancesBefore.ETHBalance); + } + // Otherwise, ensure that the trader's ETH balance increased. + else { + assertEq( + address(hyperdrive).balance, + hyperdriveBalancesBefore.ETHBalance + ); + assertEq( + bob.balance, + traderBalancesBefore.ETHBalance + baseProceeds + ); + } + + // Ensure that the base balances were updated correctly. + ( + uint256 hyperdriveBaseAfter, + uint256 hyperdriveSharesAfter + ) = getTokenBalances(address(hyperdrive)); + ( + uint256 traderBaseAfter, + uint256 traderSharesAfter + ) = getTokenBalances(address(trader)); + assertEq(hyperdriveBaseAfter, hyperdriveBalancesBefore.baseBalance); + assertEq( + traderBaseAfter, + traderBalancesBefore.baseBalance + baseProceeds + ); + + // Ensure that the shares balances were updated correctly. + assertApproxEqAbs( + hyperdriveSharesAfter, + hyperdriveBalancesBefore.sharesBalance - + convertToShares(baseProceeds), + config.verifyWithdrawalTolerance + ); + assertApproxEqAbs( + traderSharesAfter, + traderBalancesBefore.sharesBalance, + 1 + ); + } else { + // If vault share withdrawals aren't supported, we revert since this + // route shouldn't be called. + if (!config.enableShareWithdraws) { + revert IHyperdrive.UnsupportedToken(); + } + + // Ensure that the total supply stayed the same. + (uint256 totalBase, uint256 totalShares) = getSupply(); + assertApproxEqAbs(totalBase, totalBaseBefore, 1); + assertApproxEqAbs(totalShares, totalSharesBefore, 1); + + // Ensure that the ETH balances didn't change. + assertEq( + address(hyperdrive).balance, + hyperdriveBalancesBefore.ETHBalance + ); + assertEq(bob.balance, traderBalancesBefore.ETHBalance); + + // Ensure that the base balances didn't change. + ( + uint256 hyperdriveBaseAfter, + uint256 hyperdriveSharesAfter + ) = getTokenBalances(address(hyperdrive)); + ( + uint256 traderBaseAfter, + uint256 traderSharesAfter + ) = getTokenBalances(address(trader)); + assertApproxEqAbs( + hyperdriveBaseAfter, + hyperdriveBalancesBefore.baseBalance, + 1 + ); + assertApproxEqAbs( + traderBaseAfter, + traderBalancesBefore.baseBalance, + 1 + ); + + // Ensure that the shares balances were updated correctly. + assertApproxEqAbs( + hyperdriveSharesAfter, + hyperdriveBalancesBefore.sharesBalance - + convertToShares(baseProceeds), + config.verifyWithdrawalTolerance + ); + assertApproxEqAbs( + traderSharesAfter, + traderBalancesBefore.sharesBalance + + convertToShares(baseProceeds), + config.verifyWithdrawalTolerance + ); + } + } /// @dev A virtual function that fetches the token balance information of an account. /// @param account The account to fetch token balances of. @@ -566,6 +806,7 @@ abstract contract InstanceTest is HyperdriveTest { /// @return totalSupplyShares The total supply of the share token. function getSupply() internal + view virtual returns (uint256 totalSupplyBase, uint256 totalSupplyShares);