diff --git a/packages/gelato-web3-function/delay-dispatch/index.ts b/packages/gelato-web3-function/delay-dispatch/index.ts index 87d9ba8..0ddb084 100644 --- a/packages/gelato-web3-function/delay-dispatch/index.ts +++ b/packages/gelato-web3-function/delay-dispatch/index.ts @@ -1,6 +1,7 @@ import { Web3Function } from "@gelatonetwork/web3-functions-sdk"; import { GelatoRelay } from "@gelatonetwork/relay-sdk"; import { BigNumber, Contract } from "ethers"; +import { MulticallWrapper } from "ethers-multicall-provider"; import ky from "ky"; // gelato recommends using ky as axios doesn't support fetch by default const DELAY_ABI = [ @@ -41,22 +42,29 @@ Web3Function.onRun(async (context) => { throw new Error(`Invalid allowanceInterval userArg: ${allowanceInterval}`); } - const provider = multiChainProvider.default(); // this will be a provider for the chain this function is deployed for (gelatoArgs.chainId) + // This will be a provider for the chain this function is deployed for (gelatoArgs.chainId) + // It will automatically batch calls via the Multicall3 contract + const provider = MulticallWrapper.wrap(multiChainProvider.default()); + const delayMod = new Contract(delayModAddress, DELAY_ABI, provider); - if ((await provider.getCode(delayModAddress)) === "0x") { + // Query Delay mod contract for current nonce, cooldown, expiration + let nonce, cooldown, expiration: BigNumber; + let currentBlock: number; + try { + [nonce, cooldown, expiration, currentBlock] = await Promise.all([ + delayMod.txNonce() as Promise, + delayMod.txCooldown() as Promise, + delayMod.txExpiration() as Promise, + provider.getBlockNumber(), + ]); + } catch (e) { + console.error(e); return { canExec: false, message: `Delay mod contract not deployed at ${delayModAddress}`, }; } - const delayMod = new Contract(delayModAddress, DELAY_ABI, provider); - - // Query Delay mod contract for current nonce, cooldown, expiration - const nonce = (await delayMod.txNonce()) as BigNumber; - const cooldown = (await delayMod.txCooldown()) as BigNumber; - const expiration = (await delayMod.txExpiration()) as BigNumber; - // Query subgraph for upcoming transactions let transactions: Transaction[] = []; try { @@ -70,7 +78,7 @@ Web3Function.onRun(async (context) => { } // Retrieve current & last processed block number - const currentBlock = await provider.getBlockNumber(); + const lastBlockStr = await storage.get("lastBlockNumber"); let lastBlock = lastBlockStr ? parseInt(lastBlockStr) : 0; console.log( diff --git a/packages/gelato-web3-function/package.json b/packages/gelato-web3-function/package.json index e14ab1d..b1af040 100644 --- a/packages/gelato-web3-function/package.json +++ b/packages/gelato-web3-function/package.json @@ -30,6 +30,7 @@ "@gelatonetwork/relay-sdk": "^5.5.1", "@gelatonetwork/web3-functions-sdk": "^2.1.7", "ethers": "^5.7.2", + "ethers-multicall-provider": "3.1.2", "ky": "^1.1.0" }, "installConfig": { diff --git a/packages/gelato-web3-function/test/function.test.ts b/packages/gelato-web3-function/test/function.test.ts index ccdd6e0..392e115 100644 --- a/packages/gelato-web3-function/test/function.test.ts +++ b/packages/gelato-web3-function/test/function.test.ts @@ -1,10 +1,6 @@ -import path from "path"; -import { Web3Function } from "@gelatonetwork/web3-functions-sdk"; -import { Web3FunctionContextData } from "@gelatonetwork/web3-functions-sdk"; -import { Web3FunctionLoader } from "@gelatonetwork/web3-functions-sdk/loader"; import { GelatoRelay } from "@gelatonetwork/relay-sdk"; -import { runWeb3Function } from "./run"; import { BigNumber, providers } from "ethers"; +import { runWeb3Function } from "./run"; // Mock and spy on Relay SDK sponsoredCall method const mockImpl: typeof GelatoRelay.prototype.sponsoredCall = async () => { @@ -47,6 +43,19 @@ describe("delay-dispatch web3 function", () => { sponsoredCallSpy.mockClear(); }); + let timeDelta = 0; + const now = Date.now; + const setTimeTo = (millis: number) => { + timeDelta = Date.now() - millis; + }; + + beforeAll(() => { + jest.spyOn(Date, "now").mockImplementation(() => now() - timeDelta); + }); + afterEach(() => { + timeDelta = 0; + }); + it("throws if the secret or a userArg is not set", async () => { await expect( runWeb3Function({ userArgs, secrets: {}, provider }) @@ -88,9 +97,7 @@ describe("delay-dispatch web3 function", () => { it("skips over expired transactions to execute executable transactions", async () => { const timeFirstTxIsExpired = QUEUE[0].createdAt + COOLDOWN + EXPIRATION + 1; - jest - .spyOn(Date, "now") - .mockImplementation(() => timeFirstTxIsExpired * 1000); + setTimeTo(timeFirstTxIsExpired * 1000); const result = await runWeb3Function({ userArgs, provider }); expect(result).toEqual({ canExec: true, callData: [] }); @@ -105,9 +112,7 @@ describe("delay-dispatch web3 function", () => { it("respects the gas allowance", async () => { const timeFirstTxIsExpired = QUEUE[0].createdAt + COOLDOWN + EXPIRATION + 1; - jest - .spyOn(Date, "now") - .mockImplementation(() => timeFirstTxIsExpired * 1000); + setTimeTo(timeFirstTxIsExpired * 1000); const result = await runWeb3Function({ userArgs: { ...userArgs, gasAllowance: 300_000 }, // since we hard-code the gas to 123_000, this should only execute 2 txs, but not 3 @@ -120,9 +125,7 @@ describe("delay-dispatch web3 function", () => { it("returns `canExec: false` if nothing has been relayed", async () => { const timeFirstTxIsExpired = QUEUE[0].createdAt + COOLDOWN + EXPIRATION + 1; - jest - .spyOn(Date, "now") - .mockImplementation(() => timeFirstTxIsExpired * 1000); + setTimeTo(timeFirstTxIsExpired * 1000); const result = await runWeb3Function({ userArgs: { ...userArgs, gasAllowance: 10 }, // not enough for making a single call diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 5cb1000..ec226b2 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -31,4 +31,4 @@ "@graphprotocol/graph-ts": "^0.31.0", "assemblyscript-json": "^1.1.0" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index f779b70..afd1b0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6041,6 +6041,23 @@ __metadata: languageName: node linkType: hard +"ethers-multicall-provider@npm:3.1.2": + version: 3.1.2 + resolution: "ethers-multicall-provider@npm:3.1.2" + dependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/providers": ^5.7.2 + ethers: ^5.0.0 + lodash: ^4.17.0 + peerDependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/providers": ^5.7.2 + ethers: ^5.0.0 + lodash: ^4.17.0 + checksum: 06932858ff0c835d731081513338586d4e78a12568bf5c1ac4972b7c72ac2fe6223c8ba4a96b21757fe2ee9fb6465179e24bc799863eb507b0c7025527308c83 + languageName: node + linkType: hard + "ethers@npm:6.7.0": version: 6.7.0 resolution: "ethers@npm:6.7.0" @@ -6056,7 +6073,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.4.6, ethers@npm:^5.7.2": +"ethers@npm:^5.0.0, ethers@npm:^5.4.6, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: @@ -9332,7 +9349,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.11, lodash@npm:^4.17.12, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21": +"lodash@npm:^4.17.0, lodash@npm:^4.17.11, lodash@npm:^4.17.12, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -13687,6 +13704,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.40.0 "@typescript-eslint/parser": ^5.6.0 ethers: ^5.7.2 + ethers-multicall-provider: 3.1.2 jest: ^29.4.2 jest-environment-node: ^29.4.2 ky: ^1.1.0