Skip to content

Commit e1d44e0

Browse files
Amxxernestognw
andauthored
Add factory(), factoryData() and paymasterData() helpers to ERC4337Utils (#5313)
Co-authored-by: Ernesto García <ernestognw@gmail.com>
1 parent 0513853 commit e1d44e0

File tree

4 files changed

+104
-24
lines changed

4 files changed

+104
-24
lines changed

contracts/account/utils/draft-ERC4337Utils.sol

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ library ERC4337Utils {
9898
return result;
9999
}
100100

101+
/// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted.
102+
function factory(PackedUserOperation calldata self) internal pure returns (address) {
103+
return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20]));
104+
}
105+
106+
/// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted.
107+
function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
108+
return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:];
109+
}
110+
101111
/// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
102112
function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
103113
return uint128(self.accountGasLimits.extract_32_16(0x00));
@@ -130,16 +140,29 @@ library ERC4337Utils {
130140

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

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

141151
/// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
142152
function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
143-
return uint128(bytes16(self.paymasterAndData[36:52]));
153+
return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52]));
154+
}
155+
156+
/// @dev Returns the forth section of `paymasterAndData` from the {PackedUserOperation}.
157+
function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
158+
return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:];
159+
}
160+
161+
// slither-disable-next-line write-after-write
162+
function _emptyCalldataBytes() private pure returns (bytes calldata result) {
163+
assembly ("memory-safe") {
164+
result.offset := 0
165+
result.length := 0
166+
}
144167
}
145168
}

test/account/utils/draft-ERC4337Utils.test.js

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ const { ethers } = require('hardhat');
22
const { expect } = require('chai');
33
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
44

5-
const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337');
5+
const { packValidationData, UserOperation } = require('../../helpers/erc4337');
66
const { MAX_UINT48 } = require('../../helpers/constants');
77

88
const fixture = async () => {
9-
const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners();
9+
const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners();
1010
const utils = await ethers.deployContract('$ERC4337Utils');
11-
return { utils, authorizer, sender, entrypoint, paymaster };
11+
return { utils, authorizer, sender, entrypoint, factory, paymaster };
1212
};
1313

1414
describe('ERC4337Utils', function () {
@@ -144,6 +144,33 @@ describe('ERC4337Utils', function () {
144144
});
145145

146146
describe('userOp values', function () {
147+
describe('intiCode', function () {
148+
beforeEach(async function () {
149+
this.userOp = new UserOperation({
150+
sender: this.sender,
151+
nonce: 1,
152+
verificationGas: 0x12345678n,
153+
factory: this.factory,
154+
factoryData: '0x123456',
155+
});
156+
157+
this.emptyUserOp = new UserOperation({
158+
sender: this.sender,
159+
nonce: 1,
160+
});
161+
});
162+
163+
it('returns factory', async function () {
164+
expect(this.utils.$factory(this.userOp.packed)).to.eventually.equal(this.factory);
165+
expect(this.utils.$factory(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
166+
});
167+
168+
it('returns factoryData', async function () {
169+
expect(this.utils.$factoryData(this.userOp.packed)).to.eventually.equal('0x123456');
170+
expect(this.utils.$factoryData(this.emptyUserOp.packed)).to.eventually.equal('0x');
171+
});
172+
});
173+
147174
it('returns verificationGasLimit', async function () {
148175
const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n });
149176
expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas);
@@ -176,28 +203,43 @@ describe('ERC4337Utils', function () {
176203

177204
describe('paymasterAndData', function () {
178205
beforeEach(async function () {
179-
this.verificationGasLimit = 0x12345678n;
180-
this.postOpGasLimit = 0x87654321n;
181-
this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit);
182206
this.userOp = new UserOperation({
183207
sender: this.sender,
184208
nonce: 1,
185-
paymasterAndData: this.paymasterAndData,
209+
paymaster: this.paymaster,
210+
paymasterVerificationGasLimit: 0x12345678n,
211+
paymasterPostOpGasLimit: 0x87654321n,
212+
paymasterData: '0xbeefcafe',
213+
});
214+
215+
this.emptyUserOp = new UserOperation({
216+
sender: this.sender,
217+
nonce: 1,
186218
});
187219
});
188220

189221
it('returns paymaster', async function () {
190-
expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster);
222+
expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.userOp.paymaster);
223+
expect(this.utils.$paymaster(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
191224
});
192225

193226
it('returns verificationGasLimit', async function () {
194227
expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal(
195-
this.verificationGasLimit,
228+
this.userOp.paymasterVerificationGasLimit,
196229
);
230+
expect(this.utils.$paymasterVerificationGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
197231
});
198232

199233
it('returns postOpGasLimit', async function () {
200-
expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(this.postOpGasLimit);
234+
expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(
235+
this.userOp.paymasterPostOpGasLimit,
236+
);
237+
expect(this.utils.$paymasterPostOpGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
238+
});
239+
240+
it('returns data', async function () {
241+
expect(this.utils.$paymasterData(this.userOp.packed)).to.eventually.equal(this.userOp.paymasterData);
242+
expect(this.utils.$paymasterData(this.emptyUserOp.packed)).to.eventually.equal('0x');
201243
});
202244
});
203245
});

test/account/utils/draft-ERC7579Utils.test.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const { ethers } = require('hardhat');
22
const { expect } = require('chai');
3-
const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers');
3+
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
44
const {
55
EXEC_TYPE_DEFAULT,
66
EXEC_TYPE_TRY,
@@ -17,11 +17,10 @@ const coder = ethers.AbiCoder.defaultAbiCoder();
1717

1818
const fixture = async () => {
1919
const [sender] = await ethers.getSigners();
20-
const utils = await ethers.deployContract('$ERC7579Utils');
20+
const utils = await ethers.deployContract('$ERC7579Utils', { value: ethers.parseEther('1') });
2121
const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock');
2222
const target = await ethers.deployContract('CallReceiverMock');
2323
const anotherTarget = await ethers.deployContract('CallReceiverMock');
24-
await setBalance(utils.target, ethers.parseEther('1'));
2524
return { utils, utilsGlobal, target, anotherTarget, sender };
2625
};
2726

test/helpers/erc4337.js

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@ function packValidationData(validAfter, validUntil, authorizer) {
2626
);
2727
}
2828

29-
function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) {
29+
function packInitCode(factory, factoryData) {
30+
return ethers.solidityPacked(['address', 'bytes'], [getAddress(factory), factoryData]);
31+
}
32+
33+
function packPaymasterAndData(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData) {
3034
return ethers.solidityPacked(
31-
['address', 'uint128', 'uint128'],
32-
[getAddress(paymaster), verificationGasLimit, postOpGasLimit],
35+
['address', 'uint128', 'uint128', 'bytes'],
36+
[getAddress(paymaster), paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData],
3337
);
3438
}
3539

@@ -38,27 +42,38 @@ class UserOperation {
3842
constructor(params) {
3943
this.sender = getAddress(params.sender);
4044
this.nonce = params.nonce;
41-
this.initCode = params.initCode ?? '0x';
45+
this.factory = params.factory ?? undefined;
46+
this.factoryData = params.factoryData ?? '0x';
4247
this.callData = params.callData ?? '0x';
4348
this.verificationGas = params.verificationGas ?? 10_000_000n;
4449
this.callGas = params.callGas ?? 100_000n;
4550
this.preVerificationGas = params.preVerificationGas ?? 100_000n;
4651
this.maxPriorityFee = params.maxPriorityFee ?? 100_000n;
4752
this.maxFeePerGas = params.maxFeePerGas ?? 100_000n;
48-
this.paymasterAndData = params.paymasterAndData ?? '0x';
53+
this.paymaster = params.paymaster ?? undefined;
54+
this.paymasterVerificationGasLimit = params.paymasterVerificationGasLimit ?? 0n;
55+
this.paymasterPostOpGasLimit = params.paymasterPostOpGasLimit ?? 0n;
56+
this.paymasterData = params.paymasterData ?? '0x';
4957
this.signature = params.signature ?? '0x';
5058
}
5159

5260
get packed() {
5361
return {
5462
sender: this.sender,
5563
nonce: this.nonce,
56-
initCode: this.initCode,
64+
initCode: this.factory ? packInitCode(this.factory, this.factoryData) : '0x',
5765
callData: this.callData,
5866
accountGasLimits: pack(this.verificationGas, this.callGas),
5967
preVerificationGas: this.preVerificationGas,
6068
gasFees: pack(this.maxPriorityFee, this.maxFeePerGas),
61-
paymasterAndData: this.paymasterAndData,
69+
paymasterAndData: this.paymaster
70+
? packPaymasterAndData(
71+
this.paymaster,
72+
this.paymasterVerificationGasLimit,
73+
this.paymasterPostOpGasLimit,
74+
this.paymasterData,
75+
)
76+
: '0x',
6277
signature: this.signature,
6378
};
6479
}
@@ -90,6 +105,7 @@ module.exports = {
90105
SIG_VALIDATION_SUCCESS,
91106
SIG_VALIDATION_FAILURE,
92107
packValidationData,
93-
packPaymasterData,
108+
packInitCode,
109+
packPaymasterAndData,
94110
UserOperation,
95111
};

0 commit comments

Comments
 (0)