From 6326641e5a4f74571ab3981e57c470cc1e032928 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:51:14 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(gnovm):=20phase=202=20=E2=80=94=20iner?= =?UTF-8?q?t=20package=20storage=20and=20enable/disable=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add CodeSubmissionPolicyInert and PkgApprovers param. When policy=inert, MsgAddPackage stores packages in inert_pkg: key space without typechecking or execution. MsgEnablePackage (approver-gated) typechecks and activates. MsgDisablePackage stubbed pending baseStore object eviction strategy. --- gno.land/adr/pr5885_phase2_inert_packages.md | 57 +++ .../pkg/sdk/vm/apphash_crossrealm38_test.go | 2 +- gno.land/pkg/sdk/vm/handler.go | 22 ++ gno.land/pkg/sdk/vm/keeper.go | 109 ++++++ gno.land/pkg/sdk/vm/msgs.go | 78 ++++ gno.land/pkg/sdk/vm/package.go | 2 + gno.land/pkg/sdk/vm/params.go | 86 +++++ gno.land/pkg/sdk/vm/pb3_gen.go | 342 ++++++++++++++++++ gnovm/pkg/gnolang/store.go | 39 ++ 9 files changed, 736 insertions(+), 1 deletion(-) create mode 100644 gno.land/adr/pr5885_phase2_inert_packages.md diff --git a/gno.land/adr/pr5885_phase2_inert_packages.md b/gno.land/adr/pr5885_phase2_inert_packages.md new file mode 100644 index 00000000000..59935bc9a0b --- /dev/null +++ b/gno.land/adr/pr5885_phase2_inert_packages.md @@ -0,0 +1,57 @@ +# ADR: Phase 2 — Inert Package Storage with Oracle Activation + +Companion to [PR #5885](https://github.com/gnolang/gno/pull/5885) (Phase 1: permissioned +code submission policy). + +## Context + +Phase 1 adds a `permissioned` policy that restricts `MsgAddPackage` / `MsgRun` to an +allowlist. Phase 2 enables permissionless submission while keeping the Go typechecker +off the critical path: packages are stored in an **inert** state (no typechecking, no +execution) and activated later by a trusted approver — possibly an off-chain oracle. + +## Decision + +### New policy value + +`code_submission_policy = "inert"`: any address may submit a package, but it is stored +in a separate key space (`inert_pkg:` in iavlStore) that is invisible to the +normal package resolver. The package is not typechecked or executed at submission time. + +### New param + +| Param | Type | Default | Description | +|---|---|---|---| +| `pkg_approvers` | []address | `[]` | Who may call `MsgEnablePackage` / `MsgDisablePackage` | + +### New messages + +**`MsgEnablePackage { Approver, PkgPath }`** +Approver must be in `pkg_approvers`. Chain retrieves the inert package, runs the +typechecker (oracle is untrusted for correctness), executes initialization, and moves +the package to the active store. After this point the package is importable. + +**`MsgDisablePackage { Approver, PkgPath }`** +Interface stub. Full disable requires evicting executed objects from the base store; +not yet implemented. Returns an error until a follow-up PR delivers it. + +### Store layer + +`gnovm.Store` gains three new methods: +- `AddInertPackage(mpkg)` — store at `inert_pkg:` in iavlStore +- `GetInertPackage(path)` — read from `inert_pkg:` +- `DelInertPackage(path)` — remove from `inert_pkg:` + +These keys are disjoint from `pkg:` so normal `GetPackage` / `GetMemPackage` +never see inert packages. + +## Consequences + +- **Permissionless submission, deferred typechecking**: the DoS surface from the + typechecker is removed from block execution time. +- **On-chain correctness guarantee**: the chain re-runs the typechecker at + `MsgEnablePackage`; the oracle cannot activate a package that fails typecheck. +- **Default unaffected**: `code_submission_policy` still defaults to + `"permissionless"`. Chains that don't opt in see no change. +- **Disable deferred**: MsgDisablePackage is stubbed; implementation requires a + strategy for cleaning up executed objects from the base store. diff --git a/gno.land/pkg/sdk/vm/apphash_crossrealm38_test.go b/gno.land/pkg/sdk/vm/apphash_crossrealm38_test.go index 8e7b1e01d2f..c22e026e671 100644 --- a/gno.land/pkg/sdk/vm/apphash_crossrealm38_test.go +++ b/gno.land/pkg/sdk/vm/apphash_crossrealm38_test.go @@ -68,7 +68,7 @@ import ( // their *_test.gno source bytes), which shifts the iavlStore Merkle root. This // is the only consensus-relevant change in that PR; verified by bisection that // no other change in the PR moves this hash. The shift is therefore expected. -const expectedCrossrealm38Hash = "28f55f0ad9842bc3c4d8984f1a63a709203a1e99fae7e816786825e26629f618" +const expectedCrossrealm38Hash = "133a70aa761f36a99f57f68ed1249ff56cbc42b77fa654534a922dd7993db887" func TestAppHashCrossrealm38(t *testing.T) { env := setupTestEnv() diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go index 45d7de6f5e5..575f2f5c435 100644 --- a/gno.land/pkg/sdk/vm/handler.go +++ b/gno.land/pkg/sdk/vm/handler.go @@ -31,6 +31,10 @@ func (vh vmHandler) Process(ctx sdk.Context, msg std.Msg) sdk.Result { return vh.handleMsgCall(ctx, msg) case MsgRun: return vh.handleMsgRun(ctx, msg) + case MsgEnablePackage: + return vh.handleMsgEnablePackage(ctx, msg) + case MsgDisablePackage: + return vh.handleMsgDisablePackage(ctx, msg) default: errMsg := fmt.Sprintf("unrecognized vm message type: %T", msg) return abciResult(std.ErrUnknownRequest(errMsg)) @@ -66,6 +70,24 @@ func (vh vmHandler) handleMsgRun(ctx sdk.Context, msg MsgRun) (res sdk.Result) { return } +// Handle MsgEnablePackage. +func (vh vmHandler) handleMsgEnablePackage(ctx sdk.Context, msg MsgEnablePackage) sdk.Result { + err := vh.vm.EnablePackage(ctx, msg) + if err != nil { + return abciResult(err) + } + return sdk.Result{} +} + +// Handle MsgDisablePackage. +func (vh vmHandler) handleMsgDisablePackage(ctx sdk.Context, msg MsgDisablePackage) sdk.Result { + err := vh.vm.DisablePackage(ctx, msg) + if err != nil { + return abciResult(err) + } + return sdk.Result{} +} + // ---------------------------------------- // Query diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 189b5d6b9ca..b7891c138ff 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -634,6 +634,31 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { if _, ok := gno.IsGnoRunPath(pkgPath); ok { return ErrInvalidPkgPath("reserved package name: " + pkgPath) } + // If the chain is operating in "inert" submission mode, store the package + // without typechecking or execution. It becomes callable only after an + // approver sends MsgEnablePackage. + if vm.GetParams(ctx).CodeSubmissionPolicy == CodeSubmissionPolicyInert { + gm, err := gnomod.ParseMemPackage(memPkg) + if err != nil { + return ErrInvalidPackage(err.Error()) + } + gm.Module = pkgPath + gm.AddPkg.Creator = creator.String() + gm.AddPkg.Height = int(ctx.BlockHeight()) + memPkg.SetFile("gnomod.toml", gm.WriteString()) + if err := vm.checkNamespacePermission(ctx, creator, pkgPath); err != nil { + return err + } + if err := vm.checkCLASignature(ctx, creator); err != nil { + return err + } + if err := vm.bank.SendCoins(ctx, creator, gno.DerivePkgCryptoAddr(pkgPath), send); err != nil { + return err + } + gnostore.AddInertPackage(memPkg) + return nil + } + opts := gno.TypeCheckOptions{ Getter: gnostore, TestGetter: vm.testStdlibCache.memPackageGetter(gnostore), @@ -772,6 +797,90 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { return nil } +// EnablePackage activates an inert package: runs the typechecker and +// initializes the package so it becomes importable and callable on-chain. +// Only addresses listed in Params.PkgApprovers may call this. +func (vm *VMKeeper) EnablePackage(ctx sdk.Context, msg MsgEnablePackage) (err error) { + params := vm.GetParams(ctx) + if !isApprover(params.PkgApprovers, msg.Approver) { + return std.ErrUnauthorized(fmt.Sprintf( + "address %s is not a pkg approver", msg.Approver)) + } + gnostore := vm.getGnoTransactionStore(ctx) + memPkg := gnostore.GetInertPackage(msg.PkgPath) + if memPkg == nil { + return ErrInvalidPkgPath("no inert package at path: " + msg.PkgPath) + } + // Typecheck the stored package. + opts := gno.TypeCheckOptions{ + Getter: gnostore, + TestGetter: vm.testStdlibCache.memPackageGetter(gnostore), + Mode: gno.TCLatestStrict, + Cache: vm.getTypeCheckCache(ctx), + } + if _, err = gno.TypeCheckMemPackage(memPkg, opts); err != nil { + return ErrTypeCheck(err) + } + // Execute and persist the package. + ctx = ContextWithParamsAccum(ctx) + msgCtx := stdlibs.ExecContext{ + ChainID: ctx.ChainID(), + ChainDomain: vm.getChainDomainParam(ctx), + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: msg.Approver.Bech32(), + OriginSend: std.Coins{}, + OriginSendSpent: new(std.Coins), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm.prmk, ctx), + EventLogger: ctx.EventLogger(), + SessionAccount: getSessionAccount(ctx, msg.Approver), + } + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "", + Output: vm.Output, + Store: gnostore, + Alloc: gnostore.GetAllocator(), + Context: msgCtx, + GasMeter: ctx.GasMeter(), + BoundedPanicRender: true, + }) + defer m2.Release() + defer doRecover(m2, &err) + preAlloc := gno.NewAllocator(maxAllocTx) + preAlloc.SetGasMeter(ctx.GasMeter()) + gnostore.SetPreprocessAllocator(preAlloc) + defer gnostore.SetPreprocessAllocator(nil) + m2.RunMemPackage(memPkg, true) + // Remove from inert store now that it is active. + gnostore.DelInertPackage(msg.PkgPath) + return nil +} + +// isApprover reports whether addr is in the approvers list. +func isApprover(approvers []crypto.Address, addr crypto.Address) bool { + for _, a := range approvers { + if a == addr { + return true + } + } + return false +} + +// DisablePackage moves an active package back to inert state. +// NOTE: full disable requires evicting executed objects from the base store, +// which is not yet implemented. This stub is provided for interface completeness. +func (vm *VMKeeper) DisablePackage(ctx sdk.Context, msg MsgDisablePackage) error { + params := vm.GetParams(ctx) + if !isApprover(params.PkgApprovers, msg.Approver) { + return std.ErrUnauthorized(fmt.Sprintf( + "address %s is not a pkg approver", msg.Approver)) + } + // TODO: evict executed package objects from baseStore and move source back + // to inert_pkg key. Tracked in a follow-up PR. + return std.ErrUnknownRequest("disable_package is not yet implemented") +} + // Call calls a public Gno function (for delivertx). func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { // Session spend on msg.Send is enforced inside bank.Keeper.SendCoins diff --git a/gno.land/pkg/sdk/vm/msgs.go b/gno.land/pkg/sdk/vm/msgs.go index 5d1b4046953..9dd56c01570 100644 --- a/gno.land/pkg/sdk/vm/msgs.go +++ b/gno.land/pkg/sdk/vm/msgs.go @@ -274,3 +274,81 @@ func (msg MsgRun) SpendForSigner(signer crypto.Address) std.Coins { } return msg.Send } + +//---------------------------------------- +// MsgEnablePackage + +// MsgEnablePackage activates an inert package: runs typecheck and init, +// then makes the package importable on-chain. +// Only addresses listed in Params.PkgApprovers may send this message. +type MsgEnablePackage struct { + Approver crypto.Address `json:"approver" yaml:"approver"` + PkgPath string `json:"pkg_path" yaml:"pkg_path"` +} + +var _ std.Msg = MsgEnablePackage{} + +func (msg MsgEnablePackage) Route() string { return RouterKey } +func (msg MsgEnablePackage) Type() string { return "enable_package" } + +func (msg MsgEnablePackage) ValidateBasic() error { + if msg.Approver.IsZero() { + return std.ErrInvalidAddress("missing approver address") + } + if msg.PkgPath == "" { + return ErrInvalidPkgPath("missing package path") + } + return nil +} + +func (msg MsgEnablePackage) GetSignBytes() []byte { + return std.MustSortJSON(amino.MustMarshalJSON(msg)) +} + +func (msg MsgEnablePackage) GetSigners() []crypto.Address { + return []crypto.Address{msg.Approver} +} + +func (msg MsgEnablePackage) GetReceived() std.Coins { return nil } + +func (msg MsgEnablePackage) SpendForSigner(_ crypto.Address) std.Coins { return nil } + +//---------------------------------------- +// MsgDisablePackage + +// MsgDisablePackage moves an active package back to inert state, preventing +// further calls. Only addresses listed in Params.PkgApprovers may send this. +// +// NOTE: full disable (cleaning up executed objects from the base store) is not +// yet implemented; the handler returns an error until a follow-up PR completes it. +type MsgDisablePackage struct { + Approver crypto.Address `json:"approver" yaml:"approver"` + PkgPath string `json:"pkg_path" yaml:"pkg_path"` +} + +var _ std.Msg = MsgDisablePackage{} + +func (msg MsgDisablePackage) Route() string { return RouterKey } +func (msg MsgDisablePackage) Type() string { return "disable_package" } + +func (msg MsgDisablePackage) ValidateBasic() error { + if msg.Approver.IsZero() { + return std.ErrInvalidAddress("missing approver address") + } + if msg.PkgPath == "" { + return ErrInvalidPkgPath("missing package path") + } + return nil +} + +func (msg MsgDisablePackage) GetSignBytes() []byte { + return std.MustSortJSON(amino.MustMarshalJSON(msg)) +} + +func (msg MsgDisablePackage) GetSigners() []crypto.Address { + return []crypto.Address{msg.Approver} +} + +func (msg MsgDisablePackage) GetReceived() std.Coins { return nil } + +func (msg MsgDisablePackage) SpendForSigner(_ crypto.Address) std.Coins { return nil } diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go index 660c476e7c2..603daa7d5b2 100644 --- a/gno.land/pkg/sdk/vm/package.go +++ b/gno.land/pkg/sdk/vm/package.go @@ -17,6 +17,8 @@ var Package = amino.RegisterPackage(amino.NewPackage( MsgCall{}, "m_call", MsgRun{}, "m_run", MsgAddPackage{}, "m_addpkg", // TODO rename both to MsgAddPkg? + MsgEnablePackage{}, "m_enable_pkg", + MsgDisablePackage{}, "m_disable_pkg", // errors InvalidPkgPathError{}, "InvalidPkgPathError", diff --git a/gno.land/pkg/sdk/vm/params.go b/gno.land/pkg/sdk/vm/params.go index 09d66c29a7f..6a918a1a5e2 100644 --- a/gno.land/pkg/sdk/vm/params.go +++ b/gno.land/pkg/sdk/vm/params.go @@ -14,6 +14,22 @@ import ( "github.com/gnolang/gno/tm2/pkg/store" ) +// CodeSubmissionPolicy controls who may submit MsgAddPackage and MsgRun, and +// how submitted packages are processed. +type CodeSubmissionPolicy string + +const ( + // CodeSubmissionPolicyPermissionless allows any address to submit code (default). + CodeSubmissionPolicyPermissionless CodeSubmissionPolicy = "permissionless" + // CodeSubmissionPolicyPermissioned restricts code submission to addresses + // listed in Params.CodeSubmitters. + CodeSubmissionPolicyPermissioned CodeSubmissionPolicy = "permissioned" + // CodeSubmissionPolicyInert accepts packages from any address but stores them + // without typechecking or execution (inert state). Packages become callable + // only after an approver sends MsgEnablePackage. + CodeSubmissionPolicyInert CodeSubmissionPolicy = "inert" +) + const ( sysNamesPkgDefault = "gno.land/r/sys/names" sysCLAPkgDefault = "gno.land/r/sys/cla" @@ -21,6 +37,7 @@ const ( depositDefault = "600000000ugnot" storagePriceDefault = "100ugnot" // cost per byte (1 gnot per 10KB) 1.333B GNOT == 13.33TB storageFeeCollectorNameDefault = "storage_fee_collector" + codeSubmissionPolicyDefault = CodeSubmissionPolicyPermissionless // Depth floors calibrated for B+32 at 100M items with 10K cache, batched 1000 muts. minGetReadDepth100Default = int64(300) // 3.0 GET read ops @@ -51,6 +68,15 @@ type Params struct { // "no floor / use tree estimate") because zero iter-step cost // would effectively disable iteration gas charging. IterNextCostFlat int64 `json:"iter_next_cost_flat" yaml:"iter_next_cost_flat"` + + // CodeSubmissionPolicy controls who may submit MsgAddPackage/MsgRun and + // how packages are processed on arrival. Defaults to "permissionless". + CodeSubmissionPolicy CodeSubmissionPolicy `json:"code_submission_policy" yaml:"code_submission_policy"` + // CodeSubmitters is the allowlist used when CodeSubmissionPolicy == "permissioned". + CodeSubmitters []crypto.Address `json:"code_submitters" yaml:"code_submitters"` + // PkgApprovers may call MsgEnablePackage / MsgDisablePackage. + // Required when CodeSubmissionPolicy == "inert". + PkgApprovers []crypto.Address `json:"pkg_approvers" yaml:"pkg_approvers"` } // NewParams creates a new Params object @@ -69,6 +95,7 @@ func NewParams(namesPkgPath, claPkgPath, chainDomain, defaultDeposit, storagePri FixedSetReadDepth100: minSetReadDepth100, FixedWriteDepth100: minWriteDepth100, IterNextCostFlat: iterNextCostFlat, + CodeSubmissionPolicy: codeSubmissionPolicyDefault, } } @@ -97,6 +124,9 @@ func (p Params) String() string { sb.WriteString(fmt.Sprintf("FixedSetReadDepth100: %d\n", p.FixedSetReadDepth100)) sb.WriteString(fmt.Sprintf("FixedWriteDepth100: %d\n", p.FixedWriteDepth100)) sb.WriteString(fmt.Sprintf("IterNextCostFlat: %d\n", p.IterNextCostFlat)) + sb.WriteString(fmt.Sprintf("CodeSubmissionPolicy: %q\n", p.CodeSubmissionPolicy)) + sb.WriteString(fmt.Sprintf("CodeSubmitters: %v\n", p.CodeSubmitters)) + sb.WriteString(fmt.Sprintf("PkgApprovers: %v\n", p.PkgApprovers)) return sb.String() } @@ -159,6 +189,56 @@ func (p Params) Validate() error { if p.IterNextCostFlat > maxIterNextCostFlat { return fmt.Errorf("IterNextCostFlat must be <= %d, got %d", maxIterNextCostFlat, p.IterNextCostFlat) } + switch p.CodeSubmissionPolicy { + case CodeSubmissionPolicyPermissionless, CodeSubmissionPolicyPermissioned, + CodeSubmissionPolicyInert: + // valid + case "": + // treat empty as permissionless (zero-value compat) + default: + return fmt.Errorf("invalid code_submission_policy %q", p.CodeSubmissionPolicy) + } + if err := validateAddressSlice("CodeSubmitters", p.CodeSubmitters); err != nil { + return err + } + if err := validateAddressSlice("PkgApprovers", p.PkgApprovers); err != nil { + return err + } + return nil +} + +func mustParseAddressSlice(paramName string, value any) []crypto.Address { + s := sdkparams.MustParamString(paramName, value) + if s == "" { + return nil + } + var addrs []crypto.Address + for _, part := range strings.Split(s, ",") { + part = strings.TrimSpace(part) + if part == "" { + continue + } + addr, err := crypto.AddressFromString(part) + if err != nil { + panic(fmt.Sprintf("invalid %s address %q: %v", paramName, part, err)) + } + addrs = append(addrs, addr) + } + return addrs +} + +func validateAddressSlice(name string, addrs []crypto.Address) error { + seen := make(map[string]struct{}, len(addrs)) + for i, addr := range addrs { + if addr.IsZero() { + return fmt.Errorf("%s[%d] is a zero address", name, i) + } + key := addr.String() + if _, dup := seen[key]; dup { + return fmt.Errorf("%s contains duplicate address %s", name, key) + } + seen[key] = struct{}{} + } return nil } @@ -261,6 +341,12 @@ func (vm *VMKeeper) WillSetParam(ctx sdk.Context, key string, value any) { params.FixedWriteDepth100 = sdkparams.MustParamInt64("fixed_write_depth_100", value) case "p:iter_next_cost_flat": params.IterNextCostFlat = sdkparams.MustParamInt64("iter_next_cost_flat", value) + case "p:code_submission_policy": + params.CodeSubmissionPolicy = CodeSubmissionPolicy(sdkparams.MustParamString("code_submission_policy", value)) + case "p:code_submitters": + params.CodeSubmitters = mustParseAddressSlice("code_submitters", value) + case "p:pkg_approvers": + params.PkgApprovers = mustParseAddressSlice("pkg_approvers", value) default: if strings.HasPrefix(key, "p:") { panic(fmt.Sprintf("unknown vm param key: %q", key)) diff --git a/gno.land/pkg/sdk/vm/pb3_gen.go b/gno.land/pkg/sdk/vm/pb3_gen.go index 29ce0c7b0b9..388c1d53191 100644 --- a/gno.land/pkg/sdk/vm/pb3_gen.go +++ b/gno.land/pkg/sdk/vm/pb3_gen.go @@ -8,6 +8,7 @@ import ( "reflect" "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -21,6 +22,8 @@ func init() { amino.RegisterGenproto2Type(reflect.TypeOf((*MsgCall)(nil)).Elem()) amino.RegisterGenproto2Type(reflect.TypeOf((*MsgRun)(nil)).Elem()) amino.RegisterGenproto2Type(reflect.TypeOf((*MsgAddPackage)(nil)).Elem()) + amino.RegisterGenproto2Type(reflect.TypeOf((*MsgEnablePackage)(nil)).Elem()) + amino.RegisterGenproto2Type(reflect.TypeOf((*MsgDisablePackage)(nil)).Elem()) amino.RegisterGenproto2Type(reflect.TypeOf((*InvalidPkgPathError)(nil)).Elem()) amino.RegisterGenproto2Type(reflect.TypeOf((*NoRenderDeclError)(nil)).Elem()) amino.RegisterGenproto2Type(reflect.TypeOf((*PkgExistError)(nil)).Elem()) @@ -1169,6 +1172,42 @@ func (goo *GenesisState) UnmarshalBinary2(cdc *amino.Codec, bz []byte, anyDepth func (goo Params) MarshalBinary2(cdc *amino.Codec, buf []byte, offset int) (int, error) { var err error + for i := len(goo.PkgApprovers) - 1; i >= 0; i-- { + repr, err := goo.PkgApprovers[i].MarshalAmino() + if err != nil { + return offset, err + } + if repr != "" { + offset = amino.PrependString(buf, offset, string(repr)) + } else { + offset = amino.PrependByte(buf, offset, 0x00) + } + offset = amino.PrependFieldNumberAndTyp3(buf, offset, 16, amino.Typ3ByteLength) + } + for i := len(goo.CodeSubmitters) - 1; i >= 0; i-- { + repr, err := goo.CodeSubmitters[i].MarshalAmino() + if err != nil { + return offset, err + } + if repr != "" { + offset = amino.PrependString(buf, offset, string(repr)) + } else { + offset = amino.PrependByte(buf, offset, 0x00) + } + offset = amino.PrependFieldNumberAndTyp3(buf, offset, 15, amino.Typ3ByteLength) + } + if goo.CodeSubmissionPolicy != "" { + { + before := offset + offset = amino.PrependString(buf, offset, string(goo.CodeSubmissionPolicy)) + valueLen := before - offset + if valueLen > 1 || (valueLen == 1 && buf[offset] != 0x00) { + offset = amino.PrependFieldNumberAndTyp3(buf, offset, 14, amino.Typ3ByteLength) + } else { + offset = before + } + } + } if goo.IterNextCostFlat != 0 { { before := offset @@ -1381,6 +1420,25 @@ func (goo Params) SizeBinary2(cdc *amino.Codec) (int, error) { if goo.IterNextCostFlat != 0 { s += 1 + amino.VarintSize(int64(goo.IterNextCostFlat)) } + if goo.CodeSubmissionPolicy != "" { + s += 1 + amino.UvarintSize(uint64(len(goo.CodeSubmissionPolicy))) + len(goo.CodeSubmissionPolicy) + } + for _, elem := range goo.CodeSubmitters { + repr, err := elem.MarshalAmino() + if err != nil { + return 0, err + } + vs := amino.UvarintSize(uint64(len(repr))) + len(repr) + s += 1 + vs + } + for _, elem := range goo.PkgApprovers { + repr, err := elem.MarshalAmino() + if err != nil { + return 0, err + } + vs := amino.UvarintSize(uint64(len(repr))) + len(repr) + s += 1 + vs + } return s, nil } @@ -1533,9 +1591,293 @@ func (goo *Params) UnmarshalBinary2(cdc *amino.Codec, bz []byte, anyDepth int) e } bz = bz[n:] goo.IterNextCostFlat = int64(v) + case 14: + if typ3 != amino.Typ3ByteLength { + return fmt.Errorf("field 14: expected typ3 %v, got %v", amino.Typ3ByteLength, typ3) + } + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + goo.CodeSubmissionPolicy = CodeSubmissionPolicy(v) + case 15: + if typ3 != amino.Typ3ByteLength { + return fmt.Errorf("field 15: expected typ3 %v, got %v", amino.Typ3ByteLength, typ3) + } + var addr crypto.Address + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + if err := addr.UnmarshalAmino(string(v)); err != nil { + return err + } + goo.CodeSubmitters = append(goo.CodeSubmitters, addr) + for len(bz) > 0 { + var nextFnum uint32 + var nextTyp3 amino.Typ3 + nextFnum, nextTyp3, n, err = amino.DecodeFieldNumberAndTyp3(bz) + if err != nil { + return err + } + if nextFnum != 15 { + break + } + if nextTyp3 != amino.Typ3ByteLength { + return fmt.Errorf("field 15: expected typ3 %v, got %v", amino.Typ3ByteLength, nextTyp3) + } + bz = bz[n:] + var elem crypto.Address + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + if err := elem.UnmarshalAmino(string(v)); err != nil { + return err + } + goo.CodeSubmitters = append(goo.CodeSubmitters, elem) + } + case 16: + if typ3 != amino.Typ3ByteLength { + return fmt.Errorf("field 16: expected typ3 %v, got %v", amino.Typ3ByteLength, typ3) + } + var addr crypto.Address + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + if err := addr.UnmarshalAmino(string(v)); err != nil { + return err + } + goo.PkgApprovers = append(goo.PkgApprovers, addr) + for len(bz) > 0 { + var nextFnum uint32 + var nextTyp3 amino.Typ3 + nextFnum, nextTyp3, n, err = amino.DecodeFieldNumberAndTyp3(bz) + if err != nil { + return err + } + if nextFnum != 16 { + break + } + if nextTyp3 != amino.Typ3ByteLength { + return fmt.Errorf("field 16: expected typ3 %v, got %v", amino.Typ3ByteLength, nextTyp3) + } + bz = bz[n:] + var elem crypto.Address + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + if err := elem.UnmarshalAmino(string(v)); err != nil { + return err + } + goo.PkgApprovers = append(goo.PkgApprovers, elem) + } default: return fmt.Errorf("unknown field number %d for Params", fnum) } } return nil } + +func (goo MsgEnablePackage) MarshalBinary2(cdc *amino.Codec, buf []byte, offset int) (int, error) { + var err error + if goo.PkgPath != "" { + { + before := offset + offset = amino.PrependString(buf, offset, string(goo.PkgPath)) + valueLen := before - offset + if valueLen > 1 || (valueLen == 1 && buf[offset] != 0x00) { + offset = amino.PrependFieldNumberAndTyp3(buf, offset, 2, amino.Typ3ByteLength) + } else { + offset = before + } + } + } + { + repr, err := goo.Approver.MarshalAmino() + if err != nil { + return offset, err + } + if repr != "" { + { + before := offset + offset = amino.PrependString(buf, offset, string(repr)) + valueLen := before - offset + if valueLen > 1 || (valueLen == 1 && buf[offset] != 0x00) { + offset = amino.PrependFieldNumberAndTyp3(buf, offset, 1, amino.Typ3ByteLength) + } else { + offset = before + } + } + } + } + return offset, err +} + +func (goo MsgEnablePackage) SizeBinary2(cdc *amino.Codec) (int, error) { + var s int + { + repr, err := goo.Approver.MarshalAmino() + if err != nil { + return 0, err + } + if repr != "" { + s += 1 + amino.UvarintSize(uint64(len(repr))) + len(repr) + } + } + if goo.PkgPath != "" { + s += 1 + amino.UvarintSize(uint64(len(goo.PkgPath))) + len(goo.PkgPath) + } + return s, nil +} + +func (goo *MsgEnablePackage) UnmarshalBinary2(cdc *amino.Codec, bz []byte, anyDepth int) error { + *goo = MsgEnablePackage{} + var lastFieldNum uint32 + for len(bz) > 0 { + fnum, typ3, n, err := amino.DecodeFieldNumberAndTyp3(bz) + _ = typ3 + if err != nil { + return err + } + if fnum <= lastFieldNum { + return fmt.Errorf("encountered fieldNum: %v, but we have already seen fnum: %v", fnum, lastFieldNum) + } + lastFieldNum = fnum + bz = bz[n:] + switch fnum { + case 1: + if typ3 != amino.Typ3ByteLength { + return fmt.Errorf("field 1: expected typ3 %v, got %v", amino.Typ3ByteLength, typ3) + } + var repr string + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + repr = string(v) + if err := goo.Approver.UnmarshalAmino(repr); err != nil { + return err + } + case 2: + if typ3 != amino.Typ3ByteLength { + return fmt.Errorf("field 2: expected typ3 %v, got %v", amino.Typ3ByteLength, typ3) + } + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + goo.PkgPath = string(v) + default: + return fmt.Errorf("unknown field number %d for MsgEnablePackage", fnum) + } + } + return nil +} + +func (goo MsgDisablePackage) MarshalBinary2(cdc *amino.Codec, buf []byte, offset int) (int, error) { + var err error + if goo.PkgPath != "" { + { + before := offset + offset = amino.PrependString(buf, offset, string(goo.PkgPath)) + valueLen := before - offset + if valueLen > 1 || (valueLen == 1 && buf[offset] != 0x00) { + offset = amino.PrependFieldNumberAndTyp3(buf, offset, 2, amino.Typ3ByteLength) + } else { + offset = before + } + } + } + { + repr, err := goo.Approver.MarshalAmino() + if err != nil { + return offset, err + } + if repr != "" { + { + before := offset + offset = amino.PrependString(buf, offset, string(repr)) + valueLen := before - offset + if valueLen > 1 || (valueLen == 1 && buf[offset] != 0x00) { + offset = amino.PrependFieldNumberAndTyp3(buf, offset, 1, amino.Typ3ByteLength) + } else { + offset = before + } + } + } + } + return offset, err +} + +func (goo MsgDisablePackage) SizeBinary2(cdc *amino.Codec) (int, error) { + var s int + { + repr, err := goo.Approver.MarshalAmino() + if err != nil { + return 0, err + } + if repr != "" { + s += 1 + amino.UvarintSize(uint64(len(repr))) + len(repr) + } + } + if goo.PkgPath != "" { + s += 1 + amino.UvarintSize(uint64(len(goo.PkgPath))) + len(goo.PkgPath) + } + return s, nil +} + +func (goo *MsgDisablePackage) UnmarshalBinary2(cdc *amino.Codec, bz []byte, anyDepth int) error { + *goo = MsgDisablePackage{} + var lastFieldNum uint32 + for len(bz) > 0 { + fnum, typ3, n, err := amino.DecodeFieldNumberAndTyp3(bz) + _ = typ3 + if err != nil { + return err + } + if fnum <= lastFieldNum { + return fmt.Errorf("encountered fieldNum: %v, but we have already seen fnum: %v", fnum, lastFieldNum) + } + lastFieldNum = fnum + bz = bz[n:] + switch fnum { + case 1: + if typ3 != amino.Typ3ByteLength { + return fmt.Errorf("field 1: expected typ3 %v, got %v", amino.Typ3ByteLength, typ3) + } + var repr string + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + repr = string(v) + if err := goo.Approver.UnmarshalAmino(repr); err != nil { + return err + } + case 2: + if typ3 != amino.Typ3ByteLength { + return fmt.Errorf("field 2: expected typ3 %v, got %v", amino.Typ3ByteLength, typ3) + } + v, n, err := amino.DecodeString(bz) + if err != nil { + return err + } + bz = bz[n:] + goo.PkgPath = string(v) + default: + return fmt.Errorf("unknown field number %d for MsgDisablePackage", fnum) + } + } + return nil +} diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 3e00e5b8466..93bb8507bbf 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -82,6 +82,11 @@ type Store interface { AddMemPackage(mpkg *std.MemPackage, mptype MemPackageType) GetMemPackage(path string) *std.MemPackage GetMemFile(path string, name string) *std.MemFile + // Inert package storage: packages pending activation, stored without + // typechecking or execution and invisible to the normal package resolver. + AddInertPackage(mpkg *std.MemPackage) + GetInertPackage(path string) *std.MemPackage + DelInertPackage(path string) FindPathsByPrefix(prefix string) iter.Seq[string] IterMemPackage() <-chan *std.MemPackage ClearObjectCache() // run before processing a message @@ -1050,6 +1055,38 @@ func (ds *defaultStore) GetMemFile(path string, name string) *std.MemFile { return memFile } +// AddInertPackage stores a MemPackage in the inert key space without +// typechecking or execution. The package is invisible to the normal resolver +// until activated via EnablePackage. +func (ds *defaultStore) AddInertPackage(mpkg *std.MemPackage) { + bz := amino.MustMarshal(mpkg) + gas := overflow.Mulp(ds.gasConfig.GasAminoEncode, store.Gas(len(bz))) + ds.consumeGas(gas, GasAminoEncodeDesc) + pathkey := []byte(backendInertPackagePathKey(mpkg.Path)) + ds.iavlStore.Set(ds.gctx, pathkey, bz) +} + +// GetInertPackage retrieves the MemPackage from the inert key space. +// Returns nil if no inert package exists at path. +func (ds *defaultStore) GetInertPackage(path string) *std.MemPackage { + pathkey := []byte(backendInertPackagePathKey(path)) + bz := ds.iavlStore.Get(ds.gctx, pathkey) + if bz == nil { + return nil + } + gas := overflow.Mulp(ds.gasConfig.GasAminoDecode, store.Gas(len(bz))) + ds.consumeGas(gas, GasAminoDecodeDesc) + var mpkg *std.MemPackage + amino.MustUnmarshal(bz, &mpkg) + return mpkg +} + +// DelInertPackage removes a package from the inert key space. +func (ds *defaultStore) DelInertPackage(path string) { + pathkey := []byte(backendInertPackagePathKey(path)) + ds.iavlStore.Delete(ds.gctx, pathkey) +} + // FindPathsByPrefix retrieves all paths starting with the given prefix. func (ds *defaultStore) FindPathsByPrefix(prefix string) iter.Seq[string] { // If prefix is empty range every package @@ -1294,6 +1331,8 @@ func backendPackageStdlibPath(path string) string { return "pkg:_/" + path } func backendPackageGlobalPath(path string) string { return "pkg:" + path } +func backendInertPackagePathKey(path string) string { return "inert_pkg:" + path } + func decodeBackendPackagePathKey(key string) string { path := strings.TrimPrefix(key, "pkg:") return strings.TrimPrefix(path, "_/") From 86385a6b465a19e6778f1ef52fed53bc07368b92 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:52:03 +0200 Subject: [PATCH 2/2] test(vm): update TestParamsString to cover CodeSubmitters and PkgApprovers fields --- gno.land/pkg/sdk/vm/params_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/sdk/vm/params_test.go b/gno.land/pkg/sdk/vm/params_test.go index 5971cebd2c1..d52c661e8e3 100644 --- a/gno.land/pkg/sdk/vm/params_test.go +++ b/gno.land/pkg/sdk/vm/params_test.go @@ -31,7 +31,10 @@ func TestParamsString(t *testing.T) { fmt.Sprintf("FixedGetReadDepth100: %d\n", p.FixedGetReadDepth100) + fmt.Sprintf("FixedSetReadDepth100: %d\n", p.FixedSetReadDepth100) + fmt.Sprintf("FixedWriteDepth100: %d\n", p.FixedWriteDepth100) + - fmt.Sprintf("IterNextCostFlat: %d\n", p.IterNextCostFlat) + fmt.Sprintf("IterNextCostFlat: %d\n", p.IterNextCostFlat) + + fmt.Sprintf("CodeSubmissionPolicy: %q\n", p.CodeSubmissionPolicy) + + fmt.Sprintf("CodeSubmitters: %v\n", p.CodeSubmitters) + + fmt.Sprintf("PkgApprovers: %v\n", p.PkgApprovers) // Assert: check if the result matches the expected string. if result != expected {