From 602245d2efde7af76fd9846c57a91838b2024f2d Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Wed, 9 Oct 2024 06:52:31 +0200 Subject: [PATCH] feat: inject ParamsKeeper in VMKeeper Signed-off-by: moul <94029+moul@users.noreply.github.com> --- gno.land/pkg/sdk/vm/builtins.go | 50 ++++++++++++++ gno.land/pkg/sdk/vm/common_test.go | 5 +- gno.land/pkg/sdk/vm/keeper.go | 11 ++- gno.land/pkg/sdk/vm/keeper_test.go | 54 +++++++++++++++ gnovm/stdlibs/generated.go | 104 +++++++++++++++++++++++++++++ gnovm/stdlibs/std/context.go | 1 + gnovm/stdlibs/std/params.gno | 28 ++++++++ gnovm/stdlibs/std/params.go | 62 +++++++++++++++++ misc/genstd/Makefile | 6 ++ tm2/pkg/sdk/params/doc.go | 2 + tm2/pkg/sdk/params/keeper.go | 4 ++ tm2/pkg/sdk/params/table.go | 5 +- 12 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 gnovm/stdlibs/std/params.gno create mode 100644 gnovm/stdlibs/std/params.go create mode 100644 misc/genstd/Makefile diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index de58cd3e8ae..3355d6e088e 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -3,6 +3,7 @@ package vm import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -55,3 +56,52 @@ func (bnk *SDKBanker) RemoveCoin(b32addr crypto.Bech32Address, denom string, amo panic(err) } } + +// ---------------------------------------- +// SDKParams + +type SDKParams struct { + vmk *VMKeeper + ctx sdk.Context +} + +func NewSDKParams(vmk *VMKeeper, ctx sdk.Context) *SDKParams { + return &SDKParams{ + vmk: vmk, + ctx: ctx, + } +} + +// SetXXX helpers: +// - dynamically register a new key with the corresponding type in the paramset table (only once). +// - set the value. + +func (prm *SDKParams) SetString(key, value string) { + if !prm.vmk.prmk.Has(prm.ctx, key) { + prm.vmk.prmk.RegisterType(params.NewParamSetPair(key, "", validateNoOp)) + } + prm.vmk.prmk.Set(prm.ctx, key, value) +} + +func (prm *SDKParams) SetBool(key string, value bool) { + if !prm.vmk.prmk.Has(prm.ctx, key) { + prm.vmk.prmk.RegisterType(params.NewParamSetPair(key, true, validateNoOp)) + } + prm.vmk.prmk.Set(prm.ctx, key, value) +} + +func (prm *SDKParams) SetInt64(key string, value int64) { + if !prm.vmk.prmk.Has(prm.ctx, key) { + prm.vmk.prmk.RegisterType(params.NewParamSetPair(key, int64(0), validateNoOp)) + } + prm.vmk.prmk.Set(prm.ctx, key, value) +} + +func (prm *SDKParams) SetUint64(key string, value uint64) { + if !prm.vmk.prmk.Has(prm.ctx, key) { + prm.vmk.prmk.RegisterType(params.NewParamSetPair(key, uint64(0), validateNoOp)) + } + prm.vmk.prmk.Set(prm.ctx, key, value) +} + +func validateNoOp(_ interface{}) error { return nil } diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index 43a8fe1fbec..e3acb278fb6 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -11,6 +11,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/sdk" authm "github.com/gnolang/gno/tm2/pkg/sdk/auth" bankm "github.com/gnolang/gno/tm2/pkg/sdk/bank" + paramsm "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" @@ -47,7 +48,9 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) acck := authm.NewAccountKeeper(iavlCapKey, std.ProtoBaseAccount) bank := bankm.NewBankKeeper(acck) - vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, 100_000_000) + prmk := paramsm.NewParamsKeeper(iavlCapKey, "params") + maxCycles := int64(100_000_000) // XXX: use x/params for 100_000_000 + vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, prmk, maxCycles) mcw := ms.MultiCacheWrap() vmk.Initialize(log.NewNoopLogger(), mcw) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 365473b3e7a..d6c6e4e63d1 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -23,6 +23,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" @@ -59,6 +60,7 @@ type VMKeeper struct { iavlKey store.StoreKey acck auth.AccountKeeper bank bank.BankKeeper + prmk params.ParamsKeeper // cached, the DeliverTx persistent state. gnoStore gno.Store @@ -72,14 +74,15 @@ func NewVMKeeper( iavlKey store.StoreKey, acck auth.AccountKeeper, bank bank.BankKeeper, + prmk params.ParamsKeeper, maxCycles int64, ) *VMKeeper { - // TODO: create an Options struct to avoid too many constructor parameters vmk := &VMKeeper{ baseKey: baseKey, iavlKey: iavlKey, acck: acck, bank: bank, + prmk: prmk, maxCycles: maxCycles, } return vmk @@ -262,6 +265,7 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add OrigPkgAddr: pkgAddr.Bech32(), // XXX: should we remove the banker ? Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), } @@ -363,6 +367,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { OrigSendSpent: new(std.Coins), OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), } // Parse and run the files, construct *PV. @@ -464,6 +469,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { OrigSendSpent: new(std.Coins), OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), } // Construct machine and evaluate. @@ -563,6 +569,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { OrigSendSpent: new(std.Coins), OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), } // Parse and run the files, construct *PV. @@ -724,6 +731,7 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res // OrigSendSpent: nil, OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. + Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), } m := gno.NewMachineWithOptions( @@ -791,6 +799,7 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string // OrigSendSpent: nil, OrigPkgAddr: pkgAddr.Bech32(), Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. + Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), } m := gno.NewMachineWithOptions( diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index 9257da2ddaf..9f3c3e422bf 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -298,6 +298,60 @@ func Echo(msg string) string { assert.Error(t, err) } +// Using x/params from a realm. +func TestVMKeeperParams(t *testing.T) { + env := setupTestEnv() + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) + + // Give "addr1" some gnots. + addr := crypto.AddressFromPreimage([]byte("addr1")) + acc := env.acck.NewAccountWithAddress(ctx, addr) + env.acck.SetAccount(ctx, acc) + env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + // env.prmk. + assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + + // Create test package. + files := []*std.MemFile{ + {"init.gno", ` +package test + +import "std" + +func init() { + std.SetConfig("foo", "foo1") +} + +func Do() string { + std.SetConfig("bar", int64(1337)) + std.SetConfig("foo", "foo2") // override init + + return "XXX" // return std.GetConfig("gno.land/r/test.foo"), if we want to expose std.GetConfig, maybe as a std.TestGetConfig +}`}, + } + pkgPath := "gno.land/r/test" + msg1 := NewMsgAddPackage(addr, pkgPath, files) + err := env.vmk.AddPackage(ctx, msg1) + assert.NoError(t, err) + + // Run Echo function. + coins := std.MustParseCoins(ugnot.ValueString(9_000_000)) + msg2 := NewMsgCall(addr, coins, pkgPath, "Do", []string{}) + + res, err := env.vmk.Call(ctx, msg2) + assert.NoError(t, err) + _ = res + expected := fmt.Sprintf("(\"%s\" string)\n\n", "XXX") // XXX: return something more useful + assert.Equal(t, expected, res) + + var foo string + var bar int64 + env.vmk.prmk.Get(ctx, "gno.land/r/test.foo.string", &foo) + env.vmk.prmk.Get(ctx, "gno.land/r/test.bar.int64", &bar) + assert.Equal(t, "foo2", foo) + assert.Equal(t, int64(1337), bar) +} + // Assign admin as OrigCaller on deploying the package. func TestVMKeeperOrigCallerInit(t *testing.T) { env := setupTestEnv() diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 4c460e220b7..e6eca53ffe3 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -721,6 +721,110 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "std", + "setConfigString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + libs_std.X_setConfigString( + m, + p0, p1) + }, + }, + { + "std", + "setConfigBool", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("bool")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 bool + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + libs_std.X_setConfigBool( + m, + p0, p1) + }, + }, + { + "std", + "setConfigInt64", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("int64")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 int64 + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + libs_std.X_setConfigInt64( + m, + p0, p1) + }, + }, + { + "std", + "setConfigUint64", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("uint64")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 uint64 + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + libs_std.X_setConfigUint64( + m, + p0, p1) + }, + }, { "strconv", "Itoa", diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index ff5c91a14eb..a0dafe5dc44 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -18,6 +18,7 @@ type ExecContext struct { OrigSend std.Coins OrigSendSpent *std.Coins // mutable Banker BankerInterface + Params ParamsInterface EventLogger *sdk.EventLogger } diff --git a/gnovm/stdlibs/std/params.gno b/gnovm/stdlibs/std/params.gno new file mode 100644 index 00000000000..a3dc47d4bd4 --- /dev/null +++ b/gnovm/stdlibs/std/params.gno @@ -0,0 +1,28 @@ +package std + +// These are native bindings to the banker's functions. +func setConfigString(key string, val string) +func setConfigBool(key string, val bool) +func setConfigInt64(key string, val int64) +func setConfigUint64(key string, val uint64) + +// XXX: add doc +func SetConfig(key string, val interface{}) { + switch v := val.(type) { + case string: + setConfigString(key, v) + case bool: + setConfigBool(key, v) + case int64: + setConfigInt64(key, v) + case uint64: + setConfigUint64(key, v) + default: + panic("unsupported type") + } +} + +// XXX: add doc +//func GetConfig(key string) string { +// return getConfig(key) +//} diff --git a/gnovm/stdlibs/std/params.go b/gnovm/stdlibs/std/params.go new file mode 100644 index 00000000000..008315bfd98 --- /dev/null +++ b/gnovm/stdlibs/std/params.go @@ -0,0 +1,62 @@ +package std + +import ( + "fmt" + "unicode" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +// ParamsInterface is the interface through which Gno is capable of accessing +// the blockchain's params. +// +// The name is what it is to avoid a collision with Gno's Params, when +// transpiling. +type ParamsInterface interface { + SetString(key, val string) + SetBool(key string, val bool) + SetInt64(key string, val int64) + SetUint64(key string, val uint64) + // XXX: GetString(key string) (string, error)? + +} + +func X_setConfigString(m *gno.Machine, key, val string) { + pk := pkey(m, key, "string") + GetContext(m).Params.SetString(pk, val) +} + +func X_setConfigBool(m *gno.Machine, key string, val bool) { + pk := pkey(m, key, "bool") + GetContext(m).Params.SetBool(pk, val) +} + +func X_setConfigInt64(m *gno.Machine, key string, val int64) { + pk := pkey(m, key, "int64") + GetContext(m).Params.SetInt64(pk, val) +} + +func X_setConfigUint64(m *gno.Machine, key string, val uint64) { + pk := pkey(m, key, "uint64") + GetContext(m).Params.SetUint64(pk, val) +} + +func pkey(m *gno.Machine, key string, kind string) string { + // validate key. + if len(key) == 0 { + panic("empty param key") + } + first := rune(key[0]) + if !unicode.IsLetter(first) && first != '_' { + panic("invalid param key: " + key) + } + for _, char := range key[1:] { + if !unicode.IsLetter(char) && !unicode.IsDigit(char) && char != '_' { + panic("invalid param key: " + key) + } + } + + // decorate key with realm and type. + _, rlmPath := currentRealm(m) + return fmt.Sprintf("%s.%s.%s", rlmPath, key, kind) +} diff --git a/misc/genstd/Makefile b/misc/genstd/Makefile new file mode 100644 index 00000000000..2022a6cc2b4 --- /dev/null +++ b/misc/genstd/Makefile @@ -0,0 +1,6 @@ +run: + cd ../../gnovm/stdlibs && go run ../../misc/genstd + cd ../../gnovm/tests/stdlibs && go run ../../../misc/genstd + +test: + go test -v . diff --git a/tm2/pkg/sdk/params/doc.go b/tm2/pkg/sdk/params/doc.go index bf7449adb49..a433b5eb115 100644 --- a/tm2/pkg/sdk/params/doc.go +++ b/tm2/pkg/sdk/params/doc.go @@ -10,4 +10,6 @@ // transient store and .Modified helper have also been removed but can be // implemented later if needed. Keys are represented as strings instead of // []byte. +// +// XXX: removes isAlphaNum validation for keys. package params diff --git a/tm2/pkg/sdk/params/keeper.go b/tm2/pkg/sdk/params/keeper.go index da1283bd5cf..e24e216bafe 100644 --- a/tm2/pkg/sdk/params/keeper.go +++ b/tm2/pkg/sdk/params/keeper.go @@ -145,6 +145,10 @@ func (pk ParamsKeeper) WithKeyTable(table KeyTable) ParamsKeeper { return pk } +func (pk ParamsKeeper) RegisterType(psp ParamSetPair) { + pk.table.RegisterType(psp) +} + // XXX: GetAllKeys // XXX: GetAllParams // XXX: ViewKeeper diff --git a/tm2/pkg/sdk/params/table.go b/tm2/pkg/sdk/params/table.go index 1ce6a3b92db..34747f0e455 100644 --- a/tm2/pkg/sdk/params/table.go +++ b/tm2/pkg/sdk/params/table.go @@ -34,9 +34,10 @@ func (t KeyTable) RegisterType(psp ParamSetPair) KeyTable { if len(psp.Key) == 0 { panic("cannot register ParamSetPair with an parameter empty key") } - if !isAlphaNumeric(psp.Key) { + // XXX: sanitize more? + /*if !isAlphaNumeric(psp.Key) { panic("cannot register ParamSetPair with a non-alphanumeric parameter key") - } + }*/ if psp.ValidatorFn == nil { panic("cannot register ParamSetPair without a value validation function") }