Skip to content
This repository was archived by the owner on Jun 9, 2026. It is now read-only.

Commit 9b4178f

Browse files
ceyonurARR4N
andauthored
feat: Make minPrice and excessScalingFactor Configurable (#80)
This PR makes minPrice and excessScalingFactor Configurable, also adds a static pricing mode. These parameters will be required by ACP-224, which intends to set these via a system contract. Closes: #77 --------- Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com> Co-authored-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com>
1 parent b728488 commit 9b4178f

103 files changed

Lines changed: 1757 additions & 130 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

blocks/block_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func newChain(tb testing.TB, db ethdb.Database, xdb saedb.ExecutionResults, star
7171
// The target and excess are irrelevant for the purposes of
7272
// [newChain], and non-zero sub-second time for genesis is
7373
// unnecessary.
74-
h := &hookstest.Stub{Target: 1}
74+
h := hookstest.NewStub(1)
7575
require.NoError(tb, b.MarkSynchronous(h, db, xdb, 0), "MarkSynchronous()")
7676
}
7777

blocks/blockstest/blocks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func NewGenesis(tb testing.TB, db ethdb.Database, xdb saedb.ExecutionResults, co
133133
require.NoErrorf(tb, tdb.Commit(hash, true), "%T.Commit(core.SetupGenesisBlock(...))", tdb)
134134

135135
b := NewBlock(tb, gen.ToBlock(), nil, nil)
136-
h := &hookstest.Stub{Target: conf.gasTarget}
136+
h := hookstest.NewStub(conf.gasTarget)
137137
require.NoErrorf(tb, b.MarkSynchronous(h, db, xdb, conf.gasExcess), "%T.MarkSynchronous()", b)
138138
return b
139139
}

blocks/execution_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/ava-labs/avalanchego/utils/logging"
15+
"github.com/ava-labs/avalanchego/vms/components/gas"
1516
"github.com/ava-labs/libevm/common"
1617
"github.com/ava-labs/libevm/core/rawdb"
1718
"github.com/ava-labs/libevm/core/types"
@@ -24,6 +25,7 @@ import (
2425

2526
"github.com/ava-labs/strevm/cmputils"
2627
"github.com/ava-labs/strevm/gastime"
28+
"github.com/ava-labs/strevm/hook"
2729
"github.com/ava-labs/strevm/saedb"
2830
"github.com/ava-labs/strevm/saetest"
2931
)
@@ -61,7 +63,8 @@ func TestMarkExecuted(t *testing.T) {
6163
xdb := saetest.NewExecutionResultsDB()
6264

6365
settles := newBlock(t, newEthBlock(0, 0, nil), nil, nil)
64-
settles.markExecutedForTests(t, db, xdb, gastime.New(time.Unix(0, 0), 1, 0))
66+
tm := mustNewGasTime(t, time.Unix(0, 0), 1, 0, gastime.DefaultGasPriceConfig())
67+
settles.markExecutedForTests(t, db, xdb, tm)
6568
b := newBlock(t, ethB, nil, settles)
6669

6770
t.Run("before_MarkExecuted", func(t *testing.T) {
@@ -94,7 +97,7 @@ func TestMarkExecuted(t *testing.T) {
9497
}
9598
})
9699

97-
gasTime := gastime.New(time.Unix(42, 0), 1e6, 42)
100+
gasTime := mustNewGasTime(t, time.Unix(42, 0), 1e6, 42, gastime.DefaultGasPriceConfig())
98101
wallTime := time.Unix(42, 100)
99102
stateRoot := common.Hash{'s', 't', 'a', 't', 'e'}
100103
baseFee := uint256.NewInt(314159)
@@ -191,3 +194,10 @@ func TestMarkExecuted(t *testing.T) {
191194
type selfAsHasher common.Hash
192195

193196
func (h selfAsHasher) Hash() common.Hash { return common.Hash(h) }
197+
198+
func mustNewGasTime(tb testing.TB, at time.Time, target, excess gas.Gas, gasPriceConfig hook.GasPriceConfig) *gastime.Time {
199+
tb.Helper()
200+
tm, err := gastime.New(at, target, excess, gasPriceConfig)
201+
require.NoError(tb, err, "gastime.New()")
202+
return tm
203+
}

blocks/settlement.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,20 @@ func (b *Block) MarkSynchronous(hooks hook.Points, db ethdb.Database, xdb saedb.
9191
// itself. As the only reason to pass receipts here is for later settlement
9292
// in another block, there is no need to pass anything meaningful as it
9393
// would also require them to be received as an argument to MarkSynchronous.
94+
target, cfg := hooks.GasConfigAfter(b.Header())
95+
execTime, err := gastime.New(
96+
PreciseTime(hooks, b.Header()),
97+
// Target, excess, and config _after_ are a requirement of
98+
// [Block.MarkExecuted].
99+
target,
100+
excessAfter,
101+
cfg,
102+
)
103+
if err != nil {
104+
return err
105+
}
94106
e := &executionResults{
95-
byGas: *gastime.New(
96-
PreciseTime(hooks, b.Header()),
97-
hooks.GasTargetAfter(b.Header()), // target _after_ is a requirement of [Block.MarkExecuted]
98-
excessAfter,
99-
),
107+
byGas: *execTime.Clone(),
100108
receiptRoot: ethB.ReceiptHash(),
101109
stateRootPost: ethB.Root(),
102110
}

blocks/settlement_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ func TestSettlementInvariants(t *testing.T) {
5959
db := rawdb.NewMemoryDatabase()
6060
xdb := saetest.NewExecutionResultsDB()
6161
for _, b := range []*Block{b, parent, lastSettled} {
62-
b.markExecutedForTests(t, db, xdb, gastime.New(preciseTime(b.Header(), 0), 1, 0))
62+
tm := mustNewGasTime(t, preciseTime(b.Header(), 0), 1, 0, gastime.DefaultGasPriceConfig())
63+
b.markExecutedForTests(t, db, xdb, tm)
6364
}
6465

6566
t.Run("before_MarkSettled", func(t *testing.T) {
@@ -226,7 +227,7 @@ func TestLastToSettleAt(t *testing.T) {
226227
}
227228
})
228229

229-
tm := gastime.New(time.Unix(0, 0), 5 /*target*/, 0)
230+
tm := mustNewGasTime(t, time.Unix(0, 0), 5 /*target*/, 0, gastime.DefaultGasPriceConfig())
230231
require.Equal(t, gas.Gas(10), tm.Rate())
231232

232233
requireTime := func(t *testing.T, sec uint64, numerator gas.Gas) {
@@ -365,7 +366,7 @@ func TestLastToSettleAt(t *testing.T) {
365366
for _, tt := range tests {
366367
t.Run(tt.name, func(t *testing.T) {
367368
settleAt := time.Unix(int64(tt.settleAt), 0) //nolint:gosec // Hard-coded, non-overflowing values
368-
got, gotOK, err := LastToSettleAt(&hookstest.Stub{}, settleAt, tt.parent)
369+
got, gotOK, err := LastToSettleAt(hookstest.NewStub(0), settleAt, tt.parent)
369370
if err != nil || gotOK != tt.wantOK {
370371
t.Fatalf("LastToSettleAt(%d, [parent height %d]) got (_, %t, %v); want (_, %t, nil)", tt.settleAt, tt.parent.Height(), gotOK, err, tt.wantOK)
371372
}

blocks/time.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ func preciseTime(hdr *types.Header, subSec time.Duration) time.Time { //nolint:s
3131
// GasTime is the gas equivalent of [PreciseTime], deriving the gas rate from
3232
// the parent header and the hooks.
3333
func GasTime(hooks hook.Points, hdr, parent *types.Header) *proxytime.Time[gas.Gas] {
34-
rate := gastime.SafeRateOfTarget(
35-
hooks.GasTargetAfter(parent),
36-
)
34+
target, _ := hooks.GasConfigAfter(parent)
35+
rate := gastime.SafeRateOfTarget(target)
3736
tm := proxytime.New(hdr.Time, rate)
3837
tm.Tick(gastime.SubSecond(hooks, hdr, rate))
3938
return tm

blocks/time_test.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,9 @@ func TestGasTime(t *testing.T) {
3232
)
3333
rate := gastime.SafeRateOfTarget(target)
3434

35-
hooks := &hookstest.Stub{
36-
Now: func() time.Time {
37-
return time.Unix(unix, nanos)
38-
},
39-
Target: target,
40-
}
35+
hooks := hookstest.NewStub(target, hookstest.WithNow(func() time.Time {
36+
return time.Unix(unix, nanos)
37+
}))
4138
parent := &types.Header{
4239
Number: big.NewInt(1),
4340
}
@@ -69,12 +66,9 @@ func FuzzTimeExtraction(f *testing.F) {
6966
t.Skip("Zero target")
7067
}
7168

72-
hooks := &hookstest.Stub{
73-
Now: func() time.Time {
74-
return time.Unix(unix, subSec)
75-
},
76-
Target: gas.Gas(target),
77-
}
69+
hooks := hookstest.NewStub(gas.Gas(target), hookstest.WithNow(func() time.Time {
70+
return time.Unix(unix, subSec)
71+
}))
7872
parent := &types.Header{
7973
Number: big.NewInt(1),
8074
}

gasprice/estimator_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,10 @@ func TestFeeHistory(t *testing.T) {
291291
cfg := DefaultConfig()
292292
cfg.HistoryMaxBlocksFromHead = 1
293293
cfg.HistoryMaxBlocks = 2
294+
gt, err := gastime.New(time.Now(), 1, math.MaxUint64, gastime.DefaultGasPriceConfig())
295+
require.NoError(t, err)
294296
bounds := &blocks.WorstCaseBounds{
295-
LatestEndTime: gastime.New(time.Now(), 1, math.MaxUint64),
297+
LatestEndTime: gt,
296298
}
297299
type (
298300
blockSpec []*types.Transaction

gastime/acp176.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package gastime
55

66
import (
77
"fmt"
8+
"math"
89

910
"github.com/ava-labs/avalanchego/vms/components/gas"
1011
"github.com/ava-labs/libevm/core/types"
@@ -21,13 +22,62 @@ func (tm *Time) BeforeBlock(hooks hook.Points, h *types.Header) {
2122
)
2223
}
2324

24-
// AfterBlock is intended to be called after processing a block, with the target
25-
// sourced from [hook.Points] and [types.Header].
25+
// AfterBlock is intended to be called after processing a block, with the
26+
// target and gas configuration sourced from [hook.Points] and [types.Header].
2627
func (tm *Time) AfterBlock(used gas.Gas, hooks hook.Points, h *types.Header) error {
2728
tm.Tick(used)
28-
target := hooks.GasTargetAfter(h)
29+
target, hookCfg := hooks.GasConfigAfter(h)
30+
// Although [Time.SetTarget] scales the excess by the same factor as the
31+
// change in target, it rounds when necessary, which might alter the price
32+
// by a negligible amount. We therefore take a price snapshot beforehand
33+
// otherwise we'd call [Time.findExcessForPrice] with a different value,
34+
// which makes it extremely hard to test.
35+
p := tm.Price()
2936
if err := tm.SetTarget(target); err != nil {
3037
return fmt.Errorf("%T.SetTarget() after block: %w", tm, err)
3138
}
39+
40+
cfg, err := newConfig(hookCfg)
41+
if err != nil {
42+
return fmt.Errorf("%T.newConfig() after block: %w", tm, err)
43+
}
44+
if cfg.equals(tm.config) {
45+
return nil
46+
}
47+
tm.config = cfg
48+
tm.excess = tm.findExcessForPrice(p)
49+
3250
return nil
3351
}
52+
53+
// findExcessForPrice uses binary search over uint64 to find the smallest excess
54+
// value that produces targetPrice with the current [config]. This maintains
55+
// price continuity under a change in [config], with the following scenarios:
56+
//
57+
// - K changes (via TargetToExcessScaling): Scale excess to maintain current price
58+
// - StaticPricing is true: Set excess to 0, enabling fixed price mode
59+
// - M decreases: Scale excess to maintain current price
60+
// - M increases AND current price >= new M: Scale excess to maintain current price
61+
// - M increases AND current price < new M: Price bumps to new M (excess becomes 0)
62+
func (tm *Time) findExcessForPrice(targetPrice gas.Price) gas.Gas {
63+
// We return 0 in case targetPrice < minPrice because we should at least maintain the minimum price
64+
// by setting the excess to 0. ( P = M * e^(0 / K) = M )
65+
// Note: Even though we return 0 for excess it won't avoid accumulating excess in the long run.
66+
if targetPrice <= tm.config.minPrice || tm.config.staticPricing {
67+
return 0
68+
}
69+
70+
k := tm.excessScalingFactor()
71+
72+
// The price function is monotonic non-decreasing so binary search is appropriate.
73+
lo, hi := gas.Gas(0), gas.Gas(math.MaxUint64)
74+
for lo < hi {
75+
mid := lo + (hi-lo)/2
76+
if gas.CalculatePrice(tm.config.minPrice, mid, k) >= targetPrice {
77+
hi = mid
78+
} else {
79+
lo = mid + 1
80+
}
81+
}
82+
return lo
83+
}

0 commit comments

Comments
 (0)