From b20dfd92de7b262b0f16e7c3027bee67085aeb48 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 18 Sep 2025 01:00:46 +0300 Subject: [PATCH 01/15] refactor common parts from acp-176 and acp-226 --- vms/evm/{ => upgrades}/acp176/acp176.go | 66 ++++++------------ vms/evm/{ => upgrades}/acp176/acp176_test.go | 0 vms/evm/{ => upgrades}/acp226/acp226.go | 38 ++++------- vms/evm/{ => upgrades}/acp226/acp226_test.go | 0 vms/evm/upgrades/common/target_excess.go | 72 ++++++++++++++++++++ 5 files changed, 104 insertions(+), 72 deletions(-) rename vms/evm/{ => upgrades}/acp176/acp176.go (74%) rename vms/evm/{ => upgrades}/acp176/acp176_test.go (100%) rename vms/evm/{ => upgrades}/acp226/acp226.go (66%) rename vms/evm/{ => upgrades}/acp226/acp226_test.go (100%) create mode 100644 vms/evm/upgrades/common/target_excess.go diff --git a/vms/evm/acp176/acp176.go b/vms/evm/upgrades/acp176/acp176.go similarity index 74% rename from vms/evm/acp176/acp176.go rename to vms/evm/upgrades/acp176/acp176.go index 3b226cc58ab3..b2f0b984e726 100644 --- a/vms/evm/acp176/acp176.go +++ b/vms/evm/upgrades/acp176/acp176.go @@ -11,14 +11,12 @@ import ( "fmt" "math" "math/big" - "sort" "github.com/holiman/uint256" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/gas" - - safemath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/vms/evm/upgrades/common" ) const ( @@ -41,7 +39,16 @@ const ( maxTargetExcess = 1_024_950_627 // TargetConversion * ln(MaxUint64 / MinTargetPerSecond) + 1 ) -var ErrStateInsufficientLength = errors.New("insufficient length for fee state") +var ( + ErrStateInsufficientLength = errors.New("insufficient length for fee state") + + acp176Params = common.TargetExcessParams{ + MinTarget: MinTargetPerSecond, + TargetConversion: TargetConversion, + MaxExcessDiff: MaxTargetExcessDiff, + MaxExcess: maxTargetExcess, + } +) // State represents the current state of the gas pricing and constraints. type State struct { @@ -74,17 +81,13 @@ func ParseState(bytes []byte) (State, error) { // // Target = MinTargetPerSecond * e^(TargetExcess / TargetConversion) func (s *State) Target() gas.Gas { - return gas.Gas(gas.CalculatePrice( - MinTargetPerSecond, - s.TargetExcess, - TargetConversion, - )) + return gas.Gas(acp176Params.CalculateTarget(uint64(s.TargetExcess))) } // MaxCapacity returns the maximum possible accrued gas capacity, `C`. func (s *State) MaxCapacity() gas.Gas { targetPerSecond := s.Target() - return mulWithUpperBound(targetPerSecond, TargetToMaxCapacity) + return gas.Gas(common.MulWithUpperBound(uint64(targetPerSecond), TargetToMaxCapacity)) } // GasPrice returns the current required fee per gas. @@ -92,16 +95,16 @@ func (s *State) MaxCapacity() gas.Gas { // GasPrice = MinGasPrice * e^(Excess / (Target() * TargetToPriceUpdateConversion)) func (s *State) GasPrice() gas.Price { targetPerSecond := s.Target() - priceUpdateConversion := mulWithUpperBound(targetPerSecond, TargetToPriceUpdateConversion) // K - return gas.CalculatePrice(MinGasPrice, s.Gas.Excess, priceUpdateConversion) + priceUpdateConversion := common.MulWithUpperBound(uint64(targetPerSecond), TargetToPriceUpdateConversion) // K + return gas.CalculatePrice(MinGasPrice, s.Gas.Excess, gas.Gas(priceUpdateConversion)) } // AdvanceTime increases the gas capacity and decreases the gas excess based on // the elapsed seconds. func (s *State) AdvanceTime(seconds uint64) { targetPerSecond := s.Target() - maxPerSecond := mulWithUpperBound(targetPerSecond, TargetToMax) // R - maxCapacity := mulWithUpperBound(maxPerSecond, TimeToFillCapacity) // C + maxPerSecond := gas.Gas(common.MulWithUpperBound(uint64(targetPerSecond), TargetToMax)) // R + maxCapacity := gas.Gas(common.MulWithUpperBound(uint64(maxPerSecond), TimeToFillCapacity)) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerSecond, @@ -145,7 +148,7 @@ func (s *State) ConsumeGas( // desiredTargetExcess without exceeding the maximum targetExcess change. func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { previousTargetPerSecond := s.Target() - s.TargetExcess = targetExcess(s.TargetExcess, desiredTargetExcess) + s.TargetExcess = gas.Gas(acp176Params.TargetExcess(uint64(s.TargetExcess), uint64(desiredTargetExcess))) newTargetPerSecond := s.Target() s.Gas.Excess = scaleExcess( s.Gas.Excess, @@ -154,7 +157,7 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { ) // Ensure the gas capacity does not exceed the maximum capacity. - newMaxCapacity := mulWithUpperBound(newTargetPerSecond, TargetToMaxCapacity) // C + newMaxCapacity := gas.Gas(common.MulWithUpperBound(uint64(newTargetPerSecond), TargetToMaxCapacity)) // C s.Gas.Capacity = min(s.Gas.Capacity, newMaxCapacity) } @@ -170,26 +173,7 @@ func (s *State) Bytes() []byte { // DesiredTargetExcess calculates the optimal desiredTargetExcess given the // desired target. func DesiredTargetExcess(desiredTarget gas.Gas) gas.Gas { - // This could be solved directly by calculating D * ln(desiredTarget / P) - // using floating point math. However, it introduces inaccuracies. So, we - // use a binary search to find the closest integer solution. - return gas.Gas(sort.Search(maxTargetExcess, func(targetExcessGuess int) bool { - state := State{ - TargetExcess: gas.Gas(targetExcessGuess), - } - return state.Target() >= desiredTarget - })) -} - -// targetExcess calculates the optimal new targetExcess for a block proposer to -// include given the current and desired excess values. -func targetExcess(excess, desired gas.Gas) gas.Gas { - change := safemath.AbsDiff(excess, desired) - change = min(change, MaxTargetExcessDiff) - if excess < desired { - return excess + change - } - return excess - change + return gas.Gas(acp176Params.DesiredTargetExcess(uint64(desiredTarget))) } // scaleExcess scales the excess during gas target modifications to keep the @@ -213,13 +197,3 @@ func scaleExcess( } return gas.Gas(bigExcess.Uint64()) } - -// mulWithUpperBound multiplies two numbers and returns the result. If the -// result overflows, it returns [math.MaxUint64]. -func mulWithUpperBound(a, b gas.Gas) gas.Gas { - product, err := safemath.Mul(a, b) - if err != nil { - return math.MaxUint64 - } - return product -} diff --git a/vms/evm/acp176/acp176_test.go b/vms/evm/upgrades/acp176/acp176_test.go similarity index 100% rename from vms/evm/acp176/acp176_test.go rename to vms/evm/upgrades/acp176/acp176_test.go diff --git a/vms/evm/acp226/acp226.go b/vms/evm/upgrades/acp226/acp226.go similarity index 66% rename from vms/evm/acp226/acp226.go rename to vms/evm/upgrades/acp226/acp226.go index 22d4e08632c4..a05c16f511a9 100644 --- a/vms/evm/acp226/acp226.go +++ b/vms/evm/upgrades/acp226/acp226.go @@ -6,11 +6,7 @@ package acp226 import ( - "sort" - - "github.com/ava-labs/avalanchego/vms/components/gas" - - safemath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/vms/evm/upgrades/common" ) const ( @@ -24,6 +20,14 @@ const ( maxDelayExcess = 46_516_320 // ConversionRate * ln(MaxUint64 / MinDelayMilliseconds) + 1 ) +// acp226Params is the params used for the acp226 upgrade. +var acp226Params = common.TargetExcessParams{ + MinTarget: MinDelayMilliseconds, + TargetConversion: ConversionRate, + MaxExcessDiff: MaxDelayExcessDiff, + MaxExcess: maxDelayExcess, +} + // DelayExcess represents the excess for delay calculation in the dynamic minimum block delay mechanism. type DelayExcess uint64 @@ -31,17 +35,13 @@ type DelayExcess uint64 // // Delay = MinDelayMilliseconds * e^(DelayExcess / ConversionRate) func (t DelayExcess) Delay() uint64 { - return uint64(gas.CalculatePrice( - MinDelayMilliseconds, - gas.Gas(t), - ConversionRate, - )) + return acp226Params.CalculateTarget(uint64(t)) } // UpdateDelayExcess updates the DelayExcess to be as close as possible to the // desiredDelayExcess without exceeding the maximum DelayExcess change. func (t *DelayExcess) UpdateDelayExcess(desiredDelayExcess uint64) { - *t = DelayExcess(calculateDelayExcess(uint64(*t), desiredDelayExcess)) + *t = DelayExcess(acp226Params.TargetExcess(uint64(*t), desiredDelayExcess)) } // DesiredDelayExcess calculates the optimal delay excess given the desired @@ -50,19 +50,5 @@ func DesiredDelayExcess(desiredDelay uint64) uint64 { // This could be solved directly by calculating D * ln(desired / M) // using floating point math. However, it introduces inaccuracies. So, we // use a binary search to find the closest integer solution. - return uint64(sort.Search(maxDelayExcess, func(delayExcessGuess int) bool { - excess := DelayExcess(delayExcessGuess) - return excess.Delay() >= desiredDelay - })) -} - -// calculateDelayExcess calculates the optimal new DelayExcess for a block proposer to -// include given the current and desired excess values. -func calculateDelayExcess(excess, desired uint64) uint64 { - change := safemath.AbsDiff(excess, desired) - change = min(change, MaxDelayExcessDiff) - if excess < desired { - return excess + change - } - return excess - change + return acp226Params.DesiredTargetExcess(desiredDelay) } diff --git a/vms/evm/acp226/acp226_test.go b/vms/evm/upgrades/acp226/acp226_test.go similarity index 100% rename from vms/evm/acp226/acp226_test.go rename to vms/evm/upgrades/acp226/acp226_test.go diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/target_excess.go new file mode 100644 index 000000000000..d2f667efe8f9 --- /dev/null +++ b/vms/evm/upgrades/common/target_excess.go @@ -0,0 +1,72 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package common provides shared utility functions for ACP implementations. +package common + +import ( + "math" + "sort" + + safemath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/vms/components/gas" +) + +// TargetExcessParams contains the parameters needed for target excess calculations. +type TargetExcessParams struct { + MinTarget uint64 // Minimum target value (P or M) + TargetConversion uint64 // Conversion factor for exponential calculations (D) + MaxExcessDiff uint64 // Maximum change in excess per update (Q) + MaxExcess uint64 // Maximum possible excess value +} + +// TargetExcess calculates the optimal new targetExcess for a block proposer to +// include given the current and desired excess values. +func (p TargetExcessParams) TargetExcess(excess, desired uint64) uint64 { + change := safemath.AbsDiff(excess, desired) + change = min(change, p.MaxExcessDiff) + if excess < desired { + return excess + change + } + return excess - change +} + +// DesiredTargetExcess calculates the optimal desiredTargetExcess given the +// desired target using binary search. +func (p TargetExcessParams) DesiredTargetExcess(desiredTarget uint64) uint64 { + // This could be solved directly by calculating D * ln(desiredTarget / P) + // using floating point math. However, it introduces inaccuracies. So, we + // use a binary search to find the closest integer solution. + return uint64(sort.Search(int(p.MaxExcess), func(targetExcessGuess int) bool { + calculatedTarget := p.CalculateTarget(uint64(targetExcessGuess)) + return calculatedTarget >= desiredTarget + })) +} + +// CalculateTarget calculates the target value using exponential formula: +// Target = MinTarget * e^(Excess / TargetConversion) +func (p TargetExcessParams) CalculateTarget(excess uint64) uint64 { + return uint64(gas.CalculatePrice( + gas.Price(p.MinTarget), + gas.Gas(excess), + gas.Gas(p.TargetConversion), + )) +} + +// MulWithUpperBound multiplies two numbers and returns the result. If the +// result overflows, it returns [math.MaxUint64]. +func MulWithUpperBound(a, b uint64) uint64 { + product, err := safemath.Mul(a, b) + if err != nil { + return math.MaxUint64 + } + return product +} + +// min returns the minimum of two uint64 values. +func min(a, b uint64) uint64 { + if a < b { + return a + } + return b +} From 70d12c3f950ca57f2ef9d9d6c116a5bd59c2f744 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 18 Sep 2025 01:21:27 +0300 Subject: [PATCH 02/15] move fake expo calculation to safemath --- utils/math/exponential.go | 77 ++++++++++++++++++++++++ vms/components/gas/gas.go | 61 +------------------ vms/evm/upgrades/common/target_excess.go | 8 +-- 3 files changed, 82 insertions(+), 64 deletions(-) create mode 100644 utils/math/exponential.go diff --git a/utils/math/exponential.go b/utils/math/exponential.go new file mode 100644 index 000000000000..0ca9b384ab75 --- /dev/null +++ b/utils/math/exponential.go @@ -0,0 +1,77 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package math + +import ( + "math" + + "github.com/holiman/uint256" +) + +var max256Uint64 = new(uint256.Int).SetUint64(math.MaxUint64) + +// CalculateExponential returns the approximate exponential result given the factor, the +// numerator, and the denominator. +// +// It is defined as an approximation of: +// +// factor * e^(numerator / denominator) +// +// This implements the EIP-4844 fake exponential formula: +// +// def fake_exponential(factor: int, numerator: int, denominator: int) -> int: +// i = 1 +// output = 0 +// numerator_accum = factor * denominator +// while numerator_accum > 0: +// output += numerator_accum +// numerator_accum = (numerator_accum * numerator) // (denominator * i) +// i += 1 +// return output // denominator +// +// This implementation is optimized with the knowledge that any value greater +// than MaxUint64 gets returned as MaxUint64. This means that every intermediate +// value is guaranteed to be at most MaxUint193. So, we can safely use +// uint256.Int. +// +// This function does not perform any memory allocations. +// +//nolint:dupword // The python is copied from the EIP-4844 specification +func CalculateExponential( + factor uint64, + numerator uint64, + denominator uint64, +) uint64 { + var ( + num uint256.Int + denom uint256.Int + + i uint256.Int + output uint256.Int + numeratorAccum uint256.Int + + maxOutput uint256.Int + ) + num.SetUint64(uint64(numerator)) // range is [0, MaxUint64] + denom.SetUint64(uint64(denominator)) // range is [0, MaxUint64] + + i.SetOne() + numeratorAccum.SetUint64(uint64(factor)) // range is [0, MaxUint64] + numeratorAccum.Mul(&numeratorAccum, &denom) // range is [0, MaxUint128] + + maxOutput.Mul(&denom, max256Uint64) // range is [0, MaxUint128] + for numeratorAccum.Sign() > 0 { + output.Add(&output, &numeratorAccum) // range is [0, MaxUint192+MaxUint128] + if output.Cmp(&maxOutput) >= 0 { + return math.MaxUint64 + } + // maxOutput < MaxUint128 so numeratorAccum < MaxUint128. + numeratorAccum.Mul(&numeratorAccum, &num) // range is [0, MaxUint192] + numeratorAccum.Div(&numeratorAccum, &denom) + numeratorAccum.Div(&numeratorAccum, &i) + + i.AddUint64(&i, 1) + } + return output.Div(&output, &denom).Uint64() +} diff --git a/vms/components/gas/gas.go b/vms/components/gas/gas.go index ef8d63963596..b9216820a9a7 100644 --- a/vms/components/gas/gas.go +++ b/vms/components/gas/gas.go @@ -6,13 +6,9 @@ package gas import ( "math" - "github.com/holiman/uint256" - safemath "github.com/ava-labs/avalanchego/utils/math" ) -var maxUint64 = new(uint256.Int).SetUint64(math.MaxUint64) - type ( Gas uint64 Price uint64 @@ -57,65 +53,10 @@ func (g Gas) SubOverTime(gasRate Gas, duration uint64) Gas { // CalculatePrice returns the gas price given the minimum gas price, the // excess gas, and the excess conversion constant. -// -// It is defined as an approximation of: -// -// minPrice * e^(excess / excessConversionConstant) -// -// This implements the EIP-4844 fake exponential formula: -// -// def fake_exponential(factor: int, numerator: int, denominator: int) -> int: -// i = 1 -// output = 0 -// numerator_accum = factor * denominator -// while numerator_accum > 0: -// output += numerator_accum -// numerator_accum = (numerator_accum * numerator) // (denominator * i) -// i += 1 -// return output // denominator -// -// This implementation is optimized with the knowledge that any value greater -// than MaxUint64 gets returned as MaxUint64. This means that every intermediate -// value is guaranteed to be at most MaxUint193. So, we can safely use -// uint256.Int. -// -// This function does not perform any memory allocations. -// -//nolint:dupword // The python is copied from the EIP-4844 specification func CalculatePrice( minPrice Price, excess Gas, excessConversionConstant Gas, ) Price { - var ( - numerator uint256.Int - denominator uint256.Int - - i uint256.Int - output uint256.Int - numeratorAccum uint256.Int - - maxOutput uint256.Int - ) - numerator.SetUint64(uint64(excess)) // range is [0, MaxUint64] - denominator.SetUint64(uint64(excessConversionConstant)) // range is [0, MaxUint64] - - i.SetOne() - numeratorAccum.SetUint64(uint64(minPrice)) // range is [0, MaxUint64] - numeratorAccum.Mul(&numeratorAccum, &denominator) // range is [0, MaxUint128] - - maxOutput.Mul(&denominator, maxUint64) // range is [0, MaxUint128] - for numeratorAccum.Sign() > 0 { - output.Add(&output, &numeratorAccum) // range is [0, MaxUint192+MaxUint128] - if output.Cmp(&maxOutput) >= 0 { - return math.MaxUint64 - } - // maxOutput < MaxUint128 so numeratorAccum < MaxUint128. - numeratorAccum.Mul(&numeratorAccum, &numerator) // range is [0, MaxUint192] - numeratorAccum.Div(&numeratorAccum, &denominator) - numeratorAccum.Div(&numeratorAccum, &i) - - i.AddUint64(&i, 1) - } - return Price(output.Div(&output, &denominator).Uint64()) + return Price(safemath.CalculateExponential(uint64(minPrice), uint64(excess), uint64(excessConversionConstant))) } diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/target_excess.go index d2f667efe8f9..dbfdda594bf9 100644 --- a/vms/evm/upgrades/common/target_excess.go +++ b/vms/evm/upgrades/common/target_excess.go @@ -46,10 +46,10 @@ func (p TargetExcessParams) DesiredTargetExcess(desiredTarget uint64) uint64 { // CalculateTarget calculates the target value using exponential formula: // Target = MinTarget * e^(Excess / TargetConversion) func (p TargetExcessParams) CalculateTarget(excess uint64) uint64 { - return uint64(gas.CalculatePrice( - gas.Price(p.MinTarget), - gas.Gas(excess), - gas.Gas(p.TargetConversion), + return uint64(safemath.CalculateExponential( + p.MinTarget, + excess, + p.TargetConversion, )) } From f1609fa313fde82b2216fee71e889b1c41aace9e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 18 Sep 2025 23:55:12 +0300 Subject: [PATCH 03/15] fix linter --- utils/math/exponential.go | 6 +++--- vms/evm/upgrades/acp226/acp226.go | 4 +--- vms/evm/upgrades/common/target_excess.go | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/utils/math/exponential.go b/utils/math/exponential.go index 0ca9b384ab75..02722a875e2a 100644 --- a/utils/math/exponential.go +++ b/utils/math/exponential.go @@ -53,11 +53,11 @@ func CalculateExponential( maxOutput uint256.Int ) - num.SetUint64(uint64(numerator)) // range is [0, MaxUint64] - denom.SetUint64(uint64(denominator)) // range is [0, MaxUint64] + num.SetUint64(numerator) // range is [0, MaxUint64] + denom.SetUint64(denominator) // range is [0, MaxUint64] i.SetOne() - numeratorAccum.SetUint64(uint64(factor)) // range is [0, MaxUint64] + numeratorAccum.SetUint64(factor) // range is [0, MaxUint64] numeratorAccum.Mul(&numeratorAccum, &denom) // range is [0, MaxUint128] maxOutput.Mul(&denom, max256Uint64) // range is [0, MaxUint128] diff --git a/vms/evm/upgrades/acp226/acp226.go b/vms/evm/upgrades/acp226/acp226.go index a05c16f511a9..d0cfc12da29f 100644 --- a/vms/evm/upgrades/acp226/acp226.go +++ b/vms/evm/upgrades/acp226/acp226.go @@ -5,9 +5,7 @@ // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/226-dynamic-minimum-block-times/README.md package acp226 -import ( - "github.com/ava-labs/avalanchego/vms/evm/upgrades/common" -) +import "github.com/ava-labs/avalanchego/vms/evm/upgrades/common" const ( // MinDelayMilliseconds (M) is the minimum block delay in milliseconds diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/target_excess.go index dbfdda594bf9..694b57028ca9 100644 --- a/vms/evm/upgrades/common/target_excess.go +++ b/vms/evm/upgrades/common/target_excess.go @@ -46,11 +46,11 @@ func (p TargetExcessParams) DesiredTargetExcess(desiredTarget uint64) uint64 { // CalculateTarget calculates the target value using exponential formula: // Target = MinTarget * e^(Excess / TargetConversion) func (p TargetExcessParams) CalculateTarget(excess uint64) uint64 { - return uint64(safemath.CalculateExponential( + return safemath.CalculateExponential( p.MinTarget, excess, p.TargetConversion, - )) + ) } // MulWithUpperBound multiplies two numbers and returns the result. If the From 4b1dd8360d742ae28a90a8cddad7dd251222b388 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 19 Sep 2025 00:00:33 +0300 Subject: [PATCH 04/15] remove extra import --- vms/evm/upgrades/common/target_excess.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/target_excess.go index 694b57028ca9..e5d8a53cc9ef 100644 --- a/vms/evm/upgrades/common/target_excess.go +++ b/vms/evm/upgrades/common/target_excess.go @@ -9,7 +9,6 @@ import ( "sort" safemath "github.com/ava-labs/avalanchego/utils/math" - "github.com/ava-labs/avalanchego/vms/components/gas" ) // TargetExcessParams contains the parameters needed for target excess calculations. From 62000d4a1952d05eb3f9ee96169395d340c53874 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 19 Sep 2025 00:13:46 +0300 Subject: [PATCH 05/15] fix predeclared identifier --- vms/evm/upgrades/common/target_excess.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/target_excess.go index e5d8a53cc9ef..f0280e05060e 100644 --- a/vms/evm/upgrades/common/target_excess.go +++ b/vms/evm/upgrades/common/target_excess.go @@ -23,7 +23,7 @@ type TargetExcessParams struct { // include given the current and desired excess values. func (p TargetExcessParams) TargetExcess(excess, desired uint64) uint64 { change := safemath.AbsDiff(excess, desired) - change = min(change, p.MaxExcessDiff) + change = getMin(change, p.MaxExcessDiff) if excess < desired { return excess + change } @@ -62,8 +62,8 @@ func MulWithUpperBound(a, b uint64) uint64 { return product } -// min returns the minimum of two uint64 values. -func min(a, b uint64) uint64 { +// getMin returns the minimum of two uint64 values. +func getMin(a, b uint64) uint64 { if a < b { return a } From 1a03e687011f5f20ba43c58075fd8e20394c056b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 19 Sep 2025 14:26:54 +0300 Subject: [PATCH 06/15] use builtin min --- vms/evm/upgrades/common/target_excess.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/target_excess.go index f0280e05060e..6b509a2b5ca1 100644 --- a/vms/evm/upgrades/common/target_excess.go +++ b/vms/evm/upgrades/common/target_excess.go @@ -23,7 +23,7 @@ type TargetExcessParams struct { // include given the current and desired excess values. func (p TargetExcessParams) TargetExcess(excess, desired uint64) uint64 { change := safemath.AbsDiff(excess, desired) - change = getMin(change, p.MaxExcessDiff) + change = min(change, p.MaxExcessDiff) if excess < desired { return excess + change } @@ -61,11 +61,3 @@ func MulWithUpperBound(a, b uint64) uint64 { } return product } - -// getMin returns the minimum of two uint64 values. -func getMin(a, b uint64) uint64 { - if a < b { - return a - } - return b -} From 4b2b0bd844e90642dcc4a13b58af06b35ed819dd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 24 Sep 2025 11:00:25 +0300 Subject: [PATCH 07/15] remove target prefixes --- utils/math/exponential.go | 4 +-- vms/evm/upgrades/acp176/acp176.go | 16 +++++----- vms/evm/upgrades/acp226/acp226.go | 16 +++++----- vms/evm/upgrades/common/target_excess.go | 40 ++++++++++++------------ 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/utils/math/exponential.go b/utils/math/exponential.go index 02722a875e2a..0ff260c74c97 100644 --- a/utils/math/exponential.go +++ b/utils/math/exponential.go @@ -9,7 +9,7 @@ import ( "github.com/holiman/uint256" ) -var max256Uint64 = new(uint256.Int).SetUint64(math.MaxUint64) +var maxUint64 = new(uint256.Int).SetUint64(math.MaxUint64) // CalculateExponential returns the approximate exponential result given the factor, the // numerator, and the denominator. @@ -60,7 +60,7 @@ func CalculateExponential( numeratorAccum.SetUint64(factor) // range is [0, MaxUint64] numeratorAccum.Mul(&numeratorAccum, &denom) // range is [0, MaxUint128] - maxOutput.Mul(&denom, max256Uint64) // range is [0, MaxUint128] + maxOutput.Mul(&denom, maxUint64) // range is [0, MaxUint128] for numeratorAccum.Sign() > 0 { output.Add(&output, &numeratorAccum) // range is [0, MaxUint192+MaxUint128] if output.Cmp(&maxOutput) >= 0 { diff --git a/vms/evm/upgrades/acp176/acp176.go b/vms/evm/upgrades/acp176/acp176.go index b2f0b984e726..2a35d08ef540 100644 --- a/vms/evm/upgrades/acp176/acp176.go +++ b/vms/evm/upgrades/acp176/acp176.go @@ -42,11 +42,11 @@ const ( var ( ErrStateInsufficientLength = errors.New("insufficient length for fee state") - acp176Params = common.TargetExcessParams{ - MinTarget: MinTargetPerSecond, - TargetConversion: TargetConversion, - MaxExcessDiff: MaxTargetExcessDiff, - MaxExcess: maxTargetExcess, + acp176Params = common.ExcessParams{ + MinValue: MinTargetPerSecond, + ConversionRate: TargetConversion, + MaxExcessDiff: MaxTargetExcessDiff, + MaxExcess: maxTargetExcess, } ) @@ -81,7 +81,7 @@ func ParseState(bytes []byte) (State, error) { // // Target = MinTargetPerSecond * e^(TargetExcess / TargetConversion) func (s *State) Target() gas.Gas { - return gas.Gas(acp176Params.CalculateTarget(uint64(s.TargetExcess))) + return gas.Gas(acp176Params.CalculateValue(uint64(s.TargetExcess))) } // MaxCapacity returns the maximum possible accrued gas capacity, `C`. @@ -148,7 +148,7 @@ func (s *State) ConsumeGas( // desiredTargetExcess without exceeding the maximum targetExcess change. func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { previousTargetPerSecond := s.Target() - s.TargetExcess = gas.Gas(acp176Params.TargetExcess(uint64(s.TargetExcess), uint64(desiredTargetExcess))) + s.TargetExcess = gas.Gas(acp176Params.AdjustExcess(uint64(s.TargetExcess), uint64(desiredTargetExcess))) newTargetPerSecond := s.Target() s.Gas.Excess = scaleExcess( s.Gas.Excess, @@ -173,7 +173,7 @@ func (s *State) Bytes() []byte { // DesiredTargetExcess calculates the optimal desiredTargetExcess given the // desired target. func DesiredTargetExcess(desiredTarget gas.Gas) gas.Gas { - return gas.Gas(acp176Params.DesiredTargetExcess(uint64(desiredTarget))) + return gas.Gas(acp176Params.DesiredExcess(uint64(desiredTarget))) } // scaleExcess scales the excess during gas target modifications to keep the diff --git a/vms/evm/upgrades/acp226/acp226.go b/vms/evm/upgrades/acp226/acp226.go index d0cfc12da29f..fe7ab070d78d 100644 --- a/vms/evm/upgrades/acp226/acp226.go +++ b/vms/evm/upgrades/acp226/acp226.go @@ -19,11 +19,11 @@ const ( ) // acp226Params is the params used for the acp226 upgrade. -var acp226Params = common.TargetExcessParams{ - MinTarget: MinDelayMilliseconds, - TargetConversion: ConversionRate, - MaxExcessDiff: MaxDelayExcessDiff, - MaxExcess: maxDelayExcess, +var acp226Params = common.ExcessParams{ + MinValue: MinDelayMilliseconds, + ConversionRate: ConversionRate, + MaxExcessDiff: MaxDelayExcessDiff, + MaxExcess: maxDelayExcess, } // DelayExcess represents the excess for delay calculation in the dynamic minimum block delay mechanism. @@ -33,13 +33,13 @@ type DelayExcess uint64 // // Delay = MinDelayMilliseconds * e^(DelayExcess / ConversionRate) func (t DelayExcess) Delay() uint64 { - return acp226Params.CalculateTarget(uint64(t)) + return acp226Params.CalculateValue(uint64(t)) } // UpdateDelayExcess updates the DelayExcess to be as close as possible to the // desiredDelayExcess without exceeding the maximum DelayExcess change. func (t *DelayExcess) UpdateDelayExcess(desiredDelayExcess uint64) { - *t = DelayExcess(acp226Params.TargetExcess(uint64(*t), desiredDelayExcess)) + *t = DelayExcess(acp226Params.AdjustExcess(uint64(*t), desiredDelayExcess)) } // DesiredDelayExcess calculates the optimal delay excess given the desired @@ -48,5 +48,5 @@ func DesiredDelayExcess(desiredDelay uint64) uint64 { // This could be solved directly by calculating D * ln(desired / M) // using floating point math. However, it introduces inaccuracies. So, we // use a binary search to find the closest integer solution. - return acp226Params.DesiredTargetExcess(desiredDelay) + return acp226Params.DesiredExcess(desiredDelay) } diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/target_excess.go index 6b509a2b5ca1..25b9bd03a9e1 100644 --- a/vms/evm/upgrades/common/target_excess.go +++ b/vms/evm/upgrades/common/target_excess.go @@ -11,17 +11,17 @@ import ( safemath "github.com/ava-labs/avalanchego/utils/math" ) -// TargetExcessParams contains the parameters needed for target excess calculations. -type TargetExcessParams struct { - MinTarget uint64 // Minimum target value (P or M) - TargetConversion uint64 // Conversion factor for exponential calculations (D) - MaxExcessDiff uint64 // Maximum change in excess per update (Q) - MaxExcess uint64 // Maximum possible excess value +// ExcessParams contains the parameters needed for excess calculations. +type ExcessParams struct { + MinValue uint64 // Minimum value (P or M) + ConversionRate uint64 // Conversion factor for exponential calculations (D) + MaxExcessDiff uint64 // Maximum change in excess per update (Q) + MaxExcess uint64 // Maximum possible excess value } -// TargetExcess calculates the optimal new targetExcess for a block proposer to -// include given the current and desired excess values. -func (p TargetExcessParams) TargetExcess(excess, desired uint64) uint64 { +// AdjustExcess calculates the optimal new excess given the current and desired excess values, +// ensuring the change does not exceed the maximum excess difference. +func (p ExcessParams) AdjustExcess(excess, desired uint64) uint64 { change := safemath.AbsDiff(excess, desired) change = min(change, p.MaxExcessDiff) if excess < desired { @@ -30,25 +30,25 @@ func (p TargetExcessParams) TargetExcess(excess, desired uint64) uint64 { return excess - change } -// DesiredTargetExcess calculates the optimal desiredTargetExcess given the -// desired target using binary search. -func (p TargetExcessParams) DesiredTargetExcess(desiredTarget uint64) uint64 { - // This could be solved directly by calculating D * ln(desiredTarget / P) +// DesiredExcess calculates the optimal desiredExcess given the +// desired value using binary search. +func (p ExcessParams) DesiredExcess(desiredValue uint64) uint64 { + // This could be solved directly by calculating D * ln(desiredValue / P) // using floating point math. However, it introduces inaccuracies. So, we // use a binary search to find the closest integer solution. return uint64(sort.Search(int(p.MaxExcess), func(targetExcessGuess int) bool { - calculatedTarget := p.CalculateTarget(uint64(targetExcessGuess)) - return calculatedTarget >= desiredTarget + calculatedValue := p.CalculateValue(uint64(targetExcessGuess)) + return calculatedValue >= desiredValue })) } -// CalculateTarget calculates the target value using exponential formula: -// Target = MinTarget * e^(Excess / TargetConversion) -func (p TargetExcessParams) CalculateTarget(excess uint64) uint64 { +// CalculateValue calculates the value using exponential formula: +// Value = MinValue * e^(Excess / ConversionRate) +func (p ExcessParams) CalculateValue(excess uint64) uint64 { return safemath.CalculateExponential( - p.MinTarget, + p.MinValue, excess, - p.TargetConversion, + p.ConversionRate, ) } From e43a12fc322c7bb768dac73d52e8628848d1da13 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 24 Sep 2025 11:05:28 +0300 Subject: [PATCH 08/15] remove comment --- vms/evm/upgrades/acp226/acp226.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/vms/evm/upgrades/acp226/acp226.go b/vms/evm/upgrades/acp226/acp226.go index fe7ab070d78d..7e8d97afd3c0 100644 --- a/vms/evm/upgrades/acp226/acp226.go +++ b/vms/evm/upgrades/acp226/acp226.go @@ -45,8 +45,5 @@ func (t *DelayExcess) UpdateDelayExcess(desiredDelayExcess uint64) { // DesiredDelayExcess calculates the optimal delay excess given the desired // delay. func DesiredDelayExcess(desiredDelay uint64) uint64 { - // This could be solved directly by calculating D * ln(desired / M) - // using floating point math. However, it introduces inaccuracies. So, we - // use a binary search to find the closest integer solution. return acp226Params.DesiredExcess(desiredDelay) } From 9efd27a86c45b4d5875dbd89a79b55ea0940e207 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 24 Sep 2025 11:06:11 +0300 Subject: [PATCH 09/15] revert var name change --- utils/math/exponential.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/math/exponential.go b/utils/math/exponential.go index 0ff260c74c97..02722a875e2a 100644 --- a/utils/math/exponential.go +++ b/utils/math/exponential.go @@ -9,7 +9,7 @@ import ( "github.com/holiman/uint256" ) -var maxUint64 = new(uint256.Int).SetUint64(math.MaxUint64) +var max256Uint64 = new(uint256.Int).SetUint64(math.MaxUint64) // CalculateExponential returns the approximate exponential result given the factor, the // numerator, and the denominator. @@ -60,7 +60,7 @@ func CalculateExponential( numeratorAccum.SetUint64(factor) // range is [0, MaxUint64] numeratorAccum.Mul(&numeratorAccum, &denom) // range is [0, MaxUint128] - maxOutput.Mul(&denom, maxUint64) // range is [0, MaxUint128] + maxOutput.Mul(&denom, max256Uint64) // range is [0, MaxUint128] for numeratorAccum.Sign() > 0 { output.Add(&output, &numeratorAccum) // range is [0, MaxUint192+MaxUint128] if output.Cmp(&maxOutput) >= 0 { From d5b7f34ea7fa76b6fd87b17e2b941d65f2549153 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 24 Sep 2025 19:27:41 +0300 Subject: [PATCH 10/15] fix import path --- tests/e2e/c/dynamic_fees.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/c/dynamic_fees.go b/tests/e2e/c/dynamic_fees.go index 23ee1710554a..4a6bce836dd5 100644 --- a/tests/e2e/c/dynamic_fees.go +++ b/tests/e2e/c/dynamic_fees.go @@ -20,7 +20,7 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/vms/evm/acp176" + "github.com/ava-labs/avalanchego/vms/evm/upgrades/acp176" ) // This test uses the compiled bytecode for `consume_gas.sol` as well as its ABI From 7f3cfafcf3178fccfb51626c60a0fe2a59c197a1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 25 Sep 2025 21:11:19 +0300 Subject: [PATCH 11/15] Rename target_excess.go to excess_params.go Signed-off-by: Ceyhun Onur --- vms/evm/upgrades/common/{target_excess.go => excess_params.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vms/evm/upgrades/common/{target_excess.go => excess_params.go} (100%) diff --git a/vms/evm/upgrades/common/target_excess.go b/vms/evm/upgrades/common/excess_params.go similarity index 100% rename from vms/evm/upgrades/common/target_excess.go rename to vms/evm/upgrades/common/excess_params.go From bc657541320549882d255eb7ac6c78b637cbee36 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 26 Sep 2025 18:19:35 +0300 Subject: [PATCH 12/15] resolve conflict --- vms/evm/upgrades/acp176/acp176.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vms/evm/upgrades/acp176/acp176.go b/vms/evm/upgrades/acp176/acp176.go index 8ef2a51dbedd..1bdf1c9c32de 100644 --- a/vms/evm/upgrades/acp176/acp176.go +++ b/vms/evm/upgrades/acp176/acp176.go @@ -120,9 +120,9 @@ func (s *State) AdvanceSeconds(seconds uint64) { func (s *State) AdvanceMilliseconds(milliseconds uint64) { targetPerSecond := s.Target() targetPerMS := targetPerSecond / 1000 - maxPerMS := targetPerMS * TargetToMax // R - this can't overflow since 1000 > TargetToMax. - maxPerSecond := mulWithUpperBound(targetPerSecond, TargetToMax) // rate used for calculating maxCapacity - maxCapacity := mulWithUpperBound(maxPerSecond, TimeToFillCapacity) // C + maxPerMS := targetPerMS * TargetToMax // R - this can't overflow since 1000 > TargetToMax. + maxPerSecond := common.MulWithUpperBound(uint64(targetPerSecond), TargetToMax) // rate used for calculating maxCapacity + maxCapacity := gas.Gas(common.MulWithUpperBound(maxPerSecond, TimeToFillCapacity)) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerMS, From 0c58d100dd0374dbd202b2d1cd4c124085af7709 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 6 Oct 2025 16:15:04 +0300 Subject: [PATCH 13/15] reviews --- utils/math/exponential.go | 26 ++++---------- vms/components/gas/gas.go | 2 +- vms/evm/{upgrades => }/acp176/acp176.go | 36 ++++++++++++------- vms/evm/{upgrades => }/acp176/acp176_test.go | 0 vms/evm/{upgrades => }/acp226/acp226.go | 10 +++--- vms/evm/{upgrades => }/acp226/acp226_test.go | 0 .../excess_params.go => excess/params.go} | 23 ++++++------ 7 files changed, 46 insertions(+), 51 deletions(-) rename vms/evm/{upgrades => }/acp176/acp176.go (86%) rename vms/evm/{upgrades => }/acp176/acp176_test.go (100%) rename vms/evm/{upgrades => }/acp226/acp226.go (89%) rename vms/evm/{upgrades => }/acp226/acp226_test.go (100%) rename vms/evm/{upgrades/common/excess_params.go => excess/params.go} (70%) diff --git a/utils/math/exponential.go b/utils/math/exponential.go index 02722a875e2a..12ccba626cc0 100644 --- a/utils/math/exponential.go +++ b/utils/math/exponential.go @@ -11,34 +11,20 @@ import ( var max256Uint64 = new(uint256.Int).SetUint64(math.MaxUint64) -// CalculateExponential returns the approximate exponential result given the factor, the -// numerator, and the denominator. +// ApproximateExponential implements the EIP-4844 fake exponential formula and +// returns the approximate exponential result given the factor, the numerator, and the denominator. // // It is defined as an approximation of: // // factor * e^(numerator / denominator) // -// This implements the EIP-4844 fake exponential formula: -// -// def fake_exponential(factor: int, numerator: int, denominator: int) -> int: -// i = 1 -// output = 0 -// numerator_accum = factor * denominator -// while numerator_accum > 0: -// output += numerator_accum -// numerator_accum = (numerator_accum * numerator) // (denominator * i) -// i += 1 -// return output // denominator -// // This implementation is optimized with the knowledge that any value greater // than MaxUint64 gets returned as MaxUint64. This means that every intermediate // value is guaranteed to be at most MaxUint193. So, we can safely use // uint256.Int. // // This function does not perform any memory allocations. -// -//nolint:dupword // The python is copied from the EIP-4844 specification -func CalculateExponential( +func ApproximateExponential( factor uint64, numerator uint64, denominator uint64, @@ -53,11 +39,11 @@ func CalculateExponential( maxOutput uint256.Int ) - num.SetUint64(numerator) // range is [0, MaxUint64] - denom.SetUint64(denominator) // range is [0, MaxUint64] + num.SetUint64(numerator) + denom.SetUint64(denominator) i.SetOne() - numeratorAccum.SetUint64(factor) // range is [0, MaxUint64] + numeratorAccum.SetUint64(factor) numeratorAccum.Mul(&numeratorAccum, &denom) // range is [0, MaxUint128] maxOutput.Mul(&denom, max256Uint64) // range is [0, MaxUint128] diff --git a/vms/components/gas/gas.go b/vms/components/gas/gas.go index b9216820a9a7..3e942c9cbe7b 100644 --- a/vms/components/gas/gas.go +++ b/vms/components/gas/gas.go @@ -58,5 +58,5 @@ func CalculatePrice( excess Gas, excessConversionConstant Gas, ) Price { - return Price(safemath.CalculateExponential(uint64(minPrice), uint64(excess), uint64(excessConversionConstant))) + return Price(safemath.ApproximateExponential(uint64(minPrice), uint64(excess), uint64(excessConversionConstant))) } diff --git a/vms/evm/upgrades/acp176/acp176.go b/vms/evm/acp176/acp176.go similarity index 86% rename from vms/evm/upgrades/acp176/acp176.go rename to vms/evm/acp176/acp176.go index 1bdf1c9c32de..7522a87881b3 100644 --- a/vms/evm/upgrades/acp176/acp176.go +++ b/vms/evm/acp176/acp176.go @@ -16,7 +16,9 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/gas" - "github.com/ava-labs/avalanchego/vms/evm/upgrades/common" + "github.com/ava-labs/avalanchego/vms/evm/excess" + + safemath "github.com/ava-labs/avalanchego/utils/math" ) const ( @@ -42,10 +44,10 @@ const ( var ( ErrStateInsufficientLength = errors.New("insufficient length for fee state") - acp176Params = common.ExcessParams{ - MinValue: MinTargetPerSecond, - ConversionRate: TargetConversion, - MaxExcessDiff: MaxTargetExcessDiff, + acp176Params = excess.Params{ + MinValue: MinTargetPerSecond, // P + ConversionRate: TargetConversion, // D + MaxExcessDiff: MaxTargetExcessDiff, // Q MaxExcess: maxTargetExcess, } ) @@ -87,7 +89,7 @@ func (s *State) Target() gas.Gas { // MaxCapacity returns the maximum possible accrued gas capacity, `C`. func (s *State) MaxCapacity() gas.Gas { targetPerSecond := s.Target() - return gas.Gas(common.MulWithUpperBound(uint64(targetPerSecond), TargetToMaxCapacity)) + return gas.Gas(excess.MulWithUpperBound(uint64(targetPerSecond), TargetToMaxCapacity)) } // GasPrice returns the current required fee per gas. @@ -95,7 +97,7 @@ func (s *State) MaxCapacity() gas.Gas { // GasPrice = MinGasPrice * e^(Excess / (Target() * TargetToPriceUpdateConversion)) func (s *State) GasPrice() gas.Price { targetPerSecond := s.Target() - priceUpdateConversion := common.MulWithUpperBound(uint64(targetPerSecond), TargetToPriceUpdateConversion) // K + priceUpdateConversion := excess.MulWithUpperBound(uint64(targetPerSecond), TargetToPriceUpdateConversion) // K return gas.CalculatePrice(MinGasPrice, s.Gas.Excess, gas.Gas(priceUpdateConversion)) } @@ -104,8 +106,8 @@ func (s *State) GasPrice() gas.Price { // This is used in Fortuna. func (s *State) AdvanceSeconds(seconds uint64) { targetPerSecond := s.Target() - maxPerSecond := gas.Gas(common.MulWithUpperBound(uint64(targetPerSecond), TargetToMax)) // R - maxCapacity := gas.Gas(common.MulWithUpperBound(uint64(maxPerSecond), TimeToFillCapacity)) // C + maxPerSecond := gas.Gas(excess.MulWithUpperBound(uint64(targetPerSecond), TargetToMax)) // R + maxCapacity := gas.Gas(excess.MulWithUpperBound(uint64(maxPerSecond), TimeToFillCapacity)) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerSecond, @@ -121,8 +123,8 @@ func (s *State) AdvanceMilliseconds(milliseconds uint64) { targetPerSecond := s.Target() targetPerMS := targetPerSecond / 1000 maxPerMS := targetPerMS * TargetToMax // R - this can't overflow since 1000 > TargetToMax. - maxPerSecond := common.MulWithUpperBound(uint64(targetPerSecond), TargetToMax) // rate used for calculating maxCapacity - maxCapacity := gas.Gas(common.MulWithUpperBound(maxPerSecond, TimeToFillCapacity)) // C + maxPerSecond := excess.MulWithUpperBound(uint64(targetPerSecond), TargetToMax) // rate used for calculating maxCapacity + maxCapacity := gas.Gas(excess.MulWithUpperBound(maxPerSecond, TimeToFillCapacity)) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerMS, @@ -175,7 +177,7 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { ) // Ensure the gas capacity does not exceed the maximum capacity. - newMaxCapacity := gas.Gas(common.MulWithUpperBound(uint64(newTargetPerSecond), TargetToMaxCapacity)) // C + newMaxCapacity := gas.Gas(excess.MulWithUpperBound(uint64(newTargetPerSecond), TargetToMaxCapacity)) // C s.Gas.Capacity = min(s.Gas.Capacity, newMaxCapacity) } @@ -215,3 +217,13 @@ func scaleExcess( } return gas.Gas(bigExcess.Uint64()) } + +// mulWithUpperBound multiplies two numbers and returns the result. If the +// result overflows, it returns [math.MaxUint64]. +func mulWithUpperBound(a, b uint64) uint64 { + product, err := safemath.Mul(a, b) + if err != nil { + return math.MaxUint64 + } + return product +} diff --git a/vms/evm/upgrades/acp176/acp176_test.go b/vms/evm/acp176/acp176_test.go similarity index 100% rename from vms/evm/upgrades/acp176/acp176_test.go rename to vms/evm/acp176/acp176_test.go diff --git a/vms/evm/upgrades/acp226/acp226.go b/vms/evm/acp226/acp226.go similarity index 89% rename from vms/evm/upgrades/acp226/acp226.go rename to vms/evm/acp226/acp226.go index 8ca7f7a38f6b..cdf0b857fde7 100644 --- a/vms/evm/upgrades/acp226/acp226.go +++ b/vms/evm/acp226/acp226.go @@ -5,8 +5,6 @@ // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/226-dynamic-minimum-block-times/README.md package acp226 -import "github.com/ava-labs/avalanchego/vms/evm/upgrades/common" - const ( // MinDelayMilliseconds (M) is the minimum block delay in milliseconds MinDelayMilliseconds = 1 // ms @@ -23,10 +21,10 @@ const ( ) // acp226Params is the params used for the acp226 upgrade. -var acp226Params = common.ExcessParams{ - MinValue: MinDelayMilliseconds, - ConversionRate: ConversionRate, - MaxExcessDiff: MaxDelayExcessDiff, +var acp226Params = excess.ExcessParams{ + MinValue: MinDelayMilliseconds, // M + ConversionRate: ConversionRate, // D + MaxExcessDiff: MaxDelayExcessDiff, // Q MaxExcess: maxDelayExcess, } diff --git a/vms/evm/upgrades/acp226/acp226_test.go b/vms/evm/acp226/acp226_test.go similarity index 100% rename from vms/evm/upgrades/acp226/acp226_test.go rename to vms/evm/acp226/acp226_test.go diff --git a/vms/evm/upgrades/common/excess_params.go b/vms/evm/excess/params.go similarity index 70% rename from vms/evm/upgrades/common/excess_params.go rename to vms/evm/excess/params.go index 25b9bd03a9e1..0efd167eed23 100644 --- a/vms/evm/upgrades/common/excess_params.go +++ b/vms/evm/excess/params.go @@ -1,8 +1,7 @@ // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -// Package common provides shared utility functions for ACP implementations. -package common +package excess import ( "math" @@ -11,17 +10,17 @@ import ( safemath "github.com/ava-labs/avalanchego/utils/math" ) -// ExcessParams contains the parameters needed for excess calculations. -type ExcessParams struct { - MinValue uint64 // Minimum value (P or M) - ConversionRate uint64 // Conversion factor for exponential calculations (D) - MaxExcessDiff uint64 // Maximum change in excess per update (Q) +// Params contains the parameters needed for excess calculations. +type Params struct { + MinValue uint64 // Minimum value + ConversionRate uint64 // Conversion factor for exponential calculations + MaxExcessDiff uint64 // Maximum change in excess per update MaxExcess uint64 // Maximum possible excess value } // AdjustExcess calculates the optimal new excess given the current and desired excess values, // ensuring the change does not exceed the maximum excess difference. -func (p ExcessParams) AdjustExcess(excess, desired uint64) uint64 { +func (p Params) AdjustExcess(excess, desired uint64) uint64 { change := safemath.AbsDiff(excess, desired) change = min(change, p.MaxExcessDiff) if excess < desired { @@ -32,8 +31,8 @@ func (p ExcessParams) AdjustExcess(excess, desired uint64) uint64 { // DesiredExcess calculates the optimal desiredExcess given the // desired value using binary search. -func (p ExcessParams) DesiredExcess(desiredValue uint64) uint64 { - // This could be solved directly by calculating D * ln(desiredValue / P) +func (p Params) DesiredExcess(desiredValue uint64) uint64 { + // This could be solved directly by calculating ConversionRate * ln(desiredValue / MinValue) // using floating point math. However, it introduces inaccuracies. So, we // use a binary search to find the closest integer solution. return uint64(sort.Search(int(p.MaxExcess), func(targetExcessGuess int) bool { @@ -44,8 +43,8 @@ func (p ExcessParams) DesiredExcess(desiredValue uint64) uint64 { // CalculateValue calculates the value using exponential formula: // Value = MinValue * e^(Excess / ConversionRate) -func (p ExcessParams) CalculateValue(excess uint64) uint64 { - return safemath.CalculateExponential( +func (p Params) CalculateValue(excess uint64) uint64 { + return safemath.ApproximateExponential( p.MinValue, excess, p.ConversionRate, From 34a58f9f8f3b5d4b9bd25219bfb164f29b219b49 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 6 Oct 2025 16:22:28 +0300 Subject: [PATCH 14/15] fix import paths --- tests/e2e/c/dynamic_fees.go | 2 +- vms/evm/acp226/acp226.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/e2e/c/dynamic_fees.go b/tests/e2e/c/dynamic_fees.go index 4a6bce836dd5..23ee1710554a 100644 --- a/tests/e2e/c/dynamic_fees.go +++ b/tests/e2e/c/dynamic_fees.go @@ -20,7 +20,7 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/vms/evm/upgrades/acp176" + "github.com/ava-labs/avalanchego/vms/evm/acp176" ) // This test uses the compiled bytecode for `consume_gas.sol` as well as its ABI diff --git a/vms/evm/acp226/acp226.go b/vms/evm/acp226/acp226.go index cdf0b857fde7..e8f9d2bf09fc 100644 --- a/vms/evm/acp226/acp226.go +++ b/vms/evm/acp226/acp226.go @@ -5,6 +5,8 @@ // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/226-dynamic-minimum-block-times/README.md package acp226 +import "github.com/ava-labs/avalanchego/vms/evm/excess" + const ( // MinDelayMilliseconds (M) is the minimum block delay in milliseconds MinDelayMilliseconds = 1 // ms @@ -21,7 +23,7 @@ const ( ) // acp226Params is the params used for the acp226 upgrade. -var acp226Params = excess.ExcessParams{ +var acp226Params = excess.Params{ MinValue: MinDelayMilliseconds, // M ConversionRate: ConversionRate, // D MaxExcessDiff: MaxDelayExcessDiff, // Q From 3e179816ae3ed4783bac92e58c10e28b47160aaa Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 6 Oct 2025 18:50:19 +0300 Subject: [PATCH 15/15] unnecessary conversions --- vms/evm/acp176/acp176.go | 20 ++++++++++---------- vms/evm/acp226/acp226.go | 4 ++-- vms/evm/excess/params.go | 11 ----------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/vms/evm/acp176/acp176.go b/vms/evm/acp176/acp176.go index 7522a87881b3..45e0de849907 100644 --- a/vms/evm/acp176/acp176.go +++ b/vms/evm/acp176/acp176.go @@ -89,7 +89,7 @@ func (s *State) Target() gas.Gas { // MaxCapacity returns the maximum possible accrued gas capacity, `C`. func (s *State) MaxCapacity() gas.Gas { targetPerSecond := s.Target() - return gas.Gas(excess.MulWithUpperBound(uint64(targetPerSecond), TargetToMaxCapacity)) + return mulWithUpperBound(targetPerSecond, TargetToMaxCapacity) } // GasPrice returns the current required fee per gas. @@ -97,8 +97,8 @@ func (s *State) MaxCapacity() gas.Gas { // GasPrice = MinGasPrice * e^(Excess / (Target() * TargetToPriceUpdateConversion)) func (s *State) GasPrice() gas.Price { targetPerSecond := s.Target() - priceUpdateConversion := excess.MulWithUpperBound(uint64(targetPerSecond), TargetToPriceUpdateConversion) // K - return gas.CalculatePrice(MinGasPrice, s.Gas.Excess, gas.Gas(priceUpdateConversion)) + priceUpdateConversion := mulWithUpperBound(targetPerSecond, TargetToPriceUpdateConversion) // K + return gas.CalculatePrice(MinGasPrice, s.Gas.Excess, priceUpdateConversion) } // AdvanceSeconds increases the gas capacity and decreases the gas excess based on @@ -106,8 +106,8 @@ func (s *State) GasPrice() gas.Price { // This is used in Fortuna. func (s *State) AdvanceSeconds(seconds uint64) { targetPerSecond := s.Target() - maxPerSecond := gas.Gas(excess.MulWithUpperBound(uint64(targetPerSecond), TargetToMax)) // R - maxCapacity := gas.Gas(excess.MulWithUpperBound(uint64(maxPerSecond), TimeToFillCapacity)) // C + maxPerSecond := mulWithUpperBound(targetPerSecond, TargetToMax) // R + maxCapacity := mulWithUpperBound(maxPerSecond, TimeToFillCapacity) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerSecond, @@ -122,9 +122,9 @@ func (s *State) AdvanceSeconds(seconds uint64) { func (s *State) AdvanceMilliseconds(milliseconds uint64) { targetPerSecond := s.Target() targetPerMS := targetPerSecond / 1000 - maxPerMS := targetPerMS * TargetToMax // R - this can't overflow since 1000 > TargetToMax. - maxPerSecond := excess.MulWithUpperBound(uint64(targetPerSecond), TargetToMax) // rate used for calculating maxCapacity - maxCapacity := gas.Gas(excess.MulWithUpperBound(maxPerSecond, TimeToFillCapacity)) // C + maxPerMS := targetPerMS * TargetToMax // R - this can't overflow since 1000 > TargetToMax. + maxPerSecond := mulWithUpperBound(targetPerSecond, TargetToMax) // rate used for calculating maxCapacity + maxCapacity := mulWithUpperBound(maxPerSecond, TimeToFillCapacity) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerMS, @@ -177,7 +177,7 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { ) // Ensure the gas capacity does not exceed the maximum capacity. - newMaxCapacity := gas.Gas(excess.MulWithUpperBound(uint64(newTargetPerSecond), TargetToMaxCapacity)) // C + newMaxCapacity := mulWithUpperBound(newTargetPerSecond, TargetToMaxCapacity) // C s.Gas.Capacity = min(s.Gas.Capacity, newMaxCapacity) } @@ -220,7 +220,7 @@ func scaleExcess( // mulWithUpperBound multiplies two numbers and returns the result. If the // result overflows, it returns [math.MaxUint64]. -func mulWithUpperBound(a, b uint64) uint64 { +func mulWithUpperBound(a, b gas.Gas) gas.Gas { product, err := safemath.Mul(a, b) if err != nil { return math.MaxUint64 diff --git a/vms/evm/acp226/acp226.go b/vms/evm/acp226/acp226.go index e8f9d2bf09fc..816104d256f9 100644 --- a/vms/evm/acp226/acp226.go +++ b/vms/evm/acp226/acp226.go @@ -49,6 +49,6 @@ func (t *DelayExcess) UpdateDelayExcess(desiredDelayExcess DelayExcess) { // DesiredDelayExcess calculates the optimal delay excess given the desired // delay. -func DesiredDelayExcess(desiredDelay uint64) uint64 { - return acp226Params.DesiredExcess(desiredDelay) +func DesiredDelayExcess(desiredDelay uint64) DelayExcess { + return DelayExcess(acp226Params.DesiredExcess(desiredDelay)) } diff --git a/vms/evm/excess/params.go b/vms/evm/excess/params.go index 0efd167eed23..7ccfc4dbfac0 100644 --- a/vms/evm/excess/params.go +++ b/vms/evm/excess/params.go @@ -4,7 +4,6 @@ package excess import ( - "math" "sort" safemath "github.com/ava-labs/avalanchego/utils/math" @@ -50,13 +49,3 @@ func (p Params) CalculateValue(excess uint64) uint64 { p.ConversionRate, ) } - -// MulWithUpperBound multiplies two numbers and returns the result. If the -// result overflows, it returns [math.MaxUint64]. -func MulWithUpperBound(a, b uint64) uint64 { - product, err := safemath.Mul(a, b) - if err != nil { - return math.MaxUint64 - } - return product -}