diff --git a/contracts/src/HyperdriveDataProvider.sol b/contracts/src/HyperdriveDataProvider.sol index b58e5eb62..c94e7ddd9 100644 --- a/contracts/src/HyperdriveDataProvider.sol +++ b/contracts/src/HyperdriveDataProvider.sol @@ -101,6 +101,20 @@ abstract contract HyperdriveDataProvider is _revert(abi.encode(poolInfo)); } + /// @notice Gets info about the fees presently accrued by the pool + /// @return Governance fees denominated in shares yet to be collected + function getUncollectedGovernanceFees() external view returns (uint256) { + _revert(abi.encode(_governanceFeesAccrued)); + } + + function getMarketState() + external + view + returns (IHyperdrive.MarketState memory) + { + _revert(abi.encode(_marketState)); + } + /// @notice Allows plugin data libs to provide getters or other complex /// logic instead of the main. /// @param _slots The storage slots the caller wants the data from diff --git a/contracts/src/HyperdriveShort.sol b/contracts/src/HyperdriveShort.sol index d8bc35d2f..d3d115164 100644 --- a/contracts/src/HyperdriveShort.sol +++ b/contracts/src/HyperdriveShort.sol @@ -449,13 +449,13 @@ abstract contract HyperdriveShort is HyperdriveLP { _sharePrice ); - // Remove the curve fee from the amount of shares to remove from the shareReserves. - // We do this bc the shareReservesDelta represents how many shares to remove - // from the shareReserves. Making the shareReservesDelta smaller pays out the - // totalCurveFee to the LPs. - // The shareReservesDelta and the totalCurveFee are both in terms of shares - // shares -= shares - shareReservesDelta -= totalCurveFee; + // ShareReservesDelta is the number of shares to remove from the shareReserves and + // since the totalCurveFee includes the totalGovernanceFee it needs to be added back + // to so that it is removed from the shareReserves. The shareReservesDelta, + // totalCurveFee and totalGovernanceFee are all in terms of shares: + + // shares -= shares - shares + shareReservesDelta -= totalCurveFee - totalGovernanceFee; return (shareReservesDelta, totalGovernanceFee); } diff --git a/contracts/src/interfaces/IHyperdriveRead.sol b/contracts/src/interfaces/IHyperdriveRead.sol index dc9f81624..4fc6d97fc 100644 --- a/contracts/src/interfaces/IHyperdriveRead.sol +++ b/contracts/src/interfaces/IHyperdriveRead.sol @@ -21,6 +21,13 @@ interface IHyperdriveRead is IMultiTokenRead { view returns (IHyperdrive.PoolConfig memory); + function getMarketState() + external + view + returns (IHyperdrive.MarketState memory); + + function getUncollectedGovernanceFees() external view returns (uint256); + function getPoolInfo() external view returns (IHyperdrive.PoolInfo memory); function load( diff --git a/test/units/hyperdrive/OpenShortTest.t.sol b/test/units/hyperdrive/OpenShortTest.t.sol index c0345d4a6..d5a339b16 100644 --- a/test/units/hyperdrive/OpenShortTest.t.sol +++ b/test/units/hyperdrive/OpenShortTest.t.sol @@ -6,7 +6,7 @@ import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; -import { HyperdriveTest, HyperdriveUtils } from "../../utils/HyperdriveTest.sol"; +import { HyperdriveTest, HyperdriveUtils, IERC20, MockHyperdrive, MockHyperdriveDataProvider } from "../../utils/HyperdriveTest.sol"; import { Lib } from "../../utils/Lib.sol"; contract OpenShortTest is HyperdriveTest { @@ -179,6 +179,71 @@ contract OpenShortTest is HyperdriveTest { //I think we could trigger this with big short, open long, and short? } + function test_governance_fees_excluded_share_reserves() public { + uint256 apr = 0.05e18; + uint256 contribution = 500_000_000e18; + + // 1. Deploy a pool with zero fees + IHyperdrive.PoolConfig memory config = testConfig(apr); + deploy(address(deployer), config); + // Initialize the pool with a large amount of capital. + initialize(alice, apr, contribution); + + // 2. Open a short + uint256 bondAmount = (hyperdrive.calculateMaxShort() * 90) / 100; + openShort(bob, bondAmount); + + // 3. Record Share Reserves + IHyperdrive.MarketState memory zeroFeeState = hyperdrive + .getMarketState(); + + // 4. deploy a pool with 100% curve fees and 100% gov fees (this is nice bc + // it ensures that all the fees are credited to governance and thus subtracted + // from the shareReserves + config = testConfig(apr); + config.fees = IHyperdrive.Fees({ + curve: 1e18, + flat: 1e18, + governance: 1e18 + }); + deploy(address(deployer), config); + initialize(alice, apr, contribution); + + // 5. Open a Short + bondAmount = (hyperdrive.calculateMaxShort() * 90) / 100; + openShort(bob, bondAmount); + + // 6. Record Share Reserves + IHyperdrive.MarketState memory maxFeeState = hyperdrive + .getMarketState(); + + // Since the fees are subtracted from reserves and accounted for + // seperately, so this will be true + assertEq(zeroFeeState.shareReserves, maxFeeState.shareReserves); + + uint256 govFees = hyperdrive.getUncollectedGovernanceFees(); + // Governance fees collected are non-zero + assert(govFees > 1e5); + + // 7. deploy a pool with 100% curve fees and 0% gov fees + config = testConfig(apr); + config.fees = IHyperdrive.Fees({ curve: 1e18, flat: 0, governance: 0 }); + // Deploy and initialize the new pool + deploy(address(deployer), config); + initialize(alice, apr, contribution); + + // 8. Open a Short + bondAmount = (hyperdrive.calculateMaxShort() * 90) / 100; + openShort(bob, bondAmount); + + // 9. Record Share Reserves + IHyperdrive.MarketState memory maxCurveFeeState = hyperdrive + .getMarketState(); + + // shareReserves should be greater here because there is no Gov being deducted + assertGe(maxCurveFeeState.shareReserves, maxFeeState.shareReserves); + } + function verifyOpenShort( IHyperdrive.PoolInfo memory poolInfoBefore, uint256 contribution,