@@ -27,9 +27,19 @@ import (
2727 "github.com/ethereum/go-ethereum/params"
2828)
2929
30+ const (
31+ // MaxBaseFeeChangePercent limits the maximum base fee change per block to 5% of parent base fee.
32+ // This prevents excessive fee volatility by capping both increases and decreases.
33+ // The 5% limit provides protection against aggressive parameter configurations while
34+ // accommodating the natural behavior of default post-Dandeli parameters (maximum ~1.7% change).
35+ MaxBaseFeeChangePercent = 5
36+ )
37+
3038// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559,
3139// - gas limit check
32- // - basefee check
40+ // - basefee check with different rules pre/post Dandeli:
41+ // - Pre-Dandeli: Strict validation (baseFee must exactly match calculated value)
42+ // - Post-Dandeli: Boundary validation (baseFee change must be within MaxBaseFeeChangePercent)
3343func VerifyEIP1559Header (config * params.ChainConfig , parent , header * types.Header ) error {
3444 // Verify that the gas limit remains within allowed bounds
3545 parentGasLimit := parent .GasLimit
@@ -43,7 +53,13 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
4353 if header .BaseFee == nil {
4454 return errors .New ("header is missing baseFee" )
4555 }
46- // Verify the baseFee is correct based on the parent header.
56+
57+ // Post-Dandeli: Validate that base fee changes are within allowed boundaries
58+ if config .Bor != nil && config .Bor .IsDandeli (header .Number ) {
59+ return verifyBaseFeeWithinBoundaries (parent , header )
60+ }
61+
62+ // Pre-Dandeli: Verify the baseFee is correct based on the parent header
4763 expectedBaseFee := CalcBaseFee (config , parent )
4864 if header .BaseFee .Cmp (expectedBaseFee ) != 0 {
4965 return fmt .Errorf ("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d" ,
@@ -53,6 +69,33 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
5369 return nil
5470}
5571
72+ // verifyBaseFeeWithinBoundaries checks that the base fee change is within the allowed boundary.
73+ // This prevents excessive fee volatility while allowing dynamic fee adjustment post-Dandeli.
74+ // The boundary limit is defined by MaxBaseFeeChangePercent constant.
75+ func verifyBaseFeeWithinBoundaries (parent , header * types.Header ) error {
76+ // Calculate the maximum allowed change (MaxBaseFeeChangePercent of parent base fee)
77+ maxAllowedChange := new (big.Int ).Mul (parent .BaseFee , big .NewInt (MaxBaseFeeChangePercent ))
78+ maxAllowedChange .Div (maxAllowedChange , big .NewInt (100 ))
79+
80+ // Calculate the actual change in base fee
81+ actualChange := new (big.Int )
82+ if header .BaseFee .Cmp (parent .BaseFee ) >= 0 {
83+ // Base fee increased or stayed the same
84+ actualChange .Sub (header .BaseFee , parent .BaseFee )
85+ } else {
86+ // Base fee decreased
87+ actualChange .Sub (parent .BaseFee , header .BaseFee )
88+ }
89+
90+ // Verify the change is within the allowed boundary
91+ if actualChange .Cmp (maxAllowedChange ) > 0 {
92+ return fmt .Errorf ("baseFee change exceeds %d%% limit: change=%s, maxAllowed=%s, parentBaseFee=%s, headerBaseFee=%s" ,
93+ MaxBaseFeeChangePercent , actualChange , maxAllowedChange , parent .BaseFee , header .BaseFee )
94+ }
95+
96+ return nil
97+ }
98+
5699// CalcBaseFee calculates the basefee of the header.
57100func CalcBaseFee (config * params.ChainConfig , parent * types.Header ) * big.Int {
58101 // If the current block is the first EIP-1559 block, return the InitialBaseFee.
@@ -70,16 +113,30 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
70113 var (
71114 num = new (big.Int )
72115 denom = new (big.Int )
73- baseFeeChangeDenominatorUint64 = baseFeeChangeDenominator (config , parent .Number )
116+ baseFeeChangeDenominatorUint64 = params . BaseFeeChangeDenominator (config . Bor , parent .Number )
74117 )
75118
119+ // Calculate maximum allowed change (only applies post-Dandeli)
120+ var maxAllowedChange * big.Int
121+ applyBoundaryCap := config .Bor != nil && config .Bor .IsDandeli (parent .Number )
122+ if applyBoundaryCap {
123+ maxAllowedChange = new (big.Int ).Mul (parent .BaseFee , big .NewInt (MaxBaseFeeChangePercent ))
124+ maxAllowedChange .Div (maxAllowedChange , big .NewInt (100 ))
125+ }
126+
76127 if parent .GasUsed > parentGasTarget {
77128 // If the parent block used more gas than its target, the baseFee should increase.
78129 // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
79130 num .SetUint64 (parent .GasUsed - parentGasTarget )
80131 num .Mul (num , parent .BaseFee )
81132 num .Div (num , denom .SetUint64 (parentGasTarget ))
82133 num .Div (num , denom .SetUint64 (baseFeeChangeDenominatorUint64 ))
134+
135+ // Cap the increase to MaxBaseFeeChangePercent post-Dandeli
136+ if applyBoundaryCap && num .Cmp (maxAllowedChange ) > 0 {
137+ num .Set (maxAllowedChange )
138+ }
139+
83140 if num .Cmp (common .Big1 ) < 0 {
84141 return num .Add (parent .BaseFee , common .Big1 )
85142 }
@@ -92,6 +149,11 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
92149 num .Div (num , denom .SetUint64 (parentGasTarget ))
93150 num .Div (num , denom .SetUint64 (baseFeeChangeDenominatorUint64 ))
94151
152+ // Cap the decrease to MaxBaseFeeChangePercent post-Dandeli
153+ if applyBoundaryCap && num .Cmp (maxAllowedChange ) > 0 {
154+ num .Set (maxAllowedChange )
155+ }
156+
95157 baseFee := num .Sub (parent .BaseFee , num )
96158 if baseFee .Cmp (common .Big0 ) < 0 {
97159 baseFee = common .Big0
@@ -100,31 +162,14 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
100162 }
101163}
102164
103- // baseFeeChangeDenominator returns the denominator used to bound the amount the base fee can change between blocks.
104- // The value varies based on the hard fork:
105- // - Pre-Delhi: 8 (default)
106- // - Post-Delhi: 16
107- // - Post-Bhilai: 64
108- // If borConfig is nil, returns the default value of 8.
109- func baseFeeChangeDenominator (config * params.ChainConfig , number * big.Int ) uint64 {
110- if config .Bor == nil {
111- return params .DefaultBaseFeeChangeDenominator
112- }
113- if config .Bor .IsBhilai (number ) {
114- return params .BaseFeeChangeDenominatorPostBhilai
115- } else if config .Bor .IsDelhi (number ) {
116- return params .BaseFeeChangeDenominatorPostDelhi
117- } else {
118- return params .DefaultBaseFeeChangeDenominator
119- }
120- }
121-
122165// calcParentGasTarget calculates the target gas based on parent block gas limit. Earlier
123166// it was derived by `ElasticityMultiplier` as it had an integer multiplier value. Post
124- // dandeli HF, a percentage value will be used to calculate the gas target.
167+ // Dandeli HF, a percentage value is used to calculate the gas target (validated with fallback to default) .
125168func calcParentGasTarget (config * params.ChainConfig , parent * types.Header ) uint64 {
126169 if config .Bor != nil && config .Bor .IsDandeli (parent .Number ) {
127- return parent .GasLimit * params .TargetGasPercentagePostDandeli / 100
170+ // Use helper function which validates and provides defaults
171+ targetPercentage := config .Bor .GetTargetGasPercentage (parent .Number )
172+ return parent .GasLimit * targetPercentage / 100
128173 }
129174 return parent .GasLimit / config .ElasticityMultiplier ()
130175}
0 commit comments