Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions arbos/l2pricing/l2pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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
Expand Down
2 changes: 1 addition & 1 deletion contracts-local/src/precompiles
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About GetGasAccountingParams being deprecated, I think this makes sense, but we should also create another method that returns just the block gas limit. This is not used directly by the L2 gas pricing model, but it is used in other places, so it is a good opportunity to clean this up. See #3545

32 changes: 32 additions & 0 deletions precompiles/ArbGasInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,35 @@ func (con ArbGasInfo) GetL1PricingUnitsSinceUpdate(c ctx, evm mech) (uint64, err
func (con ArbGasInfo) GetLastL1PricingSurplus(c ctx, evm mech) (*big.Int, error) {
return c.State.L1PricingState().LastSurplus()
}

// 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()
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
}
27 changes: 27 additions & 0 deletions precompiles/ArbOwner.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,30 @@ func (con ArbOwner) SetChainConfig(c ctx, evm mech, serializedChainConfig []byte
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.
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
}
167 changes: 167 additions & 0 deletions precompiles/constraints_test.go
Original file line number Diff line number Diff line change
@@ -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])
}
2 changes: 1 addition & 1 deletion precompiles/precompile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: 99,
0: 101,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add those two precompiles to ArbosVersion_50. See https://github.com/OffchainLabs/nitro/blob/708fcd36ca/precompiles/precompile.go#L537

params.ArbosVersion_5: 3,
params.ArbosVersion_10: 2,
params.ArbosVersion_11: 4,
Expand Down
Loading