Add factory(), factoryData() and paymasterData() helpers to ERC4337Utils (#5313)
Co-authored-by: Ernesto García <ernestognw@gmail.com>
This commit is contained in:
@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user