diff --git a/.gitmodules b/.gitmodules index 4e4365394..3008c272f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,6 +12,3 @@ [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate -[submodule "lib/yield-daddy"] - path = lib/yield-daddy - url = https://github.com/timeless-fi/yield-daddy diff --git a/contracts/src/deployers/HyperdriveDeployerCoordinator.sol b/contracts/src/deployers/HyperdriveDeployerCoordinator.sol index 931b7deac..4b782ab10 100644 --- a/contracts/src/deployers/HyperdriveDeployerCoordinator.sol +++ b/contracts/src/deployers/HyperdriveDeployerCoordinator.sol @@ -57,7 +57,7 @@ abstract contract HyperdriveDeployerCoordinator is IDeployerCoordinator { function deploy( IHyperdrive.PoolDeployConfig memory _deployConfig, bytes memory _extraData - ) external override returns (address) { + ) public virtual returns (address) { // Convert the deploy config into the pool config and set the initial // vault share price. IHyperdrive.PoolConfig memory _config = _copyPoolConfig(_deployConfig); diff --git a/contracts/src/deployers/erc4626/ERC4626HyperdriveDeployerCoordinator.sol b/contracts/src/deployers/erc4626/ERC4626HyperdriveDeployerCoordinator.sol index 1ccfbaf70..f6523b533 100644 --- a/contracts/src/deployers/erc4626/ERC4626HyperdriveDeployerCoordinator.sol +++ b/contracts/src/deployers/erc4626/ERC4626HyperdriveDeployerCoordinator.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import { IERC4626 } from "../../interfaces/IERC4626.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; import { ONE } from "../../libraries/FixedPointMath.sol"; import { HyperdriveDeployerCoordinator } from "../HyperdriveDeployerCoordinator.sol"; @@ -34,6 +35,42 @@ contract ERC4626HyperdriveDeployerCoordinator is HyperdriveDeployerCoordinator { ) {} + /// @notice Deploys a Hyperdrive instance with the given parameters. + /// @param _deployConfig The deploy configuration of the Hyperdrive pool. + /// @param _extraData The extra data that contains the pool and sweep targets. + /// @return The address of the newly deployed ERC4626Hyperdrive Instance. + function deploy( + IHyperdrive.PoolDeployConfig memory _deployConfig, + bytes memory _extraData + ) public override returns (address) { + // Ensure that the minimum share reserves are large enough to meet the + // minimum requirements for safety. + // + // NOTE: Some pools may require larger minimum share reserves to be + // considered safe. This is just a sanity check. + if ( + _deployConfig.minimumShareReserves < + 10 ** (_deployConfig.baseToken.decimals() - 4) + ) { + revert IHyperdrive.InvalidMinimumShareReserves(); + } + + // Ensure that the minimum transaction amount is large enough to meet + // the minimum requirements for safety. + // + // NOTE: Some pools may require larger minimum transaction amounts to be + // considered safe. This is just a sanity check. + if ( + _deployConfig.minimumShareReserves < + 10 ** (_deployConfig.baseToken.decimals() - 4) + ) { + revert IHyperdrive.InvalidMinimumTransactionAmount(); + } + + // Deploy the Hyperdrive instance. + return super.deploy(_deployConfig, _extraData); + } + /// @dev Gets the initial vault share price of the Hyperdrive pool. /// @param _extraData The extra data passed to the child deployers. /// @return The initial vault share price of the Hyperdrive pool. diff --git a/contracts/src/factory/HyperdriveFactory.sol b/contracts/src/factory/HyperdriveFactory.sol index 3495b182d..c1b9c44ee 100644 --- a/contracts/src/factory/HyperdriveFactory.sol +++ b/contracts/src/factory/HyperdriveFactory.sol @@ -7,6 +7,7 @@ import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveFactory } from "../interfaces/IHyperdriveFactory.sol"; import { IDeployerCoordinator } from "../interfaces/IDeployerCoordinator.sol"; import { FixedPointMath, ONE } from "../libraries/FixedPointMath.sol"; +import { HyperdriveMath } from "../libraries/HyperdriveMath.sol"; /// @author DELV /// @title HyperdriveFactory @@ -61,6 +62,18 @@ contract HyperdriveFactory is IHyperdriveFactory { /// deployments. uint256 public maxPositionDuration; + /// @notice The minimum fixed APR that can be used by new deployments. + uint256 public minFixedAPR; + + /// @notice The maximum fixed APR that can be used by new deployments. + uint256 public maxFixedAPR; + + /// @notice The minimum time stretch APR that can be used by new deployments. + uint256 public minTimeStretchAPR; + + /// @notice The maximum time stretch APR that can be used by new deployments. + uint256 public maxTimeStretchAPR; + /// @notice The minimum fee parameters that can be used by new deployments. IHyperdrive.Fees internal _minFees; @@ -93,6 +106,16 @@ contract HyperdriveFactory is IHyperdriveFactory { /// @dev The maximum position duration that can be used in new /// deployments. uint256 maxPositionDuration; + /// @dev The minimum fixed APR that can be used in new deployments. + uint256 minFixedAPR; + /// @dev The maximum fixed APR that can be used in new deployments. + uint256 maxFixedAPR; + /// @dev The minimum time stretch APR that can be used in new + /// deployments. + uint256 minTimeStretchAPR; + /// @dev The maximum time stretch APR that can be used in new + /// deployments. + uint256 maxTimeStretchAPR; /// @dev The lower bound on the fees that can be used in new deployments. IHyperdrive.Fees minFees; /// @dev The upper bound on the fees that can be used in new deployments. @@ -175,6 +198,24 @@ contract HyperdriveFactory is IHyperdriveFactory { } maxPositionDuration = _factoryConfig.maxPositionDuration; + // Ensure that the minimum fixed APR is less than or equal to the + // maximum fixed APR. + if (_factoryConfig.minFixedAPR > _factoryConfig.maxFixedAPR) { + revert IHyperdriveFactory.InvalidFixedAPR(); + } + minFixedAPR = _factoryConfig.minFixedAPR; + maxFixedAPR = _factoryConfig.maxFixedAPR; + + // Ensure that the minimum time stretch APR is less than or equal to the + // maximum time stretch APR. + if ( + _factoryConfig.minTimeStretchAPR > _factoryConfig.maxTimeStretchAPR + ) { + revert IHyperdriveFactory.InvalidTimeStretchAPR(); + } + minTimeStretchAPR = _factoryConfig.minTimeStretchAPR; + maxTimeStretchAPR = _factoryConfig.maxTimeStretchAPR; + // Ensure that the max fees are each less than or equal to 100% and set // the fees. if ( @@ -373,6 +414,66 @@ contract HyperdriveFactory is IHyperdriveFactory { emit MinPositionDurationUpdated(_minPositionDuration); } + /// @notice Allows governance to update the maximum fixed APR. + /// @param _maxFixedAPR The new maximum fixed APR. + function updateMaxFixedAPR(uint256 _maxFixedAPR) external onlyGovernance { + // Ensure that the maximum fixed APR is greater than or equal to the + // minimum fixed APR. + if (_maxFixedAPR < minFixedAPR) { + revert IHyperdriveFactory.InvalidMaxFixedAPR(); + } + + // Update the maximum fixed APR and emit an event. + maxFixedAPR = _maxFixedAPR; + emit MaxFixedAPRUpdated(_maxFixedAPR); + } + + /// @notice Allows governance to update the minimum fixed APR. + /// @param _minFixedAPR The new minimum fixed APR. + function updateMinFixedAPR(uint256 _minFixedAPR) external onlyGovernance { + // Ensure that the minimum fixed APR is less than or equal to the + // maximum fixed APR. + if (_minFixedAPR > maxFixedAPR) { + revert IHyperdriveFactory.InvalidMinFixedAPR(); + } + + // Update the minimum fixed APR and emit an event. + minFixedAPR = _minFixedAPR; + emit MinFixedAPRUpdated(_minFixedAPR); + } + + /// @notice Allows governance to update the maximum time stretch APR. + /// @param _maxTimeStretchAPR The new maximum time stretch APR. + function updateMaxTimeStretchAPR( + uint256 _maxTimeStretchAPR + ) external onlyGovernance { + // Ensure that the maximum time stretch APR is greater than or equal + // to the minimum time stretch APR. + if (_maxTimeStretchAPR < minTimeStretchAPR) { + revert IHyperdriveFactory.InvalidMaxTimeStretchAPR(); + } + + // Update the maximum time stretch APR and emit an event. + maxTimeStretchAPR = _maxTimeStretchAPR; + emit MaxTimeStretchAPRUpdated(_maxTimeStretchAPR); + } + + /// @notice Allows governance to update the minimum time stretch APR. + /// @param _minTimeStretchAPR The new minimum time stretch APR. + function updateMinTimeStretchAPR( + uint256 _minTimeStretchAPR + ) external onlyGovernance { + // Ensure that the minimum time stretch APR is less than or equal + // to the maximum time stretch APR. + if (_minTimeStretchAPR > maxTimeStretchAPR) { + revert IHyperdriveFactory.InvalidMinTimeStretchAPR(); + } + + // Update the minimum time stretch APR and emit an event. + minTimeStretchAPR = _minTimeStretchAPR; + emit MinTimeStretchAPRUpdated(_minTimeStretchAPR); + } + /// @notice Allows governance to update the maximum fee parameters. /// @param __maxFees The new maximum fee parameters. function updateMaxFees( @@ -472,8 +573,9 @@ contract HyperdriveFactory is IHyperdriveFactory { /// @param _deployConfig The deploy configuration of the Hyperdrive pool. /// @param _extraData The extra data that contains data necessary for the /// specific deployer. - /// @param _contribution Base token to call init with - /// @param _apr The apr to call init with + /// @param _contribution The contribution amount in base to the pool. + /// @param _fixedAPR The fixed APR used to initialize the pool. + /// @param _timeStretchAPR The time stretch APR used to initialize the pool. /// @param _initializeExtraData The extra data for the `initialize` call. /// @return The hyperdrive address deployed. function deployAndInitialize( @@ -481,7 +583,8 @@ contract HyperdriveFactory is IHyperdriveFactory { IHyperdrive.PoolDeployConfig memory _deployConfig, bytes memory _extraData, uint256 _contribution, - uint256 _apr, + uint256 _fixedAPR, + uint256 _timeStretchAPR, bytes memory _initializeExtraData ) public payable virtual returns (IHyperdrive) { // Ensure that the target deployer has been registered. @@ -530,22 +633,46 @@ contract HyperdriveFactory is IHyperdriveFactory { // and governance addresses aren't set. This ensures that the // deployer isn't trying to set these values. if ( + _deployConfig.governance != address(0) || + _deployConfig.feeCollector != address(0) || _deployConfig.linkerFactory != address(0) || _deployConfig.linkerCodeHash != bytes32(0) || - _deployConfig.feeCollector != address(0) || - _deployConfig.governance != address(0) + _deployConfig.timeStretch != 0 ) { revert IHyperdriveFactory.InvalidDeployConfig(); } + // Ensure that specified fixed APR is within the minimum and maximum + // fixed APRs. + if (_fixedAPR < minFixedAPR || _fixedAPR > maxFixedAPR) { + revert IHyperdriveFactory.InvalidFixedAPR(); + } + + // Calculate the time stretch using the provided APR and ensure that + // the time stretch falls within a safe range and the guards specified + // by governance. + uint256 lowerBound = _fixedAPR.divDown(2e18).max(0.005e18); + if ( + _timeStretchAPR < minTimeStretchAPR.max(lowerBound) || + _timeStretchAPR > + maxTimeStretchAPR.min(_fixedAPR.max(lowerBound).mulDown(2e18)) + ) { + revert IHyperdriveFactory.InvalidTimeStretchAPR(); + } + uint256 timeStretch = HyperdriveMath.calculateTimeStretch( + _timeStretchAPR, + _deployConfig.positionDuration + ); + // Override the config values to the default values set by governance. // The factory assumes the governance role during deployment so that it // can set up some initial values; however the governance role will // ultimately be transferred to the hyperdrive governance address. + _deployConfig.governance = address(this); + _deployConfig.feeCollector = feeCollector; _deployConfig.linkerFactory = linkerFactory; _deployConfig.linkerCodeHash = linkerCodeHash; - _deployConfig.feeCollector = feeCollector; - _deployConfig.governance = address(this); + _deployConfig.timeStretch = timeStretch; // Deploy the Hyperdrive instance with the specified Hyperdrive // deployer. @@ -581,7 +708,7 @@ contract HyperdriveFactory is IHyperdriveFactory { // Initialize the Hyperdrive instance. hyperdrive.initialize{ value: _contribution }( _contribution, - _apr, + _fixedAPR, IHyperdrive.Options({ destination: msg.sender, asBase: true, @@ -607,7 +734,7 @@ contract HyperdriveFactory is IHyperdriveFactory { // Initialize the Hyperdrive instance. hyperdrive.initialize( _contribution, - _apr, + _fixedAPR, IHyperdrive.Options({ destination: msg.sender, asBase: true, diff --git a/contracts/src/instances/steth/StETHBase.sol b/contracts/src/instances/steth/StETHBase.sol index 712003312..fc53d2c58 100644 --- a/contracts/src/instances/steth/StETHBase.sol +++ b/contracts/src/instances/steth/StETHBase.sol @@ -34,6 +34,12 @@ abstract contract StETHBase is HyperdriveBase { if (_minimumShareReserves != 1e15) { revert IHyperdrive.InvalidMinimumShareReserves(); } + + // Ensure that the minimum transaction amount are equal to 1e15. This + // value has been tested to prevent precision issues. + if (_minimumTransactionAmount != 1e15) { + revert IHyperdrive.InvalidMinimumTransactionAmount(); + } } /// Yield Source /// diff --git a/contracts/src/interfaces/IHyperdrive.sol b/contracts/src/interfaces/IHyperdrive.sol index e7a3cfa10..ccdf39a42 100644 --- a/contracts/src/interfaces/IHyperdrive.sol +++ b/contracts/src/interfaces/IHyperdrive.sol @@ -248,6 +248,9 @@ interface IHyperdrive is /// reserves. error InvalidMinimumShareReserves(); + /// @notice Thrown when the minimum transaction amount is too small. + error InvalidMinimumTransactionAmount(); + /// @notice Thrown when the position duration is smaller than the checkpoint /// duration or is not a multiple of the checkpoint duration. error InvalidPositionDuration(); diff --git a/contracts/src/interfaces/IHyperdriveFactory.sol b/contracts/src/interfaces/IHyperdriveFactory.sol index f994ef04a..bbe426639 100644 --- a/contracts/src/interfaces/IHyperdriveFactory.sol +++ b/contracts/src/interfaces/IHyperdriveFactory.sol @@ -43,6 +43,14 @@ interface IHyperdriveFactory { event MinPositionDurationUpdated(uint256 newMinPositionDuration); + event MaxFixedAPRUpdated(uint256 newMaxFixedAPR); + + event MinFixedAPRUpdated(uint256 newMinFixedAPR); + + event MaxTimeStretchAPRUpdated(uint256 newMaxTimeStretchAPR); + + event MinTimeStretchAPRUpdated(uint256 newMinTimeStretchAPR); + event MaxFeesUpdated(IHyperdrive.Fees newMaxFees); event MinFeesUpdated(IHyperdrive.Fees newMinFees); @@ -83,6 +91,18 @@ interface IHyperdriveFactory { error InvalidPositionDuration(); + error InvalidMaxFixedAPR(); + + error InvalidMinFixedAPR(); + + error InvalidFixedAPR(); + + error InvalidMaxTimeStretchAPR(); + + error InvalidMinTimeStretchAPR(); + + error InvalidTimeStretchAPR(); + error TransferFailed(); error Unauthorized(); diff --git a/contracts/src/libraries/HyperdriveMath.sol b/contracts/src/libraries/HyperdriveMath.sol index cc1086e40..1e7432dae 100644 --- a/contracts/src/libraries/HyperdriveMath.sol +++ b/contracts/src/libraries/HyperdriveMath.sol @@ -17,6 +17,62 @@ library HyperdriveMath { using FixedPointMath for int256; using SafeCast for uint256; + /// @dev Calculates the time stretch parameter for the YieldSpace curve. + /// This parameter modifies the curvature in order to support a larger + /// or smaller range of APRs. The lower the time stretch, the flatter + /// the curve will be and the narrower the range of feasible APRs. The + /// higher the time stretch, the higher the curvature will be and the + /// wider the range of feasible APRs. + /// @param _apr The target APR to use when calculating the time stretch. + /// @param _positionDuration The position duration in seconds. + /// @return The time stretch parameter. + function calculateTimeStretch( + uint256 _apr, + uint256 _positionDuration + ) internal pure returns (uint256) { + // Calculate the benchmark time stretch. This time stretch is tuned for + // a position duration of 1 year. + uint256 timeStretch = uint256(5.24592e18).divDown( + uint256(0.04665e18).mulDown(_apr * 100) + ); + timeStretch = ONE.divDown(timeStretch); + + // If the position duration is 1 year, we can return the benchmark. + if (_positionDuration == 365 days) { + return timeStretch; + } + + // Otherwise, we need to adjust the time stretch to account for the + // position duration. We do this by holding the reserve ratio constant + // and solving for the new time stretch directly. + // + // We can calculate the spot price at the target apr and position + // duration as: + // + // p = 1 / (1 + apr * (positionDuration / 365 days)) + // + // We then calculate the benchmark reserve ratio, `ratio`, implied by + // the benchmark time stretch using the `calculateInitialBondReserves` + // function. + // + // We can then derive the adjusted time stretch using the spot price + // calculation: + // + // p = ratio ** timeStretch + // => + // timeStretch = ln(p) / ln(ratio) + uint256 targetSpotPrice = ONE.divDown( + ONE + _apr.mulDivDown(_positionDuration, 365 days) + ); + uint256 benchmarkReserveRatio = ONE.divDown( + calculateInitialBondReserves(ONE, ONE, _apr, 365 days, timeStretch) + ); + return + uint256(-int256(targetSpotPrice).ln()).divDown( + uint256(-int256(benchmarkReserveRatio).ln()) + ); + } + /// @dev Calculates the spot price of bonds in terms of base. This /// calculation underestimates the pool's spot price. /// @param _effectiveShareReserves The pool's effective share reserves. The diff --git a/contracts/test/MockERC4626.sol b/contracts/test/MockERC4626.sol index b0ea20dfc..0048bbe65 100644 --- a/contracts/test/MockERC4626.sol +++ b/contracts/test/MockERC4626.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import { Authority } from "solmate/auth/Auth.sol"; import { MultiRolesAuthority } from "solmate/auth/authorities/MultiRolesAuthority.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; -import { ERC4626 } from "solmate/mixins/ERC4626.sol"; +import { ERC4626 } from "solmate/tokens/ERC4626.sol"; import { FixedPointMath } from "../src/libraries/FixedPointMath.sol"; import { ERC20Mintable } from "./ERC20Mintable.sol"; diff --git a/contracts/test/MockHyperdriveMath.sol b/contracts/test/MockHyperdriveMath.sol index 661b9de77..ac6344e16 100644 --- a/contracts/test/MockHyperdriveMath.sol +++ b/contracts/test/MockHyperdriveMath.sol @@ -167,7 +167,7 @@ contract MockHyperdriveMath { uint256 apr, uint256 positionDuration ) external pure returns (uint256) { - uint256 result = HyperdriveUtils.calculateTimeStretch( + uint256 result = HyperdriveMath.calculateTimeStretch( apr, positionDuration ); diff --git a/contracts/test/MockMultiToken.sol b/contracts/test/MockMultiToken.sol index 703a68622..5497e7a16 100644 --- a/contracts/test/MockMultiToken.sol +++ b/contracts/test/MockMultiToken.sol @@ -7,6 +7,7 @@ import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { IMultiToken } from "contracts/src/interfaces/IMultiToken.sol"; import { HyperdriveMultiToken } from "contracts/src/internal/HyperdriveMultiToken.sol"; import { HyperdriveStorage } from "contracts/src/internal/HyperdriveStorage.sol"; +import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { MockHyperdriveBase, MockHyperdriveTarget0 } from "contracts/test/MockHyperdrive.sol"; import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; @@ -57,7 +58,7 @@ contract MockMultiToken is HyperdriveMultiToken, MockHyperdriveBase { minimumTransactionAmount: 1e15, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( + timeStretch: HyperdriveMath.calculateTimeStretch( 0.05e18, 365 days ), @@ -84,7 +85,7 @@ contract MockMultiToken is HyperdriveMultiToken, MockHyperdriveBase { minimumTransactionAmount: 1e15, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( + timeStretch: HyperdriveMath.calculateTimeStretch( 0.05e18, 365 days ), diff --git a/crates/hyperdrive-wrappers/build.rs b/crates/hyperdrive-wrappers/build.rs index a575faaea..4de819f1f 100644 --- a/crates/hyperdrive-wrappers/build.rs +++ b/crates/hyperdrive-wrappers/build.rs @@ -17,7 +17,6 @@ const TARGETS: &[&str] = &[ "ERC4626Target1", "ERC4626Target2", "ERC4626Target3", - "ERC4626HyperdriveFactory", // Test Contracts "ERC20Mintable", "EtchingVault", diff --git a/lib/solmate b/lib/solmate index 2001af43a..c89230993 160000 --- a/lib/solmate +++ b/lib/solmate @@ -1 +1 @@ -Subproject commit 2001af43aedb46fdc2335d2a7714fb2dae7cfcd1 +Subproject commit c892309933b25c03d32b1b0d674df7ae292ba925 diff --git a/lib/yield-daddy b/lib/yield-daddy deleted file mode 160000 index d9ffa1cf2..000000000 --- a/lib/yield-daddy +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d9ffa1cf23a630112851285207d8c9fdcc2af748 diff --git a/script/DevnetMigration.s.sol b/script/DevnetMigration.s.sol index ee16eab2e..c28bce40e 100644 --- a/script/DevnetMigration.s.sol +++ b/script/DevnetMigration.s.sol @@ -21,6 +21,7 @@ import { IERC20 } from "contracts/src/interfaces/IERC20.sol"; import { IERC4626 } from "contracts/src/interfaces/IERC4626.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { ILido } from "contracts/src/interfaces/ILido.sol"; +import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockERC4626 } from "contracts/test/MockERC4626.sol"; @@ -39,10 +40,16 @@ import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; /// particular legal or regulatory significance. contract DevnetMigration is Script { using stdJson for string; + using HyperdriveMath for *; using HyperdriveUtils for *; function setUp() external {} + ERC20Mintable internal baseToken; + HyperdriveFactory internal factory; + MockERC4626 internal pool; + MockLido internal lido; + struct Config { // admin configuration address admin; @@ -63,6 +70,10 @@ contract DevnetMigration is Script { uint256 factoryMaxCheckpointDuration; uint256 factoryMinPositionDuration; uint256 factoryMaxPositionDuration; + uint256 factoryMinFixedAPR; + uint256 factoryMaxFixedAPR; + uint256 factoryMinTimeStretchAPR; + uint256 factoryMaxTimeStretchAPR; uint256 factoryMinCurveFee; uint256 factoryMinFlatFee; uint256 factoryMinGovernanceLPFee; @@ -73,24 +84,24 @@ contract DevnetMigration is Script { uint256 factoryMaxGovernanceZombieFee; // erc4626 hyperdrive configuration uint256 erc4626HyperdriveContribution; - uint256 erc4626HyperdriveFixedRate; + uint256 erc4626HyperdriveFixedAPR; + uint256 erc4626HyperdriveTimeStretchAPR; uint256 erc4626HyperdriveMinimumShareReserves; uint256 erc4626HyperdriveMinimumTransactionAmount; uint256 erc4626HyperdrivePositionDuration; uint256 erc4626HyperdriveCheckpointDuration; - uint256 erc4626HyperdriveTimeStretchApr; uint256 erc4626HyperdriveCurveFee; uint256 erc4626HyperdriveFlatFee; uint256 erc4626HyperdriveGovernanceLPFee; uint256 erc4626HyperdriveGovernanceZombieFee; // steth hyperdrive configuration uint256 stethHyperdriveContribution; - uint256 stethHyperdriveFixedRate; + uint256 stethHyperdriveFixedAPR; + uint256 stethHyperdriveTimeStretchAPR; uint256 stethHyperdriveMinimumShareReserves; uint256 stethHyperdriveMinimumTransactionAmount; uint256 stethHyperdrivePositionDuration; uint256 stethHyperdriveCheckpointDuration; - uint256 stethHyperdriveTimeStretchApr; uint256 stethHyperdriveCurveFee; uint256 stethHyperdriveFlatFee; uint256 stethHyperdriveGovernanceLPFee; @@ -146,6 +157,22 @@ contract DevnetMigration is Script { "FACTORY_MAX_POSITION_DURATION", uint256(10 * 365 days) ), + factoryMinFixedAPR: vm.envOr( + "FACTORY_MIN_FIXED_APR", + uint256(0.01e18) + ), + factoryMaxFixedAPR: vm.envOr( + "FACTORY_MAX_FIXED_APR", + uint256(0.5e18) + ), + factoryMinTimeStretchAPR: vm.envOr( + "FACTORY_MIN_TIME_STRETCH_APR", + uint256(0.01e18) + ), + factoryMaxTimeStretchAPR: vm.envOr( + "FACTORY_MAX_TIME_STRETCH_APR", + uint256(0.5e18) + ), factoryMinCurveFee: vm.envOr( "FACTORY_MIN_CURVE_FEE", uint256(0.001e18) @@ -183,8 +210,8 @@ contract DevnetMigration is Script { "ERC4626_HYPERDRIVE_CONTRIBUTION", uint256(100_000_000e18) ), - erc4626HyperdriveFixedRate: vm.envOr( - "ERC4626_HYPERDRIVE_FIXED_RATE", + erc4626HyperdriveFixedAPR: vm.envOr( + "ERC4626_HYPERDRIVE_FIXED_APR", uint256(0.05e18) ), erc4626HyperdriveMinimumShareReserves: vm.envOr( @@ -203,7 +230,7 @@ contract DevnetMigration is Script { "ERC4626_HYPERDRIVE_CHECKPOINT_DURATION", uint256(1 hours) ), - erc4626HyperdriveTimeStretchApr: vm.envOr( + erc4626HyperdriveTimeStretchAPR: vm.envOr( "ERC4626_HYPERDRIVE_TIME_STRETCH_APR", uint256(0.05e18) ), @@ -228,8 +255,8 @@ contract DevnetMigration is Script { "STETH_HYPERDRIVE_CONTRIBUTION", uint256(50_000e18) ), - stethHyperdriveFixedRate: vm.envOr( - "STETH_HYPERDRIVE_FIXED_RATE", + stethHyperdriveFixedAPR: vm.envOr( + "STETH_HYPERDRIVE_FIXED_APR", uint256(0.035e18) ), stethHyperdriveMinimumShareReserves: vm.envOr( @@ -248,7 +275,7 @@ contract DevnetMigration is Script { "STETH_HYPERDRIVE_CHECKPOINT_DURATION", uint256(1 hours) ), - stethHyperdriveTimeStretchApr: vm.envOr( + stethHyperdriveTimeStretchAPR: vm.envOr( "STETH_HYPERDRIVE_TIME_STRETCH_APR", uint256(0.035e18) ), @@ -271,14 +298,14 @@ contract DevnetMigration is Script { }); // Deploy the base token and the vault. - ERC20Mintable baseToken = new ERC20Mintable( + baseToken = new ERC20Mintable( config.baseTokenName, config.baseTokenSymbol, config.baseTokenDecimals, msg.sender, config.isCompetitionMode ); - MockERC4626 pool = new MockERC4626( + pool = new MockERC4626( baseToken, config.vaultName, config.vaultSymbol, @@ -302,7 +329,7 @@ contract DevnetMigration is Script { // Deploy the mock Lido system. We fund Lido with 1 eth to start to // avoid reverts when we initialize the pool. - MockLido lido = new MockLido( + lido = new MockLido( config.lidoStartingRate, msg.sender, config.isCompetitionMode @@ -311,7 +338,6 @@ contract DevnetMigration is Script { lido.submit{ value: 1 ether }(address(0)); // Deploy the Hyperdrive factory. - HyperdriveFactory factory; { address[] memory defaultPausers = new address[](1); defaultPausers[0] = config.admin; @@ -328,6 +354,10 @@ contract DevnetMigration is Script { maxCheckpointDuration: config.factoryMaxCheckpointDuration, minPositionDuration: config.factoryMinPositionDuration, maxPositionDuration: config.factoryMaxPositionDuration, + minFixedAPR: config.factoryMinFixedAPR, + maxFixedAPR: config.factoryMaxFixedAPR, + minTimeStretchAPR: config.factoryMinTimeStretchAPR, + maxTimeStretchAPR: config.factoryMaxTimeStretchAPR, minFees: IHyperdrive.Fees({ curve: config.factoryMinCurveFee, flat: config.factoryMinFlatFee, @@ -361,10 +391,11 @@ contract DevnetMigration is Script { // Deploy and initialize an initial ERC4626Hyperdrive instance. IHyperdrive erc4626Hyperdrive; { - uint256 contribution = config.erc4626HyperdriveContribution; - uint256 fixedRate = config.erc4626HyperdriveFixedRate; - baseToken.mint(msg.sender, contribution); - baseToken.approve(address(factory), contribution); + baseToken.mint(msg.sender, config.erc4626HyperdriveContribution); + baseToken.approve( + address(factory), + config.erc4626HyperdriveContribution + ); IHyperdrive.PoolDeployConfig memory poolConfig = IHyperdrive .PoolDeployConfig({ baseToken: IERC20(address(baseToken)), @@ -377,11 +408,7 @@ contract DevnetMigration is Script { positionDuration: config.erc4626HyperdrivePositionDuration, checkpointDuration: config .erc4626HyperdriveCheckpointDuration, - timeStretch: config - .erc4626HyperdriveTimeStretchApr - .calculateTimeStretch( - config.erc4626HyperdrivePositionDuration - ), + timeStretch: 0, governance: address(0), feeCollector: address(0), fees: IHyperdrive.Fees({ @@ -396,8 +423,9 @@ contract DevnetMigration is Script { erc4626DeployerCoordinator, poolConfig, abi.encode(address(pool), new address[](0)), - contribution, - fixedRate, + config.erc4626HyperdriveContribution, + config.erc4626HyperdriveFixedAPR, + config.erc4626HyperdriveTimeStretchAPR, new bytes(0) ); } @@ -418,9 +446,7 @@ contract DevnetMigration is Script { // Deploy and initialize an initial StETHHyperdrive instance. IHyperdrive stethHyperdrive; { - uint256 contribution = config.stethHyperdriveContribution; - uint256 fixedRate = config.stethHyperdriveFixedRate; - vm.deal(msg.sender, contribution); + vm.deal(msg.sender, config.stethHyperdriveContribution); IHyperdrive.PoolDeployConfig memory poolConfig = IHyperdrive .PoolDeployConfig({ baseToken: IERC20(address(ETH)), @@ -433,11 +459,7 @@ contract DevnetMigration is Script { positionDuration: config.stethHyperdrivePositionDuration, checkpointDuration: config .stethHyperdriveCheckpointDuration, - timeStretch: config - .stethHyperdriveTimeStretchApr - .calculateTimeStretch( - config.stethHyperdrivePositionDuration - ), + timeStretch: 0, governance: address(0), feeCollector: address(0), fees: IHyperdrive.Fees({ @@ -449,13 +471,14 @@ contract DevnetMigration is Script { }) }); stethHyperdrive = factory.deployAndInitialize{ - value: contribution + value: config.stethHyperdriveContribution }( stethDeployerCoordinator, poolConfig, abi.encode(address(pool), new address[](0)), - contribution, - fixedRate, + config.stethHyperdriveContribution, + config.stethHyperdriveFixedAPR, + config.stethHyperdriveTimeStretchAPR, new bytes(0) ); } diff --git a/test/instances/erc4626/AaveV3ERC4626.t.sol b/test/instances/erc4626/AaveV3ERC4626.t.sol deleted file mode 100644 index 4cdd129ba..000000000 --- a/test/instances/erc4626/AaveV3ERC4626.t.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.19; - -import { AaveV3ERC4626Factory, IPool, IRewardsController, ERC20 } from "yield-daddy/src/aave-v3/AaveV3ERC4626Factory.sol"; -import { IERC20 } from "contracts/src/interfaces/IERC20.sol"; -import { IERC4626 } from "contracts/src/interfaces/IERC4626.sol"; -import { ERC4626ValidationTest } from "./ERC4626Validation.t.sol"; - -contract AaveV3ERC4626Test is ERC4626ValidationTest { - function setUp() public override __mainnet_fork(17_318_972) { - super.setUp(); - - // Aave v3 Lending Pool Contract - IPool pool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2); - AaveV3ERC4626Factory yieldDaddyFactory = new AaveV3ERC4626Factory( - pool, - address(0), - IRewardsController(address(0)) - ); - - // Dai is the underlying token used for Aave instances - ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); - - // Deploy a new instance of an Aave v3 ERC4626 token, for aDai - token = IERC4626(address(yieldDaddyFactory.createERC4626(dai))); - underlyingToken = IERC20(address(dai)); - - // Alice account must be prefunded with lots of the underlyingToken. - address daiWhale = 0x60FaAe176336dAb62e284Fe19B885B095d29fB7F; - whaleTransfer(daiWhale, IERC20(address(dai)), alice); - - _setUp(); - } - - function advanceTimeWithYield( - uint256 timeDelta, - int256 // unused - ) public override { - // Aave derives interest based on time, so all we need - // to do is advance the block timestamp. - vm.warp(block.timestamp + timeDelta); - } -} diff --git a/test/instances/erc4626/ERC4626Hyperdrive.t.sol b/test/instances/erc4626/ERC4626Hyperdrive.t.sol index 6bd5063f4..9a8da9c61 100644 --- a/test/instances/erc4626/ERC4626Hyperdrive.t.sol +++ b/test/instances/erc4626/ERC4626Hyperdrive.t.sol @@ -19,6 +19,7 @@ import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { IDeployerCoordinator } from "contracts/src/interfaces/IDeployerCoordinator.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol"; +import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockERC4626, ERC20 } from "contracts/test/MockERC4626.sol"; @@ -90,6 +91,10 @@ contract ERC4626HyperdriveTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.005e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0, flat: 0, @@ -279,10 +284,7 @@ contract ERC4626HyperdriveTest is HyperdriveTest { minimumTransactionAmount: 0.001e18, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( - apr, - 365 days - ), + timeStretch: 0, governance: address(0), feeCollector: address(0), fees: IHyperdrive.Fees(0, 0, 0, 0) @@ -294,6 +296,7 @@ contract ERC4626HyperdriveTest is HyperdriveTest { abi.encode(address(pool), new address[](0)), contribution, apr, + apr, new bytes(0) ); @@ -333,10 +336,7 @@ contract ERC4626HyperdriveTest is HyperdriveTest { minimumTransactionAmount: 0.001e18, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( - apr, - 365 days - ), + timeStretch: 0, governance: address(0), feeCollector: address(0), fees: IHyperdrive.Fees(0, 0, 0, 0) @@ -348,6 +348,7 @@ contract ERC4626HyperdriveTest is HyperdriveTest { abi.encode(address(pool), new address[](0)), contribution, apr, + apr, new bytes(0) ); diff --git a/test/instances/erc4626/ERC4626Validation.t.sol b/test/instances/erc4626/ERC4626Validation.t.sol index d313e4eb5..e4cf1d129 100644 --- a/test/instances/erc4626/ERC4626Validation.t.sol +++ b/test/instances/erc4626/ERC4626Validation.t.sol @@ -77,6 +77,10 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.005e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0, flat: 0, @@ -100,10 +104,11 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { POSITION_DURATION ); config.baseToken = underlyingToken; - config.feeCollector = address(0); config.governance = address(0); + config.feeCollector = address(0); config.linkerFactory = address(0); config.linkerCodeHash = bytes32(0); + config.timeStretch = 0; uint256 contribution = 7_500e18; vm.stopPrank(); vm.startPrank(alice); @@ -120,6 +125,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { abi.encode(address(token), new address[](0)), contribution, FIXED_RATE, + FIXED_RATE, new bytes(0) ); @@ -149,6 +155,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { config.feeCollector = address(0); config.linkerFactory = address(0); config.linkerCodeHash = bytes32(0); + config.timeStretch = 0; config.baseToken = underlyingToken; // Designed to ensure compatibility ../../contracts/src/instances/ERC4626Hyperdrive.sol#L122C1-L122C1 config.minimumTransactionAmount = hyperdrive @@ -167,6 +174,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { abi.encode(address(token), new address[](0)), contribution, FIXED_RATE, + FIXED_RATE, new bytes(0) ); diff --git a/test/instances/erc4626/Sweep.t.sol b/test/instances/erc4626/Sweep.t.sol index e33746ce4..c6d956140 100644 --- a/test/instances/erc4626/Sweep.t.sol +++ b/test/instances/erc4626/Sweep.t.sol @@ -11,6 +11,7 @@ import { IERC4626 } from "contracts/src/interfaces/IERC4626.sol"; import { IERC4626Hyperdrive } from "contracts/src/interfaces/IERC4626Hyperdrive.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { ONE } from "contracts/src/libraries/FixedPointMath.sol"; +import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockERC4626 } from "contracts/test/MockERC4626.sol"; import { BaseTest } from "test/utils/BaseTest.sol"; @@ -55,10 +56,7 @@ contract SweepTest is BaseTest { minimumTransactionAmount: 0.001e18, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( - 0.01e18, - 365 days - ), + timeStretch: HyperdriveMath.calculateTimeStretch(0.01e18, 365 days), governance: alice, feeCollector: bob, fees: IHyperdrive.Fees(0, 0, 0, 0) diff --git a/test/instances/erc4626/UsdcERC4626.t.sol b/test/instances/erc4626/UsdcERC4626.t.sol index 238c30cd5..1450eebf9 100644 --- a/test/instances/erc4626/UsdcERC4626.t.sol +++ b/test/instances/erc4626/UsdcERC4626.t.sol @@ -85,6 +85,10 @@ contract UsdcERC4626 is ERC4626ValidationTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.005e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0, flat: 0, @@ -111,6 +115,7 @@ contract UsdcERC4626 is ERC4626ValidationTest { config.feeCollector = address(0); config.linkerFactory = address(0); config.linkerCodeHash = bytes32(0); + config.timeStretch = 0; config.baseToken = underlyingToken; config.minimumTransactionAmount = 1e6; config.minimumShareReserves = 1e6; @@ -130,6 +135,7 @@ contract UsdcERC4626 is ERC4626ValidationTest { abi.encode(address(token), new address[](0)), contribution, FIXED_RATE, + FIXED_RATE, new bytes(0) ); diff --git a/test/instances/steth/StETHHyperdrive.t.sol b/test/instances/steth/StETHHyperdrive.t.sol index 2d8328ae0..f7d57c120 100644 --- a/test/instances/steth/StETHHyperdrive.t.sol +++ b/test/instances/steth/StETHHyperdrive.t.sol @@ -62,6 +62,10 @@ contract StETHHyperdriveTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.005e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0, flat: 0, @@ -102,13 +106,10 @@ contract StETHHyperdriveTest is HyperdriveTest { linkerFactory: address(0), linkerCodeHash: bytes32(0), minimumShareReserves: 1e15, - minimumTransactionAmount: 1e14, + minimumTransactionAmount: 1e15, positionDuration: POSITION_DURATION, checkpointDuration: CHECKPOINT_DURATION, - timeStretch: HyperdriveUtils.calculateTimeStretch( - 0.05e18, - POSITION_DURATION - ), + timeStretch: 0, governance: address(0), feeCollector: address(0), fees: IHyperdrive.Fees({ @@ -125,6 +126,7 @@ contract StETHHyperdriveTest is HyperdriveTest { new bytes(0), contribution, FIXED_RATE, + FIXED_RATE, new bytes(0) ); @@ -170,13 +172,10 @@ contract StETHHyperdriveTest is HyperdriveTest { linkerFactory: address(0), linkerCodeHash: bytes32(0), minimumShareReserves: 1e15, - minimumTransactionAmount: 1e14, + minimumTransactionAmount: 1e15, positionDuration: POSITION_DURATION, checkpointDuration: CHECKPOINT_DURATION, - timeStretch: HyperdriveUtils.calculateTimeStretch( - 0.05e18, - POSITION_DURATION - ), + timeStretch: 0, fees: IHyperdrive.Fees({ curve: 0, flat: 0, @@ -191,6 +190,7 @@ contract StETHHyperdriveTest is HyperdriveTest { new bytes(0), contribution, FIXED_RATE, + FIXED_RATE, new bytes(0) ); assertEq(address(bob).balance, bobBalanceBefore - contribution); diff --git a/test/instances/steth/Sweep.t.sol b/test/instances/steth/Sweep.t.sol index 56d9100d8..131f75a1d 100644 --- a/test/instances/steth/Sweep.t.sol +++ b/test/instances/steth/Sweep.t.sol @@ -11,6 +11,7 @@ import { ILido } from "contracts/src/interfaces/ILido.sol"; import { IStETHHyperdrive } from "contracts/src/interfaces/IStETHHyperdrive.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { ONE } from "contracts/src/libraries/FixedPointMath.sol"; +import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockLido } from "contracts/test/MockLido.sol"; import { BaseTest } from "test/utils/BaseTest.sol"; @@ -52,13 +53,10 @@ contract SweepTest is BaseTest { linkerCodeHash: bytes32(0), initialVaultSharePrice: ONE, minimumShareReserves: 1e15, - minimumTransactionAmount: 1e12, + minimumTransactionAmount: 1e15, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( - 0.01e18, - 365 days - ), + timeStretch: HyperdriveMath.calculateTimeStretch(0.01e18, 365 days), governance: alice, feeCollector: bob, fees: IHyperdrive.Fees(0, 0, 0, 0) diff --git a/test/integrations/factory/HyperdriveFactory.t.sol b/test/integrations/factory/HyperdriveFactory.t.sol index a04f04583..20556f8ac 100644 --- a/test/integrations/factory/HyperdriveFactory.t.sol +++ b/test/integrations/factory/HyperdriveFactory.t.sol @@ -51,6 +51,14 @@ contract HyperdriveFactoryTest is HyperdriveTest { event MinPositionDurationUpdated(uint256 newMinPositionDuration); + event MaxFixedAPRUpdated(uint256 newMaxFixedAPR); + + event MinFixedAPRUpdated(uint256 newMinFixedAPR); + + event MaxTimeStretchAPRUpdated(uint256 newMaxTimeStretchAPR); + + event MinTimeStretchAPRUpdated(uint256 newMinTimeStretchAPR); + event MaxFeesUpdated(IHyperdrive.Fees newMaxFees); event MinFeesUpdated(IHyperdrive.Fees newMinFees); @@ -74,6 +82,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.005e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0.001e18, flat: 0.0001e18, @@ -112,6 +124,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -136,6 +152,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -160,6 +180,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 7 hours, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -184,6 +208,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 8.5 hours, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -205,6 +233,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 8 hours, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -227,6 +259,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days + 30 minutes, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -248,6 +284,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 6 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -270,6 +310,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days + 30 minutes, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -291,6 +335,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(2 * ONE, ONE, ONE, ONE), linkerFactory: address(0), @@ -312,6 +360,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, 2 * ONE, ONE, ONE), linkerFactory: address(0), @@ -333,6 +385,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, 2 * ONE, ONE), linkerFactory: address(0), @@ -354,6 +410,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, 2 * ONE), linkerFactory: address(0), @@ -375,6 +435,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(ONE, 0, 0, 0), maxFees: IHyperdrive.Fees(0, ONE, ONE, ONE), linkerFactory: address(0), @@ -396,6 +460,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, ONE, 0, 0), maxFees: IHyperdrive.Fees(ONE, 0, ONE, ONE), linkerFactory: address(0), @@ -417,6 +485,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, ONE, 0), maxFees: IHyperdrive.Fees(ONE, ONE, 0, ONE), linkerFactory: address(0), @@ -438,6 +510,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, ONE), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, 0), linkerFactory: address(0), @@ -458,6 +534,10 @@ contract HyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees(0, 0, 0, 0), maxFees: IHyperdrive.Fees(ONE, ONE, ONE, ONE), linkerFactory: address(0xdeadbeef), @@ -477,6 +557,8 @@ contract HyperdriveFactoryTest is HyperdriveTest { assertEq(factory.maxCheckpointDuration(), config.maxCheckpointDuration); assertEq(factory.minPositionDuration(), config.minPositionDuration); assertEq(factory.maxPositionDuration(), config.maxPositionDuration); + assertEq(factory.minTimeStretchAPR(), config.minTimeStretchAPR); + assertEq(factory.maxTimeStretchAPR(), config.maxTimeStretchAPR); assertEq( keccak256(abi.encode(factory.minFees())), keccak256(abi.encode(config.minFees)) @@ -868,6 +950,118 @@ contract HyperdriveFactoryTest is HyperdriveTest { assertEq(factory.minPositionDuration(), newMinPositionDuration); } + function test_updateMaxFixedAPR() external { + uint256 newMaxFixedAPR = 0.25e18; + + // Ensure that the max fixed APR can't be updated by someone other than + // the current governance. + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.Unauthorized.selector); + factory.updateMaxFixedAPR(newMaxFixedAPR); + + // Ensure that the max fixed APR can't be set to a value less than the + // min fixed APR. + vm.stopPrank(); + vm.startPrank(factory.governance()); + uint256 minFixedAPR = factory.minFixedAPR(); + vm.expectRevert(IHyperdriveFactory.InvalidMaxFixedAPR.selector); + factory.updateMaxFixedAPR(minFixedAPR - 1); + + // Ensure that the max fixed APR was updated successfully and that the + // correct event was emitted. + vm.stopPrank(); + vm.startPrank(factory.governance()); + vm.expectEmit(true, true, true, true); + emit MaxFixedAPRUpdated(newMaxFixedAPR); + factory.updateMaxFixedAPR(newMaxFixedAPR); + assertEq(factory.maxFixedAPR(), newMaxFixedAPR); + } + + function test_updateMinFixedAPR() external { + uint256 newMinFixedAPR = 0.01e18; + + // Ensure that the min fixed APR can't be updated by someone other than + // the current governance. + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.Unauthorized.selector); + factory.updateMinFixedAPR(newMinFixedAPR); + + // Ensure that the min fixed APR can't be set to a value greater than + // the max fixed APR. + vm.stopPrank(); + vm.startPrank(factory.governance()); + uint256 maxFixedAPR = factory.maxFixedAPR(); + vm.expectRevert(IHyperdriveFactory.InvalidMinFixedAPR.selector); + factory.updateMinFixedAPR(maxFixedAPR + 1); + + // Ensure that the min fixed APR was updated successfully and that the + // correct event was emitted. + vm.stopPrank(); + vm.startPrank(factory.governance()); + vm.expectEmit(true, true, true, true); + emit MinFixedAPRUpdated(newMinFixedAPR); + factory.updateMinFixedAPR(newMinFixedAPR); + assertEq(factory.minFixedAPR(), newMinFixedAPR); + } + + function test_updateMaxTimeStretchAPR() external { + uint256 newMaxTimeStretchAPR = 0.25e18; + + // Ensure that the max timeStretch APR can't be updated by someone + // other than the current governance. + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.Unauthorized.selector); + factory.updateMaxTimeStretchAPR(newMaxTimeStretchAPR); + + // Ensure that the max timeStretch APR can't be set to a value + // less than the min timeStretch APR. + vm.stopPrank(); + vm.startPrank(factory.governance()); + uint256 minTimeStretchAPR = factory.minTimeStretchAPR(); + vm.expectRevert(IHyperdriveFactory.InvalidMaxTimeStretchAPR.selector); + factory.updateMaxTimeStretchAPR(minTimeStretchAPR - 1); + + // Ensure that the max timeStretch APR was updated successfully and + // that the correct event was emitted. + vm.stopPrank(); + vm.startPrank(factory.governance()); + vm.expectEmit(true, true, true, true); + emit MaxTimeStretchAPRUpdated(newMaxTimeStretchAPR); + factory.updateMaxTimeStretchAPR(newMaxTimeStretchAPR); + assertEq(factory.maxTimeStretchAPR(), newMaxTimeStretchAPR); + } + + function test_updateMinTimeStretchAPR() external { + uint256 newMinTimeStretchAPR = 0.01e18; + + // Ensure that the min timeStretch APR can't be updated by someone + // other than the current governance. + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.Unauthorized.selector); + factory.updateMinTimeStretchAPR(newMinTimeStretchAPR); + + // Ensure that the min timeStretch APR can't be set to a value + // greater than the max timeStretch APR. + vm.stopPrank(); + vm.startPrank(factory.governance()); + uint256 maxTimeStretchAPR = factory.maxTimeStretchAPR(); + vm.expectRevert(IHyperdriveFactory.InvalidMinTimeStretchAPR.selector); + factory.updateMinTimeStretchAPR(maxTimeStretchAPR + 1); + + // Ensure that the min timeStretch APR was updated successfully and + // that the correct event was emitted. + vm.stopPrank(); + vm.startPrank(factory.governance()); + vm.expectEmit(true, true, true, true); + emit MinTimeStretchAPRUpdated(newMinTimeStretchAPR); + factory.updateMinTimeStretchAPR(newMinTimeStretchAPR); + assertEq(factory.minTimeStretchAPR(), newMinTimeStretchAPR); + } + function test_updateMaxFees() external { IHyperdrive.Fees memory newMaxFees = IHyperdrive.Fees({ curve: 0.1e18, @@ -1236,10 +1430,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { minimumTransactionAmount: 1e15, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( - 0.05e18, - 365 days - ), + timeStretch: 0, governance: address(0), feeCollector: address(0), fees: IHyperdrive.Fees(0.01e18, 0.001e18, 0.15e18, 0.03e18), @@ -1259,6 +1450,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); @@ -1277,6 +1469,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.checkpointDuration = oldCheckpointDuration; @@ -1296,6 +1489,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.checkpointDuration = oldCheckpointDuration; @@ -1313,6 +1507,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.checkpointDuration = oldCheckpointDuration; @@ -1332,6 +1527,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.positionDuration = oldPositionDuration; @@ -1351,6 +1547,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.positionDuration = oldPositionDuration; @@ -1368,10 +1565,145 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.positionDuration = oldPositionDuration; + // Ensure than an instance can't be deployed with a fixed APR less than + // the minimum. + vm.stopPrank(); + vm.startPrank(bob); + uint256 fixedAPR = factory.minFixedAPR() - 1; + vm.expectRevert(IHyperdriveFactory.InvalidFixedAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + fixedAPR, + 0.05e18, + new bytes(0) + ); + + // Ensure than an instance can't be deployed with a fixed APR greater + // than the maximum. + vm.stopPrank(); + vm.startPrank(bob); + fixedAPR = factory.maxFixedAPR() + 1; + vm.expectRevert(IHyperdriveFactory.InvalidFixedAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + fixedAPR, + 0.05e18, + new bytes(0) + ); + + // Ensure than an instance can't be deployed with a time stretch APR + // less than 0.5%. + vm.stopPrank(); + vm.startPrank(factory.governance()); + factory.updateMinTimeStretchAPR(0.001e18); + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.InvalidTimeStretchAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + 0.001e18, + 0.004e18, + new bytes(0) + ); + + // Ensure than an instance can't be deployed with a time stretch APR + // less than the fixed rate divided by two. + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.InvalidTimeStretchAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + 0.02e18, + 0.006e18, + new bytes(0) + ); + + // Ensure than an instance can't be deployed with a time stretch APR + // less than the minimum time stretch APR specified by governance. + vm.stopPrank(); + vm.startPrank(factory.governance()); + factory.updateMinTimeStretchAPR(0.02e18); + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.InvalidTimeStretchAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + 0.02e18, + 0.019e18, + new bytes(0) + ); + + // Ensure than an instance can't be deployed with a time stretch APR + // greater than the lower bound (0.5%) multiplied by two. + vm.stopPrank(); + vm.startPrank(factory.governance()); + factory.updateMinTimeStretchAPR(0.001e18); + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.InvalidTimeStretchAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + 0.003e18, + 0.011e18, + new bytes(0) + ); + + // Ensure than an instance can't be deployed with a time stretch APR + // greater than the fixed rate multiplied by two. + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.InvalidTimeStretchAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + 0.012e18, + 0.025e18, + new bytes(0) + ); + + // Ensure than an instance can't be deployed with a time stretch APR + // greater than the max time stretch APR specified by governance. + vm.stopPrank(); + vm.startPrank(factory.governance()); + factory.updateMaxTimeStretchAPR(0.3e18); + vm.stopPrank(); + vm.startPrank(bob); + vm.expectRevert(IHyperdriveFactory.InvalidTimeStretchAPR.selector); + factory.deployAndInitialize( + deployerCoordinator, + config, + extraData, + 10_000e18, + 0.3e18, + 0.31e18, + new bytes(0) + ); + // Ensure than an instance can't be deployed with a curve fee greater // than the maximum curve fee. vm.stopPrank(); @@ -1385,6 +1717,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.curve = oldCurveFee; @@ -1402,6 +1735,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.curve = oldCurveFee; @@ -1419,6 +1753,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.flat = oldFlatFee; @@ -1436,6 +1771,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.flat = oldFlatFee; @@ -1453,6 +1789,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.governanceLP = oldGovernanceLPFee; @@ -1470,6 +1807,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.governanceLP = oldGovernanceLPFee; @@ -1487,6 +1825,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.governanceZombie = oldGovernanceZombieFee; @@ -1504,6 +1843,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.fees.governanceZombie = oldGovernanceZombieFee; @@ -1521,6 +1861,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.linkerFactory = oldLinkerFactory; @@ -1538,6 +1879,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.linkerCodeHash = oldLinkerCodeHash; @@ -1555,6 +1897,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.feeCollector = oldFeeCollector; @@ -1572,6 +1915,7 @@ contract HyperdriveFactoryTest is HyperdriveTest { extraData, 10_000e18, 0.02e18, + 0.05e18, new bytes(0) ); config.governance = oldGovernance; @@ -1638,6 +1982,10 @@ contract HyperdriveFactoryBaseTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0.001e18, flat: 0.0001e18, @@ -1662,7 +2010,7 @@ contract HyperdriveFactoryBaseTest is HyperdriveTest { minimumTransactionAmount: 1e15, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch(APR, 365 days), + timeStretch: 0, governance: address(0), feeCollector: address(0), fees: IHyperdrive.Fees(0.01e18, 0.001e18, 0.15e18, 0.03e18), @@ -1721,6 +2069,7 @@ contract HyperdriveFactoryBaseTest is HyperdriveTest { abi.encode(address(pool), new address[](0)), // TODO: Add test with sweeps CONTRIBUTION, APR, + APR, new bytes(0) ); @@ -1773,6 +2122,7 @@ contract ERC4626FactoryMultiDeployTest is HyperdriveFactoryBaseTest { abi.encode(address(pool1), new address[](0)), CONTRIBUTION, APR, + APR, new bytes(0) ); @@ -1819,6 +2169,7 @@ contract ERC4626FactoryMultiDeployTest is HyperdriveFactoryBaseTest { abi.encode(address(pool2), new address[](0)), CONTRIBUTION, APR, + APR, new bytes(0) ); @@ -1872,6 +2223,7 @@ contract ERC4626FactoryMultiDeployTest is HyperdriveFactoryBaseTest { abi.encode(address(pool2), new address[](0)), CONTRIBUTION, APR, + APR, new bytes(0) ); @@ -1996,6 +2348,10 @@ contract DeployerCoordinatorGetterTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0.001e18, flat: 0.0001e18, @@ -2165,6 +2521,10 @@ contract HyperdriveFactoryAddHyperdriveFactoryTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0.001e18, flat: 0.0001e18, @@ -2265,6 +2625,10 @@ contract HyperdriveFactoryRemoveInstanceTest is HyperdriveTest { maxCheckpointDuration: 1 days, minPositionDuration: 7 days, maxPositionDuration: 10 * 365 days, + minFixedAPR: 0.001e18, + maxFixedAPR: 0.5e18, + minTimeStretchAPR: 0.01e18, + maxTimeStretchAPR: 0.5e18, minFees: IHyperdrive.Fees({ curve: 0.001e18, flat: 0.0001e18, diff --git a/test/integrations/hyperdrive/RoundTripTest.t.sol b/test/integrations/hyperdrive/RoundTripTest.t.sol index c40594bb1..dc7420003 100644 --- a/test/integrations/hyperdrive/RoundTripTest.t.sol +++ b/test/integrations/hyperdrive/RoundTripTest.t.sol @@ -15,6 +15,7 @@ contract RoundTripTest is HyperdriveTest { using HyperdriveUtils for *; using Lib for *; + /// forge-config: default.fuzz.runs = 1000 function test_long_round_trip_immediately_at_checkpoint( uint256 fixedRate, uint256 timeStretchFixedRate, @@ -65,6 +66,7 @@ contract RoundTripTest is HyperdriveTest { assertEq(poolInfoAfter.bondReserves, poolInfoBefore.bondReserves); } + /// forge-config: default.fuzz.runs = 1000 function test_long_round_trip_immediately_partially_thru_checkpoint( uint256 fixedRate, uint256 timeStretchFixedRate, @@ -121,6 +123,7 @@ contract RoundTripTest is HyperdriveTest { assertEq(poolInfoAfter.bondReserves, poolInfoBefore.bondReserves); } + /// forge-config: default.fuzz.runs = 1000 function test_short_round_trip_immediately_at_checkpoint( uint256 fixedRate, uint256 timeStretchFixedRate, @@ -170,6 +173,7 @@ contract RoundTripTest is HyperdriveTest { assertEq(poolInfoAfter.bondReserves, poolInfoBefore.bondReserves); } + /// forge-config: default.fuzz.runs = 1000 function test_short_round_trip_immediately_partially_thru_checkpoint( uint256 fixedRate, uint256 timeStretchFixedRate, @@ -226,6 +230,7 @@ contract RoundTripTest is HyperdriveTest { assertEq(poolInfoAfter.bondReserves, poolInfoBefore.bondReserves); } + /// forge-config: default.fuzz.runs = 1000 function test_sandwiched_long_round_trip( uint256 fixedRate, uint256 timeStretchFixedRate @@ -275,6 +280,7 @@ contract RoundTripTest is HyperdriveTest { assertEq(poolInfoAfter.bondReserves, poolInfoBefore.bondReserves); } + /// forge-config: default.fuzz.runs = 1000 function test_long_multiblock_round_trip_end_of_checkpoint( uint256 fixedRate, uint256 timeStretchFixedRate, diff --git a/test/integrations/hyperdrive/ZombieInterestTest.t.sol b/test/integrations/hyperdrive/ZombieInterestTest.t.sol index 9b34ddb4e..25fd37a95 100644 --- a/test/integrations/hyperdrive/ZombieInterestTest.t.sol +++ b/test/integrations/hyperdrive/ZombieInterestTest.t.sol @@ -13,6 +13,7 @@ contract ZombieInterestTest is HyperdriveTest { using HyperdriveUtils for *; using Lib for *; + /// forge-config: default.fuzz.runs = 1000 function test_zombie_interest_long_lp( uint256 variableRateParam, uint256 longTradeSizeParam, @@ -203,6 +204,7 @@ contract ZombieInterestTest is HyperdriveTest { ); } + /// forge-config: default.fuzz.runs = 1000 function test_zombie_interest_short_lp( uint256 variableRateParam, uint256 shortTradeSizeParam, @@ -431,8 +433,7 @@ contract ZombieInterestTest is HyperdriveTest { ); } - // This test just demonstrates that shorts redeemed late do not receive - // zombie interest. + /// forge-config: default.fuzz.runs = 1000 function test_zombie_short() external { // Initialize the pool with capital. deploy(bob, 0.035e18, 1e18, 0, 0, 0, 0); @@ -499,6 +500,7 @@ contract ZombieInterestTest is HyperdriveTest { ); } + /// forge-config: default.fuzz.runs = 1000 function test_skipped_checkpoint( uint256 variableRateParam, uint256 longTradeSizeParam @@ -590,6 +592,7 @@ contract ZombieInterestTest is HyperdriveTest { assertApproxEqAbs(shareReserves1, shareReserves2, 5 wei); } + /// forge-config: default.fuzz.runs = 1000 function test_zombie_long_short(uint256 zombieTime) external { _test_zombie_long_short(zombieTime); } diff --git a/test/units/ForceRevertDelegatecall.t.sol b/test/units/ForceRevertDelegatecall.t.sol index 1c055acfe..2bcd1aafe 100644 --- a/test/units/ForceRevertDelegatecall.t.sol +++ b/test/units/ForceRevertDelegatecall.t.sol @@ -6,6 +6,7 @@ import { Hyperdrive } from "contracts/src/external/Hyperdrive.sol"; import { IERC20 } from "contracts/src/interfaces/IERC20.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { HyperdriveStorage } from "contracts/src/internal/HyperdriveStorage.sol"; +import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { MockHyperdriveBase } from "contracts/test/MockHyperdrive.sol"; import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; @@ -36,7 +37,7 @@ contract DummyHyperdrive is Hyperdrive, MockHyperdriveBase { minimumTransactionAmount: 1e15, positionDuration: 365 days, checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( + timeStretch: HyperdriveMath.calculateTimeStretch( 0.05e18, 365 days ), diff --git a/test/units/libraries/HyperdriveMath.t.sol b/test/units/libraries/HyperdriveMath.t.sol index c9e39dc97..729abfe1d 100644 --- a/test/units/libraries/HyperdriveMath.t.sol +++ b/test/units/libraries/HyperdriveMath.t.sol @@ -441,7 +441,7 @@ contract HyperdriveMathTest is HyperdriveTest { fixedRate = fixedRate.normalizeToRange(0.005e18, 1e18); uint256 initialShareReserves = 500_000_000e18; uint256 initialVaultSharePrice = INITIAL_SHARE_PRICE; - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = hyperdriveMath.calculateTimeStretch( fixedRate, POSITION_DURATION ); @@ -512,7 +512,7 @@ contract HyperdriveMathTest is HyperdriveTest { fixedRate = fixedRate.normalizeToRange(0.005e18, 1e18); uint256 initialShareReserves = 500_000_000e18; uint256 initialVaultSharePrice = INITIAL_SHARE_PRICE; - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = hyperdriveMath.calculateTimeStretch( fixedRate, POSITION_DURATION ); diff --git a/test/units/libraries/LPMath.t.sol b/test/units/libraries/LPMath.t.sol index 0ba121648..5caa57822 100644 --- a/test/units/libraries/LPMath.t.sol +++ b/test/units/libraries/LPMath.t.sol @@ -22,7 +22,7 @@ contract LPMathTest is HyperdriveTest { uint256 apr = 0.02e18; uint256 initialVaultSharePrice = 1e18; uint256 positionDuration = 365 days; - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = HyperdriveMath.calculateTimeStretch( apr, positionDuration ); @@ -635,7 +635,7 @@ contract LPMathTest is HyperdriveTest { uint256 apr = 0.02e18; uint256 initialVaultSharePrice = 0.5e18; uint256 positionDuration = 365 days; - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = HyperdriveMath.calculateTimeStretch( apr, positionDuration ); @@ -997,7 +997,7 @@ contract LPMathTest is HyperdriveTest { uint256 apr = 0.02e18; uint256 initialVaultSharePrice = 1e18; uint256 positionDuration = 365 days; - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = HyperdriveMath.calculateTimeStretch( apr, positionDuration ); @@ -1294,7 +1294,7 @@ contract LPMathTest is HyperdriveTest { uint256 apr = 0.02e18; uint256 initialVaultSharePrice = 1e18; uint256 positionDuration = 365 days; - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = HyperdriveMath.calculateTimeStretch( apr, positionDuration ); diff --git a/test/units/libraries/YieldSpaceMath.t.sol b/test/units/libraries/YieldSpaceMath.t.sol index c7214ab1c..d6a11191b 100644 --- a/test/units/libraries/YieldSpaceMath.t.sol +++ b/test/units/libraries/YieldSpaceMath.t.sol @@ -149,7 +149,7 @@ contract YieldSpaceMathTest is Test { ); for (uint256 j = i - (i / 2 + 1); j < i; j++) { // Calculate the bond reserves that give the pool the expected spot rate. - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = HyperdriveMath.calculateTimeStretch( fixedRate, 365 days ); @@ -223,7 +223,7 @@ contract YieldSpaceMathTest is Test { ); // Calculate the bond reserves that give the pool the expected spot rate. - uint256 timeStretch = HyperdriveUtils.calculateTimeStretch( + uint256 timeStretch = HyperdriveMath.calculateTimeStretch( fixedRate, 365 days ); diff --git a/test/utils/HyperdriveTest.sol b/test/utils/HyperdriveTest.sol index 7e1e69d5c..d89f73e60 100644 --- a/test/utils/HyperdriveTest.sol +++ b/test/utils/HyperdriveTest.sol @@ -152,7 +152,7 @@ contract HyperdriveTest is IHyperdriveEvents, BaseTest { minimumTransactionAmount: MINIMUM_TRANSACTION_AMOUNT, positionDuration: positionDuration, checkpointDuration: CHECKPOINT_DURATION, - timeStretch: HyperdriveUtils.calculateTimeStretch( + timeStretch: HyperdriveMath.calculateTimeStretch( fixedRate, positionDuration ), diff --git a/test/utils/HyperdriveUtils.sol b/test/utils/HyperdriveUtils.sol index 9aab9bf69..ab0e4b340 100644 --- a/test/utils/HyperdriveUtils.sol +++ b/test/utils/HyperdriveUtils.sol @@ -159,59 +159,6 @@ library HyperdriveUtils { return (_principal, 0); } - function calculateTimeStretch( - uint256 apr, - uint256 positionDuration - ) internal pure returns (uint256) { - // Calculate the benchmark time stretch. This time stretch is tuned for - // a position duration of 1 year. - uint256 timeStretch = uint256(5.24592e18).divDown( - uint256(0.04665e18).mulDown(apr * 100) - ); - timeStretch = ONE.divDown(timeStretch); - - // If the position duration is 1 year, we can return the benchmark. - if (positionDuration == 365 days) { - return timeStretch; - } - - // Otherwise, we need to adjust the time stretch to account for the - // position duration. We do this by holding the reserve ratio constant - // and solving for the new time stretch directly. - // - // We can calculate the spot price at the target apr and position - // duration as: - // - // p = 1 / (1 + apr * (positionDuration / 365 days)) - // - // We then calculate the benchmark reserve ratio, `ratio`, implied by - // the benchmark time stretch using the `calculateInitialBondReserves` - // function. - // - // We can then derive the adjusted time stretch using the spot price - // calculation: - // - // p = ratio ** timeStretch - // => - // timeStretch = ln(p) / ln(ratio) - uint256 targetSpotPrice = ONE.divDown( - ONE + apr.mulDivDown(positionDuration, 365 days) - ); - uint256 benchmarkReserveRatio = ONE.divDown( - HyperdriveMath.calculateInitialBondReserves( - ONE, - ONE, - apr, - 365 days, - timeStretch - ) - ); - return - uint256(-int256(targetSpotPrice).ln()).divDown( - uint256(-int256(benchmarkReserveRatio).ln()) - ); - } - /// Trade Utils /// /// @dev Calculates the maximum amount of longs that can be opened.