Skip to content

Commit 4821b87

Browse files
authored
[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
1 parent 0d2f60c commit 4821b87

File tree

12 files changed

+241
-112
lines changed

12 files changed

+241
-112
lines changed

ethereum/contracts/pyth/Pyth.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
536536
}
537537

538538
function version() public pure returns (string memory) {
539-
return "1.1.0";
539+
return "1.2.0";
540540
}
541541
}

ethereum/deploy.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ while [[ $# -ne 0 ]]; do
2525
NETWORK=$1
2626
shift
2727

28-
echo "=========== Deploying to ${NETWORK} ==========="
28+
echo "=========== Deploying to ${NETWORK} (if not deployed) ==========="
2929

3030
# Load the configuration environment variables for deploying your network. make sure to use right env file.
3131
# 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
3535
npx truffle migrate --network $MIGRATIONS_NETWORK
3636

3737
echo "Deployment to $NETWORK finished successfully"
38+
39+
echo "=========== Syncing contract state ==========="
40+
npx truffle exec scripts/syncPythState.js --network $MIGRATIONS_NETWORK || echo "Syncing failed/incomplete.. skipping"
3841
done
3942

4043
echo "=========== Finished ==========="

ethereum/migrations/prod-receiver/11_pyth_make_interface_simpler.js

Lines changed: 0 additions & 25 deletions
This file was deleted.

ethereum/migrations/prod/12_pyth_set_fee_1_wei.js

Lines changed: 0 additions & 34 deletions
This file was deleted.

ethereum/migrations/test/12_pyth_set_fee_1_wei.js

Lines changed: 0 additions & 33 deletions
This file was deleted.

ethereum/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ethereum/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/pyth-evm-contract",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "",
55
"devDependencies": {
66
"@chainsafe/truffle-plugin-abigen": "0.0.1",

ethereum/scripts/assertVaaPayloadEquals.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,25 @@ const {
55
setDefaultWasm("node");
66
const { assert } = require("chai");
77

8+
/**
9+
* Assert the VAA has payload equal to `expectedPayload`
10+
* @param {string} vaaHex
11+
* @param {Buffer} expectedPayload
12+
*/
813
module.exports = async function assertVaaPayloadEquals(
9-
vaaHexString,
10-
expectedPayloadBuffer
14+
vaaHex,
15+
expectedPayload
1116
) {
1217
const { parse_vaa } = await importCoreWasm();
1318

14-
if (vaaHexString.startsWith("0x")) {
15-
vaaHexString = vaaHexString.substring(2);
19+
if (vaaHex.startsWith("0x")) {
20+
vaaHex = vaaHex.substring(2);
1621
}
1722

18-
const vaaPayload = Buffer.from(
19-
parse_vaa(Buffer.from(vaaHexString, "hex")).payload
20-
);
23+
const vaaPayload = Buffer.from(parse_vaa(Buffer.from(vaaHex, "hex")).payload);
2124

2225
assert(
23-
expectedPayloadBuffer.equals(vaaPayload),
26+
expectedPayload.equals(vaaPayload),
2427
"The VAA payload is not equal to the expected payload"
2528
);
2629
};

ethereum/scripts/loadEnv.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const dotenv = require("dotenv");
22
var path = require("path");
33

4+
/**
5+
* Load environment variables for truffle. This method will load some
6+
* cluster-wide environment variables if `CLUSTER` is set in
7+
* `{rootPath}/.env`.
8+
* @param {string} rootPath
9+
*/
410
module.exports = function loadEnv(rootPath) {
511
dotenv.config({ path: path.join(rootPath, ".env") });
612
if (process.env.CLUSTER !== undefined) {

ethereum/scripts/syncPythState.js

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
const governance = require("@pythnetwork/xc-governance-sdk");
2+
const assertVaaPayloadEquals = require("./assertVaaPayloadEquals");
3+
const { assert } = require("chai");
4+
const util = require("node:util");
5+
const exec = util.promisify(require("node:child_process").exec);
6+
const fs = require("fs");
7+
8+
const loadEnv = require("./loadEnv");
9+
loadEnv("../");
10+
11+
const network = process.env.MIGRATIONS_NETWORK;
12+
const chainName = process.env.WORMHOLE_CHAIN_NAME;
13+
const cluster = process.env.CLUSTER;
14+
const PythUpgradable = artifacts.require("PythUpgradable");
15+
16+
/**
17+
*
18+
* @param {string} cmd
19+
* @returns {Promise<string>} output of the multisig command
20+
*/
21+
async function execMultisigCommand(cmd) {
22+
const multisigCluster = cluster === "mainnet" ? "mainnet" : "devnet";
23+
const fullCmd = `npm start -- ${cmd} -c ${multisigCluster}`;
24+
console.log(`Executing "${fullCmd}"`);
25+
26+
const { stdout, stderr } = await exec(fullCmd, {
27+
cwd: "../third_party/pyth/multisig-wh-message-builder",
28+
});
29+
30+
console.log("stdout:");
31+
console.log(stdout);
32+
console.log("stderr");
33+
console.log(stderr);
34+
35+
return stdout;
36+
}
37+
38+
/**
39+
*
40+
* @param {string} payload Payload in hex string without leading 0x
41+
* @returns {Promise<string>}
42+
*/
43+
async function createMultisigTx(payload) {
44+
console.log("Creating a multisig transaction for this transaction");
45+
const stdout = await execMultisigCommand(`create -p ${payload}`);
46+
47+
const txKey = stdout.match(/Tx key: (.*)\n/)[1];
48+
assert(txKey !== undefined && txKey.length > 10);
49+
console.log(`Created a multisig tx with key: ${txKey}`);
50+
51+
return txKey;
52+
}
53+
54+
/**
55+
*
56+
* @param {string} txKey
57+
* @param {string} payload
58+
* @returns {Promise<string>} VAA for the tx as hex (without leading 0x).
59+
*/
60+
async function executeMultisigTxAndGetVaa(txKey) {
61+
console.log("Executing a multisig transaction for this transaction");
62+
const stdout = await execMultisigCommand(`execute -t ${txKey}`);
63+
64+
let /** @type {string} */ vaa;
65+
try {
66+
vaa = stdout.match(/VAA \(Hex\): (.*)\n/)[1];
67+
assert(vaa !== undefined && vaa.length > 10);
68+
} catch (err) {
69+
throw new Error("Couldn't find VAA from the logs.");
70+
}
71+
72+
console.log(`Executed multisig tx and got VAA: ${vaa}`);
73+
74+
return vaa;
75+
}
76+
77+
/**
78+
*
79+
* @param {string} payload
80+
* @returns {Promise<string>} VAA for the tx as hex (without leading 0x).
81+
*/
82+
async function createVaaFromPayload(payload) {
83+
const msVaaCachePath = `.${network}.ms_vaa_${payload}`;
84+
let vaa;
85+
if (fs.existsSync(msVaaCachePath)) {
86+
vaa = fs.readFileSync(msVaaCachePath).toString().trim();
87+
console.log(`VAA already exists: ${vaa}`);
88+
return vaa;
89+
} else {
90+
const msTxCachePath = `.${network}.ms_tx_${payload}`;
91+
92+
let txKey;
93+
if (fs.existsSync(msTxCachePath)) {
94+
txKey = fs.readFileSync(msTxCachePath).toString();
95+
} else {
96+
console.log(
97+
`Creating multisig to send VAA with this payload: ${payload} ...`
98+
);
99+
txKey = await createMultisigTx(payload);
100+
fs.writeFileSync(msTxCachePath, txKey);
101+
throw new Error(
102+
"Contract not sync yet. Run the script again once the multisig transaction is ready to be executed."
103+
);
104+
}
105+
106+
try {
107+
vaa = await executeMultisigTxAndGetVaa(txKey, payload);
108+
} catch (e) {
109+
console.error(e);
110+
throw new Error(
111+
"Could not execute multisig tx. If the transaction is executed please get the VAA manually " +
112+
`and put it on .${network}.ms_vaa_${payload}. Then execute the script again.`
113+
);
114+
}
115+
116+
fs.writeFileSync(msVaaCachePath, vaa);
117+
fs.rmSync(`.${network}.ms_tx_${payload}`);
118+
}
119+
120+
return vaa;
121+
}
122+
123+
function cleanUpVaaCache(payload) {
124+
fs.rmSync(`.${network}.ms_vaa_${payload}`);
125+
}
126+
127+
async function upgradeContract(proxy) {
128+
console.log("Upgrading the contract...");
129+
130+
const implCachePath = `.${network}.new_impl`;
131+
let newImplementationAddress;
132+
if (fs.existsSync(implCachePath)) {
133+
newImplementationAddress = fs.readFileSync(implCachePath).toString();
134+
console.log(
135+
`A new implementation has already been deployed at address ${newImplementationAddress}`
136+
);
137+
} else {
138+
console.log("Deploying a new implementation...");
139+
const newImplementation = await PythUpgradable.new();
140+
console.log(`Tx hash: ${newImplementation.transactionHash}`);
141+
console.log(`New implementation address: ${newImplementation.address}`);
142+
fs.writeFileSync(implCachePath, newImplementation.address);
143+
newImplementationAddress = newImplementation.address;
144+
}
145+
146+
const upgradePayload = new governance.EthereumUpgradeContractInstruction(
147+
governance.CHAINS[chainName],
148+
new governance.HexString20Bytes(newImplementationAddress)
149+
).serialize();
150+
151+
const upgradePayloadHex = upgradePayload.toString("hex");
152+
153+
const vaa = await createVaaFromPayload(upgradePayloadHex);
154+
assertVaaPayloadEquals(vaa, upgradePayload);
155+
156+
console.log(`Executing the VAA...`);
157+
158+
await proxy.executeGovernanceInstruction("0x" + vaa);
159+
160+
const newVersion = await proxy.version();
161+
const { version: targetVersion } = require("../package.json");
162+
assert(targetVersion == newVersion, "New contract version is not a match");
163+
164+
fs.rmSync(implCachePath);
165+
cleanUpVaaCache(upgradePayloadHex);
166+
167+
console.log(`Contract upgraded successfully`);
168+
}
169+
170+
async function syncContractCode(proxy) {
171+
let deployedVersion = await proxy.version();
172+
const { version: targetVersion } = require("../package.json");
173+
174+
if (deployedVersion === targetVersion) {
175+
console.log("Contract version up to date");
176+
return;
177+
} else {
178+
console.log(
179+
`Deployed version: ${deployedVersion}, target version: ${targetVersion}. On-chain contract is outdated.`
180+
);
181+
await upgradeContract(proxy);
182+
}
183+
}
184+
185+
module.exports = async function (callback) {
186+
try {
187+
const proxy = await PythUpgradable.deployed();
188+
console.log(`Syncing Pyth contract deployed on ${proxy.address}...`);
189+
await syncContractCode(proxy);
190+
191+
callback();
192+
} catch (e) {
193+
callback(e);
194+
}
195+
};

0 commit comments

Comments
 (0)