@@ -116,7 +116,11 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runCtx *c
116116 // already activated and up to date
117117 return 0 , codeHash , common.Hash {}, nil , false , ProgramUpToDateError ()
118118 }
119- wasm , err := getWasm (statedb , address , params , burner )
119+ charger , err := newFragmentReadCharger (burner , evm .ChainConfig ().MaxCodeSize ())
120+ if err != nil {
121+ return 0 , codeHash , common.Hash {}, nil , false , err
122+ }
123+ wasm , err := getWasm (statedb , address , params , charger )
120124 if err != nil {
121125 return 0 , codeHash , common.Hash {}, nil , false , err
122126 }
@@ -310,14 +314,65 @@ func evmMemoryCost(size uint64) uint64 {
310314 return linearCost + squareCost
311315}
312316
313- func getWasm (statedb vm.StateDB , program common.Address , params * StylusParams , burner burn.Burner ) ([]byte , error ) {
317+ type fragmentReadCharger struct {
318+ burner burn.Burner
319+ maxCodeSize uint64
320+ }
321+
322+ func newFragmentReadCharger (burner burn.Burner , maxCodeSize uint64 ) (* fragmentReadCharger , error ) {
323+ if burner == nil {
324+ if maxCodeSize != 0 {
325+ return nil , errors .New ("maxCodeSize requires fragment read burner" )
326+ }
327+ return nil , nil
328+ }
329+ if maxCodeSize == 0 {
330+ return nil , errors .New ("fragment read burner requires maxCodeSize" )
331+ }
332+ return & fragmentReadCharger {
333+ burner : burner ,
334+ maxCodeSize : maxCodeSize ,
335+ }, nil
336+ }
337+
338+ func (charger * fragmentReadCharger ) canReadNewFragment (statedb vm.StateDB , addr common.Address ) error {
339+ if charger == nil {
340+ return nil
341+ }
342+ cost , err := fragmentReadGasCost (statedb .AddressInAccessList (addr ), charger .maxCodeSize )
343+ if err != nil {
344+ return err
345+ }
346+ if charger .burner .GasLeft () < cost .SingleGas () {
347+ return charger .burner .BurnOut ()
348+ }
349+ return nil
350+ }
351+
352+ func (charger * fragmentReadCharger ) chargeForReadingFragment (statedb vm.StateDB , addr common.Address , codeSize uint64 ) error {
353+ if charger == nil {
354+ return nil
355+ }
356+ warm := statedb .AddressInAccessList (addr )
357+ if ! warm {
358+ statedb .AddAddressToAccessList (addr )
359+ }
360+ cost , err := fragmentReadGasCost (warm , codeSize )
361+ if err != nil {
362+ log .Trace ("fragment copy gas overflow" , "address" , addr , "codeSize" , codeSize , "err" , err )
363+ return err
364+ }
365+ return charger .burner .BurnMultiGas (cost )
366+ }
367+
368+ func getWasm (statedb vm.StateDB , program common.Address , params * StylusParams , charger * fragmentReadCharger ) ([]byte , error ) {
314369 prefixedWasm := statedb .GetCode (program )
315- return getWasmFromContractCode (statedb , prefixedWasm , params , burner )
370+ return getWasmFromContractCode (statedb , prefixedWasm , params , charger )
316371}
317372
318- // burner is used to charge gas for reading fragments. If it is present activation is assumed, and activation checks are enforced.
319- // Only pass a burner if activating the program.
320- func getWasmFromContractCode (statedb vm.StateDB , prefixedWasm []byte , params * StylusParams , burner burn. Burner ) ([]byte , error ) {
373+ // charger is used to charge gas for reading fragments. If it is present activation is assumed, and activation checks are enforced.
374+ // Only pass a charger if activating the program.
375+ func getWasmFromContractCode (statedb vm.StateDB , prefixedWasm []byte , params * StylusParams , charger * fragmentReadCharger ) ([]byte , error ) {
321376 if len (prefixedWasm ) == 0 {
322377 return nil , ProgramNotWasmError ()
323378 }
@@ -328,7 +383,7 @@ func getWasmFromContractCode(statedb vm.StateDB, prefixedWasm []byte, params *St
328383
329384 if params .arbosVersion >= gethParams .ArbosVersion_StylusContractLimit {
330385 if state .IsStylusRootProgramPrefix (prefixedWasm ) {
331- return getWasmFromRootStylus (statedb , prefixedWasm , params .MaxWasmSize , params .MaxFragmentCount , burner )
386+ return getWasmFromRootStylus (statedb , prefixedWasm , params .MaxWasmSize , params .MaxFragmentCount , charger )
332387 }
333388
334389 if state .IsStylusFragmentPrefix (prefixedWasm ) {
@@ -353,15 +408,15 @@ func getWasmFromClassicStylus(data []byte, maxSize uint32) ([]byte, error) {
353408 return arbcompress .DecompressWithDictionary (wasm , int (maxSize ), dict )
354409}
355410
356- // burner is used to charge gas for reading fragments. If it is present activation is assumed, and activation checks are enforced.
357- // Only pass a burner if activating the program.
358- func getWasmFromRootStylus (statedb vm.StateDB , data []byte , maxSize uint32 , maxFragments uint8 , burner burn. Burner ) ([]byte , error ) {
411+ // charger is used to charge gas for reading fragments. If it is present activation is assumed, and activation checks are enforced.
412+ // Only pass a charger if activating the program.
413+ func getWasmFromRootStylus (statedb vm.StateDB , data []byte , maxSize uint32 , maxFragments uint8 , charger * fragmentReadCharger ) ([]byte , error ) {
359414 root , err := state .NewStylusRoot (data )
360415 if err != nil {
361416 return nil , err
362417 }
363418
364- if burner != nil {
419+ if charger != nil {
365420 if root .DecompressedLength > maxSize {
366421 return nil , fmt .Errorf ("invalid wasm: decompressedLength %d is greater then MaxWasmSize %d" , root .DecompressedLength , maxSize )
367422 }
@@ -376,11 +431,15 @@ func getWasmFromRootStylus(statedb vm.StateDB, data []byte, maxSize uint32, maxF
376431
377432 var compressedWasm []byte
378433 for _ , addr := range root .Addresses {
434+ // Fail before touching state unless the caller can afford a max-sized fragment
435+ // read; once the fragment length is known, charge only the actual read cost.
436+ if err := charger .canReadNewFragment (statedb , addr ); err != nil {
437+ return nil , err
438+ }
439+
379440 fragCode := statedb .GetCode (addr )
380- if burner != nil {
381- if err := chargeFragmentReadGas (burner , statedb , addr , uint64 (len (fragCode ))); err != nil {
382- return nil , err
383- }
441+ if err := charger .chargeForReadingFragment (statedb , addr , uint64 (len (fragCode ))); err != nil {
442+ return nil , err
384443 }
385444
386445 payload , err := state .StripStylusFragmentPrefix (fragCode )
@@ -408,31 +467,24 @@ func getWasmFromRootStylus(statedb vm.StateDB, data []byte, maxSize uint32, maxF
408467 return wasm , nil
409468}
410469
411- // chargeFragmentReadGas charges EXTCODECOPY-style gas for reading fragment code.
412- func chargeFragmentReadGas (burner burn.Burner , statedb vm.StateDB , addr common.Address , codeSize uint64 ) error {
413- // charge access gas
470+ func fragmentReadGasCost (warm bool , codeSize uint64 ) (multigas.MultiGas , error ) {
414471 var cost multigas.MultiGas
415- if statedb . AddressInAccessList ( addr ) {
472+ if warm {
416473 cost = multigas .ComputationGas (gethParams .WarmStorageReadCostEIP2929 )
417474 } else {
418- statedb .AddAddressToAccessList (addr )
419475 cost = multigas .StorageAccessReadGas (gethParams .ColdAccountAccessCostEIP2929 )
420476 }
421- // charge copy gas
422477 words := ToWordSize (codeSize )
423478 copyGas , overflow := gethMath .SafeMul (words , gethParams .CopyGas )
424479 if overflow {
425- log .Trace ("fragment copy gas overflow" , "address" , addr , " codeSize" , codeSize , "words" , words , "copyGas" , gethParams .CopyGas )
426- return vm .ErrGasUintOverflow
480+ log .Trace ("fragment copy gas overflow" , "codeSize" , codeSize , "words" , words , "copyGas" , gethParams .CopyGas )
481+ return multigas . ZeroGas (), vm .ErrGasUintOverflow
427482 }
428483 if cost , overflow = cost .SafeIncrement (multigas .ResourceKindStorageAccessRead , copyGas ); overflow {
429- log .Trace ("fragment copy gas overflow" , "address" , addr , "codeSize" , codeSize , "copyGas" , copyGas )
430- return vm .ErrGasUintOverflow
431- }
432- if err := burner .BurnMultiGas (cost ); err != nil {
433- return err
484+ log .Trace ("fragment copy gas overflow" , "codeSize" , codeSize , "copyGas" , copyGas )
485+ return multigas .ZeroGas (), vm .ErrGasUintOverflow
434486 }
435- return nil
487+ return cost , nil
436488}
437489
438490func getStylusCompressionDict (id byte ) (arbcompress.Dictionary , error ) {
0 commit comments