Skip to content

Commit a37b493

Browse files
feat(evm): allow gas bumping on contract.NewWrite
contract.NewWrite is used by operations-gen in the generated code, we also want to support gas bumping here
1 parent fc56aeb commit a37b493

5 files changed

Lines changed: 101 additions & 1 deletion

File tree

.changeset/fast-rules-sneeze.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": minor
3+
---
4+
5+
feat(evm): allow gas bumping on contract.NewWrite

chain/evm/operations2/contract/function.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ package contract
44
type FunctionInput[ARGS any] struct {
55
// Args are the parameters passed to the contract call.
66
Args ARGS `json:"args"`
7+
// GasLimit and GasPrice optionally override the deployer transact opts on EVM writes.
8+
// Used by RetryWriteWithGasBoost to increase gas on retry attempts.
9+
GasLimit uint64 `json:"gasLimit,omitempty"`
10+
GasPrice uint64 `json:"gasPrice,omitempty"`
711
}

chain/evm/operations2/contract/gas_boost.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,30 @@ func RetryDeployWithGasBoost[ARGS any](cfg *GasBoostConfig) operations.ExecuteOp
5656
})
5757
}
5858

59+
// RetryWriteWithGasBoost enables the default operation retry policy for contract writes and
60+
// increases gas on EVM retries when the prior attempt failed with a gas-related error.
61+
// The first execution attempt uses the chain deployer's default gas settings.
62+
// On ZkSync VM chains, gas fields are not adjusted.
63+
// When cfg is nil, enables default retries without gas adjustment (legacy call-retry behavior).
64+
func RetryWriteWithGasBoost[ARGS any](cfg *GasBoostConfig) operations.ExecuteOption[FunctionInput[ARGS], evm.Chain] {
65+
if cfg == nil {
66+
return operations.WithRetry[FunctionInput[ARGS], evm.Chain]()
67+
}
68+
c := *cfg
69+
70+
return operations.WithRetryInput(func(attempt uint, err error, in FunctionInput[ARGS], deps evm.Chain) FunctionInput[ARGS] {
71+
if deps.IsZkSyncVM || !isGasRetryableError(err) {
72+
return in
73+
}
74+
75+
gasLimit, gasPrice := nextBoostedGas(c, attempt, in.GasLimit, in.GasPrice)
76+
in.GasLimit = gasLimit
77+
in.GasPrice = gasPrice
78+
79+
return in
80+
})
81+
}
82+
5983
func (cfg GasBoostConfig) gasLimitIncrement() uint64 {
6084
if cfg.GasLimitIncrement == nil {
6185
return defaultGasLimitIncrement

chain/evm/operations2/contract/gas_boost_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,70 @@ func TestRetryDeployWithGasBoostSkipsZkSync(t *testing.T) {
236236
func ptrUint64(v uint64) *uint64 {
237237
return &v
238238
}
239+
240+
func TestRetryWriteWithGasBoostRetriesOnGasError(t *testing.T) {
241+
t.Parallel()
242+
243+
failures := 2
244+
var gasLimits []uint64
245+
op := operations.NewOperation(
246+
"write-gas-boost-retry",
247+
semver.MustParse("1.0.0"),
248+
"test",
249+
func(_ operations.Bundle, _ evm.Chain, input FunctionInput[struct{}]) (struct{}, error) {
250+
gasLimits = append(gasLimits, input.GasLimit)
251+
if failures > 0 {
252+
failures--
253+
return struct{}{}, errors.New("out of gas: gas required exceeds allowance")
254+
}
255+
256+
return struct{}{}, nil
257+
},
258+
)
259+
260+
cfg := &GasBoostConfig{
261+
InitialGasLimit: 1_000_000,
262+
GasLimitIncrement: ptrUint64(100_000),
263+
}
264+
bundle := optest.NewBundle(t)
265+
_, err := operations.ExecuteOperation(
266+
bundle,
267+
op,
268+
evm.Chain{Selector: 1},
269+
FunctionInput[struct{}]{},
270+
RetryWriteWithGasBoost[struct{}](cfg),
271+
)
272+
require.NoError(t, err)
273+
require.Equal(t, []uint64{0, 1_000_000, 1_100_000}, gasLimits)
274+
}
275+
276+
func TestRetryWriteWithGasBoostNilConfigEnablesRetry(t *testing.T) {
277+
t.Parallel()
278+
279+
attempts := 0
280+
op := operations.NewOperation(
281+
"write-retry-nil-config",
282+
semver.MustParse("1.0.0"),
283+
"test",
284+
func(_ operations.Bundle, _ evm.Chain, _ FunctionInput[struct{}]) (struct{}, error) {
285+
attempts++
286+
if attempts < 2 {
287+
return struct{}{}, errors.New("transient failure")
288+
}
289+
290+
return struct{}{}, nil
291+
},
292+
)
293+
294+
bundle := optest.NewBundle(t)
295+
_, err := operations.ExecuteOperation(
296+
bundle,
297+
op,
298+
evm.Chain{Selector: 1},
299+
FunctionInput[struct{}]{},
300+
RetryWriteWithGasBoost[struct{}](nil),
301+
)
302+
require.NoError(t, err)
303+
require.Equal(t, 2, attempts)
304+
}
305+

chain/evm/operations2/contract/write.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func NewWrite[ARGS any, C interface{ Address() common.Address }](params WritePar
9494
}
9595
opts := deployment.SimTransactOpts()
9696
if allowed {
97-
opts = chain.DeployerKey
97+
opts = deployTransactOpts(chain.DeployerKey, input.GasLimit, input.GasPrice)
9898
}
9999
var execInfo *ExecInfo
100100
tx, callErr := params.CallContract(params.Contract, opts, input.Args)

0 commit comments

Comments
 (0)