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

Co-authored-by: Ernesto García <ernestognw@gmail.com>
pull/5315/head^2
Hadrien Croubois 2 months ago committed by GitHub
parent 0513853ca5
commit e1d44e0342
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 29
      contracts/account/utils/draft-ERC4337Utils.sol
  2. 62
      test/account/utils/draft-ERC4337Utils.test.js
  3. 5
      test/account/utils/draft-ERC7579Utils.test.js
  4. 32
      test/helpers/erc4337.js

@ -98,6 +98,16 @@ library ERC4337Utils {
return result;
}
/// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted.
function factory(PackedUserOperation calldata self) internal pure returns (address) {
return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20]));
}
/// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted.
function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:];
}
/// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
return uint128(self.accountGasLimits.extract_32_16(0x00));
@ -130,16 +140,29 @@ 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
function _emptyCalldataBytes() private pure returns (bytes calldata result) {
assembly ("memory-safe") {
result.offset := 0
result.length := 0
}
}
}

@ -2,13 +2,13 @@ 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 () => {
const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners();
const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners();
const utils = await ethers.deployContract('$ERC4337Utils');
return { utils, authorizer, sender, entrypoint, paymaster };
return { utils, authorizer, sender, entrypoint, factory, paymaster };
};
describe('ERC4337Utils', function () {
@ -144,6 +144,33 @@ describe('ERC4337Utils', function () {
});
describe('userOp values', function () {
describe('intiCode', function () {
beforeEach(async function () {
this.userOp = new UserOperation({
sender: this.sender,
nonce: 1,
verificationGas: 0x12345678n,
factory: this.factory,
factoryData: '0x123456',
});
this.emptyUserOp = new UserOperation({
sender: this.sender,
nonce: 1,
});
});
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', 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 () {
const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n });
expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas);
@ -176,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 data', 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('0x');
});
});
});

@ -1,6 +1,6 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const {
EXEC_TYPE_DEFAULT,
EXEC_TYPE_TRY,
@ -17,11 +17,10 @@ const coder = ethers.AbiCoder.defaultAbiCoder();
const fixture = async () => {
const [sender] = await ethers.getSigners();
const utils = await ethers.deployContract('$ERC7579Utils');
const utils = await ethers.deployContract('$ERC7579Utils', { value: ethers.parseEther('1') });
const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock');
const target = await ethers.deployContract('CallReceiverMock');
const anotherTarget = await ethers.deployContract('CallReceiverMock');
await setBalance(utils.target, ethers.parseEther('1'));
return { utils, utilsGlobal, target, anotherTarget, sender };
};

@ -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],
);
}
@ -38,14 +42,18 @@ class UserOperation {
constructor(params) {
this.sender = getAddress(params.sender);
this.nonce = params.nonce;
this.initCode = params.initCode ?? '0x';
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';
}
@ -53,12 +61,19 @@ class UserOperation {
return {
sender: this.sender,
nonce: this.nonce,
initCode: this.initCode,
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,
};
}
@ -90,6 +105,7 @@ module.exports = {
SIG_VALIDATION_SUCCESS,
SIG_VALIDATION_FAILURE,
packValidationData,
packPaymasterData,
packInitCode,
packPaymasterAndData,
UserOperation,
};

Loading…
Cancel
Save