diff --git a/plugin/evm/upgrade/acp176/acp176.go b/plugin/evm/upgrade/acp176/acp176.go index 88b582749f..c262ab7e79 100644 --- a/plugin/evm/upgrade/acp176/acp176.go +++ b/plugin/evm/upgrade/acp176/acp176.go @@ -25,7 +25,7 @@ const ( TimeToFillCapacity = 10 // in seconds TargetToMax = 2 // multiplier to convert from target per second to max per second - TargetToPriceUpdateConversion = 43 // 43s ~= 30s * ln(2) which makes the price double at most every ~30 seconds + TargetToPriceUpdateConversion = 87 // 87s ~= 60s * ln(2) which makes the price double at most every ~60 seconds MaxTargetChangeRate = 1024 // Controls the rate that the target can change per block. targetToMaxCapacity = TargetToMax * TimeToFillCapacity @@ -52,23 +52,15 @@ func (s *State) Target() gas.Gas { // MaxCapacity returns the maximum possible accrued gas capacity, `C`. func (s *State) MaxCapacity() gas.Gas { targetPerSecond := s.Target() - maxCapacity, err := safemath.Mul(targetToMaxCapacity, targetPerSecond) - if err != nil { - maxCapacity = math.MaxUint64 - } - return maxCapacity + return mulWithUpperBound(targetPerSecond, targetToMaxCapacity) } // GasPrice returns the current required fee per gas. // // GasPrice = MinGasPrice * e^(Excess / (Target() * TargetToPriceUpdateConversion)) func (s *State) GasPrice() gas.Price { - target := s.Target() - priceUpdateConversion, err := safemath.Mul(TargetToPriceUpdateConversion, target) // K - if err != nil { - priceUpdateConversion = math.MaxUint64 - } - + targetPerSecond := s.Target() + priceUpdateConversion := mulWithUpperBound(targetPerSecond, TargetToPriceUpdateConversion) // K return gas.CalculatePrice(MinGasPrice, s.Gas.Excess, priceUpdateConversion) } @@ -76,14 +68,8 @@ func (s *State) GasPrice() gas.Price { // the elapsed seconds. func (s *State) AdvanceTime(seconds uint64) { targetPerSecond := s.Target() - maxPerSecond, err := safemath.Mul(TargetToMax, targetPerSecond) // R - if err != nil { - maxPerSecond = math.MaxUint64 - } - maxCapacity, err := safemath.Mul(TimeToFillCapacity, maxPerSecond) // C - if err != nil { - maxCapacity = math.MaxUint64 - } + maxPerSecond := mulWithUpperBound(targetPerSecond, TargetToMax) // R + maxCapacity := mulWithUpperBound(maxPerSecond, TimeToFillCapacity) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerSecond, @@ -134,15 +120,18 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { newTargetPerSecond, previousTargetPerSecond, ) + + // Ensure the gas capacity does not exceed the maximum capacity. + newMaxCapacity := mulWithUpperBound(newTargetPerSecond, targetToMaxCapacity) // C + s.Gas.Capacity = min(s.Gas.Capacity, newMaxCapacity) } // DesiredTargetExcess calculates the optimal desiredTargetExcess given the // desired target. -// -// 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. 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), @@ -180,3 +169,13 @@ func scaleExcess( bigExcess.Div(&bigExcess, &bigTarget) 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/plugin/evm/upgrade/acp176/acp176_test.go b/plugin/evm/upgrade/acp176/acp176_test.go index c0d513c4f7..fbb35ff1ba 100644 --- a/plugin/evm/upgrade/acp176/acp176_test.go +++ b/plugin/evm/upgrade/acp176/acp176_test.go @@ -40,7 +40,7 @@ var ( name: "almost_excess_change", state: State{ Gas: gas.State{ - Excess: 29_805_331, // MinTargetPerSecond * ln(2) * TargetToPriceUpdateConversion + Excess: 60_303_808, // MinTargetPerSecond * ln(2) * TargetToPriceUpdateConversion }, TargetExcess: 33, // Largest excess that doesn't increase the target }, @@ -53,7 +53,7 @@ var ( name: "small_excess_change", state: State{ Gas: gas.State{ - Excess: 29_805_362, // (MinTargetPerSecond + 1) * ln(2) * TargetToPriceUpdateConversion + Excess: 60_303_868, // (MinTargetPerSecond + 1) * ln(2) * TargetToPriceUpdateConversion }, TargetExcess: 34, // Smallest excess that increases the target }, @@ -65,7 +65,7 @@ var ( name: "max_initial_excess_change", state: State{ Gas: gas.State{ - Excess: 47_286_485, // (MinTargetPerSecond + 977) * ln(3) * TargetToPriceUpdateConversion + Excess: 95_672_652, // (MinTargetPerSecond + 977) * ln(3) * TargetToPriceUpdateConversion }, TargetExcess: MaxTargetExcessDiff, }, @@ -78,85 +78,85 @@ var ( name: "current_target", state: State{ Gas: gas.State{ - Excess: 1_336_650_647, // 1_500_000 * ln(nAVAX) * TargetToPriceUpdateConversion + Excess: 2_704_386_192, // 1_500_000 * ln(nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 13_605_152, // 2^25 * ln(1.5) }, target: 1_500_000, maxCapacity: targetToMaxCapacity * 1_500_000, - gasPrice: (nAVAX + 7) * MinGasPrice, // +7 due to approximation + gasPrice: nAVAX*MinGasPrice + 2, // +2 due to approximation }, { name: "3m_target", state: State{ Gas: gas.State{ - Excess: 3_267_368_247, // 3_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 6_610_721_802, // 3_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 36_863_312, // 2^25 * ln(3) }, target: 3_000_000, maxCapacity: targetToMaxCapacity * 3_000_000, - gasPrice: (100*nAVAX + 31) * MinGasPrice, // +31 due to approximation + gasPrice: 100*nAVAX*MinGasPrice + 4, // +4 due to approximation }, { name: "6m_target", state: State{ Gas: gas.State{ - Excess: 6_534_736_494, // 6_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 13_221_443_604, // 6_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 60_121_472, // 2^25 * ln(6) }, target: 6_000_000, maxCapacity: targetToMaxCapacity * 6_000_000, - gasPrice: (100*nAVAX + 31) * MinGasPrice, // +31 due to approximation + gasPrice: 100*nAVAX*MinGasPrice + 4, // +4 due to approximation }, { name: "10m_target", state: State{ Gas: gas.State{ - Excess: 10_891_227_490, // 10_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 22_035_739_340, // 10_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 77_261_935, // 2^25 * ln(10) }, target: 10_000_000, maxCapacity: targetToMaxCapacity * 10_000_000, - gasPrice: (100*nAVAX + 31) * MinGasPrice, // +31 due to approximation + gasPrice: 100*nAVAX*MinGasPrice + 5, // +5 due to approximation }, { name: "100m_target", state: State{ Gas: gas.State{ - Excess: 108_912_274_899, // 100_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 220_357_393_400, // 100_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 154_523_870, // 2^25 * ln(100) }, target: 100_000_000, maxCapacity: targetToMaxCapacity * 100_000_000, - gasPrice: (100*nAVAX + 8) * MinGasPrice, // +8 due to approximation + gasPrice: 100*nAVAX*MinGasPrice + 5, // +5 due to approximation }, { name: "low_1b_target", state: State{ Gas: gas.State{ - Excess: 1_089_122_722_848, // (1_000_000_000 - 24) * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 2_203_573_881_110, // (1_000_000_000 - 24) * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 231_785_804, // 2^25 * ln(1000) }, target: 1_000_000_000 - 24, maxCapacity: targetToMaxCapacity * (1_000_000_000 - 24), - gasPrice: (100*nAVAX + 1) * MinGasPrice, // +1 due to approximation + gasPrice: 100 * nAVAX * MinGasPrice, }, { name: "high_1b_target", state: State{ Gas: gas.State{ - Excess: 1_089_122_755_521, // (1_000_000_000 + 6) * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 2_203_573_947_217, // (1_000_000_000 + 6) * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 231_785_805, // 2^25 * ln(1000) + 1 }, target: 1_000_000_000 + 6, maxCapacity: targetToMaxCapacity * (1_000_000_000 + 6), - gasPrice: (100 * nAVAX) * MinGasPrice, + gasPrice: 100 * nAVAX * MinGasPrice, }, { name: "largest_max_capacity", @@ -558,6 +558,42 @@ var ( TargetExcess: 2 * MaxTargetExcessDiff, }, }, + { + name: "reduce_capacity", + initial: State{ + Gas: gas.State{ + Capacity: 20_039_100, // MinTargetPerSecond * e^(2*MaxTargetExcessDiff / TargetConversion) + Excess: 2_000_000_000, + }, + TargetExcess: 2 * MaxTargetExcessDiff, + }, + desiredTargetExcess: 0, + expected: State{ + Gas: gas.State{ + Capacity: 20_019_540, // MinTargetPerSecond * e^(MaxTargetExcessDiff / TargetConversion) + Excess: 1_998_047_816, // 2M * NewTarget / OldTarget + }, + TargetExcess: MaxTargetExcessDiff, + }, + }, + { + name: "overflow_max_capacity", + initial: State{ + Gas: gas.State{ + Capacity: math.MaxUint64, + Excess: 2_000_000_000, + }, + TargetExcess: maxTargetExcess, + }, + desiredTargetExcess: 0, + expected: State{ + Gas: gas.State{ + Capacity: math.MaxUint64, + Excess: 1_998_047_867, // 2M * NewTarget / OldTarget + }, + TargetExcess: maxTargetExcess - MaxTargetExcessDiff, + }, + }, } )