From e6b377b4a79c52134170f8f713535d6d4ae8be11 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 6 Jul 2023 16:22:27 +0200 Subject: [PATCH 1/9] feat: create cli to add sudo account into genesis (#1494) * add sudo root account cmd * add sudo root command into cobra cli * use make lint * replace set validator as sudoer into script localnet * add changelog --- CHANGELOG.md | 1 + cmd/nibid/cmd/root.go | 3 ++ contrib/scripts/localnet.sh | 2 +- x/sudo/cli/cli_test.go | 2 +- x/sudo/cli/gen_root.go | 76 +++++++++++++++++++++++++++++++++++++ x/sudo/cli/gen_root_test.go | 67 ++++++++++++++++++++++++++++++++ x/sudo/types/genesis.go | 27 +++++++++++++ x/sudo/types/msgs.go | 9 ----- 8 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 x/sudo/cli/gen_root.go create mode 100644 x/sudo/cli/gen_root_test.go create mode 100644 x/sudo/types/genesis.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4206e3e9e..8911ef15a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Improvements * #[1466](https://github.com/NibiruChain/nibiru/pull/1466) - refactor(perp): `PositionLiquidatedEvent` +* #[1494](https://github.com/NibiruChain/nibiru/pull/1494) - feat: create cli to add sudo account into genesis ### Features diff --git a/cmd/nibid/cmd/root.go b/cmd/nibid/cmd/root.go index d5da7be6f..be0ac1913 100644 --- a/cmd/nibid/cmd/root.go +++ b/cmd/nibid/cmd/root.go @@ -5,6 +5,8 @@ import ( "io" "os" + "github.com/NibiruChain/nibiru/x/sudo/cli" + dbm "github.com/cometbft/cometbft-db" tmcli "github.com/cometbft/cometbft/libs/cli" "github.com/cometbft/cometbft/libs/log" @@ -153,6 +155,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig app.EncodingConfig) { encodingConfig, oraclecli.AddGenesisPricefeederDelegationCmd(app.DefaultNodeHome), perpv2cli.AddMarketGenesisCmd(app.DefaultNodeHome), + cli.AddSudoRootAccountCmd(app.DefaultNodeHome), ), queryCommand(), txCommand(), diff --git a/contrib/scripts/localnet.sh b/contrib/scripts/localnet.sh index 941a1e73b..15e2fc355 100755 --- a/contrib/scripts/localnet.sh +++ b/contrib/scripts/localnet.sh @@ -231,7 +231,7 @@ else fi # set validator as sudoer -add_genesis_param '.app_state.sudo.sudoers.root = "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl"' +$BINARY genesis add-sudo-root-account "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl" # hack for localnet since we don't have a pricefeeder yet add_genesis_param '.app_state.oracle.exchange_rates[0].pair = "ubtc:unusd"' diff --git a/x/sudo/cli/cli_test.go b/x/sudo/cli/cli_test.go index beaa1509c..d686e2a8f 100644 --- a/x/sudo/cli/cli_test.go +++ b/x/sudo/cli/cli_test.go @@ -68,7 +68,7 @@ func (msg MsgEditSudoersPlus) ToJson(t *testing.T) (fileJsonBz []byte, fileName return fileJsonBz, fileName } -func (msg MsgEditSudoersPlus) Exec( +func (MsgEditSudoersPlus) Exec( network *testutilcli.Network, fileName string, from sdk.AccAddress, diff --git a/x/sudo/cli/gen_root.go b/x/sudo/cli/gen_root.go new file mode 100644 index 000000000..4369ab3b8 --- /dev/null +++ b/x/sudo/cli/gen_root.go @@ -0,0 +1,76 @@ +package cli + +import ( + "encoding/json" + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/spf13/cobra" + + "github.com/NibiruChain/nibiru/x/sudo/types" +) + +func AddSudoRootAccountCmd(defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "add-sudo-root-account", + Short: "Add sudo module root account to genesis.json.", + Long: `Add sudo module root account to genesis.json.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + serverCtx := server.GetServerContextFromCmd(cmd) + config := serverCtx.Config + + config.SetRoot(clientCtx.HomeDir) + + genFile := config.GenesisFile() + appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + if err != nil { + return err + } + + rootAccount := args[0] + addr, err := sdk.AccAddressFromBech32(rootAccount) + if err != nil { + return fmt.Errorf("failed to parse address: %w", err) + } + + sudoGenState := types.GetGenesisStateFromAppState(clientCtx.Codec, appState) + sudoGenState.Sudoers.Root = addr.String() + + sudoGenStateBz, err := clientCtx.Codec.MarshalJSON(sudoGenState) + if err != nil { + return fmt.Errorf("failed to marshal market genesis state: %w", err) + } + + appState[types.ModuleName] = sudoGenStateBz + + appStateJSON, err := json.Marshal(appState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + genDoc.AppState = appStateJSON + err = genutil.ExportGenesisFile(genDoc, genFile) + if err != nil { + return err + } + + err = clientCtx.PrintString(fmt.Sprintf("sudo module root account added to genesis.json: %s\n", rootAccount)) + if err != nil { + return err + } + + return nil + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + + return cmd +} diff --git a/x/sudo/cli/gen_root_test.go b/x/sudo/cli/gen_root_test.go new file mode 100644 index 000000000..02e9b4057 --- /dev/null +++ b/x/sudo/cli/gen_root_test.go @@ -0,0 +1,67 @@ +package cli_test + +import ( + "context" + "testing" + + "github.com/cometbft/cometbft/libs/log" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/types/module" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + "github.com/NibiruChain/nibiru/x/sudo/cli" +) + +func TestAddSudoRootAccountCmd(t *testing.T) { + tests := []struct { + name string + account string + + expectErr bool + }{ + { + name: "valid", + account: "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl", + expectErr: false, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + logger := log.NewNopLogger() + + home := t.TempDir() + cfg, err := genutiltest.CreateDefaultTendermintConfig(home) + require.NoError(t, err) + + testModuleBasicManager := module.NewBasicManager(genutil.AppModuleBasic{}) + appCodec := moduletestutil.MakeTestEncodingConfig().Codec + err = genutiltest.ExecInitCmd(testModuleBasicManager, home, appCodec) + require.NoError(t, err) + + serverCtx := server.NewContext(viper.New(), cfg, logger) + clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home) + + ctx := context.Background() + ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) + ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) + + cmd := cli.AddSudoRootAccountCmd(home) + cmd.SetArgs([]string{ + tc.account, + }) + + if tc.expectErr { + require.Error(t, cmd.ExecuteContext(ctx)) + } else { + require.NoError(t, cmd.ExecuteContext(ctx)) + } + }) + } +} diff --git a/x/sudo/types/genesis.go b/x/sudo/types/genesis.go new file mode 100644 index 000000000..fffb4d2e2 --- /dev/null +++ b/x/sudo/types/genesis.go @@ -0,0 +1,27 @@ +package types + +import ( + "encoding/json" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" +) + +func (gen *GenesisState) Validate() error { + if gen.Sudoers.Contracts == nil { + return fmt.Errorf("nil contract state must be []string") + } else if err := gen.Sudoers.Validate(); err != nil { + return err + } + return nil +} + +func GetGenesisStateFromAppState(cdc codec.JSONCodec, appState map[string]json.RawMessage) *GenesisState { + var genesisState GenesisState + + if appState[ModuleName] != nil { + cdc.MustUnmarshalJSON(appState[ModuleName], &genesisState) + } + + return &genesisState +} diff --git a/x/sudo/types/msgs.go b/x/sudo/types/msgs.go index ec8388780..3d03432d0 100644 --- a/x/sudo/types/msgs.go +++ b/x/sudo/types/msgs.go @@ -20,15 +20,6 @@ var ( RouterKey = ModuleName ) -func (gen *GenesisState) Validate() error { - if gen.Sudoers.Contracts == nil { - return fmt.Errorf("nil contract state must be []string") - } else if err := gen.Sudoers.Validate(); err != nil { - return err - } - return nil -} - // MsgEditSudoers func (m *MsgEditSudoers) Route() string { return RouterKey } From 7dc5617ab26a29fc20050c4d574dd105e4c30187 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:48:22 -0400 Subject: [PATCH 2/9] ci(chaosnet): fix chaosnet for v0.21 (#1496) * ci: fix chaosnet for v0.21 * Update CHAOSNET.md * use different mnemonics for liquidator, pricefeeder, and faucet * Update chaosnet.sh --- CHAOSNET.md | 8 + .../docker-compose-chaosnet.yml | 201 ++++-------------- contrib/make/chaosnet.mk | 17 +- contrib/scripts/chaosnet.sh | 22 +- contrib/scripts/localnet.sh | 8 +- 5 files changed, 80 insertions(+), 176 deletions(-) diff --git a/CHAOSNET.md b/CHAOSNET.md index 17a0c51ab..f7cbd8e03 100644 --- a/CHAOSNET.md +++ b/CHAOSNET.md @@ -32,6 +32,14 @@ make chaosnet-build to force re-build and pull images. +## Endpoints + +- `http://localhost:5555` -> GraphQL server +- `http://localhost:26657` -> Tendermint RPC server +- `tcp://localhost:9090` -> Cosmos SDK gRPC server +- `http://localhost:1317` -> Cosmos SDK LCD (REST) server +- `http://localhost:8000` -> Faucet server (HTTP POST only) + ## FAQ ### `make chaosnet` says that "Additional property name is not allowed" diff --git a/contrib/docker-compose/docker-compose-chaosnet.yml b/contrib/docker-compose/docker-compose-chaosnet.yml index 9484820b7..2efda80bf 100644 --- a/contrib/docker-compose/docker-compose-chaosnet.yml +++ b/contrib/docker-compose/docker-compose-chaosnet.yml @@ -2,214 +2,99 @@ name: chaosnet services: nibiru: - image: ghcr.io/nibiruchain/chaosnet:latest build: context: ../.. # nibiru project folder dockerfile: ./contrib/docker/chaosnet.Dockerfile # relative to context directory (i.e. nibiru project folder) args: MNEMONIC: guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host ports: - - 26656:26656 - 26657:26657 - 1317:1317 - 9090:9090 + volumes: + - nibid-data:/root/.nibid/data:rw faucet: + restart: always image: ghcr.io/nibiruchain/go-faucet:latest environment: NODE: nibiru:9090 - MNEMONIC: guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host + MNEMONIC: undo donkey arena rule old portion long forget rescue post stuff normal reduce raw unable warrior method stairs valley enhance glory lens sign zero SEND_COINS: 11000000unibi,100000000unusd,100000000uusdt MAX_SEND_COINS: 110000000unibi,1000000000unusd,1000000000uusdt depends_on: - - nibiru - restart: on-failure + nibiru: + condition: service_started ports: - 8000:8000 - liquidation-db: - image: postgres:latest - restart: always - environment: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: liquidator - ports: - - 5433:5432 - - liquidator: - image: ghcr.io/nibiruchain/liquidation:latest - platform: linux/amd64 # see https://github.com/psycopg/psycopg2/issues/1360 for why we need to force linux/amd64 - restart: always - environment: - - ENVIRONMENT=localnet - - NETWORK_INSECURE=true - - CHAIN_ID=nibiru-localnet-0 - - LCD_ENDPOINT=http://nibiru:1317 - - GRPC_ENDPOINT=nibiru:9090 - - RPC_ENDPOINT=nibiru:26657 - - WEBSOCKET_ENDPOINT=ws://nibiru:26657/websocket - - DATABASE_URI=postgresql+psycopg2://postgres:postgres@liquidation-db:5432/liquidator - - WALLET_MNEMONIC=guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host - - VPOOLS=ubtc:unusd - - heart-monitor-db: - image: postgres:latest + heartmonitor-db: + image: postgres:14 restart: always environment: POSTGRES_PASSWORD: postgres POSTGRES_DB: heart-monitor ports: - - 5434:5432 + - 5433:5432 - heart-monitor: - image: ghcr.io/nibiruchain/heart-monitor:latest - platform: linux/amd64 # see https://github.com/psycopg/psycopg2/issues/1360 for why we need to force linux/amd64 + heartmonitor: + image: ghcr.io/nibiruchain/go-heartmonitor:latest restart: always + command: --clean + volumes: + - nibid-data:/heartmonitor/.nibid/data:ro environment: - - DATABASE_URI=postgresql+psycopg2://postgres:postgres@heart-monitor-db:5432/heart-monitor - - ENVIRONMENT=localnet - - NETWORK_INSECURE=true - - LCD_ENDPOINT=http://nibiru:1317 - - GRPC_ENDPOINT=tcp://nibiru:9090 + - DATABASE_URI=postgresql://postgres:postgres@heartmonitor-db:5432/heart-monitor?sslmode=disable - TENDERMINT_RPC_ENDPOINT=http://nibiru:26657 - - WEBSOCKET_ENDPOINT=ws://nibiru:26657/websocket - - VALIDATOR_MNEMONIC=guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host - - MODULE_ACCOUNTS={'nibi1yl6hdjhmkf37639730gffanpzndzdpmhe6js7s':'transfer','nibi1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3z7dksy':'bonded_tokens_pool','nibi1trh2mamq64u4g042zfeevvjk4cukrthvppfnc7':'perp_ef','nibi1tygms3xhhs3yv487phx3dw4a95jn7t7lk738xs':'not_bonded_tokens_pool','nibi10d07y265gmmuvt4z0w9aw880jnsr700jd8hulq':'gov','nibi1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8s6q9qv':'distribution','nibi143kdcynewz7d08fmlu0ugtnn9sv8vljxwp7eug':'fee_pool','nibi1m3h30wlvsf8llruxtpukdvsy0km2kum8l5rpwn':'mint','nibi1umc2r7a58jy3jmw0e0hctyy0rx45chmurptawl':'vault','nibi17xpfvakm2amg962yls6f84z3kell8c5l8u8ezw':'fee_collector'} - - LOGGER_TIMESTAMP=true - - # BDJUNO - bdjuno-repo-clone-task: - image: alpine/git - command: - [ - "clone", - "https://github.com/NibiruChain/bdjuno.git", - "/bdjuno" - ] - volumes: - - bdjuno-repo:/bdjuno - - bdjuno-database-schema-task: - image: alpine:latest - command: - [ - "/bin/sh", - "-c", - "mv -v /bdjuno/database/schema/* /docker-entrypoint-initdb.d" - ] - volumes: - - bdjuno-repo:/bdjuno - - bdjuno-docker-entrypoint:/docker-entrypoint-initdb.d - depends_on: - bdjuno-repo-clone-task: - condition: service_completed_successfully - - bdjuno-hasura-metadata-task: - image: alpine:latest - command: - [ - "/bin/sh", - "-c", - "mv -v /bdjuno/hasura/metadata/* /hasura-metadata" - ] - volumes: - - bdjuno-repo:/bdjuno - - bdjuno-hasura-metadata:/hasura-metadata - depends_on: - bdjuno-repo-clone-task: - condition: service_completed_successfully + - GRPC_ENDPOINT=tcp://nibiru:9090 + - GRPC_INSECURE=true + - NO_PARTITIONS=true + - CHAIN_DB_PATH=/heartmonitor/.nibid/data - bdjuno-db: - image: postgres:latest + liquidator: + image: ghcr.io/nibiruchain/go-heartmonitor:latest restart: always + command: --liquidator environment: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: bdjuno - ports: - - 5435:5432 - volumes: - - bdjuno-docker-entrypoint:/docker-entrypoint-initdb.d:ro - depends_on: - bdjuno-repo-clone-task: - condition: service_completed_successfully - - bdjuno-genesis-task: - image: apteno/alpine-jq - command: - [ - "sh", - "-c", - "curl -s http://nibiru:26657/genesis | jq -r '.result.genesis' > /bdjuno/.bdjuno/genesis.json" - ] - volumes: - - bdjuno-genesis:/bdjuno/.bdjuno - depends_on: - nibiru: - condition: service_started - - bdjuno: - image: ghcr.io/nibiruchain/bdjuno:latest - restart: always - command: [ "bdjuno", "start", "--home", "/bdjuno/.bdjuno" ] - volumes: - - type: bind - source: ../templates/bdjuno.yaml - target: /bdjuno/.bdjuno/config.yaml - - bdjuno-genesis:/bdjuno/.bdjuno + - DATABASE_URI=postgresql://postgres:postgres@heartmonitor-db:5432/heart-monitor?sslmode=disable + - TENDERMINT_RPC_ENDPOINT=http://nibiru:26657 + - GRPC_ENDPOINT=tcp://nibiru:9090 + - GRPC_INSECURE=true + - NO_PARTITIONS=true + - LIQUIDATOR_MNEMONIC=record damage person caution truly riot resource luxury rude guide mushroom athlete fantasy dentist friend mule depth salmon photo unfold exclude coyote idea evoke + - LIQUIDATOR_GAS_LIMIT_INITIAL=500000 + - LIQUIDATOR_GAS_MULTIPLIER=5 + - LIQUIDATOR_GAS_MAX_ATTEMPTS=10 depends_on: - bdjuno-genesis-task: - condition: service_completed_successfully - bdjuno-db: + heartmonitor: condition: service_started - hasura: - image: hasura/graphql-engine:latest.cli-migrations-v3 + graphql: + image: ghcr.io/nibiruchain/go-heartmonitor:latest restart: always + command: --graphql environment: - HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgres@bdjuno-db:5432/bdjuno - HASURA_GRAPHQL_ENABLE_CONSOLE: "true" - HASURA_GRAPHQL_ADMIN_SECRET: hasura - HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous - ACTION_BASE_URL: http://bdjuno:3000 - HASURA_GRAPHQL_ENABLED_LOG_TYPES: "startup, http-log, webhook-log, websocket-log, query-log" - ports: - - 8080:8080 - volumes: - - bdjuno-hasura-metadata:/hasura-metadata - depends_on: - bdjuno-hasura-metadata-task: - condition: service_completed_successfully - bdjuno-db: - condition: service_started - - big-dipper: - image: ghcr.io/nibiruchain/big-dipper:localnet-0 - platform: linux/amd64 - restart: always + - DATABASE_URI=postgresql://postgres:postgres@heartmonitor-db:5432/heart-monitor?sslmode=disable + - NO_PARTITIONS=true ports: - - 3000:3000 - environment: - NODE_ENV: production + - 5555:5555 depends_on: - hasura: + heartmonitor: condition: service_started pricefeeder: image: ghcr.io/nibiruchain/pricefeeder:latest restart: always environment: - CHAIN_ID: "nibiru-localnet-0" + CHAIN_ID: nibiru-localnet-0 GRPC_ENDPOINT: nibiru:9090 WEBSOCKET_ENDPOINT: ws://nibiru:26657/websocket - FEEDER_MNEMONIC: guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host + FEEDER_MNEMONIC: empower dice proud brick treat reward pull jeans right rubber infant hamster pet foster among crush quick report rival bracket easily mouse topple absorb EXCHANGE_SYMBOLS_MAP: '{ "bitfinex": { "ubtc:uusd": "tBTCUSD", "ueth:uusd": "tETHUSD", "uusdt:uusd": "tUSTUSD" }, "binance": { "ubtc:uusd": "BTCUSD", "ueth:uusd": "ETHUSD", "uusdt:uusd": "USDTUSD", "uusdc:uusd": "USDCUSD", "uatom:uusd": "ATOMUSD", "ubnb:uusd": "BNBUSD", "uavax:uusd": "AVAXUSD", "usol:uusd": "SOLUSD", "uada:uusd": "ADAUSD", "ubtc:unusd": "BTCUSD", "ueth:unusd": "ETHUSD", "uusdt:unusd": "USDTUSD", "uusdc:unusd": "USDCUSD", "uatom:unusd": "ATOMUSD", "ubnb:unusd": "BNBUSD", "uavax:unusd": "AVAXUSD", "usol:unusd": "SOLUSD", "uada:unusd": "ADAUSD" } }' - + VALIDATOR_ADDRESS: nibivaloper1zaavvzxez0elundtn32qnk9lkm8kmcszuwx9jz depends_on: nibiru: condition: service_started volumes: - bdjuno-repo: - bdjuno-docker-entrypoint: - bdjuno-genesis: - bdjuno-hasura-metadata: + nibid-data: diff --git a/contrib/make/chaosnet.mk b/contrib/make/chaosnet.mk index 024894864..df46d516b 100644 --- a/contrib/make/chaosnet.mk +++ b/contrib/make/chaosnet.mk @@ -21,22 +21,31 @@ chaosnet-down: ### Chaosnet Logs ### ############################################################################### -# Run a chaosnet testnet locally .PHONY: chaosnet-logs chaosnet-logs: docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs -# Run a chaosnet testnet locally .PHONY: chaosnet-logs-faucet chaosnet-logs-faucet: - docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs faucet + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs go-faucet + +.PHONY: chaosnet-logs-pricefeeder +chaosnet-logs-pricefeeder: + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs pricefeeder --follow + +.PHONY: chaosnet-logs-go-heartmonitor +chaosnet-logs-go-heartmonitor: + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml logs go-heartmonitor ############################################################################### ### Chaosnet SSH ### ############################################################################### -# Run a chaosnet testnet locally .PHONY: chaosnet-ssh-nibiru chaosnet-ssh-nibiru: docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml exec -it nibiru /bin/sh +.PHONY: chaosnet-ssh-go-heartmonitor +chaosnet-ssh-go-heartmonitor: + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml exec -it go-heartmonitor /bin/sh + diff --git a/contrib/scripts/chaosnet.sh b/contrib/scripts/chaosnet.sh index 99ad55b79..6dff2dc90 100755 --- a/contrib/scripts/chaosnet.sh +++ b/contrib/scripts/chaosnet.sh @@ -9,15 +9,21 @@ rm -rf $HOME/.nibid nibid init $CHAIN_ID --chain-id $CHAIN_ID --home $HOME/.nibid --overwrite nibid config keyring-backend test nibid config chain-id $CHAIN_ID -nibid config broadcast-mode block +nibid config broadcast-mode sync nibid config output json sed -i '/\[api\]/,+3 s/enable = false/enable = true/' $HOME/.nibid/config/app.toml -sed -i 's/swagger = false/swagger = true/' $HOME/.nibid/config/app.toml sed -i 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/' $HOME/.nibid/config/app.toml sed -i 's/127.0.0.1/0.0.0.0/' $HOME/.nibid/config/config.toml +sed -i 's/localhost/0.0.0.0/' $HOME/.nibid/config/config.toml +sed -i 's/localhost/0.0.0.0/' $HOME/.nibid/config/app.toml +sed -i 's/localhost/0.0.0.0/' $HOME/.nibid/config/app.toml + echo "$MNEMONIC" | nibid keys add validator --recover nibid genesis add-genesis-account $(nibid keys show validator -a) "10000000000000unibi,10000000000000unusd,10000000000000uusdt,10000000000000uusdc" +nibid genesis add-genesis-account nibi1wx9360p9rvy9m5cdhsua6qpdf9ktvwhjqw949s "10000000000000unibi,10000000000000unusd,10000000000000uusdt,10000000000000uusdc" # faucet +nibid genesis add-genesis-account nibi1g7vzqfthhf4l4vs6skyjj27vqhe97m5gp33hxy "10000000000000unibi" # liquidator +nibid genesis add-genesis-account nibi19n0clnacpjv0d3t8evvzp3fptlup9srjdqunzs "10000000000000unibi" # pricefeeder nibid genesis gentx validator 900000000unibi --chain-id $CHAIN_ID nibid genesis collect-gentxs @@ -57,7 +63,7 @@ add_genesis_perp_markets_with_coingecko_prices() { return 1 fi - nibid add-genesis-perp-market --pair=ubtc:unusd --sqrt-depth=$reserve_amt --price-multiplier=$price_btc + nibid genesis add-genesis-perp-market --pair=ubtc:unusd --sqrt-depth=$reserve_amt --price-multiplier=$price_btc price_eth=$(cat tmp_market_prices.json | jq -r '.ethereum.usd') price_eth=${price_eth%.*} @@ -65,16 +71,14 @@ add_genesis_perp_markets_with_coingecko_prices() { return 1 fi - nibid add-genesis-perp-market --pair=ueth:unusd --sqrt-depth=$reserve_amt --price-multiplier=$price_eth + nibid genesis add-genesis-perp-market --pair=ueth:unusd --sqrt-depth=$reserve_amt --price-multiplier=$price_eth } add_genesis_perp_markets_with_coingecko_prices # x/oracle add_genesis_param '.app_state.oracle.params.twap_lookback_window = "900s"' -add_genesis_param '.app_state.oracle.params.vote_period = "10000"' +add_genesis_param '.app_state.oracle.params.vote_period = "10"' add_genesis_param '.app_state.oracle.params.min_voters = "1"' -add_genesis_param '.app_state.oracle.exchange_rates[0].pair = "ubtc:unusd"' -add_genesis_param '.app_state.oracle.exchange_rates[0].exchange_rate = "20000"' -add_genesis_param '.app_state.oracle.exchange_rates[1].pair = "ueth:unusd"' -add_genesis_param '.app_state.oracle.exchange_rates[1].exchange_rate = "2000"' + +nibid genesis add-genesis-pricefeeder-delegation --validator $(nibid keys show validator -a --bech val) --pricefeeder nibi19n0clnacpjv0d3t8evvzp3fptlup9srjdqunzs \ No newline at end of file diff --git a/contrib/scripts/localnet.sh b/contrib/scripts/localnet.sh index 15e2fc355..62e6aac05 100755 --- a/contrib/scripts/localnet.sh +++ b/contrib/scripts/localnet.sh @@ -233,11 +233,9 @@ fi # set validator as sudoer $BINARY genesis add-sudo-root-account "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl" -# hack for localnet since we don't have a pricefeeder yet -add_genesis_param '.app_state.oracle.exchange_rates[0].pair = "ubtc:unusd"' -add_genesis_param '.app_state.oracle.exchange_rates[0].exchange_rate = "20000"' -add_genesis_param '.app_state.oracle.exchange_rates[1].pair = "ueth:unusd"' -add_genesis_param '.app_state.oracle.exchange_rates[1].exchange_rate = "2000"' +add_genesis_param '.app_state.oracle.params.twap_lookback_window = "900s"' +add_genesis_param '.app_state.oracle.params.vote_period = "10"' +add_genesis_param '.app_state.oracle.params.min_voters = "1"' # Start the network echo_info "Starting $CHAIN_ID in $CHAIN_DIR..." From fb33cb1fd63acef1b530df826eaaa0fee842b494 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jul 2023 19:11:02 +0000 Subject: [PATCH 3/9] chore(deps): Bump google.golang.org/grpc from 1.56.1 to 1.56.2 (#1497) * chore(deps): Bump google.golang.org/grpc from 1.56.1 to 1.56.2 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.56.1 to 1.56.2. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.56.1...v1.56.2) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Updated changelog - dependabot --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Unique-Divine --- CHANGELOG.md | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8911ef15a..2ba0d61de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `github.com/spf13/cast` from 1.5.0 to 1.5.1 (#1358) - Bump `github.com/stretchr/testify` from 1.8.2 to 1.8.4 (#1384, #1435) - Bump `cosmossdk.io/math` from 1.0.0-beta.6 to 1.0.1 (#1394) -- Bump `google.golang.org/grpc` from 1.53.0 to 1.56.1 (#1395, #1437, #1443) +- Bump `google.golang.org/grpc` from 1.53.0 to 1.56.2 (#1395, #1437, #1443, #1497) - Bump `github.com/gin-gonic/gin` from 1.8.1 to 1.9.1 (#1409) - Bump `github.com/spf13/viper` from 1.15.0 to 1.16.0 (#1436) - Bump `github.com/prometheus/client_golang` from 1.15.1 to 1.16.0 (#1431) @@ -612,4 +612,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Testing * [#695](https://github.com/NibiruChain/nibiru/pull/695) Add `OpenPosition` integration tests. -* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods. +* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods. \ No newline at end of file diff --git a/go.mod b/go.mod index 428560898..ae75a26f0 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 - google.golang.org/grpc v1.56.1 + google.golang.org/grpc v1.56.2 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 6140c9c33..676411ae3 100644 --- a/go.sum +++ b/go.sum @@ -1801,8 +1801,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= -google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 27c75034a74e37adfc438c5cbc90588101d875c8 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Sat, 8 Jul 2023 12:00:54 -0400 Subject: [PATCH 4/9] feat(perp): ClosePosition with bad debt (#1493) * refactor: clean code * refactor: clean code * refactor: clean code * refactor: clean code * test(perp): fix RemoveMargin test * fix(perp): allow closing positions with bad debt * test(perp): close position tests * test(perp): close short position * test(perp): close long position with bad debt * chore: remove redundant ClosePosition tests * Update CHANGELOG.md * refactor: clean code * fix(perp): add bad debt check on partial close * refactor: add context to error messages * test(perp): partial close below maintenance margin ratio * test(perp); partial close below mmr * test(perp); partial close with bad debt * test(perp): partial close short position with bad debt * refactor: clean code * fix: use updated AMM when calculating the margin ratio --- CHANGELOG.md | 5 +- x/perp/v2/integration/action/market.go | 4 +- x/perp/v2/integration/action/position.go | 4 +- x/perp/v2/integration/assertion/event.go | 15 +- x/perp/v2/integration/assertion/position.go | 2 +- x/perp/v2/keeper/clearing_house.go | 91 +- x/perp/v2/keeper/clearing_house_test.go | 1087 +++++++++++-------- x/perp/v2/keeper/margin_test.go | 54 +- x/perp/v2/types/errors.go | 2 +- 9 files changed, 735 insertions(+), 529 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba0d61de..6ec9ab96f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,8 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Improvements -* #[1466](https://github.com/NibiruChain/nibiru/pull/1466) - refactor(perp): `PositionLiquidatedEvent` -* #[1494](https://github.com/NibiruChain/nibiru/pull/1494) - feat: create cli to add sudo account into genesis +* [#1466](https://github.com/NibiruChain/nibiru/pull/1466) - refactor(perp): `PositionLiquidatedEvent` +* [#1494](https://github.com/NibiruChain/nibiru/pull/1494) - feat: create cli to add sudo account into genesis +* [#1493](https://github.com/NibiruChain/nibiru/pull/1493) - fix(perp): allow `ClosePosition` when there is bad debt ### Features diff --git a/x/perp/v2/integration/action/market.go b/x/perp/v2/integration/action/market.go index ba058c862..b184004e8 100644 --- a/x/perp/v2/integration/action/market.go +++ b/x/perp/v2/integration/action/market.go @@ -89,9 +89,9 @@ func WithSqrtDepth(amount sdk.Dec) marketModifier { } } -func WithMarketLatestCPF(cpf sdk.Dec) marketModifier { +func WithLatestMarketCPF(amount sdk.Dec) marketModifier { return func(market *types.Market, amm *types.AMM) { - market.LatestCumulativePremiumFraction = cpf + market.LatestCumulativePremiumFraction = amount } } diff --git a/x/perp/v2/integration/action/position.go b/x/perp/v2/integration/action/position.go index e9f615410..65ec26869 100644 --- a/x/perp/v2/integration/action/position.go +++ b/x/perp/v2/integration/action/position.go @@ -353,10 +353,10 @@ func (p partialCloseFails) Do(app *app.NibiruApp, ctx sdk.Context) (sdk.Context, _, err := app.PerpKeeperV2.PartialClose(ctx, p.pair, p.trader, p.amount) if !errors.Is(err, p.expectedErr) { - return ctx, fmt.Errorf("expected error %s, got %s", p.expectedErr, err), true + return ctx, fmt.Errorf("expected error %s, got %s", p.expectedErr, err), false } - return ctx, nil, true + return ctx, nil, false } func PartialCloseFails(trader sdk.AccAddress, pair asset.Pair, amount sdk.Dec, expecedErr error) action.Action { diff --git a/x/perp/v2/integration/assertion/event.go b/x/perp/v2/integration/assertion/event.go index 2afadd8b3..cd465a0f5 100644 --- a/x/perp/v2/integration/assertion/event.go +++ b/x/perp/v2/integration/assertion/event.go @@ -32,9 +32,9 @@ func (act containsLiquidateEvent) Do(_ *app.NibiruApp, ctx sdk.Context) ( outCtx sdk.Context, err error, isMandatory bool, ) { foundEvent := false - events := ctx.EventManager().Events() matchingEvents := []abci.Event{} - for _, sdkEvent := range events { + + for _, sdkEvent := range ctx.EventManager().Events() { if sdkEvent.Type != proto.MessageName(act.expectedEvent) { continue } @@ -74,7 +74,7 @@ func (act containsLiquidateEvent) Do(_ *app.NibiruApp, ctx sdk.Context) ( return ctx, errors.New( strings.Join([]string{ fmt.Sprintf("expected: %+v.", sdk.StringifyEvents([]abci.Event{abci.Event(expected)})), - fmt.Sprintf("found %v events:", len(events)), + fmt.Sprintf("found %v events:", len(ctx.EventManager().Events())), fmt.Sprintf("events of matching type:\n%v", sdk.StringifyEvents(matchingEvents).String()), }, "\n"), ), false @@ -99,10 +99,12 @@ func (p positionChangedEventShouldBeEqual) Do(_ *app.NibiruApp, ctx sdk.Context) if sdkEvent.Type != proto.MessageName(p.expectedEvent) { continue } + abciEvent := abci.Event{ Type: sdkEvent.Type, Attributes: sdkEvent.Attributes, } + typedEvent, err := sdk.ParseTypedEvent(abciEvent) if err != nil { return ctx, err, false @@ -119,16 +121,17 @@ func (p positionChangedEventShouldBeEqual) Do(_ *app.NibiruApp, ctx sdk.Context) if !reflect.DeepEqual(p.expectedEvent, positionChangedEvent) { expected, _ := sdk.TypedEventToEvent(p.expectedEvent) - actual, _ := sdk.TypedEventToEvent(positionChangedEvent) return ctx, fmt.Errorf(`expected event is not equal to the actual event. want: %+v got: -%+v`, sdk.StringifyEvents([]abci.Event{abci.Event(expected)}), sdk.StringifyEvents([]abci.Event{abci.Event(actual)})), false +%+v`, sdk.StringifyEvents([]abci.Event{abci.Event(expected)}), sdk.StringifyEvents([]abci.Event{abciEvent})), false } + + return ctx, nil, false } - return ctx, nil, false + return ctx, fmt.Errorf("unable to find desired event of type %s", proto.MessageName(p.expectedEvent)), false } // PositionChangedEventShouldBeEqual checks that the position changed event is diff --git a/x/perp/v2/integration/assertion/position.go b/x/perp/v2/integration/assertion/position.go index eab6e7f5f..b969681e8 100644 --- a/x/perp/v2/integration/assertion/position.go +++ b/x/perp/v2/integration/assertion/position.go @@ -77,7 +77,7 @@ type positionShouldNotExist struct { func (p positionShouldNotExist) Do(app *app.NibiruApp, ctx sdk.Context) (sdk.Context, error, bool) { _, err := app.PerpKeeperV2.Positions.Get(ctx, collections.Join(p.Pair, p.Account)) if err == nil { - return ctx, fmt.Errorf("position should not exist"), false + return ctx, fmt.Errorf("position should not exist, but it does with pair %s", p.Pair), false } return ctx, nil, false diff --git a/x/perp/v2/keeper/clearing_house.go b/x/perp/v2/keeper/clearing_house.go index f5faf9493..ab668931b 100644 --- a/x/perp/v2/keeper/clearing_house.go +++ b/x/perp/v2/keeper/clearing_house.go @@ -38,16 +38,16 @@ func (k Keeper) MarketOrder( ) (positionResp *types.PositionResp, err error) { market, err := k.Markets.Get(ctx, pair) if err != nil { - return nil, fmt.Errorf("%w: %s", types.ErrPairNotFound, pair) + return nil, types.ErrPairNotFound.Wrapf("pair %s not found", pair) } if !market.Enabled { - return nil, fmt.Errorf("%w: %s", types.ErrMarketNotEnabled, pair) + return nil, types.ErrMarketNotEnabled.Wrapf("market pair %s not enabled", pair) } amm, err := k.AMMs.Get(ctx, pair) if err != nil { - return nil, fmt.Errorf("%w: %s", types.ErrPairNotFound, pair) + return nil, types.ErrPairNotFound.Wrapf("pair %s not found", pair) } err = checkMarketOrderRequirements(market, quoteAssetAmt, leverage) @@ -97,6 +97,18 @@ func (k Keeper) MarketOrder( } } + // check bad debt + if !positionResp.BadDebt.IsZero() { + return nil, types.ErrBadDebt.Wrapf("bad debt %s", positionResp.BadDebt) + } + + if !positionResp.Position.Size_.IsZero() { + err = k.checkMarginRatio(ctx, market, *updatedAMM, positionResp.Position) + if err != nil { + return nil, err + } + } + if err = k.afterPositionUpdate( ctx, market, *updatedAMM, traderAddr, *positionResp, types.ChangeReason_MarketOrder, ); err != nil { @@ -144,10 +156,20 @@ func (k Keeper) increasePosition( baseAmtLimit sdk.Dec, // unsigned leverage sdk.Dec, // unsigned ) (updatedAMM *types.AMM, positionResp *types.PositionResp, err error) { - positionResp = &types.PositionResp{} - marginIncrease := increasedNotional.Quo(leverage) - fundingPayment := FundingPayment(currentPosition, market.LatestCumulativePremiumFraction) // signed - remainingMargin := currentPosition.Margin.Add(marginIncrease).Sub(fundingPayment) // signed + positionNotional, err := PositionNotionalSpot(amm, currentPosition) + if err != nil { + return nil, nil, err + } + + positionResp = &types.PositionResp{ + RealizedPnl: sdk.ZeroDec(), + MarginToVault: increasedNotional.Quo(leverage), // unsigned + FundingPayment: FundingPayment(currentPosition, market.LatestCumulativePremiumFraction), // signed + ExchangedNotionalValue: increasedNotional, // unsigned + PositionNotional: positionNotional.Add(increasedNotional), // unsigned + } + + remainingMargin := currentPosition.Margin.Add(positionResp.MarginToVault).Sub(positionResp.FundingPayment) // signed updatedAMM, baseAssetDeltaAbs, err := k.SwapQuoteAsset( ctx, @@ -167,16 +189,6 @@ func (k Keeper) increasePosition( positionResp.ExchangedPositionSize = baseAssetDeltaAbs.Neg() } - positionNotional, err := PositionNotionalSpot(amm, currentPosition) - if err != nil { - return nil, nil, err - } - - positionResp.ExchangedNotionalValue = increasedNotional - positionResp.PositionNotional = positionNotional.Add(increasedNotional) - positionResp.RealizedPnl = sdk.ZeroDec() - positionResp.MarginToVault = marginIncrease - positionResp.FundingPayment = fundingPayment positionResp.BadDebt = sdk.MinDec(sdk.ZeroDec(), remainingMargin).Abs() positionResp.Position = types.Position{ TraderAddress: currentPosition.TraderAddress, @@ -539,18 +551,6 @@ func (k Keeper) afterPositionUpdate( positionResp types.PositionResp, changeType types.ChangeReason, ) (err error) { - // check bad debt - if !positionResp.BadDebt.IsZero() { - return fmt.Errorf("bad debt must be zero to prevent attacker from leveraging it") - } - - if !positionResp.Position.Size_.IsZero() { - err = k.checkMarginRatio(ctx, market, amm, positionResp.Position) - if err != nil { - return err - } - } - // transfer trader <=> vault marginToVault := positionResp.MarginToVault.RoundInt() switch { @@ -566,7 +566,9 @@ func (k Keeper) afterPositionUpdate( } } - transferredFee, err := k.transferFee(ctx, market.Pair, traderAddr, positionResp.ExchangedNotionalValue) + transferredFee, err := k.transferFee(ctx, market.Pair, traderAddr, positionResp.ExchangedNotionalValue, + market.ExchangeFeeRatio, market.EcosystemFundFeeRatio, + ) if err != nil { return err } @@ -642,13 +644,10 @@ func (k Keeper) transferFee( pair asset.Pair, trader sdk.AccAddress, positionNotional sdk.Dec, + exchangeFeeRatio sdk.Dec, + ecosystemFundFeeRatio sdk.Dec, ) (fees sdkmath.Int, err error) { - m, err := k.Markets.Get(ctx, pair) - if err != nil { - return sdkmath.Int{}, err - } - - feeToExchangeFeePool := m.ExchangeFeeRatio.Mul(positionNotional).RoundInt() + feeToExchangeFeePool := exchangeFeeRatio.Mul(positionNotional).RoundInt() if feeToExchangeFeePool.IsPositive() { if err = k.BankKeeper.SendCoinsFromAccountToModule( ctx, @@ -665,7 +664,7 @@ func (k Keeper) transferFee( } } - feeToEcosystemFund := m.EcosystemFundFeeRatio.Mul(positionNotional).RoundInt() + feeToEcosystemFund := ecosystemFundFeeRatio.Mul(positionNotional).RoundInt() if feeToEcosystemFund.IsPositive() { if err = k.BankKeeper.SendCoinsFromAccountToModule( ctx, @@ -731,8 +730,6 @@ func (k Keeper) ClosePosition(ctx sdk.Context, pair asset.Pair, traderAddr sdk.A ); err != nil { return nil, err } - - return positionResp, nil } if err = k.afterPositionUpdate( @@ -843,16 +840,16 @@ func (k Keeper) PartialClose( ctx sdk.Context, pair asset.Pair, traderAddr sdk.AccAddress, - sizeAmt sdk.Dec, + sizeAmt sdk.Dec, //unsigned ) (*types.PositionResp, error) { market, err := k.Markets.Get(ctx, pair) if err != nil { - return nil, fmt.Errorf("%w: %s", types.ErrPairNotFound, pair) + return nil, types.ErrPairNotFound.Wrapf("pair: %s", pair) } amm, err := k.AMMs.Get(ctx, pair) if err != nil { - return nil, fmt.Errorf("%w: %s", types.ErrPairNotFound, pair) + return nil, types.ErrPairNotFound.Wrapf("pair: %s", pair) } position, err := k.Positions.Get(ctx, collections.Join(pair, traderAddr)) @@ -886,6 +883,16 @@ func (k Keeper) PartialClose( return nil, err } + if positionResp.BadDebt.IsPositive() { + if err = k.realizeBadDebt( + ctx, + market, + positionResp.BadDebt.RoundInt(), + ); err != nil { + return nil, err + } + } + err = k.afterPositionUpdate( ctx, market, diff --git a/x/perp/v2/keeper/clearing_house_test.go b/x/perp/v2/keeper/clearing_house_test.go index c0ad44756..dfebfa0f1 100644 --- a/x/perp/v2/keeper/clearing_house_test.go +++ b/x/perp/v2/keeper/clearing_house_test.go @@ -5,20 +5,16 @@ import ( "time" sdkmath "cosmossdk.io/math" - - "github.com/NibiruChain/nibiru/x/common/testutil/assertion" - + "github.com/NibiruChain/collections" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/NibiruChain/collections" - "github.com/NibiruChain/nibiru/x/common/asset" "github.com/NibiruChain/nibiru/x/common/denoms" "github.com/NibiruChain/nibiru/x/common/testutil" . "github.com/NibiruChain/nibiru/x/common/testutil/action" + . "github.com/NibiruChain/nibiru/x/common/testutil/assertion" "github.com/NibiruChain/nibiru/x/common/testutil/mock" "github.com/NibiruChain/nibiru/x/common/testutil/testapp" . "github.com/NibiruChain/nibiru/x/oracle/integration/action" @@ -30,23 +26,23 @@ import ( func TestMarketOrder(t *testing.T) { alice := testutil.AccAddress() - pairBtcUsdc := asset.Registry.Pair(denoms.BTC, denoms.NUSD) + pairBtcNusd := asset.Registry.Pair(denoms.BTC, denoms.NUSD) startBlockTime := time.Now() tc := TestCases{ TC("new long position"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1020)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(10_000), @@ -65,8 +61,8 @@ func TestMarketOrder(t *testing.T) { ), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(10_000), @@ -76,7 +72,7 @@ func TestMarketOrder(t *testing.T) { })), PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(10_000), @@ -100,17 +96,17 @@ func TestMarketOrder(t *testing.T) { Given( SetBlockNumber(1), SetBlockTime(startBlockTime), - CreateCustomMarket(pairBtcUsdc), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + CreateCustomMarket(pairBtcNusd), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(2040)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). When( MoveToNextBlock(), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20_000), @@ -131,7 +127,7 @@ func TestMarketOrder(t *testing.T) { Then( PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20_000), @@ -153,19 +149,19 @@ func TestMarketOrder(t *testing.T) { TC("existing long position, decrease a bit"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1030)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). When( MoveToNextBlock(), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(500), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(500), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(5000), @@ -187,7 +183,7 @@ func TestMarketOrder(t *testing.T) { Then( PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(5000), @@ -209,19 +205,19 @@ func TestMarketOrder(t *testing.T) { TC("existing long position, decrease a lot"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(4080)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). When( MoveToNextBlock(), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(3000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(3000), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20000), @@ -243,7 +239,7 @@ func TestMarketOrder(t *testing.T) { Then( PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20000), @@ -265,17 +261,17 @@ func TestMarketOrder(t *testing.T) { TC("new short position"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1020)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(10_000), @@ -294,9 +290,9 @@ func TestMarketOrder(t *testing.T) { ), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(10_000), @@ -307,7 +303,7 @@ func TestMarketOrder(t *testing.T) { ), PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(10_000), @@ -329,19 +325,19 @@ func TestMarketOrder(t *testing.T) { TC("existing short position, go more short"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(2040)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). When( MoveToNextBlock(), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20_000), @@ -363,7 +359,7 @@ func TestMarketOrder(t *testing.T) { Then( PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20_000), @@ -385,19 +381,19 @@ func TestMarketOrder(t *testing.T) { TC("existing short position, decrease a bit"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1030)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). When( MoveToNextBlock(), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(500), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(500), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(5000), @@ -419,7 +415,7 @@ func TestMarketOrder(t *testing.T) { Then( PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(1000), OpenNotional: sdk.NewDec(5000), @@ -441,19 +437,19 @@ func TestMarketOrder(t *testing.T) { TC("existing short position, decrease a lot"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(4080)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). When( MoveToNextBlock(), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(3000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(3000), sdk.NewDec(10), sdk.ZeroDec(), MarketOrderResp_PositionShouldBeEqual( types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20000), @@ -475,7 +471,7 @@ func TestMarketOrder(t *testing.T) { Then( PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.NewDec(2000), OpenNotional: sdk.NewDec(20000), @@ -497,215 +493,215 @@ func TestMarketOrder(t *testing.T) { TC("user has insufficient funds"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(99)))), ). When( MarketOrderFails( - alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(100), sdk.OneDec(), sdk.ZeroDec(), + alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(100), sdk.OneDec(), sdk.ZeroDec(), sdkerrors.ErrInsufficientFunds), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("new long position, can close position after market is not enabled"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec()), - SetMarketEnabled(pairBtcUsdc, false), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec()), + SetMarketEnabled(pairBtcNusd, false), ). When( - ClosePosition(alice, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("new long position, can not open new position after market is not enabled"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), - SetMarketEnabled(pairBtcUsdc, false), + SetMarketEnabled(pairBtcNusd, false), ). When( - MarketOrderFails(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec(), types.ErrMarketNotEnabled), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("existing long position, can not open new one but can close"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(50_000), sdk.OneDec(), sdk.ZeroDec()), - SetMarketEnabled(pairBtcUsdc, false), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(50_000), sdk.OneDec(), sdk.ZeroDec()), + SetMarketEnabled(pairBtcNusd, false), ). When( - MarketOrderFails(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec(), types.ErrMarketNotEnabled), - ClosePosition(alice, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("market doesn't exist"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), ). When( - MarketOrderFails(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec(), types.ErrPairNotFound), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("zero quote asset amount"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + CreateCustomMarket(pairBtcNusd), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), ). When( - MarketOrderFails(alice, pairBtcUsdc, types.Direction_LONG, sdk.ZeroInt(), sdk.OneDec(), sdk.ZeroDec(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_LONG, sdk.ZeroInt(), sdk.OneDec(), sdk.ZeroDec(), types.ErrInputQuoteAmtNegative), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("zero leverage"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + CreateCustomMarket(pairBtcNusd), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( - MarketOrderFails(alice, pairBtcUsdc, types.Direction_LONG, sdk.OneInt(), sdk.ZeroDec(), sdk.ZeroDec(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_LONG, sdk.OneInt(), sdk.ZeroDec(), sdk.ZeroDec(), types.ErrUserLeverageNegative), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("user leverage greater than market max leverage"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + CreateCustomMarket(pairBtcNusd), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( - MarketOrderFails(alice, pairBtcUsdc, types.Direction_LONG, sdk.OneInt(), sdk.NewDec(11), sdk.ZeroDec(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_LONG, sdk.OneInt(), sdk.NewDec(11), sdk.ZeroDec(), types.ErrLeverageIsTooHigh), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("position should not exist after opening a closing manually"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(25_000)), + CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), + SetOraclePrice(pairBtcNusd, sdk.NewDec(25_000)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20_000_000_000+20_000_000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.OneDec(), sdk.ZeroDec()), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.OneDec(), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.OneDec(), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.OneDec(), sdk.ZeroDec()), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("position should not exist after opening a closing manually - reverse with leverage"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("position should not exist after opening a closing manually - open with leverage"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("position should not exist after opening a closing manually - reverse with leverage"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("position should not exist after opening a closing manually - reverse with leverage - more steps"). Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - CreateCustomMarket(pairBtcUsdc, WithPricePeg(sdk.MustNewDecFromStr("25000"))), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(2)), + CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25000"))), + SetOraclePrice(pairBtcNusd, sdk.NewDec(2)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(4), sdk.ZeroDec()), - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(100_000), sdk.OneDec(), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(50_000), sdk.NewDec(4), sdk.ZeroDec()), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(50_000), sdk.NewDec(2), sdk.ZeroDec()), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), } @@ -845,219 +841,24 @@ func TestMarketOrderError(t *testing.T) { } } -func TestClosePosition(t *testing.T) { - tests := []struct { - name string - - initialPosition types.Position - newPriceMultiplier sdk.Dec - newLatestCPF sdk.Dec - - expectedFundingPayment sdk.Dec - expectedBadDebt sdk.Dec - expectedRealizedPnl sdk.Dec - expectedMarginToVault sdk.Dec - expectedExchangedNotionalValue sdk.Dec - }{ - { - name: "long position, positive PnL", - // user bought in at 100 BTC for 10 NUSD at 10x leverage (1 BTC = 1 NUSD) - // notional value is 100 NUSD - // BTC doubles in value, now its price is 1 BTC = 2 NUSD - // user has position notional value of 200 NUSD and unrealized PnL of +100 NUSD - // user closes position - // user ends up with realized PnL of +100 NUSD, unrealized PnL after of 0 NUSD, - // position notional value of 0 NUSD - initialPosition: types.Position{ - TraderAddress: testutil.AccAddress().String(), - Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), - Size_: sdk.NewDec(100), // 100 BTC - Margin: sdk.NewDec(10), // 10 NUSD - OpenNotional: sdk.NewDec(100), // 100 NUSD - LatestCumulativePremiumFraction: sdk.ZeroDec(), - LastUpdatedBlockNumber: 0, - }, - newPriceMultiplier: sdk.NewDec(2), - newLatestCPF: sdk.MustNewDecFromStr("0.02"), - - expectedExchangedNotionalValue: sdk.MustNewDecFromStr("199.999999980000000002"), - expectedBadDebt: sdk.ZeroDec(), - expectedFundingPayment: sdk.NewDec(2), - expectedRealizedPnl: sdk.MustNewDecFromStr("99.999999980000000002"), - expectedMarginToVault: sdk.MustNewDecFromStr("-107.999999980000000002"), - }, - { - name: "close long position, negative PnL", - // user bought in at 100 BTC for 10 NUSD at 10x leverage (1 BTC = 1 NUSD) - // position and open notional value is 100 NUSD - // BTC drops in value, now its price is 1 BTC = 0.95 NUSD - // user has position notional value of 195 NUSD and unrealized PnL of -5 NUSD - // user closes position - // user ends up with realized PnL of -5 NUSD, unrealized PnL of 0 NUSD, - // position notional value of 0 NUSD - initialPosition: types.Position{ - TraderAddress: testutil.AccAddress().String(), - Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), - Size_: sdk.NewDec(100), - Margin: sdk.NewDec(10), - OpenNotional: sdk.NewDec(100), - LatestCumulativePremiumFraction: sdk.ZeroDec(), - LastUpdatedBlockNumber: 0, - }, - newPriceMultiplier: sdk.MustNewDecFromStr("0.95"), - newLatestCPF: sdk.MustNewDecFromStr("0.02"), - - expectedBadDebt: sdk.ZeroDec(), - expectedFundingPayment: sdk.NewDec(2), - expectedRealizedPnl: sdk.MustNewDecFromStr("-5.000000009499999999"), - expectedMarginToVault: sdk.MustNewDecFromStr("-2.999999990500000001"), // 10(old) + (-5)(realized PnL) - (2)(funding payment) - expectedExchangedNotionalValue: sdk.MustNewDecFromStr("94.999999990500000001"), - }, - - /*==========================SHORT POSITIONS===========================*/ - { - name: "close short position, positive PnL", - // user bought in at 100 BTC for 10 NUSD at 10x leverage (1 BTC = 1 NUSD) - // position and open notional value is 100 NUSD - // BTC drops in value, now its price is 1 BTC = 0.95 NUSD - // user has position notional value of 95 NUSD and unrealized PnL of 5 NUSD - // user closes position - // user ends up with realized PnL of 5 NUSD, unrealized PnL of 0 NUSD, - // position notional value of 0 NUSD - initialPosition: types.Position{ - TraderAddress: testutil.AccAddress().String(), - Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), - Size_: sdk.NewDec(-100), - Margin: sdk.NewDec(10), - OpenNotional: sdk.NewDec(100), - LatestCumulativePremiumFraction: sdk.ZeroDec(), - LastUpdatedBlockNumber: 0, - }, - newPriceMultiplier: sdk.MustNewDecFromStr("0.95"), - newLatestCPF: sdk.MustNewDecFromStr("0.02"), - - expectedBadDebt: sdk.ZeroDec(), - expectedFundingPayment: sdk.NewDec(-2), - expectedRealizedPnl: sdk.MustNewDecFromStr("4.999999990499999999"), - expectedMarginToVault: sdk.MustNewDecFromStr("-16.999999990499999999"), // old(10) + (5)(realizedPnL) - (-2)(fundingPayment) - expectedExchangedNotionalValue: sdk.MustNewDecFromStr("95.000000009500000001"), - }, - { - name: "decrease short position, negative PnL", - // user bought in at 100 BTC for 10 NUSD at 10x leverage (1 BTC = 1 NUSD) - // position and open notional value is 100 NUSD - // BTC increases in value, now its price is 1 BTC = 1.05 NUSD - // user has position notional value of 105 NUSD and unrealized PnL of -5 NUSD - // user closes their position - // user ends up with realized PnL of -5 NUSD, unrealized PnL of 0 NUSD - // position notional value of 0 NUSD - initialPosition: types.Position{ - TraderAddress: testutil.AccAddress().String(), - Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), - Size_: sdk.NewDec(-100), // -100 BTC - Margin: sdk.NewDec(10), // 10 NUSD - OpenNotional: sdk.NewDec(100), // 100 NUSD - LatestCumulativePremiumFraction: sdk.ZeroDec(), - LastUpdatedBlockNumber: 0, - }, - newPriceMultiplier: sdk.MustNewDecFromStr("1.05"), - newLatestCPF: sdk.MustNewDecFromStr("0.02"), - - expectedBadDebt: sdk.ZeroDec(), - expectedFundingPayment: sdk.NewDec(-2), - expectedRealizedPnl: sdk.MustNewDecFromStr("-5.000000010500000001"), - expectedMarginToVault: sdk.MustNewDecFromStr("-6.999999989499999999"), // old(10) + (-5)(realizedPnL) - (-2)(fundingPayment) - expectedExchangedNotionalValue: sdk.MustNewDecFromStr("105.000000010500000001"), - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - app, ctx := testapp.NewNibiruTestAppAndContext(true) - traderAddr := sdk.MustAccAddressFromBech32(tc.initialPosition.TraderAddress) - - market := mock.TestMarket().WithLatestCumulativePremiumFraction(tc.newLatestCPF) - amm := mock.TestAMMDefault().WithPriceMultiplier(tc.newPriceMultiplier) - app.PerpKeeperV2.Markets.Insert(ctx, tc.initialPosition.Pair, *market) - app.PerpKeeperV2.AMMs.Insert(ctx, tc.initialPosition.Pair, *amm) - app.PerpKeeperV2.ReserveSnapshots.Insert(ctx, collections.Join(tc.initialPosition.Pair, ctx.BlockTime()), types.ReserveSnapshot{ - Amm: *amm, - TimestampMs: ctx.BlockTime().UnixMilli(), - }) - app.PerpKeeperV2.Positions.Insert(ctx, collections.Join(tc.initialPosition.Pair, traderAddr), tc.initialPosition) - require.NoError(t, testapp.FundModuleAccount(app.BankKeeper, ctx, types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 1e18)))) - require.NoError(t, testapp.FundModuleAccount(app.BankKeeper, ctx, types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 1e18)))) - - resp, err := app.PerpKeeperV2.ClosePosition( - ctx, - tc.initialPosition.Pair, - traderAddr, - ) - - require.NoError(t, err) - assert.Equal(t, types.PositionResp{ - Position: types.Position{ - TraderAddress: tc.initialPosition.TraderAddress, - Pair: tc.initialPosition.Pair, - Size_: sdk.ZeroDec(), - Margin: sdk.ZeroDec(), - OpenNotional: sdk.ZeroDec(), - LatestCumulativePremiumFraction: tc.newLatestCPF, - LastUpdatedBlockNumber: ctx.BlockHeight(), - }, - ExchangedNotionalValue: tc.expectedExchangedNotionalValue, - ExchangedPositionSize: tc.initialPosition.Size_.Neg(), - BadDebt: tc.expectedBadDebt, - FundingPayment: tc.expectedFundingPayment, - RealizedPnl: tc.expectedRealizedPnl, - UnrealizedPnlAfter: sdk.ZeroDec(), - MarginToVault: tc.expectedMarginToVault, - PositionNotional: sdk.ZeroDec(), - }, *resp) - - testutil.RequireHasTypedEvent(t, ctx, &types.PositionChangedEvent{ - FinalPosition: types.Position{ - TraderAddress: tc.initialPosition.TraderAddress, - Pair: tc.initialPosition.Pair, - Size_: sdk.ZeroDec(), - Margin: sdk.ZeroDec(), - OpenNotional: sdk.ZeroDec(), - LatestCumulativePremiumFraction: tc.newLatestCPF, - LastUpdatedBlockNumber: ctx.BlockHeight(), - }, - PositionNotional: sdk.ZeroDec(), - RealizedPnl: tc.expectedRealizedPnl, - BadDebt: sdk.NewCoin(denoms.NUSD, sdk.ZeroInt()), - FundingPayment: tc.expectedFundingPayment, - TransactionFee: sdk.NewInt64Coin(denoms.NUSD, 0), - BlockHeight: ctx.BlockHeight(), - // exchangedMargin = - marginToVault - transferredFee - MarginToUser: tc.expectedMarginToVault.RoundInt().Neg().SubRaw(0), - ChangeReason: types.ChangeReason_ClosePosition, - }) - }) - } -} - func TestPartialClose(t *testing.T) { alice := testutil.AccAddress() - pairBtcUsdc := asset.Registry.Pair(denoms.BTC, denoms.NUSD) + pairBtcNusd := asset.Registry.Pair(denoms.BTC, denoms.NUSD) startBlockTime := time.Now() tc := TestCases{ TC("partial close long position with positive PnL"). Given( CreateCustomMarket( - pairBtcUsdc, + pairBtcNusd, WithPricePeg(sdk.NewDec(2)), - WithMarketLatestCPF(sdk.MustNewDecFromStr("0.0002")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), ), SetBlockTime(startBlockTime), SetBlockNumber(1), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10)))), InsertPosition( - WithPair(pairBtcUsdc), + WithPair(pairBtcNusd), WithTrader(alice), WithSize(sdk.NewDec(10_000)), WithMargin(sdk.NewDec(1_000)), @@ -1065,11 +866,11 @@ func TestPartialClose(t *testing.T) { ), ). When( - PartialClose(alice, pairBtcUsdc, sdk.NewDec(2_500)), + PartialClose(alice, pairBtcNusd, sdk.NewDec(2_500)), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("3497.999950000000500000"), OpenNotional: sdk.MustNewDecFromStr("7499.999962500000468750"), @@ -1079,7 +880,7 @@ func TestPartialClose(t *testing.T) { })), PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("3497.999950000000500000"), OpenNotional: sdk.MustNewDecFromStr("7499.999962500000468750"), @@ -1097,18 +898,19 @@ func TestPartialClose(t *testing.T) { ChangeReason: types.ChangeReason_PartialClose, }), ), + TC("partial close long position with negative PnL"). Given( CreateCustomMarket( - pairBtcUsdc, + pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("0.95")), - WithMarketLatestCPF(sdk.MustNewDecFromStr("0.0002")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), ), SetBlockTime(startBlockTime), SetBlockNumber(1), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(4)))), InsertPosition( - WithPair(pairBtcUsdc), + WithPair(pairBtcNusd), WithTrader(alice), WithSize(sdk.NewDec(10_000)), WithMargin(sdk.NewDec(1_000)), @@ -1116,11 +918,11 @@ func TestPartialClose(t *testing.T) { ), ). When( - PartialClose(alice, pairBtcUsdc, sdk.NewDec(2_500)), + PartialClose(alice, pairBtcNusd, sdk.NewDec(2_500)), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("872.999976250000237500"), OpenNotional: sdk.MustNewDecFromStr("7499.999982187500222656"), @@ -1130,7 +932,7 @@ func TestPartialClose(t *testing.T) { })), PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("872.999976250000237500"), OpenNotional: sdk.MustNewDecFromStr("7499.999982187500222656"), @@ -1148,18 +950,71 @@ func TestPartialClose(t *testing.T) { ChangeReason: types.ChangeReason_PartialClose, }), ), + + TC("partial close long position without bad debt but below maintenance margin ratio"). + Given( + CreateCustomMarket( + pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.94")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockTime(startBlockTime), + SetBlockNumber(1), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(4)))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithSize(sdk.NewDec(10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + PartialClose(alice, pairBtcNusd, sdk.NewDec(2_500)), + ). + Then( + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.MustNewDecFromStr("847.999976500000235000"), + OpenNotional: sdk.MustNewDecFromStr("7499.999982375000220312"), + Size_: sdk.MustNewDecFromStr("7499.999999999999999999"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + })), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.MustNewDecFromStr("847.999976500000235000"), + OpenNotional: sdk.MustNewDecFromStr("7499.999982375000220312"), + Size_: sdk.MustNewDecFromStr("7499.999999999999999999"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.MustNewDecFromStr("7049.999911875000925312"), + RealizedPnl: sdk.MustNewDecFromStr("-150.000023499999765000"), + BadDebt: sdk.NewCoin(denoms.NUSD, sdk.ZeroInt()), + FundingPayment: sdk.NewDec(2), + TransactionFee: sdk.NewCoin(denoms.NUSD, sdk.NewInt(4)), + BlockHeight: 1, + MarginToUser: sdk.NewInt(-4), + ChangeReason: types.ChangeReason_PartialClose, + })), + TC("partial close long position with bad debt"). Given( CreateCustomMarket( - pairBtcUsdc, - WithPricePeg(sdk.MustNewDecFromStr("0.9")), - WithMarketLatestCPF(sdk.MustNewDecFromStr("0.0002")), + pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.59")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), ), SetBlockTime(startBlockTime), SetBlockNumber(1), - FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(4020)))), + FundAccount(alice, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 2))), + FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 27))), InsertPosition( - WithPair(pairBtcUsdc), + WithPair(pairBtcNusd), WithTrader(alice), WithSize(sdk.NewDec(10_000)), WithMargin(sdk.NewDec(1_000)), @@ -1167,31 +1022,50 @@ func TestPartialClose(t *testing.T) { ), ). When( - PartialCloseFails(alice, pairBtcUsdc, sdk.NewDec(2_500), types.ErrMarginRatioTooLow), + PartialClose(alice, pairBtcNusd, sdk.NewDec(2_500)), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, TraderAddress: alice.String(), - Margin: sdk.NewDec(1_000), - OpenNotional: sdk.NewDec(10_000), - Size_: sdk.NewDec(10_000), - LastUpdatedBlockNumber: 0, - LatestCumulativePremiumFraction: sdk.ZeroDec(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.MustNewDecFromStr("7499.999988937500138281"), + Size_: sdk.MustNewDecFromStr("7500.000000000000000000"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), })), - ), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.MustNewDecFromStr("7499.999988937500138281"), + Size_: sdk.MustNewDecFromStr("7500.000000000000000000"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.MustNewDecFromStr("4424.999944687500580781"), + RealizedPnl: sdk.MustNewDecFromStr("-1025.000014749999852500"), + BadDebt: sdk.NewInt64Coin(denoms.NUSD, 27), + FundingPayment: sdk.NewDec(2), + TransactionFee: sdk.NewInt64Coin(denoms.NUSD, 2), + BlockHeight: 1, + MarginToUser: sdk.NewInt(-2), + ChangeReason: types.ChangeReason_PartialClose, + })), + TC("partial close short position with positive PnL"). Given( CreateCustomMarket( - pairBtcUsdc, + pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("0.10")), - WithMarketLatestCPF(sdk.MustNewDecFromStr("0.0002")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), ), SetBlockTime(startBlockTime), SetBlockNumber(1), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(2)))), InsertPosition( - WithPair(pairBtcUsdc), + WithPair(pairBtcNusd), WithTrader(alice), WithSize(sdk.NewDec(-10_000)), WithMargin(sdk.NewDec(1_000)), @@ -1199,11 +1073,11 @@ func TestPartialClose(t *testing.T) { ), ). When( - PartialClose(alice, pairBtcUsdc, sdk.NewDec(7_500)), + PartialClose(alice, pairBtcNusd, sdk.NewDec(7_500)), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("7751.999992499999925000"), OpenNotional: sdk.MustNewDecFromStr("2500.000001875000032812"), @@ -1213,7 +1087,7 @@ func TestPartialClose(t *testing.T) { })), PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("7751.999992499999925000"), OpenNotional: sdk.MustNewDecFromStr("2500.000001875000032812"), @@ -1231,18 +1105,19 @@ func TestPartialClose(t *testing.T) { ChangeReason: types.ChangeReason_PartialClose, }), ), + TC("partial close short position with negative PnL"). Given( CreateCustomMarket( - pairBtcUsdc, + pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("1.05")), - WithMarketLatestCPF(sdk.MustNewDecFromStr("0.0002")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), ), SetBlockTime(startBlockTime), SetBlockNumber(1), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(16)))), InsertPosition( - WithPair(pairBtcUsdc), + WithPair(pairBtcNusd), WithTrader(alice), WithSize(sdk.NewDec(-10_000)), WithMargin(sdk.NewDec(1_000)), @@ -1250,11 +1125,11 @@ func TestPartialClose(t *testing.T) { ), ). When( - PartialClose(alice, pairBtcUsdc, sdk.NewDec(7_500)), + PartialClose(alice, pairBtcNusd, sdk.NewDec(7_500)), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("626.999921249999212500"), OpenNotional: sdk.MustNewDecFromStr("2500.000019687500344531"), @@ -1264,7 +1139,7 @@ func TestPartialClose(t *testing.T) { })), PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ FinalPosition: types.Position{ - Pair: pairBtcUsdc, + Pair: pairBtcNusd, TraderAddress: alice.String(), Margin: sdk.MustNewDecFromStr("626.999921249999212500"), OpenNotional: sdk.MustNewDecFromStr("2500.000019687500344531"), @@ -1282,18 +1157,19 @@ func TestPartialClose(t *testing.T) { ChangeReason: types.ChangeReason_PartialClose, }), ), - TC("partial close short position with bad debt"). + + TC("partial close short position with no bad debt but below maintenance margin ratio"). Given( CreateCustomMarket( - pairBtcUsdc, - WithPricePeg(sdk.MustNewDecFromStr("1.10")), - WithMarketLatestCPF(sdk.MustNewDecFromStr("0.0002")), + pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("1.09")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), ), SetBlockTime(startBlockTime), SetBlockNumber(1), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(16)))), InsertPosition( - WithPair(pairBtcUsdc), + WithPair(pairBtcNusd), WithTrader(alice), WithSize(sdk.NewDec(-10_000)), WithMargin(sdk.NewDec(1_000)), @@ -1301,55 +1177,372 @@ func TestPartialClose(t *testing.T) { ), ). When( - PartialCloseFails(alice, pairBtcUsdc, sdk.NewDec(7_500), types.ErrMarginRatioTooLow), + PartialClose(alice, pairBtcNusd, sdk.NewDec(7_500)), ). Then( - PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ - Pair: pairBtcUsdc, + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, TraderAddress: alice.String(), - Margin: sdk.NewDec(1_000), - OpenNotional: sdk.NewDec(10_000), - Size_: sdk.NewDec(-10_000), - LastUpdatedBlockNumber: 0, - LatestCumulativePremiumFraction: sdk.ZeroDec(), + Margin: sdk.MustNewDecFromStr("326.999918249999182500"), + OpenNotional: sdk.MustNewDecFromStr("2500.000020437500357656"), + Size_: sdk.MustNewDecFromStr("-2500.000000000000000000"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), })), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.MustNewDecFromStr("326.999918249999182500"), + OpenNotional: sdk.MustNewDecFromStr("2500.000020437500357656"), + Size_: sdk.MustNewDecFromStr("-2500.000000000000000000"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.MustNewDecFromStr("2725.000047687500630156"), + RealizedPnl: sdk.MustNewDecFromStr("-675.000081750000817500"), + BadDebt: sdk.NewCoin(denoms.NUSD, sdk.ZeroInt()), + FundingPayment: sdk.NewDec(-2), + TransactionFee: sdk.NewCoin(denoms.NUSD, sdk.NewInt(16)), + BlockHeight: 1, + MarginToUser: sdk.NewInt(-16), + ChangeReason: types.ChangeReason_PartialClose, + }), + ), + + TC("partial close short position with bad debt"). + Given( + CreateCustomMarket( + pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("1.14")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockTime(startBlockTime), + SetBlockNumber(1), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(18)))), + FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 48))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithSize(sdk.NewDec(-10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + PartialClose(alice, pairBtcNusd, sdk.NewDec(7_500)), + ). + Then( + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.MustNewDecFromStr("2500.000021375000374062"), + Size_: sdk.MustNewDecFromStr("-2500.000000000000000000"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + })), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.MustNewDecFromStr("2500.000021375000374062"), + Size_: sdk.MustNewDecFromStr("-2500.000000000000000000"), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.MustNewDecFromStr("2850.000049875000659062"), + RealizedPnl: sdk.MustNewDecFromStr("-1050.000085500000855000"), + BadDebt: sdk.NewInt64Coin(denoms.NUSD, 48), + FundingPayment: sdk.NewDec(-2), + TransactionFee: sdk.NewCoin(denoms.NUSD, sdk.NewInt(18)), + BlockHeight: 1, + MarginToUser: sdk.NewInt(-18), + ChangeReason: types.ChangeReason_PartialClose, + }), ), } NewTestSuite(t).WithTestCases(tc...).Run() } -func TestClosePositionWithBadDebt(t *testing.T) { - pairBtcUsdc := asset.Registry.Pair(denoms.BTC, denoms.USDC) - +func TestClosePosition(t *testing.T) { alice := testutil.AccAddress() - startTime := time.Now() + pairBtcNusd := asset.Registry.Pair(denoms.BTC, denoms.NUSD) + startBlockTime := time.Now() tc := TestCases{ - TC("close position with bad debt"). + TC("close long position with positive PnL"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.NewDec(2)), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockTime(startBlockTime), + SetBlockNumber(1), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(40)))), + FundModule(types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 10_998))), + InsertPosition( + WithTrader(alice), + WithPair(pairBtcNusd), + WithSize(sdk.NewDec(10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + ClosePosition(alice, pairBtcNusd), + ). + Then( + PositionShouldNotExist(alice, pairBtcNusd), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.ZeroDec(), + RealizedPnl: sdk.MustNewDecFromStr("9999.999800000002000000"), + BadDebt: sdk.NewCoin(denoms.NUSD, sdk.ZeroInt()), + FundingPayment: sdk.NewDec(2), + TransactionFee: sdk.NewCoin(denoms.NUSD, sdk.NewInt(40)), + BlockHeight: 1, + MarginToUser: sdk.NewInt(10_958), + ChangeReason: types.ChangeReason_ClosePosition, + }), + ), + + TC("close long position with negative PnL"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.99")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockTime(startBlockTime), + SetBlockNumber(1), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20)))), + FundModule(types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 898))), + InsertPosition( + WithTrader(alice), + WithPair(pairBtcNusd), + WithSize(sdk.NewDec(10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + ClosePosition(alice, pairBtcNusd), + ). + Then( + PositionShouldNotExist(alice, pairBtcNusd), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.ZeroDec(), + RealizedPnl: sdk.MustNewDecFromStr("-100.000098999999010000"), + BadDebt: sdk.NewCoin(denoms.NUSD, sdk.ZeroInt()), + FundingPayment: sdk.NewDec(2), + TransactionFee: sdk.NewCoin(denoms.NUSD, sdk.NewInt(20)), + BlockHeight: 1, + MarginToUser: sdk.NewInt(878), + ChangeReason: types.ChangeReason_ClosePosition, + }), + ), + + TC("close long position with bad debt"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.89")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + InsertPosition( + WithTrader(alice), + WithPair(pairBtcNusd), + WithSize(sdk.NewDec(10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + FundAccount(alice, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 18))), + FundModule(types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 1000))), + FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 102))), + ). + When( + MoveToNextBlock(), + ClosePosition(alice, pairBtcNusd), + ). + Then( + PositionShouldNotExist(alice, pairBtcNusd), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + TraderAddress: alice.String(), + Pair: pairBtcNusd, + Size_: sdk.ZeroDec(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + LastUpdatedBlockNumber: 2, + }, + PositionNotional: sdk.ZeroDec(), + TransactionFee: sdk.NewInt64Coin(denoms.NUSD, 18), + RealizedPnl: sdk.MustNewDecFromStr("-1100.000088999999110000"), + BadDebt: sdk.NewCoin(denoms.NUSD, sdk.NewInt(102)), + FundingPayment: sdk.NewDec(2), + BlockHeight: 2, + MarginToUser: sdk.NewInt(-18), + ChangeReason: types.ChangeReason_ClosePosition, + }), + ModuleBalanceEqual(types.VaultModuleAccount, denoms.NUSD, sdk.NewInt(1102)), // 1000 + 102 from perp ef + ModuleBalanceEqual(types.PerpEFModuleAccount, denoms.NUSD, sdk.NewInt(9)), + ), + + TC("close short position with positive PnL"). Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.10")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockTime(startBlockTime), SetBlockNumber(1), - SetBlockTime(startTime), - CreateCustomMarket(pairBtcUsdc), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(2)))), + FundModule(types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 10_002))), + InsertPosition( + WithTrader(alice), + WithPair(pairBtcNusd), + WithSize(sdk.NewDec(-10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + ClosePosition(alice, pairBtcNusd), + ). + Then( + PositionShouldNotExist(alice, pairBtcNusd), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.ZeroDec(), + RealizedPnl: sdk.MustNewDecFromStr("8999.999989999999900000"), + BadDebt: sdk.NewCoin(denoms.NUSD, sdk.ZeroInt()), + FundingPayment: sdk.NewDec(-2), + TransactionFee: sdk.NewCoin(denoms.NUSD, sdk.NewInt(2)), + BlockHeight: 1, + MarginToUser: sdk.NewInt(10_000), + ChangeReason: types.ChangeReason_ClosePosition, + }), + ), + TC("close short position with negative PnL"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("1.01")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockTime(startBlockTime), + SetBlockNumber(1), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20)))), + FundModule(types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 902))), InsertPosition( WithTrader(alice), - WithPair(pairBtcUsdc), - WithSize(sdk.NewDec(10000)), - WithMargin(sdk.NewDec(750)), - WithOpenNotional(sdk.NewDec(10800))), + WithPair(pairBtcNusd), + WithSize(sdk.NewDec(-10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + ClosePosition(alice, pairBtcNusd), + ). + Then( + PositionShouldNotExist(alice, pairBtcNusd), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 1, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.ZeroDec(), + RealizedPnl: sdk.MustNewDecFromStr("-100.000101000001010000"), + BadDebt: sdk.NewCoin(denoms.NUSD, sdk.ZeroInt()), + FundingPayment: sdk.NewDec(-2), + TransactionFee: sdk.NewCoin(denoms.NUSD, sdk.NewInt(20)), + BlockHeight: 1, + MarginToUser: sdk.NewInt(882), + ChangeReason: types.ChangeReason_ClosePosition, + }), + ), - FundModule(types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.USDC, 1000))), - FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.USDC, 50))), + TC("close short position with bad debt"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("1.11")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockTime(startBlockTime), + SetBlockNumber(1), + InsertPosition( + WithTrader(alice), + WithPair(pairBtcNusd), + WithSize(sdk.NewDec(-10_000)), + WithMargin(sdk.NewDec(1_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + FundAccount(alice, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 22))), + FundModule(types.VaultModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 1000))), + FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewInt64Coin(denoms.NUSD, 98))), ). When( MoveToNextBlock(), - ClosePosition(alice, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), - assertion.ModuleBalanceEqual(types.VaultModuleAccount, denoms.USDC, sdk.NewInt(1050)), // 1000 + 50 from perp ef - assertion.ModuleBalanceEqual(types.PerpEFModuleAccount, denoms.USDC, sdk.NewInt(0)), + PositionShouldNotExist(alice, pairBtcNusd), + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + TraderAddress: alice.String(), + Pair: pairBtcNusd, + Size_: sdk.ZeroDec(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + LastUpdatedBlockNumber: 2, + }, + PositionNotional: sdk.ZeroDec(), + TransactionFee: sdk.NewInt64Coin(denoms.NUSD, 22), + RealizedPnl: sdk.MustNewDecFromStr("-1100.000111000001110000"), + BadDebt: sdk.NewInt64Coin(denoms.NUSD, 98), + FundingPayment: sdk.NewDec(-2), + BlockHeight: 2, + MarginToUser: sdk.NewInt(-22), + ChangeReason: types.ChangeReason_ClosePosition, + }), + ModuleBalanceEqual(types.VaultModuleAccount, denoms.NUSD, sdk.NewInt(1098)), // 1000 + 98 from perp ef + ModuleBalanceEqual(types.PerpEFModuleAccount, denoms.NUSD, sdk.NewInt(11)), ), } @@ -1359,7 +1552,7 @@ func TestClosePositionWithBadDebt(t *testing.T) { func TestUpdateSwapInvariant(t *testing.T) { alice := testutil.AccAddress() bob := testutil.AccAddress() - pairBtcUsdc := asset.Registry.Pair(denoms.BTC, denoms.NUSD) + pairBtcNusd := asset.Registry.Pair(denoms.BTC, denoms.NUSD) startBlockTime := time.Now() startingSwapInvariant := sdk.NewDec(1_000_000_000_000).Mul(sdk.NewDec(1_000_000_000_000)) @@ -1367,182 +1560,182 @@ func TestUpdateSwapInvariant(t *testing.T) { tc := TestCases{ TC("only long position - no change to swap invariant"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), - ClosePosition(alice, pairBtcUsdc), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("only short position - no change to swap invariant"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.NewDec(10_000_000_000_000)), - ClosePosition(alice, pairBtcUsdc), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.NewDec(10_000_000_000_000)), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ), TC("only long position - increasing k"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), - EditSwapInvariant(pairBtcUsdc, startingSwapInvariant.MulInt64(100)), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), + EditSwapInvariant(pairBtcNusd, startingSwapInvariant.MulInt64(100)), AMMShouldBeEqual( - pairBtcUsdc, + pairBtcNusd, AMM_SwapInvariantShouldBeEqual(sdk.MustNewDecFromStr("99999999999999999999999999.999999000000000000"))), - ClosePosition(alice, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins()), ), TC("only short position - increasing k"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), - EditSwapInvariant(pairBtcUsdc, startingSwapInvariant.MulInt64(100)), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), + EditSwapInvariant(pairBtcNusd, startingSwapInvariant.MulInt64(100)), AMMShouldBeEqual( - pairBtcUsdc, + pairBtcNusd, AMM_SwapInvariantShouldBeEqual(sdk.MustNewDecFromStr("99999999999999999999999999.999999000000000000"))), - ClosePosition(alice, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.OneInt()))), ), TC("only long position - decreasing k"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), - EditSwapInvariant(pairBtcUsdc, startingSwapInvariant.Mul(sdk.MustNewDecFromStr("0.1"))), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), + EditSwapInvariant(pairBtcNusd, startingSwapInvariant.Mul(sdk.MustNewDecFromStr("0.1"))), AMMShouldBeEqual( - pairBtcUsdc, + pairBtcNusd, AMM_SwapInvariantShouldBeEqual(sdk.MustNewDecFromStr("99999999999999999873578.871987715651277660"))), - ClosePosition(alice, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins()), ), TC("only short position - decreasing k"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), - EditSwapInvariant(pairBtcUsdc, startingSwapInvariant.Mul(sdk.MustNewDecFromStr("0.1"))), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), + EditSwapInvariant(pairBtcNusd, startingSwapInvariant.Mul(sdk.MustNewDecFromStr("0.1"))), AMMShouldBeEqual( - pairBtcUsdc, + pairBtcNusd, AMM_SwapInvariantShouldBeEqual(sdk.MustNewDecFromStr("99999999999999999873578.871987801032774485"))), - ClosePosition(alice, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins()), ), TC("long and short position - increasing k"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundAccount(bob, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), - MarketOrder(bob, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.NewDec(10_000_000_000_000)), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), + MarketOrder(bob, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.NewDec(10_000_000_000_000)), - EditSwapInvariant(pairBtcUsdc, startingSwapInvariant.MulInt64(100)), + EditSwapInvariant(pairBtcNusd, startingSwapInvariant.MulInt64(100)), AMMShouldBeEqual( - pairBtcUsdc, + pairBtcNusd, AMM_BiasShouldBeEqual(sdk.ZeroDec()), AMM_SwapInvariantShouldBeEqual(sdk.MustNewDecFromStr("100000000000000000000000000.000000000000000000"))), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20_000_000_000)))), ModuleBalanceShouldBeEqualTo(types.FeePoolModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20_000_000)))), // Fees are 10_000_000_000 * 0.0010 * 2 - ClosePosition(alice, pairBtcUsdc), - ClosePosition(bob, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), + ClosePosition(bob, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), - PositionShouldNotExist(bob, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), + PositionShouldNotExist(bob, pairBtcNusd), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins()), ModuleBalanceShouldBeEqualTo(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(39_782_394)))), ), TC("long and short position - reducing k"). Given( - CreateCustomMarket(pairBtcUsdc), + CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcUsdc, sdk.NewDec(1)), + SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundAccount(bob, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), ). When( - MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), - MarketOrder(bob, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.NewDec(10_000_000_000_000)), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.ZeroDec()), + MarketOrder(bob, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(10_000_000_000), sdk.NewDec(1), sdk.NewDec(10_000_000_000_000)), - EditSwapInvariant(pairBtcUsdc, startingSwapInvariant.Mul(sdk.MustNewDecFromStr("0.1"))), + EditSwapInvariant(pairBtcNusd, startingSwapInvariant.Mul(sdk.MustNewDecFromStr("0.1"))), AMMShouldBeEqual( - pairBtcUsdc, + pairBtcNusd, AMM_BiasShouldBeEqual(sdk.ZeroDec()), AMM_SwapInvariantShouldBeEqual(sdk.MustNewDecFromStr("99999999999999999873578.871987712489000000"))), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20_000_000_000)))), ModuleBalanceShouldBeEqualTo(types.FeePoolModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20_000_000)))), // Fees are 10_000_000_000 * 0.0010 * 2 - ClosePosition(alice, pairBtcUsdc), - ClosePosition(bob, pairBtcUsdc), + ClosePosition(alice, pairBtcNusd), + ClosePosition(bob, pairBtcNusd), ). Then( - PositionShouldNotExist(alice, pairBtcUsdc), - PositionShouldNotExist(bob, pairBtcUsdc), + PositionShouldNotExist(alice, pairBtcNusd), + PositionShouldNotExist(bob, pairBtcNusd), ModuleBalanceShouldBeEqualTo(types.VaultModuleAccount, sdk.NewCoins()), ModuleBalanceShouldBeEqualTo(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(39_200_810)))), diff --git a/x/perp/v2/keeper/margin_test.go b/x/perp/v2/keeper/margin_test.go index 5ca6e1d8f..9a9569168 100644 --- a/x/perp/v2/keeper/margin_test.go +++ b/x/perp/v2/keeper/margin_test.go @@ -11,7 +11,6 @@ import ( "github.com/NibiruChain/nibiru/x/common/testutil" . "github.com/NibiruChain/nibiru/x/common/testutil/action" . "github.com/NibiruChain/nibiru/x/common/testutil/assertion" - . "github.com/NibiruChain/nibiru/x/oracle/integration/action" . "github.com/NibiruChain/nibiru/x/perp/v2/integration/action" . "github.com/NibiruChain/nibiru/x/perp/v2/integration/assertion" types "github.com/NibiruChain/nibiru/x/perp/v2/types" @@ -28,7 +27,6 @@ func TestAddMargin(t *testing.T) { CreateCustomMarket(pairBtcUsdc), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.MustNewDecFromStr("2.1")), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.USDC, sdk.NewInt(2020)))), MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -77,7 +75,6 @@ func TestAddMargin(t *testing.T) { CreateCustomMarket(pairBtcUsdc), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.MustNewDecFromStr("2.1")), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.USDC, sdk.NewInt(2020)))), MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -136,7 +133,6 @@ func TestRemoveMargin(t *testing.T) { CreateCustomMarket(pairBtcUsdc), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.MustNewDecFromStr("2.1")), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.USDC, sdk.NewInt(1002)))), MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.OneDec(), sdk.ZeroDec()), ). @@ -178,12 +174,11 @@ func TestRemoveMargin(t *testing.T) { ModuleBalanceEqual(types.FeePoolModuleAccount, denoms.USDC, sdk.NewInt(1)), ), - TC("existing long position, remove almost all margin"). + TC("existing long position, remove almost all margin fails"). Given( CreateCustomMarket(pairBtcUsdc), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.MustNewDecFromStr("2.1")), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.USDC, sdk.NewInt(1002)))), MarketOrder(alice, pairBtcUsdc, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(1), sdk.ZeroDec()), MoveToNextBlock(), @@ -201,25 +196,6 @@ func TestRemoveMargin(t *testing.T) { LatestCumulativePremiumFraction: sdk.ZeroDec(), LastUpdatedBlockNumber: 1, })), - PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ - FinalPosition: types.Position{ - Pair: pairBtcUsdc, - TraderAddress: alice.String(), - Size_: sdk.MustNewDecFromStr("999.999999000000001000"), - Margin: sdk.NewDec(1000), - OpenNotional: sdk.NewDec(1000), - LatestCumulativePremiumFraction: sdk.ZeroDec(), - LastUpdatedBlockNumber: 1, - }, - PositionNotional: sdk.NewDec(1000), - RealizedPnl: sdk.ZeroDec(), - BadDebt: sdk.NewCoin(denoms.USDC, sdk.ZeroInt()), - FundingPayment: sdk.ZeroDec(), - TransactionFee: sdk.NewCoin(denoms.USDC, sdk.ZeroInt()), - BlockHeight: 2, - MarginToUser: sdk.NewInt(0), - ChangeReason: types.ChangeReason_RemoveMargin, - }), BalanceEqual(alice, denoms.USDC, sdk.NewInt(0)), ModuleBalanceEqual(types.PerpEFModuleAccount, denoms.USDC, sdk.NewInt(1)), ModuleBalanceEqual(types.FeePoolModuleAccount, denoms.USDC, sdk.NewInt(1)), @@ -230,7 +206,6 @@ func TestRemoveMargin(t *testing.T) { CreateCustomMarket(pairBtcUsdc), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcUsdc, sdk.MustNewDecFromStr("2.1")), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.USDC, sdk.NewInt(1002)))), MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.OneDec(), sdk.ZeroDec()), ). @@ -271,6 +246,33 @@ func TestRemoveMargin(t *testing.T) { ModuleBalanceEqual(types.PerpEFModuleAccount, denoms.USDC, sdk.NewInt(1)), ModuleBalanceEqual(types.FeePoolModuleAccount, denoms.USDC, sdk.NewInt(1)), ), + + TC("existing short position, remove almost all margin fails"). + Given( + CreateCustomMarket(pairBtcUsdc), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.USDC, sdk.NewInt(1002)))), + MarketOrder(alice, pairBtcUsdc, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(1), sdk.ZeroDec()), + MoveToNextBlock(), + ). + When( + RemoveMarginFail(alice, pairBtcUsdc, sdk.NewInt(999), types.ErrMarginRatioTooLow), + ). + Then( + PositionShouldBeEqual(alice, pairBtcUsdc, Position_PositionShouldBeEqualTo(types.Position{ + Pair: pairBtcUsdc, + TraderAddress: alice.String(), + Size_: sdk.MustNewDecFromStr("-1000.000001000000001000"), + Margin: sdk.NewDec(1000), + OpenNotional: sdk.NewDec(1000), + LatestCumulativePremiumFraction: sdk.ZeroDec(), + LastUpdatedBlockNumber: 1, + })), + BalanceEqual(alice, denoms.USDC, sdk.NewInt(0)), + ModuleBalanceEqual(types.PerpEFModuleAccount, denoms.USDC, sdk.NewInt(1)), + ModuleBalanceEqual(types.FeePoolModuleAccount, denoms.USDC, sdk.NewInt(1)), + ), } NewTestSuite(t).WithTestCases(tc...).Run() diff --git a/x/perp/v2/types/errors.go b/x/perp/v2/types/errors.go index abb31f18d..6eeecd031 100644 --- a/x/perp/v2/types/errors.go +++ b/x/perp/v2/types/errors.go @@ -25,7 +25,7 @@ var ( ErrMarginRatioTooHigh = sdkerrors.Register(ModuleName, 13, "margin ratio is too healthy to liquidate") ErrPairNotFound = sdkerrors.Register(ModuleName, 14, "pair doesn't have live market") ErrPositionZero = sdkerrors.Register(ModuleName, 15, "position is zero") - ErrBadDebt = sdkerrors.Register(ModuleName, 16, "failed to remove margin; position would have bad debt if removed") + ErrBadDebt = sdkerrors.Register(ModuleName, 16, "position is underwater") ErrInputQuoteAmtNegative = sdkerrors.Register(ModuleName, 17, "quote amount cannot be zero") ErrInputBaseAmtNegative = sdkerrors.Register(ModuleName, 30, "base amount cannot be zero") From 9d4f3dce077527c3ec372f6507c3f329f5f55d48 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:52:46 -0400 Subject: [PATCH 5/9] refactor: remove unnecessary module methods (#1460) * fix(spot): wire msg server to module * chore(spot): remove handler * test(spot): join pool cmd * test(spot): create pool cmd * test(spot): create pool errors * refactor: rename testutil variables * test(spot): create pool errors * test(spot): create pool cmd * test(spot): join stable pool * test(spot): swap assets * test(spot): swap stable assets cmd * test(spot): exit pool cmd * test(spot): exit stable pool cmd * Update CHANGELOG.md * refactor(epoch): remove unused methods * refactor(inflation): remove unnecessary methods * refactor(perp): clean up module * refactor(spot): clean up module * refactor(stablecoin): clean up module * refactor(sudo): clean up module --- CHANGELOG.md | 2 ++ x/epochs/handler.go | 23 ----------------- x/epochs/module.go | 20 +++------------ x/epochs/types/codec.go | 17 ------------- x/inflation/module.go | 12 --------- x/perp/v2/module/module.go | 11 +-------- x/perp/v2/types/codec.go | 2 +- x/spot/module.go | 11 +-------- x/spot/types/codec.go | 9 ++++--- x/stablecoin/handler.go | 49 ------------------------------------- x/stablecoin/module.go | 14 +---------- x/stablecoin/types/codec.go | 3 +-- x/sudo/module.go | 2 +- x/sudo/types/codec.go | 3 +-- 14 files changed, 19 insertions(+), 159 deletions(-) delete mode 100644 x/epochs/handler.go delete mode 100644 x/epochs/types/codec.go delete mode 100644 x/stablecoin/handler.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ec9ab96f..8191c0be1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Improvements +* [#1463](https://github.com/NibiruChain/nibiru/pull/1463) - feat(oracle): add genesis pricefeeder delegation +* [#1466](https://github.com/NibiruChain/nibiru/pull/1466) - refactor(perp): `PositionLiquidatedEvent` * [#1462](https://github.com/NibiruChain/nibiru/pull/1462) - fix(perp): Add pair to liquidation failed event. * [#1424](https://github.com/NibiruChain/nibiru/pull/1424) - feat(perp): Add change type and exchanged margin to position changed events. * [#1390](https://github.com/NibiruChain/nibiru/pull/1390) - fix(localnet.sh): Fix genesis market initialization + add force exits on failure diff --git a/x/epochs/handler.go b/x/epochs/handler.go deleted file mode 100644 index 8f753b653..000000000 --- a/x/epochs/handler.go +++ /dev/null @@ -1,23 +0,0 @@ -package epochs - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/types/errors" - - sdkerrors "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/NibiruChain/nibiru/x/epochs/types" -) - -// NewHandler returns a handler for epochs module messages. -func NewHandler() sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - switch msg := msg.(type) { - default: - errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) - return nil, sdkerrors.Wrap(errors.ErrUnknownRequest, errMsg) - } - } -} diff --git a/x/epochs/module.go b/x/epochs/module.go index 239a3167e..527e224c7 100644 --- a/x/epochs/module.go +++ b/x/epochs/module.go @@ -12,20 +12,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" "github.com/NibiruChain/nibiru/x/epochs/client/cli" - "github.com/NibiruChain/nibiru/x/epochs/client/rest" "github.com/NibiruChain/nibiru/x/epochs/keeper" "github.com/NibiruChain/nibiru/x/epochs/simulation" "github.com/NibiruChain/nibiru/x/epochs/types" ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModule = AppModule{} + _ module.AppModuleSimulation = AppModule{} ) // ---------------------------------------------------------------------------- @@ -46,17 +45,11 @@ func (AppModuleBasic) Name() string { return types.ModuleName } -func (AppModuleBasic) RegisterCodec(cdc *codec.LegacyAmino) { - types.RegisterCodec(cdc) -} - func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - types.RegisterCodec(cdc) } // RegisterInterfaces registers the module's interface types. func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { - types.RegisterInterfaces(reg) } // DefaultGenesis returns the capability module's default genesis state. @@ -73,11 +66,6 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod return genState.Validate() } -// RegisterRESTRoutes registers the capability module's REST service handlers. -func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { - rest.RegisterRoutes(clientCtx, rtr) -} - // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck @@ -174,7 +162,7 @@ func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return nil // TODO + return nil } // ConsensusVersion implements AppModule/ConsensusVersion. diff --git a/x/epochs/types/codec.go b/x/epochs/types/codec.go deleted file mode 100644 index a7a04d308..000000000 --- a/x/epochs/types/codec.go +++ /dev/null @@ -1,17 +0,0 @@ -package types - -import ( - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" -) - -func RegisterCodec(cdc *codec.LegacyAmino) { -} - -func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { -} - -var ( - amino = codec.NewLegacyAmino() - ModuleCdc = codec.NewAminoCodec(amino) -) diff --git a/x/inflation/module.go b/x/inflation/module.go index 966255df7..0be1f79e2 100644 --- a/x/inflation/module.go +++ b/x/inflation/module.go @@ -14,7 +14,6 @@ import ( simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -51,7 +50,6 @@ func (AppModuleBasic) ConsensusVersion() uint64 { // RegisterInterfaces registers interfaces and implementations of the incentives // module. func (AppModuleBasic) RegisterInterfaces(interfaceRegistry codectypes.InterfaceRegistry) { - // types.RegisterInterfaces(interfaceRegistry) } // DefaultGenesis returns default genesis state as raw bytes for the incentives @@ -70,10 +68,6 @@ func (b AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncoding return genesisState.Validate() } -// RegisterRESTRoutes performs a no-op as the inflation module doesn't expose REST -// endpoints -func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} - // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the inflation module. func (b AppModuleBasic) RegisterGRPCGatewayRoutes(c client.Context, serveMux *runtime.ServeMux) { if err := types.RegisterQueryHandlerClient(context.Background(), serveMux, types.NewQueryClient(c)); err != nil { @@ -121,15 +115,9 @@ func (AppModule) Name() string { // RegisterInvariants registers the inflation module invariants. func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} -// QuerierRoute returns the inflation module's querier route name. -func (am AppModule) QuerierRoute() string { - return types.RouterKey -} - // RegisterServices registers a gRPC query service to respond to the // module-specific gRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { - // types.RegisterMsgServer(cfg.MsgServer(), am.keeper) types.RegisterQueryServer(cfg.QueryServer(), am.keeper) } diff --git a/x/perp/v2/module/module.go b/x/perp/v2/module/module.go index d70a43f44..12195ccca 100644 --- a/x/perp/v2/module/module.go +++ b/x/perp/v2/module/module.go @@ -11,7 +11,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -47,12 +46,8 @@ func (AppModuleBasic) RegisterInterfaces(interfaceRegistry codectypes.InterfaceR types.RegisterInterfaces(interfaceRegistry) } -func (AppModuleBasic) RegisterCodec(aminoCodec *codec.LegacyAmino) { - types.RegisterCodec(aminoCodec) -} - func (AppModuleBasic) RegisterLegacyAminoCodec(aminoCodec *codec.LegacyAmino) { - types.RegisterCodec(aminoCodec) + types.RegisterLegacyAminoCodec(aminoCodec) } // DefaultGenesis returns default genesis state as raw bytes for the erc20 @@ -72,10 +67,6 @@ func (AppModuleBasic) ValidateGenesis( return genState.Validate() } -// RegisterRESTRoutes registers the capability module's REST service handlers. -func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { -} - // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. func (AppModuleBasic) RegisterGRPCGatewayRoutes( clientCtx client.Context, mux *runtime.ServeMux, diff --git a/x/perp/v2/types/codec.go b/x/perp/v2/types/codec.go index f26c888a2..f50ee8eba 100644 --- a/x/perp/v2/types/codec.go +++ b/x/perp/v2/types/codec.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" ) -func RegisterCodec(cdc *codec.LegacyAmino) { +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgAddMargin{}, "perpv2/add_margin", nil) cdc.RegisterConcrete(&MsgRemoveMargin{}, "perpv2/remove_margin", nil) cdc.RegisterConcrete(&MsgMarketOrder{}, "perpv2/market_order", nil) diff --git a/x/spot/module.go b/x/spot/module.go index 63466ab4a..c0fc8a40f 100644 --- a/x/spot/module.go +++ b/x/spot/module.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -45,12 +44,8 @@ func (AppModuleBasic) Name() string { return types.ModuleName } -func (AppModuleBasic) RegisterCodec(cdc *codec.LegacyAmino) { - types.RegisterCodec(cdc) -} - func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - types.RegisterCodec(cdc) + types.RegisterLegacyAminoCodec(cdc) } // RegisterInterfaces registers the module's interface types @@ -72,10 +67,6 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod return genState.Validate() } -// RegisterRESTRoutes registers the capability module's REST service handlers. -func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { -} - // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { diff --git a/x/spot/types/codec.go b/x/spot/types/codec.go index cb55dc909..612461b3d 100644 --- a/x/spot/types/codec.go +++ b/x/spot/types/codec.go @@ -7,9 +7,11 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" ) -func RegisterCodec(cdc *codec.LegacyAmino) { +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgCreatePool{}, "spot/CreatePool", nil) - // TODO(k-yang): register MsgJoinPool + cdc.RegisterConcrete(&MsgJoinPool{}, "spot/JoinPool", nil) + cdc.RegisterConcrete(&MsgExitPool{}, "spot/ExitPool", nil) + cdc.RegisterConcrete(&MsgSwapAssets{}, "spot/SwapAssets", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -18,12 +20,13 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { /* implementations */ &MsgCreatePool{}, &MsgJoinPool{}, + &MsgExitPool{}, + &MsgSwapAssets{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } var ( - Amino = codec.NewLegacyAmino() ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) ) diff --git a/x/stablecoin/handler.go b/x/stablecoin/handler.go deleted file mode 100644 index 08c2303af..000000000 --- a/x/stablecoin/handler.go +++ /dev/null @@ -1,49 +0,0 @@ -package stablecoin - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/types/errors" - - sdkerrors "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/NibiruChain/nibiru/x/stablecoin/keeper" - "github.com/NibiruChain/nibiru/x/stablecoin/types" -) - -/* -NewHandler returns an sdk.Handler for "x/stablecoin" messages. -A handler defines the core state transition functions of an application. -First, the handler performs stateful checks to make sure each 'msg' is valid. -At this stage, the 'msg.ValidateBasic()' method has already been called, meaning -stateless checks on the message (like making sure parameters are correctly -formatted) have already been performed. -*/ -func NewHandler(k keeper.Keeper) sdk.Handler { - msgServer := keeper.NewMsgServerImpl(k) - - return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - ctx = ctx.WithEventManager(sdk.NewEventManager()) - goCtx := sdk.WrapSDKContext(ctx) - - switch msg := msg.(type) { - case *types.MsgMintStable: - res, err := msgServer.MintStable(goCtx, msg) - return sdk.WrapServiceResult(ctx, res, err) - case *types.MsgBurnStable: - res, err := msgServer.BurnStable(goCtx, msg) - return sdk.WrapServiceResult(ctx, res, err) - case *types.MsgRecollateralize: - res, err := msgServer.Recollateralize(goCtx, msg) - return sdk.WrapServiceResult(ctx, res, err) - case *types.MsgBuyback: - res, err := msgServer.Buyback(goCtx, msg) - return sdk.WrapServiceResult(ctx, res, err) - default: - errMsg := fmt.Sprintf( - "unrecognized %s message type: %T", types.ModuleName, msg) - return nil, sdkerrors.Wrap(errors.ErrUnknownRequest, errMsg) - } - } -} diff --git a/x/stablecoin/module.go b/x/stablecoin/module.go index b1e3e0d21..394e5269b 100644 --- a/x/stablecoin/module.go +++ b/x/stablecoin/module.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -48,12 +47,8 @@ func (AppModuleBasic) RegisterInterfaces(interfaceRegistry codectypes.InterfaceR types.RegisterInterfaces(interfaceRegistry) } -func (AppModuleBasic) RegisterCodec(aminoCodec *codec.LegacyAmino) { - types.RegisterCodec(aminoCodec) -} - func (AppModuleBasic) RegisterLegacyAminoCodec(aminoCodec *codec.LegacyAmino) { - types.RegisterCodec(aminoCodec) + types.RegisterLegacyAminoCodec(aminoCodec) } // DefaultGenesis returns default genesis state as raw bytes for the erc20 @@ -73,10 +68,6 @@ func (AppModuleBasic) ValidateGenesis( return genState.Validate() } -// RegisterRESTRoutes registers the capability module's REST service handlers. -func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { -} - // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. func (AppModuleBasic) RegisterGRPCGatewayRoutes( clientCtx client.Context, mux *runtime.ServeMux, @@ -131,9 +122,6 @@ func (am AppModule) Name() string { return am.AppModuleBasic.Name() } -// QuerierRoute returns the capability module's query routing key. -func (AppModule) QuerierRoute() string { return types.QuerierRoute } - // RegisterServices registers a GRPC query service to respond to the // module-specific GRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { diff --git a/x/stablecoin/types/codec.go b/x/stablecoin/types/codec.go index 5057f13d0..6b73f9f9f 100644 --- a/x/stablecoin/types/codec.go +++ b/x/stablecoin/types/codec.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" ) -func RegisterCodec(cdc *codec.LegacyAmino) { +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgMintStable{}, "stablecoin/MintStable", nil) cdc.RegisterConcrete(&MsgBurnStable{}, "stablecoin/BurnStable", nil) } @@ -22,6 +22,5 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { } var ( - Amino = codec.NewLegacyAmino() ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) ) diff --git a/x/sudo/module.go b/x/sudo/module.go index 7ad9795db..ac9fed14d 100644 --- a/x/sudo/module.go +++ b/x/sudo/module.go @@ -47,7 +47,7 @@ func (AppModuleBasic) RegisterInterfaces(interfaceRegistry codectypes.InterfaceR } func (AppModuleBasic) RegisterLegacyAminoCodec(aminoCodec *codec.LegacyAmino) { - types.RegisterCodec(aminoCodec) + types.RegisterLegacyAminoCodec(aminoCodec) } // DefaultGenesis returns default genesis state as raw bytes for the module. diff --git a/x/sudo/types/codec.go b/x/sudo/types/codec.go index b939e5f64..e40d39e36 100644 --- a/x/sudo/types/codec.go +++ b/x/sudo/types/codec.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" ) -func RegisterCodec(cdc *codec.LegacyAmino) { +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgEditSudoers{}, "sudo/edit_sudoers", nil) } @@ -22,6 +22,5 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { } var ( - Amino = codec.NewLegacyAmino() ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) ) From ec443b108eff3137e891eb82f1baf54505a00fa9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 22:34:08 +0200 Subject: [PATCH 6/9] chore(deps): Bump cosmossdk.io/errors from 1.0.0-beta.7 to 1.0.0 (#1499) --- CHANGELOG.md | 1 + go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8191c0be1..de7433b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `github.com/cosmos/ibc-go/v7` from 7.1.0 to 7.2.0 (#1445) - Bump `bufbuild/buf-setup-action` from 1.21.0 to 1.23.1 (#1449, #1469) - Bump `google.golang.org/protobuf` from 1.30.0 to 1.31.0 (#1450) +- Bump `cosmossdk.io/errors` from 1.0.0-beta.7 to 1.0.0 (#1499) ### Breaking diff --git a/go.mod b/go.mod index ae75a26f0..e11b6c146 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/NibiruChain/nibiru go 1.19 require ( - cosmossdk.io/errors v1.0.0-beta.7 + cosmossdk.io/errors v1.0.0 cosmossdk.io/math v1.0.1 github.com/CosmWasm/wasmd v0.40.0 github.com/CosmWasm/wasmvm v1.2.4 @@ -155,13 +155,13 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 676411ae3..5027e7fb1 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,8 @@ cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s= cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA= cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU= -cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= -cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= +cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= +cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= cosmossdk.io/log v1.1.0 h1:v0ogPHYeTzPcBTcPR1A3j1hkei4pZama8kz8LKlCMv0= cosmossdk.io/log v1.1.0/go.mod h1:6zjroETlcDs+mm62gd8Ig7mZ+N+fVOZS91V17H+M4N4= cosmossdk.io/math v1.0.1 h1:Qx3ifyOPaMLNH/89WeZFH268yCvU4xEcnPLu3sJqPPg= @@ -1217,8 +1217,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1329,8 +1329,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1481,14 +1481,14 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1500,8 +1500,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 95618f2d97bcff8963cdb6e1477d8a2021b63243 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Wed, 12 Jul 2023 01:04:34 +0200 Subject: [PATCH 7/9] feat: add cli to change root sudo command (#1498) * create msg change root proto * use test keeper only for tests * move convenience wrapper * move convenience wrapper * remove pb folder * move files where convention rules * split msg server from keeper * split querier from keeper * add message server change root command * change root cli command * add change root command * add changelog * add test trick for ordering --- CHANGELOG.md | 1 + proto/nibiru/sudo/v1/event.proto | 2 +- proto/nibiru/sudo/v1/query.proto | 2 +- proto/nibiru/sudo/v1/state.proto | 2 +- proto/nibiru/sudo/v1/tx.proto | 22 +- x/sudo/cli/cli.go | 47 ++ x/sudo/cli/cli_test.go | 17 + x/sudo/keeper/keeper.go | 137 ++++++ x/sudo/keeper/msg_server.go | 127 ++++++ .../{sudo_test.go => msg_server_test.go} | 100 ++--- x/sudo/keeper/querier.go | 14 +- x/sudo/keeper/querier_test.go | 68 +++ x/sudo/keeper/sudo.go | 234 ---------- x/sudo/module.go | 4 +- x/sudo/types/errors.go | 7 + x/sudo/types/event.pb.go | 11 +- x/sudo/types/keys.go | 6 + x/sudo/types/msgs.go | 43 +- x/sudo/types/query.pb.go | 10 +- x/sudo/types/query.pb.gw.go | 2 +- x/sudo/types/state.pb.go | 10 +- x/sudo/types/tx.pb.go | 424 +++++++++++++++++- x/sudo/types/tx.pb.gw.go | 85 +++- x/wasm/binding/exec_test.go | 60 ++- 24 files changed, 1056 insertions(+), 379 deletions(-) create mode 100644 x/sudo/keeper/keeper.go create mode 100644 x/sudo/keeper/msg_server.go rename x/sudo/keeper/{sudo_test.go => msg_server_test.go} (90%) create mode 100644 x/sudo/keeper/querier_test.go delete mode 100644 x/sudo/keeper/sudo.go create mode 100644 x/sudo/types/errors.go create mode 100644 x/sudo/types/keys.go diff --git a/CHANGELOG.md b/CHANGELOG.md index de7433b9f..db6f9a14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/proto/nibiru/sudo/v1/event.proto b/proto/nibiru/sudo/v1/event.proto index d797f0805..a01d41227 100644 --- a/proto/nibiru/sudo/v1/event.proto +++ b/proto/nibiru/sudo/v1/event.proto @@ -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 ]; diff --git a/proto/nibiru/sudo/v1/query.proto b/proto/nibiru/sudo/v1/query.proto index 02254d2d9..5729fab52 100644 --- a/proto/nibiru/sudo/v1/query.proto +++ b/proto/nibiru/sudo/v1/query.proto @@ -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 { diff --git a/proto/nibiru/sudo/v1/state.proto b/proto/nibiru/sudo/v1/state.proto index 1ff7bc8d8..164d18937 100644 --- a/proto/nibiru/sudo/v1/state.proto +++ b/proto/nibiru/sudo/v1/state.proto @@ -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; diff --git a/proto/nibiru/sudo/v1/tx.proto b/proto/nibiru/sudo/v1/tx.proto index a1ffe47a2..95c719962 100644 --- a/proto/nibiru/sudo/v1/tx.proto +++ b/proto/nibiru/sudo/v1/tx.proto @@ -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 @@ -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 -------------------------- @@ -35,4 +39,18 @@ message MsgEditSudoers { } // MsgEditSudoersResponse indicates the successful execution of MsgEditSudeors. -message MsgEditSudoersResponse {} \ No newline at end of file +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 {} \ No newline at end of file diff --git a/x/sudo/cli/cli.go b/x/sudo/cli/cli.go index c4e3637eb..3e53138c7 100644 --- a/x/sudo/cli/cli.go +++ b/x/sudo/cli/cli.go @@ -28,6 +28,7 @@ func GetTxCmd() *cobra.Command { // Add subcommands txCmd.AddCommand( CmdEditSudoers(), + CmdChangeRoot(), ) return txCmd @@ -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 --from= + `, 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", diff --git a/x/sudo/cli/cli_test.go b/x/sudo/cli/cli_test.go index d686e2a8f..1de51ed0f 100644 --- a/x/sudo/cli/cli_test.go +++ b/x/sudo/cli/cli_test.go @@ -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. diff --git a/x/sudo/keeper/keeper.go b/x/sudo/keeper/keeper.go new file mode 100644 index 000000000..1ac4888ca --- /dev/null +++ b/x/sudo/keeper/keeper.go @@ -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 +} diff --git a/x/sudo/keeper/msg_server.go b/x/sudo/keeper/msg_server.go new file mode 100644 index 000000000..ad22c3afb --- /dev/null +++ b/x/sudo/keeper/msg_server.go @@ -0,0 +1,127 @@ +package keeper + +import ( + "context" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" + + "github.com/NibiruChain/nibiru/x/common/set" + sudotypes "github.com/NibiruChain/nibiru/x/sudo/types" +) + +type MsgServer struct { + keeper Keeper +} + +func NewMsgServer(keeper Keeper) *MsgServer { + return &MsgServer{keeper: keeper} +} + +// Ensure the interface is properly implemented at compile time +var _ sudotypes.MsgServer = MsgServer{} + +// EditSudoers adds or removes sudo contracts from state. +func (m MsgServer) EditSudoers( + goCtx context.Context, msg *sudotypes.MsgEditSudoers, +) (*sudotypes.MsgEditSudoersResponse, error) { + switch msg.RootAction() { + case sudotypes.AddContracts: + return m.keeper.AddContracts(goCtx, msg) + case sudotypes.RemoveContracts: + return m.keeper.RemoveContracts(goCtx, msg) + default: + return nil, fmt.Errorf("invalid action type specified on msg: %s", msg) + } +} + +func (m MsgServer) ChangeRoot(ctx context.Context, msg *sudotypes.MsgChangeRoot) (*sudotypes.MsgChangeRootResponse, error) { + sdkContext := sdk.UnwrapSDKContext(ctx) + + pbSudoers, err := m.keeper.Sudoers.Get(sdkContext) + if err != nil { + return nil, fmt.Errorf("failed to get sudoers: %w", err) + } + + err = m.validateRootPermissions(pbSudoers, msg) + if err != nil { + return nil, err + } + + pbSudoers.Root = msg.NewRoot + m.keeper.Sudoers.Set(sdkContext, pbSudoers) + + return &sudotypes.MsgChangeRootResponse{}, nil +} + +func (m MsgServer) validateRootPermissions(pbSudoers sudotypes.Sudoers, msg *sudotypes.MsgChangeRoot) error { + root, err := sdk.AccAddressFromBech32(pbSudoers.Root) + if err != nil { + return fmt.Errorf("failed to parse root address: %w", err) + } + + sender, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return fmt.Errorf("failed to parse sender address: %w", err) + } + + if !root.Equals(sender) { + return sudotypes.ErrUnauthorized + } + + return nil +} + +// ———————————————————————————————————————————————————————————————————————————— +// Encoder for the Sudoers type +// ———————————————————————————————————————————————————————————————————————————— + +func SudoersValueEncoder(cdc codec.BinaryCodec) collections.ValueEncoder[sudotypes.Sudoers] { + return collections.ProtoValueEncoder[sudotypes.Sudoers](cdc) +} + +type Sudoers struct { + Root string `json:"root"` + Contracts set.Set[string] `json:"contracts"` +} + +func (sudo Sudoers) String() string { + return sudo.ToPb().String() +} + +func (sudo Sudoers) ToPb() sudotypes.Sudoers { + return sudotypes.Sudoers{ + Root: sudo.Root, + Contracts: sudo.Contracts.ToSlice(), + } +} + +func SudoersFromPb(pbSudoers sudotypes.Sudoers) Sudoers { + return Sudoers{ + Root: pbSudoers.Root, + Contracts: set.New[string](pbSudoers.Contracts...), + } +} + +// AddContracts adds contract addresses to the sudoer set. +func (sudo *Sudoers) AddContracts( + contracts []string, +) (out set.Set[string], err error) { + for _, contractStr := range contracts { + contract, err := sdk.AccAddressFromBech32(contractStr) + if err != nil { + return out, err + } + sudo.Contracts.Add(contract.String()) + } + return sudo.Contracts, err +} + +func (sudo *Sudoers) RemoveContracts(contracts []string) { + for _, contract := range contracts { + sudo.Contracts.Remove(contract) + } +} diff --git a/x/sudo/keeper/sudo_test.go b/x/sudo/keeper/msg_server_test.go similarity index 90% rename from x/sudo/keeper/sudo_test.go rename to x/sudo/keeper/msg_server_test.go index c4fc9e351..b8ea3273d 100644 --- a/x/sudo/keeper/sudo_test.go +++ b/x/sudo/keeper/msg_server_test.go @@ -23,7 +23,6 @@ import ( func init() { useNibiAccPrefix() } - func setup() (*app.NibiruApp, sdk.Context) { genState := app.NewDefaultGenesisState(app.DefaultEncoding().Marshaler) nibiru := testapp.NewNibiruTestApp(genState) @@ -188,6 +187,42 @@ func TestSudo_AddContracts(t *testing.T) { } } +func TestMsgServer_ChangeRoot(t *testing.T) { + app, ctx := setup() + + _, err := app.SudoKeeper.Sudoers.Get(ctx) + require.NoError(t, err) + + actualRoot := testutil.AccAddress().String() + newRoot := testutil.AccAddress().String() + fakeRoot := testutil.AccAddress().String() + + app.SudoKeeper.Sudoers.Set(ctx, types.Sudoers{ + Root: actualRoot, + }) + + // try to change root with non-root account + msgServer := keeper.NewMsgServer(app.SudoKeeper) + _, err = msgServer.ChangeRoot( + sdk.WrapSDKContext(ctx), + &types.MsgChangeRoot{Sender: fakeRoot, NewRoot: newRoot}, + ) + require.EqualError(t, err, "unauthorized: missing sudo permissions") + + // try to change root with root account + _, err = msgServer.ChangeRoot( + sdk.WrapSDKContext(ctx), + &types.MsgChangeRoot{Sender: actualRoot, NewRoot: newRoot}, + ) + require.NoError(t, err) + + // check that root has changed + sudoers, err := app.SudoKeeper.Sudoers.Get(ctx) + require.NoError(t, err) + + require.Equal(t, newRoot, sudoers.Root) +} + func TestSudo_FromPbSudoers(t *testing.T) { for _, tc := range []struct { name string @@ -216,7 +251,7 @@ func TestSudo_FromPbSudoers(t *testing.T) { assert.EqualValues(t, tc.out.Contracts, out.Contracts) assert.EqualValues(t, tc.out.Root, out.Root) - pbSudoers := keeper.SudoersToPb(out) + pbSudoers := out.ToPb() for _, contract := range tc.in.Contracts { assert.True(t, set.New(pbSudoers.Contracts...).Has(contract)) } @@ -327,7 +362,8 @@ func TestKeeper_AddContracts(t *testing.T) { t.Log("Execute message") // Check via message handler directly - res, err := k.EditSudoers(sdk.WrapSDKContext(ctx), tc.msg) + msgServer := keeper.NewMsgServer(k) + res, err := msgServer.EditSudoers(sdk.WrapSDKContext(ctx), tc.msg) // Check via Keeper res2, err2 := k.AddContracts(sdk.WrapSDKContext(ctx), tc.msg) if tc.shouldFail { @@ -446,7 +482,8 @@ func TestKeeper_RemoveContracts(t *testing.T) { t.Log("Execute message") // Check via message handler directly - res, err := k.EditSudoers(ctx, tc.msg) + msgServer := keeper.NewMsgServer(k) + res, err := msgServer.EditSudoers(ctx, tc.msg) // Check via Keeper res2, err2 := k.RemoveContracts(sdk.WrapSDKContext(ctx), tc.msg) if tc.shouldFail { @@ -469,58 +506,3 @@ func TestKeeper_RemoveContracts(t *testing.T) { }) } } - -func TestQuerySudoers(t *testing.T) { - for _, tc := range []struct { - name string - state types.Sudoers - }{ - { - name: "happy 1", - state: types.Sudoers{ - Root: "alice", - Contracts: []string{"contractA", "contractB"}, - }, - }, - - { - name: "happy 2 (empty)", - state: types.Sudoers{ - Root: "", - Contracts: []string(nil), - }, - }, - - { - name: "happy 3", - state: types.Sudoers{ - Root: "", - Contracts: []string{"boop", "blap"}, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - nibiru, ctx := setup() - - nibiru.SudoKeeper.Sudoers.Set(ctx, tc.state) - - req := new(types.QuerySudoersRequest) - resp, err := nibiru.SudoKeeper.QuerySudoers( - sdk.WrapSDKContext(ctx), req, - ) - require.NoError(t, err) - - outSudoers := resp.Sudoers - require.EqualValues(t, tc.state, outSudoers) - }) - } - - t.Run("nil request should error", func(t *testing.T) { - nibiru, ctx := setup() - var req *types.QuerySudoersRequest = nil - _, err := nibiru.SudoKeeper.QuerySudoers( - sdk.WrapSDKContext(ctx), req, - ) - require.Error(t, err) - }) -} diff --git a/x/sudo/keeper/querier.go b/x/sudo/keeper/querier.go index d0ebcc623..a4d224783 100644 --- a/x/sudo/keeper/querier.go +++ b/x/sudo/keeper/querier.go @@ -11,9 +11,17 @@ import ( ) // Ensure the interface is properly implemented at compile time -var _ types.QueryServer = Keeper{} +var _ types.QueryServer = Querier{} -func (k Keeper) QuerySudoers( +type Querier struct { + keeper Keeper +} + +func NewQuerier(k Keeper) types.QueryServer { + return Querier{keeper: k} +} + +func (q Querier) QuerySudoers( goCtx context.Context, req *types.QuerySudoersRequest, ) (resp *types.QuerySudoersResponse, err error) { @@ -22,7 +30,7 @@ func (k Keeper) QuerySudoers( } ctx := sdk.UnwrapSDKContext(goCtx) - sudoers, err := k.Sudoers.Get(ctx) + sudoers, err := q.keeper.Sudoers.Get(ctx) return &types.QuerySudoersResponse{ Sudoers: sudoers, diff --git a/x/sudo/keeper/querier_test.go b/x/sudo/keeper/querier_test.go new file mode 100644 index 000000000..4bfff282b --- /dev/null +++ b/x/sudo/keeper/querier_test.go @@ -0,0 +1,68 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/NibiruChain/nibiru/x/sudo/keeper" + "github.com/NibiruChain/nibiru/x/sudo/types" +) + +func TestQuerySudoers(t *testing.T) { + for _, tc := range []struct { + name string + state types.Sudoers + }{ + { + name: "happy 1", + state: types.Sudoers{ + Root: "alice", + Contracts: []string{"contractA", "contractB"}, + }, + }, + + { + name: "happy 2 (empty)", + state: types.Sudoers{ + Root: "", + Contracts: []string(nil), + }, + }, + + { + name: "happy 3", + state: types.Sudoers{ + Root: "", + Contracts: []string{"boop", "blap"}, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + nibiru, ctx := setup() + + nibiru.SudoKeeper.Sudoers.Set(ctx, tc.state) + + req := new(types.QuerySudoersRequest) + querier := keeper.NewQuerier(nibiru.SudoKeeper) + resp, err := querier.QuerySudoers( + sdk.WrapSDKContext(ctx), req, + ) + require.NoError(t, err) + + outSudoers := resp.Sudoers + require.EqualValues(t, tc.state, outSudoers) + }) + } + + t.Run("nil request should error", func(t *testing.T) { + nibiru, ctx := setup() + var req *types.QuerySudoersRequest = nil + querier := keeper.NewQuerier(nibiru.SudoKeeper) + _, err := querier.QuerySudoers( + sdk.WrapSDKContext(ctx), req, + ) + require.Error(t, err) + }) +} diff --git a/x/sudo/keeper/sudo.go b/x/sudo/keeper/sudo.go deleted file mode 100644 index 557587600..000000000 --- a/x/sudo/keeper/sudo.go +++ /dev/null @@ -1,234 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/NibiruChain/collections" - - "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)), - } -} - -// Ensure the interface is properly implemented at compile time -var _ sudotypes.MsgServer = Keeper{} - -// EditSudoers adds or removes sudo contracts from state. -func (k Keeper) EditSudoers( - goCtx context.Context, msg *sudotypes.MsgEditSudoers, -) (*sudotypes.MsgEditSudoersResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - switch msg.RootAction() { - case sudotypes.AddContracts: - return k.AddContracts(goCtx, msg) - case sudotypes.RemoveContracts: - return k.RemoveContracts(goCtx, msg) - default: - return nil, fmt.Errorf("invalid action type specified on msg: %s", msg) - } -} - -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 -} - -// ———————————————————————————————————————————————————————————————————————————— -// Encoder for the Sudoers type -// ———————————————————————————————————————————————————————————————————————————— - -func SudoersValueEncoder(cdc codec.BinaryCodec) collections.ValueEncoder[sudotypes.Sudoers] { - return collections.ProtoValueEncoder[sudotypes.Sudoers](cdc) -} - -type Sudoers struct { - Root string `json:"root"` - Contracts set.Set[string] `json:"contracts"` -} - -func (sudo Sudoers) String() string { - return sudo.ToPb().String() -} - -func (sudo Sudoers) ToPb() sudotypes.Sudoers { - return sudotypes.Sudoers{ - Root: sudo.Root, - Contracts: sudo.Contracts.ToSlice(), - } -} - -func SudoersFromPb(pbSudoers sudotypes.Sudoers) Sudoers { - return Sudoers{ - Root: pbSudoers.Root, - Contracts: set.New[string](pbSudoers.Contracts...), - } -} - -func SudoersToPb(sudo Sudoers) sudotypes.Sudoers { - return sudo.ToPb() -} - -// ———————————————————————————————————————————————————————————————————————————— -// AddContracts -// ———————————————————————————————————————————————————————————————————————————— - -// AddContracts adds contract addresses to the sudoer set. -func (sudo *Sudoers) AddContracts( - contracts []string, -) (out set.Set[string], err error) { - for _, contractStr := range contracts { - contract, err := sdk.AccAddressFromBech32(contractStr) - if err != nil { - return out, err - } - sudo.Contracts.Add(contract.String()) - } - return sudo.Contracts, err -} - -// 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 := SudoersToPb(Sudoers{Root: sudoersBefore.Root, Contracts: contracts}) - 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 = SudoersToPb(sudoers) - k.Sudoers.Set(ctx, pbSudoers) - - msgResp = new(sudotypes.MsgEditSudoersResponse) - return msgResp, ctx.EventManager().EmitTypedEvent(&sudotypes.EventUpdateSudoers{ - Sudoers: pbSudoers, - Action: msg.Action, - }) -} - -func (sudo *Sudoers) RemoveContracts(contracts []string) { - for _, contract := range contracts { - sudo.Contracts.Remove(contract) - } -} - -// ———————————————————————————————————————————————————————————————————————————— -// Helper Functions -// ———————————————————————————————————————————————————————————————————————————— - -// 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 -} - -// ———————————————————————————————————————————————————————————————————————————— -// Setters - for use in tests -// ———————————————————————————————————————————————————————————————————————————— - -// SetSudoContracts overwrites the state. This function is a convenience -// function for testing with permissioned contracts in other modules.. -func (k Keeper) SetSudoContracts(contracts []string, ctx sdk.Context) { - k.Sudoers.Set(ctx, sudotypes.Sudoers{ - Root: "", - Contracts: contracts, - }) -} diff --git a/x/sudo/module.go b/x/sudo/module.go index ac9fed14d..72f35fbae 100644 --- a/x/sudo/module.go +++ b/x/sudo/module.go @@ -114,8 +114,8 @@ func (am AppModule) Name() string { // RegisterServices registers a GRPC query service to respond to the // module-specific GRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterQueryServer(cfg.QueryServer(), am.keeper) - types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + types.RegisterQueryServer(cfg.QueryServer(), sudokeeper.NewQuerier(am.keeper)) + types.RegisterMsgServer(cfg.MsgServer(), sudokeeper.NewMsgServer(am.keeper)) } // RegisterInvariants registers the capability module's invariants. diff --git a/x/sudo/types/errors.go b/x/sudo/types/errors.go new file mode 100644 index 000000000..41fd742a4 --- /dev/null +++ b/x/sudo/types/errors.go @@ -0,0 +1,7 @@ +package types + +import sdkerrors "cosmossdk.io/errors" + +var ( + ErrUnauthorized = sdkerrors.Register(ModuleName, 2, "unauthorized: missing sudo permissions") +) diff --git a/x/sudo/types/event.pb.go b/x/sudo/types/event.pb.go index 7da3259ee..8dc8afb33 100644 --- a/x/sudo/types/event.pb.go +++ b/x/sudo/types/event.pb.go @@ -84,7 +84,7 @@ func init() { func init() { proto.RegisterFile("nibiru/sudo/v1/event.proto", fileDescriptor_7e6085948b018986) } var fileDescriptor_7e6085948b018986 = []byte{ - // 239 bytes of a gzipped FileDescriptorProto + // 242 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0xcb, 0x4c, 0xca, 0x2c, 0x2a, 0xd5, 0x2f, 0x2e, 0x4d, 0xc9, 0xd7, 0x2f, 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x83, 0xc8, 0xe9, 0x81, 0xe4, 0xf4, 0xca, 0x0c, @@ -95,11 +95,12 @@ var fileDescriptor_7e6085948b018986 = []byte{ 0xfc, 0xd4, 0xa2, 0x62, 0x21, 0x73, 0x2e, 0xf6, 0x62, 0x08, 0x53, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x5c, 0x0f, 0xd5, 0x1d, 0x7a, 0x50, 0x95, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0xc1, 0x54, 0x0b, 0x89, 0x71, 0xb1, 0x25, 0x26, 0x83, 0xec, 0x96, 0x60, 0x52, 0x60, 0xd4, 0xe0, - 0x0c, 0x82, 0xf2, 0x9c, 0x1c, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, + 0x0c, 0x82, 0xf2, 0x9c, 0x5c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, - 0x3d, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0xdf, 0x0f, 0x6c, 0x87, 0x73, - 0x46, 0x62, 0x66, 0x9e, 0x3e, 0xd4, 0xcd, 0x15, 0x10, 0x57, 0x17, 0x24, 0x25, 0xb1, 0x81, 0x1d, - 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x93, 0x41, 0x5c, 0x93, 0x2e, 0x01, 0x00, 0x00, + 0x2b, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0xdf, 0x0f, 0x6c, 0x87, 0x73, + 0x46, 0x62, 0x66, 0x9e, 0x3e, 0xd4, 0xcd, 0x15, 0x10, 0x57, 0x97, 0x54, 0x16, 0xa4, 0x16, 0x27, + 0xb1, 0x81, 0xdd, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xb3, 0x68, 0xee, 0x31, 0x01, + 0x00, 0x00, } func (m *EventUpdateSudoers) Marshal() (dAtA []byte, err error) { diff --git a/x/sudo/types/keys.go b/x/sudo/types/keys.go new file mode 100644 index 000000000..0b929c41c --- /dev/null +++ b/x/sudo/types/keys.go @@ -0,0 +1,6 @@ +package types + +const ( + ModuleName = "sudo" + StoreKey = ModuleName +) diff --git a/x/sudo/types/msgs.go b/x/sudo/types/msgs.go index 3d03432d0..dc3cf81f4 100644 --- a/x/sudo/types/msgs.go +++ b/x/sudo/types/msgs.go @@ -1,30 +1,15 @@ package types import ( - fmt "fmt" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) var _ sdk.Msg = &MsgEditSudoers{} -const ( - ModuleName = "sudo" -) - -var ( - // StoreKey defines the primary module store key. - StoreKey = ModuleName - - // RouterKey is the message route for transactions. - RouterKey = ModuleName -) - // MsgEditSudoers -func (m *MsgEditSudoers) Route() string { return RouterKey } -func (m *MsgEditSudoers) Type() string { return "msg_edit_sudoers" } - func (m *MsgEditSudoers) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil { return err @@ -46,10 +31,6 @@ func (m *MsgEditSudoers) ValidateBasic() error { return nil } -func (m *MsgEditSudoers) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(m)) -} - func (m *MsgEditSudoers) GetSigners() []sdk.AccAddress { signer, err := sdk.AccAddressFromBech32(m.Sender) if err != nil { @@ -61,3 +42,25 @@ func (m *MsgEditSudoers) GetSigners() []sdk.AccAddress { func (m *MsgEditSudoers) RootAction() RootAction { return RootAction(m.Action) } + +// MsgChangeRoot + +func (m *MsgChangeRoot) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +func (m *MsgChangeRoot) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil { + return err + } + + if _, err := sdk.AccAddressFromBech32(m.NewRoot); err != nil { + return err + } + + return nil +} diff --git a/x/sudo/types/query.pb.go b/x/sudo/types/query.pb.go index 5440e39b4..af9c1399e 100644 --- a/x/sudo/types/query.pb.go +++ b/x/sudo/types/query.pb.go @@ -118,7 +118,7 @@ func init() { func init() { proto.RegisterFile("nibiru/sudo/v1/query.proto", fileDescriptor_3c5c8e03d8d77d77) } var fileDescriptor_3c5c8e03d8d77d77 = []byte{ - // 277 bytes of a gzipped FileDescriptorProto + // 280 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0xcb, 0x4c, 0xca, 0x2c, 0x2a, 0xd5, 0x2f, 0x2e, 0x4d, 0xc9, 0xd7, 0x2f, 0x33, 0xd4, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x83, 0xc8, 0xe9, 0x81, 0xe4, 0xf4, 0xca, 0x0c, @@ -131,12 +131,12 @@ var fileDescriptor_3c5c8e03d8d77d77 = []byte{ 0x1e, 0xaa, 0x03, 0xf5, 0xa0, 0x3a, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xa9, 0x36, 0x6a, 0x60, 0xe4, 0x62, 0x05, 0x9b, 0x28, 0x54, 0xce, 0xc5, 0x83, 0x6c, 0xb4, 0x90, 0x32, 0xba, 0x09, 0x58, 0xdc, 0x23, 0xa5, 0x82, 0x5f, 0x11, 0xc4, 0x75, 0x4a, 0x32, 0x4d, 0x97, 0x9f, 0x4c, - 0x66, 0x12, 0x13, 0x12, 0xd1, 0x47, 0xf6, 0x31, 0xd4, 0x09, 0x4e, 0x8e, 0x27, 0x1e, 0xc9, 0x31, + 0x66, 0x12, 0x13, 0x12, 0xd1, 0x47, 0xf6, 0x31, 0xd4, 0x09, 0x4e, 0x2e, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, - 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, 0x9e, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, + 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, 0x95, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0xef, 0x07, 0xd6, 0xe9, 0x9c, 0x91, 0x98, 0x99, 0x07, 0x33, 0xa5, 0x02, 0x62, 0x4e, - 0x41, 0x52, 0x12, 0x1b, 0x38, 0xd0, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x22, 0xb4, - 0xee, 0xb2, 0x01, 0x00, 0x00, + 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0x38, 0xdc, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x97, 0xc1, 0x8d, 0xe0, 0xb5, 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/sudo/types/query.pb.gw.go b/x/sudo/types/query.pb.gw.go index ef9f305e6..42c9a6a60 100644 --- a/x/sudo/types/query.pb.gw.go +++ b/x/sudo/types/query.pb.gw.go @@ -2,7 +2,7 @@ // source: nibiru/sudo/v1/query.proto /* -Package pb is a reverse proxy. +Package types is a reverse proxy. It translates gRPC into RESTful JSON APIs. */ diff --git a/x/sudo/types/state.pb.go b/x/sudo/types/state.pb.go index b9305290e..99b71715a 100644 --- a/x/sudo/types/state.pb.go +++ b/x/sudo/types/state.pb.go @@ -130,7 +130,7 @@ func init() { func init() { proto.RegisterFile("nibiru/sudo/v1/state.proto", fileDescriptor_4b462ff6aaf658cf) } var fileDescriptor_4b462ff6aaf658cf = []byte{ - // 261 bytes of a gzipped FileDescriptorProto + // 264 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0xcb, 0x4c, 0xca, 0x2c, 0x2a, 0xd5, 0x2f, 0x2e, 0x4d, 0xc9, 0xd7, 0x2f, 0x33, 0xd4, 0x2f, 0x2e, 0x49, 0x2c, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x83, 0xc8, 0xe9, 0x81, 0xe4, 0xf4, 0xca, 0x0c, @@ -142,12 +142,12 @@ var fileDescriptor_4b462ff6aaf658cf = []byte{ 0x60, 0x52, 0x60, 0xd6, 0xe0, 0x0c, 0x42, 0x08, 0x58, 0xb1, 0xcc, 0x58, 0x20, 0xcf, 0xa0, 0xe4, 0xce, 0xc5, 0xe3, 0x9e, 0x9a, 0x97, 0x5a, 0x9c, 0x59, 0x1c, 0x0c, 0x72, 0x9c, 0x90, 0x39, 0x17, 0x7b, 0x31, 0xc4, 0x48, 0xb0, 0x51, 0xdc, 0x46, 0xe2, 0x7a, 0xa8, 0x0e, 0xd5, 0x83, 0xda, 0xe8, - 0xc4, 0x72, 0xe2, 0x9e, 0x3c, 0x43, 0x10, 0x4c, 0xb5, 0x93, 0xe3, 0x89, 0x47, 0x72, 0x8c, 0x17, + 0xc4, 0x72, 0xe2, 0x9e, 0x3c, 0x43, 0x10, 0x4c, 0xb5, 0x93, 0xcb, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, - 0x37, 0x1e, 0xcb, 0x31, 0x44, 0xa9, 0xa7, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, + 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x69, 0xa5, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xfb, 0x81, 0xcd, 0x72, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x87, 0x06, 0x4e, 0x05, 0x24, 0x78, - 0x0a, 0x92, 0x92, 0xd8, 0xc0, 0xbe, 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xce, 0x16, 0x05, - 0x26, 0x37, 0x01, 0x00, 0x00, + 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x1e, 0x33, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, + 0x5e, 0x2c, 0x41, 0x6d, 0x3a, 0x01, 0x00, 0x00, } func (m *Sudoers) Marshal() (dAtA []byte, err error) { diff --git a/x/sudo/types/tx.pb.go b/x/sudo/types/tx.pb.go index 86179c98f..6d59db760 100644 --- a/x/sudo/types/tx.pb.go +++ b/x/sudo/types/tx.pb.go @@ -132,34 +132,132 @@ func (m *MsgEditSudoersResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgEditSudoersResponse proto.InternalMessageInfo +// MsgChangeRoot: Msg to update the "Sudoers" state. +type MsgChangeRoot struct { + // Sender: Address for the signer of the transaction. + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` + // NewRoot: New root address. + NewRoot string `protobuf:"bytes,2,opt,name=new_root,json=newRoot,proto3" json:"new_root,omitempty"` +} + +func (m *MsgChangeRoot) Reset() { *m = MsgChangeRoot{} } +func (m *MsgChangeRoot) String() string { return proto.CompactTextString(m) } +func (*MsgChangeRoot) ProtoMessage() {} +func (*MsgChangeRoot) Descriptor() ([]byte, []int) { + return fileDescriptor_a610e3c1609cdcbc, []int{2} +} +func (m *MsgChangeRoot) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChangeRoot) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChangeRoot.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChangeRoot) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChangeRoot.Merge(m, src) +} +func (m *MsgChangeRoot) XXX_Size() int { + return m.Size() +} +func (m *MsgChangeRoot) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChangeRoot.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChangeRoot proto.InternalMessageInfo + +func (m *MsgChangeRoot) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgChangeRoot) GetNewRoot() string { + if m != nil { + return m.NewRoot + } + return "" +} + +// MsgChangeRootResponse indicates the successful execution of MsgChangeRoot. +type MsgChangeRootResponse struct { +} + +func (m *MsgChangeRootResponse) Reset() { *m = MsgChangeRootResponse{} } +func (m *MsgChangeRootResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChangeRootResponse) ProtoMessage() {} +func (*MsgChangeRootResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a610e3c1609cdcbc, []int{3} +} +func (m *MsgChangeRootResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChangeRootResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChangeRootResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChangeRootResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChangeRootResponse.Merge(m, src) +} +func (m *MsgChangeRootResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChangeRootResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChangeRootResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChangeRootResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgEditSudoers)(nil), "nibiru.sudo.v1.MsgEditSudoers") proto.RegisterType((*MsgEditSudoersResponse)(nil), "nibiru.sudo.v1.MsgEditSudoersResponse") + proto.RegisterType((*MsgChangeRoot)(nil), "nibiru.sudo.v1.MsgChangeRoot") + proto.RegisterType((*MsgChangeRootResponse)(nil), "nibiru.sudo.v1.MsgChangeRootResponse") } func init() { proto.RegisterFile("nibiru/sudo/v1/tx.proto", fileDescriptor_a610e3c1609cdcbc) } var fileDescriptor_a610e3c1609cdcbc = []byte{ - // 290 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xc1, 0x4a, 0xc3, 0x30, - 0x18, 0xc7, 0xd7, 0x15, 0x06, 0x8d, 0xb0, 0x43, 0x91, 0x59, 0xeb, 0x08, 0xb3, 0x07, 0xdd, 0x29, - 0x61, 0xfa, 0x04, 0x2a, 0x1e, 0xe7, 0xa1, 0xde, 0x3c, 0x28, 0x69, 0x1b, 0xb2, 0x80, 0xe6, 0x2b, - 0x4d, 0x3a, 0x7a, 0x13, 0x7c, 0x02, 0xc1, 0x97, 0xf2, 0x38, 0xf0, 0xe2, 0x51, 0x5a, 0x1f, 0x44, - 0xda, 0x38, 0x5c, 0x2f, 0xde, 0xf2, 0xe5, 0xf7, 0xff, 0xff, 0x42, 0x3e, 0x74, 0xa0, 0x64, 0x22, - 0x8b, 0x92, 0xea, 0x32, 0x03, 0xba, 0x5e, 0x50, 0x53, 0x91, 0xbc, 0x00, 0x03, 0xfe, 0xd8, 0x02, - 0xd2, 0x02, 0xb2, 0x5e, 0x84, 0xfb, 0x02, 0x04, 0x74, 0x88, 0xb6, 0x27, 0x9b, 0x0a, 0xa7, 0x02, - 0x40, 0x3c, 0x72, 0xca, 0x72, 0x49, 0x99, 0x52, 0x60, 0x98, 0x91, 0xa0, 0xb4, 0xa5, 0xd1, 0x3d, - 0x1a, 0x2f, 0xb5, 0xb8, 0xce, 0xa4, 0xb9, 0x2d, 0x33, 0xe0, 0x85, 0xf6, 0x27, 0x68, 0xc4, 0xd2, - 0x36, 0x12, 0x38, 0x33, 0x67, 0xee, 0xc5, 0xbf, 0x93, 0x3f, 0x45, 0x5e, 0x0a, 0xca, 0x14, 0x2c, - 0x35, 0x3a, 0x18, 0xce, 0xdc, 0xb9, 0x17, 0xff, 0x5d, 0xb4, 0x2d, 0xcd, 0x55, 0xc6, 0x8b, 0xc0, - 0xb5, 0x2d, 0x3b, 0x45, 0x01, 0x9a, 0xf4, 0xfd, 0x31, 0xd7, 0x39, 0x28, 0xcd, 0xcf, 0x9e, 0x91, - 0xbb, 0xd4, 0xc2, 0xaf, 0xd0, 0xde, 0xee, 0xeb, 0x98, 0xf4, 0x3f, 0x45, 0xfa, 0xed, 0xf0, 0xe4, - 0x7f, 0xbe, 0xb5, 0x47, 0xc7, 0x2f, 0x1f, 0xdf, 0x6f, 0xc3, 0xa3, 0xe8, 0x90, 0xee, 0x6e, 0x8f, - 0x67, 0xd2, 0x3c, 0x68, 0x1b, 0xbd, 0xbc, 0x78, 0xaf, 0xb1, 0xb3, 0xa9, 0xb1, 0xf3, 0x55, 0x63, - 0xe7, 0xb5, 0xc1, 0x83, 0x4d, 0x83, 0x07, 0x9f, 0x0d, 0x1e, 0xdc, 0x9d, 0x0a, 0x69, 0x56, 0x65, - 0x42, 0x52, 0x78, 0xa2, 0x37, 0x5d, 0xfd, 0x6a, 0xc5, 0xa4, 0xda, 0xaa, 0x2a, 0x2b, 0xcb, 0x93, - 0x64, 0xd4, 0x2d, 0xf1, 0xfc, 0x27, 0x00, 0x00, 0xff, 0xff, 0xec, 0x57, 0x55, 0x00, 0xa3, 0x01, - 0x00, 0x00, + // 368 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0xcd, 0x4e, 0xea, 0x40, + 0x18, 0x86, 0x29, 0x24, 0x9c, 0xc3, 0x9c, 0x1c, 0x16, 0x8d, 0x42, 0xa9, 0xd8, 0x60, 0x13, 0x0d, + 0x71, 0xd1, 0x09, 0x7a, 0x07, 0xa0, 0x4b, 0x5c, 0xd4, 0x9d, 0x0b, 0xc9, 0xd0, 0x4e, 0x86, 0x49, + 0x74, 0xbe, 0xa6, 0x33, 0x05, 0xdc, 0x7a, 0x05, 0x26, 0xde, 0x94, 0x4b, 0x12, 0x37, 0x2e, 0x0d, + 0x78, 0x0b, 0xee, 0x4d, 0xa7, 0xfc, 0xb4, 0x89, 0x61, 0xd7, 0xe9, 0xf3, 0xbd, 0xcf, 0x3b, 0x33, + 0x2d, 0x6a, 0x0a, 0x3e, 0xe6, 0x71, 0x82, 0x65, 0x12, 0x02, 0x9e, 0xf6, 0xb0, 0x9a, 0x7b, 0x51, + 0x0c, 0x0a, 0xcc, 0x7a, 0x06, 0xbc, 0x14, 0x78, 0xd3, 0x9e, 0x7d, 0xc0, 0x80, 0x81, 0x46, 0x38, + 0x7d, 0xca, 0xa6, 0xec, 0x36, 0x03, 0x60, 0x0f, 0x14, 0x93, 0x88, 0x63, 0x22, 0x04, 0x28, 0xa2, + 0x38, 0x08, 0x99, 0x51, 0xf7, 0x1e, 0xd5, 0x87, 0x92, 0x5d, 0x87, 0x5c, 0xdd, 0x26, 0x21, 0xd0, + 0x58, 0x9a, 0x0d, 0x54, 0x25, 0x41, 0x3a, 0x62, 0x19, 0x1d, 0xa3, 0x5b, 0xf3, 0xd7, 0x2b, 0xb3, + 0x8d, 0x6a, 0x01, 0x08, 0x15, 0x93, 0x40, 0x49, 0xab, 0xdc, 0xa9, 0x74, 0x6b, 0xfe, 0xee, 0x45, + 0x9a, 0x92, 0x54, 0x84, 0x34, 0xb6, 0x2a, 0x59, 0x2a, 0x5b, 0xb9, 0x16, 0x6a, 0x14, 0xfd, 0x3e, + 0x95, 0x11, 0x08, 0x49, 0xdd, 0x3e, 0xfa, 0x3f, 0x94, 0x6c, 0x30, 0x21, 0x82, 0x51, 0x1f, 0x40, + 0xe5, 0x14, 0x46, 0x5e, 0x61, 0xb6, 0xd0, 0x5f, 0x41, 0x67, 0xa3, 0x18, 0x40, 0x59, 0x65, 0x4d, + 0xfe, 0x08, 0x3a, 0x4b, 0x23, 0x6e, 0x13, 0x1d, 0x16, 0x1c, 0x1b, 0xf9, 0xc5, 0xb7, 0x81, 0x2a, + 0x43, 0xc9, 0xcc, 0x39, 0xfa, 0x97, 0x3f, 0x9b, 0xe3, 0x15, 0xaf, 0xcc, 0x2b, 0xee, 0xcd, 0x3e, + 0xdb, 0xcf, 0xb7, 0x7b, 0x3f, 0x79, 0x7e, 0xff, 0x7a, 0x2d, 0x1f, 0xb9, 0x2d, 0x9c, 0xff, 0x36, + 0x34, 0xe4, 0x6a, 0x24, 0xd7, 0x55, 0x0a, 0xa1, 0xdc, 0xd9, 0x8e, 0x7f, 0x11, 0xef, 0xb0, 0x7d, + 0xba, 0x17, 0x6f, 0x6b, 0x3b, 0xba, 0xd6, 0x76, 0xad, 0x42, 0x6d, 0xa0, 0x07, 0xf5, 0xfd, 0xf4, + 0xaf, 0xde, 0x96, 0x8e, 0xb1, 0x58, 0x3a, 0xc6, 0xe7, 0xd2, 0x31, 0x5e, 0x56, 0x4e, 0x69, 0xb1, + 0x72, 0x4a, 0x1f, 0x2b, 0xa7, 0x74, 0x77, 0xce, 0xb8, 0x9a, 0x24, 0x63, 0x2f, 0x80, 0x47, 0x7c, + 0xa3, 0xd3, 0x83, 0x09, 0xe1, 0x62, 0x63, 0x9a, 0x67, 0x2e, 0xf5, 0x14, 0x51, 0x39, 0xae, 0xea, + 0x7f, 0xe3, 0xf2, 0x27, 0x00, 0x00, 0xff, 0xff, 0x0d, 0x5c, 0x76, 0xe7, 0x7a, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -176,6 +274,7 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { // EditSudoers updates the "Sudoers" state EditSudoers(ctx context.Context, in *MsgEditSudoers, opts ...grpc.CallOption) (*MsgEditSudoersResponse, error) + ChangeRoot(ctx context.Context, in *MsgChangeRoot, opts ...grpc.CallOption) (*MsgChangeRootResponse, error) } type msgClient struct { @@ -195,10 +294,20 @@ func (c *msgClient) EditSudoers(ctx context.Context, in *MsgEditSudoers, opts .. return out, nil } +func (c *msgClient) ChangeRoot(ctx context.Context, in *MsgChangeRoot, opts ...grpc.CallOption) (*MsgChangeRootResponse, error) { + out := new(MsgChangeRootResponse) + err := c.cc.Invoke(ctx, "/nibiru.sudo.v1.Msg/ChangeRoot", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // EditSudoers updates the "Sudoers" state EditSudoers(context.Context, *MsgEditSudoers) (*MsgEditSudoersResponse, error) + ChangeRoot(context.Context, *MsgChangeRoot) (*MsgChangeRootResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -208,6 +317,9 @@ type UnimplementedMsgServer struct { func (*UnimplementedMsgServer) EditSudoers(ctx context.Context, req *MsgEditSudoers) (*MsgEditSudoersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EditSudoers not implemented") } +func (*UnimplementedMsgServer) ChangeRoot(ctx context.Context, req *MsgChangeRoot) (*MsgChangeRootResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChangeRoot not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -231,6 +343,24 @@ func _Msg_EditSudoers_Handler(srv interface{}, ctx context.Context, dec func(int return interceptor(ctx, in, info, handler) } +func _Msg_ChangeRoot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChangeRoot) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChangeRoot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nibiru.sudo.v1.Msg/ChangeRoot", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChangeRoot(ctx, req.(*MsgChangeRoot)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "nibiru.sudo.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -239,6 +369,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "EditSudoers", Handler: _Msg_EditSudoers_Handler, }, + { + MethodName: "ChangeRoot", + Handler: _Msg_ChangeRoot_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "nibiru/sudo/v1/tx.proto", @@ -313,6 +447,66 @@ func (m *MsgEditSudoersResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *MsgChangeRoot) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChangeRoot) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChangeRoot) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NewRoot) > 0 { + i -= len(m.NewRoot) + copy(dAtA[i:], m.NewRoot) + i = encodeVarintTx(dAtA, i, uint64(len(m.NewRoot))) + i-- + dAtA[i] = 0x12 + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChangeRootResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChangeRootResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChangeRootResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -356,6 +550,32 @@ func (m *MsgEditSudoersResponse) Size() (n int) { return n } +func (m *MsgChangeRoot) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.NewRoot) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChangeRootResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -558,6 +778,170 @@ func (m *MsgEditSudoersResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgChangeRoot) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChangeRoot: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChangeRoot: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewRoot", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewRoot = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChangeRootResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChangeRootResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChangeRootResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/sudo/types/tx.pb.gw.go b/x/sudo/types/tx.pb.gw.go index 45ec6493a..e2b7b401d 100644 --- a/x/sudo/types/tx.pb.gw.go +++ b/x/sudo/types/tx.pb.gw.go @@ -2,7 +2,7 @@ // source: nibiru/sudo/v1/tx.proto /* -Package pb is a reverse proxy. +Package types is a reverse proxy. It translates gRPC into RESTful JSON APIs. */ @@ -69,6 +69,42 @@ func local_request_Msg_EditSudoers_0(ctx context.Context, marshaler runtime.Mars } +var ( + filter_Msg_ChangeRoot_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Msg_ChangeRoot_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgChangeRoot + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_ChangeRoot_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ChangeRoot(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Msg_ChangeRoot_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgChangeRoot + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_ChangeRoot_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ChangeRoot(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterMsgHandlerServer registers the http handlers for service Msg to "mux". // UnaryRPC :call MsgServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -98,6 +134,29 @@ func RegisterMsgHandlerServer(ctx context.Context, mux *runtime.ServeMux, server }) + mux.Handle("POST", pattern_Msg_ChangeRoot_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Msg_ChangeRoot_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_ChangeRoot_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -159,13 +218,37 @@ func RegisterMsgHandlerClient(ctx context.Context, mux *runtime.ServeMux, client }) + mux.Handle("POST", pattern_Msg_ChangeRoot_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Msg_ChangeRoot_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_ChangeRoot_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( pattern_Msg_EditSudoers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"nibiru", "sudo", "edit_sudoers"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Msg_ChangeRoot_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"nibiru", "sudo", "change_root"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Msg_EditSudoers_0 = runtime.ForwardResponseMessage + + forward_Msg_ChangeRoot_0 = runtime.ForwardResponseMessage ) diff --git a/x/wasm/binding/exec_test.go b/x/wasm/binding/exec_test.go index b1ab3b2eb..359bf7a76 100644 --- a/x/wasm/binding/exec_test.go +++ b/x/wasm/binding/exec_test.go @@ -5,6 +5,9 @@ import ( "testing" "time" + "github.com/NibiruChain/nibiru/x/sudo/keeper" + sudotypes "github.com/NibiruChain/nibiru/x/sudo/types" + "github.com/NibiruChain/nibiru/x/oracle/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -26,6 +29,22 @@ import ( "github.com/NibiruChain/nibiru/x/wasm/binding/wasmbin" ) +// ———————————————————————————————————————————————————————————————————————————— +// Keeper only used for testing, never for production +// ———————————————————————————————————————————————————————————————————————————— +type TestKeeper struct { + keeper.Keeper +} + +// SetSudoContracts overwrites the state. This function is a convenience +// function for testing with permissioned contracts in other modules.. +func (k TestKeeper) SetSudoContracts(contracts []string, ctx sdk.Context) { + k.Sudoers.Set(ctx, sudotypes.Sudoers{ + Root: "", + Contracts: contracts, + }) +} + func TestSuiteExecutor_RunAll(t *testing.T) { suite.Run(t, new(TestSuiteExecutor)) } @@ -65,6 +84,8 @@ type TestSuiteExecutor struct { ctx sdk.Context contractDeployer sdk.AccAddress + keeper TestKeeper + contractPerp sdk.AccAddress contractController sdk.AccAddress contractShifter sdk.AccAddress @@ -93,6 +114,7 @@ func (s *TestSuiteExecutor) SetupSuite() { nibiru, ctx = SetupAllContracts(s.T(), sender, nibiru, ctx) s.nibiru = nibiru s.ctx = ctx + s.keeper = TestKeeper{Keeper: s.nibiru.SudoKeeper} s.contractPerp = ContractMap[wasmbin.WasmKeyPerpBinding] s.contractController = ContractMap[wasmbin.WasmKeyController] @@ -128,7 +150,7 @@ func (s *TestSuiteExecutor) TestOpenAddRemoveClose() { } s.T().Log("Executing with permission should succeed") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{s.contractPerp.String()}, s.ctx, ) @@ -180,14 +202,14 @@ func (s *TestSuiteExecutor) TestOracleParams() { s.Require().Equal(defaultParams, params) s.T().Log("Executing without permission should fail") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{}, s.ctx, ) contractRespBz, err := s.ExecuteAgainstContract(s.contractController, execMsg) s.Errorf(err, "contractRespBz: %s", contractRespBz) s.T().Log("Executing with permission should succeed") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{s.contractController.String()}, s.ctx, ) @@ -353,14 +375,14 @@ func (s *TestSuiteExecutor) TestPegShift() { s.T().Log("Executing with permission should succeed") contract := s.contractShifter - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err := s.ExecuteAgainstContract(contract, execMsg) s.NoErrorf(err, "contractRespBz: %s", contractRespBz) s.T().Log("Executing without permission should fail") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -368,7 +390,7 @@ func (s *TestSuiteExecutor) TestPegShift() { s.T().Log("Executing the wrong contract should fail") contract = s.contractPerp - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -387,14 +409,14 @@ func (s *TestSuiteExecutor) TestDepthShift() { s.T().Log("Executing with permission should succeed") contract := s.contractShifter - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err := s.ExecuteAgainstContract(contract, execMsg) s.NoErrorf(err, "contractRespBz: %s", contractRespBz) s.T().Log("Executing without permission should fail") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -402,7 +424,7 @@ func (s *TestSuiteExecutor) TestDepthShift() { s.T().Log("Executing the wrong contract should fail") contract = s.contractPerp - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -422,14 +444,14 @@ func (s *TestSuiteExecutor) TestInsuranceFundWithdraw() { s.T().Log("Executing should fail since the IF doesn't have funds") contract := s.contractController - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err := s.ExecuteAgainstContract(contract, execMsg) s.Errorf(err, "contractRespBz: %s", contractRespBz) s.T().Log("Executing without permission should fail") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -443,7 +465,7 @@ func (s *TestSuiteExecutor) TestInsuranceFundWithdraw() { sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(420))), ) s.NoError(err) - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -451,7 +473,7 @@ func (s *TestSuiteExecutor) TestInsuranceFundWithdraw() { s.T().Log("Executing the wrong contract should fail") contract = s.contractPerp - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -474,7 +496,7 @@ func (s *TestSuiteExecutor) TestSetMarketEnabled() { } s.T().Logf("Execute - happy %v: market: %s", testIdx, market.Pair) - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err := s.ExecuteAgainstContract(contract, execMsg) @@ -486,7 +508,7 @@ func (s *TestSuiteExecutor) TestSetMarketEnabled() { } s.T().Log("Executing without permission should fail") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{}, s.ctx, ) contractRespBz, err := s.ExecuteAgainstContract(contract, execMsg) @@ -494,7 +516,7 @@ func (s *TestSuiteExecutor) TestSetMarketEnabled() { s.T().Log("Executing the wrong contract should fail") contract = s.contractPerp - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -515,7 +537,7 @@ func (s *TestSuiteExecutor) TestCreateMarket() { } s.T().Logf("Execute - happy: market: %s", pair) - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err := s.ExecuteAgainstContract(contract, execMsg) @@ -528,7 +550,7 @@ func (s *TestSuiteExecutor) TestCreateMarket() { s.EqualValues(pair, market.Pair) s.T().Log("Executing without permission should fail") - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) @@ -536,7 +558,7 @@ func (s *TestSuiteExecutor) TestCreateMarket() { s.T().Log("Executing the wrong contract should fail") contract = s.contractPerp - s.nibiru.SudoKeeper.SetSudoContracts( + s.keeper.SetSudoContracts( []string{contract.String()}, s.ctx, ) contractRespBz, err = s.ExecuteAgainstContract(contract, execMsg) From 60deca723f99a252cbd9867abb980f1ab1a46bd0 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Wed, 12 Jul 2023 03:59:02 -0400 Subject: [PATCH 8/9] refactor(perp): reverse market order (#1500) * remove commented code * test(perp): reverse market order with bad debt * test(perp): reverse market order with short position * test(perp): more test cases * Update CHANGELOG.md --- CHANGELOG.md | 1 + x/perp/v2/integration/action/position.go | 6 +- x/perp/v2/integration/assertion/position.go | 10 - x/perp/v2/keeper/clearing_house.go | 49 ++-- x/perp/v2/keeper/clearing_house_test.go | 300 ++++++++++++++++++-- x/perp/v2/keeper/grpc_query.go | 5 - 6 files changed, 291 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db6f9a14d..bfc13895b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#1466](https://github.com/NibiruChain/nibiru/pull/1466) - refactor(perp): `PositionLiquidatedEvent` * [#1494](https://github.com/NibiruChain/nibiru/pull/1494) - feat: create cli to add sudo account into genesis * [#1493](https://github.com/NibiruChain/nibiru/pull/1493) - fix(perp): allow `ClosePosition` when there is bad debt +* [#1500](https://github.com/NibiruChain/nibiru/pull/1500) - refactor(perp): clean up reverse market order mechanics ### Features diff --git a/x/perp/v2/integration/action/position.go b/x/perp/v2/integration/action/position.go index 65ec26869..1d6546217 100644 --- a/x/perp/v2/integration/action/position.go +++ b/x/perp/v2/integration/action/position.go @@ -122,11 +122,7 @@ type MarketOrderResponseChecker func(resp *types.PositionResp) error // MarketOrderResp_PositionShouldBeEqual checks that the position included in the response is equal to the expected position response. func MarketOrderResp_PositionShouldBeEqual(expected types.Position) MarketOrderResponseChecker { return func(actual *types.PositionResp) error { - if err := types.PositionsAreEqual(&expected, &actual.Position); err != nil { - return err - } - - return nil + return types.PositionsAreEqual(&expected, &actual.Position) } } diff --git a/x/perp/v2/integration/assertion/position.go b/x/perp/v2/integration/assertion/position.go index b969681e8..2e61d85eb 100644 --- a/x/perp/v2/integration/assertion/position.go +++ b/x/perp/v2/integration/assertion/position.go @@ -59,16 +59,6 @@ func Position_PositionShouldBeEqualTo(expectedPosition types.Position) PositionC } } -// Position_PositionSizeShouldBeEqualTo checks if the position size is equal to the expected position size -func Position_PositionSizeShouldBeEqualTo(expectedSize sdk.Dec) PositionChecker { - return func(position types.Position) error { - if position.Size_.Equal(expectedSize) { - return nil - } - return fmt.Errorf("expected position size %s, got %s", expectedSize, position.Size_.String()) - } -} - type positionShouldNotExist struct { Account sdk.AccAddress Pair asset.Pair diff --git a/x/perp/v2/keeper/clearing_house.go b/x/perp/v2/keeper/clearing_house.go index ab668931b..b15bba7e4 100644 --- a/x/perp/v2/keeper/clearing_house.go +++ b/x/perp/v2/keeper/clearing_house.go @@ -98,11 +98,11 @@ func (k Keeper) MarketOrder( } // check bad debt - if !positionResp.BadDebt.IsZero() { - return nil, types.ErrBadDebt.Wrapf("bad debt %s", positionResp.BadDebt) - } - if !positionResp.Position.Size_.IsZero() { + if !positionResp.BadDebt.IsZero() { + return nil, types.ErrBadDebt.Wrapf("position has bad debt %s", positionResp.BadDebt) + } + err = k.checkMarginRatio(ctx, market, *updatedAMM, positionResp.Position) if err != nil { return nil, err @@ -425,7 +425,8 @@ func (k Keeper) closeAndOpenReversePosition( } if closePositionResp.BadDebt.IsPositive() { - return nil, nil, fmt.Errorf("underwater position") + // if there's already bad debt, then we don't allow the user to continue and just early return + return updatedAMM, closePositionResp, nil } reverseNotionalValue := leverage.Mul(quoteAssetAmount) @@ -438,37 +439,29 @@ func (k Keeper) closeAndOpenReversePosition( closePositionResp.ExchangedNotionalValue, reverseNotionalValue) } - var sideToTake types.Direction + var dir types.Direction // flipped since we are going against the current position if existingPosition.Size_.IsPositive() { - sideToTake = types.Direction_SHORT + dir = types.Direction_SHORT } else { - sideToTake = types.Direction_LONG + dir = types.Direction_LONG } - _, sizeAvailable, err := k.SwapQuoteAsset( - ctx, - market, - amm, - sideToTake, - remainingReverseNotionalValue, - baseAmtLimit, - ) + // check if it's worth continuing with the increase position + quoteReserveAmt := updatedAMM.FromQuoteAssetToReserve(remainingReverseNotionalValue) + possibleNextSize, err := updatedAMM.GetBaseReserveAmt(quoteReserveAmt, dir) if err != nil { return nil, nil, err } - - if sizeAvailable.IsZero() { - // nothing to do + if possibleNextSize.IsZero() { + // nothing to do, early return return updatedAMM, closePositionResp, nil } - var increasePositionResp *types.PositionResp - updatedBaseAmtLimit := baseAmtLimit if baseAmtLimit.IsPositive() { - updatedBaseAmtLimit = baseAmtLimit.Sub(closePositionResp.ExchangedPositionSize.Abs()) + baseAmtLimit = baseAmtLimit.Sub(closePositionResp.ExchangedPositionSize.Abs()) } - if updatedBaseAmtLimit.IsNegative() { + if baseAmtLimit.IsNegative() { return nil, nil, fmt.Errorf( "position size changed by greater than the specified base limit: %s", baseAmtLimit, @@ -480,23 +473,19 @@ func (k Keeper) closeAndOpenReversePosition( existingPosition.Pair, trader, ) - updatedAMM, increasePositionResp, err = k.increasePosition( + updatedAMM, increasePositionResp, err := k.increasePosition( ctx, market, *updatedAMM, newPosition, - sideToTake, + dir, remainingReverseNotionalValue, - updatedBaseAmtLimit, + baseAmtLimit, leverage, ) if err != nil { return nil, nil, err } - err = k.checkMarginRatio(ctx, market, amm, increasePositionResp.Position) - if err != nil { - return nil, nil, err - } positionResp = &types.PositionResp{ Position: increasePositionResp.Position, diff --git a/x/perp/v2/keeper/clearing_house_test.go b/x/perp/v2/keeper/clearing_house_test.go index dfebfa0f1..880e1ddc3 100644 --- a/x/perp/v2/keeper/clearing_house_test.go +++ b/x/perp/v2/keeper/clearing_house_test.go @@ -17,7 +17,6 @@ import ( . "github.com/NibiruChain/nibiru/x/common/testutil/assertion" "github.com/NibiruChain/nibiru/x/common/testutil/mock" "github.com/NibiruChain/nibiru/x/common/testutil/testapp" - . "github.com/NibiruChain/nibiru/x/oracle/integration/action" . "github.com/NibiruChain/nibiru/x/perp/v2/integration/action" . "github.com/NibiruChain/nibiru/x/perp/v2/integration/assertion" @@ -35,7 +34,6 @@ func TestMarketOrder(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1020)))), ). When( @@ -97,7 +95,6 @@ func TestMarketOrder(t *testing.T) { SetBlockNumber(1), SetBlockTime(startBlockTime), CreateCustomMarket(pairBtcNusd), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(2040)))), MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -147,12 +144,47 @@ func TestMarketOrder(t *testing.T) { }), ), + TC("existing long position, go more long but there's bad debt"). + Given( + CreateCustomMarket( + pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.89")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(18)))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithMargin(sdk.NewDec(1_000)), + WithSize(sdk.NewDec(10_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + MoveToNextBlock(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1), sdk.NewDec(1), sdk.ZeroDec(), + types.ErrMarginRatioTooLow, + ), + ). + Then( + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + TraderAddress: alice.String(), + Pair: pairBtcNusd, + Size_: sdk.NewDec(10_000), + Margin: sdk.NewDec(1_000), + OpenNotional: sdk.NewDec(10_000), + LatestCumulativePremiumFraction: sdk.ZeroDec(), + LastUpdatedBlockNumber: 0, + })), + ), + TC("existing long position, decrease a bit"). Given( CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1030)))), MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -203,12 +235,47 @@ func TestMarketOrder(t *testing.T) { }), ), + TC("existing long position, decrease a bit but there's bad debt"). + Given( + CreateCustomMarket( + pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.89")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(18)))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithMargin(sdk.NewDec(1_000)), + WithSize(sdk.NewDec(10_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + MoveToNextBlock(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1), sdk.NewDec(1), sdk.ZeroDec(), + types.ErrMarginRatioTooLow, + ), + ). + Then( + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + TraderAddress: alice.String(), + Pair: pairBtcNusd, + Size_: sdk.NewDec(10_000), + Margin: sdk.NewDec(1_000), + OpenNotional: sdk.NewDec(10_000), + LatestCumulativePremiumFraction: sdk.ZeroDec(), + LastUpdatedBlockNumber: 0, + })), + ), + TC("existing long position, decrease a lot"). Given( CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(4080)))), MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -259,12 +326,75 @@ func TestMarketOrder(t *testing.T) { }), ), + TC("existing long position, decrease a lot but there's bad debt"). + Given( + CreateCustomMarket( + pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("0.89")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(18)))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithMargin(sdk.NewDec(1_000)), + WithSize(sdk.NewDec(10_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + MoveToNextBlock(), + MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(3000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrderResp_PositionShouldBeEqual( + types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 2, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + ), + MarketOrderResp_ExchangeNotionalValueShouldBeEqual(sdk.MustNewDecFromStr("8899.999911000000890000")), + MarketOrderResp_ExchangedPositionSizeShouldBeEqual(sdk.MustNewDecFromStr("-10000")), + MarketOrderResp_BadDebtShouldBeEqual(sdk.MustNewDecFromStr("102.000088999999110000")), + MarketOrderResp_FundingPaymentShouldBeEqual(sdk.NewDec(2)), + MarketOrderResp_RealizedPnlShouldBeEqual(sdk.MustNewDecFromStr("-1100.000088999999110000")), + MarketOrderResp_UnrealizedPnlAfterShouldBeEqual(sdk.ZeroDec()), + MarketOrderResp_MarginToVaultShouldBeEqual(sdk.ZeroDec()), + MarketOrderResp_PositionNotionalShouldBeEqual(sdk.ZeroDec()), + ), + ). + Then( + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 2, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.ZeroDec(), + RealizedPnl: sdk.MustNewDecFromStr("-1100.000088999999110000"), + BadDebt: sdk.NewInt64Coin(denoms.NUSD, 102), + FundingPayment: sdk.NewDec(2), + TransactionFee: sdk.NewInt64Coin(denoms.NUSD, 18), // 20 bps + BlockHeight: 2, + MarginToUser: sdk.NewInt(-18), + ChangeReason: types.ChangeReason_MarketOrder, + }), + ), + TC("new short position"). Given( CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1020)))), ). When( @@ -328,7 +458,6 @@ func TestMarketOrder(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(2040)))), MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -379,12 +508,46 @@ func TestMarketOrder(t *testing.T) { }), ), + TC("existing short position, go more short but there's bad debt"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("1.11")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(22)))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithMargin(sdk.NewDec(1_000)), + WithSize(sdk.NewDec(-10_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + MoveToNextBlock(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1), sdk.NewDec(1), sdk.ZeroDec(), + types.ErrMarginRatioTooLow, + ), + ). + Then( + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + TraderAddress: alice.String(), + Pair: pairBtcNusd, + Size_: sdk.NewDec(-10_000), + Margin: sdk.NewDec(1_000), + OpenNotional: sdk.NewDec(10_000), + LatestCumulativePremiumFraction: sdk.ZeroDec(), + LastUpdatedBlockNumber: 0, + })), + ), + TC("existing short position, decrease a bit"). Given( CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1030)))), MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -435,12 +598,46 @@ func TestMarketOrder(t *testing.T) { }), ), + TC("existing short position, decrease a bit but there's bad debt"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("1.11")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(22)))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithMargin(sdk.NewDec(1_000)), + WithSize(sdk.NewDec(-10_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + MoveToNextBlock(), + MarketOrderFails(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(1), sdk.NewDec(1), sdk.ZeroDec(), + types.ErrMarginRatioTooLow, + ), + ). + Then( + PositionShouldBeEqual(alice, pairBtcNusd, Position_PositionShouldBeEqualTo(types.Position{ + TraderAddress: alice.String(), + Pair: pairBtcNusd, + Size_: sdk.NewDec(-10_000), + Margin: sdk.NewDec(1_000), + OpenNotional: sdk.NewDec(10_000), + LatestCumulativePremiumFraction: sdk.ZeroDec(), + LastUpdatedBlockNumber: 0, + })), + ), + TC("existing short position, decrease a lot"). Given( CreateCustomMarket(pairBtcNusd), SetBlockNumber(1), SetBlockTime(startBlockTime), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(4080)))), MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(1000), sdk.NewDec(10), sdk.ZeroDec()), ). @@ -491,12 +688,75 @@ func TestMarketOrder(t *testing.T) { }), ), + TC("existing short position, decrease a lot but there's bad debt"). + Given( + CreateCustomMarket(pairBtcNusd, + WithPricePeg(sdk.MustNewDecFromStr("1.11")), + WithLatestMarketCPF(sdk.MustNewDecFromStr("0.0002")), + ), + SetBlockNumber(1), + SetBlockTime(startBlockTime), + FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(22)))), + InsertPosition( + WithPair(pairBtcNusd), + WithTrader(alice), + WithMargin(sdk.NewDec(1_000)), + WithSize(sdk.NewDec(-10_000)), + WithOpenNotional(sdk.NewDec(10_000)), + ), + ). + When( + MoveToNextBlock(), + MarketOrder(alice, pairBtcNusd, types.Direction_LONG, sdk.NewInt(3000), sdk.NewDec(10), sdk.ZeroDec(), + MarketOrderResp_PositionShouldBeEqual( + types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 2, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + ), + MarketOrderResp_ExchangeNotionalValueShouldBeEqual(sdk.MustNewDecFromStr("11100.000111000001110000")), + MarketOrderResp_ExchangedPositionSizeShouldBeEqual(sdk.MustNewDecFromStr("10000")), + MarketOrderResp_BadDebtShouldBeEqual(sdk.MustNewDecFromStr("98.000111000001110000")), + MarketOrderResp_FundingPaymentShouldBeEqual(sdk.NewDec(-2)), + MarketOrderResp_RealizedPnlShouldBeEqual(sdk.MustNewDecFromStr("-1100.000111000001110000")), + MarketOrderResp_UnrealizedPnlAfterShouldBeEqual(sdk.ZeroDec()), + MarketOrderResp_MarginToVaultShouldBeEqual(sdk.ZeroDec()), + MarketOrderResp_PositionNotionalShouldBeEqual(sdk.ZeroDec()), + ), + ). + Then( + PositionChangedEventShouldBeEqual(&types.PositionChangedEvent{ + FinalPosition: types.Position{ + Pair: pairBtcNusd, + TraderAddress: alice.String(), + Margin: sdk.ZeroDec(), + OpenNotional: sdk.ZeroDec(), + Size_: sdk.ZeroDec(), + LastUpdatedBlockNumber: 2, + LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0.0002"), + }, + PositionNotional: sdk.ZeroDec(), + RealizedPnl: sdk.MustNewDecFromStr("-1100.000111000001110000"), + BadDebt: sdk.NewInt64Coin(denoms.NUSD, 98), + FundingPayment: sdk.NewDec(-2), + TransactionFee: sdk.NewInt64Coin(denoms.NUSD, 22), // 20 bps + BlockHeight: 2, + // exchangedMargin = - marginToVault - transferredFee + MarginToUser: sdk.NewInt(-22), + ChangeReason: types.ChangeReason_MarketOrder, + }), + ), + TC("user has insufficient funds"). Given( CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(99)))), ). When( @@ -513,7 +773,6 @@ func TestMarketOrder(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(47_619_047_619), sdk.OneDec(), sdk.ZeroDec()), SetMarketEnabled(pairBtcNusd, false), @@ -530,7 +789,6 @@ func TestMarketOrder(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), SetMarketEnabled(pairBtcNusd, false), ). @@ -547,7 +805,6 @@ func TestMarketOrder(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), MarketOrder(alice, pairBtcNusd, types.Direction_SHORT, sdk.NewInt(50_000), sdk.OneDec(), sdk.ZeroDec()), SetMarketEnabled(pairBtcNusd, false), @@ -565,7 +822,6 @@ func TestMarketOrder(t *testing.T) { Given( SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), ). When( @@ -581,7 +837,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(47_714_285_715)))), ). When( @@ -597,7 +852,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( @@ -613,7 +867,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( @@ -629,7 +882,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcNusd, sdk.NewDec(25_000)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(20_000_000_000+20_000_000)))), ). When( @@ -645,7 +897,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( @@ -660,7 +911,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( @@ -676,7 +926,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25001.0112"))), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( @@ -692,7 +941,6 @@ func TestMarketOrder(t *testing.T) { SetBlockTime(startBlockTime), SetBlockNumber(1), CreateCustomMarket(pairBtcNusd, WithPricePeg(sdk.MustNewDecFromStr("25000"))), - SetOraclePrice(pairBtcNusd, sdk.NewDec(2)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1e6)))), ). When( @@ -1563,7 +1811,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100000)))), ). @@ -1579,7 +1826,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100000)))), ). @@ -1595,7 +1841,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). @@ -1616,7 +1861,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). @@ -1638,7 +1882,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). @@ -1659,7 +1902,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundModule(types.PerpEFModuleAccount, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(100_000_000)))), ). @@ -1681,7 +1923,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundAccount(bob, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), ). @@ -1713,7 +1954,6 @@ func TestUpdateSwapInvariant(t *testing.T) { CreateCustomMarket(pairBtcNusd), SetBlockTime(startBlockTime), SetBlockNumber(1), - SetOraclePrice(pairBtcNusd, sdk.NewDec(1)), FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), FundAccount(bob, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(10_200_000_000)))), ). diff --git a/x/perp/v2/keeper/grpc_query.go b/x/perp/v2/keeper/grpc_query.go index 53d1f7ea5..fa0c9ebdd 100644 --- a/x/perp/v2/keeper/grpc_query.go +++ b/x/perp/v2/keeper/grpc_query.go @@ -95,11 +95,6 @@ func (q queryServer) position(ctx sdk.Context, pair asset.Pair, trader sdk.AccAd } unrealizedPnl := UnrealizedPnl(position, positionNotional) - // marginRatioMark, err := q.k.GetMarginRatio(ctx, market, amm, position, types.MarginCalculationPriceOption_MAX_PNL) - // if err != nil { - // return nil, err - // } - return types.QueryPositionResponse{ Position: position, PositionNotional: positionNotional, From ab97b8f702d30f0d47fd2d6fdc5c15cbe27bb44a Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:35:47 +0200 Subject: [PATCH 9/9] feat(scripts): python buf gen + localnet.sh improvements (#1501) * feat(proto): python buf template * docs: comments to help with running proto gen in python * feat(proto): python buf generation * feat(localnet.sh): (1) Makes script run while offline. (2) Adds --no-build option for external usage * changelog --- CHANGELOG.md | 4 +- contrib/scripts/localnet.sh | 94 ++++++++++++++++++++++++++++--------- proto/buf.gen.py.yaml | 28 +++++++++++ 3 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 proto/buf.gen.py.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index bfc13895b..06aea3f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ 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 +* [#1501](https://github.com/NibiruChain/nibiru/pull/1501) - feat(localnet.sh): (1) Make it possible to run while offline. (2) Implement --no-build option to use the script with the current `nibid` installed. +* [#1501](https://github.com/NibiruChain/nibiru/pull/1501) - feat(proto): add Python buf generation logic for py-sdk ### Bug Fixes @@ -618,4 +620,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Testing * [#695](https://github.com/NibiruChain/nibiru/pull/695) Add `OpenPosition` integration tests. -* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods. \ No newline at end of file +* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods. diff --git a/contrib/scripts/localnet.sh b/contrib/scripts/localnet.sh index 62e6aac05..868ea94b0 100755 --- a/contrib/scripts/localnet.sh +++ b/contrib/scripts/localnet.sh @@ -34,14 +34,39 @@ echo_success() { echo "${reset}" } -echo_info "Building from source..." -if make install; then - echo_success "Successfully built binary" -else - echo_error "Could not build binary. Failed to make install." - exit 1 +# Flag parsing: --flag-name (BASH_VAR_NAME) +# +# --no-build ($FLAG_NO_BUILD): toggles whether to build from source. The default +# behavior of the script is to run make install. +FLAG_NO_BUILD=false + +build_from_source() { + echo_info "Building from source..." + if make install; then + echo_success "Successfully built binary" + else + echo_error "Could not build binary. Failed to make install." + exit 1 + fi +} + +echo_info "Parsing flags for the script..." + +# Iterate over all arguments to the script +for arg in "$@" +do + if [ "$arg" == "--no-build" ] ; then + FLAG_NO_BUILD=true + fi +done + + +# Check if FLAG_NO_BUILD was set to true +if ! $FLAG_NO_BUILD ; then + build_from_source fi + # Set localnet settings BINARY="nibid" CHAIN_ID="nibiru-localnet-0" @@ -53,11 +78,16 @@ CHAIN_DIR="$HOME/.nibid" echo "CHAIN_DIR: $CHAIN_DIR" echo "CHAIN_ID: $CHAIN_ID" + SEDOPTION="" if [[ "$OSTYPE" == "darwin"* ]]; then SEDOPTION="''" fi +# ------------------------------------------------------------------------ +echo_info "Successfully finished localnet script setup." +# ------------------------------------------------------------------------ + # Stop nibid if it is already running if pgrep -x "$BINARY" >/dev/null; then echo_error "Terminating $BINARY..." @@ -139,15 +169,21 @@ else fi echo_info "Adding genesis accounts..." -echo "$MNEMONIC" | $BINARY keys add validator --recover -if $BINARY add-genesis-account $($BINARY keys show validator -a) $GENESIS_COINS; then - echo_success "Successfully added genesis accounts" + +val_key_name="validator" + +echo "$MNEMONIC" | $BINARY keys add $val_key_name --recover +if $BINARY add-genesis-account $($BINARY keys show $val_key_name -a) $GENESIS_COINS; then + echo_success "Successfully added genesis account: $val_key_name" else - echo_error "Failed to add genesis accounts" + echo_error "Failed to add genesis account: $val_key_name" fi +val_address=$($BINARY keys list | jq -r '.[] | select(.name == "validator") | .address') +val_address=${val_address:-"nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl"} + echo_info "Adding gentx validator..." -if $BINARY genesis gentx validator 900000000unibi --chain-id $CHAIN_ID; then +if $BINARY genesis gentx $val_key_name 900000000unibi --chain-id $CHAIN_ID; then echo_success "Successfully added gentx" else echo_error "Failed to add gentx" @@ -176,6 +212,13 @@ add_genesis_param() { mv $CHAIN_DIR/config/tmp_genesis.json $CHAIN_DIR/config/genesis.json } +add_genesis_reserve_amt() { + local M=1000000 + local num_users=300000 + local faucet_nusd_amt=100 + local reserve_amt=$(($num_users * $faucet_nusd_amt * $M)) + echo "$reserve_amt" +} add_genesis_perp_markets_with_coingecko_prices() { local temp_json_fname="tmp_market_prices.json" @@ -184,11 +227,7 @@ add_genesis_perp_markets_with_coingecko_prices() { -H 'accept: application/json' \ >$temp_json_fname - local M=1000000 - - local num_users=300000 - local faucet_nusd_amt=100 - local reserve_amt=$(($num_users * $faucet_nusd_amt * $M)) + local reserve_amt=$(add_genesis_reserve_amt) price_btc=$(cat tmp_market_prices.json | jq -r '.bitcoin.usd') price_btc=${price_btc%.*} @@ -202,7 +241,7 @@ add_genesis_perp_markets_with_coingecko_prices() { else echo_error "Command \"$*\" failed." exit 1 - fi + fi } nibid genesis add-genesis-perp-market --pair=ubtc:unusd --sqrt-depth=$reserve_amt --price-multiplier=$price_btc @@ -222,20 +261,33 @@ add_genesis_perp_markets_with_coingecko_prices() { rm -f $temp_json_fname } +add_genesis_perp_markets_offline() { + local reserve_amt=$(add_genesis_reserve_amt) + price_btc="20000" + price_eth="2000" + nibid genesis add-genesis-perp-market --pair=ubtc:unusd --sqrt-depth=$reserve_amt --price-multiplier=$price_btc + nibid genesis add-genesis-perp-market --pair=ueth:unusd --sqrt-depth=$reserve_amt --price-multiplier=$price_eth +} + echo_info "Configuring genesis params" + if add_genesis_perp_markets_with_coingecko_prices; then echo_success "set perp markets with coingecko prices" +elif add_genesis_perp_markets_offline; then + echo_success "set perp markets with offline defaults" else echo_error "failed to set genesis perp markets" exit 1 fi # set validator as sudoer -$BINARY genesis add-sudo-root-account "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl" +add_genesis_param '.app_state.sudo.sudoers.root = "'"$val_address"'"' -add_genesis_param '.app_state.oracle.params.twap_lookback_window = "900s"' -add_genesis_param '.app_state.oracle.params.vote_period = "10"' -add_genesis_param '.app_state.oracle.params.min_voters = "1"' +# hack for localnet since we don't have a pricefeeder yet +add_genesis_param '.app_state.oracle.exchange_rates[0].pair = "ubtc:unusd"' +add_genesis_param '.app_state.oracle.exchange_rates[0].exchange_rate = "'"$price_btc"'"' +add_genesis_param '.app_state.oracle.exchange_rates[1].pair = "ueth:unusd"' +add_genesis_param '.app_state.oracle.exchange_rates[1].exchange_rate = "'"$price_eth"'"' # Start the network echo_info "Starting $CHAIN_ID in $CHAIN_DIR..." diff --git a/proto/buf.gen.py.yaml b/proto/buf.gen.py.yaml new file mode 100644 index 000000000..a5b46655f --- /dev/null +++ b/proto/buf.gen.py.yaml @@ -0,0 +1,28 @@ +# buf.gen.py.yaml +# +# Run with: +# +# proto_dir="proto" +# out_dir="gen_python" +# buf generate $proto_dir --template proto/buf.gen.py.yaml -o $out_dir +version: v1 +plugins: + - plugin: buf.build/protocolbuffers/python:v21.9 + out: . + # The mypy extension generated .pyi Python interface definitions. + # These are important for the LSP to understand which classes and + # functions the proto generation makes available. + # + # See: + # - https://mypy.readthedocs.io/en/stable/stubs.html + # - https://buf.build/community/nipunn1313-mypy + - plugin: buf.build/community/nipunn1313-mypy:v3.4.0 + out: . + # GRPC stubs are necessary for creating grpc.UnaryUnaryMultiCallable + # instances, which enable GRPC clients in Python for Nibiru. + # + # See: + # - https://grpc.github.io/grpc/python/grpc.html + # - https://buf.build/grpc/python + - plugin: buf.build/grpc/python:v1.56.0 + out: .