Skip to content

Commit

Permalink
add failing edge case test and add min lpShare check
Browse files Browse the repository at this point in the history
  • Loading branch information
jrhea committed Nov 6, 2023
1 parent ecb3124 commit 45bd1e2
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 51 deletions.
9 changes: 8 additions & 1 deletion contracts/src/HyperdriveLP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ abstract contract HyperdriveLP is IHyperdriveWrite, HyperdriveTWAP {
_positionDuration,
_timeStretch
);
if (apr < _minApr || apr > _maxApr) revert IHyperdrive.InvalidApr();
if (apr < _minApr || apr > _maxApr) {
revert IHyperdrive.InvalidApr();
}

// Deposit for the user, this call also transfers from them
(uint256 vaultShares, uint256 sharePrice) = _deposit(
Expand Down Expand Up @@ -185,6 +187,11 @@ abstract contract HyperdriveLP is IHyperdriveWrite, HyperdriveTWAP {
lpTotalSupply,
startingPresentValue
);

// Ensure that enough lp shares are minted so that they can be redeemed.
if (lpShares < _minimumTransactionAmount) {
revert IHyperdrive.MinimumTransactionAmount();
}
}

// Mint LP shares to the supplier.
Expand Down
145 changes: 95 additions & 50 deletions test/integrations/hyperdrive/NonstandardDecimals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { HyperdriveTest } from "test/utils/HyperdriveTest.sol";
import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol";
import { Lib } from "test/utils/Lib.sol";

import "forge-std/console2.sol";

contract NonstandardDecimalsTest is HyperdriveTest {
using FixedPointMath for int256;
using FixedPointMath for uint256;
Expand Down Expand Up @@ -372,6 +374,11 @@ contract NonstandardDecimalsTest is HyperdriveTest {
uint256 shortAmount = 4726;
_test_nonstandard_decimals_lp(longBasePaid, shortAmount);
}
{
uint256 longBasePaid = 23380152699926527608478591154369565406497241350176542278464371342740135389; // 0.000060348175507674
uint256 shortAmount = 38653169555116283775616658498588757899099; // 0.001142522704554135
_test_nonstandard_decimals_lp(longBasePaid, shortAmount);
}
}

// TODO: This test should be re-written to avoid such large tolerances.
Expand All @@ -384,7 +391,6 @@ contract NonstandardDecimalsTest is HyperdriveTest {
config.minimumShareReserves = 1e6;
config.minimumTransactionAmount = 1e6;
deploy(deployer, config);

uint256 minimumTransactionAmount = hyperdrive
.getPoolConfig()
.minimumTransactionAmount;
Expand All @@ -411,6 +417,7 @@ contract NonstandardDecimalsTest is HyperdriveTest {

// Bob adds liquidity.
uint256 bobLpShares = addLiquidity(bob, testParams.contribution);
uint256 spotAPRBefore = hyperdrive.calculateSpotAPR();

// Bob opens a long.
{
Expand All @@ -436,6 +443,7 @@ contract NonstandardDecimalsTest is HyperdriveTest {
minimumTransactionAmount,
maxShort - minimumTransactionAmount
);

testParams.shortAmount = shortAmount;
(uint256 shortMaturityTime, uint256 shortBasePaid) = openShort(
bob,
Expand All @@ -458,58 +466,95 @@ contract NonstandardDecimalsTest is HyperdriveTest {
assertEq(aliceBaseProceeds, estimatedBaseProceeds);

// Celine adds liquidity.
uint256 celineLpShares = addLiquidity(celine, testParams.contribution);

// Bob closes his long and his short.
{
closeLong(bob, testParams.longMaturityTime, testParams.longAmount);
closeShort(
bob,
testParams.shortMaturityTime,
testParams.shortAmount
// Note that fuzzing will occasionally create long and short trades so large
// that the Celine gets less than the minimum transaction amount. This situation
// can be caught by setting Min/Max APR slippage guards and checking for revert
DepositOverrides memory overrides = DepositOverrides({
asBase: true,
depositAmount: testParams.contribution,
minSharePrice: 0, // unused
minSlippage: spotAPRBefore - 0.015e18, // min spot rate of .5%
maxSlippage: spotAPRBefore + 0.015e18, // max spot rate of 3.5%
extraData: new bytes(0) // unused
});
uint256 celineLpShares;
if (
hyperdrive.calculateSpotAPR() < overrides.minSlippage ||
hyperdrive.calculateSpotAPR() > overrides.maxSlippage
) {
baseToken.mint(overrides.depositAmount);
baseToken.approve(address(hyperdrive), overrides.depositAmount);
vm.expectRevert(IHyperdrive.InvalidApr.selector);

celineLpShares = hyperdrive.addLiquidity(
overrides.depositAmount,
overrides.minSlippage, // min spot rate
overrides.maxSlippage, // max spot rate
IHyperdrive.Options({
destination: celine,
asBase: overrides.asBase,
extraData: overrides.extraData
})
);
}
} else {
celineLpShares = addLiquidity(celine, testParams.contribution);

// Bob closes his long and his short.
{
closeLong(
bob,
testParams.longMaturityTime,
testParams.longAmount
);
closeShort(
bob,
testParams.shortMaturityTime,
testParams.shortAmount
);
}

// Redeem Alice's withdrawal shares. Alice gets at least the margin released
// from Bob's long.
(uint256 aliceRedeemProceeds, ) = redeemWithdrawalShares(
alice,
aliceWithdrawalShares
);
{
uint256 estimatedRedeemProceeds = lpMargin.mulDivDown(
aliceLpShares,
aliceLpShares + bobLpShares
// Redeem Alice's withdrawal shares. Alice gets at least the margin released
// from Bob's long.
(uint256 aliceRedeemProceeds, ) = redeemWithdrawalShares(
alice,
aliceWithdrawalShares
);
{
uint256 estimatedRedeemProceeds = lpMargin.mulDivDown(
aliceLpShares,
aliceLpShares + bobLpShares
);
assertGe(aliceRedeemProceeds, estimatedRedeemProceeds);
}

// Bob and Celine remove their liquidity. Bob should receive more base
// proceeds than Celine since Celine's add liquidity resulted in an
// increase in slippage for the outstanding positions.
(
uint256 bobBaseProceeds,
uint256 bobWithdrawalShares
) = removeLiquidity(bob, bobLpShares);
(
uint256 celineBaseProceeds,
uint256 celineWithdrawalShares
) = removeLiquidity(celine, celineLpShares);
assertGe(bobBaseProceeds + 1e6, celineBaseProceeds);
uint256 _contribution = testParams.contribution; // Avoid stack too deep error
assertGe(bobBaseProceeds + 1e6, _contribution);
assertApproxEqAbs(bobWithdrawalShares, 0, 1);
assertApproxEqAbs(celineWithdrawalShares, 0, 1);
assertApproxEqAbs(
hyperdrive.totalSupply(AssetId._WITHDRAWAL_SHARE_ASSET_ID) -
hyperdrive.getPoolInfo().withdrawalSharesReadyToWithdraw,
0,
1 wei
);
assertGe(aliceRedeemProceeds, estimatedRedeemProceeds);
}

// Bob and Celine remove their liquidity. Bob should receive more base
// proceeds than Celine since Celine's add liquidity resulted in an
// increase in slippage for the outstanding positions.
(
uint256 bobBaseProceeds,
uint256 bobWithdrawalShares
) = removeLiquidity(bob, bobLpShares);
(
uint256 celineBaseProceeds,
uint256 celineWithdrawalShares
) = removeLiquidity(celine, celineLpShares);
assertGe(bobBaseProceeds + 1e6, celineBaseProceeds);
assertGe(bobBaseProceeds + 1e6, testParams.contribution);
assertApproxEqAbs(bobWithdrawalShares, 0, 1);
assertApproxEqAbs(celineWithdrawalShares, 0, 1);
assertApproxEqAbs(
hyperdrive.totalSupply(AssetId._WITHDRAWAL_SHARE_ASSET_ID) -
hyperdrive.getPoolInfo().withdrawalSharesReadyToWithdraw,
0,
1 wei
);

// TODO: There is an edge case where the withdrawal pool doesn't receive
// all of its portion of the available idle liquidity when a closed
// position doesn't perform well.
// Ensure that the ending base balance of Hyperdrive is zero.
// assertApproxEqAbs(baseToken.balanceOf(address(hyperdrive)), 0, 1);
// TODO: There is an edge case where the withdrawal pool doesn't receive
// all of its portion of the available idle liquidity when a closed
// position doesn't perform well.
// Ensure that the ending base balance of Hyperdrive is zero.
// assertApproxEqAbs(baseToken.balanceOf(address(hyperdrive)), 0, 1);
}
}
}

0 comments on commit 45bd1e2

Please sign in to comment.