diff --git a/src/client/element/element-bridge-data.test.ts b/src/client/element/element-bridge-data.test.ts index 36750ce9f..5255a0f18 100644 --- a/src/client/element/element-bridge-data.test.ts +++ b/src/client/element/element-bridge-data.test.ts @@ -10,7 +10,7 @@ import { IVault__factory, } from "../../../typechain-types"; import { AztecAssetType } from "../bridge-data"; -import { ChainProperties, ElementBridgeData } from "./element-bridge-data"; +import { ElementBridgeData } from "./element-bridge-data"; jest.mock("../aztec/provider", () => ({ createWeb3Provider: jest.fn(), @@ -139,6 +139,11 @@ describe("element bridge data", () => { }, } as any; + const prepareGetTransactionReceiptMockForNonce = (nonce: number) => { + const event = defiEvents.find(x => x.nonce === nonce); + (elementBridge.provider as any).getTransactionReceipt = async () => ({ blockNumber: event?.blockNumber }); + }; + const rollupContract: Mockify = { queryFilter: jest.fn().mockImplementation((filter: any, from: number, to: number) => { const nonce = filter.interactionNonce; @@ -172,12 +177,17 @@ describe("element bridge data", () => { element: ElementBridge = elementBridge as any, balancer: IVault = balancerContract as any, rollup: IRollupProcessor = rollupContract as any, - chainProperties: ChainProperties = { eventBatchSize: 10 }, ) => { ElementBridge__factory.connect = () => element as any; IVault__factory.connect = () => balancer as any; IRollupProcessor__factory.connect = () => rollup as any; - return ElementBridgeData.create({} as any, EthAddress.ZERO, EthAddress.ZERO, EthAddress.ZERO, chainProperties); // can pass in dummy values here as the above factories do all of the work + return ElementBridgeData.create( + {} as any, + EthAddress.ZERO, + EthAddress.ZERO, + EthAddress.ZERO, + "https://api.aztec.network/aztec-connect-prod/falafel/graphql", + ); // can pass in dummy values here as the above factories do all of the work }; it("should return the correct amount of interest", async () => { @@ -192,6 +202,7 @@ describe("element bridge data", () => { const totalInput = defiEvents.find(x => x.nonce === 56)!.totalInputValue; const userShareDivisor = 2n; const defiEvent = getDefiEvent(56)!; + prepareGetTransactionReceiptMockForNonce(56); const [daiValue] = await elementBridgeData.getInteractionPresentValue(56, totalInput / userShareDivisor); const delta = outputValue - defiEvent.totalInputValue; const timeElapsed = BigInt(now) - startDate; @@ -217,6 +228,9 @@ describe("element bridge data", () => { const totalInput = defiEvents.find(x => x.nonce === nonce)!.totalInputValue; const userShareDivisor = 2n; + // Update mock return + (elementBridge.provider as any).getTransactionReceipt = async () => ({ blockNumber: 59 }); + prepareGetTransactionReceiptMockForNonce(nonce); const [daiValue] = await elementBridgeData.getInteractionPresentValue(nonce, totalInput / userShareDivisor); const delta = interactions[nonce].quantityPT.toBigInt() - defiEvent.totalInputValue; const timeElapsed = BigInt(now) - startDate; @@ -237,6 +251,7 @@ describe("element bridge data", () => { it("requesting the present value of an unknown interaction should return empty values", async () => { const elementBridgeData = createElementBridgeData(); + prepareGetTransactionReceiptMockForNonce(57); const values = await elementBridgeData.getInteractionPresentValue(57, 0n); expect(values).toStrictEqual([]); }); diff --git a/src/client/element/element-bridge-data.ts b/src/client/element/element-bridge-data.ts index 21aa22dce..4f7c93ed0 100644 --- a/src/client/element/element-bridge-data.ts +++ b/src/client/element/element-bridge-data.ts @@ -13,6 +13,7 @@ import { import { AsyncDefiBridgeProcessedEvent } from "../../../typechain-types/IRollupProcessor"; import { createWeb3Provider } from "../aztec/provider"; import { AuxDataConfig, AztecAsset, BridgeDataFieldGetters, SolidityType } from "../bridge-data"; +import "isomorphic-fetch"; export type BatchSwapStep = { poolId: string; @@ -34,14 +35,6 @@ export type FundManagement = { toInternalBalance: boolean; }; -// Some operations on this class require us to scan back over the blockchain -// to find published events. This is done in batches of blocks. The batch size in this set of properties -// determines how many blocks to request in each batch. -// Users of this class can customise this to their provider requirements -export type ChainProperties = { - eventBatchSize: number; -}; - interface EventBlock { nonce: number; blockNumber: number; @@ -77,7 +70,7 @@ export class ElementBridgeData implements BridgeDataFieldGetters { private elementBridgeContract: ElementBridge, private balancerContract: IVault, private rollupContract: IRollupProcessor, - private chainProperties: ChainProperties, + private falafelGraphQlEndpoint: string, ) {} static create( @@ -85,13 +78,13 @@ export class ElementBridgeData implements BridgeDataFieldGetters { elementBridgeAddress: EthAddress, balancerAddress: EthAddress, rollupContractAddress: EthAddress, - chainProperties: ChainProperties = { eventBatchSize: 10000 }, + falafelGraphQlEndpoint: string, ) { const ethersProvider = createWeb3Provider(provider); const elementBridgeContract = ElementBridge__factory.connect(elementBridgeAddress.toString(), ethersProvider); const rollupContract = IRollupProcessor__factory.connect(rollupContractAddress.toString(), ethersProvider); const vaultContract = IVault__factory.connect(balancerAddress.toString(), ethersProvider); - return new ElementBridgeData(elementBridgeContract, vaultContract, rollupContract, chainProperties); + return new ElementBridgeData(elementBridgeContract, vaultContract, rollupContract, falafelGraphQlEndpoint); } private async storeEventBlocks(events: AsyncDefiBridgeProcessedEvent[]) { @@ -123,14 +116,32 @@ export class ElementBridgeData implements BridgeDataFieldGetters { return this.elementBridgeContract.provider.getBlock("latest"); } + private async getBlockNumber(interactionNonce: number) { + const id = Math.floor(interactionNonce / 32); + const query = `query Block($id: Int!) { + block: rollup(id: $id) { + ethTxHash + } + }`; + + const response = await fetch(this.falafelGraphQlEndpoint, { + headers: { "Content-Type": "application/json" }, + method: "POST", + body: JSON.stringify({ + query, + operationName: "Block", + variables: { id }, + }), + }); + + const data = await response.json(); + const txhash = `0x${data["data"]["block"]["ethTxHash"]}`; + const tx = await this.elementBridgeContract.provider.getTransactionReceipt(txhash); + return tx.blockNumber; + } + private async findDefiEventForNonce(interactionNonce: number) { // start off with the earliest possible block being the block in which the tranche was first deployed - let earliestBlockNumber = Number( - await this.elementBridgeContract.getTrancheDeploymentBlockNumber(interactionNonce), - ); - // start with the last block being the current block - const lastBlock = await this.getCurrentBlock(); - let latestBlockNumber = lastBlock.number; // try and find previously stored events that encompass the nonce we are looking for // also if we find the exact nonce then just return the stored data for (let i = 0; i < this.interactionBlockNumbers.length; i++) { @@ -138,40 +149,24 @@ export class ElementBridgeData implements BridgeDataFieldGetters { if (storedBlock.nonce == interactionNonce) { return storedBlock; } - if (storedBlock.nonce < interactionNonce) { - earliestBlockNumber = storedBlock.blockNumber; - } - if (storedBlock.nonce > interactionNonce) { - // this is the first block beyond the one we are looking for, we can break here - latestBlockNumber = storedBlock.blockNumber; - break; - } } - let end = latestBlockNumber; - let start = end - (this.chainProperties.eventBatchSize - 1); - start = Math.max(start, earliestBlockNumber); - - while (end > earliestBlockNumber) { - const events = await this.rollupContract.queryFilter( - this.rollupContract.filters.AsyncDefiBridgeProcessed(undefined, interactionNonce), - start, - end, - ); - // capture these event markers - await this.storeEventBlocks(events); - // there should just be one event, the one we are searching for. but to be sure we will process everything received - for (const event of events) { - const newEventBlock = await decodeEvent(event); - if (newEventBlock.nonce == interactionNonce) { - return newEventBlock; - } - } + const start = await this.getBlockNumber(interactionNonce); + const end = start + 1; - // if we didn't find an event then go round again but search further back in time - end = start - 1; - start = end - (this.chainProperties.eventBatchSize - 1); - start = Math.max(start, earliestBlockNumber); + const events = await this.rollupContract.queryFilter( + this.rollupContract.filters.AsyncDefiBridgeProcessed(undefined, interactionNonce), + start, + end, + ); + // capture these event markers + await this.storeEventBlocks(events); + // there should just be one event, the one we are searching for. but to be sure we will process everything received + for (const event of events) { + const newEventBlock = await decodeEvent(event); + if (newEventBlock.nonce == interactionNonce) { + return newEventBlock; + } } }