Skip to content

feat(MulticallerClient): Allow caller to use PermissionedMulticaller #2219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/clients/MultiCallerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getProvider,
Multicall2Call,
assert,
getPermissionedMultisender,
} from "../utils";
import { AugmentedTransaction, TransactionClient } from "./TransactionClient";
import lodash from "lodash";
Expand Down Expand Up @@ -260,10 +261,21 @@ export class MultiCallerClient {
return this.baseSigner ? getMultisender(chainId, this.baseSigner.connect(await getProvider(chainId))) : undefined;
}

async _getPermissionedMultisender(chainId: number): Promise<Contract | undefined> {
return this.baseSigner
? getPermissionedMultisender(chainId, this.baseSigner.connect(await getProvider(chainId)))
: undefined;
}

async buildMultiSenderBundle(transactions: AugmentedTransaction[]): Promise<AugmentedTransaction> {
// Validate all transactions have the same chainId and can be sent from multisender.
const { chainId } = transactions[0];
const multisender = await this._getMultisender(chainId);
// If any transactions are to be sent through the permissioned multisender, then we should use that instead
// of the default multisender. This means that the caller must only batch transactions to this chain that can
// be executed from the permissioned multisender.
const multisender = transactions.some((t) => t.sendThroughPermissionedMulticall)
? await this._getPermissionedMultisender(chainId)
: await this._getMultisender(chainId);
if (!multisender) {
throw new Error("Multisender not available for this chain");
}
Expand All @@ -272,12 +284,17 @@ export class MultiCallerClient {
const callData: Multicall2Call[] = [];
let gasLimit: BigNumber | undefined = bnZero;
transactions.forEach((txn, idx) => {
if (!txn.unpermissioned || txn.chainId !== chainId) {
if (!txn.unpermissioned || !txn.sendThroughPermissionedMulticall || txn.chainId !== chainId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct?

This check seems to be saying: "if any of the txns in transactions required permissions => throw". But this is not what we're going for with the PermissionedMulticall?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we fix it here, let's first discuss our strat for using this in finalizer in Slack

Copy link
Member Author

@nicholaspai nicholaspai May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, should be:

Suggested change
if (!txn.unpermissioned || !txn.sendThroughPermissionedMulticall || txn.chainId !== chainId) {
if (!(txn.unpermissioned || txn.sendThroughPermissionedMulticall) || txn.chainId !== chainId) {

this.logger.error({
at: "MultiCallerClient#buildMultiSenderBundle",
message: "Some transactions in the queue contain different target chain or are permissioned",
transactions: transactions.map(({ contract, chainId, unpermissioned }) => {
return { target: getTarget(contract.address), unpermissioned: Boolean(unpermissioned), chainId };
return {
target: getTarget(contract.address),
unpermissioned: Boolean(unpermissioned),
sendThroughPermissionedMulticall: Boolean(txn.sendThroughPermissionedMulticall),
chainId,
};
}),
notificationPath: "across-error",
});
Expand Down Expand Up @@ -380,7 +397,7 @@ export class MultiCallerClient {
multisenderTxns = [],
unsendableTxns = [],
} = lodash.groupBy(txns, (txn) => {
if (txn.unpermissioned) {
if (txn.unpermissioned || txn.sendThroughPermissionedMulticall) {
return "multisenderTxns";
} else if (txn.contract.multicall) {
return "multicallerTxns";
Expand Down
2 changes: 2 additions & 0 deletions src/clients/TransactionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export interface AugmentedTransaction {
value?: BigNumber;
unpermissioned?: boolean; // If false, the transaction must be sent from the enqueuer of the method.
// If true, then can be sent from the MakerDAO multisender contract.
sendThroughPermissionedMulticall?: boolean;
// If true, then should be sent through the permissioned multisender contract.
canFailInSimulation?: boolean;
// Optional batch ID to use to group transactions
groupId?: string;
Expand Down
4 changes: 2 additions & 2 deletions src/finalizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,8 @@ export async function finalize(
hubChainId,
...configuredChainIds,
]).map(
async (chainId) =>
[chainId, await getMultisender(chainId, spokePoolClients[chainId].spokePool.signer)] as [number, Contract]
(chainId) =>
[chainId, getMultisender(chainId, spokePoolClients[chainId].spokePool.signer)] as [number, Contract]
)
)
);
Expand Down
6 changes: 5 additions & 1 deletion src/utils/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ export function getNetworkError(err: unknown): string {
return isEthersError(err) ? err.reason : isError(err) ? err.message : "unknown error";
}

export async function getMultisender(chainId: number, baseSigner: Signer): Promise<Contract | undefined> {
export function getMultisender(chainId: number, baseSigner: Signer): Contract | undefined {
return sdkUtils.getMulticall3(chainId, baseSigner);
}

export function getPermissionedMultisender(chainId: number, baseSigner: Signer): Contract | undefined {
return sdkUtils.getPermissionedMulticall3(chainId, baseSigner);
}

// Note that this function will throw if the call to the contract on method for given args reverts. Implementers
// of this method should be considerate of this and catch the response to deal with the error accordingly.
export async function runTransaction(
Expand Down