Skip to content
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

Merged
merged 50 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
71a9559
WIP
nicholaspai Apr 26, 2021
a0911cf
Updates
nicholaspai Apr 26, 2021
4de73e2
Merge branch 'master' into npai/beacon-oracle
nicholaspai Apr 28, 2021
736908d
reorg contracts
nicholaspai Apr 29, 2021
18e8668
refactored Source and Sink oracle to be much simpler, updated tests
nicholaspai Apr 29, 2021
c44fb8c
Set resource ID inside contract
nicholaspai Apr 29, 2021
c6c5eea
Add destinationChainId to sinkOracle constructor
nicholaspai Apr 29, 2021
f85a2f5
mvp works; need to refactor and comment
nicholaspai Apr 29, 2021
921fd28
clean up tests
nicholaspai Apr 29, 2021
f99ea36
Sink and Source should use same resource ID, link by source chain ID
nicholaspai Apr 30, 2021
0217baa
Update packages/core/contracts/chainbridge/BeaconOracle.sol
nicholaspai May 3, 2021
6143633
respond to matt's comments
nicholaspai May 3, 2021
1e579f9
WIP
nicholaspai May 4, 2021
b12cf3a
Add chainID to price request hash key
nicholaspai May 4, 2021
5a84fb7
refactor has/getPrice
nicholaspai May 4, 2021
14a8f85
Merge branch 'npai/beacon-oracle' into npai/beacon-oracle-unit-tests
nicholaspai May 4, 2021
6128cd5
finish tests
nicholaspai May 4, 2021
19d7d51
Update sourceOracle.js
nicholaspai May 5, 2021
64826ac
remove OracleAncillaryInterface from SourceOracle
nicholaspai May 5, 2021
e1630a3
Merge branch 'npai/beacon-oracle' into npai/beacon-oracle-unit-tests
nicholaspai May 5, 2021
660d379
change tests
nicholaspai May 5, 2021
534b0b3
Merge branch 'master' into npai/beacon-oracle-unit-tests
nicholaspai May 5, 2021
2760cf0
bump BeaconMock to 0.8
nicholaspai May 5, 2021
96efcd7
WIP
nicholaspai May 5, 2021
148b879
Merge branch 'master' into npai/beacon-deployment
nicholaspai May 6, 2021
0e3681a
Update package.json
nicholaspai May 6, 2021
53d8b73
add demo tasks
nicholaspai May 6, 2021
5484f95
remove ts-compile
nicholaspai May 6, 2021
4b8c9dd
Merge branch 'master' into npai/beacon-deployment
nicholaspai May 7, 2021
ef921b2
Add idWhitelist and MockOracle
nicholaspai May 7, 2021
f92108d
Update scripts and readmes
nicholaspai May 7, 2021
ace8741
Remove hardhat from core/package.json
nicholaspai May 7, 2021
c52f65b
Lint
nicholaspai May 7, 2021
81fd8e7
Update PublicNetworks.js
nicholaspai May 7, 2021
98e351c
Update finder.js
nicholaspai May 7, 2021
a3205f5
Update README.md
nicholaspai May 7, 2021
f1af286
WIP
nicholaspai May 7, 2021
d3b7cb4
Update scripts and README
nicholaspai May 7, 2021
264c216
lint-fix
nicholaspai May 7, 2021
3fe8857
add tags
nicholaspai May 7, 2021
84faedf
Add tags to distinguish between production, sink-oracle, source-oracle
nicholaspai May 7, 2021
9fe9977
Remove deployed.json
nicholaspai May 7, 2021
8ce9c89
use tags instead of deploy.sh script
nicholaspai May 7, 2021
9447363
Update 008_mock_oracle.js
nicholaspai May 8, 2021
b607c9a
Change "production" tag to "dvm"
nicholaspai May 8, 2021
e9215c4
Update README.md
nicholaspai May 10, 2021
62a0cc1
Add identifiers.js task
nicholaspai May 10, 2021
c6907a5
Lint
nicholaspai May 10, 2021
60f992b
Add Mumbai net ID
nicholaspai May 10, 2021
9375e0c
lint
nicholaspai May 10, 2021
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: 2 additions & 1 deletion packages/common/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ module.exports = {
...require("./src/TimeUtils"),
...require("./src/VotingUtils"),
...require("./src/PriceIdentifierUtils"),
...require("./src/MultiVersionTestHelpers.js")
...require("./src/MultiVersionTestHelpers.js"),
...require("./src/Ethers.js")
};
3 changes: 2 additions & 1 deletion packages/common/src/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const interfaceName = {
FundingRateStore: "FundingRateStore",
OptimisticOracle: "OptimisticOracle",
Bridge: "Bridge",
GenericHandler: "GenericHandler"
GenericHandler: "GenericHandler",
MockOracleAncillary: "Oracle"
Copy link
Member Author

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.

};

module.exports = {
Expand Down
20 changes: 20 additions & 0 deletions packages/common/src/Ethers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { hexlify, toUtf8Bytes } = require("ethers/utils");

function stringToBytes32(text) {
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it from this ethers issue

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
};
4 changes: 4 additions & 0 deletions packages/common/src/PublicNetworks.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const PublicNetworks = {
daiAddress: "0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
wethAddress: "0xc778417E063141139Fce010982780140Aa0cD5Ab"
},
5: {
name: "goerli",
etherscan: "https://goerli.etherscan.io/"
},
42: {
name: "kovan",
ethFaucet: "https://faucet.kovan.network/",
Expand Down
9 changes: 6 additions & 3 deletions packages/common/src/TruffleConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ const gasPx = argv.gasPrice ? Web3.utils.toWei(argv.gasPrice, "gwei") : 20000000
const gas = undefined; // Defining this as undefined (rather than leaving undefined) forces truffle estimate gas usage.

// If a custom node URL is provided, use that. Otherwise use an infura websocket connection.
function getNodeUrl(networkName) {
function getNodeUrl(networkName, useHttps = false) {
if (isPublicNetwork(networkName) && !networkName.includes("fork")) {
const infuraApiKey = process.env.INFURA_API_KEY || "e34138b2db5b496ab5cc52319d2f0299";
const name = networkName.split("_")[0];
return process.env.CUSTOM_NODE_URL || `wss://${name}.infura.io/ws/v3/${infuraApiKey}`;
return (
process.env.CUSTOM_NODE_URL ||
(useHttps ? `https://${name}.infura.io/v3/${infuraApiKey}` : `wss://${name}.infura.io/ws/v3/${infuraApiKey}`)
);
}

const port = process.env.CUSTOM_LOCAL_NODE_PORT || "9545";
Expand Down Expand Up @@ -212,4 +215,4 @@ function getTruffleConfig(truffleContextDir = "./") {
};
}

module.exports = { getTruffleConfig, getNodeUrl };
module.exports = { getTruffleConfig, getNodeUrl, mnemonic };
1 change: 1 addition & 0 deletions packages/core/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ artifacts/
flattened/
types/
contract-types/
deployments/
42 changes: 42 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The 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).
Copy link
Member

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

@mrice32 mrice32 May 7, 2021

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand Down
14 changes: 14 additions & 0 deletions packages/core/deploy/001_deploy_finder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const func = async function(hre) {
Copy link
Member

Choose a reason for hiding this comment

The 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)?

Copy link
Member Author

Choose a reason for hiding this comment

The 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"];
29 changes: 29 additions & 0 deletions packages/core/deploy/002_deploy_registry.js
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"];
38 changes: 38 additions & 0 deletions packages/core/deploy/003_deploy_bridge.js
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"];
32 changes: 32 additions & 0 deletions packages/core/deploy/004_deploy_generic_handler.js
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"];
27 changes: 27 additions & 0 deletions packages/core/deploy/005_deploy_sink_oracle.js
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"];
22 changes: 22 additions & 0 deletions packages/core/deploy/006_deploy_source_oracle.js
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"];
29 changes: 29 additions & 0 deletions packages/core/deploy/007_identifier_whitelist.js
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(
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So my rule has been to only log if there is additional work being done besides the deployment itself. For example, this script adds the IDWhitelist to the Finder.

`Set ${interfaceName.IdentifierWhitelist} in Finder to deployed instance @ ${deployResult.address}, tx: ${txn.transactionHash}`
);
}
};
module.exports = func;
func.tags = ["IdentifierWhitelist", "dvm"];
func.dependencies = ["Finder"];
33 changes: 33 additions & 0 deletions packages/core/deploy/008_mock_oracle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { stringToBytes32, interfaceName, ZERO_ADDRESS } = require("@uma/common");
Copy link
Member

Choose a reason for hiding this comment

The 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 prod tag and a test tag or something so we can deploy the entire test setup or the entire prod setup? Also, are you allowed to provide multiple tags on deployment? If so, how do they mix (union or intersection of the tagged scripts?)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes we should totally use prod and test tags. I'm thinking we give each contract two tags at least, its name (for use as a dependency in other deployment scripts) and a prod tag. This way we can leave out the prod tag from the MockOracle so we can just run yarn hardhat deploy --tags prod to deploy all prod contracts


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"];
Loading