diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index fa9e2d11e29..9fa5998cb63 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -122,6 +122,8 @@ func NewDevNode(ctx context.Context, cfg *NodeConfig) (*Node, error) { initialState: cfg.InitialTxs, currentStateIndex: len(cfg.InitialTxs), } + + // generate genesis state genesis := gnoland.DefaultGenState() genesis.Balances = cfg.BalancesList genesis.Txs = append(pkgsTxs, cfg.InitialTxs...) diff --git a/contribs/gnodev/pkg/dev/node_state.go b/contribs/gnodev/pkg/dev/node_state.go index 3f996bc7716..2ac3044d026 100644 --- a/contribs/gnodev/pkg/dev/node_state.go +++ b/contribs/gnodev/pkg/dev/node_state.go @@ -131,7 +131,6 @@ func (n *Node) ExportStateAsGenesis(ctx context.Context) (*bft.GenesisDoc, error // Get current blockstore state doc := *n.Node.GenesisDoc() // copy doc - genState := doc.AppState.(gnoland.GnoGenesisState) genState.Balances = n.config.BalancesList genState.Txs = state diff --git a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno deleted file mode 100644 index 13ca572c512..00000000000 --- a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno +++ /dev/null @@ -1,132 +0,0 @@ -package main - -import ( - "gno.land/p/demo/dao" - "gno.land/r/gov/dao/bridge" - govdaov2 "gno.land/r/gov/dao/v2" - "gno.land/r/sys/params" -) - -func init() { - mExec := params.NewStringPropExecutor("prop1.string", "value1") - title := "Setting prop1.string param" - comment := "setting prop1.string param" - prop := dao.ProposalRequest{ - Title: title, - Description: comment, - Executor: mExec, - } - id := bridge.GovDAO().Propose(prop) - println("new prop", id) -} - -func main() { - println("--") - println(govdaov2.Render("")) - println("--") - println(govdaov2.Render("0")) - println("--") - bridge.GovDAO().VoteOnProposal(0, "YES") - println("--") - println(govdaov2.Render("0")) - println("--") - bridge.GovDAO().ExecuteProposal(0) - println("--") - println(govdaov2.Render("0")) -} - -// Output: -// new prop 0 -// -- -// # GovDAO Proposals -// -// ## [Prop #0 - Setting prop1.string param](/r/gov/dao/v2:0) -// -// **Status: ACTIVE** -// -// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** -// -// -// -- -// # Proposal #0 - Setting prop1.string param -// -// ## Description -// -// setting prop1.string param -// -// ## Proposal information -// -// **Status: ACTIVE** -// -// **Voting stats:** -// - YES 0 (0%) -// - NO 0 (0%) -// - ABSTAIN 0 (0%) -// - MISSING VOTES 10 (100%) -// -// -// **Threshold met: FALSE** -// -// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** -// -// ### Actions -// -// #### [[Vote YES](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=YES)] - [[Vote NO](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=NO)] - [[Vote ABSTAIN](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=ABSTAIN)] -// -// -// -- -// -- -// # Proposal #0 - Setting prop1.string param -// -// ## Description -// -// setting prop1.string param -// -// ## Proposal information -// -// **Status: ACCEPTED** -// -// **Voting stats:** -// - YES 10 (100%) -// - NO 0 (0%) -// - ABSTAIN 0 (0%) -// - MISSING VOTES 0 (0%) -// -// -// **Threshold met: TRUE** -// -// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** -// -// ### Actions -// -// The voting period for this proposal is over. -// -// -// -- -// -- -// # Proposal #0 - Setting prop1.string param -// -// ## Description -// -// setting prop1.string param -// -// ## Proposal information -// -// **Status: EXECUTION SUCCESSFUL** -// -// **Voting stats:** -// - YES 10 (100%) -// - NO 0 (0%) -// - ABSTAIN 0 (0%) -// - MISSING VOTES 0 (0%) -// -// -// **Threshold met: TRUE** -// -// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** -// -// ### Actions -// -// The voting period for this proposal is over. -// -// diff --git a/examples/gno.land/r/nemanya/config/gno.mod b/examples/gno.land/r/nemanya/config/gno.mod index 8ffbc32f571..4388b5bd525 100644 --- a/examples/gno.land/r/nemanya/config/gno.mod +++ b/examples/gno.land/r/nemanya/config/gno.mod @@ -1 +1 @@ -module gno.land/r/nemanya/config \ No newline at end of file +module gno.land/r/nemanya/config diff --git a/examples/gno.land/r/nemanya/home/gno.mod b/examples/gno.land/r/nemanya/home/gno.mod index 1994cf7c11b..d0220197489 100644 --- a/examples/gno.land/r/nemanya/home/gno.mod +++ b/examples/gno.land/r/nemanya/home/gno.mod @@ -1 +1 @@ -module gno.land/r/nemanya/home \ No newline at end of file +module gno.land/r/nemanya/home diff --git a/examples/gno.land/r/nemanya/home/home.gno b/examples/gno.land/r/nemanya/home/home.gno index 08b831b0d17..08e24baecfd 100644 --- a/examples/gno.land/r/nemanya/home/home.gno +++ b/examples/gno.land/r/nemanya/home/home.gno @@ -27,12 +27,12 @@ type Project struct { } var ( - textArt string - aboutMe string - sponsorInfo string - socialLinks map[string]SocialLink - gnoProjects map[string]Project - otherProjects map[string]Project + textArt string + aboutMe string + sponsorInfo string + socialLinks map[string]SocialLink + gnoProjects map[string]Project + otherProjects map[string]Project totalDonations std.Coins ) @@ -266,15 +266,15 @@ func Withdraw() string { panic(config.ErrUnauthorized) } - banker := std.GetBanker(std.BankerTypeRealmSend) - realmAddress := std.GetOrigPkgAddr() - coins := banker.GetCoins(realmAddress) + banker := std.GetBanker(std.BankerTypeRealmSend) + realmAddress := std.GetOrigPkgAddr() + coins := banker.GetCoins(realmAddress) - if len(coins) == 0 { - return "No coins available to withdraw" - } + if len(coins) == 0 { + return "No coins available to withdraw" + } - banker.SendCoins(realmAddress, config.Address(), coins) + banker.SendCoins(realmAddress, config.Address(), coins) - return "Successfully withdrew all coins to config address" + return "Successfully withdrew all coins to config address" } diff --git a/examples/gno.land/r/sys/params/params.gno b/examples/gno.land/r/sys/params/params.gno deleted file mode 100644 index fa04c90de3f..00000000000 --- a/examples/gno.land/r/sys/params/params.gno +++ /dev/null @@ -1,54 +0,0 @@ -// Package params provides functions for creating parameter executors that -// interface with the Params Keeper. -// -// This package enables setting various parameter types (such as strings, -// integers, booleans, and byte slices) through the GovDAO proposal mechanism. -// Each function returns an executor that, when called, sets the specified -// parameter in the Params Keeper. -// -// The executors are designed to be used within governance proposals to modify -// parameters dynamically. The integration with the GovDAO allows for parameter -// changes to be proposed and executed in a controlled manner, ensuring that -// modifications are subject to governance processes. -// -// Example usage: -// -// executor := params.NewStringPropExecutor("exampleKey", "exampleValue") -// // This executor can be used in a governance proposal to set the parameter. -package params - -import ( - "std" - - "gno.land/p/demo/dao" - "gno.land/r/gov/dao/bridge" -) - -func NewStringPropExecutor(key string, value string) dao.Executor { - return newPropExecutor(key, func() { std.SetParamString(key, value) }) -} - -func NewInt64PropExecutor(key string, value int64) dao.Executor { - return newPropExecutor(key, func() { std.SetParamInt64(key, value) }) -} - -func NewUint64PropExecutor(key string, value uint64) dao.Executor { - return newPropExecutor(key, func() { std.SetParamUint64(key, value) }) -} - -func NewBoolPropExecutor(key string, value bool) dao.Executor { - return newPropExecutor(key, func() { std.SetParamBool(key, value) }) -} - -func NewBytesPropExecutor(key string, value []byte) dao.Executor { - return newPropExecutor(key, func() { std.SetParamBytes(key, value) }) -} - -func newPropExecutor(key string, fn func()) dao.Executor { - callback := func() error { - fn() - std.Emit("set", "k", key) - return nil - } - return bridge.GovDAO().NewGovDAOExecutor(callback) -} diff --git a/examples/gno.land/r/sys/params/params_test.gno b/examples/gno.land/r/sys/params/params_test.gno deleted file mode 100644 index eaa1ad039d3..00000000000 --- a/examples/gno.land/r/sys/params/params_test.gno +++ /dev/null @@ -1,15 +0,0 @@ -package params - -import "testing" - -// Testing this package is limited because it only contains an `std.Set` method -// without a corresponding `std.Get` method. For comprehensive testing, refer to -// the tests located in the r/gov/dao/ directory, specifically in one of the -// propX_filetest.gno files. - -func TestNewStringPropExecutor(t *testing.T) { - executor := NewStringPropExecutor("foo", "bar") - if executor == nil { - t.Errorf("executor shouldn't be nil") - } -} diff --git a/examples/gno.land/r/sys/params/unlock.gno b/examples/gno.land/r/sys/params/unlock.gno new file mode 100644 index 00000000000..14ce724009a --- /dev/null +++ b/examples/gno.land/r/sys/params/unlock.gno @@ -0,0 +1,42 @@ +package params + +import ( + "std" + + "gno.land/p/demo/dao" + "gno.land/r/gov/dao/bridge" +) + +const ( + lockSendKey = "lockSend.bool" + UnlockSendTitle = "Proposal to unlock the sending functionality for ugnot." + LockSendTitle = "Proposal to lock the sending functionality for ugnot." +) + +func ProposeUnlockSend() uint64 { + callback := func() error { + std.SetParamBool(lockSendKey, false) + return nil + } + return propose(callback, UnlockSendTitle, "") +} + +func ProposeLockSend() uint64 { + callback := func() error { + std.SetParamBool(lockSendKey, true) + return nil + } + return propose(callback, LockSendTitle, "") +} + +func propose(callback func() error, title, desc string) uint64 { + // The callback function is executed only after the proposal is voted on + // and approved by the GovDAO. + exe := bridge.GovDAO().NewGovDAOExecutor(callback) + prop := dao.ProposalRequest{ + Title: title, + Description: desc, + Executor: exe, + } + return bridge.GovDAO().Propose(prop) +} diff --git a/examples/gno.land/r/sys/params/unlock_test.gno b/examples/gno.land/r/sys/params/unlock_test.gno new file mode 100644 index 00000000000..6be6f9f4d6c --- /dev/null +++ b/examples/gno.land/r/sys/params/unlock_test.gno @@ -0,0 +1,49 @@ +package params + +import ( + "testing" + + "gno.land/p/demo/dao" + "gno.land/p/demo/simpledao" + "gno.land/p/demo/urequire" + "gno.land/r/gov/dao/bridge" +) + +func TestProUnlockSend(t *testing.T) { + govdao := bridge.GovDAO() + id := ProposeUnlockSend() + p, err := govdao.GetPropStore().ProposalByID(id) + urequire.NoError(t, err) + urequire.Equal(t, UnlockSendTitle, p.Title()) +} + +func TestFailUnlockSend(t *testing.T) { + govdao := bridge.GovDAO() + id := ProposeUnlockSend() + urequire.PanicsWithMessage( + t, + simpledao.ErrProposalNotAccepted.Error(), + func() { + govdao.ExecuteProposal(id) + }, + ) +} + +func TestExeUnlockSend(t *testing.T) { + govdao := bridge.GovDAO() + id := ProposeUnlockSend() + p, err := govdao.GetPropStore().ProposalByID(id) + urequire.NoError(t, err) + urequire.True(t, dao.Active == p.Status()) + + govdao.VoteOnProposal(id, dao.YesVote) + urequire.True(t, dao.Accepted == p.Status()) + urequire.NotPanics( + t, + func() { + govdao.ExecuteProposal(id) + }, + ) + + urequire.True(t, dao.ExecutionSuccessful == p.Status()) +} diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 9e8f2163441..4665b33bb1e 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -89,11 +89,15 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, cfg.DB) // Construct keepers. - paramsKpr := params.NewParamsKeeper(mainKey, "vm") + + km := params.NewPrefixKeyMapper() + km.RegisterPrefix(auth.ParamsPrefixKey) + km.RegisterPrefix(bank.ParamsPrefixKey) + km.RegisterPrefix(vm.ParamsPrefixKey) + paramsKpr := params.NewParamsKeeper(mainKey, km) acctKpr := auth.NewAccountKeeper(mainKey, paramsKpr, ProtoGnoAccount) + bankKpr := bank.NewBankKeeper(acctKpr, paramsKpr) gpKpr := auth.NewGasPriceKeeper(mainKey) - bankKpr := bank.NewBankKeeper(acctKpr) - vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, paramsKpr) vmk.Output = cfg.VMOutput @@ -116,7 +120,6 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { ) { // Add last gas price in the context ctx = ctx.WithValue(auth.GasPriceContextKey{}, gpKpr.LastGasPrice(ctx)) - // Override auth params. ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acctKpr.GetParams(ctx)) // Continue on with default auth ante handler. @@ -300,11 +303,8 @@ func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, appState any) ([]abci if !ok { return nil, fmt.Errorf("invalid AppState of type %T", appState) } - cfg.acctKpr.InitGenesis(ctx, state.Auth) - params := cfg.acctKpr.GetParams(ctx) - ctx = ctx.WithValue(auth.AuthParamsContextKey{}, params) - auth.InitChainer(ctx, cfg.gpKpr.(auth.GasPriceKeeper), params.InitialGasPrice) + cfg.bankKpr.InitGenesis(ctx, state.Bank) // Apply genesis balances. for _, bal := range state.Balances { acc := cfg.acctKpr.NewAccountWithAddress(ctx, bal.Address) @@ -319,6 +319,14 @@ func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, appState any) ([]abci for _, param := range state.Params { param.register(ctx, cfg.paramsKpr) } + // The account keeper's initial genesis state must be set after genesis + // accounts are created in account keeeper with genesis balances + cfg.acctKpr.InitGenesis(ctx, state.Auth) + cfg.vmKpr.InitGenesis(ctx, state.VM) + + params := cfg.acctKpr.GetParams(ctx) + ctx = ctx.WithValue(auth.AuthParamsContextKey{}, params) + auth.InitChainer(ctx, cfg.gpKpr, params.InitialGasPrice) // Replay genesis txs. txResponses := make([]abci.ResponseDeliverTx, 0, len(state.Txs)) diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 375602cfa4a..afe8c3b03f4 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -114,6 +114,7 @@ func TestNewAppWithOptions(t *testing.T) { {"params/vm/foo.bool", `true`}, {"params/vm/foo.bytes", `"SGkh"`}, // XXX: make this test more readable } + for _, tc := range tcs { qres := bapp.Query(abci.RequestQuery{ Path: tc.path, @@ -216,12 +217,17 @@ func testInitChainerLoadStdlib(t *testing.T, cached bool) { //nolint:thelper cfg := InitChainerConfig{ StdlibDir: stdlibDir, vmKpr: mock, + acctKpr: &mockAuthKeeper{}, + bankKpr: &mockBankKeeper{}, + paramsKpr: &mockParamsKeeper{}, + gpKpr: &mockGasPriceKeeper{}, CacheStdlibLoad: cached, } // Construct keepers. - paramsKpr := params.NewParamsKeeper(iavlCapKey, "") - cfg.acctKpr = auth.NewAccountKeeper(iavlCapKey, paramsKpr, ProtoGnoAccount) - cfg.gpKpr = auth.NewGasPriceKeeper(iavlCapKey) + // paramsKpr := params.NewParamsKeeper(iavlCapKey, params.PrefixKeyMapper{}) + // cfg.paramsKpr = &mockParamsKeeper{} + // cfg.acctKpr = auth.NewAccountKeeper(iavlCapKey, paramsKpr, ProtoGnoAccount) + // cfg.gpKpr = auth.NewGasPriceKeeper(iavlCapKey) cfg.InitChainer(testCtx, abci.RequestInitChain{ AppState: DefaultGenState(), }) @@ -821,11 +827,16 @@ func newGasPriceTestApp(t *testing.T) abci.Application { baseApp.MountStoreWithDB(mainKey, iavl.StoreConstructor, cfg.DB) baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, cfg.DB) + km := params.NewPrefixKeyMapper() + km.RegisterPrefix(auth.ParamsPrefixKey) + km.RegisterPrefix(bank.ParamsPrefixKey) + km.RegisterPrefix(vm.ParamsPrefixKey) + // Construct keepers. - paramsKpr := params.NewParamsKeeper(mainKey, "") + paramsKpr := params.NewParamsKeeper(mainKey, km) acctKpr := auth.NewAccountKeeper(mainKey, paramsKpr, ProtoGnoAccount) gpKpr := auth.NewGasPriceKeeper(mainKey) - bankKpr := bank.NewBankKeeper(acctKpr) + bankKpr := bank.NewBankKeeper(acctKpr, paramsKpr) vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, paramsKpr) // Set InitChainer diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index d7844d77b57..d2d9bc72af5 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -13,6 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" "github.com/pelletier/go-toml" ) @@ -203,7 +204,8 @@ func DefaultGenState() GnoGenesisState { Balances: []Balance{}, Txs: []TxWithMetadata{}, Auth: authGen, + Bank: bank.DefaultGenesisState(), + VM: vmm.DefaultGenesisState(), } - return gs } diff --git a/gno.land/pkg/gnoland/mock_test.go b/gno.land/pkg/gnoland/mock_test.go index 62aecaf5278..7788830ab4d 100644 --- a/gno.land/pkg/gnoland/mock_test.go +++ b/gno.land/pkg/gnoland/mock_test.go @@ -4,10 +4,15 @@ import ( "log/slog" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "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/service" + "github.com/gnolang/gno/tm2/pkg/std" ) type ( @@ -113,6 +118,82 @@ func (m *mockVMKeeper) CommitGnoTransactionStore(ctx sdk.Context) { } } +func (m *mockVMKeeper) InitGenesis(ctx sdk.Context, gs vm.GenesisState) { + // TODO: +} + +type mockBankKeeper struct{} + +func (m *mockBankKeeper) InputOutputCoins(ctx sdk.Context, inputs []bank.Input, outputs []bank.Output) error { + return nil +} + +func (m *mockBankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + return nil +} + +func (m *mockBankKeeper) SubtractCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) { + return nil, nil +} + +func (m *mockBankKeeper) AddCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) { + return nil, nil +} + +func (m *mockBankKeeper) SetCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) error { + return nil +} + +func (m *mockBankKeeper) InitGenesis(ctx sdk.Context, data bank.GenesisState) {} +func (m *mockBankKeeper) GetParams(ctx sdk.Context) bank.Params { return bank.Params{} } +func (m *mockBankKeeper) GetCoins(ctx sdk.Context, addr crypto.Address) std.Coins { return nil } +func (m *mockBankKeeper) HasCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) bool { + return true +} + +type mockAuthKeeper struct{} + +func (m *mockAuthKeeper) NewAccountWithAddress(ctx sdk.Context, addr crypto.Address) std.Account { + return nil +} +func (m *mockAuthKeeper) GetAccount(ctx sdk.Context, addr crypto.Address) std.Account { return nil } +func (m *mockAuthKeeper) GetAllAccounts(ctx sdk.Context) []std.Account { return nil } +func (m *mockAuthKeeper) SetAccount(ctx sdk.Context, acc std.Account) {} +func (m *mockAuthKeeper) IterateAccounts(ctx sdk.Context, process func(std.Account) bool) {} +func (m *mockAuthKeeper) InitGenesis(ctx sdk.Context, data auth.GenesisState) {} +func (m *mockAuthKeeper) GetParams(ctx sdk.Context) auth.Params { return auth.Params{} } + +type mockParamsKeeper struct{} + +func (m *mockParamsKeeper) GetString(ctx sdk.Context, key string, ptr *string) {} +func (m *mockParamsKeeper) GetInt64(ctx sdk.Context, key string, ptr *int64) {} +func (m *mockParamsKeeper) GetUint64(ctx sdk.Context, key string, ptr *uint64) {} +func (m *mockParamsKeeper) GetBool(ctx sdk.Context, key string, ptr *bool) {} +func (m *mockParamsKeeper) GetBytes(ctx sdk.Context, key string, ptr *[]byte) {} + +func (m *mockParamsKeeper) SetString(ctx sdk.Context, key string, value string) {} +func (m *mockParamsKeeper) SetInt64(ctx sdk.Context, key string, value int64) {} +func (m *mockParamsKeeper) SetUint64(ctx sdk.Context, key string, value uint64) {} +func (m *mockParamsKeeper) SetBool(ctx sdk.Context, key string, value bool) {} +func (m *mockParamsKeeper) SetBytes(ctx sdk.Context, key string, value []byte) {} + +func (m *mockParamsKeeper) Has(ctx sdk.Context, key string) bool { return false } +func (m *mockParamsKeeper) GetRaw(ctx sdk.Context, key string) []byte { return nil } + +func (m *mockParamsKeeper) GetParams(ctx sdk.Context, prefixKey string, key string, target interface{}) (bool, error) { + return true, nil +} + +func (m *mockParamsKeeper) SetParams(ctx sdk.Context, prefixKey string, key string, params interface{}) error { + return nil +} + +type mockGasPriceKeeper struct{} + +func (m *mockGasPriceKeeper) LastGasPrice(ctx sdk.Context) std.GasPrice { return std.GasPrice{} } +func (m *mockGasPriceKeeper) SetGasPrice(ctx sdk.Context, gp std.GasPrice) {} +func (m *mockGasPriceKeeper) UpdateGasPrice(ctx sdk.Context) {} + type ( lastBlockHeightDelegate func() int64 loggerDelegate func() *slog.Logger diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go index ed35c4141f4..af4a881e907 100644 --- a/gno.land/pkg/gnoland/types.go +++ b/gno.land/pkg/gnoland/types.go @@ -7,8 +7,10 @@ import ( "fmt" "os" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -28,8 +30,10 @@ func ProtoGnoAccount() std.Account { type GnoGenesisState struct { Balances []Balance `json:"balances"` Txs []TxWithMetadata `json:"txs"` - Params []Param `json:"params"` Auth auth.GenesisState `json:"auth"` + Bank bank.GenesisState `json:"bank"` + VM vm.GenesisState `json:"vm"` + Params []Param `json:"params"` } type TxWithMetadata struct { diff --git a/gno.land/pkg/integration/testdata/genesis_params.txtar b/gno.land/pkg/integration/testdata/genesis_params.txtar index d09ededf78a..302542cb993 100644 --- a/gno.land/pkg/integration/testdata/genesis_params.txtar +++ b/gno.land/pkg/integration/testdata/genesis_params.txtar @@ -25,4 +25,3 @@ gnokey query params/vm/gno.land/r/sys/params.test.foo.bool stdout 'data: true' # TODO: Consider adding a test case for a byte array parameter - diff --git a/gno.land/pkg/integration/testdata/params.txtar b/gno.land/pkg/integration/testdata/params.txtar index 30363aa6369..617527511f0 100644 --- a/gno.land/pkg/integration/testdata/params.txtar +++ b/gno.land/pkg/integration/testdata/params.txtar @@ -3,57 +3,57 @@ gnoland start # query before adding the package -gnokey query params/vm/gno.land/r/sys/setter.foo.string +gnokey query params/vm/gno.land/r/sys/params.foo.string stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +gnokey query params/vm/gno.land/r/sys/params.bar.bool stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 +gnokey query params/vm/gno.land/r/sys/params.baz.int64 stdout 'data: $' -gnokey maketx addpkg -pkgdir $WORK/setter -pkgpath gno.land/r/sys/setter -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK/params -pkgpath gno.land/r/sys/params -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 # query after adding the package, but before setting values -gnokey query params/vm/gno.land/r/sys/setter.foo.string +gnokey query params/vm/gno.land/r/sys/params.foo.string stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +gnokey query params/vm/gno.land/r/sys/params.bar.bool stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 +gnokey query params/vm/gno.land/r/sys/params.baz.int64 stdout 'data: $' # set foo (string) -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetFoo -args foo1 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.foo.string +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetFoo -args foo1 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm/gno.land/r/sys/params.foo.string stdout 'data: "foo1"' # override foo -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetFoo -args foo2 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.foo.string +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetFoo -args foo2 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm/gno.land/r/sys/params.foo.string stdout 'data: "foo2"' # set bar (bool) -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBar -args true -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetBar -args true -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm/gno.land/r/sys/params.bar.bool stdout 'data: true' -# override bar -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBar -args false -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +# override bar +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetBar -args false -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm/gno.land/r/sys/params.bar.bool stdout 'data: false' # set baz (bool) -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBaz -args 1337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetBaz -args 1337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm/gno.land/r/sys/params.baz.int64 stdout 'data: "1337"' # override baz -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBaz -args 31337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetBaz -args 31337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm/gno.land/r/sys/params.baz.int64 stdout 'data: "31337"' --- setter/setter.gno -- +-- params/setter.gno -- package setter import ( diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 8550419f205..3acdb3d2a0d 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 96411' +stdout 'GAS USED: 96693' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 96411' # same as simulate only +stdout 'GAS USED: 96693' # same as simulate only -- package/package.gno -- diff --git a/gno.land/pkg/integration/testdata/transfer_lock.txtar b/gno.land/pkg/integration/testdata/transfer_lock.txtar new file mode 100644 index 00000000000..9a67ceccd36 --- /dev/null +++ b/gno.land/pkg/integration/testdata/transfer_lock.txtar @@ -0,0 +1,54 @@ +## It tests locking token transfers while allowing the payment of gas fees. + +## locking transfer applies to regular acocunts +adduser regular1 + + +loadpkg gno.land/r/demo/wugnot +loadpkg gno.land/r/demo/echo + +## start a new node. +## The -lock-transfer flag is intended for integration testing purposes +## and is not a valid application flag for gnoland. + +gnoland start -lock-transfer + +## User test1 is an unrestricted account specified in the genesis state +gnokey maketx send -send "9999999ugnot" -to $USER_ADDR_regular1 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +stdout 'OK!' + +## Restricted simple token transfer +! gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test regular1 +stderr 'restricted token transfer error' + +## Restricted token transfer by calling a realm deposit function. +! gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -gas-fee 1000000ugnot -send "10000ugnot" -gas-wanted 2000000 -broadcast -chainid=tendermint_test regular1 +stderr 'restricted token transfer error' + + +## Restricted token transfer with depositing to a realm package while adding a package. +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/bank -deposit "1000ugnot" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test regular1 +stderr 'restricted token transfer error' + +## paying gas fees to add a package is acceptable. +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/bank -gas-fee 1000000ugnot -gas-wanted 9500000 -broadcast -chainid=tendermint_test regular1 +stdout 'OK!' + +## paying gas fees to call a realm function is acceptable. +gnokey maketx call -pkgpath gno.land/r/demo/echo -func Render -args "Hello!" -gas-fee 1000000ugnot -gas-wanted 9500000 -broadcast -chainid=tendermint_test regular1 +stdout 'Hello!' + +-- bank.gno -- +package bank +import ( +"std" +) +func Withdraw(denom string, amt int64) string{ + caller := std.GetOrigCaller() + coin := std.Coins{{denom, amt}} + banker := std.GetBanker(std.BankerTypeOrigSend) + pkgaddr := std.GetOrigPkgAddr() + banker.SendCoins(pkgaddr, caller, coin) + return "Withdrawed!" +} diff --git a/gno.land/pkg/integration/testdata/transfer_unlock.txtar b/gno.land/pkg/integration/testdata/transfer_unlock.txtar new file mode 100644 index 00000000000..29891ebc2ea --- /dev/null +++ b/gno.land/pkg/integration/testdata/transfer_unlock.txtar @@ -0,0 +1,42 @@ +## It tests unlocking token transfers through GovDAO voting +loadpkg gno.land/r/sys/params +loadpkg gno.land/r/gov/dao/v2 + +patchpkg "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + +adduser regular1 + +## The -lock-transfer flag is not a Gnoland service flag; it is a flag for the txtar setting. +gnoland start -lock-transfer + +## User test1 is an unrestricted account specified in the genesis state +gnokey maketx send -send "9999999ugnot" -to $USER_ADDR_regular1 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +stdout 'OK!' + +## Restricted simple token transfer for a regular account +! gnokey maketx send -send "100ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test regular1 + +stderr 'restricted token transfer error' + +## Submit a proposal to unlock the transfer. When token transfer is locked, only the predefined unrestricted account test1 in the genesis state can +## pay the fee and submit a proposal to unlock the transfer. +gnokey maketx call -pkgpath gno.land/r/sys/params -func ProposeUnlockSend -send 250000000ugnot -gas-fee 1ugnot -gas-wanted 9500000 -broadcast -chainid=tendermint_test test1 + +stdout '(0 uint64)' + + +## Vote unlock proposal with unrestricted account test1 +gnokey maketx call -pkgpath gno.land/r/gov/dao/v2 -func VoteOnProposal -args 0 -args "YES" -send 250000000ugnot -gas-fee 1ugnot -gas-wanted 9500000 -broadcast -chainid=tendermint_test test1 + +stdout 'OK!' + +## Execute unlock proposal with unrestricted account test1 +gnokey maketx call -pkgpath gno.land/r/gov/dao/v2 -func ExecuteProposal -args 0 -send 250000000ugnot -gas-fee 1ugnot -gas-wanted 9500000 -broadcast -chainid=tendermint_test test1 + +stdout 'OK!' + +## Restricted transfer is unlocked, allowing simple token transfers for regular accounts. +gnokey maketx send -send "100ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test regular1 + +stdout 'OK!' diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 0a181950bb3..02ffaf7d62d 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -134,13 +134,11 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // Track new user balances added via the `adduser` // command and packages added with the `loadpkg` command. // This genesis will be use when node is started. - - genesis := gnoland.DefaultGenState() - genesis.Balances = LoadDefaultGenesisBalanceFile(t, gnoRootDir) - genesis.Params = LoadDefaultGenesisParamFile(t, gnoRootDir) - genesis.Auth.Params.InitialGasPrice = std.GasPrice{Gas: 0, Price: std.Coin{Amount: 0, Denom: "ugnot"}} - genesis.Txs = []gnoland.TxWithMetadata{} - + cfg := TestingMinimalNodeConfig(t, gnoRootDir) + gs := cfg.Genesis.AppState.(gnoland.GnoGenesisState) + gs.Balances = LoadDefaultGenesisBalanceFile(t, gnoRootDir) + gs.Params = LoadDefaultGenesisParamFile(t, gnoRootDir) + genesis := &gs // test1 must be created outside of the loop below because it is already included in genesis so // attempting to recreate results in it getting overwritten and breaking existing tests that // rely on its address being static. @@ -148,7 +146,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed) env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address) - env.Values[envKeyGenesis] = &genesis + env.Values[envKeyGenesis] = genesis env.Values[envKeyPkgsLoader] = newPkgsLoader() env.Setenv("GNOROOT", gnoRootDir) @@ -180,6 +178,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // parse flags fs := flag.NewFlagSet("start", flag.ContinueOnError) nonVal := fs.Bool("non-validator", false, "set up node as a non-validator") + lockTransfer := fs.Bool("lock-transfer", false, "lock transfer ugnot") if err := fs.Parse(args); err != nil { ts.Fatalf("unable to parse `gnoland start` flags: %s", err) } @@ -188,11 +187,10 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { pkgs := ts.Value(envKeyPkgsLoader).(*pkgsLoader) // grab logger creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) - // we need to define a new err1 otherwise the out err would be shadowed in the case "start": + // we need to define a new loadErr otherwise the out err would be shadowed in the "start" case: pkgsTxs, loadErr := pkgs.LoadPackages(creator, defaultFee, nil) - if loadErr != nil { - ts.Fatalf("unable to load packages txs: %s", err) + ts.Fatalf("unable to load packages txs: %s", loadErr) } // Warp up `ts` so we can pass it to other testing method @@ -200,9 +198,12 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // Generate config and node cfg := TestingMinimalNodeConfig(t, gnoRootDir) + // add pkg txs to genesis genesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState) genesis.Txs = append(pkgsTxs, genesis.Txs...) - + if *lockTransfer { + genesis.Bank.Params.RestrictedDenoms = []string{"ugnot"} + } // setup genesis state cfg.Genesis.AppState = *genesis if *nonVal { diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go index 7eaf3457b03..f8d819582ab 100644 --- a/gno.land/pkg/integration/testing_node.go +++ b/gno.land/pkg/integration/testing_node.go @@ -14,6 +14,8 @@ import ( bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/require" ) @@ -57,14 +59,12 @@ func TestingInMemoryNode(t TestingTS, logger *slog.Logger, config *gnoland.InMem func TestingNodeConfig(t TestingTS, gnoroot string, additionalTxs ...gnoland.TxWithMetadata) (*gnoland.InMemoryNodeConfig, bft.Address) { cfg := TestingMinimalNodeConfig(t, gnoroot) - creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 - params := LoadDefaultGenesisParamFile(t, gnoroot) + creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 balances := LoadDefaultGenesisBalanceFile(t, gnoroot) txs := make([]gnoland.TxWithMetadata, 0) txs = append(txs, LoadDefaultPackages(t, creator, gnoroot)...) txs = append(txs, additionalTxs...) - ggs := cfg.Genesis.AppState.(gnoland.GnoGenesisState) ggs.Balances = balances ggs.Txs = txs @@ -97,6 +97,9 @@ func TestingMinimalNodeConfig(t TestingTS, gnoroot string) *gnoland.InMemoryNode } func DefaultTestingGenesisConfig(t TestingTS, gnoroot string, self crypto.PubKey, tmconfig *tmcfg.Config) *bft.GenesisDoc { + authGen := auth.DefaultGenesisState() + authGen.Params.UnrestrictedAddrs = []crypto.Address{crypto.MustAddressFromString(DefaultAccount_Address)} + authGen.Params.InitialGasPrice = std.GasPrice{Gas: 0, Price: std.Coin{Amount: 0, Denom: "ugnot"}} genState := gnoland.DefaultGenState() genState.Balances = []gnoland.Balance{ { @@ -106,6 +109,9 @@ func DefaultTestingGenesisConfig(t TestingTS, gnoroot string, self crypto.PubKey } genState.Txs = []gnoland.TxWithMetadata{} genState.Params = []gnoland.Param{} + genState.Auth = authGen + genState.Bank = bank.DefaultGenesisState() + return &bft.GenesisDoc{ GenesisTime: time.Now(), ChainID: tmconfig.ChainID(), diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 161e459873d..e243bfce992 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -1,6 +1,9 @@ package vm import ( + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" @@ -62,8 +65,13 @@ func (bnk *SDKBanker) RemoveCoin(b32addr crypto.Bech32Address, denom string, amo type SDKParams struct { vmk *VMKeeper ctx sdk.Context + // The curRealmPath is used to track the current realm accessing the SDKParams from the VM. + // It serves as a safeguard to control access from the VM. + curRealmPath string } +// These are the native function implementations bound with standard libraries in Gno. +// All methods of this struct are not supposed to be called from outside vm/stdlibs/std. func NewSDKParams(vmk *VMKeeper, ctx sdk.Context) *SDKParams { return &SDKParams{ vmk: vmk, @@ -71,10 +79,45 @@ func NewSDKParams(vmk *VMKeeper, ctx sdk.Context) *SDKParams { } } -func (prm *SDKParams) SetString(key, value string) { prm.vmk.prmk.SetString(prm.ctx, key, value) } -func (prm *SDKParams) SetBool(key string, value bool) { prm.vmk.prmk.SetBool(prm.ctx, key, value) } -func (prm *SDKParams) SetInt64(key string, value int64) { prm.vmk.prmk.SetInt64(prm.ctx, key, value) } +func (prm *SDKParams) SetString(key, value string) { + prm.assertRealmAccess() + prm.vmk.prmk.SetString(prm.ctx, key, value) +} + +func (prm *SDKParams) SetBool(key string, value bool) { + prm.assertRealmAccess() + realmParamKey := fmt.Sprintf("%s.%s", prm.curRealmPath, lockSendKey) + if key == realmParamKey { + if value == true { // lock sending ugnot + prm.vmk.bank.AddRestrictedDenoms(prm.ctx, ugnot.Denom) + } else { // unlock sending ugnot + prm.vmk.bank.DelRestrictedDenoms(prm.ctx, ugnot.Denom) + } + } + prm.vmk.prmk.SetBool(prm.ctx, key, value) +} + +func (prm *SDKParams) SetInt64(key string, value int64) { + prm.assertRealmAccess() + prm.vmk.prmk.SetInt64(prm.ctx, key, value) +} + func (prm *SDKParams) SetUint64(key string, value uint64) { + prm.assertRealmAccess() prm.vmk.prmk.SetUint64(prm.ctx, key, value) } -func (prm *SDKParams) SetBytes(key string, value []byte) { prm.vmk.prmk.SetBytes(prm.ctx, key, value) } + +func (prm *SDKParams) SetBytes(key string, value []byte) { + prm.assertRealmAccess() + prm.vmk.prmk.SetBytes(prm.ctx, key, value) +} + +func (prm *SDKParams) SetCurRealmPath(realmPath string) { + prm.curRealmPath = realmPath +} + +func (prm *SDKParams) assertRealmAccess() { + if prm.curRealmPath != ParamsRealmPath { + panic(fmt.Sprintf("Set parameters can only be accessed from: %s", ParamsRealmPath)) + } +} diff --git a/gno.land/pkg/sdk/vm/builtins_test.go b/gno.land/pkg/sdk/vm/builtins_test.go new file mode 100644 index 00000000000..cc885560395 --- /dev/null +++ b/gno.land/pkg/sdk/vm/builtins_test.go @@ -0,0 +1,61 @@ +package vm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParamsRestricted(t *testing.T) { + env := setupTestEnv() + params := NewSDKParams(env.vmk, env.ctx) + params.SetCurRealmPath("gno.land/r/foo") + + testCases := []struct { + name string + setFunc func() + expectedMsg string + }{ + { + name: "SetString should panic", + setFunc: func() { + params.SetString("name.string", "foo") + }, + expectedMsg: "Set parameters can only be accessed from: " + ParamsRealmPath, + }, + { + name: "SetBool should panic", + setFunc: func() { + params.SetBool("isFoo.bool", true) + }, + expectedMsg: "Set parameters can only be accessed from: " + ParamsRealmPath, + }, + { + name: "SetInt64 should panic", + setFunc: func() { + params.SetInt64("nummber.int64", -100) + }, + expectedMsg: "Set parameters can only be accessed from: " + ParamsRealmPath, + }, + { + name: "SetUint64 should panic", + setFunc: func() { + params.SetUint64("nummber.uint64", 100) + }, + expectedMsg: "Set parameters can only be accessed from: " + ParamsRealmPath, + }, + { + name: "SetBytes should panic", + setFunc: func() { + params.SetBytes("name.bytes", []byte("foo")) + }, + expectedMsg: "Set parameters can only be accessed from: " + ParamsRealmPath, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.PanicsWithValue(t, tc.expectedMsg, tc.setFunc, "The panic message did not match the expected value") + }) + } +} diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index 10402f31f64..7972e7fd315 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -21,7 +21,7 @@ import ( type testEnv struct { ctx sdk.Context vmk *VMKeeper - bank bankm.BankKeeper + bank *bankm.BankKeeper acck authm.AccountKeeper vmh vmHandler } @@ -47,10 +47,12 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { ms.LoadLatestVersion() ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) - prmk := paramsm.NewParamsKeeper(iavlCapKey, "params") + km := paramsm.NewPrefixKeyMapper() + prefix := "params_test" + km.RegisterPrefix(prefix) + prmk := paramsm.NewParamsKeeper(iavlCapKey, km) acck := authm.NewAccountKeeper(iavlCapKey, prmk, std.ProtoBaseAccount) - bank := bankm.NewBankKeeper(acck) - + bank := bankm.NewBankKeeper(acck, prmk) vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, prmk) mcw := ms.MultiCacheWrap() diff --git a/gno.land/pkg/sdk/vm/consts.go b/gno.land/pkg/sdk/vm/consts.go index 292f34a9d20..493a42c8215 100644 --- a/gno.land/pkg/sdk/vm/consts.go +++ b/gno.land/pkg/sdk/vm/consts.go @@ -1,6 +1,9 @@ package vm const ( - ModuleName = "vm" - RouterKey = ModuleName + ModuleName = "vm" + RouterKey = ModuleName + ParamsPrefixKey = ModuleName + ParamsRealmPath = "gno.land/r/sys/params" + lockSendKey = "lockSend.bool" ) diff --git a/gno.land/pkg/sdk/vm/genesis.go b/gno.land/pkg/sdk/vm/genesis.go new file mode 100644 index 00000000000..29db3e83cc4 --- /dev/null +++ b/gno.land/pkg/sdk/vm/genesis.go @@ -0,0 +1,48 @@ +package vm + +import ( + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/sdk" +) + +// GenesisState - all state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(params Params) GenesisState { + return GenesisState{params} +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(DefaultParams()) +} + +// ValidateGenesis performs basic validation of genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { + return data.Params.Validate() +} + +// InitGenesis - Init store state from genesis data +func (vm *VMKeeper) InitGenesis(ctx sdk.Context, data GenesisState) { + if amino.DeepEqual(data, GenesisState{}) { + return + } + + if err := ValidateGenesis(data); err != nil { + panic(err) + } + if err := vm.SetParams(ctx, data.Params); err != nil { + panic(err) + } +} + +// ExportGenesis returns a GenesisState for a given context and keeper +func (vm *VMKeeper) ExportGenesis(ctx sdk.Context) GenesisState { + params := vm.GetParams(ctx) + + return NewGenesisState(params) +} diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index bf16cd44243..2aa31835e02 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -52,6 +52,7 @@ type VMKeeperI interface { LoadStdlibCached(ctx sdk.Context, stdlibDir string) MakeGnoTransactionStore(ctx sdk.Context) sdk.Context CommitGnoTransactionStore(ctx sdk.Context) + InitGenesis(ctx sdk.Context, data GenesisState) } var _ VMKeeperI = &VMKeeper{} @@ -64,8 +65,9 @@ type VMKeeper struct { baseKey store.StoreKey iavlKey store.StoreKey acck auth.AccountKeeper - bank bank.BankKeeper + bank *bank.BankKeeper prmk params.ParamsKeeper + params Params // cached, the DeliverTx persistent state. gnoStore gno.Store @@ -76,7 +78,7 @@ func NewVMKeeper( baseKey store.StoreKey, iavlKey store.StoreKey, acck auth.AccountKeeper, - bank bank.BankKeeper, + bank *bank.BankKeeper, prmk params.ParamsKeeper, ) *VMKeeper { vmk := &VMKeeper{ diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index f8144988c44..e17b9439449 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -352,7 +352,7 @@ func TestVMKeeperParams(t *testing.T) { // Create test package. files := []*gnovm.MemFile{ {Name: "init.gno", Body: ` -package test +package params import "std" @@ -367,7 +367,7 @@ func Do() string { 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" + pkgPath := ParamsRealmPath msg1 := NewMsgAddPackage(addr, pkgPath, files) err := env.vmk.AddPackage(ctx, msg1) assert.NoError(t, err) @@ -384,8 +384,8 @@ func Do() string { var foo string var bar int64 - env.vmk.prmk.GetString(ctx, "gno.land/r/test.foo.string", &foo) - env.vmk.prmk.GetInt64(ctx, "gno.land/r/test.bar.int64", &bar) + env.vmk.prmk.GetString(ctx, "gno.land/r/sys/params.foo.string", &foo) + env.vmk.prmk.GetInt64(ctx, "gno.land/r/sys/params.bar.int64", &bar) assert.Equal(t, "foo2", foo) assert.Equal(t, int64(1337), bar) } diff --git a/gno.land/pkg/sdk/vm/params.go b/gno.land/pkg/sdk/vm/params.go index 248fb8a81fb..c7ac5157e2f 100644 --- a/gno.land/pkg/sdk/vm/params.go +++ b/gno.land/pkg/sdk/vm/params.go @@ -1,10 +1,88 @@ package vm -import "github.com/gnolang/gno/tm2/pkg/sdk" +import ( + "fmt" + "regexp" + "strings" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/sdk" +) + +const ( + sysUsersPkgDefault = "gno.land/r/sys/users" + + paramsKey = "p" +) + +// Params defines the parameters for the bank module. +type Params struct { + SysUsersPkg string `json:"sysusers_pkgpath" yaml:"sysusers_pkgpath"` +} + +// NewParams creates a new Params object +func NewParams(pkgPath string) Params { + return Params{ + SysUsersPkg: pkgPath, + } +} + +// DefaultParams returns a default set of parameters. +func DefaultParams() Params { + return NewParams(sysUsersPkgDefault) +} + +// String implements the stringer interface. +func (p Params) String() string { + var sb strings.Builder + sb.WriteString("Params: \n") + sb.WriteString(fmt.Sprintf("SysUsersPkg: %q\n", p.SysUsersPkg)) + return sb.String() +} + +func (p Params) Validate() error { + // XXX: This line is copied from gnovm/memfile.go. Should we instead export rePkgOrRlmPath with a getter method from gnovm? + rePkgOrRlmPath := regexp.MustCompile(`^gno\.land\/(?:p|r)(?:\/_?[a-z]+[a-z0-9_]*)+$`) + if !rePkgOrRlmPath.MatchString(p.SysUsersPkg) { + return fmt.Errorf("invalid package/realm path %q, failed to match %q", p.SysUsersPkg, rePkgOrRlmPath) + } + return nil +} + +// Equals returns a boolean determining if two Params types are identical. +func (p Params) Equals(p2 Params) bool { + return amino.DeepEqual(p, p2) +} + +func (vm *VMKeeper) SetParams(ctx sdk.Context, params Params) error { + if params.Equals(Params{}) { + return nil + } + if err := params.Validate(); err != nil { + return err + } + vm.params = params + err := vm.prmk.SetParams(ctx, ModuleName, paramsKey, params) + return err +} + +func (vm *VMKeeper) GetParams(ctx sdk.Context) Params { + params := &Params{} + + ok, err := vm.prmk.GetParams(ctx, ModuleName, paramsKey, params) + + if !ok { + panic("params key " + ModuleName + " does not exist") + } + if err != nil { + panic(err.Error()) + } + return *params +} const ( sysUsersPkgParamPath = "gno.land/r/sys/params.sys.users_pkgpath.string" - chainDomainParamPath = "gno.land/r/sys/params.chain_domain.string" + chainDomainParamPath = "gno.land/r/sys/params.vm.chain_domain.string" ) func (vm *VMKeeper) getChainDomainParam(ctx sdk.Context) string { diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index d06540761d7..fdd40404680 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -87,11 +87,12 @@ func newTestParams() *testParams { return &testParams{} } -func (tp *testParams) SetBool(key string, val bool) { /* noop */ } -func (tp *testParams) SetBytes(key string, val []byte) { /* noop */ } -func (tp *testParams) SetInt64(key string, val int64) { /* noop */ } -func (tp *testParams) SetUint64(key string, val uint64) { /* noop */ } -func (tp *testParams) SetString(key string, val string) { /* noop */ } +func (tp *testParams) SetBool(key string, val bool) { /* noop */ } +func (tp *testParams) SetBytes(key string, val []byte) { /* noop */ } +func (tp *testParams) SetInt64(key string, val int64) { /* noop */ } +func (tp *testParams) SetUint64(key string, val uint64) { /* noop */ } +func (tp *testParams) SetString(key string, val string) { /* noop */ } +func (tp *testParams) SetCurRealmPath(curRealmPath string) { /* noop */ } // ---------------------------------------- // main test function diff --git a/gnovm/stdlibs/std/params.go b/gnovm/stdlibs/std/params.go index e21bd9912dd..8fa25e4dedb 100644 --- a/gnovm/stdlibs/std/params.go +++ b/gnovm/stdlibs/std/params.go @@ -19,6 +19,7 @@ type ParamsInterface interface { SetInt64(key string, val int64) SetUint64(key string, val uint64) SetBytes(key string, val []byte) + SetCurRealmPath(curRealmPath string) } func X_setParamString(m *gno.Machine, key, val string) { @@ -66,7 +67,8 @@ func pkey(m *gno.Machine, key string, kind string) string { } } - // decorate key with realm and type. _, rlmPath := currentRealm(m) + GetContext(m).Params.SetCurRealmPath(rlmPath) + // decorate key with realm and type. return fmt.Sprintf("%s.%s", rlmPath, key) } diff --git a/tm2/pkg/sdk/auth/abci.go b/tm2/pkg/sdk/auth/abci.go index 86cbf962fad..9cbebe676e9 100644 --- a/tm2/pkg/sdk/auth/abci.go +++ b/tm2/pkg/sdk/auth/abci.go @@ -14,6 +14,6 @@ func EndBlocker(ctx sdk.Context, gk GasPriceKeeperI) { // InitChainer is called in the InitChain(), it set the initial gas price in the // GasPriceKeeper store // for the next gas price -func InitChainer(ctx sdk.Context, gk GasPriceKeeper, gp std.GasPrice) { +func InitChainer(ctx sdk.Context, gk GasPriceKeeperI, gp std.GasPrice) { gk.SetGasPrice(ctx, gp) } diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index 997478fe4b5..fcfdbb3fe18 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -322,7 +322,8 @@ func DeductFees(bank BankKeeperI, ctx sdk.Context, acc std.Account, fees std.Coi )) } - err := bank.SendCoins(ctx, acc.GetAddress(), FeeCollectorAddress(), fees) + // Sending coins is unrestricted to pay for gas fees + err := bank.SendCoinsUnrestricted(ctx, acc.GetAddress(), FeeCollectorAddress(), fees) if err != nil { return abciResult(err) } diff --git a/tm2/pkg/sdk/auth/consts.go b/tm2/pkg/sdk/auth/consts.go index 462ca0cd64d..7a74be63e7e 100644 --- a/tm2/pkg/sdk/auth/consts.go +++ b/tm2/pkg/sdk/auth/consts.go @@ -23,6 +23,9 @@ const ( GasPriceKey = "gasPrice" // param key for global account number GlobalAccountNumberKey = "globalAccountNumber" + + // param + ParamsPrefixKey = ModuleName ) // AddressStoreKey turn an address to key used to get it from the account store diff --git a/tm2/pkg/sdk/auth/genesis.go b/tm2/pkg/sdk/auth/genesis.go index c863c237a41..dde1082d778 100644 --- a/tm2/pkg/sdk/auth/genesis.go +++ b/tm2/pkg/sdk/auth/genesis.go @@ -5,6 +5,27 @@ import ( "github.com/gnolang/gno/tm2/pkg/sdk" ) +// GenesisState - all state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(params Params) GenesisState { + return GenesisState{params} +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(DefaultParams()) +} + +// ValidateGenesis performs basic validation of genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { + return data.Params.Validate() +} + // InitGenesis - Init store state from genesis data func (ak AccountKeeper) InitGenesis(ctx sdk.Context, data GenesisState) { if amino.DeepEqual(data, GenesisState{}) { @@ -18,6 +39,15 @@ func (ak AccountKeeper) InitGenesis(ctx sdk.Context, data GenesisState) { panic(err) } + // The unrestricted address must have been created as one of the genesis accounts. + // Otherwise, we cannot verify the unrestricted address in the genesis state. + + for _, addr := range data.Params.UnrestrictedAddrs { + acc := ak.GetAccount(ctx, addr) + acc.SetUnrestricted(true) + ak.SetAccount(ctx, acc) + } + if err := ak.SetParams(ctx, data.Params); err != nil { panic(err) } diff --git a/tm2/pkg/sdk/auth/keeper.go b/tm2/pkg/sdk/auth/keeper.go index fc83997fdc4..741c8e8d287 100644 --- a/tm2/pkg/sdk/auth/keeper.go +++ b/tm2/pkg/sdk/auth/keeper.go @@ -17,8 +17,10 @@ import ( type AccountKeeper struct { // The (unexposed) key used to access the store from the Context. key store.StoreKey - // The keeper used to store auth parameters + + // store module parameters paramk params.ParamsKeeper + // The prototypical Account constructor. proto func() std.Account } diff --git a/tm2/pkg/sdk/auth/params.go b/tm2/pkg/sdk/auth/params.go index 3fe08ed444d..7429daa34ed 100644 --- a/tm2/pkg/sdk/auth/params.go +++ b/tm2/pkg/sdk/auth/params.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -13,25 +14,29 @@ type AuthParamsContextKey struct{} // Default parameter values const ( - DefaultMaxMemoBytes int64 = 65536 - DefaultTxSigLimit int64 = 7 - DefaultTxSizeCostPerByte int64 = 10 - DefaultSigVerifyCostED25519 int64 = 590 - DefaultSigVerifyCostSecp256k1 int64 = 1000 + DefaultMaxMemoBytes int64 = 65536 + DefaultTxSigLimit int64 = 7 + DefaultTxSizeCostPerByte int64 = 10 + DefaultSigVerifyCostED25519 int64 = 590 + DefaultSigVerifyCostSecp256k1 int64 = 1000 + DefaultGasPricesChangeCompressor int64 = 10 DefaultTargetGasRatio int64 = 70 // 70% of the MaxGas in a block + + paramsKey = "p" ) // Params defines the parameters for the auth module. type Params struct { - MaxMemoBytes int64 `json:"max_memo_bytes" yaml:"max_memo_bytes"` - TxSigLimit int64 `json:"tx_sig_limit" yaml:"tx_sig_limit"` - TxSizeCostPerByte int64 `json:"tx_size_cost_per_byte" yaml:"tx_size_cost_per_byte"` - SigVerifyCostED25519 int64 `json:"sig_verify_cost_ed25519" yaml:"sig_verify_cost_ed25519"` - SigVerifyCostSecp256k1 int64 `json:"sig_verify_cost_secp256k1" yaml:"sig_verify_cost_secp256k1"` - GasPricesChangeCompressor int64 `json:"gas_price_change_compressor" yaml:"gas_price_change_compressor"` - TargetGasRatio int64 `json:"target_gas_ratio" yaml:"target_gas_ratio"` - InitialGasPrice std.GasPrice `json:"initial_gasprice"` + MaxMemoBytes int64 `json:"max_memo_bytes" yaml:"max_memo_bytes"` + TxSigLimit int64 `json:"tx_sig_limit" yaml:"tx_sig_limit"` + TxSizeCostPerByte int64 `json:"tx_size_cost_per_byte" yaml:"tx_size_cost_per_byte"` + SigVerifyCostED25519 int64 `json:"sig_verify_cost_ed25519" yaml:"sig_verify_cost_ed25519"` + SigVerifyCostSecp256k1 int64 `json:"sig_verify_cost_secp256k1" yaml:"sig_verify_cost_secp256k1"` + GasPricesChangeCompressor int64 `json:"gas_price_change_compressor" yaml:"gas_price_change_compressor"` + TargetGasRatio int64 `json:"target_gas_ratio" yaml:"target_gas_ratio"` + InitialGasPrice std.GasPrice `json:"initial_gasprice"` + UnrestrictedAddrs []crypto.Address `json:"unrestricted_addrs" yaml:"unrestricted_addrs"` } // NewParams creates a new Params object @@ -56,15 +61,15 @@ func (p Params) Equals(p2 Params) bool { // DefaultParams returns a default set of parameters. func DefaultParams() Params { - return Params{ - MaxMemoBytes: DefaultMaxMemoBytes, - TxSigLimit: DefaultTxSigLimit, - TxSizeCostPerByte: DefaultTxSizeCostPerByte, - SigVerifyCostED25519: DefaultSigVerifyCostED25519, - SigVerifyCostSecp256k1: DefaultSigVerifyCostSecp256k1, - GasPricesChangeCompressor: DefaultGasPricesChangeCompressor, - TargetGasRatio: DefaultTargetGasRatio, - } + return NewParams( + DefaultMaxMemoBytes, + DefaultTxSigLimit, + DefaultTxSizeCostPerByte, + DefaultSigVerifyCostED25519, + DefaultSigVerifyCostSecp256k1, + DefaultGasPricesChangeCompressor, + DefaultTargetGasRatio, + ) } // String implements the stringer interface. @@ -82,16 +87,19 @@ func (p Params) String() string { } func (p Params) Validate() error { - if p.TxSigLimit == 0 { + if p.MaxMemoBytes <= 0 { + return fmt.Errorf("invalid max memo bytes: %d", p.MaxMemoBytes) + } + if p.TxSigLimit <= 0 { return fmt.Errorf("invalid tx signature limit: %d", p.TxSigLimit) } - if p.SigVerifyCostED25519 == 0 { + if p.SigVerifyCostED25519 <= 0 { return fmt.Errorf("invalid ED25519 signature verification cost: %d", p.SigVerifyCostED25519) } - if p.SigVerifyCostSecp256k1 == 0 { + if p.SigVerifyCostSecp256k1 <= 0 { return fmt.Errorf("invalid SECK256k1 signature verification cost: %d", p.SigVerifyCostSecp256k1) } - if p.TxSizeCostPerByte == 0 { + if p.TxSizeCostPerByte <= 0 { return fmt.Errorf("invalid tx size cost per byte: %d", p.TxSizeCostPerByte) } if p.GasPricesChangeCompressor <= 0 { @@ -107,14 +115,14 @@ func (ak AccountKeeper) SetParams(ctx sdk.Context, params Params) error { if err := params.Validate(); err != nil { return err } - err := ak.paramk.SetParams(ctx, ModuleName, params) + err := ak.paramk.SetParams(ctx, ModuleName, paramsKey, params) return err } func (ak AccountKeeper) GetParams(ctx sdk.Context) Params { params := &Params{} - ok, err := ak.paramk.GetParams(ctx, ModuleName, params) + ok, err := ak.paramk.GetParams(ctx, ModuleName, paramsKey, params) if !ok { panic("params key " + ModuleName + " does not exist") diff --git a/tm2/pkg/sdk/auth/test_common.go b/tm2/pkg/sdk/auth/test_common.go index e0a6316bead..99aab95dfde 100644 --- a/tm2/pkg/sdk/auth/test_common.go +++ b/tm2/pkg/sdk/auth/test_common.go @@ -28,8 +28,9 @@ func setupTestEnv() testEnv { ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(authCapKey, iavl.StoreConstructor, db) ms.LoadLatestVersion() - - paramk := params.NewParamsKeeper(authCapKey, "") + km := params.NewPrefixKeyMapper() + km.RegisterPrefix(ParamsPrefixKey) + paramk := params.NewParamsKeeper(authCapKey, km) acck := NewAccountKeeper(authCapKey, paramk, std.ProtoBaseAccount) bank := NewDummyBankKeeper(acck) gk := NewGasPriceKeeper(authCapKey) @@ -63,6 +64,10 @@ func NewDummyBankKeeper(acck AccountKeeper) DummyBankKeeper { return DummyBankKeeper{acck} } +func (bank DummyBankKeeper) SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + return bank.SendCoins(ctx, fromAddr, toAddr, amt) +} + // SendCoins for the dummy supply keeper func (bank DummyBankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { fromAcc := bank.acck.GetAccount(ctx, fromAddr) diff --git a/tm2/pkg/sdk/auth/types.go b/tm2/pkg/sdk/auth/types.go index 3fb2d10fbb5..4965122bf67 100644 --- a/tm2/pkg/sdk/auth/types.go +++ b/tm2/pkg/sdk/auth/types.go @@ -22,6 +22,7 @@ var _ AccountKeeperI = AccountKeeper{} // Limited interface only needed for auth. type BankKeeperI interface { SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error + SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error } type GasPriceKeeperI interface { @@ -31,24 +32,3 @@ type GasPriceKeeperI interface { } var _ GasPriceKeeperI = GasPriceKeeper{} - -// GenesisState - all auth state that must be provided at genesis -type GenesisState struct { - Params Params `json:"params"` -} - -// NewGenesisState - Create a new genesis state -func NewGenesisState(params Params) GenesisState { - return GenesisState{params} -} - -// DefaultGenesisState - Return a default genesis state -func DefaultGenesisState() GenesisState { - return NewGenesisState(DefaultParams()) -} - -// ValidateGenesis performs basic validation of auth genesis data returning an -// error for any failed validation criteria. -func ValidateGenesis(data GenesisState) error { - return data.Params.Validate() -} diff --git a/tm2/pkg/sdk/bank/common_test.go b/tm2/pkg/sdk/bank/common_test.go index c8210be7175..1ecffadd0fd 100644 --- a/tm2/pkg/sdk/bank/common_test.go +++ b/tm2/pkg/sdk/bank/common_test.go @@ -16,9 +16,10 @@ import ( ) type testEnv struct { - ctx sdk.Context - bank BankKeeper - acck auth.AccountKeeper + ctx sdk.Context + bank *BankKeeper + acck auth.AccountKeeper + paramk params.ParamsKeeper } func setupTestEnv() testEnv { @@ -29,13 +30,14 @@ func setupTestEnv() testEnv { ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(authCapKey, iavl.StoreConstructor, db) ms.LoadLatestVersion() - paramk := params.NewParamsKeeper(authCapKey, "") ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) + km := params.NewPrefixKeyMapper() + km.RegisterPrefix(ParamsPrefixKey) + paramk := params.NewParamsKeeper(authCapKey, km) acck := auth.NewAccountKeeper( authCapKey, paramk, std.ProtoBaseAccount, ) + bank := NewBankKeeper(acck, paramk) - bank := NewBankKeeper(acck) - - return testEnv{ctx: ctx, bank: bank, acck: acck} + return testEnv{ctx: ctx, bank: bank, acck: acck, paramk: paramk} } diff --git a/tm2/pkg/sdk/bank/consts.go b/tm2/pkg/sdk/bank/consts.go index 4284a44c940..233f9c04191 100644 --- a/tm2/pkg/sdk/bank/consts.go +++ b/tm2/pkg/sdk/bank/consts.go @@ -1,5 +1,6 @@ package bank const ( - ModuleName = "bank" + ModuleName = "bank" + ParamsPrefixKey = ModuleName ) diff --git a/tm2/pkg/sdk/bank/genesis.go b/tm2/pkg/sdk/bank/genesis.go new file mode 100644 index 00000000000..2354effc1e4 --- /dev/null +++ b/tm2/pkg/sdk/bank/genesis.go @@ -0,0 +1,55 @@ +package bank + +import ( + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/sdk" +) + +// GenesisState - all state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(params Params) GenesisState { + return GenesisState{params} +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(DefaultParams()) +} + +// ValidateGenesis performs basic validation of genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { + return data.Params.Validate() +} + +// InitGenesis - Init store state from genesis data +func (bank *BankKeeper) InitGenesis(ctx sdk.Context, data GenesisState) { + if amino.DeepEqual(data, GenesisState{}) { + return + } + if err := ValidateGenesis(data); err != nil { + panic(err) + } + + bank.restrictedDenoms = map[string]struct{}{} + + params := data.Params + for _, denom := range params.RestrictedDenoms { + bank.restrictedDenoms[denom] = struct{}{} + } + + if err := bank.SetParams(ctx, data.Params); err != nil { + panic(err) + } +} + +// ExportGenesis returns a GenesisState for a given context and keeper +func (bank *BankKeeper) ExportGenesis(ctx sdk.Context) GenesisState { + params := bank.GetParams(ctx) + + return NewGenesisState(params) +} diff --git a/tm2/pkg/sdk/bank/handler.go b/tm2/pkg/sdk/bank/handler.go index b151af11064..08fe6fb6e5d 100644 --- a/tm2/pkg/sdk/bank/handler.go +++ b/tm2/pkg/sdk/bank/handler.go @@ -12,11 +12,11 @@ import ( ) type bankHandler struct { - bank BankKeeper + bank *BankKeeper } // NewHandler returns a handler for "bank" type messages. -func NewHandler(bank BankKeeper) bankHandler { +func NewHandler(bank *BankKeeper) bankHandler { return bankHandler{ bank: bank, } diff --git a/tm2/pkg/sdk/bank/handler_test.go b/tm2/pkg/sdk/bank/handler_test.go index 85fc68fc304..619285d8e4f 100644 --- a/tm2/pkg/sdk/bank/handler_test.go +++ b/tm2/pkg/sdk/bank/handler_test.go @@ -18,7 +18,7 @@ import ( func TestInvalidMsg(t *testing.T) { t.Parallel() - h := NewHandler(BankKeeper{}) + h := NewHandler(&BankKeeper{}) res := h.Process(sdk.NewContext(sdk.RunTxModeDeliver, nil, &bft.Header{ChainID: "test-chain"}, nil), tu.NewTestMsg()) require.False(t, res.IsOK()) require.True(t, strings.Contains(res.Log, "unrecognized bank message type")) diff --git a/tm2/pkg/sdk/bank/keeper.go b/tm2/pkg/sdk/bank/keeper.go index f98e6b3e225..e994bdda303 100644 --- a/tm2/pkg/sdk/bank/keeper.go +++ b/tm2/pkg/sdk/bank/keeper.go @@ -7,6 +7,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -21,9 +22,12 @@ type BankKeeperI interface { SubtractCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) AddCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) SetCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) error + + InitGenesis(ctx sdk.Context, data GenesisState) + GetParams(ctx sdk.Context) Params } -var _ BankKeeperI = BankKeeper{} +var _ BankKeeperI = &BankKeeper{} // BankKeeper only allows transfers between accounts without the possibility of // creating coins. It implements the BankKeeperI interface. @@ -31,13 +35,74 @@ type BankKeeper struct { ViewKeeper acck auth.AccountKeeper + // The keeper used to store parameters + paramk params.ParamsKeeper + params Params + restrictedDenoms map[string]struct{} } // NewBankKeeper returns a new BankKeeper. -func NewBankKeeper(acck auth.AccountKeeper) BankKeeper { - return BankKeeper{ +func NewBankKeeper(acck auth.AccountKeeper, pk params.ParamsKeeper) *BankKeeper { + rdm := map[string]struct{}{} + + params := DefaultParams() + for _, denom := range params.RestrictedDenoms { + rdm[denom] = struct{}{} + } + return &BankKeeper{ ViewKeeper: NewViewKeeper(acck), acck: acck, + paramk: pk, + params: params, + // Store restricted denoms in a map's keys for fast + // comparison when filtering out restricted denoms from a send message. + restrictedDenoms: rdm, + } +} + +func (bank *BankKeeper) AddRestrictedDenoms(ctx sdk.Context, restrictedDenoms ...string) { + if len(restrictedDenoms) == 0 { + return + } + for _, denom := range restrictedDenoms { + bank.restrictedDenoms[denom] = struct{}{} + } + if len(bank.params.RestrictedDenoms) == 0 { + bank.params.RestrictedDenoms = restrictedDenoms + if err := bank.SetParams(ctx, bank.params); err != nil { + panic(err) + } + } + bank.updateParams(ctx) +} + +func (bank *BankKeeper) DelRestrictedDenoms(ctx sdk.Context, restrictedDenoms ...string) { + for denom := range bank.restrictedDenoms { + delete(bank.restrictedDenoms, denom) + } + bank.updateParams(ctx) +} + +func (bank *BankKeeper) DelAllRestrictedDenoms(ctx sdk.Context) { + bank.restrictedDenoms = map[string]struct{}{} + bank.updateParams(ctx) +} + +func (bank *BankKeeper) RestrictedDenoms(ctx sdk.Context) []string { + // covert restricted denoms map into a slice + denoms := make([]string, 0, len(bank.restrictedDenoms)) + for d := range bank.restrictedDenoms { + denoms = append(denoms, d) + } + return denoms +} + +func (bank *BankKeeper) updateParams(ctx sdk.Context) { + params := bank.GetParams(ctx) + params.RestrictedDenoms = bank.RestrictedDenoms(ctx) + bank.params = params + if err := bank.SetParams(ctx, params); err != nil { + panic(err) } } @@ -50,6 +115,9 @@ func (bank BankKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs } for _, in := range inputs { + if !bank.canSendCoins(ctx, in.Address, in.Coins) { + return std.RestrictedTransferError{} + } _, err := bank.SubtractCoins(ctx, in.Address, in.Coins) if err != nil { return err @@ -84,8 +152,42 @@ func (bank BankKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs return nil } -// SendCoins moves coins from one account to another +// canSendCoins returns true if the coins can be sent without violating any restriction. +func (bank BankKeeper) canSendCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) bool { + if len(bank.restrictedDenoms) == 0 { + // No restrictions. + return true + } + if amt.ContainOneOfDenom(bank.restrictedDenoms) { + if acc := bank.acck.GetAccount(ctx, addr); acc != nil && acc.IsRestricted() { + return false + } + } + return true +} + +// SendCoins moves coins from one account to another, restrction could be applied func (bank BankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + // read restricted boolean value from param.IsRestrictedTransfer() + // canSendCoins is true until they have agreed to the waiver + if !bank.canSendCoins(ctx, fromAddr, amt) { + return std.RestrictedTransferError{} + } + + return bank.sendCoins(ctx, fromAddr, toAddr, amt) +} + +// SendCoinsUnrestricted is used for paying gas. +func (bank BankKeeper) SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + return bank.sendCoins(ctx, fromAddr, toAddr, amt) +} + +func (bank BankKeeper) sendCoins( + ctx sdk.Context, + fromAddr crypto.Address, + toAddr crypto.Address, + amt std.Coins, +) error { _, err := bank.SubtractCoins(ctx, fromAddr, amt) if err != nil { return err diff --git a/tm2/pkg/sdk/bank/keeper_test.go b/tm2/pkg/sdk/bank/keeper_test.go index df2039a682c..35a9f8b0f05 100644 --- a/tm2/pkg/sdk/bank/keeper_test.go +++ b/tm2/pkg/sdk/bank/keeper_test.go @@ -95,7 +95,7 @@ func TestBankKeeper(t *testing.T) { env := setupTestEnv() ctx := env.ctx - bank := NewBankKeeper(env.acck) + bank := env.bank addr := crypto.AddressFromPreimage([]byte("addr1")) addr2 := crypto.AddressFromPreimage([]byte("addr2")) @@ -137,6 +137,53 @@ func TestBankKeeper(t *testing.T) { require.Error(t, err) } +func TestBankKeeperWithRestrictions(t *testing.T) { + env := setupTestEnv() + ctx := env.ctx + + bankKeeper := env.bank + bankKeeper.AddRestrictedDenoms(ctx, "foocoin") + addr := crypto.AddressFromPreimage([]byte("addr1")) + addr2 := crypto.AddressFromPreimage([]byte("addr2")) + acc := env.acck.NewAccountWithAddress(ctx, addr) + + // Test GetCoins/SetCoins + env.acck.SetAccount(ctx, acc) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(std.NewCoins())) + + env.bank.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + + // Test HasCoins + require.True(t, bankKeeper.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, bankKeeper.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 5)))) + require.False(t, bankKeeper.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15)))) + require.False(t, bankKeeper.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5)))) + + env.bank.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15))) + + // Test sending coins restricted to locked accounts. + err := bankKeeper.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) + require.ErrorIs(t, err, std.RestrictedTransferError{}, "expected restricted transfer error, got %v", err) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 15)))) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 0)))) + + // Test sending coins unrestricted to locked accounts. + env.bank.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 30))) + err = bankKeeper.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("barcoin", 10))) + require.NoError(t, err) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 15)))) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10)))) + + // Remove the restrictions + bankKeeper.DelAllRestrictedDenoms(ctx) + // Test sending coins restricted to locked accounts. + err = bankKeeper.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) + require.NoError(t, err) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 10)))) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 5)))) +} + func TestViewKeeper(t *testing.T) { t.Parallel() diff --git a/tm2/pkg/sdk/bank/params.go b/tm2/pkg/sdk/bank/params.go new file mode 100644 index 00000000000..3ef9895d098 --- /dev/null +++ b/tm2/pkg/sdk/bank/params.go @@ -0,0 +1,75 @@ +package bank + +import ( + "fmt" + "strings" + + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/std" +) + +const paramsKey = "p" + +type BankParamsContextKey struct{} + +// Params defines the parameters for the bank module. +type Params struct { + RestrictedDenoms []string `json:"restriced_denoms" yaml:"restriced_denoms"` +} + +// NewParams creates a new Params object +func NewParams(restDenoms []string) Params { + return Params{ + RestrictedDenoms: restDenoms, + } +} + +// DefaultParams returns a default set of parameters. +func DefaultParams() Params { + return NewParams([]string{}) +} + +// String implements the stringer interface. +func (p Params) String() string { + var sb strings.Builder + sb.WriteString("Params: \n") + sb.WriteString(fmt.Sprintf("RestrictedDenom: %q\n", p.RestrictedDenoms)) + return sb.String() +} + +func (p *Params) Validate() error { + for _, denom := range p.RestrictedDenoms { + err := std.ValidateDenom(denom) + if err != nil { + return fmt.Errorf("invalid restricted denom: %s", denom) + } + } + return nil +} + +func (bank BankKeeper) SetParams(ctx sdk.Context, params Params) error { + if len(params.RestrictedDenoms) == 0 { + return nil + } + if err := params.Validate(); err != nil { + return err + } + bank.params = params + err := bank.paramk.SetParams(ctx, ModuleName, paramsKey, params) + + return err +} + +func (bank BankKeeper) GetParams(ctx sdk.Context) Params { + params := &Params{} + + ok, err := bank.paramk.GetParams(ctx, ModuleName, paramsKey, params) + + if !ok { + panic("params module key " + ModuleName + " does not exist") + } + if err != nil { + panic(err.Error()) + } + return *params +} diff --git a/tm2/pkg/sdk/params/handler.go b/tm2/pkg/sdk/params/handler.go index b662fc06c58..a74cdc36ee0 100644 --- a/tm2/pkg/sdk/params/handler.go +++ b/tm2/pkg/sdk/params/handler.go @@ -28,14 +28,13 @@ func (bh paramsHandler) Process(ctx sdk.Context, msg std.Msg) sdk.Result { // Query func (bh paramsHandler) Query(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - switch secondPart(req.Path) { - case bh.params.prefix: + prefix := secondPart(req.Path) + if bh.params.PrefixExists(prefix) { return bh.queryParam(ctx, req) - default: - res = sdk.ABCIResponseQueryFromError( - std.ErrUnknownRequest("unknown params query endpoint")) - return } + res = sdk.ABCIResponseQueryFromError( + std.ErrUnknownRequest("unknown params query endpoint")) + return } // queryParam returns param for a key. diff --git a/tm2/pkg/sdk/params/keeper.go b/tm2/pkg/sdk/params/keeper.go index c99b9dbfde1..151fe729a1c 100644 --- a/tm2/pkg/sdk/params/keeper.go +++ b/tm2/pkg/sdk/params/keeper.go @@ -37,6 +37,9 @@ type ParamsKeeperI interface { Has(ctx sdk.Context, key string) bool GetRaw(ctx sdk.Context, key string) []byte + GetParams(ctx sdk.Context, prefixKey string, key string, target interface{}) (bool, error) + SetParams(ctx sdk.Context, prefixKey string, key string, params interface{}) error + // XXX: ListKeys? } @@ -44,42 +47,19 @@ var _ ParamsKeeperI = ParamsKeeper{} // global paramstore Keeper. type ParamsKeeper struct { - key store.StoreKey - prefix string + key store.StoreKey + // prefix string + prefixKeyMapper PrefixKeyMapper } // NewParamsKeeper returns a new ParamsKeeper. -func NewParamsKeeper(key store.StoreKey, prefix string) ParamsKeeper { +func NewParamsKeeper(key store.StoreKey, pkm PrefixKeyMapper) ParamsKeeper { return ParamsKeeper{ - key: key, - prefix: prefix, + key: key, + prefixKeyMapper: pkm, } } -// GetParam gets a param value from the global param store. -func (pk ParamsKeeper) GetParams(ctx sdk.Context, key string, target interface{}) (bool, error) { - stor := ctx.Store(pk.key) - - bz := stor.Get(ValueStoreKey(key)) - if bz == nil { - return false, nil - } - - return true, amino.UnmarshalJSON(bz, target) -} - -// SetParam sets a param value to the global param store. -func (pk ParamsKeeper) SetParams(ctx sdk.Context, key string, param interface{}) error { - stor := ctx.Store(pk.key) - bz, err := amino.MarshalJSON(param) - if err != nil { - return err - } - - stor.Set(ValueStoreKey(key), bz) - return nil -} - // XXX: why do we expose this? func (pk ParamsKeeper) Logger(ctx sdk.Context) *slog.Logger { return ctx.Logger().With("module", ModuleName) @@ -145,6 +125,57 @@ func (pk ParamsKeeper) SetBytes(ctx sdk.Context, key string, value []byte) { pk.set(ctx, key, value) } +// GetParam gets a param value from the global param store. +func (pk ParamsKeeper) GetParams(ctx sdk.Context, prefixKey string, key string, target interface{}) (bool, error) { + vk, err := pk.valueStoreKey(prefixKey, key) + if err != nil { + return false, err + } + + stor := ctx.Store(pk.key) + + bz := stor.Get(vk) + if bz == nil { + return false, nil + } + + return true, amino.UnmarshalJSON(bz, target) +} + +// SetParam sets a param value to the global param store. +func (pk ParamsKeeper) SetParams(ctx sdk.Context, prefixKey string, key string, param interface{}) error { + vk, err := pk.valueStoreKey(prefixKey, key) + if err != nil { + return err + } + + bz, err := amino.MarshalJSON(param) + if err != nil { + return err + } + + stor := ctx.Store(pk.key) + + stor.Set(vk, bz) + return nil +} + +func (pk ParamsKeeper) valueStoreKey(prefix string, key string) ([]byte, error) { + prefix, err := pk.prefixKeyMapper.Map(prefix) + if err != nil { + return nil, err + } + return append([]byte(prefix), []byte(key)...), nil +} + +func (pk ParamsKeeper) PrefixExists(prefix string) bool { + _, err := pk.prefixKeyMapper.Map(prefix) + if err != nil { + return false + } + return true +} + func (pk ParamsKeeper) getIfExists(ctx sdk.Context, key string, ptr interface{}) { stor := ctx.Store(pk.key) bz := stor.Get([]byte(key)) diff --git a/tm2/pkg/sdk/params/keeper_test.go b/tm2/pkg/sdk/params/keeper_test.go index aedfaa9d5a3..bbfcfe2ba06 100644 --- a/tm2/pkg/sdk/params/keeper_test.go +++ b/tm2/pkg/sdk/params/keeper_test.go @@ -147,17 +147,18 @@ type Params struct { } func TestGetAndSetParams(t *testing.T) { + prefix := "params_test" env := setupTestEnv() ctx := env.ctx keeper := env.keeper // SetParams a := Params{p1: 1, p2: "a"} - err := keeper.SetParams(ctx, ModuleName, a) + err := keeper.SetParams(ctx, prefix, "p", a) require.NoError(t, err) // GetParams a1 := Params{} - _, err1 := keeper.GetParams(ctx, ModuleName, &a1) + _, err1 := keeper.GetParams(ctx, prefix, "p", &a1) require.NoError(t, err1) require.True(t, amino.DeepEqual(a, a1), "a and a1 should equal") } diff --git a/tm2/pkg/sdk/params/keymapper.go b/tm2/pkg/sdk/params/keymapper.go new file mode 100644 index 00000000000..0e46c1d7f25 --- /dev/null +++ b/tm2/pkg/sdk/params/keymapper.go @@ -0,0 +1,33 @@ +package params + +import "fmt" + +// PrefixKeyMapper is used to map one key string to another. +type PrefixKeyMapper struct { + keyMap map[string]string +} + +func NewPrefixKeyMapper() PrefixKeyMapper { + return PrefixKeyMapper{ + keyMap: map[string]string{}, + } +} + +func (pkm PrefixKeyMapper) RegisterPrefix(prefix string) { + pkm.keyMap[prefix] = "/" + prefix + "/" +} + +func (pkm PrefixKeyMapper) IsExist(prefix string) bool { + _, ok := pkm.keyMap[prefix] + return ok +} + +// Map does a transformation on an input key to produce the key +// appropriate for accessing a param keeper's storage instance. +func (pkm PrefixKeyMapper) Map(prefix string) (string, error) { + v, ok := pkm.keyMap[prefix] + if !ok { + return "", fmt.Errorf("prefix %s does not exisit", prefix) + } + return v, nil +} diff --git a/tm2/pkg/sdk/params/test_common.go b/tm2/pkg/sdk/params/test_common.go index 8243ee867de..ef240d86d12 100644 --- a/tm2/pkg/sdk/params/test_common.go +++ b/tm2/pkg/sdk/params/test_common.go @@ -24,7 +24,9 @@ func setupTestEnv() testEnv { ms.LoadLatestVersion() prefix := "params_test" - keeper := NewParamsKeeper(paramsCapKey, prefix) + km := NewPrefixKeyMapper() + km.RegisterPrefix(prefix) + keeper := NewParamsKeeper(paramsCapKey, km) ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{Height: 1, ChainID: "test-chain-id"}, log.NewNoopLogger()) // XXX: context key? diff --git a/tm2/pkg/std/account.go b/tm2/pkg/std/account.go index c70f43d22e9..d1c2876610d 100644 --- a/tm2/pkg/std/account.go +++ b/tm2/pkg/std/account.go @@ -34,6 +34,9 @@ type Account interface { GetCoins() Coins SetCoins(Coins) error + IsRestricted() bool + SetUnrestricted(bool) + // Ensure that account implements stringer String() string } @@ -49,6 +52,7 @@ type BaseAccount struct { PubKey crypto.PubKey `json:"public_key" yaml:"public_key"` AccountNumber uint64 `json:"account_number" yaml:"account_number"` Sequence uint64 `json:"sequence" yaml:"sequence"` + Unrestricted bool `json:"unrestricted" yaml:"unrestricted"` } // NewBaseAccount creates a new BaseAccount object @@ -151,3 +155,12 @@ func (acc *BaseAccount) SetSequence(seq uint64) error { acc.Sequence = seq return nil } + +func (acc *BaseAccount) IsRestricted() bool { + return !acc.Unrestricted +} + +// IsLocked returns true if the account is locked. +func (acc *BaseAccount) SetUnrestricted(unrestricted bool) { + acc.Unrestricted = unrestricted +} diff --git a/tm2/pkg/std/coin.go b/tm2/pkg/std/coin.go index fba20a5ba78..118567ffe40 100644 --- a/tm2/pkg/std/coin.go +++ b/tm2/pkg/std/coin.go @@ -63,7 +63,7 @@ func (coin Coin) String() string { // validate returns an error if the Coin has a negative amount or if // the denom is invalid. func validate(denom string, amount int64) error { - if err := validateDenom(denom); err != nil { + if err := ValidateDenom(denom); err != nil { return err } @@ -229,7 +229,7 @@ func (coins Coins) IsValid() bool { case 0: return true case 1: - if err := validateDenom(coins[0].Denom); err != nil { + if err := ValidateDenom(coins[0].Denom); err != nil { return false } return coins[0].IsPositive() @@ -328,6 +328,21 @@ func (coins Coins) AddUnsafe(coinsB Coins) Coins { } } +// ContainOneOfDenom check if a Coins instance contains a denom in the provided denomos +func (coins Coins) ContainOneOfDenom(denoms map[string]struct{}) bool { + if len(denoms) == 0 { + return false + } + + for _, coin := range coins { + if _, ok := denoms[coin.Denom]; ok { + return true + } + } + + return false +} + // DenomsSubsetOf returns true if receiver's denom set // is subset of coinsB's denoms. func (coins Coins) DenomsSubsetOf(coinsB Coins) bool { @@ -623,7 +638,7 @@ var ( reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnmString)) ) -func validateDenom(denom string) error { +func ValidateDenom(denom string) error { if !reDnm.MatchString(denom) { return fmt.Errorf("invalid denom: %s", denom) } @@ -631,7 +646,7 @@ func validateDenom(denom string) error { } func mustValidateDenom(denom string) { - if err := validateDenom(denom); err != nil { + if err := ValidateDenom(denom); err != nil { panic(err) } } @@ -661,7 +676,7 @@ func ParseCoin(coinStr string) (coin Coin, err error) { return Coin{}, errors.Wrapf(err, "failed to parse coin amount: %s", amountStr) } - if err := validateDenom(denomStr); err != nil { + if err := ValidateDenom(denomStr); err != nil { return Coin{}, fmt.Errorf("invalid denom cannot contain upper case characters or spaces: %w", err) } diff --git a/tm2/pkg/std/errors.go b/tm2/pkg/std/errors.go index 715b27b3eb4..48bd93be1de 100644 --- a/tm2/pkg/std/errors.go +++ b/tm2/pkg/std/errors.go @@ -14,43 +14,45 @@ func (abciError) AssertABCIError() {} type InternalError struct{ abciError } type ( - TxDecodeError struct{ abciError } - InvalidSequenceError struct{ abciError } - UnauthorizedError struct{ abciError } - InsufficientFundsError struct{ abciError } - UnknownRequestError struct{ abciError } - InvalidAddressError struct{ abciError } - UnknownAddressError struct{ abciError } - InvalidPubKeyError struct{ abciError } - InsufficientCoinsError struct{ abciError } - InvalidCoinsError struct{ abciError } - InvalidGasWantedError struct{ abciError } - OutOfGasError struct{ abciError } - MemoTooLargeError struct{ abciError } - InsufficientFeeError struct{ abciError } - TooManySignaturesError struct{ abciError } - NoSignaturesError struct{ abciError } - GasOverflowError struct{ abciError } + TxDecodeError struct{ abciError } + InvalidSequenceError struct{ abciError } + UnauthorizedError struct{ abciError } + InsufficientFundsError struct{ abciError } + UnknownRequestError struct{ abciError } + InvalidAddressError struct{ abciError } + UnknownAddressError struct{ abciError } + InvalidPubKeyError struct{ abciError } + InsufficientCoinsError struct{ abciError } + InvalidCoinsError struct{ abciError } + InvalidGasWantedError struct{ abciError } + OutOfGasError struct{ abciError } + MemoTooLargeError struct{ abciError } + InsufficientFeeError struct{ abciError } + TooManySignaturesError struct{ abciError } + NoSignaturesError struct{ abciError } + GasOverflowError struct{ abciError } + RestrictedTransferError struct{ abciError } ) -func (e InternalError) Error() string { return "internal error" } -func (e TxDecodeError) Error() string { return "tx decode error" } -func (e InvalidSequenceError) Error() string { return "invalid sequence error" } -func (e UnauthorizedError) Error() string { return "unauthorized error" } -func (e InsufficientFundsError) Error() string { return "insufficient funds error" } -func (e UnknownRequestError) Error() string { return "unknown request error" } -func (e InvalidAddressError) Error() string { return "invalid address error" } -func (e UnknownAddressError) Error() string { return "unknown address error" } -func (e InvalidPubKeyError) Error() string { return "invalid pubkey error" } -func (e InsufficientCoinsError) Error() string { return "insufficient coins error" } -func (e InvalidCoinsError) Error() string { return "invalid coins error" } -func (e InvalidGasWantedError) Error() string { return "invalid gas wanted" } -func (e OutOfGasError) Error() string { return "out of gas error" } -func (e MemoTooLargeError) Error() string { return "memo too large error" } -func (e InsufficientFeeError) Error() string { return "insufficient fee error" } -func (e TooManySignaturesError) Error() string { return "too many signatures error" } -func (e NoSignaturesError) Error() string { return "no signatures error" } -func (e GasOverflowError) Error() string { return "gas overflow error" } +func (e InternalError) Error() string { return "internal error" } +func (e TxDecodeError) Error() string { return "tx decode error" } +func (e InvalidSequenceError) Error() string { return "invalid sequence error" } +func (e UnauthorizedError) Error() string { return "unauthorized error" } +func (e InsufficientFundsError) Error() string { return "insufficient funds error" } +func (e UnknownRequestError) Error() string { return "unknown request error" } +func (e InvalidAddressError) Error() string { return "invalid address error" } +func (e UnknownAddressError) Error() string { return "unknown address error" } +func (e InvalidPubKeyError) Error() string { return "invalid pubkey error" } +func (e InsufficientCoinsError) Error() string { return "insufficient coins error" } +func (e InvalidCoinsError) Error() string { return "invalid coins error" } +func (e InvalidGasWantedError) Error() string { return "invalid gas wanted" } +func (e OutOfGasError) Error() string { return "out of gas error" } +func (e MemoTooLargeError) Error() string { return "memo too large error" } +func (e InsufficientFeeError) Error() string { return "insufficient fee error" } +func (e TooManySignaturesError) Error() string { return "too many signatures error" } +func (e NoSignaturesError) Error() string { return "no signatures error" } +func (e GasOverflowError) Error() string { return "gas overflow error" } +func (e RestrictedTransferError) Error() string { return "restricted token transfer error" } // NOTE also update pkg/std/package.go registrations. diff --git a/tm2/pkg/std/package.go b/tm2/pkg/std/package.go index a1aadc17cb6..471c32f3f5e 100644 --- a/tm2/pkg/std/package.go +++ b/tm2/pkg/std/package.go @@ -36,4 +36,5 @@ var Package = amino.RegisterPackage(amino.NewPackage( TooManySignaturesError{}, "TooManySignaturesError", NoSignaturesError{}, "NoSignaturesError", GasOverflowError{}, "GasOverflowError", + RestrictedTransferError{}, "RestrictedTransferError", ))