Skip to content

Commit 8bbc36d

Browse files
committed
Implement Stylus Contract limit increase
1 parent b4178a0 commit 8bbc36d

File tree

17 files changed

+900
-48
lines changed

17 files changed

+900
-48
lines changed

arbcompress/native.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func Compress(input []byte, level uint32, dictionary Dictionary) ([]byte, error)
4141

4242
status := C.brotli_compress(inbuf, outbuf, C.Dictionary(dictionary), u32(level))
4343
if status != C.BrotliStatus_Success {
44-
return nil, fmt.Errorf("failed decompression: %d", status)
44+
return nil, fmt.Errorf("failed compression: %d", status)
4545
}
4646
output = output[:*outbuf.len]
4747
return output, nil

arbos/arbosState/arbosstate.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,10 @@ func (state *ArbosState) UpgradeArbosVersion(
446446
// these versions are left to Orbit chains for custom upgrades.
447447

448448
case params.ArbosVersion_60:
449-
// no change state needed
449+
p, err := state.Programs().Params()
450+
ensure(err)
451+
ensure(p.UpgradeToArbosVersion(nextArbosVersion))
452+
ensure(p.Save())
450453
default:
451454
return fmt.Errorf(
452455
"the chain is upgrading to unsupported ArbOS version %v, %w",

arbos/programs/native.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func activateProgramInternal(
262262
}
263263

264264
// getCompiledProgram gets compiled wasm for all targets and recompiles missing ones.
265-
func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codehash common.Hash, maxWasmSize uint32, pagelimit uint16, time uint64, debugMode bool, program Program, runCtx *core.MessageRunContext) (map[rawdb.WasmTarget][]byte, error) {
265+
func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codehash common.Hash, params *StylusParams, time uint64, debugMode bool, program Program, runCtx *core.MessageRunContext) (map[rawdb.WasmTarget][]byte, error) {
266266
targets := runCtx.WasmTargets()
267267
// even though we need only asm for local target, make sure that all configured targets are available as they are needed during multi-target recording of a program call
268268
asmMap, missingTargets, err := statedb.ActivatedAsmMap(targets, moduleHash)
@@ -277,7 +277,7 @@ func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLo
277277
}
278278

279279
// addressForLogging may be empty or may not correspond to the code, so we need to be careful to use the code passed in separately
280-
wasm, err := getWasmFromContractCode(code, maxWasmSize)
280+
wasm, err := getWasmFromContractCode(statedb, code, params, false)
281281
if err != nil {
282282
log.Error("Failed to reactivate program: getWasm", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err)
283283
return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err)
@@ -290,7 +290,7 @@ func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLo
290290
// we know program is activated, so it must be in correct version and not use too much memory
291291
moduleActivationMandatory := false
292292
// compile only missing targets
293-
info, newlyBuilt, err := activateProgramInternal(addressForLogging, codehash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas, missingTargets, moduleActivationMandatory)
293+
info, newlyBuilt, err := activateProgramInternal(addressForLogging, codehash, wasm, params.PageLimit, program.version, zeroArbosVersion, debugMode, &zeroGas, missingTargets, moduleActivationMandatory)
294294
if err != nil {
295295
log.Error("failed to reactivate program", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err)
296296
return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err)
@@ -399,7 +399,7 @@ func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out
399399
func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codehash common.Hash, params *StylusParams, debug bool, time uint64, runCtx *core.MessageRunContext) {
400400
if runCtx.IsCommitMode() {
401401
// address is only used for logging
402-
asmMap, err := getCompiledProgram(db, module, addressForLogging, code, codehash, params.MaxWasmSize, params.PageLimit, time, debug, program, runCtx)
402+
asmMap, err := getCompiledProgram(db, module, addressForLogging, code, codehash, params, time, debug, program, runCtx)
403403
var ok bool
404404
var localAsm []byte
405405
if asmMap != nil {

arbos/programs/params.go

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@ const InitialPageGas = 1000 // linear cost per allocation.
2424
const initialPageRamp = 620674314 // targets 8MB costing 32 million gas, minus the linear term.
2525
const initialPageLimit = 128 // reject wasms with memories larger than 8MB.
2626
const initialInkPrice = 10000 // 1 evm gas buys 10k ink.
27-
const initialMinInitGas = 72 // charge 72 * 128 = 9216 gas.
28-
const initialMinCachedGas = 11 // charge 11 * 32 = 352 gas.
29-
const initialInitCostScalar = 50 // scale costs 1:1 (100%)
30-
const initialCachedCostScalar = 50 // scale costs 1:1 (100%)
31-
const initialExpiryDays = 365 // deactivate after 1 year.
32-
const initialKeepaliveDays = 31 // wait a month before allowing reactivation.
33-
const initialRecentCacheSize = 32 // cache the 32 most recent programs.
27+
const initialMaxFragmentCount = 2
28+
const initialMinInitGas = 72 // charge 72 * 128 = 9216 gas.
29+
const initialMinCachedGas = 11 // charge 11 * 32 = 352 gas.
30+
const initialInitCostScalar = 50 // scale costs 1:1 (100%)
31+
const initialCachedCostScalar = 50 // scale costs 1:1 (100%)
32+
const initialExpiryDays = 365 // deactivate after 1 year.
33+
const initialKeepaliveDays = 31 // wait a month before allowing reactivation.
34+
const initialRecentCacheSize = 32 // cache the 32 most recent programs.
3435

3536
const v2MinInitGas = 69 // charge 69 * 128 = 8832 gas (minCachedGas will also be charged in v2).
3637

@@ -60,6 +61,7 @@ type StylusParams struct {
6061
KeepaliveDays uint16
6162
BlockCacheSize uint16
6263
MaxWasmSize uint32
64+
MaxFragmentCount uint16
6365
}
6466

6567
// Provides a view of the Stylus parameters. Call Save() to persist.
@@ -110,6 +112,11 @@ func (p Programs) Params() (*StylusParams, error) {
110112
} else {
111113
stylusParams.MaxWasmSize = initialMaxWasmSize
112114
}
115+
if p.ArbosVersion >= params.ArbosVersion_StylusContractLimit {
116+
stylusParams.MaxFragmentCount = arbmath.BytesToUint16(take(2))
117+
} else {
118+
stylusParams.MaxFragmentCount = 0
119+
}
113120
return stylusParams, nil
114121
}
115122

@@ -139,6 +146,9 @@ func (p *StylusParams) Save() error {
139146
if p.arbosVersion >= params.ArbosVersion_40 {
140147
data = append(data, arbmath.Uint32ToBytes(p.MaxWasmSize)...)
141148
}
149+
if p.arbosVersion >= params.ArbosVersion_StylusContractLimit {
150+
data = append(data, arbmath.Uint16ToBytes(p.MaxFragmentCount)...)
151+
}
142152

143153
slot := uint64(0)
144154
for len(data) != 0 {
@@ -171,23 +181,24 @@ func (p *StylusParams) UpgradeToVersion(version uint16) error {
171181
}
172182

173183
func (p *StylusParams) UpgradeToArbosVersion(newArbosVersion uint64) error {
174-
if newArbosVersion == params.ArbosVersion_50 {
175-
if p.arbosVersion >= params.ArbosVersion_50 {
176-
return fmt.Errorf("unexpected arbosVersion upgrade to %d from %d", newArbosVersion, p.arbosVersion)
177-
}
184+
if p.arbosVersion >= newArbosVersion {
185+
return fmt.Errorf("unexpected arbosVersion upgrade to %d from %d", newArbosVersion, p.arbosVersion)
186+
}
187+
188+
switch newArbosVersion {
189+
case params.ArbosVersion_50:
178190
if p.MaxStackDepth > arbOS50MaxWasmSize {
179191
p.MaxStackDepth = arbOS50MaxWasmSize
180192
}
181-
}
182-
if newArbosVersion == params.ArbosVersion_40 {
183-
if p.arbosVersion >= params.ArbosVersion_40 {
184-
return fmt.Errorf("unexpected arbosVersion upgrade to %d from %d", newArbosVersion, p.arbosVersion)
185-
}
193+
case params.ArbosVersion_40:
186194
if p.Version != 2 {
187195
return fmt.Errorf("unexpected arbosVersion upgrade to %d while stylus version %d", newArbosVersion, p.Version)
188196
}
189197
p.MaxWasmSize = initialMaxWasmSize
198+
case params.ArbosVersion_StylusContractLimit:
199+
p.MaxFragmentCount = initialMaxFragmentCount
190200
}
201+
191202
p.arbosVersion = newArbosVersion
192203
return nil
193204
}
@@ -214,5 +225,9 @@ func initStylusParams(arbosVersion uint64, sto *storage.Storage) {
214225
if arbosVersion >= params.ArbosVersion_40 {
215226
stylusParams.MaxWasmSize = initialMaxWasmSize
216227
}
228+
if arbosVersion >= params.ArbosVersion_StylusContractLimit {
229+
stylusParams.MaxFragmentCount = initialMaxFragmentCount
230+
}
231+
217232
_ = stylusParams.Save()
218233
}

arbos/programs/programs.go

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runCtx *c
114114
// already activated and up to date
115115
return 0, codeHash, common.Hash{}, nil, false, ProgramUpToDateError()
116116
}
117-
wasm, err := getWasm(statedb, address, params.MaxWasmSize)
117+
wasm, err := getWasm(statedb, address, params)
118118
if err != nil {
119119
return 0, codeHash, common.Hash{}, nil, false, err
120120
}
@@ -225,7 +225,7 @@ func (p Programs) CallProgram(
225225
statedb.AddStylusPages(program.footprint)
226226
defer statedb.SetStylusPagesOpen(open)
227227

228-
asmMap, err := getCompiledProgram(statedb, moduleHash, contract.Address(), contract.Code, contract.CodeHash, params.MaxWasmSize, params.PageLimit, evm.Context.Time, debugMode, program, runCtx)
228+
asmMap, err := getCompiledProgram(statedb, moduleHash, contract.Address(), contract.Code, contract.CodeHash, params, evm.Context.Time, debugMode, program, runCtx)
229229
var ok bool
230230
var localAsm []byte
231231
if asmMap != nil {
@@ -307,30 +307,105 @@ func evmMemoryCost(size uint64) uint64 {
307307
return linearCost + squareCost
308308
}
309309

310-
func getWasm(statedb vm.StateDB, program common.Address, maxWasmSize uint32) ([]byte, error) {
310+
func getWasm(statedb vm.StateDB, program common.Address, params *StylusParams) ([]byte, error) {
311311
prefixedWasm := statedb.GetCode(program)
312-
return getWasmFromContractCode(prefixedWasm, maxWasmSize)
312+
return getWasmFromContractCode(statedb, prefixedWasm, params, true)
313313
}
314314

315-
func getWasmFromContractCode(prefixedWasm []byte, maxWasmSize uint32) ([]byte, error) {
316-
if prefixedWasm == nil {
315+
func getWasmFromContractCode(statedb vm.StateDB, prefixedWasm []byte, params *StylusParams, isActivation bool) ([]byte, error) {
316+
if len(prefixedWasm) == 0 {
317317
return nil, ProgramNotWasmError()
318318
}
319-
wasm, dictByte, err := state.StripStylusPrefix(prefixedWasm)
319+
320+
if state.IsStylusProgramClassic(prefixedWasm) {
321+
return handleClassicStylus(prefixedWasm, params.MaxWasmSize)
322+
}
323+
324+
if params.arbosVersion >= gethParams.ArbosVersion_StylusContractLimit {
325+
if state.IsStylusProgramRoot(prefixedWasm) {
326+
return handleRootStylus(statedb, prefixedWasm, params.MaxWasmSize, params.MaxFragmentCount, isActivation)
327+
}
328+
329+
if state.IsStylusProgramFragment(prefixedWasm) {
330+
return nil, errors.New("fragmented stylus programs cannot be activated directly; activate the root program instead")
331+
}
332+
}
333+
334+
return nil, ProgramNotWasmError()
335+
}
336+
337+
func handleClassicStylus(data []byte, maxSize uint32) ([]byte, error) {
338+
wasm, dictByte, err := state.StripStylusPrefix(data)
339+
if err != nil {
340+
return nil, err
341+
}
342+
343+
dict, err := getStylusCompressionDict(dictByte)
344+
if err != nil {
345+
return nil, err
346+
}
347+
348+
return arbcompress.DecompressWithDictionary(wasm, int(maxSize), dict)
349+
}
350+
351+
func handleRootStylus(statedb vm.StateDB, data []byte, maxSize uint32, maxFragments uint16, isActivation bool) ([]byte, error) {
352+
root, err := state.NewStylusRoot(data)
353+
if err != nil {
354+
return nil, err
355+
}
356+
357+
if isActivation {
358+
if root.DecompressedLength > maxSize {
359+
return nil, fmt.Errorf("invalid wasm: decompressedLength %d is greater then MaxWasmSize %d", root.DecompressedLength, maxSize)
360+
}
361+
if len(root.Addresses) > int(maxFragments) {
362+
return nil, fmt.Errorf("invalid wasm: fragment count exceeds limit of %d", maxFragments)
363+
}
364+
}
365+
366+
if len(root.Addresses) == 0 {
367+
return nil, fmt.Errorf("invalid wasm: fragment count cannot be zero")
368+
}
369+
370+
var compressedWasm []byte
371+
for _, addr := range root.Addresses {
372+
fragCode := statedb.GetCode(addr)
373+
374+
payload, err := state.StripStylusFragmentPrefix(fragCode)
375+
if err != nil {
376+
return nil, err
377+
}
378+
379+
compressedWasm = append(compressedWasm, payload...)
380+
}
381+
382+
dict, err := getStylusCompressionDict(root.DictionaryType)
320383
if err != nil {
321384
return nil, err
322385
}
323386

324-
var dict arbcompress.Dictionary
325-
switch dictByte {
387+
wasm, err := arbcompress.DecompressWithDictionary(compressedWasm, int(root.DecompressedLength), dict)
388+
if err != nil {
389+
return nil, err
390+
}
391+
392+
if len(wasm) != int(root.DecompressedLength) {
393+
return nil, fmt.Errorf("invalid wasm: decompressed length %d does not match expected length %d", len(wasm), root.DecompressedLength)
394+
}
395+
396+
return wasm, nil
397+
}
398+
399+
// Named return parameters allow us to return the zero-value for 'dict' implicitly on error
400+
func getStylusCompressionDict(id byte) (dict arbcompress.Dictionary, err error) {
401+
switch id {
326402
case 0:
327-
dict = arbcompress.EmptyDictionary
403+
return arbcompress.EmptyDictionary, nil
328404
case 1:
329-
dict = arbcompress.StylusProgramDictionary
405+
return arbcompress.StylusProgramDictionary, nil
330406
default:
331-
return nil, fmt.Errorf("unsupported dictionary %v", dictByte)
407+
return dict, fmt.Errorf("unsupported dictionary type: %d", id)
332408
}
333-
return arbcompress.DecompressWithDictionary(wasm, int(maxWasmSize), dict)
334409
}
335410

336411
// Gets a program entry, which may be expired or not yet activated.

arbos/programs/wasm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func startProgram(module uint32) uint32
136136
//go:wasmimport programs send_response
137137
func sendResponse(req_id uint32) uint32
138138

139-
func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, maxWasmSize uint32, pagelimit uint16, time uint64, debugMode bool, program Program, runCtx *core.MessageRunContext) (map[rawdb.WasmTarget][]byte, error) {
139+
func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, stylusParams *StylusParams, time uint64, debugMode bool, program Program, runCtx *core.MessageRunContext) (map[rawdb.WasmTarget][]byte, error) {
140140
// we need to return asm map with an entry for local target to make checks for local target work
141141
return map[rawdb.WasmTarget][]byte{rawdb.LocalTarget(): {}}, nil
142142
}

arbos/programs/wasmstorehelper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash
5151
return nil
5252
}
5353

54-
wasm, err := getWasmFromContractCode(code, progParams.MaxWasmSize)
54+
wasm, err := getWasmFromContractCode(statedb, code, progParams, false)
5555
if err != nil {
5656
log.Error("Failed to reactivate program while rebuilding wasm store: getWasmFromContractCode", "expected moduleHash", moduleHash, "err", err)
5757
return fmt.Errorf("failed to reactivate program while rebuilding wasm store: %w", err)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Added
2+
- Increase Stylus smart contract size limit via merge-on-activate

0 commit comments

Comments
 (0)