Skip to content

Commit

Permalink
correctly convert base token values from hyperdrive (#38)
Browse files Browse the repository at this point in the history
Some values received from hyperdrive are denominated in base tokens.

These must be converted to vault shares token denominated values when `asBase==true || isWrapped==true` in the strategy
  • Loading branch information
mcclurejt authored Dec 15, 2024
1 parent a1591dc commit e82d8b3
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 23 deletions.
29 changes: 23 additions & 6 deletions contracts/EverlongStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ contract EverlongStrategy is BaseStrategy {
uint256 toSpend = _totalIdle.min(availableDepositLimit(address(this)));

// If Everlong has sufficient idle, open a new position.
if (toSpend > _poolConfig.minimumTransactionAmount) {
if (toSpend > _minimumTransactionAmount()) {
(uint256 maturityTime, uint256 bondAmount) = _openLong(
toSpend,
tendConfig.minOutput,
Expand Down Expand Up @@ -473,7 +473,7 @@ contract EverlongStrategy is BaseStrategy {
uint256 _targetOutput
) internal returns (uint256 output) {
// Round `_targetOutput` up to Hyperdrive's minimum transaction amount.
_targetOutput = _targetOutput.max(_poolConfig.minimumTransactionAmount);
_targetOutput = _targetOutput.max(_minimumTransactionAmount());

// Since multiple position's worth of bonds may need to be closed,
// iterate through each position starting with the most mature.
Expand Down Expand Up @@ -502,8 +502,9 @@ contract EverlongStrategy is BaseStrategy {
// Hyperdrive's minimum transaction amount.
if (
totalPositionValue >
(_targetOutput - output + _poolConfig.minimumTransactionAmount)
.mulUp(ONE + partialPositionClosureBuffer)
(_targetOutput - output + _minimumTransactionAmount()).mulUp(
ONE + partialPositionClosureBuffer
)
) {
// Calculate the amount of bonds to close from the position.
uint256 bondsNeeded = uint256(position.bondAmount).mulDivUp(
Expand Down Expand Up @@ -681,6 +682,22 @@ contract EverlongStrategy is BaseStrategy {
}
}

/// @dev Retrieve hyperdrive's minimum transaction amount.
/// @return amount Hyperdrive's minimum transaction amount.
function _minimumTransactionAmount()
internal
view
returns (uint256 amount)
{
amount = _poolConfig.minimumTransactionAmount;

// Since `amount` is denominated in hyperdrive's base currency. We must
// convert it.
if (!asBase || isWrapped) {
IHyperdrive(hyperdrive)._convertToShares(amount);
}
}

// ╭───────────────────────────────────────────────────────────────────────╮
// │ Views │
// ╰───────────────────────────────────────────────────────────────────────╯
Expand All @@ -696,7 +713,7 @@ contract EverlongStrategy is BaseStrategy {
// Only pre-approved addresses are able to deposit.
if (_depositors[_depositor] || _depositor == address(this)) {
// Limit deposits to the maximum long that can be opened in hyperdrive.
return IHyperdrive(hyperdrive).calculateMaxLong();
return IHyperdrive(hyperdrive).calculateMaxLong(asBase);
}
// Address has not been pre-approved, return 0.
return 0;
Expand Down Expand Up @@ -735,7 +752,7 @@ contract EverlongStrategy is BaseStrategy {
/// @return True if a new position can be opened, false otherwise.
function canOpenPosition() public view returns (bool) {
uint256 currentBalance = asset.balanceOf(address(this));
return currentBalance > _poolConfig.minimumTransactionAmount;
return currentBalance > _minimumTransactionAmount();
}

/// @notice Converts an amount denominated in wrapped tokens to an amount
Expand Down
26 changes: 20 additions & 6 deletions contracts/libraries/HyperdriveExecution.sol
Original file line number Diff line number Diff line change
Expand Up @@ -492,15 +492,18 @@ library HyperdriveExecutionLibrary {
// HACK: Copied from `delvtech/hyperdrive` repo.
//
/// @dev Calculates the maximum amount of longs that can be opened.
/// @param _asBase Whether to transact using hyperdrive's base or vault
/// shares token.
/// @param _maxIterations The maximum number of iterations to use.
/// @return baseAmount The cost of buying the maximum amount of longs.
/// @return amount The cost of buying the maximum amount of longs.
function calculateMaxLong(
IHyperdrive self,
bool _asBase,
uint256 _maxIterations
) internal view returns (uint256 baseAmount) {
) internal view returns (uint256 amount) {
IHyperdrive.PoolConfig memory poolConfig = self.getPoolConfig();
IHyperdrive.PoolInfo memory poolInfo = self.getPoolInfo();
(baseAmount, ) = calculateMaxLong(
(amount, ) = calculateMaxLong(
MaxTradeParams({
shareReserves: poolInfo.shareReserves,
shareAdjustment: poolInfo.shareAdjustment,
Expand All @@ -518,17 +521,28 @@ library HyperdriveExecutionLibrary {
self.getCheckpointExposure(latestCheckpoint(self)),
_maxIterations
);
return baseAmount;

// The above `amount` is denominated in hyperdrive's base token.
// If `_asBase == false` then hyperdrive's vault shares token is being
// used and we must convert the value.
if (!_asBase) {
amount = _convertToShares(self, amount);
}

return amount;
}

// HACK: Copied from `delvtech/hyperdrive` repo.
//
/// @dev Calculates the maximum amount of longs that can be opened.
/// @param _asBase Whether to transact using hyperdrive's base or vault
/// shares token.
/// @return baseAmount The cost of buying the maximum amount of longs.
function calculateMaxLong(
IHyperdrive self
IHyperdrive self,
bool _asBase
) internal view returns (uint256 baseAmount) {
return calculateMaxLong(self, 7);
return calculateMaxLong(self, _asBase, 7);
}

// HACK: Copied from `delvtech/hyperdrive` repo.
Expand Down
2 changes: 1 addition & 1 deletion test/everlong/integration/PartialClosures.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ contract TestPartialClosures is EverlongTest {
uint256 aliceDepositAmount = bound(
_deposit,
MINIMUM_TRANSACTION_AMOUNT * 100, // Increase minimum bound otherwise partial redemption won't occur
hyperdrive.calculateMaxLong()
hyperdrive.calculateMaxLong(AS_BASE)
);
uint256 aliceShares = depositStrategy(aliceDepositAmount, alice, true);
uint256 positionBondsAfterDeposit = IEverlongStrategy(address(strategy))
Expand Down
16 changes: 8 additions & 8 deletions test/everlong/integration/Sandwich.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ contract TestSandwichHelper is EverlongTest {
_bystanderDeposit = bound(
_bystanderDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 bystanderShares = depositVault(
_bystanderDeposit,
Expand All @@ -67,7 +67,7 @@ contract TestSandwichHelper is EverlongTest {
_attackerDeposit = bound(
_attackerDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 attackerShares = depositVault(
_attackerDeposit,
Expand Down Expand Up @@ -125,7 +125,7 @@ contract TestSandwichHelper is EverlongTest {
_bystanderDeposit = bound(
_bystanderDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 bystanderShares = depositVault(
_bystanderDeposit,
Expand All @@ -139,7 +139,7 @@ contract TestSandwichHelper is EverlongTest {
_attackerDeposit = bound(
_attackerDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 attackerShares = depositVault(
_attackerDeposit,
Expand Down Expand Up @@ -212,7 +212,7 @@ contract TestSandwichHelper is EverlongTest {
_bystanderDeposit = bound(
_bystanderDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 bystanderEverlongShares = depositVault(
_bystanderDeposit,
Expand All @@ -226,7 +226,7 @@ contract TestSandwichHelper is EverlongTest {
_attackerDeposit = bound(
_attackerDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 attackerEverlongShares = depositVault(
_attackerDeposit,
Expand Down Expand Up @@ -279,7 +279,7 @@ contract TestSandwichHelper is EverlongTest {
_bystanderDeposit = bound(
_bystanderDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 bystanderShares = depositVault(
_bystanderDeposit,
Expand All @@ -293,7 +293,7 @@ contract TestSandwichHelper is EverlongTest {
_attackerDeposit = bound(
_attackerDeposit,
MINIMUM_TRANSACTION_AMOUNT * 5,
hyperdrive.calculateMaxLong() / 3
hyperdrive.calculateMaxLong(AS_BASE) / 3
);
uint256 attackerShares = depositVault(
_attackerDeposit,
Expand Down
4 changes: 2 additions & 2 deletions test/everlong/units/HyperdriveExecution.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ contract TestHyperdriveExecution is EverlongTest {
// decrease the value of the share adjustment to a non-trivial value.
matureLongAmount = matureLongAmount.normalizeToRange(
MINIMUM_TRANSACTION_AMOUNT + 1,
hyperdrive.calculateMaxLong() / 2
hyperdrive.calculateMaxLong(AS_BASE) / 2
);
openLong(alice, matureLongAmount);
advanceTime(hyperdrive.getPoolConfig().positionDuration, 0);
Expand Down Expand Up @@ -343,7 +343,7 @@ contract TestHyperdriveExecution is EverlongTest {
// value which stress tests the max long function.
initialLongAmount = initialLongAmount.normalizeToRange(
MINIMUM_TRANSACTION_AMOUNT + 1,
hyperdrive.calculateMaxLong() / 2
hyperdrive.calculateMaxLong(AS_BASE) / 2
);
openLong(bob, initialLongAmount);
initialShortAmount = initialShortAmount.normalizeToRange(
Expand Down

0 comments on commit e82d8b3

Please sign in to comment.