Skip to content
This repository has been archived by the owner on Nov 5, 2023. It is now read-only.

Add multi-action send transaction method to bls signer #540

Merged
merged 12 commits into from
Mar 14, 2023
Merged
126 changes: 95 additions & 31 deletions contracts/clients/src/BlsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import { Deferrable } from "ethers/lib/utils";

import { ActionData, Bundle } from "./signer/types";
import Aggregator, { BundleReceipt } from "./Aggregator";
import BlsSigner, { UncheckedBlsSigner, _constructorGuard } from "./BlsSigner";
import BlsSigner, {
TransactionBatchResponse,
UncheckedBlsSigner,
_constructorGuard,
} from "./BlsSigner";
import poll from "./helpers/poll";
import BlsWalletWrapper from "./BlsWalletWrapper";
import {
AggregatorUtilities__factory,
BLSWallet__factory,
} from "../typechain-types";
import addSafetyPremiumToFee from "./helpers/addSafetyDivisorToFee";

export default class BlsProvider extends ethers.providers.JsonRpcProvider {
readonly aggregator: Aggregator;
Expand Down Expand Up @@ -57,37 +62,18 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
this,
);

const aggregatorUtilitiesContract = AggregatorUtilities__factory.connect(
this.aggregatorUtilitiesAddress,
this,
);
const actionWithFeePaymentAction =
this._addFeePaymentActionForFeeEstimation([action]);

const feeEstimate = await this.aggregator.estimateFee(
this.signer.wallet.sign({
nonce,
actions: [
action,
{
ethValue: 1,
// Provide 1 wei with this action so that the fee transfer to
// tx.origin can be included in the gas estimate.
contractAddress: this.aggregatorUtilitiesAddress,
encodedFunction:
aggregatorUtilitiesContract.interface.encodeFunctionData(
"sendEthToTxOrigin",
),
},
],
actions: [...actionWithFeePaymentAction],
}),
);

// Due to small fluctuations is gas estimation, we add a little safety premium
// to the fee to increase the chance that it actually gets accepted during
// aggregation.
const safetyDivisor = 5;
const feeRequired = BigNumber.from(feeEstimate.feeRequired);
const safetyPremium = feeRequired.div(safetyDivisor);
return feeRequired.add(safetyPremium);
return addSafetyPremiumToFee(feeRequired);
}

override async sendTransaction(
Expand All @@ -101,20 +87,24 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
}

const resolvedTransaction = await signedTransaction;
const userBundle: Bundle = JSON.parse(resolvedTransaction);
const bundle: Bundle = JSON.parse(resolvedTransaction);

const result = await this.aggregator.add(userBundle);
if (bundle.operations.length > 1) {
throw new Error(
"Can only operate on single operations. Call provider.sendTransactionBatch instead",
);
}

const result = await this.aggregator.add(bundle);

if ("failures" in result) {
throw new Error(JSON.stringify(result.failures));
}

// TODO: bls-wallet #375 Add multi-action transactions to BlsProvider & BlsSigner
// We're assuming the first operation and action constitute the correct values. We will need to refactor this when we add multi-action transactions
const actionData: ActionData = {
ethValue: userBundle.operations[0].actions[0].ethValue,
contractAddress: userBundle.operations[0].actions[0].contractAddress,
encodedFunction: userBundle.operations[0].actions[0].encodedFunction,
ethValue: bundle.operations[0].actions[0].ethValue,
contractAddress: bundle.operations[0].actions[0].contractAddress,
encodedFunction: bundle.operations[0].actions[0].encodedFunction,
};

return this.signer.constructTransactionResponse(
Expand All @@ -124,6 +114,35 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
);
}

async sendTransactionBatch(
signedTransactionBatch: string,
): Promise<TransactionBatchResponse> {
// TODO: bls-wallet #413 Move references to private key outside of BlsSigner.
// Without doing this, we would have to call `const signer = this.getSigner(privateKey)`.
// We do not want to pass the private key to this method.
if (!this.signer) {
throw new Error("Call provider.getSigner first");
}

const bundle: Bundle = JSON.parse(signedTransactionBatch);

const result = await this.aggregator.add(bundle);

if ("failures" in result) {
throw new Error(JSON.stringify(result.failures));
}

const actionData: Array<ActionData> = bundle.operations
.map((operation) => operation.actions)
.flat();

return this.signer.constructTransactionBatchResponse(
actionData,
result.hash,
this.signer.wallet.address,
);
}

override getSigner(
privateKey: string,
addressOrIndex?: string | number,
Expand Down Expand Up @@ -231,4 +250,49 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
status: bundleReceipt.status,
};
}

_addFeePaymentActionForFeeEstimation(
actions: Array<ActionData>,
): Array<ActionData> {
const aggregatorUtilitiesContract = AggregatorUtilities__factory.connect(
this.aggregatorUtilitiesAddress,
this,
);

return [
...actions,
{
// Provide 1 wei with this action so that the fee transfer to
// tx.origin can be included in the gas estimate.
ethValue: 1,
contractAddress: this.aggregatorUtilitiesAddress,
encodedFunction:
aggregatorUtilitiesContract.interface.encodeFunctionData(
"sendEthToTxOrigin",
),
},
];
}

_addFeePaymentActionWithSafeFee(
actions: Array<ActionData>,
fee: BigNumber,
): Array<ActionData> {
const aggregatorUtilitiesContract = AggregatorUtilities__factory.connect(
this.aggregatorUtilitiesAddress,
this,
);

return [
...actions,
{
ethValue: fee,
contractAddress: this.aggregatorUtilitiesAddress,
encodedFunction:
aggregatorUtilitiesContract.interface.encodeFunctionData(
"sendEthToTxOrigin",
),
},
];
}
}
Loading