From 1bf495da805da3dbf6f0767486143432045db444 Mon Sep 17 00:00:00 2001 From: David de Kloet Date: Thu, 21 Aug 2025 10:32:28 +0200 Subject: [PATCH 1/5] Support porBalance endpoint of ethereum-cl-indexer in proof-of-reserves --- .changeset/real-apples-approve.md | 5 ++ .../src/endpoint/reserves.ts | 4 +- .../proof-of-reserves/src/utils/reduce.ts | 21 +++++ .../test/unit/reduce.test.ts | 86 ++++++++++++++++++- 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 .changeset/real-apples-approve.md diff --git a/.changeset/real-apples-approve.md b/.changeset/real-apples-approve.md new file mode 100644 index 0000000000..184c4b5bce --- /dev/null +++ b/.changeset/real-apples-approve.md @@ -0,0 +1,5 @@ +--- +'@chainlink/proof-of-reserves-adapter': minor +--- + +Support porBalance endpoint of ethereum-cl-indexer diff --git a/packages/composites/proof-of-reserves/src/endpoint/reserves.ts b/packages/composites/proof-of-reserves/src/endpoint/reserves.ts index 6226701f27..61aa8014d9 100644 --- a/packages/composites/proof-of-reserves/src/endpoint/reserves.ts +++ b/packages/composites/proof-of-reserves/src/endpoint/reserves.ts @@ -157,13 +157,14 @@ export const execute: ExecuteWithConfig = async (input, context, config) const validatedAddresses = getValidAddresses(protocolOutput, validator) + const indexerEndpoint = validator.validated.data.indexerEndpoint const balanceOutput = await runBalanceAdapter( indexer, context, confirmations, config, validatedAddresses, - validator.validated.data.indexerEndpoint, + indexerEndpoint, validator.validated.data.indexerParams, ) @@ -171,6 +172,7 @@ export const execute: ExecuteWithConfig = async (input, context, config) indexer, context, balanceOutput, + indexerEndpoint, validator.validated.data.viewFunctionIndexerResultDecimals, ) reduceOutput.data.description = validator.validated.data.description diff --git a/packages/composites/proof-of-reserves/src/utils/reduce.ts b/packages/composites/proof-of-reserves/src/utils/reduce.ts index 8d888b5714..d1325819d6 100644 --- a/packages/composites/proof-of-reserves/src/utils/reduce.ts +++ b/packages/composites/proof-of-reserves/src/utils/reduce.ts @@ -48,6 +48,7 @@ export const runReduceAdapter = async ( indexer: string, context: AdapterContext, input: AdapterResponse, + indexerEndpoint?: string, viewFunctionIndexerResultDecimals?: number, ): Promise => { // Some adapters' balances come already reduced @@ -71,6 +72,26 @@ export const runReduceAdapter = async ( // TODO: type makeExecute response return returnParsedUnits(input.jobRunID, input.data.result as string, 0) case ETHEREUM_CL_INDEXER: + if (indexerEndpoint === 'porBalance') { + const hasInvalidResults = (input.data.result as unknown as Record[])?.some( + (result) => result.isValid === false, + ) + if (hasInvalidResults) { + throw new AdapterError({ + statusCode: 400, + message: 'ETHEREUM_CL_INDEXER endpoint porBalance ripcord is true', + }) + } + // If all results are valid, use default processing below the + // switch block. + break + } else if (indexerEndpoint !== 'etherFiBalance') { + throw new AdapterError({ + statusCode: 400, + message: `ETHEREUM_CL_INDEXER indexerEndpoint is not supported: ${indexerEndpoint}`, + }) + } + // For etherFiBalance endpoint: if (input.data.isValid) { return { jobRunID: input.jobRunID, diff --git a/packages/composites/proof-of-reserves/test/unit/reduce.test.ts b/packages/composites/proof-of-reserves/test/unit/reduce.test.ts index 61012dac0d..ed750e9748 100644 --- a/packages/composites/proof-of-reserves/test/unit/reduce.test.ts +++ b/packages/composites/proof-of-reserves/test/unit/reduce.test.ts @@ -5,6 +5,8 @@ import { runReduceAdapter } from '../../src/utils/reduce' describe('reduce', () => { describe('ethereum-cl-indexer', () => { describe('etherFiBalance endpoint', () => { + const indexerEndpoint = 'etherFiBalance' + it('should get totalBalance', async () => { const jobRunID = '45' const totalBalance = '123000000000000000000' @@ -17,7 +19,12 @@ describe('reduce', () => { }, } as unknown as AdapterResponse) - const response = await runReduceAdapter('ETHEREUM_CL_INDEXER', context, input) + const response = await runReduceAdapter( + 'ETHEREUM_CL_INDEXER', + context, + input, + indexerEndpoint, + ) expect(response).toEqual({ jobRunID, @@ -42,9 +49,82 @@ describe('reduce', () => { }, } as unknown as AdapterResponse) - await expect(() => runReduceAdapter('ETHEREUM_CL_INDEXER', context, input)).rejects.toThrow( - `ETHEREUM_CL_INDEXER ripcord is true: ${JSON.stringify(input.data)}`, + await expect(() => + runReduceAdapter('ETHEREUM_CL_INDEXER', context, input, indexerEndpoint), + ).rejects.toThrow(`ETHEREUM_CL_INDEXER ripcord is true: ${JSON.stringify(input.data)}`) + }) + }) + + describe('porBalance endpoint', () => { + const indexerEndpoint = 'porBalance' + + it('should add balances', async () => { + const jobRunID = '45' + const balance1 = '1000000000000000000' + const balance2 = '2000000000000000000' + const totalBalance = '3000000000000000000' + const context = makeStub('context', {} as AdapterContext) + const input = makeStub('input', { + jobRunID, + data: { + result: [ + { + isValid: true, + balance: balance1, + length: undefined, + }, + { + isValid: true, + balance: balance2, + length: undefined, + }, + ], + }, + } as unknown as AdapterResponse) + + const response = await runReduceAdapter( + 'ETHEREUM_CL_INDEXER', + context, + input, + indexerEndpoint, ) + + expect(response).toEqual({ + jobRunID, + result: totalBalance, + statusCode: 200, + providerStatusCode: 200, + data: { + result: totalBalance, + }, + }) + }) + + it('should throw if any result is not valid', async () => { + const jobRunID = '45' + const balance = '1000000000000000000' + const context = makeStub('context', {} as AdapterContext) + const input = makeStub('input', { + jobRunID, + data: { + result: [ + { + isValid: true, + balance: balance, + length: undefined, + }, + { + isValid: false, + balance: '0', + length: undefined, + }, + ], + }, + } as unknown as AdapterResponse) + + await expect(() => + runReduceAdapter('ETHEREUM_CL_INDEXER', context, input, indexerEndpoint), + ).rejects.toThrow('ETHEREUM_CL_INDEXER endpoint porBalance ripcord is true') }) }) }) From 2de9d9f2e4da8f6954a63b698b013c7fd4495b9e Mon Sep 17 00:00:00 2001 From: David de Kloet Date: Fri, 5 Sep 2025 15:06:45 +0200 Subject: [PATCH 2/5] Re-arrange endpoint logic --- .../proof-of-reserves/src/utils/reduce.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/composites/proof-of-reserves/src/utils/reduce.ts b/packages/composites/proof-of-reserves/src/utils/reduce.ts index d1325819d6..052627d8da 100644 --- a/packages/composites/proof-of-reserves/src/utils/reduce.ts +++ b/packages/composites/proof-of-reserves/src/utils/reduce.ts @@ -85,27 +85,27 @@ export const runReduceAdapter = async ( // If all results are valid, use default processing below the // switch block. break - } else if (indexerEndpoint !== 'etherFiBalance') { - throw new AdapterError({ - statusCode: 400, - message: `ETHEREUM_CL_INDEXER indexerEndpoint is not supported: ${indexerEndpoint}`, - }) - } - // For etherFiBalance endpoint: - if (input.data.isValid) { - return { - jobRunID: input.jobRunID, - result: input.data.totalBalance as string, - statusCode: 200, - data: { + } else if (indexerEndpoint === 'etherFiBalance') { + if (input.data.isValid) { + return { + jobRunID: input.jobRunID, result: input.data.totalBalance as string, statusCode: 200, - }, + data: { + result: input.data.totalBalance as string, + statusCode: 200, + }, + } + } else { + throw new AdapterError({ + statusCode: 400, + message: `ETHEREUM_CL_INDEXER ripcord is true: ${JSON.stringify(input.data)}`, + }) } } else { throw new AdapterError({ statusCode: 400, - message: `ETHEREUM_CL_INDEXER ripcord is true: ${JSON.stringify(input.data)}`, + message: `ETHEREUM_CL_INDEXER indexerEndpoint is not supported: ${indexerEndpoint}`, }) } case viewFunctionMultiChain.name: From 2e013c8e0bc2254ac86efd8b55d8a1b83667de09 Mon Sep 17 00:00:00 2001 From: David de Kloet Date: Fri, 5 Sep 2025 15:29:02 +0200 Subject: [PATCH 3/5] remove break --- packages/composites/proof-of-reserves/src/utils/reduce.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/composites/proof-of-reserves/src/utils/reduce.ts b/packages/composites/proof-of-reserves/src/utils/reduce.ts index 052627d8da..2d1ddce190 100644 --- a/packages/composites/proof-of-reserves/src/utils/reduce.ts +++ b/packages/composites/proof-of-reserves/src/utils/reduce.ts @@ -84,7 +84,6 @@ export const runReduceAdapter = async ( } // If all results are valid, use default processing below the // switch block. - break } else if (indexerEndpoint === 'etherFiBalance') { if (input.data.isValid) { return { From 8a5f90bc3286b8583d121308b38a2285b6c13f27 Mon Sep 17 00:00:00 2001 From: David de Kloet Date: Fri, 5 Sep 2025 15:46:00 +0200 Subject: [PATCH 4/5] Revert "remove break" This reverts commit 2e013c8e0bc2254ac86efd8b55d8a1b83667de09. --- packages/composites/proof-of-reserves/src/utils/reduce.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/composites/proof-of-reserves/src/utils/reduce.ts b/packages/composites/proof-of-reserves/src/utils/reduce.ts index 2d1ddce190..052627d8da 100644 --- a/packages/composites/proof-of-reserves/src/utils/reduce.ts +++ b/packages/composites/proof-of-reserves/src/utils/reduce.ts @@ -84,6 +84,7 @@ export const runReduceAdapter = async ( } // If all results are valid, use default processing below the // switch block. + break } else if (indexerEndpoint === 'etherFiBalance') { if (input.data.isValid) { return { From 24553348de74352686945b077cc1acf194e7ec57 Mon Sep 17 00:00:00 2001 From: David de Kloet Date: Fri, 5 Sep 2025 15:55:56 +0200 Subject: [PATCH 5/5] Move break --- packages/composites/proof-of-reserves/src/utils/reduce.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/composites/proof-of-reserves/src/utils/reduce.ts b/packages/composites/proof-of-reserves/src/utils/reduce.ts index 052627d8da..394f5b2715 100644 --- a/packages/composites/proof-of-reserves/src/utils/reduce.ts +++ b/packages/composites/proof-of-reserves/src/utils/reduce.ts @@ -84,7 +84,6 @@ export const runReduceAdapter = async ( } // If all results are valid, use default processing below the // switch block. - break } else if (indexerEndpoint === 'etherFiBalance') { if (input.data.isValid) { return { @@ -108,6 +107,7 @@ export const runReduceAdapter = async ( message: `ETHEREUM_CL_INDEXER indexerEndpoint is not supported: ${indexerEndpoint}`, }) } + break case viewFunctionMultiChain.name: if (!viewFunctionIndexerResultDecimals) { throw new Error(