-
Notifications
You must be signed in to change notification settings - Fork 6
Closed
Labels
Description
There was an intermittent failure while running the benchmark: https://github.com/delvtech/hyperdrive/actions/runs/7978957722/job/21785238086. After looking into it, it became apparent that small net curve trades can actually result in liveness issues.
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.20;
// FIXME
import { console2 as console } from "forge-std/console2.sol";
import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol";
import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol";
import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol";
import { YieldSpaceMath } from "contracts/src/libraries/HyperdriveMath.sol";
import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol";
import { IMockHyperdrive } from "contracts/test/MockHyperdrive.sol";
import { MockHyperdriveMath } from "contracts/test/MockHyperdriveMath.sol";
import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol";
import { HyperdriveTest } from "test/utils/HyperdriveTest.sol";
import { Lib } from "test/utils/Lib.sol";
contract ExampleTest is HyperdriveTest {
using FixedPointMath for uint256;
using HyperdriveUtils for IHyperdrive;
using Lib for *;
function test_checkpoint_liveness(
uint256 fixedRate,
uint256 contribution,
uint256 initialLongAmount,
uint256 initialShortAmount,
uint256 finalLongAmount
) external {
// FIXME
fixedRate = 3988;
contribution = 370950184595018764582435593;
initialLongAmount = 10660;
initialShortAmount = 999000409571;
finalLongAmount = 1000000000012659;
_test__calculateMaxLong(
fixedRate,
contribution,
initialLongAmount,
initialShortAmount,
finalLongAmount
);
}
function _test__calculateMaxLong(
uint256 fixedRate,
uint256 contribution,
uint256 initialLongAmount,
uint256 initialShortAmount,
uint256 finalLongAmount
) internal {
// Deploy Hyperdrive.
fixedRate = fixedRate.normalizeToRange(0.001e18, 0.5e18);
deploy(alice, fixedRate, 0, 0, 0, 0);
// Initialize the Hyperdrive pool.
contribution = contribution.normalizeToRange(1_000e18, 500_000_000e18);
initialize(alice, fixedRate, contribution);
// Ensure that the max long is actually the max long.
_verifyMaxLong(
fixedRate,
initialLongAmount,
initialShortAmount,
finalLongAmount
);
}
function _verifyMaxLong(
uint256 fixedRate,
uint256 initialLongAmount,
uint256 initialShortAmount,
uint256 finalLongAmount
) internal {
// Open a long and a short. This sets the long buffer to a non-trivial
// value which stress tests the max long function.
initialLongAmount = initialLongAmount.normalizeToRange(
MINIMUM_TRANSACTION_AMOUNT,
hyperdrive.calculateMaxLong() / 2
);
openLong(bob, initialLongAmount);
initialShortAmount = initialShortAmount.normalizeToRange(
MINIMUM_TRANSACTION_AMOUNT,
hyperdrive.calculateMaxShort() / 2
);
openShort(bob, initialShortAmount);
advanceTime(CHECKPOINT_DURATION, int256(0));
console.log("should get here");
hyperdrive.checkpoint(hyperdrive.latestCheckpoint());
console.log("shouldn't get here");
// TODO: The fact that we need such a large amount of iterations could
// indicate a bug in the max long function.
//
// Open the maximum long on Hyperdrive.
IHyperdrive.PoolConfig memory config = hyperdrive.getPoolConfig();
IHyperdrive.PoolInfo memory info = hyperdrive.getPoolInfo();
uint256 maxIterations = 10;
if (fixedRate > 0.15e18) {
maxIterations += 5;
}
if (fixedRate > 0.35e18) {
maxIterations += 5;
}
(uint256 maxLong, ) = HyperdriveUtils.calculateMaxLong(
HyperdriveUtils.MaxTradeParams({
shareReserves: info.shareReserves,
shareAdjustment: info.shareAdjustment,
bondReserves: info.bondReserves,
longsOutstanding: info.longsOutstanding,
longExposure: info.longExposure,
timeStretch: config.timeStretch,
vaultSharePrice: info.vaultSharePrice,
initialVaultSharePrice: config.initialVaultSharePrice,
minimumShareReserves: config.minimumShareReserves,
curveFee: config.fees.curve,
flatFee: config.fees.flat,
governanceLPFee: config.fees.governanceLP
}),
hyperdrive.getCheckpointExposure(hyperdrive.latestCheckpoint()),
maxIterations
);
(uint256 maturityTime, uint256 longAmount) = openLong(bob, maxLong);
// TODO: Re-visit this after fixing `calculateMaxLong` to work with
// matured positions.
//
// Ensure that opening another long fails. We fuzz in the range of
// 10% to 1000x the max long.
//
// NOTE: The max spot price increases after we open the first long
// because the spot price increases. In some cases, this could cause
// a small trade to suceed after the large trade, so we use relatively
// large amounts for the second trade.
vm.stopPrank();
vm.startPrank(bob);
finalLongAmount = finalLongAmount.normalizeToRange(
maxLong.mulDown(0.1e18).max(MINIMUM_TRANSACTION_AMOUNT),
maxLong.mulDown(1000e18).max(
MINIMUM_TRANSACTION_AMOUNT.mulDown(10e18)
)
);
baseToken.mint(bob, finalLongAmount);
baseToken.approve(address(hyperdrive), finalLongAmount);
vm.expectRevert();
hyperdrive.openLong(
finalLongAmount,
0,
0,
IHyperdrive.Options({
destination: bob,
asBase: true,
extraData: new bytes(0)
})
);
// Ensure that the long can be closed.
closeLong(bob, maturityTime, longAmount);
}
}