diff --git a/arbos/programs/memory.go b/arbos/programs/memory.go index 2afa3984b..60da3c076 100644 --- a/arbos/programs/memory.go +++ b/arbos/programs/memory.go @@ -21,16 +21,6 @@ func NewMemoryModel(freePages uint16, pageGas uint16) *MemoryModel { } } -func (p Programs) memoryModel() (*MemoryModel, error) { - freePages, err := p.FreePages() - if err != nil { - return nil, err - } - pageGas, err := p.PageGas() - - return NewMemoryModel(freePages, pageGas), err -} - // Determines the gas cost of allocating `new` pages given `open` are active and `ever` have ever been. func (model *MemoryModel) GasCost(new, open, ever uint16) uint64 { newOpen := arbmath.SaturatingUAdd(open, new) diff --git a/arbos/programs/params.go b/arbos/programs/params.go new file mode 100644 index 000000000..54056ffd7 --- /dev/null +++ b/arbos/programs/params.go @@ -0,0 +1,126 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbos/storage" + am "github.com/offchainlabs/nitro/util/arbmath" +) + +const MaxWasmSize = 128 * 1024 // max decompressed wasm size (programs are also bounded by compressed size) +const initialStackDepth = 4 * 65536 // 4 page stack. +const InitialFreePages = 2 // 2 pages come free (per tx). +const InitialPageGas = 1000 // linear cost per allocation. +const initialPageRamp = 620674314 // targets 8MB costing 32 million gas, minus the linear term. +const initialPageLimit = 128 // reject wasms with memories larger than 8MB. +const initialInkPrice = 10000 // 1 evm gas buys 10k ink. +const initialMinInitGas = 0 // assume pricer is correct (update in case of emergency) +const initialExpiryDays = 365 // deactivate after 1 year. +const initialKeepaliveDays = 31 // wait a month before allowing reactivation +const initialInitTableBits = 7 // cache the last 128 programs +const initialTrieTableBits = 11 // cache the hottest 1024 slots + +// This struct exists to collect the many Stylus configuration parameters into a single word. +// The items here must only be modified in ArbOwner precompile methods (or in ArbOS upgrades). +type StylusParams struct { + backingStorage *storage.Storage + Version uint16 // must only be changed during ArbOS upgrades + InkPrice uint24 + MaxStackDepth uint32 + FreePages uint16 + PageGas uint16 + PageRamp uint64 + PageLimit uint16 + MinInitGas uint16 + ExpiryDays uint16 + KeepaliveDays uint16 + InitTableBits uint8 + TrieTableBits uint8 +} + +// Provides a view of the Stylus parameters. Call Save() to persist. +// Note: this method never returns nil. +func (p Programs) Params() (*StylusParams, error) { + sto := p.backingStorage.OpenSubStorage(paramsKey) + + // assume read is warm due to the frequency of access + if err := sto.Burner().Burn(params.WarmStorageReadCostEIP2929); err != nil { + return &StylusParams{}, err + } + + // paid for the read above + word := sto.GetFree(common.Hash{}) + data := word[:] + take := func(count int) []byte { + value := data[:count] + data = data[count:] + return value + } + + return &StylusParams{ + backingStorage: sto, + Version: am.BytesToUint16(take(2)), + InkPrice: am.BytesToUint24(take(3)), + MaxStackDepth: am.BytesToUint32(take(4)), + FreePages: am.BytesToUint16(take(2)), + PageGas: am.BytesToUint16(take(2)), + PageRamp: am.BytesToUint(take(8)), + PageLimit: am.BytesToUint16(take(2)), + MinInitGas: am.BytesToUint16(take(2)), + ExpiryDays: am.BytesToUint16(take(2)), + KeepaliveDays: am.BytesToUint16(take(2)), + InitTableBits: am.BytesToUint8(take(1)), + TrieTableBits: am.BytesToUint8(take(1)), + }, nil +} + +// Writes the params to permanent storage. +func (p *StylusParams) Save() error { + if p.backingStorage == nil { + log.Error("tried to Save invalid StylusParams") + return errors.New("invalid StylusParams") + } + + data := am.ConcatByteSlices( + am.Uint16ToBytes(p.Version), + am.Uint24ToBytes(p.InkPrice), + am.Uint32ToBytes(p.MaxStackDepth), + am.Uint16ToBytes(p.FreePages), + am.Uint16ToBytes(p.PageGas), + am.UintToBytes(p.PageRamp), + am.Uint16ToBytes(p.PageLimit), + am.Uint16ToBytes(p.MinInitGas), + am.Uint16ToBytes(p.ExpiryDays), + am.Uint16ToBytes(p.KeepaliveDays), + am.Uint8ToBytes(p.InitTableBits), + am.Uint8ToBytes(p.TrieTableBits), + ) + word := common.Hash{} + copy(word[:], data) // right-pad with zeros + return p.backingStorage.SetByUint64(0, word) +} + +func initStylusParams(sto *storage.Storage) { + params := &StylusParams{ + backingStorage: sto, + Version: 1, + InkPrice: initialInkPrice, + MaxStackDepth: initialStackDepth, + FreePages: InitialFreePages, + PageGas: InitialPageGas, + PageRamp: initialPageRamp, + PageLimit: initialPageLimit, + MinInitGas: initialMinInitGas, + ExpiryDays: initialExpiryDays, + KeepaliveDays: initialKeepaliveDays, + InitTableBits: initialInitTableBits, + TrieTableBits: initialTrieTableBits, + } + _ = params.Save() +} diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index d7e8822ad..a10376522 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -9,7 +9,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" @@ -25,16 +24,6 @@ type Programs struct { programs *storage.Storage moduleHashes *storage.Storage dataPricer *DataPricer - inkPrice storage.StorageBackedUint24 - maxStackDepth storage.StorageBackedUint32 - freePages storage.StorageBackedUint16 - pageGas storage.StorageBackedUint16 - pageRamp storage.StorageBackedUint64 - pageLimit storage.StorageBackedUint16 - minInitGas storage.StorageBackedUint16 - expiryDays storage.StorageBackedUint16 - keepaliveDays storage.StorageBackedUint16 - version storage.StorageBackedUint16 // Must only be changed during ArbOS upgrades } type Program struct { @@ -48,22 +37,10 @@ type Program struct { type uint24 = arbmath.Uint24 -var programDataKey = []byte{0} -var moduleHashesKey = []byte{1} -var dataPricerKey = []byte{2} - -const ( - versionOffset uint64 = iota - inkPriceOffset - maxStackDepthOffset - freePagesOffset - pageGasOffset - pageRampOffset - pageLimitOffset - minInitGasOffset - expiryDaysOffset - keepaliveDaysOffset -) +var paramsKey = []byte{0} +var programDataKey = []byte{1} +var moduleHashesKey = []byte{2} +var dataPricerKey = []byte{3} var ErrProgramActivation = errors.New("program activation failed") @@ -73,38 +50,8 @@ var ProgramExpiredError func(age uint64) error var ProgramUpToDateError func() error var ProgramKeepaliveTooSoon func(age uint64) error -const MaxWasmSize = 128 * 1024 -const initialFreePages = 2 -const initialPageGas = 1000 -const initialPageRamp = 620674314 // targets 8MB costing 32 million gas, minus the linear term. -const initialPageLimit = 128 // reject wasms with memories larger than 8MB. -const initialInkPrice = 10000 // 1 evm gas buys 10k ink. -const initialMinCallGas = 0 // assume pricer is correct -const initialExpiryDays = 365 // deactivate after 1 year. -const initialKeepaliveDays = 31 // wait a month before allowing reactivation - func Initialize(sto *storage.Storage) { - inkPrice := sto.OpenStorageBackedUint24(inkPriceOffset) - maxStackDepth := sto.OpenStorageBackedUint32(maxStackDepthOffset) - freePages := sto.OpenStorageBackedUint16(freePagesOffset) - pageGas := sto.OpenStorageBackedUint16(pageGasOffset) - pageRamp := sto.OpenStorageBackedUint64(pageRampOffset) - pageLimit := sto.OpenStorageBackedUint16(pageLimitOffset) - minInitGas := sto.OpenStorageBackedUint16(minInitGasOffset) - expiryDays := sto.OpenStorageBackedUint16(expiryDaysOffset) - keepaliveDays := sto.OpenStorageBackedUint16(keepaliveDaysOffset) - version := sto.OpenStorageBackedUint16(versionOffset) - _ = inkPrice.Set(initialInkPrice) - _ = maxStackDepth.Set(math.MaxUint32) - _ = freePages.Set(initialFreePages) - _ = pageGas.Set(initialPageGas) - _ = pageRamp.Set(initialPageRamp) - _ = pageLimit.Set(initialPageLimit) - _ = minInitGas.Set(initialMinCallGas) - _ = expiryDays.Set(initialExpiryDays) - _ = keepaliveDays.Set(initialKeepaliveDays) - _ = version.Set(1) - + initStylusParams(sto.OpenSubStorage(paramsKey)) initDataPricer(sto.OpenSubStorage(dataPricerKey)) } @@ -114,97 +61,7 @@ func Open(sto *storage.Storage) *Programs { programs: sto.OpenSubStorage(programDataKey), moduleHashes: sto.OpenSubStorage(moduleHashesKey), dataPricer: openDataPricer(sto.OpenSubStorage(dataPricerKey)), - inkPrice: sto.OpenStorageBackedUint24(inkPriceOffset), - maxStackDepth: sto.OpenStorageBackedUint32(maxStackDepthOffset), - freePages: sto.OpenStorageBackedUint16(freePagesOffset), - pageGas: sto.OpenStorageBackedUint16(pageGasOffset), - pageRamp: sto.OpenStorageBackedUint64(pageRampOffset), - pageLimit: sto.OpenStorageBackedUint16(pageLimitOffset), - minInitGas: sto.OpenStorageBackedUint16(minInitGasOffset), - expiryDays: sto.OpenStorageBackedUint16(expiryDaysOffset), - keepaliveDays: sto.OpenStorageBackedUint16(keepaliveDaysOffset), - version: sto.OpenStorageBackedUint16(versionOffset), - } -} - -func (p Programs) StylusVersion() (uint16, error) { - return p.version.Get() -} - -func (p Programs) InkPrice() (uint24, error) { - return p.inkPrice.Get() -} - -func (p Programs) SetInkPrice(value uint32) error { - ink, err := arbmath.IntToUint24(value) - if err != nil || ink == 0 { - return errors.New("ink price must be a positive uint24") } - return p.inkPrice.Set(ink) -} - -func (p Programs) MaxStackDepth() (uint32, error) { - return p.maxStackDepth.Get() -} - -func (p Programs) SetMaxStackDepth(depth uint32) error { - return p.maxStackDepth.Set(depth) -} - -func (p Programs) FreePages() (uint16, error) { - return p.freePages.Get() -} - -func (p Programs) SetFreePages(pages uint16) error { - return p.freePages.Set(pages) -} - -func (p Programs) PageGas() (uint16, error) { - return p.pageGas.Get() -} - -func (p Programs) SetPageGas(gas uint16) error { - return p.pageGas.Set(gas) -} - -func (p Programs) PageRamp() (uint64, error) { - return p.pageRamp.Get() -} - -func (p Programs) SetPageRamp(ramp uint64) error { - return p.pageRamp.Set(ramp) -} - -func (p Programs) PageLimit() (uint16, error) { - return p.pageLimit.Get() -} - -func (p Programs) SetPageLimit(limit uint16) error { - return p.pageLimit.Set(limit) -} - -func (p Programs) MinInitGas() (uint16, error) { - return p.minInitGas.Get() -} - -func (p Programs) SetMinInitGas(gas uint16) error { - return p.minInitGas.Set(gas) -} - -func (p Programs) ExpiryDays() (uint16, error) { - return p.expiryDays.Get() -} - -func (p Programs) SetExpiryDays(days uint16) error { - return p.expiryDays.Set(days) -} - -func (p Programs) KeepaliveDays() (uint16, error) { - return p.keepaliveDays.Get() -} - -func (p Programs) SetKeepaliveDays(days uint16) error { - return p.keepaliveDays.Set(days) } func (p Programs) DataPricer() *DataPricer { @@ -219,11 +76,13 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode burner := p.programs.Burner() time := evm.Context.Time - stylusVersion, err := p.StylusVersion() + params, err := p.Params() if err != nil { return 0, codeHash, common.Hash{}, nil, false, err } - currentVersion, expired, err := p.programExists(codeHash, time) + + stylusVersion := params.Version + currentVersion, expired, err := p.programExists(codeHash, time, params) if err != nil { return 0, codeHash, common.Hash{}, nil, false, err } @@ -237,11 +96,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode } // require the program's footprint not exceed the remaining memory budget - pageLimit, err := p.PageLimit() - if err != nil { - return 0, codeHash, common.Hash{}, nil, false, err - } - pageLimit = arbmath.SaturatingUSub(pageLimit, statedb.GetStylusPagesOpen()) + pageLimit := arbmath.SaturatingUSub(params.PageLimit, statedb.GetStylusPagesOpen()) info, err := activateProgram(statedb, address, wasm, pageLimit, stylusVersion, debugMode, burner) if err != nil { @@ -287,18 +142,20 @@ func (p Programs) CallProgram( contract := scope.Contract debugMode := evm.ChainConfig().DebugMode() - program, err := p.getProgram(contract.CodeHash, evm.Context.Time) + params, err := p.Params() if err != nil { return nil, err } - moduleHash, err := p.moduleHashes.Get(contract.CodeHash) + + program, err := p.getProgram(contract.CodeHash, evm.Context.Time, params) if err != nil { return nil, err } - params, err := p.goParams(program.version, debugMode) + moduleHash, err := p.moduleHashes.Get(contract.CodeHash) if err != nil { return nil, err } + goParams := p.goParams(program.version, debugMode, params) l1BlockNumber, err := evm.ProcessingHook.L1BlockNumber(evm.Context) if err != nil { return nil, err @@ -306,16 +163,9 @@ func (p Programs) CallProgram( // pay for program init open, ever := statedb.GetStylusPages() - model, err := p.memoryModel() - if err != nil { - return nil, err - } + model := NewMemoryModel(params.FreePages, params.PageGas) memoryCost := model.GasCost(program.footprint, open, ever) - minInitGas, err := p.MinInitGas() - if err != nil { - return nil, err - } - callCost := uint64(program.initGas) + uint64(minInitGas) + callCost := uint64(program.initGas) + uint64(params.MinInitGas) cost := common.SaturatingUAdd(memoryCost, callCost) if err := contract.BurnGas(cost); err != nil { return nil, err @@ -345,7 +195,7 @@ func (p Programs) CallProgram( } return callProgram( address, moduleHash, scope, statedb, interpreter, - tracingInfo, calldata, evmData, params, model, + tracingInfo, calldata, evmData, goParams, model, ) } @@ -371,7 +221,7 @@ func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) { return arbcompress.DecompressWithDictionary(wasm, MaxWasmSize, dict) } -func (p Programs) getProgram(codeHash common.Hash, time uint64) (Program, error) { +func (p Programs) getProgram(codeHash common.Hash, time uint64, params *StylusParams) (Program, error) { data, err := p.programs.Get(codeHash) if err != nil { return Program{}, err @@ -388,19 +238,13 @@ func (p Programs) getProgram(codeHash common.Hash, time uint64) (Program, error) } // check that the program is up to date - stylusVersion, err := p.StylusVersion() - if err != nil { - return program, err - } + stylusVersion := params.Version if program.version != stylusVersion { return program, ProgramNeedsUpgradeError(program.version, stylusVersion) } // ensure the program hasn't expired - expiryDays, err := p.ExpiryDays() - if err != nil { - return program, err - } + expiryDays := params.ExpiryDays age := time - program.activatedAt expirySeconds := arbmath.DaysToSeconds(expiryDays) if age > expirySeconds { @@ -420,39 +264,29 @@ func (p Programs) setProgram(codehash common.Hash, program Program) error { return p.programs.Set(codehash, data) } -func (p Programs) programExists(codeHash common.Hash, time uint64) (uint16, bool, error) { +func (p Programs) programExists(codeHash common.Hash, time uint64, params *StylusParams) (uint16, bool, error) { data, err := p.programs.Get(codeHash) if err != nil { return 0, false, err } - expiryDays, err := p.ExpiryDays() - if err != nil { - return 0, false, err - } version := arbmath.BytesToUint16(data[:2]) activatedAt := arbmath.BytesToUint(data[10:18]) - expired := time-activatedAt > arbmath.DaysToSeconds(expiryDays) + expired := time-activatedAt > arbmath.DaysToSeconds(params.ExpiryDays) return version, expired, err } -func (p Programs) ProgramKeepalive(codeHash common.Hash, time uint64) (*big.Int, error) { - program, err := p.getProgram(codeHash, time) - if err != nil { - return nil, err - } - keepaliveDays, err := p.KeepaliveDays() +func (p Programs) ProgramKeepalive(codeHash common.Hash, time uint64, params *StylusParams) (*big.Int, error) { + program, err := p.getProgram(codeHash, time, params) if err != nil { return nil, err } + keepaliveDays := params.KeepaliveDays if program.secondsLeft < arbmath.DaysToSeconds(keepaliveDays) { return nil, ProgramKeepaliveTooSoon(time - program.activatedAt) } - stylusVersion, err := p.StylusVersion() - if err != nil { - return nil, err - } + stylusVersion := params.Version if program.version != stylusVersion { return nil, ProgramNeedsUpgradeError(program.version, stylusVersion) } @@ -467,29 +301,29 @@ func (p Programs) ProgramKeepalive(codeHash common.Hash, time uint64) (*big.Int, } -func (p Programs) CodehashVersion(codeHash common.Hash, time uint64) (uint16, error) { - program, err := p.getProgram(codeHash, time) +func (p Programs) CodehashVersion(codeHash common.Hash, time uint64, params *StylusParams) (uint16, error) { + program, err := p.getProgram(codeHash, time, params) if err != nil { return 0, err } return program.version, nil } -func (p Programs) ProgramTimeLeft(codeHash common.Hash, time uint64) (uint64, error) { - program, err := p.getProgram(codeHash, time) +func (p Programs) ProgramTimeLeft(codeHash common.Hash, time uint64, params *StylusParams) (uint64, error) { + program, err := p.getProgram(codeHash, time, params) if err != nil { return 0, err } return program.secondsLeft, nil } -func (p Programs) ProgramInitGas(codeHash common.Hash, time uint64) (uint32, error) { - program, err := p.getProgram(codeHash, time) +func (p Programs) ProgramInitGas(codeHash common.Hash, time uint64, params *StylusParams) (uint32, error) { + program, err := p.getProgram(codeHash, time, params) return uint32(program.initGas), err } -func (p Programs) ProgramMemoryFootprint(codeHash common.Hash, time uint64) (uint16, error) { - program, err := p.getProgram(codeHash, time) +func (p Programs) ProgramMemoryFootprint(codeHash common.Hash, time uint64, params *StylusParams) (uint16, error) { + program, err := p.getProgram(codeHash, time, params) return program.footprint, err } @@ -500,25 +334,16 @@ type goParams struct { debugMode uint32 } -func (p Programs) goParams(version uint16, debug bool) (*goParams, error) { - maxDepth, err := p.MaxStackDepth() - if err != nil { - return nil, err - } - inkPrice, err := p.InkPrice() - if err != nil { - return nil, err - } - +func (p Programs) goParams(version uint16, debug bool, params *StylusParams) *goParams { config := &goParams{ version: version, - maxDepth: maxDepth, - inkPrice: inkPrice, + maxDepth: params.MaxStackDepth, + inkPrice: params.InkPrice, } if debug { config.debugMode = 1 } - return config, nil + return config } type evmData struct { diff --git a/arbos/storage/storage.go b/arbos/storage/storage.go index e0a10c7ff..a94f0651c 100644 --- a/arbos/storage/storage.go +++ b/arbos/storage/storage.go @@ -113,7 +113,12 @@ func (store *Storage) Get(key common.Hash) (common.Hash, error) { if info := store.burner.TracingInfo(); info != nil { info.RecordStorageGet(key) } - return store.db.GetState(store.account, mapAddress(store.storageKey, key)), nil + return store.GetFree(key), nil +} + +// Gets a storage slot for free. Dangerous due to DoS potential. +func (store *Storage) GetFree(key common.Hash) common.Hash { + return store.db.GetState(store.account, mapAddress(store.storageKey, key)) } func (store *Storage) GetStorageSlot(key common.Hash) common.Hash { diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index e85909bce..a625c62be 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -11,6 +11,7 @@ import ( "math/big" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" @@ -163,48 +164,97 @@ func (con ArbOwner) ReleaseL1PricerSurplusFunds(c ctx, evm mech, maxWeiToRelease } // Sets the amount of ink 1 gas buys -func (con ArbOwner) SetInkPrice(c ctx, evm mech, ink uint32) error { - return c.State.Programs().SetInkPrice(ink) +func (con ArbOwner) SetInkPrice(c ctx, evm mech, inkPrice uint32) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + ink, err := arbmath.IntToUint24(inkPrice) + if err != nil || ink == 0 { + return errors.New("ink price must be a positive uint24") + } + params.InkPrice = ink + return params.Save() } // Sets the maximum depth (in wasm words) a wasm stack may grow func (con ArbOwner) SetWasmMaxStackDepth(c ctx, evm mech, depth uint32) error { - return c.State.Programs().SetMaxStackDepth(depth) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.MaxStackDepth = depth + return params.Save() } // Gets the number of free wasm pages a tx gets func (con ArbOwner) SetWasmFreePages(c ctx, evm mech, pages uint16) error { - return c.State.Programs().SetFreePages(pages) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.FreePages = pages + return params.Save() } // Sets the base cost of each additional wasm page func (con ArbOwner) SetWasmPageGas(c ctx, evm mech, gas uint16) error { - return c.State.Programs().SetPageGas(gas) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.PageGas = gas + return params.Save() } // Sets the ramp that drives exponential wasm memory costs func (con ArbOwner) SetWasmPageRamp(c ctx, evm mech, ramp uint64) error { - return c.State.Programs().SetPageRamp(ramp) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.PageRamp = ramp + return params.Save() } // Sets the initial number of pages a wasm may allocate func (con ArbOwner) SetWasmPageLimit(c ctx, evm mech, limit uint16) error { - return c.State.Programs().SetPageLimit(limit) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.PageLimit = limit + return params.Save() } // Sets the minimum cost to invoke a program func (con ArbOwner) SetWasmMinInitGas(c ctx, _ mech, gas uint16) error { - return c.State.Programs().SetMinInitGas(gas) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.MinInitGas = gas + return params.Save() } // Sets the number of days after which programs deactivate func (con ArbOwner) SetWasmExpiryDays(c ctx, _ mech, days uint16) error { - return c.State.Programs().SetExpiryDays(days) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.ExpiryDays = days + return params.Save() } // Sets the age a program must be to perform a keepalive func (con ArbOwner) SetWasmKeepaliveDays(c ctx, _ mech, days uint16) error { - return c.State.Programs().SetKeepaliveDays(days) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.KeepaliveDays = days + return params.Save() } func (con ArbOwner) SetChainConfig(c ctx, evm mech, serializedChainConfig []byte) error { diff --git a/precompiles/ArbWasm.go b/precompiles/ArbWasm.go index b79c8af6a..11602eed1 100644 --- a/precompiles/ArbWasm.go +++ b/precompiles/ArbWasm.go @@ -4,6 +4,8 @@ package precompiles import ( + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -47,7 +49,11 @@ func (con ArbWasm) ActivateProgram(c ctx, evm mech, value huge, program addr) (u // Extends a program's expiration date (reverts if too soon) func (con ArbWasm) CodehashKeepalive(c ctx, evm mech, value huge, codehash bytes32) error { - dataFee, err := c.State.Programs().ProgramKeepalive(codehash, evm.Context.Time) + params, err := c.State.Programs().Params() + if err != nil { + return err + } + dataFee, err := c.State.Programs().ProgramKeepalive(codehash, evm.Context.Time, params) if err != nil { return err } @@ -79,43 +85,71 @@ func (con ArbWasm) payActivationDataFee(c ctx, evm mech, value, dataFee huge) er // Gets the latest stylus version func (con ArbWasm) StylusVersion(c ctx, evm mech) (uint16, error) { - return c.State.Programs().StylusVersion() + params, err := c.State.Programs().Params() + return params.Version, err } // Gets the amount of ink 1 gas buys func (con ArbWasm) InkPrice(c ctx, _ mech) (uint32, error) { - ink, err := c.State.Programs().InkPrice() - return ink.ToUint32(), err + params, err := c.State.Programs().Params() + return params.InkPrice.ToUint32(), err } // Gets the wasm stack size limit func (con ArbWasm) MaxStackDepth(c ctx, _ mech) (uint32, error) { - return c.State.Programs().MaxStackDepth() + params, err := c.State.Programs().Params() + return params.MaxStackDepth, err } // Gets the number of free wasm pages a tx gets func (con ArbWasm) FreePages(c ctx, _ mech) (uint16, error) { - return c.State.Programs().FreePages() + params, err := c.State.Programs().Params() + return params.FreePages, err } // Gets the base cost of each additional wasm page func (con ArbWasm) PageGas(c ctx, _ mech) (uint16, error) { - return c.State.Programs().PageGas() + params, err := c.State.Programs().Params() + return params.PageGas, err } // Gets the ramp that drives exponential memory costs func (con ArbWasm) PageRamp(c ctx, _ mech) (uint64, error) { - return c.State.Programs().PageRamp() + params, err := c.State.Programs().Params() + return params.PageRamp, err } // Gets the maximum initial number of pages a wasm may allocate func (con ArbWasm) PageLimit(c ctx, _ mech) (uint16, error) { - return c.State.Programs().PageLimit() + params, err := c.State.Programs().Params() + return params.PageLimit, err +} + +// Gets the minimum cost to invoke a program +func (con ArbWasm) MinInitGas(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.MinInitGas, err +} + +// Gets the number of days after which programs deactivate +func (con ArbWasm) ExpiryDays(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.ExpiryDays, err +} + +// Gets the age a program must be to perform a keepalive +func (con ArbWasm) KeepaliveDays(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.KeepaliveDays, err } // Gets the stylus version that program with codehash was most recently compiled with func (con ArbWasm) CodehashVersion(c ctx, evm mech, codehash bytes32) (uint16, error) { - return c.State.Programs().CodehashVersion(codehash, evm.Context.Time) + params, err := c.State.Programs().Params() + if err != nil { + return 0, err + } + return c.State.Programs().CodehashVersion(codehash, evm.Context.Time, params) } // Gets the stylus version that program at addr was most recently compiled with @@ -129,42 +163,36 @@ func (con ArbWasm) ProgramVersion(c ctx, evm mech, program addr) (uint16, error) // Gets the cost to invoke the program (not including MinInitGas) func (con ArbWasm) ProgramInitGas(c ctx, evm mech, program addr) (uint32, error) { - codehash, err := c.GetCodeHash(program) + codehash, params, err := con.getCodeHash(c, program) if err != nil { return 0, err } - return c.State.Programs().ProgramInitGas(codehash, evm.Context.Time) + return c.State.Programs().ProgramInitGas(codehash, evm.Context.Time, params) } // Gets the footprint of program at addr func (con ArbWasm) ProgramMemoryFootprint(c ctx, evm mech, program addr) (uint16, error) { - codehash, err := c.GetCodeHash(program) + codehash, params, err := con.getCodeHash(c, program) if err != nil { return 0, err } - return c.State.Programs().ProgramMemoryFootprint(codehash, evm.Context.Time) + return c.State.Programs().ProgramMemoryFootprint(codehash, evm.Context.Time, params) } // Gets returns the amount of time remaining until the program expires func (con ArbWasm) ProgramTimeLeft(c ctx, evm mech, program addr) (uint64, error) { - codehash, err := c.GetCodeHash(program) + codehash, params, err := con.getCodeHash(c, program) if err != nil { return 0, err } - return c.State.Programs().ProgramTimeLeft(codehash, evm.Context.Time) + return c.State.Programs().ProgramTimeLeft(codehash, evm.Context.Time, params) } -// Gets the minimum cost to invoke a program -func (con ArbWasm) MinInitGas(c ctx, _ mech) (uint16, error) { - return c.State.Programs().MinInitGas() -} - -// Gets the number of days after which programs deactivate -func (con ArbWasm) ExpiryDays(c ctx, _ mech) (uint16, error) { - return c.State.Programs().ExpiryDays() -} - -// Gets the age a program must be to perform a keepalive -func (con ArbWasm) KeepaliveDays(c ctx, _ mech) (uint16, error) { - return c.State.Programs().KeepaliveDays() +func (con ArbWasm) getCodeHash(c ctx, program addr) (hash, *programs.StylusParams, error) { + params, err := c.State.Programs().Params() + if err != nil { + return common.Hash{}, params, err + } + codehash, err := c.GetCodeHash(program) + return codehash, params, err } diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 8dc63d1f9..864932745 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -848,7 +848,7 @@ func testMemory(t *testing.T, jit bool) { EnsureTxFailed(t, ctx, l2client, tx) } - model := programs.NewMemoryModel(2, 1000) + model := programs.NewMemoryModel(programs.InitialFreePages, programs.InitialPageGas) // expand to 128 pages, retract, then expand again to 128. // - multicall takes 1 page to init, and then 1 more at runtime. @@ -860,9 +860,9 @@ func testMemory(t *testing.T, jit bool) { receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) gasCost := receipt.GasUsedForL2() memCost := model.GasCost(128, 0, 0) + model.GasCost(126, 2, 128) - logical := uint64(32000000 + 126*1000) + logical := uint64(32000000 + 126*programs.InitialPageGas) if !arbmath.WithinRange(gasCost, memCost, memCost+2e5) || !arbmath.WithinRange(gasCost, logical, logical+2e5) { - Fatal(t, "unexpected cost", gasCost, memCost) + Fatal(t, "unexpected cost", gasCost, memCost, logical) } // check that we'd normally run out of gas diff --git a/util/arbmath/bits.go b/util/arbmath/bits.go index 2cc109588..7782a0cd8 100644 --- a/util/arbmath/bits.go +++ b/util/arbmath/bits.go @@ -52,6 +52,11 @@ func Uint16ToBytes(value uint16) []byte { return result } +// casts a uint8 to its big-endian representation +func Uint8ToBytes(value uint8) []byte { + return []byte{value} +} + // BytesToUint creates a uint64 from its big-endian representation func BytesToUint(value []byte) uint64 { return binary.BigEndian.Uint64(value) @@ -67,6 +72,11 @@ func BytesToUint16(value []byte) uint16 { return binary.BigEndian.Uint16(value) } +// creates a uint8 from its big-endian representation +func BytesToUint8(value []byte) uint8 { + return value[0] +} + // BoolToUint32 assigns a nonzero value when true func BoolToUint32(value bool) uint32 { if value {