-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add hardhat deployment infrastructure #2950
Changes from 45 commits
71a9559
a0911cf
4de73e2
736908d
18e8668
c44fb8c
c6c5eea
f85a2f5
921fd28
f99ea36
0217baa
6143633
1e579f9
b12cf3a
5a84fb7
14a8f85
6128cd5
19d7d51
64826ac
e1630a3
660d379
534b0b3
2760cf0
96efcd7
148b879
0e3681a
53d8b73
5484f95
4b8c9dd
ef921b2
f92108d
ace8741
c52f65b
81fd8e7
98e351c
a3205f5
f1af286
d3b7cb4
264c216
3fe8857
84faedf
9fe9977
8ce9c89
9447363
b607c9a
e9215c4
62a0cc1
c6907a5
60f992b
9375e0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const { hexlify, toUtf8Bytes } = require("ethers/utils"); | ||
|
||
function stringToBytes32(text) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, is this copied from somewhere? I feel like I recognize the code There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it from this ethers issue There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yep! I think @Tulun used this some snippet in the voter dapp. May be useful (later) to pull this out into a separate lib. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah thats why I started this Ethers.js file to start collecting common ethers utils |
||
let result = toUtf8Bytes(text); | ||
if (result.length > 32) { | ||
throw new Error("String too long"); | ||
} | ||
result = hexlify(result); | ||
while (result.length < 66) { | ||
result += "0"; | ||
} | ||
if (result.length !== 66) { | ||
throw new Error("invalid web3 implicit bytes32"); | ||
} | ||
return result; | ||
} | ||
|
||
module.exports = { | ||
stringToBytes32 | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ artifacts/ | |
flattened/ | ||
types/ | ||
contract-types/ | ||
deployments/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,6 +70,48 @@ const governor = await governor.deployed(); | |
Note: this use case is not particularly common, but it is sometimes useful to have access to multiple abi versions | ||
side-by-side. | ||
|
||
## Deployment with Hardhat | ||
|
||
Here is a list of scripts you can execute: | ||
|
||
`yarn void:deploy` | ||
|
||
This will deploy your contracts on the in-memory hardhat network and exit, leaving no trace. Quickest way to ensure that deployments work as intended without consequences. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you want to put the description above the command? makes it a bit easier to read. |
||
|
||
`yarn hardhat deploy --network <NETWORK-NAME> --tags <TAGS>` | ||
|
||
Deploy all contracts to specified network. Requires a `CUSTOM_NODE_URL` HTTP(s) endpoint and a `MNEMONIC` to be set in environment. Available contract tags can be found in `/deploy` scripts, and available networks are found in the `networks` object within `hardhat.config.js`. Tags can be powerful, for example running `yarn hardhat deploy --tags Bridge` will only deploy the Bridge contract its dependencies (such as the Finder). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. siiick |
||
|
||
`yarn hardhat deploy --tags dvm` | ||
|
||
Deploys all production DVM contracts, which doesn't include the `MockOracle` or BeaconOracles for example. | ||
|
||
`yarn hardhat deploy --tags sink-oracle <NETWORK-NAME>` | ||
|
||
Deploys minimum contracts necessary to set up Sink Oracle on L2 on the network, which would be used to deploy to Polygon for example. | ||
|
||
`yarn hardhat deploy --tags source-oracle,test <NETWORK-NAME>` | ||
|
||
Deploys minimum contracts necessary to set up Source Oracle on L1 on the network, along with test-specific versions of contracts, like the MockOracle intead of Voting. | ||
|
||
`./scripts/hardhat/verifyDeployedContracts.sh <NETWORK-NAME>` | ||
|
||
Verify contracts for selected network on Etherscan. Requires an `ETHERSCAN_API_KEY` to be set in environment. This script requires that the local `./core/deployments` has solc standard-input json files, which will be generated after running the `deploy` command. | ||
|
||
`yarn hardhat export --export-all ./networks/hardhat/deployed.json` | ||
|
||
Export deployed contract data such as ABI and addresses to `deployed.json` in order to make data available for a front-end client, for example. For example, a newly deployed `Finder` address on Rinkeby can be imported via `deployed.4.rinkeby.contracts.Finder.address`. | ||
|
||
The following commands are implemented as [hardhat tasks](https://hardhat.org/guides/create-task.html) that make it easy to interact with deployed contracts via the CLI: | ||
|
||
`yarn hardhat register-deployer --network <NETWORK-NAME>` | ||
|
||
Registers the `deployer` account (as defined in the `namedAccounts` param in `hardhat.config.js`) with the deployed Registry for the network. | ||
|
||
`yarn hardhat setup-finder --registry --bridge --generichandler --network <NETWORK-NAME>` | ||
|
||
Sets specified contracts in the deployed Finder. More contracts available to be set can be found in the `core/scripts/hardhat/tasks/finder.js` script. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the idea that most/all scripts that we currently have in scripts/ should be implemented as tasks in hardhat? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's what my crazy thought is long term. They could be ported pretty easily as scripts, which would make the CLI invocation a lot less verbose. The main benefit it seems of using tasks is you get access to the hardhat runtime environment which has a lot of nifty objects: https://hardhat.org/advanced/hardhat-runtime-environment.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you could also get the HRE in hardhat scripts too right? https://hardhat.org/guides/scripts.html Note: nothing to change now, but worth a discussion as we start to port things over. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep you can use HRE in js scripts |
||
|
||
## Typescript! | ||
|
||
In addition to the above import styles, you can import typescript types for truffle, ethers, and web3. Because of existing | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
const func = async function(hre) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm guessing we'll add to these when we add contracts to the setup (like financial contracts, collateral whitelists, etc)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup! For each contract we add extra functionality to run if the contract gets newly deployed. |
||
const { deployments, getNamedAccounts } = hre; | ||
const { deploy } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
await deploy("Finder", { | ||
from: deployer, | ||
args: [], | ||
log: true | ||
}); | ||
}; | ||
module.exports = func; | ||
func.tags = ["Finder", "dvm", "sink-oracle", "source-oracle"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const { stringToBytes32, interfaceName } = require("@uma/common"); | ||
const func = async function(hre) { | ||
const { deployments, getNamedAccounts } = hre; | ||
const { deploy, log, execute } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
const deployResult = await deploy("Registry", { | ||
from: deployer, | ||
args: [], | ||
log: true | ||
}); | ||
|
||
if (deployResult.newlyDeployed) { | ||
const txn = await execute( | ||
"Finder", | ||
{ from: deployer }, | ||
"changeImplementationAddress", | ||
stringToBytes32(interfaceName.Registry), | ||
deployResult.address | ||
); | ||
log( | ||
`Set ${interfaceName.Bridge} in Finder to deployed instance @ ${deployResult.address}, tx: ${txn.transactionHash}` | ||
); | ||
} | ||
}; | ||
module.exports = func; | ||
func.tags = ["Registry", "dvm", "sink-oracle", "source-oracle"]; | ||
func.dependencies = ["Finder"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
const { stringToBytes32, interfaceName } = require("@uma/common"); | ||
const func = async function(hre) { | ||
const { deployments, getNamedAccounts, getChainId } = hre; | ||
const { deploy, log, execute } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
const chainId = await getChainId(); | ||
|
||
const args = [ | ||
chainId, // Current chain ID. | ||
[deployer], // Initial relayers defaults to deployer as 1 of 1 | ||
1, // Relayer threshold set to 1 | ||
0, // Deposit fee | ||
100 // # of blocks after which a proposal expires | ||
]; | ||
const deployResult = await deploy("Bridge", { | ||
from: deployer, | ||
args, | ||
log: true | ||
}); | ||
|
||
if (deployResult.newlyDeployed) { | ||
const txn = await execute( | ||
"Finder", | ||
{ from: deployer }, | ||
"changeImplementationAddress", | ||
stringToBytes32(interfaceName.Bridge), | ||
deployResult.address | ||
); | ||
log( | ||
`Set ${interfaceName.Bridge} in Finder to deployed instance @ ${deployResult.address}, tx: ${txn.transactionHash}` | ||
); | ||
} | ||
}; | ||
module.exports = func; | ||
func.tags = ["Bridge", "sink-oracle", "source-oracle"]; | ||
func.dependencies = ["Finder"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
const { stringToBytes32, interfaceName } = require("@uma/common"); | ||
const func = async function(hre) { | ||
const { deployments, getNamedAccounts } = hre; | ||
const { deploy, log, execute } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
const Bridge = await deployments.get("Bridge"); | ||
|
||
const args = [Bridge.address, [], [], [], []]; | ||
const deployResult = await deploy("GenericHandler", { | ||
from: deployer, | ||
args, | ||
log: true | ||
}); | ||
|
||
if (deployResult.newlyDeployed) { | ||
const txn = await execute( | ||
"Finder", | ||
{ from: deployer }, | ||
"changeImplementationAddress", | ||
stringToBytes32(interfaceName.GenericHandler), | ||
deployResult.address | ||
); | ||
log( | ||
`Set ${interfaceName.GenericHandler} in Finder to deployed instance @ ${deployResult.address}, tx: ${txn.transactionHash}` | ||
); | ||
} | ||
}; | ||
module.exports = func; | ||
func.tags = ["GenericHandler", "sink-oracle", "source-oracle"]; | ||
func.dependencies = ["Bridge", "Finder"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
require("dotenv").config(); | ||
// Default source oracle chain ID is 1, corresponding to the mainnet network. | ||
const SOURCE_ORACLE_CHAIN_ID = process.env.SOURCE_ORACLE_CHAIN_ID || 1; | ||
|
||
const func = async function(hre) { | ||
const { deployments, getNamedAccounts, getChainId } = hre; | ||
const { deploy } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
const chainId = await getChainId(); | ||
const Finder = await deployments.get("Finder"); | ||
|
||
const args = [ | ||
Finder.address, | ||
chainId, // Current chain ID. | ||
SOURCE_ORACLE_CHAIN_ID // Chain ID where SourceOracle is located that this SinkOracle will make price requests to. | ||
]; | ||
await deploy("SinkOracle", { | ||
from: deployer, | ||
args, | ||
log: true | ||
}); | ||
}; | ||
module.exports = func; | ||
func.tags = ["SinkOracle", "sink-oracle"]; | ||
func.dependencies = ["Finder"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const func = async function(hre) { | ||
const { deployments, getNamedAccounts, getChainId } = hre; | ||
const { deploy } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
const chainId = await getChainId(); | ||
const Finder = await deployments.get("Finder"); | ||
|
||
const args = [ | ||
Finder.address, | ||
chainId // Current chain ID. | ||
]; | ||
await deploy("SourceOracle", { | ||
from: deployer, | ||
args, | ||
log: true | ||
}); | ||
}; | ||
module.exports = func; | ||
func.tags = ["SourceOracle", "source-oracle"]; | ||
func.dependencies = ["Finder"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const { stringToBytes32, interfaceName } = require("@uma/common"); | ||
const func = async function(hre) { | ||
const { deployments, getNamedAccounts } = hre; | ||
const { deploy, log, execute } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
const deployResult = await deploy("IdentifierWhitelist", { | ||
from: deployer, | ||
args: [], | ||
log: true | ||
}); | ||
|
||
if (deployResult.newlyDeployed) { | ||
const txn = await execute( | ||
"Finder", | ||
{ from: deployer }, | ||
"changeImplementationAddress", | ||
stringToBytes32(interfaceName.IdentifierWhitelist), | ||
deployResult.address | ||
); | ||
log( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. some scripts have logs and others dont. should we make that more consistent? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So my rule has been to only |
||
`Set ${interfaceName.IdentifierWhitelist} in Finder to deployed instance @ ${deployResult.address}, tx: ${txn.transactionHash}` | ||
); | ||
} | ||
}; | ||
module.exports = func; | ||
func.tags = ["IdentifierWhitelist", "dvm"]; | ||
func.dependencies = ["Finder"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
const { stringToBytes32, interfaceName, ZERO_ADDRESS } = require("@uma/common"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the mock oracle only expected to be deployed on a test L1? Do you think we should have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes we should totally use |
||
|
||
const func = async function(hre) { | ||
const { deployments, getNamedAccounts } = hre; | ||
const { deploy, log, execute } = deployments; | ||
|
||
const { deployer } = await getNamedAccounts(); | ||
|
||
const Finder = await deployments.get("Finder"); | ||
|
||
const args = [Finder.address, ZERO_ADDRESS]; | ||
const deployResult = await deploy("MockOracleAncillary", { | ||
from: deployer, | ||
args, | ||
log: true | ||
}); | ||
|
||
if (deployResult.newlyDeployed) { | ||
const txn = await execute( | ||
"Finder", | ||
{ from: deployer }, | ||
"changeImplementationAddress", | ||
stringToBytes32(interfaceName.MockOracleAncillary), | ||
deployResult.address | ||
); | ||
log( | ||
`Set ${interfaceName.MockOracleAncillary} in Finder to deployed instance @ ${deployResult.address}, tx: ${txn.transactionHash}` | ||
); | ||
} | ||
}; | ||
module.exports = func; | ||
func.tags = ["MockOracle", "test"]; | ||
func.dependencies = ["Finder", "IdentifierWhitelist"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mrice32 I don't think there's any risk to adding this mapping here, which is convenient for the
core/scripts/hardhat/finder.js
script but wanted to highlight it.