check_proposal.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import yargs from "yargs";
  2. import { hideBin } from "yargs/helpers";
  3. import { CosmWasmChain, EvmChain } from "../src/core/chains";
  4. import { createHash } from "crypto";
  5. import { DefaultStore } from "../src/node/utils/store";
  6. import {
  7. CosmosUpgradeContract,
  8. EvmExecute,
  9. EvmSetWormholeAddress,
  10. EvmUpgradeContract,
  11. getProposalInstructions,
  12. MultisigParser,
  13. WormholeMultisigInstruction,
  14. } from "@pythnetwork/xc-admin-common";
  15. import SquadsMesh from "@sqds/mesh";
  16. import {
  17. getPythClusterApiUrl,
  18. PythCluster,
  19. } from "@pythnetwork/client/lib/cluster";
  20. import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
  21. import { AccountMeta, Keypair, PublicKey } from "@solana/web3.js";
  22. import {
  23. EvmEntropyContract,
  24. EvmPriceFeedContract,
  25. getCodeDigestWithoutAddress,
  26. EvmWormholeContract,
  27. } from "../src/core/contracts/evm";
  28. import Web3 from "web3";
  29. const parser = yargs(hideBin(process.argv))
  30. .usage("Usage: $0 --cluster <cluster_id> --proposal <proposal_address>")
  31. .options({
  32. cluster: {
  33. type: "string",
  34. demandOption: true,
  35. desc: "Multsig Cluster name to check proposal on can be one of [devnet, testnet, mainnet-beta]",
  36. },
  37. proposal: {
  38. type: "string",
  39. demandOption: true,
  40. desc: "The proposal address to check",
  41. },
  42. });
  43. async function main() {
  44. const argv = await parser.argv;
  45. const cluster = argv.cluster as PythCluster;
  46. const squad = SquadsMesh.endpoint(
  47. getPythClusterApiUrl(cluster),
  48. new NodeWallet(Keypair.generate()), // dummy wallet
  49. );
  50. const transaction = await squad.getTransaction(new PublicKey(argv.proposal));
  51. const instructions = await getProposalInstructions(squad, transaction);
  52. const multisigParser = MultisigParser.fromCluster(cluster);
  53. const parsedInstructions = instructions.map((instruction) => {
  54. return multisigParser.parseInstruction({
  55. programId: instruction.programId,
  56. data: instruction.data as Buffer,
  57. keys: instruction.keys as AccountMeta[],
  58. });
  59. });
  60. for (const instruction of parsedInstructions) {
  61. if (instruction instanceof WormholeMultisigInstruction) {
  62. if (instruction.governanceAction instanceof EvmSetWormholeAddress) {
  63. console.log(
  64. `Verifying EVM set wormhole address on ${instruction.governanceAction.targetChainId}`,
  65. );
  66. for (const chain of Object.values(DefaultStore.chains)) {
  67. if (
  68. chain instanceof EvmChain &&
  69. chain.wormholeChainName ===
  70. instruction.governanceAction.targetChainId
  71. ) {
  72. const address = instruction.governanceAction.address;
  73. const contract = new EvmWormholeContract(chain, address);
  74. const currentIndex = await contract.getCurrentGuardianSetIndex();
  75. const guardianSet = await contract.getGuardianSet();
  76. const proxyContract = new EvmPriceFeedContract(chain, address);
  77. const proxyCode = await proxyContract.getCode();
  78. const receiverImplementation =
  79. await proxyContract.getImplementationAddress();
  80. const implementationCode = await new EvmPriceFeedContract(
  81. chain,
  82. receiverImplementation,
  83. ).getCode();
  84. const proxyDigest = Web3.utils.keccak256(proxyCode);
  85. const implementationDigest =
  86. Web3.utils.keccak256(implementationCode);
  87. const guardianSetDigest = Web3.utils.keccak256(
  88. JSON.stringify(guardianSet),
  89. );
  90. console.log(
  91. `${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}`,
  92. );
  93. }
  94. }
  95. }
  96. if (instruction.governanceAction instanceof EvmUpgradeContract) {
  97. console.log(
  98. `Verifying EVM Upgrade Contract on ${instruction.governanceAction.targetChainId}`,
  99. );
  100. for (const chain of Object.values(DefaultStore.chains)) {
  101. if (
  102. chain instanceof EvmChain &&
  103. chain.isMainnet() === (cluster === "mainnet-beta") &&
  104. chain.wormholeChainName ===
  105. instruction.governanceAction.targetChainId
  106. ) {
  107. const address = instruction.governanceAction.address;
  108. const contract = new EvmPriceFeedContract(chain, address);
  109. const code = await contract.getCodeDigestWithoutAddress();
  110. // this should be the same keccak256 of the deployedCode property generated by truffle
  111. console.log(`${chain.getId()} Address:${address} digest:${code}`);
  112. }
  113. }
  114. }
  115. if (instruction.governanceAction instanceof CosmosUpgradeContract) {
  116. console.log(
  117. `Verifying Cosmos Upgrade Contract on ${instruction.governanceAction.targetChainId}`,
  118. );
  119. for (const chain of Object.values(DefaultStore.chains)) {
  120. if (
  121. chain instanceof CosmWasmChain &&
  122. chain.wormholeChainName ===
  123. instruction.governanceAction.targetChainId
  124. ) {
  125. const codeId = instruction.governanceAction.codeId;
  126. const code = await chain.getCode(Number(codeId));
  127. // this should be the same checksums.txt in our release file
  128. console.log(
  129. `${chain.getId()} Code Id:${codeId} digest:${createHash("sha256")
  130. .update(code)
  131. .digest("hex")}`,
  132. );
  133. }
  134. }
  135. }
  136. if (instruction.governanceAction instanceof EvmExecute) {
  137. // Note: it only checks for upgrade entropy contracts right now
  138. console.log(
  139. `Verifying EVMExecute on ${instruction.governanceAction.targetChainId}`,
  140. );
  141. for (const chain of Object.values(DefaultStore.chains)) {
  142. if (
  143. chain instanceof EvmChain &&
  144. chain.wormholeChainName ===
  145. instruction.governanceAction.targetChainId
  146. ) {
  147. const executorAddress =
  148. instruction.governanceAction.executorAddress;
  149. const callAddress = instruction.governanceAction.callAddress;
  150. const calldata = instruction.governanceAction.calldata;
  151. // TODO: If we add additional EVM contracts using the executor, we need to
  152. // add some logic here to identify what kind of contract is at the call address.
  153. const contract = new EvmEntropyContract(chain, callAddress);
  154. const owner = await contract.getOwner();
  155. if (
  156. executorAddress.toUpperCase() !==
  157. owner.replace("0x", "").toUpperCase()
  158. ) {
  159. console.log(
  160. `Executor Address: ${executorAddress.toUpperCase()} is not equal to Owner Address: ${owner
  161. .replace("0x", "")
  162. .toUpperCase()}`,
  163. );
  164. continue;
  165. }
  166. // TODO: This logic assumes we are calling upgradeTo on the contract at callAddress.
  167. // In the future, this logic may need to be generalized to support calling other functions.
  168. const invokedMethod = "upgradeTo(address)";
  169. const calldataHex = calldata.toString("hex");
  170. const web3 = new Web3();
  171. const methodSignature = web3.eth.abi
  172. .encodeFunctionSignature(invokedMethod)
  173. .replace("0x", "");
  174. let newImplementationAddress: string | undefined = undefined;
  175. if (calldataHex.startsWith(methodSignature)) {
  176. newImplementationAddress = web3.eth.abi.decodeParameter(
  177. "address",
  178. calldataHex.replace(methodSignature, ""),
  179. ) as unknown as string;
  180. }
  181. if (newImplementationAddress === undefined) {
  182. console.log(
  183. `We couldn't parse the instruction for ${chain.getId()}`,
  184. );
  185. continue;
  186. }
  187. const newImplementationCode = await getCodeDigestWithoutAddress(
  188. chain.getWeb3(),
  189. newImplementationAddress,
  190. );
  191. // this should be the same keccak256 of the deployedCode property generated by truffle
  192. console.log(
  193. `${chain.getId()} call ${invokedMethod} with arguments (${newImplementationAddress}) on ${contract.getType()} at address:${callAddress} from executor:${executorAddress}.`,
  194. );
  195. console.log(
  196. `${chain.getId()} new implementation address:${newImplementationAddress} has code digest:${newImplementationCode}`,
  197. );
  198. }
  199. }
  200. }
  201. }
  202. }
  203. }
  204. main();