Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/devnet_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,13 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
FACTORY_MIN_CURVE_FEE=0
FACTORY_MIN_FLAT_FEE=0
FACTORY_MIN_GOVERNANCE_LP_FEE=0
FACTORY_MIN_GOVERNANCE_ZOMBIE_FEE=0
FACTORY_MAX_CURVE_FEE=1000000000000000000
FACTORY_MAX_FLAT_FEE=1000000000000000000
FACTORY_MAX_GOVERNANCE_LP_FEE=1000000000000000000
FACTORY_MAX_GOVERNANCE_ZOMBIE_FEE=1000000000000000000
FACTORY_MIN_POSITION_DURATION=86400
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
branch = v5.0.1
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Tests](https://github.com/delvtech/hyperdrive/actions/workflows/test.yml/badge.svg)](https://github.com/delvtech/hyperdrive/actions/workflows/test.yml)
[![Tests](https://github.com/delvtech/hyperdrive/actions/workflows/solidity_test.yml/badge.svg)](https://github.com/delvtech/hyperdrive/actions/workflows/solidity_test.yml)
[![Coverage](https://coveralls.io/repos/github/delvtech/hyperdrive/badge.svg?branch=main&t=vnW3xG&kill_cache=1&service=github)](https://coveralls.io/github/delvtech/hyperdrive?branch=main)

# Hyperdrive
Expand Down Expand Up @@ -41,7 +41,7 @@ If you want to automatically format the code, run `yarn prettier`.

## Yield Sources

The current suggested way of integrating your yield source with hyperdrive is through the [ERC-4626 standard](https://eips.ethereum.org/EIPS/eip-4626) although accomodations can be made if this is not possible. Hyperdrive currently makes use of [Yield Daddy](https://github.com/timeless-fi/yield-daddy) to wrap many existing yield sources into this standard.
The current suggested way of integrating your yield source with hyperdrive is through the [ERC-4626 standard](https://eips.ethereum.org/EIPS/eip-4626) although accomodations can be made if this is not possible.

# Disclaimer

Expand Down
297 changes: 265 additions & 32 deletions contracts/src/deployers/HyperdriveDeployerCoordinator.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;
pragma solidity 0.8.20;

import { IHyperdrive } from "../interfaces/IHyperdrive.sol";
import { IHyperdriveCoreDeployer } from "../interfaces/IHyperdriveCoreDeployer.sol";
import { IDeployerCoordinator } from "../interfaces/IDeployerCoordinator.sol";
import { IHyperdriveDeployerCoordinator } from "../interfaces/IHyperdriveDeployerCoordinator.sol";
import { IHyperdriveTargetDeployer } from "../interfaces/IHyperdriveTargetDeployer.sol";

/// @author DELV
Expand All @@ -14,7 +14,36 @@ import { IHyperdriveTargetDeployer } from "../interfaces/IHyperdriveTargetDeploy
/// @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 HyperdriveDeployerCoordinator is IDeployerCoordinator {
abstract contract HyperdriveDeployerCoordinator is
IHyperdriveDeployerCoordinator
{
struct Deployment {
/// @dev The hash of the config used in this deployment. This is used to
/// ensure that the config is the same across all deployments in
/// the batch.
bytes32 configHash;
/// @dev The hash of the extra data passed to the child deployers. This
/// is used to ensure that the extra data is the same across all
/// deployments in the batch.
bytes32 extraDataHash;
/// @dev The initial share price used in the first part of this
/// deployment. This is used to ensure that the initial share price
/// is the same across all deployments in the batch.
uint256 initialSharePrice;
/// @dev The address of the Hyperdrive entrypoint.
address hyperdrive;
/// @dev The address of the HyperdriveTarget0 contract.
address target0;
/// @dev The address of the HyperdriveTarget1 contract.
address target1;
/// @dev The address of the HyperdriveTarget2 contract.
address target2;
/// @dev The address of the HyperdriveTarget3 contract.
address target3;
/// @dev The address of the HyperdriveTarget4 contract.
address target4;
}

/// @notice The contract used to deploy new instances of Hyperdrive.
address public immutable coreDeployer;

Expand All @@ -30,67 +59,271 @@ abstract contract HyperdriveDeployerCoordinator is IDeployerCoordinator {
/// @notice The contract used to deploy new instances of HyperdriveTarget3.
address public immutable target3Deployer;

/// @notice The contract used to deploy new instances of HyperdriveTarget4.
address public immutable target4Deployer;

/// @notice A mapping from deployer to deployment ID to deployment.
mapping(address => mapping(bytes32 => Deployment)) public deployments;

/// @notice Instantiates the deployer coordinator.
/// @param _coreDeployer The core deployer.
/// @param _target0Deployer The target0 deployer.
/// @param _target1Deployer The target1 deployer.
/// @param _target2Deployer The target2 deployer.
/// @param _target3Deployer The target3 deployer.
/// @param _target4Deployer The target4 deployer.
constructor(
address _coreDeployer,
address _target0Deployer,
address _target1Deployer,
address _target2Deployer,
address _target3Deployer
address _target3Deployer,
address _target4Deployer
) {
coreDeployer = _coreDeployer;
target0Deployer = _target0Deployer;
target1Deployer = _target1Deployer;
target2Deployer = _target2Deployer;
target3Deployer = _target3Deployer;
target4Deployer = _target4Deployer;
}

/// @notice Deploys a Hyperdrive instance with the given parameters.
/// @param _deploymentId The ID of the deployment.
/// @param _deployConfig The deploy configuration of the Hyperdrive pool.
/// @param _extraData The extra data that contains the pool and sweep targets.
/// @param _salt The create2 salt used to deploy Hyperdrive.
/// @return The address of the newly deployed ERC4626Hyperdrive Instance.
function deploy(
bytes32 _deploymentId,
IHyperdrive.PoolDeployConfig memory _deployConfig,
bytes memory _extraData
) public virtual returns (address) {
bytes memory _extraData,
bytes32 _salt
) external returns (address) {
// Ensure that the Hyperdrive entrypoint has not already been deployed.
Deployment memory deployment = deployments[msg.sender][_deploymentId];
if (deployment.hyperdrive != address(0)) {
revert IHyperdriveDeployerCoordinator.HyperdriveAlreadyDeployed();
}

// Ensure that the deployment is not a fresh deployment. We can check
// this by ensuring that the config hash is set.
if (deployment.configHash == bytes32(0)) {
revert IHyperdriveDeployerCoordinator.DeploymentDoesNotExist();
}

// Ensure that all of the targets have been deployed.
if (
deployment.target0 == address(0) ||
deployment.target1 == address(0) ||
deployment.target2 == address(0) ||
deployment.target3 == address(0) ||
deployment.target4 == address(0)
) {
revert IHyperdriveDeployerCoordinator.IncompleteDeployment();
}

// Ensure that the provided config matches the config hash.
if (keccak256(abi.encode(_deployConfig)) != deployment.configHash) {
revert IHyperdriveDeployerCoordinator.MismatchedConfig();
}

// Ensure that the provided extra data matches the extra data hash.
if (keccak256(_extraData) != deployment.extraDataHash) {
revert IHyperdriveDeployerCoordinator.MismatchedExtraData();
}

// Convert the deploy config into the pool config and set the initial
// vault share price.
IHyperdrive.PoolConfig memory _config = _copyPoolConfig(_deployConfig);
_config.initialVaultSharePrice = _getInitialVaultSharePrice(_extraData);

// Deploy the target0 contract.
address target0 = IHyperdriveTargetDeployer(target0Deployer).deploy(
_config,
_extraData
);
address target1 = IHyperdriveTargetDeployer(target1Deployer).deploy(
_config,
_extraData
);
address target2 = IHyperdriveTargetDeployer(target2Deployer).deploy(
_config,
_extraData
);
address target3 = IHyperdriveTargetDeployer(target3Deployer).deploy(
_config,
_extraData
);
IHyperdrive.PoolConfig memory config = _copyPoolConfig(_deployConfig);
config.initialVaultSharePrice = deployment.initialSharePrice;

// Deploy the Hyperdrive instance.
return
IHyperdriveCoreDeployer(coreDeployer).deploy(
_config,
config,
_extraData,
deployment.target0,
deployment.target1,
deployment.target2,
deployment.target3,
deployment.target4,
_salt
);
}

/// @notice Deploys a Hyperdrive target instance with the given parameters.
/// @dev As a convention, target0 must be deployed first. After this, the
/// targets can be deployed in any order.
/// @param _deploymentId The ID of the deployment.
/// @param _deployConfig The deploy configuration of the Hyperdrive pool.
/// @param _extraData The extra data that contains the pool and sweep targets.
/// @param _targetIndex The index of the target to deploy.
/// @param _salt The create2 salt used to deploy the target.
/// @return target The address of the newly deployed target instance.
function deployTarget(
bytes32 _deploymentId,
IHyperdrive.PoolDeployConfig memory _deployConfig,
bytes memory _extraData,
uint256 _targetIndex,
bytes32 _salt
) external returns (address target) {
// If the target index is 0, then we're deploying the target0 instance.
// By convention, this target must be deployed first, and as part of the
// deployment of target0, we will register the deployment in the state.
if (_targetIndex == 0) {
// Ensure that the deployment is a fresh deployment. We can check this
// by ensuring that the config hash is not set.
if (
deployments[msg.sender][_deploymentId].configHash != bytes32(0)
) {
revert IHyperdriveDeployerCoordinator.DeploymentAlreadyExists();
}

// Check the pool configuration to ensure that it's a valid
// configuration for this instance.
_checkPoolConfig(_deployConfig);

// Get the initial share price and the hashes of the config and extra
// data.
uint256 initialSharePrice = _getInitialVaultSharePrice(_extraData);
bytes32 configHash = keccak256(abi.encode(_deployConfig));
bytes32 extraDataHash = keccak256(_extraData);

// Convert the deploy config into the pool config and set the initial
// vault share price.
IHyperdrive.PoolConfig memory config_ = _copyPoolConfig(
_deployConfig
);
config_.initialVaultSharePrice = initialSharePrice;

// Deploy the target0 contract.
target = IHyperdriveTargetDeployer(target0Deployer).deploy(
config_,
_extraData,
_salt
);

// Store the deployment.
deployments[msg.sender][_deploymentId].configHash = configHash;
deployments[msg.sender][_deploymentId]
.extraDataHash = extraDataHash;
deployments[msg.sender][_deploymentId]
.initialSharePrice = initialSharePrice;
deployments[msg.sender][_deploymentId].target0 = target;

return target;
}

// Ensure that the deployment is not a fresh deployment. We can check
// this by ensuring that the config hash is set.
if (deployments[msg.sender][_deploymentId].configHash == bytes32(0)) {
revert IHyperdriveDeployerCoordinator.DeploymentDoesNotExist();
}

// Ensure that the provided config matches the config hash.
if (
keccak256(abi.encode(_deployConfig)) !=
deployments[msg.sender][_deploymentId].configHash
) {
revert IHyperdriveDeployerCoordinator.MismatchedConfig();
}

// Ensure that the provided extra data matches the extra data hash.
if (
keccak256(_extraData) !=
deployments[msg.sender][_deploymentId].extraDataHash
) {
revert IHyperdriveDeployerCoordinator.MismatchedExtraData();
}

// Convert the deploy config into the pool config and set the initial
// vault share price.
IHyperdrive.PoolConfig memory config = _copyPoolConfig(_deployConfig);
config.initialVaultSharePrice = deployments[msg.sender][_deploymentId]
.initialSharePrice;

// If the target index is greater than 0, then we're deploying one of
// the other target instances. We don't allow targets to be deployed
// more than once, and their addresses are stored in the deployment
// state.
if (_targetIndex == 1) {
if (deployments[msg.sender][_deploymentId].target1 != address(0)) {
revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed();
}
target = IHyperdriveTargetDeployer(target1Deployer).deploy(
config,
_extraData,
_salt
);
deployments[msg.sender][_deploymentId].target1 = target;
} else if (_targetIndex == 2) {
if (deployments[msg.sender][_deploymentId].target2 != address(0)) {
revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed();
}
target = IHyperdriveTargetDeployer(target2Deployer).deploy(
config,
_extraData,
target0,
target1,
target2,
target3
_salt
);
deployments[msg.sender][_deploymentId].target2 = target;
} else if (_targetIndex == 3) {
if (deployments[msg.sender][_deploymentId].target3 != address(0)) {
revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed();
}
target = IHyperdriveTargetDeployer(target3Deployer).deploy(
config,
_extraData,
_salt
);
deployments[msg.sender][_deploymentId].target3 = target;
} else if (_targetIndex == 4) {
if (deployments[msg.sender][_deploymentId].target4 != address(0)) {
revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed();
}
target = IHyperdriveTargetDeployer(target4Deployer).deploy(
config,
_extraData,
_salt
);
deployments[msg.sender][_deploymentId].target4 = target;
} else {
revert IHyperdriveDeployerCoordinator.InvalidTargetIndex();
}

return target;
}

/// @notice Checks the pool configuration to ensure that it is valid.
/// @param _deployConfig The deploy configuration of the Hyperdrive pool.
function _checkPoolConfig(
IHyperdrive.PoolDeployConfig memory _deployConfig
) internal view virtual {
// Ensure that the minimum share reserves is at least 1e3. Deployer
// coordinators should override this to be stricter.
if (_deployConfig.minimumShareReserves < 1e3) {
revert IHyperdriveDeployerCoordinator.InvalidMinimumShareReserves();
}

if (_deployConfig.checkpointDuration == 0) {
revert IHyperdriveDeployerCoordinator.InvalidCheckpointDuration();
}
if (
_deployConfig.positionDuration < _deployConfig.checkpointDuration ||
_deployConfig.positionDuration % _deployConfig.checkpointDuration !=
0
) {
revert IHyperdriveDeployerCoordinator.InvalidPositionDuration();
}

// Ensure that the fees don't exceed 100%.
if (
_deployConfig.fees.curve > 1e18 ||
_deployConfig.fees.flat > 1e18 ||
_deployConfig.fees.governanceLP > 1e18 ||
_deployConfig.fees.governanceZombie > 1e18
) {
revert IHyperdriveDeployerCoordinator.InvalidFeeAmounts();
}
}

/// @dev Gets the initial vault share price of the Hyperdrive pool.
Expand Down
Loading