-
Notifications
You must be signed in to change notification settings - Fork 195
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(contract_manager): script for EVM feeds contract deployment #1254
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
299 changes: 299 additions & 0 deletions
299
contract_manager/scripts/deploy_evm_pricefeed_contracts.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
import yargs from "yargs"; | ||
import { hideBin } from "yargs/helpers"; | ||
import { EvmChain } from "../src/chains"; | ||
import { DefaultStore } from "../src/store"; | ||
import { existsSync, readFileSync, writeFileSync } from "fs"; | ||
import { | ||
DeploymentType, | ||
EvmPriceFeedContract, | ||
getDefaultDeploymentConfig, | ||
PrivateKey, | ||
toDeploymentType, | ||
toPrivateKey, | ||
WormholeEvmContract, | ||
} from "../src"; | ||
import { join } from "path"; | ||
import Web3 from "web3"; | ||
import { Contract } from "web3-eth-contract"; | ||
|
||
type DeploymentConfig = { | ||
type: DeploymentType; | ||
validTimePeriodSeconds: number; | ||
singleUpdateFeeInWei: number; | ||
gasMultiplier: number; | ||
gasPriceMultiplier: number; | ||
privateKey: PrivateKey; | ||
jsonOutputDir: string; | ||
saveContract: boolean; | ||
}; | ||
|
||
const CACHE_FILE = ".cache-deploy-evm"; | ||
|
||
const parser = yargs(hideBin(process.argv)) | ||
.scriptName("deploy_evm_pricefeed_contracts.ts") | ||
.usage( | ||
"Usage: $0 --std-output-dir <path/to/std-output-dir/> --private-key <private-key> --chain <chain0> --chain <chain1>" | ||
) | ||
.options({ | ||
"std-output-dir": { | ||
type: "string", | ||
demandOption: true, | ||
desc: "Path to the standard JSON output of the contracts (build artifact) directory", | ||
}, | ||
"private-key": { | ||
type: "string", | ||
demandOption: true, | ||
desc: "Private key to use for the deployment", | ||
}, | ||
chain: { | ||
type: "array", | ||
demandOption: true, | ||
desc: "Chain to upload the contract on. Can be one of the evm chains available in the store", | ||
}, | ||
"deployment-type": { | ||
type: "string", | ||
demandOption: false, | ||
default: "stable", | ||
desc: "Deployment type to use. Can be 'stable' or 'beta'", | ||
}, | ||
"valid-time-period-seconds": { | ||
type: "number", | ||
demandOption: false, | ||
default: 60, | ||
desc: "Valid time period in seconds for the price feed staleness", | ||
}, | ||
"single-update-fee-in-wei": { | ||
type: "number", | ||
demandOption: false, | ||
default: 1, | ||
desc: "Single update fee in wei for the price feed", | ||
}, | ||
"gas-multiplier": { | ||
type: "number", | ||
demandOption: false, | ||
// Pyth Proxy (ERC1967) gas estimate is insufficient in many networks and thus we use 2 by default to make it work. | ||
default: 2, | ||
desc: "Gas multiplier to use for the deployment. This is useful when gas estimates are not accurate", | ||
}, | ||
"gas-price-multiplier": { | ||
type: "number", | ||
demandOption: false, | ||
default: 1, | ||
desc: "Gas price multiplier to use for the deployment. This is useful when gas price estimates are not accurate", | ||
}, | ||
"save-contract": { | ||
type: "boolean", | ||
demandOption: false, | ||
default: true, | ||
desc: "Save the contract to the store", | ||
}, | ||
}); | ||
|
||
async function deployIfNotCached( | ||
chain: EvmChain, | ||
config: DeploymentConfig, | ||
artifactName: string, | ||
deployArgs: any[] // eslint-disable-line @typescript-eslint/no-explicit-any | ||
): Promise<string> { | ||
const cache = existsSync(CACHE_FILE) | ||
? JSON.parse(readFileSync(CACHE_FILE, "utf8")) | ||
: {}; | ||
|
||
const cacheKey = `${chain.getId()}-${artifactName}`; | ||
if (cache[cacheKey]) { | ||
const address = cache[cacheKey]; | ||
console.log( | ||
`Using cached deployment of ${artifactName} on ${chain.getId()} at ${address}` | ||
); | ||
return address; | ||
} | ||
|
||
const artifact = JSON.parse( | ||
readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8") | ||
); | ||
|
||
console.log(`Deploying ${artifactName} on ${chain.getId()}...`); | ||
|
||
const addr = await chain.deploy( | ||
config.privateKey, | ||
artifact["abi"], | ||
artifact["bytecode"], | ||
deployArgs, | ||
config.gasMultiplier, | ||
config.gasPriceMultiplier | ||
); | ||
|
||
console.log(`✅ Deployed ${artifactName} on ${chain.getId()} at ${addr}`); | ||
|
||
cache[cacheKey] = addr; | ||
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2)); | ||
return addr; | ||
} | ||
|
||
function getWeb3Contract( | ||
config: DeploymentConfig, | ||
artifactName: string, | ||
address: string | ||
): Contract { | ||
const artifact = JSON.parse( | ||
readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8") | ||
); | ||
const web3 = new Web3(); | ||
return new web3.eth.Contract(artifact["abi"], address); | ||
} | ||
|
||
async function deployWormholeReceiverContracts( | ||
chain: EvmChain, | ||
config: DeploymentConfig | ||
): Promise<string> { | ||
const receiverSetupAddr = await deployIfNotCached( | ||
chain, | ||
config, | ||
"ReceiverSetup", | ||
[] | ||
); | ||
|
||
const receiverImplAddr = await deployIfNotCached( | ||
chain, | ||
config, | ||
"ReceiverImplementation", | ||
[] | ||
); | ||
|
||
// Craft the init data for the proxy contract | ||
const setupContract = getWeb3Contract( | ||
config, | ||
"ReceiverSetup", | ||
receiverSetupAddr | ||
); | ||
|
||
const { wormholeConfig } = getDefaultDeploymentConfig(config.type); | ||
|
||
const initData = setupContract.methods | ||
.setup( | ||
receiverImplAddr, | ||
wormholeConfig.initialGuardianSet.map((addr: string) => "0x" + addr), | ||
chain.getWormholeChainId(), | ||
wormholeConfig.governanceChainId, | ||
"0x" + wormholeConfig.governanceContract | ||
) | ||
.encodeABI(); | ||
|
||
const wormholeReceiverAddr = await deployIfNotCached( | ||
chain, | ||
config, | ||
"WormholeReceiver", | ||
[receiverSetupAddr, initData] | ||
); | ||
|
||
const wormholeEvmContract = new WormholeEvmContract( | ||
chain, | ||
wormholeReceiverAddr | ||
); | ||
|
||
if (config.type === "stable") { | ||
console.log(`Syncing mainnet guardian sets for ${chain.getId()}...`); | ||
// TODO: Add a way to pass gas configs to this | ||
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. Since this is a generic method I can't easily add the gas options to it. Something to refactor later in the future. |
||
await wormholeEvmContract.syncMainnetGuardianSets(config.privateKey); | ||
console.log(`✅ Synced mainnet guardian sets for ${chain.getId()}`); | ||
} | ||
|
||
return wormholeReceiverAddr; | ||
} | ||
|
||
async function deployPriceFeedContracts( | ||
chain: EvmChain, | ||
config: DeploymentConfig, | ||
wormholeAddr: string | ||
): Promise<string> { | ||
const pythImplAddr = await deployIfNotCached( | ||
chain, | ||
config, | ||
"PythUpgradable", | ||
[] | ||
); | ||
|
||
// Craft the init data for the proxy contract | ||
const { dataSources, governanceDataSource } = getDefaultDeploymentConfig( | ||
config.type | ||
); | ||
|
||
const pythImplContract = getWeb3Contract( | ||
config, | ||
"PythUpgradable", | ||
pythImplAddr | ||
); | ||
|
||
const pythInitData = pythImplContract.methods | ||
.initialize( | ||
wormholeAddr, | ||
dataSources.map((ds) => ds.emitterChain), | ||
dataSources.map((ds) => "0x" + ds.emitterAddress), | ||
governanceDataSource.emitterChain, | ||
"0x" + governanceDataSource.emitterAddress, | ||
0, // governanceInitialSequence | ||
config.validTimePeriodSeconds, | ||
config.singleUpdateFeeInWei | ||
) | ||
.encodeABI(); | ||
|
||
return await deployIfNotCached(chain, config, "ERC1967Proxy", [ | ||
pythImplAddr, | ||
pythInitData, | ||
]); | ||
} | ||
|
||
async function main() { | ||
const argv = await parser.argv; | ||
|
||
const deploymentConfig: DeploymentConfig = { | ||
type: toDeploymentType(argv.deploymentType), | ||
validTimePeriodSeconds: argv.validTimePeriodSeconds, | ||
singleUpdateFeeInWei: argv.singleUpdateFeeInWei, | ||
gasMultiplier: argv.gasMultiplier, | ||
gasPriceMultiplier: argv.gasPriceMultiplier, | ||
privateKey: toPrivateKey(argv.privateKey), | ||
jsonOutputDir: argv.stdOutputDir, | ||
saveContract: argv.saveContract, | ||
}; | ||
|
||
console.log( | ||
`Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n` | ||
); | ||
|
||
const chainNames = argv.chain; | ||
|
||
for (const chainName of chainNames) { | ||
const chain = DefaultStore.chains[chainName]; | ||
if (!chain) { | ||
throw new Error(`Chain ${chain} not found`); | ||
} else if (!(chain instanceof EvmChain)) { | ||
throw new Error(`Chain ${chain} is not an EVM chain`); | ||
} | ||
|
||
console.log(`Deploying price feed contracts on ${chain.getId()}...`); | ||
|
||
const wormholeAddr = await deployWormholeReceiverContracts( | ||
chain, | ||
deploymentConfig | ||
); | ||
const priceFeedAddr = await deployPriceFeedContracts( | ||
chain, | ||
deploymentConfig, | ||
wormholeAddr | ||
); | ||
|
||
if (deploymentConfig.saveContract) { | ||
console.log("Saving the contract in the store..."); | ||
const contract = new EvmPriceFeedContract(chain, priceFeedAddr); | ||
DefaultStore.contracts[contract.getId()] = contract; | ||
DefaultStore.saveAllContracts(); | ||
} | ||
|
||
console.log( | ||
`✅ Deployed price feed contracts on ${chain.getId()} at ${priceFeedAddr}\n\n` | ||
); | ||
} | ||
} | ||
|
||
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,18 +44,18 @@ const parser = yargs(hideBin(process.argv)) | |
}, | ||
}); | ||
|
||
async function run_if_not_cached( | ||
cache_key: string, | ||
async function runIfNotCached( | ||
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. format the naming style |
||
cacheKey: string, | ||
fn: () => Promise<string> | ||
): Promise<string> { | ||
const cache = existsSync(CACHE_FILE) | ||
? JSON.parse(readFileSync(CACHE_FILE, "utf8")) | ||
: {}; | ||
if (cache[cache_key]) { | ||
return cache[cache_key]; | ||
if (cache[cacheKey]) { | ||
return cache[cacheKey]; | ||
} | ||
const result = await fn(); | ||
cache[cache_key] = result; | ||
cache[cacheKey] = result; | ||
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2)); | ||
return result; | ||
} | ||
|
@@ -102,7 +102,7 @@ async function main() { | |
for (const chain of selectedChains) { | ||
const artifact = JSON.parse(readFileSync(argv["std-output"], "utf8")); | ||
console.log("Deploying contract to", chain.getId()); | ||
const address = await run_if_not_cached(`deploy-${chain.getId()}`, () => { | ||
const address = await runIfNotCached(`deploy-${chain.getId()}`, () => { | ||
return chain.deploy( | ||
toPrivateKey(argv["private-key"]), | ||
artifact["abi"], | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Can we use runIfNotCached here?
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.
Yeah it's a good idea. Let me do it in a separate PR (because this one is already very big)