diff --git a/contracts/src/external/Hyperdrive.sol b/contracts/src/external/Hyperdrive.sol index d0603a385..47e63602c 100644 --- a/contracts/src/external/Hyperdrive.sol +++ b/contracts/src/external/Hyperdrive.sol @@ -218,7 +218,7 @@ abstract contract Hyperdrive is uint256, IHyperdrive.Options calldata ) external payable returns (uint256) { - _delegate(target1); + _delegate(target3); } /// @inheritdoc IHyperdriveCore diff --git a/contracts/src/external/HyperdriveTarget0.sol b/contracts/src/external/HyperdriveTarget0.sol index c662a076c..8b7032459 100644 --- a/contracts/src/external/HyperdriveTarget0.sol +++ b/contracts/src/external/HyperdriveTarget0.sol @@ -406,6 +406,13 @@ abstract contract HyperdriveTarget0 is _revert(abi.encode(_perTokenApprovals[tokenId][account][spender])); } + /// @notice Gets the decimals of the MultiToken. This is the same as the + /// decimals used by the base token. + /// @return The decimals of the MultiToken. + function decimals() external view virtual returns (uint8) { + _revert(abi.encode(_baseToken.decimals())); + } + /// @notice Gets the name of a sub-token. /// @param tokenId The sub-token id. /// @return The name. diff --git a/contracts/src/external/HyperdriveTarget1.sol b/contracts/src/external/HyperdriveTarget1.sol index 2d3e7c2b7..59395c899 100644 --- a/contracts/src/external/HyperdriveTarget1.sol +++ b/contracts/src/external/HyperdriveTarget1.sol @@ -32,19 +32,6 @@ abstract contract HyperdriveTarget1 is /// LPs /// - /// @notice Allows the first LP to initialize the market with a target APR. - /// @param _contribution The amount of base to supply. - /// @param _apr The target APR. - /// @param _options The options that configure how the operation is settled. - /// @return lpShares The initial number of LP shares created. - function initialize( - uint256 _contribution, - uint256 _apr, - IHyperdrive.Options calldata _options - ) external payable returns (uint256 lpShares) { - return _initialize(_contribution, _apr, _options); - } - /// @notice Allows LPs to supply liquidity for LP shares. /// @param _contribution The amount of base to supply. /// @param _minLpSharePrice The minimum LP share price the LP is willing diff --git a/contracts/src/external/HyperdriveTarget3.sol b/contracts/src/external/HyperdriveTarget3.sol index ea23993bd..c863baeb2 100644 --- a/contracts/src/external/HyperdriveTarget3.sol +++ b/contracts/src/external/HyperdriveTarget3.sol @@ -30,6 +30,21 @@ abstract contract HyperdriveTarget3 is IHyperdrive.PoolConfig memory _config ) HyperdriveStorage(_config) {} + /// LPs /// + + /// @notice Allows the first LP to initialize the market with a target APR. + /// @param _contribution The amount of base to supply. + /// @param _apr The target APR. + /// @param _options The options that configure how the operation is settled. + /// @return lpShares The initial number of LP shares created. + function initialize( + uint256 _contribution, + uint256 _apr, + IHyperdrive.Options calldata _options + ) external payable returns (uint256 lpShares) { + return _initialize(_contribution, _apr, _options); + } + /// Longs /// /// @notice Opens a long position. diff --git a/contracts/src/instances/steth/StETHTarget0.sol b/contracts/src/instances/steth/StETHTarget0.sol index e9e9b448f..9429c7a2f 100644 --- a/contracts/src/instances/steth/StETHTarget0.sol +++ b/contracts/src/instances/steth/StETHTarget0.sol @@ -67,4 +67,10 @@ contract StETHTarget0 is HyperdriveTarget0, StETHBase { function lido() external view returns (ILido) { _revert(abi.encode(_lido)); } + + /// @notice Returns the MultiToken's decimals. + /// @return The MultiToken's decimals. + function decimals() external pure override returns (uint8) { + _revert(abi.encode(uint8(18))); + } } diff --git a/contracts/src/interfaces/IHyperdriveEvents.sol b/contracts/src/interfaces/IHyperdriveEvents.sol index 7aabc4e33..20d52567c 100644 --- a/contracts/src/interfaces/IHyperdriveEvents.sol +++ b/contracts/src/interfaces/IHyperdriveEvents.sol @@ -9,7 +9,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { address indexed provider, uint256 lpAmount, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 apr ); @@ -18,7 +19,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { address indexed provider, uint256 lpAmount, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 lpSharePrice ); @@ -27,7 +29,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { address indexed provider, uint256 lpAmount, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 withdrawalShareAmount, uint256 lpSharePrice ); @@ -37,7 +40,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { address indexed provider, uint256 withdrawalShareAmount, uint256 baseAmount, - uint256 vaultSharePrice + uint256 vaultShareAmount, + bool asBase ); /// @notice Emitted when a long position is opened. @@ -46,7 +50,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { uint256 indexed assetId, uint256 maturityTime, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 bondAmount ); @@ -56,7 +61,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { uint256 indexed assetId, uint256 maturityTime, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 bondAmount ); @@ -66,7 +72,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { uint256 indexed assetId, uint256 maturityTime, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 bondAmount ); @@ -76,7 +83,8 @@ interface IHyperdriveEvents is IMultiTokenEvents { uint256 indexed assetId, uint256 maturityTime, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 bondAmount ); diff --git a/contracts/src/interfaces/IMultiTokenRead.sol b/contracts/src/interfaces/IMultiTokenRead.sol index 5e58729ab..4dfea8bca 100644 --- a/contracts/src/interfaces/IMultiTokenRead.sol +++ b/contracts/src/interfaces/IMultiTokenRead.sol @@ -2,6 +2,10 @@ pragma solidity 0.8.20; interface IMultiTokenRead { + /// @notice Gets the decimals of the MultiToken. + /// @return The decimals of the MultiToken. + function decimals() external view returns (uint8); + /// @notice Gets the name of the MultiToken. /// @return The name of the MultiToken. function name(uint256 id) external view returns (string memory); diff --git a/contracts/src/internal/HyperdriveBase.sol b/contracts/src/internal/HyperdriveBase.sol index c5d6d78ca..cd5de9e64 100644 --- a/contracts/src/internal/HyperdriveBase.sol +++ b/contracts/src/internal/HyperdriveBase.sol @@ -599,6 +599,25 @@ abstract contract HyperdriveBase is IHyperdriveEvents, HyperdriveStorage { } } + /// @dev Converts input to vault shares if necessary according to what is + /// specified in options. + /// @param _amount The amount to convert. + /// @param _vaultSharePrice The current vault share price. + /// @param _options The options that configure the conversion. + /// @return The converted amount. + function _convertToVaultSharesFromOption( + uint256 _amount, + uint256 _vaultSharePrice, + IHyperdrive.Options calldata _options + ) internal pure returns (uint256) { + if (_options.asBase) { + // NOTE: Round down to underestimate the vault share amount. + return _amount.divDown(_vaultSharePrice); + } else { + return _amount; + } + } + /// @dev Converts input to what is specified in the options from base. /// @param _amount The amount to convert. /// @param _vaultSharePrice The current vault share price. diff --git a/contracts/src/internal/HyperdriveLP.sol b/contracts/src/internal/HyperdriveLP.sol index 0faa845b9..a6757e316 100644 --- a/contracts/src/internal/HyperdriveLP.sol +++ b/contracts/src/internal/HyperdriveLP.sol @@ -107,7 +107,8 @@ abstract contract HyperdriveLP is _options.destination, lpShares, baseContribution, - vaultSharePrice, + vaultShares, + _options.asBase, _apr ); @@ -239,7 +240,8 @@ abstract contract HyperdriveLP is _options.destination, lpShares, baseContribution, - vaultSharePrice, + vaultShares, + _options.asBase, lpSharePrice ); } @@ -297,16 +299,16 @@ abstract contract HyperdriveLP is withdrawalShares = _lpShares - withdrawalSharesRedeemed; // Emit a RemoveLiquidity event. - uint256 baseProceeds = _convertToBaseFromOption( - proceeds, - vaultSharePrice, - _options - ); emit RemoveLiquidity( _options.destination, _lpShares, - baseProceeds, - vaultSharePrice, + _convertToBaseFromOption(proceeds, vaultSharePrice, _options), + _convertToVaultSharesFromOption( + proceeds, + vaultSharePrice, + _options + ), + _options.asBase, uint256(withdrawalShares), _calculateLPSharePrice(vaultSharePrice) ); @@ -351,16 +353,16 @@ abstract contract HyperdriveLP is ); // Emit a RedeemWithdrawalShares event. - uint256 baseProceeds = _convertToBaseFromOption( - proceeds, - vaultSharePrice, - _options - ); emit RedeemWithdrawalShares( _options.destination, withdrawalSharesRedeemed, - baseProceeds, - vaultSharePrice + _convertToBaseFromOption(proceeds, vaultSharePrice, _options), + _convertToVaultSharesFromOption( + proceeds, + vaultSharePrice, + _options + ), + _options.asBase ); return (proceeds, withdrawalSharesRedeemed); diff --git a/contracts/src/internal/HyperdriveLong.sol b/contracts/src/internal/HyperdriveLong.sol index 2f1bed23b..516dc5ccd 100644 --- a/contracts/src/internal/HyperdriveLong.sol +++ b/contracts/src/internal/HyperdriveLong.sol @@ -114,18 +114,21 @@ abstract contract HyperdriveLong is IHyperdriveEvents, HyperdriveLP { // Emit an OpenLong event. uint256 amount = _amount; // Avoid stack too deep error. + uint256 maturityTime_ = maturityTime; // Avoid stack too deep error. + uint256 bondProceeds_ = bondProceeds; // Avoid stack too deep error. + uint256 vaultSharePrice_ = vaultSharePrice; // Avoid stack too deep error. IHyperdrive.Options calldata options = _options; // Avoid stack too deep error. - uint256 _bondProceeds = bondProceeds; // Avoid stack too deep error. emit OpenLong( - _options.destination, + options.destination, assetId, - maturityTime, - _convertToBaseFromOption(amount, vaultSharePrice, options), - vaultSharePrice, - _bondProceeds + maturityTime_, + _convertToBaseFromOption(amount, vaultSharePrice_, options), + _convertToVaultSharesFromOption(amount, vaultSharePrice_, options), + options.asBase, + bondProceeds_ ); - return (maturityTime, _bondProceeds); + return (maturityTime, bondProceeds_); } /// @dev Closes a long position with a specified maturity time. @@ -218,12 +221,19 @@ abstract contract HyperdriveLong is IHyperdriveEvents, HyperdriveLP { // Emit a CloseLong event. uint256 bondAmount = _bondAmount; // Avoid stack too deep error. + uint256 vaultSharePrice_ = vaultSharePrice; // Avoid stack too deep error. + IHyperdrive.Options calldata options = _options; // Avoid stack too deep error. emit CloseLong( - _options.destination, + options.destination, AssetId.encodeAssetId(AssetId.AssetIdPrefix.Long, maturityTime), maturityTime, baseProceeds, - vaultSharePrice, + _convertToVaultSharesFromOption( + proceeds, + vaultSharePrice_, + options + ), + options.asBase, bondAmount ); diff --git a/contracts/src/internal/HyperdriveShort.sol b/contracts/src/internal/HyperdriveShort.sol index 440b57eb2..9a261ba34 100644 --- a/contracts/src/internal/HyperdriveShort.sol +++ b/contracts/src/internal/HyperdriveShort.sol @@ -117,12 +117,14 @@ abstract contract HyperdriveShort is IHyperdriveEvents, HyperdriveLP { _mint(assetId, _options.destination, bondAmount); // Emit an OpenShort event. + IHyperdrive.Options calldata options = _options; emit OpenShort( - _options.destination, + options.destination, assetId, maturityTime, baseDeposit, - vaultSharePrice, + baseDeposit.divDown(vaultSharePrice), + options.asBase, bondAmount ); @@ -223,12 +225,19 @@ abstract contract HyperdriveShort is IHyperdriveEvents, HyperdriveLP { // Emit a CloseShort event. uint256 bondAmount = _bondAmount; // Avoid stack too deep error. uint256 maturityTime = _maturityTime; // Avoid stack too deep error. + uint256 vaultSharePrice_ = vaultSharePrice; // Avoid stack too deep error. + IHyperdrive.Options calldata options = _options; // Avoid stack too deep error. emit CloseShort( - _options.destination, + options.destination, AssetId.encodeAssetId(AssetId.AssetIdPrefix.Short, maturityTime), maturityTime, baseProceeds, - vaultSharePrice, + _convertToVaultSharesFromOption( + proceeds, + vaultSharePrice_, + options + ), + options.asBase, bondAmount ); diff --git a/test/instances/erc4626/ERC4626Validation.t.sol b/test/instances/erc4626/ERC4626Validation.t.sol index 7a323fdb3..bb076fee7 100644 --- a/test/instances/erc4626/ERC4626Validation.t.sol +++ b/test/instances/erc4626/ERC4626Validation.t.sol @@ -287,7 +287,11 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { bytes32(uint256(0xfade)) ); - // Ensure minimumShareReserves were added, and lpTotalSupply increased + // Ensure that the decimals are set correctly. + assertEq(hyperdrive.decimals(), decimals); + + // Ensure that the minimum share reserves was successfully capitalized + // and that the LP total supply was updated to the correct value. assertEq( hyperdrive.getPoolInfo().lpTotalSupply, hyperdrive.getPoolInfo().shareReserves - config.minimumShareReserves diff --git a/test/instances/steth/StETHHyperdrive.t.sol b/test/instances/steth/StETHHyperdrive.t.sol index 88c0ce115..532dcf783 100644 --- a/test/instances/steth/StETHHyperdrive.t.sol +++ b/test/instances/steth/StETHHyperdrive.t.sol @@ -301,6 +301,9 @@ contract StETHHyperdriveTest is HyperdriveTest { ); assertEq(address(bob).balance, bobBalanceBefore - contribution); + // Ensure that the decimals are set correctly. + assertEq(hyperdrive.decimals(), 18); + // Ensure that Bob received the correct amount of LP tokens. He should // receive LP shares totaling the amount of shares that he contributed // minus the shares set aside for the minimum share reserves and the diff --git a/test/units/hyperdrive/CloseLongTest.t.sol b/test/units/hyperdrive/CloseLongTest.t.sol index 68ff2ecc9..cdd1d0e6c 100644 --- a/test/units/hyperdrive/CloseLongTest.t.sol +++ b/test/units/hyperdrive/CloseLongTest.t.sol @@ -910,12 +910,22 @@ contract CloseLongTest is HyperdriveTest { ( uint256 eventMaturityTime, uint256 eventBaseAmount, - uint256 eventSharePrice, + uint256 eventVaultShareAmount, + bool eventAsBase, uint256 eventBondAmount - ) = abi.decode(log.data, (uint256, uint256, uint256, uint256)); + ) = abi.decode( + log.data, + (uint256, uint256, uint256, bool, uint256) + ); assertEq(eventMaturityTime, testCase.maturityTime); assertEq(eventBaseAmount, testCase.baseProceeds); - assertEq(eventSharePrice, hyperdrive.getPoolInfo().vaultSharePrice); + assertEq( + eventVaultShareAmount, + testCase.baseProceeds.divDown( + hyperdrive.getPoolInfo().vaultSharePrice + ) + ); + assertEq(eventAsBase, true); assertEq(eventBondAmount, testCase.bondAmount); } diff --git a/test/units/hyperdrive/CloseShortTest.t.sol b/test/units/hyperdrive/CloseShortTest.t.sol index b8db0c742..175354783 100644 --- a/test/units/hyperdrive/CloseShortTest.t.sol +++ b/test/units/hyperdrive/CloseShortTest.t.sol @@ -879,12 +879,22 @@ contract CloseShortTest is HyperdriveTest { ( uint256 eventMaturityTime, uint256 eventBaseAmount, - uint256 eventSharePrice, + uint256 eventVaultShareAmount, + bool eventAsBase, uint256 eventBondAmount - ) = abi.decode(log.data, (uint256, uint256, uint256, uint256)); + ) = abi.decode( + log.data, + (uint256, uint256, uint256, bool, uint256) + ); assertEq(eventMaturityTime, testCase.maturityTime); assertEq(eventBaseAmount, testCase.baseProceeds); - assertEq(eventSharePrice, hyperdrive.getPoolInfo().vaultSharePrice); + assertEq( + eventVaultShareAmount, + testCase.baseProceeds.divDown( + hyperdrive.getPoolInfo().vaultSharePrice + ) + ); + assertEq(eventAsBase, true); assertEq(eventBondAmount, testCase.bondAmount); } diff --git a/test/units/hyperdrive/InitializeTest.t.sol b/test/units/hyperdrive/InitializeTest.t.sol index 8e93aa6a4..453ea76c3 100644 --- a/test/units/hyperdrive/InitializeTest.t.sol +++ b/test/units/hyperdrive/InitializeTest.t.sol @@ -139,12 +139,17 @@ contract InitializeTest is HyperdriveTest { ( uint256 lpShares, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 spotRate - ) = abi.decode(log.data, (uint256, uint256, uint256, uint256)); + ) = abi.decode(log.data, (uint256, uint256, uint256, bool, uint256)); assertEq(lpShares, expectedLpShares); assertEq(baseAmount, expectedBaseAmount); - assertEq(vaultSharePrice, hyperdrive.getPoolInfo().vaultSharePrice); + assertEq( + vaultShareAmount, + expectedBaseAmount.divDown(hyperdrive.getPoolInfo().vaultSharePrice) + ); + assertEq(asBase, true); assertEq(spotRate, expectedSpotRate); } } diff --git a/test/units/hyperdrive/OpenLongTest.t.sol b/test/units/hyperdrive/OpenLongTest.t.sol index 2f05ec391..2c9eb6f38 100644 --- a/test/units/hyperdrive/OpenLongTest.t.sol +++ b/test/units/hyperdrive/OpenLongTest.t.sol @@ -365,12 +365,20 @@ contract OpenLongTest is HyperdriveTest { ( uint256 eventMaturityTime, uint256 eventBaseAmount, - uint256 eventSharePrice, + uint256 eventVaultShareAmount, + bool eventAsBase, uint256 eventBondAmount - ) = abi.decode(log.data, (uint256, uint256, uint256, uint256)); + ) = abi.decode( + log.data, + (uint256, uint256, uint256, bool, uint256) + ); assertEq(eventMaturityTime, maturityTime); assertEq(eventBaseAmount, baseAmount); - assertEq(eventSharePrice, hyperdrive.getPoolInfo().vaultSharePrice); + assertEq( + eventVaultShareAmount, + baseAmount.divDown(hyperdrive.getPoolInfo().vaultSharePrice) + ); + assertEq(eventAsBase, true); assertEq(eventBondAmount, bondAmount); } diff --git a/test/units/hyperdrive/OpenShortTest.t.sol b/test/units/hyperdrive/OpenShortTest.t.sol index bbcbdd8a6..83a17cf1c 100644 --- a/test/units/hyperdrive/OpenShortTest.t.sol +++ b/test/units/hyperdrive/OpenShortTest.t.sol @@ -392,12 +392,20 @@ contract OpenShortTest is HyperdriveTest { ( uint256 eventMaturityTime, uint256 eventBaseAmount, - uint256 eventSharePrice, + uint256 eventVaultShareAmount, + bool eventAsBase, uint256 eventBondAmount - ) = abi.decode(log.data, (uint256, uint256, uint256, uint256)); + ) = abi.decode( + log.data, + (uint256, uint256, uint256, bool, uint256) + ); assertEq(eventMaturityTime, maturityTime); assertEq(eventBaseAmount, basePaid); - assertEq(eventSharePrice, hyperdrive.getPoolInfo().vaultSharePrice); + assertEq( + eventVaultShareAmount, + basePaid.divDown(hyperdrive.getPoolInfo().vaultSharePrice) + ); + assertEq(eventAsBase, true); assertEq(eventBondAmount, shortAmount); } diff --git a/test/units/hyperdrive/RemoveLiquidityTest.t.sol b/test/units/hyperdrive/RemoveLiquidityTest.t.sol index ef1b563b7..c3ba35651 100644 --- a/test/units/hyperdrive/RemoveLiquidityTest.t.sol +++ b/test/units/hyperdrive/RemoveLiquidityTest.t.sol @@ -281,12 +281,17 @@ contract RemoveLiquidityTest is HyperdriveTest { ( uint256 lpShares, uint256 baseAmount, - uint256 vaultSharePrice, + uint256 vaultShareAmount, + bool asBase, uint256 withdrawalShares - ) = abi.decode(log.data, (uint256, uint256, uint256, uint256)); + ) = abi.decode(log.data, (uint256, uint256, uint256, bool, uint256)); assertEq(lpShares, expectedLpShares); assertEq(baseAmount, expectedBaseAmount); - assertEq(vaultSharePrice, hyperdrive.getPoolInfo().vaultSharePrice); + assertEq( + vaultShareAmount, + expectedBaseAmount.divDown(hyperdrive.getPoolInfo().vaultSharePrice) + ); + assertEq(asBase, true); assertEq(withdrawalShares, expectedWithdrawalShares); } diff --git a/test/utils/HyperdriveTest.sol b/test/utils/HyperdriveTest.sol index aca059599..d77df5984 100644 --- a/test/utils/HyperdriveTest.sol +++ b/test/utils/HyperdriveTest.sol @@ -991,11 +991,12 @@ contract HyperdriveTest is IHyperdriveEvents, BaseTest { ( uint256 eventLpAmount, uint256 eventBaseAmount, - uint256 eventSharePrice, + uint256 eventShareAmount, + bool eventAsBase, uint256 eventApr ) = abi.decode( filteredLogs[0].data, - (uint256, uint256, uint256, uint256) + (uint256, uint256, uint256, bool, uint256) ); uint256 contribution_ = contribution; IHyperdrive hyperdrive_ = _hyperdrive; @@ -1007,10 +1008,14 @@ contract HyperdriveTest is IHyperdriveEvents, BaseTest { tolerance ); assertEq(eventBaseAmount, contribution_); - assertEq( - eventSharePrice, - hyperdrive_.getPoolInfo().vaultSharePrice + assertApproxEqAbs( + eventShareAmount, + contribution_.divDown( + hyperdrive_.getPoolInfo().vaultSharePrice + ), + 1e5 ); + assertEq(eventAsBase, true); assertEq(eventApr, apr); } }