Skip to content

Commit

Permalink
Data Provider (#220)
Browse files Browse the repository at this point in the history
* Fixed the stack-too-deep issue

* Implemented a data provider contract

* Fixed most of the tests

* Fixed the remaining tests

* Configured the workflows to use repository secrets for RPC URLs

* Updated the Python test

* Update contracts/src/instances/AaveHyperdriveDataProvider.sol

Co-authored-by: Jonny Rhea <[email protected]>

* Addressed review feedback from @jhrea

---------

Co-authored-by: Jonny Rhea <[email protected]>
  • Loading branch information
jalextowle and jrhea authored May 4, 2023
1 parent 9e960c5 commit 0bf7a64
Show file tree
Hide file tree
Showing 57 changed files with 1,323 additions and 1,075 deletions.
6 changes: 6 additions & 0 deletions .env_template
Original file line number Diff line number Diff line change
@@ -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=
3 changes: 3 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
37 changes: 37 additions & 0 deletions contracts/src/DataProvider.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
41 changes: 8 additions & 33 deletions contracts/src/Hyperdrive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
174 changes: 49 additions & 125 deletions contracts/src/HyperdriveBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;

Expand All @@ -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 ///
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 0bf7a64

Please sign in to comment.