@@ -6,10 +6,12 @@ package programs
66import (
77 "errors"
88 "fmt"
9+ "math"
910 "math/big"
1011
1112 "github.com/ethereum/go-ethereum/arbitrum/multigas"
1213 "github.com/ethereum/go-ethereum/common"
14+ gethMath "github.com/ethereum/go-ethereum/common/math"
1315 "github.com/ethereum/go-ethereum/core"
1416 "github.com/ethereum/go-ethereum/core/state"
1517 "github.com/ethereum/go-ethereum/core/vm"
@@ -19,6 +21,7 @@ import (
1921
2022 "github.com/offchainlabs/nitro/arbcompress"
2123 "github.com/offchainlabs/nitro/arbos/addressSet"
24+ "github.com/offchainlabs/nitro/arbos/burn"
2225 "github.com/offchainlabs/nitro/arbos/storage"
2326 "github.com/offchainlabs/nitro/arbos/util"
2427 "github.com/offchainlabs/nitro/arbutil"
@@ -113,7 +116,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runCtx *c
113116 // already activated and up to date
114117 return 0 , codeHash , common.Hash {}, nil , false , ProgramUpToDateError ()
115118 }
116- wasm , err := getWasm (statedb , address , params )
119+ wasm , err := getWasm (statedb , address , params , burner )
117120 if err != nil {
118121 return 0 , codeHash , common.Hash {}, nil , false , err
119122 }
@@ -290,20 +293,31 @@ func attributeWasmComputation(contract *vm.Contract, startingGas uint64) {
290293 }
291294}
292295
296+ // toWordSize returns the ceiled word size required for memory expansion.
297+ func ToWordSize (size uint64 ) uint64 {
298+ if size > math .MaxUint64 - 31 {
299+ return math .MaxUint64 / 32 + 1
300+ }
301+
302+ return (size + 31 ) / 32
303+ }
304+
293305func evmMemoryCost (size uint64 ) uint64 {
294306 // It would take 100GB to overflow this calculation, so no need to worry about that
295- words := (size + 31 ) / 32
307+ words := ToWordSize (size )
296308 linearCost := words * gethParams .MemoryGas
297309 squareCost := (words * words ) / gethParams .QuadCoeffDiv
298310 return linearCost + squareCost
299311}
300312
301- func getWasm (statedb vm.StateDB , program common.Address , params * StylusParams ) ([]byte , error ) {
313+ func getWasm (statedb vm.StateDB , program common.Address , params * StylusParams , burner burn. Burner ) ([]byte , error ) {
302314 prefixedWasm := statedb .GetCode (program )
303- return getWasmFromContractCode (statedb , prefixedWasm , params , true )
315+ return getWasmFromContractCode (statedb , prefixedWasm , params , burner )
304316}
305317
306- func getWasmFromContractCode (statedb vm.StateDB , prefixedWasm []byte , params * StylusParams , isActivation bool ) ([]byte , error ) {
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 ) {
307321 if len (prefixedWasm ) == 0 {
308322 return nil , ProgramNotWasmError ()
309323 }
@@ -314,7 +328,7 @@ func getWasmFromContractCode(statedb vm.StateDB, prefixedWasm []byte, params *St
314328
315329 if params .arbosVersion >= gethParams .ArbosVersion_StylusContractLimit {
316330 if state .IsStylusRootProgramPrefix (prefixedWasm ) {
317- return getWasmFromRootStylus (statedb , prefixedWasm , params .MaxWasmSize , params .MaxFragmentCount , isActivation )
331+ return getWasmFromRootStylus (statedb , prefixedWasm , params .MaxWasmSize , params .MaxFragmentCount , burner )
318332 }
319333
320334 if state .IsStylusFragmentPrefix (prefixedWasm ) {
@@ -339,13 +353,15 @@ func getWasmFromClassicStylus(data []byte, maxSize uint32) ([]byte, error) {
339353 return arbcompress .DecompressWithDictionary (wasm , int (maxSize ), dict )
340354}
341355
342- func getWasmFromRootStylus (statedb vm.StateDB , data []byte , maxSize uint32 , maxFragments uint8 , isActivation bool ) ([]byte , error ) {
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 ) {
343359 root , err := state .NewStylusRoot (data )
344360 if err != nil {
345361 return nil , err
346362 }
347363
348- if isActivation {
364+ if burner != nil {
349365 if root .DecompressedLength > maxSize {
350366 return nil , fmt .Errorf ("invalid wasm: decompressedLength %d is greater then MaxWasmSize %d" , root .DecompressedLength , maxSize )
351367 }
@@ -361,6 +377,11 @@ func getWasmFromRootStylus(statedb vm.StateDB, data []byte, maxSize uint32, maxF
361377 var compressedWasm []byte
362378 for _ , addr := range root .Addresses {
363379 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+ }
384+ }
364385
365386 payload , err := state .StripStylusFragmentPrefix (fragCode )
366387 if err != nil {
@@ -387,6 +408,33 @@ func getWasmFromRootStylus(statedb vm.StateDB, data []byte, maxSize uint32, maxF
387408 return wasm , nil
388409}
389410
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
414+ var cost multigas.MultiGas
415+ if statedb .AddressInAccessList (addr ) {
416+ cost = multigas .ComputationGas (gethParams .WarmStorageReadCostEIP2929 )
417+ } else {
418+ statedb .AddAddressToAccessList (addr )
419+ cost = multigas .StorageAccessGas (gethParams .ColdAccountAccessCostEIP2929 )
420+ }
421+ // charge copy gas
422+ words := ToWordSize (codeSize )
423+ copyGas , overflow := gethMath .SafeMul (words , gethParams .CopyGas )
424+ if overflow {
425+ log .Trace ("fragment copy gas overflow" , "address" , addr , "codeSize" , codeSize , "words" , words , "copyGas" , gethParams .CopyGas )
426+ return vm .ErrGasUintOverflow
427+ }
428+ if cost , overflow = cost .SafeIncrement (multigas .ResourceKindStorageAccess , 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
434+ }
435+ return nil
436+ }
437+
390438func getStylusCompressionDict (id byte ) (arbcompress.Dictionary , error ) {
391439 switch id {
392440 case 0 :
0 commit comments