Skip to content
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

Add factory(), factoryData() and paymasterData() helpers to ERC4337Utils #5313

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
ERC4337Utils: add paymasterData()
  • Loading branch information
Amxx committed Nov 20, 2024
commit 2eb1be16af7ca713b86f81b6d4894756894b10a0
11 changes: 8 additions & 3 deletions contracts/account/utils/draft-ERC4337Utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,22 @@ library ERC4337Utils {

/// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
return address(bytes20(self.paymasterAndData[0:20]));
return self.paymasterAndData.length < 52 ? address(0) : address(bytes20(self.paymasterAndData[0:20]));
}

/// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}.
function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(bytes16(self.paymasterAndData[20:36]));
return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[20:36]));
}

/// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(bytes16(self.paymasterAndData[36:52]));
return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52]));
}

/// @dev Returns the forth section of `paymasterAndData` from the {PackedUserOperation}.
function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:];
}

// slither-disable-next-line write-after-write
Expand Down
78 changes: 44 additions & 34 deletions test/account/utils/draft-ERC4337Utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');

const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337');
const { packValidationData, UserOperation } = require('../../helpers/erc4337');
const { MAX_UINT48 } = require('../../helpers/constants');

const fixture = async () => {
Expand Down Expand Up @@ -144,36 +144,31 @@ describe('ERC4337Utils', function () {
});

describe('userOp values', function () {
it('returns factory', async function () {
const userOp = new UserOperation({
sender: this.sender,
nonce: 1,
verificationGas: 0x12345678n,
factory: this.factory,
factoryData: '0x123456',
});
expect(this.utils.$factory(userOp.packed)).to.eventually.equal(this.factory);
});
describe('intiCode', function () {
beforeEach(async function () {
this.userOp = new UserOperation({
sender: this.sender,
nonce: 1,
verificationGas: 0x12345678n,
factory: this.factory,
factoryData: '0x123456',
});

it('returns factoryData', async function () {
const userOp = new UserOperation({
sender: this.sender,
nonce: 1,
verificationGas: 0x12345678n,
factory: this.factory,
factoryData: '0x123456',
this.emptyUserOp = new UserOperation({
sender: this.sender,
nonce: 1,
});
});
expect(this.utils.$factoryData(userOp.packed)).to.eventually.equal('0x123456');
});

it("returns factory when UserOperation doesn't include no initcode", async function () {
const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n });
expect(this.utils.$factory(userOp.packed)).to.eventually.equal(ethers.ZeroAddress);
});
it('returns factory', async function () {
expect(this.utils.$factory(this.userOp.packed)).to.eventually.equal(this.factory);
expect(this.utils.$factory(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
});

it("returns factoryData when UserOperation doesn't include no initcode", async function () {
const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n });
expect(this.utils.$factoryData(userOp.packed)).to.eventually.equal('0x');
it('returns factoryData', async function () {
expect(this.utils.$factoryData(this.userOp.packed)).to.eventually.equal('0x123456');
expect(this.utils.$factoryData(this.emptyUserOp.packed)).to.eventually.equal('0x');
});
});

it('returns verificationGasLimit', async function () {
Expand Down Expand Up @@ -208,28 +203,43 @@ describe('ERC4337Utils', function () {

describe('paymasterAndData', function () {
beforeEach(async function () {
this.verificationGasLimit = 0x12345678n;
this.postOpGasLimit = 0x87654321n;
this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit);
this.userOp = new UserOperation({
sender: this.sender,
nonce: 1,
paymasterAndData: this.paymasterAndData,
paymaster: this.paymaster,
paymasterVerificationGasLimit: 0x12345678n,
paymasterPostOpGasLimit: 0x87654321n,
paymasterData: '0xbeefcafe',
});

this.emptyUserOp = new UserOperation({
sender: this.sender,
nonce: 1,
});
});

it('returns paymaster', async function () {
expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster);
expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.userOp.paymaster);
expect(this.utils.$paymaster(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
});

it('returns verificationGasLimit', async function () {
expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal(
this.verificationGasLimit,
this.userOp.paymasterVerificationGasLimit,
);
expect(this.utils.$paymasterVerificationGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
});

it('returns postOpGasLimit', async function () {
expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(this.postOpGasLimit);
expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(
this.userOp.paymasterPostOpGasLimit,
);
expect(this.utils.$paymasterPostOpGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
});

it('returns paymasterData', async function () {
expect(this.utils.$paymasterData(this.userOp.packed)).to.eventually.equal(this.userOp.paymasterData);
expect(this.utils.$paymasterData(this.emptyUserOp.packed)).to.eventually.equal(0n);
});
});
});
Expand Down
31 changes: 23 additions & 8 deletions test/helpers/erc4337.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ function packValidationData(validAfter, validUntil, authorizer) {
);
}

function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) {
function packInitCode(factory, factoryData) {
return ethers.solidityPacked(['address', 'bytes'], [getAddress(factory), factoryData]);
}

function packPaymasterAndData(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData) {
return ethers.solidityPacked(
['address', 'uint128', 'uint128'],
[getAddress(paymaster), verificationGasLimit, postOpGasLimit],
['address', 'uint128', 'uint128', 'bytes'],
[getAddress(paymaster), paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData],
);
}

Expand All @@ -38,28 +42,38 @@ class UserOperation {
constructor(params) {
this.sender = getAddress(params.sender);
this.nonce = params.nonce;
this.factory = params.factory?.target ?? params.factory?.address ?? params.factory ?? null;
this.factory = params.factory ?? undefined;
this.factoryData = params.factoryData ?? '0x';
this.callData = params.callData ?? '0x';
this.verificationGas = params.verificationGas ?? 10_000_000n;
this.callGas = params.callGas ?? 100_000n;
this.preVerificationGas = params.preVerificationGas ?? 100_000n;
this.maxPriorityFee = params.maxPriorityFee ?? 100_000n;
this.maxFeePerGas = params.maxFeePerGas ?? 100_000n;
this.paymasterAndData = params.paymasterAndData ?? '0x';
this.paymaster = params.paymaster ?? undefined;
this.paymasterVerificationGasLimit = params.paymasterVerificationGasLimit ?? 0n;
this.paymasterPostOpGasLimit = params.paymasterPostOpGasLimit ?? 0n;
this.paymasterData = params.paymasterData ?? '0x';
this.signature = params.signature ?? '0x';
}

get packed() {
return {
sender: this.sender,
nonce: this.nonce,
initCode: ethers.concat([this.factory ?? '0x', this.factoryData]),
initCode: this.factory ? packInitCode(this.factory, this.factoryData) : '0x',
callData: this.callData,
accountGasLimits: pack(this.verificationGas, this.callGas),
preVerificationGas: this.preVerificationGas,
gasFees: pack(this.maxPriorityFee, this.maxFeePerGas),
paymasterAndData: this.paymasterAndData,
paymasterAndData: this.paymaster
? packPaymasterAndData(
this.paymaster,
this.paymasterVerificationGasLimit,
this.paymasterPostOpGasLimit,
this.paymasterData,
)
: '0x',
signature: this.signature,
};
}
Expand Down Expand Up @@ -91,6 +105,7 @@ module.exports = {
SIG_VALIDATION_SUCCESS,
SIG_VALIDATION_FAILURE,
packValidationData,
packPaymasterData,
packInitCode,
packPaymasterAndData,
UserOperation,
};
Loading