diff --git a/README.md b/README.md index 43f2ad2..34dcf1d 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ self.addEventListener('message', async (event) => { | `INVALID_JSON_FORMAT` | Deposit data file must be in JSON format | `MERKLE_TREE_GENERATION_ERROR` | Failed to generate the Merkle tree | `INVALID_PUBLIC_KEY_FORMAT` | Failed to parse deposit data public key +| `DUPLICATE_DEPOSIT_DATA` | The deposit data file has already been uploaded. | `INVALID_WITHDRAW_ADDRESS` | The withdrawal addresses don’t match Eigen pods | `MISSING_FIELDS` | Failed to verify the deposit data public keys. Missing fields: {fields} | `DUPLICATE_PUBLIC_KEYS` | Failed to verify the deposit data public keys. All the entries must be unique. diff --git a/src/parser/depositDataParser.spec.ts b/src/parser/depositDataParser.spec.ts index 4cabcfd..28fc661 100644 --- a/src/parser/depositDataParser.spec.ts +++ b/src/parser/depositDataParser.spec.ts @@ -1,9 +1,13 @@ import type { FileItem, SupportedNetworks, ParserInput, ParserOutput } from './types' -import { ErrorTypes, mockData, ParserError } from './helpers' +import { ErrorTypes, mockData, ParserError, requests } from './helpers' import { networkNames } from './verifySignature' import { depositDataParser } from './index' +type MockVaultInfo = jest.MockedFunction + +jest.mock('./helpers/requests/getVaultInfo') + const testData = mockData[0] const validInput: ParserInput = { @@ -20,6 +24,9 @@ const validOutput: ParserOutput = { } describe('depositDataParser',() => { + afterEach(() => { + jest.clearAllMocks() + }) it('processes valid input without throwing errors ', async() => { await expect(depositDataParser(validInput)).resolves.toEqual(validOutput) @@ -95,5 +102,17 @@ describe('depositDataParser',() => { await expect(depositDataParser({ ...validInput, data: [] })).rejects.toThrow(errorText) }) + it('throws ParserError if the deposit data file has already been uploaded', async () => { + + (requests.getVaultInfo as MockVaultInfo).mockResolvedValue({ + isRestake: false, + depositDataRoot: '0x406de60516154112c876f7250d8b289d4e3d840074e8cf755922dd5d3c75d1c0', + }) + + const errorText = new ParserError(ErrorTypes.DUPLICATE_DEPOSIT_DATA) + + await expect(depositDataParser(validInput)).rejects.toThrow(errorText) + }) + }) diff --git a/src/parser/getDepositData.spec.ts b/src/parser/getDepositData.spec.ts index 610a943..6cc1381 100644 --- a/src/parser/getDepositData.spec.ts +++ b/src/parser/getDepositData.spec.ts @@ -8,6 +8,7 @@ const { pubkey } = mockData[0] const mockInput = { pubkey, + isRestake: false, vaultAddress: '0x9b6a6867d222d62dc301528190e3984d60adb06b', } diff --git a/src/parser/getDepositData.ts b/src/parser/getDepositData.ts index 887cdfe..b7e1e1c 100644 --- a/src/parser/getDepositData.ts +++ b/src/parser/getDepositData.ts @@ -3,7 +3,6 @@ import { prefix0x, ErrorTypes, ParserError, - requests, getBytes, getAmount, getEigenPodAddress, @@ -13,17 +12,16 @@ import { export type DepositDataInput = { pubkey: string + isRestake: boolean vaultAddress: string withdrawalAddress?: string network: SupportedNetworks } const getDepositData = async (values: DepositDataInput): Promise => { - const { pubkey, vaultAddress, withdrawalAddress, network } = values + const { pubkey, vaultAddress, isRestake, withdrawalAddress, network } = values - const isRestakeVault = await requests.checkIsRestakeVault(vaultAddress, network) - - const withdrawalCredentialAddress = isRestakeVault + const withdrawalCredentialAddress = isRestake ? await getEigenPodAddress({ vaultAddress, withdrawalAddress, network }) : vaultAddress diff --git a/src/parser/getPostMessage.ts b/src/parser/getPostMessage.ts index db7af9c..8939dad 100644 --- a/src/parser/getPostMessage.ts +++ b/src/parser/getPostMessage.ts @@ -1,22 +1,28 @@ import { StandardMerkleTree } from '@openzeppelin/merkle-tree' import type { DepositDataFile, ParserOutput } from './types' +import { ParserError, ErrorTypes } from './helpers' type Input = { pubkeySet: Set + depositDataRoot: string treeLeaves: Uint8Array[] parsedFile: DepositDataFile } const getPostMessage = (values: Input) => { - const { pubkeySet, treeLeaves, parsedFile } = values + const { depositDataRoot, pubkeySet, treeLeaves, parsedFile } = values const merkleTree = StandardMerkleTree.of( treeLeaves.map((value, index) => [ value, index ]), [ 'bytes', 'uint256' ] ) + if (depositDataRoot === merkleTree.root) { + throw new ParserError(ErrorTypes.DUPLICATE_DEPOSIT_DATA) + } + const postMessage: ParserOutput = { merkleRoot: merkleTree.root, validators: parsedFile?.length || 0, diff --git a/src/parser/helpers/errors.ts b/src/parser/helpers/errors.ts index 745f659..c14d2dc 100644 --- a/src/parser/helpers/errors.ts +++ b/src/parser/helpers/errors.ts @@ -2,11 +2,12 @@ type DynamicValues = Record export enum ErrorTypes { EMPTY_FILE = 'EMPTY_FILE', - EIGEN_PODS_EMPTY = 'EIGEN_PODS_EMPTY', MISSING_FIELDS = 'MISSING_FIELDS', + EIGEN_PODS_EMPTY = 'EIGEN_PODS_EMPTY', INVALID_SIGNATURE = 'INVALID_SIGNATURE', INVALID_JSON_FORMAT = 'INVALID_JSON_FORMAT', DUPLICATE_PUBLIC_KEYS = 'DUPLICATE_PUBLIC_KEYS', + DUPLICATE_DEPOSIT_DATA = 'DUPLICATE_DEPOSIT_DATA', INVALID_WITHDRAW_ADDRESS = 'INVALID_WITHDRAW_ADDRESS', INVALID_PUBLIC_KEY_FORMAT = 'INVALID_PUBLIC_KEY_FORMAT', MERKLE_TREE_GENERATION_ERROR = 'MERKLE_TREE_GENERATION_ERROR', @@ -18,6 +19,7 @@ export const ErrorMessages: Record = { [ErrorTypes.INVALID_JSON_FORMAT]: 'Deposit data file must be in JSON format.', [ErrorTypes.MERKLE_TREE_GENERATION_ERROR]: 'Failed to generate the Merkle tree', [ErrorTypes.INVALID_PUBLIC_KEY_FORMAT]: 'Failed to parse deposit data public key', + [ErrorTypes.DUPLICATE_DEPOSIT_DATA]: `The deposit data file has already been uploaded.`, [ErrorTypes.INVALID_WITHDRAW_ADDRESS]: `The withdrawal addresses don’t match Eigen pods`, [ErrorTypes.MISSING_FIELDS]: 'Failed to verify the deposit data public keys. Missing fields: {fields}', [ErrorTypes.DUPLICATE_PUBLIC_KEYS]: 'Failed to verify the deposit data public keys. All the entries must be unique.', diff --git a/src/parser/helpers/requests/checkIsRestakeVault.ts b/src/parser/helpers/requests/getVaultInfo.ts similarity index 57% rename from src/parser/helpers/requests/checkIsRestakeVault.ts rename to src/parser/helpers/requests/getVaultInfo.ts index a884cde..c09b828 100644 --- a/src/parser/helpers/requests/checkIsRestakeVault.ts +++ b/src/parser/helpers/requests/getVaultInfo.ts @@ -2,19 +2,20 @@ import gqlRequest from './gqlRequest' import { SupportedNetworks } from '../../types' -const checkIsRestakeVault = async (vaultId: string, network: SupportedNetworks) => { - const query = `query Vault($vaultId: ID!) { vault(id: $vaultId) { isRestake }}` +const getVaultInfo = async (vaultId: string, network: SupportedNetworks) => { + const query = `query Vault($vaultId: ID!) { vault(id: $vaultId) { isRestake depositDataRoot }}` + const variables = { vaultId: vaultId.toLowerCase() } try { const data = await gqlRequest({ query, variables }, network) - return data?.vault?.isRestake + return data?.vault } catch (error) { - console.error('Error fetching isRestake:', error) + console.error('Error fetching Vault info:', error) } } -export default checkIsRestakeVault +export default getVaultInfo diff --git a/src/parser/helpers/requests/index.ts b/src/parser/helpers/requests/index.ts index 9bf8864..069b5bc 100644 --- a/src/parser/helpers/requests/index.ts +++ b/src/parser/helpers/requests/index.ts @@ -1,2 +1,2 @@ export { default as getEigenPods } from './getEigenPods' -export { default as checkIsRestakeVault } from './checkIsRestakeVault' +export { default as getVaultInfo } from './getVaultInfo' diff --git a/src/parser/index.ts b/src/parser/index.ts index 6062119..81adc7f 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -1,4 +1,4 @@ -import { ParserError, ErrorTypes } from './helpers' +import { ParserError, ErrorTypes, requests } from './helpers' import initBls from './initBls' import getTreeLeaf from './getTreeLeaf' @@ -19,13 +19,20 @@ export const depositDataParser = async (input: ParserInput) => { const pubkeySet = new Set() const treeLeaves: Uint8Array[] = [] + const vaultInfo = await requests.getVaultInfo(vaultAddress, network) + for (let index = 0; index < parsedFile.length; index++) { const item = parsedFile[index] const { pubkey, signature, withdrawal_address } = item validateFields({ item }) - const depositData = await getDepositData({ pubkey, vaultAddress, withdrawalAddress: withdrawal_address, network }) + const depositData = await getDepositData({ + network, + pubkey, vaultAddress, + isRestake: vaultInfo?.isRestake, + withdrawalAddress: withdrawal_address, + }) verifySignature({ bls, pubkey, signature, depositData, network }) @@ -46,5 +53,10 @@ export const depositDataParser = async (input: ParserInput) => { throw new ParserError(ErrorTypes.DUPLICATE_PUBLIC_KEYS) } - return getPostMessage({ pubkeySet, parsedFile, treeLeaves }) + return getPostMessage({ + pubkeySet, + parsedFile, + treeLeaves, + depositDataRoot: vaultInfo?.depositDataRoot, + }) }