[eth] Improve and automate deployment process (#412)
* Bump contract version * Some refactoring to add types with JSDoc * Use better RPCs for some networks * Remove unneeded migration files * Add initial syncPythState script that does upgrade * Update truffle-config for new gas values
This commit is contained in:
parent
0d2f60cf28
commit
4821b877e3
|
@ -536,6 +536,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
|
|||
}
|
||||
|
||||
function version() public pure returns (string memory) {
|
||||
return "1.1.0";
|
||||
return "1.2.0";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ while [[ $# -ne 0 ]]; do
|
|||
NETWORK=$1
|
||||
shift
|
||||
|
||||
echo "=========== Deploying to ${NETWORK} ==========="
|
||||
echo "=========== Deploying to ${NETWORK} (if not deployed) ==========="
|
||||
|
||||
# Load the configuration environment variables for deploying your network. make sure to use right env file.
|
||||
# If it is a new chain you are deploying to, create a new env file and commit it to the repo.
|
||||
|
@ -35,6 +35,9 @@ while [[ $# -ne 0 ]]; do
|
|||
npx truffle migrate --network $MIGRATIONS_NETWORK
|
||||
|
||||
echo "Deployment to $NETWORK finished successfully"
|
||||
|
||||
echo "=========== Syncing contract state ==========="
|
||||
npx truffle exec scripts/syncPythState.js --network $MIGRATIONS_NETWORK || echo "Syncing failed/incomplete.. skipping"
|
||||
done
|
||||
|
||||
echo "=========== Finished ==========="
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
const loadEnv = require("../../scripts/loadEnv");
|
||||
loadEnv("../../");
|
||||
|
||||
const PythUpgradable = artifacts.require("PythUpgradable");
|
||||
const governanceChainId = process.env.GOVERNANCE_CHAIN_ID;
|
||||
const governanceEmitter = process.env.GOVERNANCE_EMITTER;
|
||||
|
||||
console.log("governanceEmitter: " + governanceEmitter);
|
||||
console.log("governanceChainId: " + governanceChainId);
|
||||
|
||||
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
|
||||
|
||||
/**
|
||||
* Version 1.1.0
|
||||
*
|
||||
* This change:
|
||||
* - Use pyth-sdk-solidity 1.0.0 which simplifies the PriceFeed interface
|
||||
*/
|
||||
module.exports = async function (deployer) {
|
||||
const proxy = await PythUpgradable.deployed();
|
||||
await upgradeProxy(proxy.address, PythUpgradable, {
|
||||
deployer,
|
||||
unsafeSkipStorageCheck: true,
|
||||
});
|
||||
};
|
|
@ -1,34 +0,0 @@
|
|||
const governance = require("@pythnetwork/xc-governance-sdk");
|
||||
const assertVaaPayloadEquals = require("../../scripts/assertVaaPayloadEquals");
|
||||
const { assert } = require("chai");
|
||||
|
||||
const loadEnv = require("../../scripts/loadEnv");
|
||||
loadEnv("../../");
|
||||
|
||||
const setFeeVaa = process.env.MIGRATION_12_SET_FEE_VAA;
|
||||
console.log("Set fee vaa: ", setFeeVaa);
|
||||
|
||||
const PythUpgradable = artifacts.require("PythUpgradable");
|
||||
|
||||
/**
|
||||
*
|
||||
* This change:
|
||||
* - Executes the VAA to set the fee to 1 wei
|
||||
*/
|
||||
module.exports = async function (_deployer) {
|
||||
const proxy = await PythUpgradable.deployed();
|
||||
|
||||
const setFeeInstruction = new governance.SetFeeInstruction(
|
||||
governance.CHAINS.unset, // All the chains
|
||||
BigInt(1),
|
||||
BigInt(0)
|
||||
).serialize();
|
||||
|
||||
console.log("SetFeeInstruction: 0x", setFeeInstruction.toString("hex"));
|
||||
|
||||
assertVaaPayloadEquals(setFeeVaa, setFeeInstruction);
|
||||
|
||||
await proxy.executeGovernanceInstruction(setFeeVaa);
|
||||
|
||||
assert.equal((await proxy.singleUpdateFeeInWei()).toString(), "1");
|
||||
};
|
|
@ -1,33 +0,0 @@
|
|||
const createLocalnetGovernanceVaa = require("../../scripts/createLocalnetGovernanceVaa");
|
||||
const assertVaaPayloadEquals = require("../../scripts/assertVaaPayloadEquals");
|
||||
const governance = require("@pythnetwork/xc-governance-sdk");
|
||||
const { assert } = require("chai");
|
||||
|
||||
const loadEnv = require("../../scripts/loadEnv");
|
||||
loadEnv("../../");
|
||||
|
||||
const PythUpgradable = artifacts.require("PythUpgradable");
|
||||
|
||||
/**
|
||||
*
|
||||
* This change:
|
||||
* - Executes the VAA to set the fee to 1 wei
|
||||
*/
|
||||
module.exports = async function (_deployer) {
|
||||
const setFeeInstruction = new governance.SetFeeInstruction(
|
||||
governance.CHAINS.unset, // All the chains
|
||||
BigInt(1),
|
||||
BigInt(0)
|
||||
).serialize();
|
||||
|
||||
console.log("SetFeeInstruction: 0x", setFeeInstruction.toString("hex"));
|
||||
|
||||
const setFeeVaa = createLocalnetGovernanceVaa(setFeeInstruction, 2);
|
||||
|
||||
assertVaaPayloadEquals(setFeeVaa, setFeeInstruction);
|
||||
|
||||
const proxy = await PythUpgradable.deployed();
|
||||
await proxy.executeGovernanceInstruction(setFeeVaa);
|
||||
|
||||
assert.equal((await proxy.singleUpdateFeeInWei()).toString(), "1");
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/pyth-evm-contract",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/pyth-evm-contract",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@chainsafe/truffle-plugin-abigen": "0.0.1",
|
||||
|
|
|
@ -5,22 +5,25 @@ const {
|
|||
setDefaultWasm("node");
|
||||
const { assert } = require("chai");
|
||||
|
||||
/**
|
||||
* Assert the VAA has payload equal to `expectedPayload`
|
||||
* @param {string} vaaHex
|
||||
* @param {Buffer} expectedPayload
|
||||
*/
|
||||
module.exports = async function assertVaaPayloadEquals(
|
||||
vaaHexString,
|
||||
expectedPayloadBuffer
|
||||
vaaHex,
|
||||
expectedPayload
|
||||
) {
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
|
||||
if (vaaHexString.startsWith("0x")) {
|
||||
vaaHexString = vaaHexString.substring(2);
|
||||
if (vaaHex.startsWith("0x")) {
|
||||
vaaHex = vaaHex.substring(2);
|
||||
}
|
||||
|
||||
const vaaPayload = Buffer.from(
|
||||
parse_vaa(Buffer.from(vaaHexString, "hex")).payload
|
||||
);
|
||||
const vaaPayload = Buffer.from(parse_vaa(Buffer.from(vaaHex, "hex")).payload);
|
||||
|
||||
assert(
|
||||
expectedPayloadBuffer.equals(vaaPayload),
|
||||
expectedPayload.equals(vaaPayload),
|
||||
"The VAA payload is not equal to the expected payload"
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
const dotenv = require("dotenv");
|
||||
var path = require("path");
|
||||
|
||||
/**
|
||||
* Load environment variables for truffle. This method will load some
|
||||
* cluster-wide environment variables if `CLUSTER` is set in
|
||||
* `{rootPath}/.env`.
|
||||
* @param {string} rootPath
|
||||
*/
|
||||
module.exports = function loadEnv(rootPath) {
|
||||
dotenv.config({ path: path.join(rootPath, ".env") });
|
||||
if (process.env.CLUSTER !== undefined) {
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
const governance = require("@pythnetwork/xc-governance-sdk");
|
||||
const assertVaaPayloadEquals = require("./assertVaaPayloadEquals");
|
||||
const { assert } = require("chai");
|
||||
const util = require("node:util");
|
||||
const exec = util.promisify(require("node:child_process").exec);
|
||||
const fs = require("fs");
|
||||
|
||||
const loadEnv = require("./loadEnv");
|
||||
loadEnv("../");
|
||||
|
||||
const network = process.env.MIGRATIONS_NETWORK;
|
||||
const chainName = process.env.WORMHOLE_CHAIN_NAME;
|
||||
const cluster = process.env.CLUSTER;
|
||||
const PythUpgradable = artifacts.require("PythUpgradable");
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} cmd
|
||||
* @returns {Promise<string>} output of the multisig command
|
||||
*/
|
||||
async function execMultisigCommand(cmd) {
|
||||
const multisigCluster = cluster === "mainnet" ? "mainnet" : "devnet";
|
||||
const fullCmd = `npm start -- ${cmd} -c ${multisigCluster}`;
|
||||
console.log(`Executing "${fullCmd}"`);
|
||||
|
||||
const { stdout, stderr } = await exec(fullCmd, {
|
||||
cwd: "../third_party/pyth/multisig-wh-message-builder",
|
||||
});
|
||||
|
||||
console.log("stdout:");
|
||||
console.log(stdout);
|
||||
console.log("stderr");
|
||||
console.log(stderr);
|
||||
|
||||
return stdout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} payload Payload in hex string without leading 0x
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function createMultisigTx(payload) {
|
||||
console.log("Creating a multisig transaction for this transaction");
|
||||
const stdout = await execMultisigCommand(`create -p ${payload}`);
|
||||
|
||||
const txKey = stdout.match(/Tx key: (.*)\n/)[1];
|
||||
assert(txKey !== undefined && txKey.length > 10);
|
||||
console.log(`Created a multisig tx with key: ${txKey}`);
|
||||
|
||||
return txKey;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} txKey
|
||||
* @param {string} payload
|
||||
* @returns {Promise<string>} VAA for the tx as hex (without leading 0x).
|
||||
*/
|
||||
async function executeMultisigTxAndGetVaa(txKey) {
|
||||
console.log("Executing a multisig transaction for this transaction");
|
||||
const stdout = await execMultisigCommand(`execute -t ${txKey}`);
|
||||
|
||||
let /** @type {string} */ vaa;
|
||||
try {
|
||||
vaa = stdout.match(/VAA \(Hex\): (.*)\n/)[1];
|
||||
assert(vaa !== undefined && vaa.length > 10);
|
||||
} catch (err) {
|
||||
throw new Error("Couldn't find VAA from the logs.");
|
||||
}
|
||||
|
||||
console.log(`Executed multisig tx and got VAA: ${vaa}`);
|
||||
|
||||
return vaa;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} payload
|
||||
* @returns {Promise<string>} VAA for the tx as hex (without leading 0x).
|
||||
*/
|
||||
async function createVaaFromPayload(payload) {
|
||||
const msVaaCachePath = `.${network}.ms_vaa_${payload}`;
|
||||
let vaa;
|
||||
if (fs.existsSync(msVaaCachePath)) {
|
||||
vaa = fs.readFileSync(msVaaCachePath).toString().trim();
|
||||
console.log(`VAA already exists: ${vaa}`);
|
||||
return vaa;
|
||||
} else {
|
||||
const msTxCachePath = `.${network}.ms_tx_${payload}`;
|
||||
|
||||
let txKey;
|
||||
if (fs.existsSync(msTxCachePath)) {
|
||||
txKey = fs.readFileSync(msTxCachePath).toString();
|
||||
} else {
|
||||
console.log(
|
||||
`Creating multisig to send VAA with this payload: ${payload} ...`
|
||||
);
|
||||
txKey = await createMultisigTx(payload);
|
||||
fs.writeFileSync(msTxCachePath, txKey);
|
||||
throw new Error(
|
||||
"Contract not sync yet. Run the script again once the multisig transaction is ready to be executed."
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
vaa = await executeMultisigTxAndGetVaa(txKey, payload);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new Error(
|
||||
"Could not execute multisig tx. If the transaction is executed please get the VAA manually " +
|
||||
`and put it on .${network}.ms_vaa_${payload}. Then execute the script again.`
|
||||
);
|
||||
}
|
||||
|
||||
fs.writeFileSync(msVaaCachePath, vaa);
|
||||
fs.rmSync(`.${network}.ms_tx_${payload}`);
|
||||
}
|
||||
|
||||
return vaa;
|
||||
}
|
||||
|
||||
function cleanUpVaaCache(payload) {
|
||||
fs.rmSync(`.${network}.ms_vaa_${payload}`);
|
||||
}
|
||||
|
||||
async function upgradeContract(proxy) {
|
||||
console.log("Upgrading the contract...");
|
||||
|
||||
const implCachePath = `.${network}.new_impl`;
|
||||
let newImplementationAddress;
|
||||
if (fs.existsSync(implCachePath)) {
|
||||
newImplementationAddress = fs.readFileSync(implCachePath).toString();
|
||||
console.log(
|
||||
`A new implementation has already been deployed at address ${newImplementationAddress}`
|
||||
);
|
||||
} else {
|
||||
console.log("Deploying a new implementation...");
|
||||
const newImplementation = await PythUpgradable.new();
|
||||
console.log(`Tx hash: ${newImplementation.transactionHash}`);
|
||||
console.log(`New implementation address: ${newImplementation.address}`);
|
||||
fs.writeFileSync(implCachePath, newImplementation.address);
|
||||
newImplementationAddress = newImplementation.address;
|
||||
}
|
||||
|
||||
const upgradePayload = new governance.EthereumUpgradeContractInstruction(
|
||||
governance.CHAINS[chainName],
|
||||
new governance.HexString20Bytes(newImplementationAddress)
|
||||
).serialize();
|
||||
|
||||
const upgradePayloadHex = upgradePayload.toString("hex");
|
||||
|
||||
const vaa = await createVaaFromPayload(upgradePayloadHex);
|
||||
assertVaaPayloadEquals(vaa, upgradePayload);
|
||||
|
||||
console.log(`Executing the VAA...`);
|
||||
|
||||
await proxy.executeGovernanceInstruction("0x" + vaa);
|
||||
|
||||
const newVersion = await proxy.version();
|
||||
const { version: targetVersion } = require("../package.json");
|
||||
assert(targetVersion == newVersion, "New contract version is not a match");
|
||||
|
||||
fs.rmSync(implCachePath);
|
||||
cleanUpVaaCache(upgradePayloadHex);
|
||||
|
||||
console.log(`Contract upgraded successfully`);
|
||||
}
|
||||
|
||||
async function syncContractCode(proxy) {
|
||||
let deployedVersion = await proxy.version();
|
||||
const { version: targetVersion } = require("../package.json");
|
||||
|
||||
if (deployedVersion === targetVersion) {
|
||||
console.log("Contract version up to date");
|
||||
return;
|
||||
} else {
|
||||
console.log(
|
||||
`Deployed version: ${deployedVersion}, target version: ${targetVersion}. On-chain contract is outdated.`
|
||||
);
|
||||
await upgradeContract(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function (callback) {
|
||||
try {
|
||||
const proxy = await PythUpgradable.deployed();
|
||||
console.log(`Syncing Pyth contract deployed on ${proxy.address}...`);
|
||||
await syncContractCode(proxy);
|
||||
|
||||
callback();
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
};
|
|
@ -17,7 +17,7 @@ module.exports = {
|
|||
),
|
||||
network_id: 1,
|
||||
gas: 10000000,
|
||||
gasPrice: 20000000000,
|
||||
gasPrice: 17000000000,
|
||||
confirmations: 1,
|
||||
timeoutBlocks: 200,
|
||||
skipDryRun: false,
|
||||
|
@ -62,7 +62,7 @@ module.exports = {
|
|||
provider: () => {
|
||||
return new HDWalletProvider(
|
||||
process.env.MNEMONIC,
|
||||
"https://bsc-dataseed3.defibit.io/"
|
||||
"https://rpc.ankr.com/bsc"
|
||||
);
|
||||
},
|
||||
network_id: "56",
|
||||
|
@ -73,7 +73,7 @@ module.exports = {
|
|||
provider: () =>
|
||||
new HDWalletProvider(
|
||||
process.env.MNEMONIC,
|
||||
"https://data-seed-prebsc-1-s1.binance.org:8545"
|
||||
"https://rpc.ankr.com/bsc_testnet_chapel"
|
||||
),
|
||||
network_id: "97",
|
||||
confirmations: 10,
|
||||
|
@ -168,7 +168,10 @@ module.exports = {
|
|||
},
|
||||
optimism: {
|
||||
provider: () => {
|
||||
return new HDWalletProvider(process.env.MNEMONIC, "https://1rpc.io/op");
|
||||
return new HDWalletProvider(
|
||||
process.env.MNEMONIC,
|
||||
"https://rpc.ankr.com/optimism"
|
||||
);
|
||||
},
|
||||
network_id: 10,
|
||||
},
|
||||
|
@ -176,7 +179,7 @@ module.exports = {
|
|||
provider: () => {
|
||||
return new HDWalletProvider(
|
||||
process.env.MNEMONIC,
|
||||
"https://opt-goerli.g.alchemy.com/v2/demo"
|
||||
"https://rpc.ankr.com/optimism_testnet"
|
||||
);
|
||||
},
|
||||
network_id: 420,
|
||||
|
@ -185,19 +188,19 @@ module.exports = {
|
|||
provider: () => {
|
||||
return new HDWalletProvider(
|
||||
process.env.MNEMONIC,
|
||||
"https://rpc.ftm.tools/"
|
||||
"https://rpc.ankr.com/fantom"
|
||||
);
|
||||
},
|
||||
network_id: 250,
|
||||
gas: 8000000,
|
||||
gasPrice: 3000000000,
|
||||
gasPrice: 50000000000,
|
||||
timeoutBlocks: 15000,
|
||||
},
|
||||
fantom_testnet: {
|
||||
provider: () => {
|
||||
return new HDWalletProvider(
|
||||
process.env.MNEMONIC,
|
||||
"https://rpc.testnet.fantom.network/"
|
||||
"https://rpc.ankr.com/fantom_testnet"
|
||||
);
|
||||
},
|
||||
network_id: 0xfa2,
|
||||
|
|
|
@ -425,6 +425,7 @@ async function addInstructionsToTx(
|
|||
console.log("Approving transaction...");
|
||||
await squad.approveTransaction(txKey);
|
||||
console.log("Transaction approved.");
|
||||
console.log(`Tx key: ${txKey}`);
|
||||
console.log(
|
||||
`Tx URL: https://mesh${
|
||||
cluster === "devnet" ? "-devnet" : ""
|
||||
|
@ -681,6 +682,16 @@ async function executeMultisigTx(
|
|||
msAccount.authorityIndex
|
||||
);
|
||||
|
||||
const tx = await squad.getTransaction(txPDA);
|
||||
if ((tx.status as any).executeReady === undefined) {
|
||||
console.log(
|
||||
`Transaction is either executed or not ready yet. Status: ${JSON.stringify(
|
||||
tx.status
|
||||
)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const executeIx = await squad.buildExecuteTransaction(
|
||||
txPDA,
|
||||
squad.wallet.publicKey
|
||||
|
|
Loading…
Reference in New Issue