pyth-crosschain/contract_manager/scripts/check_proposal.ts

209 lines
7.9 KiB
TypeScript

import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { CosmWasmChain, EvmChain } from "../src/chains";
import { createHash } from "crypto";
import { DefaultStore } from "../src/store";
import {
CosmosUpgradeContract,
EvmExecute,
EvmSetWormholeAddress,
EvmUpgradeContract,
getProposalInstructions,
MultisigParser,
WormholeMultisigInstruction,
} from "xc_admin_common";
import SquadsMesh from "@sqds/mesh";
import {
getPythClusterApiUrl,
PythCluster,
} from "@pythnetwork/client/lib/cluster";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js";
import {
EvmEntropyContract,
EvmPriceFeedContract,
getCodeDigestWithoutAddress,
EvmWormholeContract,
} from "../src/contracts/evm";
import Web3 from "web3";
const parser = yargs(hideBin(process.argv))
.usage("Usage: $0 --cluster <cluster_id> --proposal <proposal_address>")
.options({
cluster: {
type: "string",
demandOption: true,
desc: "Multsig Cluster name to check proposal on can be one of [devnet, testnet, mainnet-beta]",
},
proposal: {
type: "string",
demandOption: true,
desc: "The proposal address to check",
},
});
async function main() {
const argv = await parser.argv;
const cluster = argv.cluster as PythCluster;
const squad = SquadsMesh.endpoint(
getPythClusterApiUrl(cluster),
new NodeWallet(Keypair.generate()) // dummy wallet
);
const transaction = await squad.getTransaction(new PublicKey(argv.proposal));
const instructions = await getProposalInstructions(squad, transaction);
const multisigParser = MultisigParser.fromCluster(cluster);
const parsedInstructions = instructions.map((instruction) => {
return multisigParser.parseInstruction({
programId: instruction.programId,
data: instruction.data as Buffer,
keys: instruction.keys as AccountMeta[],
});
});
for (const instruction of parsedInstructions) {
if (instruction instanceof WormholeMultisigInstruction) {
if (instruction.governanceAction instanceof EvmSetWormholeAddress) {
console.log(
`Verifying EVM set wormhole address on ${instruction.governanceAction.targetChainId}`
);
for (const chain of Object.values(DefaultStore.chains)) {
if (
chain instanceof EvmChain &&
chain.wormholeChainName ===
instruction.governanceAction.targetChainId
) {
const address = instruction.governanceAction.address;
const contract = new EvmWormholeContract(chain, address);
const currentIndex = await contract.getCurrentGuardianSetIndex();
const guardianSet = await contract.getGuardianSet();
const proxyContract = new EvmPriceFeedContract(chain, address);
const proxyCode = await proxyContract.getCode();
const receiverImplementation =
await proxyContract.getImplementationAddress();
const implementationCode = await new EvmPriceFeedContract(
chain,
receiverImplementation
).getCode();
const proxyDigest = Web3.utils.keccak256(proxyCode);
const implementationDigest =
Web3.utils.keccak256(implementationCode);
const guardianSetDigest = Web3.utils.keccak256(
JSON.stringify(guardianSet)
);
console.log(
`${chain.getId()} Address:\t\t${address}\nproxy digest:\t\t${proxyDigest}\nimplementation digest:\t${implementationDigest} \nguardian set index:\t${currentIndex} \nguardian set:\t\t${guardianSetDigest}`
);
}
}
}
if (instruction.governanceAction instanceof EvmUpgradeContract) {
console.log(
`Verifying EVM Upgrade Contract on ${instruction.governanceAction.targetChainId}`
);
for (const chain of Object.values(DefaultStore.chains)) {
if (
chain instanceof EvmChain &&
chain.isMainnet() === (cluster === "mainnet-beta") &&
chain.wormholeChainName ===
instruction.governanceAction.targetChainId
) {
const address = instruction.governanceAction.address;
const contract = new EvmPriceFeedContract(chain, address);
const code = await contract.getCodeDigestWithoutAddress();
// this should be the same keccak256 of the deployedCode property generated by truffle
console.log(`${chain.getId()} Address:${address} digest:${code}`);
}
}
}
if (instruction.governanceAction instanceof CosmosUpgradeContract) {
console.log(
`Verifying Cosmos Upgrade Contract on ${instruction.governanceAction.targetChainId}`
);
for (const chain of Object.values(DefaultStore.chains)) {
if (
chain instanceof CosmWasmChain &&
chain.wormholeChainName ===
instruction.governanceAction.targetChainId
) {
const codeId = instruction.governanceAction.codeId;
const code = await chain.getCode(Number(codeId));
// this should be the same checksums.txt in our release file
console.log(
`${chain.getId()} Code Id:${codeId} digest:${createHash("sha256")
.update(code)
.digest("hex")}`
);
}
}
}
if (instruction.governanceAction instanceof EvmExecute) {
// Note: it only checks for upgrade entropy contracts right now
console.log(
`Verifying EVMExecute Contract on ${instruction.governanceAction.targetChainId}`
);
for (const chain of Object.values(DefaultStore.chains)) {
if (
chain instanceof EvmChain &&
chain.wormholeChainName ===
instruction.governanceAction.targetChainId
) {
const executorAddress =
instruction.governanceAction.executorAddress;
const callAddress = instruction.governanceAction.callAddress;
const calldata = instruction.governanceAction.calldata;
// currently executor is only being used by the entropy contract
const contract = new EvmEntropyContract(chain, callAddress);
const owner = await contract.getOwner();
if (
executorAddress.toUpperCase() !==
owner.replace("0x", "").toUpperCase()
) {
console.log(
`Executor Address: ${executorAddress.toUpperCase()} is not equal to Owner Address: ${owner
.replace("0x", "")
.toUpperCase()}`
);
continue;
}
const calldataHex = calldata.toString("hex");
const web3 = new Web3();
const methodSignature = web3.eth.abi
.encodeFunctionSignature("upgradeTo(address)")
.replace("0x", "");
let newImplementationAddress: string | undefined = undefined;
if (calldataHex.startsWith(methodSignature)) {
newImplementationAddress = web3.eth.abi.decodeParameter(
"address",
calldataHex.replace(methodSignature, "")
) as unknown as string;
}
if (newImplementationAddress === undefined) {
console.log(
`We couldn't parse the instruction for ${chain.getId()}`
);
continue;
}
const newImplementationCode = await getCodeDigestWithoutAddress(
chain.getRpcUrl(),
newImplementationAddress
);
// this should be the same keccak256 of the deployedCode property generated by truffle
console.log(
`${chain.getId()} new implementation address:${newImplementationAddress} digest:${newImplementationCode}`
);
}
}
}
}
}
}
main();