diff --git a/x/exchange/client/cli/cli_test.go b/x/exchange/client/cli/cli_test.go index 884a98fdb8..98d25575d5 100644 --- a/x/exchange/client/cli/cli_test.go +++ b/x/exchange/client/cli/cli_test.go @@ -53,6 +53,7 @@ type CmdTestSuite struct { keyring keyring.Keyring keyringDir string accountAddrs []sdk.AccAddress + feeDenom string addr0 sdk.AccAddress addr1 sdk.AccAddress @@ -79,6 +80,7 @@ func (s *CmdTestSuite) SetupSuite() { s.cfg.NumValidators = 1 s.cfg.ChainID = antewrapper.SimAppChainID s.cfg.TimeoutCommit = 500 * time.Millisecond + s.feeDenom = pioconfig.GetProvenanceConfig().FeeDenom s.generateAccountsWithKeyring(10) s.addr0 = s.accountAddrs[0] @@ -170,7 +172,6 @@ func (s *CmdTestSuite) SetupSuite() { AcceptingCommitments: true, CommitmentSettlementBips: 50, IntermediaryDenom: "cherry", - FeeCreateCommitmentFlat: []sdk.Coin{sdk.NewInt64Coin("peach", 15)}, }, exchange.Market{ MarketId: 5, @@ -229,7 +230,7 @@ func (s *CmdTestSuite) SetupSuite() { ReqAttrCreateCommitment: []string{"committer.kyc"}, }, exchange.Market{ - // This market has an invalid setup. Don't mess with it. + // This market has an invalid setup. Don't use it for anything else. MarketId: 421, MarketDetails: exchange.MarketDetails{Name: "Broken"}, FeeSellerSettlementRatios: []exchange.FeeRatio{ @@ -239,6 +240,9 @@ func (s *CmdTestSuite) SetupSuite() { {Price: sdk.NewInt64Coin("peach", 56), Fee: sdk.NewInt64Coin("peach", 1)}, {Price: sdk.NewInt64Coin("plum", 57), Fee: sdk.NewInt64Coin("plum", 1)}, }, + AccessGrants: []exchange.AccessGrant{ + {Address: s.addr1.String(), Permissions: exchange.AllPermissions()}, + }, }, ) @@ -255,7 +259,6 @@ func (s *CmdTestSuite) SetupSuite() { exchangeGen.Commitments = append(exchangeGen.Commitments, *com) } } - exchangeGen.Commitments = append(exchangeGen.Commitments, exchange.Commitment{ Account: s.addr1.String(), MarketId: 421, @@ -292,7 +295,7 @@ func (s *CmdTestSuite) SetupSuite() { markerGen.NetAssetValues = append(markerGen.NetAssetValues, []markertypes.MarkerNetAssetValues{ { Address: cherryMarker.Address, - NetAssetValues: []markertypes.NetAssetValue{{Price: sdk.NewInt64Coin(pioconfig.GetProvenanceConfig().FeeDenom, 100), Volume: 1}}, + NetAssetValues: []markertypes.NetAssetValue{{Price: s.feeCoin(100), Volume: 1}}, }, { Address: appleMarker.Address, @@ -314,7 +317,8 @@ func (s *CmdTestSuite) SetupSuite() { // Any initial holds for an account are added to this so that // this is what's available to each at the start of the unit tests. balance := sdk.NewCoins( - sdk.NewInt64Coin(s.cfg.BondDenom, 1_000_000_000), + s.bondCoin(1_000_000_000), + s.feeCoin(1_000_000_000), sdk.NewInt64Coin("acorn", 1_000_000_000), sdk.NewInt64Coin("apple", 1_000_000_000), sdk.NewInt64Coin("peach", 1_000_000_000), @@ -464,6 +468,8 @@ type txCmdTestCase struct { preRun func() ([]string, func(*sdk.TxResponse)) // args are the arguments to provide to the command. args []string + // addedFees is any fees to add to the default 10 amount. + addedFees sdk.Coins // expInErr are strings to expect in an error from the cmd. // Errors that come from the endpoint will not be here; use expInRawLog for those. expInErr []string @@ -489,10 +495,15 @@ func (s *CmdTestSuite) runTxCmdTestCase(tc txCmdTestCase) { cmd := cli.CmdTx() + fees := s.bondCoins(10) + if !tc.addedFees.IsZero() { + fees = fees.Add(tc.addedFees...) + } + args := append(tc.args, extraArgs...) args = append(args, "--"+flags.FlagGas, "250000", - "--"+flags.FlagFees, s.bondCoins(10).String(), + "--"+flags.FlagFees, fees.String(), "--"+flags.FlagBroadcastMode, flags.BroadcastBlock, "--"+flags.FlagSkipConfirmation, ) @@ -836,6 +847,16 @@ func (s *CmdTestSuite) bondCoins(amt int64) sdk.Coins { return sdk.NewCoins(sdk.NewInt64Coin(s.cfg.BondDenom, amt)) } +// feeCoin returns a Coin with the fee denom and the provided amount. +func (s *CmdTestSuite) feeCoin(amt int64) sdk.Coin { + return sdk.NewInt64Coin(s.feeDenom, amt) +} + +// feeCoins returns a Coins with just an entry with the fee denom and the provided amount. +func (s *CmdTestSuite) feeCoins(amt int64) sdk.Coins { + return sdk.NewCoins(sdk.NewInt64Coin(s.feeDenom, amt)) +} + // adjustBalance creates a new Balance with the order owner's Address and a Coins that's // the result of the order and fees applied to the provided current balance. func (s *CmdTestSuite) adjustBalance(curBal sdk.Coins, order *exchange.Order, creationFees ...sdk.Coin) banktypes.Balance { @@ -943,6 +964,45 @@ func (s *CmdTestSuite) createOrder(order *exchange.Order, creationFee *sdk.Coin) return s.asOrderID(orderIDStr) } +// commitFunds issues a command to commit funds. +func (s *CmdTestSuite) commitFunds(addr sdk.AccAddress, marketID uint32, amount sdk.Coins, creationFee sdk.Coins) { + cmd := cli.CmdTx() + args := []string{ + "commit", + "--from", addr.String(), + "--market", fmt.Sprintf("%d", marketID), + "--amount", amount.String(), + } + if !creationFee.IsZero() { + args = append(args, "--creation-fee", creationFee.String()) + } + + args = append(args, + "--"+flags.FlagFees, s.bondCoins(10).String(), + "--"+flags.FlagBroadcastMode, flags.BroadcastBlock, + "--"+flags.FlagSkipConfirmation, + ) + + cmdName := cmd.Name() + var outBz []byte + defer func() { + if s.T().Failed() { + s.T().Logf("Command: %s\nArgs: %q\nOutput\n%s", cmdName, args, string(outBz)) + } + }() + + clientCtx := s.getClientCtx() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + outBz = out.Bytes() + + s.Require().NoError(err, "ExecTestCLICmd error") + + var resp sdk.TxResponse + err = clientCtx.Codec.UnmarshalJSON(outBz, &resp) + s.Require().NoError(err, "UnmarshalJSON(command output) error") + s.Require().Equal(int(0), int(resp.Code), "response code:\n%v", resp) +} + // queryBankBalances executes a bank query to get an account's balances. func (s *CmdTestSuite) queryBankBalances(addr string) sdk.Coins { clientCtx := s.getClientCtx() diff --git a/x/exchange/client/cli/query_test.go b/x/exchange/client/cli/query_test.go index cf432758ee..9e4e22e3eb 100644 --- a/x/exchange/client/cli/query_test.go +++ b/x/exchange/client/cli/query_test.go @@ -6,7 +6,6 @@ import ( "strings" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/provenance-io/provenance/internal/pioconfig" "github.com/provenance-io/provenance/x/exchange" ) @@ -717,10 +716,8 @@ func (s *CmdTestSuite) TestCmdQueryCommitmentSettlementFeeCalc() { tx := newTx(s.T(), fileMsg) writeFileAsJson(s.T(), filename, tx) - feeDenom := pioconfig.GetProvenanceConfig().FeeDenom - // existing navs: - // 1cherry => 100 + // 1cherry => 100 // 1apple => 8cherry // 17acorn => 3cherry // 3peach => 778cherry @@ -757,13 +754,13 @@ func (s *CmdTestSuite) TestCmdQueryCommitmentSettlementFeeCalc() { // 30peach * 778cherry/3peach = 7780cherry // 1945apple * 4cherry/1apple = 7780cherry // total = 15560cherry - // 15560cherry * 100nhash / 1 cherry = 1556000nhash - // 1556000nhash * 50 / 20000 = 3890nhash + // 15560cherry * 100 / 1 cherry = 1556000 + // 1556000 * 50 / 20000 = 3890 expOut: `conversion_navs: [] converted_total: [] exchange_fees: - amount: "3890" - denom: ` + feeDenom + ` + denom: ` + s.feeDenom + ` input_total: [] to_fee_nav: null `, @@ -775,17 +772,17 @@ to_fee_nav: null // 15banana * 35cherry/15banana = 35cherry // 50peach * 778cherry/3peach = 12966.67cherry // sum = 13081.67 => 13082cherry - // 13082cherry * 100nhash/1cherry = 1308200cherry - // 1308200 * 50 / 20000 = 3270.5 => 3271nhash + // 13082cherry * 100/1cherry = 1308200cherry + // 1308200 * 50 / 20000 = 3270.5 => 3271 expInOut: []string{ - `"exchange_fees":[{"denom":"nhash","amount":"3271"}]`, + `"exchange_fees":[{"denom":"` + s.feeDenom + `","amount":"3271"}]`, `"input_total":[{"denom":"apple","amount":"10"},{"denom":"banana","amount":"15"},{"denom":"peach","amount":"50"}]`, `"converted_total":[{"denom":"cherry","amount":"13082"}]`, `"conversion_navs":[`, `{"assets":{"denom":"apple","amount":"1"},"price":{"denom":"cherry","amount":"8"}}`, `{"assets":{"denom":"banana","amount":"15"},"price":{"denom":"cherry","amount":"35"}}`, `{"assets":{"denom":"peach","amount":"3"},"price":{"denom":"cherry","amount":"778"}}`, - `"to_fee_nav":{"assets":{"denom":"cherry","amount":"1"},"price":{"denom":"nhash","amount":"100"}}`, + `"to_fee_nav":{"assets":{"denom":"cherry","amount":"1"},"price":{"denom":"` + s.feeDenom + `","amount":"100"}}`, }, }, } diff --git a/x/exchange/client/cli/tx_test.go b/x/exchange/client/cli/tx_test.go index e5501d587b..48f0918e1b 100644 --- a/x/exchange/client/cli/tx_test.go +++ b/x/exchange/client/cli/tx_test.go @@ -2,6 +2,7 @@ package cli_test import ( "bytes" + "fmt" "sort" "golang.org/x/exp/maps" @@ -22,6 +23,8 @@ var ( invReqCode = sdkerrors.ErrInvalidRequest.ABCICode() // invSigCode is the TxResponse code for an ErrInvalidSigner. invSigCode = govtypes.ErrInvalidSigner.ABCICode() + // insFeeCode is the TxResponse code for an ErrInsufficientFunds. + insFeeCode = sdkerrors.ErrInsufficientFunds.ABCICode() ) func (s *CmdTestSuite) TestCmdTxCreateAsk() { @@ -383,7 +386,7 @@ func (s *CmdTestSuite) TestCmdTxMarketSettle() { expInErr: []string{"required flag(s) \"asks\" not set"}, }, { - name: "endpoint error", + name: "no permission", args: []string{"market-settle", "--from", s.addr9.String(), "--market", "419", "--bids", "18446744073709551614", "--asks", "18446744073709551615"}, expInRawLog: []string{"failed to execute message", "invalid request", "account " + s.addr9.String() + " does not have permission to settle orders for market 419", @@ -455,9 +458,188 @@ func (s *CmdTestSuite) TestCmdTxMarketSettle() { } } -// TODO[1789]: func (s *CmdTestSuite) TestCmdTxMarketCommitmentSettle() +func (s *CmdTestSuite) TestCmdTxMarketCommitmentSettle() { + tests := []txCmdTestCase{ + { + name: "cmd error", + args: []string{"market-commitment-settle", + "--from", s.addr1.String(), + "--inputs", s.addr8.String() + ":10apple", + "--outputs", s.addr9.String() + ":10apple", + }, + expInErr: []string{"at least one of the flags in the group [file market] is required"}, + }, + { + name: "market does not exist", + args: []string{"commitment-settle", + "--from", s.addr9.String(), + "--market", "419", + "--inputs", s.addr8.String() + ":10apple", + "--outputs", s.addr9.String() + ":10apple", + }, + expInErr: nil, + expInRawLog: []string{"failed to execute message", "invalid request", + "account " + s.addr9.String() + " does not have permission to settle commitments for market 419", + }, + expectedCode: invReqCode, + }, + { + name: "insufficient fees", + preRun: func() ([]string, func(*sdk.TxResponse)) { + var marketID uint32 = 3 + amount := sdk.NewCoins(sdk.NewInt64Coin("apple", 41)) + // 41apple * 8cherry/1apple = 328cherry + // 328cherry * 100 = 32800 + // 32800 * 50/20000 = 82 + tag := "iliketomoveitmoveit" + args := []string{ + "--market", fmt.Sprintf("%d", marketID), + "--inputs", fmt.Sprintf("%s:%s", s.addr5, amount), + "--outputs", fmt.Sprintf("%s:%s", s.addr6, amount), + "--tag", tag, + } + + s.commitFunds(s.addr5, marketID, amount, nil) + + balsAddr5 := s.queryBankBalances(s.addr5.String()) + balsAddr6 := s.queryBankBalances(s.addr6.String()) + spendBalsAddr5 := s.queryBankSpendableBalances(s.addr5.String()) + spendBalsAddr6 := s.queryBankSpendableBalances(s.addr6.String()) -// TODO[1789]: func (s *CmdTestSuite) TestCmdTxMarketReleaseCommitments() + expBals := []banktypes.Balance{ + {Address: s.addr5.String(), Coins: balsAddr5}, + {Address: s.addr6.String(), Coins: balsAddr6}, + } + expSpendBals := []banktypes.Balance{ + {Address: s.addr5.String(), Coins: spendBalsAddr5}, + {Address: s.addr6.String(), Coins: spendBalsAddr6}, + } + + fups := s.composeFollowups( + s.assertBalancesFollowup(expBals), + s.assertSpendableBalancesFollowup(expSpendBals), + ) + return args, fups + }, + args: []string{"market-settle-commitments", "--from", s.addr1.String()}, + expInRawLog: []string{"insufficient funds", + "negative balance after sending coins to accounts and fee collector", + "remainingFees: \"10stake\", sentCoins: \"82nhash\"", + }, + expectedCode: insFeeCode, + }, + { + name: "settlement done", + preRun: func() ([]string, func(*sdk.TxResponse)) { + var marketID uint32 = 3 + amount := sdk.NewCoins(sdk.NewInt64Coin("apple", 41)) + // 41apple * 8cherry/1apple = 328cherry + // 328cherry * 100 = 32800 + // 32800 * 50/20000 = 82 + tag := "iliketomoveitmoveit" + args := []string{ + "--market", fmt.Sprintf("%d", marketID), + "--inputs", fmt.Sprintf("%s:%s", s.addr5, amount), + "--outputs", fmt.Sprintf("%s:%s", s.addr6, amount), + "--tag", tag, + } + + s.commitFunds(s.addr5, marketID, amount, nil) + + balsAddr5 := s.queryBankBalances(s.addr5.String()) + balsAddr6 := s.queryBankBalances(s.addr6.String()) + spendBalsAddr5 := s.queryBankSpendableBalances(s.addr5.String()) + spendBalsAddr6 := s.queryBankSpendableBalances(s.addr6.String()) + + expBals := []banktypes.Balance{ + {Address: s.addr5.String(), Coins: balsAddr5.Sub(amount...)}, + {Address: s.addr6.String(), Coins: balsAddr6.Add(amount...)}, + } + expSpendBals := []banktypes.Balance{ + {Address: s.addr5.String(), Coins: spendBalsAddr5}, + {Address: s.addr6.String(), Coins: spendBalsAddr6}, + } + expEvents := sdk.Events{ + s.untypeEvent(exchange.NewEventCommitmentReleased(s.addr5.String(), marketID, amount, tag)), + s.untypeEvent(exchange.NewEventFundsCommitted(s.addr6.String(), marketID, amount, tag)), + } + s.markAttrsIndexed(expEvents) + + fups := s.composeFollowups( + s.assertBalancesFollowup(expBals), + s.assertSpendableBalancesFollowup(expSpendBals), + s.assertEventsContains(expEvents), + ) + return args, fups + }, + args: []string{"settle-commitments", "--from", s.addr1.String()}, + addedFees: s.feeCoins(82), + expectedCode: 0, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.runTxCmdTestCase(tc) + }) + } +} + +func (s *CmdTestSuite) TestCmdTxMarketReleaseCommitments() { + tests := []txCmdTestCase{ + { + name: "cmd error", + args: []string{"market-release-commitments", "--from", s.addr1.String(), + "--release", s.addr9.String() + ":10apple"}, + expInErr: []string{"required flag(s) \"market\" not set"}, + }, + { + name: "no permission", + args: []string{"release-commitments", "--from", s.addr9.String(), "--market", "419", + "--release-all", s.addr9.String()}, + expInRawLog: []string{"failed to execute message", "invalid request", + "account " + s.addr9.String() + " does not have permission to release commitments for market 419", + }, + expectedCode: invReqCode, + }, + { + name: "funds released", + preRun: func() ([]string, func(*sdk.TxResponse)) { + var marketID uint32 = 3 + toRelease := sdk.NewCoins(sdk.NewInt64Coin("apple", 44), sdk.NewInt64Coin("peach", 76)) + tag := "letitgo" + addr := s.addr2 + args := []string{ + "--market", fmt.Sprintf("%d", marketID), + "--release", fmt.Sprintf("%s:%s", addr, toRelease), + "--tag", tag, + } + + curSpend := s.queryBankSpendableBalances(addr.String()) + s.commitFunds(addr, marketID, toRelease, nil) + expSpendBals := []banktypes.Balance{{Address: addr.String(), Coins: curSpend.Sub(s.bondCoin(10))}} + + expEvent := exchange.NewEventCommitmentReleased(addr.String(), marketID, toRelease, tag) + expEvents := sdk.Events{s.untypeEvent(expEvent)} + s.markAttrsIndexed(expEvents) + + fup := s.composeFollowups( + s.assertSpendableBalancesFollowup(expSpendBals), + s.assertEventsContains(expEvents), + ) + return args, fup + }, + args: []string{"market-release-commitments", "--from", s.addr3.String()}, + expectedCode: 0, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.runTxCmdTestCase(tc) + }) + } +} func (s *CmdTestSuite) TestCmdTxMarketSetOrderExternalID() { tests := []txCmdTestCase{ @@ -718,9 +900,86 @@ func (s *CmdTestSuite) TestCmdTxMarketUpdateUserSettle() { } } -// TODO[1789]: func (s *CmdTestSuite) TestCmdTxMarketUpdateAcceptingCommitments() +func (s *CmdTestSuite) TestCmdTxMarketUpdateAcceptingCommitments() { + tests := []txCmdTestCase{ + { + name: "no market", + args: []string{"market-accepting-commitments", "--from", s.addr1.String(), "--enable"}, + expInErr: []string{"required flag(s) \"market\" not set"}, + }, + { + name: "market does not exist", + args: []string{"market-update-accepting-commitments", "--market", "419", + "--from", s.addr4.String(), "--enable"}, + expInRawLog: []string{"failed to execute message", "invalid request", + "account " + s.addr4.String() + " does not have permission to update market 419", + }, + expectedCode: invReqCode, + }, + { + name: "disable market", + preRun: func() ([]string, func(*sdk.TxResponse)) { + market420 := s.getMarket("420") + market420.AcceptingCommitments = false + return nil, s.getMarketFollowup("420", market420) + }, + args: []string{"update-market-accepting-commitments", "--disable", "--market", "420", "--from", s.addr1.String()}, + expectedCode: 0, + }, + { + name: "enable market", + preRun: func() ([]string, func(*sdk.TxResponse)) { + market420 := s.getMarket("420") + market420.AcceptingCommitments = true + return nil, s.getMarketFollowup("420", market420) + }, + args: []string{"update-accepting-commitments", "--enable", "--market", "420", "--from", s.addr1.String()}, + expectedCode: 0, + }, + } -// TODO[1789]: func (s *CmdTestSuite) TestCmdTxMarketUpdateIntermediaryDenom() + for _, tc := range tests { + s.Run(tc.name, func() { + s.runTxCmdTestCase(tc) + }) + } +} + +func (s *CmdTestSuite) TestCmdTxMarketUpdateIntermediaryDenom() { + tests := []txCmdTestCase{ + { + name: "cmd error", + args: []string{"market-intermediary-denom", "--from", s.addr2.String(), "--denom", "banana"}, + expInErr: []string{"required flag(s) \"market\" not set"}, + }, + { + name: "no permission", + args: []string{"update-intermediary-denom", "--from", s.addr2.String(), + "--denom", "banana", "--market", "421"}, + expInRawLog: []string{"failed to execute message", "invalid request", + "account " + s.addr2.String() + " does not have permission to update market 421", + }, + expectedCode: invReqCode, + }, + { + name: "updated", + preRun: func() ([]string, func(*sdk.TxResponse)) { + expMarket := s.getMarket("421") + expMarket.IntermediaryDenom = "orange" + return nil, s.getMarketFollowup("421", expMarket) + }, + args: []string{"update-market-intermediary-denom", "--from", s.addr1.String(), + "--denom", "orange", "--market", "421"}, + expectedCode: 0, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.runTxCmdTestCase(tc) + }) + } +} func (s *CmdTestSuite) TestCmdTxMarketManagePermissions() { tests := []txCmdTestCase{ @@ -964,7 +1223,42 @@ func (s *CmdTestSuite) TestCmdTxGovManageFees() { } } -// TODO[1789]: func (s *CmdTestSuite) TestCmdTxGovCloseMarket() +func (s *CmdTestSuite) TestCmdTxGovCloseMarket() { + tests := []txCmdTestCase{ + { + name: "cmd error", + args: []string{"gov-close-market"}, + expInErr: []string{"required flag(s) \"market\" not set"}, + }, + { + name: "wrong authority", + args: []string{"close-market", "--market", "419", + "--from", s.addr2.String(), "--authority", s.addr2.String()}, + expInRawLog: []string{"failed to execute message", + s.addr2.String(), "expected gov account as only signer for proposal message", + }, + expectedCode: invSigCode, + }, + { + name: "prop created", + preRun: func() ([]string, func(*sdk.TxResponse)) { + expMsg := &exchange.MsgGovCloseMarketRequest{ + Authority: cli.AuthorityAddr.String(), + MarketId: 419, + } + return nil, s.govPropFollowup(expMsg) + }, + args: []string{"close-market", "--market", "419", "--from", s.addr2.String()}, + expectedCode: 0, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + s.runTxCmdTestCase(tc) + }) + } +} func (s *CmdTestSuite) TestCmdTxGovUpdateParams() { tests := []txCmdTestCase{