diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a3df17a --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +PACKAGES=$(shell go list ./... | grep -v '/simulation') + +VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') +COMMIT := $(shell git log -1 --format='%H') + +ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=NameService \ + -X github.com/cosmos/cosmos-sdk/version.ServerName=orgd \ + -X github.com/cosmos/cosmos-sdk/version.ClientName=orgcli \ + -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) + +BUILD_FLAGS := -ldflags '$(ldflags)' + +include Makefile.ledger +all: install + +install: go.sum + go install -mod=readonly $(BUILD_FLAGS) ./cmd/orgd + go install -mod=readonly $(BUILD_FLAGS) ./cmd/orgcli + +go.sum: go.mod + @echo "--> Ensure dependencies have not been modified" + GO111MODULE=on go mod verify + +test: + @go test -mod=readonly $(PACKAGES) \ No newline at end of file diff --git a/Makefile.ledger b/Makefile.ledger new file mode 100644 index 0000000..e3bbdb6 --- /dev/null +++ b/Makefile.ledger @@ -0,0 +1,25 @@ +LEDGER_ENABLED ?= true + +build_tags = +ifeq ($(LEDGER_ENABLED),true) + ifeq ($(OS),Windows_NT) + GCCEXE = $(shell where gcc.exe 2> NUL) + ifeq ($(GCCEXE),) + $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + else + UNAME_S = $(shell uname -s) + ifeq ($(UNAME_S),OpenBSD) + $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) + else + GCC = $(shell command -v gcc 2> /dev/null) + ifeq ($(GCC),) + $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + endif + endif +endif diff --git a/app.go b/app.go new file mode 100644 index 0000000..ad71bf6 --- /dev/null +++ b/app.go @@ -0,0 +1,313 @@ +package app + +import ( + "encoding/json" + "os" + + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/genaccounts" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" + + "github.com/cosmos/e025/organization/x/organization" +) + +const appName = "organization" + +var ( + // default home directories for the application CLI + DefaultCLIHome = os.ExpandEnv("$HOME/.orgcli") + + // DefaultNodeHome sets the folder where the applcation data and configuration will be stored + DefaultNodeHome = os.ExpandEnv("$HOME/.orgd") + + // NewBasicManager is in charge of setting up basic module elemnets + ModuleBasics = module.NewBasicManager( + genaccounts.AppModuleBasic{}, + genutil.AppModuleBasic{}, + auth.AppModuleBasic{}, + bank.AppModuleBasic{}, + staking.AppModuleBasic{}, + distr.AppModuleBasic{}, + params.AppModuleBasic{}, + slashing.AppModuleBasic{}, + supply.AppModuleBasic{}, + + organization.AppModule{}, + ) + // account permissions + maccPerms = map[string][]string{ + auth.FeeCollectorName: nil, + distr.ModuleName: nil, + staking.BondedPoolName: {supply.Burner, supply.Staking}, + staking.NotBondedPoolName: {supply.Burner, supply.Staking}, + } +) + +// MakeCodec generates the necessary codecs for Amino +func MakeCodec() *codec.Codec { + var cdc = codec.New() + ModuleBasics.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + return cdc +} + +type orgServiceApp struct { + *bam.BaseApp + cdc *codec.Codec + + // keys to access the substores + keys map[string]*sdk.KVStoreKey + tkeys map[string]*sdk.TransientStoreKey + + // Keepers + accountKeeper auth.AccountKeeper + bankKeeper bank.Keeper + stakingKeeper staking.Keeper + slashingKeeper slashing.Keeper + distrKeeper distr.Keeper + supplyKeeper supply.Keeper + paramsKeeper params.Keeper + nsKeeper organization.Keeper + + // Module Manager + mm *module.Manager +} + +// NewOrgServiceApp is a constructor function for orgServiceApp +func NewOrgServiceApp( + logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp), +) *orgServiceApp { + + // First define the top level codec that will be shared by the different modules + cdc := MakeCodec() + + // BaseApp handles interactions with Tendermint through the ABCI protocol + bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) + + bApp.SetAppVersion(version.Version) + + keys := sdk.NewKVStoreKeys(bam.MainStoreKey, auth.StoreKey, staking.StoreKey, + supply.StoreKey, distr.StoreKey, slashing.StoreKey, params.StoreKey, organization.StoreKey) + + tkeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) + + // Here you initialize your application with the store keys it requires + var app = &orgServiceApp{ + BaseApp: bApp, + cdc: cdc, + keys: keys, + tkeys: tkeys, + } + + // The ParamsKeeper handles parameter storage for the application + app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace) + // Set specific supspaces + authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) + bankSupspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) + stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) + distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) + slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) + + // The AccountKeeper handles address -> account lookups + app.accountKeeper = auth.NewAccountKeeper( + app.cdc, + keys[auth.StoreKey], + authSubspace, + auth.ProtoBaseAccount, + ) + + // The BankKeeper allows you perform sdk.Coins interactions + app.bankKeeper = bank.NewBaseKeeper( + app.accountKeeper, + bankSupspace, + bank.DefaultCodespace, + app.ModuleAccountAddrs(), + ) + + // The SupplyKeeper collects transaction fees and renders them to the fee distribution module + app.supplyKeeper = supply.NewKeeper( + app.cdc, + keys[supply.StoreKey], + app.accountKeeper, + app.bankKeeper, + maccPerms, + ) + + // The staking keeper + stakingKeeper := staking.NewKeeper( + app.cdc, + keys[staking.StoreKey], + tkeys[staking.TStoreKey], + app.supplyKeeper, + stakingSubspace, + staking.DefaultCodespace, + ) + + app.distrKeeper = distr.NewKeeper( + app.cdc, + keys[distr.StoreKey], + distrSubspace, + &stakingKeeper, + app.supplyKeeper, + distr.DefaultCodespace, + auth.FeeCollectorName, + app.ModuleAccountAddrs(), + ) + + app.slashingKeeper = slashing.NewKeeper( + app.cdc, + keys[slashing.StoreKey], + &stakingKeeper, + slashingSubspace, + slashing.DefaultCodespace, + ) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.stakingKeeper = *stakingKeeper.SetHooks( + staking.NewMultiStakingHooks( + app.distrKeeper.Hooks(), + app.slashingKeeper.Hooks()), + ) + + // The OrgServiceKeeper is the Keeper from the module for this tutorial + // It handles interactions with the orgstore + app.nsKeeper = organization.NewKeeper( + app.bankKeeper, + keys[organization.StoreKey], + app.cdc, + ) + + app.mm = module.NewManager( + genaccounts.NewAppModule(app.accountKeeper), + genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), + auth.NewAppModule(app.accountKeeper), + bank.NewAppModule(app.bankKeeper, app.accountKeeper), + organization.NewAppModule(app.nsKeeper, app.bankKeeper), + supply.NewAppModule(app.supplyKeeper, app.accountKeeper), + distr.NewAppModule(app.distrKeeper, app.supplyKeeper), + slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), + staking.NewAppModule(app.stakingKeeper, app.distrKeeper, app.accountKeeper, app.supplyKeeper), + ) + + app.mm.SetOrderBeginBlockers(distr.ModuleName, slashing.ModuleName) + app.mm.SetOrderEndBlockers(staking.ModuleName) + + // Sets the order of Genesis - Order matters, genutil is to always come last + // NOTE: The genutils moodule must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + app.mm.SetOrderInitGenesis( + genaccounts.ModuleName, + distr.ModuleName, + staking.ModuleName, + auth.ModuleName, + bank.ModuleName, + slashing.ModuleName, + organization.ModuleName, + supply.ModuleName, + genutil.ModuleName, + ) + + // register all module routes and module queriers + app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) + + // The initChainer handles translating the genesis.json file into initial state for the network + app.SetInitChainer(app.InitChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + + // The AnteHandler handles signature verification and transaction pre-processing + app.SetAnteHandler( + auth.NewAnteHandler( + app.accountKeeper, + app.supplyKeeper, + auth.DefaultSigVerificationGasConsumer, + ), + ) + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + + err := app.LoadLatestVersion(app.keys[bam.MainStoreKey]) + if err != nil { + cmn.Exit(err.Error()) + } + + return app +} + +// GenesisState represents chain state at the start of the chain. Any initial state (account balances) are stored here. +type GenesisState map[string]json.RawMessage + +func NewDefaultGenesisState() GenesisState { + return ModuleBasics.DefaultGenesis() +} + +func (app *orgServiceApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var genesisState GenesisState + + err := app.cdc.UnmarshalJSON(req.AppStateBytes, &genesisState) + if err != nil { + panic(err) + } + + return app.mm.InitGenesis(ctx, genesisState) +} + +func (app *orgServiceApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + return app.mm.BeginBlock(ctx, req) +} +func (app *orgServiceApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + return app.mm.EndBlock(ctx, req) +} +func (app *orgServiceApp) LoadHeight(height int64) error { + return app.LoadVersion(height, app.keys[bam.MainStoreKey]) +} + +// ModuleAccountAddrs returns all the app's module account addresses. +func (app *orgServiceApp) ModuleAccountAddrs() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range maccPerms { + modAccAddrs[supply.NewModuleAddress(acc).String()] = true + } + + return modAccAddrs +} + +//_________________________________________________________ + +func (app *orgServiceApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string, +) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) + + genState := app.mm.ExportGenesis(ctx) + appState, err = codec.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + + validators = staking.WriteValidators(ctx, app.stakingKeeper) + + return appState, validators, nil +} diff --git a/cmd/orgcli/main.go b/cmd/orgcli/main.go new file mode 100644 index 0000000..eecfda1 --- /dev/null +++ b/cmd/orgcli/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "os" + "path" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/lcd" + "github.com/cosmos/cosmos-sdk/client/rpc" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" + bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + app "github.com/cosmos/e025/organization" + "github.com/spf13/cobra" + "github.com/spf13/viper" + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/libs/cli" +) + +func main() { + cobra.EnableCommandSorting = false + + cdc := app.MakeCodec() + + // Read in the configuration file for the sdk + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + rootCmd := &cobra.Command{ + Use: "orgcli", + Short: "orgservice Client", + } + + // Add --chain-id to persistent flags and mark it required + rootCmd.PersistentFlags().String(client.FlagChainID, "", "Chain ID of tendermint node") + rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error { + return initConfig(rootCmd) + } + + // Construct Root Command + rootCmd.AddCommand( + rpc.StatusCommand(), + client.ConfigCmd(app.DefaultCLIHome), + queryCmd(cdc), + txCmd(cdc), + client.LineBreak, + lcd.ServeCommand(cdc, registerRoutes), + client.LineBreak, + keys.Commands(), + client.LineBreak, + version.Cmd, + client.NewCompletionCmd(rootCmd, true), + ) + + executor := cli.PrepareMainCmd(rootCmd, "ORG", app.DefaultCLIHome) + err := executor.Execute() + if err != nil { + panic(err) + } +} + +func registerRoutes(rs *lcd.RestServer) { + client.RegisterRoutes(rs.CliCtx, rs.Mux) + authrest.RegisterTxRoutes(rs.CliCtx, rs.Mux) + app.ModuleBasics.RegisterRESTRoutes(rs.CliCtx, rs.Mux) +} + +func queryCmd(cdc *amino.Codec) *cobra.Command { + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + } + + queryCmd.AddCommand( + authcmd.GetAccountCmd(cdc), + client.LineBreak, + rpc.ValidatorCommand(cdc), + rpc.BlockCommand(), + authcmd.QueryTxsByEventsCmd(cdc), + authcmd.QueryTxCmd(cdc), + client.LineBreak, + ) + + // add modules' query commands + app.ModuleBasics.AddQueryCommands(queryCmd, cdc) + + return queryCmd +} + +func txCmd(cdc *amino.Codec) *cobra.Command { + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + } + + txCmd.AddCommand( + bankcmd.SendTxCmd(cdc), + client.LineBreak, + authcmd.GetSignCommand(cdc), + authcmd.GetMultiSignCommand(cdc), + client.LineBreak, + authcmd.GetBroadcastCommand(cdc), + authcmd.GetEncodeCommand(cdc), + client.LineBreak, + ) + + // add modules' tx commands + app.ModuleBasics.AddTxCommands(txCmd, cdc) + + return txCmd +} + +func initConfig(cmd *cobra.Command) error { + home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) + if err != nil { + return err + } + + cfgFile := path.Join(home, "config", "config.toml") + if _, err := os.Stat(cfgFile); err == nil { + viper.SetConfigFile(cfgFile) + + if err := viper.ReadInConfig(); err != nil { + return err + } + } + if err := viper.BindPFlag(client.FlagChainID, cmd.PersistentFlags().Lookup(client.FlagChainID)); err != nil { + return err + } + if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { + return err + } + return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag)) +} diff --git a/cmd/orgd/main.go b/cmd/orgd/main.go new file mode 100644 index 0000000..87b7950 --- /dev/null +++ b/cmd/orgd/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "encoding/json" + "io" + + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/x/genaccounts" + genaccscli "github.com/cosmos/cosmos-sdk/x/genaccounts/client/cli" + "github.com/cosmos/cosmos-sdk/x/staking" + + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + app "github.com/cosmos/e025/organization" + abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" +) + +func main() { + cobra.EnableCommandSorting = false + + cdc := app.MakeCodec() + + config := sdk.GetConfig() + config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) + config.Seal() + + ctx := server.NewDefaultContext() + + rootCmd := &cobra.Command{ + Use: "orgd", + Short: "orgservice App Daemon (server)", + PersistentPreRunE: server.PersistentPreRunEFn(ctx), + } + // CLI commands to initialize the chain + rootCmd.AddCommand( + genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome), + genutilcli.CollectGenTxsCmd(ctx, cdc, genaccounts.AppModuleBasic{}, app.DefaultNodeHome), + genutilcli.GenTxCmd( + ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, + genaccounts.AppModuleBasic{}, app.DefaultNodeHome, app.DefaultCLIHome, + ), + genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics), + // AddGenesisAccountCmd allows users to add accounts to the genesis file + genaccscli.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), + ) + + server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) + + // prepare and add flags + executor := cli.PrepareBaseCmd(rootCmd, "NS", app.DefaultNodeHome) + err := executor.Execute() + if err != nil { + panic(err) + } +} + +func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { + return app.NewOrgServiceApp(logger, db) +} + +func exportAppStateAndTMValidators( + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, +) (json.RawMessage, []tmtypes.GenesisValidator, error) { + + if height != -1 { + orgApp := app.NewOrgServiceApp(logger, db) + err := orgApp.LoadHeight(height) + if err != nil { + return nil, nil, err + } + return orgApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) + } + + orgApp := app.NewOrgServiceApp(logger, db) + + return orgApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2c1d283 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/cosmos/e025/organization + +go 1.13 + +require ( + github.com/cosmos/cosmos-sdk v0.37.4 + github.com/gorilla/mux v1.7.3 + github.com/spf13/cobra v0.0.5 + github.com/spf13/viper v1.6.1 + github.com/tendermint/go-amino v0.15.1 + github.com/tendermint/tendermint v0.32.8 + github.com/tendermint/tm-db v0.2.0 +) diff --git a/x/organization/alias.go b/x/organization/alias.go new file mode 100644 index 0000000..73f946e --- /dev/null +++ b/x/organization/alias.go @@ -0,0 +1,36 @@ +package organization + +import ( + "github.com/cosmos/e025/organization/x/organization/internal/keeper" + "github.com/cosmos/e025/organization/x/organization/internal/types" +) + +const ( + ModuleName = types.ModuleName + RouterKey = types.RouterKey + StoreKey = types.StoreKey +) + +var ( + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + NewMsgCreateOrg = types.NewMsgCreateOrg + NewOrg = types.NewOrg + NewMsgAddUser = types.NewMsgAddUser + NewMsgDeleteUser = types.NewMsgDeleteUser + NewMsgDeleteOrg = types.NewMsgDeleteOrg + ModuleCdc = types.ModuleCdc + RegisterCodec = types.RegisterCodec +) + +type ( + Keeper = keeper.Keeper + MsgCreateOrg = types.MsgCreateOrg + MsgAddUser = types.MsgAddUser + MsgDeleteUser = types.MsgDeleteUser + MsgDeleteOrg = types.MsgDeleteOrg + Org = types.Org + User = types.User + QueryResResolve = types.QueryResResolve + QueryResNames = types.QueryResNames +) diff --git a/x/organization/client/cli/query.go b/x/organization/client/cli/query.go new file mode 100644 index 0000000..65fc5f5 --- /dev/null +++ b/x/organization/client/cli/query.go @@ -0,0 +1,71 @@ +package cli + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/e025/organization/x/organization/internal/types" + "github.com/spf13/cobra" +) + +func GetQueryCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + orgServiceQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the organization module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + orgServiceQueryCmd.AddCommand(client.GetCommands( + GetCmdOrg(storeKey, cdc), + GetCmdOrgs(storeKey, cdc), + )...) + return orgServiceQueryCmd +} + +// GetCmdOrg queries information about a domain +func GetCmdOrg(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "org [name]", + Short: "Query org info of name", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + name := args[0] + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/org/%s", queryRoute, name), nil) + if err != nil { + fmt.Printf("could not resolve whois - %s \n", name) + return nil + } + + var out types.Org + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(out) + }, + } +} + +// GetCmdOrgs queries a list of all names +func GetCmdOrgs(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "orgs", + Short: "All orgs", + // Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/orgs", queryRoute), nil) + if err != nil { + fmt.Printf("could not get query names\n", err) + return nil + } + + var out types.QueryResNames + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(out) + }, + } +} diff --git a/x/organization/client/cli/tx.go b/x/organization/client/cli/tx.go new file mode 100644 index 0000000..8d6b1b5 --- /dev/null +++ b/x/organization/client/cli/tx.go @@ -0,0 +1,123 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/cosmos/e025/organization/x/organization/internal/types" +) + +func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + orgServiceTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Nameservice transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + orgServiceTxCmd.AddCommand(client.PostCommands( + GetCmdCreateOrg(cdc), + GetCmdAddUser(cdc), + GetCmdDeleteUser(cdc), + GetCmdDeleteOrg(cdc), + )...) + + return orgServiceTxCmd +} + +// GetCmdCreateOrg is the CLI command for sending a SetName transaction +func GetCmdCreateOrg(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "create-org [org-name]", + Short: "set the org with a name that you own", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + msg := types.NewMsgCreateOrg(args[0], cliCtx.GetFromAddress()) + err := msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} + +// GetCmdCreateOrg is the CLI command for sending a SetName transaction +func GetCmdAddUser(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "add-user [org-name] [user-name] [address] [role]", + Short: "add user info in the organization", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + addr, err := sdk.AccAddressFromBech32(args[2]) + if err != nil { + return err + } + + msg := types.NewMsgAddUser(args[0], args[1], addr, args[3], cliCtx.GetFromAddress()) + err = msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} + +func GetCmdDeleteUser(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delete-user [org-name] [user-name]", + Short: "remove user info in the organization", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + msg := types.NewMsgDeleteUser(args[0], args[1], cliCtx.GetFromAddress()) + err := msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} + +func GetCmdDeleteOrg(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delete-org [org-name]", + Short: "delete the organization", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + msg := types.NewMsgDeleteOrg(args[0], cliCtx.GetFromAddress()) + err := msg.ValidateBasic() + if err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} diff --git a/x/organization/client/rest/query.go b/x/organization/client/rest/query.go new file mode 100644 index 0000000..6a90d42 --- /dev/null +++ b/x/organization/client/rest/query.go @@ -0,0 +1,42 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + + "github.com/cosmos/cosmos-sdk/types/rest" + + "github.com/gorilla/mux" +) + +func orgsHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + paramType := vars[restName] + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/orgs", storeName, paramType), nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func orgHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + paramType := vars[restName] + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/orgs/%s", storeName, paramType), nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} diff --git a/x/organization/client/rest/rest.go b/x/organization/client/rest/rest.go new file mode 100644 index 0000000..48a512c --- /dev/null +++ b/x/organization/client/rest/rest.go @@ -0,0 +1,20 @@ +package rest + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client/context" + + "github.com/gorilla/mux" +) + +const ( + restName = "name" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, storeName string) { + r.HandleFunc(fmt.Sprintf("/%s/orgs", storeName), orgsHandler(cliCtx, storeName)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/orgs/{%s}/org", storeName, restName), orgHandler(cliCtx, storeName)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/orgs", storeName), createOrgHandler(cliCtx)).Methods("POST") +} diff --git a/x/organization/client/rest/tx.go b/x/organization/client/rest/tx.go new file mode 100644 index 0000000..f8da5f1 --- /dev/null +++ b/x/organization/client/rest/tx.go @@ -0,0 +1,50 @@ +package rest + +import ( + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/e025/organization/x/organization/internal/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" +) + +type createOrgReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Name string `json:"name"` + Owner string `json:"owner"` +} + +func createOrgHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req createOrgReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + addr, err := sdk.AccAddressFromBech32(req.Owner) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // create the message + msg := types.NewMsgCreateOrg(req.Name, addr) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } +} + diff --git a/x/organization/genesis.go b/x/organization/genesis.go new file mode 100644 index 0000000..fefec45 --- /dev/null +++ b/x/organization/genesis.go @@ -0,0 +1,54 @@ +package organization + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +type GenesisState struct { + OrgRecords []Org `json:"org_records"` +} + +func NewGenesisState(whoIsRecords []Org) GenesisState { + return GenesisState{OrgRecords: nil} +} + +func ValidateGenesis(data GenesisState) error { + for _, record := range data.OrgRecords { + if record.Owner == nil { + return fmt.Errorf("invalid WhoisRecord: Value: %s. Error: Missing Owner", record.Name) + } + if record.Name == "" { + return fmt.Errorf("invalid WhoisRecord: Owner: %s. Error: Missing Value", record.Owner) + } + } + return nil +} + +func DefaultGenesisState() GenesisState { + return GenesisState{ + OrgRecords: []Org{}, + } +} + +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) []abci.ValidatorUpdate { + for _, record := range data.OrgRecords { + keeper.SetOrg(ctx, record.Name, record) + } + return []abci.ValidatorUpdate{} +} + +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + var records []Org + iterator := k.GetNamesIterator(ctx) + for ; iterator.Valid(); iterator.Next() { + + name := string(iterator.Key()) + org := k.GetOrg(ctx, name) + records = append(records, org) + + } + return GenesisState{OrgRecords: records} +} diff --git a/x/organization/handler.go b/x/organization/handler.go new file mode 100644 index 0000000..62474a9 --- /dev/null +++ b/x/organization/handler.go @@ -0,0 +1,106 @@ +package organization + +import ( + "fmt" + + "github.com/cosmos/e025/organization/x/organization/internal/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewHandler returns a handler for "orgservice" type messages. +func NewHandler(keeper Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case MsgCreateOrg: + return handleMsgCreateOrg(ctx, keeper, msg) + case MsgAddUser: + return handleMsgAddUser(ctx, keeper, msg) + case MsgDeleteUser: + return handleMsgDeleteUser(ctx, keeper, msg) + case MsgDeleteOrg: + return handleMsgDeleteOrg(ctx, keeper, msg) + default: + errMsg := fmt.Sprintf("Unrecognized orgservice Msg type: %v", msg.Type()) + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +// Handle a message to set name +func handleMsgCreateOrg(ctx sdk.Context, keeper Keeper, msg MsgCreateOrg) sdk.Result { + if keeper.HasOwner(ctx, msg.Name) { + return sdk.ErrUnknownRequest("Address already exist").Result() + } + + org := types.Org{ + Name: msg.Name, + Owner: msg.Owner, + } + keeper.SetOrg(ctx, msg.Name, org) // If so, set the name to the value specified in the msg. + return sdk.Result{} // return +} + +func handleMsgAddUser(ctx sdk.Context, keeper Keeper, msg MsgAddUser) sdk.Result { + org := keeper.GetOrg(ctx, msg.OrgName) + if org.Name == "" { + return sdk.ErrUnknownRequest("Organization does not exist").Result() + } + + if org.Owner.String() != msg.Sender.String() { + return sdk.ErrUnknownRequest("Incorrect Owner").Result() + } + + users := org.Users + users = append(users, msg.User) + org.Users = users + keeper.SetOrg(ctx, msg.OrgName, org) + return sdk.Result{} +} + +func handleMsgDeleteUser(ctx sdk.Context, keeper Keeper, msg MsgDeleteUser) sdk.Result { + org := keeper.GetOrg(ctx, msg.OrgName) + if org.Name == "" { + return sdk.ErrUnknownRequest("Organization does not exist").Result() + } + + if org.Owner.String() != msg.Sender.String() { + return sdk.ErrUnknownRequest("Incorrect Owner").Result() + } + + users := org.Users + index := -1 + + for i, user := range users { + if user.Name == msg.UserName { + index = i + break + } + } + + if index != -1 { + copy(users[index:], users[index+1:]) + users[len(users)-1] = User{} + users = users[:len(users)-1] + } else { + return sdk.ErrUnknownRequest("user not found").Result() + } + + org.Users = users + keeper.SetOrg(ctx, msg.OrgName, org) + return sdk.Result{} +} + +func handleMsgDeleteOrg(ctx sdk.Context, keeper Keeper, msg MsgDeleteOrg) sdk.Result{ + org := keeper.GetOrg(ctx, msg.OrgName) + if org.Name == "" { + return sdk.ErrUnknownRequest("Organization does not exist").Result() + } + + if org.Owner.String() != msg.Sender.String() { + return sdk.ErrUnknownRequest("Incorrect Owner").Result() + } + + keeper.DeleteOrg(ctx, msg.OrgName) + return sdk.Result{} +} \ No newline at end of file diff --git a/x/organization/internal/keeper/keeper.go b/x/organization/internal/keeper/keeper.go new file mode 100644 index 0000000..12b9c79 --- /dev/null +++ b/x/organization/internal/keeper/keeper.go @@ -0,0 +1,73 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/e025/organization/x/organization/internal/types" +) + +type Keeper struct { + CoinKeeper bank.Keeper + + storeKey sdk.StoreKey // Unexposed key to access store from sdk.Context + + cdc *codec.Codec // The wire codec for binary encoding/decoding. +} + +func NewKeeper(coinKeeper bank.Keeper, storeKey sdk.StoreKey, cdc *codec.Codec) Keeper { + return Keeper{ + CoinKeeper: coinKeeper, + storeKey: storeKey, + cdc: cdc, + } +} + +func (k Keeper) GetOrg(ctx sdk.Context, name string) types.Org { + store := ctx.KVStore(k.storeKey) + if !k.IsNamePresent(ctx, name) { + return types.Org{} + } + bz := store.Get([]byte(name)) + var org types.Org + k.cdc.MustUnmarshalBinaryBare(bz, &org) + return org +} + +func (k Keeper) GetOrgOwnerAddress(ctx sdk.Context, name string) sdk.Address { + return k.GetOrg(ctx, name).Owner +} + +func (k Keeper) SetOrg(ctx sdk.Context, name string, org types.Org) { + if org.Owner.Empty() { + return + } + store := ctx.KVStore(k.storeKey) + store.Set([]byte(name), k.cdc.MustMarshalBinaryBare(org)) +} + +// Deletes the entire Whois metadata struct for a name +func (k Keeper) DeleteOrg(ctx sdk.Context, name string) { + store := ctx.KVStore(k.storeKey) + store.Delete([]byte(name)) +} + +func (k Keeper) HasOwner(ctx sdk.Context, name string) bool { + return !k.GetOrg(ctx, name).Owner.Empty() +} + +// ResolveName - returns the string that the name resolves to +func (k Keeper) ResolveName(ctx sdk.Context, name string) string { + return k.GetOrg(ctx, name).Name +} + +func (k Keeper) GetNamesIterator(ctx sdk.Context) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + return sdk.KVStorePrefixIterator(store, nil) +} + +// Check if the name is present in the store or not +func (k Keeper) IsNamePresent(ctx sdk.Context, name string) bool { + store := ctx.KVStore(k.storeKey) + return store.Has([]byte(name)) +} diff --git a/x/organization/internal/keeper/querier.go b/x/organization/internal/keeper/querier.go new file mode 100644 index 0000000..b7982ed --- /dev/null +++ b/x/organization/internal/keeper/querier.go @@ -0,0 +1,77 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/e025/organization/x/organization/internal/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// query endpoints supported by the orgservice Querier +const ( + QueryResolve = "resolve" + QueryOrg = "org" + QueryNames = "orgs" +) + +// NewQuerier is the module level router for state queries +func NewQuerier(keeper Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + switch path[0] { + case QueryResolve: + return queryResolve(ctx, path[1:], req, keeper) + case QueryOrg: + return queryWhois(ctx, path[1:], req, keeper) + case QueryNames: + return queryNames(ctx, req, keeper) + default: + return nil, sdk.ErrUnknownRequest("unknown orgservice query endpoint") + } + } +} + +// nolint: unparam +func queryResolve(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + value := keeper.ResolveName(ctx, path[0]) + + if value == "" { + return []byte{}, sdk.ErrUnknownRequest("could not resolve name") + } + + res, err := codec.MarshalJSONIndent(keeper.cdc, types.QueryResResolve{Value: value}) + if err != nil { + panic("could not marshal result to JSON") + } + + return res, nil +} + +// nolint: unparam +func queryWhois(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + org := keeper.GetOrg(ctx, path[0]) + + res, err := codec.MarshalJSONIndent(keeper.cdc, org) + if err != nil { + panic("could not marshal result to JSON") + } + + return res, nil +} + +func queryNames(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + var namesList types.QueryResNames + + iterator := keeper.GetNamesIterator(ctx) + + for ; iterator.Valid(); iterator.Next() { + namesList = append(namesList, string(iterator.Key())) + } + + res, err := codec.MarshalJSONIndent(keeper.cdc, namesList) + if err != nil { + panic("could not marshal result to JSON") + } + + return res, nil +} diff --git a/x/organization/internal/types/codec.go b/x/organization/internal/types/codec.go new file mode 100644 index 0000000..7499493 --- /dev/null +++ b/x/organization/internal/types/codec.go @@ -0,0 +1,20 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// ModuleCdc is the codec for the module +var ModuleCdc = codec.New() + +func init() { + RegisterCodec(ModuleCdc) +} + +// RegisterCodec registers concrete types on the Amino codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgCreateOrg{}, "organization/CreateOrg", nil) + cdc.RegisterConcrete(MsgAddUser{}, "organization/AddUser", nil) + cdc.RegisterConcrete(MsgDeleteUser{}, "organization/DeleteUser", nil) + cdc.RegisterConcrete(MsgDeleteOrg{}, "organization/DeleteOrg", nil) +} diff --git a/x/organization/internal/types/errors.go b/x/organization/internal/types/errors.go new file mode 100644 index 0000000..c4b4877 --- /dev/null +++ b/x/organization/internal/types/errors.go @@ -0,0 +1,17 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// DefaultCodespace is the Module Name +const ( + DefaultCodespace sdk.CodespaceType = ModuleName + + CodeNameDoesNotExist sdk.CodeType = 101 +) + +// ErrNameDoesNotExist is the error for name not existing +func ErrNameDoesNotExist(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeNameDoesNotExist, "Name does not exist") +} diff --git a/x/organization/internal/types/key.go b/x/organization/internal/types/key.go new file mode 100644 index 0000000..3680d32 --- /dev/null +++ b/x/organization/internal/types/key.go @@ -0,0 +1,9 @@ +package types + +const ( + // ModuleName is the name of the module + ModuleName = "orgservice" + + // StoreKey to be used when creating the KVStore + StoreKey = ModuleName +) diff --git a/x/organization/internal/types/msgs.go b/x/organization/internal/types/msgs.go new file mode 100644 index 0000000..6a2104e --- /dev/null +++ b/x/organization/internal/types/msgs.go @@ -0,0 +1,191 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// RouterKey is the module name router key +const RouterKey = ModuleName // this was defined in your key.go file + +// MsgSetName defines a SetName message +type MsgCreateOrg struct { + Name string `json:"name"` + Owner sdk.AccAddress `json:"owner"` +} + +// NewMsgSetName is a constructor function for MsgSetName +func NewMsgCreateOrg(name string, owner sdk.AccAddress) MsgCreateOrg { + return MsgCreateOrg{ + Name: name, + Owner: owner, + } +} + +// Route should return the name of the module +func (msg MsgCreateOrg) Route() string { return RouterKey } + +// Type should return the action +func (msg MsgCreateOrg) Type() string { return "set_name" } + +// ValidateBasic runs stateless checks on the message +func (msg MsgCreateOrg) ValidateBasic() sdk.Error { + if msg.Owner.Empty() { + return sdk.ErrInvalidAddress(msg.Owner.String()) + } + if len(msg.Name) == 0 { + return sdk.ErrUnknownRequest("Name cannot be empty") + } + return nil +} + +// GetSignBytes encodes the message for signing +func (msg MsgCreateOrg) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners defines whose signature is required +func (msg MsgCreateOrg) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Owner} +} + +//------------------------------------------------------------- + +// MsgAddUser defines a add user message +type MsgAddUser struct { + OrgName string `json:"org_name"` + User User `json:"user"` + Sender sdk.AccAddress `json:"sender"` +} + +// NewMsgSetName is a constructor function for MsgSetName +func NewMsgAddUser(orgName string, userName string, address sdk.AccAddress, role string, msgSender sdk.AccAddress) MsgAddUser { + return MsgAddUser{ + OrgName: orgName, + User: User{ + Name: userName, + Address: address, + Role: role, + }, + Sender: msgSender, + } +} + +// Route should return the name of the module +func (msg MsgAddUser) Route() string { return RouterKey } + +// Type should return the action +func (msg MsgAddUser) Type() string { return "add_user" } + +// ValidateBasic runs stateless checks on the message +func (msg MsgAddUser) ValidateBasic() sdk.Error { + if msg.Sender.Empty() { + return sdk.ErrUnknownRequest("Sender cannot be empty") + } + if len(msg.OrgName) == 0 { + return sdk.ErrUnknownRequest("OrgName cannot be empty") + } + if msg.User.Address.Empty() { + return sdk.ErrUnknownRequest("User address cannot be empty") + } + if len(msg.User.Name) == 0 { + return sdk.ErrUnknownRequest("Name cannot be empty") + } + if len(msg.User.Role) == 0 { + return sdk.ErrUnknownRequest("Role cannot be empty") + } + return nil +} + +// GetSignBytes encodes the message for signing +func (msg MsgAddUser) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners defines whose signature is required +func (msg MsgAddUser) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} + +//------------------------------------------------------- + +// MsgAddUser defines a add user message +type MsgDeleteUser struct { + OrgName string `json:"org_name"` + UserName string `json:"user_name"` + Sender sdk.AccAddress `json:"sender"` +} + +// NewMsgSetName is a constructor function for MsgSetName +func NewMsgDeleteUser(orgName string, userName string, msgSender sdk.AccAddress) MsgDeleteUser { + return MsgDeleteUser{ + OrgName: orgName, + UserName: userName, + Sender: msgSender, + } +} + +// Route should return the name of the module +func (msg MsgDeleteUser) Route() string { return RouterKey } + +// Type should return the action +func (msg MsgDeleteUser) Type() string { return "delete_user" } + +// ValidateBasic runs stateless checks on the message +func (msg MsgDeleteUser) ValidateBasic() sdk.Error { + if msg.Sender.Empty() { + return sdk.ErrUnknownRequest("Sender cannot be empty") + } + if len(msg.OrgName) == 0 { + return sdk.ErrUnknownRequest("OrgName cannot be empty") + } + if len(msg.UserName) == 0 { + return sdk.ErrUnknownRequest("Address cannot be empty") + } + return nil +} + +// GetSignBytes encodes the message for signing +func (msg MsgDeleteUser) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners defines whose signature is required +func (msg MsgDeleteUser) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} + +//------------------------------------------------------------------- + +type MsgDeleteOrg struct { + OrgName string `json:"org_name"` + Sender sdk.AccAddress `json:"sender"` +} + +func NewMsgDeleteOrg(orgName string, msgSender sdk.AccAddress) MsgDeleteOrg { + return MsgDeleteOrg{ + OrgName: orgName, + Sender: msgSender, + } +} + +func (msg MsgDeleteOrg) Route() string { return RouterKey } + +func (msg MsgDeleteOrg) Type() string { return "delete_user" } + +func (msg MsgDeleteOrg) ValidateBasic() sdk.Error { + if msg.Sender.Empty() { + return sdk.ErrUnknownRequest("Sender cannot be empty") + } + if len(msg.OrgName) == 0 { + return sdk.ErrUnknownRequest("OrgName cannot be empty") + } + return nil +} + +func (msg MsgDeleteOrg) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +func (msg MsgDeleteOrg) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} diff --git a/x/organization/internal/types/querier.go b/x/organization/internal/types/querier.go new file mode 100644 index 0000000..f7a63a3 --- /dev/null +++ b/x/organization/internal/types/querier.go @@ -0,0 +1,21 @@ +package types + +import "strings" + +// QueryResResolve Queries Result Payload for a resolve query +type QueryResResolve struct { + Value string `json:"value"` +} + +// implement fmt.Stringer +func (r QueryResResolve) String() string { + return r.Value +} + +// QueryResNames Queries Result Payload for a names query +type QueryResNames []string + +// implement fmt.Stringer +func (n QueryResNames) String() string { + return strings.Join(n[:], "\n") +} diff --git a/x/organization/internal/types/types.go b/x/organization/internal/types/types.go new file mode 100644 index 0000000..0efb3b4 --- /dev/null +++ b/x/organization/internal/types/types.go @@ -0,0 +1,32 @@ +package types + +import ( + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var MinNamePrice = sdk.Coins{sdk.NewInt64Coin("orgtoken", 1)} + +type User struct { + Name string `json:"name"` + Address sdk.AccAddress `json:"address"` + Role string `json:"role"` +} + +type Org struct { + Name string `json:"name"` + Owner sdk.AccAddress `json:"owner"` + Users []User `json:"users"` +} + +func NewOrg() Org { + return Org{} +} + +func (o Org) String() string { + return strings.TrimSpace(fmt.Sprintf(`Owner: %s +Value: %s +Price: %s`, o.Name, o.Owner)) +} diff --git a/x/organization/module.go b/x/organization/module.go new file mode 100644 index 0000000..d9b0e24 --- /dev/null +++ b/x/organization/module.go @@ -0,0 +1,118 @@ +package organization + +import ( + "encoding/json" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/e025/organization/x/organization/client/cli" + "github.com/cosmos/e025/organization/x/organization/client/rest" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// type check to ensure the interface is properly implemented +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// app module Basics object +type AppModuleBasic struct{} + +func (AppModuleBasic) Name() string { + return ModuleName +} + +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + RegisterCodec(cdc) +} + +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) +} + +// Validation check of the Genesis +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &data) + if err != nil { + return err + } + // Once json successfully marshalled, passes along to genesis.go + return ValidateGenesis(data) +} + +// Register rest routes +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + rest.RegisterRoutes(ctx, rtr, StoreKey) +} + +// Get the root query command of this module +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetQueryCmd(StoreKey, cdc) +} + +// Get the root tx command of this module +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(StoreKey, cdc) +} + +type AppModule struct { + AppModuleBasic + keeper Keeper + coinKeeper bank.Keeper +} + +// NewAppModule creates a new AppModule Object +func NewAppModule(k Keeper, bankKeeper bank.Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: k, + coinKeeper: bankKeeper, + } +} + +func (AppModule) Name() string { + return ModuleName +} + +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +func (am AppModule) Route() string { + return RouterKey +} + +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} +func (am AppModule) QuerierRoute() string { + return ModuleName +} + +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} + +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +func (am AppModule) EndBlock(sdk.Context, abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + return InitGenesis(ctx, am.keeper, genesisState) +} + +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) +}