Skip to content

Commit c27925c

Browse files
committed
Refactor ERC-4337 helper
1 parent 6d74f30 commit c27925c

File tree

2 files changed

+31
-55
lines changed

2 files changed

+31
-55
lines changed

test/account/Account.behavior.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function shouldBehaveLikeAnAccountBase() {
2525
it('should revert if the caller is not the canonical entrypoint', async function () {
2626
const selector = this.mock.interface.getFunction('executeUserOp').selector;
2727
const operation = await this.mock
28-
.createOp({
28+
.createUserOp({
2929
callData: ethers.concat([
3030
selector,
3131
ethers.AbiCoder.defaultAbiCoder().encode(
@@ -49,7 +49,7 @@ function shouldBehaveLikeAnAccountBase() {
4949
it('should return SIG_VALIDATION_SUCCESS if the signature is valid', async function () {
5050
const selector = this.mock.interface.getFunction('executeUserOp').selector;
5151
const operation = await this.mock
52-
.createOp({
52+
.createUserOp({
5353
callData: ethers.concat([
5454
selector,
5555
ethers.AbiCoder.defaultAbiCoder().encode(
@@ -69,7 +69,7 @@ function shouldBehaveLikeAnAccountBase() {
6969

7070
it('should return SIG_VALIDATION_FAILURE if the signature is invalid', async function () {
7171
const selector = this.mock.interface.getFunction('executeUserOp').selector;
72-
const operation = await this.mock.createOp({
72+
const operation = await this.mock.createUserOp({
7373
callData: ethers.concat([
7474
selector,
7575
ethers.AbiCoder.defaultAbiCoder().encode(
@@ -91,7 +91,7 @@ function shouldBehaveLikeAnAccountBase() {
9191
it('should pay missing account funds for execution', async function () {
9292
const selector = this.mock.interface.getFunction('executeUserOp').selector;
9393
const operation = await this.mock
94-
.createOp({
94+
.createUserOp({
9595
callData: ethers.concat([
9696
selector,
9797
ethers.AbiCoder.defaultAbiCoder().encode(
@@ -209,7 +209,7 @@ function shouldBehaveLikeAnAccountBaseExecutor({ deployable = true } = {}) {
209209

210210
const selector = this.mock.interface.getFunction('executeUserOp').selector;
211211
const operation = await this.mock
212-
.createOp({
212+
.createUserOp({
213213
callData: ethers.concat([
214214
selector,
215215
ethers.AbiCoder.defaultAbiCoder().encode(
@@ -230,7 +230,7 @@ function shouldBehaveLikeAnAccountBaseExecutor({ deployable = true } = {}) {
230230
it('should be created with handleOps and increase nonce', async function () {
231231
const selector = this.mock.interface.getFunction('executeUserOp').selector;
232232
const operation = await this.mock
233-
.createOp({
233+
.createUserOp({
234234
callData: ethers.concat([
235235
selector,
236236
ethers.AbiCoder.defaultAbiCoder().encode(
@@ -253,7 +253,7 @@ function shouldBehaveLikeAnAccountBaseExecutor({ deployable = true } = {}) {
253253
it('should revert if the signature is invalid', async function () {
254254
const selector = this.mock.interface.getFunction('executeUserOp').selector;
255255
const operation = await this.mock
256-
.createOp({
256+
.createUserOp({
257257
callData: ethers.concat([
258258
selector,
259259
ethers.AbiCoder.defaultAbiCoder().encode(
@@ -279,7 +279,7 @@ function shouldBehaveLikeAnAccountBaseExecutor({ deployable = true } = {}) {
279279
it('should increase nonce and call target', async function () {
280280
const selector = this.mock.interface.getFunction('executeUserOp').selector;
281281
const operation = await this.mock
282-
.createOp({
282+
.createUserOp({
283283
callData: ethers.concat([
284284
selector,
285285
ethers.AbiCoder.defaultAbiCoder().encode(

test/helpers/erc4337.js

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const parseInitCode = initCode => ({
1111
/// Global ERC-4337 environment helper.
1212
class ERC4337Helper {
1313
constructor() {
14-
this.cache = new Map();
1514
this.envAsPromise = Promise.all([ethers.provider.getNetwork(), ethers.deployContract('Create2Mock')]).then(
1615
([{ chainId }, factory]) => ({
1716
chainId,
@@ -25,17 +24,14 @@ class ERC4337Helper {
2524
}
2625

2726
async newAccount(name, extraArgs = [], params = {}) {
28-
const { factory } = await this.wait();
27+
const { factory, chainId } = await this.wait();
2928

30-
if (!this.cache.has(name)) {
31-
await ethers.getContractFactory(name).then(factory => this.cache.set(name, factory));
32-
}
33-
const accountFactory = this.cache.get(name);
29+
const accountFactory = await ethers.getContractFactory(name);
3430

3531
if (params.erc7702signer) {
3632
const delegate = await accountFactory.deploy(...extraArgs);
3733
const instance = await params.erc7702signer.getAddress().then(address => accountFactory.attach(address));
38-
return new ERC7702SmartAccount(instance, delegate, this);
34+
return new ERC7702SmartAccount(instance, chainId, delegate);
3935
} else {
4036
const initCode = await accountFactory
4137
.getDeployTransaction(...extraArgs)
@@ -46,34 +42,18 @@ class ERC4337Helper {
4642
const instance = await senderCreator.createSender
4743
.staticCall(initCode)
4844
.then(address => accountFactory.attach(address));
49-
return new SmartAccount(instance, initCode, this);
50-
}
51-
}
52-
53-
async fillUserOp(userOp) {
54-
if (!userOp.nonce) {
55-
userOp.nonce = await entrypoint.getNonce(userOp.sender, 0);
56-
}
57-
if (ethers.isAddressable(userOp.paymaster)) {
58-
userOp.paymaster = await ethers.resolveAddress(userOp.paymaster);
59-
userOp.paymasterVerificationGasLimit ??= 100_000n;
60-
userOp.paymasterPostOpGasLimit ??= 100_000n;
61-
userOp.paymasterAndData = ethers.solidityPacked(
62-
['address', 'uint128', 'uint128'],
63-
[userOp.paymaster, userOp.paymasterVerificationGasLimit, userOp.paymasterPostOpGasLimit],
64-
);
45+
return new SmartAccount(instance, chainId, initCode);
6546
}
66-
return userOp;
6747
}
6848
}
6949

7050
/// Represent one ERC-4337 account contract.
7151
class SmartAccount extends ethers.BaseContract {
72-
constructor(instance, initCode, helper) {
52+
constructor(instance, chainId, initCode) {
7353
super(instance.target, instance.interface, instance.runner, instance.deployTx);
7454
this.address = instance.target;
55+
this.chainId = chainId;
7556
this.initCode = initCode;
76-
this.helper = helper;
7757
}
7858

7959
async deploy(account = this.runner) {
@@ -82,48 +62,44 @@ class SmartAccount extends ethers.BaseContract {
8262
return this;
8363
}
8464

85-
createOp(userOp = {}) {
86-
return this.helper
87-
.fillUserOp({ sender: this, ...userOp })
88-
.then(filledUserOp => new UserOperationWithContext(filledUserOp));
65+
async createUserOp(userOp = {}) {
66+
userOp.sender ??= this;
67+
userOp.nonce ??= await entrypoint.getNonce(userOp.sender, 0);
68+
if (ethers.isAddressable(userOp.paymaster)) {
69+
userOp.paymaster = await ethers.resolveAddress(userOp.paymaster);
70+
userOp.paymasterVerificationGasLimit ??= 100_000n;
71+
userOp.paymasterPostOpGasLimit ??= 100_000n;
72+
}
73+
return new UserOperationWithContext(userOp);
8974
}
9075
}
9176

92-
class ERC7702SmartAccount extends ethers.BaseContract {
93-
constructor(instance, delegate, helper) {
94-
super(instance.target, instance.interface, instance.runner, instance.deployTx);
95-
this.address = instance.target;
77+
class ERC7702SmartAccount extends SmartAccount {
78+
constructor(instance, chainId, delegate) {
79+
super(instance, chainId);
9680
this.delegate = delegate;
97-
this.helper = helper;
9881
}
9982

10083
async deploy() {
10184
await ethers.provider.getCode(this.delegate).then(code => setCode(this.target, code));
10285
return this;
10386
}
104-
105-
createOp(userOp = {}) {
106-
return this.helper
107-
.fillUserOp({ sender: this, ...userOp })
108-
.then(filledUserOp => new UserOperationWithContext(filledUserOp));
109-
}
11087
}
11188

11289
class UserOperationWithContext extends UserOperation {
11390
constructor(params) {
11491
super(params);
115-
this.params = params;
92+
this.initCode = params.sender?.initCode;
93+
this.chainId = params.sender?.chainId;
11694
}
11795

11896
addInitCode() {
119-
const { initCode } = this.params.sender;
120-
if (!initCode) throw new Error('No init code available for the sender of this user operation');
121-
return Object.assign(this, parseInitCode(initCode));
97+
if (!this.initCode) throw new Error('No init code available for the sender of this user operation');
98+
return Object.assign(this, parseInitCode(this.initCode));
12299
}
123100

124101
hash() {
125-
const { chainId } = this.params.sender.helper.env;
126-
return super.hash(entrypoint, chainId);
102+
return super.hash(entrypoint, this.chainId);
127103
}
128104
}
129105

0 commit comments

Comments
 (0)