From 4f4bec508e1f0b800b9aca9652b6f48be1e43ff3 Mon Sep 17 00:00:00 2001 From: Tom Pointon Date: Fri, 13 May 2022 09:25:35 +0100 Subject: [PATCH] EVM deployment scripts and documentation (#141) * Make Migrations contract Ownable * Add prod deployment migrations * Add BSC Testnet deployment configuration * Add documentation for deploying contracts to production * Fix test environment script for deployment to development network * Fix tilt issues + refactor Co-authored-by: Ali Behjati --- devnet/eth-devnet.yaml | 6 +- ethereum/.dockerignore | 3 + ethereum/.env.prod.binance_testnet | 7 +++ ethereum/.env.prod.development | 8 +++ ethereum/.env.test | 4 +- ethereum/Deploying.md | 30 +++++++++ ethereum/contracts/Migrations.sol | 19 +++--- .../migrations/prod/1_initial_migration.js | 12 ++++ ethereum/migrations/prod/2_deploy_pyth.js | 35 +++++++++++ ethereum/migrations/test/5_deploy_pyth.js | 4 +- ethereum/package-lock.json | 62 ++++++++++++++++--- ethereum/package.json | 3 +- ethereum/truffle-config.js | 8 ++- 13 files changed, 170 insertions(+), 31 deletions(-) create mode 100644 ethereum/.env.prod.binance_testnet create mode 100644 ethereum/.env.prod.development create mode 100644 ethereum/Deploying.md create mode 100644 ethereum/migrations/prod/1_initial_migration.js create mode 100644 ethereum/migrations/prod/2_deploy_pyth.js diff --git a/devnet/eth-devnet.yaml b/devnet/eth-devnet.yaml index 82e5e9f5..3ba70695 100644 --- a/devnet/eth-devnet.yaml +++ b/devnet/eth-devnet.yaml @@ -73,9 +73,9 @@ spec: command: - /bin/sh - -c - - "npm run migrate && - npx truffle test test/pyth.js 2>&1 && - nc -lkp 2000 0.0.0.0" + - "npm run migrate --network development && + npx truffle test test/pyth.js 2>&1 && + nc -lkp 2000 0.0.0.0" readinessProbe: periodSeconds: 1 failureThreshold: 300 diff --git a/ethereum/.dockerignore b/ethereum/.dockerignore index dd87e2d7..0364bb2d 100644 --- a/ethereum/.dockerignore +++ b/ethereum/.dockerignore @@ -1,2 +1,5 @@ node_modules build +.openzeppelin +networks +.env \ No newline at end of file diff --git a/ethereum/.env.prod.binance_testnet b/ethereum/.env.prod.binance_testnet new file mode 100644 index 00000000..0264d90a --- /dev/null +++ b/ethereum/.env.prod.binance_testnet @@ -0,0 +1,7 @@ +MIGRATIONS_DIR=./migrations/prod +MIGRATIONS_NETWORK=binance_testnet + +#Pyth +WORMHOME_BRIDGE_ADDRESS=0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D +PYTH_TO_WORMHOLE_CHAIN_ID=0x1 +PYTH_TO_WORMHOLE_EMITTER=0xf346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0 diff --git a/ethereum/.env.prod.development b/ethereum/.env.prod.development new file mode 100644 index 00000000..90109a62 --- /dev/null +++ b/ethereum/.env.prod.development @@ -0,0 +1,8 @@ +# Migrations Metadata +MIGRATIONS_DIR=./migrations/prod +MIGRATIONS_NETWORK=development + +#Pyth +WORMHOME_BRIDGE_ADDRESS=0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D +PYTH_TO_WORMHOLE_CHAIN_ID=0x1 +PYTH_TO_WORMHOLE_EMITTER=0x6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25 diff --git a/ethereum/.env.test b/ethereum/.env.test index 8f693ff8..4abcc8e8 100644 --- a/ethereum/.env.test +++ b/ethereum/.env.test @@ -14,6 +14,6 @@ BRIDGE_INIT_GOV_CHAIN_ID=0x1 BRIDGE_INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004 BRIDGE_INIT_WETH=0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E -#Pyth Migrations +#Pyth PYTH_TO_WORMHOLE_CHAIN_ID=0x1 -PYTH_TO_WORMHOLE_EMITTER=8fuAZUxHecYLMC76ZNjYzwRybUiDv9LhkRQsAccEykLr +PYTH_TO_WORMHOLE_EMITTER=0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b diff --git a/ethereum/Deploying.md b/ethereum/Deploying.md new file mode 100644 index 00000000..ba8035de --- /dev/null +++ b/ethereum/Deploying.md @@ -0,0 +1,30 @@ +# Deploying Contracts to Production + +Running the Truffle migrations in [`migrations/prod`](migrations/prod) will deploy the contracts to production. Truffle stores the address of the deployed contracts in the build artifacts, which can make local development difficult. We use [`truffle-deploy-registry`](https://github.com/MedXProtocol/truffle-deploy-registry) to store the addresses separately from the artifacts, in the [`networks`](networks) directory. When we need to perform operations on the deployed contracts, such as performing additional migrations, we can run `apply-registry -n networks/$NETWORK` to populate the artifacts with the correct addresses. + +An example deployment process, for deploying to Binance Smart Chain Testnet: + +```bash +# Load the configuration environment variables for deploying to BSC Testnet. +rm -f .env; ln -s .env.prod.binance_testnet .env && set -o allexport && source .env set && set +o allexport + +# The Secret Recovery Phrase for the wallet the contract will be deployed from. +export MNEMONIC=... + +# Ensure that we deploy a fresh build with up-to-date dependencies. +rm -rf build .openzeppelin node_modules && npm install && npx truffle compile --all + +# Merge the network addresses into the artifacts, if some contracts are already deployed. +npx apply-registry -n networks/$MIGRATIONS_NETWORK + +# Perform the migration +npx truffle migrate --network $MIGRATIONS_NETWORK + +# Running the migration will cause a JSON file to be written to the networks/ +# directory, with a filename like 1648198934288.json (the Truffle network ID). +# To make it more obvious which network this corresponds to, move this file +# to networks/$MIGRATIONS_NETWORK. +mkdir -p networks/$MIGRATIONS_NETWORK && mv networks/NETWORK__ID.json networks/$MIGRATIONS_NETWORK +``` + +As a sanity check, it is recommended to deploy the migrations in `migrations/prod` to the Truffle `development` network first. You can do this by using the configuration values in [`.env.prod.development`](.env.prod.development). diff --git a/ethereum/contracts/Migrations.sol b/ethereum/contracts/Migrations.sol index 51afb196..e7d1dbcd 100644 --- a/ethereum/contracts/Migrations.sol +++ b/ethereum/contracts/Migrations.sol @@ -1,19 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; -contract Migrations { - address public owner = msg.sender; +import "@openzeppelin/contracts/access/Ownable.sol"; + +// Needed for truffle migrate to work correctly. +// Simply stores the last completed migration. +contract Migrations is Ownable { uint public last_completed_migration; - modifier restricted() { - require( - msg.sender == owner, - "This function is restricted to the contract's owner" - ); - _; - } - - function setCompleted(uint completed) public restricted { + function setCompleted(uint completed) public onlyOwner { last_completed_migration = completed; } -} \ No newline at end of file +} diff --git a/ethereum/migrations/prod/1_initial_migration.js b/ethereum/migrations/prod/1_initial_migration.js new file mode 100644 index 00000000..ec354c0e --- /dev/null +++ b/ethereum/migrations/prod/1_initial_migration.js @@ -0,0 +1,12 @@ +const Migrations = artifacts.require("Migrations"); + +const tdr = require('truffle-deploy-registry'); + +module.exports = async function (deployer, network) { + await deployer.deploy(Migrations); + let migrationsInstance = await Migrations.deployed(); + + if (!tdr.isDryRunNetworkName(network)) { + await tdr.appendInstance(migrationsInstance); + } +}; diff --git a/ethereum/migrations/prod/2_deploy_pyth.js b/ethereum/migrations/prod/2_deploy_pyth.js new file mode 100644 index 00000000..7564f951 --- /dev/null +++ b/ethereum/migrations/prod/2_deploy_pyth.js @@ -0,0 +1,35 @@ +require('dotenv').config({ path: "../.env" }); +const bs58 = require("bs58"); + +const PythUpgradable = artifacts.require("PythUpgradable"); + +const wormholeBridgeAddress = process.env.WORMHOME_BRIDGE_ADDRESS; +const pyth2WormholeChainId = process.env.PYTH_TO_WORMHOLE_CHAIN_ID; +const pyth2WormholeEmitter = process.env.PYTH_TO_WORMHOLE_EMITTER; + +const { deployProxy } = require("@openzeppelin/truffle-upgrades"); +const tdr = require('truffle-deploy-registry'); + +console.log("Wormhole bridge address: " + wormholeBridgeAddress) +console.log("pyth2WormholeEmitter: " + pyth2WormholeEmitter) +console.log("pyth2WormholeChainId: " + pyth2WormholeChainId) + +module.exports = async function (deployer, network) { + // Deploy the proxy. This will return an instance of PythUpgradable, + // with the address field corresponding to the fronting ERC1967Proxy. + let proxyInstance = await deployProxy(PythUpgradable, + [ + wormholeBridgeAddress, + pyth2WormholeChainId, + pyth2WormholeEmitter + ], + { deployer }); + + // Add the ERC1967Proxy address to the PythUpgradable contract's + // entry in the registry. This allows us to call upgradeProxy + // functions with the value of PythUpgradable.deployed().address: + // e.g. upgradeProxy(PythUpgradable.deployed().address, NewImplementation) + if (!tdr.isDryRunNetworkName(network)) { + await tdr.appendInstance(proxyInstance); + } +}; diff --git a/ethereum/migrations/test/5_deploy_pyth.js b/ethereum/migrations/test/5_deploy_pyth.js index ca842ef1..689b7e58 100644 --- a/ethereum/migrations/test/5_deploy_pyth.js +++ b/ethereum/migrations/test/5_deploy_pyth.js @@ -5,7 +5,7 @@ const PythUpgradable = artifacts.require("PythUpgradable"); const Wormhole = artifacts.require("Wormhole"); const pyth2WormholeChainId = process.env.PYTH_TO_WORMHOLE_CHAIN_ID; -const pyth2WormholeEmitter = bs58.decode(process.env.PYTH_TO_WORMHOLE_EMITTER); // base58, must fit into bytes32 +const pyth2WormholeEmitter = process.env.PYTH_TO_WORMHOLE_EMITTER; const { deployProxy } = require("@openzeppelin/truffle-upgrades"); @@ -17,7 +17,7 @@ module.exports = async function (deployer) { [ (await Wormhole.deployed()).address, pyth2WormholeChainId, - "0x" + pyth2WormholeEmitter.toString("hex") + pyth2WormholeEmitter ], { deployer }); }; diff --git a/ethereum/package-lock.json b/ethereum/package-lock.json index 9f84a55a..737d32d5 100644 --- a/ethereum/package-lock.json +++ b/ethereum/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.2", + "@pythnetwork/pyth-sdk-solidity": "0.0.3", "dotenv": "^10.0.0", "elliptic": "^6.5.2", "ganache-cli": "^6.12.1", @@ -19,7 +21,6 @@ "devDependencies": { "@chainsafe/truffle-plugin-abigen": "0.0.1", "@openzeppelin/cli": "^2.8.2", - "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/test-environment": "^0.1.9", "@openzeppelin/test-helpers": "^0.5.15", "@openzeppelin/truffle-upgrades": "^1.14.0", @@ -29,6 +30,7 @@ "mocha": "^8.2.1", "truffle": "^5.5.5", "truffle-assertions": "^0.9.2", + "truffle-deploy-registry": "^0.5.1", "truffle-plugin-verify": "^0.5.11" } }, @@ -2030,8 +2032,7 @@ "node_modules/@openzeppelin/contracts": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.5.0.tgz", - "integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA==", - "dev": true + "integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA==" }, "node_modules/@openzeppelin/contracts-upgradeable": { "version": "4.5.2", @@ -3672,6 +3673,11 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "dev": true }, + "node_modules/@pythnetwork/pyth-sdk-solidity": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.0.3.tgz", + "integrity": "sha512-4FaoGD4Sizvdm0+8KuQsYzARHa+myNwmFE2GBYxmMcw3zMtwvD7PH0RWM8nK9UyExJ+vyAAth7SbVzyLfN4Yow==" + }, "node_modules/@redux-saga/core": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.1.3.tgz", @@ -32672,6 +32678,27 @@ "node": ">=4" } }, + "node_modules/truffle-deploy-registry": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/truffle-deploy-registry/-/truffle-deploy-registry-0.5.1.tgz", + "integrity": "sha512-ZvyRpn67J67cWTfN5Z+sIryOirzxOuvpS7WO1WJYx3V1GcJR0gWWEM38KTBoPQ5L98/n7XxWC8+TtkOypKEp6A==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "commander": "^2.19.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.3" + }, + "bin": { + "apply-registry": "lib/command.js" + } + }, + "node_modules/truffle-deploy-registry/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/truffle-error": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/truffle-error/-/truffle-error-0.0.5.tgz", @@ -38580,8 +38607,7 @@ "@openzeppelin/contracts": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.5.0.tgz", - "integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA==", - "dev": true + "integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA==" }, "@openzeppelin/contracts-upgradeable": { "version": "4.5.2", @@ -39972,9 +39998,9 @@ "dev": true }, "@pythnetwork/pyth-sdk-solidity": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.0.2.tgz", - "integrity": "sha512-/nxo+HqikNFDbi2dW/GQFJQYLjbe/PZL9x3ILxT4AdM1Nd76ykd+AK5aq08Giz6H4FGGrjsjJH4TtmgPJsUpDw==" + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.0.3.tgz", + "integrity": "sha512-4FaoGD4Sizvdm0+8KuQsYzARHa+myNwmFE2GBYxmMcw3zMtwvD7PH0RWM8nK9UyExJ+vyAAth7SbVzyLfN4Yow==" }, "@redux-saga/core": { "version": "1.1.3", @@ -66512,6 +66538,26 @@ } } }, + "truffle-deploy-registry": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/truffle-deploy-registry/-/truffle-deploy-registry-0.5.1.tgz", + "integrity": "sha512-ZvyRpn67J67cWTfN5Z+sIryOirzxOuvpS7WO1WJYx3V1GcJR0gWWEM38KTBoPQ5L98/n7XxWC8+TtkOypKEp6A==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "commander": "^2.19.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, "truffle-error": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/truffle-error/-/truffle-error-0.0.5.tgz", diff --git a/ethereum/package.json b/ethereum/package.json index 773d3cef..f62ca6c5 100644 --- a/ethereum/package.json +++ b/ethereum/package.json @@ -6,7 +6,6 @@ "devDependencies": { "@chainsafe/truffle-plugin-abigen": "0.0.1", "@openzeppelin/cli": "^2.8.2", - "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/test-environment": "^0.1.9", "@openzeppelin/test-helpers": "^0.5.15", "@openzeppelin/truffle-upgrades": "^1.14.0", @@ -16,6 +15,7 @@ "mocha": "^8.2.1", "truffle": "^5.5.5", "truffle-assertions": "^0.9.2", + "truffle-deploy-registry": "^0.5.1", "truffle-plugin-verify": "^0.5.11" }, "scripts": { @@ -30,6 +30,7 @@ "author": "", "license": "ISC", "dependencies": { + "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.2", "@pythnetwork/pyth-sdk-solidity": "0.0.3", "dotenv": "^10.0.0", diff --git a/ethereum/truffle-config.js b/ethereum/truffle-config.js index f8df2a47..2458d941 100644 --- a/ethereum/truffle-config.js +++ b/ethereum/truffle-config.js @@ -59,11 +59,13 @@ module.exports = { binance_testnet: { provider: () => new HDWalletProvider( process.env.MNEMONIC, - "https://data-seed-prebsc-1-s1.binance.org:8545/" + "https://data-seed-prebsc-1-s1.binance.org:8545" ), network_id: "97", - gas: 70000000, - gasPrice: 8000000000, + confirmations: 10, + networkCheckTimeout: 1000000, + timeoutBlocks: 1000, + skipDryRun: true, }, polygon: { provider: () => {