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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@ prettier:

deploy:
./scripts/deploy.sh

generate-deploy:
./scripts/generate-deploy-config.sh
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ If you want to automatically format the code, run:
make prettier
```


## Deploy

To deploy the smart contracts, run:
Expand All @@ -127,6 +126,28 @@ To deploy the smart contracts, run:
NETWORK=<hardhat|localhost|sepolia|mainnet> make deploy
```

### Generate Deploy Configurations

Factory, coordinator, and instance deploy configurations can be generated via templates.

To get started, create a new variable file from the sample by running:

```sh
cp \
tasks/deploy/config/<factory|coordinator|instance>.sample.env \
<factory|coordinator|instance>.env
```

Next, set values for each of the fields in the file `<factory|coordinator|instance>.env`. Sample values are provided as examples, but should be overwritten.

Finally, to generate the configuration files run:

```sh
make generate-deploy
```

The above command will output next steps to integrate the new configuration files with existing configuration.

# Disclaimer

The language used in this code and documentation is not intended to, and does not, have any particular financial, legal, or regulatory significance.
Expand Down
141 changes: 141 additions & 0 deletions scripts/generate-deploy-config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/bin/bash

set -e

config_dir=tasks/deploy/config

# Store names of generated configurations for integration instructions at end of script.
factories=()
coordinators=()
instances=()

# Non-destructively updates the `index.ts` file for the network and global config.
function update_indexes() {
local network=$1
echo " - updating $config_dir/$network/index.ts"
echo "" >"$config_dir/$network/index.ts"
for file in "$config_dir/$network"/*; do
base=$(basename $file)
if [[ "$base" != 'index.ts' ]]; then
echo "export * from \"./$(echo $base | cut -d"." -f1)\";" >>"$config_dir/$network/index.ts"
fi
done
network_export="$(grep -i "$network" <"$config_dir/index.ts" || true)"
if [[ -z $network_export ]]; then
echo " - updating $config_dir/index.ts"
echo "export * from \"./$network\";" >>"$config_dir/index.ts"
fi
}

# Generates factory from `factory.env` if present, see `echo` statements for context.
factory_input=factory.env
factory_template=$config_dir/factory.ts.tmpl
factory_output_filename=factory.ts
if [ -f $factory_input ]; then
echo "generating factory configuration..."
echo " - reading configuration from ${factory_input}"
export $(grep -v '^#' $factory_input | xargs)

# Skip generation if output file already exists.
factory_output_dir="$config_dir/$NETWORK_NAME"
factory_output_file="$factory_output_dir/$factory_output_filename"
if [ -f $factory_output_file ]; then
echo " - skipping generation, factory exists for network $NETWORK_NAME"
else
echo " - writing configuration to ${factory_output_file}"
mkdir -p $factory_output_dir
envsubst <$factory_template >$factory_output_file
update_indexes $NETWORK_NAME
factories+="${NETWORK_NAME}_FACTORY"
fi
echo "done! \n"
fi

# Generates coordinator from `coordinator.env` if present, see `echo` statements for context.
coordinator_input=coordinator.env
coordinator_template=$config_dir/coordinator.ts.tmpl
if [ -f $coordinator_input ]; then
echo "generating coordinator configuration..."
echo " - reading configuration from ${coordinator_input}"
export $(grep -v '^#' $coordinator_input | xargs)

# Skip generation if output file already exists.
coordinator_output_filename="$NAME.ts"
coordinator_output_dir="$config_dir/$NETWORK_NAME"
coordinator_output_file="$coordinator_output_dir/$coordinator_output_filename"
if [ -f $coordinator_output_file ]; then
echo " - skipping generation, coordinator exists for network $NETWORK_NAME"
else
echo " - writing configuration to ${coordinator_output_file}"
mkdir -p $coordinator_output_dir
envsubst <$coordinator_template >$coordinator_output_file
update_indexes $NETWORK_NAME
coordinators+="$NAME"
fi
echo "done! \n"
fi

# Generates instance from `instance.env` if present, see `echo` statements for context.
instance_input=instance.env
instance_template=$config_dir/instance.ts.tmpl
if [ -f $instance_input ]; then
echo "generating instance configuration..."
echo " - reading configuration from ${instance_input}"
export $(grep -v '^#' $instance_input | xargs)

# Skip generation if output file already exists.
instance_output_filename="$NAME.ts"
instance_output_dir="$config_dir/$NETWORK_NAME"
instance_output_file="$instance_output_dir/$instance_output_filename"
if [ -f $instance_output_file ]; then
echo " - skipping generation, instance exists for network $NETWORK_NAME"
else
echo " - writing configuration to ${instance_output_file}"
mkdir -p $instance_output_dir
envsubst <$instance_template >$instance_output_file
update_indexes $NETWORK_NAME
instances+="$NAME"
fi
echo "done! \n"
fi

# Outputs instructions for integrating the generated configurations with
# existing deploy configurations in `hardhat.config.ts`.
names=(${factories[@]} ${coordinators[@]} ${instances[@]})
IFS=,\n
if [ ! ${#names[@]} -eq 0 ]; then
echo "
All configurations have been generated.

Add:

${names[*]}

to the import from \"./tasks/deploy/config/\" in hardhat.config.ts

Then, merge the following into the network configuration for \"${NETWORK_NAME}\".
"
if [ ! ${#factories[@]} -eq 0 ]; then
echo "
factories: [
${factories[*]}
]
"
fi
if [ ! ${#coordinators[@]} -eq 0 ]; then
echo "
coordinators: [
${coordinators[*]}
]
"
fi
if [ ! ${#instances[@]} -eq 0 ]; then
echo "
instances: [
${instances[*]}
]
"
fi
else
echo "No configuration was generated."
fi
10 changes: 10 additions & 0 deletions tasks/deploy/config/coordinator.sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
NETWORK_NAME=<sepolia | base_sepolia | mainnet>
NAME=<some name>_COORDINATOR
PREFIX=<ERC4626 | StETH | RETH | EzETH>

# Token address to include in the constructor for non-ERC4626 coordinators.
VAULT_SHARES_TOKEN_ADDRESS=0x...

# Extra argument passed to coordinator (and all target deployer) constructors.
# Only necessary for coordinators with prefix 'EzETH'.
EXTRA_CONSTRUCTOR_ARG=0xd94a3A0BfC798b98a700a785D5C610E8a2d5DBD8
11 changes: 11 additions & 0 deletions tasks/deploy/config/coordinator.ts.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { HyperdriveCoordinatorConfig } from "../../lib";

export const ${NAME}_COORDINATOR: HyperdriveCoordinatorConfig<"$PREFIX"> = {
name: "${NAME}_COORDINATOR".toUpperCase(),
prefix: "$PREFIX",
factoryAddress: async (hre) =>
hre.hyperdriveDeploy.deployments.byName("FACTORY").address,
targetCount: 4,
extraConstructorArgs: ["$EXTRA_CONSTRUCTOR_ARG"],
token: "$PREFIX" === "ERC4626" ? undefined : "$VAULT_SHARES_TOKEN_ADDRESS",
};
21 changes: 21 additions & 0 deletions tasks/deploy/config/factory.sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
NETWORK_NAME=<sepolia | base_sepolia | mainnet>
GOV_ADDRESS=0x...
CHECKPOINT_DURATION_RESOLUTION_HOURS=8
MIN_CHECKPOINT_DURATION_HOURS=24
MAX_CHECKPOINT_DURATION_HOURS=24
MIN_POSITION_DURATION_DAYS=7
MAX_POSITION_DURATION_DAYS=365
MIN_FIXED_APR=0.01
MAX_FIXED_APR=0.6
MIN_TIMESTRETCH_APR=0.01
MAX_TIMESTRETCH_APR=0.6
MIN_CIRCUIT_BREAKER_DELTA=0.5
MAX_CIRCUIT_BREAKER_DELTA=1
MIN_CURVE_FEE=0.001
MAX_CURVE_FEE=0.0001
MIN_FLAT_FEE=0.15
MAX_FLAT_FEE=0.03
MIN_GOV_LP_FEE=0.05
MAX_GOV_LP_FEE=0.005
MIN_GOV_ZOMBIE_FEE=0.15
MAX_GOV_ZOMBIE_FEE=0.03
66 changes: 66 additions & 0 deletions tasks/deploy/config/factory.ts.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { parseEther } from "viem";
import { HyperdriveFactoryConfig, parseDuration } from "../../lib";

export const ${NETWORK_NAME}_FACTORY: HyperdriveFactoryConfig = {
name: "FACTORY",
prepare: async (hre, options) => {
await hre.hyperdriveDeploy.ensureDeployed(
"FACTORY_FORWARDER",
"ERC20ForwarderFactory",
[],
options,
);
},
constructorArguments: async (hre) => [
{
governance: "$GOV_ADDRESS",
deployerCoordinatorManager: (await hre.getNamedAccounts())['deployer'],
hyperdriveGovernance: "$GOV_ADDRESS",
defaultPausers: ["$GOV_ADDRESS"],
feeCollector: "$GOV_ADDRESS",
sweepCollector: "$GOV_ADDRESS",
checkpointDurationResolution: parseDuration(
"$CHECKPOINT_DURATION_RESOLUTION_HOURS hours",
),
minCheckpointDuration: parseDuration(
"$MIN_CHECKPOINT_DURATION_HOURS hours",
),
maxCheckpointDuration: parseDuration(
"$MAX_CHECKPOINT_DURATION_HOURS hours",
),
minPositionDuration: parseDuration(
"$MIN_POSITION_DURATION_DAYS days",
),
maxPositionDuration: parseDuration("$MAX_POSITION_DURATION_DAYS days"),
minFixedAPR: parseEther("$MIN_FIXED_APR"),
maxFixedAPR: parseEther("$MAX_FIXED_APR"),
minTimeStretchAPR: parseEther("$MIN_TIMESTRETCH_APR"),
maxTimeStretchAPR: parseEther("$MAX_FIXED_APR"),
minCircuitBreakerDelta: parseEther("$MIN_CIRCUIT_BREAKER_DELTA"),
maxCircuitBreakerDelta: parseEther("$MAX_CIRCUIT_BREAKER_DELTA"),
minFees: {
curve: parseEther("$MIN_CURVE_FEE"),
flat: parseEther("$MIN_FLAT_FEE"),
governanceLP: parseEther("$MIN_GOV_LP_FEE"),
governanceZombie: parseEther("$MIN_GOV_ZOMBIE_FEE"),
},
maxFees: {
curve: parseEther("$MAX_CURVE_FEE"),
flat: parseEther("$MAX_FLAT_FEE"),
governanceLP: parseEther("$MAX_GOV_LP_FEE"),
governanceZombie: parseEther("$MAX_GOV_ZOMBIE_FEE"),
},
linkerFactory:
hre.hyperdriveDeploy.deployments.byName("FACTORY_FORWARDER")
.address,
linkerCodeHash: await (
await hre.viem.getContractAt(
"ERC20ForwarderFactory",
hre.hyperdriveDeploy.deployments.byName("FACTORY_FORWARDER")
.address,
)
).read.ERC20LINK_HASH(),
},
"FACTORY",
],
};
20 changes: 20 additions & 0 deletions tasks/deploy/config/instance.sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
NETWORK_NAME=<sepolia | base_sepolia | mainnet>
NAME=<token symbol>-<duration>
PREFIX=<ERC4626 | StETH | RETH | EzETH>
COORDINATOR_NAME=<obtained from coordinator config file for network>
SALT=0x...
CONTRIBUTION=500
FIXED_APR=0.05
TIMESTRETCH_APR=0.05
AS_BASE=false
BASE_TOKEN=0x...
VAULT_SHARES_TOKEN=0x...
CIRCUIT_BREAKER_DELTA=0.6
MIN_SHARE_RESERVES=0.001
MIN_TX_AMOUNT=0.001
POSITION_DURATION_DAYS=30
CHECKPOINT_DURATION_DAYS=1
CURVE_FEE=0.01
FLAT_FEE=0.0005
GOV_LP_FEE=0.15
GOV_ZOMBIE_FEE=0.03
56 changes: 56 additions & 0 deletions tasks/deploy/config/instance.ts.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { parseEther } from "viem";
import {
HyperdriveInstanceConfig,
getLinkerDetails,
normalizeFee,
parseDuration,
toBytes32,
} from "../../lib";

const CONTRIBUTION = parseEther("$CONTRIBUTION");

export const $NAME: HyperdriveInstanceConfig<"$PREFIX"> = {
name: "$NAME",
prefix: "$PREFIX",
coordinatorAddress: async (hre) =>
hre.hyperdriveDeploy.deployments.byName("$COORDINATOR_NAME").address,
deploymentId: toBytes32("$NAME"),
salt: toBytes32("$SALT"),
extraData: "0x",
contribution: CONTRIBUTION,
fixedAPR: parseEther("$FIXED_APR"),
timestretchAPR: parseEther("$TIMESTRETCH_APR"),
options: async (hre) => ({
destination: (await hre.getNamedAccounts())['deployer'] as any,
asBase: $AS_BASE,
extraData: "0x",
}),
poolDeployConfig: async (hre) => {
let factoryAddress = hre.hyperdriveDeploy.deployments.byName("FACTORY").address;
let factoryContract = await hre.viem.getContractAt("HyperdriveFactory", factoryAddress);
let govAddress = await factoryContract.read.governance();
return {
baseToken: "$BASE_TOKEN",
vaultSharesToken: "$VAULT_SHARES_TOKEN",
circuitBreakerDelta: parseEther("$CIRCUIT_BREAKER_DELTA"),
minimumShareReserves: parseEther("$MIN_SHARE_RESERVES"),
minimumTransactionAmount: parseEther("$MIN_TX_AMOUNT"),
positionDuration: parseDuration("$POSITION_DURATION_DAYS days"),
checkpointDuration: parseDuration("$CHECKPOINT_DURATION_DAYS days"),
timeStretch: 0n,
governance: govAddress,
feeCollector: govAddress,
sweepCollector: govAddress,
...(await getLinkerDetails(
hre,
factoryAddress,
)),
fees: {
curve: parseEther("$CURVE_FEE"),
flat: normalizeFee(parseEther("FLAT_FEE"), "$POSITION_DURATION_DAYS days"),
governanceLP: parseEther("GOV_LP_FEE"),
governanceZombie: parseEther("GOV_ZOMBIE_FEE"),
},
};
},
};
1 change: 0 additions & 1 deletion tasks/deploy/config/sepolia/erc4626-coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export const SEPOLIA_ERC4626_COORDINATOR: HyperdriveCoordinatorConfig<"ERC4626">
name: "ERC4626_COORDINATOR",
prefix: "ERC4626",
targetCount: 4,
extraConstructorArgs: [],
factoryAddress: async (hre) =>
hre.hyperdriveDeploy.deployments.byName("FACTORY").address,
};
1 change: 0 additions & 1 deletion tasks/deploy/config/sepolia/reth-coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const SEPOLIA_RETH_COORDINATOR: HyperdriveCoordinatorConfig<"RETH"> = {
factoryAddress: async (hre) =>
hre.hyperdriveDeploy.deployments.byName("FACTORY").address,
targetCount: 4,
extraConstructorArgs: [],
prepare: async (hre, options) => {
let pc = await hre.viem.getPublicClient();
let deployer = (await hre.getNamedAccounts())["deployer"];
Expand Down
1 change: 0 additions & 1 deletion tasks/deploy/config/sepolia/steth-coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const SEPOLIA_STETH_COORDINATOR: HyperdriveCoordinatorConfig<"StETH"> = {
factoryAddress: async (hre) =>
hre.hyperdriveDeploy.deployments.byName("FACTORY").address,
targetCount: 4,
extraConstructorArgs: [],
prepare: async (hre, options) => {
let deployer = (await hre.getNamedAccounts())["deployer"];
let pc = await hre.viem.getPublicClient();
Expand Down