Skip to content

Commit c1c7363

Browse files
feat(arbos60): add multi-gas constraints pricing model
Add ArbOS 60 multi-gas constraints with per-resource weights: - MultiGasConstraint: storage-backed constraint with weighted resources - MultiGasFees: per-resource base fee storage (next-block/current-block) - L2PricingState: constraint management, exponent calculation, pricing updates - GasModel enum for routing: Legacy, SingleGasConstraints, MultiGasConstraints - Input validation and bounds checking throughout # Conflicts: # src/Nethermind
1 parent 51503d6 commit c1c7363

13 files changed

Lines changed: 2280 additions & 124 deletions

File tree

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
// SPDX-FileCopyrightText: https://github.com/NethermindEth/nethermind-arbitrum/blob/main/LICENSE.md
3+
4+
using FluentAssertions;
5+
using Nethermind.Arbitrum.Arbos;
6+
using Nethermind.Arbitrum.Arbos.Storage;
7+
using Nethermind.Arbitrum.Evm;
8+
using Nethermind.Arbitrum.Test.Infrastructure;
9+
using Nethermind.Core.Test;
10+
using Nethermind.Evm.State;
11+
12+
namespace Nethermind.Arbitrum.Test.Arbos.L2Pricing;
13+
14+
[TestFixture]
15+
public class MultiGasConstraintTests
16+
{
17+
[Test]
18+
public void SetAndGet_Target_ReturnsCorrectValue()
19+
{
20+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
21+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
22+
_ = ArbOSInitialization.Create(worldState);
23+
24+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
25+
.WithArbosState()
26+
.WithArbosVersion(ArbosVersion.Sixty)
27+
.WithReleaseSpec();
28+
29+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
30+
31+
// Add a multi-gas constraint
32+
var weights = new Dictionary<ResourceKind, ulong>
33+
{
34+
{ ResourceKind.Computation, 100 },
35+
{ ResourceKind.StorageAccess, 200 }
36+
};
37+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 5_000_000, weights);
38+
39+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
40+
41+
constraint.Target.Should().Be(1_000_000);
42+
constraint.AdjustmentWindow.Should().Be(60);
43+
constraint.Backlog.Should().Be(5_000_000);
44+
constraint.MaxWeight.Should().Be(200); // Max of 100 and 200
45+
constraint.GetResourceWeight(ResourceKind.Computation).Should().Be(100);
46+
constraint.GetResourceWeight(ResourceKind.StorageAccess).Should().Be(200);
47+
constraint.GetResourceWeight(ResourceKind.Unknown).Should().Be(0); // Not set
48+
}
49+
50+
[Test]
51+
public void SetResourceWeights_InvalidKind_ThrowsError()
52+
{
53+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
54+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
55+
_ = ArbOSInitialization.Create(worldState);
56+
57+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
58+
.WithArbosState()
59+
.WithArbosVersion(ArbosVersion.Sixty)
60+
.WithReleaseSpec();
61+
62+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
63+
64+
// Add a constraint first
65+
var validWeights = new Dictionary<ResourceKind, ulong>
66+
{
67+
{ ResourceKind.Computation, 100 }
68+
};
69+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 0, validWeights);
70+
71+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
72+
73+
// invalid resource kind (cast beyond valid range)
74+
var invalidWeights = new Dictionary<ResourceKind, ulong>
75+
{
76+
{ (ResourceKind)255, 100 } // Invalid
77+
};
78+
79+
Action act = () => constraint.SetResourceWeights(invalidWeights);
80+
act.Should().Throw<ArgumentOutOfRangeException>();
81+
}
82+
83+
[Test]
84+
public void GrowBacklog_MultipleResources_AggregatesWeighted()
85+
{
86+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
87+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
88+
_ = ArbOSInitialization.Create(worldState);
89+
90+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
91+
.WithArbosState()
92+
.WithArbosVersion(ArbosVersion.Sixty)
93+
.WithReleaseSpec();
94+
95+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
96+
97+
// Weights: Computation=2, StorageAccess=3
98+
var weights = new Dictionary<ResourceKind, ulong>
99+
{
100+
{ ResourceKind.Computation, 2 },
101+
{ ResourceKind.StorageAccess, 3 }
102+
};
103+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 0, weights);
104+
105+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
106+
constraint.Backlog.Should().Be(0);
107+
108+
// Create MultiGas with: Computation=100, StorageAccess=200
109+
MultiGas gas = default;
110+
gas.Increment(ResourceKind.Computation, 100);
111+
gas.Increment(ResourceKind.StorageAccess, 200);
112+
113+
constraint.GrowBacklog(gas);
114+
115+
// Expected: 100*2 + 200*3 = 200 + 600 = 800
116+
constraint.Backlog.Should().Be(800);
117+
}
118+
119+
[Test]
120+
public void GrowBacklog_MultipleCalls_AccumulatesBacklog()
121+
{
122+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
123+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
124+
_ = ArbOSInitialization.Create(worldState);
125+
126+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
127+
.WithArbosState()
128+
.WithArbosVersion(ArbosVersion.Sixty)
129+
.WithReleaseSpec();
130+
131+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
132+
133+
var weights = new Dictionary<ResourceKind, ulong>
134+
{
135+
{ ResourceKind.Computation, 1 }
136+
};
137+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 100, weights);
138+
139+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
140+
constraint.Backlog.Should().Be(100);
141+
142+
MultiGas gas = default;
143+
gas.Increment(ResourceKind.Computation, 50);
144+
145+
// grow multiple times
146+
constraint.GrowBacklog(gas);
147+
constraint.Backlog.Should().Be(150);
148+
149+
constraint.GrowBacklog(gas);
150+
constraint.Backlog.Should().Be(200);
151+
152+
constraint.GrowBacklog(gas);
153+
constraint.Backlog.Should().Be(250);
154+
}
155+
156+
[Test]
157+
public void ShrinkBacklog_Underflow_ClampsToZero()
158+
{
159+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
160+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
161+
_ = ArbOSInitialization.Create(worldState);
162+
163+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
164+
.WithArbosState()
165+
.WithArbosVersion(ArbosVersion.Sixty)
166+
.WithReleaseSpec();
167+
168+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
169+
170+
var weights = new Dictionary<ResourceKind, ulong>
171+
{
172+
{ ResourceKind.Computation, 1 }
173+
};
174+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 100, weights);
175+
176+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
177+
constraint.Backlog.Should().Be(100);
178+
179+
// Try to shrink by more than available
180+
MultiGas gas = default;
181+
gas.Increment(ResourceKind.Computation, 500);
182+
183+
constraint.ShrinkBacklog(gas);
184+
185+
// should clamp to 0, not underflow
186+
constraint.Backlog.Should().Be(0);
187+
}
188+
189+
[Test]
190+
public void GetUsedResources_WithMixedWeights_ReturnsOnlyNonZeroWeights()
191+
{
192+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
193+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
194+
_ = ArbOSInitialization.Create(worldState);
195+
196+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
197+
.WithArbosState()
198+
.WithArbosVersion(ArbosVersion.Sixty)
199+
.WithReleaseSpec();
200+
201+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
202+
203+
var weights = new Dictionary<ResourceKind, ulong>
204+
{
205+
{ ResourceKind.Computation, 10 },
206+
{ ResourceKind.StorageGrowth, 20 },
207+
{ ResourceKind.HistoryGrowth, 30 }
208+
};
209+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 0, weights);
210+
211+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
212+
213+
ResourceKind[] usedResources = constraint.GetUsedResources();
214+
215+
usedResources.Should().HaveCount(3);
216+
usedResources.Should().Contain(ResourceKind.Computation);
217+
usedResources.Should().Contain(ResourceKind.StorageGrowth);
218+
usedResources.Should().Contain(ResourceKind.HistoryGrowth);
219+
usedResources.Should().NotContain(ResourceKind.Unknown);
220+
usedResources.Should().NotContain(ResourceKind.StorageAccess);
221+
}
222+
223+
[Test]
224+
public void GetResourcesWithWeights_WithSetWeights_ReturnsCorrectMapping()
225+
{
226+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
227+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
228+
_ = ArbOSInitialization.Create(worldState);
229+
230+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
231+
.WithArbosState()
232+
.WithArbosVersion(ArbosVersion.Sixty)
233+
.WithReleaseSpec();
234+
235+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
236+
237+
var weights = new Dictionary<ResourceKind, ulong>
238+
{
239+
{ ResourceKind.Computation, 100 },
240+
{ ResourceKind.StorageAccess, 200 }
241+
};
242+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 0, weights);
243+
244+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
245+
246+
Dictionary<ResourceKind, ulong> result = constraint.GetResourcesWithWeights();
247+
248+
result.Should().HaveCount(2);
249+
result[ResourceKind.Computation].Should().Be(100);
250+
result[ResourceKind.StorageAccess].Should().Be(200);
251+
}
252+
253+
[Test]
254+
public void Clear_WhenCalled_RemovesAllData()
255+
{
256+
IWorldState worldState = TestWorldStateFactory.CreateForTest();
257+
using IDisposable worldStateDisposer = worldState.BeginScope(IWorldState.PreGenesis);
258+
_ = ArbOSInitialization.Create(worldState);
259+
260+
PrecompileTestContextBuilder context = new PrecompileTestContextBuilder(worldState, ulong.MaxValue)
261+
.WithArbosState()
262+
.WithArbosVersion(ArbosVersion.Sixty)
263+
.WithReleaseSpec();
264+
265+
L2PricingState l2Pricing = context.ArbosState.L2PricingState;
266+
267+
var weights = new Dictionary<ResourceKind, ulong>
268+
{
269+
{ ResourceKind.Computation, 100 },
270+
{ ResourceKind.StorageAccess, 200 }
271+
};
272+
l2Pricing.AddMultiGasConstraint(1_000_000, 60, 5_000_000, weights);
273+
274+
MultiGasConstraint constraint = l2Pricing.OpenMultiGasConstraintAt(0);
275+
276+
constraint.Clear();
277+
278+
constraint.Target.Should().Be(0);
279+
constraint.AdjustmentWindow.Should().Be(0);
280+
constraint.Backlog.Should().Be(0);
281+
constraint.MaxWeight.Should().Be(0);
282+
constraint.GetResourceWeight(ResourceKind.Computation).Should().Be(0);
283+
constraint.GetResourceWeight(ResourceKind.StorageAccess).Should().Be(0);
284+
}
285+
}

0 commit comments

Comments
 (0)