Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cli to change root sudo command #1498

Merged
merged 14 commits into from
Jul 11, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [#1463](https://github.com/NibiruChain/nibiru/pull/1463) - feat(oracle): add genesis pricefeeder delegation
* [#1479](https://github.com/NibiruChain/nibiru/pull/1479) - feat(perp): implement `PartialClose`
* [#1498](https://github.com/NibiruChain/nibiru/pull/1498) - feat: add cli to change root sudo command

### Bug Fixes

Expand Down
2 changes: 1 addition & 1 deletion proto/nibiru/sudo/v1/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "nibiru/sudo/v1/state.proto";

option go_package = "github.com/NibiruChain/nibiru/x/sudo/pb";
option go_package = "github.com/NibiruChain/nibiru/x/sudo/types";

message EventUpdateSudoers {
Sudoers sudoers = 1 [ (gogoproto.nullable) = false ];
Expand Down
2 changes: 1 addition & 1 deletion proto/nibiru/sudo/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "google/api/annotations.proto";

import "nibiru/sudo/v1/state.proto";

option go_package = "github.com/NibiruChain/nibiru/x/sudo/pb";
option go_package = "github.com/NibiruChain/nibiru/x/sudo/types";

// Query defines the gRPC querier service.
service Query {
Expand Down
2 changes: 1 addition & 1 deletion proto/nibiru/sudo/v1/state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package nibiru.sudo.v1;
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";

option go_package = "github.com/NibiruChain/nibiru/x/sudo/pb";
option go_package = "github.com/NibiruChain/nibiru/x/sudo/types";

message Sudoers {
option (gogoproto.goproto_stringer) = false;
Expand Down
22 changes: 20 additions & 2 deletions proto/nibiru/sudo/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package nibiru.sudo.v1;
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";

option go_package = "github.com/NibiruChain/nibiru/x/sudo/pb";
option go_package = "github.com/NibiruChain/nibiru/x/sudo/types";

// Msg defines the x/sudo module's Msg service. Protobuf `Msg` services are
// called from `BaseApp` instances during `DeliverTx`. The `Msg` service will be
Expand All @@ -16,6 +16,10 @@ service Msg {
rpc EditSudoers(MsgEditSudoers) returns (MsgEditSudoersResponse) {
option (google.api.http).post = "/nibiru/sudo/edit_sudoers";
}

rpc ChangeRoot(MsgChangeRoot) returns (MsgChangeRootResponse) {
option (google.api.http).post = "/nibiru/sudo/change_root";
}
}

// -------------------------- EditSudoers --------------------------
Expand All @@ -35,4 +39,18 @@ message MsgEditSudoers {
}

// MsgEditSudoersResponse indicates the successful execution of MsgEditSudeors.
message MsgEditSudoersResponse {}
message MsgEditSudoersResponse {}

// -------------------------- ChangeRoot --------------------------

/* MsgChangeRoot: Msg to update the "Sudoers" state. */
message MsgChangeRoot {
// Sender: Address for the signer of the transaction.
string sender = 1;

// NewRoot: New root address.
string new_root = 2;
}

// MsgChangeRootResponse indicates the successful execution of MsgChangeRoot.
message MsgChangeRootResponse {}
47 changes: 47 additions & 0 deletions x/sudo/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func GetTxCmd() *cobra.Command {
// Add subcommands
txCmd.AddCommand(
CmdEditSudoers(),
CmdChangeRoot(),
)

return txCmd
Expand Down Expand Up @@ -112,6 +113,52 @@ func CmdEditSudoers() *cobra.Command {
return cmd
}

// CmdChangeRoot is a terminal command corresponding to the ChangeRoot
func CmdChangeRoot() *cobra.Command {
cmd := &cobra.Command{
Use: "change-root [new-root-address]",
Args: cobra.ExactArgs(1),
Short: "Change the root address of the x/sudo state",
Example: strings.TrimSpace(fmt.Sprintf(`
Example:
$ %s tx sudo change-root <new-root-address> --from=<key_or_address>
`, version.AppName)),
Long: strings.TrimSpace(
`Change the root address of the x/sudo state, giving the
new address, should be executed by the current root address.
`),
RunE: func(cmd *cobra.Command, args []string) (err error) {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

msg := new(types.MsgChangeRoot)

// marshals contents into the proto.Message to which 'msg' points.
root := args[0]
if err != nil {
return err
}

// Parse the message sender
from := clientCtx.GetFromAddress()
msg.Sender = from.String()
msg.NewRoot = root

if err = msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}

func CmdQuerySudoers() *cobra.Command {
cmd := &cobra.Command{
Use: "state",
Expand Down
17 changes: 17 additions & 0 deletions x/sudo/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,23 @@ func (s *IntegrationSuite) TestCmdEditSudoers() {
}
}

func (s *IntegrationSuite) Test_ZCmdChangeRoot() {
val := s.network.Validators[0]

sudoers, err := testutilcli.QuerySudoers(val.ClientCtx)
s.NoError(err)
initialRoot := sudoers.Sudoers.Root

newRoot := testutil.AccAddress()
_, err = testutilcli.ExecTx(s.network, cli.CmdChangeRoot(), s.root.addr, []string{newRoot.String()})
require.NoError(s.T(), err)

sudoers, err = testutilcli.QuerySudoers(val.ClientCtx)
s.NoError(err)
require.NotEqual(s.T(), sudoers.Sudoers.Root, initialRoot)
require.Equal(s.T(), sudoers.Sudoers.Root, newRoot.String())
}

// TestMarshal_EditSudoers verifies that the expected proto.Message for
// the EditSudoders fn marshals and unmarshals properly from JSON.
// This unmarshaling is used in the main body of the CmdEditSudoers command.
Expand Down
137 changes: 137 additions & 0 deletions x/sudo/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package keeper

import (
"context"
"fmt"

"github.com/NibiruChain/collections"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/NibiruChain/nibiru/x/common/set"
sudotypes "github.com/NibiruChain/nibiru/x/sudo/types"
)

type Keeper struct {
Sudoers collections.Item[sudotypes.Sudoers]
}

func NewKeeper(
cdc codec.BinaryCodec,
storeKey types.StoreKey,
) Keeper {
return Keeper{
Sudoers: collections.NewItem(storeKey, 1, SudoersValueEncoder(cdc)),
}
}

func (k Keeper) senderHasPermission(sender string, root string) error {
if sender != root {
return fmt.Errorf(`message must be sent by root user. root: "%s", sender: "%s"`,
root, sender,
)
}
return nil
}

// AddContracts executes a MsgEditSudoers message with action type
// "add_contracts". This adds contract addresses to the sudoer set.
func (k Keeper) AddContracts(
goCtx context.Context, msg *sudotypes.MsgEditSudoers,
) (msgResp *sudotypes.MsgEditSudoersResponse, err error) {
if msg.RootAction() != sudotypes.AddContracts {
err = fmt.Errorf("invalid action type %s for msg add contracts", msg.Action)
return
}

// Read state
ctx := sdk.UnwrapSDKContext(goCtx)
pbSudoersBefore, err := k.Sudoers.Get(ctx)
if err != nil {
return
}
sudoersBefore := SudoersFromPb(pbSudoersBefore)
err = k.senderHasPermission(msg.Sender, sudoersBefore.Root)
if err != nil {
return
}

// Update state
contracts, err := sudoersBefore.AddContracts(msg.Contracts)
if err != nil {
return
}
pbSudoers := Sudoers{Root: sudoersBefore.Root, Contracts: contracts}.ToPb()
k.Sudoers.Set(ctx, pbSudoers)
msgResp = new(sudotypes.MsgEditSudoersResponse)
return msgResp, ctx.EventManager().EmitTypedEvent(&sudotypes.EventUpdateSudoers{
Sudoers: pbSudoers,
Action: msg.Action,
})
}

// ————————————————————————————————————————————————————————————————————————————
// RemoveContracts
// ————————————————————————————————————————————————————————————————————————————

func (k Keeper) RemoveContracts(
goCtx context.Context, msg *sudotypes.MsgEditSudoers,
) (msgResp *sudotypes.MsgEditSudoersResponse, err error) {
if msg.RootAction() != sudotypes.RemoveContracts {
err = fmt.Errorf("invalid action type %s for msg add contracts", msg.Action)
return
}

// Skip "msg.ValidateBasic" since this is a remove' operation. That means we
// can only remove from state but can't write anything invalid that would
// corrupt it.

// Read state
ctx := sdk.UnwrapSDKContext(goCtx)
pbSudoers, err := k.Sudoers.Get(ctx)
if err != nil {
return
}
sudoers := SudoersFromPb(pbSudoers)
err = k.senderHasPermission(msg.Sender, sudoers.Root)
if err != nil {
return
}

// Update state
sudoers.RemoveContracts(msg.Contracts)
pbSudoers = sudoers.ToPb()
k.Sudoers.Set(ctx, pbSudoers)

msgResp = new(sudotypes.MsgEditSudoersResponse)
return msgResp, ctx.EventManager().EmitTypedEvent(&sudotypes.EventUpdateSudoers{
Sudoers: pbSudoers,
Action: msg.Action,
})
}

// CheckPermissions Checks if a contract is contained within the set of sudo
// contracts defined in the x/sudo module. These smart contracts are able to
// execute certain permissioned functions.
func (k Keeper) CheckPermissions(
contract sdk.AccAddress, ctx sdk.Context,
) error {
contracts, err := k.GetSudoContracts(ctx)
if err != nil {
return err
}
hasPermission := set.New(contracts...).Has(contract.String())
if !hasPermission {
return fmt.Errorf(
"insufficient permissions on smart contract: %s. The sudo contracts are: %s",
contract, contracts,
)
}
return nil
}

func (k Keeper) GetSudoContracts(ctx sdk.Context) (contracts []string, err error) {
state, err := k.Sudoers.Get(ctx)
return state.Contracts, err
}
Loading