diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5c4bcd0cb..4410c823b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,6 +18,8 @@ jobs: - run: npm ci - run: npm run build --if-present - run: npm test + env: + ETH_RPC: ${{ secrets.ETH_RPC }} - run: | version="$GITHUB_REF_NAME" diff --git a/core/base/src/constants/guardians.ts b/core/base/src/constants/guardians.ts index 9ac5bd732..957785a09 100644 --- a/core/base/src/constants/guardians.ts +++ b/core/base/src/constants/guardians.ts @@ -1,40 +1,78 @@ -import type { MapLevels} from './../utils/index.js'; -import { constMap, filterIndexes, zip, cartesianRightRecursive } from './../utils/index.js'; -import type { Network } from './networks.js'; - -// prettier-ignore -const guardianKeyAndNameEntries = [[ - "Mainnet", [ - ["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5", "JumpCrypto" ], - ["0xfF6CB952589BDE862c25Ef4392132fb9D4A42157", "Staked" ], - ["0x114De8460193bdf3A2fCf81f86a09765F4762fD1", "Figment" ], - ["0x107A0086b32d7A0977926A205131d8731D39cbEB", "ChainodeTech" ], - ["0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2", "Inotel" ], - ["0x11b39756C042441BE6D8650b69b54EbE715E2343", "HashQuark" ], - ["0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd", "Chainlayer" ], - ["0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20", "xLabs" ], - ["0x74a3bf913953D695260D88BC1aA25A4eeE363ef0", "Forbole" ], - ["0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e", "StakingFund" ], - ["0xAF45Ced136b9D9e24903464AE889F5C8a723FC14", "MoonletWallet" ], - ["0xf93124b7c738843CBB89E864c862c38cddCccF95", "P2PValidator" ], - ["0xD2CC37A4dc036a8D232b48f62cDD4731412f4890", "01Node" ], - ["0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811", "MCF" ], - ["0x71AA1BE1D36CaFE3867910F99C09e347899C19C3", "Everstake" ], - ["0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf", "ChorusOne" ], - ["0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8", "Syncnode" ], - ["0x5E1487F35515d02A92753504a8D75471b9f49EdB", "Triton" ], - ["0x6FbEBc898F403E4773E95feB15E80C9A99c8348d", "StakingFacilities"], - ]], [ - "Testnet", [ - ["0x13947Bd48b18E53fdAeEe77F3473391aC727C638", "Testnet guardian"] - ]] -] as const satisfies MapLevels<[Network, string, string]>; - -export const [guardianKeys, guardianNames] = - filterIndexes(zip(cartesianRightRecursive(guardianKeyAndNameEntries)), [1, 2]); - -export const guardianNameToKey = constMap(guardianKeyAndNameEntries, [[0, 2], 1]); -export const guardianKeyToName = constMap(guardianKeyAndNameEntries, [1, [0, 2]]); +import { replaceElement } from "./../utils/index.js"; + +// Mainnet guardian sets +const guardianSet1 = [ + ["0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5", "Certus One"], + ["0xfF6CB952589BDE862c25Ef4392132fb9D4A42157", "Staked"], + ["0x114De8460193bdf3A2fCf81f86a09765F4762fD1", "Figment"], + ["0x107A0086b32d7A0977926A205131d8731D39cbEB", "ChainodeTech"], + ["0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2", "Inotel"], + ["0x11b39756C042441BE6D8650b69b54EbE715E2343", "HashQuark"], + ["0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd", "ChainLayer"], + ["0xeB5F7389Fa26941519f0863349C223b73a6DDEE7", "DokiaCapital"], + ["0x74a3bf913953D695260D88BC1aA25A4eeE363ef0", "Forbole"], + ["0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e", "Staking Fund"], + ["0xAF45Ced136b9D9e24903464AE889F5C8a723FC14", "Moonlet"], + ["0xf93124b7c738843CBB89E864c862c38cddCccF95", "P2P Validator"], + ["0xD2CC37A4dc036a8D232b48f62cDD4731412f4890", "01node"], + ["0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811", "MCF"], + ["0x71AA1BE1D36CaFE3867910F99C09e347899C19C3", "Everstake"], + ["0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf", "Chorus One"], + ["0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8", "syncnode"], + ["0x5E1487F35515d02A92753504a8D75471b9f49EdB", "Triton"], + ["0x6FbEBc898F403E4773E95feB15E80C9A99c8348d", "Staking Facilities"], +] as const; + +const guardianSet2 = replaceElement(guardianSet1, 7, [ + "0x66B9590e1c41e0B226937bf9217D1d67Fd4E91F5", + "FTX", +] as const); + +const guardianSet3 = replaceElement(guardianSet2, 7, [ + "0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20", + "xLabs", +] as const); + +const guardianSet4 = replaceElement(guardianSet3, 0, [ + "0x5893B5A76c3f739645648885bDCcC06cd70a3Cd3", + "RockawayX", +] as const); + +// Testnet guardian sets +const testnetGuardianSet1 = [ + ["0x13947Bd48b18E53fdAeEe77F3473391aC727C638", "Testnet guardian"], +] as const; + +// TODO: Attempting to use `constMap` results in a type instantiation too deep error +const guardianSetsMap = { + Mainnet: { + 1: guardianSet1, + 2: guardianSet2, + 3: guardianSet3, + 4: guardianSet4, + }, + Testnet: { + 1: testnetGuardianSet1, + }, +} as const; + +type GuardianSetsMap = typeof guardianSetsMap; + +type GuardianInfo = { + address: string; + name: string; +}; + +export function getGuardianSet( + network: N, + index: I, +): GuardianInfo[] { + const guardianSet = guardianSetsMap[network][index] as readonly [string, string][]; + return guardianSet.map(([address, name]) => ({ + address, + name, + })); +} export const devnetGuardianPrivateKey = "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0"; diff --git a/core/base/src/utils/array.ts b/core/base/src/utils/array.ts index ddd9bf49d..c5251fda0 100644 --- a/core/base/src/utils/array.ts +++ b/core/base/src/utils/array.ts @@ -174,3 +174,18 @@ export type Cartesian = : R extends RoArray ? [...{ [K in keyof R]: K extends `${number}` ? [L, R[K]] : never }] : [L, R]; + +export type ReplaceElement = { + readonly [K in keyof A]: K extends `${I}` ? NE : A[K]; +}; + +export const replaceElement = < + const A extends RoArray, + const I extends number, + const NE +>(arr: A, index: I, newElement: NE, +) => { + const newArr = [...arr]; + newArr[index] = newElement; + return newArr as ReplaceElement; +} diff --git a/platforms/evm/__tests__/unit/core.test.ts b/platforms/evm/__tests__/unit/core.test.ts new file mode 100644 index 000000000..977e71f31 --- /dev/null +++ b/platforms/evm/__tests__/unit/core.test.ts @@ -0,0 +1,33 @@ +import { describe, test } from '@jest/globals'; +import { ethers } from 'ethers'; +import { EvmWormholeCore } from '@wormhole-foundation/sdk-evm-core'; +import { contracts } from '@wormhole-foundation/sdk-connect'; +import { guardians } from '@wormhole-foundation/sdk-base'; + +import '@wormhole-foundation/sdk-evm-core'; + +describe('Core tests', function () { + test('Check latest mainnet guardian set', async () => { + const rpc = process.env.ETH_RPC + ? new ethers.JsonRpcProvider(process.env.ETH_RPC) + : ethers.getDefaultProvider('mainnet'); + + const core = new EvmWormholeCore('Mainnet', 'Ethereum', rpc, { + coreBridge: contracts.coreBridge.get('Mainnet', 'Ethereum'), + }); + + const index = await core.getGuardianSetIndex(); + // If this test fails, the guardian set index may have been updated + expect(index).toBe(4); + + const guardianSet = await core.getGuardianSet(index); + expect(guardianSet.index).toBe(index); + + const localGuardianSet = [...guardians.getGuardianSet('Mainnet', 4)]; + expect(localGuardianSet.length).toBe(guardianSet.keys.length); + + for (let i = 0; i < guardianSet.keys.length; i++) { + expect(guardianSet.keys[i]).toBe(localGuardianSet[i]?.address); + } + }); +}); diff --git a/platforms/solana/protocols/core/src/utils/instructions/index.ts b/platforms/solana/protocols/core/src/utils/instructions/index.ts index c2950df7d..4390dee51 100644 --- a/platforms/solana/protocols/core/src/utils/instructions/index.ts +++ b/platforms/solana/protocols/core/src/utils/instructions/index.ts @@ -3,4 +3,5 @@ export * from './governance.js'; export * from './initialize.js'; export * from './postMessage.js'; export * from './postVaa.js'; +export * from './secp256k1.js'; export * from './verifySignature.js'; diff --git a/platforms/solana/protocols/core/src/utils/instructions/verifySignature.ts b/platforms/solana/protocols/core/src/utils/instructions/verifySignature.ts index e9be79034..23396fda6 100644 --- a/platforms/solana/protocols/core/src/utils/instructions/verifySignature.ts +++ b/platforms/solana/protocols/core/src/utils/instructions/verifySignature.ts @@ -12,7 +12,11 @@ import { } from '@solana/web3.js'; import type { VAA } from '@wormhole-foundation/sdk-connect'; import { createReadOnlyWormholeProgramInterface } from '../program.js'; -import { deriveGuardianSetKey, getGuardianSet } from './../accounts/index.js'; +import { + GuardianSetData, + deriveGuardianSetKey, + getGuardianSet, +} from './../accounts/index.js'; import { createSecp256k1Instruction } from './secp256k1.js'; const MAX_LEN_GUARDIAN_KEYS = 19; @@ -35,6 +39,7 @@ const MAX_LEN_GUARDIAN_KEYS = 19; * @param {SignedVaa | ParsedVaa} vaa - either signed VAA bytes or parsed VAA * @param {PublicKeyInitData} signatureSet - address to account of verified signatures * @param {web3.ConfirmOptions} [options] - Solana confirmation options + * @param {GuardianSetData} [guardianSetData] - guardian set data */ export async function createVerifySignaturesInstructions( connection: Connection, @@ -43,14 +48,17 @@ export async function createVerifySignaturesInstructions( vaa: VAA, signatureSet: PublicKeyInitData, commitment?: Commitment, + guardianSetData?: GuardianSetData, ): Promise { const guardianSetIndex = vaa.guardianSet; - const guardianSetData = await getGuardianSet( - connection, - wormholeProgramId, - guardianSetIndex, - commitment, - ); + if (guardianSetData === undefined) { + guardianSetData = await getGuardianSet( + connection, + wormholeProgramId, + guardianSetIndex, + commitment, + ); + } const guardianSignatures = vaa.signatures; const guardianKeys = guardianSetData.keys;