Skip to content

Commit

Permalink
Added blockTag support (#5).
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed Jun 16, 2024
1 parent 33d7ecb commit 904712d
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 54 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@ethers-ext/provider-multicall",
"version": "6.0.0-beta.1",
"version": "6.0.0-beta.2",
"description": "Ethers extension for a simple multicall-based Provider to batch calls.",
"dependencies": {
"ethers": "^6.7.0"
"ethers": "^6.13.0"
},
"devDependencies": {
"solc": "0.8.21",
Expand Down
119 changes: 72 additions & 47 deletions src.ts/provider-multicall.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
AbiCoder, AbstractProvider,
assertArgument, concat
AbiCoder, AbstractProvider, toQuantity,
assertArgument, concat, makeError
} from "ethers";

import type {
PerformActionRequest,
// PerformActionTransaction,
// BlockTag
BlockTag
} from "ethers";

import { bin } from "./_contract.js";
Expand All @@ -26,7 +26,7 @@ export type DebugEventMulticallProvider = {
export type CallResult = { status: boolean, data: string };

interface CallHandle {
request: { to: string, data: string };
request: { to: string, data: string, blockTag?: BlockTag };
resolve: (result: any) => void;
reject: (error: any) => void;
}
Expand Down Expand Up @@ -75,64 +75,89 @@ export class MulticallProvider extends AbstractProvider {
}
};

queueCall(to: string, data: string): Promise<CallResult> {
queueCall(to: string, data: string, blockTag?: BlockTag): Promise<CallResult> {
if (this.#drainTimer == null && this.#drainInterval >= 0) {
this.#drainTimer = setTimeout(() => {
this.drainCallQueue();
}, this.#drainInterval);
}

return new Promise((resolve, reject) => {
this.#callQueue.push({ request: { to, data }, resolve, reject });
this.#callQueue.push({ request: { to, data, blockTag }, resolve, reject });
});
}

async drainCallQueue(): Promise<Array<CallResult>> {
async drainCallQueue(): Promise<void> {
this.#drainTimer = null;

const callQueue = this.#callQueue;
const _callQueue = this.#callQueue;
this.#callQueue = [ ];

const data = concat([ bin, AbiCoder.defaultAbiCoder().encode([
"tuple(address, bytes)[]"
], [
callQueue.map(({ request }) => {
return [ request.to, request.data ];
})
])]);

this.emit("debug", {
action: "sendMulticall", data,
call: callQueue.map(({ request }) => {
return { to: request.to, data: request.data };
})
});

const _data = await this.subprovider.call({ data });

const [ blockNumber, results ] = AbiCoder.defaultAbiCoder().decode([ "uint", "tuple(bool, bytes)[]"], _data);

if (blockNumber) { } // @TODO: check block number

this.emit("debug", {
action: "receiveMulticallResult", data,
call: callQueue.map(({ request }, i) => {
return {
to: request.to, data: request.data,
status: results[i][0], result: results[i][1]
};
})
});

const output: Array<CallResult> = [ ];
for (let i = 0; i < callQueue.length; i++) {
const result = results[i];
const { resolve } = callQueue[i];
resolve({ status: result[0], data: result[1] });
output.push({ status: result[0], data: result[1] });
const blockTags = _callQueue.reduce((accum, { request }) => {
const blockTag = request.blockTag;
if (blockTag != null && accum.indexOf(blockTag) === -1) {
accum.push(blockTag);
}
return accum;
}, <Array<BlockTag>>[ ]);

const runners: Array<Promise<void>> = [ ];

for (const blockTag of blockTags) {
const callQueue = _callQueue.filter(({ request }) => request.blockTag === blockTag);

const data = concat([ bin, AbiCoder.defaultAbiCoder().encode([
"tuple(address, bytes)[]"
], [
callQueue.map(({ request }) => {
return [ request.to, request.data ];
})
])]);

this.emit("debug", {
action: "sendMulticall", data,
call: callQueue.map(({ request }) => {
return { to: request.to, data: request.data };
})
});

runners.push((async () => {

const _data = await this.subprovider.call({ data, blockTag });

const [ _blockNumber, results ] = AbiCoder.defaultAbiCoder().decode([ "uint", "tuple(bool, bytes)[]"], _data);
const blockNumber = toQuantity(_blockNumber);

if (blockTag !== "latest" && blockTag !== "pending" && blockNumber !== blockTag) {
callQueue.forEach(({ reject }) => {
reject(makeError("backend does not support archive access", "UNSUPPORTED_OPERATION", {
operation: "call(blockTag)",
info: { expectedBlockTag: blockTag, blockNumber }
}));
});
}

this.emit("debug", {
action: "receiveMulticallResult", data,
call: callQueue.map(({ request }, i) => {
return {
to: request.to, data: request.data,
status: results[i][0], result: results[i][1]
};
})
});

const output: Array<CallResult> = [ ];
for (let i = 0; i < callQueue.length; i++) {
const result = results[i];
const { resolve } = callQueue[i];
resolve({ status: result[0], data: result[1] });
output.push({ status: result[0], data: result[1] });
}
})());
}

return output;
await Promise.all(runners);
}

_detectNetwork() {
Expand All @@ -145,7 +170,7 @@ export class MulticallProvider extends AbstractProvider {
const to = (tx.to || null), data = (tx.data || "0x");
assertArgument(typeof(to) === "string", "deployment transactions unsupported", "tx.to", tx.to);

const result = await this.queueCall(to, data);
const result = await this.queueCall(to, data, req.blockTag);
if (result.status) { return <T>result.data; }

// Throw a CallException
Expand Down
22 changes: 17 additions & 5 deletions src.ts/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,38 @@ import { bin, abi } from "./_contract.js";

(async function() {
const subprovider = new ethers.InfuraProvider();
//const subprovider = new ethers.ChainstackProvider();
//const subprovider = new ethers.QuickNodeProvider();
/*
subprovider.on("debug", (req) => {
console.log("SUB");
console.dir(req, { depth: null });
});
*/

const provider = new MulticallProvider(subprovider);

const contract = new ethers.Contract("dai.tokens.ethers.eth", [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function balanceOf(address) view returns (uint256)",
"function balanceOf(address) view returns (uint256)",
], provider);

/*
provider.on("debug", (req) => {
console.log("MULTI");
console.dir(req, { depth: null });
});
*/
const blockNumber = await provider.getBlockNumber();
const ricmooEth = "0x5555763613a12d8f3e73be831dff8598089d3dca";

const [ name, sym ] = await Promise.all([
contract.name(),
contract.symbol()
const [ name, sym, name_1, symbol_1 ] = await Promise.all([
contract.name({ blockTag: "latest" }),
contract.symbol({ blockTag: "latest" }),
contract.balanceOf(ricmooEth, { blockTag: "latest" }),
contract.balanceOf(ricmooEth, { blockTag: blockNumber - 1 }),
]);

console.log(name, sym);
console.log({ name, sym, name_1, symbol_1 });
})();

0 comments on commit 904712d

Please sign in to comment.