Skip to content
This repository was archived by the owner on Oct 20, 2024. It is now read-only.

Commit 6ad110d

Browse files
authored
Fix VGL estimates that have a dependency on execution (#343)
1 parent 9d94ff4 commit 6ad110d

File tree

9 files changed

+89
-38
lines changed

9 files changed

+89
-38
lines changed

e2e/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ const config: IConfig = {
1616

1717
// https://github.com/stackup-wallet/contracts/blob/main/contracts/test
1818
testERC20Token: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B",
19-
testGas: "0xc2e76Ee793a194Dd930C18c4cDeC93E7C75d567C",
20-
testAccount: "0x3dFD39F2c17625b301ae0EF72B411D1de5211325",
19+
testGas: "0x450d8479B0ceF1e6933DED809e12845aF413A50D",
20+
testAccount: "0x6D7d359cE9e60dDa36EE712cE9B5947B4C72F862",
2121
};
2222

2323
export default config;

e2e/src/abi.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,19 @@ export const testGasABI = [
177177
type: "function",
178178
},
179179
{
180-
inputs: [{ internalType: "uint256", name: "", type: "uint256" }],
180+
inputs: [{ internalType: "uint256", name: "key", type: "uint256" }],
181181
name: "store",
182-
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
182+
outputs: [{ internalType: "uint256", name: "value", type: "uint256" }],
183183
stateMutability: "view",
184184
type: "function",
185185
},
186+
{
187+
inputs: [{ internalType: "uint256", name: "times", type: "uint256" }],
188+
name: "wasteGas",
189+
outputs: [],
190+
stateMutability: "nonpayable",
191+
type: "function",
192+
},
186193
];
187194

188195
export const testAccountABI = [
@@ -241,15 +248,15 @@ export const testAccountABI = [
241248
inputs: [
242249
{
243250
internalType: "uint256",
244-
name: "",
251+
name: "key",
245252
type: "uint256",
246253
},
247254
],
248255
name: "store",
249256
outputs: [
250257
{
251258
internalType: "uint256",
252-
name: "",
259+
name: "value",
253260
type: "uint256",
254261
},
255262
],
@@ -342,6 +349,19 @@ export const testAccountABI = [
342349
stateMutability: "nonpayable",
343350
type: "function",
344351
},
352+
{
353+
inputs: [
354+
{
355+
internalType: "uint256",
356+
name: "times",
357+
type: "uint256",
358+
},
359+
],
360+
name: "wasteGas",
361+
outputs: [],
362+
stateMutability: "nonpayable",
363+
type: "function",
364+
},
345365
{
346366
stateMutability: "payable",
347367
type: "receive",

e2e/src/testAccount.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { EntryPoint, EntryPoint__factory } from "userop/dist/typechain";
1111
import { testAccountABI } from "./abi";
1212

1313
const RECURSIVE_CALL_MODE = "0x0001";
14+
const FORCE_VALIDATION_OOG_MODE = "0x0002";
1415

1516
export class TestAccount extends UserOperationBuilder {
1617
private provider: ethers.providers.JsonRpcProvider;
@@ -64,4 +65,10 @@ export class TestAccount extends UserOperationBuilder {
6465
)
6566
).setSignature(RECURSIVE_CALL_MODE);
6667
}
68+
69+
forceValidationOOG(wasteGasMultiplier: number) {
70+
return this.setCallData(
71+
ethers.utils.defaultAbiCoder.encode(["uint256"], [wasteGasMultiplier])
72+
).setSignature(FORCE_VALIDATION_OOG_MODE);
73+
}
6774
}

e2e/test/verification.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,17 @@ describe("During the verification phase", () => {
3030
});
3131
});
3232
});
33+
34+
describe("With dependency on callGasLimit", () => {
35+
[0, 1, 2, 3, 4, 5].forEach((times) => {
36+
test(`Sender can run validation with non-simulated code that uses ${times} storage writes`, async () => {
37+
const response = await client.sendUserOperation(
38+
acc.forceValidationOOG(times)
39+
);
40+
const event = await response.wait();
41+
42+
expect(event?.args.success).toBe(true);
43+
});
44+
});
45+
});
3346
});

internal/config/values.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ type Values struct {
2121
SupportedEntryPoints []common.Address
2222
MaxVerificationGas *big.Int
2323
MaxBatchGasLimit *big.Int
24-
PMGasEstBuffer int64
2524
MaxOpTTL time.Duration
2625
MaxOpsForUnstakedSender int
2726
Beneficiary string
@@ -87,7 +86,6 @@ func GetValues() *Values {
8786
viper.SetDefault("erc4337_bundler_supported_entry_points", "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")
8887
viper.SetDefault("erc4337_bundler_max_verification_gas", 3000000)
8988
viper.SetDefault("erc4337_bundler_max_batch_gas_limit", 25000000)
90-
viper.SetDefault("erc4337_bundler_paymaster_gas_estimate_buffer", 10)
9189
viper.SetDefault("erc4337_bundler_max_op_ttl_seconds", 180)
9290
viper.SetDefault("erc4337_bundler_max_ops_for_unstaked_sender", 4)
9391
viper.SetDefault("erc4337_bundler_blocks_in_the_future", 25)
@@ -117,7 +115,6 @@ func GetValues() *Values {
117115
_ = viper.BindEnv("erc4337_bundler_beneficiary")
118116
_ = viper.BindEnv("erc4337_bundler_max_verification_gas")
119117
_ = viper.BindEnv("erc4337_bundler_max_batch_gas_limit")
120-
_ = viper.BindEnv("erc4337_bundler_paymaster_gas_estimate_buffer")
121118
_ = viper.BindEnv("erc4337_bundler_max_op_ttl_seconds")
122119
_ = viper.BindEnv("erc4337_bundler_max_ops_for_unstaked_sender")
123120
_ = viper.BindEnv("erc4337_bundler_eth_builder_url")
@@ -176,7 +173,6 @@ func GetValues() *Values {
176173
beneficiary := viper.GetString("erc4337_bundler_beneficiary")
177174
maxVerificationGas := big.NewInt(int64(viper.GetInt("erc4337_bundler_max_verification_gas")))
178175
maxBatchGasLimit := big.NewInt(int64(viper.GetInt("erc4337_bundler_max_batch_gas_limit")))
179-
pmGasEstBuffer := int64(viper.GetInt("erc4337_bundler_paymaster_gas_estimate_buffer"))
180176
maxOpTTL := time.Second * viper.GetDuration("erc4337_bundler_max_op_ttl_seconds")
181177
maxOpsForUnstakedSender := viper.GetInt("erc4337_bundler_max_ops_for_unstaked_sender")
182178
ethBuilderUrl := viper.GetString("erc4337_bundler_eth_builder_url")
@@ -198,7 +194,6 @@ func GetValues() *Values {
198194
Beneficiary: beneficiary,
199195
MaxVerificationGas: maxVerificationGas,
200196
MaxBatchGasLimit: maxBatchGasLimit,
201-
PMGasEstBuffer: pmGasEstBuffer,
202197
MaxOpTTL: maxOpTTL,
203198
MaxOpsForUnstakedSender: maxOpsForUnstakedSender,
204199
EthBuilderUrl: ethBuilderUrl,

internal/start/private.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func PrivateMode() {
131131
c := client.New(mem, ov, chain, conf.SupportedEntryPoints)
132132
c.SetGetUserOpReceiptFunc(client.GetUserOpReceiptWithEthClient(eth))
133133
c.SetGetGasEstimateFunc(
134-
client.GetGasEstimateWithEthClient(rpc, ov, chain, conf.MaxBatchGasLimit, conf.PMGasEstBuffer),
134+
client.GetGasEstimateWithEthClient(rpc, ov, chain, conf.MaxBatchGasLimit),
135135
)
136136
c.SetGetUserOpByHashFunc(client.GetUserOpByHashWithEthClient(eth))
137137
c.UseLogger(logr)

internal/start/searcher.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func SearcherMode() {
123123
c := client.New(mem, ov, chain, conf.SupportedEntryPoints)
124124
c.SetGetUserOpReceiptFunc(client.GetUserOpReceiptWithEthClient(eth))
125125
c.SetGetGasEstimateFunc(
126-
client.GetGasEstimateWithEthClient(rpc, ov, chain, conf.MaxBatchGasLimit, conf.PMGasEstBuffer),
126+
client.GetGasEstimateWithEthClient(rpc, ov, chain, conf.MaxBatchGasLimit),
127127
)
128128
c.SetGetUserOpByHashFunc(client.GetUserOpByHashWithEthClient(eth))
129129
c.UseLogger(logr)

pkg/client/utils.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,15 @@ func GetGasEstimateWithEthClient(
4949
ov *gas.Overhead,
5050
chain *big.Int,
5151
maxGasLimit *big.Int,
52-
paymasterBuffer int64,
5352
) GetGasEstimateFunc {
5453
return func(ep common.Address, op *userop.UserOperation) (verificationGas uint64, callGas uint64, err error) {
5554
return gas.EstimateGas(&gas.EstimateInput{
56-
Rpc: rpc,
57-
EntryPoint: ep,
58-
Op: op,
59-
Ov: ov,
60-
ChainID: chain,
61-
MaxGasLimit: maxGasLimit,
62-
PaymasterBuffer: paymasterBuffer,
55+
Rpc: rpc,
56+
EntryPoint: ep,
57+
Op: op,
58+
Ov: ov,
59+
ChainID: chain,
60+
MaxGasLimit: maxGasLimit,
6361
})
6462
}
6563
}

pkg/gas/estimate.go

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414

1515
var (
1616
fallBackBinarySearchCutoff = int64(30000)
17+
maxRetries = int64(7)
18+
baseVGLBuffer = int64(25)
1719
)
1820

1921
func isPrefundNotPaid(err error) bool {
@@ -41,13 +43,34 @@ func isExecutionReverted(err error) bool {
4143
}
4244

4345
type EstimateInput struct {
44-
Rpc *rpc.Client
45-
EntryPoint common.Address
46-
Op *userop.UserOperation
47-
Ov *Overhead
48-
ChainID *big.Int
49-
MaxGasLimit *big.Int
50-
PaymasterBuffer int64
46+
Rpc *rpc.Client
47+
EntryPoint common.Address
48+
Op *userop.UserOperation
49+
Ov *Overhead
50+
ChainID *big.Int
51+
MaxGasLimit *big.Int
52+
53+
attempts int64
54+
lastVGL int64
55+
}
56+
57+
// retryEstimateGas will recursively call estimateGas if execution has caused VGL to be under estimated. This
58+
// can occur for edge cases where a paymaster's postOp > gas required during verification or if verification
59+
// has a dependency on CGL. Reset the estimate with a higher buffer on VGL.
60+
func retryEstimateGas(err error, vgl int64, in *EstimateInput) (uint64, uint64, error) {
61+
if isValidationOOG(err) && in.attempts < maxRetries {
62+
return EstimateGas(&EstimateInput{
63+
Rpc: in.Rpc,
64+
EntryPoint: in.EntryPoint,
65+
Op: in.Op,
66+
Ov: in.Ov,
67+
ChainID: in.ChainID,
68+
MaxGasLimit: in.MaxGasLimit,
69+
attempts: in.attempts + 1,
70+
lastVGL: vgl,
71+
})
72+
}
73+
return 0, 0, err
5174
}
5275

5376
// EstimateGas uses the simulateHandleOp method on the EntryPoint to derive an estimate for
@@ -76,9 +99,9 @@ func EstimateGas(in *EstimateInput) (verificationGas uint64, callGas uint64, err
7699
// estimate.
77100
l := int64(0)
78101
r := in.MaxGasLimit.Int64()
79-
f := int64(0)
102+
f := in.lastVGL
80103
var simErr error
81-
for r-l >= fallBackBinarySearchCutoff {
104+
for in.lastVGL == 0 && r-l >= fallBackBinarySearchCutoff {
82105
m := (l + r) / 2
83106

84107
data["verificationGasLimit"] = hexutil.EncodeBig(big.NewInt(int64(m)))
@@ -113,12 +136,7 @@ func EstimateGas(in *EstimateInput) (verificationGas uint64, callGas uint64, err
113136
if f == 0 {
114137
return 0, 0, simErr
115138
}
116-
// TODO: Find a more reliable approach for the edge case where the gas required during a paymaster's
117-
// postOp > gas required during verification. As a workaround we add a configurable percentage buffer if a
118-
// paymaster is included.
119-
if in.Op.GetPaymaster() != common.HexToAddress("0x") {
120-
f = (f * (100 + in.PaymasterBuffer)) / 100
121-
}
139+
f = (f * (100 + baseVGLBuffer)) / 100
122140
data["verificationGasLimit"] = hexutil.EncodeBig(big.NewInt(int64(f)))
123141

124142
// Find the optimal callGasLimit by setting the gas price to 0 and maxing out the gas limit. We don't run
@@ -138,7 +156,7 @@ func EstimateGas(in *EstimateInput) (verificationGas uint64, callGas uint64, err
138156
TraceFeeCap: in.Op.MaxFeePerGas,
139157
})
140158
if err != nil {
141-
return 0, 0, err
159+
return retryEstimateGas(err, f, in)
142160
}
143161

144162
// Calculate final values for verificationGasLimit and callGasLimit.
@@ -211,7 +229,7 @@ func EstimateGas(in *EstimateInput) (verificationGas uint64, callGas uint64, err
211229
}
212230
return simOp.VerificationGasLimit.Uint64(), big.NewInt(f).Uint64(), nil
213231
}
214-
return 0, 0, err
232+
return retryEstimateGas(err, simOp.VerificationGasLimit.Int64(), in)
215233
}
216234
return simOp.VerificationGasLimit.Uint64(), simOp.CallGasLimit.Uint64(), nil
217235
}

0 commit comments

Comments
 (0)