209 lines
7.9 KiB
TypeScript
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();
|