diff --git a/.env_template b/.env_template new file mode 100644 index 00000000..9892d7bf --- /dev/null +++ b/.env_template @@ -0,0 +1,6 @@ +# ethereum rpc endpoints used in fork tests and migration scripts +GOERLI_RPC_URL= +MAINNET_RPC_URL= + +# private key used for running migration scripts +PRIVATE_KEY= diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index cea604b5..292e1e3d 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -12,6 +12,9 @@ jobs: benchmark: name: benchmark runs-on: ubuntu-latest + env: + GOERLI_RPC_URL: ${{ secrets.GOERLI_RPC_URL }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} steps: # Install the dependencies. - uses: actions/checkout@v2 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 12290405..bbe931bf 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,6 +12,9 @@ jobs: coverage: name: coverage runs-on: ubuntu-latest + env: + GOERLI_RPC_URL: ${{ secrets.GOERLI_RPC_URL }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1d199c6e..3944ad90 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,6 +12,9 @@ jobs: lint: name: lint runs-on: ubuntu-latest + env: + GOERLI_RPC_URL: ${{ secrets.GOERLI_RPC_URL }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2342188..9e2db77b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,9 @@ jobs: test: name: test runs-on: ubuntu-latest + env: + GOERLI_RPC_URL: ${{ secrets.GOERLI_RPC_URL }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} steps: - uses: actions/checkout@v3 with: diff --git a/README.md b/README.md index d9e6b444..a39680a7 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ prettify the source code. Proceed through the following steps to set up the repo - Install lib/forge-std dependencies by running `forge install` from the project root - Install node.js dependencies by running `yarn` from the project root +## Environment Variables + +The test suite and migration scripts make use of several environment variables. +Copy `.env_template` to `.env` and populate the file with your private key and +provider URLs. + ## Build To build the smart contracts, run `yarn build`. diff --git a/contracts/src/DataProvider.sol b/contracts/src/DataProvider.sol new file mode 100644 index 00000000..b932e3b9 --- /dev/null +++ b/contracts/src/DataProvider.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { Errors } from "./libraries/Errors.sol"; + +/// @author DELV +/// @title DataProvider +/// @notice Implements a fallback function that serves as a generalized getter. +/// This helps contracts stay under the code size limit. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract DataProvider { + address internal immutable dataProvider; + + /// @notice Initializes the data provider. + /// @param _dataProvider The address of the data provider. + constructor(address _dataProvider) { + dataProvider = _dataProvider; + } + + /// @notice Fallback function that delegates calls to the data provider. + /// @param _data The data to be passed to the data provider. + /// @return The return data from the data provider. + fallback(bytes calldata _data) external returns (bytes memory) { + // Delegatecall into the data provider. We use a force-revert + // delegatecall pattern to ensure that no state changes were made + // during the call to the data provider. + (bool success, bytes memory returndata) = dataProvider.delegatecall( + _data + ); + if (success) { + revert Errors.UnexpectedSuccess(); + } + return returndata; + } +} diff --git a/contracts/src/Hyperdrive.sol b/contracts/src/Hyperdrive.sol index de4af051..763003c4 100644 --- a/contracts/src/Hyperdrive.sol +++ b/contracts/src/Hyperdrive.sol @@ -27,43 +27,18 @@ abstract contract Hyperdrive is using SafeCast for uint256; /// @notice Initializes a Hyperdrive pool. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _dataProvider The address of the data provider. /// @param _linkerCodeHash The hash of the ERC20 linker contract's /// constructor code. /// @param _linkerFactory The address of the factory which is used to deploy /// the ERC20 linker contracts. - /// @param _baseToken The base token contract. - /// @param _initialSharePrice The initial share price. - /// @param _checkpointsPerTerm The number of checkpoints that elapses before - /// bonds can be redeemed one-to-one for base. - /// @param _checkpointDuration The time in seconds between share price - /// checkpoints. Position duration must be a multiple of checkpoint - /// duration. - /// @param _timeStretch The time stretch of the pool. - /// @param _fees The fees to apply to trades. - /// @param _governance The address of the governance contract. constructor( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, - address _linkerFactory, - IERC20 _baseToken, - uint256 _initialSharePrice, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, - IHyperdrive.Fees memory _fees, - address _governance - ) - HyperdriveBase( - _linkerCodeHash, - _linkerFactory, - _baseToken, - _initialSharePrice, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, - _fees, - _governance - ) - {} // solhint-disable-line no-empty-blocks + address _linkerFactory + ) HyperdriveBase(_config, _dataProvider, _linkerCodeHash, _linkerFactory) {} // solhint-disable-line no-empty-blocks /// @notice Allows anyone to mint a new checkpoint. /// @param _checkpointTime The time of the checkpoint to create. @@ -128,7 +103,7 @@ abstract contract Hyperdrive is checkpoints[_checkpointTime].sharePrice = _sharePrice.toUint128(); // Pay out the long withdrawal pool for longs that have matured. - uint256 maturedLongsAmount = totalSupply[ + uint256 maturedLongsAmount = _totalSupply[ AssetId.encodeAssetId(AssetId.AssetIdPrefix.Long, _checkpointTime) ]; if (maturedLongsAmount > 0) { @@ -143,7 +118,7 @@ abstract contract Hyperdrive is } // Pay out the short withdrawal pool for shorts that have matured. - uint256 maturedShortsAmount = totalSupply[ + uint256 maturedShortsAmount = _totalSupply[ AssetId.encodeAssetId(AssetId.AssetIdPrefix.Short, _checkpointTime) ]; if (maturedShortsAmount > 0) { diff --git a/contracts/src/HyperdriveBase.sol b/contracts/src/HyperdriveBase.sol index 6f606977..9d05d66c 100644 --- a/contracts/src/HyperdriveBase.sol +++ b/contracts/src/HyperdriveBase.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.18; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { HyperdriveStorage } from "./HyperdriveStorage.sol"; import { MultiToken } from "./MultiToken.sol"; import { AssetId } from "./libraries/AssetId.sol"; import { Errors } from "./libraries/Errors.sol"; @@ -16,7 +17,7 @@ import { IHyperdrive } from "./interfaces/IHyperdrive.sol"; /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. -abstract contract HyperdriveBase is MultiToken { +abstract contract HyperdriveBase is MultiToken, HyperdriveStorage { using FixedPointMath for uint256; using SafeCast for uint256; @@ -41,78 +42,39 @@ abstract contract HyperdriveBase is MultiToken { // @notice The share price at the time the pool was created. uint256 internal immutable initialSharePrice; - /// @notice The reserves and the buffers. This is the primary state used for - /// pricing trades and maintaining solvency. - IHyperdrive.MarketState internal marketState; - - /// @notice The state corresponding to the withdraw pool, expressed as a struct. - IHyperdrive.WithdrawPool internal withdrawPool; - - /// @notice The fee percentages to be applied to the trade equation - IHyperdrive.Fees internal fees; - - /// @notice Hyperdrive positions are bucketed into checkpoints, which - /// allows us to avoid poking in any period that has LP or trading - /// activity. The checkpoints contain the starting share price from - /// the checkpoint as well as aggregate volume values. - mapping(uint256 => IHyperdrive.Checkpoint) public checkpoints; - - /// @notice Addresses approved in this mapping can pause all deposits into - /// the contract and other non essential functionality. - mapping(address => bool) public pausers; - - // TODO: This shouldn't be public. - // - // Governance fees that haven't been collected yet denominated in shares. - uint256 public governanceFeesAccrued; - - // TODO: This shouldn't be public. - // - // TODO: Should this be immutable? - // - // The address that receives governance fees. - address public governance; - /// @notice Initializes a Hyperdrive pool. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _dataProvider The address of the data provider. /// @param _linkerCodeHash The hash of the ERC20 linker contract's /// constructor code. /// @param _linkerFactory The address of the factory which is used to deploy /// the ERC20 linker contracts. - /// @param _baseToken The base token contract. - /// @param _initialSharePrice The initial share price. - /// @param _checkpointsPerTerm The number of checkpoints that elapses before - /// bonds can be redeemed one-to-one for base. - /// @param _checkpointDuration The time in seconds between share price - /// checkpoints. Position duration must be a multiple of checkpoint - /// duration. - /// @param _timeStretch The time stretch of the pool. - /// @param _fees The fees to apply to trades. - /// @param _governance The address that receives governance fees. constructor( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, - address _linkerFactory, - IERC20 _baseToken, - uint256 _initialSharePrice, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, - IHyperdrive.Fees memory _fees, - address _governance - ) MultiToken(_linkerCodeHash, _linkerFactory) { + address _linkerFactory + ) MultiToken(_dataProvider, _linkerCodeHash, _linkerFactory) { // Initialize the base token address. - baseToken = _baseToken; + baseToken = _config.baseToken; // Initialize the time configurations. There must be at least one // checkpoint per term to avoid having a position duration of zero. - if (_checkpointsPerTerm == 0) { - revert Errors.InvalidCheckpointsPerTerm(); + if (_config.checkpointDuration == 0) { + revert Errors.InvalidCheckpointDuration(); } - positionDuration = _checkpointsPerTerm * _checkpointDuration; - checkpointDuration = _checkpointDuration; - timeStretch = _timeStretch; - initialSharePrice = _initialSharePrice; - fees = _fees; - governance = _governance; + checkpointDuration = _config.checkpointDuration; + if ( + _config.positionDuration < _config.checkpointDuration || + _config.positionDuration % _config.checkpointDuration != 0 + ) { + revert Errors.InvalidPositionDuration(); + } + positionDuration = _config.positionDuration; + timeStretch = _config.timeStretch; + initialSharePrice = _config.initialSharePrice; + fees = _config.fees; + governance = _config.governance; } /// Yield Source /// @@ -150,6 +112,29 @@ abstract contract HyperdriveBase is MultiToken { virtual returns (uint256 sharePrice); + /// Pause /// + + ///@notice Allows governance to set the ability of an address to pause deposits + ///@param who The address to change + ///@param status The new pauser status + function setPauser(address who, bool status) external { + if (msg.sender != governance) revert Errors.Unauthorized(); + pausers[who] = status; + } + + ///@notice Allows an authorized address to pause this contract + ///@param status True to pause all deposits and false to unpause them + function pause(bool status) external { + if (!pausers[msg.sender]) revert Errors.Unauthorized(); + marketState.isPaused = status; + } + + ///@notice Blocks a function execution if the contract is paused + modifier isNotPaused() { + if (marketState.isPaused) revert Errors.Paused(); + _; + } + /// Checkpoint /// /// @notice Allows anyone to mint a new checkpoint. @@ -190,77 +175,16 @@ abstract contract HyperdriveBase is MultiToken { { return IHyperdrive.PoolConfig({ + baseToken: baseToken, initialSharePrice: initialSharePrice, positionDuration: positionDuration, checkpointDuration: checkpointDuration, timeStretch: timeStretch, - flatFee: fees.flat, - curveFee: fees.curve, - governanceFee: fees.governance - }); - } - - /// @notice Gets info about the pool's reserves and other state that is - /// important to evaluate potential trades. - /// @return The PoolInfo struct. - function getPoolInfo() external view returns (IHyperdrive.PoolInfo memory) { - return - IHyperdrive.PoolInfo({ - shareReserves: marketState.shareReserves, - bondReserves: marketState.bondReserves, - lpTotalSupply: totalSupply[AssetId._LP_ASSET_ID], - sharePrice: _pricePerShare(), - longsOutstanding: marketState.longsOutstanding, - longAverageMaturityTime: marketState.longAverageMaturityTime, - shortsOutstanding: marketState.shortsOutstanding, - shortAverageMaturityTime: marketState.shortAverageMaturityTime, - shortBaseVolume: marketState.shortBaseVolume, - withdrawalSharesReadyToWithdraw: withdrawPool.readyToWithdraw, - withdrawalSharesProceeds: withdrawPool.proceeds + governance: governance, + fees: fees }); } - ///@notice Allows governance to set the ability of an address to pause deposits - ///@param who The address to change - ///@param status The new pauser status - function setPauser(address who, bool status) external { - if (msg.sender != governance) revert Errors.Unauthorized(); - pausers[who] = status; - } - - ///@notice Allows an authorized address to pause this contract - ///@param status True to pause all deposits and false to unpause them - function pause(bool status) external { - if (!pausers[msg.sender]) revert Errors.Unauthorized(); - marketState.isPaused = status; - } - - ///@notice Blocks a function execution if the contract is paused - modifier isNotPaused() { - if (marketState.isPaused) revert Errors.Paused(); - _; - } - - ///@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 - ///@return A raw array of loaded data - function load( - uint256[] calldata _slots - ) external view returns (bytes32[] memory) { - bytes32[] memory loaded = new bytes32[](_slots.length); - - // Iterate on requested loads and then do them - for (uint256 i = 0; i < _slots.length; i++) { - uint256 slot = _slots[i]; - bytes32 data; - assembly ("memory-safe") { - data := sload(slot) - } - loaded[i] = data; - } - return loaded; - } - /// Helpers /// /// @dev Calculates the normalized time remaining of a position. diff --git a/contracts/src/HyperdriveDataProvider.sol b/contracts/src/HyperdriveDataProvider.sol new file mode 100644 index 00000000..416843b1 --- /dev/null +++ b/contracts/src/HyperdriveDataProvider.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { MultiTokenDataProvider } from "./MultiTokenDataProvider.sol"; +import { HyperdriveStorage } from "./HyperdriveStorage.sol"; +import { IHyperdrive } from "./interfaces/IHyperdrive.sol"; +import { AssetId } from "./libraries/AssetId.sol"; + +/// @author DELV +/// @title HyperdriveDataProvider +/// @notice The Hyperdrive data provider. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +abstract contract HyperdriveDataProvider is + HyperdriveStorage, + MultiTokenDataProvider +{ + /// Yield Source /// + + ///@notice Loads the share price from the yield source + ///@return sharePrice The current share price. + function _pricePerShare() + internal + view + virtual + returns (uint256 sharePrice); + + /// Getters /// + + /// @notice Gets a specified checkpoint. + /// @param _checkpointId The checkpoint ID. + /// @return The checkpoint. + function getCheckpoint( + uint256 _checkpointId + ) external view returns (IHyperdrive.Checkpoint memory) { + _revert(abi.encode(checkpoints[_checkpointId])); + } + + /// @notice Gets info about the pool's reserves and other state that is + /// important to evaluate potential trades. + /// @return The PoolInfo struct. + function getPoolInfo() external view returns (IHyperdrive.PoolInfo memory) { + IHyperdrive.PoolInfo memory poolInfo = IHyperdrive.PoolInfo({ + shareReserves: marketState.shareReserves, + bondReserves: marketState.bondReserves, + lpTotalSupply: _totalSupply[AssetId._LP_ASSET_ID], + sharePrice: _pricePerShare(), + longsOutstanding: marketState.longsOutstanding, + longAverageMaturityTime: marketState.longAverageMaturityTime, + shortsOutstanding: marketState.shortsOutstanding, + shortAverageMaturityTime: marketState.shortAverageMaturityTime, + shortBaseVolume: marketState.shortBaseVolume, + withdrawalSharesReadyToWithdraw: withdrawPool.readyToWithdraw, + withdrawalSharesProceeds: withdrawPool.proceeds + }); + _revert(abi.encode(poolInfo)); + } + + /// @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 + /// @return A raw array of loaded data + function load( + uint256[] calldata _slots + ) external view returns (bytes32[] memory) { + bytes32[] memory loaded = new bytes32[](_slots.length); + + // Iterate on requested loads and then do them + for (uint256 i = 0; i < _slots.length; i++) { + uint256 slot = _slots[i]; + bytes32 data; + assembly ("memory-safe") { + data := sload(slot) + } + loaded[i] = data; + } + + _revert(abi.encode(loaded)); + } +} diff --git a/contracts/src/HyperdriveLP.sol b/contracts/src/HyperdriveLP.sol index 4bab2b53..0c37da74 100644 --- a/contracts/src/HyperdriveLP.sol +++ b/contracts/src/HyperdriveLP.sol @@ -118,10 +118,10 @@ abstract contract HyperdriveLP is HyperdriveBase { // // TODO: We should have a constant for the withdrawal shares asset ID if // we're not going to tranche. - uint256 withdrawalSharesOutstanding = totalSupply[ + uint256 withdrawalSharesOutstanding = _totalSupply[ AssetId.encodeAssetId(AssetId.AssetIdPrefix.WithdrawalShare, 0) ] - withdrawPool.readyToWithdraw; - uint256 lpTotalSupply = totalSupply[AssetId._LP_ASSET_ID] + + uint256 lpTotalSupply = _totalSupply[AssetId._LP_ASSET_ID] + withdrawalSharesOutstanding; // Calculate the number of LP shares to mint. @@ -231,8 +231,8 @@ abstract contract HyperdriveLP is HyperdriveBase { _applyCheckpoint(_latestCheckpoint(), sharePrice); // Burn the LP shares. - uint256 activeLpTotalSupply = totalSupply[AssetId._LP_ASSET_ID]; - uint256 withdrawalSharesOutstanding = totalSupply[ + uint256 activeLpTotalSupply = _totalSupply[AssetId._LP_ASSET_ID]; + uint256 withdrawalSharesOutstanding = _totalSupply[ AssetId.encodeAssetId(AssetId.AssetIdPrefix.WithdrawalShare, 0) ] - withdrawPool.readyToWithdraw; uint256 lpTotalSupply = activeLpTotalSupply + @@ -444,7 +444,7 @@ abstract contract HyperdriveLP is HyperdriveBase { shortBaseVolume: marketState.shortBaseVolume }) ); - uint256 lpTotalSupply = totalSupply[AssetId._LP_ASSET_ID] + + uint256 lpTotalSupply = _totalSupply[AssetId._LP_ASSET_ID] + _withdrawalSharesOutstanding; _compensateWithdrawalPool( _withdrawalProceeds, diff --git a/contracts/src/HyperdriveLong.sol b/contracts/src/HyperdriveLong.sol index 75606666..bb35489c 100644 --- a/contracts/src/HyperdriveLong.sol +++ b/contracts/src/HyperdriveLong.sol @@ -195,7 +195,7 @@ abstract contract HyperdriveLong is HyperdriveLP { ) .updateWeightedAverage( uint256( - totalSupply[ + _totalSupply[ AssetId.encodeAssetId( AssetId.AssetIdPrefix.Long, _maturityTime @@ -294,7 +294,7 @@ abstract contract HyperdriveLong is HyperdriveLP { // amount of withdrawal shares. The proceeds owed to LPs when a long is // closed is equivalent to short proceeds as LPs take the other side of // every trade. - uint256 withdrawalSharesOutstanding = totalSupply[ + uint256 withdrawalSharesOutstanding = _totalSupply[ AssetId.encodeAssetId(AssetId.AssetIdPrefix.WithdrawalShare, 0) ] - withdrawPool.readyToWithdraw; if (withdrawalSharesOutstanding > 0) { diff --git a/contracts/src/HyperdriveShort.sol b/contracts/src/HyperdriveShort.sol index 7827001d..26210f68 100644 --- a/contracts/src/HyperdriveShort.sol +++ b/contracts/src/HyperdriveShort.sol @@ -290,7 +290,7 @@ abstract contract HyperdriveShort is HyperdriveLP { // being closed. If the shorts are closed before maturity, we add the // amount of shorts being closed since the total supply is decreased // when burning the short tokens. - uint256 checkpointAmount = totalSupply[ + uint256 checkpointAmount = _totalSupply[ AssetId.encodeAssetId( AssetId.AssetIdPrefix.Short, _maturityTime @@ -328,7 +328,7 @@ abstract contract HyperdriveShort is HyperdriveLP { // amount of withdrawal shares. The proceeds owed to LPs when a long is // closed is equivalent to short proceeds as LPs take the other side of // every trade. - uint256 withdrawalSharesOutstanding = totalSupply[ + uint256 withdrawalSharesOutstanding = _totalSupply[ AssetId.encodeAssetId(AssetId.AssetIdPrefix.WithdrawalShare, 0) ] - withdrawPool.readyToWithdraw; if (withdrawalSharesOutstanding > 0) { diff --git a/contracts/src/HyperdriveStorage.sol b/contracts/src/HyperdriveStorage.sol new file mode 100644 index 00000000..d38b938e --- /dev/null +++ b/contracts/src/HyperdriveStorage.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { IHyperdrive } from "./interfaces/IHyperdrive.sol"; +import { MultiTokenStorage } from "./MultiTokenStorage.sol"; + +/// @author DELV +/// @title HyperdriveStorage +/// @notice The storage contract of the Hyperdrive inheritance hierarchy. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +abstract contract HyperdriveStorage is MultiTokenStorage { + /// Market State /// + + /// @notice The state of the market. This includes the reserves, buffers, + /// and other data used to price trades and maintain solvency. + IHyperdrive.MarketState internal marketState; + + /// @notice The state corresponding to the withdraw pool. + IHyperdrive.WithdrawPool internal withdrawPool; + + // TODO: Shouldn't these be immutable? + // + /// @notice The fee percentages to be applied to trades. + IHyperdrive.Fees internal fees; + + /// @notice Hyperdrive positions are bucketed into checkpoints, which + /// allows us to avoid poking in any period that has LP or trading + /// activity. The checkpoints contain the starting share price from + /// the checkpoint as well as aggregate volume values. + mapping(uint256 => IHyperdrive.Checkpoint) internal checkpoints; + + /// @notice Addresses approved in this mapping can pause all deposits into + /// the contract and other non essential functionality. + mapping(address => bool) internal pausers; + + // Governance fees that haven't been collected yet denominated in shares. + uint256 internal governanceFeesAccrued; + + // TODO: Should this be immutable? + // + // The address that receives governance fees. + address internal governance; +} diff --git a/contracts/src/MultiToken.sol b/contracts/src/MultiToken.sol index b0e9f45e..dd5e9756 100644 --- a/contracts/src/MultiToken.sol +++ b/contracts/src/MultiToken.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import { IMultiToken } from "./interfaces/IMultiToken.sol"; +import { DataProvider } from "./DataProvider.sol"; +import { MultiTokenStorage } from "./MultiTokenStorage.sol"; +import { IMultiTokenWrite } from "./interfaces/IMultiTokenWrite.sol"; import { Errors } from "./libraries/Errors.sol"; // A lite version of a semi fungible, which removes some methods and so @@ -10,28 +12,11 @@ import { Errors } from "./libraries/Errors.sol"; // NOTE - We remove on transfer callbacks and safe transfer because of the // risk of external calls to untrusted code. -contract MultiToken is IMultiToken { +contract MultiToken is DataProvider, MultiTokenStorage, IMultiTokenWrite { // TODO - Choose to change names to perfect match the 1155 ie adding 'safe', // choose whether to support the batch methods, and to support token uris // or names - // Allows loading of each balance - mapping(uint256 => mapping(address => uint256)) public balanceOf; - // Allows loading of each total supply - mapping(uint256 => uint256) public totalSupply; - // Uniform approval for all tokens - mapping(address => mapping(address => bool)) - public - override isApprovedForAll; - // Additional optional per token approvals - // Note - non standard for erc1150 but we want to replicate erc20 interface - mapping(uint256 => mapping(address => mapping(address => uint256))) - public - override perTokenApprovals; - // Sub Token Name and Symbol, created by inheriting contracts - mapping(uint256 => string) internal _name; - mapping(uint256 => string) internal _symbol; - // The contract which deployed this one address public immutable factory; // The bytecode hash of the contract which forwards purely erc20 calls @@ -47,13 +32,15 @@ contract MultiToken is IMultiToken { "PermitForAll(address owner,address spender,bool _approved,uint256 nonce,uint256 deadline" ); - // A mapping to track the permitForAll signature nonces - mapping(address => uint256) public nonces; - /// @notice Runs the initial deployment code + /// @param _dataProvider The address of the data provider /// @param _linkerCodeHash The hash of the erc20 linker contract deploy code /// @param _factory The factory which is used to deploy the linking contracts - constructor(bytes32 _linkerCodeHash, address _factory) { + constructor( + address _dataProvider, + bytes32 _linkerCodeHash, + address _factory + ) DataProvider(_dataProvider) { // Set the immutables factory = _factory; linkerCodeHash = _linkerCodeHash; @@ -110,9 +97,7 @@ contract MultiToken is IMultiToken { /// by this contract. /// @param id The pool id to load the name of /// @return Returns the name of this token - function name( - uint256 id - ) external view virtual override returns (string memory) { + function name(uint256 id) external view virtual returns (string memory) { return _name[id]; } @@ -120,9 +105,7 @@ contract MultiToken is IMultiToken { /// by this contract. /// @param id The pool id to load the name of /// @return Returns the symbol of this token - function symbol( - uint256 id - ) external view virtual override returns (string memory) { + function symbol(uint256 id) external view virtual returns (string memory) { return _symbol[id]; } @@ -176,33 +159,36 @@ contract MultiToken is IMultiToken { if (caller != from) { // Or if the transaction sender can access all user assets, no need for // more validation - if (!isApprovedForAll[from][caller]) { + if (!_isApprovedForAll[from][caller]) { // Finally we load the per asset approval - uint256 approved = perTokenApprovals[tokenID][from][caller]; + uint256 approved = _perTokenApprovals[tokenID][from][caller]; // If it is not an infinite approval if (approved != type(uint256).max) { // Then we subtract the amount the caller wants to use // from how much they can use, reverting on underflow. // NOTE - This reverts without message for unapproved callers when // debugging that's the likely source of any mystery reverts - perTokenApprovals[tokenID][from][caller] -= amount; + _perTokenApprovals[tokenID][from][caller] -= amount; } } } // Reaching this point implies the transfer is authorized so we remove // from the source and add to the destination. - balanceOf[tokenID][from] -= amount; - balanceOf[tokenID][to] += amount; + _balanceOf[tokenID][from] -= amount; + _balanceOf[tokenID][to] += amount; emit TransferSingle(caller, from, to, tokenID, amount); } /// @notice Allows a user to approve an operator to use all of their assets /// @param operator The eth address which can access the caller's assets /// @param approved True to approve, false to remove approval - function setApprovalForAll(address operator, bool approved) public { + function setApprovalForAll( + address operator, + bool approved + ) external override { // set the appropriate state - isApprovedForAll[msg.sender][operator] = approved; + _isApprovedForAll[msg.sender][operator] = approved; // Emit an event to track approval emit ApprovalForAll(msg.sender, operator, approved); } @@ -247,7 +233,7 @@ contract MultiToken is IMultiToken { uint256 amount, address caller ) internal { - perTokenApprovals[tokenID][caller][operator] = amount; + _perTokenApprovals[tokenID][caller][operator] = amount; // Emit an event to track approval emit Approval(caller, operator, amount); } @@ -262,8 +248,8 @@ contract MultiToken is IMultiToken { address to, uint256 amount ) internal virtual { - balanceOf[tokenID][to] += amount; - totalSupply[tokenID] += amount; + _balanceOf[tokenID][to] += amount; + _totalSupply[tokenID] += amount; // Emit an event to track minting emit TransferSingle(msg.sender, address(0), to, tokenID, amount); } @@ -275,8 +261,8 @@ contract MultiToken is IMultiToken { /// @dev Must be used from inheriting contracts function _burn(uint256 tokenID, address from, uint256 amount) internal { // Decrement from the source and supply - balanceOf[tokenID][from] -= amount; - totalSupply[tokenID] -= amount; + _balanceOf[tokenID][from] -= amount; + _totalSupply[tokenID] -= amount; // Emit an event to track burning emit TransferSingle(msg.sender, from, address(0), tokenID, amount); } @@ -343,7 +329,7 @@ contract MultiToken is IMultiToken { owner, spender, _approved, - nonces[owner], + _nonces[owner], deadline ) ) @@ -355,9 +341,9 @@ contract MultiToken is IMultiToken { if (signer != owner) revert Errors.InvalidSignature(); // Increment the signature nonce - nonces[owner]++; + _nonces[owner]++; // set the state - isApprovedForAll[owner][spender] = _approved; + _isApprovedForAll[owner][spender] = _approved; // Emit an event to track approval emit ApprovalForAll(owner, spender, _approved); } diff --git a/contracts/src/MultiTokenDataProvider.sol b/contracts/src/MultiTokenDataProvider.sol new file mode 100644 index 00000000..004fea26 --- /dev/null +++ b/contracts/src/MultiTokenDataProvider.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { MultiTokenStorage } from "./MultiTokenStorage.sol"; +import { IMultiTokenRead } from "./interfaces/IMultiTokenRead.sol"; + +/// @author DELV +/// @title MultiTokenDataProvider +/// @notice The MultiToken data provider. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract MultiTokenDataProvider is MultiTokenStorage, IMultiTokenRead { + /// @notice Gets an account's balance of a sub-token. + /// @param tokenId The sub-token id. + /// @param account The account. + /// @return The balance. + function balanceOf( + uint256 tokenId, + address account + ) external view override returns (uint256) { + _revert(abi.encode(_balanceOf[tokenId][account])); + } + + /// @notice Gets the total supply of a sub-token. + /// @param tokenId The sub-token id. + /// @return The total supply. + function totalSupply( + uint256 tokenId + ) external view override returns (uint256) { + _revert(abi.encode(_totalSupply[tokenId])); + } + + /// @notice Gets the approval status of an operator for an account. + /// @param account The account. + /// @param operator The operator. + /// @return The approval status. + function isApprovedForAll( + address account, + address operator + ) external view override returns (bool) { + _revert(abi.encode(_isApprovedForAll[account][operator])); + } + + /// @notice Gets the approval status of an operator for an account. + /// @param tokenId The sub-token id. + /// @param account The account. + /// @param spender The spender. + /// @return The approval status. + function perTokenApprovals( + uint256 tokenId, + address account, + address spender + ) external view override returns (uint256) { + _revert(abi.encode(_perTokenApprovals[tokenId][account][spender])); + } + + /// @notice Gets the name of a sub-token. + /// @param tokenId The sub-token id. + /// @return The name. + function name( + uint256 tokenId + ) external view override returns (string memory) { + _revert(abi.encode(_name[tokenId])); + } + + /// @notice Gets the symbol of a sub-token. + /// @param tokenId The sub-token id. + /// @return The symbol. + function symbol( + uint256 tokenId + ) external view override returns (string memory) { + _revert(abi.encode(_symbol[tokenId])); + } + + /// @notice Gets the permitForAll signature nonce for an account. + /// @param account The account. + /// @return The signature nonce. + function nonces(address account) external view override returns (uint256) { + _revert(abi.encode(_nonces[account])); + } + + /// @dev Reverts with the provided bytes. This is useful in getters used + /// with the force-revert delegatecall pattern. + /// @param _bytes The bytes to revert with. + function _revert(bytes memory _bytes) internal pure { + assembly { + revert(add(_bytes, 32), mload(_bytes)) + } + } +} diff --git a/contracts/src/MultiTokenStorage.sol b/contracts/src/MultiTokenStorage.sol new file mode 100644 index 00000000..a7724871 --- /dev/null +++ b/contracts/src/MultiTokenStorage.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +/// @author DELV +/// @title MultiTokenStorage +/// @notice The MultiToken storage contract. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract MultiTokenStorage { + // Allows loading of each balance + mapping(uint256 => mapping(address => uint256)) internal _balanceOf; + + // Allows loading of each total supply + mapping(uint256 => uint256) internal _totalSupply; + + // Uniform approval for all tokens + mapping(address => mapping(address => bool)) internal _isApprovedForAll; + + // Additional optional per token approvals + // Note - non standard for erc1150 but we want to replicate erc20 interface + mapping(uint256 => mapping(address => mapping(address => uint256))) + internal _perTokenApprovals; + + // Sub Token Name and Symbol, created by inheriting contracts + mapping(uint256 => string) internal _name; + mapping(uint256 => string) internal _symbol; + + // A mapping to track the permitForAll signature nonces + mapping(address => uint256) internal _nonces; +} diff --git a/contracts/src/factory/AaveHyperdriveDeployer.sol b/contracts/src/factory/AaveHyperdriveDeployer.sol index 19e3d5a0..3c014418 100644 --- a/contracts/src/factory/AaveHyperdriveDeployer.sol +++ b/contracts/src/factory/AaveHyperdriveDeployer.sol @@ -14,8 +14,9 @@ interface IAToken { /// @author DELV /// @title AaveHyperdriveDeployer -/// @notice This is a minimal factory which contains only the logic to deploy hyperdrive -/// and is called by a more complex factory which also initializes hyperdrives. +/// @notice This is a minimal factory which contains only the logic to deploy +/// hyperdrive and is called by a more complex factory which +/// initializes the Hyperdrive instances and acts as a registry. /// @dev We use two contracts to avoid any code size limit issues with Hyperdrive. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any @@ -27,54 +28,34 @@ contract AaveHyperdriveDeployer is IHyperdriveDeployer { pool = _pool; } - /// @notice Deploys a copy of hyperdrive with the given params. NOTE - - /// This function varies from interface by requring aToken in the baseToken - /// field. + /// @notice Deploys a copy of hyperdrive with the given params. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _dataProvider The address of the data provider. /// @param _linkerCodeHash The hash of the ERC20 linker contract's /// constructor code. /// @param _linkerFactory The address of the factory which is used to deploy /// the ERC20 linker contracts. - /// @param _baseToken The a token of the aave pool - /// @param _checkpointsPerTerm The number of checkpoints that elapses before - /// bonds can be redeemed one-to-one for base. - /// @param _checkpointDuration The time in seconds between share price - /// checkpoints. Position duration must be a multiple of checkpoint - /// duration. - /// @param _timeStretch The time stretch of the pool. - /// @param _fees The fees to apply to trades. - /// @param _governance The address of the governance contract. + /// FIXME: We should ensure that the aToken address is a valid aToken. + /// @param _extraData This extra data contains the address of the aToken. function deploy( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, address _linkerFactory, - IERC20 _baseToken, - uint256, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, - IHyperdrive.Fees memory _fees, - address _governance, bytes32[] calldata _extraData ) external override returns (address) { // We force convert - bytes32 loaded = _extraData[0]; - IERC20 aToken; - assembly ("memory-safe") { - aToken := loaded - } + IERC20 aToken = IERC20(address(uint160(uint256(_extraData[0])))); // Need a hard convert cause no direct bytes32 -> address return ( address( new AaveHyperdrive( + _config, + _dataProvider, _linkerCodeHash, _linkerFactory, - _baseToken, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, aToken, - pool, - _fees, - _governance + pool ) ) ); diff --git a/contracts/src/factory/HyperdriveFactory.sol b/contracts/src/factory/HyperdriveFactory.sol index a3a1a8bd..ee9caffb 100644 --- a/contracts/src/factory/HyperdriveFactory.sol +++ b/contracts/src/factory/HyperdriveFactory.sol @@ -1,15 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveDeployer } from "../interfaces/IHyperdriveDeployer.sol"; import { Errors } from "../libraries/Errors.sol"; /// @author DELV /// @title HyperdriveFactory -/// @notice Deploys hyperdrive instances and initializes them. It also holds a registry of -/// all deployed hyperdrive instances. +/// @notice Deploys hyperdrive instances and initializes them. It also holds a +/// registry of all deployed hyperdrive instances. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. @@ -60,59 +59,54 @@ contract HyperdriveFactory { governance = newGovernance; } + // TODO: Consider adding the data provider deployments to the factory so + // that (1) we can ensure they are deployed properly and (2) we can + // keep track of them. + // /// @notice Deploys a copy of hyperdrive with the given params + /// @param _config The configuration of the Hyperdrive pool. + /// @param _dataProvider The address of the data provider. /// @param _linkerCodeHash The hash of the ERC20 linker contract's /// constructor code. /// @param _linkerFactory The address of the factory which is used to deploy /// the ERC20 linker contracts. - /// @param _baseToken The base token contract. - /// @param _initialSharePrice The initial share price. - /// @param _checkpointsPerTerm The number of checkpoints that elapses before - /// bonds can be redeemed one-to-one for base. - /// @param _checkpointDuration The time in seconds between share price - /// checkpoints. Position duration must be a multiple of checkpoint - /// duration. - /// @param _timeStretch The time stretch of the pool. - /// @param _fees The fees to apply to trades. /// @param _extraData The extra data is used by some factories /// @param _contribution Base token to call init with /// @param _apr The apr to call init with /// @return The hyperdrive address deployed - function deployAndImplement( + function deployAndInitialize( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, address _linkerFactory, - IERC20 _baseToken, - uint256 _initialSharePrice, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, - IHyperdrive.Fees memory _fees, bytes32[] memory _extraData, uint256 _contribution, uint256 _apr ) external returns (IHyperdrive) { // No invalid deployments if (_contribution == 0) revert Errors.InvalidContribution(); + // TODO: We should also overwrite the governance fee field. + // + // Overwrite the governance field of the config. + _config.governance = hyperdriveGovernance; // First we call the simplified factory IHyperdrive hyperdrive = IHyperdrive( hyperdriveDeployer.deploy( + _config, + _dataProvider, _linkerCodeHash, _linkerFactory, - _baseToken, - _initialSharePrice, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, - _fees, - hyperdriveGovernance, _extraData ) ); - // Then start the process to init - _baseToken.transferFrom(msg.sender, address(this), _contribution); - _baseToken.approve(address(hyperdrive), type(uint256).max); - // Initialize + // Initialize the Hyperdrive instance. + _config.baseToken.transferFrom( + msg.sender, + address(this), + _contribution + ); + _config.baseToken.approve(address(hyperdrive), type(uint256).max); hyperdrive.initialize(_contribution, _apr, msg.sender, true); // Mark as a version diff --git a/contracts/src/factory/MakerDsrHyperdriveDeployer.sol b/contracts/src/factory/MakerDsrHyperdriveDeployer.sol index 07ca009e..6b4adf77 100644 --- a/contracts/src/factory/MakerDsrHyperdriveDeployer.sol +++ b/contracts/src/factory/MakerDsrHyperdriveDeployer.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { MakerDsrHyperdrive } from "../instances/MakerDsrHyperdrive.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveDeployer } from "../interfaces/IHyperdriveDeployer.sol"; @@ -9,8 +8,9 @@ import { DsrManager } from "../interfaces/IMaker.sol"; /// @author DELV /// @title MakerDsrHyperdriveFactory -/// @notice This is a minimal factory which contains only the logic to deploy hyperdrive -/// and is called by a more complex factory which also initializes hyperdrives. +/// @notice This is a minimal factory which contains only the logic to deploy +/// hyperdrive and is called by a more complex factory which +/// initializes the Hyperdrive instances and acts as a registry. /// @dev We use two contracts to avoid any code size limit issues with Hyperdrive. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any @@ -22,41 +22,27 @@ contract MakerDsrHyperdriveDeployer is IHyperdriveDeployer { dsrManager = _dsrManager; } - /// @notice Deploys a copy of hyperdrive with the given params + /// @notice Deploys a copy of hyperdrive with the given params. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _dataProvider The address of the data provider. /// @param _linkerCodeHash The hash of the ERC20 linker contract's /// constructor code. /// @param _linkerFactory The address of the factory which is used to deploy /// the ERC20 linker contracts. - /// @param _checkpointsPerTerm The number of checkpoints that elapses before - /// bonds can be redeemed one-to-one for base. - /// @param _checkpointDuration The time in seconds between share price - /// checkpoints. Position duration must be a multiple of checkpoint - /// duration. - /// @param _timeStretch The time stretch of the pool. - /// @param _fees The fees to apply to trades. - /// @param _governance The address of the governance contract. function deploy( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, address _linkerFactory, - IERC20, - uint256, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, - IHyperdrive.Fees memory _fees, - address _governance, bytes32[] calldata ) external override returns (address) { return ( address( new MakerDsrHyperdrive( + _config, + _dataProvider, _linkerCodeHash, _linkerFactory, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, - _fees, - _governance, dsrManager ) ) diff --git a/contracts/src/instances/AaveHyperdrive.sol b/contracts/src/instances/AaveHyperdrive.sol index b149d20e..e5a31edc 100644 --- a/contracts/src/instances/AaveHyperdrive.sol +++ b/contracts/src/instances/AaveHyperdrive.sol @@ -14,50 +14,34 @@ contract AaveHyperdrive is Hyperdrive { // The aave deployment details, the a token for this asset and the aave pool IERC20 public immutable aToken; IPool public immutable pool; - // The shares created by this pool, starts at 1 to one with deposits and increases - uint256 public totalShares; + // The shares created by this pool, starts at one to one with deposits and increases + uint256 internal totalShares; /// @notice Initializes a Hyperdrive pool. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _dataProvider The address of the data provider. /// @param _linkerCodeHash The hash of the ERC20 linker contract's /// constructor code. /// @param _linkerFactory The factory which is used to deploy the ERC20 /// linker contracts. - /// @param _baseToken The base token contract. - /// @param _checkpointsPerTerm The number of checkpoints that elapses before - /// bonds can be redeemed one-to-one for base. - /// @param _checkpointDuration The time in seconds between share price - /// checkpoints. Position duration must be a multiple of checkpoint - /// duration. - /// @param _timeStretch The time stretch of the pool. - /// @param _fees The fees to apply to trades. - /// @param _governance The governance address. + /// @param _aToken The assets aToken. + /// @param _pool The aave pool. constructor( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, address _linkerFactory, - IERC20 _baseToken, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, IERC20 _aToken, - IPool _pool, - IHyperdrive.Fees memory _fees, - address _governance - ) - Hyperdrive( - _linkerCodeHash, - _linkerFactory, - _baseToken, - FixedPointMath.ONE_18, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, - _fees, - _governance - ) - { + IPool _pool + ) Hyperdrive(_config, _dataProvider, _linkerCodeHash, _linkerFactory) { + // Ensure that the Hyperdrive pool was configured properly. + if (_config.initialSharePrice != FixedPointMath.ONE_18) { + revert Errors.InvalidInitialSharePrice(); + } + aToken = _aToken; pool = _pool; - _baseToken.approve(address(pool), type(uint256).max); + _config.baseToken.approve(address(pool), type(uint256).max); } ///@notice Transfers amount of 'token' from the user and commits it to the yield source. diff --git a/contracts/src/instances/AaveHyperdriveDataProvider.sol b/contracts/src/instances/AaveHyperdriveDataProvider.sol new file mode 100644 index 00000000..724d3715 --- /dev/null +++ b/contracts/src/instances/AaveHyperdriveDataProvider.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { HyperdriveDataProvider } from "../HyperdriveDataProvider.sol"; +import { FixedPointMath } from "../libraries/FixedPointMath.sol"; +import { Errors } from "../libraries/Errors.sol"; +import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; + +/// @author DELV +/// @title AaveHyperdriveDataProvider +/// @notice The data provider for AaveHyperdrive instances. +/// @custom:disclaimer The language used in this code is for coding convenience +/// only, and is not intended to, and does not, have any +/// particular legal or regulatory significance. +contract AaveHyperdriveDataProvider is HyperdriveDataProvider { + using FixedPointMath for uint256; + + // The aave deployment details, the aave pool + IERC20 internal immutable aToken; + // The shares created by this pool, starts at one to one with deposits and increases + uint256 internal totalShares; + + /// @notice Initializes the data provider. + /// @param _aToken The assets aToken. + constructor(IERC20 _aToken) { + aToken = _aToken; + } + + ///@notice Loads the share price from the yield source. + ///@return sharePrice The current share price. + function _pricePerShare() + internal + view + override + returns (uint256 sharePrice) + { + uint256 assets = aToken.balanceOf(address(this)); + sharePrice = totalShares != 0 ? assets.divDown(totalShares) : 0; + return sharePrice; + } +} diff --git a/contracts/src/instances/MakerDsrHyperdrive.sol b/contracts/src/instances/MakerDsrHyperdrive.sol index 3de9d2ba..b6eda88e 100644 --- a/contracts/src/instances/MakerDsrHyperdrive.sol +++ b/contracts/src/instances/MakerDsrHyperdrive.sol @@ -13,50 +13,40 @@ contract MakerDsrHyperdrive is Hyperdrive { // @notice The shares created by this pool, starts at 1 to one with // deposits and increases - uint256 public totalShares; + uint256 internal totalShares; + // @notice The pool management contract DsrManager public immutable dsrManager; + // @notice The core Maker accounting module for the Dai Savings Rate Pot public immutable pot; + // @notice Maker constant uint256 public constant RAY = 1e27; /// @notice Initializes a Hyperdrive pool. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _dataProvider The address of the data provider. /// @param _linkerCodeHash The hash of the ERC20 linker contract's /// constructor code. /// @param _linkerFactory The factory which is used to deploy the ERC20 /// linker contracts. - /// @param _checkpointsPerTerm The number of checkpoints that elapses before - /// bonds can be redeemed one-to-one for base. - /// @param _checkpointDuration The time in seconds between share price - /// checkpoints. Position duration must be a multiple of checkpoint - /// duration. - /// @param _timeStretch The time stretch of the pool. - /// @param _fees The fees to apply to trades. - /// @param _governance The governance address. /// @param _dsrManager The "dai savings rate" manager contract constructor( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, address _linkerFactory, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, - IHyperdrive.Fees memory _fees, - address _governance, DsrManager _dsrManager - ) - Hyperdrive( - _linkerCodeHash, - _linkerFactory, - IERC20(address(_dsrManager.dai())), // baseToken will always be DAI - FixedPointMath.ONE_18, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, - _fees, - _governance - ) - { + ) Hyperdrive(_config, _dataProvider, _linkerCodeHash, _linkerFactory) { + // Ensure that the Hyperdrive pool was configured properly. + if (address(_config.baseToken) != address(_dsrManager.dai())) { + revert Errors.InvalidBaseToken(); + } + if (_config.initialSharePrice != FixedPointMath.ONE_18) { + revert Errors.InvalidInitialSharePrice(); + } + dsrManager = _dsrManager; pot = Pot(dsrManager.pot()); baseToken.approve(address(dsrManager), type(uint256).max); diff --git a/contracts/src/instances/MakerDsrHyperdriveDataProvider.sol b/contracts/src/instances/MakerDsrHyperdriveDataProvider.sol new file mode 100644 index 00000000..e1ce5699 --- /dev/null +++ b/contracts/src/instances/MakerDsrHyperdriveDataProvider.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { HyperdriveDataProvider } from "../HyperdriveDataProvider.sol"; +import { FixedPointMath } from "../libraries/FixedPointMath.sol"; +import { Errors } from "../libraries/Errors.sol"; +import { Pot, DsrManager } from "../interfaces/IMaker.sol"; +import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; + +contract MakerDsrHyperdriveDataProvider is HyperdriveDataProvider { + using FixedPointMath for uint256; + + // @notice The shares created by this pool, starts at 1 to one with + // deposits and increases + uint256 internal _totalShares; + + // @notice The pool management contract + DsrManager internal immutable dsrManager; + + // @notice The core Maker accounting module for the Dai Savings Rate + Pot internal immutable pot; + + // @notice Maker constant + uint256 internal constant RAY = 1e27; + + /// @notice Initializes the data provider. + /// @param _dsrManager The "dai savings rate" manager contract + constructor(DsrManager _dsrManager) { + dsrManager = _dsrManager; + pot = Pot(dsrManager.pot()); + } + + /// Getters /// + + /// @notice Gets the total number of shares in existence. + /// @return The total number of shares. + function totalShares() external view returns (uint256) { + _revert(abi.encode(_totalShares)); + } + + /// Yield Source /// + + /// @notice Loads the share price from the yield source. + /// @return sharePrice The current share price. + function _pricePerShare() + internal + view + override + returns (uint256 sharePrice) + { + // The normalized DAI amount owned by this contract + uint256 pie = dsrManager.pieOf(address(this)); + // Load the balance of this contract + uint256 totalBase = pie.mulDivDown(chi(), RAY); + // The share price is assets divided by shares + return (totalBase.divDown(_totalShares)); + } + + /// TODO Is this actually worthwhile versus using drip? + /// @notice Gets the current up to date value of the rate accumulator + /// @dev The Maker protocol uses a tick based accounting mechanic to + /// accumulate interest in a single variable called the rate + /// accumulator or more commonly "chi". + /// This is re-calibrated on any interaction with the maker protocol by + /// a function pot.drip(). The rationale for not using this is that it + /// is not a view function and so the purpose of this function is to + /// get the real chi value without interacting with the core maker + /// system and expensively mutating state. + /// return chi The rate accumulator + function chi() public view returns (uint256) { + // timestamp when drip was last called + uint256 rho = pot.rho(); + // Rate accumulator as of last drip + uint256 _chi = pot.chi(); + // Annualized interest rate + uint256 dsr = pot.dsr(); + // Calibrates the rate accumulator to current time + return + (block.timestamp > rho) + ? _rpow(dsr, block.timestamp - rho, RAY).mulDivDown(_chi, RAY) + : _chi; + } + + /// @notice Taken from https://github.com/makerdao/dss/blob/master/src/pot.sol#L85 + /// @return z + function _rpow(uint x, uint n, uint base) internal pure returns (uint z) { + assembly ("memory-safe") { + switch x + case 0 { + switch n + case 0 { + z := base + } + default { + z := 0 + } + } + default { + switch mod(n, 2) + case 0 { + z := base + } + default { + z := x + } + let half := div(base, 2) // for rounding. + for { + n := div(n, 2) + } n { + n := div(n, 2) + } { + let xx := mul(x, x) + if iszero(eq(div(xx, x), x)) { + revert(0, 0) + } + let xxRound := add(xx, half) + if lt(xxRound, xx) { + revert(0, 0) + } + x := div(xxRound, base) + if mod(n, 2) { + let zx := mul(z, x) + if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { + revert(0, 0) + } + let zxRound := add(zx, half) + if lt(zxRound, zx) { + revert(0, 0) + } + z := div(zxRound, base) + } + } + } + } + } +} diff --git a/contracts/src/interfaces/IHyperdrive.sol b/contracts/src/interfaces/IHyperdrive.sol index eebcfbbd..8a80ea3f 100644 --- a/contracts/src/interfaces/IHyperdrive.sol +++ b/contracts/src/interfaces/IHyperdrive.sol @@ -2,9 +2,11 @@ pragma solidity ^0.8.18; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IHyperdriveRead } from "./IHyperdriveRead.sol"; +import { IHyperdriveWrite } from "./IHyperdriveWrite.sol"; import { IMultiToken } from "./IMultiToken.sol"; -interface IHyperdrive is IMultiToken { +interface IHyperdrive is IHyperdriveRead, IHyperdriveWrite, IMultiToken { struct MarketState { /// @dev The pool's share reserves. uint128 shareReserves; @@ -66,20 +68,20 @@ interface IHyperdrive is IMultiToken { } struct PoolConfig { - /// @dev The initial share price of the base asset. + /// @dev The address of the base token. + IERC20 baseToken; + /// @dev The initial share price. uint256 initialSharePrice; - /// @dev The duration of a long or short trade. + /// @dev The duration of a position prior to maturity. uint256 positionDuration; /// @dev The duration of a checkpoint. uint256 checkpointDuration; /// @dev A parameter which decreases slippage around a target rate. uint256 timeStretch; - /// @dev The LP fee applied to the flat portion of a trade. - uint256 flatFee; - /// @dev The LP fee applied to the curve portion of a trade. - uint256 curveFee; - /// @dev The percentage fee applied to the LP fees. - uint256 governanceFee; + /// @dev The address of the governance contract. + address governance; + /// @dev The fees applied to trades. + IHyperdrive.Fees fees; } struct PoolInfo { @@ -106,81 +108,4 @@ interface IHyperdrive is IMultiToken { /// @dev The proceeds recovered by the withdrawal pool. uint256 withdrawalSharesProceeds; } - - function baseToken() external view returns (address); - - function checkpoints( - uint256 _checkpoint - ) external view returns (Checkpoint memory); - - function withdrawPool() external view returns (WithdrawPool memory); - - function getPoolConfig() external view returns (PoolConfig memory); - - function getPoolInfo() external view returns (PoolInfo memory); - - function checkpoint(uint256 _checkpointTime) external; - - function setPauser(address who, bool status) external; - - function pause(bool status) external; - - function initialize( - uint256 _contribution, - uint256 _apr, - address _destination, - bool _asUnderlying - ) external; - - function addLiquidity( - uint256 _contribution, - uint256 _minApr, - uint256 _maxApr, - address _destination, - bool _asUnderlying - ) external returns (uint256); - - function removeLiquidity( - uint256 _shares, - uint256 _minOutput, - address _destination, - bool _asUnderlying - ) external returns (uint256, uint256); - - function redeemWithdrawalShares( - uint256 _shares, - uint256 _minOutput, - address _destination, - bool _asUnderlying - ) external returns (uint256 _proceeds); - - function openLong( - uint256 _baseAmount, - uint256 _minOutput, - address _destination, - bool _asUnderlying - ) external returns (uint256); - - function closeLong( - uint256 _maturityTime, - uint256 _bondAmount, - uint256 _minOutput, - address _destination, - bool _asUnderlying - ) external returns (uint256); - - function openShort( - uint256 _bondAmount, - uint256 _maxDeposit, - address _destination, - bool _asUnderlying - ) external returns (uint256); - - function closeShort( - uint256 _maturityTime, - uint256 _bondAmount, - uint256 _minOutput, - address _destination, - bool _asUnderlying - ) external returns (uint256); } diff --git a/contracts/src/interfaces/IHyperdriveDeployer.sol b/contracts/src/interfaces/IHyperdriveDeployer.sol index b834f602..e671e64a 100644 --- a/contracts/src/interfaces/IHyperdriveDeployer.sol +++ b/contracts/src/interfaces/IHyperdriveDeployer.sol @@ -6,15 +6,10 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IHyperdriveDeployer { function deploy( + IHyperdrive.PoolConfig memory _config, + address _dataProvider, bytes32 _linkerCodeHash, address _linkerFactory, - IERC20 _baseToken, - uint256 _initialSharePrice, - uint256 _checkpointsPerTerm, - uint256 _checkpointDuration, - uint256 _timeStretch, - IHyperdrive.Fees memory _fees, - address _governance, bytes32[] memory _extraData ) external returns (address); } diff --git a/contracts/src/interfaces/IHyperdriveRead.sol b/contracts/src/interfaces/IHyperdriveRead.sol new file mode 100644 index 00000000..de1880bf --- /dev/null +++ b/contracts/src/interfaces/IHyperdriveRead.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { IHyperdrive } from "./IHyperdrive.sol"; +import { IMultiTokenRead } from "./IMultiTokenRead.sol"; + +interface IHyperdriveRead is IMultiTokenRead { + function baseToken() external view returns (address); + + function getCheckpoint( + uint256 _checkpointId + ) external view returns (IHyperdrive.Checkpoint memory); + + function withdrawPool() + external + view + returns (IHyperdrive.WithdrawPool memory); + + function getPoolConfig() + external + view + returns (IHyperdrive.PoolConfig memory); + + function getPoolInfo() external view returns (IHyperdrive.PoolInfo memory); +} diff --git a/contracts/src/interfaces/IHyperdriveWrite.sol b/contracts/src/interfaces/IHyperdriveWrite.sol new file mode 100644 index 00000000..958412f6 --- /dev/null +++ b/contracts/src/interfaces/IHyperdriveWrite.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import { IMultiTokenWrite } from "./IMultiTokenWrite.sol"; + +interface IHyperdriveWrite is IMultiTokenWrite { + function checkpoint(uint256 _checkpointTime) external; + + function setPauser(address who, bool status) external; + + function pause(bool status) external; + + function initialize( + uint256 _contribution, + uint256 _apr, + address _destination, + bool _asUnderlying + ) external; + + function addLiquidity( + uint256 _contribution, + uint256 _minApr, + uint256 _maxApr, + address _destination, + bool _asUnderlying + ) external returns (uint256); + + function removeLiquidity( + uint256 _shares, + uint256 _minOutput, + address _destination, + bool _asUnderlying + ) external returns (uint256, uint256); + + function redeemWithdrawalShares( + uint256 _shares, + uint256 _minOutput, + address _destination, + bool _asUnderlying + ) external returns (uint256 _proceeds); + + function openLong( + uint256 _baseAmount, + uint256 _minOutput, + address _destination, + bool _asUnderlying + ) external returns (uint256); + + function closeLong( + uint256 _maturityTime, + uint256 _bondAmount, + uint256 _minOutput, + address _destination, + bool _asUnderlying + ) external returns (uint256); + + function openShort( + uint256 _bondAmount, + uint256 _maxDeposit, + address _destination, + bool _asUnderlying + ) external returns (uint256); + + function closeShort( + uint256 _maturityTime, + uint256 _bondAmount, + uint256 _minOutput, + address _destination, + bool _asUnderlying + ) external returns (uint256); +} diff --git a/contracts/src/interfaces/IMultiToken.sol b/contracts/src/interfaces/IMultiToken.sol index e05dcafb..712914e5 100644 --- a/contracts/src/interfaces/IMultiToken.sol +++ b/contracts/src/interfaces/IMultiToken.sol @@ -1,74 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -interface IMultiToken { - event TransferSingle( - address indexed operator, - address indexed from, - address indexed to, - uint256 id, - uint256 value - ); +import { IMultiTokenRead } from "./IMultiTokenRead.sol"; +import { IMultiTokenWrite } from "./IMultiTokenWrite.sol"; - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - - event ApprovalForAll( - address indexed account, - address indexed operator, - bool approved - ); - - function name(uint256 id) external view returns (string memory); - - function symbol(uint256 id) external view returns (string memory); - - function totalSupply(uint256 id) external view returns (uint256); - - function isApprovedForAll( - address owner, - address spender - ) external view returns (bool); - - function perTokenApprovals( - uint256 tokenId, - address owner, - address spender - ) external view returns (uint256); - - function balanceOf( - uint256 tokenId, - address owner - ) external view returns (uint256); - - function transferFrom( - uint256 tokenID, - address from, - address to, - uint256 amount - ) external; - - function transferFromBridge( - uint256 tokenID, - address from, - address to, - uint256 amount, - address caller - ) external; - - function setApproval( - uint256 tokenID, - address operator, - uint256 amount - ) external; - - function setApprovalBridge( - uint256 tokenID, - address operator, - uint256 amount, - address caller - ) external; -} +interface IMultiToken is IMultiTokenRead, IMultiTokenWrite {} diff --git a/contracts/src/interfaces/IMultiTokenRead.sol b/contracts/src/interfaces/IMultiTokenRead.sol new file mode 100644 index 00000000..a612fe3e --- /dev/null +++ b/contracts/src/interfaces/IMultiTokenRead.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +interface IMultiTokenRead { + function name(uint256 id) external view returns (string memory); + + function symbol(uint256 id) external view returns (string memory); + + function totalSupply(uint256 id) external view returns (uint256); + + function isApprovedForAll( + address owner, + address spender + ) external view returns (bool); + + function perTokenApprovals( + uint256 tokenId, + address owner, + address spender + ) external view returns (uint256); + + function balanceOf( + uint256 tokenId, + address owner + ) external view returns (uint256); + + function nonces(address owner) external view returns (uint256); +} diff --git a/contracts/src/interfaces/IMultiTokenWrite.sol b/contracts/src/interfaces/IMultiTokenWrite.sol new file mode 100644 index 00000000..23b6bdb7 --- /dev/null +++ b/contracts/src/interfaces/IMultiTokenWrite.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +interface IMultiTokenWrite { + event TransferSingle( + address indexed operator, + address indexed from, + address indexed to, + uint256 id, + uint256 value + ); + + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + event ApprovalForAll( + address indexed account, + address indexed operator, + bool approved + ); + + function transferFrom( + uint256 tokenID, + address from, + address to, + uint256 amount + ) external; + + function transferFromBridge( + uint256 tokenID, + address from, + address to, + uint256 amount, + address caller + ) external; + + function setApproval( + uint256 tokenID, + address operator, + uint256 amount + ) external; + + function setApprovalBridge( + uint256 tokenID, + address operator, + uint256 amount, + address caller + ) external; + + function setApprovalForAll(address operator, bool approved) external; +} diff --git a/contracts/src/libraries/Errors.sol b/contracts/src/libraries/Errors.sol index 15588d98..77d6293f 100644 --- a/contracts/src/libraries/Errors.sol +++ b/contracts/src/libraries/Errors.sol @@ -13,10 +13,12 @@ library Errors { /// ################## error BaseBufferExceedsShareReserves(); error InvalidApr(); + error InvalidBaseToken(); error InvalidCheckpointTime(); error InvalidCheckpointDuration(); - error InvalidCheckpointsPerTerm(); + error InvalidInitialSharePrice(); error InvalidMaturityTime(); + error InvalidPositionDuration(); error NegativeInterest(); error OutputLimit(); error Paused(); @@ -27,6 +29,17 @@ library Errors { error ZeroAmount(); error ZeroLpTotalSupply(); + /// #################### + /// ### DataProvider ### + /// #################### + error UnexpectedSuccess(); + + /// ############### + /// ### Factory ### + /// ############### + error Unauthorized(); + error InvalidContribution(); + /// ###################### /// ### ERC20Forwarder ### /// ###################### @@ -36,20 +49,6 @@ library Errors { error InvalidERC20Bridge(); error RestrictedZeroAddress(); - /// ###################### - /// ### FixedPointMath ### - /// ###################### - error FixedPointMath_AddOverflow(); - error FixedPointMath_SubOverflow(); - error FixedPointMath_InvalidExponent(); - error FixedPointMath_NegativeOrZeroInput(); - error FixedPointMath_NegativeInput(); - - /// ############### - /// ### AssetId ### - /// ############### - error InvalidTimestamp(); - /// ##################### /// ### BondWrapper ### /// ##################### @@ -58,9 +57,17 @@ library Errors { error BondNotMatured(); error InsufficientPrice(); - /// ##################### - /// ### Factory ### - /// ##################### - error Unauthorized(); - error InvalidContribution(); + /// ############### + /// ### AssetId ### + /// ############### + error InvalidTimestamp(); + + /// ###################### + /// ### FixedPointMath ### + /// ###################### + error FixedPointMath_AddOverflow(); + error FixedPointMath_SubOverflow(); + error FixedPointMath_InvalidExponent(); + error FixedPointMath_NegativeOrZeroInput(); + error FixedPointMath_NegativeInput(); } diff --git a/contracts/test/ERC20Mintable.sol b/contracts/test/ERC20Mintable.sol index 79bca8a6..6df77b0e 100644 --- a/contracts/test/ERC20Mintable.sol +++ b/contracts/test/ERC20Mintable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.15; +pragma solidity ^0.8.18; import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/contracts/test/MockHyperdriveTestnet.sol b/contracts/test/MockHyperdriveTestnet.sol index 10c2b043..c6f1be2d 100644 --- a/contracts/test/MockHyperdriveTestnet.sol +++ b/contracts/test/MockHyperdriveTestnet.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.15; +pragma solidity ^0.8.18; import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; import { Hyperdrive } from "../src/Hyperdrive.sol"; +import { HyperdriveDataProvider } from "../src/HyperdriveDataProvider.sol"; import { FixedPointMath } from "../src/libraries/FixedPointMath.sol"; import { Errors } from "../src/libraries/Errors.sol"; import { ERC20Mintable } from "./ERC20Mintable.sol"; @@ -16,25 +17,29 @@ contract MockHyperdriveTestnet is Hyperdrive { uint256 internal totalShares; constructor( - ERC20Mintable baseToken, + address _dataProvider, + ERC20Mintable _baseToken, uint256 _initialRate, uint256 _initialSharePrice, - uint256 _checkpointsPerTerm, + uint256 _positionDuration, uint256 _checkpointDuration, uint256 _timeStretch, IHyperdrive.Fees memory _fees, address _governance ) Hyperdrive( + IHyperdrive.PoolConfig({ + baseToken: _baseToken, + initialSharePrice: _initialSharePrice, + positionDuration: _positionDuration, + checkpointDuration: _checkpointDuration, + timeStretch: _timeStretch, + governance: _governance, + fees: _fees + }), + _dataProvider, bytes32(0), - address(0), - baseToken, - _initialSharePrice, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, - _fees, - _governance + address(0) ) { rate = _initialRate; @@ -129,3 +134,36 @@ contract MockHyperdriveTestnet is Hyperdrive { lastUpdated = block.timestamp; } } + +contract MockHyperdriveDataProviderTestnet is HyperdriveDataProvider { + using FixedPointMath for uint256; + + ERC20Mintable internal immutable baseToken; + + uint256 internal rate; + uint256 internal lastUpdated; + uint256 internal totalShares; + + constructor(ERC20Mintable _baseToken) { + baseToken = _baseToken; + } + + /// Overrides /// + + function _pricePerShare() internal view override returns (uint256) { + uint256 underlying = baseToken.balanceOf(address(this)) + + getAccruedInterest(); + return underlying.divDown(totalShares); + } + + /// Helpers /// + + function getAccruedInterest() internal view returns (uint256) { + // base_balance = base_balance * (1 + r * t) + uint256 timeElapsed = (block.timestamp - lastUpdated).divDown(365 days); + return + baseToken.balanceOf(address(this)).mulDown( + rate.mulDown(timeElapsed) + ); + } +} diff --git a/contracts/test/MockMakerDsrHyperdrive.sol b/contracts/test/MockMakerDsrHyperdrive.sol index 39f81c08..6e9099e0 100644 --- a/contracts/test/MockMakerDsrHyperdrive.sol +++ b/contracts/test/MockMakerDsrHyperdrive.sol @@ -7,20 +7,45 @@ import { FixedPointMath } from "../src/libraries/FixedPointMath.sol"; import { ForwarderFactory } from "../src/ForwarderFactory.sol"; import { IHyperdrive } from "../src/interfaces/IHyperdrive.sol"; +interface IMockMakerDsrHyperdrive is IHyperdrive { + function totalShares() external view returns (uint256); + + function deposit( + uint256 amount, + bool asUnderlying + ) external returns (uint256, uint256); + + function withdraw( + uint256 shares, + address destination, + bool asUnderlying + ) external returns (uint256, uint256); + + function pricePerShare() external view returns (uint256); +} + contract MockMakerDsrHyperdrive is MakerDsrHyperdrive { using FixedPointMath for uint256; constructor( + address _dataProvider, DsrManager _dsrManager ) MakerDsrHyperdrive( + IHyperdrive.PoolConfig({ + baseToken: IERC20(address(_dsrManager.dai())), + initialSharePrice: FixedPointMath.ONE_18, + positionDuration: 365 days, + checkpointDuration: 1 days, + timeStretch: FixedPointMath.ONE_18.divDown( + 22.186877016851916266e18 + ), + governance: address(0), + fees: IHyperdrive.Fees({ curve: 0, flat: 0, governance: 0 }) + }), + _dataProvider, bytes32(0), address(0), - 365, - 1 days, - FixedPointMath.ONE_18.divDown(22.186877016851916266e18), - IHyperdrive.Fees({ curve: 0, flat: 0, governance: 0 }), - address(0), _dsrManager ) {} diff --git a/contracts/test/MockMultiToken.sol b/contracts/test/MockMultiToken.sol index a3974658..aa218919 100644 --- a/contracts/test/MockMultiToken.sol +++ b/contracts/test/MockMultiToken.sol @@ -1,14 +1,38 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.15; +pragma solidity ^0.8.18; import { MultiToken } from "../src/MultiToken.sol"; +import { IMultiToken } from "../src/interfaces/IMultiToken.sol"; import { ForwarderFactory } from "../src/ForwarderFactory.sol"; +interface IMockMultiToken is IMultiToken { + function __setNameAndSymbol( + uint256 tokenId, + string memory __name, + string memory __symbol + ) external; + + function __setBalanceOf( + uint256 _tokenId, + address _who, + uint256 _amount + ) external; + + function __external_transferFrom( + uint256 tokenID, + address from, + address to, + uint256 amount, + address caller + ) external; +} + contract MockMultiToken is MultiToken { constructor( + address _dataProvider, bytes32 _linkerCodeHash, address _factory - ) MultiToken(_linkerCodeHash, _factory) {} + ) MultiToken(_dataProvider, _linkerCodeHash, _factory) {} function __setNameAndSymbol( uint256 tokenId, @@ -24,7 +48,7 @@ contract MockMultiToken is MultiToken { address _who, uint256 _amount ) external { - balanceOf[_tokenId][_who] = _amount; + _balanceOf[_tokenId][_who] = _amount; } function __external_transferFrom( diff --git a/foundry.toml b/foundry.toml index 8ce064a2..02c8285b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -33,4 +33,5 @@ gas_reports = ["*"] runs = 1000 [rpc_endpoints] +mainnet = "${MAINNET_RPC_URL}" goerli = "${GOERLI_RPC_URL}" diff --git a/migrations/MockHyperdrive.s.sol b/migrations/MockHyperdrive.s.sol index ce4a7694..bef5751e 100644 --- a/migrations/MockHyperdrive.s.sol +++ b/migrations/MockHyperdrive.s.sol @@ -25,7 +25,7 @@ contract MockHyperdriveScript is Script { BASE, 5e18, FixedPointMath.ONE_18, - 365, + 365 days, 1 days, FixedPointMath.ONE_18.divDown(22.186877016851916266e18), IHyperdrive.Fees({ diff --git a/test/combinatorial/BondWrapper.mint.t.sol b/test/combinatorial/BondWrapper.mint.t.sol index afdda85c..cdbb6b99 100644 --- a/test/combinatorial/BondWrapper.mint.t.sol +++ b/test/combinatorial/BondWrapper.mint.t.sol @@ -4,25 +4,34 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; import "forge-std/console2.sol"; -import { CombinatorialTest } from "test/utils/CombinatorialTest.sol"; -import { MockMultiToken } from "contracts/test/MockMultiToken.sol"; -import { MockBondWrapper } from "contracts/test/MockBondWrapper.sol"; -import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; -import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { MultiTokenDataProvider } from "contracts/src/MultiTokenDataProvider.sol"; +import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { Errors } from "contracts/src/libraries/Errors.sol"; +import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; +import { MockBondWrapper } from "contracts/test/MockBondWrapper.sol"; +import { MockMultiToken, IMockMultiToken } from "contracts/test/MockMultiToken.sol"; +import { CombinatorialTest } from "test/utils/CombinatorialTest.sol"; contract BondWrapper_mint is CombinatorialTest { - MockMultiToken multiToken; + IMockMultiToken multiToken; MockBondWrapper bondWrapper; ERC20Mintable baseToken; function setUp() public override { super.setUp(); vm.startPrank(deployer); - - multiToken = new MockMultiToken(bytes32(0), address(forwarderFactory)); + address dataProvider = address(new MultiTokenDataProvider()); + multiToken = IMockMultiToken( + address( + new MockMultiToken( + dataProvider, + bytes32(0), + address(forwarderFactory) + ) + ) + ); baseToken = new ERC20Mintable(); } diff --git a/test/combinatorial/MultiToken._transferFrom.t.sol b/test/combinatorial/MultiToken._transferFrom.t.sol index 01e11f15..1ff29ad1 100644 --- a/test/combinatorial/MultiToken._transferFrom.t.sol +++ b/test/combinatorial/MultiToken._transferFrom.t.sol @@ -4,18 +4,28 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; import "forge-std/console2.sol"; -import { CombinatorialTest } from "test/utils/CombinatorialTest.sol"; -import { MockMultiToken } from "contracts/test/MockMultiToken.sol"; import { ForwarderFactory } from "contracts/src/ForwarderFactory.sol"; +import { MultiTokenDataProvider } from "contracts/src/MultiTokenDataProvider.sol"; +import { MockMultiToken, IMockMultiToken } from "contracts/test/MockMultiToken.sol"; +import { CombinatorialTest } from "test/utils/CombinatorialTest.sol"; contract MultiToken__transferFrom is CombinatorialTest { - MockMultiToken multiToken; + IMockMultiToken multiToken; function setUp() public override { // MultiToken deployment super.setUp(); vm.startPrank(deployer); - multiToken = new MockMultiToken(bytes32(0), address(forwarderFactory)); + address dataProvider = address(new MultiTokenDataProvider()); + multiToken = IMockMultiToken( + address( + new MockMultiToken( + dataProvider, + bytes32(0), + address(forwarderFactory) + ) + ) + ); vm.stopPrank(); } diff --git a/test/integrations/AaveFixedBorrow.t.sol b/test/integrations/AaveFixedBorrow.t.sol index 73c958c3..ef5159e8 100644 --- a/test/integrations/AaveFixedBorrow.t.sol +++ b/test/integrations/AaveFixedBorrow.t.sol @@ -1,17 +1,24 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import { BaseTest } from "test/utils/BaseTest.sol"; -import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; -import { AaveFixedBorrowAction, IHyperdrive, IPool } from "contracts/src/actions/AaveFixedBorrow.sol"; -import { AssetId } from "contracts/src/libraries/AssetId.sol"; -import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; -import { DsrManager } from "contracts/src/interfaces/IMaker.sol"; -import { IERC20Mint } from "contracts/src/interfaces/IERC20Mint.sol"; -import { IERC20Permit } from "contracts/src/interfaces/IERC20Permit.sol"; import { ICreditDelegationToken } from "@aave/interfaces/ICreditDelegationToken.sol"; import { DataTypes } from "@aave/protocol/libraries/types/DataTypes.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { AaveFixedBorrowAction, IHyperdrive, IPool } from "contracts/src/actions/AaveFixedBorrow.sol"; +import { MakerDsrHyperdrive } from "contracts/src/instances/MakerDsrHyperdrive.sol"; +import { MakerDsrHyperdriveDataProvider } from "contracts/src/instances/MakerDsrHyperdriveDataProvider.sol"; +import { IERC20Mint } from "contracts/src/interfaces/IERC20Mint.sol"; +import { IERC20Permit } from "contracts/src/interfaces/IERC20Permit.sol"; +import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; +import { DsrManager } from "contracts/src/interfaces/IMaker.sol"; +import { AssetId } from "contracts/src/libraries/AssetId.sol"; +import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; +import { BaseTest } from "test/utils/BaseTest.sol"; +import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; + +interface Faucet { + function mint(address token, address to, uint256 amount) external; +} contract AaveFixedBorrowTest is BaseTest { using FixedPointMath for uint256; @@ -21,25 +28,68 @@ contract AaveFixedBorrowTest is BaseTest { IPool pool; IERC20Permit dai; IERC20Permit wsteth; + DsrManager dsrManager; + Faucet faucet; - // Token addresses taken from: - // https://github.com/phoenixlabsresearch/sparklend/blob/master/script/output/5/spark-latest.json function setUp() public override __goerli_fork(8749473) { super.setUp(); + // These addresses are taken from: + // https://github.com/phoenixlabsresearch/sparklend/blob/master/script/output/5/spark-latest.json wsteth = IERC20Permit( address(0x6E4F1e8d4c5E5E6e2781FD814EE0744cc16Eb352) ); dai = IERC20Permit(address(0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844)); - pool = IPool(address(0x26ca51Af4506DE7a6f0785D20CD776081a05fF6d)); + dsrManager = DsrManager( + address(0xF7F0de3744C82825D77EdA8ce78f07A916fB6bE7) + ); + faucet = Faucet(address(0xe2bE5BfdDbA49A86e27f3Dd95710B528D43272C2)); + + // Mint some DAI for the deployer. + vm.startPrank(deployer); + faucet.mint(address(dai), deployer, 50_000e18); + // Deploy the Hyperdrive instance. + address dataProvider = address( + new MakerDsrHyperdriveDataProvider(dsrManager) + ); hyperdrive = IHyperdrive( - address(0xB311B825171AF5A60d69aAD590B857B1E5ed23a2) + address( + new MakerDsrHyperdrive( + IHyperdrive.PoolConfig({ + baseToken: dai, + initialSharePrice: FixedPointMath.ONE_18, + positionDuration: 365 days, // 1 year term + checkpointDuration: 1 days, // 1 day checkpoints + timeStretch: HyperdriveUtils.calculateTimeStretch( + 0.02e18 + ), // 2% APR time stretch + governance: address(0), + fees: IHyperdrive.Fees({ + curve: 0.1e18, // 10% curve fee + flat: 0.05e18, // 5% flat fee + governance: 0.1e18 // 10% governance fee + }) + }), + dataProvider, + bytes32(0), + address(0), + dsrManager + ) + ) ); + // Initialize the Hyperdrive instance. + uint256 apr = 0.01e18; + uint256 contribution = 50_000e18; + dai.approve(address(hyperdrive), contribution); + hyperdrive.initialize(contribution, apr, deployer, true); + + // Deploy the fixed borrow instance. action = new AaveFixedBorrowAction(hyperdrive, pool); + vm.stopPrank(); vm.startPrank(alice); IERC20Mint(address(wsteth)).mint(1000e18); diff --git a/test/integrations/HyperdriveAaveDeploy.t.sol b/test/integrations/HyperdriveAaveDeploy.t.sol index 29f6b2e2..91ee1387 100644 --- a/test/integrations/HyperdriveAaveDeploy.t.sol +++ b/test/integrations/HyperdriveAaveDeploy.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import { AssetId } from "contracts/src/libraries/AssetId.sol"; -import { Errors } from "contracts/src/libraries/Errors.sol"; +import { IPool } from "@aave/interfaces/IPool.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { AaveHyperdriveDeployer, IPool } from "contracts/src/factory/AaveHyperdriveDeployer.sol"; import { HyperdriveFactory } from "contracts/src/factory/HyperdriveFactory.sol"; -import { HyperdriveTest } from "../utils/HyperdriveTest.sol"; -import { IHyperdriveDeployer } from "contracts/src/interfaces/IHyperdriveDeployer.sol"; +import { AaveHyperdriveDataProvider } from "contracts/src/instances/AaveHyperdriveDataProvider.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IHyperdriveDeployer } from "contracts/src/interfaces/IHyperdriveDeployer.sol"; +import { AssetId } from "contracts/src/libraries/AssetId.sol"; +import { Errors } from "contracts/src/libraries/Errors.sol"; import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; -import { IPool } from "@aave/interfaces/IPool.sol"; +import { HyperdriveTest } from "../utils/HyperdriveTest.sol"; contract HyperdriveDSRTest is HyperdriveTest { using FixedPointMath for *; @@ -46,7 +47,7 @@ contract HyperdriveDSRTest is HyperdriveTest { setUp(); // We've just copied the values used by the original tests to ensure this runs - vm.prank(alice); + vm.startPrank(alice); bytes32[] memory aToken = new bytes32[](1); // we do a little force convert bytes32 aTokenEncode; @@ -55,16 +56,22 @@ contract HyperdriveDSRTest is HyperdriveTest { } aToken[0] = aTokenEncode; dai.approve(address(factory), type(uint256).max); - vm.prank(alice); - hyperdrive = factory.deployAndImplement( + address dataProvider = address(new AaveHyperdriveDataProvider(aDai)); + hyperdrive = factory.deployAndInitialize( + IHyperdrive.PoolConfig({ + baseToken: dai, + initialSharePrice: FixedPointMath.ONE_18, + positionDuration: 365 days, + checkpointDuration: 1 days, + timeStretch: FixedPointMath.ONE_18.divDown( + 22.186877016851916266e18 + ), + governance: address(0), + fees: IHyperdrive.Fees(0, 0, 0) + }), + dataProvider, bytes32(0), address(0), - dai, - 0, - 365, - 1 days, - FixedPointMath.ONE_18.divDown(22.186877016851916266e18), - IHyperdrive.Fees(0, 0, 0), aToken, 2500e18, //1% apr diff --git a/test/integrations/HyperdriveDSRDeploy.t.sol b/test/integrations/HyperdriveDSRDeploy.t.sol index 19f66ec4..969a3222 100644 --- a/test/integrations/HyperdriveDSRDeploy.t.sol +++ b/test/integrations/HyperdriveDSRDeploy.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import { AssetId } from "contracts/src/libraries/AssetId.sol"; -import { Errors } from "contracts/src/libraries/Errors.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { MakerDsrHyperdriveDeployer } from "contracts/src/factory/MakerDsrHyperdriveDeployer.sol"; import { HyperdriveFactory } from "contracts/src/factory/HyperdriveFactory.sol"; -import { HyperdriveTest } from "../utils/HyperdriveTest.sol"; -import { DsrManager } from "contracts/test/MockMakerDsrHyperdrive.sol"; +import { MakerDsrHyperdriveDataProvider } from "contracts/src/instances/MakerDsrHyperdriveDataProvider.sol"; import { IHyperdriveDeployer } from "contracts/src/interfaces/IHyperdriveDeployer.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { AssetId } from "contracts/src/libraries/AssetId.sol"; +import { Errors } from "contracts/src/libraries/Errors.sol"; import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; +import { DsrManager } from "contracts/test/MockMakerDsrHyperdrive.sol"; +import { HyperdriveTest } from "../utils/HyperdriveTest.sol"; contract HyperdriveDSRTest is HyperdriveTest { using FixedPointMath for *; @@ -46,19 +47,27 @@ contract HyperdriveDSRTest is HyperdriveTest { setUp(); // We've just copied the values used by the original tests to ensure this runs - vm.prank(alice); + vm.startPrank(alice); bytes32[] memory empty = new bytes32[](0); dai.approve(address(factory), type(uint256).max); - vm.prank(alice); - hyperdrive = factory.deployAndImplement( + address dataProvider = address( + new MakerDsrHyperdriveDataProvider(manager) + ); + hyperdrive = factory.deployAndInitialize( + IHyperdrive.PoolConfig({ + baseToken: dai, + initialSharePrice: FixedPointMath.ONE_18, + positionDuration: 365 days, + checkpointDuration: 1 days, + timeStretch: FixedPointMath.ONE_18.divDown( + 22.186877016851916266e18 + ), + governance: address(0), + fees: IHyperdrive.Fees(0, 0, 0) + }), + dataProvider, bytes32(0), address(0), - dai, - 0, - 365, - 1 days, - FixedPointMath.ONE_18.divDown(22.186877016851916266e18), - IHyperdrive.Fees(0, 0, 0), empty, 2500e18, //1% apr diff --git a/test/integrations/MakerDsrHyperdrive.t.sol b/test/integrations/MakerDsrHyperdrive.t.sol index 02857cd2..142b06fa 100644 --- a/test/integrations/MakerDsrHyperdrive.t.sol +++ b/test/integrations/MakerDsrHyperdrive.t.sol @@ -1,21 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import "forge-std/Test.sol"; -import "forge-std/Vm.sol"; -import "forge-std/console2.sol"; - -import { BaseTest } from "test/utils/BaseTest.sol"; -import { MockMakerDsrHyperdrive, DsrManager } from "contracts/test/MockMakerDsrHyperdrive.sol"; -import { ForwarderFactory } from "contracts/src/ForwarderFactory.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ForwarderFactory } from "contracts/src/ForwarderFactory.sol"; +import { MakerDsrHyperdriveDataProvider } from "contracts/src/instances/MakerDsrHyperdriveDataProvider.sol"; import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; import { Errors } from "contracts/src/libraries/Errors.sol"; +import { IMockMakerDsrHyperdrive, MockMakerDsrHyperdrive, DsrManager } from "contracts/test/MockMakerDsrHyperdrive.sol"; +import { BaseTest } from "test/utils/BaseTest.sol"; contract MakerDsrHyperdrive is BaseTest { using FixedPointMath for uint256; - MockMakerDsrHyperdrive hyperdrive; + IMockMakerDsrHyperdrive hyperdrive; IERC20 dai; IERC20 chai; DsrManager dsrManager; @@ -29,7 +26,12 @@ contract MakerDsrHyperdrive is BaseTest { ); vm.startPrank(deployer); - hyperdrive = new MockMakerDsrHyperdrive(dsrManager); + address dataProvider = address( + new MakerDsrHyperdriveDataProvider(dsrManager) + ); + hyperdrive = IMockMakerDsrHyperdrive( + address(new MockMakerDsrHyperdrive(dataProvider, dsrManager)) + ); address daiWhale = 0x075e72a5eDf65F0A5f44699c7654C1a76941Ddc8; @@ -161,9 +163,6 @@ contract MakerDsrHyperdrive is BaseTest { // Get total and per-user amounts of underlying invested uint256 underlyingInvested = dsrManager.daiBalance(address(hyperdrive)); - // uint256 pricePerShare = hyperdrive.pricePerShare(); - // uint256 underlyingForBob = sharesBob.mulDown(pricePerShare); - // uint256 underlyingForAlice = sharesAlice.mulDown(pricePerShare); // Bob should have accrued 1% (uint256 amountWithdrawnBob, ) = hyperdrive.withdraw( diff --git a/test/mocks/MockHyperdrive.sol b/test/mocks/MockHyperdrive.sol index 17fb48e5..d6ea8da6 100644 --- a/test/mocks/MockHyperdrive.sol +++ b/test/mocks/MockHyperdrive.sol @@ -4,45 +4,113 @@ pragma solidity ^0.8.18; import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; import { ForwarderFactory } from "contracts/src/ForwarderFactory.sol"; import { Hyperdrive } from "contracts/src/Hyperdrive.sol"; +import { HyperdriveDataProvider } from "contracts/src/HyperdriveDataProvider.sol"; import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; import { Errors } from "contracts/src/libraries/Errors.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; +interface IMockHyperdrive { + function accrue(uint256 time, int256 apr) external; + + function calculateFeesOutGivenSharesIn( + uint256 _amountIn, + uint256 _amountOut, + uint256 _normalizedTimeRemaining, + uint256 _spotPrice, + uint256 sharePrice + ) + external + view + returns ( + uint256 totalCurveFee, + uint256 totalFlatFee, + uint256 governanceCurveFee, + uint256 governanceFlatFee + ); + + function calculateFeesOutGivenBondsIn( + uint256 _amountIn, + uint256 _normalizedTimeRemaining, + uint256 _spotPrice, + uint256 sharePrice + ) + external + view + returns ( + uint256 totalCurveFee, + uint256 totalFlatFee, + uint256 totalGovernanceFee + ); + + function calculateFeesInGivenBondsOut( + uint256 _amountOut, + uint256 _normalizedTimeRemaining, + uint256 _spotPrice, + uint256 sharePrice + ) + external + view + returns ( + uint256 totalCurveFee, + uint256 totalFlatFee, + uint256 governanceCurveFee, + uint256 governanceFlatFee + ); + + function calculateOpenLong( + uint256 _shareAmount, + uint256 _sharePrice, + uint256 _timeRemaining + ) + external + view + returns ( + uint256 shareReservesDelta, + uint256 bondReservesDelta, + uint256 bondProceeds, + uint256 totalGovernanceFee + ); + + function setReserves(uint256 shareReserves, uint256 bondReserves) external; + + function getGovernanceFeesAccrued() external view returns (uint256); +} + contract MockHyperdrive is Hyperdrive { using FixedPointMath for uint256; uint256 internal totalShares; constructor( - ERC20Mintable baseToken, + address _dataProvider, + ERC20Mintable _baseToken, uint256 _initialSharePrice, - uint256 _checkpointsPerTerm, + uint256 _positionDuration, uint256 _checkpointDuration, uint256 _timeStretch, IHyperdrive.Fees memory _fees, address _governance ) Hyperdrive( + IHyperdrive.PoolConfig({ + baseToken: _baseToken, + initialSharePrice: _initialSharePrice, + positionDuration: _positionDuration, + checkpointDuration: _checkpointDuration, + timeStretch: _timeStretch, + governance: _governance, + fees: _fees + }), + _dataProvider, bytes32(0), - address(new ForwarderFactory()), - baseToken, - _initialSharePrice, - _checkpointsPerTerm, - _checkpointDuration, - _timeStretch, - _fees, - _governance + address(new ForwarderFactory()) ) {} /// Mocks /// - function getGovernanceFeesAccrued() external view returns (uint256) { - return governanceFeesAccrued; - } - // Accrues compounded interest for a given number of seconds and readjusts // share price to reflect such compounding function accrue(uint256 time, int256 apr) external { @@ -72,7 +140,7 @@ contract MockHyperdrive is Hyperdrive { uint256 _spotPrice, uint256 sharePrice ) - public + external view returns ( uint256 totalCurveFee, @@ -107,7 +175,7 @@ contract MockHyperdrive is Hyperdrive { uint256 _spotPrice, uint256 sharePrice ) - public + external view returns ( uint256 totalCurveFee, @@ -134,7 +202,7 @@ contract MockHyperdrive is Hyperdrive { uint256 _spotPrice, uint256 sharePrice ) - public + external view returns ( uint256 totalCurveFee, @@ -180,7 +248,7 @@ contract MockHyperdrive is Hyperdrive { return _calculateOpenLong(_shareAmount, _sharePrice, _timeRemaining); } - function setReserves(uint256 shareReserves, uint256 bondReserves) public { + function setReserves(uint256 shareReserves, uint256 bondReserves) external { marketState.shareReserves = uint128(shareReserves); marketState.bondReserves = uint128(bondReserves); } @@ -240,3 +308,34 @@ contract MockHyperdrive is Hyperdrive { return sharePrice; } } + +contract MockHyperdriveDataProvider is HyperdriveDataProvider { + using FixedPointMath for uint256; + + ERC20Mintable internal immutable baseToken; + + uint256 internal totalShares; + + constructor(ERC20Mintable _baseToken) { + baseToken = _baseToken; + } + + /// Mocks /// + + function getGovernanceFeesAccrued() external view returns (uint256) { + _revert(abi.encode(governanceFeesAccrued)); + } + + /// Overrides /// + + function _pricePerShare() + internal + view + override + returns (uint256 sharePrice) + { + uint256 assets = baseToken.balanceOf(address(this)); + sharePrice = totalShares != 0 ? assets.divDown(totalShares) : 0; + return sharePrice; + } +} diff --git a/test/python/test_deploy.py b/test/python/test_deploy.py index 3cd44168..3be55397 100644 --- a/test/python/test_deploy.py +++ b/test/python/test_deploy.py @@ -17,6 +17,7 @@ def test_deploy(): share_price = int(1e18) checkpoints = 365 checkpoint_duration = 86400 + position_duration = checkpoints * checkpoint_duration curve_fee = 0 flat_fee = 0 gov_fee = 0 @@ -36,12 +37,17 @@ def test_deploy(): base_ERC20.mint(base_supply, sender=deployer) time_stretch = fixed_math.divDown(int(1e18), t_stretch) + hyperdrive_data_provider_address = deployer.deploy( + project.MockHyperdriveDataProviderTestnet, + base_ERC20, + ) hyperdrive_address = deployer.deploy( project.MockHyperdriveTestnet, + hyperdrive_data_provider_address, base_ERC20, initial_apr, share_price, - checkpoints, + position_duration, checkpoint_duration, int(time_stretch), (curve_fee, flat_fee, gov_fee), diff --git a/test/units/MultiToken.t.sol b/test/units/MultiToken.t.sol index 18fe9680..8e6f513c 100644 --- a/test/units/MultiToken.t.sol +++ b/test/units/MultiToken.t.sol @@ -1,18 +1,28 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import { BaseTest } from "test/utils/BaseTest.sol"; -import { MockMultiToken } from "contracts/test/MockMultiToken.sol"; import { ForwarderFactory } from "contracts/src/ForwarderFactory.sol"; +import { MultiTokenDataProvider } from "contracts/src/MultiTokenDataProvider.sol"; +import { MockMultiToken, IMockMultiToken } from "contracts/test/MockMultiToken.sol"; +import { BaseTest } from "test/utils/BaseTest.sol"; contract MultiTokenTest is BaseTest { - MockMultiToken multiToken; + IMockMultiToken multiToken; function setUp() public override { super.setUp(); vm.startPrank(deployer); forwarderFactory = new ForwarderFactory(); - multiToken = new MockMultiToken(bytes32(0), address(forwarderFactory)); + address dataProvider = address(new MultiTokenDataProvider()); + multiToken = IMockMultiToken( + address( + new MockMultiToken( + dataProvider, + bytes32(0), + address(forwarderFactory) + ) + ) + ); vm.stopPrank(); } diff --git a/test/units/hyperdrive/CalculateOpenLongTest.t.sol b/test/units/hyperdrive/CalculateOpenLongTest.t.sol deleted file mode 100644 index 8ddeef6a..00000000 --- a/test/units/hyperdrive/CalculateOpenLongTest.t.sol +++ /dev/null @@ -1,389 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; -import { MockHyperdrive } from "../../mocks/MockHyperdrive.sol"; -import { HyperdriveTest, HyperdriveUtils, IHyperdrive } from "../../utils/HyperdriveTest.sol"; - -// Assumptions: -// -// - The "state" values were arbitraily selected with a bias to non-rounded -// figures. -// - Where fractional values are indicated in commentary, the utilised value is -// its fixed point representation rounded down to 18 decimals. -// - Calculations as stated in the assertion commentary are derived using -// WolframAlpha and are generally represented in a form exceeding 18 decimals -// - Values in assertions are the fixed point representation of the WolframAlpha -// results rounded (down unless otherwise stated) to 18 decimals. -// - "Hand-rolled" calculations which are dependent on results from other -// calculations use the 18 decimal precision representation which will incur -// some precision loss from the mathematically "true" value of these -// computations. -contract CalculateOpenLongTest is HyperdriveTest { - using FixedPointMath for uint256; - - // State variables - uint256 initialSharePrice; - uint256 sharePrice; - uint256 normalizedTimeRemaining; - uint256 shareAmount; - uint256 bondReserves; - uint256 shareReserves; - uint256 timeStretch; - IHyperdrive.Fees fees; - - // Intermediary calculation variables - uint256 dz_curve; - uint256 dy_$_flat; - uint256 one_minus_t_stretch; - uint256 c_div_mu; - uint256 mu_z; - uint256 mu_z_pow_one_minus_t_stretch; - uint256 y_pow_one_minus_t_stretch; - uint256 k; - uint256 mu_z_and_dzcurve; - uint256 mu_z_and_dzcurve_pow_one_minus_t_stretch; - uint256 z_; - uint256 inverse_one_minus_t_stretch; - uint256 _y; - uint256 dy_$_curve; - uint256 dy; - uint256 tau; - uint256 p; - uint256 flat_fee; - uint256 curve_fee; - uint256 governance_flat_fee; - uint256 governance_curve_fee; - - // Assertion variables - uint256 expectedTotalGovernanceFee; - uint256 expectedBondReservesDelta; - uint256 expectedBondProceeds; - uint256 expectedShareReservesDelta; - - function test_calculate_open_long() external { - // µ = 1 * 45/41 - initialSharePrice = 1.097560975609756097e18; - // c = 1 * 49/41 - sharePrice = 1.195121951219512195e18; - // t = 34/35 - normalizedTimeRemaining = 0.971428571428571428e18; - // Δx = c · Δz - // Δz = Δx / c - // Δz = 100000 / 1.195121951219512195 - // Δz = 83673.469387755102049354 - shareAmount = 83673.469387755102049354e18; - // y = 100000000 * 7672/1998.5 - bondReserves = 383887915.936952714535901926e18; - // z = 100000000 * 7672/2149 - 20,000,000 - shareReserves = 337003257.328990228013029315e18; - // 1 / (3.09396 / (0.02789 * 5)) - // timeStretch = 0.045071688063194094e18; - timeStretch = HyperdriveUtils.calculateTimeStretch(0.05e18); - - // State setup - fees = IHyperdrive.Fees({ - curve: 0.025e18, // 2.5% - flat: 0.03e18, // 3% - governance: 0.425e18 // 42.5% - }); - - hyperdrive = IHyperdrive( - address( - new MockHyperdrive( - baseToken, - initialSharePrice, - CHECKPOINTS_PER_TERM, - CHECKPOINT_DURATION, - timeStretch, - fees, - governance - ) - ) - ); - MockHyperdrive(address(hyperdrive)).setReserves( - shareReserves, - bondReserves - ); - - // Δz_curve = Δz · t - // Δz_curve = 83673.469387755102049354 * 0.971428571428571428 - // Δz_curve = 81282.798833819241942987617492711370257512 - dz_curve = shareAmount.mulDown(normalizedTimeRemaining); - assertEq(dz_curve, 81282.798833819241942987e18, "dz_curve"); - - // Δy'flat = Δz · (1 - t) * c - // Δy'flat = 83673.469387755102049354 * (1 - 0.971428571428571428) * 1.195121951219512195 - // Δy'flat = 2857.14285714285719999998512408447699634341676740382564116 - dy_$_flat = shareAmount - .mulDown(FixedPointMath.ONE_18.sub(normalizedTimeRemaining)) - .mulDown(sharePrice); - assertEq(dy_$_flat, 2857.142857142857199999e18, "dy_apost_flat"); - - // For the curve trade, (1 - t) is reframed as (1 - timeStretch) - // (1 - t) = (1 - 0.045071688063194094) - // (1 - t) = 0.954928311936805906 - one_minus_t_stretch = FixedPointMath.ONE_18.sub(timeStretch); - assertEq( - one_minus_t_stretch, - 0.954928311936805906e18, - "one_minus_t_stretch" - ); - - // (c / µ) = (1.195121951219512195 / 1.097560975609756097) - // (c / µ) = 1.0888888888888888893343209876543209878819862825788751715841263222... - c_div_mu = sharePrice.divDown(initialSharePrice); - assertEq(c_div_mu, 1.088888888888888889e18, "c_div_mu"); - - // (µ · z) = (1.097560975609756097 * 337003257.328990228013029315) - // (µ · z) = 369881623.897672201288664494059346945260983555 - mu_z = initialSharePrice.mulDown(shareReserves); - assertEq(mu_z, 369881623.897672201288664494e18, "mu_z"); - - // (µ · z)^(1 - t) = 369881623.897672201288664494^(0.954928311936805906) - // (µ · z)^(1 - t) = 152014740.974500528173941042763114476815073326183010430 - mu_z_pow_one_minus_t_stretch = initialSharePrice - .mulDown(shareReserves) - .pow(one_minus_t_stretch); - assertApproxEqAbs( - mu_z_pow_one_minus_t_stretch, - 152014740.974500528173941042e18, - 5e7, - "mu_z_pow_one_minus_t" - ); // TODO Precision - - // y^(1 - t) = (383887915.936952714535901926)^(0.954928311936805906) - // y^(1 - t) = 157506998.923528080399004587041700209859953096737243167 - y_pow_one_minus_t_stretch = bondReserves.pow(one_minus_t_stretch); - assertApproxEqAbs( - y_pow_one_minus_t_stretch, - 157506998.923528080399004587e18, - 3e8, - "y_pow_one_minus_t_stretch" - ); // TODO Precision - - // k = (c / µ) · (µ · z)^(1 - t) + y^(1 - t) - // k = 1.088888888888888889 * 152014740.974500528173941042 + 157506998.923528080399004587 - // k = 323034161.317984211094186470619388947574882338 - k = c_div_mu.mulDown(mu_z_pow_one_minus_t_stretch).add( - y_pow_one_minus_t_stretch - ); - assertApproxEqAbs(k, 323034161.31798421109418647e18, 3e8, "k"); // TODO Precision - - // (µ · (z + Δz_curve)) = 1.097560975609756097 * (337003257.328990228013029315 + 81282.798833819241942987) - // (µ · (z + Δz_curve)) = 369970836.725660539480995345538049924710625294 - mu_z_and_dzcurve = initialSharePrice.mulDown( - shareReserves.add(dz_curve) - ); - assertEq( - mu_z_and_dzcurve, - 369970836.725660539480995345e18, - "mu_z_and_dzcurve" - ); - - // (µ · (z + Δz_curve))^(1 - t) = 369970836.725660539480995345^0.954928311936805906 - // (µ · (z + Δz_curve))^(1 - t) = 152049753.115098833898787866715676618030157129075568782 - mu_z_and_dzcurve_pow_one_minus_t_stretch = mu_z_and_dzcurve.pow( - one_minus_t_stretch - ); - assertApproxEqAbs( - mu_z_and_dzcurve_pow_one_minus_t_stretch, - 152049753.115098833898787866e18, - 5e7, - "mu_z_and_dzcurve_pow_one_minus_t_stretch" - ); // TODO Precision - - // (c / µ) · (µ · (z + Δz_curve))^(1 - t) = 1.088888888888888889 * 152049753.115098833898787866 - // (c / µ) · (µ · (z + Δz_curve))^(1 - t) = 165565286.725329841373352315546122092655420874 - z_ = c_div_mu.mulDown(mu_z_and_dzcurve_pow_one_minus_t_stretch); - assertApproxEqAbs(z_, 165565286.725329841373352315e18, 5e7, "z_"); // TODO Precision - - // (1 / (1 - t)) = 1 / 0.954928311936805906 - // (1 / (1 - t)) = 1.0471990279267966596926226784985107956999882836058167426007625189... - inverse_one_minus_t_stretch = FixedPointMath.ONE_18.divUp( - one_minus_t_stretch - ); - assertEq( - inverse_one_minus_t_stretch, - 1.04719902792679666e18, // Rounded up - "inverse_one_minus_t_stretch" - ); - - // (k - (c / µ) · (µ · (z + Δz_curve))^(1 - t))^(1 / (1 - t)) = (323034161.31798421109418647 - 165565286.725329841373352315)^1.04719902792679666 - // (k - (c / µ) · (µ · (z + Δz_curve))^(1 - t))^(1 / (1 - t)) = (157468874.592654369720834155)^1.04719902792679666 - // (k - (c / µ) · (µ · (z + Δz_curve))^(1 - t))^(1 / (1 - t)) = 383790611.293791832749419609975176889050228160919979985 - _y = k.sub(z_).pow(inverse_one_minus_t_stretch); - assertApproxEqAbs(_y, 383790611.293791832749419609e18, 7e8, "_y"); // TODO Precision - - // Δy'curve = y - (k - (c / µ) · (µ · (z + Δz_curve))^(1 - t))^(1 / (1 - t)) - // Δy'curve = 383887915.936952714535901926 - 383790611.293791832749419609 - // Δy'curve = 97304.643160881786482317 - dy_$_curve = bondReserves.sub(_y); - assertApproxEqAbs( - dy_$_curve, - 97304.643160881786482317e18, - 7e8, - "dy_apost_curve" - ); // TODO Precision - - // Δy = Δ'y_curve + Δ'y_flat - // Δy = 97304.643160881786482317 + 2857.142857142857199999 - // Δy = 100161.786018024643682316 - dy = dy_$_curve.add(dy_$_flat); - assertApproxEqAbs(dy, 100161.786018024643682316e18, 7e8, "dy"); - - // τ = t * timeStretch - // τ = 0.971428571428571428 * 0.045071688063194094 - // τ = 0.043783925547102834145673321106746232 - tau = normalizedTimeRemaining.mulDown(timeStretch); - assertEq(tau, 0.043783925547102834e18, "tau"); - - // p = (µ · z / y)^τ - // p = (369881623.897672201288664494 / 383887915.936952714535901926)^0.043783925547102834 - // p = (0.9635146315947574221524973828409339252968448231146639381459727054)^0.043783925547102834 - // p = 0.9983739797397207901274666301517165600456415425139442692974 - p = mu_z.divDown(bondReserves).pow(tau); - assertEq(p, 0.99837397973972079e18, "p"); - - // flat_fee = c · Δz · (1 - t) · Φ_flat - // flat_fee = 1.195121951219512195 * 83673.469387755102049354 * (1 - 0.971428571428571428) * (3/100) - // flat_fee = 85.7142857142857159999995537225343098903025030221147692348 - flat_fee = sharePrice - .mulDown(shareAmount) - .mulDown(FixedPointMath.ONE_18.sub(normalizedTimeRemaining)) - .mulDown(fees.flat); - assertEq(flat_fee, 85.714285714285715999e18, "flat_fee"); - - // curve_fee = ((1 / p) - 1) · Φ_curve * c · Δz · t - // curve_fee = ((1 / 0.99837397973972079) - 1) * (2.5/100) * 1.195121951219512195 * 83673.469387755102049354 * 0.971428571428571428 - // curve_fee = 3.9553378058008476341813458022271009778187353456297919835792269030 - curve_fee = ( - (FixedPointMath.ONE_18.divDown(p)).sub(FixedPointMath.ONE_18) - ).mulDown(fees.curve).mulDown(sharePrice).mulDown(shareAmount).mulDown( - normalizedTimeRemaining - ); - assertApproxEqAbs(curve_fee, 3.955337805800847634e18, 2e5, "curve_fee"); - - // governance_flat_fee = (flat_fee · Φ_governance) - // governance_flat_fee = (85.714285714285715999 * 42.5/100) - // governance_flat_fee = 36.428571428571429299575 - governance_flat_fee = flat_fee.mulDown(fees.governance); - assertEq( - governance_flat_fee, - 36.428571428571429299e18, - "governance_flat_fee" - ); - - // governance_curve_fee = Δz * (curve_fee / Δy) * c * Φ_governance - // governance_curve_fee = 83673.469387755102049354 * (3.955337805800847634 / 100161.786018024643682316) * 1.195121951219512195 * (42.5/100) - // governance_curve_fee = 1.678303307373983979665963589851097667486890664120111384375177702967713899401845398... - governance_curve_fee = shareAmount - .mulDivDown(curve_fee, dy) - .mulDown(sharePrice) - .mulDown(fees.governance); - assertApproxEqAbs( - governance_curve_fee, - 1.678303307373983979e18, - 6e4, - "governance_curve_fee" - ); // TODO Precision - - // governance_fee = (governance_flat_fee + governance_curve_fee) / c - // governance_fee = (36.428571428571429299 + 1.678303307373983979) / 1.195121951219512195 - // governance_fee = 31.88534416681146825627401471089912941390551170519378871570464405155038660364333102... - expectedTotalGovernanceFee = ( - governance_flat_fee.add(governance_curve_fee) - ).divDown(sharePrice); - assertApproxEqAbs( - expectedTotalGovernanceFee, - 31.885344166811468256e18, - 5e4, - "expectedTotalGovernanceFee" - ); // TODO Precision - - // bondReservesDelta = Δ'y_curve - (curve_fee - governance_curve_fee) - // bondReservesDelta = 97304.643160881786482317 - (3.955337805800847634 - 1.678303307373983979) - // bondReservesDelta = 97302.373979129693414529 - expectedBondReservesDelta = dy_$_curve.sub( - curve_fee.sub(governance_curve_fee) - ); - assertApproxEqAbs( - expectedBondReservesDelta, - 97302.366126383359618662e18, - 7e8, - "expectedBondReservesDelta" - ); // TODO Precision - - // bondProceeds = Δy - (curve_fee + flat_fee) - // bondProceeds = 100161.786018024643682316 - (3.955337805800847634 + 85.714285714285715999) - // bondProceeds = 100072.116394504557118683 - expectedBondProceeds = dy.sub(curve_fee.add(flat_fee)); - assertApproxEqAbs( - expectedBondProceeds, - 100072.116394504557118683e18, - 7e8, - "expectedBondProceeds" - ); // TODO Precision - - // shareReservesDelta = Δz_curve - (governance_curve_fee / c) - // shareReservesDelta = 81282.798833819241942987 - (1.678303307373983979 / 1.195121951219512195) - // shareReservesDelta = 81281.39453921511269108606078626488477021418987614947803777695815062749775895683246... - expectedShareReservesDelta = dz_curve.sub( - governance_curve_fee.divDown(sharePrice) - ); - assertApproxEqAbs( - expectedShareReservesDelta, - 81281.394539215112691086e18, - 5e4, - "expectedShareReservesDelta" - ); // TODO Precision - - ( - uint256 shareReservesDelta, - uint256 bondReservesDelta, - uint256 bondProceeds, - uint256 totalGovernanceFee - ) = MockHyperdrive(address(hyperdrive)).calculateOpenLong( - shareAmount, - sharePrice, - normalizedTimeRemaining - ); - - assertEq( - shareReservesDelta, - expectedShareReservesDelta, - "shareReservesDelta computation misaligned" - ); - assertEq( - bondReservesDelta, - expectedBondReservesDelta, - "bondReservesDelta computation misaligned" - ); - assertEq( - bondProceeds, - expectedBondProceeds, - "bondProceeds computation misaligned" - ); - assertEq( - totalGovernanceFee, - expectedTotalGovernanceFee, - "totalGovernanceFee computation misaligned" - ); - - // Adding explicit delta assertions so that any change in how these - // values are derived will fail the test - // TODO Precision - assertWithDelta( - shareReservesDelta, - -49215, - 81281.394539215112691086e18 - ); - assertWithDelta( - bondReservesDelta, - -691486610, - 97302.366126383359618662e18 - ); - assertWithDelta(bondProceeds, -691545427, 100072.116394504557118683e18); - assertWithDelta(totalGovernanceFee, 49214, 31.885344166811468256e18); - } -} diff --git a/test/units/hyperdrive/CheckpointTest.t.sol b/test/units/hyperdrive/CheckpointTest.t.sol index 05fc4b0e..5a37903c 100644 --- a/test/units/hyperdrive/CheckpointTest.t.sol +++ b/test/units/hyperdrive/CheckpointTest.t.sol @@ -44,7 +44,7 @@ contract CheckpointTest is HyperdriveTest { // Ensure that the checkpoint contains the share price prior to the // share price update. - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( HyperdriveUtils.latestCheckpoint(hyperdrive) ); assertEq(checkpoint.sharePrice, sharePrice); @@ -80,7 +80,7 @@ contract CheckpointTest is HyperdriveTest { ); // Ensure that the checkpoint contains the latest share price. - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( HyperdriveUtils.latestCheckpoint(hyperdrive) ); assertEq(checkpoint.sharePrice, sharePrice); @@ -110,7 +110,7 @@ contract CheckpointTest is HyperdriveTest { // Ensure that the checkpoint contains the share price prior to the // share price update. - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( HyperdriveUtils.latestCheckpoint(hyperdrive) ); IHyperdrive.PoolInfo memory poolInfo = hyperdrive.getPoolInfo(); @@ -151,14 +151,14 @@ contract CheckpointTest is HyperdriveTest { // Ensure that the checkpoint contains the share price prior to the // share price update. - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( HyperdriveUtils.latestCheckpoint(hyperdrive) ); IHyperdrive.PoolInfo memory poolInfo = hyperdrive.getPoolInfo(); assertEq(checkpoint.sharePrice, poolInfo.sharePrice); // Ensure that the previous checkpoint contains the closest share price. - checkpoint = hyperdrive.checkpoints(previousCheckpoint); + checkpoint = hyperdrive.getCheckpoint(previousCheckpoint); assertEq(checkpoint.sharePrice, poolInfo.sharePrice); // Ensure that the long and short balance has gone to zero (all of the diff --git a/test/units/hyperdrive/CloseLongTest.t.sol b/test/units/hyperdrive/CloseLongTest.t.sol index 55597fe8..41e2b8e2 100644 --- a/test/units/hyperdrive/CloseLongTest.t.sol +++ b/test/units/hyperdrive/CloseLongTest.t.sol @@ -173,7 +173,7 @@ contract CloseLongTest is HyperdriveTest { // Ensure that the average open share price was updated correctly. assertEq( - hyperdrive.checkpoints(block.timestamp).longSharePrice, + hyperdrive.getCheckpoint(block.timestamp).longSharePrice, hyperdrive.getPoolInfo().sharePrice ); } @@ -446,7 +446,7 @@ contract CloseLongTest is HyperdriveTest { // Verify that the other states were correct. IHyperdrive.PoolInfo memory poolInfoAfter = hyperdrive.getPoolInfo(); - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( checkpointTime ); if (wasCheckpointed) { diff --git a/test/units/hyperdrive/CloseShortTest.t.sol b/test/units/hyperdrive/CloseShortTest.t.sol index 7da43820..87f1058b 100644 --- a/test/units/hyperdrive/CloseShortTest.t.sol +++ b/test/units/hyperdrive/CloseShortTest.t.sol @@ -313,7 +313,7 @@ contract CloseShortTest is HyperdriveTest { // Retrieve the pool info after the trade. IHyperdrive.PoolInfo memory poolInfoAfter = hyperdrive.getPoolInfo(); - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( checkpointTime ); diff --git a/test/units/hyperdrive/FeeTest.t.sol b/test/units/hyperdrive/FeeTest.t.sol index 2ace27bf..a9e1ce84 100644 --- a/test/units/hyperdrive/FeeTest.t.sol +++ b/test/units/hyperdrive/FeeTest.t.sol @@ -5,7 +5,7 @@ import { stdError } from "forge-std/StdError.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { Errors } from "contracts/src/libraries/Errors.sol"; import { FixedPointMath } from "contracts/src/libraries/FixedPointMath.sol"; -import { MockHyperdrive } from "../../mocks/MockHyperdrive.sol"; +import { MockHyperdrive, IMockHyperdrive } from "../../mocks/MockHyperdrive.sol"; import { HyperdriveTest, HyperdriveUtils } from "../../utils/HyperdriveTest.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; @@ -24,7 +24,7 @@ contract FeeTest is HyperdriveTest { // Open a long, record the accrued fees x share price uint256 baseAmount = 10e18; openLong(bob, baseAmount); - uint256 governanceFeesAfterOpenLong = MockHyperdrive( + uint256 governanceFeesAfterOpenLong = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued().mulDown( hyperdrive.getPoolInfo().sharePrice @@ -53,7 +53,7 @@ contract FeeTest is HyperdriveTest { assertEq(governanceBalanceBefore, 0); // Ensure that fees are initially zero. - uint256 governanceFeesBeforeOpenLong = MockHyperdrive( + uint256 governanceFeesBeforeOpenLong = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertEq(governanceFeesBeforeOpenLong, 0); @@ -63,7 +63,7 @@ contract FeeTest is HyperdriveTest { (uint256 maturityTime, uint256 bondAmount) = openLong(bob, baseAmount); // Ensure that governance fees have been accrued. - uint256 governanceFeesAfterOpenLong = MockHyperdrive( + uint256 governanceFeesAfterOpenLong = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertGt(governanceFeesAfterOpenLong, governanceFeesBeforeOpenLong); @@ -75,7 +75,7 @@ contract FeeTest is HyperdriveTest { closeLong(bob, maturityTime, bondAmount); // Ensure that governance fees after close are greater than before close. - uint256 governanceFeesAfterCloseLong = MockHyperdrive( + uint256 governanceFeesAfterCloseLong = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertGt(governanceFeesAfterCloseLong, governanceFeesAfterOpenLong); @@ -84,7 +84,7 @@ contract FeeTest is HyperdriveTest { MockHyperdrive(address(hyperdrive)).collectGovernanceFee(); // Ensure that governance fees after collection are zero. - uint256 governanceFeesAfterCollection = MockHyperdrive( + uint256 governanceFeesAfterCollection = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertEq(governanceFeesAfterCollection, 0); @@ -108,7 +108,7 @@ contract FeeTest is HyperdriveTest { assertEq(governanceBalanceBefore, 0); // Ensure that fees are initially zero. - uint256 governanceFeesBeforeOpenShort = MockHyperdrive( + uint256 governanceFeesBeforeOpenShort = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertEq(governanceFeesBeforeOpenShort, 0); @@ -118,7 +118,7 @@ contract FeeTest is HyperdriveTest { (uint256 maturityTime, ) = openShort(bob, bondAmount); // Ensure that governance fees have been accrued. - uint256 governanceFeesAfterOpenShort = MockHyperdrive( + uint256 governanceFeesAfterOpenShort = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertGt(governanceFeesAfterOpenShort, governanceFeesBeforeOpenShort); @@ -130,7 +130,7 @@ contract FeeTest is HyperdriveTest { closeShort(bob, maturityTime, bondAmount); // Ensure that governance fees after close are greater than before close. - uint256 governanceFeesAfterCloseShort = MockHyperdrive( + uint256 governanceFeesAfterCloseShort = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertGt(governanceFeesAfterCloseShort, governanceFeesAfterOpenShort); @@ -139,7 +139,7 @@ contract FeeTest is HyperdriveTest { MockHyperdrive(address(hyperdrive)).collectGovernanceFee(); // Ensure that governance fees after collection are zero. - uint256 governanceFeesAfterCollection = MockHyperdrive( + uint256 governanceFeesAfterCollection = IMockHyperdrive( address(hyperdrive) ).getGovernanceFeesAccrued(); assertEq(governanceFeesAfterCollection, 0); diff --git a/test/units/hyperdrive/OpenLongTest.t.sol b/test/units/hyperdrive/OpenLongTest.t.sol index f0756df2..08553a67 100644 --- a/test/units/hyperdrive/OpenLongTest.t.sol +++ b/test/units/hyperdrive/OpenLongTest.t.sol @@ -162,7 +162,7 @@ contract OpenLongTest is HyperdriveTest { // p = 1 / (1 + r) // roughly ((1/.9523 - 1) * .1) * 10e18 * 1 = 5e16, or 10% of the 5% bond - base spread. uint256 p = (uint256(1 ether)).divDown(1 ether + 0.05 ether); - uint256 phi = hyperdrive.getPoolConfig().curveFee; + uint256 phi = hyperdrive.getPoolConfig().fees.curve; uint256 curveFeeAmount = (uint256(1 ether).divDown(p) - 1 ether) .mulDown(phi) .mulDown(baseAmount); @@ -242,7 +242,7 @@ contract OpenLongTest is HyperdriveTest { // TODO: This problem gets much worse as the baseAmount to open a long gets smaller. // Figure out a solution to this. - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( checkpointTime ); assertApproxEqAbs( diff --git a/test/units/hyperdrive/OpenShortTest.t.sol b/test/units/hyperdrive/OpenShortTest.t.sol index 1d3c80af..74225cc1 100644 --- a/test/units/hyperdrive/OpenShortTest.t.sol +++ b/test/units/hyperdrive/OpenShortTest.t.sol @@ -151,7 +151,7 @@ contract OpenShortTest is HyperdriveTest { IHyperdrive.PoolInfo memory poolInfoAfter = hyperdrive.getPoolInfo(); { - IHyperdrive.Checkpoint memory checkpoint = hyperdrive.checkpoints( + IHyperdrive.Checkpoint memory checkpoint = hyperdrive.getCheckpoint( checkpointTime ); assertEq( diff --git a/test/utils/BaseTest.sol b/test/utils/BaseTest.sol index a5d54ecd..6b34fc59 100644 --- a/test/utils/BaseTest.sol +++ b/test/utils/BaseTest.sol @@ -37,14 +37,12 @@ contract BaseTest is Test { uint256 __init__; // time setup function was ran + string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + string GOERLI_RPC_URL = vm.envString("GOERLI_RPC_URL"); + constructor() { - // TODO Hide these in environment variables - mainnetForkId = vm.createFork( - "https://eth-mainnet.alchemyapi.io/v2/kwjMP-X-Vajdk1ItCfU-56Uaq1wwhamK" - ); - goerliForkId = vm.createFork( - "https://eth-goerli.alchemyapi.io/v2/kwjMP-X-Vajdk1ItCfU-56Uaq1wwhamK" - ); + mainnetForkId = vm.createFork(MAINNET_RPC_URL); + goerliForkId = vm.createFork(GOERLI_RPC_URL); } function setUp() public virtual { diff --git a/test/utils/HyperdriveTest.sol b/test/utils/HyperdriveTest.sol index 05309a93..f3cd66e3 100644 --- a/test/utils/HyperdriveTest.sol +++ b/test/utils/HyperdriveTest.sol @@ -9,7 +9,7 @@ import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { YieldSpaceMath } from "contracts/src/libraries/YieldSpaceMath.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { HyperdriveBase } from "contracts/src/HyperdriveBase.sol"; -import { MockHyperdrive } from "../mocks/MockHyperdrive.sol"; +import { MockHyperdrive, MockHyperdriveDataProvider } from "../mocks/MockHyperdrive.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { HyperdriveUtils } from "./HyperdriveUtils.sol"; @@ -21,9 +21,7 @@ contract HyperdriveTest is BaseTest { uint256 internal constant INITIAL_SHARE_PRICE = FixedPointMath.ONE_18; uint256 internal constant CHECKPOINT_DURATION = 1 days; - uint256 internal constant CHECKPOINTS_PER_TERM = 365; - uint256 internal constant POSITION_DURATION = - CHECKPOINT_DURATION * CHECKPOINTS_PER_TERM; + uint256 internal constant POSITION_DURATION = 365 days; function setUp() public virtual override { super.setUp(); @@ -38,12 +36,16 @@ contract HyperdriveTest is BaseTest { }); // Instantiate Hyperdrive. uint256 apr = 0.05e18; + address dataProvider = address( + new MockHyperdriveDataProvider(baseToken) + ); hyperdrive = IHyperdrive( address( new MockHyperdrive( + dataProvider, baseToken, INITIAL_SHARE_PRICE, - CHECKPOINTS_PER_TERM, + POSITION_DURATION, CHECKPOINT_DURATION, HyperdriveUtils.calculateTimeStretch(apr), fees, @@ -76,12 +78,16 @@ contract HyperdriveTest is BaseTest { governance: governanceFee }); + address dataProvider = address( + new MockHyperdriveDataProvider(baseToken) + ); hyperdrive = IHyperdrive( address( new MockHyperdrive( + dataProvider, baseToken, INITIAL_SHARE_PRICE, - CHECKPOINTS_PER_TERM, + POSITION_DURATION, CHECKPOINT_DURATION, HyperdriveUtils.calculateTimeStretch(apr), fees, diff --git a/test/utils/HyperdriveUtils.sol b/test/utils/HyperdriveUtils.sol index 21a64f39..a06c3762 100644 --- a/test/utils/HyperdriveUtils.sol +++ b/test/utils/HyperdriveUtils.sol @@ -234,7 +234,7 @@ library HyperdriveUtils { uint256 checkpoint = latestCheckpoint(_hyperdrive); uint256 maturityTime = checkpoint + poolConfig.positionDuration; timeRemaining = calculateTimeRemaining(_hyperdrive, maturityTime); - openSharePrice = _hyperdrive.checkpoints(checkpoint).sharePrice; + openSharePrice = _hyperdrive.getCheckpoint(checkpoint).sharePrice; } // Calculate the openShort trade @@ -261,7 +261,7 @@ library HyperdriveUtils { uint256 curveFee = FixedPointMath .ONE_18 .sub(spotPrice) - .mulDown(poolConfig.curveFee) + .mulDown(poolConfig.fees.curve) .mulDown(_bondAmount) .mulDivDown(timeRemaining, poolInfo.sharePrice); uint256 flatFee = ( @@ -269,7 +269,7 @@ library HyperdriveUtils { FixedPointMath.ONE_18.sub(timeRemaining), poolInfo.sharePrice ) - ).mulDown(poolConfig.flatFee); + ).mulDown(poolConfig.fees.flat); shareProceeds -= curveFee + flatFee; // Return the proceeds of the short