From f63e57476fb79f08f6722ecaaed31251414273e3 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Thu, 16 Oct 2025 19:51:40 +0200 Subject: [PATCH 01/16] Add precompiles for Multi-Constraint Pricer (stubs) --- arbos/l2pricing/l2pricing.go | 16 +++ precompiles/ArbGasInfo.go | 37 +++++-- precompiles/ArbOwner.go | 27 +++++- precompiles/constraints_test.go | 167 ++++++++++++++++++++++++++++++++ 4 files changed, 235 insertions(+), 12 deletions(-) create mode 100644 precompiles/constraints_test.go diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index 17698a678d2..6b3bf7f1f99 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -47,6 +47,22 @@ func (c *GasConstraint) Clear() error { return nil } +func (c *GasConstraint) Target() (uint64, error) { + return c.target.Get() +} + +func (c *GasConstraint) Period() (uint64, error) { + return c.period.Get() +} + +func (c *GasConstraint) Backlog() (uint64, error) { + return c.backlog.Get() +} + +func (c *GasConstraint) SetBacklog(val uint64) error { + return c.backlog.Set(val) +} + type L2PricingState struct { storage *storage.Storage speedLimitPerSecond storage.StorageBackedUint64 diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index eeffc02196f..839bd280460 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -4,7 +4,6 @@ package precompiles import ( - "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -286,14 +285,34 @@ func (con ArbGasInfo) GetLastL1PricingSurplus(c ctx, evm mech) (*big.Int, error) return c.State.L1PricingState().LastSurplus() } -// GetMaxBlockGasLimit gets the maximum block gas limit -func (con ArbGasInfo) GetMaxBlockGasLimit(c ctx, evm mech) (uint64, error) { - // todo: update with real code once https://github.com/OffchainLabs/nitro/pull/3860 is merged - return 0, fmt.Errorf("GetMaxBlockGasLimit is a stub implementation, since definitions were upstreamed, but implementations not done") -} - // GetGasPricingConstraints gets the current gas pricing constraints used by the Multi-Constraint Pricer. func (con ArbGasInfo) GetGasPricingConstraints(c ctx, evm mech) ([][3]uint64, error) { - // todo: update with real code once https://github.com/OffchainLabs/nitro/pull/3860 is merged - return nil, fmt.Errorf("GetGasPricingConstraints is a stub implementation, since definitions were upstreamed, but implementations not done") + len, err := c.State.L2PricingState().ConstraintsLength() + if err != nil { + return nil, err + } + + constraints := make([][3]uint64, 0, len) + for i := range len { + constraint := c.State.L2PricingState().OpenConstraintAt(i) + target, err := constraint.Target() + if err != nil { + return nil, err + } + period, err := constraint.Period() + if err != nil { + return nil, err + } + backlog, err := constraint.Backlog() + if err != nil { + return nil, err + } + + constraints = append(constraints, [3]uint64{ + target, + period, + backlog, + }) + } + return constraints, nil } diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 7ececacde3b..4a97b6ddea6 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -455,7 +455,28 @@ func (con ArbOwner) SetCalldataPriceIncrease(c ctx, _ mech, enable bool) error { } // SetGasPricingConstraints sets the gas pricing constraints used by the Multi-Constraint Pricer. -func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][3]uint64) error { - // todo: update with real code once https://github.com/OffchainLabs/nitro/pull/3860 is merged - return fmt.Errorf("SetGasPricingConstraints is a stub implementation, since definitions were upstreamed, but implementations not done") +func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][2]uint64) error { + if len(constraints) == 0 { + return errors.New("must provide at least one constraint") + } + + err := c.State.L2PricingState().ClearConstraints() + if err != nil { + return fmt.Errorf("failed to clear existing constraints: %w", err) + } + + for _, constraint := range constraints { + target := constraint[0] + period := constraint[1] + + if target == 0 || period == 0 { + return fmt.Errorf("invalid constraint with target %d and period %d", target, period) + } + + err := c.State.L2PricingState().AddConstraint(target, period, 0) + if err != nil { + return fmt.Errorf("failed to add constraint (target: %d, period: %d): %w", target, period, err) + } + } + return nil } diff --git a/precompiles/constraints_test.go b/precompiles/constraints_test.go new file mode 100644 index 00000000000..7a9405b70f1 --- /dev/null +++ b/precompiles/constraints_test.go @@ -0,0 +1,167 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package precompiles + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/offchainlabs/nitro/arbos/arbosState" + "github.com/offchainlabs/nitro/arbos/burn" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func setupResourceConstraintHandles( + t *testing.T, +) ( + *vm.EVM, + *arbosState.ArbosState, + *Context, + *ArbGasInfo, + *ArbOwner, +) { + t.Helper() + + evm := newMockEVMForTesting() + caller := common.BytesToAddress(crypto.Keccak256([]byte{})[:20]) + tracer := util.NewTracingInfo(evm, testhelpers.RandomAddress(), types.ArbosAddress, util.TracingDuringEVM) + state, err := arbosState.OpenArbosState(evm.StateDB, burn.NewSystemBurner(tracer, false)) + require.NoError(t, err) + + arbGasInfo := &ArbGasInfo{} + arbOwner := &ArbOwner{} + + callCtx := testContext(caller, evm) + + return evm, state, callCtx, arbGasInfo, arbOwner +} + +func TestFailToSetInvalidConstraints(t *testing.T) { + t.Parallel() + + evm, _, callCtx, _, arbOwner := setupResourceConstraintHandles(t) + + // Empty constraints + err := arbOwner.SetGasPricingConstraints(callCtx, evm, [][2]uint64{}) + require.Error(t, err) + + // Zero target + err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][2]uint64{{0, 17}}) + require.Error(t, err) + + // Zero period + err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][2]uint64{{10_000_000, 0}}) + require.Error(t, err) +} + +func TestConstraintsStorage(t *testing.T) { + t.Parallel() + + evm, state, callCtx, arbGasInfo, arbOwner := setupResourceConstraintHandles(t) + + // Check there is no constraints initially + result, err := arbGasInfo.GetGasPricingConstraints(callCtx, evm) + require.NoError(t, err) + require.Equal(t, len(result), 0) + + // Set constraints + constraints := [][2]uint64{ + {30_000_000, 1}, // short-term + {15_000_000, 86400}, // long-term + } + err = arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) + require.NoError(t, err) + + // Verify constraints are stored correctly + length, err := state.L2PricingState().ConstraintsLength() + require.NoError(t, err) + require.Equal(t, uint64(2), length) + + first := state.L2PricingState().OpenConstraintAt(0) + second := state.L2PricingState().OpenConstraintAt(1) + + firstTarget, err := first.Target() + require.NoError(t, err) + require.Equal(t, uint64(30_000_000), firstTarget) + + firstPeriod, err := first.Period() + require.NoError(t, err) + require.Equal(t, uint64(1), firstPeriod) + + secondTarget, err := second.Target() + require.NoError(t, err) + require.Equal(t, uint64(15_000_000), secondTarget) + + secondPeriod, err := second.Period() + require.NoError(t, err) + require.Equal(t, uint64(86400), secondPeriod) + + // Get constraints and verify + result, err = arbGasInfo.GetGasPricingConstraints(callCtx, evm) + require.NoError(t, err) + require.Equal(t, 2, len(result)) + require.Equal(t, uint64(30_000_000), result[0][0]) + require.Equal(t, uint64(1), result[0][1]) + require.Equal(t, uint64(15_000_000), result[1][0]) + require.Equal(t, uint64(86400), result[1][1]) + + // Set new constraints + constraints = [][2]uint64{ + {7_000_000, 12}, + } + err = arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) + require.NoError(t, err) + + // Verify old constraints are cleared and new constraint is stored correctly + length, err = state.L2PricingState().ConstraintsLength() + require.NoError(t, err) + require.Equal(t, uint64(1), length) + + first = state.L2PricingState().OpenConstraintAt(0) + target, err := first.Target() + require.NoError(t, err) + period, err := first.Period() + require.NoError(t, err) + require.Equal(t, uint64(7_000_000), target) + require.Equal(t, uint64(12), period) + + result, err = arbGasInfo.GetGasPricingConstraints(callCtx, evm) + require.NoError(t, err) + require.Equal(t, len(result), 1) + require.Equal(t, result[0][0], uint64(7_000_000)) + require.Equal(t, result[0][1], uint64(12)) +} + +func TestConstraintsBacklogUpdate(t *testing.T) { + t.Parallel() + + evm, state, callCtx, arbGasInfo, arbOwner := setupResourceConstraintHandles(t) + + // Set constraints + constraints := [][2]uint64{ + {30_000_000, 1}, // short-term + {15_000_000, 86400}, // long-term + } + err := arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) + require.NoError(t, err) + + err = state.L2PricingState().OpenConstraintAt(0).SetBacklog(5_000_000) + require.NoError(t, err) + err = state.L2PricingState().OpenConstraintAt(1).SetBacklog(10_000_000) + require.NoError(t, err) + + // Verify backlogs are updated correctly + result, err := arbGasInfo.GetGasPricingConstraints(callCtx, evm) + require.NoError(t, err) + require.Equal(t, 2, len(result)) + require.Equal(t, uint64(5_000_000), result[0][2]) + require.Equal(t, uint64(10_000_000), result[1][2]) +} From d6dcdbde83767ae32054b890221567fb779c4076 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Mon, 20 Oct 2025 11:37:42 +0200 Subject: [PATCH 02/16] Add precompile `GetMaxBlockGasLimit` and specify ArbOS version --- precompiles/ArbGasInfo.go | 5 +++++ precompiles/precompile.go | 3 +++ precompiles/precompile_test.go | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 839bd280460..1eb95c00523 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -285,6 +285,11 @@ func (con ArbGasInfo) GetLastL1PricingSurplus(c ctx, evm mech) (*big.Int, error) return c.State.L1PricingState().LastSurplus() } +// GetMaxBlockGasLimit gets the maximum block gas limit +func (con ArbGasInfo) GetMaxBlockGasLimit(c ctx, evm mech) (uint64, error) { + return c.State.L2PricingState().PerBlockGasLimit() +} + // GetGasPricingConstraints gets the current gas pricing constraints used by the Multi-Constraint Pricer. func (con ArbGasInfo) GetGasPricingConstraints(c ctx, evm mech) ([][3]uint64, error) { len, err := c.State.L2PricingState().ConstraintsLength() diff --git a/precompiles/precompile.go b/precompiles/precompile.go index 1ad1e486fff..0d6d6750610 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -535,6 +535,8 @@ func Precompiles() map[addr]ArbosPrecompile { ArbGasInfo.methodsByName["GetL1PricingUnitsSinceUpdate"].arbosVersion = params.ArbosVersion_20 ArbGasInfo.methodsByName["GetLastL1PricingSurplus"].arbosVersion = params.ArbosVersion_20 ArbGasInfo.methodsByName["GetMaxTxGasLimit"].arbosVersion = params.ArbosVersion_50 + ArbGasInfo.methodsByName["GetMaxBlockGasLimit"].arbosVersion = params.ArbosVersion_50 + ArbGasInfo.methodsByName["GetGasPricingConstraints"].arbosVersion = params.ArbosVersion_50 insert(MakePrecompile(precompilesgen.ArbAggregatorMetaData, &ArbAggregator{Address: types.ArbAggregatorAddress})) insert(MakePrecompile(precompilesgen.ArbStatisticsMetaData, &ArbStatistics{Address: types.ArbStatisticsAddress})) @@ -615,6 +617,7 @@ func Precompiles() map[addr]ArbosPrecompile { ArbOwner.methodsByName["ReleaseL1PricerSurplusFunds"].arbosVersion = params.ArbosVersion_10 ArbOwner.methodsByName["SetChainConfig"].arbosVersion = params.ArbosVersion_11 ArbOwner.methodsByName["SetBrotliCompressionLevel"].arbosVersion = params.ArbosVersion_20 + ArbOwner.methodsByName["SetGasPricingConstraints"].arbosVersion = params.ArbosVersion_50 stylusMethods := []string{ "SetInkPrice", "SetWasmMaxStackDepth", "SetWasmFreePages", "SetWasmPageGas", "SetWasmPageLimit", "SetWasmMinInitGas", "SetWasmInitCostScalar", diff --git a/precompiles/precompile_test.go b/precompiles/precompile_test.go index 400df63e7b9..34dc92b633b 100644 --- a/precompiles/precompile_test.go +++ b/precompiles/precompile_test.go @@ -191,7 +191,7 @@ func TestPrecompilesPerArbosVersion(t *testing.T) { params.ArbosVersion_31: 1, params.ArbosVersion_40: 3, params.ArbosVersion_41: 10, - params.ArbosVersion_50: 5, + params.ArbosVersion_50: 8, } precompiles := Precompiles() From 821ad6ddaf6a962b49b39ee31f89203630b334ba Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Mon, 20 Oct 2025 15:41:28 +0200 Subject: [PATCH 03/16] Include nitro-testnode submodule changes --- nitro-testnode | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nitro-testnode b/nitro-testnode index 947cb198289..662e7c03028 160000 --- a/nitro-testnode +++ b/nitro-testnode @@ -1 +1 @@ -Subproject commit 947cb19828993fa95cc4f734ffe8c44afff8f7fd +Subproject commit 662e7c030286a07cdf33f0b41522da6d32677992 From 2fef3cdd9b1b7bd0a9f63f209ee23f744a2bc691 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Tue, 21 Oct 2025 13:21:37 +0200 Subject: [PATCH 04/16] Enable multiple constraints pricer based on the ArbOS version --- arbos/arbosState/arbosstate.go | 1 + arbos/internal_tx.go | 7 ++++++- arbos/l2pricing/l2pricing.go | 3 +++ arbos/tx_processor.go | 4 ++-- precompiles/ArbRetryableTx.go | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 7338dc8485d..5ed0ce0b81d 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -391,6 +391,7 @@ func (state *ArbosState) UpgradeArbosVersion( ensure(p.UpgradeToArbosVersion(nextArbosVersion)) ensure(p.Save()) ensure(state.l2PricingState.SetMaxPerTxGasLimit(l2pricing.InitialPerTxGasLimitV50)) + ensure(state.l2PricingState.SetConstraintsFromLegacy()) default: return fmt.Errorf( "the chain is upgrading to unsupported ArbOS version %v, %w", diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 8b2e2d1ddbb..138b4b765e7 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -107,7 +107,12 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) - state.L2PricingState().UpdatePricingModel(l2BaseFee, timePassed, false) + // For ArbOS version 50 and above call Multi-Constraint Pricer update + if state.ArbOSVersion() >= params.ArbosVersion_50 { + state.L2PricingState().UpdatePricingModelMultiConstraints(timePassed) + } else { + state.L2PricingState().UpdatePricingModel(l2BaseFee, timePassed, false) + } return state.UpgradeArbosVersionIfNecessary(currentTime, evm.StateDB, evm.ChainConfig()) case InternalTxBatchPostingReportMethodID: diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index 6b3bf7f1f99..0de07dbe0fd 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -226,6 +226,9 @@ func (ps *L2PricingState) AddConstraint(target uint64, inertia uint64, backlog u if err := constraint.backlog.Set(backlog); err != nil { return fmt.Errorf("failed to set backlog: %w", err) } + if err := constraint.backlog.Set(backlog); err != nil { + return fmt.Errorf("failed to set backlog: %w", err) + } return nil } diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 74074ca9c51..7a17b3192b5 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -632,7 +632,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { } } // we've already credited the network fee account, but we didn't charge the gas pool yet - p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast[int64](gasUsed))) + p.state.Restrict(p.state.L2PricingState().AddToGasPoolWithArbosVersion(-arbmath.SaturatingCast[int64](gasUsed), p.state.ArbOSVersion())) return } @@ -695,7 +695,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { log.Error("total gas used < poster gas component", "gasUsed", gasUsed, "posterGas", p.posterGas) computeGas = gasUsed } - p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast[int64](computeGas))) + p.state.Restrict(p.state.L2PricingState().AddToGasPoolWithArbosVersion(-arbmath.SaturatingCast[int64](computeGas), p.state.ArbOSVersion())) } } diff --git a/precompiles/ArbRetryableTx.go b/precompiles/ArbRetryableTx.go index 6e4207aef0e..cc2d8d51fc6 100644 --- a/precompiles/ArbRetryableTx.go +++ b/precompiles/ArbRetryableTx.go @@ -128,7 +128,7 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er // Add the gasToDonate back to the gas pool: the retryable attempt will then consume it. // This ensures that the gas pool has enough gas to run the retryable attempt. - return retryTxHash, c.State.L2PricingState().AddToGasPool(arbmath.SaturatingCast[int64](gasToDonate)) + return retryTxHash, c.State.L2PricingState().AddToGasPoolWithArbosVersion(arbmath.SaturatingCast[int64](gasToDonate), c.State.ArbOSVersion()) } // GetLifetime gets the default lifetime period a retryable has at creation From df7ceb6ba978667b1529b8035bd22fb9d05d0da9 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Tue, 21 Oct 2025 17:07:03 +0200 Subject: [PATCH 05/16] Support legacy precompile methods for ne model --- arbos/l2pricing/l2pricing.go | 41 +++++++++++++++++++++++ arbos/l2pricing/l2pricing_test.go | 38 ++++++++++++++++++++++ nitro-testnode | 2 +- precompiles/ArbGasInfo.go | 54 +++++++++++++++++++++++++++++-- precompiles/ArbOwner.go | 14 ++++++-- precompiles/constraints_test.go | 40 ++++++++++++----------- 6 files changed, 166 insertions(+), 23 deletions(-) diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index 0de07dbe0fd..c53f599629f 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -55,6 +55,19 @@ func (c *GasConstraint) Period() (uint64, error) { return c.period.Get() } +func (c *GasConstraint) ComputeInertiaFromPeriod() (uint64, error) { + period, err := c.period.Get() + if err != nil { + return 0, err + } + + // Approximate legacy inertia from the constraint period + periodSqrt := arbmath.SquareUint(period) + inertia := arbmath.SaturatingUMul(periodSqrt, ConstraintDivisorMultiplier) + + return inertia, nil +} + func (c *GasConstraint) Backlog() (uint64, error) { return c.backlog.Get() } @@ -257,3 +270,31 @@ func (ps *L2PricingState) ClearConstraints() error { } return nil } + +func (ps *L2PricingState) HighestPeriodConstraint() (*GasConstraint, error) { + length, err := ps.ConstraintsLength() + if err != nil { + return nil, err + } + if length == 0 { + return nil, fmt.Errorf("no constraints configured") + } + + var ( + maxPeriod uint64 + maxIdx uint64 + ) + for i := uint64(0); i < length; i++ { + constraint := ps.OpenConstraintAt(uint64(i)) + period, err := constraint.period.Get() + if err != nil { + return nil, fmt.Errorf("failed to get period for constraint %d: %w", i, err) + } + if period > maxPeriod { + maxPeriod = period + maxIdx = uint64(i) + } + } + + return ps.OpenConstraintAt(maxIdx), nil +} diff --git a/arbos/l2pricing/l2pricing_test.go b/arbos/l2pricing/l2pricing_test.go index 95db3e29af6..6afe121cf2a 100644 --- a/arbos/l2pricing/l2pricing_test.go +++ b/arbos/l2pricing/l2pricing_test.go @@ -159,3 +159,41 @@ func Fail(t *testing.T, printables ...interface{}) { t.Helper() testhelpers.FailImpl(t, printables...) } + +func TestComputeInertiaFromPeriod(t *testing.T) { + pricing := PricingForTest(t) + Require(t, pricing.ClearConstraints()) + + const n uint64 = 5 + for i := range n { + inertia := (i + 1) * 100 + // same approximation used in SetConstraintsFromLegacy + periodSqrt := inertia / ConstraintDivisorMultiplier + period := arbmath.SaturatingUMul(periodSqrt, periodSqrt) + if period == 0 { + period = 1 + } + Require(t, pricing.AddConstraint(1000, period, 0)) + } + + length, err := pricing.ConstraintsLength() + Require(t, err) + if length != n { + t.Fatalf("wrong number of constraints: got %v want %v", length, n) + } + + for i := range n { + constraint := pricing.OpenConstraintAt(i) + computed, err := constraint.ComputeInertiaFromPeriod() + Require(t, err) + + // Reconstruct original inertia for comparison + period, err := constraint.period.Get() + Require(t, err) + want := arbmath.SaturatingUMul(arbmath.SquareUint(period), ConstraintDivisorMultiplier) + + if computed != want { + t.Errorf("wrong inertia for constraint %v: got %v, want %v", i, computed, want) + } + } +} diff --git a/nitro-testnode b/nitro-testnode index 662e7c03028..8aa458bb1dd 160000 --- a/nitro-testnode +++ b/nitro-testnode @@ -1 +1 @@ -Subproject commit 662e7c030286a07cdf33f0b41522da6d32677992 +Subproject commit 8aa458bb1ddad9546cf60c392054e0855fa6f8e1 diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 1eb95c00523..a6a21e17de6 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -159,9 +159,38 @@ func (con ArbGasInfo) GetPricesInArbGas(c ctx, evm mech) (huge, huge, huge, erro // GetGasAccountingParams gets the rollup's speed limit, pool size, and block gas limit func (con ArbGasInfo) GetGasAccountingParams(c ctx, evm mech) (huge, huge, huge, error) { l2pricing := c.State.L2PricingState() - speedLimit, _ := l2pricing.SpeedLimitPerSecond() + + var ( + speedLimit uint64 + err error + ) + + // For ArbOS version 50 and above, the speed limit is defined by the highest-period constraint + if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + constraint, cerr := l2pricing.HighestPeriodConstraint() + if cerr != nil { + return nil, nil, nil, cerr + } + speedLimit, err = constraint.Target() + } else { + speedLimit, err = l2pricing.SpeedLimitPerSecond() + } + if err != nil { + return nil, nil, nil, err + } + maxBlockGasLimit, err := l2pricing.PerBlockGasLimit() - return arbmath.UintToBig(speedLimit), arbmath.UintToBig(maxBlockGasLimit), arbmath.UintToBig(maxBlockGasLimit), err + if err != nil { + return nil, nil, nil, err + } + + // gasPoolMax is always zero under both pricing models. + gasPoolMax := uint64(0) + + return arbmath.UintToBig(speedLimit), + arbmath.UintToBig(gasPoolMax), + arbmath.UintToBig(maxBlockGasLimit), + nil } // GetMaxTxGasLimit gets the max tx gas limit @@ -208,16 +237,37 @@ func (con ArbGasInfo) GetCurrentTxL1GasFees(c ctx, evm mech) (huge, error) { // GetGasBacklog gets the backlogged amount of gas burnt in excess of the speed limit func (con ArbGasInfo) GetGasBacklog(c ctx, evm mech) (uint64, error) { + // For ArbOS version 50 and above, the backlog is defined by the highest-period constraint + if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + constraint, err := c.State.L2PricingState().HighestPeriodConstraint() + if err != nil { + return 0, err + } + return constraint.Backlog() + } return c.State.L2PricingState().GasBacklog() } // GetPricingInertia gets how slowly ArbOS updates the L2 basefee in response to backlogged gas func (con ArbGasInfo) GetPricingInertia(c ctx, evm mech) (uint64, error) { + // For ArbOS version 50 and above, compute the inertia from period of highest-period constraint + if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + constraint, err := c.State.L2PricingState().HighestPeriodConstraint() + if err != nil { + return 0, err + } + return constraint.ComputeInertiaFromPeriod() + } return c.State.L2PricingState().PricingInertia() } // GetGasBacklogTolerance gets the forgivable amount of backlogged gas ArbOS will ignore when raising the basefee func (con ArbGasInfo) GetGasBacklogTolerance(c ctx, evm mech) (uint64, error) { + // For ArbOS version 50 and above, the backlog tolerance is always zero + if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + return 0, nil + } + return c.State.L2PricingState().BacklogTolerance() } diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 4a97b6ddea6..ec8593e0171 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -141,6 +141,9 @@ func (con ArbOwner) SetMinimumL2BaseFee(c ctx, evm mech, priceInWei huge) error // SetSpeedLimit sets the computational speed limit for the chain func (con ArbOwner) SetSpeedLimit(c ctx, evm mech, limit uint64) error { + if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + return fmt.Errorf("SetSpeedLimit is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", params.ArbosVersion_50) + } if limit == 0 { return errors.New("speed limit must be nonzero") } @@ -162,6 +165,9 @@ func (con ArbOwner) SetMaxBlockGasLimit(c ctx, evm mech, limit uint64) error { // SetL2GasPricingInertia sets the L2 gas pricing inertia func (con ArbOwner) SetL2GasPricingInertia(c ctx, evm mech, sec uint64) error { + if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + return fmt.Errorf("SetL2GasPricingInertia is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", params.ArbosVersion_50) + } if sec == 0 { return errors.New("price inertia must be nonzero") } @@ -170,6 +176,9 @@ func (con ArbOwner) SetL2GasPricingInertia(c ctx, evm mech, sec uint64) error { // SetL2GasBacklogTolerance sets the L2 gas backlog tolerance func (con ArbOwner) SetL2GasBacklogTolerance(c ctx, evm mech, sec uint64) error { + if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + return fmt.Errorf("SetL2GasBacklogTolerance is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", params.ArbosVersion_50) + } return c.State.L2PricingState().SetBacklogTolerance(sec) } @@ -455,7 +464,7 @@ func (con ArbOwner) SetCalldataPriceIncrease(c ctx, _ mech, enable bool) error { } // SetGasPricingConstraints sets the gas pricing constraints used by the Multi-Constraint Pricer. -func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][2]uint64) error { +func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][3]uint64) error { if len(constraints) == 0 { return errors.New("must provide at least one constraint") } @@ -468,12 +477,13 @@ func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][2]u for _, constraint := range constraints { target := constraint[0] period := constraint[1] + backlog := constraint[2] if target == 0 || period == 0 { return fmt.Errorf("invalid constraint with target %d and period %d", target, period) } - err := c.State.L2PricingState().AddConstraint(target, period, 0) + err := c.State.L2PricingState().AddConstraint(target, period, backlog) if err != nil { return fmt.Errorf("failed to add constraint (target: %d, period: %d): %w", target, period, err) } diff --git a/precompiles/constraints_test.go b/precompiles/constraints_test.go index 7a9405b70f1..55e71fc6de2 100644 --- a/precompiles/constraints_test.go +++ b/precompiles/constraints_test.go @@ -50,15 +50,15 @@ func TestFailToSetInvalidConstraints(t *testing.T) { evm, _, callCtx, _, arbOwner := setupResourceConstraintHandles(t) // Empty constraints - err := arbOwner.SetGasPricingConstraints(callCtx, evm, [][2]uint64{}) + err := arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{}) require.Error(t, err) // Zero target - err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][2]uint64{{0, 17}}) + err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{0, 17, 1000}}) require.Error(t, err) // Zero period - err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][2]uint64{{10_000_000, 0}}) + err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{10_000_000, 0, 0}}) require.Error(t, err) } @@ -67,17 +67,12 @@ func TestConstraintsStorage(t *testing.T) { evm, state, callCtx, arbGasInfo, arbOwner := setupResourceConstraintHandles(t) - // Check there is no constraints initially - result, err := arbGasInfo.GetGasPricingConstraints(callCtx, evm) - require.NoError(t, err) - require.Equal(t, len(result), 0) - // Set constraints - constraints := [][2]uint64{ - {30_000_000, 1}, // short-term - {15_000_000, 86400}, // long-term + constraints := [][3]uint64{ + {30_000_000, 1, 800_000}, // short-term + {15_000_000, 86400, 1_600_000}, // long-term } - err = arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) + err := arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) require.NoError(t, err) // Verify constraints are stored correctly @@ -96,6 +91,10 @@ func TestConstraintsStorage(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(1), firstPeriod) + firstBacklog, err := first.Backlog() + require.NoError(t, err) + require.Equal(t, uint64(800_000), firstBacklog) + secondTarget, err := second.Target() require.NoError(t, err) require.Equal(t, uint64(15_000_000), secondTarget) @@ -104,8 +103,12 @@ func TestConstraintsStorage(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(86400), secondPeriod) + secondBacklog, err := second.Backlog() + require.NoError(t, err) + require.Equal(t, uint64(1_600_000), secondBacklog) + // Get constraints and verify - result, err = arbGasInfo.GetGasPricingConstraints(callCtx, evm) + result, err := arbGasInfo.GetGasPricingConstraints(callCtx, evm) require.NoError(t, err) require.Equal(t, 2, len(result)) require.Equal(t, uint64(30_000_000), result[0][0]) @@ -114,8 +117,8 @@ func TestConstraintsStorage(t *testing.T) { require.Equal(t, uint64(86400), result[1][1]) // Set new constraints - constraints = [][2]uint64{ - {7_000_000, 12}, + constraints = [][3]uint64{ + {7_000_000, 12, 50_000_000}, } err = arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) require.NoError(t, err) @@ -138,6 +141,7 @@ func TestConstraintsStorage(t *testing.T) { require.Equal(t, len(result), 1) require.Equal(t, result[0][0], uint64(7_000_000)) require.Equal(t, result[0][1], uint64(12)) + require.Equal(t, result[0][2], uint64(50_000_000)) } func TestConstraintsBacklogUpdate(t *testing.T) { @@ -146,9 +150,9 @@ func TestConstraintsBacklogUpdate(t *testing.T) { evm, state, callCtx, arbGasInfo, arbOwner := setupResourceConstraintHandles(t) // Set constraints - constraints := [][2]uint64{ - {30_000_000, 1}, // short-term - {15_000_000, 86400}, // long-term + constraints := [][3]uint64{ + {30_000_000, 1, 0}, // short-term + {15_000_000, 86400, 8000}, // long-term } err := arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) require.NoError(t, err) From ec2f39a4ad78b47b47bc020b8449743907b294cf Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Tue, 21 Oct 2025 17:47:14 +0200 Subject: [PATCH 06/16] Add system test and stub for missing precompile --- arbos/l2pricing/l2pricing.go | 4 +- arbos/l2pricing/l2pricing_test.go | 50 ++--- precompiles/ArbDebug.go | 4 + system_tests/multi_constraint_pricer_test.go | 184 +++++++++++++++++++ 4 files changed, 216 insertions(+), 26 deletions(-) create mode 100644 system_tests/multi_constraint_pricer_test.go diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index c53f599629f..b9eeb90d237 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -61,8 +61,8 @@ func (c *GasConstraint) ComputeInertiaFromPeriod() (uint64, error) { return 0, err } - // Approximate legacy inertia from the constraint period - periodSqrt := arbmath.SquareUint(period) + // Reverse of SetConstraintsFromLegacy: inertia ≈ sqrt(period) * ConstraintDivisorMultiplier + periodSqrt := arbmath.ApproxSquareRoot(period) inertia := arbmath.SaturatingUMul(periodSqrt, ConstraintDivisorMultiplier) return inertia, nil diff --git a/arbos/l2pricing/l2pricing_test.go b/arbos/l2pricing/l2pricing_test.go index 6afe121cf2a..26c1211fd25 100644 --- a/arbos/l2pricing/l2pricing_test.go +++ b/arbos/l2pricing/l2pricing_test.go @@ -164,36 +164,38 @@ func TestComputeInertiaFromPeriod(t *testing.T) { pricing := PricingForTest(t) Require(t, pricing.ClearConstraints()) - const n uint64 = 5 - for i := range n { - inertia := (i + 1) * 100 - // same approximation used in SetConstraintsFromLegacy - periodSqrt := inertia / ConstraintDivisorMultiplier - period := arbmath.SaturatingUMul(periodSqrt, periodSqrt) - if period == 0 { - period = 1 - } - Require(t, pricing.AddConstraint(1000, period, 0)) - } + inertias := []uint64{1000, 10000, 120000, 500000} - length, err := pricing.ConstraintsLength() - Require(t, err) - if length != n { - t.Fatalf("wrong number of constraints: got %v want %v", length, n) - } + for i, inertia := range inertias { + Require(t, pricing.SetSpeedLimitPerSecond(20_000_000)) + Require(t, pricing.SetPricingInertia(inertia)) + Require(t, pricing.SetBacklogTolerance(0)) - for i := range n { - constraint := pricing.OpenConstraintAt(i) - computed, err := constraint.ComputeInertiaFromPeriod() + Require(t, pricing.SetConstraintsFromLegacy()) + + length, err := pricing.ConstraintsLength() Require(t, err) + if length != 1 { + t.Fatalf("expected 1 constraint after migration, got %v", length) + } - // Reconstruct original inertia for comparison - period, err := constraint.period.Get() + constraint := pricing.OpenConstraintAt(0) + computed, err := constraint.ComputeInertiaFromPeriod() Require(t, err) - want := arbmath.SaturatingUMul(arbmath.SquareUint(period), ConstraintDivisorMultiplier) - if computed != want { - t.Errorf("wrong inertia for constraint %v: got %v, want %v", i, computed, want) + // Compare to original legacy inertia with 1% tolerance + var diff uint64 + if computed > inertia { + diff = computed - inertia + } else { + diff = inertia - computed + } + tolerance := inertia / 100 // 1% + if diff > tolerance { + t.Errorf("inertia mismatch (case %d): got %v, want ~%v (diff=%v, tol=%v)", + i, computed, inertia, diff, tolerance) } + + Require(t, pricing.ClearConstraints()) } } diff --git a/precompiles/ArbDebug.go b/precompiles/ArbDebug.go index 1a70822f1c3..4ba2b138614 100644 --- a/precompiles/ArbDebug.go +++ b/precompiles/ArbDebug.go @@ -74,3 +74,7 @@ func (con ArbDebug) Panic(c ctx, evm mech) error { func (con ArbDebug) LegacyError(c ctx) error { return errors.New("example legacy error") } + +func (con ArbDebug) OverwriteContractCode(c ctx, evm mech, target common.Address, newCode []byte) ([]byte, error) { + return nil, errors.New("not implemented") +} diff --git a/system_tests/multi_constraint_pricer_test.go b/system_tests/multi_constraint_pricer_test.go new file mode 100644 index 00000000000..f1a659eedff --- /dev/null +++ b/system_tests/multi_constraint_pricer_test.go @@ -0,0 +1,184 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package arbtest + +import ( + "context" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/precompilesgen" +) + +func TestMultiConstraintPricerPrecompiles(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + cleanup := builder.Build(t) + defer cleanup() + + auth := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + callOpts := &bind.CallOpts{Context: ctx} + + arbOwner, err := precompilesgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + require.NoError(t, err) + arbGasInfo, err := precompilesgen.NewArbGasInfo(types.ArbGasInfoAddress, builder.L2.Client) + require.NoError(t, err) + + // Set constraints + constraints := [][3]uint64{ + {30_000_000, 1, 800_000}, // short-term + {15_000_000, 86400, 1_600_000}, // long-term + } + tx, err := arbOwner.SetGasPricingConstraints(&auth, constraints) + require.NoError(t, err) + require.NotNil(t, tx) + + // Get and check values + constraints, err = arbGasInfo.GetGasPricingConstraints(callOpts) + require.NoError(t, err) + require.Equal(t, 2, len(constraints)) + require.Equal(t, uint64(30_000_000), constraints[0][0]) + require.Equal(t, uint64(1), constraints[0][1]) + require.GreaterOrEqual(t, constraints[0][2], uint64(800_000)) + require.Equal(t, uint64(15_000_000), constraints[1][0]) + require.Equal(t, uint64(86400), constraints[1][1]) + require.GreaterOrEqual(t, constraints[1][2], uint64(1_600_000)) + + // Should return speed limit derived from long-term constraint + speedLimit, gasPoolMax, _, err := arbGasInfo.GetGasAccountingParams(callOpts) + require.NoError(t, err) + require.Zero(t, big.NewInt(15_000_000).Cmp(speedLimit)) + require.Zero(t, big.NewInt(0).Cmp(gasPoolMax)) + + // Should return the long-term constraint's backlog (the highest-period constraint) + gasBacklog, err := arbGasInfo.GetGasBacklog(callOpts) + require.NoError(t, err) + require.GreaterOrEqual(t, gasBacklog, uint64(1_600_000)) + + // Should return the long-term constraint's computed inertia + pricingInertia, err := arbGasInfo.GetPricingInertia(callOpts) + require.NoError(t, err) + require.GreaterOrEqual(t, pricingInertia, uint64(86400)) + + // Should return zero backlog tolerance + backlogTolerance, err := arbGasInfo.GetGasBacklogTolerance(callOpts) + require.NoError(t, err) + require.Equal(t, uint64(0), backlogTolerance) + + // Check SetSpeedLimit returns error + _, err = arbOwner.SetSpeedLimit(&auth, 20_000_000) + require.Error(t, err) + + // Check SetL2GasPricingInertia returns error + _, err = arbOwner.SetL2GasPricingInertia(&auth, 200) + require.Error(t, err) + + // Check SetL2GasBacklogTolerance returns error + _, err = arbOwner.SetL2GasBacklogTolerance(&auth, 100) + require.Error(t, err) +} + +func TestMultiConstraintPricerMigration(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).WithArbOSVersion(params.ArbosVersion_41) + cleanup := builder.Build(t) + defer cleanup() + + auth := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + callOpts := &bind.CallOpts{Context: ctx} + + arbOwner, err := precompilesgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + require.NoError(t, err) + arbGasInfo, err := precompilesgen.NewArbGasInfo(types.ArbGasInfoAddress, builder.L2.Client) + require.NoError(t, err) + arbSys, err := precompilesgen.NewArbSys(types.ArbSysAddress, builder.L2.Client) + require.NoError(t, err) + + // Check version before upgrade + arbosVersion, err := arbSys.ArbOSVersion(callOpts) + require.NoError(t, err) + require.Equal(t, arbosVersion.Uint64()-55, params.ArbosVersion_41) + + // Set legacy parameters + _, err = arbOwner.SetSpeedLimit(&auth, 20_000_000) + require.NoError(t, err) + + _, err = arbOwner.SetL2GasPricingInertia(&auth, 300) + require.NoError(t, err) + + _, err = arbOwner.SetL2GasBacklogTolerance(&auth, 150) + require.NoError(t, err) + + // Check GetGasAccountingParams returns expected speed limit + speedLimit, gasPoolMax, _, err := arbGasInfo.GetGasAccountingParams(callOpts) + require.NoError(t, err) + require.Zero(t, big.NewInt(20_000_000).Cmp(speedLimit)) + require.Zero(t, big.NewInt(0).Cmp(gasPoolMax)) + + // Check GetPricingInertia returns expected inertia + pricingInertia, err := arbGasInfo.GetPricingInertia(callOpts) + require.NoError(t, err) + require.Equal(t, uint64(300), pricingInertia) + + // Check GetGasBacklogTolerance returns expected tolerance + backlogTolerance, err := arbGasInfo.GetGasBacklogTolerance(callOpts) + require.NoError(t, err) + require.Equal(t, uint64(150), backlogTolerance) + + // Upgrade to ArbOS version 50 + tx, err := arbOwner.ScheduleArbOSUpgrade(&auth, params.ArbosVersion_50, 0) + require.NoError(t, err) + + _, err = builder.L2.EnsureTxSucceeded(tx) + require.NoError(t, err) + + // Send a dummy transaction to trigger the upgrade + var data []byte + for i := range 10 { + for range 100 { + data = append(data, byte(i)) + } + } + tx = builder.L2Info.PrepareTx("Owner", "Owner", builder.L2Info.TransferGas, big.NewInt(1e12), data) + err = builder.L2.Client.SendTransaction(ctx, tx) + require.NoError(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + require.NoError(t, err) + + arbosVersion, err = arbSys.ArbOSVersion(callOpts) + require.NoError(t, err) + require.GreaterOrEqual(t, arbosVersion.Uint64()-55, params.ArbosVersion_50) + + // Check SetSpeedLimit returns error after upgrade + _, err = arbOwner.SetSpeedLimit(&auth, 25_000_000) + require.Error(t, err) + + // Check GetPricingInertia returns expected inertia + pricingInertia, err = arbGasInfo.GetPricingInertia(callOpts) + require.NoError(t, err) + require.Equal(t, uint64(300), pricingInertia) + + // Get and check pricing constraints after upgrade + constraints, err := arbGasInfo.GetGasPricingConstraints(callOpts) + require.NoError(t, err) + require.Equal(t, 1, len(constraints)) + require.Equal(t, uint64(20_000_000), constraints[0][0]) + require.Equal(t, uint64(100), constraints[0][1]) + require.GreaterOrEqual(t, constraints[0][2], uint64(0)) + + // Check GetGasAccountingParams after upgrade + speedLimit, _, _, err = arbGasInfo.GetGasAccountingParams(callOpts) + require.NoError(t, err) + require.Equal(t, big.NewInt(20_000_000), speedLimit) +} From 18519f5e12f49d273d0e982c7dc4480ec5ef8949 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Tue, 21 Oct 2025 21:45:47 +0200 Subject: [PATCH 07/16] Fix tests with overrided methods --- precompiles/ArbDebug.go | 4 -- precompiles/precompile_test.go | 2 +- system_tests/multi_constraint_pricer_test.go | 2 +- system_tests/precompile_test.go | 44 +++++++++++++++----- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/precompiles/ArbDebug.go b/precompiles/ArbDebug.go index 4ba2b138614..1a70822f1c3 100644 --- a/precompiles/ArbDebug.go +++ b/precompiles/ArbDebug.go @@ -74,7 +74,3 @@ func (con ArbDebug) Panic(c ctx, evm mech) error { func (con ArbDebug) LegacyError(c ctx) error { return errors.New("example legacy error") } - -func (con ArbDebug) OverwriteContractCode(c ctx, evm mech, target common.Address, newCode []byte) ([]byte, error) { - return nil, errors.New("not implemented") -} diff --git a/precompiles/precompile_test.go b/precompiles/precompile_test.go index 34dc92b633b..8347de05ac1 100644 --- a/precompiles/precompile_test.go +++ b/precompiles/precompile_test.go @@ -182,7 +182,7 @@ func TestPrecompilesPerArbosVersion(t *testing.T) { // Each new precompile contract and each method on new or existing precompile // contracts should be counted. expectedNewEntriesPerArbosVersion := map[uint64]int{ - 0: 102, + 0: 99, params.ArbosVersion_5: 3, params.ArbosVersion_10: 2, params.ArbosVersion_11: 4, diff --git a/system_tests/multi_constraint_pricer_test.go b/system_tests/multi_constraint_pricer_test.go index f1a659eedff..bc2503db2ca 100644 --- a/system_tests/multi_constraint_pricer_test.go +++ b/system_tests/multi_constraint_pricer_test.go @@ -67,7 +67,7 @@ func TestMultiConstraintPricerPrecompiles(t *testing.T) { // Should return the long-term constraint's computed inertia pricingInertia, err := arbGasInfo.GetPricingInertia(callOpts) require.NoError(t, err) - require.GreaterOrEqual(t, pricingInertia, uint64(86400)) + require.GreaterOrEqual(t, pricingInertia, uint64(8790)) // Computed from 86400s period // Should return zero backlog tolerance backlogTolerance, err := arbGasInfo.GetGasBacklogTolerance(callOpts) diff --git a/system_tests/precompile_test.go b/system_tests/precompile_test.go index 35443d77685..87ed379bfa1 100644 --- a/system_tests/precompile_test.go +++ b/system_tests/precompile_test.go @@ -256,12 +256,15 @@ func setupArbOwnerAndArbGasInfo( bind.TransactOpts, *precompilesgen.ArbOwner, *precompilesgen.ArbGasInfo, + uint64, ) { ctx, cancel := context.WithCancel(context.Background()) builder := NewNodeBuilder(ctx).DefaultConfig(t, false) builderCleanup := builder.Build(t) + arbosVersion := builder.chainConfig.ArbitrumChainParams.InitialArbOSVersion + cleanup := func() { builderCleanup() cancel() @@ -274,11 +277,11 @@ func setupArbOwnerAndArbGasInfo( arbGasInfo, err := precompilesgen.NewArbGasInfo(common.HexToAddress("0x6c"), builder.L2.Client) Require(t, err) - return builder, cleanup, auth, arbOwner, arbGasInfo + return builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion } func TestL1BaseFeeEstimateInertia(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -296,7 +299,7 @@ func TestL1BaseFeeEstimateInertia(t *testing.T) { // Similar to TestL1BaseFeeEstimateInertia, but now using a different setter from ArbOwner func TestL1PricingInertia(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -313,7 +316,7 @@ func TestL1PricingInertia(t *testing.T) { } func TestL1PricingRewardRate(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -330,7 +333,7 @@ func TestL1PricingRewardRate(t *testing.T) { } func TestL1PricingRewardRecipient(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -347,12 +350,19 @@ func TestL1PricingRewardRecipient(t *testing.T) { } func TestL2GasPricingInertia(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx inertia := uint64(14) tx, err := arbOwner.SetL2GasPricingInertia(&auth, inertia) + if arbosVersion >= params.ArbosVersion_50 { + // Expect error for multi-constraint pricer + if err == nil { + t.Fatalf("expected an error, got nil") + } + return + } Require(t, err) _, err = builder.L2.EnsureTxSucceeded(tx) Require(t, err) @@ -364,12 +374,19 @@ func TestL2GasPricingInertia(t *testing.T) { } func TestL2GasBacklogTolerance(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx gasTolerance := uint64(15) tx, err := arbOwner.SetL2GasBacklogTolerance(&auth, gasTolerance) + if arbosVersion >= params.ArbosVersion_50 { + // Expect error for multi-constraint pricer + if err == nil { + t.Fatalf("expected an error, got nil") + } + return + } Require(t, err) _, err = builder.L2.EnsureTxSucceeded(tx) Require(t, err) @@ -381,7 +398,7 @@ func TestL2GasBacklogTolerance(t *testing.T) { } func TestPerBatchGasCharge(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -398,7 +415,7 @@ func TestPerBatchGasCharge(t *testing.T) { } func TestL1PricingEquilibrationUnits(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -415,13 +432,20 @@ func TestL1PricingEquilibrationUnits(t *testing.T) { } func TestGasAccountingParams(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx speedLimit := uint64(18) blockGasLimit := uint64(19) tx, err := arbOwner.SetSpeedLimit(&auth, speedLimit) + if arbosVersion >= params.ArbosVersion_50 { + // Expect error for multi-constraint pricer + if err == nil { + t.Fatalf("expected an error, got nil") + } + return + } Require(t, err) _, err = builder.L2.EnsureTxSucceeded(tx) Require(t, err) From fd988d676f3106332c1376bda9abb58f963641b0 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Wed, 22 Oct 2025 12:31:42 +0200 Subject: [PATCH 08/16] Review fixes and minor improvements --- arbnode/inbox_test.go | 6 +-- arbos/internal_tx.go | 10 +--- arbos/l2pricing/l2pricing.go | 6 +-- arbos/l2pricing/l2pricing_test.go | 7 +-- arbos/l2pricing/model.go | 21 ++++++-- precompiles/ArbGasInfo.go | 50 +++++++++----------- precompiles/ArbGasInfo_test.go | 11 ++++- precompiles/ArbOwner.go | 13 ++--- system_tests/deployment_test.go | 7 ++- system_tests/estimation_test.go | 25 ++++++++-- system_tests/eth_config_test.go | 2 +- system_tests/infra_fee_test.go | 13 +++-- system_tests/multi_constraint_pricer_test.go | 3 +- system_tests/multigas_stylus_program_test.go | 2 +- system_tests/precompile_test.go | 7 +-- system_tests/program_test.go | 4 ++ 16 files changed, 111 insertions(+), 76 deletions(-) diff --git a/arbnode/inbox_test.go b/arbnode/inbox_test.go index 8081b39439d..96539a1c697 100644 --- a/arbnode/inbox_test.go +++ b/arbnode/inbox_test.go @@ -20,7 +20,6 @@ import ( "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbostypes" - "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/execution" @@ -193,7 +192,8 @@ func TestTransactionStreamer(t *testing.T) { Require(t, err) exec.Start(ctx) - maxExpectedGasCost := big.NewInt(l2pricing.InitialBaseFeeWei) + gasPrice := big.NewInt(params.GWei) // 1 Gwei to handle base fee growth + maxExpectedGasCost := new(big.Int).Set(gasPrice) maxExpectedGasCost.Mul(maxExpectedGasCost, big.NewInt(2100*2)) minBalance := new(big.Int).Mul(maxExpectedGasCost, big.NewInt(100)) @@ -247,7 +247,7 @@ func TestTransactionStreamer(t *testing.T) { var l2Message []byte l2Message = append(l2Message, arbos.L2MessageKind_ContractTx) l2Message = append(l2Message, arbmath.Uint64ToU256Bytes(gas)...) - l2Message = append(l2Message, arbmath.Uint64ToU256Bytes(l2pricing.InitialBaseFeeWei)...) + l2Message = append(l2Message, arbmath.Uint64ToU256Bytes(gasPrice.Uint64())...) l2Message = append(l2Message, destHash.Bytes()...) l2Message = append(l2Message, arbmath.U256Bytes(value)...) var requestId common.Hash diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 138b4b765e7..65d0585ddc7 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -94,9 +94,6 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos oldL1BlockNumber, err := state.Blockhashes().L1BlockNumber() state.Restrict(err) - l2BaseFee, err := state.L2PricingState().BaseFeeWei() - state.Restrict(err) - if l1BlockNumber > oldL1BlockNumber { state.Restrict(state.Blockhashes().RecordNewL1Block(l1BlockNumber-1, prevHash, state.ArbOSVersion())) } @@ -107,12 +104,7 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) - // For ArbOS version 50 and above call Multi-Constraint Pricer update - if state.ArbOSVersion() >= params.ArbosVersion_50 { - state.L2PricingState().UpdatePricingModelMultiConstraints(timePassed) - } else { - state.L2PricingState().UpdatePricingModel(l2BaseFee, timePassed, false) - } + state.L2PricingState().UpdatePricingModelWithArbosVersion(timePassed, state.ArbOSVersion()) return state.UpgradeArbosVersionIfNecessary(currentTime, evm.StateDB, evm.ChainConfig()) case InternalTxBatchPostingReportMethodID: diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index b9eeb90d237..2817b5e701d 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -284,15 +284,15 @@ func (ps *L2PricingState) HighestPeriodConstraint() (*GasConstraint, error) { maxPeriod uint64 maxIdx uint64 ) - for i := uint64(0); i < length; i++ { - constraint := ps.OpenConstraintAt(uint64(i)) + for i := range length { + constraint := ps.OpenConstraintAt(i) period, err := constraint.period.Get() if err != nil { return nil, fmt.Errorf("failed to get period for constraint %d: %w", i, err) } if period > maxPeriod { maxPeriod = period - maxIdx = uint64(i) + maxIdx = i } } diff --git a/arbos/l2pricing/l2pricing_test.go b/arbos/l2pricing/l2pricing_test.go index 26c1211fd25..a10f1ba6fb0 100644 --- a/arbos/l2pricing/l2pricing_test.go +++ b/arbos/l2pricing/l2pricing_test.go @@ -22,9 +22,10 @@ func PricingForTest(t *testing.T) *L2PricingState { } func fakeBlockUpdate(t *testing.T, pricing *L2PricingState, gasUsed int64, timePassed uint64) { - basefee := getPrice(t, pricing) - pricing.storage.Burner().Restrict(pricing.AddToGasPool(-gasUsed)) - pricing.UpdatePricingModel(arbmath.UintToBig(basefee), timePassed, true) + t.Helper() + + pricing.storage.Burner().Restrict(pricing.addToGasPool(-gasUsed)) + pricing.updatePricingModelLegacy(timePassed) } func TestPricingModelExp(t *testing.T) { diff --git a/arbos/l2pricing/model.go b/arbos/l2pricing/model.go index 001cea1a9f3..e9b24f6a113 100644 --- a/arbos/l2pricing/model.go +++ b/arbos/l2pricing/model.go @@ -12,6 +12,8 @@ import ( "github.com/offchainlabs/nitro/util/arbmath" ) +const ArbosMultiConstraintsVersion = params.ArbosVersion_50 + const InitialSpeedLimitPerSecondV0 = 1000000 const InitialPerBlockGasLimitV0 uint64 = 20 * 1000000 const InitialSpeedLimitPerSecondV6 = 7000000 @@ -31,7 +33,7 @@ func (ps *L2PricingState) AddToGasPool(gas int64) error { return ps.SetGasBacklog(backlog) } -func (ps *L2PricingState) AddToGasPoolMultiConstraints(gas int64) error { +func (ps *L2PricingState) addToGasPoolMultiConstraints(gas int64) error { constraintsLength, err := ps.constraints.Length() if err != nil { return fmt.Errorf("failed to get number of constraints: %w", err) @@ -50,6 +52,16 @@ func (ps *L2PricingState) AddToGasPoolMultiConstraints(gas int64) error { return nil } +// UpdatePricingModelWithArbosVersion updates the pricing model with info from the last block +func (ps *L2PricingState) UpdatePricingModelWithArbosVersion(timePassed uint64, arbosVersion uint64) { + // For ArbOS version 50 and above call Multi-Constraint Pricer update + if arbosVersion >= ArbosMultiConstraintsVersion { + ps.updatePricingModelMultiConstraints(timePassed) + } else { + ps.updatePricingModelLegacy(timePassed) + } +} + // applyGasDelta grows the backlog if the gas is negative and pays off if the gas is positive. func applyGasDelta(backlog uint64, gas int64) uint64 { if gas > 0 { @@ -59,10 +71,9 @@ func applyGasDelta(backlog uint64, gas int64) uint64 { } } -// UpdatePricingModel updates the pricing model with info from the last block -func (ps *L2PricingState) UpdatePricingModel(l2BaseFee *big.Int, timePassed uint64, debug bool) { +func (ps *L2PricingState) updatePricingModelLegacy(timePassed uint64) { speedLimit, _ := ps.SpeedLimitPerSecond() - _ = ps.AddToGasPool(arbmath.SaturatingCast[int64](arbmath.SaturatingUMul(timePassed, speedLimit))) + _ = ps.addToGasPool(arbmath.SaturatingCast[int64](arbmath.SaturatingUMul(timePassed, speedLimit))) inertia, _ := ps.PricingInertia() tolerance, _ := ps.BacklogTolerance() backlog, _ := ps.GasBacklog() @@ -76,7 +87,7 @@ func (ps *L2PricingState) UpdatePricingModel(l2BaseFee *big.Int, timePassed uint _ = ps.SetBaseFeeWei(baseFee) } -func (ps *L2PricingState) UpdatePricingModelMultiConstraints(timePassed uint64) { +func (ps *L2PricingState) updatePricingModelMultiConstraints(timePassed uint64) { // Compute exponent used in the basefee formula totalExponent := arbmath.Bips(0) constraintsLength, _ := ps.constraints.Length() diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index a6a21e17de6..8887436fe10 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -158,39 +159,34 @@ func (con ArbGasInfo) GetPricesInArbGas(c ctx, evm mech) (huge, huge, huge, erro // GetGasAccountingParams gets the rollup's speed limit, pool size, and block gas limit func (con ArbGasInfo) GetGasAccountingParams(c ctx, evm mech) (huge, huge, huge, error) { - l2pricing := c.State.L2PricingState() + l2ps := c.State.L2PricingState() - var ( - speedLimit uint64 - err error - ) + maxBlockGasLimit, err := l2ps.PerBlockGasLimit() + if err != nil { + return nil, nil, nil, err + } // For ArbOS version 50 and above, the speed limit is defined by the highest-period constraint - if c.State.ArbOSVersion() >= params.ArbosVersion_50 { - constraint, cerr := l2pricing.HighestPeriodConstraint() + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { + constraint, cerr := l2ps.HighestPeriodConstraint() if cerr != nil { return nil, nil, nil, cerr } - speedLimit, err = constraint.Target() - } else { - speedLimit, err = l2pricing.SpeedLimitPerSecond() - } - if err != nil { - return nil, nil, nil, err - } - maxBlockGasLimit, err := l2pricing.PerBlockGasLimit() - if err != nil { - return nil, nil, nil, err - } + speedLimit, err := constraint.Target() + if err != nil { + return nil, nil, nil, err + } - // gasPoolMax is always zero under both pricing models. - gasPoolMax := uint64(0) + // gasPoolMax is always zero for multi-constraint pricer + return arbmath.UintToBig(speedLimit), + arbmath.UintToBig(uint64(0)), + arbmath.UintToBig(maxBlockGasLimit), + nil + } - return arbmath.UintToBig(speedLimit), - arbmath.UintToBig(gasPoolMax), - arbmath.UintToBig(maxBlockGasLimit), - nil + speedLimit, err := l2ps.SpeedLimitPerSecond() + return arbmath.UintToBig(speedLimit), arbmath.UintToBig(maxBlockGasLimit), arbmath.UintToBig(maxBlockGasLimit), err } // GetMaxTxGasLimit gets the max tx gas limit @@ -238,7 +234,7 @@ func (con ArbGasInfo) GetCurrentTxL1GasFees(c ctx, evm mech) (huge, error) { // GetGasBacklog gets the backlogged amount of gas burnt in excess of the speed limit func (con ArbGasInfo) GetGasBacklog(c ctx, evm mech) (uint64, error) { // For ArbOS version 50 and above, the backlog is defined by the highest-period constraint - if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { constraint, err := c.State.L2PricingState().HighestPeriodConstraint() if err != nil { return 0, err @@ -251,7 +247,7 @@ func (con ArbGasInfo) GetGasBacklog(c ctx, evm mech) (uint64, error) { // GetPricingInertia gets how slowly ArbOS updates the L2 basefee in response to backlogged gas func (con ArbGasInfo) GetPricingInertia(c ctx, evm mech) (uint64, error) { // For ArbOS version 50 and above, compute the inertia from period of highest-period constraint - if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { constraint, err := c.State.L2PricingState().HighestPeriodConstraint() if err != nil { return 0, err @@ -264,7 +260,7 @@ func (con ArbGasInfo) GetPricingInertia(c ctx, evm mech) (uint64, error) { // GetGasBacklogTolerance gets the forgivable amount of backlogged gas ArbOS will ignore when raising the basefee func (con ArbGasInfo) GetGasBacklogTolerance(c ctx, evm mech) (uint64, error) { // For ArbOS version 50 and above, the backlog tolerance is always zero - if c.State.ArbOSVersion() >= params.ArbosVersion_50 { + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { return 0, nil } diff --git a/precompiles/ArbGasInfo_test.go b/precompiles/ArbGasInfo_test.go index 52d1d8e60d2..c607f39959c 100644 --- a/precompiles/ArbGasInfo_test.go +++ b/precompiles/ArbGasInfo_test.go @@ -15,6 +15,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/burn" + "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/util/testhelpers" @@ -46,8 +47,14 @@ func TestGetGasBacklog(t *testing.T) { evm, state, callCtx, arbGasInfo := setupArbGasInfo(t) backlog := uint64(1000) - err := state.L2PricingState().SetGasBacklog(backlog) - Require(t, err) + if state.ArbOSVersion() < l2pricing.ArbosMultiConstraintsVersion { + err := state.L2PricingState().SetGasBacklog(backlog) + Require(t, err) + } else { + constraint := state.L2PricingState().OpenConstraintAt(0) + err := constraint.SetBacklog(backlog) + Require(t, err) + } retrievedBacklog, err := arbGasInfo.GetGasBacklog(callCtx, evm) Require(t, err) if retrievedBacklog != backlog { diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index ec8593e0171..204a842dd57 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -141,8 +142,8 @@ func (con ArbOwner) SetMinimumL2BaseFee(c ctx, evm mech, priceInWei huge) error // SetSpeedLimit sets the computational speed limit for the chain func (con ArbOwner) SetSpeedLimit(c ctx, evm mech, limit uint64) error { - if c.State.ArbOSVersion() >= params.ArbosVersion_50 { - return fmt.Errorf("SetSpeedLimit is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", params.ArbosVersion_50) + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { + return fmt.Errorf("SetSpeedLimit is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", l2pricing.ArbosMultiConstraintsVersion) } if limit == 0 { return errors.New("speed limit must be nonzero") @@ -165,8 +166,8 @@ func (con ArbOwner) SetMaxBlockGasLimit(c ctx, evm mech, limit uint64) error { // SetL2GasPricingInertia sets the L2 gas pricing inertia func (con ArbOwner) SetL2GasPricingInertia(c ctx, evm mech, sec uint64) error { - if c.State.ArbOSVersion() >= params.ArbosVersion_50 { - return fmt.Errorf("SetL2GasPricingInertia is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", params.ArbosVersion_50) + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { + return fmt.Errorf("SetL2GasPricingInertia is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", l2pricing.ArbosMultiConstraintsVersion) } if sec == 0 { return errors.New("price inertia must be nonzero") @@ -176,8 +177,8 @@ func (con ArbOwner) SetL2GasPricingInertia(c ctx, evm mech, sec uint64) error { // SetL2GasBacklogTolerance sets the L2 gas backlog tolerance func (con ArbOwner) SetL2GasBacklogTolerance(c ctx, evm mech, sec uint64) error { - if c.State.ArbOSVersion() >= params.ArbosVersion_50 { - return fmt.Errorf("SetL2GasBacklogTolerance is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", params.ArbosVersion_50) + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { + return fmt.Errorf("SetL2GasBacklogTolerance is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", l2pricing.ArbosMultiConstraintsVersion) } return c.State.L2PricingState().SetBacklogTolerance(sec) } diff --git a/system_tests/deployment_test.go b/system_tests/deployment_test.go index ebcb5c95d05..5c63dc2d6a8 100644 --- a/system_tests/deployment_test.go +++ b/system_tests/deployment_test.go @@ -6,6 +6,7 @@ package arbtest import ( "bytes" "context" + "math/big" "strings" "testing" @@ -62,11 +63,15 @@ func testContractDeployment(t *testing.T, ctx context.Context, client *ethclient nonce, err := client.PendingNonceAt(ctx, accountInfo.Address) Require(t, err) + // Raise gas fee cap by 25% because new multi-constraint pricing is slightly more aggressive + gasFeeCap := new(big.Int).Mul(latestHeader.BaseFee, big.NewInt(5)) + gasFeeCap.Div(gasFeeCap, big.NewInt(4)) + tx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainId, Nonce: nonce, GasTipCap: common.Big0, - GasFeeCap: latestHeader.BaseFee, + GasFeeCap: gasFeeCap, Gas: deploymentGas, To: nil, Value: common.Big0, diff --git a/system_tests/estimation_test.go b/system_tests/estimation_test.go index a336f95aa0f..9439a328562 100644 --- a/system_tests/estimation_test.go +++ b/system_tests/estimation_test.go @@ -51,6 +51,20 @@ func TestDeploy(t *testing.T) { } } +// equalWithinTolerance returns true if a and b differ by less than or equal to percent. +func equalWithinTolerance(a, b *big.Int, percent int64) bool { + if a == nil || b == nil { + return false + } + diff := new(big.Int).Sub(a, b) + if diff.Sign() < 0 { + diff.Neg(diff) + } + allowed := new(big.Int).Mul(a, big.NewInt(percent)) + allowed.Div(allowed, big.NewInt(100)) + return diff.Cmp(allowed) <= 0 +} + func TestEstimate(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -81,13 +95,13 @@ func TestEstimate(t *testing.T) { numTriesLeft := 20 for !equilibrated && numTriesLeft > 0 { // make an empty block to let the gas price update - builder.L2Info.GasPrice = new(big.Int).Mul(builder.L2Info.GasPrice, big.NewInt(2)) builder.L2.TransferBalance(t, "Owner", "Owner", common.Big0, builder.L2Info) // check if the price has equilibrated _, _, _, _, _, setPrice, err := arbGasInfo.GetPricesInWei(&bind.CallOpts{}) Require(t, err, "could not get L2 gas price") - if gasPrice.Cmp(setPrice) == 0 { + + if equalWithinTolerance(gasPrice, setPrice, 5) { // 5% tolerance for multi-constraint pricer equilibrated = true } numTriesLeft-- @@ -107,7 +121,7 @@ func TestEstimate(t *testing.T) { header, err := builder.L2.Client.HeaderByNumber(ctx, receipt.BlockNumber) Require(t, err, "could not get header") - if header.BaseFee.Cmp(gasPrice) != 0 { + if !equalWithinTolerance(gasPrice, header.BaseFee, 5) { // 5% tolerance for multi-constraint pricer Fatal(t, "Header has wrong basefee", header.BaseFee, gasPrice) } @@ -115,7 +129,8 @@ func TestEstimate(t *testing.T) { Require(t, err, "could not get balance") expectedCost := receipt.GasUsed * gasPrice.Uint64() observedCost := initialBalance.Uint64() - balance.Uint64() - if expectedCost != observedCost { + // #nosec G115 + if !equalWithinTolerance(big.NewInt(int64(expectedCost)), big.NewInt(int64(observedCost)), 5) { Fatal(t, "Expected deployment to cost", expectedCost, "instead of", observedCost) } @@ -276,7 +291,7 @@ func TestComponentEstimate(t *testing.T) { if !arbmath.BigEquals(l1BaseFeeEstimate, l1BaseFee) { Fatal(t, l1BaseFeeEstimate, l1BaseFee) } - if !arbmath.BigEquals(baseFee, l2BaseFee) { + if !equalWithinTolerance(baseFee, l2BaseFee, 5) { Fatal(t, baseFee, l2BaseFee.Uint64()) } diff --git a/system_tests/eth_config_test.go b/system_tests/eth_config_test.go index ed0ae3848e8..8e1189e6bc4 100644 --- a/system_tests/eth_config_test.go +++ b/system_tests/eth_config_test.go @@ -49,7 +49,7 @@ func TestEthConfig(t *testing.T) { ActivationTime: 0, BlobSchedule: nil, ChainId: (*hexutil.Big)(hexutil.MustDecodeBig("0x64aba")), - ForkId: (hexutil.Bytes)(hexutil.MustDecode("0x93dd2fec")), + ForkId: (hexutil.Bytes)(hexutil.MustDecode("0x8527382e")), Precompiles: map[string]common.Address{ "ArbAddressTable": common.HexToAddress("0x0000000000000000000000000000000000000066"), "ArbAggregator": common.HexToAddress("0x000000000000000000000000000000000000006d"), diff --git a/system_tests/infra_fee_test.go b/system_tests/infra_fee_test.go index 97663154105..e81b2cf6d3b 100644 --- a/system_tests/infra_fee_test.go +++ b/system_tests/infra_fee_test.go @@ -65,10 +65,13 @@ func TestInfraFee(t *testing.T) { infraFeeBalanceAfter, err := builder.L2.Client.BalanceAt(ctx, infraFeeAddr, nil) Require(t, err) - if !arbmath.BigEquals(netFeeBalanceBefore, netFeeBalanceAfter) { - Fatal(t, netFeeBalanceBefore, netFeeBalanceAfter) - } - if !arbmath.BigEquals(infraFeeBalanceAfter, expectedBalanceAfter) { - Fatal(t, infraFeeBalanceBefore, expectedFunds, infraFeeBalanceAfter, expectedBalanceAfter) + // For constraint-based pricer these balances would be different + if builder.chainConfig.ArbitrumChainParams.InitialArbOSVersion < l2pricing.ArbosMultiConstraintsVersion { + if !arbmath.BigEquals(netFeeBalanceBefore, netFeeBalanceAfter) { + Fatal(t, netFeeBalanceBefore, netFeeBalanceAfter) + } + if !arbmath.BigEquals(infraFeeBalanceAfter, expectedBalanceAfter) { + Fatal(t, infraFeeBalanceBefore, expectedFunds, infraFeeBalanceAfter, expectedBalanceAfter) + } } } diff --git a/system_tests/multi_constraint_pricer_test.go b/system_tests/multi_constraint_pricer_test.go index bc2503db2ca..db17c04ca85 100644 --- a/system_tests/multi_constraint_pricer_test.go +++ b/system_tests/multi_constraint_pricer_test.go @@ -121,10 +121,9 @@ func TestMultiConstraintPricerMigration(t *testing.T) { require.NoError(t, err) // Check GetGasAccountingParams returns expected speed limit - speedLimit, gasPoolMax, _, err := arbGasInfo.GetGasAccountingParams(callOpts) + speedLimit, _, _, err := arbGasInfo.GetGasAccountingParams(callOpts) require.NoError(t, err) require.Zero(t, big.NewInt(20_000_000).Cmp(speedLimit)) - require.Zero(t, big.NewInt(0).Cmp(gasPoolMax)) // Check GetPricingInertia returns expected inertia pricingInertia, err := arbGasInfo.GetPricingInertia(callOpts) diff --git a/system_tests/multigas_stylus_program_test.go b/system_tests/multigas_stylus_program_test.go index f6e400f05ef..4f787e8fd77 100644 --- a/system_tests/multigas_stylus_program_test.go +++ b/system_tests/multigas_stylus_program_test.go @@ -377,9 +377,9 @@ func TestMultigasStylus_StorageWrite(t *testing.T) { require.Equal(t, params.ColdSloadCostEIP2929, receipt.MultiGasUsed.Get(multigas.ResourceKindStorageAccess)) require.Equal(t, params.SstoreSetGasEIP2200, receipt.MultiGasUsed.Get(multigas.ResourceKindStorageGrowth)) } else { - require.Error(t, err) receipt, err := l2client.TransactionReceipt(ctx, tx.Hash()) require.NoError(t, err) + require.Equal(t, uint64(0), receipt.MultiGasUsed.Get(multigas.ResourceKindStorageGrowth)) require.Equal(t, receipt.GasUsed, receipt.MultiGasUsed.SingleGas()) } }) diff --git a/system_tests/precompile_test.go b/system_tests/precompile_test.go index 87ed379bfa1..5d537df107a 100644 --- a/system_tests/precompile_test.go +++ b/system_tests/precompile_test.go @@ -25,6 +25,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/solgen/go/localgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" @@ -356,7 +357,7 @@ func TestL2GasPricingInertia(t *testing.T) { inertia := uint64(14) tx, err := arbOwner.SetL2GasPricingInertia(&auth, inertia) - if arbosVersion >= params.ArbosVersion_50 { + if arbosVersion >= l2pricing.ArbosMultiConstraintsVersion { // Expect error for multi-constraint pricer if err == nil { t.Fatalf("expected an error, got nil") @@ -380,7 +381,7 @@ func TestL2GasBacklogTolerance(t *testing.T) { gasTolerance := uint64(15) tx, err := arbOwner.SetL2GasBacklogTolerance(&auth, gasTolerance) - if arbosVersion >= params.ArbosVersion_50 { + if arbosVersion >= l2pricing.ArbosMultiConstraintsVersion { // Expect error for multi-constraint pricer if err == nil { t.Fatalf("expected an error, got nil") @@ -439,7 +440,7 @@ func TestGasAccountingParams(t *testing.T) { speedLimit := uint64(18) blockGasLimit := uint64(19) tx, err := arbOwner.SetSpeedLimit(&auth, speedLimit) - if arbosVersion >= params.ArbosVersion_50 { + if arbosVersion >= l2pricing.ArbosMultiConstraintsVersion { // Expect error for multi-constraint pricer if err == nil { t.Fatalf("expected an error, got nil") diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 06cfbb4fd7b..2419782cb47 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -1042,6 +1042,10 @@ func testMemory(t *testing.T, jit bool) { l2client := builder.L2.Client defer cleanup() + // Set gas price to 1 Gwei to handle base fee growth under new pricing model + // (base fee can climb to ~0.5 Gwei during this test) + l2info.GasPrice = big.NewInt(params.GWei) + ensure := func(tx *types.Transaction, err error) *types.Receipt { t.Helper() Require(t, err) From 1beb4bed3b58252bf06c2310ed81aed3ff6e77a7 Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Thu, 23 Oct 2025 08:23:45 -0300 Subject: [PATCH 09/16] Undo changes in tests --- arbnode/inbox_test.go | 6 +-- system_tests/deployment_test.go | 7 +-- system_tests/estimation_test.go | 25 +++-------- system_tests/eth_config_test.go | 2 +- system_tests/infra_fee_test.go | 13 +++--- system_tests/multigas_stylus_program_test.go | 2 +- system_tests/precompile_test.go | 45 +++++--------------- system_tests/program_test.go | 4 -- 8 files changed, 26 insertions(+), 78 deletions(-) diff --git a/arbnode/inbox_test.go b/arbnode/inbox_test.go index 96539a1c697..8081b39439d 100644 --- a/arbnode/inbox_test.go +++ b/arbnode/inbox_test.go @@ -20,6 +20,7 @@ import ( "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/execution" @@ -192,8 +193,7 @@ func TestTransactionStreamer(t *testing.T) { Require(t, err) exec.Start(ctx) - gasPrice := big.NewInt(params.GWei) // 1 Gwei to handle base fee growth - maxExpectedGasCost := new(big.Int).Set(gasPrice) + maxExpectedGasCost := big.NewInt(l2pricing.InitialBaseFeeWei) maxExpectedGasCost.Mul(maxExpectedGasCost, big.NewInt(2100*2)) minBalance := new(big.Int).Mul(maxExpectedGasCost, big.NewInt(100)) @@ -247,7 +247,7 @@ func TestTransactionStreamer(t *testing.T) { var l2Message []byte l2Message = append(l2Message, arbos.L2MessageKind_ContractTx) l2Message = append(l2Message, arbmath.Uint64ToU256Bytes(gas)...) - l2Message = append(l2Message, arbmath.Uint64ToU256Bytes(gasPrice.Uint64())...) + l2Message = append(l2Message, arbmath.Uint64ToU256Bytes(l2pricing.InitialBaseFeeWei)...) l2Message = append(l2Message, destHash.Bytes()...) l2Message = append(l2Message, arbmath.U256Bytes(value)...) var requestId common.Hash diff --git a/system_tests/deployment_test.go b/system_tests/deployment_test.go index 5c63dc2d6a8..ebcb5c95d05 100644 --- a/system_tests/deployment_test.go +++ b/system_tests/deployment_test.go @@ -6,7 +6,6 @@ package arbtest import ( "bytes" "context" - "math/big" "strings" "testing" @@ -63,15 +62,11 @@ func testContractDeployment(t *testing.T, ctx context.Context, client *ethclient nonce, err := client.PendingNonceAt(ctx, accountInfo.Address) Require(t, err) - // Raise gas fee cap by 25% because new multi-constraint pricing is slightly more aggressive - gasFeeCap := new(big.Int).Mul(latestHeader.BaseFee, big.NewInt(5)) - gasFeeCap.Div(gasFeeCap, big.NewInt(4)) - tx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainId, Nonce: nonce, GasTipCap: common.Big0, - GasFeeCap: gasFeeCap, + GasFeeCap: latestHeader.BaseFee, Gas: deploymentGas, To: nil, Value: common.Big0, diff --git a/system_tests/estimation_test.go b/system_tests/estimation_test.go index 9439a328562..a336f95aa0f 100644 --- a/system_tests/estimation_test.go +++ b/system_tests/estimation_test.go @@ -51,20 +51,6 @@ func TestDeploy(t *testing.T) { } } -// equalWithinTolerance returns true if a and b differ by less than or equal to percent. -func equalWithinTolerance(a, b *big.Int, percent int64) bool { - if a == nil || b == nil { - return false - } - diff := new(big.Int).Sub(a, b) - if diff.Sign() < 0 { - diff.Neg(diff) - } - allowed := new(big.Int).Mul(a, big.NewInt(percent)) - allowed.Div(allowed, big.NewInt(100)) - return diff.Cmp(allowed) <= 0 -} - func TestEstimate(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -95,13 +81,13 @@ func TestEstimate(t *testing.T) { numTriesLeft := 20 for !equilibrated && numTriesLeft > 0 { // make an empty block to let the gas price update + builder.L2Info.GasPrice = new(big.Int).Mul(builder.L2Info.GasPrice, big.NewInt(2)) builder.L2.TransferBalance(t, "Owner", "Owner", common.Big0, builder.L2Info) // check if the price has equilibrated _, _, _, _, _, setPrice, err := arbGasInfo.GetPricesInWei(&bind.CallOpts{}) Require(t, err, "could not get L2 gas price") - - if equalWithinTolerance(gasPrice, setPrice, 5) { // 5% tolerance for multi-constraint pricer + if gasPrice.Cmp(setPrice) == 0 { equilibrated = true } numTriesLeft-- @@ -121,7 +107,7 @@ func TestEstimate(t *testing.T) { header, err := builder.L2.Client.HeaderByNumber(ctx, receipt.BlockNumber) Require(t, err, "could not get header") - if !equalWithinTolerance(gasPrice, header.BaseFee, 5) { // 5% tolerance for multi-constraint pricer + if header.BaseFee.Cmp(gasPrice) != 0 { Fatal(t, "Header has wrong basefee", header.BaseFee, gasPrice) } @@ -129,8 +115,7 @@ func TestEstimate(t *testing.T) { Require(t, err, "could not get balance") expectedCost := receipt.GasUsed * gasPrice.Uint64() observedCost := initialBalance.Uint64() - balance.Uint64() - // #nosec G115 - if !equalWithinTolerance(big.NewInt(int64(expectedCost)), big.NewInt(int64(observedCost)), 5) { + if expectedCost != observedCost { Fatal(t, "Expected deployment to cost", expectedCost, "instead of", observedCost) } @@ -291,7 +276,7 @@ func TestComponentEstimate(t *testing.T) { if !arbmath.BigEquals(l1BaseFeeEstimate, l1BaseFee) { Fatal(t, l1BaseFeeEstimate, l1BaseFee) } - if !equalWithinTolerance(baseFee, l2BaseFee, 5) { + if !arbmath.BigEquals(baseFee, l2BaseFee) { Fatal(t, baseFee, l2BaseFee.Uint64()) } diff --git a/system_tests/eth_config_test.go b/system_tests/eth_config_test.go index 8e1189e6bc4..ed0ae3848e8 100644 --- a/system_tests/eth_config_test.go +++ b/system_tests/eth_config_test.go @@ -49,7 +49,7 @@ func TestEthConfig(t *testing.T) { ActivationTime: 0, BlobSchedule: nil, ChainId: (*hexutil.Big)(hexutil.MustDecodeBig("0x64aba")), - ForkId: (hexutil.Bytes)(hexutil.MustDecode("0x8527382e")), + ForkId: (hexutil.Bytes)(hexutil.MustDecode("0x93dd2fec")), Precompiles: map[string]common.Address{ "ArbAddressTable": common.HexToAddress("0x0000000000000000000000000000000000000066"), "ArbAggregator": common.HexToAddress("0x000000000000000000000000000000000000006d"), diff --git a/system_tests/infra_fee_test.go b/system_tests/infra_fee_test.go index e81b2cf6d3b..97663154105 100644 --- a/system_tests/infra_fee_test.go +++ b/system_tests/infra_fee_test.go @@ -65,13 +65,10 @@ func TestInfraFee(t *testing.T) { infraFeeBalanceAfter, err := builder.L2.Client.BalanceAt(ctx, infraFeeAddr, nil) Require(t, err) - // For constraint-based pricer these balances would be different - if builder.chainConfig.ArbitrumChainParams.InitialArbOSVersion < l2pricing.ArbosMultiConstraintsVersion { - if !arbmath.BigEquals(netFeeBalanceBefore, netFeeBalanceAfter) { - Fatal(t, netFeeBalanceBefore, netFeeBalanceAfter) - } - if !arbmath.BigEquals(infraFeeBalanceAfter, expectedBalanceAfter) { - Fatal(t, infraFeeBalanceBefore, expectedFunds, infraFeeBalanceAfter, expectedBalanceAfter) - } + if !arbmath.BigEquals(netFeeBalanceBefore, netFeeBalanceAfter) { + Fatal(t, netFeeBalanceBefore, netFeeBalanceAfter) + } + if !arbmath.BigEquals(infraFeeBalanceAfter, expectedBalanceAfter) { + Fatal(t, infraFeeBalanceBefore, expectedFunds, infraFeeBalanceAfter, expectedBalanceAfter) } } diff --git a/system_tests/multigas_stylus_program_test.go b/system_tests/multigas_stylus_program_test.go index 4f787e8fd77..f6e400f05ef 100644 --- a/system_tests/multigas_stylus_program_test.go +++ b/system_tests/multigas_stylus_program_test.go @@ -377,9 +377,9 @@ func TestMultigasStylus_StorageWrite(t *testing.T) { require.Equal(t, params.ColdSloadCostEIP2929, receipt.MultiGasUsed.Get(multigas.ResourceKindStorageAccess)) require.Equal(t, params.SstoreSetGasEIP2200, receipt.MultiGasUsed.Get(multigas.ResourceKindStorageGrowth)) } else { + require.Error(t, err) receipt, err := l2client.TransactionReceipt(ctx, tx.Hash()) require.NoError(t, err) - require.Equal(t, uint64(0), receipt.MultiGasUsed.Get(multigas.ResourceKindStorageGrowth)) require.Equal(t, receipt.GasUsed, receipt.MultiGasUsed.SingleGas()) } }) diff --git a/system_tests/precompile_test.go b/system_tests/precompile_test.go index 5d537df107a..35443d77685 100644 --- a/system_tests/precompile_test.go +++ b/system_tests/precompile_test.go @@ -25,7 +25,6 @@ import ( "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/l1pricing" - "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/solgen/go/localgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" @@ -257,15 +256,12 @@ func setupArbOwnerAndArbGasInfo( bind.TransactOpts, *precompilesgen.ArbOwner, *precompilesgen.ArbGasInfo, - uint64, ) { ctx, cancel := context.WithCancel(context.Background()) builder := NewNodeBuilder(ctx).DefaultConfig(t, false) builderCleanup := builder.Build(t) - arbosVersion := builder.chainConfig.ArbitrumChainParams.InitialArbOSVersion - cleanup := func() { builderCleanup() cancel() @@ -278,11 +274,11 @@ func setupArbOwnerAndArbGasInfo( arbGasInfo, err := precompilesgen.NewArbGasInfo(common.HexToAddress("0x6c"), builder.L2.Client) Require(t, err) - return builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion + return builder, cleanup, auth, arbOwner, arbGasInfo } func TestL1BaseFeeEstimateInertia(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -300,7 +296,7 @@ func TestL1BaseFeeEstimateInertia(t *testing.T) { // Similar to TestL1BaseFeeEstimateInertia, but now using a different setter from ArbOwner func TestL1PricingInertia(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -317,7 +313,7 @@ func TestL1PricingInertia(t *testing.T) { } func TestL1PricingRewardRate(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -334,7 +330,7 @@ func TestL1PricingRewardRate(t *testing.T) { } func TestL1PricingRewardRecipient(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -351,19 +347,12 @@ func TestL1PricingRewardRecipient(t *testing.T) { } func TestL2GasPricingInertia(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx inertia := uint64(14) tx, err := arbOwner.SetL2GasPricingInertia(&auth, inertia) - if arbosVersion >= l2pricing.ArbosMultiConstraintsVersion { - // Expect error for multi-constraint pricer - if err == nil { - t.Fatalf("expected an error, got nil") - } - return - } Require(t, err) _, err = builder.L2.EnsureTxSucceeded(tx) Require(t, err) @@ -375,19 +364,12 @@ func TestL2GasPricingInertia(t *testing.T) { } func TestL2GasBacklogTolerance(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx gasTolerance := uint64(15) tx, err := arbOwner.SetL2GasBacklogTolerance(&auth, gasTolerance) - if arbosVersion >= l2pricing.ArbosMultiConstraintsVersion { - // Expect error for multi-constraint pricer - if err == nil { - t.Fatalf("expected an error, got nil") - } - return - } Require(t, err) _, err = builder.L2.EnsureTxSucceeded(tx) Require(t, err) @@ -399,7 +381,7 @@ func TestL2GasBacklogTolerance(t *testing.T) { } func TestPerBatchGasCharge(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -416,7 +398,7 @@ func TestPerBatchGasCharge(t *testing.T) { } func TestL1PricingEquilibrationUnits(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, _ := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -433,20 +415,13 @@ func TestL1PricingEquilibrationUnits(t *testing.T) { } func TestGasAccountingParams(t *testing.T) { - builder, cleanup, auth, arbOwner, arbGasInfo, arbosVersion := setupArbOwnerAndArbGasInfo(t) + builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx speedLimit := uint64(18) blockGasLimit := uint64(19) tx, err := arbOwner.SetSpeedLimit(&auth, speedLimit) - if arbosVersion >= l2pricing.ArbosMultiConstraintsVersion { - // Expect error for multi-constraint pricer - if err == nil { - t.Fatalf("expected an error, got nil") - } - return - } Require(t, err) _, err = builder.L2.EnsureTxSucceeded(tx) Require(t, err) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 2419782cb47..06cfbb4fd7b 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -1042,10 +1042,6 @@ func testMemory(t *testing.T, jit bool) { l2client := builder.L2.Client defer cleanup() - // Set gas price to 1 Gwei to handle base fee growth under new pricing model - // (base fee can climb to ~0.5 Gwei during this test) - l2info.GasPrice = big.NewInt(params.GWei) - ensure := func(tx *types.Transaction, err error) *types.Receipt { t.Helper() Require(t, err) From c066f450637712fc9ae887fd473ff31d715ce45d Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Thu, 23 Oct 2025 09:33:29 -0300 Subject: [PATCH 10/16] Support legacy and multi-constraints simultaneously --- arbos/arbosState/arbosstate.go | 1 - arbos/internal_tx.go | 2 +- arbos/l2pricing/l2pricing.go | 2 +- arbos/l2pricing/l2pricing_test.go | 2 +- arbos/l2pricing/model.go | 34 ++++++++++++++++++---- arbos/l2pricing/model_test.go | 2 +- arbos/tx_processor.go | 4 +-- precompiles/ArbGasInfo.go | 46 ++---------------------------- precompiles/ArbGasInfo_test.go | 11 ++----- precompiles/ArbOwner.go | 14 --------- precompiles/ArbRetryableTx.go | 2 +- precompiles/ArbRetryableTx_test.go | 2 +- precompiles/constraints_test.go | 6 +--- 13 files changed, 42 insertions(+), 86 deletions(-) diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 5ed0ce0b81d..7338dc8485d 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -391,7 +391,6 @@ func (state *ArbosState) UpgradeArbosVersion( ensure(p.UpgradeToArbosVersion(nextArbosVersion)) ensure(p.Save()) ensure(state.l2PricingState.SetMaxPerTxGasLimit(l2pricing.InitialPerTxGasLimitV50)) - ensure(state.l2PricingState.SetConstraintsFromLegacy()) default: return fmt.Errorf( "the chain is upgrading to unsupported ArbOS version %v, %w", diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index 65d0585ddc7..02fd6650209 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -104,7 +104,7 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) _ = state.RetryableState().TryToReapOneRetryable(currentTime, evm, util.TracingDuringEVM) - state.L2PricingState().UpdatePricingModelWithArbosVersion(timePassed, state.ArbOSVersion()) + state.L2PricingState().UpdatePricingModel(timePassed, state.ArbOSVersion()) return state.UpgradeArbosVersionIfNecessary(currentTime, evm.StateDB, evm.ChainConfig()) case InternalTxBatchPostingReportMethodID: diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index 2817b5e701d..477d913f41a 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -200,7 +200,7 @@ func (ps *L2PricingState) Restrict(err error) { ps.storage.Burner().Restrict(err) } -func (ps *L2PricingState) SetConstraintsFromLegacy() error { +func (ps *L2PricingState) setConstraintsFromLegacy() error { if err := ps.ClearConstraints(); err != nil { return err } diff --git a/arbos/l2pricing/l2pricing_test.go b/arbos/l2pricing/l2pricing_test.go index a10f1ba6fb0..25d2500c21d 100644 --- a/arbos/l2pricing/l2pricing_test.go +++ b/arbos/l2pricing/l2pricing_test.go @@ -24,7 +24,7 @@ func PricingForTest(t *testing.T) *L2PricingState { func fakeBlockUpdate(t *testing.T, pricing *L2PricingState, gasUsed int64, timePassed uint64) { t.Helper() - pricing.storage.Burner().Restrict(pricing.addToGasPool(-gasUsed)) + pricing.storage.Burner().Restrict(pricing.addToGasPoolLegacy(-gasUsed)) pricing.updatePricingModelLegacy(timePassed) } diff --git a/arbos/l2pricing/model.go b/arbos/l2pricing/model.go index e9b24f6a113..39f73eb9666 100644 --- a/arbos/l2pricing/model.go +++ b/arbos/l2pricing/model.go @@ -24,7 +24,29 @@ const InitialPricingInertia = 102 const InitialBacklogTolerance = 10 const InitialPerTxGasLimitV50 uint64 = 32 * 1000000 -func (ps *L2PricingState) AddToGasPool(gas int64) error { +func (ps *L2PricingState) ShouldUseMultiConstraints(arbosVersion uint64) (bool, error) { + if arbosVersion >= ArbosMultiConstraintsVersion { + constraintsLength, err := ps.ConstraintsLength() + if err != nil { + return false, err + } + return constraintsLength > 0, nil + } + return false, nil +} + +func (ps *L2PricingState) AddToGasPool(gas int64, arbosVersion uint64) error { + shouldUseMultiConstraints, err := ps.ShouldUseMultiConstraints(arbosVersion) + if err != nil { + return err + } + if shouldUseMultiConstraints { + return ps.addToGasPoolMultiConstraints(gas) + } + return ps.addToGasPoolLegacy(gas) +} + +func (ps *L2PricingState) addToGasPoolLegacy(gas int64) error { backlog, err := ps.GasBacklog() if err != nil { return err @@ -52,10 +74,10 @@ func (ps *L2PricingState) addToGasPoolMultiConstraints(gas int64) error { return nil } -// UpdatePricingModelWithArbosVersion updates the pricing model with info from the last block -func (ps *L2PricingState) UpdatePricingModelWithArbosVersion(timePassed uint64, arbosVersion uint64) { - // For ArbOS version 50 and above call Multi-Constraint Pricer update - if arbosVersion >= ArbosMultiConstraintsVersion { +// UpdatePricingModel updates the pricing model with info from the last block +func (ps *L2PricingState) UpdatePricingModel(timePassed uint64, arbosVersion uint64) { + shouldUseMultiConstraints, _ := ps.ShouldUseMultiConstraints(arbosVersion) + if shouldUseMultiConstraints { ps.updatePricingModelMultiConstraints(timePassed) } else { ps.updatePricingModelLegacy(timePassed) @@ -73,7 +95,7 @@ func applyGasDelta(backlog uint64, gas int64) uint64 { func (ps *L2PricingState) updatePricingModelLegacy(timePassed uint64) { speedLimit, _ := ps.SpeedLimitPerSecond() - _ = ps.addToGasPool(arbmath.SaturatingCast[int64](arbmath.SaturatingUMul(timePassed, speedLimit))) + _ = ps.addToGasPoolLegacy(arbmath.SaturatingCast[int64](arbmath.SaturatingUMul(timePassed, speedLimit))) inertia, _ := ps.PricingInertia() tolerance, _ := ps.BacklogTolerance() backlog, _ := ps.GasBacklog() diff --git a/arbos/l2pricing/model_test.go b/arbos/l2pricing/model_test.go index 0dbb8171af1..af183d65a35 100644 --- a/arbos/l2pricing/model_test.go +++ b/arbos/l2pricing/model_test.go @@ -45,7 +45,7 @@ func TestCompareLegacyPricingModelWithMultiConstraints(t *testing.T) { _ = pricing.gasBacklog.Set(backlog) // Initialize with a single constraint based on the legacy model - _ = pricing.SetConstraintsFromLegacy() + _ = pricing.setConstraintsFromLegacy() pricing.UpdatePricingModel(nil, timePassed, false) legacyPrice, _ := pricing.baseFeeWei.Get() diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 7a17b3192b5..47fbb3f4437 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -632,7 +632,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { } } // we've already credited the network fee account, but we didn't charge the gas pool yet - p.state.Restrict(p.state.L2PricingState().AddToGasPoolWithArbosVersion(-arbmath.SaturatingCast[int64](gasUsed), p.state.ArbOSVersion())) + p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast[int64](gasUsed), p.state.ArbOSVersion())) return } @@ -695,7 +695,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { log.Error("total gas used < poster gas component", "gasUsed", gasUsed, "posterGas", p.posterGas) computeGas = gasUsed } - p.state.Restrict(p.state.L2PricingState().AddToGasPoolWithArbosVersion(-arbmath.SaturatingCast[int64](computeGas), p.state.ArbOSVersion())) + p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast[int64](computeGas), p.state.ArbOSVersion())) } } diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 8887436fe10..3d7e5b21df0 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -159,33 +159,9 @@ func (con ArbGasInfo) GetPricesInArbGas(c ctx, evm mech) (huge, huge, huge, erro // GetGasAccountingParams gets the rollup's speed limit, pool size, and block gas limit func (con ArbGasInfo) GetGasAccountingParams(c ctx, evm mech) (huge, huge, huge, error) { - l2ps := c.State.L2PricingState() - - maxBlockGasLimit, err := l2ps.PerBlockGasLimit() - if err != nil { - return nil, nil, nil, err - } - - // For ArbOS version 50 and above, the speed limit is defined by the highest-period constraint - if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { - constraint, cerr := l2ps.HighestPeriodConstraint() - if cerr != nil { - return nil, nil, nil, cerr - } - - speedLimit, err := constraint.Target() - if err != nil { - return nil, nil, nil, err - } - - // gasPoolMax is always zero for multi-constraint pricer - return arbmath.UintToBig(speedLimit), - arbmath.UintToBig(uint64(0)), - arbmath.UintToBig(maxBlockGasLimit), - nil - } - - speedLimit, err := l2ps.SpeedLimitPerSecond() + l2pricing := c.State.L2PricingState() + speedLimit, _ := l2pricing.SpeedLimitPerSecond() + maxBlockGasLimit, err := l2pricing.PerBlockGasLimit() return arbmath.UintToBig(speedLimit), arbmath.UintToBig(maxBlockGasLimit), arbmath.UintToBig(maxBlockGasLimit), err } @@ -233,27 +209,11 @@ func (con ArbGasInfo) GetCurrentTxL1GasFees(c ctx, evm mech) (huge, error) { // GetGasBacklog gets the backlogged amount of gas burnt in excess of the speed limit func (con ArbGasInfo) GetGasBacklog(c ctx, evm mech) (uint64, error) { - // For ArbOS version 50 and above, the backlog is defined by the highest-period constraint - if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { - constraint, err := c.State.L2PricingState().HighestPeriodConstraint() - if err != nil { - return 0, err - } - return constraint.Backlog() - } return c.State.L2PricingState().GasBacklog() } // GetPricingInertia gets how slowly ArbOS updates the L2 basefee in response to backlogged gas func (con ArbGasInfo) GetPricingInertia(c ctx, evm mech) (uint64, error) { - // For ArbOS version 50 and above, compute the inertia from period of highest-period constraint - if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { - constraint, err := c.State.L2PricingState().HighestPeriodConstraint() - if err != nil { - return 0, err - } - return constraint.ComputeInertiaFromPeriod() - } return c.State.L2PricingState().PricingInertia() } diff --git a/precompiles/ArbGasInfo_test.go b/precompiles/ArbGasInfo_test.go index c607f39959c..52d1d8e60d2 100644 --- a/precompiles/ArbGasInfo_test.go +++ b/precompiles/ArbGasInfo_test.go @@ -15,7 +15,6 @@ import ( "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/burn" - "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/util/testhelpers" @@ -47,14 +46,8 @@ func TestGetGasBacklog(t *testing.T) { evm, state, callCtx, arbGasInfo := setupArbGasInfo(t) backlog := uint64(1000) - if state.ArbOSVersion() < l2pricing.ArbosMultiConstraintsVersion { - err := state.L2PricingState().SetGasBacklog(backlog) - Require(t, err) - } else { - constraint := state.L2PricingState().OpenConstraintAt(0) - err := constraint.SetBacklog(backlog) - Require(t, err) - } + err := state.L2PricingState().SetGasBacklog(backlog) + Require(t, err) retrievedBacklog, err := arbGasInfo.GetGasBacklog(callCtx, evm) Require(t, err) if retrievedBacklog != backlog { diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 204a842dd57..87f2655bea0 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/l1pricing" - "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -142,9 +141,6 @@ func (con ArbOwner) SetMinimumL2BaseFee(c ctx, evm mech, priceInWei huge) error // SetSpeedLimit sets the computational speed limit for the chain func (con ArbOwner) SetSpeedLimit(c ctx, evm mech, limit uint64) error { - if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { - return fmt.Errorf("SetSpeedLimit is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", l2pricing.ArbosMultiConstraintsVersion) - } if limit == 0 { return errors.New("speed limit must be nonzero") } @@ -166,9 +162,6 @@ func (con ArbOwner) SetMaxBlockGasLimit(c ctx, evm mech, limit uint64) error { // SetL2GasPricingInertia sets the L2 gas pricing inertia func (con ArbOwner) SetL2GasPricingInertia(c ctx, evm mech, sec uint64) error { - if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { - return fmt.Errorf("SetL2GasPricingInertia is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", l2pricing.ArbosMultiConstraintsVersion) - } if sec == 0 { return errors.New("price inertia must be nonzero") } @@ -177,9 +170,6 @@ func (con ArbOwner) SetL2GasPricingInertia(c ctx, evm mech, sec uint64) error { // SetL2GasBacklogTolerance sets the L2 gas backlog tolerance func (con ArbOwner) SetL2GasBacklogTolerance(c ctx, evm mech, sec uint64) error { - if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { - return fmt.Errorf("SetL2GasBacklogTolerance is not supported in ArbOS version %d and above; use setGasPricingConstraints instead", l2pricing.ArbosMultiConstraintsVersion) - } return c.State.L2PricingState().SetBacklogTolerance(sec) } @@ -466,10 +456,6 @@ func (con ArbOwner) SetCalldataPriceIncrease(c ctx, _ mech, enable bool) error { // SetGasPricingConstraints sets the gas pricing constraints used by the Multi-Constraint Pricer. func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][3]uint64) error { - if len(constraints) == 0 { - return errors.New("must provide at least one constraint") - } - err := c.State.L2PricingState().ClearConstraints() if err != nil { return fmt.Errorf("failed to clear existing constraints: %w", err) diff --git a/precompiles/ArbRetryableTx.go b/precompiles/ArbRetryableTx.go index cc2d8d51fc6..d561a817c38 100644 --- a/precompiles/ArbRetryableTx.go +++ b/precompiles/ArbRetryableTx.go @@ -128,7 +128,7 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er // Add the gasToDonate back to the gas pool: the retryable attempt will then consume it. // This ensures that the gas pool has enough gas to run the retryable attempt. - return retryTxHash, c.State.L2PricingState().AddToGasPoolWithArbosVersion(arbmath.SaturatingCast[int64](gasToDonate), c.State.ArbOSVersion()) + return retryTxHash, c.State.L2PricingState().AddToGasPool(arbmath.SaturatingCast[int64](gasToDonate), c.State.ArbOSVersion()) } // GetLifetime gets the default lifetime period a retryable has at creation diff --git a/precompiles/ArbRetryableTx_test.go b/precompiles/ArbRetryableTx_test.go index 645ad19d048..6030bc502f9 100644 --- a/precompiles/ArbRetryableTx_test.go +++ b/precompiles/ArbRetryableTx_test.go @@ -80,7 +80,7 @@ func TestRetryableRedeem(t *testing.T) { ) Require(t, err) - if gasLeft != storage.StorageWriteCost-storage.StorageWriteZeroCost { + if gasLeft != storage.StorageWriteCost-storage.StorageWriteZeroCost-storage.StorageReadCost { // We expect to have some gas left over, because in this test we write a zero, but in other // use cases the precompile would cause a non-zero write. So the precompile allocates enough gas // to handle both cases, and some will be left over in this test's use case. diff --git a/precompiles/constraints_test.go b/precompiles/constraints_test.go index 55e71fc6de2..b5d9fdc2eed 100644 --- a/precompiles/constraints_test.go +++ b/precompiles/constraints_test.go @@ -49,12 +49,8 @@ func TestFailToSetInvalidConstraints(t *testing.T) { evm, _, callCtx, _, arbOwner := setupResourceConstraintHandles(t) - // Empty constraints - err := arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{}) - require.Error(t, err) - // Zero target - err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{0, 17, 1000}}) + err := arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{0, 17, 1000}}) require.Error(t, err) // Zero period From ba0becc318944fc18f4de436e5ab465009b27216 Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Thu, 23 Oct 2025 09:46:22 -0300 Subject: [PATCH 11/16] Rename period to inertia --- precompiles/ArbOwner.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 87f2655bea0..eb46d46ed1c 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -463,16 +463,16 @@ func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][3]u for _, constraint := range constraints { target := constraint[0] - period := constraint[1] + inertia := constraint[1] backlog := constraint[2] - if target == 0 || period == 0 { - return fmt.Errorf("invalid constraint with target %d and period %d", target, period) + if target == 0 || inertia == 0 { + return fmt.Errorf("invalid constraint with target %d and inertia %d", target, inertia) } - err := c.State.L2PricingState().AddConstraint(target, period, backlog) + err := c.State.L2PricingState().AddConstraint(target, inertia, backlog) if err != nil { - return fmt.Errorf("failed to add constraint (target: %d, period: %d): %w", target, period, err) + return fmt.Errorf("failed to add constraint (target: %d, inertia: %d): %w", target, inertia, err) } } return nil From 80c5666c391ad43f2225bd93f2fdbf7f9ec10eea Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Thu, 23 Oct 2025 09:48:59 -0300 Subject: [PATCH 12/16] Undo deprecation of GetGasBacklogTolerance --- precompiles/ArbGasInfo.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 3d7e5b21df0..1eb95c00523 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/l1pricing" - "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -219,11 +218,6 @@ func (con ArbGasInfo) GetPricingInertia(c ctx, evm mech) (uint64, error) { // GetGasBacklogTolerance gets the forgivable amount of backlogged gas ArbOS will ignore when raising the basefee func (con ArbGasInfo) GetGasBacklogTolerance(c ctx, evm mech) (uint64, error) { - // For ArbOS version 50 and above, the backlog tolerance is always zero - if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { - return 0, nil - } - return c.State.L2PricingState().BacklogTolerance() } From f83aa49a2c84002f646233481eb57be13927ebea Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Thu, 23 Oct 2025 19:52:53 +0200 Subject: [PATCH 13/16] Update on new precompile methods --- contracts-local/src/precompiles | 2 +- nitro-testnode | 2 +- precompiles/ArbGasInfo.go | 4 ++-- precompiles/ArbOwner.go | 7 ++++++- precompiles/constraints_test.go | 10 +++++----- precompiles/precompile.go | 1 + precompiles/precompile_test.go | 2 +- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/contracts-local/src/precompiles b/contracts-local/src/precompiles index c395f7b6860..f49a4889b48 160000 --- a/contracts-local/src/precompiles +++ b/contracts-local/src/precompiles @@ -1 +1 @@ -Subproject commit c395f7b6860bdccce940b27d79f2e08b04de6dd5 +Subproject commit f49a4889b486fd804a7901203f5f663cfd1581c8 diff --git a/nitro-testnode b/nitro-testnode index 8aa458bb1dd..15ab86232eb 160000 --- a/nitro-testnode +++ b/nitro-testnode @@ -1 +1 @@ -Subproject commit 8aa458bb1ddad9546cf60c392054e0855fa6f8e1 +Subproject commit 15ab86232eb0b6059f6c69472d31ffe3ebaa39f9 diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 1eb95c00523..2eeb21c3b96 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -304,7 +304,7 @@ func (con ArbGasInfo) GetGasPricingConstraints(c ctx, evm mech) ([][3]uint64, er if err != nil { return nil, err } - period, err := constraint.Period() + inertia, err := constraint.Inertia() if err != nil { return nil, err } @@ -315,7 +315,7 @@ func (con ArbGasInfo) GetGasPricingConstraints(c ctx, evm mech) ([][3]uint64, er constraints = append(constraints, [3]uint64{ target, - period, + inertia, backlog, }) } diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index eb46d46ed1c..e6411188b67 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -454,7 +454,12 @@ func (con ArbOwner) SetCalldataPriceIncrease(c ctx, _ mech, enable bool) error { return c.State.Features().SetCalldataPriceIncrease(enable) } -// SetGasPricingConstraints sets the gas pricing constraints used by the Multi-Constraint Pricer. +// SetGasBacklog sets the L2 gas backlog directly (used by single-constraint pricing model only) +func (con ArbOwner) SetGasBacklog(c ctx, evm mech, backlog uint64) error { + return c.State.L2PricingState().SetGasBacklog(backlog) +} + +// SetGasPricingConstraints sets the gas pricing constraints used by the multi-constraint pricing model func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][3]uint64) error { err := c.State.L2PricingState().ClearConstraints() if err != nil { diff --git a/precompiles/constraints_test.go b/precompiles/constraints_test.go index b5d9fdc2eed..cbab7701a47 100644 --- a/precompiles/constraints_test.go +++ b/precompiles/constraints_test.go @@ -53,7 +53,7 @@ func TestFailToSetInvalidConstraints(t *testing.T) { err := arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{0, 17, 1000}}) require.Error(t, err) - // Zero period + // Zero inertia err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{10_000_000, 0, 0}}) require.Error(t, err) } @@ -65,8 +65,8 @@ func TestConstraintsStorage(t *testing.T) { // Set constraints constraints := [][3]uint64{ - {30_000_000, 1, 800_000}, // short-term - {15_000_000, 86400, 1_600_000}, // long-term + {30_000_000, 1, 800_000}, // short-term + {15_000_000, 102, 1_600_000}, // long-term } err := arbOwner.SetGasPricingConstraints(callCtx, evm, constraints) require.NoError(t, err) @@ -97,7 +97,7 @@ func TestConstraintsStorage(t *testing.T) { secondPeriod, err := second.Period() require.NoError(t, err) - require.Equal(t, uint64(86400), secondPeriod) + require.Equal(t, uint64(102), secondInertia) secondBacklog, err := second.Backlog() require.NoError(t, err) @@ -110,7 +110,7 @@ func TestConstraintsStorage(t *testing.T) { require.Equal(t, uint64(30_000_000), result[0][0]) require.Equal(t, uint64(1), result[0][1]) require.Equal(t, uint64(15_000_000), result[1][0]) - require.Equal(t, uint64(86400), result[1][1]) + require.Equal(t, uint64(102), result[1][1]) // Set new constraints constraints = [][3]uint64{ diff --git a/precompiles/precompile.go b/precompiles/precompile.go index 0d6d6750610..73315a4f150 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -618,6 +618,7 @@ func Precompiles() map[addr]ArbosPrecompile { ArbOwner.methodsByName["SetChainConfig"].arbosVersion = params.ArbosVersion_11 ArbOwner.methodsByName["SetBrotliCompressionLevel"].arbosVersion = params.ArbosVersion_20 ArbOwner.methodsByName["SetGasPricingConstraints"].arbosVersion = params.ArbosVersion_50 + ArbOwner.methodsByName["SetGasBacklog"].arbosVersion = params.ArbosVersion_50 stylusMethods := []string{ "SetInkPrice", "SetWasmMaxStackDepth", "SetWasmFreePages", "SetWasmPageGas", "SetWasmPageLimit", "SetWasmMinInitGas", "SetWasmInitCostScalar", diff --git a/precompiles/precompile_test.go b/precompiles/precompile_test.go index 8347de05ac1..61398d4ff11 100644 --- a/precompiles/precompile_test.go +++ b/precompiles/precompile_test.go @@ -191,7 +191,7 @@ func TestPrecompilesPerArbosVersion(t *testing.T) { params.ArbosVersion_31: 1, params.ArbosVersion_40: 3, params.ArbosVersion_41: 10, - params.ArbosVersion_50: 8, + params.ArbosVersion_50: 9, } precompiles := Precompiles() From e7c175f2445cb5f9c63169854f820aafc7d37608 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Thu, 23 Oct 2025 22:52:20 +0200 Subject: [PATCH 14/16] Fix for ArbRetryableTx operation price --- precompiles/ArbRetryableTx.go | 4 ++++ precompiles/ArbRetryableTx_test.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/precompiles/ArbRetryableTx.go b/precompiles/ArbRetryableTx.go index d561a817c38..658f227f627 100644 --- a/precompiles/ArbRetryableTx.go +++ b/precompiles/ArbRetryableTx.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/retryables" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/arbos/util" @@ -99,6 +100,9 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er // Result is 32 bytes long which is 1 word gasCostToReturnResult := params.CopyGas gasPoolUpdateCost := storage.StorageReadCost + storage.StorageWriteCost + if c.State.ArbOSVersion() >= l2pricing.ArbosMultiConstraintsVersion { + gasPoolUpdateCost += storage.StorageReadCost + } futureGasCosts := eventCost + gasCostToReturnResult + gasPoolUpdateCost if c.GasLeft() < futureGasCosts { return hash{}, c.Burn(multigas.ResourceKindComputation, futureGasCosts) // this will error diff --git a/precompiles/ArbRetryableTx_test.go b/precompiles/ArbRetryableTx_test.go index 6030bc502f9..645ad19d048 100644 --- a/precompiles/ArbRetryableTx_test.go +++ b/precompiles/ArbRetryableTx_test.go @@ -80,7 +80,7 @@ func TestRetryableRedeem(t *testing.T) { ) Require(t, err) - if gasLeft != storage.StorageWriteCost-storage.StorageWriteZeroCost-storage.StorageReadCost { + if gasLeft != storage.StorageWriteCost-storage.StorageWriteZeroCost { // We expect to have some gas left over, because in this test we write a zero, but in other // use cases the precompile would cause a non-zero write. So the precompile allocates enough gas // to handle both cases, and some will be left over in this test's use case. From f64302372fee6764543769776083d6a10bed3a19 Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Fri, 24 Oct 2025 12:56:14 +0200 Subject: [PATCH 15/16] Review fixes and rebase cleanup --- arbos/l2pricing/l2pricing.go | 52 +------ arbos/l2pricing/l2pricing_test.go | 40 ------ arbos/l2pricing/model_test.go | 4 +- nitro-testnode | 2 +- precompiles/ArbGasInfo.go | 8 +- precompiles/ArbOwner.go | 14 +- precompiles/constraints_test.go | 28 +++- system_tests/multi_constraint_pricer_test.go | 140 +------------------ 8 files changed, 46 insertions(+), 242 deletions(-) diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index 477d913f41a..b47410fa41a 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -18,8 +18,8 @@ const ( ) // GasConstraint tries to keep the gas backlog under the target (per second) for the given inertia factor. -// The inertia can be computed as 30*sqrt(period), where period is the number of seconds which the -// constraint is acting over. For instance, the default inertia of 102 is an approximation of 30*sqrt(12). +// Target stands for gas usage per second +// The inertia is the time over which the price will rise by a factor of e if demand is 2x the target type GasConstraint struct { target storage.StorageBackedUint64 inertia storage.StorageBackedUint64 @@ -51,21 +51,8 @@ func (c *GasConstraint) Target() (uint64, error) { return c.target.Get() } -func (c *GasConstraint) Period() (uint64, error) { - return c.period.Get() -} - -func (c *GasConstraint) ComputeInertiaFromPeriod() (uint64, error) { - period, err := c.period.Get() - if err != nil { - return 0, err - } - - // Reverse of SetConstraintsFromLegacy: inertia ≈ sqrt(period) * ConstraintDivisorMultiplier - periodSqrt := arbmath.ApproxSquareRoot(period) - inertia := arbmath.SaturatingUMul(periodSqrt, ConstraintDivisorMultiplier) - - return inertia, nil +func (c *GasConstraint) Inertia() (uint64, error) { + return c.inertia.Get() } func (c *GasConstraint) Backlog() (uint64, error) { @@ -239,9 +226,6 @@ func (ps *L2PricingState) AddConstraint(target uint64, inertia uint64, backlog u if err := constraint.backlog.Set(backlog); err != nil { return fmt.Errorf("failed to set backlog: %w", err) } - if err := constraint.backlog.Set(backlog); err != nil { - return fmt.Errorf("failed to set backlog: %w", err) - } return nil } @@ -270,31 +254,3 @@ func (ps *L2PricingState) ClearConstraints() error { } return nil } - -func (ps *L2PricingState) HighestPeriodConstraint() (*GasConstraint, error) { - length, err := ps.ConstraintsLength() - if err != nil { - return nil, err - } - if length == 0 { - return nil, fmt.Errorf("no constraints configured") - } - - var ( - maxPeriod uint64 - maxIdx uint64 - ) - for i := range length { - constraint := ps.OpenConstraintAt(i) - period, err := constraint.period.Get() - if err != nil { - return nil, fmt.Errorf("failed to get period for constraint %d: %w", i, err) - } - if period > maxPeriod { - maxPeriod = period - maxIdx = i - } - } - - return ps.OpenConstraintAt(maxIdx), nil -} diff --git a/arbos/l2pricing/l2pricing_test.go b/arbos/l2pricing/l2pricing_test.go index 25d2500c21d..13798d7762f 100644 --- a/arbos/l2pricing/l2pricing_test.go +++ b/arbos/l2pricing/l2pricing_test.go @@ -160,43 +160,3 @@ func Fail(t *testing.T, printables ...interface{}) { t.Helper() testhelpers.FailImpl(t, printables...) } - -func TestComputeInertiaFromPeriod(t *testing.T) { - pricing := PricingForTest(t) - Require(t, pricing.ClearConstraints()) - - inertias := []uint64{1000, 10000, 120000, 500000} - - for i, inertia := range inertias { - Require(t, pricing.SetSpeedLimitPerSecond(20_000_000)) - Require(t, pricing.SetPricingInertia(inertia)) - Require(t, pricing.SetBacklogTolerance(0)) - - Require(t, pricing.SetConstraintsFromLegacy()) - - length, err := pricing.ConstraintsLength() - Require(t, err) - if length != 1 { - t.Fatalf("expected 1 constraint after migration, got %v", length) - } - - constraint := pricing.OpenConstraintAt(0) - computed, err := constraint.ComputeInertiaFromPeriod() - Require(t, err) - - // Compare to original legacy inertia with 1% tolerance - var diff uint64 - if computed > inertia { - diff = computed - inertia - } else { - diff = inertia - computed - } - tolerance := inertia / 100 // 1% - if diff > tolerance { - t.Errorf("inertia mismatch (case %d): got %v, want ~%v (diff=%v, tol=%v)", - i, computed, inertia, diff, tolerance) - } - - Require(t, pricing.ClearConstraints()) - } -} diff --git a/arbos/l2pricing/model_test.go b/arbos/l2pricing/model_test.go index af183d65a35..be9164ab8c4 100644 --- a/arbos/l2pricing/model_test.go +++ b/arbos/l2pricing/model_test.go @@ -47,10 +47,10 @@ func TestCompareLegacyPricingModelWithMultiConstraints(t *testing.T) { // Initialize with a single constraint based on the legacy model _ = pricing.setConstraintsFromLegacy() - pricing.UpdatePricingModel(nil, timePassed, false) + pricing.updatePricingModelLegacy(timePassed) legacyPrice, _ := pricing.baseFeeWei.Get() - pricing.UpdatePricingModelMultiConstraints(timePassed) + pricing.updatePricingModelMultiConstraints(timePassed) multiPrice, _ := pricing.baseFeeWei.Get() if timePassed == 0 { diff --git a/nitro-testnode b/nitro-testnode index 15ab86232eb..88aa1f4ee31 160000 --- a/nitro-testnode +++ b/nitro-testnode @@ -1 +1 @@ -Subproject commit 15ab86232eb0b6059f6c69472d31ffe3ebaa39f9 +Subproject commit 88aa1f4ee31182011195ec99992b5ff4d2e2c24d diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 2eeb21c3b96..d6d48d0282e 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -300,11 +300,11 @@ func (con ArbGasInfo) GetGasPricingConstraints(c ctx, evm mech) ([][3]uint64, er constraints := make([][3]uint64, 0, len) for i := range len { constraint := c.State.L2PricingState().OpenConstraintAt(i) - target, err := constraint.Target() + gasTargetPerSecond, err := constraint.Target() if err != nil { return nil, err } - inertia, err := constraint.Inertia() + adjustmentWindowSeconds, err := constraint.Inertia() if err != nil { return nil, err } @@ -314,8 +314,8 @@ func (con ArbGasInfo) GetGasPricingConstraints(c ctx, evm mech) ([][3]uint64, er } constraints = append(constraints, [3]uint64{ - target, - inertia, + gasTargetPerSecond, + adjustmentWindowSeconds, backlog, }) } diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index e6411188b67..b76f8555605 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -467,17 +467,17 @@ func (con ArbOwner) SetGasPricingConstraints(c ctx, evm mech, constraints [][3]u } for _, constraint := range constraints { - target := constraint[0] - inertia := constraint[1] - backlog := constraint[2] + gasTargetPerSecond := constraint[0] + adjustmentWindowSeconds := constraint[1] + startingBacklogValue := constraint[2] - if target == 0 || inertia == 0 { - return fmt.Errorf("invalid constraint with target %d and inertia %d", target, inertia) + if gasTargetPerSecond == 0 || adjustmentWindowSeconds == 0 { + return fmt.Errorf("invalid constraint with target %d and adjustment window %d", gasTargetPerSecond, adjustmentWindowSeconds) } - err := c.State.L2PricingState().AddConstraint(target, inertia, backlog) + err := c.State.L2PricingState().AddConstraint(gasTargetPerSecond, adjustmentWindowSeconds, startingBacklogValue) if err != nil { - return fmt.Errorf("failed to add constraint (target: %d, inertia: %d): %w", target, inertia, err) + return fmt.Errorf("failed to add constraint (target: %d, adjustment window: %d): %w", gasTargetPerSecond, adjustmentWindowSeconds, err) } } return nil diff --git a/precompiles/constraints_test.go b/precompiles/constraints_test.go index cbab7701a47..ecec2307b94 100644 --- a/precompiles/constraints_test.go +++ b/precompiles/constraints_test.go @@ -58,6 +58,24 @@ func TestFailToSetInvalidConstraints(t *testing.T) { require.Error(t, err) } +func TestSetLegacyBacklog(t *testing.T) { + t.Parallel() + + evm, _, callCtx, arbGasInfo, arbOwner := setupResourceConstraintHandles(t) + + backlog, err := arbGasInfo.GetGasBacklog(callCtx, evm) + require.NoError(t, err) + require.Equal(t, uint64(0), backlog) + + newBacklog := uint64(80_000) + err = arbOwner.SetGasBacklog(callCtx, evm, newBacklog) + require.NoError(t, err) + + backlog, err = arbGasInfo.GetGasBacklog(callCtx, evm) + require.NoError(t, err) + require.Equal(t, newBacklog, backlog) +} + func TestConstraintsStorage(t *testing.T) { t.Parallel() @@ -83,9 +101,9 @@ func TestConstraintsStorage(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(30_000_000), firstTarget) - firstPeriod, err := first.Period() + firstInertia, err := first.Inertia() require.NoError(t, err) - require.Equal(t, uint64(1), firstPeriod) + require.Equal(t, uint64(1), firstInertia) firstBacklog, err := first.Backlog() require.NoError(t, err) @@ -95,7 +113,7 @@ func TestConstraintsStorage(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(15_000_000), secondTarget) - secondPeriod, err := second.Period() + secondInertia, err := second.Inertia() require.NoError(t, err) require.Equal(t, uint64(102), secondInertia) @@ -127,10 +145,10 @@ func TestConstraintsStorage(t *testing.T) { first = state.L2PricingState().OpenConstraintAt(0) target, err := first.Target() require.NoError(t, err) - period, err := first.Period() + inertia, err := first.Inertia() require.NoError(t, err) require.Equal(t, uint64(7_000_000), target) - require.Equal(t, uint64(12), period) + require.Equal(t, uint64(12), inertia) result, err = arbGasInfo.GetGasPricingConstraints(callCtx, evm) require.NoError(t, err) diff --git a/system_tests/multi_constraint_pricer_test.go b/system_tests/multi_constraint_pricer_test.go index db17c04ca85..510ebd673e7 100644 --- a/system_tests/multi_constraint_pricer_test.go +++ b/system_tests/multi_constraint_pricer_test.go @@ -5,19 +5,17 @@ package arbtest import ( "context" - "math/big" "testing" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" ) -func TestMultiConstraintPricerPrecompiles(t *testing.T) { +func TestSetAndGetGasPricingConstraints(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -35,8 +33,8 @@ func TestMultiConstraintPricerPrecompiles(t *testing.T) { // Set constraints constraints := [][3]uint64{ - {30_000_000, 1, 800_000}, // short-term - {15_000_000, 86400, 1_600_000}, // long-term + {30_000_000, 102, 800_000}, // short-term + {15_000_000, 600, 1_600_000}, // long-term } tx, err := arbOwner.SetGasPricingConstraints(&auth, constraints) require.NoError(t, err) @@ -47,137 +45,9 @@ func TestMultiConstraintPricerPrecompiles(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(constraints)) require.Equal(t, uint64(30_000_000), constraints[0][0]) - require.Equal(t, uint64(1), constraints[0][1]) + require.Equal(t, uint64(102), constraints[0][1]) require.GreaterOrEqual(t, constraints[0][2], uint64(800_000)) require.Equal(t, uint64(15_000_000), constraints[1][0]) - require.Equal(t, uint64(86400), constraints[1][1]) + require.Equal(t, uint64(600), constraints[1][1]) require.GreaterOrEqual(t, constraints[1][2], uint64(1_600_000)) - - // Should return speed limit derived from long-term constraint - speedLimit, gasPoolMax, _, err := arbGasInfo.GetGasAccountingParams(callOpts) - require.NoError(t, err) - require.Zero(t, big.NewInt(15_000_000).Cmp(speedLimit)) - require.Zero(t, big.NewInt(0).Cmp(gasPoolMax)) - - // Should return the long-term constraint's backlog (the highest-period constraint) - gasBacklog, err := arbGasInfo.GetGasBacklog(callOpts) - require.NoError(t, err) - require.GreaterOrEqual(t, gasBacklog, uint64(1_600_000)) - - // Should return the long-term constraint's computed inertia - pricingInertia, err := arbGasInfo.GetPricingInertia(callOpts) - require.NoError(t, err) - require.GreaterOrEqual(t, pricingInertia, uint64(8790)) // Computed from 86400s period - - // Should return zero backlog tolerance - backlogTolerance, err := arbGasInfo.GetGasBacklogTolerance(callOpts) - require.NoError(t, err) - require.Equal(t, uint64(0), backlogTolerance) - - // Check SetSpeedLimit returns error - _, err = arbOwner.SetSpeedLimit(&auth, 20_000_000) - require.Error(t, err) - - // Check SetL2GasPricingInertia returns error - _, err = arbOwner.SetL2GasPricingInertia(&auth, 200) - require.Error(t, err) - - // Check SetL2GasBacklogTolerance returns error - _, err = arbOwner.SetL2GasBacklogTolerance(&auth, 100) - require.Error(t, err) -} - -func TestMultiConstraintPricerMigration(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - builder := NewNodeBuilder(ctx).DefaultConfig(t, false).WithArbOSVersion(params.ArbosVersion_41) - cleanup := builder.Build(t) - defer cleanup() - - auth := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) - callOpts := &bind.CallOpts{Context: ctx} - - arbOwner, err := precompilesgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) - require.NoError(t, err) - arbGasInfo, err := precompilesgen.NewArbGasInfo(types.ArbGasInfoAddress, builder.L2.Client) - require.NoError(t, err) - arbSys, err := precompilesgen.NewArbSys(types.ArbSysAddress, builder.L2.Client) - require.NoError(t, err) - - // Check version before upgrade - arbosVersion, err := arbSys.ArbOSVersion(callOpts) - require.NoError(t, err) - require.Equal(t, arbosVersion.Uint64()-55, params.ArbosVersion_41) - - // Set legacy parameters - _, err = arbOwner.SetSpeedLimit(&auth, 20_000_000) - require.NoError(t, err) - - _, err = arbOwner.SetL2GasPricingInertia(&auth, 300) - require.NoError(t, err) - - _, err = arbOwner.SetL2GasBacklogTolerance(&auth, 150) - require.NoError(t, err) - - // Check GetGasAccountingParams returns expected speed limit - speedLimit, _, _, err := arbGasInfo.GetGasAccountingParams(callOpts) - require.NoError(t, err) - require.Zero(t, big.NewInt(20_000_000).Cmp(speedLimit)) - - // Check GetPricingInertia returns expected inertia - pricingInertia, err := arbGasInfo.GetPricingInertia(callOpts) - require.NoError(t, err) - require.Equal(t, uint64(300), pricingInertia) - - // Check GetGasBacklogTolerance returns expected tolerance - backlogTolerance, err := arbGasInfo.GetGasBacklogTolerance(callOpts) - require.NoError(t, err) - require.Equal(t, uint64(150), backlogTolerance) - - // Upgrade to ArbOS version 50 - tx, err := arbOwner.ScheduleArbOSUpgrade(&auth, params.ArbosVersion_50, 0) - require.NoError(t, err) - - _, err = builder.L2.EnsureTxSucceeded(tx) - require.NoError(t, err) - - // Send a dummy transaction to trigger the upgrade - var data []byte - for i := range 10 { - for range 100 { - data = append(data, byte(i)) - } - } - tx = builder.L2Info.PrepareTx("Owner", "Owner", builder.L2Info.TransferGas, big.NewInt(1e12), data) - err = builder.L2.Client.SendTransaction(ctx, tx) - require.NoError(t, err) - _, err = builder.L2.EnsureTxSucceeded(tx) - require.NoError(t, err) - - arbosVersion, err = arbSys.ArbOSVersion(callOpts) - require.NoError(t, err) - require.GreaterOrEqual(t, arbosVersion.Uint64()-55, params.ArbosVersion_50) - - // Check SetSpeedLimit returns error after upgrade - _, err = arbOwner.SetSpeedLimit(&auth, 25_000_000) - require.Error(t, err) - - // Check GetPricingInertia returns expected inertia - pricingInertia, err = arbGasInfo.GetPricingInertia(callOpts) - require.NoError(t, err) - require.Equal(t, uint64(300), pricingInertia) - - // Get and check pricing constraints after upgrade - constraints, err := arbGasInfo.GetGasPricingConstraints(callOpts) - require.NoError(t, err) - require.Equal(t, 1, len(constraints)) - require.Equal(t, uint64(20_000_000), constraints[0][0]) - require.Equal(t, uint64(100), constraints[0][1]) - require.GreaterOrEqual(t, constraints[0][2], uint64(0)) - - // Check GetGasAccountingParams after upgrade - speedLimit, _, _, err = arbGasInfo.GetGasAccountingParams(callOpts) - require.NoError(t, err) - require.Equal(t, big.NewInt(20_000_000), speedLimit) } From b96c2ff0aece8579689c999e11f0778b723ced1b Mon Sep 17 00:00:00 2001 From: Mikhail Rogachev Date: Fri, 24 Oct 2025 14:13:56 +0200 Subject: [PATCH 16/16] Final namings fixes --- arbos/l2pricing/l2pricing.go | 34 +++++++++++++++---------------- arbos/l2pricing/l2pricing_test.go | 2 +- arbos/l2pricing/model.go | 6 +++--- precompiles/ArbGasInfo.go | 2 +- precompiles/constraints_test.go | 14 ++++++------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/arbos/l2pricing/l2pricing.go b/arbos/l2pricing/l2pricing.go index b47410fa41a..86fe69b965e 100644 --- a/arbos/l2pricing/l2pricing.go +++ b/arbos/l2pricing/l2pricing.go @@ -13,24 +13,24 @@ import ( const ( gasConstraintTargetOffset uint64 = iota - gasConstraintInertiaOffset + gasConstraintAdjustmentWindowOffset gasConstraintBacklogOffset ) -// GasConstraint tries to keep the gas backlog under the target (per second) for the given inertia factor. +// GasConstraint tries to keep the gas backlog under the target (per second) for the given adjustment window. // Target stands for gas usage per second -// The inertia is the time over which the price will rise by a factor of e if demand is 2x the target +// Adjustment window is the time frame over which the price will rise by a factor of e if demand is 2x the target type GasConstraint struct { - target storage.StorageBackedUint64 - inertia storage.StorageBackedUint64 - backlog storage.StorageBackedUint64 + target storage.StorageBackedUint64 + adjustmentWindow storage.StorageBackedUint64 + backlog storage.StorageBackedUint64 } func OpenGasConstraint(storage *storage.Storage) *GasConstraint { return &GasConstraint{ - target: storage.OpenStorageBackedUint64(gasConstraintTargetOffset), - inertia: storage.OpenStorageBackedUint64(gasConstraintInertiaOffset), - backlog: storage.OpenStorageBackedUint64(gasConstraintBacklogOffset), + target: storage.OpenStorageBackedUint64(gasConstraintTargetOffset), + adjustmentWindow: storage.OpenStorageBackedUint64(gasConstraintAdjustmentWindowOffset), + backlog: storage.OpenStorageBackedUint64(gasConstraintBacklogOffset), } } @@ -38,7 +38,7 @@ func (c *GasConstraint) Clear() error { if err := c.target.Clear(); err != nil { return err } - if err := c.inertia.Clear(); err != nil { + if err := c.adjustmentWindow.Clear(); err != nil { return err } if err := c.backlog.Clear(); err != nil { @@ -51,8 +51,8 @@ func (c *GasConstraint) Target() (uint64, error) { return c.target.Get() } -func (c *GasConstraint) Inertia() (uint64, error) { - return c.inertia.Get() +func (c *GasConstraint) AdjustmentWindow() (uint64, error) { + return c.adjustmentWindow.Get() } func (c *GasConstraint) Backlog() (uint64, error) { @@ -195,7 +195,7 @@ func (ps *L2PricingState) setConstraintsFromLegacy() error { if err != nil { return err } - inertia, err := ps.PricingInertia() + adjustmentWindow, err := ps.PricingInertia() if err != nil { return err } @@ -208,10 +208,10 @@ func (ps *L2PricingState) setConstraintsFromLegacy() error { return err } backlog := arbmath.SaturatingUSub(oldBacklog, arbmath.SaturatingUMul(backlogTolerance, target)) - return ps.AddConstraint(target, inertia, backlog) + return ps.AddConstraint(target, adjustmentWindow, backlog) } -func (ps *L2PricingState) AddConstraint(target uint64, inertia uint64, backlog uint64) error { +func (ps *L2PricingState) AddConstraint(target uint64, adjustmentWindow uint64, backlog uint64) error { subStorage, err := ps.constraints.Push() if err != nil { return fmt.Errorf("failed to push constraint: %w", err) @@ -220,8 +220,8 @@ func (ps *L2PricingState) AddConstraint(target uint64, inertia uint64, backlog u if err := constraint.target.Set(target); err != nil { return fmt.Errorf("failed to set target: %w", err) } - if err := constraint.inertia.Set(inertia); err != nil { - return fmt.Errorf("failed to set inertia: %w", err) + if err := constraint.adjustmentWindow.Set(adjustmentWindow); err != nil { + return fmt.Errorf("failed to set adjustment window: %w", err) } if err := constraint.backlog.Set(backlog); err != nil { return fmt.Errorf("failed to set backlog: %w", err) diff --git a/arbos/l2pricing/l2pricing_test.go b/arbos/l2pricing/l2pricing_test.go index 13798d7762f..731e42d725f 100644 --- a/arbos/l2pricing/l2pricing_test.go +++ b/arbos/l2pricing/l2pricing_test.go @@ -134,7 +134,7 @@ func TestGasConstraints(t *testing.T) { if want := 100*i + 1; target != want { t.Errorf("wrong target: got %v, want %v", target, want) } - inertia, err := constraint.inertia.Get() + inertia, err := constraint.adjustmentWindow.Get() Require(t, err) if want := 100*i + 2; inertia != want { t.Errorf("wrong inertia: got %v, want %v", inertia, want) diff --git a/arbos/l2pricing/model.go b/arbos/l2pricing/model.go index 39f73eb9666..e616b326785 100644 --- a/arbos/l2pricing/model.go +++ b/arbos/l2pricing/model.go @@ -115,17 +115,17 @@ func (ps *L2PricingState) updatePricingModelMultiConstraints(timePassed uint64) constraintsLength, _ := ps.constraints.Length() for i := range constraintsLength { constraint := ps.OpenConstraintAt(i) - target, _ := constraint.target.Get() + target, _ := constraint.Target() // Pay off backlog - backlog, _ := constraint.backlog.Get() + backlog, _ := constraint.Backlog() gas := arbmath.SaturatingCast[int64](arbmath.SaturatingUMul(timePassed, target)) backlog = applyGasDelta(backlog, gas) _ = constraint.backlog.Set(backlog) // Calculate exponent with the formula backlog/divisor if backlog > 0 { - inertia, _ := constraint.inertia.Get() + inertia, _ := constraint.AdjustmentWindow() divisor := arbmath.SaturatingCastToBips(arbmath.SaturatingUMul(inertia, target)) exponent := arbmath.NaturalToBips(arbmath.SaturatingCast[int64](backlog)) / divisor totalExponent = arbmath.SaturatingBipsAdd(totalExponent, exponent) diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index d6d48d0282e..323f1b48dcb 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -304,7 +304,7 @@ func (con ArbGasInfo) GetGasPricingConstraints(c ctx, evm mech) ([][3]uint64, er if err != nil { return nil, err } - adjustmentWindowSeconds, err := constraint.Inertia() + adjustmentWindowSeconds, err := constraint.AdjustmentWindow() if err != nil { return nil, err } diff --git a/precompiles/constraints_test.go b/precompiles/constraints_test.go index ecec2307b94..6d0a0dcf76f 100644 --- a/precompiles/constraints_test.go +++ b/precompiles/constraints_test.go @@ -53,7 +53,7 @@ func TestFailToSetInvalidConstraints(t *testing.T) { err := arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{0, 17, 1000}}) require.Error(t, err) - // Zero inertia + // Zero adjustment window err = arbOwner.SetGasPricingConstraints(callCtx, evm, [][3]uint64{{10_000_000, 0, 0}}) require.Error(t, err) } @@ -101,9 +101,9 @@ func TestConstraintsStorage(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(30_000_000), firstTarget) - firstInertia, err := first.Inertia() + firstWindow, err := first.AdjustmentWindow() require.NoError(t, err) - require.Equal(t, uint64(1), firstInertia) + require.Equal(t, uint64(1), firstWindow) firstBacklog, err := first.Backlog() require.NoError(t, err) @@ -113,9 +113,9 @@ func TestConstraintsStorage(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(15_000_000), secondTarget) - secondInertia, err := second.Inertia() + secondWindow, err := second.AdjustmentWindow() require.NoError(t, err) - require.Equal(t, uint64(102), secondInertia) + require.Equal(t, uint64(102), secondWindow) secondBacklog, err := second.Backlog() require.NoError(t, err) @@ -145,10 +145,10 @@ func TestConstraintsStorage(t *testing.T) { first = state.L2PricingState().OpenConstraintAt(0) target, err := first.Target() require.NoError(t, err) - inertia, err := first.Inertia() + window, err := first.AdjustmentWindow() require.NoError(t, err) require.Equal(t, uint64(7_000_000), target) - require.Equal(t, uint64(12), inertia) + require.Equal(t, uint64(12), window) result, err = arbGasInfo.GetGasPricingConstraints(callCtx, evm) require.NoError(t, err)