diff --git a/x/exchange/keeper/genesis_test.go b/x/exchange/keeper/genesis_test.go index 935d2dae2a..4fda0b0dbf 100644 --- a/x/exchange/keeper/genesis_test.go +++ b/x/exchange/keeper/genesis_test.go @@ -1,178 +1,15 @@ package keeper_test import ( - "bytes" "fmt" - "sort" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/provenance-io/provenance/x/exchange" "github.com/provenance-io/provenance/x/exchange/keeper" ) -// copyGenState creates a copy of a GenesisState. -func (s *TestSuite) copyGenState(genState *exchange.GenesisState) *exchange.GenesisState { - if genState == nil { - return nil - } - return &exchange.GenesisState{ - Params: s.copyParams(genState.Params), - Markets: s.copyMarkets(genState.Markets), - Orders: s.copyOrders(genState.Orders), - LastMarketId: genState.LastMarketId, - LastOrderId: genState.LastOrderId, - } -} - -// copyMarkets creates a copy of a slice of markets. -func (s *TestSuite) copyMarkets(orig []exchange.Market) []exchange.Market { - return copySlice(orig, s.copyMarket) -} - -// copyOrders creates a copy of a slice of orders. -func (s *TestSuite) copyOrders(orig []exchange.Order) []exchange.Order { - return copySlice(orig, s.copyOrder) -} - -// copyOrder creates a copy of an order. -func (s *TestSuite) copyOrder(orig exchange.Order) exchange.Order { - rv := exchange.NewOrder(orig.OrderId) - switch { - case orig.IsAskOrder(): - rv.WithAsk(s.copyAskOrder(orig.GetAskOrder())) - case orig.IsBidOrder(): - rv.WithBid(s.copyBidOrder(orig.GetBidOrder())) - default: - rv.Order = orig.Order - } - return *rv -} - -// copyAskOrder creates a copy of an AskOrder. -func (s *TestSuite) copyAskOrder(orig *exchange.AskOrder) *exchange.AskOrder { - if orig == nil { - return nil - } - return &exchange.AskOrder{ - MarketId: orig.MarketId, - Seller: orig.Seller, - Assets: s.copyCoin(orig.Assets), - Price: s.copyCoin(orig.Price), - SellerSettlementFlatFee: s.copyCoinP(orig.SellerSettlementFlatFee), - AllowPartial: orig.AllowPartial, - ExternalId: orig.ExternalId, - } -} - -// copyBidOrder creates a copy of a BidOrder. -func (s *TestSuite) copyBidOrder(orig *exchange.BidOrder) *exchange.BidOrder { - if orig == nil { - return nil - } - return &exchange.BidOrder{ - MarketId: orig.MarketId, - Buyer: orig.Buyer, - Assets: s.copyCoin(orig.Assets), - Price: s.copyCoin(orig.Price), - BuyerSettlementFees: s.copyCoins(orig.BuyerSettlementFees), - AllowPartial: orig.AllowPartial, - ExternalId: orig.ExternalId, - } -} - -// copyParams creates a copy of exchange Params. -func (s *TestSuite) copyParams(orig *exchange.Params) *exchange.Params { - if orig == nil { - return nil - } - return &exchange.Params{ - DefaultSplit: orig.DefaultSplit, - DenomSplits: s.copyDenomSplits(orig.DenomSplits), - } -} - -// creates a copy of a DenomSplit. -func (s *TestSuite) copyDenomSplit(orig exchange.DenomSplit) exchange.DenomSplit { - return exchange.DenomSplit{ - Denom: orig.Denom, - Split: orig.Split, - } -} - -// copyDenomSplits creates a copy of a slice of DenomSplits. -func (s *TestSuite) copyDenomSplits(orig []exchange.DenomSplit) []exchange.DenomSplit { - return copySlice(orig, s.copyDenomSplit) -} - -// sortGenState sorts the contents of a GenesisState. -func (s *TestSuite) sortGenState(genState *exchange.GenesisState) *exchange.GenesisState { - if genState == nil { - return nil - } - if genState.Params != nil && len(genState.Params.DenomSplits) > 0 { - sort.Slice(genState.Params.DenomSplits, func(i, j int) bool { - return genState.Params.DenomSplits[i].Denom < genState.Params.DenomSplits[j].Denom - }) - } - if len(genState.Markets) > 0 { - sort.Slice(genState.Markets, func(i, j int) bool { - return genState.Markets[i].MarketId < genState.Markets[j].MarketId - }) - for _, market := range genState.Markets { - s.sortMarket(&market) - } - } - if len(genState.Orders) > 0 { - sort.Slice(genState.Orders, func(i, j int) bool { - return genState.Orders[i].OrderId < genState.Orders[j].OrderId - }) - } - return genState -} - -// sortMarket sorts all the fields in a market. -func (s *TestSuite) sortMarket(market *exchange.Market) *exchange.Market { - if len(market.FeeSellerSettlementRatios) > 0 { - sort.Slice(market.FeeSellerSettlementRatios, func(i, j int) bool { - if market.FeeSellerSettlementRatios[i].Price.Denom < market.FeeSellerSettlementRatios[j].Price.Denom { - return true - } - if market.FeeSellerSettlementRatios[i].Price.Denom > market.FeeSellerSettlementRatios[j].Price.Denom { - return false - } - return market.FeeSellerSettlementRatios[i].Fee.Denom < market.FeeSellerSettlementRatios[j].Fee.Denom - }) - } - if len(market.FeeBuyerSettlementRatios) > 0 { - sort.Slice(market.FeeBuyerSettlementRatios, func(i, j int) bool { - if market.FeeBuyerSettlementRatios[i].Price.Denom < market.FeeBuyerSettlementRatios[j].Price.Denom { - return true - } - if market.FeeBuyerSettlementRatios[i].Price.Denom > market.FeeBuyerSettlementRatios[j].Price.Denom { - return false - } - return market.FeeBuyerSettlementRatios[i].Fee.Denom < market.FeeBuyerSettlementRatios[j].Fee.Denom - }) - } - if len(market.AccessGrants) > 0 { - sort.Slice(market.AccessGrants, func(i, j int) bool { - // Horribly inefficient. Not meant for production. - addrI, err := sdk.AccAddressFromBech32(market.AccessGrants[i].Address) - s.Require().NoError(err, "AccAddressFromBech32(%q)", market.AccessGrants[i].Address) - addrJ, err := sdk.AccAddressFromBech32(market.AccessGrants[j].Address) - s.Require().NoError(err, "AccAddressFromBech32(%q)", market.AccessGrants[j].Address) - return bytes.Compare(addrI, addrJ) < 0 - }) - for _, ag := range market.AccessGrants { - sort.Slice(ag.Permissions, func(i, j int) bool { - return ag.Permissions[i] < ag.Permissions[j] - }) - } - } - return market -} - func (s *TestSuite) TestKeeper_InitAndExportGenesis() { marketAcc := func(marketID uint32, name string) *exchange.MarketAccount { return &exchange.MarketAccount{ diff --git a/x/exchange/keeper/grpc_query_test.go b/x/exchange/keeper/grpc_query_test.go index 2897528587..32f0d1658a 100644 --- a/x/exchange/keeper/grpc_query_test.go +++ b/x/exchange/keeper/grpc_query_test.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" - "github.com/provenance-io/provenance/testutil/assertions" "github.com/provenance-io/provenance/x/exchange" "github.com/provenance-io/provenance/x/exchange/keeper" ) @@ -21,79 +20,10 @@ type querySetupFunc func() // queryRunner is a function that will call a query endpoint, returning its response and error. type queryRunner func(goCtx context.Context) (interface{}, error) -// doQueryTest runs the provided setup and runner, requiring the runner to not panic and asserting its error's content -// to contain the provided strings (or be nil if none are provided). -// A cache context is used for this, so the setup function won't affect other test cases. -// The query's response is returned. -func (s *TestSuite) doQueryTest(setup querySetupFunc, runner queryRunner, expInErr []string, msg string, args ...interface{}) interface{} { - s.T().Helper() - origCtx := s.ctx - defer func() { - s.ctx = origCtx - }() - s.ctx, _ = s.ctx.CacheContext() - if setup != nil { - setup() - } - goCtx := sdk.WrapSDKContext(s.ctx) - var rv interface{} - var err error - testFunc := func() { - rv, err = runner(goCtx) - } - s.Require().NotPanicsf(testFunc, msg, args...) - s.assertErrorContentsf(err, expInErr, msg+" error", args...) - return rv -} - -// getOrderIDs gets a comma+space separated string of all the order ids in the given orders, E.g. "1, 8, 55" -func (s *TestSuite) getOrderIDs(orders []*exchange.Order) string { - rv := make([]string, len(orders)) - for i, exp := range orders { - if exp == nil { - rv[i] = "" - } else { - rv[i] = fmt.Sprintf("%d", exp.OrderId) - } - } - return strings.Join(rv, ", ") -} - -// getOrderIDs gets a comma+space separated string of all the order ids in the given orders, E.g. "1, 8, 55" -func (s *TestSuite) getMarketIDs(markets []*exchange.MarketBrief) string { - rv := make([]string, len(markets)) - for i, exp := range markets { - if exp == nil { - rv[i] = "" - } else { - rv[i] = fmt.Sprintf("%d", exp.MarketId) - } - } - return strings.Join(rv, ", ") -} - -// assertEqualOrders asserts that the to slices of orders are equal. -// If not, some further assertions are made to try to help clarify the differences. -func (s *TestSuite) assertEqualOrders(expected, actual []*exchange.Order, msg string, args ...interface{}) bool { - if s.Assert().Equalf(expected, actual, msg, args...) { - return true - } - // If the order ids are different, that should be enough info in the failure message. - expIDs := s.getOrderIDs(expected) - actIDs := s.getOrderIDs(actual) - if !s.Assert().Equalf(expIDs, actIDs, msg+" order ids", args...) { - return false - } - // Same order ids, so compare each individually. - for i := range expected { - s.Assertions.Equalf(expected[i], actual[i], msg+fmt.Sprintf("[%d]", i), args...) - } - return false -} - // assertEqualPageResponse asserts that two PageResponses are equal. // If not, some further assertions are made to try to help clarify the differences. func (s *TestSuite) assertEqualPageResponse(expected, actual *query.PageResponse, msg string, args ...interface{}) bool { + s.T().Helper() if s.Assert().Equalf(expected, actual, msg, args...) { return true } @@ -114,12 +44,29 @@ func (s *TestSuite) assertEqualPageResponse(expected, actual *query.PageResponse return false } -// requireCreateMarketUnmocked calls CreateMarket making sure it doesn't panic or return an error. -func (s *TestSuite) requireCreateMarketUnmocked(market exchange.Market) { - assertions.RequireNotPanicsNoErrorf(s.T(), func() error { - _, err := s.k.CreateMarket(s.ctx, market) - return err - }, "CreateMarket(%d)", market.MarketId) +// doQueryTest runs the provided setup and runner, requiring the runner to not panic and asserting its error's content +// to contain the provided strings (or be nil if none are provided). +// A cache context is used for this, so the setup function won't affect other test cases. +// The query's response is returned. +func (s *TestSuite) doQueryTest(setup querySetupFunc, runner queryRunner, expInErr []string, msg string, args ...interface{}) interface{} { + s.T().Helper() + origCtx := s.ctx + defer func() { + s.ctx = origCtx + }() + s.ctx, _ = s.ctx.CacheContext() + if setup != nil { + setup() + } + goCtx := sdk.WrapSDKContext(s.ctx) + var rv interface{} + var err error + testFunc := func() { + rv, err = runner(goCtx) + } + s.Require().NotPanicsf(testFunc, msg, args...) + s.assertErrorContentsf(err, expInErr, msg+" error", args...) + return rv } func (s *TestSuite) TestQueryServer_OrderFeeCalc() { @@ -3519,6 +3466,13 @@ func (s *TestSuite) TestQueryServer_GetAllMarkets() { }, } + briefIDStringer := func(brief *exchange.MarketBrief) string { + if brief == nil { + return "" + } + return fmt.Sprintf("%d", brief.MarketId) + } + for _, tc := range tests { s.Run(tc.name, func() { respRaw := s.doQueryTest(tc.setup, runner(tc.req), tc.expInErr, queryName) @@ -3534,21 +3488,7 @@ func (s *TestSuite) TestQueryServer_GetAllMarkets() { s.Assert().Equal(tc.expResp.Pagination.NextKey, resp.Pagination.NextKey, queryName+" result Pagination.NextKey") s.Assert().Equal(int(tc.expResp.Pagination.Total), int(resp.Pagination.Total), queryName+" result Pagination.Total") } - expIDs := s.getMarketIDs(tc.expResp.Markets) - actIDs := s.getMarketIDs(resp.Markets) - s.Require().Equal(expIDs, actIDs, queryName+" result market ids") - for i := range tc.expResp.Markets { - s.Assert().Equal(tc.expResp.Markets[i].MarketAddress, resp.Markets[i].MarketAddress, - queryName+" [%d] MarketAddress", i) - s.Assert().Equal(tc.expResp.Markets[i].MarketDetails.Name, resp.Markets[i].MarketDetails.Name, - queryName+" [%d] MarketDetails.Name", i) - s.Assert().Equal(tc.expResp.Markets[i].MarketDetails.Description, resp.Markets[i].MarketDetails.Description, - queryName+" [%d] MarketDetails.Description", i) - s.Assert().Equal(tc.expResp.Markets[i].MarketDetails.WebsiteUrl, resp.Markets[i].MarketDetails.WebsiteUrl, - queryName+" [%d] MarketDetails.WebsiteUrl", i) - s.Assert().Equal(tc.expResp.Markets[i].MarketDetails.IconUri, resp.Markets[i].MarketDetails.IconUri, - queryName+" [%d] MarketDetails.IconUri", i) - } + assertEqualSlice(s, tc.expResp.Markets, resp.Markets, briefIDStringer, queryName+" result markets") }) } } diff --git a/x/exchange/keeper/keeper_test.go b/x/exchange/keeper/keeper_test.go index bc20aa446e..cd8ab27666 100644 --- a/x/exchange/keeper/keeper_test.go +++ b/x/exchange/keeper/keeper_test.go @@ -1,434 +1,17 @@ package keeper_test import ( - "bytes" "fmt" "strings" - "testing" - "github.com/gogo/protobuf/proto" - "github.com/rs/zerolog" - "github.com/stretchr/testify/suite" - - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - - "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/bank/testutil" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/provenance-io/provenance/app" - "github.com/provenance-io/provenance/testutil/assertions" "github.com/provenance-io/provenance/x/exchange" - "github.com/provenance-io/provenance/x/exchange/keeper" ) -type TestSuite struct { - suite.Suite - - app *app.App - ctx sdk.Context - - k keeper.Keeper - acctKeeper exchange.AccountKeeper - attrKeeper exchange.AttributeKeeper - bankKeeper exchange.BankKeeper - holdKeeper exchange.HoldKeeper - - bondDenom string - initBal sdk.Coins - initAmount int64 - - addr1 sdk.AccAddress - addr2 sdk.AccAddress - addr3 sdk.AccAddress - addr4 sdk.AccAddress - addr5 sdk.AccAddress - - marketAddr1 sdk.AccAddress - marketAddr2 sdk.AccAddress - marketAddr3 sdk.AccAddress - - feeCollector string - feeCollectorAddr sdk.AccAddress - - accKeeper *MockAccountKeeper - - logBuffer bytes.Buffer -} - -func (s *TestSuite) SetupTest() { - bufferedLoggerMaker := func() log.Logger { - lw := zerolog.ConsoleWriter{ - Out: &s.logBuffer, - NoColor: true, - PartsExclude: []string{"time"}, // Without this, each line starts with " " - } - // Error log lines will start with "ERR ". - // Info log lines will start with "INF ". - // Debug log lines are omitted, but would start with "DBG ". - logger := zerolog.New(lw).Level(zerolog.InfoLevel) - return server.ZeroLogWrapper{Logger: logger} - } - // swap in the buffered logger maker so it's used in app.Setup, but then put it back (since that's a global thing). - defer app.SetLoggerMaker(app.SetLoggerMaker(bufferedLoggerMaker)) - - s.app = app.Setup(s.T()) - s.logBuffer.Reset() - s.ctx = s.app.BaseApp.NewContext(false, tmproto.Header{}) - s.k = s.app.ExchangeKeeper - s.acctKeeper = s.app.AccountKeeper - s.attrKeeper = s.app.AttributeKeeper - s.bankKeeper = s.app.BankKeeper - s.holdKeeper = s.app.HoldKeeper - - s.bondDenom = s.app.StakingKeeper.BondDenom(s.ctx) - s.initAmount = 1_000_000_000 - s.initBal = sdk.NewCoins(sdk.NewCoin(s.bondDenom, sdk.NewInt(s.initAmount))) - - addrs := app.AddTestAddrsIncremental(s.app, s.ctx, 5, sdk.NewInt(s.initAmount)) - s.addr1 = addrs[0] - s.addr2 = addrs[1] - s.addr3 = addrs[2] - s.addr4 = addrs[3] - s.addr5 = addrs[4] - - s.marketAddr1 = exchange.GetMarketAddress(1) - s.marketAddr2 = exchange.GetMarketAddress(2) - s.marketAddr3 = exchange.GetMarketAddress(3) - - s.feeCollector = s.k.GetFeeCollectorName() - s.feeCollectorAddr = authtypes.NewModuleAddress(s.feeCollector) -} - -func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(TestSuite)) -} - -// sliceStrings converts each val into a string using the provided stringer. -func sliceStrings[T any](vals []T, stringer func(T) string) []string { - if vals == nil { - return nil - } - strs := make([]string, len(vals)) - for i, v := range vals { - strs[i] = fmt.Sprintf("[%d]:%q", i, stringer(v)) - } - return strs -} - -// sliceString converts each val into a string using the provided stringer and joins them with ", ". -func sliceString[T any](vals []T, stringer func(T) string) string { - if vals == nil { - return "" - } - return strings.Join(sliceStrings(vals, stringer), ", ") -} - -// copySlice returns a copy of a slice using the provided copier for each value. -func copySlice[T any](vals []T, copier func(T) T) []T { - if vals == nil { - return nil - } - rv := make([]T, len(vals)) - for i, v := range vals { - rv[i] = copier(v) - } - return rv -} - -// noOpCopier is a passthrough "copier" function that just returns the exact same thing that was provided. -func noOpCopier[T any](val T) T { - return val -} - -// reverseSlice returns a new slice with the entries reversed. -func reverseSlice[T any](vals []T) []T { - if vals == nil { - return nil - } - rv := make([]T, len(vals)) - for i, val := range vals { - rv[len(vals)-i-1] = val - } - return rv -} - -// getLogOutput gets the log buffer contents. This (probably) also clears the log buffer. -func (s *TestSuite) getLogOutput(msg string, args ...interface{}) string { - logOutput := s.logBuffer.String() - s.T().Logf(msg+" log output:\n%s", append(args, logOutput)...) - return logOutput -} - -// coins creates an sdk.Coins from a string, requiring it to work. -func (s *TestSuite) coins(coins string) sdk.Coins { - s.T().Helper() - rv, err := sdk.ParseCoinsNormalized(coins) - s.Require().NoError(err, "ParseCoinsNormalized(%q)", coins) - return rv -} - -// coin creates a new coin without doing any validation on it. -func (s *TestSuite) coin(coin string) sdk.Coin { - rv, err := sdk.ParseCoinNormalized(coin) - s.Require().NoError(err, "ParseCoinNormalized(%q)", coin) - return rv -} - -// coinP creates a reference to a new coin without doing any validation on it. -func (s *TestSuite) coinP(coin string) *sdk.Coin { - rv := s.coin(coin) - return &rv -} - -// coinsString converts a slice of coin entries into a string. -// This is different from sdk.Coins.String because the entries aren't sorted. -func (s *TestSuite) coinsString(coins []sdk.Coin) string { - return sliceString(coins, func(coin sdk.Coin) string { - return fmt.Sprintf("%q", coin) - }) -} - -// coinPString converts the provided coin to a quoted string, or "". -func (s *TestSuite) coinPString(coin *sdk.Coin) string { - if coin == nil { - return "" - } - return fmt.Sprintf("%q", coin) -} - -// ratio creates a FeeRatio from a ":" string. -func (s *TestSuite) ratio(ratioStr string) exchange.FeeRatio { - rv, err := exchange.ParseFeeRatio(ratioStr) - s.Require().NoError(err, "ParseFeeRatio(%q)", ratioStr) - return *rv -} - -// ratios creates a slice of Fee ratio from a comma delimited list of ":" entries in a string. -func (s *TestSuite) ratios(ratiosStr string) []exchange.FeeRatio { - if len(ratiosStr) == 0 { - return nil - } - - ratios := strings.Split(ratiosStr, ",") - rv := make([]exchange.FeeRatio, len(ratios)) - for i, r := range ratios { - rv[i] = s.ratio(r) - } - return rv -} - -// ratiosStrings converts the ratios into strings. It's because comparsions on sdk.Coin (or sdkmath.Int) are annoying. -func (s *TestSuite) ratiosStrings(ratios []exchange.FeeRatio) []string { - return sliceStrings(ratios, exchange.FeeRatio.String) -} - -// ratiosString converts a slice of ratio entries into a string. -func (s *TestSuite) ratiosString(ratios []exchange.FeeRatio) string { - if len(ratios) == 0 { - return "" - } - strs := make([]string, len(ratios)) - for i, r := range ratios { - strs[i] = r.String() - } - return strings.Join(strs, ",") -} - -// joinErrs joins the provided error strings into a single one to match what errors.Join does. -func (s *TestSuite) joinErrs(errs ...string) string { - return strings.Join(errs, "\n") -} - -// copyCoin creates a copy of a coin (as best as possible). -func (s *TestSuite) copyCoin(orig sdk.Coin) sdk.Coin { - return sdk.NewCoin(orig.Denom, orig.Amount.AddRaw(0)) -} - -// copyCoinP copies a coin that's a reference. -func (s *TestSuite) copyCoinP(orig *sdk.Coin) *sdk.Coin { - if orig == nil { - return nil - } - rv := s.copyCoin(*orig) - return &rv -} - -// copyCoins creates a copy of coins (as best as possible). -func (s *TestSuite) copyCoins(orig []sdk.Coin) []sdk.Coin { - return copySlice(orig, s.copyCoin) -} - -// copyratio creates a copy of a FeeRatio. -func (s *TestSuite) copyratio(orig exchange.FeeRatio) exchange.FeeRatio { - return exchange.FeeRatio{ - Price: s.copyCoin(orig.Price), - Fee: s.copyCoin(orig.Fee), - } -} - -// copyRatios creates a copy of a slice of FeeRatios. -func (s *TestSuite) copyRatios(orig []exchange.FeeRatio) []exchange.FeeRatio { - return copySlice(orig, s.copyratio) -} - -// copyAccessGrant creates a copy of an AccessGrant. -func (s *TestSuite) copyAccessGrant(orig exchange.AccessGrant) exchange.AccessGrant { - return exchange.AccessGrant{ - Address: orig.Address, - Permissions: copySlice(orig.Permissions, noOpCopier[exchange.Permission]), - } -} - -// copyStrings creates a copy of a slice of strings. -func (s *TestSuite) copyStrings(orig []string) []string { - return copySlice(orig, noOpCopier[string]) -} - -// copyAccessGrants creates a copy of a slice of AccessGrants. -func (s *TestSuite) copyAccessGrants(orig []exchange.AccessGrant) []exchange.AccessGrant { - return copySlice(orig, s.copyAccessGrant) -} - -// getAddrName returns the name of the variable in this TestSuite holding the provided address. -func (s *TestSuite) getAddrName(addr sdk.AccAddress) string { - switch string(addr) { - case string(s.addr1): - return "addr1" - case string(s.addr2): - return "addr2" - case string(s.addr3): - return "addr3" - case string(s.addr4): - return "addr4" - case string(s.addr5): - return "addr5" - case string(s.marketAddr1): - return "marketAddr1" - case string(s.marketAddr2): - return "marketAddr2" - case string(s.marketAddr3): - return "marketAddr3" - case string(s.feeCollectorAddr): - return "feeCollectorAddr" - default: - return addr.String() - } -} - -// getAddrStrName returns the name of the variable in this TestSuite holding the provided address. -func (s *TestSuite) getAddrStrName(addrStr string) string { - addr, err := sdk.AccAddressFromBech32(addrStr) - if err != nil { - return addrStr - } - return s.getAddrName(addr) -} - -// getStore gets the exchange store. -func (s *TestSuite) getStore() sdk.KVStore { - return s.k.GetStore(s.ctx) -} - -// clearExchangeState deletes everything from the exchange state store. -func (s *TestSuite) clearExchangeState() { - keeper.DeleteAll(s.getStore(), nil) - s.accKeeper = nil -} - -// stateEntryString converts the provided key and value into a ""="" string. -func (s *TestSuite) stateEntryString(key, value []byte) string { - return fmt.Sprintf("%q=%q", key, value) -} - -// dumpExchangeState creates a string for each entry in the hold state store. -// Each entry has the format `""=""`. -func (s *TestSuite) dumpExchangeState() []string { - var rv []string - keeper.Iterate(s.getStore(), nil, func(key, value []byte) bool { - rv = append(rv, s.stateEntryString(key, value)) - return false - }) - return rv -} - -// requireFundAccount calls testutil.FundAccount, making sure it doesn't panic or return an error. -func (s *TestSuite) requireFundAccount(addr sdk.AccAddress, coins string) { - assertions.RequireNotPanicsNoErrorf(s.T(), func() error { - return testutil.FundAccount(s.app.BankKeeper, s.ctx, addr, s.coins(coins)) - }, "FundAccount(%s, %q)", s.getAddrName(addr), coins) -} - -// requireAddHold calls s.app.HoldKeeper.AddHold, making sure it doesn't panic or return an error. -func (s *TestSuite) requireAddHold(addr sdk.AccAddress, holdCoins string, orderID uint64) { - coins := s.coins(holdCoins) - reason := fmt.Sprintf("test hold on order %d", orderID) - assertions.RequireNotPanicsNoErrorf(s.T(), func() error { - return s.app.HoldKeeper.AddHold(s.ctx, addr, coins, reason) - }, "AddHold(%s, %q, %q)", s.getAddrName(addr), holdCoins, reason) -} - -// requireSetOrderInStore calls SetOrderInStore making sure it doesn't panic or return an error. -func (s *TestSuite) requireSetOrderInStore(store sdk.KVStore, order *exchange.Order) { - assertions.RequireNotPanicsNoErrorf(s.T(), func() error { - return s.k.SetOrderInStore(store, *order) - }, "SetOrderInStore(%d)", order.OrderId) -} - -// requireCreateMarket calls CreateMarket making sure it doesn't panic or return an error. -// It also uses the TestSuite.accKeeper for the market account. -func (s *TestSuite) requireCreateMarket(market exchange.Market) { - if s.accKeeper == nil { - s.accKeeper = NewMockAccountKeeper() - } - assertions.RequireNotPanicsNoErrorf(s.T(), func() error { - _, err := s.k.WithAccountKeeper(s.accKeeper).CreateMarket(s.ctx, market) - return err - }, "CreateMarket(%d)", market.MarketId) -} - -// assertErrorValue is a wrapper for assertions.AssertErrorValue for this TestSuite. -func (s *TestSuite) assertErrorValue(theError error, expected string, msgAndArgs ...interface{}) bool { - return assertions.AssertErrorValue(s.T(), theError, expected, msgAndArgs...) -} - -// assertErrorContentsf is a wrapper for assertions.AssertErrorContentsf for this TestSuite. -func (s *TestSuite) assertErrorContentsf(theError error, contains []string, msg string, args ...interface{}) bool { - return assertions.AssertErrorContentsf(s.T(), theError, contains, msg, args...) -} - -// assertEqualEvents is a wrapper for assertions.AssertEqualEvents for this TestSuite. -func (s *TestSuite) assertEqualEvents(expected, actual sdk.Events, msgAndArgs ...interface{}) bool { - return assertions.AssertEqualEvents(s.T(), expected, actual, msgAndArgs...) -} - -// requirePanicEquals is a wrapper for assertions.RequirePanicEquals for this TestSuite. -func (s *TestSuite) requirePanicEquals(f assertions.PanicTestFunc, expected string, msgAndArgs ...interface{}) { - assertions.RequirePanicEquals(s.T(), f, expected, msgAndArgs...) -} - -// untypeEvent returns sdk.TypedEventToEvent(tev) requiring it to not error. -func (s *TestSuite) untypeEvent(tev proto.Message) sdk.Event { - rv, err := sdk.TypedEventToEvent(tev) - s.Require().NoError(err, "TypedEventToEvent(%T)", tev) - return rv -} - -// untypeEvents applies sdk.TypedEventToEvent(tev) to each of the provided things, requiring it to not error. -func untypeEvents[P proto.Message](s *TestSuite, tevs []P) sdk.Events { - rv := make(sdk.Events, len(tevs)) - for i, tev := range tevs { - event, err := sdk.TypedEventToEvent(tev) - s.Require().NoError(err, "[%d]TypedEventToEvent(%T)", i, tev) - rv[i] = event - } - return rv -} - func (s *TestSuite) TestKeeper_GetAuthority() { expected := authtypes.NewModuleAddress(govtypes.ModuleName).String() var actual string diff --git a/x/exchange/keeper/market_test.go b/x/exchange/keeper/market_test.go index fec583780c..386a803a2b 100644 --- a/x/exchange/keeper/market_test.go +++ b/x/exchange/keeper/market_test.go @@ -6,33 +6,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/provenance-io/provenance/x/exchange" "github.com/provenance-io/provenance/x/exchange/keeper" ) -func (s *TestSuite) copyMarket(orig exchange.Market) exchange.Market { - return exchange.Market{ - MarketId: orig.MarketId, - MarketDetails: exchange.MarketDetails{ - Name: orig.MarketDetails.Name, - Description: orig.MarketDetails.Description, - WebsiteUrl: orig.MarketDetails.WebsiteUrl, - IconUri: orig.MarketDetails.IconUri, - }, - FeeCreateAskFlat: s.copyCoins(orig.FeeCreateAskFlat), - FeeCreateBidFlat: s.copyCoins(orig.FeeCreateBidFlat), - FeeSellerSettlementFlat: s.copyCoins(orig.FeeSellerSettlementFlat), - FeeSellerSettlementRatios: s.copyRatios(orig.FeeSellerSettlementRatios), - FeeBuyerSettlementFlat: s.copyCoins(orig.FeeBuyerSettlementFlat), - FeeBuyerSettlementRatios: s.copyRatios(orig.FeeBuyerSettlementRatios), - AcceptingOrders: orig.AcceptingOrders, - AllowUserSettlement: orig.AllowUserSettlement, - AccessGrants: s.copyAccessGrants(orig.AccessGrants), - ReqAttrCreateAsk: s.copyStrings(orig.ReqAttrCreateAsk), - ReqAttrCreateBid: s.copyStrings(orig.ReqAttrCreateBid), - } -} - func (s *TestSuite) TestKeeper_IterateKnownMarketIDs() { var marketIDs []uint32 stopAfter := func(n int) func(marketID uint32) bool { @@ -2417,9 +2395,9 @@ func (s *TestSuite) TestKeeper_UpdateFees() { createAsk: sdk.Coins(s.k.GetCreateAskFlatFees(s.ctx, marketID)).String(), createBid: sdk.Coins(s.k.GetCreateBidFlatFees(s.ctx, marketID)).String(), sellerFlat: sdk.Coins(s.k.GetSellerSettlementFlatFees(s.ctx, marketID)).String(), - sellerRatio: s.ratiosString(s.k.GetSellerSettlementRatios(s.ctx, marketID)), + sellerRatio: exchange.FeeRatiosString(s.k.GetSellerSettlementRatios(s.ctx, marketID)), buyerFlat: sdk.Coins(s.k.GetBuyerSettlementFlatFees(s.ctx, marketID)).String(), - buyerRatio: s.ratiosString(s.k.GetBuyerSettlementRatios(s.ctx, marketID)), + buyerRatio: exchange.FeeRatiosString(s.k.GetBuyerSettlementRatios(s.ctx, marketID)), } } @@ -3875,14 +3853,10 @@ type permChecker func(ctx sdk.Context, marketID uint32, address string) bool // runPermTest runs a set of tests on a permission checking function, e.g. CanSettleOrders. func (s *TestSuite) runPermTest(perm exchange.Permission, checker permChecker, name string) { allPermsAcc := sdk.AccAddress("allPerms____________") - allPermsAddr := allPermsAcc.String() justPermAcc := sdk.AccAddress("justPerm____________") - justPermAddr := justPermAcc.String() otherPermsAcc := sdk.AccAddress("otherPerms__________") - otherPermsAddr := otherPermsAcc.String() noPermsAcc := sdk.AccAddress("noPerms_____________") - noPermsAddr := noPermsAcc.String() - authorityAddr := s.k.GetAuthority() + authority := s.k.GetAuthority() allPerms := exchange.AllPermissions() otherPerms := make([]exchange.Permission, 0, len(allPermsAcc)-1) @@ -3925,31 +3899,31 @@ func (s *TestSuite) runPermTest(perm exchange.Permission, checker permChecker, n { name: "empty state: authority", marketID: 1, - admin: authorityAddr, + admin: authority, expected: true, }, { name: "empty state: addr with all perms", marketID: 1, - admin: allPermsAddr, + admin: allPermsAcc.String(), expected: false, }, { name: "empty state: addr with just perm", marketID: 1, - admin: justPermAddr, + admin: justPermAcc.String(), expected: false, }, { name: "empty state: addr with all other perms", marketID: 1, - admin: otherPermsAddr, + admin: otherPermsAcc.String(), expected: false, }, { name: "empty state: addr without any perms", marketID: 1, - admin: noPermsAddr, + admin: noPermsAcc.String(), expected: false, }, @@ -3964,35 +3938,35 @@ func (s *TestSuite) runPermTest(perm exchange.Permission, checker permChecker, n name: "existing market: authority", setup: defaultSetup, marketID: 11, - admin: authorityAddr, + admin: authority, expected: true, }, { name: "existing market: addr with all perms", setup: defaultSetup, marketID: 11, - admin: allPermsAddr, + admin: allPermsAcc.String(), expected: true, }, { name: "existing market: addr with just perm", setup: defaultSetup, marketID: 11, - admin: justPermAddr, + admin: justPermAcc.String(), expected: true, }, { name: "existing market: addr with all other perms", setup: defaultSetup, marketID: 11, - admin: otherPermsAddr, + admin: otherPermsAcc.String(), expected: false, }, { name: "existing market: addr without any perms", setup: defaultSetup, marketID: 11, - admin: noPermsAddr, + admin: noPermsAcc.String(), expected: false, }, } diff --git a/x/exchange/keeper/mocks_test.go b/x/exchange/keeper/mocks_test.go index a0b6596805..95de9161a7 100644 --- a/x/exchange/keeper/mocks_test.go +++ b/x/exchange/keeper/mocks_test.go @@ -3,44 +3,17 @@ package keeper_test import ( "errors" "fmt" - "strings" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/quarantine" + attrtypes "github.com/provenance-io/provenance/x/attribute/types" "github.com/provenance-io/provenance/x/exchange" ) -// toStrings converts a slice to indexed strings using the provided stringer func. -func toStrings[T any](vals []T, stringer func(T) string) []string { - if vals == nil { - return nil - } - rv := make([]string, len(vals)) - for i, val := range vals { - rv[i] = fmt.Sprintf("[%d]:%s", i, stringer(val)) - } - return rv -} - -// assertEqualSlice asserts that expected = actual and returns true if so. -// If not, returns false and the stringer is applied to each entry and the comparison -// is redone on the strings in the hopes that it helps identify the problem. -func assertEqualSlice[T any](s *TestSuite, expected, actual []T, stringer func(T) string, msg string, args ...interface{}) bool { - s.T().Helper() - if s.Assert().Equalf(expected, actual, msg, args...) { - return true - } - // compare each as strings in the hopes that makes it easier to identify the problem. - expStrs := toStrings(expected, stringer) - actStrs := toStrings(actual, stringer) - s.Assert().Equalf(expStrs, actStrs, "strings: "+msg, args...) - return false -} - // ############################################################################# // ############################# ############################# // ########################### MockAccountKeeper ########################### @@ -403,8 +376,8 @@ func (s *TestSuite) assertInputOutputCoinsCalls(mk *MockBankKeeper, expected []* func (s *TestSuite) assertBankKeeperCalls(mk *MockBankKeeper, expected BankCalls, msg string, args ...interface{}) bool { s.T().Helper() rv := s.assertSendCoinsCalls(mk, expected.SendCoins, msg, args...) - rv = s.assertSendCoinsFromAccountToModuleCalls(mk, expected.SendCoinsFromAccountToModule, msg, args...) && rv - return s.assertInputOutputCoinsCalls(mk, expected.InputOutputCoins, msg, args...) && rv + rv = s.assertInputOutputCoinsCalls(mk, expected.InputOutputCoins, msg, args...) && rv + return s.assertSendCoinsFromAccountToModuleCalls(mk, expected.SendCoinsFromAccountToModule, msg, args...) && rv } // NewSendCoinsArgs creates a new record of args provided to a call to SendCoins. @@ -463,8 +436,7 @@ func (s *TestSuite) inputString(a banktypes.Input) string { // inputsString creates a string of a slice of banktypes.Input substituting the address names as possible. func (s *TestSuite) inputsString(vals []banktypes.Input) string { - strs := toStrings(vals, s.inputString) - return fmt.Sprintf("{%s}", strings.Join(strs, ", ")) + return fmt.Sprintf("{%s}", sliceString(vals, s.inputString)) } // outputString creates a string of a banktypes.Output substituting the address names as possible. @@ -474,8 +446,7 @@ func (s *TestSuite) outputString(a banktypes.Output) string { // outputsString creates a string of a slice of banktypes.Output substituting the address names as possible. func (s *TestSuite) outputsString(vals []banktypes.Output) string { - strs := toStrings(vals, s.outputString) - return fmt.Sprintf("{%s}", strings.Join(strs, ", ")) + return fmt.Sprintf("{%s}", sliceString(vals, s.outputString)) } // ############################################################################# diff --git a/x/exchange/keeper/msg_server_test.go b/x/exchange/keeper/msg_server_test.go index 85afd81884..2e31c79896 100644 --- a/x/exchange/keeper/msg_server_test.go +++ b/x/exchange/keeper/msg_server_test.go @@ -9,7 +9,9 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" + "github.com/provenance-io/provenance/testutil/assertions" attrtypes "github.com/provenance-io/provenance/x/attribute/types" "github.com/provenance-io/provenance/x/exchange" "github.com/provenance-io/provenance/x/exchange/keeper" @@ -25,6 +27,8 @@ import ( const invReqErr = "invalid request" // msgServerTestDef is the definition of a msg service endoint to be tested. +// R is the request Msg type. S is the response Msg type. +// F is a type that holds arguments to provide to the followup function. type msgServerTestDef[R any, S any, F any] struct { // endpointName is the name of the endpoint being tested. endpointName string @@ -39,6 +43,8 @@ type msgServerTestDef[R any, S any, F any] struct { } // msgServerTestCase is a test case for a msg server endpoint +// R is the request Msg type. +// F is a type that holds arguments to provide to the followup function. type msgServerTestCase[R any, F any] struct { // name is the name of the test case. name string @@ -59,6 +65,8 @@ type msgServerTestCase[R any, F any] struct { // runMsgServerTestCase runs a unit test on a MsgServer endpoint. // A cached context is used so each test case won't affect the others. +// R is the request Msg type. S is the response Msg type. +// F is a type that holds arguments to provide to the td.followup function. func runMsgServerTestCase[R any, S any, F any](s *TestSuite, td msgServerTestDef[R, S, F], tc msgServerTestCase[R, F]) { s.T().Helper() origCtx := s.ctx @@ -98,71 +106,82 @@ func runMsgServerTestCase[R any, S any, F any](s *TestSuite, td msgServerTestDef td.followup(&tc.msg, tc.fArgs) } -// newAttr creates a new EventAttribute with the provided key and the quoted value. +// newAttr creates a new EventAttribute with the provided key and value. func (s *TestSuite) newAttr(key, value string) abci.EventAttribute { - return abci.EventAttribute{Key: []byte(key), Value: []byte(fmt.Sprintf("%q", value))} -} - -// newAttrNoQ creates a new EventAttribute with the provided key and value (without quoting the value). -func (s *TestSuite) newAttrNoQ(key, value string) abci.EventAttribute { return abci.EventAttribute{Key: []byte(key), Value: []byte(value)} } -// eventCoinSpent creates a new "coin_spent" event. +// eventCoinSpent creates a new "coin_spent" event (emitted by the bank module). func (s *TestSuite) eventCoinSpent(spender sdk.AccAddress, amount string) sdk.Event { return sdk.Event{ Type: "coin_spent", Attributes: []abci.EventAttribute{ - s.newAttrNoQ("spender", spender.String()), - s.newAttrNoQ("amount", amount), + s.newAttr("spender", spender.String()), + s.newAttr("amount", amount), }, } } -// eventCoinReceived creates a new "coin_received" event. +// eventCoinReceived creates a new "coin_received" event (emitted by the bank module). func (s *TestSuite) eventCoinReceived(receiver sdk.AccAddress, amount string) sdk.Event { return sdk.Event{ Type: "coin_received", Attributes: []abci.EventAttribute{ - s.newAttrNoQ("receiver", receiver.String()), - s.newAttrNoQ("amount", amount), + s.newAttr("receiver", receiver.String()), + s.newAttr("amount", amount), }, } } -// eventTransfer creates a new "transfer" event. +// eventTransfer creates a new "transfer" event (emitted by the bank module). func (s *TestSuite) eventTransfer(recipient, sender sdk.AccAddress, amount string) sdk.Event { rv := sdk.Event{Type: "transfer"} if len(recipient) > 0 { - rv.Attributes = append(rv.Attributes, s.newAttrNoQ("recipient", recipient.String())) + rv.Attributes = append(rv.Attributes, s.newAttr("recipient", recipient.String())) } if len(sender) > 0 { - rv.Attributes = append(rv.Attributes, s.newAttrNoQ("sender", sender.String())) + rv.Attributes = append(rv.Attributes, s.newAttr("sender", sender.String())) } - rv.Attributes = append(rv.Attributes, s.newAttrNoQ("amount", amount)) + rv.Attributes = append(rv.Attributes, s.newAttr("amount", amount)) return rv } -// eventMessage creates a new "message" event. -func (s *TestSuite) eventMessage(sender sdk.AccAddress) sdk.Event { +// eventMessageSender creates a new "message" event with a "sender" attr (emitted by the bank module). +func (s *TestSuite) eventMessageSender(sender sdk.AccAddress) sdk.Event { return sdk.Event{ Type: "message", - Attributes: []abci.EventAttribute{s.newAttrNoQ("sender", sender.String())}, + Attributes: []abci.EventAttribute{s.newAttr("sender", sender.String())}, } } -// eventHoldAdded creates a new event emitted when a hold is added. +// eventHoldAdded creates a new event emitted when a hold is added (emitted by the hold module). func (s *TestSuite) eventHoldAdded(addr sdk.AccAddress, amount string, orderID uint64) sdk.Event { return s.untypeEvent(&hold.EventHoldAdded{ Address: addr.String(), Amount: amount, Reason: fmt.Sprintf("x/exchange: order %d", orderID), }) } -// eventHoldAdded creates a new event emitted when a hold is released. +// eventHoldAdded creates a new event emitted when a hold is released (emitted by the hold module). func (s *TestSuite) eventHoldReleased(addr sdk.AccAddress, amount string) sdk.Event { return s.untypeEvent(&hold.EventHoldReleased{Address: addr.String(), Amount: amount}) } +// requireFundAccount calls testutil.FundAccount, making sure it doesn't panic or return an error. +func (s *TestSuite) requireFundAccount(addr sdk.AccAddress, coins string) { + assertions.RequireNotPanicsNoErrorf(s.T(), func() error { + return testutil.FundAccount(s.app.BankKeeper, s.ctx, addr, s.coins(coins)) + }, "FundAccount(%s, %q)", s.getAddrName(addr), coins) +} + +// requireAddHold calls s.app.HoldKeeper.AddHold, making sure it doesn't panic or return an error. +func (s *TestSuite) requireAddHold(addr sdk.AccAddress, holdCoins string, orderID uint64) { + coins := s.coins(holdCoins) + reason := fmt.Sprintf("test hold on order %d", orderID) + assertions.RequireNotPanicsNoErrorf(s.T(), func() error { + return s.app.HoldKeeper.AddHold(s.ctx, addr, coins, reason) + }, "AddHold(%s, %q, %q)", s.getAddrName(addr), holdCoins, reason) +} + // requireSetNameRecord creates a name record, requiring it to not error. func (s *TestSuite) requireSetNameRecord(name string, owner sdk.AccAddress) { err := s.app.NameKeeper.SetNameRecord(s.ctx, name, owner, true) @@ -464,11 +483,11 @@ func (s *TestSuite) TestMsgServer_CreateAsk() { s.eventCoinSpent(s.addr2, "8pear"), s.eventCoinReceived(s.marketAddr2, "8pear"), s.eventTransfer(s.marketAddr2, s.addr2, "8pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.marketAddr2, "1pear"), s.eventCoinReceived(s.feeCollectorAddr, "1pear"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr2, "1pear"), - s.eventMessage(s.marketAddr2), + s.eventMessageSender(s.marketAddr2), s.eventHoldAdded(s.addr2, "75apple", 7), s.untypeEvent(&exchange.EventOrderCreated{ OrderId: 7, OrderType: "ask", MarketId: 2, ExternalId: "just-an-id", @@ -507,11 +526,11 @@ func (s *TestSuite) TestMsgServer_CreateAsk() { s.eventCoinSpent(s.addr2, "8fig"), s.eventCoinReceived(s.marketAddr3, "8fig"), s.eventTransfer(s.marketAddr3, s.addr2, "8fig"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.marketAddr3, "1fig"), s.eventCoinReceived(s.feeCollectorAddr, "1fig"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr3, "1fig"), - s.eventMessage(s.marketAddr3), + s.eventMessageSender(s.marketAddr3), s.eventHoldAdded(s.addr2, "75apple,12fig", 12345), s.untypeEvent(&exchange.EventOrderCreated{ OrderId: 12345, OrderType: "ask", MarketId: 3, ExternalId: "", @@ -714,11 +733,11 @@ func (s *TestSuite) TestMsgServer_CreateBid() { s.eventCoinSpent(s.addr2, "8pear"), s.eventCoinReceived(s.marketAddr2, "8pear"), s.eventTransfer(s.marketAddr2, s.addr2, "8pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.marketAddr2, "1pear"), s.eventCoinReceived(s.feeCollectorAddr, "1pear"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr2, "1pear"), - s.eventMessage(s.marketAddr2), + s.eventMessageSender(s.marketAddr2), s.eventHoldAdded(s.addr2, "87pear", 7), s.untypeEvent(&exchange.EventOrderCreated{ OrderId: 7, OrderType: "bid", MarketId: 2, ExternalId: "some-random-id", @@ -1041,11 +1060,11 @@ func (s *TestSuite) TestMsgServer_FillBids() { s.eventCoinSpent(s.addr2, "10apple"), s.eventCoinReceived(s.addr1, "10apple"), s.eventTransfer(s.addr1, s.addr2, "10apple"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.addr1, "50pear"), s.eventCoinReceived(s.addr2, "50pear"), s.eventTransfer(s.addr2, s.addr1, "50pear"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.untypeEvent(&exchange.EventOrderFilled{ OrderId: 54, Assets: "10apple", Price: "50pear", MarketId: 3, }), @@ -1264,7 +1283,7 @@ func (s *TestSuite) TestMsgServer_FillBids() { // Asset transfer events. s.eventCoinSpent(s.addr1, "13apple"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.eventCoinReceived(s.addr2, "10apple"), s.eventTransfer(s.addr2, nil, "10apple"), s.eventCoinReceived(s.addr3, "3apple"), @@ -1272,19 +1291,19 @@ func (s *TestSuite) TestMsgServer_FillBids() { // Price transfer events. s.eventCoinSpent(s.addr2, "50pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.addr3, "20pear"), - s.eventMessage(s.addr3), + s.eventMessageSender(s.addr3), s.eventCoinReceived(s.addr1, "70pear"), s.eventTransfer(s.addr1, nil, "70pear"), // Settlement fee transfer events. s.eventCoinSpent(s.addr2, "35fig"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.addr3, "32fig"), - s.eventMessage(s.addr3), + s.eventMessageSender(s.addr3), s.eventCoinSpent(s.addr1, "9pear"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.eventCoinReceived(s.marketAddr1, "67fig,9pear"), s.eventTransfer(s.marketAddr1, nil, "67fig,9pear"), @@ -1292,7 +1311,7 @@ func (s *TestSuite) TestMsgServer_FillBids() { s.eventCoinSpent(s.marketAddr1, "4fig,1pear"), s.eventCoinReceived(s.feeCollectorAddr, "4fig,1pear"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr1, "4fig,1pear"), - s.eventMessage(s.marketAddr1), + s.eventMessageSender(s.marketAddr1), // Order filled events. s.untypeEvent(&exchange.EventOrderFilled{ @@ -1316,13 +1335,13 @@ func (s *TestSuite) TestMsgServer_FillBids() { s.eventCoinSpent(s.addr1, "10fig"), s.eventCoinReceived(s.marketAddr1, "10fig"), s.eventTransfer(s.marketAddr1, s.addr1, "10fig"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), // Transfer of exchange portion of order creation fees. s.eventCoinSpent(s.marketAddr1, "1fig"), s.eventCoinReceived(s.feeCollectorAddr, "1fig"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr1, "1fig"), - s.eventMessage(s.marketAddr1), + s.eventMessageSender(s.marketAddr1), }, }, } @@ -1414,11 +1433,11 @@ func (s *TestSuite) TestMsgServer_FillAsks() { s.eventCoinSpent(s.addr2, "10apple"), s.eventCoinReceived(s.addr1, "10apple"), s.eventTransfer(s.addr1, s.addr2, "10apple"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.addr1, "50pear"), s.eventCoinReceived(s.addr2, "50pear"), s.eventTransfer(s.addr2, s.addr1, "50pear"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.untypeEvent(&exchange.EventOrderFilled{ OrderId: 54, Assets: "10apple", Price: "50pear", MarketId: 3, }), @@ -1636,15 +1655,15 @@ func (s *TestSuite) TestMsgServer_FillAsks() { // Asset transfer events. s.eventCoinSpent(s.addr2, "10apple"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.addr3, "3apple"), - s.eventMessage(s.addr3), + s.eventMessageSender(s.addr3), s.eventCoinReceived(s.addr1, "13apple"), s.eventTransfer(s.addr1, nil, "13apple"), // Price transfer events. s.eventCoinSpent(s.addr1, "70pear"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.eventCoinReceived(s.addr2, "50pear"), s.eventTransfer(s.addr2, nil, "50pear"), s.eventCoinReceived(s.addr3, "20pear"), @@ -1652,11 +1671,11 @@ func (s *TestSuite) TestMsgServer_FillAsks() { // Settlement fee transfer events. s.eventCoinSpent(s.addr2, "8pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.addr3, "12fig,2pear"), - s.eventMessage(s.addr3), + s.eventMessageSender(s.addr3), s.eventCoinSpent(s.addr1, "37fig"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.eventCoinReceived(s.marketAddr1, "49fig,10pear"), s.eventTransfer(s.marketAddr1, nil, "49fig,10pear"), @@ -1664,7 +1683,7 @@ func (s *TestSuite) TestMsgServer_FillAsks() { s.eventCoinSpent(s.marketAddr1, "3fig,1pear"), s.eventCoinReceived(s.feeCollectorAddr, "3fig,1pear"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr1, "3fig,1pear"), - s.eventMessage(s.marketAddr1), + s.eventMessageSender(s.marketAddr1), // Order filled events. s.untypeEvent(&exchange.EventOrderFilled{ @@ -1688,13 +1707,13 @@ func (s *TestSuite) TestMsgServer_FillAsks() { s.eventCoinSpent(s.addr1, "10fig"), s.eventCoinReceived(s.marketAddr1, "10fig"), s.eventTransfer(s.marketAddr1, s.addr1, "10fig"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), // Transfer of exchange portion of order creation fees. s.eventCoinSpent(s.marketAddr1, "1fig"), s.eventCoinReceived(s.feeCollectorAddr, "1fig"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr1, "1fig"), - s.eventMessage(s.marketAddr1), + s.eventMessageSender(s.marketAddr1), }, }, } @@ -1976,9 +1995,9 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { s.eventCoinSpent(s.addr1, "7apple"), s.eventCoinReceived(s.addr4, "7apple"), s.eventTransfer(s.addr4, s.addr1, "7apple"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.eventCoinSpent(s.addr3, "11apple"), - s.eventMessage(s.addr3), + s.eventMessageSender(s.addr3), s.eventCoinReceived(s.addr4, "1apple"), s.eventTransfer(s.addr4, nil, "1apple"), s.eventCoinReceived(s.addr2, "10apple"), @@ -1986,13 +2005,13 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { // Price transfers s.eventCoinSpent(s.addr4, "85pear"), - s.eventMessage(s.addr4), + s.eventMessageSender(s.addr4), s.eventCoinReceived(s.addr1, "75pear"), s.eventTransfer(s.addr1, nil, "75pear"), s.eventCoinReceived(s.addr3, "10pear"), s.eventTransfer(s.addr3, nil, "10pear"), s.eventCoinSpent(s.addr2, "100pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinReceived(s.addr3, "98pear"), s.eventTransfer(s.addr3, nil, "98pear"), s.eventCoinReceived(s.addr1, "2pear"), @@ -2068,13 +2087,13 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { s.eventCoinSpent(s.addr1, "7apple"), s.eventCoinReceived(s.addr2, "7apple"), s.eventTransfer(s.addr2, s.addr1, "7apple"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), // Price transfer s.eventCoinSpent(s.addr2, "75pear"), s.eventCoinReceived(s.addr1, "75pear"), s.eventTransfer(s.addr1, s.addr2, "75pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), // Orders filled s.untypeEvent(&exchange.EventOrderFilled{ @@ -2141,13 +2160,13 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { s.eventCoinSpent(s.addr1, "7apple"), s.eventCoinReceived(s.addr2, "7apple"), s.eventTransfer(s.addr2, s.addr1, "7apple"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), // Price transfer s.eventCoinSpent(s.addr2, "70pear"), s.eventCoinReceived(s.addr1, "70pear"), s.eventTransfer(s.addr1, s.addr2, "70pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), // Orders filled s.untypeEvent(&exchange.EventOrderFilled{ @@ -2253,9 +2272,9 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { s.eventCoinSpent(s.addr1, "7apple"), s.eventCoinReceived(s.addr2, "7apple"), s.eventTransfer(s.addr2, s.addr1, "7apple"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.eventCoinSpent(s.addr3, "11apple"), - s.eventMessage(s.addr3), + s.eventMessageSender(s.addr3), s.eventCoinReceived(s.addr2, "3apple"), s.eventTransfer(s.addr2, nil, "3apple"), s.eventCoinReceived(s.addr4, "8apple"), @@ -2263,13 +2282,13 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { // Price transfers s.eventCoinSpent(s.addr2, "100pear"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinReceived(s.addr1, "75pear"), s.eventTransfer(s.addr1, nil, "75pear"), s.eventCoinReceived(s.addr3, "25pear"), s.eventTransfer(s.addr3, nil, "25pear"), s.eventCoinSpent(s.addr4, "85pear"), - s.eventMessage(s.addr4), + s.eventMessageSender(s.addr4), s.eventCoinReceived(s.addr3, "83pear"), s.eventTransfer(s.addr3, nil, "83pear"), s.eventCoinReceived(s.addr1, "2pear"), @@ -2277,13 +2296,13 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { // Fee transfers to market s.eventCoinSpent(s.addr1, "10fig,8pear"), - s.eventMessage(s.addr1), + s.eventMessageSender(s.addr1), s.eventCoinSpent(s.addr3, "16pear"), - s.eventMessage(s.addr3), + s.eventMessageSender(s.addr3), s.eventCoinSpent(s.addr2, "20fig"), - s.eventMessage(s.addr2), + s.eventMessageSender(s.addr2), s.eventCoinSpent(s.addr4, "10pear"), - s.eventMessage(s.addr4), + s.eventMessageSender(s.addr4), s.eventCoinReceived(s.marketAddr2, "30fig,34pear"), s.eventTransfer(s.marketAddr2, nil, "30fig,34pear"), @@ -2291,7 +2310,7 @@ func (s *TestSuite) TestMsgServer_MarketSettle() { s.eventCoinSpent(s.marketAddr2, "2fig,2pear"), s.eventCoinReceived(s.feeCollectorAddr, "2fig,2pear"), s.eventTransfer(s.feeCollectorAddr, s.marketAddr2, "2fig,2pear"), - s.eventMessage(s.marketAddr2), + s.eventMessageSender(s.marketAddr2), // Orders filled s.untypeEvent(&exchange.EventOrderFilled{ @@ -2541,7 +2560,7 @@ func (s *TestSuite) TestMsgServer_MarketWithdraw() { s.eventCoinSpent(s.marketAddr1, "3apple,50fig,100pear"), s.eventCoinReceived(s.addr1, "3apple,50fig,100pear"), s.eventTransfer(s.addr1, s.marketAddr1, "3apple,50fig,100pear"), - s.eventMessage(s.marketAddr1), + s.eventMessageSender(s.marketAddr1), s.untypeEvent(&exchange.EventMarketWithdraw{ MarketId: 1, Amount: "3apple,50fig,100pear", diff --git a/x/exchange/keeper/orders_test.go b/x/exchange/keeper/orders_test.go index 8e15e8453b..39cc67a6d4 100644 --- a/x/exchange/keeper/orders_test.go +++ b/x/exchange/keeper/orders_test.go @@ -11,17 +11,6 @@ import ( "github.com/provenance-io/provenance/x/exchange/keeper" ) -// assertEqualOrderID asserts that two uint64 values are equal, and if not, includes their decimal form in the log. -// This is nice because .Equal failures output uints in hex, which can make it difficult to identify what's going on. -func (s *TestSuite) assertEqualOrderID(expected, actual uint64, msgAndArgs ...interface{}) bool { - if s.Assert().Equal(expected, actual, msgAndArgs...) { - return true - } - s.T().Logf("Expected order id: %d", expected) - s.T().Logf(" Actual order id: %d", actual) - return false -} - func (s *TestSuite) TestKeeper_GetOrder() { tests := []struct { name string @@ -930,7 +919,7 @@ func (s *TestSuite) TestKeeper_CreateAskOrder() { s.Require().NoError(err, "GetOrder(%d) error (the one just created)", orderID) s.Assert().Equal(expOrder, order, "GetOrder(%d) (the one just created)", orderID) lastOrderID := keeper.GetLastOrderID(s.getStore()) - s.Assert().Equal(tc.expOrderID, lastOrderID, "last order id") + s.assertEqualOrderID(tc.expOrderID, lastOrderID, "last order id") }) } } @@ -1431,7 +1420,7 @@ func (s *TestSuite) TestKeeper_CreateBidOrder() { s.Require().NoError(err, "error from GetOrder(%d) (the one just created)", orderID) s.Assert().Equal(expOrder, order, "GetOrder(%d) (the one just created)", orderID) lastOrderID := keeper.GetLastOrderID(s.getStore()) - s.Assert().Equal(tc.expOrderID, lastOrderID, "last order id") + s.assertEqualOrderID(tc.expOrderID, lastOrderID, "last order id") }) } } @@ -2272,27 +2261,39 @@ func (s *TestSuite) TestKeeper_IterateOrders() { } s.Require().NotPanics(testFunc, "IterateOrders") s.assertErrorValue(err, tc.expErr, "IterateOrders error") - s.Assert().Equal(tc.expOrders, orders, "orders iterated") + s.assertEqualOrders(tc.expOrders, orders, "orders iterated") }) } } -func (s *TestSuite) TestKeeper_IterateMarketOrders() { - type cbArgs struct { - orderID uint64 - orderTypeByte byte - } - newCBArgs := func(orderID uint64, orderTypeByte byte) cbArgs { - return cbArgs{orderID: orderID, orderTypeByte: orderTypeByte} +// orderIterCBArgs are the args provided to an order index iterator. +type orderIterCBArgs struct { + orderID uint64 + orderTypeByte byte +} + +// orderIDString gets this order id as a string. +func (a orderIterCBArgs) orderIDString() string { + return fmt.Sprintf("%d", a.orderID) +} + +// newOrderIterCBArgs creates a new orderIterCBArgs. +func newOrderIterCBArgs(orderID uint64, orderTypeByte byte) orderIterCBArgs { + return orderIterCBArgs{ + orderID: orderID, + orderTypeByte: orderTypeByte, } - var seen []cbArgs +} + +func (s *TestSuite) TestKeeper_IterateMarketOrders() { + var seen []orderIterCBArgs getAll := func(orderID uint64, orderTypeByte byte) bool { - seen = append(seen, newCBArgs(orderID, orderTypeByte)) + seen = append(seen, newOrderIterCBArgs(orderID, orderTypeByte)) return false } stopAfter := func(count int) func(orderID uint64, orderTypeByte byte) bool { return func(orderID uint64, orderTypeByte byte) bool { - seen = append(seen, newCBArgs(orderID, orderTypeByte)) + seen = append(seen, newOrderIterCBArgs(orderID, orderTypeByte)) return len(seen) >= count } } @@ -2302,7 +2303,7 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { setup func() marketID uint32 cb func(orderID uint64, orderTypeByte byte) bool - expSeen []cbArgs + expSeen []orderIterCBArgs }{ { name: "empty state", @@ -2336,7 +2337,7 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { store.Set(keeper.MakeIndexKeyMarketToOrder(3, 7), []byte{keeper.OrderKeyTypeAsk}) }, marketID: 2, - expSeen: []cbArgs{newCBArgs(4, keeper.OrderKeyTypeAsk)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(4, keeper.OrderKeyTypeAsk)}, }, { name: "one entry: bid", @@ -2351,7 +2352,7 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { store.Set(keeper.MakeIndexKeyMarketToOrder(3, 7), []byte{keeper.OrderKeyTypeAsk}) }, marketID: 2, - expSeen: []cbArgs{newCBArgs(4, keeper.OrderKeyTypeBid)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(4, keeper.OrderKeyTypeBid)}, }, { name: "one entry no value", @@ -2380,12 +2381,12 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { store.Set(keeper.MakeIndexKeyMarketToOrder(4, 5), []byte{keeper.OrderKeyTypeBid}) }, marketID: 4, - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeBid), - newCBArgs(2, keeper.OrderKeyTypeBid), - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(4, keeper.OrderKeyTypeAsk), - newCBArgs(5, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(2, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(4, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(5, keeper.OrderKeyTypeBid), }, }, { @@ -2400,7 +2401,7 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { }, marketID: 4, cb: stopAfter(1), - expSeen: []cbArgs{newCBArgs(1, keeper.OrderKeyTypeBid)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(1, keeper.OrderKeyTypeBid)}, }, { name: "five entries, 1 through 5: get three", @@ -2414,10 +2415,10 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { }, marketID: 4, cb: stopAfter(3), - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeAsk), - newCBArgs(2, keeper.OrderKeyTypeBid), - newCBArgs(3, keeper.OrderKeyTypeAsk), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(2, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(3, keeper.OrderKeyTypeAsk), }, }, { @@ -2431,12 +2432,12 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { store.Set(keeper.MakeIndexKeyMarketToOrder(7, 56), []byte{keeper.OrderKeyTypeAsk}) }, marketID: 7, - expSeen: []cbArgs{ - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(44, keeper.OrderKeyTypeAsk), - newCBArgs(56, keeper.OrderKeyTypeAsk), - newCBArgs(75, keeper.OrderKeyTypeBid), - newCBArgs(96, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(44, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(56, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(75, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(96, keeper.OrderKeyTypeBid), }, }, { @@ -2451,7 +2452,7 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { }, marketID: 7, cb: stopAfter(1), - expSeen: []cbArgs{newCBArgs(3, keeper.OrderKeyTypeAsk)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(3, keeper.OrderKeyTypeAsk)}, }, { name: "five entries, random: get three", @@ -2465,10 +2466,10 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { }, marketID: 7, cb: stopAfter(3), - expSeen: []cbArgs{ - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(44, keeper.OrderKeyTypeBid), - newCBArgs(56, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(44, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(56, keeper.OrderKeyTypeBid), }, }, { @@ -2482,10 +2483,10 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { store.Set(keeper.MakeIndexKeyMarketToOrder(27, 5), []byte{keeper.OrderKeyTypeAsk}) }, marketID: 27, - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeAsk), - newCBArgs(4, keeper.OrderKeyTypeBid), - newCBArgs(5, keeper.OrderKeyTypeAsk), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(4, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(5, keeper.OrderKeyTypeAsk), }, }, } @@ -2506,27 +2507,20 @@ func (s *TestSuite) TestKeeper_IterateMarketOrders() { s.k.IterateMarketOrders(s.ctx, tc.marketID, tc.cb) } s.Require().NotPanics(testFunc, "IterateMarketOrders(%d)", tc.marketID) - s.Assert().Equal(tc.expSeen, seen, "args provided to callback") + assertEqualSlice(s, tc.expSeen, seen, orderIterCBArgs.orderIDString, "args provided to callback") }) } } func (s *TestSuite) TestKeeper_IterateAddressOrders() { - type cbArgs struct { - orderID uint64 - orderTypeByte byte - } - newCBArgs := func(orderID uint64, orderTypeByte byte) cbArgs { - return cbArgs{orderID: orderID, orderTypeByte: orderTypeByte} - } - var seen []cbArgs + var seen []orderIterCBArgs getAll := func(orderID uint64, orderTypeByte byte) bool { - seen = append(seen, newCBArgs(orderID, orderTypeByte)) + seen = append(seen, newOrderIterCBArgs(orderID, orderTypeByte)) return false } stopAfter := func(count int) func(orderID uint64, orderTypeByte byte) bool { return func(orderID uint64, orderTypeByte byte) bool { - seen = append(seen, newCBArgs(orderID, orderTypeByte)) + seen = append(seen, newOrderIterCBArgs(orderID, orderTypeByte)) return len(seen) >= count } } @@ -2536,7 +2530,7 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { setup func() addr sdk.AccAddress cb func(orderID uint64, orderTypeByte byte) bool - expSeen []cbArgs + expSeen []orderIterCBArgs }{ { name: "empty state", @@ -2570,7 +2564,7 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { store.Set(keeper.MakeIndexKeyAddressToOrder(s.addr3, 7), []byte{keeper.OrderKeyTypeAsk}) }, addr: s.addr2, - expSeen: []cbArgs{newCBArgs(4, keeper.OrderKeyTypeAsk)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(4, keeper.OrderKeyTypeAsk)}, }, { name: "one entry: bid", @@ -2585,7 +2579,7 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { store.Set(keeper.MakeIndexKeyAddressToOrder(s.addr3, 7), []byte{keeper.OrderKeyTypeAsk}) }, addr: s.addr2, - expSeen: []cbArgs{newCBArgs(4, keeper.OrderKeyTypeBid)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(4, keeper.OrderKeyTypeBid)}, }, { name: "one entry no value", @@ -2614,12 +2608,12 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { store.Set(keeper.MakeIndexKeyAddressToOrder(s.addr4, 5), []byte{keeper.OrderKeyTypeBid}) }, addr: s.addr4, - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeBid), - newCBArgs(2, keeper.OrderKeyTypeBid), - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(4, keeper.OrderKeyTypeAsk), - newCBArgs(5, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(2, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(4, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(5, keeper.OrderKeyTypeBid), }, }, { @@ -2634,7 +2628,7 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { }, addr: s.addr4, cb: stopAfter(1), - expSeen: []cbArgs{newCBArgs(1, keeper.OrderKeyTypeBid)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(1, keeper.OrderKeyTypeBid)}, }, { name: "five entries, 1 through 5: get three", @@ -2648,10 +2642,10 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { }, addr: s.addr2, cb: stopAfter(3), - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeAsk), - newCBArgs(2, keeper.OrderKeyTypeBid), - newCBArgs(3, keeper.OrderKeyTypeAsk), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(2, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(3, keeper.OrderKeyTypeAsk), }, }, { @@ -2665,12 +2659,12 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { store.Set(keeper.MakeIndexKeyAddressToOrder(s.addr5, 56), []byte{keeper.OrderKeyTypeAsk}) }, addr: s.addr5, - expSeen: []cbArgs{ - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(44, keeper.OrderKeyTypeAsk), - newCBArgs(56, keeper.OrderKeyTypeAsk), - newCBArgs(75, keeper.OrderKeyTypeBid), - newCBArgs(96, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(44, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(56, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(75, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(96, keeper.OrderKeyTypeBid), }, }, { @@ -2685,7 +2679,7 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { }, addr: s.addr3, cb: stopAfter(1), - expSeen: []cbArgs{newCBArgs(3, keeper.OrderKeyTypeAsk)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(3, keeper.OrderKeyTypeAsk)}, }, { name: "five entries, random: get three", @@ -2699,10 +2693,10 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { }, addr: s.addr3, cb: stopAfter(3), - expSeen: []cbArgs{ - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(44, keeper.OrderKeyTypeBid), - newCBArgs(56, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(44, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(56, keeper.OrderKeyTypeBid), }, }, { @@ -2716,10 +2710,10 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { store.Set(keeper.MakeIndexKeyAddressToOrder(s.addr3, 5), []byte{keeper.OrderKeyTypeAsk}) }, addr: s.addr3, - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeAsk), - newCBArgs(4, keeper.OrderKeyTypeBid), - newCBArgs(5, keeper.OrderKeyTypeAsk), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(4, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(5, keeper.OrderKeyTypeAsk), }, }, } @@ -2740,27 +2734,20 @@ func (s *TestSuite) TestKeeper_IterateAddressOrders() { s.k.IterateAddressOrders(s.ctx, tc.addr, tc.cb) } s.Require().NotPanics(testFunc, "IterateAddressOrders(%s)", s.getAddrName(tc.addr)) - s.Assert().Equal(tc.expSeen, seen, "args provided to callback") + assertEqualSlice(s, tc.expSeen, seen, orderIterCBArgs.orderIDString, "args provided to callback") }) } } func (s *TestSuite) TestKeeper_IterateAssetOrders() { - type cbArgs struct { - orderID uint64 - orderTypeByte byte - } - newCBArgs := func(orderID uint64, orderTypeByte byte) cbArgs { - return cbArgs{orderID: orderID, orderTypeByte: orderTypeByte} - } - var seen []cbArgs + var seen []orderIterCBArgs getAll := func(orderID uint64, orderTypeByte byte) bool { - seen = append(seen, newCBArgs(orderID, orderTypeByte)) + seen = append(seen, newOrderIterCBArgs(orderID, orderTypeByte)) return false } stopAfter := func(count int) func(orderID uint64, orderTypeByte byte) bool { return func(orderID uint64, orderTypeByte byte) bool { - seen = append(seen, newCBArgs(orderID, orderTypeByte)) + seen = append(seen, newOrderIterCBArgs(orderID, orderTypeByte)) return len(seen) >= count } } @@ -2770,7 +2757,7 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { setup func() assetDenom string cb func(orderID uint64, orderTypeByte byte) bool - expSeen []cbArgs + expSeen []orderIterCBArgs }{ { name: "empty state", @@ -2804,7 +2791,7 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { store.Set(keeper.MakeIndexKeyAssetToOrder("cactus", 7), []byte{keeper.OrderKeyTypeAsk}) }, assetDenom: "banana", - expSeen: []cbArgs{newCBArgs(4, keeper.OrderKeyTypeAsk)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(4, keeper.OrderKeyTypeAsk)}, }, { name: "one entry: bid", @@ -2819,7 +2806,7 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { store.Set(keeper.MakeIndexKeyAssetToOrder("cactus", 7), []byte{keeper.OrderKeyTypeAsk}) }, assetDenom: "banana", - expSeen: []cbArgs{newCBArgs(4, keeper.OrderKeyTypeBid)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(4, keeper.OrderKeyTypeBid)}, }, { name: "one entry no value", @@ -2848,12 +2835,12 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { store.Set(keeper.MakeIndexKeyAssetToOrder("acorn", 5), []byte{keeper.OrderKeyTypeBid}) }, assetDenom: "acorn", - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeBid), - newCBArgs(2, keeper.OrderKeyTypeBid), - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(4, keeper.OrderKeyTypeAsk), - newCBArgs(5, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(2, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(4, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(5, keeper.OrderKeyTypeBid), }, }, { @@ -2868,7 +2855,7 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { }, assetDenom: "acorn", cb: stopAfter(1), - expSeen: []cbArgs{newCBArgs(1, keeper.OrderKeyTypeBid)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(1, keeper.OrderKeyTypeBid)}, }, { name: "five entries, 1 through 5: get three", @@ -2882,10 +2869,10 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { }, assetDenom: "acorn", cb: stopAfter(3), - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeAsk), - newCBArgs(2, keeper.OrderKeyTypeBid), - newCBArgs(3, keeper.OrderKeyTypeAsk), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(2, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(3, keeper.OrderKeyTypeAsk), }, }, { @@ -2899,12 +2886,12 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { store.Set(keeper.MakeIndexKeyAssetToOrder("raspberry", 56), []byte{keeper.OrderKeyTypeAsk}) }, assetDenom: "raspberry", - expSeen: []cbArgs{ - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(44, keeper.OrderKeyTypeAsk), - newCBArgs(56, keeper.OrderKeyTypeAsk), - newCBArgs(75, keeper.OrderKeyTypeBid), - newCBArgs(96, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(44, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(56, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(75, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(96, keeper.OrderKeyTypeBid), }, }, { @@ -2919,7 +2906,7 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { }, assetDenom: "raspberry", cb: stopAfter(1), - expSeen: []cbArgs{newCBArgs(3, keeper.OrderKeyTypeAsk)}, + expSeen: []orderIterCBArgs{newOrderIterCBArgs(3, keeper.OrderKeyTypeAsk)}, }, { name: "five entries, random: get three", @@ -2933,10 +2920,10 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { }, assetDenom: "huckleberry", cb: stopAfter(3), - expSeen: []cbArgs{ - newCBArgs(3, keeper.OrderKeyTypeBid), - newCBArgs(44, keeper.OrderKeyTypeBid), - newCBArgs(56, keeper.OrderKeyTypeBid), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(3, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(44, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(56, keeper.OrderKeyTypeBid), }, }, { @@ -2950,10 +2937,10 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { store.Set(keeper.MakeIndexKeyAssetToOrder("huckleberry", 5), []byte{keeper.OrderKeyTypeAsk}) }, assetDenom: "huckleberry", - expSeen: []cbArgs{ - newCBArgs(1, keeper.OrderKeyTypeAsk), - newCBArgs(4, keeper.OrderKeyTypeBid), - newCBArgs(5, keeper.OrderKeyTypeAsk), + expSeen: []orderIterCBArgs{ + newOrderIterCBArgs(1, keeper.OrderKeyTypeAsk), + newOrderIterCBArgs(4, keeper.OrderKeyTypeBid), + newOrderIterCBArgs(5, keeper.OrderKeyTypeAsk), }, }, } @@ -2975,6 +2962,7 @@ func (s *TestSuite) TestKeeper_IterateAssetOrders() { } s.Require().NotPanics(testFunc, "IterateAssetOrders(%q)", tc.assetDenom) s.Assert().Equal(tc.expSeen, seen, "args provided to callback") + assertEqualSlice(s, tc.expSeen, seen, orderIterCBArgs.orderIDString, "args provided to callback") }) } } diff --git a/x/exchange/keeper/suite_test.go b/x/exchange/keeper/suite_test.go new file mode 100644 index 0000000000..83e440f28b --- /dev/null +++ b/x/exchange/keeper/suite_test.go @@ -0,0 +1,646 @@ +package keeper_test + +import ( + "bytes" + "fmt" + "sort" + "strings" + "testing" + + "github.com/gogo/protobuf/proto" + "github.com/rs/zerolog" + "github.com/stretchr/testify/suite" + + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/testutil/assertions" + "github.com/provenance-io/provenance/x/exchange" + "github.com/provenance-io/provenance/x/exchange/keeper" +) + +type TestSuite struct { + suite.Suite + + app *app.App + ctx sdk.Context + + k keeper.Keeper + acctKeeper exchange.AccountKeeper + attrKeeper exchange.AttributeKeeper + bankKeeper exchange.BankKeeper + holdKeeper exchange.HoldKeeper + + bondDenom string + initBal sdk.Coins + initAmount int64 + + addr1 sdk.AccAddress + addr2 sdk.AccAddress + addr3 sdk.AccAddress + addr4 sdk.AccAddress + addr5 sdk.AccAddress + + marketAddr1 sdk.AccAddress + marketAddr2 sdk.AccAddress + marketAddr3 sdk.AccAddress + + feeCollector string + feeCollectorAddr sdk.AccAddress + + accKeeper *MockAccountKeeper + + logBuffer bytes.Buffer +} + +func (s *TestSuite) SetupTest() { + bufferedLoggerMaker := func() log.Logger { + lw := zerolog.ConsoleWriter{ + Out: &s.logBuffer, + NoColor: true, + PartsExclude: []string{"time"}, // Without this, each line starts with " " + } + // Error log lines will start with "ERR ". + // Info log lines will start with "INF ". + // Debug log lines are omitted, but would start with "DBG ". + logger := zerolog.New(lw).Level(zerolog.InfoLevel) + return server.ZeroLogWrapper{Logger: logger} + } + // swap in the buffered logger maker so it's used in app.Setup, but then put it back (since that's a global thing). + defer app.SetLoggerMaker(app.SetLoggerMaker(bufferedLoggerMaker)) + + s.app = app.Setup(s.T()) + s.logBuffer.Reset() + s.ctx = s.app.BaseApp.NewContext(false, tmproto.Header{}) + s.k = s.app.ExchangeKeeper + s.acctKeeper = s.app.AccountKeeper + s.attrKeeper = s.app.AttributeKeeper + s.bankKeeper = s.app.BankKeeper + s.holdKeeper = s.app.HoldKeeper + + s.bondDenom = s.app.StakingKeeper.BondDenom(s.ctx) + s.initAmount = 1_000_000_000 + s.initBal = sdk.NewCoins(sdk.NewCoin(s.bondDenom, sdk.NewInt(s.initAmount))) + + addrs := app.AddTestAddrsIncremental(s.app, s.ctx, 5, sdk.NewInt(s.initAmount)) + s.addr1 = addrs[0] + s.addr2 = addrs[1] + s.addr3 = addrs[2] + s.addr4 = addrs[3] + s.addr5 = addrs[4] + + s.marketAddr1 = exchange.GetMarketAddress(1) + s.marketAddr2 = exchange.GetMarketAddress(2) + s.marketAddr3 = exchange.GetMarketAddress(3) + + s.feeCollector = s.k.GetFeeCollectorName() + s.feeCollectorAddr = authtypes.NewModuleAddress(s.feeCollector) +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(TestSuite)) +} + +// sliceStrings converts each val into a string using the provided stringer. +func sliceStrings[T any](vals []T, stringer func(T) string) []string { + if vals == nil { + return nil + } + strs := make([]string, len(vals)) + for i, v := range vals { + strs[i] = fmt.Sprintf("[%d]:%s", i, stringer(v)) + } + return strs +} + +// sliceString converts each val into a string using the provided stringer and joins them with ", ". +func sliceString[T any](vals []T, stringer func(T) string) string { + if vals == nil { + return "" + } + return strings.Join(sliceStrings(vals, stringer), ", ") +} + +// copySlice returns a copy of a slice using the provided copier for each value. +func copySlice[T any](vals []T, copier func(T) T) []T { + if vals == nil { + return nil + } + rv := make([]T, len(vals)) + for i, v := range vals { + rv[i] = copier(v) + } + return rv +} + +// noOpCopier is a passthrough "copier" function that just returns the exact same thing that was provided. +func noOpCopier[T any](val T) T { + return val +} + +// reverseSlice returns a new slice with the entries reversed. +func reverseSlice[T any](vals []T) []T { + if vals == nil { + return nil + } + rv := make([]T, len(vals)) + for i, val := range vals { + rv[len(vals)-i-1] = val + } + return rv +} + +// getLogOutput gets the log buffer contents. This (probably) also clears the log buffer. +func (s *TestSuite) getLogOutput(msg string, args ...interface{}) string { + logOutput := s.logBuffer.String() + s.T().Logf(msg+" log output:\n%s", append(args, logOutput)...) + return logOutput +} + +// coins creates an sdk.Coins from a string, requiring it to work. +func (s *TestSuite) coins(coins string) sdk.Coins { + s.T().Helper() + rv, err := sdk.ParseCoinsNormalized(coins) + s.Require().NoError(err, "ParseCoinsNormalized(%q)", coins) + return rv +} + +// coin creates a new coin without doing any validation on it. +func (s *TestSuite) coin(coin string) sdk.Coin { + rv, err := sdk.ParseCoinNormalized(coin) + s.Require().NoError(err, "ParseCoinNormalized(%q)", coin) + return rv +} + +// coinP creates a reference to a new coin without doing any validation on it. +func (s *TestSuite) coinP(coin string) *sdk.Coin { + rv := s.coin(coin) + return &rv +} + +// coinsString converts a slice of coin entries into a string. +// This is different from sdk.Coins.String because the entries aren't sorted. +func (s *TestSuite) coinsString(coins []sdk.Coin) string { + return sliceString(coins, func(coin sdk.Coin) string { + return fmt.Sprintf("%q", coin) + }) +} + +// coinPString converts the provided coin to a quoted string, or "". +func (s *TestSuite) coinPString(coin *sdk.Coin) string { + if coin == nil { + return "" + } + return fmt.Sprintf("%q", coin) +} + +// ratio creates a FeeRatio from a ":" string. +func (s *TestSuite) ratio(ratioStr string) exchange.FeeRatio { + rv, err := exchange.ParseFeeRatio(ratioStr) + s.Require().NoError(err, "ParseFeeRatio(%q)", ratioStr) + return *rv +} + +// ratios creates a slice of Fee ratio from a comma delimited list of ":" entries in a string. +func (s *TestSuite) ratios(ratiosStr string) []exchange.FeeRatio { + if len(ratiosStr) == 0 { + return nil + } + + ratios := strings.Split(ratiosStr, ",") + rv := make([]exchange.FeeRatio, len(ratios)) + for i, r := range ratios { + rv[i] = s.ratio(r) + } + return rv +} + +// ratiosStrings converts the ratios into strings. It's because comparsions on sdk.Coin (or sdkmath.Int) are annoying. +func (s *TestSuite) ratiosStrings(ratios []exchange.FeeRatio) []string { + return sliceStrings(ratios, exchange.FeeRatio.String) +} + +// joinErrs joins the provided error strings into a single one to match what errors.Join does. +func (s *TestSuite) joinErrs(errs ...string) string { + return strings.Join(errs, "\n") +} + +// copyCoin creates a copy of a coin (as best as possible). +func (s *TestSuite) copyCoin(orig sdk.Coin) sdk.Coin { + return sdk.NewCoin(orig.Denom, orig.Amount.AddRaw(0)) +} + +// copyCoinP copies a coin that's a reference. +func (s *TestSuite) copyCoinP(orig *sdk.Coin) *sdk.Coin { + if orig == nil { + return nil + } + rv := s.copyCoin(*orig) + return &rv +} + +// copyCoins creates a copy of coins (as best as possible). +func (s *TestSuite) copyCoins(orig []sdk.Coin) []sdk.Coin { + return copySlice(orig, s.copyCoin) +} + +// copyRatio creates a copy of a FeeRatio. +func (s *TestSuite) copyRatio(orig exchange.FeeRatio) exchange.FeeRatio { + return exchange.FeeRatio{ + Price: s.copyCoin(orig.Price), + Fee: s.copyCoin(orig.Fee), + } +} + +// copyRatios creates a copy of a slice of FeeRatios. +func (s *TestSuite) copyRatios(orig []exchange.FeeRatio) []exchange.FeeRatio { + return copySlice(orig, s.copyRatio) +} + +// copyAccessGrant creates a copy of an AccessGrant. +func (s *TestSuite) copyAccessGrant(orig exchange.AccessGrant) exchange.AccessGrant { + return exchange.AccessGrant{ + Address: orig.Address, + Permissions: copySlice(orig.Permissions, noOpCopier[exchange.Permission]), + } +} + +// copyAccessGrants creates a copy of a slice of AccessGrants. +func (s *TestSuite) copyAccessGrants(orig []exchange.AccessGrant) []exchange.AccessGrant { + return copySlice(orig, s.copyAccessGrant) +} + +// copyStrings creates a copy of a slice of strings. +func (s *TestSuite) copyStrings(orig []string) []string { + return copySlice(orig, noOpCopier[string]) +} + +// copyMarket creates a deep copy of a market. +func (s *TestSuite) copyMarket(orig exchange.Market) exchange.Market { + return exchange.Market{ + MarketId: orig.MarketId, + MarketDetails: exchange.MarketDetails{ + Name: orig.MarketDetails.Name, + Description: orig.MarketDetails.Description, + WebsiteUrl: orig.MarketDetails.WebsiteUrl, + IconUri: orig.MarketDetails.IconUri, + }, + FeeCreateAskFlat: s.copyCoins(orig.FeeCreateAskFlat), + FeeCreateBidFlat: s.copyCoins(orig.FeeCreateBidFlat), + FeeSellerSettlementFlat: s.copyCoins(orig.FeeSellerSettlementFlat), + FeeSellerSettlementRatios: s.copyRatios(orig.FeeSellerSettlementRatios), + FeeBuyerSettlementFlat: s.copyCoins(orig.FeeBuyerSettlementFlat), + FeeBuyerSettlementRatios: s.copyRatios(orig.FeeBuyerSettlementRatios), + AcceptingOrders: orig.AcceptingOrders, + AllowUserSettlement: orig.AllowUserSettlement, + AccessGrants: s.copyAccessGrants(orig.AccessGrants), + ReqAttrCreateAsk: s.copyStrings(orig.ReqAttrCreateAsk), + ReqAttrCreateBid: s.copyStrings(orig.ReqAttrCreateBid), + } +} + +// copyMarkets creates a copy of a slice of markets. +func (s *TestSuite) copyMarkets(orig []exchange.Market) []exchange.Market { + return copySlice(orig, s.copyMarket) +} + +// copyOrder creates a copy of an order. +func (s *TestSuite) copyOrder(orig exchange.Order) exchange.Order { + rv := exchange.NewOrder(orig.OrderId) + switch { + case orig.IsAskOrder(): + rv.WithAsk(s.copyAskOrder(orig.GetAskOrder())) + case orig.IsBidOrder(): + rv.WithBid(s.copyBidOrder(orig.GetBidOrder())) + default: + rv.Order = orig.Order + } + return *rv +} + +// copyOrders creates a copy of a slice of orders. +func (s *TestSuite) copyOrders(orig []exchange.Order) []exchange.Order { + return copySlice(orig, s.copyOrder) +} + +// copyAskOrder creates a copy of an AskOrder. +func (s *TestSuite) copyAskOrder(orig *exchange.AskOrder) *exchange.AskOrder { + if orig == nil { + return nil + } + return &exchange.AskOrder{ + MarketId: orig.MarketId, + Seller: orig.Seller, + Assets: s.copyCoin(orig.Assets), + Price: s.copyCoin(orig.Price), + SellerSettlementFlatFee: s.copyCoinP(orig.SellerSettlementFlatFee), + AllowPartial: orig.AllowPartial, + ExternalId: orig.ExternalId, + } +} + +// copyBidOrder creates a copy of a BidOrder. +func (s *TestSuite) copyBidOrder(orig *exchange.BidOrder) *exchange.BidOrder { + if orig == nil { + return nil + } + return &exchange.BidOrder{ + MarketId: orig.MarketId, + Buyer: orig.Buyer, + Assets: s.copyCoin(orig.Assets), + Price: s.copyCoin(orig.Price), + BuyerSettlementFees: s.copyCoins(orig.BuyerSettlementFees), + AllowPartial: orig.AllowPartial, + ExternalId: orig.ExternalId, + } +} + +// untypeEvent returns sdk.TypedEventToEvent(tev) requiring it to not error. +func (s *TestSuite) untypeEvent(tev proto.Message) sdk.Event { + rv, err := sdk.TypedEventToEvent(tev) + s.Require().NoError(err, "TypedEventToEvent(%T)", tev) + return rv +} + +// untypeEvents applies sdk.TypedEventToEvent(tev) to each of the provided things, requiring it to not error. +func untypeEvents[P proto.Message](s *TestSuite, tevs []P) sdk.Events { + rv := make(sdk.Events, len(tevs)) + for i, tev := range tevs { + event, err := sdk.TypedEventToEvent(tev) + s.Require().NoError(err, "[%d]TypedEventToEvent(%T)", i, tev) + rv[i] = event + } + return rv +} + +// creates a copy of a DenomSplit. +func (s *TestSuite) copyDenomSplit(orig exchange.DenomSplit) exchange.DenomSplit { + return exchange.DenomSplit{ + Denom: orig.Denom, + Split: orig.Split, + } +} + +// copyDenomSplits creates a copy of a slice of DenomSplits. +func (s *TestSuite) copyDenomSplits(orig []exchange.DenomSplit) []exchange.DenomSplit { + return copySlice(orig, s.copyDenomSplit) +} + +// copyParams creates a copy of exchange Params. +func (s *TestSuite) copyParams(orig *exchange.Params) *exchange.Params { + if orig == nil { + return nil + } + return &exchange.Params{ + DefaultSplit: orig.DefaultSplit, + DenomSplits: s.copyDenomSplits(orig.DenomSplits), + } +} + +// copyGenState creates a copy of a GenesisState. +func (s *TestSuite) copyGenState(genState *exchange.GenesisState) *exchange.GenesisState { + if genState == nil { + return nil + } + return &exchange.GenesisState{ + Params: s.copyParams(genState.Params), + Markets: s.copyMarkets(genState.Markets), + Orders: s.copyOrders(genState.Orders), + LastMarketId: genState.LastMarketId, + LastOrderId: genState.LastOrderId, + } +} + +// sortMarket sorts all the fields in a market. +func (s *TestSuite) sortMarket(market *exchange.Market) *exchange.Market { + if len(market.FeeSellerSettlementRatios) > 0 { + sort.Slice(market.FeeSellerSettlementRatios, func(i, j int) bool { + if market.FeeSellerSettlementRatios[i].Price.Denom < market.FeeSellerSettlementRatios[j].Price.Denom { + return true + } + if market.FeeSellerSettlementRatios[i].Price.Denom > market.FeeSellerSettlementRatios[j].Price.Denom { + return false + } + return market.FeeSellerSettlementRatios[i].Fee.Denom < market.FeeSellerSettlementRatios[j].Fee.Denom + }) + } + if len(market.FeeBuyerSettlementRatios) > 0 { + sort.Slice(market.FeeBuyerSettlementRatios, func(i, j int) bool { + if market.FeeBuyerSettlementRatios[i].Price.Denom < market.FeeBuyerSettlementRatios[j].Price.Denom { + return true + } + if market.FeeBuyerSettlementRatios[i].Price.Denom > market.FeeBuyerSettlementRatios[j].Price.Denom { + return false + } + return market.FeeBuyerSettlementRatios[i].Fee.Denom < market.FeeBuyerSettlementRatios[j].Fee.Denom + }) + } + if len(market.AccessGrants) > 0 { + sort.Slice(market.AccessGrants, func(i, j int) bool { + // Horribly inefficient. Not meant for production. + addrI, err := sdk.AccAddressFromBech32(market.AccessGrants[i].Address) + s.Require().NoError(err, "AccAddressFromBech32(%q)", market.AccessGrants[i].Address) + addrJ, err := sdk.AccAddressFromBech32(market.AccessGrants[j].Address) + s.Require().NoError(err, "AccAddressFromBech32(%q)", market.AccessGrants[j].Address) + return bytes.Compare(addrI, addrJ) < 0 + }) + for _, ag := range market.AccessGrants { + sort.Slice(ag.Permissions, func(i, j int) bool { + return ag.Permissions[i] < ag.Permissions[j] + }) + } + } + return market +} + +// sortGenState sorts the contents of a GenesisState. +func (s *TestSuite) sortGenState(genState *exchange.GenesisState) *exchange.GenesisState { + if genState == nil { + return nil + } + if genState.Params != nil && len(genState.Params.DenomSplits) > 0 { + sort.Slice(genState.Params.DenomSplits, func(i, j int) bool { + return genState.Params.DenomSplits[i].Denom < genState.Params.DenomSplits[j].Denom + }) + } + if len(genState.Markets) > 0 { + sort.Slice(genState.Markets, func(i, j int) bool { + return genState.Markets[i].MarketId < genState.Markets[j].MarketId + }) + for _, market := range genState.Markets { + s.sortMarket(&market) + } + } + if len(genState.Orders) > 0 { + sort.Slice(genState.Orders, func(i, j int) bool { + return genState.Orders[i].OrderId < genState.Orders[j].OrderId + }) + } + return genState +} + +// getOrderIDStr gets a string of the given order's id. +func (s *TestSuite) getOrderIDStr(order *exchange.Order) string { + if order == nil { + return "" + } + return fmt.Sprintf("%d", order.OrderId) +} + +// getAddrName returns the name of the variable in this TestSuite holding the provided address. +func (s *TestSuite) getAddrName(addr sdk.AccAddress) string { + switch string(addr) { + case string(s.addr1): + return "addr1" + case string(s.addr2): + return "addr2" + case string(s.addr3): + return "addr3" + case string(s.addr4): + return "addr4" + case string(s.addr5): + return "addr5" + case string(s.marketAddr1): + return "marketAddr1" + case string(s.marketAddr2): + return "marketAddr2" + case string(s.marketAddr3): + return "marketAddr3" + case string(s.feeCollectorAddr): + return "feeCollectorAddr" + default: + return addr.String() + } +} + +// getAddrStrName returns the name of the variable in this TestSuite holding the provided address. +func (s *TestSuite) getAddrStrName(addrStr string) string { + addr, err := sdk.AccAddressFromBech32(addrStr) + if err != nil { + return addrStr + } + return s.getAddrName(addr) +} + +// getStore gets the exchange store. +func (s *TestSuite) getStore() sdk.KVStore { + return s.k.GetStore(s.ctx) +} + +// clearExchangeState deletes everything from the exchange state store. +func (s *TestSuite) clearExchangeState() { + keeper.DeleteAll(s.getStore(), nil) + s.accKeeper = nil +} + +// stateEntryString converts the provided key and value into a ""="" string. +func (s *TestSuite) stateEntryString(key, value []byte) string { + return fmt.Sprintf("%q=%q", key, value) +} + +// dumpExchangeState creates a string for each entry in the hold state store. +// Each entry has the format `""=""`. +func (s *TestSuite) dumpExchangeState() []string { + var rv []string + keeper.Iterate(s.getStore(), nil, func(key, value []byte) bool { + rv = append(rv, s.stateEntryString(key, value)) + return false + }) + return rv +} + +// requireSetOrderInStore calls SetOrderInStore making sure it doesn't panic or return an error. +func (s *TestSuite) requireSetOrderInStore(store sdk.KVStore, order *exchange.Order) { + assertions.RequireNotPanicsNoErrorf(s.T(), func() error { + return s.k.SetOrderInStore(store, *order) + }, "SetOrderInStore(%d)", order.OrderId) +} + +// requireCreateMarket calls CreateMarket making sure it doesn't panic or return an error. +// It also uses the TestSuite.accKeeper for the market account. +func (s *TestSuite) requireCreateMarket(market exchange.Market) { + if s.accKeeper == nil { + s.accKeeper = NewMockAccountKeeper() + } + assertions.RequireNotPanicsNoErrorf(s.T(), func() error { + _, err := s.k.WithAccountKeeper(s.accKeeper).CreateMarket(s.ctx, market) + return err + }, "CreateMarket(%d)", market.MarketId) +} + +// requireCreateMarketUnmocked calls CreateMarket making sure it doesn't panic or return an error. +// This uses the normal account keeper (instead of a mocked one). +func (s *TestSuite) requireCreateMarketUnmocked(market exchange.Market) { + assertions.RequireNotPanicsNoErrorf(s.T(), func() error { + _, err := s.k.CreateMarket(s.ctx, market) + return err + }, "CreateMarket(%d)", market.MarketId) +} + +// assertEqualSlice asserts that expected = actual and returns true if so. +// If not, returns false and the stringer is applied to each entry and the comparison +// is redone on the strings in the hopes that it helps identify the problem. +func assertEqualSlice[T any](s *TestSuite, expected, actual []T, stringer func(T) string, msg string, args ...interface{}) bool { + s.T().Helper() + if s.Assert().Equalf(expected, actual, msg, args...) { + return true + } + // compare each as strings in the hopes that makes it easier to identify the problem. + expStrs := sliceStrings(expected, stringer) + actStrs := sliceStrings(actual, stringer) + if !s.Assert().Equalf(expStrs, actStrs, "strings: "+msg, args...) { + return false + } + // They're the same as strings, so compare each individually. + for i := range expected { + s.Assert().Equalf(expected[i], actual[i], msg+fmt.Sprintf("[%d]", i), args...) + } + return false +} + +// assertEqualOrderID asserts that two uint64 values are equal, and if not, includes their decimal form in the log. +// This is nice because .Equal failures output uints in hex, which can make it difficult to identify what's going on. +func (s *TestSuite) assertEqualOrderID(expected, actual uint64, msgAndArgs ...interface{}) bool { + if s.Assert().Equal(expected, actual, msgAndArgs...) { + return true + } + s.T().Logf("Expected order id: %d", expected) + s.T().Logf(" Actual order id: %d", actual) + return false +} + +// assertEqualOrders asserts that the to slices of orders are equal. +// If not, some further assertions are made to try to help try to clarify the differences. +func (s *TestSuite) assertEqualOrders(expected, actual []*exchange.Order, msg string, args ...interface{}) bool { + s.T().Helper() + return assertEqualSlice(s, expected, actual, s.getOrderIDStr, msg, args...) +} + +// assertErrorValue is a wrapper for assertions.AssertErrorValue for this TestSuite. +func (s *TestSuite) assertErrorValue(theError error, expected string, msgAndArgs ...interface{}) bool { + s.T().Helper() + return assertions.AssertErrorValue(s.T(), theError, expected, msgAndArgs...) +} + +// assertErrorContentsf is a wrapper for assertions.AssertErrorContentsf for this TestSuite. +func (s *TestSuite) assertErrorContentsf(theError error, contains []string, msg string, args ...interface{}) bool { + s.T().Helper() + return assertions.AssertErrorContentsf(s.T(), theError, contains, msg, args...) +} + +// assertEqualEvents is a wrapper for assertions.AssertEqualEvents for this TestSuite. +func (s *TestSuite) assertEqualEvents(expected, actual sdk.Events, msgAndArgs ...interface{}) bool { + s.T().Helper() + return assertions.AssertEqualEvents(s.T(), expected, actual, msgAndArgs...) +} + +// requirePanicEquals is a wrapper for assertions.RequirePanicEquals for this TestSuite. +func (s *TestSuite) requirePanicEquals(f assertions.PanicTestFunc, expected string, msgAndArgs ...interface{}) { + s.T().Helper() + assertions.RequirePanicEquals(s.T(), f, expected, msgAndArgs...) +}