diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 155e316e3..46224982a 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -584,7 +584,6 @@ func (a *AppKeepers) SetupHooks() { a.IncentivesKeeper.Hooks(), a.TxFeesKeeper.Hooks(), a.DelayedAckKeeper.GetEpochHooks(), - a.DymNSKeeper.GetEpochHooks(), a.RollappKeeper.GetEpochHooks(), ), ) diff --git a/proto/dymensionxyz/dymension/dymns/market.proto b/proto/dymensionxyz/dymension/dymns/market.proto index 6981aa70a..ec2cdc4a1 100644 --- a/proto/dymensionxyz/dymension/dymns/market.proto +++ b/proto/dymensionxyz/dymension/dymns/market.proto @@ -9,8 +9,8 @@ option go_package = "github.com/dymensionxyz/dymension/v3/x/dymns/types"; // SellOrder defines a sell order, placed by owner, to sell a Dym-Name/Alias. // Sell-Order has an expiry date. // After expiry date, if no one has placed a bid, this Sell-Order will be closed, no change. -// If there is a bid, the highest bid will win, and the Dym-Name/Alias ownership will be transferred to the winner. -// If the bid matches the sell price, the Dym-Name/Alias ownership will be transferred to the bidder immediately. +// - If there is a bid, the highest bid will win, and the Dym-Name/Alias ownership will be transferred to the winner. +// - If the bid matches the sell price, the Dym-Name/Alias ownership will be transferred to the bidder immediately. message SellOrder { // asset_id is the Dym-Name/Alias being opened to be sold. string asset_id = 1; @@ -33,21 +33,6 @@ message SellOrder { SellOrderBid highest_bid = 6; } -// ActiveSellOrdersExpiration contains list of active SOs, store expiration date mapped by asset identity. -// Used by hook to find out expired SO instead of iterating through all records. -message ActiveSellOrdersExpiration { - repeated ActiveSellOrdersExpirationRecord records = 1 [(gogoproto.nullable) = false]; -} - -// ActiveSellOrdersExpirationRecord contains the expiration date of an active Sell-Order. -message ActiveSellOrdersExpirationRecord { - // asset_id is the Dym-Name/Alias being opened to be sold. - string asset_id = 1; - - // expire_at is the last effective date of this Sell-Order. - int64 expire_at = 2; -} - // SellOrderBid defines a bid placed by an account on a Sell-Order. message SellOrderBid { // bidder is the account address of the account which placed the bid. diff --git a/proto/dymensionxyz/dymension/dymns/params.proto b/proto/dymensionxyz/dymension/dymns/params.proto index 12a78b7bb..94e8fceaf 100644 --- a/proto/dymensionxyz/dymension/dymns/params.proto +++ b/proto/dymensionxyz/dymension/dymns/params.proto @@ -71,6 +71,12 @@ message PriceParams { (gogoproto.moretags) = "yaml:\"min_offer_price\"", (gogoproto.nullable) = false ]; + + // min_bid_increment_percent is the minimum percent raised compare to previous bid of a Sell-Order. + // The valid range from 0% to 100%, but capped at 10%. + uint32 min_bid_increment_percent = 6 [ + (gogoproto.moretags) = "yaml:\"min_bid_increment_percent\"" + ]; } // ChainsParams defines setting for prioritized aliases mapping. @@ -127,7 +133,6 @@ message MiscParams { bool enable_trading_name = 4; // enable_trading_alias is the flag to enable trading of Alias. - // To be used in the future when Alias trading implementation is ready - // or disable trading of Alias when needed. + // To be used to stop trading of Alias when needed. bool enable_trading_alias = 5; } diff --git a/proto/dymensionxyz/dymension/dymns/tx.proto b/proto/dymensionxyz/dymension/dymns/tx.proto index 7c9281e66..d6fe50df3 100644 --- a/proto/dymensionxyz/dymension/dymns/tx.proto +++ b/proto/dymensionxyz/dymension/dymns/tx.proto @@ -38,6 +38,11 @@ service Msg { // This will stop the advertisement and remove the Dym-Name/Alias sale from the market. // Can only be performed if no one has placed a bid on the asset. rpc CancelSellOrder(MsgCancelSellOrder) returns (MsgCancelSellOrderResponse) {} + // CompleteSellOrder is message handler, + // handles Sell-Order completion action, can be performed by either asset owner or the person who placed the highest bid. + // Can only be performed when Sell-Order expired and has a bid placed. + // If the asset was expired or prohibited trading, bid placed will be force to return to the bidder, ownership will not be transferred. + rpc CompleteSellOrder(MsgCompleteSellOrder) returns (MsgCompleteSellOrderResponse) {} // PurchaseOrder is message handler, // handles purchasing a Dym-Name/Alias from a Sell-Order, performed by the buyer. rpc PurchaseOrder(MsgPurchaseOrder) returns (MsgPurchaseOrderResponse) {} @@ -219,6 +224,23 @@ message MsgCancelSellOrder { // MsgCancelSellOrderResponse defines the response for the Sell-Order cancellation. message MsgCancelSellOrderResponse {} +// MsgCompleteSellOrder defines the message used for user to complete a Sell-Order. +message MsgCompleteSellOrder { + option (cosmos.msg.v1.signer) = "participant"; + + // asset_id is the Dym-Name/Alias about to perform Sell Order completion action. + string asset_id = 1; + + // asset_type is the type of the asset of the order, is Dym-Name/Alias. + AssetType asset_type = 2; + + // participant is the bech32-encoded address of either asset owner or highest bidder account. + string participant = 3; +} + +// MsgCompleteSellOrderResponse defines the response for the Sell-Order completion. +message MsgCompleteSellOrderResponse {} + // MsgPurchaseOrder defines the message used for user to bid/purchase a Sell-Order. message MsgPurchaseOrder { option (cosmos.msg.v1.signer) = "buyer"; diff --git a/x/dymns/client/cli/tx.go b/x/dymns/client/cli/tx.go index 39a764529..241becd6c 100644 --- a/x/dymns/client/cli/tx.go +++ b/x/dymns/client/cli/tx.go @@ -26,6 +26,8 @@ func GetTxCmd() *cobra.Command { NewUpdateDetailsTxCmd(), NewPlaceDymNameSellOrderTxCmd(), NewPlaceAliasSellOrderTxCmd(), + NewCancelSellOrderTxCmd(), + NewCompleteSellOrderTxCmd(), NewPlaceBidOnDymNameOrderTxCmd(), NewPlaceBidOnAliasOrderTxCmd(), NewOfferBuyDymNameTxCmd(), diff --git a/x/dymns/client/cli/tx_cancel_sell_order.go b/x/dymns/client/cli/tx_cancel_sell_order.go new file mode 100644 index 000000000..6acdc1c30 --- /dev/null +++ b/x/dymns/client/cli/tx_cancel_sell_order.go @@ -0,0 +1,71 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/version" + dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" + dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" + "github.com/spf13/cobra" +) + +// NewCancelSellOrderTxCmd is the CLI command for close a Sell-Order. +func NewCancelSellOrderTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "cancel-sell-order [Name/Alias/Handle] [myname/x]", + Aliases: []string{"cancel-so"}, + Short: "Cancel a sell-order (only when no bid placed)", + Example: fmt.Sprintf( + `$ %s tx %s cancel-sell-order name myname --%s owner +$ %s tx %s cancel-sell-order alias x --%s owner`, + version.AppName, dymnstypes.ModuleName, flags.FlagFrom, + version.AppName, dymnstypes.ModuleName, flags.FlagFrom, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + target := args[1] + var assetType dymnstypes.AssetType + + switch strings.ToLower(args[0]) { + case "name", "dym-name", "dymname", "n": + assetType = dymnstypes.TypeName + if !dymnsutils.IsValidDymName(target) { + return fmt.Errorf("input is not a valid Dym-Name: %s", target) + } + case "alias", "handle", "handles", "a": + assetType = dymnstypes.TypeAlias + if !dymnsutils.IsValidAlias(target) { + return fmt.Errorf("input is not a valid Alias: %s", target) + } + default: + return fmt.Errorf("invalid asset type: %s, must be 'Name' or 'Alias'/'Handle'", args[0]) + } + + owner := clientCtx.GetFromAddress().String() + if owner == "" { + return fmt.Errorf("flag --%s is required", flags.FlagFrom) + } + + msg := &dymnstypes.MsgCancelSellOrder{ + AssetId: target, + AssetType: assetType, + Owner: owner, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/dymns/client/cli/tx_complete_sell_order.go b/x/dymns/client/cli/tx_complete_sell_order.go new file mode 100644 index 000000000..5c2fad77f --- /dev/null +++ b/x/dymns/client/cli/tx_complete_sell_order.go @@ -0,0 +1,72 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/version" + dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" + dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" + "github.com/spf13/cobra" +) + +// NewCompleteSellOrderTxCmd is the CLI command for completing a Sell-Order. +func NewCompleteSellOrderTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "complete-sell-order [Name/Alias/Handle] [myname/x]", + Aliases: []string{"complete-so"}, + Short: "Complete a sell-order (must be expired and has at least one bid)", + Long: "Request to complete a sell-order (must be expired and has at least one bid). Can be submitted by either the owner or the highest bidder.", + Example: fmt.Sprintf( + `$ %s tx %s complete-sell-order name myname --%s owner/bidder +$ %s tx %s complete-sell-order alias x --%s owner/bidder`, + version.AppName, dymnstypes.ModuleName, flags.FlagFrom, + version.AppName, dymnstypes.ModuleName, flags.FlagFrom, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + target := args[1] + var assetType dymnstypes.AssetType + + switch strings.ToLower(args[0]) { + case "name", "dym-name", "dymname", "n": + assetType = dymnstypes.TypeName + if !dymnsutils.IsValidDymName(target) { + return fmt.Errorf("input is not a valid Dym-Name: %s", target) + } + case "alias", "handle", "handles", "a": + assetType = dymnstypes.TypeAlias + if !dymnsutils.IsValidAlias(target) { + return fmt.Errorf("input is not a valid Alias: %s", target) + } + default: + return fmt.Errorf("invalid asset type: %s, must be 'Name' or 'Alias'/'Handle'", args[0]) + } + + participant := clientCtx.GetFromAddress().String() + if participant == "" { + return fmt.Errorf("flag --%s is required", flags.FlagFrom) + } + + msg := &dymnstypes.MsgCompleteSellOrder{ + AssetId: target, + AssetType: assetType, + Participant: participant, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/dymns/genesis_test.go b/x/dymns/genesis_test.go index 0d6ee1e8f..cfd22c0af 100644 --- a/x/dymns/genesis_test.go +++ b/x/dymns/genesis_test.go @@ -272,7 +272,7 @@ func TestExportThenInitGenesis(t *testing.T) { // Init genesis state genState.Params.Misc.EndEpochHookIdentifier = "week" // Change the epoch identifier to test if it is imported correctly - genState.Params.Misc.SellOrderDuration = 9999 * time.Hour + genState.Params.Misc.SellOrderDuration = 6 * 24 * time.Hour newDymNsKeeper, newBankKeeper, newRollAppKeeper, newCtx := testkeeper.DymNSKeeper(t) newCtx = newCtx.WithBlockTime(now) @@ -290,7 +290,7 @@ func TestExportThenInitGenesis(t *testing.T) { importedParams := newDymNsKeeper.GetParams(newCtx) require.Equal(t, genState.Params, importedParams) require.Equal(t, "week", importedParams.Misc.EndEpochHookIdentifier) - require.Equal(t, 9999*time.Hour, importedParams.Misc.SellOrderDuration) + require.Equal(t, 6*24*time.Hour, importedParams.Misc.SellOrderDuration) }) t.Run("Dym-Names should be imported correctly", func(t *testing.T) { diff --git a/x/dymns/keeper/alias.go b/x/dymns/keeper/alias.go index 8f1f1920e..44fd57c32 100644 --- a/x/dymns/keeper/alias.go +++ b/x/dymns/keeper/alias.go @@ -112,7 +112,7 @@ func (k Keeper) GetEffectiveAliasesByChainId(ctx sdk.Context, chainId string) [] aliasesOfRollApp := k.GetAliasesOfRollAppId(ctx, chainId) // If the chain-id is a RollApp, must exclude the aliases which being reserved in params. - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. reservedAliases := k.GetAllAliasAndChainIdInParams(ctx) aliasesOfRollApp = slices.DeleteFunc(aliasesOfRollApp, func(a string) bool { _, found := reservedAliases[a] diff --git a/x/dymns/keeper/dym_name.go b/x/dymns/keeper/dym_name.go index bf662fdee..4a211facb 100644 --- a/x/dymns/keeper/dym_name.go +++ b/x/dymns/keeper/dym_name.go @@ -213,8 +213,15 @@ func (k Keeper) GetAllDymNames(ctx sdk.Context) (list []dymnstypes.DymName) { // PruneDymName removes a Dym-Name from the KVStore, as well as all related records. func (k Keeper) PruneDymName(ctx sdk.Context, name string) error { - // remove SO (force, ignore active SO) - k.DeleteSellOrder(ctx, name, dymnstypes.TypeName) + // Force removing any existing SO. + if so := k.GetSellOrder(ctx, name, dymnstypes.TypeName); so != nil { + if so.HighestBid != nil { + if err := k.RefundBid(ctx, *so.HighestBid, dymnstypes.TypeName); err != nil { + return err + } + } + k.DeleteSellOrder(ctx, name, dymnstypes.TypeName) + } dymName := k.GetDymName(ctx, name) if dymName == nil { diff --git a/x/dymns/keeper/generic_reverse_lookup.go b/x/dymns/keeper/generic_reverse_lookup.go index 74e9db232..530fecc5f 100644 --- a/x/dymns/keeper/generic_reverse_lookup.go +++ b/x/dymns/keeper/generic_reverse_lookup.go @@ -1,8 +1,10 @@ package keeper import ( + "slices" + "strings" + sdk "github.com/cosmos/cosmos-sdk/types" - dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" ) // GenericAddReverseLookupRecord is a utility method that help to add a reverse lookup record. @@ -12,26 +14,22 @@ func (k Keeper) GenericAddReverseLookupRecord( marshaller func([]string) []byte, unMarshaller func([]byte) []string, ) error { - modifiedRecord := dymnstypes.StringList{ - newElement, - } + var modifiedRecord []string store := ctx.KVStore(k.storeKey) bz := store.Get(key) if bz != nil { existingRecord := unMarshaller(bz) - modifiedRecord = dymnstypes.StringList(existingRecord).Combine( - modifiedRecord, - ) - - if len(modifiedRecord) == len(existingRecord) { - // no new mapping to add + if slices.Contains(existingRecord, newElement) { + // already exist return nil } - } - modifiedRecord = modifiedRecord.Sort() + modifiedRecord = append(existingRecord, newElement) + } else { + modifiedRecord = []string{newElement} + } bz = marshaller(modifiedRecord) store.Set(key, bz) @@ -69,8 +67,14 @@ func (k Keeper) GenericRemoveReverseLookupRecord( } existingRecord := unMarshaller(bz) + modifiedRecord := slices.DeleteFunc(existingRecord, func(r string) bool { + return r == elementToRemove + }) - modifiedRecord := dymnstypes.StringList(existingRecord).Exclude([]string{elementToRemove}) + if len(existingRecord) == len(modifiedRecord) { + // not found + return nil + } if len(modifiedRecord) == 0 { // no more, remove record @@ -78,7 +82,10 @@ func (k Keeper) GenericRemoveReverseLookupRecord( return nil } - modifiedRecord = modifiedRecord.Sort() + // just for safety, sort the records + slices.SortFunc(modifiedRecord, func(a, b string) int { + return strings.Compare(a, b) + }) bz = marshaller(modifiedRecord) store.Set(key, bz) diff --git a/x/dymns/keeper/generic_reverse_lookup_test.go b/x/dymns/keeper/generic_reverse_lookup_test.go index 790405103..403062f30 100644 --- a/x/dymns/keeper/generic_reverse_lookup_test.go +++ b/x/dymns/keeper/generic_reverse_lookup_test.go @@ -1,11 +1,15 @@ package keeper_test import ( + "crypto/rand" "fmt" + "math/big" "sort" + "testing" sdk "github.com/cosmos/cosmos-sdk/types" dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" + "github.com/stretchr/testify/require" ) var keyTestReverseLookup = []byte("test-reverse-lookup") @@ -125,21 +129,6 @@ func (s *KeeperTestSuite) TestKeeper_GenericAddGetRemoveReverseLookupRecord() { s.Equal([]string{"test", "test2"}, records) // still the same }, }, - { - name: "add - list must be sorted before persist", - testFunc: func(te testEntity, ctx sdk.Context, s *KeeperTestSuite) { - te.adder(ctx, keyTestReverseLookup, "test3", s) - te.adder(ctx, keyTestReverseLookup, "test", s) - - records := te.getter(ctx, keyTestReverseLookup, s) - s.Equal([]string{"test", "test3"}, records) - - te.adder(ctx, keyTestReverseLookup, "test2", s) - - records = te.getter(ctx, keyTestReverseLookup, s) - s.Equal([]string{"test", "test2", "test3"}, records) - }, - }, { name: "get - returns empty when getting non-exist record", testFunc: func(te testEntity, ctx sdk.Context, s *KeeperTestSuite) { @@ -155,18 +144,18 @@ func (s *KeeperTestSuite) TestKeeper_GenericAddGetRemoveReverseLookupRecord() { te.adder(ctx, keyTestReverseLookup, "test1", s) records := te.getter(ctx, keyTestReverseLookup, s) - s.Equal([]string{"test1", "test2", "test3"}, records) + s.Equal([]string{"test3", "test2", "test1"}, records) }, }, { - name: "get - result is ordered", + name: "get - result is kept as persist order", testFunc: func(te testEntity, ctx sdk.Context, s *KeeperTestSuite) { te.adder(ctx, keyTestReverseLookup, "test3", s) te.adder(ctx, keyTestReverseLookup, "test2", s) te.adder(ctx, keyTestReverseLookup, "test1", s) records := te.getter(ctx, keyTestReverseLookup, s) - s.Equal([]string{"test1", "test2", "test3"}, records) + s.Equal([]string{"test3", "test2", "test1"}, records) }, }, { @@ -281,3 +270,127 @@ func (s *KeeperTestSuite) TestKeeper_GenericAddGetRemoveReverseLookupRecord() { }) } } + +func Benchmark_GenericAddReverseLookupRecord(b *testing.B) { + b.StopTimer() + b.ReportAllocs() + + // 2024-09-26: 0.43s for appending into a list of existing 1m elements + // Benchmark_GenericAddReverseLookupRecord-8 | 3s154ms | 3 | 431924139 ns/op | 272465408 B/op | 1038262 allocs/op + + // 2024-09-26: 0.062s for appending into a list of existing 1m elements + // Benchmark_GenericAddReverseLookupRecord-8 | 2s619ms | 18 | 62825618 ns/op | 224076049 B/op | 1000054 allocs/op + // => After improve slice operations, the time needed per op reduced from 430ms to 62ms + + s := new(KeeperTestSuite) + s.SetT(&testing.T{}) + s.SetupTest() + codec := s.cdc + + const elementsCount = 1_000_000 + upperRand := new(big.Int).Exp(big.NewInt(10), big.NewInt(20), nil) + + { + + // prepare existing data + existingData := make([]string, elementsCount) + for e := 1; e <= elementsCount; e++ { + bi, err := rand.Int(rand.Reader, upperRand) + require.NoError(b, err) + v := fmt.Sprintf("test%s", bi) + existingData = append(existingData, v) + } + bz := codec.MustMarshal(&dymnstypes.ReverseLookupDymNames{ + DymNames: existingData, + }) + s.ctx.KVStore(s.dymNsStoreKey).Set(keyTestReverseLookup, bz) + } + + for r := 1; r <= b.N; r++ { + // add new element to force the most hardcore computation + bi, err := rand.Int(rand.Reader, upperRand) + require.NoError(b, err) + v := fmt.Sprintf("test%s", bi) + err = func() error { + b.StartTimer() + defer b.StopTimer() + return s.dymNsKeeper.GenericAddReverseLookupRecord( + s.ctx, + keyTestReverseLookup, v, + func(list []string) []byte { + return codec.MustMarshal(&dymnstypes.ReverseLookupDymNames{ + DymNames: list, + }) + }, func(bz []byte) []string { + var record dymnstypes.ReverseLookupDymNames + codec.MustUnmarshal(bz, &record) + return record.DymNames + }, + ) + }() + require.NoError(b, err) + } +} + +func Benchmark_GenericRemoveReverseLookupRecord(b *testing.B) { + b.StopTimer() + b.ReportAllocs() + + // 2024-09-26: 0.44s for removing from a list of existing 1m elements + // Benchmark_GenericRemoveReverseLookupRecord-8 | 3s471ms | 3 | 440934042 ns/op | 293803210 B/op | 1038246 allocs/op + + // 2024-09-26: 0.1s for removing from a list of existing 1m elements + // Benchmark_GenericRemoveReverseLookupRecord-8 | 7s638ms | 12 | 102355965 ns/op | 224075756 B/op | 1000041 allocs/op + // => After improve slice operations, the time needed per op reduced from 430ms to 62ms + + s := new(KeeperTestSuite) + s.SetT(&testing.T{}) + s.SetupTest() + codec := s.cdc + + const elementsCount = 1_000_000 + upperRand := new(big.Int).Exp(big.NewInt(10), big.NewInt(20), nil) + + { + // prepare existing data + existingData := make([]string, elementsCount) + for e := 1; e <= elementsCount; e++ { + bi, err := rand.Int(rand.Reader, upperRand) + require.NoError(b, err) + v := fmt.Sprintf("test%s", bi) + existingData = append(existingData, v) + } + bz := codec.MustMarshal(&dymnstypes.ReverseLookupDymNames{ + DymNames: existingData, + }) + s.ctx.KVStore(s.dymNsStoreKey).Set(keyTestReverseLookup, bz) + } + + for r := 1; r <= b.N; r++ { + existingElements := s.dymNsKeeper.GenericGetReverseLookupRecord(s.ctx, keyTestReverseLookup, func(bz []byte) []string { + var record dymnstypes.ReverseLookupDymNames + codec.MustUnmarshal(bz, &record) + return record.DymNames + }) + // delete the last element to force the most hardcore computation + lastElement := existingElements[len(existingElements)-1] + err := func() error { + b.StartTimer() + defer b.StopTimer() + return s.dymNsKeeper.GenericRemoveReverseLookupRecord( + s.ctx, + keyTestReverseLookup, lastElement, + func(list []string) []byte { + return codec.MustMarshal(&dymnstypes.ReverseLookupDymNames{ + DymNames: list, + }) + }, func(bz []byte) []string { + var record dymnstypes.ReverseLookupDymNames + codec.MustUnmarshal(bz, &record) + return record.DymNames + }, + ) + }() + require.NoError(b, err) + } +} diff --git a/x/dymns/keeper/grpc_query.go b/x/dymns/keeper/grpc_query.go index 9ed6be0dd..d65ed4c7f 100644 --- a/x/dymns/keeper/grpc_query.go +++ b/x/dymns/keeper/grpc_query.go @@ -483,7 +483,7 @@ func (q queryServer) Aliases(goCtx context.Context, req *dymnstypes.QueryAliases aliases := rollAppWithAliases.Aliases // Remove the preserved aliases from record. - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. aliases = slices.DeleteFunc(aliases, func(a string) bool { _, found := reservedAliases[a] return found @@ -520,7 +520,7 @@ func (q queryServer) BuyOrdersByAlias(goCtx context.Context, req *dymnstypes.Que var buyOrders []dymnstypes.BuyOrder if !q.IsAliasPresentsInParamsAsAliasOrChainId(ctx, req.Alias) { // We ignore the aliases which presents in the params because they are prohibited from trading. - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. var err error buyOrders, err = q.GetBuyOrdersOfAlias(ctx, req.Alias) @@ -556,7 +556,7 @@ func (q queryServer) BuyOrdersOfAliasesLinkedToRollApp(goCtx context.Context, re for _, alias := range aliases { if q.IsAliasPresentsInParamsAsAliasOrChainId(ctx, alias) { // ignore - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. continue } diff --git a/x/dymns/keeper/grpc_query_test.go b/x/dymns/keeper/grpc_query_test.go index ad68d874a..7955b6b9f 100644 --- a/x/dymns/keeper/grpc_query_test.go +++ b/x/dymns/keeper/grpc_query_test.go @@ -4,8 +4,11 @@ import ( "fmt" "reflect" "sort" + "testing" "time" + "github.com/stretchr/testify/require" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -3461,3 +3464,38 @@ func (s *KeeperTestSuite) Test_queryServer_Aliases() { s.Require().Contains(err.Error(), "invalid request") }) } + +func Benchmark_queryServer_Aliases(b *testing.B) { + b.StopTimer() + b.ReportAllocs() + + // 2024-09-26: 0.46s for querying a list of ~100015 elements + // Benchmark_queryServer_Aliases-8 | 5s279ms | 25 | 46316678 ns/op | 46003582 B/op | 521381 allocs/op + + s := new(KeeperTestSuite) + s.SetT(&testing.T{}) + s.SetupTest() + + // restore params which was previously cleared by test suite setup + err := s.dymNsKeeper.SetParams(s.ctx, dymnstypes.DefaultParams()) + require.NoError(b, err) + + // create large amount of RollApps + const rollAppCounts = 100_000 + for id := 1; id <= rollAppCounts; id++ { + rollApp := newRollApp(fmt.Sprintf("rollapp_%d-1", id)).WithOwner(testAddr(1).bech32()).WithAlias(fmt.Sprintf("alias%d", id)) + s.persistRollApp(*rollApp) + } + + // benchmark + for i := 0; i < b.N; i++ { + resp, err := func() (*dymnstypes.QueryAliasesResponse, error) { + b.StartTimer() + defer b.StopTimer() + return dymnskeeper.NewQueryServerImpl(s.dymNsKeeper).Aliases(sdk.WrapSDKContext(s.ctx), &dymnstypes.QueryAliasesRequest{}) + }() + require.NoError(b, err) + require.NotNil(b, resp) + require.GreaterOrEqual(b, len(resp.AliasesByChainId), rollAppCounts) + } +} diff --git a/x/dymns/keeper/hooks.go b/x/dymns/keeper/hooks.go index 85973d918..cfb1db8e7 100644 --- a/x/dymns/keeper/hooks.go +++ b/x/dymns/keeper/hooks.go @@ -5,8 +5,6 @@ import ( dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" - "github.com/cometbft/cometbft/libs/log" - errorsmod "cosmossdk.io/errors" "github.com/dymensionxyz/gerr-cosmos/gerrc" @@ -14,228 +12,9 @@ import ( "github.com/osmosis-labs/osmosis/v15/osmoutils" sdk "github.com/cosmos/cosmos-sdk/types" - dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" - epochstypes "github.com/osmosis-labs/osmosis/v15/x/epochs/types" ) -/* -------------------------------------------------------------------------- */ -/* x/epochs hooks */ -/* -------------------------------------------------------------------------- */ - -var _ epochstypes.EpochHooks = epochHooks{} - -type epochHooks struct { - Keeper -} - -// GetEpochHooks returns the epoch hooks for the module. -func (k Keeper) GetEpochHooks() epochstypes.EpochHooks { - return epochHooks{ - Keeper: k, - } -} - -// BeforeEpochStart is the epoch start hook. -func (e epochHooks) BeforeEpochStart(_ sdk.Context, _ string, _ int64) error { - return nil -} - -// AfterEpochEnd is the epoch end hook. -func (e epochHooks) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) error { - miscParams := e.MiscParams(ctx) - - if epochIdentifier != miscParams.EndEpochHookIdentifier { - return nil - } - - logger := e.Logger(ctx).With("hook", "After-Epoch-End", "epoch-number", epochNumber, "epoch-identifier", epochIdentifier) - - if miscParams.EnableTradingName { - if err := e.processActiveDymNameSellOrders(ctx, logger); err != nil { - return err - } - } - - if miscParams.EnableTradingAlias { - if err := e.processActiveAliasSellOrders(ctx, logger); err != nil { - return err - } - } - - return nil -} - -// processActiveDymNameSellOrders process the finished Dym-Name Sell-Orders. -// Sell-Order will be deleted. If the Sell-Order has a winner, the Dym-Name ownership will be transferred. -func (e epochHooks) processActiveDymNameSellOrders(ctx sdk.Context, logger log.Logger) error { - activeSellOrdersExpiration := e.Keeper.GetActiveSellOrdersExpiration(ctx, dymnstypes.TypeName) - - finishedSOs := e.getFinishedSellOrders(ctx, activeSellOrdersExpiration, dymnstypes.TypeName, logger) - - if len(finishedSOs) < 1 { - return nil - } - - logger.Info("processing finished SOs.", "count", len(finishedSOs)) - - for _, so := range finishedSOs { - // each order should be processed in a branched context, if error, discard the state change - // and process next order, to prevent chain reaction when an individual order failed to process - errApplyStateChange := osmoutils.ApplyFuncIfNoError(ctx, func(ctx sdk.Context) error { - if so.HighestBid == nil { - e.DeleteSellOrder(ctx, so.AssetId, dymnstypes.TypeName) - return nil - } - - if err := e.CompleteDymNameSellOrder(ctx, so.AssetId); err != nil { - return err - } - - return nil - }) - - if errApplyStateChange == nil { - activeSellOrdersExpiration.Remove(so.AssetId) - } else { - logger.Error( - "failed to process finished sell order.", "asset-id", so.AssetId, - "bid", so.HighestBid != nil, - "error", errApplyStateChange, - ) - } - } - - if err := e.SetActiveSellOrdersExpiration(ctx, activeSellOrdersExpiration, dymnstypes.TypeName); err != nil { - return errorsmod.Wrap(errors.Join(gerrc.ErrInternal, err), "failed to update active SO expiry") - } - - return nil -} - -// processActiveAliasSellOrders process the finished Alias Sell-Orders. -// Sell-Order will be deleted. -// If the Sell-Order has a winner, the Alias linking will be updated. -// Sell-Orders for the aliases that are prohibited to trade will be force cancelled, -// please read the code body for more information about what it is. -func (e epochHooks) processActiveAliasSellOrders(ctx sdk.Context, logger log.Logger) error { - activeSellOrdersExpiration := e.Keeper.GetActiveSellOrdersExpiration(ctx, dymnstypes.TypeAlias) - - finishedSOs := e.getFinishedSellOrders(ctx, activeSellOrdersExpiration, dymnstypes.TypeAlias, logger) - - if len(finishedSOs) < 1 { - return nil - } - - logger.Info("processing finished SOs.", "count", len(finishedSOs)) - - prohibitedToTradeAliases := e.GetAllAliasAndChainIdInParams(ctx) - - for _, so := range finishedSOs { - // each order should be processed in a branched context, if error, discard the state change - // and process next order, to prevent chain reaction when an individual order failed to process - errApplyStateChange := osmoutils.ApplyFuncIfNoError(ctx, func(ctx sdk.Context) error { - if so.HighestBid == nil { - e.DeleteSellOrder(ctx, so.AssetId, dymnstypes.TypeAlias) - return nil - } - - /** - For the Sell-Orders which the assets are prohibited to trade, - the Sell-Order will be force cancelled and the bids will be refunded. - - Why some aliases are prohibited to trade? And what are they? - In module params, there is a list of alias mapping for some external well-known chains. - So those aliases are considered as reserved for the external chains, - therefor trading is not allowed. - - Why can someone own a prohibited alias? - An alias can be bought before the reservation was made. - But when the alias becomes reserved for the external well-known chains, - the alias will be prohibited to trade. - - Why can someone place a Sell-Order for the prohibited alias? - When a Sell-Order created before the reservation was made. - */ - if _, forceCancel := prohibitedToTradeAliases[so.AssetId]; forceCancel { - // Sell-Order will be force cancelled and refund bids if any, - // when the alias is prohibited to trade - if err := e.RefundBid(ctx, *so.HighestBid, dymnstypes.TypeAlias); err != nil { - return err - } - e.DeleteSellOrder(ctx, so.AssetId, dymnstypes.TypeAlias) - return nil - } - - if err := e.CompleteAliasSellOrder(ctx, so.AssetId); err != nil { - return err - } - - return nil - }) - - if errApplyStateChange == nil { - activeSellOrdersExpiration.Remove(so.AssetId) - } else { - _, forceCancel := prohibitedToTradeAliases[so.AssetId] - logger.Error( - "failed to process finished sell order.", "asset-id", so.AssetId, - "bid", so.HighestBid != nil, "prohibited", forceCancel, - "error", errApplyStateChange, - ) - } - } - - if err := e.SetActiveSellOrdersExpiration(ctx, activeSellOrdersExpiration, dymnstypes.TypeAlias); err != nil { - return errorsmod.Wrap(errors.Join(gerrc.ErrInternal, err), "failed to update active SO expiry") - } - - return nil -} - -// getFinishedSellOrders returns the finished Sell-Orders for the asset type. -// Finished Sell-Orders are the Sell-Orders that have expired. -// Expired sell-orders can either have a bid or not. In both cases we consider them as `finished`. -func (e epochHooks) getFinishedSellOrders( - ctx sdk.Context, - activeSellOrdersExpiration *dymnstypes.ActiveSellOrdersExpiration, assetType dymnstypes.AssetType, - logger log.Logger, -) (finishedSellOrders []dymnstypes.SellOrder) { - blockEpochUTC := ctx.BlockTime().Unix() - - for _, record := range activeSellOrdersExpiration.Records { - if record.ExpireAt > blockEpochUTC { - // skip not expired ones - continue - } - - so := e.GetSellOrder(ctx, record.AssetId, assetType) - - if so == nil { - logger.Error( - "invalid entry on Active Sell Order Expiration records: Sell Order not found.", - "asset-id", record.AssetId, "asset-type", assetType.PrettyName(), - ) - // ignore the invalid entries for now, invariant will catch it - continue - } - - if !so.HasFinished(blockEpochUTC) { - logger.Error( - "invalid entry on Active Sell Order Expiration records: Sell Order not yet finished.", - "asset-id", record.AssetId, "asset-type", assetType.PrettyName(), - "record-expiry", record.ExpireAt, "actual-expiry", so.ExpireAt, - ) - // ignore the invalid entries for now, invariant will catch it - continue - } - - finishedSellOrders = append(finishedSellOrders, *so) - } - - return -} - /* -------------------------------------------------------------------------- */ /* x/rollapp hooks */ /* -------------------------------------------------------------------------- */ diff --git a/x/dymns/keeper/hooks_test.go b/x/dymns/keeper/hooks_test.go index f2126dfd6..5e134d07e 100644 --- a/x/dymns/keeper/hooks_test.go +++ b/x/dymns/keeper/hooks_test.go @@ -1,8 +1,6 @@ package keeper_test import ( - "time" - sdkmath "cosmossdk.io/math" rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" @@ -11,1595 +9,6 @@ import ( dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" ) -func (s *KeeperTestSuite) Test_epochHooks_BeforeEpochStart() { - s.Run("should do nothing", func() { - originalGas := s.ctx.GasMeter().GasConsumed() - - err := s.dymNsKeeper.GetEpochHooks().BeforeEpochStart( - s.ctx, "hour", 1, - ) - s.Require().NoError(err) - - s.Require().Equal(originalGas, s.ctx.GasMeter().GasConsumed()) - }) -} - -//goland:noinspection GoSnakeCaseUsage -func (s *KeeperTestSuite) Test_epochHooks_AfterEpochEnd() { - s.Run("should do something even nothing to do", func() { - s.RefreshContext() - - moduleParams := s.moduleParams() - - originalGas := s.ctx.GasMeter().GasConsumed() - - err := s.dymNsKeeper.GetEpochHooks().AfterEpochEnd( - s.ctx, - moduleParams.Misc.EndEpochHookIdentifier, 1, - ) - s.Require().NoError(err) - - // gas should be changed because it should at least reading the params to check epoch identifier - s.Require().Less(originalGas, s.ctx.GasMeter().GasConsumed(), "should do something") - }) - - s.Run("process active mixed Dym-Name and alias Sell-Orders", func() { - s.RefreshContext() - - dymNameOwner := testAddr(1).bech32() - dymNameBuyer := testAddr(2).bech32() - - creator1_asOwner := testAddr(3).bech32() - creator2_asBuyer := testAddr(4).bech32() - - dymName1 := dymnstypes.DymName{ - Name: "my-name", - Owner: dymNameOwner, - Controller: dymNameOwner, - ExpireAt: s.now.Add(2 * 365 * 24 * time.Hour).Unix(), - } - err := s.dymNsKeeper.SetDymName(s.ctx, dymName1) - s.Require().NoError(err) - - rollApp1_asSrc := *newRollApp("rollapp_1-1").WithOwner(creator1_asOwner).WithAlias("one") - s.persistRollApp(rollApp1_asSrc) - s.requireRollApp(rollApp1_asSrc.rollAppId).HasAlias("one") - rollApp2_asDst := *newRollApp("rollapp_2-2").WithOwner(creator2_asBuyer) - s.persistRollApp(rollApp2_asDst) - s.requireRollApp(rollApp2_asDst.rollAppId).HasNoAlias() - - const dymNameOrderPrice = 100 - const aliasOrderPrice = 200 - - s.mintToModuleAccount(dymNameOrderPrice + aliasOrderPrice + 1) - - dymNameSO := s.newDymNameSellOrder(dymName1.Name). - WithMinPrice(dymNameOrderPrice). - WithDymNameBid(dymNameBuyer, dymNameOrderPrice). - Expired().Build() - err = s.dymNsKeeper.SetSellOrder(s.ctx, dymNameSO) - s.Require().NoError(err) - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameSO.AssetId, - ExpireAt: dymNameSO.ExpireAt, - }, - }, - }, dymnstypes.TypeName) - s.Require().NoError(err) - - aliasSO := s.newAliasSellOrder(rollApp1_asSrc.alias). - WithMinPrice(aliasOrderPrice). - WithAliasBid(rollApp2_asDst.owner, aliasOrderPrice, rollApp2_asDst.rollAppId). - Expired().Build() - err = s.dymNsKeeper.SetSellOrder(s.ctx, aliasSO) - s.Require().NoError(err) - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: aliasSO.AssetId, - ExpireAt: aliasSO.ExpireAt, - }, - }, - }, dymnstypes.TypeAlias) - s.Require().NoError(err) - - moduleParams := s.moduleParams() - - err = s.dymNsKeeper.GetEpochHooks().AfterEpochEnd(s.ctx, moduleParams.Misc.EndEpochHookIdentifier, 1) - s.Require().NoError(err) - - s.Nil(s.dymNsKeeper.GetSellOrder(s.ctx, dymName1.Name, dymnstypes.TypeName)) - s.Nil(s.dymNsKeeper.GetSellOrder(s.ctx, rollApp1_asSrc.alias, dymnstypes.TypeAlias)) - - s.Equal(int64(1), s.moduleBalance()) - s.Equal(int64(dymNameOrderPrice), s.balance(dymNameOwner)) - s.Equal(int64(aliasOrderPrice), s.balance(rollApp1_asSrc.owner)) - - laterDymName := s.dymNsKeeper.GetDymName(s.ctx, dymName1.Name) - if s.NotNil(laterDymName) { - s.Equal(dymNameBuyer, laterDymName.Owner) - s.Equal(dymNameBuyer, laterDymName.Controller) - } - - s.requireRollApp(rollApp1_asSrc.rollAppId).HasNoAlias() - s.requireRollApp(rollApp2_asDst.rollAppId).HasAlias("one") - }) - - s.Run("should not process Dym-Name SO if trading is disabled", func() { - s.RefreshContext() - - dymNameOwner := testAddr(1).bech32() - dymNameBuyer := testAddr(2).bech32() - - dymName1 := dymnstypes.DymName{ - Name: "my-name", - Owner: dymNameOwner, - Controller: dymNameOwner, - ExpireAt: s.now.Add(2 * 365 * 24 * time.Hour).Unix(), - } - err := s.dymNsKeeper.SetDymName(s.ctx, dymName1) - s.Require().NoError(err) - - const dymNameOrderPrice = 100 - - s.mintToModuleAccount(dymNameOrderPrice + 1) - - dymNameSO := s.newDymNameSellOrder(dymName1.Name). - WithMinPrice(dymNameOrderPrice). - WithDymNameBid(dymNameBuyer, dymNameOrderPrice). - Expired().Build() - err = s.dymNsKeeper.SetSellOrder(s.ctx, dymNameSO) - s.Require().NoError(err) - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameSO.AssetId, - ExpireAt: dymNameSO.ExpireAt, - }, - }, - }, dymnstypes.TypeName) - s.Require().NoError(err) - - s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { - p.Misc.EnableTradingName = false - return p - }) - - moduleParams := s.moduleParams() - - err = s.dymNsKeeper.GetEpochHooks().AfterEpochEnd(s.ctx, moduleParams.Misc.EndEpochHookIdentifier, 1) - s.Require().NoError(err) - - // the SellOrder should still be there - s.NotNil(s.dymNsKeeper.GetSellOrder(s.ctx, dymName1.Name, dymnstypes.TypeName)) - - // re-enable and test again to make sure it not processes just because trading was disabled - s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { - p.Misc.EnableTradingName = true - return p - }) - - err = s.dymNsKeeper.GetEpochHooks().AfterEpochEnd(s.ctx, moduleParams.Misc.EndEpochHookIdentifier, 1) - s.Require().NoError(err) - - s.Nil(s.dymNsKeeper.GetSellOrder(s.ctx, dymName1.Name, dymnstypes.TypeName)) - }) - - s.Run("should not process Alias SO if trading is disabled", func() { - s.RefreshContext() - - creator1_asOwner := testAddr(3).bech32() - creator2_asBuyer := testAddr(4).bech32() - - rollApp1_asSrc := *newRollApp("rollapp_1-1").WithOwner(creator1_asOwner).WithAlias("one") - s.persistRollApp(rollApp1_asSrc) - s.requireRollApp(rollApp1_asSrc.rollAppId).HasAlias("one") - rollApp2_asDst := *newRollApp("rollapp_2-2").WithOwner(creator2_asBuyer) - s.persistRollApp(rollApp2_asDst) - s.requireRollApp(rollApp2_asDst.rollAppId).HasNoAlias() - - const aliasOrderPrice = 200 - - s.mintToModuleAccount(aliasOrderPrice + 1) - - aliasSO := s.newAliasSellOrder(rollApp1_asSrc.alias). - WithMinPrice(aliasOrderPrice). - WithAliasBid(rollApp2_asDst.owner, aliasOrderPrice, rollApp2_asDst.rollAppId). - Expired().Build() - err := s.dymNsKeeper.SetSellOrder(s.ctx, aliasSO) - s.Require().NoError(err) - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: aliasSO.AssetId, - ExpireAt: aliasSO.ExpireAt, - }, - }, - }, dymnstypes.TypeAlias) - s.Require().NoError(err) - - s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { - p.Misc.EnableTradingAlias = false - return p - }) - - moduleParams := s.moduleParams() - - err = s.dymNsKeeper.GetEpochHooks().AfterEpochEnd(s.ctx, moduleParams.Misc.EndEpochHookIdentifier, 1) - s.Require().NoError(err) - - // the SellOrder should still be there - s.NotNil(s.dymNsKeeper.GetSellOrder(s.ctx, rollApp1_asSrc.alias, dymnstypes.TypeAlias)) - - // re-enable and test again to make sure it not processes just because trading was disabled - s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { - p.Misc.EnableTradingAlias = true - return p - }) - - err = s.dymNsKeeper.GetEpochHooks().AfterEpochEnd(s.ctx, moduleParams.Misc.EndEpochHookIdentifier, 1) - s.Require().NoError(err) - - s.Nil(s.dymNsKeeper.GetSellOrder(s.ctx, rollApp1_asSrc.alias, dymnstypes.TypeAlias)) - }) -} - -func (s *KeeperTestSuite) Test_epochHooks_AfterEpochEnd_processActiveDymNameSellOrders() { - ownerAcc := testAddr(1) - ownerA := ownerAcc.bech32() - - bidderAcc := testAddr(2) - bidderA := bidderAcc.bech32() - - dymNameA := dymnstypes.DymName{ - Name: "a", - Owner: ownerA, - Controller: ownerA, - ExpireAt: s.now.Unix() + 100, - } - - dymNameB := dymnstypes.DymName{ - Name: "b", - Owner: ownerA, - Controller: ownerA, - ExpireAt: s.now.Unix() + 100, - } - - dymNameC := dymnstypes.DymName{ - Name: "c", - Owner: ownerA, - Controller: ownerA, - ExpireAt: s.now.Unix() + 100, - } - - dymNameD := dymnstypes.DymName{ - Name: "d", - Owner: ownerA, - Controller: ownerA, - ExpireAt: s.now.Unix() + 100, - } - - originalDymNameA := dymNameA - originalDymNameB := dymNameB - originalDymNameC := dymNameC - originalDymNameD := dymNameD - - coin100 := s.coin(100) - coin200 := s.coin(200) - - soExpiredEpoch := s.now.Unix() - 1 - soNotExpiredEpoch := s.now.Add(time.Hour).Unix() - - const soExpired = true - const soNotExpired = false - genSo := func( - dymName dymnstypes.DymName, - expired bool, sellPrice *sdk.Coin, highestBid *dymnstypes.SellOrderBid, - ) dymnstypes.SellOrder { - return dymnstypes.SellOrder{ - AssetId: dymName.Name, - AssetType: dymnstypes.TypeName, - ExpireAt: func() int64 { - if expired { - return soExpiredEpoch - } - return soNotExpiredEpoch - }(), - MinPrice: coin100, - SellPrice: sellPrice, - HighestBid: highestBid, - } - } - - tests := []struct { - name string - dymNames []dymnstypes.DymName - sellOrders []dymnstypes.SellOrder - expiryByDymName []dymnstypes.ActiveSellOrdersExpirationRecord - preMintModuleBalance int64 - customEpochIdentifier string - beforeHookTestFunc func(*KeeperTestSuite) - wantErr bool - wantErrContains string - wantExpiryByDymName []dymnstypes.ActiveSellOrdersExpirationRecord - afterHookTestFunc func(*KeeperTestSuite) - }{ - { - name: "pass - simple process expired SO", - dymNames: []dymnstypes.DymName{dymNameA, dymNameB, dymNameC, dymNameD}, - sellOrders: []dymnstypes.SellOrder{genSo(dymNameA, soExpired, &coin200, nil)}, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 200, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: nil, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireDymName(dymNameA.Name). - noActiveSO(). - mustEquals(originalDymNameA) - - s.Require().EqualValues(200, s.moduleBalance()) - - s.EqualValues(0, s.balance(dymNameA.Owner)) - - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - }, - { - name: "pass - simple process expired & completed SO", - dymNames: []dymnstypes.DymName{dymNameA}, - sellOrders: []dymnstypes.SellOrder{genSo(dymNameA, soExpired, &coin200, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin200, - })}, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 200, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: nil, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireDymName(dymNameA.Name). - noActiveSO(). - ownerChangedTo(bidderA). - expiryEquals(originalDymNameA.ExpireAt) - - s.Require().EqualValues(0, s.moduleBalance()) // 200 should be transferred to previous owner - - s.Require().EqualValues(200, s.balance(dymNameA.Owner)) // previous owner should earn from bid - - s.requireConfiguredAddress(ownerA).notMappedToAnyDymName() - s.requireConfiguredAddress(bidderA).mappedDymNames(dymNameA.Name) - s.requireFallbackAddress(ownerAcc.fallback()).notMappedToAnyDymName() - s.requireFallbackAddress(bidderAcc.fallback()).mappedDymNames(dymNameA.Name) - }, - }, - { - name: "pass - simple process expired & completed SO, match by min price", - dymNames: []dymnstypes.DymName{dymNameA}, - sellOrders: []dymnstypes.SellOrder{genSo(dymNameA, soExpired, &coin200, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin100, - })}, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 250, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: nil, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireDymName(dymNameA.Name). - noActiveSO(). - ownerChangedTo(bidderA). - expiryEquals(originalDymNameA.ExpireAt) - - s.Require().EqualValues(150, s.moduleBalance()) // 100 should be transferred to previous owner - - s.Require().EqualValues(100, s.balance(dymNameA.Owner)) // previous owner should earn from bid - - s.requireConfiguredAddress(ownerA).notMappedToAnyDymName() - s.requireConfiguredAddress(bidderA).mappedDymNames(dymNameA.Name) - s.requireFallbackAddress(ownerAcc.fallback()).notMappedToAnyDymName() - s.requireFallbackAddress(bidderAcc.fallback()).mappedDymNames(dymNameA.Name) - }, - }, - { - name: "pass - process multiple - mixed SOs", - dymNames: []dymnstypes.DymName{dymNameA, dymNameB, dymNameC, dymNameD}, - sellOrders: []dymnstypes.SellOrder{ - genSo(dymNameA, soExpired, nil, nil), - genSo(dymNameB, soNotExpired, &coin200, &dymnstypes.SellOrderBid{ - // not completed - Bidder: bidderA, - Price: coin100, - }), - genSo(dymNameC, soExpired, &coin200, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin200, - }), - genSo(dymNameD, soExpired, &coin200, &dymnstypes.SellOrderBid{ - // completed by min price - Bidder: bidderA, - Price: coin100, - }), - }, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameB.Name, - ExpireAt: soNotExpiredEpoch, - }, - { - AssetId: dymNameC.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameD.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 450, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameB.Name, - ExpireAt: soNotExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - // SO for Dym-Name A is expired without any bid/winner - s.requireDymName(dymNameA.Name). - noActiveSO(). - mustEquals(originalDymNameA) - - // SO for Dym-Name B not yet finished - soB := s.dymNsKeeper.GetSellOrder(s.ctx, dymNameB.Name, dymnstypes.TypeName) - s.Require().NotNil(soB) - s.requireDymName(dymNameB.Name). - mustEquals(originalDymNameB) - - // SO for Dym-Name C is completed with winner - s.requireDymName(dymNameC.Name). - noActiveSO(). - ownerChangedTo(bidderA). - expiryEquals(originalDymNameC.ExpireAt) - - // SO for Dym-Name D is completed with winner - s.requireDymName(dymNameD.Name). - noActiveSO(). - ownerChangedTo(bidderA). - expiryEquals(originalDymNameD.ExpireAt) - - s.Require().EqualValues(150, s.moduleBalance()) - - s.Require().EqualValues(300, s.balance(ownerA)) // 200 from SO C, 100 from SO D - - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireConfiguredAddress(bidderA).mappedDymNames(dymNameC.Name, dymNameD.Name) - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireFallbackAddress(bidderAcc.fallback()).mappedDymNames(dymNameC.Name, dymNameD.Name) - }, - }, - { - name: "pass - should do nothing if invalid epoch identifier", - dymNames: []dymnstypes.DymName{dymNameA, dymNameB, dymNameC, dymNameD}, - sellOrders: []dymnstypes.SellOrder{ - genSo(dymNameA, soExpired, nil, nil), - genSo(dymNameB, soNotExpired, &coin200, &dymnstypes.SellOrderBid{ - // not completed - Bidder: bidderA, - Price: coin100, - }), - genSo(dymNameC, soExpired, &coin200, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin200, - }), - genSo(dymNameD, soExpired, &coin200, &dymnstypes.SellOrderBid{ - // completed by min price - Bidder: bidderA, - Price: coin100, - }), - }, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameB.Name, - ExpireAt: soNotExpiredEpoch, - }, - { - AssetId: dymNameC.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameD.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 450, - customEpochIdentifier: "another", - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameB.Name, - ExpireAt: soNotExpiredEpoch, - }, - { - AssetId: dymNameC.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameD.Name, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireDymName(dymNameA.Name).mustEquals(originalDymNameA) - s.requireDymName(dymNameB.Name).mustEquals(originalDymNameB) - s.requireDymName(dymNameC.Name).mustEquals(originalDymNameC) - s.requireDymName(dymNameD.Name).mustEquals(originalDymNameD) - - s.Require().EqualValues(450, s.moduleBalance()) - - s.Require().EqualValues(0, s.balance(ownerA)) - - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name, dymNameC.Name, dymNameD.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - }, - { - name: "pass - keep expiry reference to non-exists SO, later invariants will catch it", - dymNames: []dymnstypes.DymName{dymNameA, dymNameB}, - sellOrders: []dymnstypes.SellOrder{ - genSo(dymNameA, soExpired, nil, nil), - // no SO for Dym-Name B - }, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - { - // no SO for Dym-Name B but still have reference - AssetId: dymNameB.Name, - ExpireAt: soExpiredEpoch, - }, - }, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - // removed reference to Dym-Name A because of processed - { - // the reference to non-exists existing SO should be kept - AssetId: dymNameB.Name, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - }, - { - name: "pass - ignore expiry if in-correct, later invariants will catch it", - dymNames: []dymnstypes.DymName{dymNameA, dymNameB}, - sellOrders: []dymnstypes.SellOrder{ - genSo(dymNameA, soExpired, nil, nil), - genSo(dymNameB, soNotExpired, nil, nil), // SO not expired - }, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - { - // incorrect, SO not expired - AssetId: dymNameB.Name, - ExpireAt: soExpiredEpoch, - }, - }, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameB.Name, - ExpireAt: soExpiredEpoch, // incorrect still kept, later invariant will catch it - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name, dymNameB.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - }, - { - name: "pass - ignore processing SO when error occurs", - dymNames: []dymnstypes.DymName{dymNameA}, - sellOrders: []dymnstypes.SellOrder{ - genSo(dymNameA, soExpired, nil, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin100, - }), - }, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 1, // not enough balance - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - // unchanged - - s.requireConfiguredAddress(ownerA).mappedDymNames(dymNameA.Name) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - s.requireFallbackAddress(ownerAcc.fallback()).mappedDymNames(dymNameA.Name) - s.requireFallbackAddress(bidderAcc.fallback()).notMappedToAnyDymName() - - s.requireDymName(dymNameA.Name).mustHaveActiveSO() - - s.EqualValues(1, s.moduleBalance()) - }, - }, - { - name: "pass - ignore processing SO when error occurs, one pass one fail", - dymNames: []dymnstypes.DymName{dymNameA, dymNameB}, - sellOrders: []dymnstypes.SellOrder{ - genSo(dymNameA, soExpired, nil, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin100, - }), - genSo(dymNameB, soExpired, nil, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin100, - }), - }, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameB.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 101, // just enough process the first SO - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames( - dymNameA.Name, dymNameB.Name, - ) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameB.Name, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames( - dymNameB.Name, - ) - s.requireConfiguredAddress(bidderA).mappedDymNames( - dymNameA.Name, - ) - - s.requireDymName(dymNameA.Name).noActiveSO() - s.requireDymName(dymNameB.Name).mustHaveActiveSO() - - s.EqualValues(1, s.moduleBalance()) - s.EqualValues(100, s.balance(ownerA)) - }, - }, - { - name: "pass - ignore processing SO when error occurs, one fail one pass", - dymNames: []dymnstypes.DymName{dymNameA, dymNameB}, - sellOrders: []dymnstypes.SellOrder{ - genSo(dymNameA, soExpired, nil, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin200, - }), - genSo(dymNameB, soExpired, nil, &dymnstypes.SellOrderBid{ - Bidder: bidderA, - Price: coin100, - }), - }, - expiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: dymNameB.Name, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 101, // just enough process the second SO - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames( - dymNameA.Name, dymNameB.Name, - ) - s.requireConfiguredAddress(bidderA).notMappedToAnyDymName() - }, - wantErr: false, - wantExpiryByDymName: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: dymNameA.Name, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireConfiguredAddress(ownerA).mappedDymNames( - dymNameA.Name, - ) - s.requireConfiguredAddress(bidderA).mappedDymNames( - dymNameB.Name, - ) - - s.requireDymName(dymNameA.Name).mustHaveActiveSO() - s.requireDymName(dymNameB.Name).noActiveSO() - - s.EqualValues(1, s.moduleBalance()) - s.EqualValues(100, s.balance(ownerA)) - }, - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - s.Require().NotNil(tt.beforeHookTestFunc, "mis-configured test case") - s.Require().NotNil(tt.afterHookTestFunc, "mis-configured test case") - - s.RefreshContext() - - if tt.preMintModuleBalance > 0 { - s.mintToModuleAccount(tt.preMintModuleBalance) - } - - err := s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: tt.expiryByDymName, - }, dymnstypes.TypeName) - s.Require().NoError(err) - - for _, dymName := range tt.dymNames { - s.setDymNameWithFunctionsAfter(dymName) - } - - for _, so := range tt.sellOrders { - err = s.dymNsKeeper.SetSellOrder(s.ctx, so) - s.Require().NoError(err) - } - - moduleParams := s.dymNsKeeper.GetParams(s.ctx) - - useEpochIdentifier := moduleParams.Misc.EndEpochHookIdentifier - if tt.customEpochIdentifier != "" { - useEpochIdentifier = tt.customEpochIdentifier - } - - tt.beforeHookTestFunc(s) - - err = s.dymNsKeeper.GetEpochHooks().AfterEpochEnd(s.ctx, useEpochIdentifier, 1) - - defer func() { - if s.T().Failed() { - return - } - - tt.afterHookTestFunc(s) - - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeName) - if len(tt.wantExpiryByDymName) == 0 { - s.Require().Empty(aSoe.Records) - } else { - s.Require().Equal(tt.wantExpiryByDymName, aSoe.Records) - } - }() - - if tt.wantErr { - s.Require().NotEmpty(tt.wantErrContains, "mis-configured test case") - s.Require().Error(err) - s.Require().Contains(err.Error(), tt.wantErrContains) - - return - } - - s.Require().NoError(err) - }) - } -} - -//goland:noinspection GoSnakeCaseUsage -func (s *KeeperTestSuite) Test_epochHooks_AfterEpochEnd_processActiveAliasSellOrders() { - creator_1_asOwner := testAddr(1).bech32() - creator_2_asBidder := testAddr(2).bech32() - - rollApp_1_byOwner_asSrc := *newRollApp("rollapp_1-1").WithAlias("one").WithOwner(creator_1_asOwner) - rollApp_2_byBuyer_asDst := *newRollApp("rollapp_2-2").WithOwner(creator_2_asBidder) - rollApp_3_byOwner_asSrc := *newRollApp("rollapp_3-1").WithAlias("three").WithOwner(creator_1_asOwner) - rollApp_4_byOwner_asSrc := *newRollApp("rollapp_4-1").WithAlias("four").WithOwner(creator_1_asOwner) - rollApp_5_byOwner_asSrc := *newRollApp("rollapp_5-1").WithAlias("five").WithOwner(creator_1_asOwner) - - const aliasProhibitedTrading = "prohibited" - - const minPrice = 100 - const soExpiredEpoch = 1 - soNotExpiredEpoch := s.now.Add(time.Hour).Unix() - - tests := []struct { - name string - rollApps []rollapp - sellOrders []dymnstypes.SellOrder - expiryByAlias []dymnstypes.ActiveSellOrdersExpirationRecord - preMintModuleBalance int64 - customEpochIdentifier string - beforeHookTestFunc func(s *KeeperTestSuite) - wantErr bool - wantErrContains string - wantExpiryByAlias []dymnstypes.ActiveSellOrdersExpirationRecord - afterHookTestFunc func(s *KeeperTestSuite) - }{ - { - name: "pass - simple process expired SO without bid", - rollApps: []rollapp{rollApp_1_byOwner_asSrc}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice).WithSellPrice(200). - WithExpiry(soExpiredEpoch). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 200, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - }, - wantErr: false, - wantExpiryByAlias: nil, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireAlias(rollApp_1_byOwner_asSrc.alias).noActiveSO() - - // unchanged - - s.Equal(int64(200), s.moduleBalance()) - s.Zero(s.balance(rollApp_1_byOwner_asSrc.owner)) - - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - }, - }, - { - name: "pass - simple process expired & completed SO", - rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, 200, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 200, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - }, - wantErr: false, - wantExpiryByAlias: nil, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - - s.requireAlias(rollApp_1_byOwner_asSrc.alias).noActiveSO() - - s.Zero(s.moduleBalance()) // should be transferred to previous owner - s.Equal(int64(200), s.balance(rollApp_1_byOwner_asSrc.owner)) // previous owner should earn from bid - }, - }, - { - name: "pass - simple process expired & completed SO, match by min price", - rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 250, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - }, - wantErr: false, - wantExpiryByAlias: nil, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - - s.requireAlias(rollApp_1_byOwner_asSrc.alias).noActiveSO() - - s.Equal(int64(250-minPrice), s.moduleBalance()) // should be transferred to previous owner - s.Equal(int64(minPrice), s.balance(rollApp_1_byOwner_asSrc.owner)) // previous owner should earn from bid - }, - }, - { - name: "pass - refunds records that alias presents in params", - rollApps: []rollapp{ - rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, - }, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(aliasProhibitedTrading). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: aliasProhibitedTrading, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 500, - beforeHookTestFunc: func(s *KeeperTestSuite) { - err := s.dymNsKeeper.SetAliasForRollAppId(s.ctx, rollApp_1_byOwner_asSrc.rollAppId, aliasProhibitedTrading) - s.NoError(err) - - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias( - rollApp_1_byOwner_asSrc.alias, aliasProhibitedTrading, - ) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - - s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { - p.Chains.AliasesOfChainIds = append(p.Chains.AliasesOfChainIds, dymnstypes.AliasesOfChainId{ - ChainId: "some-chain", - Aliases: []string{aliasProhibitedTrading}, - }) - return p - }) - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{}, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.Nil(s.dymNsKeeper.GetSellOrder(s.ctx, aliasProhibitedTrading, dymnstypes.TypeAlias)) - - // refunded - s.Equal(int64(500-minPrice), s.moduleBalance()) - s.Equal(int64(minPrice), s.balance(rollApp_2_byBuyer_asDst.owner)) - }, - }, - { - name: "pass - failed to refunds records that alias presents in params will keep the data as is", - rollApps: []rollapp{ - rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, - }, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(aliasProhibitedTrading). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: aliasProhibitedTrading, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 1, - beforeHookTestFunc: func(s *KeeperTestSuite) { - err := s.dymNsKeeper.SetAliasForRollAppId(s.ctx, rollApp_1_byOwner_asSrc.rollAppId, aliasProhibitedTrading) - s.NoError(err) - - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias( - rollApp_1_byOwner_asSrc.alias, aliasProhibitedTrading, - ) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - - s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { - p.Chains.AliasesOfChainIds = append(p.Chains.AliasesOfChainIds, dymnstypes.AliasesOfChainId{ - ChainId: "some-chain", - Aliases: []string{aliasProhibitedTrading}, - }) - return p - }) - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: aliasProhibitedTrading, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.NotNil(s.dymNsKeeper.GetSellOrder(s.ctx, aliasProhibitedTrading, dymnstypes.TypeAlias)) - - s.Equal(int64(1), s.moduleBalance()) - s.Zero(s.balance(rollApp_2_byBuyer_asDst.owner)) - }, - }, - { - name: "pass - process multiple - mixed SOs", - rollApps: []rollapp{ - rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, rollApp_3_byOwner_asSrc, rollApp_4_byOwner_asSrc, rollApp_5_byOwner_asSrc, - }, - sellOrders: []dymnstypes.SellOrder{ - // expired SO without bid - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice).WithSellPrice(200). - WithExpiry(soExpiredEpoch). - Build(), - // not yet finished - s.newAliasSellOrder(rollApp_3_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soNotExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - // completed by matching sell-price - s.newAliasSellOrder(rollApp_4_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, 200, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - // completed by min price - s.newAliasSellOrder(rollApp_5_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - // completed by min price, but prohibited trading because presents in module params - s.newAliasSellOrder(aliasProhibitedTrading). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soNotExpiredEpoch, - }, - { - AssetId: rollApp_4_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_5_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: aliasProhibitedTrading, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 450, - beforeHookTestFunc: func(s *KeeperTestSuite) { - err := s.dymNsKeeper.SetAliasForRollAppId(s.ctx, rollApp_1_byOwner_asSrc.rollAppId, aliasProhibitedTrading) - s.NoError(err) - - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias( - rollApp_1_byOwner_asSrc.alias, aliasProhibitedTrading, - ) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - s.requireRollApp(rollApp_4_byOwner_asSrc.rollAppId).HasAlias(rollApp_4_byOwner_asSrc.alias) - s.requireRollApp(rollApp_5_byOwner_asSrc.rollAppId).HasAlias(rollApp_5_byOwner_asSrc.alias) - - s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { - p.Chains.AliasesOfChainIds = append(p.Chains.AliasesOfChainIds, dymnstypes.AliasesOfChainId{ - ChainId: "some-chain", - Aliases: []string{aliasProhibitedTrading}, - }) - return p - }) - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soNotExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - // SO for alias 1 is expired without any bid/winner - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireAlias(rollApp_1_byOwner_asSrc.alias).noActiveSO() - - // SO of the prohibited alias should be removed - s.Nil(s.dymNsKeeper.GetSellOrder(s.ctx, aliasProhibitedTrading, dymnstypes.TypeAlias)) - - // SO for alias 3 not yet finished - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - s.NotNil(s.dymNsKeeper.GetSellOrder(s.ctx, rollApp_3_byOwner_asSrc.alias, dymnstypes.TypeAlias)) - - // SO for alias 4 is completed with winner - s.requireRollApp(rollApp_4_byOwner_asSrc.rollAppId).HasNoAlias() - s.requireAlias(rollApp_4_byOwner_asSrc.alias).noActiveSO() - - // SO for alias 5 is completed with winner - s.requireRollApp(rollApp_5_byOwner_asSrc.rollAppId).HasNoAlias() - s.requireAlias(rollApp_5_byOwner_asSrc.alias).noActiveSO() - - // aliases moved to RollApps of the winner - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId). - HasAlias(rollApp_4_byOwner_asSrc.alias, rollApp_5_byOwner_asSrc.alias) - - s.Equal(int64(50), s.moduleBalance()) - s.Equal(int64(300), s.balance(creator_1_asOwner)) // price from 2 completed SO - s.Equal(int64(100), s.balance(creator_2_asBidder)) // refunded from prohibited trading SO - }, - }, - { - name: "pass - should do nothing if invalid epoch identifier", - rollApps: []rollapp{ - rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, rollApp_3_byOwner_asSrc, rollApp_4_byOwner_asSrc, rollApp_5_byOwner_asSrc, - }, - sellOrders: []dymnstypes.SellOrder{ - // expired SO without bid - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice).WithSellPrice(200). - WithExpiry(soExpiredEpoch). - Build(), - // not yet finished - s.newAliasSellOrder(rollApp_3_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soNotExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - // completed by matching sell-price - s.newAliasSellOrder(rollApp_4_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, 200, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - // completed by min price - s.newAliasSellOrder(rollApp_5_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(200). - WithExpiry(soExpiredEpoch). - WithAliasBid(rollApp_2_byBuyer_asDst.owner, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soNotExpiredEpoch, - }, - { - AssetId: rollApp_4_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_5_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - customEpochIdentifier: "another", - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - s.requireRollApp(rollApp_4_byOwner_asSrc.rollAppId).HasAlias(rollApp_4_byOwner_asSrc.alias) - s.requireRollApp(rollApp_5_byOwner_asSrc.rollAppId).HasAlias(rollApp_5_byOwner_asSrc.alias) - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - // deep unchanged but order changed due to sorting - { - AssetId: rollApp_5_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_4_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soNotExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - // unchanged - - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - s.requireRollApp(rollApp_4_byOwner_asSrc.rollAppId).HasAlias(rollApp_4_byOwner_asSrc.alias) - s.requireRollApp(rollApp_5_byOwner_asSrc.rollAppId).HasAlias(rollApp_5_byOwner_asSrc.alias) - }, - }, - { - name: "pass - should ignore expiry reference to non-exists SO, invariants will catch it later", - rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_3_byOwner_asSrc}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithSellPrice(300). - WithExpiry(soExpiredEpoch). - Build(), - // no SO for alias of rollapp 3 - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - // no SO for alias of RollApp 3 but still have reference - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - // removed reference to alias of RollApp 1 because of processed - { - // reference to the non-existing SO will be kept and later catch by invariants - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - // unchanged - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - }, - }, - { - name: "pass - ignore incorrect expiry, later invariants will catch it", - rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, rollApp_3_byOwner_asSrc}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithExpiry(soExpiredEpoch). - Build(), - s.newAliasSellOrder(rollApp_3_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithExpiry(soNotExpiredEpoch). // SO not expired - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - // incorrect, SO not expired - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - beforeHookTestFunc: func(s *KeeperTestSuite) { - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - // removed reference to alias of RollApp 1 because of processed - // reference to alias of RollApp 3 was kept because not expired - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, // still keep the incorrect expiry - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - }, - }, - { - name: "pass - ignore processing SO when error occurs", - rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithExpiry(soExpiredEpoch). - WithAliasBid(creator_2_asBidder, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: 1, // not enough balance - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - // unchanged - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - s.requireAlias(rollApp_1_byOwner_asSrc.alias).mustHaveActiveSO() - }, - }, - { - name: "pass - ignore processing SO when error occurs, one pass one fail", - rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, rollApp_3_byOwner_asSrc}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithExpiry(soExpiredEpoch). - WithAliasBid(creator_2_asBidder, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - s.newAliasSellOrder(rollApp_3_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithExpiry(soExpiredEpoch). - WithAliasBid(creator_2_asBidder, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: minPrice + 1, // just enough for the first SO - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - - s.requireAlias(rollApp_1_byOwner_asSrc.alias).noActiveSO() - s.requireAlias(rollApp_3_byOwner_asSrc.alias).mustHaveActiveSO() - - s.EqualValues(1, s.moduleBalance()) - s.EqualValues(minPrice, s.balance(rollApp_1_byOwner_asSrc.owner)) - }, - }, - { - name: "pass - ignore processing SO when error occurs, one fail one pass", - rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, rollApp_3_byOwner_asSrc}, - sellOrders: []dymnstypes.SellOrder{ - s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithExpiry(soExpiredEpoch). - WithAliasBid(creator_2_asBidder, minPrice*2, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - s.newAliasSellOrder(rollApp_3_byOwner_asSrc.alias). - WithMinPrice(minPrice). - WithExpiry(soExpiredEpoch). - WithAliasBid(creator_2_asBidder, minPrice, rollApp_2_byBuyer_asDst.rollAppId). - Build(), - }, - expiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - { - AssetId: rollApp_3_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - preMintModuleBalance: minPrice + 1, // just enough for the second SO - beforeHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasNoAlias() - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - }, - wantErr: false, - wantExpiryByAlias: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: rollApp_1_byOwner_asSrc.alias, - ExpireAt: soExpiredEpoch, - }, - }, - afterHookTestFunc: func(s *KeeperTestSuite) { - s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId).HasAlias(rollApp_1_byOwner_asSrc.alias) - s.requireRollApp(rollApp_2_byBuyer_asDst.rollAppId).HasAlias(rollApp_3_byOwner_asSrc.alias) - s.requireRollApp(rollApp_3_byOwner_asSrc.rollAppId).HasNoAlias() - - s.requireAlias(rollApp_1_byOwner_asSrc.alias).mustHaveActiveSO() - s.requireAlias(rollApp_3_byOwner_asSrc.alias).noActiveSO() - - s.EqualValues(1, s.moduleBalance()) - s.EqualValues(minPrice, s.balance(rollApp_1_byOwner_asSrc.owner)) - }, - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - s.RefreshContext() - - s.Require().NotNil(tt.beforeHookTestFunc, "mis-configured test case") - s.Require().NotNil(tt.afterHookTestFunc, "mis-configured test case") - - if tt.preMintModuleBalance > 0 { - s.mintToModuleAccount(tt.preMintModuleBalance) - } - - err := s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: tt.expiryByAlias, - }, dymnstypes.TypeAlias) - s.Require().NoError(err) - - for _, rollApp := range tt.rollApps { - s.persistRollApp(rollApp) - } - - for _, so := range tt.sellOrders { - err = s.dymNsKeeper.SetSellOrder(s.ctx, so) - s.Require().NoError(err) - } - - useEpochIdentifier := s.moduleParams().Misc.EndEpochHookIdentifier - if tt.customEpochIdentifier != "" { - useEpochIdentifier = tt.customEpochIdentifier - } - - tt.beforeHookTestFunc(s) - - err = s.dymNsKeeper.GetEpochHooks().AfterEpochEnd(s.ctx, useEpochIdentifier, 1) - - defer func() { - tt.afterHookTestFunc(s) - - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeAlias) - if len(tt.wantExpiryByAlias) == 0 { - s.Empty(aSoe.Records) - } else { - s.Equal(tt.wantExpiryByAlias, aSoe.Records) - } - }() - - if tt.wantErr { - s.Require().ErrorContains(err, tt.wantErrContains) - - return - } - - s.Require().NoError(err) - }) - } -} - func (s *KeeperTestSuite) Test_rollappHooks_RollappCreated() { const price1L = 9 const price2L = 8 diff --git a/x/dymns/keeper/invariants.go b/x/dymns/keeper/invariants.go deleted file mode 100644 index 83e72622f..000000000 --- a/x/dymns/keeper/invariants.go +++ /dev/null @@ -1,61 +0,0 @@ -package keeper - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" -) - -const ( - sellOrderExpirationInvariantName = "sell-order-expiration" -) - -// RegisterInvariants registers the DymNS module invariants -func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { - ir.RegisterRoute(dymnstypes.ModuleName, sellOrderExpirationInvariantName, SellOrderExpirationInvariant(k)) -} - -// SellOrderExpirationInvariant checks that the records in the `ActiveSellOrdersExpiration` records are valid. -// The `ActiveSellOrdersExpiration` is used in hook and records are fetch from the store frequently, -// so we need to make sure the records are correct. -func SellOrderExpirationInvariant(k Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - var ( - broken bool - msg string - ) - - blockEpochUTC := ctx.BlockTime().Unix() - - for _, assetType := range []dymnstypes.AssetType{dymnstypes.TypeName, dymnstypes.TypeAlias} { - activeSellOrdersExpiration := k.GetActiveSellOrdersExpiration(ctx, assetType) - for _, record := range activeSellOrdersExpiration.Records { - if record.ExpireAt > blockEpochUTC { - // skip not expired ones. - // Why we skip expired ones? Because the hook only process the expired ones, - // so it is not necessary to check the not expired ones, to reduce store read. - continue - } - - sellOrder := k.GetSellOrder(ctx, record.AssetId, assetType) - if sellOrder == nil { - msg += fmt.Sprintf( - "sell order not found: assetId(%s), assetType(%s), expiry(%d)\n", - record.AssetId, assetType.PrettyName(), - record.ExpireAt, - ) - broken = true - } else if sellOrder.ExpireAt != record.ExpireAt { - msg += fmt.Sprintf( - "sell order expiration mismatch: assetId(%s), assetType(%s), expiry(%d) != actual(%d)\n", - record.AssetId, assetType.PrettyName(), - record.ExpireAt, sellOrder.ExpireAt, - ) - broken = true - } - } - } - return sdk.FormatInvariant(dymnstypes.ModuleName, sellOrderExpirationInvariantName, msg), broken - } -} diff --git a/x/dymns/keeper/invariants_test.go b/x/dymns/keeper/invariants_test.go deleted file mode 100644 index 65119746d..000000000 --- a/x/dymns/keeper/invariants_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package keeper_test - -import ( - dymnskeeper "github.com/dymensionxyz/dymension/v3/x/dymns/keeper" - dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" -) - -func (s *KeeperTestSuite) TestInvariants() { - tests := []struct { - name string - nameASoe []dymnstypes.ActiveSellOrdersExpirationRecord - aliasASoe []dymnstypes.ActiveSellOrdersExpirationRecord - sellOrders []dymnstypes.SellOrder - wantBroken bool - wantMsgContains string - }{ - { - name: "pass - all empty", - wantBroken: false, - }, - { - name: "pass - all correct", - nameASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "name1", - ExpireAt: 1, - }, - { - AssetId: "name2", - ExpireAt: 2, - }, - }, - aliasASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "alias1", - ExpireAt: 3, - }, - { - AssetId: "alias2", - ExpireAt: 4, - }, - }, - sellOrders: []dymnstypes.SellOrder{ - s.newDymNameSellOrder("name1").WithExpiry(1).WithMinPrice(1).Build(), - s.newDymNameSellOrder("name2").WithExpiry(2).WithMinPrice(2).Build(), - s.newAliasSellOrder("alias1").WithExpiry(3).WithMinPrice(3).Build(), - s.newAliasSellOrder("alias2").WithExpiry(4).WithMinPrice(4).Build(), - }, - wantBroken: false, - }, - { - name: "fail - missing a Dym-Name Sell-Order", - nameASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "name1", - ExpireAt: 1, - }, - { - AssetId: "name2", - ExpireAt: 2, - }, - }, - aliasASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "alias1", - ExpireAt: 3, - }, - { - AssetId: "alias2", - ExpireAt: 4, - }, - }, - sellOrders: []dymnstypes.SellOrder{ - s.newDymNameSellOrder("name1").WithExpiry(1).WithMinPrice(1).Build(), - s.newAliasSellOrder("alias1").WithExpiry(3).WithMinPrice(3).Build(), - s.newAliasSellOrder("alias2").WithExpiry(4).WithMinPrice(4).Build(), - }, - wantBroken: true, - wantMsgContains: "sell order not found", - }, - { - name: "fail - missing a Alias sell order", - nameASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "name1", - ExpireAt: 1, - }, - { - AssetId: "name2", - ExpireAt: 2, - }, - }, - aliasASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "alias1", - ExpireAt: 3, - }, - { - AssetId: "alias2", - ExpireAt: 4, - }, - }, - sellOrders: []dymnstypes.SellOrder{ - s.newDymNameSellOrder("name1").WithExpiry(1).WithMinPrice(1).Build(), - s.newDymNameSellOrder("name2").WithExpiry(2).WithMinPrice(2).Build(), - s.newAliasSellOrder("alias1").WithExpiry(3).WithMinPrice(3).Build(), - }, - wantBroken: true, - wantMsgContains: "sell order not found", - }, - { - name: "fail - mis-match expiry of a Dym-Name sell order", - nameASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "name1", - ExpireAt: 1, - }, - { - AssetId: "name2", - ExpireAt: 999, - }, - }, - aliasASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "alias1", - ExpireAt: 3, - }, - { - AssetId: "alias2", - ExpireAt: 4, - }, - }, - sellOrders: []dymnstypes.SellOrder{ - s.newDymNameSellOrder("name1").WithExpiry(1).WithMinPrice(1).Build(), - s.newDymNameSellOrder("name2").WithExpiry(2).WithMinPrice(2).Build(), - s.newAliasSellOrder("alias1").WithExpiry(3).WithMinPrice(3).Build(), - s.newAliasSellOrder("alias2").WithExpiry(4).WithMinPrice(4).Build(), - }, - wantBroken: true, - wantMsgContains: "sell order expiration mismatch", - }, - { - name: "fail - mis-match expiry of a Alias sell order", - nameASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "name1", - ExpireAt: 1, - }, - { - AssetId: "name2", - ExpireAt: 2, - }, - }, - aliasASoe: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "alias1", - ExpireAt: 3, - }, - { - AssetId: "alias2", - ExpireAt: 99, - }, - }, - sellOrders: []dymnstypes.SellOrder{ - s.newDymNameSellOrder("name1").WithExpiry(1).WithMinPrice(1).Build(), - s.newDymNameSellOrder("name2").WithExpiry(2).WithMinPrice(2).Build(), - s.newAliasSellOrder("alias1").WithExpiry(3).WithMinPrice(3).Build(), - s.newAliasSellOrder("alias2").WithExpiry(4).WithMinPrice(4).Build(), - }, - wantBroken: true, - wantMsgContains: "sell order expiration mismatch", - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - s.RefreshContext() - - err := s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: tt.nameASoe, - }, dymnstypes.TypeName) - s.Require().NoError(err) - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: tt.aliasASoe, - }, dymnstypes.TypeAlias) - s.Require().NoError(err) - - for _, so := range tt.sellOrders { - err := s.dymNsKeeper.SetSellOrder(s.ctx, so) - s.Require().NoError(err) - } - - msg, broken := dymnskeeper.SellOrderExpirationInvariant(s.dymNsKeeper)(s.ctx) - if tt.wantBroken { - s.Require().True(broken) - s.Require().NotEmpty(tt.wantMsgContains, "bad setup") - s.Require().Contains(msg, tt.wantMsgContains) - } else { - s.Require().False(broken) - } - }) - } -} diff --git a/x/dymns/keeper/keeper_suite_test.go b/x/dymns/keeper/keeper_suite_test.go index 20552dd80..384a08c31 100644 --- a/x/dymns/keeper/keeper_suite_test.go +++ b/x/dymns/keeper/keeper_suite_test.go @@ -55,6 +55,7 @@ type KeeperTestSuite struct { rollAppKeeper rollappkeeper.Keeper bankKeeper dymnstypes.BankKeeper + dymNsStoreKey storetypes.StoreKey rollappStoreKey storetypes.StoreKey } @@ -74,11 +75,11 @@ func (s *KeeperTestSuite) SetupTest() { var bk dymnstypes.BankKeeper var rk *rollappkeeper.Keeper - var rollappStoreKey storetypes.StoreKey + var dymNsStoreKey, rollappStoreKey storetypes.StoreKey { // initialization - dymNsStoreKey := sdk.NewKVStoreKey(dymnstypes.StoreKey) + dymNsStoreKey = sdk.NewKVStoreKey(dymnstypes.StoreKey) dymNsMemStoreKey := storetypes.NewMemoryStoreKey(dymnstypes.MemStoreKey) authStoreKey := sdk.NewKVStoreKey(authtypes.StoreKey) @@ -171,6 +172,7 @@ func (s *KeeperTestSuite) SetupTest() { s.dymNsKeeper = dk s.rollAppKeeper = *rk s.bankKeeper = bk + s.dymNsStoreKey = dymNsStoreKey s.rollappStoreKey = rollappStoreKey // custom @@ -768,13 +770,6 @@ func (m reqDymNameS) noActiveSO() reqDymNameS { return m } -func (m reqDymNameS) mustEquals(another dymnstypes.DymName) reqDymNameS { - dymName := m.s.dymNsKeeper.GetDymName(m.s.ctx, m.dymName) - m.s.Require().NotNil(dymName) - m.s.Require().Equal(another, *dymName) - return m -} - func (m reqDymNameS) ownerChangedTo(newOwner string) reqDymNameS { dymName := m.s.dymNsKeeper.GetDymName(m.s.ctx, m.dymName) m.s.Require().NotNil(dymName) @@ -784,9 +779,9 @@ func (m reqDymNameS) ownerChangedTo(newOwner string) reqDymNameS { return m } -func (m reqDymNameS) expiryEquals(expiry int64) reqDymNameS { +func (m reqDymNameS) ownerIs(expectedOwner string) reqDymNameS { dymName := m.s.dymNsKeeper.GetDymName(m.s.ctx, m.dymName) m.s.Require().NotNil(dymName) - m.s.Require().Equal(expiry, dymName.ExpireAt) + m.s.Require().Equal(expectedOwner, dymName.Owner) return m } diff --git a/x/dymns/keeper/msg_server.go b/x/dymns/keeper/msg_server.go index 66732c9b3..97d258403 100644 --- a/x/dymns/keeper/msg_server.go +++ b/x/dymns/keeper/msg_server.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" ) @@ -19,9 +21,19 @@ func NewMsgServerImpl(keeper Keeper) dymnstypes.MsgServer { // consumeMinimumGas consumes the minimum gas // if the consumed gas during tx is less than the minimum gas. -func consumeMinimumGas(ctx sdk.Context, minimumGas uint64, actionName string) { +// The original consumed gas should be captured from gas meter before invoke message execution. +// This function will panic if the gas meter consumed gas is less than the original consumed gas. +func consumeMinimumGas(ctx sdk.Context, minimumGas, originalConsumedGas uint64, actionName string) { if minimumGas > 0 { - if consumedGas := ctx.GasMeter().GasConsumed(); consumedGas < minimumGas { + laterConsumedGas := ctx.GasMeter().GasConsumed() + if laterConsumedGas < originalConsumedGas { + // unexpect gas consumption + panic(fmt.Sprintf( + "later gas is less than original gas: %d < %d", + laterConsumedGas, originalConsumedGas, + )) + } + if consumedGas := laterConsumedGas - originalConsumedGas; consumedGas < minimumGas { // we only consume the gas that is needed to reach the target minimum gas gasToConsume := minimumGas - consumedGas diff --git a/x/dymns/keeper/msg_server_accept_buy_order.go b/x/dymns/keeper/msg_server_accept_buy_order.go index b241536e3..cdcdf3c40 100644 --- a/x/dymns/keeper/msg_server_accept_buy_order.go +++ b/x/dymns/keeper/msg_server_accept_buy_order.go @@ -16,6 +16,7 @@ import ( // performed by the owner of the asset. func (k msgServer) AcceptBuyOrder(goCtx context.Context, msg *dymnstypes.MsgAcceptBuyOrder) (*dymnstypes.MsgAcceptBuyOrderResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() if err := msg.ValidateBasic(); err != nil { return nil, err @@ -47,7 +48,7 @@ func (k msgServer) AcceptBuyOrder(goCtx context.Context, msg *dymnstypes.MsgAcce } // charge protocol fee - consumeMinimumGas(ctx, dymnstypes.OpGasUpdateBuyOrder, "AcceptBuyOrder") + consumeMinimumGas(ctx, dymnstypes.OpGasUpdateBuyOrder, originalConsumedGas, "AcceptBuyOrder") return resp, nil } @@ -157,7 +158,7 @@ func (k msgServer) processAcceptBuyOrderWithAssetTypeAlias( } if k.IsAliasPresentsInParamsAsAliasOrChainId(ctx, offer.AssetId) { - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. return nil, errorsmod.Wrapf(gerrc.ErrPermissionDenied, "prohibited to trade aliases which is reserved for chain-id or alias in module params: %s", offer.AssetId, diff --git a/x/dymns/keeper/msg_server_accept_buy_order_test.go b/x/dymns/keeper/msg_server_accept_buy_order_test.go index e56ac2d30..f80191111 100644 --- a/x/dymns/keeper/msg_server_accept_buy_order_test.go +++ b/x/dymns/keeper/msg_server_accept_buy_order_test.go @@ -677,6 +677,30 @@ func (s *KeeperTestSuite) Test_msgServer_AcceptBuyOrder_Type_DymName() { wantLaterOwnerBalance: sdkmath.NewInt(2).Mul(priceMultiplier), wantMinConsumeGas: dymnstypes.OpGasUpdateBuyOrder, }, + { + name: "pass - independently charge gas", + existingDymName: dymName, + existingOffer: offer, + buyOrderId: offer.Id, + owner: dymName.Owner, + minAccept: offer.OfferPrice, + originalModuleBalance: offer.OfferPrice.Amount, + originalOwnerBalance: sdk.NewInt(0), + preRunSetupFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + wantErr: false, + wantLaterOffer: nil, + wantLaterDymName: &dymnstypes.DymName{ + Name: dymName.Name, + Owner: offer.Buyer, + Controller: offer.Buyer, + ExpireAt: dymName.ExpireAt, + }, + wantLaterModuleBalance: sdkmath.ZeroInt(), + wantLaterOwnerBalance: offer.OfferPrice.Amount, + wantMinConsumeGas: 100_000_000 + dymnstypes.OpGasUpdateBuyOrder, + }, } for _, tt := range tests { s.Run(tt.name, func() { @@ -1453,6 +1477,34 @@ func (s *KeeperTestSuite) Test_msgServer_AcceptBuyOrder_Type_Alias() { wantLaterOwnerBalance: sdk.NewInt(2), wantMinConsumeGas: dymnstypes.OpGasUpdateBuyOrder, }, + { + name: "pass - independently charge gas", + existingRollApps: []rollapp{rollApp_One_By1_SingleAlias, rollApp_Two_By2_SingleAlias}, + existingOffer: offerAliasOfRollAppOne, + buyOrderId: offerAliasOfRollAppOne.Id, + owner: rollApp_One_By1_SingleAlias.owner, + minAccept: offerAliasOfRollAppOne.OfferPrice, + originalModuleBalance: offerAliasOfRollAppOne.OfferPrice.Amount, + originalOwnerBalance: sdk.NewInt(0), + preRunSetupFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + wantErr: false, + wantLaterOffer: nil, + wantLaterRollApps: []rollapp{ + { + rollAppId: rollApp_One_By1_SingleAlias.rollAppId, + aliases: []string{}, + }, + { + rollAppId: rollApp_Two_By2_SingleAlias.rollAppId, + aliases: append(rollApp_Two_By2_SingleAlias.aliases, offerAliasOfRollAppOne.AssetId), + }, + }, + wantLaterModuleBalance: sdk.NewInt(0), + wantLaterOwnerBalance: offerAliasOfRollAppOne.OfferPrice.Amount, + wantMinConsumeGas: 100_000_000 + dymnstypes.OpGasUpdateBuyOrder, + }, } for _, tt := range tests { s.Run(tt.name, func() { diff --git a/x/dymns/keeper/msg_server_cancel_buy_order.go b/x/dymns/keeper/msg_server_cancel_buy_order.go index eef165336..1d15f66c5 100644 --- a/x/dymns/keeper/msg_server_cancel_buy_order.go +++ b/x/dymns/keeper/msg_server_cancel_buy_order.go @@ -14,6 +14,7 @@ import ( // handles canceling a Buy-Order, performed by the buyer who placed the offer. func (k msgServer) CancelBuyOrder(goCtx context.Context, msg *dymnstypes.MsgCancelBuyOrder) (*dymnstypes.MsgCancelBuyOrderResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() if err := msg.ValidateBasic(); err != nil { return nil, err @@ -41,7 +42,7 @@ func (k msgServer) CancelBuyOrder(goCtx context.Context, msg *dymnstypes.MsgCanc } // charge protocol fee - consumeMinimumGas(ctx, dymnstypes.OpGasCloseBuyOrder, "CancelBuyOrder") + consumeMinimumGas(ctx, dymnstypes.OpGasCloseBuyOrder, originalConsumedGas, "CancelBuyOrder") return resp, nil } diff --git a/x/dymns/keeper/msg_server_cancel_buy_order_test.go b/x/dymns/keeper/msg_server_cancel_buy_order_test.go index 669648ab3..0b4832b44 100644 --- a/x/dymns/keeper/msg_server_cancel_buy_order_test.go +++ b/x/dymns/keeper/msg_server_cancel_buy_order_test.go @@ -237,6 +237,23 @@ func (s *KeeperTestSuite) Test_msgServer_CancelBuyOrder_DymName() { wantLaterBuyerBalance: sdk.NewInt(2), wantMinConsumeGas: 1, }, + { + name: "pass - independently charge gas", + existingDymName: dymName, + existingOffer: offer, + buyOrderId: offer.Id, + buyer: offer.Buyer, + originalModuleBalance: offer.OfferPrice.Amount, + originalBuyerBalance: sdk.NewInt(0), + preRunSetupFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + wantErr: false, + wantLaterOffer: nil, + wantLaterModuleBalance: sdk.NewInt(0), + wantLaterBuyerBalance: offer.OfferPrice.Amount, + wantMinConsumeGas: 100_000_000 + dymnstypes.OpGasCloseBuyOrder, + }, } for _, tt := range tests { s.Run(tt.name, func() { @@ -615,6 +632,23 @@ func (s *KeeperTestSuite) Test_msgServer_CancelBuyOrder_Alias() { wantLaterBuyerBalance: sdk.NewInt(2), wantMinConsumeGas: 1, }, + { + name: "pass - independently charge gas", + existingRollApps: []rollapp{rollApp_One_By1, rollApp_Two_By2}, + existingOffer: offerAliasOfRollAppOne, + buyOrderId: offerAliasOfRollAppOne.Id, + buyer: offerAliasOfRollAppOne.Buyer, + originalModuleBalance: offerAliasOfRollAppOne.OfferPrice.Amount, + originalBuyerBalance: sdk.NewInt(0), + preRunSetupFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + wantErr: false, + wantLaterOffer: nil, + wantLaterModuleBalance: sdk.NewInt(0), + wantLaterBuyerBalance: offerAliasOfRollAppOne.OfferPrice.Amount, + wantMinConsumeGas: 100_000_000 + dymnstypes.OpGasCloseBuyOrder, + }, } for _, tt := range tests { s.Run(tt.name, func() { diff --git a/x/dymns/keeper/msg_server_cancel_sell_order.go b/x/dymns/keeper/msg_server_cancel_sell_order.go index a6b1d29cc..d93db4f5d 100644 --- a/x/dymns/keeper/msg_server_cancel_sell_order.go +++ b/x/dymns/keeper/msg_server_cancel_sell_order.go @@ -16,6 +16,7 @@ import ( // Can only be performed if no one has placed a bid on the asset. func (k msgServer) CancelSellOrder(goCtx context.Context, msg *dymnstypes.MsgCancelSellOrder) (*dymnstypes.MsgCancelSellOrderResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() if err := msg.ValidateBasic(); err != nil { return nil, err @@ -38,7 +39,7 @@ func (k msgServer) CancelSellOrder(goCtx context.Context, msg *dymnstypes.MsgCan } // charge protocol fee - consumeMinimumGas(ctx, dymnstypes.OpGasCloseSellOrder, "CancelSellOrder") + consumeMinimumGas(ctx, dymnstypes.OpGasCloseSellOrder, originalConsumedGas, "CancelSellOrder") return resp, nil } @@ -53,12 +54,6 @@ func (k msgServer) processCancelSellOrderWithAssetTypeDymName( k.DeleteSellOrder(ctx, msg.AssetId, msg.AssetType) - aSoe := k.GetActiveSellOrdersExpiration(ctx, msg.AssetType) - aSoe.Remove(msg.AssetId) - if err := k.SetActiveSellOrdersExpiration(ctx, aSoe, msg.AssetType); err != nil { - return nil, err - } - return &dymnstypes.MsgCancelSellOrderResponse{}, nil } @@ -80,10 +75,6 @@ func (k msgServer) validateCancelSellOrderWithAssetTypeDymName( return errorsmod.Wrapf(gerrc.ErrNotFound, "Sell-Order: %s", msg.AssetId) } - if so.HasExpiredAtCtx(ctx) { - return errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot cancel an expired order") - } - if so.HighestBid != nil { return errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot cancel once bid placed") } @@ -101,12 +92,6 @@ func (k msgServer) processCancelSellOrderWithAssetTypeAlias( k.DeleteSellOrder(ctx, msg.AssetId, msg.AssetType) - aSoe := k.GetActiveSellOrdersExpiration(ctx, msg.AssetType) - aSoe.Remove(msg.AssetId) - if err := k.SetActiveSellOrdersExpiration(ctx, aSoe, msg.AssetType); err != nil { - return nil, err - } - return &dymnstypes.MsgCancelSellOrderResponse{}, nil } @@ -128,10 +113,6 @@ func (k msgServer) validateCancelSellOrderWithAssetTypeAlias( return errorsmod.Wrapf(gerrc.ErrNotFound, "Sell-Order: %s", msg.AssetId) } - if so.HasExpiredAtCtx(ctx) { - return errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot cancel an expired order") - } - if so.HighestBid != nil { return errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot cancel once bid placed") } diff --git a/x/dymns/keeper/msg_server_cancel_sell_order_test.go b/x/dymns/keeper/msg_server_cancel_sell_order_test.go index 2a9c9f5e1..1afb0bb66 100644 --- a/x/dymns/keeper/msg_server_cancel_sell_order_test.go +++ b/x/dymns/keeper/msg_server_cancel_sell_order_test.go @@ -40,7 +40,9 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { dymNames := s.dymNsKeeper.GetAllNonExpiredDymNames(s.ctx) s.Require().Len(dymNames, 2) - s.Run("do not process message that not pass basic validation", func() { + s.SaveCurrentContext() + + s.Run("fail - do not process message that not pass basic validation", func() { resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: "abc", Owner: "0x1", // invalid owner @@ -51,7 +53,7 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { s.Require().Nil(resp) }) - s.Run("do not process message that refer to non-existing Dym-Name", func() { + s.Run("fail - do not process message that refer to non-existing Dym-Name", func() { resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: "not-exists", AssetType: dymnstypes.TypeName, @@ -62,7 +64,7 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { s.Require().Contains(err.Error(), "Dym-Name: not-exists: not found") }) - s.Run("do not process message that type is Unknown", func() { + s.Run("fail - do not process message that type is Unknown", func() { resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: "asset", AssetType: dymnstypes.AssetType_AT_UNKNOWN, @@ -73,7 +75,9 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { s.Require().Contains(err.Error(), "invalid asset type") }) - s.Run("do not process that owner does not match", func() { + s.Run("fail - do not process that owner does not match", func() { + s.RefreshContext() + so11 := dymnstypes.SellOrder{ AssetId: dymName1.Name, AssetType: dymnstypes.TypeName, @@ -84,10 +88,6 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { err = s.dymNsKeeper.SetSellOrder(s.ctx, so11) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeName) - }() - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeName, @@ -98,7 +98,9 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { s.Require().Contains(err.Error(), "not the owner of the Dym-Name") }) - s.Run("do not process for Dym-Name that does not have any SO", func() { + s.Run("fail - do not process for Dym-Name that does not have any SO", func() { + s.RefreshContext() + resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: dymName1.Name, AssetType: dymnstypes.TypeName, @@ -109,7 +111,9 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { s.Require().Contains(err.Error(), fmt.Sprintf("Sell-Order: %s: not found", dymName1.Name)) }) - s.Run("can not cancel expired", func() { + s.Run("pass - cancel expired order", func() { + s.RefreshContext() + so11 := dymnstypes.SellOrder{ AssetId: dymName1.Name, AssetType: dymnstypes.TypeName, @@ -120,21 +124,18 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { err = s.dymNsKeeper.SetSellOrder(s.ctx, so11) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeName) - }() - - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ + _, err = msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeName, Owner: ownerA, }) - s.Require().Error(err) - s.Require().Nil(resp) - s.Require().Contains(err.Error(), "cannot cancel an expired order") + s.Require().NoError(err) + s.requireDymName(dymName1.Name).noActiveSO() }) - s.Run("can not cancel once bid placed", func() { + s.Run("fail - can not cancel once bid placed", func() { + s.RefreshContext() + so11 := dymnstypes.SellOrder{ AssetId: dymName1.Name, AssetType: dymnstypes.TypeName, @@ -148,10 +149,6 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { err = s.dymNsKeeper.SetSellOrder(s.ctx, so11) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeName) - }() - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeName, @@ -162,58 +159,12 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { s.Require().Contains(err.Error(), "cannot cancel once bid placed") }) - s.Run("can will remove the active SO expiration mapping record", func() { - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeName) + s.Run("pass - can cancel if satisfied conditions", func() { + s.RefreshContext() - so11 := dymnstypes.SellOrder{ - AssetId: dymName1.Name, - AssetType: dymnstypes.TypeName, - ExpireAt: s.now.Unix() + 100, - MinPrice: s.coin(100), - } - err = s.dymNsKeeper.SetSellOrder(s.ctx, so11) - s.Require().NoError(err) - aSoe.Add(so11.AssetId, so11.ExpireAt) + const previousRunGasConsumed = 100_000_000 + s.ctx.GasMeter().ConsumeGas(previousRunGasConsumed, "simulate previous run") - so12 := dymnstypes.SellOrder{ - AssetId: dymName2.Name, - AssetType: dymnstypes.TypeName, - ExpireAt: s.now.Unix() + 100, - MinPrice: s.coin(100), - } - err = s.dymNsKeeper.SetSellOrder(s.ctx, so12) - s.Require().NoError(err) - aSoe.Add(so12.AssetId, so12.ExpireAt) - - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, aSoe, dymnstypes.TypeName) - s.Require().NoError(err) - - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeName) - s.dymNsKeeper.DeleteSellOrder(s.ctx, so12.AssetId, dymnstypes.TypeName) - }() - - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ - AssetId: so11.AssetId, - AssetType: dymnstypes.TypeName, - Owner: ownerA, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) - - s.Require().Nil(s.dymNsKeeper.GetSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeName), "SO should be removed from active") - - aSoe = s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeName) - - allNames := make(map[string]bool) - for _, record := range aSoe.Records { - allNames[record.AssetId] = true - } - s.Require().NotContains(allNames, so11.AssetId) - s.Require().Contains(allNames, so12.AssetId) - }) - - s.Run("can cancel if satisfied conditions", func() { moduleParams := s.dymNsKeeper.GetParams(s.ctx) moduleParams.Misc.EnableTradingName = false // allowed to cancel even if trading is disabled s.Require().NoError(s.dymNsKeeper.SetParams(s.ctx, moduleParams)) @@ -236,11 +187,6 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { err = s.dymNsKeeper.SetSellOrder(s.ctx, so12) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeName) - s.dymNsKeeper.DeleteSellOrder(s.ctx, so12.AssetId, dymnstypes.TypeName) - }() - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeName, @@ -256,6 +202,10 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_DymName() { s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasCloseSellOrder, "should consume params gas", ) + s.GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), previousRunGasConsumed+dymnstypes.OpGasCloseSellOrder, + "gas consumption should be stacked with previous run", + ) }) } @@ -275,7 +225,9 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { s.persistRollApp(ra) } - s.Run("do not process message that not pass basic validation", func() { + s.SaveCurrentContext() + + s.Run("fail - do not process message that not pass basic validation", func() { resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: rollapp_1_ofOwner.alias, Owner: "0x1", // invalid owner @@ -285,7 +237,7 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { s.Require().Nil(resp) }) - s.Run("do not process message that refer to non-existing alias", func() { + s.Run("fail - do not process message that refer to non-existing alias", func() { resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: "void", AssetType: dymnstypes.TypeAlias, @@ -296,7 +248,7 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { s.Require().Contains(err.Error(), "alias is not in-used: void: not found") }) - s.Run("do not process for Alias that does not have any SO", func() { + s.Run("fail - do not process for Alias that does not have any SO", func() { resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: rollapp_1_ofOwner.alias, AssetType: dymnstypes.TypeAlias, @@ -307,7 +259,9 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { s.Require().Contains(err.Error(), fmt.Sprintf("Sell-Order: %s: not found", rollapp_1_ofOwner.alias)) }) - s.Run("do not process that owner does not match", func() { + s.Run("fail - do not process that owner does not match", func() { + s.RefreshContext() + so11 := dymnstypes.SellOrder{ AssetId: rollapp_1_ofOwner.alias, AssetType: dymnstypes.TypeAlias, @@ -318,10 +272,6 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { err := s.dymNsKeeper.SetSellOrder(s.ctx, so11) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeAlias) - }() - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeAlias, @@ -332,7 +282,9 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { s.Require().Contains(err.Error(), "not the owner of the RollApp") }) - s.Run("can not cancel expired order", func() { + s.Run("pass - cancel expired order", func() { + s.RefreshContext() + so11 := dymnstypes.SellOrder{ AssetId: rollapp_1_ofOwner.alias, AssetType: dymnstypes.TypeAlias, @@ -343,21 +295,18 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { err := s.dymNsKeeper.SetSellOrder(s.ctx, so11) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeAlias) - }() - - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ + _, err = msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeAlias, Owner: ownerA, }) - s.Require().Error(err) - s.Require().Nil(resp) - s.Require().Contains(err.Error(), "cannot cancel an expired order") + s.Require().NoError(err) + s.requireAlias(rollapp_1_ofOwner.alias).noActiveSO() }) - s.Run("can not cancel once bid placed", func() { + s.Run("fail - can not cancel once bid placed", func() { + s.RefreshContext() + so11 := dymnstypes.SellOrder{ AssetId: rollapp_1_ofOwner.alias, AssetType: dymnstypes.TypeAlias, @@ -372,10 +321,6 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { err := s.dymNsKeeper.SetSellOrder(s.ctx, so11) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeAlias) - }() - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeAlias, @@ -386,58 +331,12 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { s.Require().Contains(err.Error(), "cannot cancel once bid placed") }) - s.Run("cancellation will remove the active SO expiration mapping record", func() { - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeAlias) - - so11 := dymnstypes.SellOrder{ - AssetId: rollapp_1_ofOwner.alias, - AssetType: dymnstypes.TypeAlias, - ExpireAt: s.now.Unix() + 100, - MinPrice: s.coin(100), - } - err := s.dymNsKeeper.SetSellOrder(s.ctx, so11) - s.Require().NoError(err) - aSoe.Add(so11.AssetId, so11.ExpireAt) - - so12 := dymnstypes.SellOrder{ - AssetId: rollapp_2_ofOwner.alias, - AssetType: dymnstypes.TypeAlias, - ExpireAt: s.now.Unix() + 100, - MinPrice: s.coin(100), - } - err = s.dymNsKeeper.SetSellOrder(s.ctx, so12) - s.Require().NoError(err) - aSoe.Add(so12.AssetId, so12.ExpireAt) - - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, aSoe, dymnstypes.TypeAlias) - s.Require().NoError(err) - - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeAlias) - s.dymNsKeeper.DeleteSellOrder(s.ctx, so12.AssetId, dymnstypes.TypeAlias) - }() - - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ - AssetId: so11.AssetId, - AssetType: dymnstypes.TypeAlias, - Owner: ownerA, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) - - s.Require().Nil(s.dymNsKeeper.GetSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeAlias), "SO should be removed from active") - - aSoe = s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeAlias) + s.Run("pass - can cancel if satisfied conditions", func() { + s.RefreshContext() - allAliases := make(map[string]bool) - for _, record := range aSoe.Records { - allAliases[record.AssetId] = true - } - s.Require().NotContains(allAliases, so11.AssetId) - s.Require().Contains(allAliases, so12.AssetId) - }) + const previousRunGasConsumed = 100_000_000 + s.ctx.GasMeter().ConsumeGas(previousRunGasConsumed, "simulate previous run") - s.Run("can cancel if satisfied conditions", func() { moduleParams := s.dymNsKeeper.GetParams(s.ctx) moduleParams.Misc.EnableTradingAlias = false // allowed to cancel even if trading is disabled s.Require().NoError(s.dymNsKeeper.SetParams(s.ctx, moduleParams)) @@ -460,11 +359,6 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { err = s.dymNsKeeper.SetSellOrder(s.ctx, so12) s.Require().NoError(err) - defer func() { - s.dymNsKeeper.DeleteSellOrder(s.ctx, so11.AssetId, dymnstypes.TypeAlias) - s.dymNsKeeper.DeleteSellOrder(s.ctx, so12.AssetId, dymnstypes.TypeAlias) - }() - resp, err := msgServer.CancelSellOrder(sdk.WrapSDKContext(s.ctx), &dymnstypes.MsgCancelSellOrder{ AssetId: so11.AssetId, AssetType: dymnstypes.TypeAlias, @@ -480,6 +374,10 @@ func (s *KeeperTestSuite) Test_msgServer_CancelSellOrder_Alias() { s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasCloseSellOrder, "should consume params gas", ) + s.GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), previousRunGasConsumed+dymnstypes.OpGasCloseSellOrder, + "gas consumption should be stacked with previous run", + ) s.requireAlias(rollapp_1_ofOwner.alias).LinkedToRollApp(rollapp_1_ofOwner.rollAppId) }) diff --git a/x/dymns/keeper/msg_server_complete_sell_order.go b/x/dymns/keeper/msg_server_complete_sell_order.go new file mode 100644 index 000000000..bef28bc21 --- /dev/null +++ b/x/dymns/keeper/msg_server_complete_sell_order.go @@ -0,0 +1,192 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + "github.com/dymensionxyz/gerr-cosmos/gerrc" + + sdk "github.com/cosmos/cosmos-sdk/types" + dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" +) + +// CompleteSellOrder is message handler, +// handles Sell-Order completion action, can be performed by either asset owner or the person who placed the highest bid. +// Can only be performed when Sell-Order expired and has a bid placed. +// If the asset was expired or prohibited trading, bid placed will be force to return to the bidder, ownership will not be transferred. +func (k msgServer) CompleteSellOrder(goCtx context.Context, msg *dymnstypes.MsgCompleteSellOrder) (*dymnstypes.MsgCompleteSellOrderResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + var resp *dymnstypes.MsgCompleteSellOrderResponse + var err error + + // process the Sell-Order based on the asset type + + if msg.AssetType == dymnstypes.TypeName { + resp, err = k.processCompleteSellOrderWithAssetTypeDymName(ctx, msg) + } else if msg.AssetType == dymnstypes.TypeAlias { + resp, err = k.processCompleteSellOrderWithAssetTypeAlias(ctx, msg) + } else { + err = errorsmod.Wrapf(gerrc.ErrInvalidArgument, "invalid asset type: %s", msg.AssetType) + } + if err != nil { + return nil, err + } + + // charge protocol fee + consumeMinimumGas(ctx, dymnstypes.OpGasCompleteSellOrder, originalConsumedGas, "CompleteSellOrder") + + return resp, nil +} + +// processCompleteSellOrderWithAssetTypeDymName handles the message handled by CompleteSellOrder, type Dym-Name. +func (k msgServer) processCompleteSellOrderWithAssetTypeDymName( + ctx sdk.Context, msg *dymnstypes.MsgCompleteSellOrder, +) (*dymnstypes.MsgCompleteSellOrderResponse, error) { + so, dymName, err := k.validateCompleteSellOrderWithAssetTypeDymName(ctx, msg) + if err != nil { + return nil, err + } + + miscParams := k.MiscParams(ctx) + + var refund bool + if !miscParams.EnableTradingName { + k.Logger(ctx).Info("Dym-Name trading is disabled, refunding the bid", "Dym-Name", dymName.Name) + refund = true + } else if dymName.IsExpiredAtCtx(ctx) { + k.Logger(ctx).Info("Dym-Name is expired, refunding the bid", "Dym-Name", dymName.Name) + refund = true + } + + if refund { + if err := k.RefundBid(ctx, *so.HighestBid, so.AssetType); err != nil { + return nil, err + } + k.DeleteSellOrder(ctx, so.AssetId, so.AssetType) + return &dymnstypes.MsgCompleteSellOrderResponse{}, nil + } + + if err := k.CompleteDymNameSellOrder(ctx, so.AssetId); err != nil { + return nil, err + } + + return &dymnstypes.MsgCompleteSellOrderResponse{}, nil +} + +// validateCompleteSellOrderWithAssetTypeDymName handles validation for the message handled by CompleteSellOrder, type Dym-Name. +func (k msgServer) validateCompleteSellOrderWithAssetTypeDymName( + ctx sdk.Context, msg *dymnstypes.MsgCompleteSellOrder, +) (*dymnstypes.SellOrder, *dymnstypes.DymName, error) { + so := k.GetSellOrder(ctx, msg.AssetId, msg.AssetType) + if so == nil { + return nil, nil, errorsmod.Wrapf(gerrc.ErrNotFound, "Sell-Order: %s", msg.AssetId) + } + + if so.HighestBid == nil { + return nil, nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "no bid placed on the Sell-Order") + } + + if !so.HasFinishedAtCtx(ctx) { + return nil, nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "Sell-Order not yet completed") + } + + dymName := k.GetDymName(ctx, msg.AssetId) + if dymName == nil { + return nil, nil, errorsmod.Wrapf(gerrc.ErrNotFound, "Dym-Name: %s", msg.AssetId) + } + + if dymName.Owner != msg.Participant && so.HighestBid.Bidder != msg.Participant { + return nil, nil, errorsmod.Wrap(gerrc.ErrPermissionDenied, "must be either Dym-Name owner or the highest bidder to complete the Sell-Order") + } + + return so, dymName, nil +} + +// processCompleteSellOrderWithAssetTypeAlias handles the message handled by CompleteSellOrder, type Alias. +func (k msgServer) processCompleteSellOrderWithAssetTypeAlias( + ctx sdk.Context, msg *dymnstypes.MsgCompleteSellOrder, +) (*dymnstypes.MsgCompleteSellOrderResponse, error) { + so, err := k.validateCompleteSellOrderWithAssetTypeAlias(ctx, msg) + if err != nil { + return nil, err + } + + miscParams := k.MiscParams(ctx) + var refund bool + + /** + For the Sell-Orders which the assets are prohibited to trade, + the Sell-Order will be force cancelled and the bids will be refunded. + + Why some aliases are prohibited to trade? And what are they? + In module params, there is a list of alias mapping for some external well-known chains. + So those aliases are considered as reserved for the external chains, + therefor trading is not allowed. + + Why can someone own a prohibited alias? + An alias can be bought before the reservation was made. + But when the alias becomes reserved for the external well-known chains, + the alias will be prohibited to trade. + + Why can someone place a Sell-Order for the prohibited alias? + When a Sell-Order created before the reservation was made. + */ + if forceCancel := k.IsAliasPresentsInParamsAsAliasOrChainId(ctx, so.AssetId); forceCancel { + // Sell-Order will be force cancelled and refund bids if any, + // when the alias is prohibited to trade + k.Logger(ctx).Info("Alias is prohibited to trade, refunding the bid", "Alias", so.AssetId) + refund = true + } else if !miscParams.EnableTradingAlias { + k.Logger(ctx).Info("Alias trading is disabled, refunding the bid", "Alias", so.AssetId) + refund = true + } + + if refund { + if err := k.RefundBid(ctx, *so.HighestBid, so.AssetType); err != nil { + return nil, err + } + k.DeleteSellOrder(ctx, so.AssetId, so.AssetType) + return &dymnstypes.MsgCompleteSellOrderResponse{}, nil + } + + if err := k.CompleteAliasSellOrder(ctx, msg.AssetId); err != nil { + return nil, err + } + + return &dymnstypes.MsgCompleteSellOrderResponse{}, nil +} + +// validateCompleteSellOrderWithAssetTypeAlias handles validation for the message handled by CompleteSellOrder, type Alias. +func (k msgServer) validateCompleteSellOrderWithAssetTypeAlias( + ctx sdk.Context, msg *dymnstypes.MsgCompleteSellOrder, +) (*dymnstypes.SellOrder, error) { + so := k.GetSellOrder(ctx, msg.AssetId, msg.AssetType) + if so == nil { + return nil, errorsmod.Wrapf(gerrc.ErrNotFound, "Sell-Order: %s", msg.AssetId) + } + + if so.HighestBid == nil { + return nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "no bid placed on the Sell-Order") + } + + if !so.HasFinishedAtCtx(ctx) { + return nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "Sell-Order not yet completed") + } + + existingRollAppIdUsingAlias, found := k.GetRollAppIdByAlias(ctx, msg.AssetId) + if !found { + return nil, errorsmod.Wrapf(gerrc.ErrNotFound, "alias is not in-use: %s", msg.AssetId) + } + + if !k.IsRollAppCreator(ctx, existingRollAppIdUsingAlias, msg.Participant) && so.HighestBid.Bidder != msg.Participant { + return nil, errorsmod.Wrapf(gerrc.ErrPermissionDenied, "must be either Roll-App creator or the highest bidder to complete the Sell-Order") + } + + return so, nil +} diff --git a/x/dymns/keeper/msg_server_complete_sell_order_test.go b/x/dymns/keeper/msg_server_complete_sell_order_test.go new file mode 100644 index 000000000..35a70fd19 --- /dev/null +++ b/x/dymns/keeper/msg_server_complete_sell_order_test.go @@ -0,0 +1,799 @@ +package keeper_test + +import ( + "time" + + "github.com/dymensionxyz/gerr-cosmos/gerrc" + + dymnskeeper "github.com/dymensionxyz/dymension/v3/x/dymns/keeper" + dymnstypes "github.com/dymensionxyz/dymension/v3/x/dymns/types" +) + +func (s *KeeperTestSuite) Test_msgServer_CompleteSellOrder_DymName() { + ownerAcc := testAddr(1) + buyerAcc := testAddr(2) + ownerA := ownerAcc.bech32() + buyerA := buyerAcc.bech32() + + s.Run("reject if message not pass validate basic", func() { + _, err := dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).CompleteSellOrder(s.ctx, &dymnstypes.MsgCompleteSellOrder{ + AssetType: dymnstypes.TypeName, + }) + + s.Require().ErrorContains(err, gerrc.ErrInvalidArgument.Error()) + }) + + s.Run("reject if message asset type is Unknown", func() { + _, err := dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).CompleteSellOrder(s.ctx, &dymnstypes.MsgCompleteSellOrder{ + AssetId: "asset", + AssetType: dymnstypes.AssetType_AT_UNKNOWN, + Participant: ownerA, + }) + + s.Require().ErrorContains(err, gerrc.ErrInvalidArgument.Error()) + }) + + dymName := dymnstypes.DymName{ + Name: "my-name", + Owner: ownerA, + Controller: ownerA, + ExpireAt: s.now.Unix() + 100, + } + + const ownerOriginalBalance int64 = 1000 + const buyerOriginalBalance int64 = 500 + const moduleOriginalBalance int64 = 100 + const minPrice int64 = 100 + + expiredSO := dymnstypes.SellOrder{ + AssetId: dymName.Name, + AssetType: dymnstypes.TypeName, + MinPrice: s.coin(minPrice), + ExpireAt: s.now.Add(-time.Second).Unix(), + HighestBid: &dymnstypes.SellOrderBid{ + Bidder: buyerA, + Price: s.coin(minPrice), + Params: nil, + }, + } + + { + // prepare test data + + s.mintToAccount(ownerA, ownerOriginalBalance) + s.mintToAccount(buyerA, buyerOriginalBalance) + s.mintToModuleAccount(moduleOriginalBalance) + + err := s.dymNsKeeper.SetDymName(s.ctx, dymName) + s.Require().NoError(err) + + err = s.dymNsKeeper.SetSellOrder(s.ctx, expiredSO) + s.Require().NoError(err) + + s.SaveCurrentContext() + } + + tests := []struct { + name string + participant string + preRunFunc func(s *KeeperTestSuite) + wantErr bool + wantErrContains string + postRunFunc func(s *KeeperTestSuite) + }{ + { + name: "pass - can complete sell order, by owner, expired, with bid", + participant: ownerA, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireDymName(dymName.Name). + noActiveSO(). + ownerChangedTo(buyerA) + }, + }, + { + name: "pass - can complete sell order, by buyer, expired, with bid", + participant: buyerA, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireDymName(dymName.Name). + noActiveSO(). + ownerChangedTo(buyerA) + }, + }, + { + name: "pass - balance changed on success", + participant: ownerA, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.Equal( + ownerOriginalBalance+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(ownerA), + "owner should receive the bid amount", + ) + s.Equal( + buyerOriginalBalance, + s.balance(buyerA), + "buyer should not be charged", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "module account should be charged", + ) + }, + }, + { + name: "pass - after completed, ownership changed to buyer", + participant: buyerA, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireDymName(dymName.Name). + ownerChangedTo(buyerA) + }, + }, + { + name: "pass - after completed, reverse resolution should be updated", + participant: buyerA, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireConfiguredAddress(ownerA).notMappedToAnyDymName() + s.requireConfiguredAddress(buyerA).mappedDymNames(dymName.Name) + s.requireFallbackAddress(ownerAcc.fallback()).notMappedToAnyDymName() + s.requireFallbackAddress(buyerAcc.fallback()).mappedDymNames(dymName.Name) + }, + }, + { + name: "pass - after completed, SO will be deleted", + participant: buyerA, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireDymName(dymName.Name).noActiveSO() + }, + }, + { + name: "pass - will refund when trading was disabled, requested by owner", + participant: ownerA, + preRunFunc: func(s *KeeperTestSuite) { + s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { + p.Misc.EnableTradingName = false + return p + }) + }, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireDymName(dymName.Name).noActiveSO().ownerIs(ownerA) + + s.Equal( + ownerOriginalBalance, + s.balance(ownerA), + "owner should not receive the bid amount", + ) + s.Equal( + buyerOriginalBalance+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(buyerA), + "buyer should get the refund amount", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "refund amount should be subtracted from module account", + ) + }, + }, + { + name: "pass - will refund when trading was disabled, requested by buyer", + participant: buyerA, + preRunFunc: func(s *KeeperTestSuite) { + s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { + p.Misc.EnableTradingName = false + return p + }) + }, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireDymName(dymName.Name).noActiveSO().ownerIs(ownerA) + + s.Equal( + ownerOriginalBalance, + s.balance(ownerA), + "owner should not receive the bid amount", + ) + s.Equal( + buyerOriginalBalance+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(buyerA), + "buyer should get the refund amount", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "refund amount should be subtracted from module account", + ) + }, + }, + { + name: "pass - will refund when Dym-Name was expired, requested by buyer", + participant: buyerA, + preRunFunc: func(s *KeeperTestSuite) { + existingDymName := s.dymNsKeeper.GetDymName(s.ctx, dymName.Name) + s.Require().NotNil(existingDymName) + + existingDymName.ExpireAt = s.now.Add(-time.Second).Unix() + err := s.dymNsKeeper.SetDymName(s.ctx, *existingDymName) + s.Require().NoError(err) + }, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireDymName(dymName.Name).noActiveSO().ownerIs(ownerA) + + s.Equal( + ownerOriginalBalance, + s.balance(ownerA), + "owner should not receive the bid amount", + ) + s.Equal( + buyerOriginalBalance+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(buyerA), + "buyer should get the refund amount", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "refund amount should be subtracted from module account", + ) + }, + }, + { + name: "fail - when failed to refund, keep as is", + participant: buyerA, + preRunFunc: func(s *KeeperTestSuite) { + // increase the highest bid amount, module balance will not be enough to refund + { + existingSO := s.dymNsKeeper.GetSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + s.Require().NotNil(existingSO) + + existingSO.HighestBid.Price.Amount = s.coin(moduleOriginalBalance + 1).Amount + err := s.dymNsKeeper.SetSellOrder(s.ctx, *existingSO) + s.Require().NoError(err) + } + + // disable trading to toggle refund logic + s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { + p.Misc.EnableTradingName = false + return p + }) + }, + wantErr: true, + wantErrContains: "insufficient", + postRunFunc: func(s *KeeperTestSuite) { + s.Equal(ownerOriginalBalance, s.balance(ownerA)) + s.Equal(buyerOriginalBalance, s.balance(buyerA)) + s.Equal(moduleOriginalBalance, s.balance(dymNsModuleAccAddr.String())) + + s.requireDymName(dymName.Name). + mustHaveActiveSO(). + ownerIs(ownerA) + }, + }, + { + name: "fail - non-participant cannot complete", + participant: testAddr(0).bech32(), + wantErr: true, + wantErrContains: gerrc.ErrPermissionDenied.Error(), + }, + { + name: "fail - can not complete when no bid placed", + participant: ownerA, + preRunFunc: func(s *KeeperTestSuite) { + existingSO := s.dymNsKeeper.GetSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + s.Require().NotNil(existingSO) + + existingSO.HighestBid = nil + err := s.dymNsKeeper.SetSellOrder(s.ctx, *existingSO) + s.Require().NoError(err) + }, + wantErr: true, + wantErrContains: "no bid placed on the Sell-Order", + }, + { + name: "fail - can not complete when SO not yet expired", + participant: buyerA, + preRunFunc: func(s *KeeperTestSuite) { + existingSO := s.dymNsKeeper.GetSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + s.Require().NotNil(existingSO) + + existingSO.ExpireAt = s.now.Add(time.Second).Unix() + err := s.dymNsKeeper.SetSellOrder(s.ctx, *existingSO) + s.Require().NoError(err) + }, + wantErr: true, + wantErrContains: "Sell-Order not yet completed", + }, + { + name: "fail - can not complete when Dym-Name does not exists", + participant: buyerA, + preRunFunc: func(s *KeeperTestSuite) { + err := s.dymNsKeeper.DeleteDymName(s.ctx, dymName.Name) + s.Require().NoError(err) + }, + wantErr: true, + wantErrContains: gerrc.ErrNotFound.Error(), + }, + { + name: "fail - can not complete when SO does not exists", + participant: buyerA, + preRunFunc: func(s *KeeperTestSuite) { + s.dymNsKeeper.DeleteSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + }, + wantErr: true, + wantErrContains: gerrc.ErrNotFound.Error(), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + s.RefreshContext() + + if tt.preRunFunc != nil { + tt.preRunFunc(s) + } + + resp, errCompleteSO := dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).CompleteSellOrder(s.ctx, &dymnstypes.MsgCompleteSellOrder{ + AssetId: dymName.Name, + AssetType: dymnstypes.TypeName, + Participant: tt.participant, + }) + + defer func() { + if tt.postRunFunc != nil { + tt.postRunFunc(s) + } + }() + + if tt.wantErr { + s.Require().ErrorContains(errCompleteSO, tt.wantErrContains) + s.Nil(resp) + + s.Less( + s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasCompleteSellOrder, + "should not consume params gas on failed operation", + ) + return + } + + s.Require().NoError(errCompleteSO) + s.NotNil(resp) + + s.GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasCompleteSellOrder, + "should consume params gas", + ) + }) + } + + s.Run("independently charge gas", func() { + s.RefreshContext() + + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + + s.setDymNameWithFunctionsAfter(dymName) + err := s.dymNsKeeper.SetSellOrder(s.ctx, expiredSO) + s.Require().NoError(err) + s.mintToModuleAccount(minPrice) + + _, err = dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).CompleteSellOrder(s.ctx, &dymnstypes.MsgCompleteSellOrder{ + AssetId: dymName.Name, + AssetType: dymnstypes.TypeName, + Participant: buyerA, + }) + s.Require().NoError(err) + s.GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), 100_000_000+dymnstypes.OpGasCompleteSellOrder, + "should consume params gas", + ) + }) +} + +//goland:noinspection GoSnakeCaseUsage +func (s *KeeperTestSuite) Test_msgServer_CompleteSellOrder_Alias() { + s.Run("reject if message not pass validate basic", func() { + _, err := dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).CompleteSellOrder(s.ctx, &dymnstypes.MsgCompleteSellOrder{ + AssetType: dymnstypes.TypeAlias, + }) + s.Require().ErrorContains(err, gerrc.ErrInvalidArgument.Error()) + }) + + creator_1_asOwner := testAddr(1).bech32() + creator_2_asBuyer := testAddr(2).bech32() + + rollApp_1_byOwner_asSrc := *newRollApp("rollapp_1-1").WithAlias("alias").WithOwner(creator_1_asOwner) + rollApp_2_byBuyer_asDst := *newRollApp("rollapp_2-2").WithOwner(creator_2_asBuyer) + + const originalBalanceCreator1 int64 = 1000 + const originalBalanceCreator2 int64 = 500 + const moduleOriginalBalance int64 = 100 + const minPrice int64 = 100 + + expiredSO := s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). + WithMinPrice(minPrice). + WithSellPrice(300). + WithAliasBid(creator_2_asBuyer, minPrice, rollApp_2_byBuyer_asDst.rollAppId). + WithExpiry(s.now.Add(-time.Second).Unix()). + Build() + + { + // prepare test data + + s.mintToAccount(creator_1_asOwner, originalBalanceCreator1) + s.mintToAccount(creator_2_asBuyer, originalBalanceCreator2) + s.mintToModuleAccount(moduleOriginalBalance) + + s.persistRollApp(rollApp_1_byOwner_asSrc) + s.persistRollApp(rollApp_2_byBuyer_asDst) + + err := s.dymNsKeeper.SetSellOrder(s.ctx, expiredSO) + s.Require().NoError(err) + + s.SaveCurrentContext() + } + + tests := []struct { + name string + participant string + preRunFunc func(s *KeeperTestSuite) + wantErr bool + wantErrContains string + postRunFunc func(s *KeeperTestSuite) + }{ + { + name: "pass - can complete sell order, by owner, expired, with bid", + participant: creator_1_asOwner, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + noActiveSO(). + LinkedToRollApp(rollApp_2_byBuyer_asDst.rollAppId) + }, + }, + { + name: "pass - can complete sell order, by buyer, expired, with bid", + participant: creator_2_asBuyer, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + noActiveSO(). + LinkedToRollApp(rollApp_2_byBuyer_asDst.rollAppId) + }, + }, + { + name: "pass - balance changed on success", + participant: creator_1_asOwner, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.Equal( + originalBalanceCreator1+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(creator_1_asOwner), + "owner should receive the bid amount", + ) + s.Equal( + originalBalanceCreator2, + s.balance(creator_2_asBuyer), + "buyer should not be charged", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "module account should be charged", + ) + }, + }, + { + name: "pass - after completed, alias linked to new RollApp", + participant: creator_2_asBuyer, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + LinkedToRollApp(rollApp_2_byBuyer_asDst.rollAppId) + }, + }, + { + name: "pass - after completed, reverse resolution should be updated", + participant: creator_2_asBuyer, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireRollApp(rollApp_1_byOwner_asSrc.rollAppId). + HasNoAlias() + + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + LinkedToRollApp(rollApp_2_byBuyer_asDst.rollAppId) + }, + }, + { + name: "pass - after completed, SO will be deleted", + participant: creator_2_asBuyer, + preRunFunc: nil, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireAlias(rollApp_1_byOwner_asSrc.alias).noActiveSO() + }, + }, + { + name: "pass - will refund when trading was disabled, requested by owner", + participant: creator_1_asOwner, + preRunFunc: func(s *KeeperTestSuite) { + s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { + p.Misc.EnableTradingAlias = false + return p + }) + }, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + noActiveSO(). + LinkedToRollApp(rollApp_1_byOwner_asSrc.rollAppId) + + s.Equal( + originalBalanceCreator1, + s.balance(creator_1_asOwner), + "owner should not receive the bid amount", + ) + s.Equal( + originalBalanceCreator2+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(creator_2_asBuyer), + "buyer should get the refund amount", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "refund amount should be subtracted from module account", + ) + }, + }, + { + name: "pass - will refund when trading was disabled, requested by buyer", + participant: creator_2_asBuyer, + preRunFunc: func(s *KeeperTestSuite) { + s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { + p.Misc.EnableTradingAlias = false + return p + }) + }, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + noActiveSO(). + LinkedToRollApp(rollApp_1_byOwner_asSrc.rollAppId) + + s.Equal( + originalBalanceCreator1, + s.balance(creator_1_asOwner), + "owner should not receive the bid amount", + ) + s.Equal( + originalBalanceCreator2+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(creator_2_asBuyer), + "buyer should get the refund amount", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "refund amount should be subtracted from module account", + ) + }, + }, + { + name: "pass - will refund when alias was prohibited trading, requested by buyer", + participant: creator_2_asBuyer, + preRunFunc: func(s *KeeperTestSuite) { + // put the alias into module params, so it will be prohibited to trade + s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { + p.Chains.AliasesOfChainIds = []dymnstypes.AliasesOfChainId{ + { + ChainId: "pseudo", + Aliases: []string{expiredSO.AssetId}, + }, + } + return p + }) + }, + wantErr: false, + postRunFunc: func(s *KeeperTestSuite) { + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + noActiveSO(). + LinkedToRollApp(rollApp_1_byOwner_asSrc.rollAppId) + + s.Equal( + originalBalanceCreator1, + s.balance(creator_1_asOwner), + "owner should not receive the bid amount", + ) + s.Equal( + originalBalanceCreator2+expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(creator_2_asBuyer), + "buyer should get the refund amount", + ) + s.Equal( + moduleOriginalBalance-expiredSO.HighestBid.Price.Amount.Int64(), + s.balance(dymNsModuleAccAddr.String()), + "refund amount should be subtracted from module account", + ) + }, + }, + { + name: "fail - when failed to refund, keep as is", + participant: creator_2_asBuyer, + preRunFunc: func(s *KeeperTestSuite) { + // increase the highest bid amount, module balance will not be enough to refund + { + existingSO := s.dymNsKeeper.GetSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + s.Require().NotNil(existingSO) + + existingSO.HighestBid.Price.Amount = s.coin(moduleOriginalBalance + 1).Amount + err := s.dymNsKeeper.SetSellOrder(s.ctx, *existingSO) + s.Require().NoError(err) + } + + // disable trading to toggle refund logic + s.updateModuleParams(func(p dymnstypes.Params) dymnstypes.Params { + p.Misc.EnableTradingAlias = false + return p + }) + }, + wantErr: true, + wantErrContains: "insufficient", + postRunFunc: func(s *KeeperTestSuite) { + s.Equal(originalBalanceCreator1, s.balance(creator_1_asOwner)) + s.Equal(originalBalanceCreator2, s.balance(creator_2_asBuyer)) + s.Equal(moduleOriginalBalance, s.balance(dymNsModuleAccAddr.String())) + + s.requireAlias(rollApp_1_byOwner_asSrc.alias). + mustHaveActiveSO(). + LinkedToRollApp(rollApp_1_byOwner_asSrc.rollAppId) + }, + }, + { + name: "fail - non-participant cannot complete", + participant: testAddr(0).bech32(), + wantErr: true, + wantErrContains: gerrc.ErrPermissionDenied.Error(), + }, + { + name: "fail - can not complete when no bid placed", + participant: creator_1_asOwner, + preRunFunc: func(s *KeeperTestSuite) { + existingSO := s.dymNsKeeper.GetSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + s.Require().NotNil(existingSO) + + existingSO.HighestBid = nil + err := s.dymNsKeeper.SetSellOrder(s.ctx, *existingSO) + s.Require().NoError(err) + }, + wantErr: true, + wantErrContains: "no bid placed on the Sell-Order", + }, + { + name: "fail - can not complete when SO not yet expired", + participant: creator_2_asBuyer, + preRunFunc: func(s *KeeperTestSuite) { + existingSO := s.dymNsKeeper.GetSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + s.Require().NotNil(existingSO) + + existingSO.ExpireAt = s.now.Add(time.Second).Unix() + err := s.dymNsKeeper.SetSellOrder(s.ctx, *existingSO) + s.Require().NoError(err) + }, + wantErr: true, + wantErrContains: "Sell-Order not yet completed", + }, + { + name: "fail - can not complete when alias no longer linking to any RollApp", + participant: creator_2_asBuyer, + preRunFunc: func(s *KeeperTestSuite) { + err := s.dymNsKeeper.RemoveAliasFromRollAppId(s.ctx, rollApp_1_byOwner_asSrc.rollAppId, expiredSO.AssetId) + s.Require().NoError(err) + }, + wantErr: true, + wantErrContains: "alias is not in-use", + }, + { + name: "fail - can not complete when the RollApp which alias was linked to, does not exists (deleted)", + participant: creator_2_asBuyer, + preRunFunc: func(s *KeeperTestSuite) { + s.rollAppKeeper.RemoveRollapp(s.ctx, rollApp_1_byOwner_asSrc.rollAppId) + }, + wantErr: true, + wantErrContains: "not found", + }, + { + name: "fail - can not complete when the RollApp which should be linked to, does not exists (deleted)", + participant: creator_2_asBuyer, + preRunFunc: func(s *KeeperTestSuite) { + existingSO := s.dymNsKeeper.GetSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + s.Require().NotNil(existingSO) + + s.rollAppKeeper.RemoveRollapp(s.ctx, existingSO.HighestBid.Params[0]) + }, + wantErr: true, + wantErrContains: "destination Roll-App does not exists", + }, + { + name: "fail - can not complete when SO does not exists", + participant: creator_1_asOwner, + preRunFunc: func(s *KeeperTestSuite) { + s.dymNsKeeper.DeleteSellOrder(s.ctx, expiredSO.AssetId, expiredSO.AssetType) + }, + wantErr: true, + wantErrContains: gerrc.ErrNotFound.Error(), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + s.RefreshContext() + + if tt.preRunFunc != nil { + tt.preRunFunc(s) + } + + // test + + resp, errCompleteSO := dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).CompleteSellOrder(s.ctx, &dymnstypes.MsgCompleteSellOrder{ + AssetId: expiredSO.AssetId, + AssetType: dymnstypes.TypeAlias, + Participant: tt.participant, + }) + + defer func() { + if tt.postRunFunc != nil { + tt.postRunFunc(s) + } + }() + + if tt.wantErr { + s.Require().ErrorContains(errCompleteSO, tt.wantErrContains) + s.Nil(resp) + + s.Less( + s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasCompleteSellOrder, + "should not consume params gas on failed operation", + ) + + return + } + + s.Require().NoError(errCompleteSO) + s.NotNil(resp) + s.GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasCompleteSellOrder, + "should consume params gas", + ) + }) + } + + s.Run("independently charge gas", func() { + s.RefreshContext() + + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + + _, err := dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).CompleteSellOrder(s.ctx, &dymnstypes.MsgCompleteSellOrder{ + AssetId: rollApp_1_byOwner_asSrc.alias, + AssetType: dymnstypes.TypeAlias, + Participant: creator_2_asBuyer, + }) + s.Require().NoError(err) + + s.Require().GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), 100_000_000+dymnstypes.OpGasCompleteSellOrder, + "gas consumption should be stacked", + ) + }) +} diff --git a/x/dymns/keeper/msg_server_place_buy_order.go b/x/dymns/keeper/msg_server_place_buy_order.go index d0702343b..145d7dc86 100644 --- a/x/dymns/keeper/msg_server_place_buy_order.go +++ b/x/dymns/keeper/msg_server_place_buy_order.go @@ -14,6 +14,7 @@ import ( // handles creating an offer to buy a Dym-Name/Alias, performed by the buyer. func (k msgServer) PlaceBuyOrder(goCtx context.Context, msg *dymnstypes.MsgPlaceBuyOrder) (*dymnstypes.MsgPlaceBuyOrderResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() if err := msg.ValidateBasic(); err != nil { return nil, err @@ -45,7 +46,7 @@ func (k msgServer) PlaceBuyOrder(goCtx context.Context, msg *dymnstypes.MsgPlace } else { minimumTxGasRequired = dymnstypes.OpGasPutBuyOrder } - consumeMinimumGas(ctx, minimumTxGasRequired, "PlaceBuyOrder") + consumeMinimumGas(ctx, minimumTxGasRequired, originalConsumedGas, "PlaceBuyOrder") return resp, nil } @@ -278,7 +279,7 @@ func (k msgServer) validatePlaceBuyOrderWithAssetTypeAlias( } if k.IsAliasPresentsInParamsAsAliasOrChainId(ctx, msg.AssetId) { - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. err = errorsmod.Wrapf(gerrc.ErrPermissionDenied, "prohibited to trade aliases which is reserved for chain-id or alias in module params: %s", msg.AssetId, ) diff --git a/x/dymns/keeper/msg_server_place_buy_order_test.go b/x/dymns/keeper/msg_server_place_buy_order_test.go index 93ce0e397..39392ed21 100644 --- a/x/dymns/keeper/msg_server_place_buy_order_test.go +++ b/x/dymns/keeper/msg_server_place_buy_order_test.go @@ -797,6 +797,32 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceBuyOrder_DymName() { s.Equal([]string{"102", "103"}, orderIds.OrderIds) }, }, + { + name: "pass - independently charge gas", + existingDymName: dymName, + existingOffer: nil, + dymName: dymName.Name, + buyer: buyerA, + offer: minOfferPriceCoin, + existingBuyOrderId: "", + originalModuleBalance: sdk.NewInt(5), + originalBuyerBalance: minOfferPriceCoin.Amount.AddRaw(2), + preRunSetupFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + wantErr: false, + wantBuyOrderId: "101", + wantLaterOffer: &dymnstypes.BuyOrder{ + Id: "101", + AssetId: dymName.Name, + AssetType: dymnstypes.TypeName, + Buyer: buyerA, + OfferPrice: minOfferPriceCoin, + }, + wantLaterModuleBalance: minOfferPriceCoin.Amount.AddRaw(5), + wantLaterBuyerBalance: sdk.NewInt(2), + wantMinConsumeGas: 100_000_000 + dymnstypes.OpGasPutBuyOrder, + }, } for _, tt := range tests { s.Run(tt.name, func() { @@ -1856,6 +1882,34 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceBuyOrder_Alias() { s.Equal([]string{"202", "203"}, orderIds.OrderIds) }, }, + { + name: "pass - independently charge gas", + existingRollApps: []rollapp{rollApp_1_by1_asSrc, rollApp_2_by2_asDest}, + existingOffer: nil, + alias: rollApp_1_by1_asSrc.alias, + buyer: rollApp_2_by2_asDest.creator, + dstRollAppId: rollApp_2_by2_asDest.rollAppID, + offer: minOfferPriceCoin, + existingBuyOrderId: "", + originalModuleBalance: sdk.NewInt(5), + originalBuyerBalance: minOfferPriceCoin.Amount.AddRaw(2), + preRunSetupFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + wantErr: false, + wantBuyOrderId: "201", + wantLaterOffer: &dymnstypes.BuyOrder{ + Id: "201", + AssetId: rollApp_1_by1_asSrc.alias, + AssetType: dymnstypes.TypeAlias, + Params: []string{rollApp_2_by2_asDest.rollAppID}, + Buyer: rollApp_2_by2_asDest.creator, + OfferPrice: minOfferPriceCoin, + }, + wantLaterModuleBalance: minOfferPriceCoin.Amount.AddRaw(5), + wantLaterBuyerBalance: sdk.NewInt(2), + wantMinConsumeGas: 100_000_000 + dymnstypes.OpGasPutBuyOrder, + }, } for _, tt := range tests { s.Run(tt.name, func() { diff --git a/x/dymns/keeper/msg_server_place_sell_order.go b/x/dymns/keeper/msg_server_place_sell_order.go index 8f102cfcd..becbfbd12 100644 --- a/x/dymns/keeper/msg_server_place_sell_order.go +++ b/x/dymns/keeper/msg_server_place_sell_order.go @@ -14,6 +14,7 @@ import ( // handles creating a Sell-Order that advertise a Dym-Name/Alias is for sale, performed by the owner. func (k msgServer) PlaceSellOrder(goCtx context.Context, msg *dymnstypes.MsgPlaceSellOrder) (*dymnstypes.MsgPlaceSellOrderResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() if err := msg.ValidateBasic(); err != nil { return nil, err @@ -40,7 +41,7 @@ func (k msgServer) PlaceSellOrder(goCtx context.Context, msg *dymnstypes.MsgPlac // Charge protocol fee. // The protocol fee mechanism is used to prevent spamming to the network. - consumeMinimumGas(ctx, dymnstypes.OpGasPlaceSellOrder, "PlaceSellOrder") + consumeMinimumGas(ctx, dymnstypes.OpGasPlaceSellOrder, originalConsumedGas, "PlaceSellOrder") return resp, nil } @@ -76,12 +77,6 @@ func (k msgServer) processPlaceSellOrderWithAssetTypeDymName( return nil, err } - aSoe := k.GetActiveSellOrdersExpiration(ctx, so.AssetType) - aSoe.Add(so.AssetId, so.ExpireAt) - if err := k.SetActiveSellOrdersExpiration(ctx, aSoe, so.AssetType); err != nil { - return nil, err - } - return &dymnstypes.MsgPlaceSellOrderResponse{}, nil } @@ -149,12 +144,6 @@ func (k msgServer) processPlaceSellOrderWithAssetTypeAlias( return nil, err } - aSoe := k.GetActiveSellOrdersExpiration(ctx, so.AssetType) - aSoe.Add(so.AssetId, so.ExpireAt) - if err := k.SetActiveSellOrdersExpiration(ctx, aSoe, so.AssetType); err != nil { - return nil, err - } - return &dymnstypes.MsgPlaceSellOrderResponse{}, nil } @@ -166,7 +155,7 @@ func (k msgServer) validatePlaceSellOrderWithAssetTypeAlias( alias := msg.AssetId if k.IsAliasPresentsInParamsAsAliasOrChainId(ctx, msg.AssetId) { - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. return errorsmod.Wrapf(gerrc.ErrPermissionDenied, "prohibited to trade aliases which is reserved for chain-id or alias in module params: %s", msg.AssetId, ) diff --git a/x/dymns/keeper/msg_server_place_sell_order_test.go b/x/dymns/keeper/msg_server_place_sell_order_test.go index 262629445..6ba44ca1b 100644 --- a/x/dymns/keeper/msg_server_place_sell_order_test.go +++ b/x/dymns/keeper/msg_server_place_sell_order_test.go @@ -52,6 +52,7 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_DymName() { preRunSetup func(*KeeperTestSuite) wantErr bool wantErrContains string + afterRunFunc func(*KeeperTestSuite) }{ { name: "fail - Dym-Name does not exists", @@ -186,6 +187,21 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_DymName() { wantErr: true, wantErrContains: "trading of Dym-Name is disabled", }, + { + name: "pass - independently charge gas", + dymNameExpiryOffsetDays: 9999, + minPrice: coin100, + sellPrice: nil, + preRunSetup: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + afterRunFunc: func(s *KeeperTestSuite) { + s.Require().GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), 100_000_000+dymnstypes.OpGasPlaceSellOrder, + "gas consumption should be stacked", + ) + }, + }, } for _, tt := range tests { s.Run(tt.name, func() { @@ -251,6 +267,12 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_DymName() { }, *laterDymName, "Dym-Name record should not be changed in any case") }() + defer func() { + if tt.afterRunFunc != nil { + tt.afterRunFunc(s) + } + }() + if tt.wantErr { s.Require().NotEmpty(tt.wantErrContains, "mis-configured test case") s.Require().Error(err) @@ -291,27 +313,14 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_DymName() { expectedSo.SellPrice = nil } - s.Require().Nil(so.HighestBid, "highest bid should not be set") + s.Nil(so.HighestBid, "highest bid should not be set") - s.Require().Equal(expectedSo, *so) + s.Equal(expectedSo, *so) - s.Require().GreaterOrEqual( + s.GreaterOrEqual( s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasPlaceSellOrder, "should consume params gas", ) - - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeName) - - var found bool - for _, record := range aSoe.Records { - if record.AssetId == name { - found = true - s.Require().Equal(expectedSo.ExpireAt, record.ExpireAt) - break - } - } - - s.Require().True(found) }) } } @@ -349,6 +358,7 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_Alias() { preRunSetup func(*KeeperTestSuite) wantErr bool wantErrContains string + afterRunFunc func(*KeeperTestSuite) }{ { name: "fail - alias does not exists", @@ -480,6 +490,20 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_Alias() { wantErr: true, wantErrContains: "trading of Alias is disabled", }, + { + name: "pass - independently charge gas", + minPrice: coin100, + sellPrice: nil, + preRunSetup: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + afterRunFunc: func(s *KeeperTestSuite) { + s.Require().GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), 100_000_000+dymnstypes.OpGasPlaceSellOrder, + "gas consumption should be stacked", + ) + }, + }, } for _, tt := range tests { s.Run(tt.name, func() { @@ -533,6 +557,12 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_Alias() { } }() + defer func() { + if tt.afterRunFunc != nil { + tt.afterRunFunc(s) + } + }() + if tt.wantErr { s.Require().NotEmpty(tt.wantErrContains, "mis-configured test case") s.Require().Error(err) @@ -573,27 +603,14 @@ func (s *KeeperTestSuite) Test_msgServer_PlaceSellOrder_Alias() { expectedSo.SellPrice = nil } - s.Require().Nil(so.HighestBid, "highest bid should not be set") + s.Nil(so.HighestBid, "highest bid should not be set") - s.Require().Equal(expectedSo, *so) + s.Equal(expectedSo, *so) - s.Require().GreaterOrEqual( + s.GreaterOrEqual( s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasPlaceSellOrder, "should consume params gas", ) - - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeAlias) - - var found bool - for _, record := range aSoe.Records { - if record.AssetId == alias { - found = true - s.Require().Equal(expectedSo.ExpireAt, record.ExpireAt) - break - } - } - - s.Require().True(found) }) } } diff --git a/x/dymns/keeper/msg_server_purchase_order.go b/x/dymns/keeper/msg_server_purchase_order.go index 35fcfd2cb..c316dfc5a 100644 --- a/x/dymns/keeper/msg_server_purchase_order.go +++ b/x/dymns/keeper/msg_server_purchase_order.go @@ -14,11 +14,13 @@ import ( // handles purchasing a Dym-Name/Alias from a Sell-Order, performed by the buyer. func (k msgServer) PurchaseOrder(goCtx context.Context, msg *dymnstypes.MsgPurchaseOrder) (*dymnstypes.MsgPurchaseOrderResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() if err := msg.ValidateBasic(); err != nil { return nil, err } + priceParams := k.PriceParams(ctx) miscParams := k.MiscParams(ctx) var resp *dymnstypes.MsgPurchaseOrderResponse @@ -27,9 +29,9 @@ func (k msgServer) PurchaseOrder(goCtx context.Context, msg *dymnstypes.MsgPurch // process the purchase order based on the asset type if msg.AssetType == dymnstypes.TypeName { - resp, err = k.processPurchaseOrderWithAssetTypeDymName(ctx, msg, miscParams) + resp, err = k.processPurchaseOrderWithAssetTypeDymName(ctx, msg, priceParams, miscParams) } else if msg.AssetType == dymnstypes.TypeAlias { - resp, err = k.processPurchaseOrderWithAssetTypeAlias(ctx, msg, miscParams) + resp, err = k.processPurchaseOrderWithAssetTypeAlias(ctx, msg, priceParams, miscParams) } else { err = errorsmod.Wrapf(gerrc.ErrInvalidArgument, "invalid asset type: %s", msg.AssetType) } @@ -38,21 +40,18 @@ func (k msgServer) PurchaseOrder(goCtx context.Context, msg *dymnstypes.MsgPurch } // charge protocol fee - consumeMinimumGas(ctx, dymnstypes.OpGasPlaceBidOnSellOrder, "PurchaseOrder") + consumeMinimumGas(ctx, dymnstypes.OpGasPlaceBidOnSellOrder, originalConsumedGas, "PurchaseOrder") return resp, nil } // processPurchaseOrderWithAssetTypeDymName handles the message handled by PurchaseOrder, type Dym-Name. -func (k msgServer) processPurchaseOrderWithAssetTypeDymName( - ctx sdk.Context, - msg *dymnstypes.MsgPurchaseOrder, miscParams dymnstypes.MiscParams, -) (*dymnstypes.MsgPurchaseOrderResponse, error) { +func (k msgServer) processPurchaseOrderWithAssetTypeDymName(ctx sdk.Context, msg *dymnstypes.MsgPurchaseOrder, priceParams dymnstypes.PriceParams, miscParams dymnstypes.MiscParams) (*dymnstypes.MsgPurchaseOrderResponse, error) { if !miscParams.EnableTradingName { return nil, errorsmod.Wrapf(gerrc.ErrFailedPrecondition, "trading of Dym-Name is disabled") } - dymName, so, err := k.validatePurchaseOrderWithAssetTypeDymName(ctx, msg) + dymName, so, err := k.validatePurchaseOrderWithAssetTypeDymName(ctx, msg, priceParams) if err != nil { return nil, err } @@ -98,7 +97,7 @@ func (k msgServer) processPurchaseOrderWithAssetTypeDymName( } // validatePurchaseOrderWithAssetTypeDymName handles validation for the message handled by PurchaseOrder, type Dym-Name. -func (k msgServer) validatePurchaseOrderWithAssetTypeDymName(ctx sdk.Context, msg *dymnstypes.MsgPurchaseOrder) (*dymnstypes.DymName, *dymnstypes.SellOrder, error) { +func (k msgServer) validatePurchaseOrderWithAssetTypeDymName(ctx sdk.Context, msg *dymnstypes.MsgPurchaseOrder, priceParams dymnstypes.PriceParams) (*dymnstypes.DymName, *dymnstypes.SellOrder, error) { dymName := k.GetDymName(ctx, msg.AssetId) if dymName == nil { return nil, nil, errorsmod.Wrapf(gerrc.ErrNotFound, "Dym-Name: %s", msg.AssetId) @@ -112,54 +111,21 @@ func (k msgServer) validatePurchaseOrderWithAssetTypeDymName(ctx sdk.Context, ms if so == nil { return nil, nil, errorsmod.Wrapf(gerrc.ErrNotFound, "Sell-Order: %s", msg.AssetId) } - - if so.HasExpiredAtCtx(ctx) { - return nil, nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot purchase an expired order") - } - - if so.HasFinishedAtCtx(ctx) { - return nil, nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot purchase a completed order") - } - - if msg.Offer.Denom != so.MinPrice.Denom { - return nil, nil, errorsmod.Wrapf(gerrc.ErrInvalidArgument, - "offer denom does not match the order denom: %s != %s", - msg.Offer.Denom, so.MinPrice.Denom, - ) - } - - if msg.Offer.IsLT(so.MinPrice) { - return nil, nil, errorsmod.Wrap(gerrc.ErrInvalidArgument, "offer is lower than minimum price") - } - - if so.HasSetSellPrice() { - if !msg.Offer.IsLTE(*so.SellPrice) { // overpaid protection - return nil, nil, errorsmod.Wrap(gerrc.ErrInvalidArgument, "offer is higher than sell price") - } - } - - if so.HighestBid != nil { - if msg.Offer.IsLTE(so.HighestBid.Price) { - return nil, nil, errorsmod.Wrap( - gerrc.ErrInvalidArgument, - "new offer must be higher than current highest bid", - ) - } + err := k.genericValidateSellOrderOfPurchaseOrder(ctx, msg, *so, priceParams) + if err != nil { + return nil, nil, err } return dymName, so, nil } // processPurchaseOrderWithAssetTypeAlias handles the message handled by PurchaseOrder, type Alias. -func (k msgServer) processPurchaseOrderWithAssetTypeAlias( - ctx sdk.Context, - msg *dymnstypes.MsgPurchaseOrder, miscParams dymnstypes.MiscParams, -) (*dymnstypes.MsgPurchaseOrderResponse, error) { +func (k msgServer) processPurchaseOrderWithAssetTypeAlias(ctx sdk.Context, msg *dymnstypes.MsgPurchaseOrder, priceParams dymnstypes.PriceParams, miscParams dymnstypes.MiscParams) (*dymnstypes.MsgPurchaseOrderResponse, error) { if !miscParams.EnableTradingAlias { return nil, errorsmod.Wrapf(gerrc.ErrFailedPrecondition, "trading of Alias is disabled") } - so, err := k.validatePurchaseOrderWithAssetTypeAlias(ctx, msg) + so, err := k.validatePurchaseOrderWithAssetTypeAlias(ctx, msg, priceParams) if err != nil { return nil, err } @@ -204,7 +170,7 @@ func (k msgServer) processPurchaseOrderWithAssetTypeAlias( } // validatePurchaseOrderWithAssetTypeAlias handles validation for the message handled by PurchaseOrder, type Alias. -func (k msgServer) validatePurchaseOrderWithAssetTypeAlias(ctx sdk.Context, msg *dymnstypes.MsgPurchaseOrder) (*dymnstypes.SellOrder, error) { +func (k msgServer) validatePurchaseOrderWithAssetTypeAlias(ctx sdk.Context, msg *dymnstypes.MsgPurchaseOrder, priceParams dymnstypes.PriceParams) (*dymnstypes.SellOrder, error) { destinationRollAppId := msg.Params[0] if !k.IsRollAppId(ctx, destinationRollAppId) { @@ -225,7 +191,7 @@ func (k msgServer) validatePurchaseOrderWithAssetTypeAlias(ctx sdk.Context, msg } if k.IsAliasPresentsInParamsAsAliasOrChainId(ctx, msg.AssetId) { - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. return nil, errorsmod.Wrapf(gerrc.ErrPermissionDenied, "prohibited to trade aliases which is reserved for chain-id or alias in module params: %s", msg.AssetId, ) @@ -235,40 +201,66 @@ func (k msgServer) validatePurchaseOrderWithAssetTypeAlias(ctx sdk.Context, msg if so == nil { return nil, errorsmod.Wrapf(gerrc.ErrNotFound, "Sell-Order: %s", msg.AssetId) } + err := k.genericValidateSellOrderOfPurchaseOrder(ctx, msg, *so, priceParams) + if err != nil { + return nil, err + } + return so, nil +} + +// genericValidateSellOrderOfPurchaseOrder is a helper function to validate the purchase order request. +func (k msgServer) genericValidateSellOrderOfPurchaseOrder(ctx sdk.Context, msg *dymnstypes.MsgPurchaseOrder, so dymnstypes.SellOrder, priceParams dymnstypes.PriceParams) error { if so.HasExpiredAtCtx(ctx) { - return nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot purchase an expired order") + return errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot purchase an expired order") } if so.HasFinishedAtCtx(ctx) { - return nil, errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot purchase a completed order") + return errorsmod.Wrap(gerrc.ErrFailedPrecondition, "cannot purchase a completed order") } if msg.Offer.Denom != so.MinPrice.Denom { - return nil, errorsmod.Wrapf(gerrc.ErrInvalidArgument, + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "offer denom does not match the order denom: %s != %s", msg.Offer.Denom, so.MinPrice.Denom, ) } if msg.Offer.IsLT(so.MinPrice) { - return nil, errorsmod.Wrap(gerrc.ErrInvalidArgument, "offer is lower than minimum price") + return errorsmod.Wrap(gerrc.ErrInvalidArgument, "offer is lower than minimum price") } if so.HasSetSellPrice() { if !msg.Offer.IsLTE(*so.SellPrice) { // overpaid protection - return nil, errorsmod.Wrap(gerrc.ErrInvalidArgument, "offer is higher than sell price") + return errorsmod.Wrap(gerrc.ErrInvalidArgument, "offer is higher than sell price") } } if so.HighestBid != nil { if msg.Offer.IsLTE(so.HighestBid.Price) { - return nil, errorsmod.Wrap( + return errorsmod.Wrap( gerrc.ErrInvalidArgument, "new offer must be higher than current highest bid", ) } + + if priceParams.MinBidIncrementPercent > 0 { + minimumIncrement := so.HighestBid.Price.Amount.MulRaw(int64(priceParams.MinBidIncrementPercent)).QuoRaw(100) + if minimumIncrement.IsPositive() { + wantMinimumBid := so.HighestBid.Price.AddAmount(minimumIncrement) + if so.HasSetSellPrice() && so.SellPrice.IsLT(wantMinimumBid) { + // skip the validation + } else { + if msg.Offer.IsLT(wantMinimumBid) { + return errorsmod.Wrapf( + gerrc.ErrInvalidArgument, + "new offer must be higher than current highest bid at least %d percent, want minimum offer: %s", priceParams.MinBidIncrementPercent, wantMinimumBid, + ) + } + } + } + } } - return so, nil + return nil } diff --git a/x/dymns/keeper/msg_server_purchase_order_test.go b/x/dymns/keeper/msg_server_purchase_order_test.go index fa2911efd..a34accbd1 100644 --- a/x/dymns/keeper/msg_server_purchase_order_test.go +++ b/x/dymns/keeper/msg_server_purchase_order_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "fmt" + "time" "github.com/dymensionxyz/sdk-utils/utils/uptr" @@ -50,23 +51,24 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_DymName() { const previousBidderOriginalBalance int64 = 400 const minPrice int64 = 100 tests := []struct { - name string - withoutDymName bool - withoutSellOrder bool - expiredSellOrder bool - sellPrice int64 - previousBid int64 - skipPreMintModuleAccount bool - overrideBuyerOriginalBalance int64 - customBuyer string - newBid int64 - customBidDenom string - wantOwnershipChanged bool - wantErr bool - wantErrContains string - wantOwnerBalanceLater int64 - wantBuyerBalanceLater int64 - wantPreviousBidderBalanceLater int64 + name string + withoutDymName bool + withoutSellOrder bool + expiredSellOrder bool + sellPrice int64 + previousBid int64 + skipPreMintModuleAccount bool + overrideBuyerOriginalBalance int64 + customBuyer string + newBid int64 + customBidDenom string + customMinimumBidIncrementPercent uint32 + wantOwnershipChanged bool + wantErr bool + wantErrContains string + wantOwnerBalanceLater int64 + wantBuyerBalanceLater int64 + wantPreviousBidderBalanceLater int64 }{ { name: "fail - Dym-Name does not exists, SO does not exists", @@ -203,6 +205,19 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_DymName() { wantBuyerBalanceLater: buyerOriginalBalance, wantPreviousBidderBalanceLater: previousBidderOriginalBalance, }, + { + name: "fail - purchase order, not expired, fail because minimum increment not met", + expiredSellOrder: false, + sellPrice: 300, + previousBid: 200, + newBid: 201, + customMinimumBidIncrementPercent: 10, + wantErr: true, + wantErrContains: "new offer must be higher than current highest bid at least", + wantOwnerBalanceLater: ownerOriginalBalance, + wantBuyerBalanceLater: buyerOriginalBalance, + wantPreviousBidderBalanceLater: previousBidderOriginalBalance, + }, { name: "fail - purchase a completed order, expired, bid equals to previous bid", expiredSellOrder: true, @@ -370,6 +385,18 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_DymName() { wantBuyerBalanceLater: buyerOriginalBalance - (300 - 1), wantPreviousBidderBalanceLater: previousBidderOriginalBalance + minPrice, // refund }, + { + name: "pass - place bid, ignore minimum threshold if wanted amount greater than sell price", + expiredSellOrder: false, + sellPrice: 300, + previousBid: 300 - 2, + newBid: 300 - 1, + customMinimumBidIncrementPercent: 10, + wantOwnershipChanged: false, + wantOwnerBalanceLater: ownerOriginalBalance, + wantBuyerBalanceLater: buyerOriginalBalance - (300 - 1), + wantPreviousBidderBalanceLater: previousBidderOriginalBalance + 300 - 2, // refund + }, { name: "pass - place bid, greater than previous bid, equals sell price, transfer ownership", expiredSellOrder: false, @@ -396,6 +423,13 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_DymName() { s.Run(tt.name, func() { s.RefreshContext() + if tt.customMinimumBidIncrementPercent > 0 { + s.updateModuleParams(func(d dymnstypes.Params) dymnstypes.Params { + d.Price.MinBidIncrementPercent = tt.customMinimumBidIncrementPercent + return d + }) + } + useOwnerOriginalBalance := ownerOriginalBalance useBuyerOriginalBalance := buyerOriginalBalance if tt.overrideBuyerOriginalBalance > 0 { @@ -567,6 +601,35 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_DymName() { }) s.Require().ErrorContains(err, "unmet precondition") }) + + s.Run("independently charge gas", func() { + s.RefreshContext() + + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + + s.setDymNameWithFunctionsAfter(dymName) + so := dymnstypes.SellOrder{ + AssetId: dymName.Name, + AssetType: dymnstypes.TypeName, + MinPrice: s.coin(minPrice), + ExpireAt: s.now.Add(time.Second).Unix(), + } + err := s.dymNsKeeper.SetSellOrder(s.ctx, so) + s.Require().NoError(err) + s.mintToAccount(buyerA, minPrice) + + _, err = dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).PurchaseOrder(s.ctx, &dymnstypes.MsgPurchaseOrder{ + AssetId: "my-name", + AssetType: dymnstypes.TypeName, + Offer: s.coin(minPrice), + Buyer: buyerA, + }) + s.Require().NoError(err) + s.Require().GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), 100_000_000+dymnstypes.OpGasPlaceBidOnSellOrder, + "should consume params gas", + ) + }) } //goland:noinspection GoSnakeCaseUsage @@ -902,6 +965,27 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_Alias() { wantErr: true, wantErrContains: "new offer must be higher than current highest bid", }, + { + name: "fail - purchase order, not expired, fail because minimum bid increment not met", + rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, rollApp_3_byAnotherBuyer_asDst}, + sellOrder: s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). + WithMinPrice(minPrice). + WithSellPrice(300). + WithAliasBid(creator_3_asAnotherBuyer, 200, rollApp_3_byAnotherBuyer_asDst.rollAppId). + BuildP(), + msg: msg( + creator_2_asBuyer, 201, + rollApp_1_byOwner_asSrc.alias, rollApp_2_byBuyer_asDst.rollAppId, + ), + preRunFunc: func(s *KeeperTestSuite) { + s.updateModuleParams(func(d dymnstypes.Params) dymnstypes.Params { + d.Price.MinBidIncrementPercent = 10 + return d + }) + }, + wantErr: true, + wantErrContains: "new offer must be higher than current highest bid at least", + }, { name: "fail - purchase a completed order, expired, bid equals to previous bid", rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst, rollApp_3_byAnotherBuyer_asDst}, @@ -1087,6 +1171,30 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_Alias() { wantLaterBalanceCreator2: originalBalanceCreator2 - 250, // charge bid wantLaterBalanceCreator3: originalBalanceCreator3 + minPrice, // refund }, + { + name: "pass - place bid, ignore minimum threshold because wanted greater than sell price, new bid under sell price", + rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst}, + sellOrder: s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). + WithMinPrice(minPrice). + WithSellPrice(300). + WithAliasBid(creator_3_asAnotherBuyer, 300-2, rollApp_3_byAnotherBuyer_asDst.rollAppId). + BuildP(), + msg: msg( + creator_2_asBuyer, 300-1, + rollApp_1_byOwner_asSrc.alias, rollApp_2_byBuyer_asDst.rollAppId, + ), + preRunFunc: func(s *KeeperTestSuite) { + s.updateModuleParams(func(d dymnstypes.Params) dymnstypes.Params { + d.Price.MinBidIncrementPercent = 10 + return d + }) + }, + wantErr: false, + wantCompleted: false, + wantLaterBalanceCreator1: originalBalanceCreator1, + wantLaterBalanceCreator2: originalBalanceCreator2 - (300 - 1), // charge bid + wantLaterBalanceCreator3: originalBalanceCreator3 + (300 - 2), // refund + }, { name: "pass - place bid, greater than previous bid, equals sell price, transfer ownership", rollApps: []rollapp{rollApp_1_byOwner_asSrc, rollApp_2_byBuyer_asDst}, @@ -1184,6 +1292,7 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_Alias() { s.Zero(tt.wantLaterBalanceCreator2, "bad setup, won't check balance on error") s.Zero(tt.wantLaterBalanceCreator3, "bad setup, won't check balance on error") } else { + s.Require().NoError(errPurchaseName) s.NotNil(resp) s.GreaterOrEqual( s.ctx.GasMeter().GasConsumed(), dymnstypes.OpGasPlaceBidOnSellOrder, @@ -1243,4 +1352,34 @@ func (s *KeeperTestSuite) Test_msgServer_PurchaseOrder_Alias() { }) s.Require().ErrorContains(err, "trading of Alias is disabled") }) + + s.Run("independently charge gas", func() { + s.RefreshContext() + + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + + s.persistRollApp(rollApp_1_byOwner_asSrc) + s.persistRollApp(rollApp_2_byBuyer_asDst) + so := s.newAliasSellOrder(rollApp_1_byOwner_asSrc.alias). + WithMinPrice(minPrice). + WithSellPrice(300). + BuildP() + err := s.dymNsKeeper.SetSellOrder(s.ctx, *so) + s.Require().NoError(err) + s.mintToAccount(creator_2_asBuyer, 100) + + _, err = dymnskeeper.NewMsgServerImpl(s.dymNsKeeper).PurchaseOrder(s.ctx, &dymnstypes.MsgPurchaseOrder{ + AssetId: "alias", + AssetType: dymnstypes.TypeAlias, + Params: []string{rollApp_2_byBuyer_asDst.rollAppId}, + Offer: s.coin(minPrice), + Buyer: creator_2_asBuyer, + }) + s.Require().NoError(err) + + s.Require().GreaterOrEqual( + s.ctx.GasMeter().GasConsumed(), 100_000_000+dymnstypes.OpGasPlaceBidOnSellOrder, + "gas consumption should be stacked", + ) + }) } diff --git a/x/dymns/keeper/msg_server_register_name.go b/x/dymns/keeper/msg_server_register_name.go index a0117a9a3..9dd7ca6c0 100644 --- a/x/dymns/keeper/msg_server_register_name.go +++ b/x/dymns/keeper/msg_server_register_name.go @@ -24,15 +24,20 @@ func (k msgServer) RegisterName(goCtx context.Context, msg *dymnstypes.MsgRegist priceParams := k.PriceParams(ctx) - addDurationInSeconds := 86400 * 365 * msg.Duration + addDurationInSeconds := 86400 * // number of seconds per day + 365 * // number of days per year + msg.Duration // number of registration years firstYearPrice := priceParams.GetFirstYearDymNamePrice(msg.Name) var prunePreviousDymNameRecord bool + var ownershipChanged, configChanged bool var totalCost sdk.Coin if dymName == nil { // register new prunePreviousDymNameRecord = true + ownershipChanged = true + configChanged = true dymName = &dymnstypes.DymName{ Name: msg.Name, @@ -69,6 +74,8 @@ func (k msgServer) RegisterName(goCtx context.Context, msg *dymnstypes.MsgRegist } else { // extends prunePreviousDymNameRecord = false + ownershipChanged = false + configChanged = false // just add duration, no need to change any existing configuration dymName.ExpireAt += addDurationInSeconds @@ -88,6 +95,8 @@ func (k msgServer) RegisterName(goCtx context.Context, msg *dymnstypes.MsgRegist } else { // take over prunePreviousDymNameRecord = true + ownershipChanged = true + configChanged = true // existing configuration will be pruned dymName = &dymnstypes.DymName{ Name: msg.Name, @@ -146,12 +155,16 @@ func (k msgServer) RegisterName(goCtx context.Context, msg *dymnstypes.MsgRegist return nil, err } - if err := k.AfterDymNameOwnerChanged(ctx, dymName.Name); err != nil { - return nil, err + if ownershipChanged || prunePreviousDymNameRecord { + if err := k.AfterDymNameOwnerChanged(ctx, dymName.Name); err != nil { + return nil, err + } } - if err := k.AfterDymNameConfigChanged(ctx, dymName.Name); err != nil { - return nil, err + if configChanged || prunePreviousDymNameRecord { + if err := k.AfterDymNameConfigChanged(ctx, dymName.Name); err != nil { + return nil, err + } } ctx.EventManager().EmitEvent(sdk.NewEvent( diff --git a/x/dymns/keeper/msg_server_register_name_test.go b/x/dymns/keeper/msg_server_register_name_test.go index 1129f0e94..a4ec7e9ab 100644 --- a/x/dymns/keeper/msg_server_register_name_test.go +++ b/x/dymns/keeper/msg_server_register_name_test.go @@ -54,6 +54,9 @@ func (s *KeeperTestSuite) Test_msgServer_RegisterName() { }) const originalModuleBalance int64 = 88 + const soMinPrice int64 = 1 + const soSellPrice int64 = 2 + const soBidAmount int64 = 2 tests := []struct { name string @@ -71,6 +74,7 @@ func (s *KeeperTestSuite) Test_msgServer_RegisterName() { wantErrContains string wantLaterBalance int64 wantPruneSellOrder bool + offsetModuleBalLater int64 }{ { name: "pass - can register, new Dym-Name", @@ -427,8 +431,9 @@ func (s *KeeperTestSuite) Test_msgServer_RegisterName() { ExpireAt: s.now.Unix() + 86400*365*2, Configs: nil, }, - wantLaterBalance: 3, - wantPruneSellOrder: true, + wantLaterBalance: 3, + wantPruneSellOrder: true, + offsetModuleBalLater: -soBidAmount, // because refunding the highest bid of the active SO }, { name: "pass - when renew previously-owned expired Dym-Name, reset config, update contact if provided", @@ -454,8 +459,9 @@ func (s *KeeperTestSuite) Test_msgServer_RegisterName() { Configs: nil, Contact: "new-contact@example.com", }, - wantLaterBalance: 3, - wantPruneSellOrder: true, + wantLaterBalance: 3, + wantPruneSellOrder: true, + offsetModuleBalLater: -soBidAmount, // because refunding the highest bid of the active SO }, { name: "pass - can take over an expired Dym-Name after grace period has passed", @@ -566,16 +572,22 @@ func (s *KeeperTestSuite) Test_msgServer_RegisterName() { err := s.dymNsKeeper.SetDymName(s.ctx, *tt.existingDymName) s.Require().NoError(err) + err = s.dymNsKeeper.AfterDymNameOwnerChanged(s.ctx, tt.existingDymName.Name) + s.Require().NoError(err) + + err = s.dymNsKeeper.AfterDymNameConfigChanged(s.ctx, tt.existingDymName.Name) + s.Require().NoError(err) + if tt.setupActiveSellOrder { so := dymnstypes.SellOrder{ AssetId: useRecordName, AssetType: dymnstypes.TypeName, ExpireAt: tt.existingDymName.ExpireAt - 1, - MinPrice: s.coin(1), - SellPrice: uptr.To(s.coin(2)), + MinPrice: s.coin(soMinPrice), + SellPrice: uptr.To(s.coin(soSellPrice)), HighestBid: &dymnstypes.SellOrderBid{ Bidder: anotherA, - Price: s.coin(2), + Price: s.coin(soBidAmount), }, } err = s.dymNsKeeper.SetSellOrder(s.ctx, so) @@ -646,7 +658,7 @@ func (s *KeeperTestSuite) Test_msgServer_RegisterName() { defer func() { laterModuleBalance := s.moduleBalance2() s.Equal( - sdk.NewInt(originalModuleBalance).Mul(priceMultiplier).String(), laterModuleBalance.String(), + sdk.NewInt(originalModuleBalance).Mul(priceMultiplier).AddRaw(tt.offsetModuleBalLater).String(), laterModuleBalance.String(), "token should be burned", ) }() diff --git a/x/dymns/keeper/msg_server_test.go b/x/dymns/keeper/msg_server_test.go index 6ffeb3e47..f6e4ab702 100644 --- a/x/dymns/keeper/msg_server_test.go +++ b/x/dymns/keeper/msg_server_test.go @@ -4,6 +4,8 @@ import ( "testing" "time" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" ) @@ -14,3 +16,60 @@ func TestTime(t *testing.T) { "if mis-match, 100% sure will causes AppHash", ) } + +func Test_consumeMinimumGas(t *testing.T) { + tests := []struct { + name string + originalConsumedGas uint64 + overrideConsumedGas *uint64 + minimumGas uint64 + wantPanic bool + wantGasMeterConsumedGas uint64 + }{ + { + name: "pass - normal gas consumption", + originalConsumedGas: 0, + minimumGas: 1_000, + wantGasMeterConsumedGas: 1_000, + }, + { + name: "pass - should be stacked with previous run", + originalConsumedGas: 20_000, + minimumGas: 1_000, + wantGasMeterConsumedGas: 21_000, + }, + { + name: "fail - should panic if later consumed gas is less than original consumed gas", + originalConsumedGas: 2, + overrideConsumedGas: func() *uint64 { + v := uint64(1) + return &v + }(), + minimumGas: 1_000, + wantPanic: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := sdk.Context{}.WithGasMeter(sdk.NewInfiniteGasMeter()) + + originalConsumedGas := tt.originalConsumedGas + if tt.overrideConsumedGas != nil { + originalConsumedGas = *tt.overrideConsumedGas + } + if originalConsumedGas > 0 { + ctx.GasMeter().ConsumeGas(originalConsumedGas, "simulate pre-run gas consumption") + } + + if tt.wantPanic { + require.Panics(t, func() { + consumeMinimumGas(ctx, tt.minimumGas, tt.originalConsumedGas, "test") + }) + return + } + + consumeMinimumGas(ctx, tt.minimumGas, tt.originalConsumedGas, "test") + require.Equal(t, tt.wantGasMeterConsumedGas, ctx.GasMeter().GasConsumed()) + }) + } +} diff --git a/x/dymns/keeper/msg_server_update_details.go b/x/dymns/keeper/msg_server_update_details.go index b11946741..c45ae8e78 100644 --- a/x/dymns/keeper/msg_server_update_details.go +++ b/x/dymns/keeper/msg_server_update_details.go @@ -14,6 +14,7 @@ import ( // handles updating Dym-Name details, performed by the controller. func (k msgServer) UpdateDetails(goCtx context.Context, msg *dymnstypes.MsgUpdateDetails) (*dymnstypes.MsgUpdateDetailsResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() dymName, err := k.validateUpdateDetails(ctx, msg) if err != nil { @@ -56,7 +57,7 @@ func (k msgServer) UpdateDetails(goCtx context.Context, msg *dymnstypes.MsgUpdat } // charge protocol fee - consumeMinimumGas(ctx, minimumTxGasRequired, "UpdateDetails") + consumeMinimumGas(ctx, minimumTxGasRequired, originalConsumedGas, "UpdateDetails") return &dymnstypes.MsgUpdateDetailsResponse{}, nil } diff --git a/x/dymns/keeper/msg_server_update_details_test.go b/x/dymns/keeper/msg_server_update_details_test.go index 2b0d7d9d3..dd2d00199 100644 --- a/x/dymns/keeper/msg_server_update_details_test.go +++ b/x/dymns/keeper/msg_server_update_details_test.go @@ -594,6 +594,31 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateDetails() { s.requireFallbackAddress(controllerAcc.fallback()).notMappedToAnyDymName() }, }, + { + name: "pass - independently charge gas", + dymName: &dymnstypes.DymName{ + Owner: ownerA, + Controller: controllerA, + ExpireAt: s.now.Unix() + 100, + Contact: "old-contact@example.com", + }, + preTestFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + msg: &dymnstypes.MsgUpdateDetails{ + Contact: "new-contact@example.com", + Controller: controllerA, + }, + wantErr: false, + wantDymName: &dymnstypes.DymName{ + Owner: ownerA, + Controller: controllerA, + ExpireAt: s.now.Unix() + 100, + Contact: "new-contact@example.com", + }, + wantMinGasConsumed: 100_000_000 + dymnstypes.OpGasUpdateContact, + postTestFunc: func(*KeeperTestSuite) {}, + }, } for _, tt := range tests { s.Run(tt.name, func() { diff --git a/x/dymns/keeper/msg_server_update_resolve_address.go b/x/dymns/keeper/msg_server_update_resolve_address.go index d0078a8f4..4fb0a142f 100644 --- a/x/dymns/keeper/msg_server_update_resolve_address.go +++ b/x/dymns/keeper/msg_server_update_resolve_address.go @@ -17,6 +17,7 @@ import ( // handles updating Dym-Name-Address resolution configuration, performed by the controller. func (k msgServer) UpdateResolveAddress(goCtx context.Context, msg *dymnstypes.MsgUpdateResolveAddress) (*dymnstypes.MsgUpdateResolveAddressResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + originalConsumedGas := ctx.GasMeter().GasConsumed() dymName, err := k.validateUpdateResolveAddress(ctx, msg) if err != nil { @@ -55,14 +56,13 @@ func (k msgServer) UpdateResolveAddress(goCtx context.Context, msg *dymnstypes.M if foundSameConfigIdAtIdx < 0 { // no-config case also falls into this branch - - // do nothing - } else { - dymName.Configs = append( - dymName.Configs[:foundSameConfigIdAtIdx], - dymName.Configs[foundSameConfigIdAtIdx+1:]..., - ) + return nil, errorsmod.Wrapf(gerrc.ErrNotFound, "config") } + + dymName.Configs = append( + dymName.Configs[:foundSameConfigIdAtIdx], + dymName.Configs[foundSameConfigIdAtIdx+1:]..., + ) } else { minimumTxGasRequired = dymnstypes.OpGasConfig @@ -97,7 +97,7 @@ func (k msgServer) UpdateResolveAddress(goCtx context.Context, msg *dymnstypes.M // Charge protocol fee. // The protocol fee mechanism is used to prevent spamming to the network. - consumeMinimumGas(ctx, minimumTxGasRequired, "UpdateResolveAddress") + consumeMinimumGas(ctx, minimumTxGasRequired, originalConsumedGas, "UpdateResolveAddress") return &dymnstypes.MsgUpdateResolveAddressResponse{}, nil } diff --git a/x/dymns/keeper/msg_server_update_resolve_address_test.go b/x/dymns/keeper/msg_server_update_resolve_address_test.go index 1a4b74a81..217dc0f7f 100644 --- a/x/dymns/keeper/msg_server_update_resolve_address_test.go +++ b/x/dymns/keeper/msg_server_update_resolve_address_test.go @@ -590,7 +590,7 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { }, }, { - name: "pass - remove record if new resolve to empty, single-config, not match any", + name: "fail - should reject when remove record, single-config, not match any", dymName: &dymnstypes.DymName{ Owner: ownerAcc.bech32(), Controller: controllerAcc.bech32(), @@ -614,7 +614,8 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { SubName: "non-exists", Controller: controllerAcc.bech32(), }, - wantErr: false, + wantErr: true, + wantErrContains: "config: not found", wantDymName: &dymnstypes.DymName{ Owner: ownerAcc.bech32(), Controller: controllerAcc.bech32(), @@ -737,7 +738,7 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { }, }, { - name: "pass - remove record if new resolve to empty, multi-config, not any of existing", + name: "fail - should reject when remove record, multi-config, not any of existing", dymName: &dymnstypes.DymName{ Owner: ownerAcc.bech32(), Controller: controllerAcc.bech32(), @@ -766,7 +767,8 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { SubName: "non-exists", Controller: controllerAcc.bech32(), }, - wantErr: false, + wantErr: true, + wantErrContains: "config: not found", wantDymName: &dymnstypes.DymName{ Owner: ownerAcc.bech32(), Controller: controllerAcc.bech32(), @@ -1502,6 +1504,37 @@ func (s *KeeperTestSuite) Test_msgServer_UpdateResolveAddress() { s.requireFallbackAddress(anotherAcc.fallback()).notMappedToAnyDymName() }, }, + { + name: "pass - independently charge gas", + dymName: &dymnstypes.DymName{ + Owner: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + ExpireAt: s.now.Unix() + 100, + }, + preTestFunc: func(s *KeeperTestSuite) { + s.ctx.GasMeter().ConsumeGas(100_000_000, "simulate previous run") + }, + msg: &dymnstypes.MsgUpdateResolveAddress{ + ResolveTo: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + }, + wantErr: false, + wantDymName: &dymnstypes.DymName{ + Owner: ownerAcc.bech32(), + Controller: controllerAcc.bech32(), + ExpireAt: s.now.Unix() + 100, + Configs: []dymnstypes.DymNameConfig{ + { + Type: dymnstypes.DymNameConfigType_DCT_NAME, + ChainId: "", + Path: "", + Value: ownerAcc.bech32(), + }, + }, + }, + wantMinGasConsumed: 100_000_000 + dymnstypes.OpGasConfig, + postTestFunc: func(*KeeperTestSuite) {}, + }, } for _, tt := range tests { s.Run(tt.name, func() { diff --git a/x/dymns/keeper/params.go b/x/dymns/keeper/params.go index 0087fc242..6e5ac7e3c 100644 --- a/x/dymns/keeper/params.go +++ b/x/dymns/keeper/params.go @@ -54,7 +54,7 @@ func (k Keeper) CanUseAliasForNewRegistration(ctx sdk.Context, aliasCandidate st } if k.IsAliasPresentsInParamsAsAliasOrChainId(ctx, aliasCandidate) { - // Please read the `processActiveAliasSellOrders` method (hooks.go) for more information. + // Please read the `processCompleteSellOrderWithAssetTypeAlias` method (msg_server_complete_sell_order.go) for more information. return false } diff --git a/x/dymns/keeper/sell_order.go b/x/dymns/keeper/sell_order.go index 552effaa9..ad54f759d 100644 --- a/x/dymns/keeper/sell_order.go +++ b/x/dymns/keeper/sell_order.go @@ -82,68 +82,3 @@ func (k Keeper) GetAllSellOrders(ctx sdk.Context) (list []dymnstypes.SellOrder) return list } - -// SetActiveSellOrdersExpiration stores the expiration of the active Sell-Orders records into the KVStore. -// When a Sell-Order is created, it has an expiration time, later be processed by the batch processing in `x/epochs` hooks, -// instead of iterating through all the Sell-Orders records in store, we store the expiration of the active Sell-Orders, -// so that we can easily find the expired Sell-Orders and conditionally load them to process. -// This expiration list should be maintained accordingly to the Sell-Order CRUD. -func (k Keeper) SetActiveSellOrdersExpiration(ctx sdk.Context, - activeSellOrdersExpiration *dymnstypes.ActiveSellOrdersExpiration, assetType dymnstypes.AssetType, -) error { - activeSellOrdersExpiration.Sort() - - if err := activeSellOrdersExpiration.Validate(); err != nil { - return err - } - - // use key according to asset type - var key []byte - switch assetType { - case dymnstypes.TypeName: - key = dymnstypes.KeyActiveSellOrdersExpirationOfDymName - case dymnstypes.TypeAlias: - key = dymnstypes.KeyActiveSellOrdersExpirationOfAlias - default: - panic("invalid asset type: " + assetType.PrettyName()) - } - - // persist record - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshal(activeSellOrdersExpiration) - store.Set(key, bz) - return nil -} - -// GetActiveSellOrdersExpiration retrieves the expiration of the active Sell-Orders records -// of the corresponding asset type. -// For more information, see SetActiveSellOrdersExpiration. -func (k Keeper) GetActiveSellOrdersExpiration(ctx sdk.Context, - assetType dymnstypes.AssetType, -) *dymnstypes.ActiveSellOrdersExpiration { - store := ctx.KVStore(k.storeKey) - - // use key according to asset type - var key []byte - switch assetType { - case dymnstypes.TypeName: - key = dymnstypes.KeyActiveSellOrdersExpirationOfDymName - case dymnstypes.TypeAlias: - key = dymnstypes.KeyActiveSellOrdersExpirationOfAlias - default: - panic("invalid asset type: " + assetType.PrettyName()) - } - - var activeSellOrdersExpiration dymnstypes.ActiveSellOrdersExpiration - - bz := store.Get(key) - if bz != nil { - k.cdc.MustUnmarshal(bz, &activeSellOrdersExpiration) - } - - if activeSellOrdersExpiration.Records == nil { - activeSellOrdersExpiration.Records = make([]dymnstypes.ActiveSellOrdersExpirationRecord, 0) - } - - return &activeSellOrdersExpiration -} diff --git a/x/dymns/keeper/sell_order_test.go b/x/dymns/keeper/sell_order_test.go index 0d2b9d169..25fd85882 100644 --- a/x/dymns/keeper/sell_order_test.go +++ b/x/dymns/keeper/sell_order_test.go @@ -300,140 +300,6 @@ func (s *KeeperTestSuite) TestKeeper_DeleteSellOrder() { }) } -func (s *KeeperTestSuite) TestKeeper_GetSetActiveSellOrdersExpiration() { - s.RefreshContext() - - supportedAssetTypes := []dymnstypes.AssetType{ - dymnstypes.TypeName, dymnstypes.TypeAlias, - } - - s.Run("get", func() { - for _, assetType := range supportedAssetTypes { - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, assetType) - s.Require().Empty(aSoe.Records, "default list must be empty") - s.Require().NotNil(aSoe.Records, "list must be initialized") - } - }) - - s.Run("set", func() { - for _, assetType := range supportedAssetTypes { - aSoe := &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "hello", - ExpireAt: 123, - }, - { - AssetId: "world", - ExpireAt: 456, - }, - }, - } - err := s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, aSoe, assetType) - s.Require().NoError(err) - - aSoe = s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, assetType) - s.Require().Len(aSoe.Records, 2) - s.Require().Equal("hello", aSoe.Records[0].AssetId) - s.Require().Equal(int64(123), aSoe.Records[0].ExpireAt) - s.Require().Equal("world", aSoe.Records[1].AssetId) - s.Require().Equal(int64(456), aSoe.Records[1].ExpireAt) - } - }) - - s.Run("must automatically sort when set", func() { - for _, assetType := range supportedAssetTypes { - err := s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "bbb", - ExpireAt: 456, - }, - { - AssetId: "aaa", - ExpireAt: 123, - }, - }, - }, assetType) - s.Require().NoError(err) - - aSoe := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, assetType) - s.Require().Len(aSoe.Records, 2) - - s.Require().Equal("aaa", aSoe.Records[0].AssetId) - s.Require().Equal(int64(123), aSoe.Records[0].ExpireAt) - s.Require().Equal("bbb", aSoe.Records[1].AssetId) - s.Require().Equal(int64(456), aSoe.Records[1].ExpireAt) - } - }) - - s.Run("can not set if set is not valid", func() { - for _, assetType := range supportedAssetTypes { - // not unique - err := s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "dup", - ExpireAt: 456, - }, - { - AssetId: "dup", - ExpireAt: 123, - }, - }, - }, assetType) - s.Require().Error(err) - - // zero expiry - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "alice", - ExpireAt: -1, - }, - { - AssetId: "bob", - ExpireAt: 0, - }, - }, - }, assetType) - s.Require().Error(err) - } - }) - - s.Run("each asset type persists separately", func() { - err := s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "asset", - ExpireAt: 1, - }, - }, - }, dymnstypes.TypeName) - s.Require().NoError(err) - - err = s.dymNsKeeper.SetActiveSellOrdersExpiration(s.ctx, &dymnstypes.ActiveSellOrdersExpiration{ - Records: []dymnstypes.ActiveSellOrdersExpirationRecord{ - { - AssetId: "asset", - ExpireAt: 2, - }, - }, - }, dymnstypes.TypeAlias) - s.Require().NoError(err) - - listName := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeName) - s.Require().Len(listName.Records, 1) - s.Require().Equal("asset", listName.Records[0].AssetId) - s.Require().Equal(int64(1), listName.Records[0].ExpireAt) - - listAlias := s.dymNsKeeper.GetActiveSellOrdersExpiration(s.ctx, dymnstypes.TypeAlias) - s.Require().Len(listAlias.Records, 1) - s.Require().Equal("asset", listAlias.Records[0].AssetId) - s.Require().Equal(int64(2), listAlias.Records[0].ExpireAt) - }) -} - func (s *KeeperTestSuite) TestKeeper_GetAllSellOrders() { supportedAssetTypes := []dymnstypes.AssetType{ dymnstypes.TypeName, dymnstypes.TypeAlias, diff --git a/x/dymns/module.go b/x/dymns/module.go index 93b2d95dc..a3695660d 100644 --- a/x/dymns/module.go +++ b/x/dymns/module.go @@ -112,8 +112,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { } // RegisterInvariants registers the module's invariants. -func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - dymnskeeper.RegisterInvariants(ir, am.keeper) +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) { } // InitGenesis performs the module's genesis initialization. It returns no validator updates. diff --git a/x/dymns/types/constants.go b/x/dymns/types/constants.go index b0ab5b994..1ffa19850 100644 --- a/x/dymns/types/constants.go +++ b/x/dymns/types/constants.go @@ -17,19 +17,21 @@ const ( MinDymNamePriceStepsCount = 4 // MinAliasPriceStepsCount is the minimum number of price steps required for Alias price. - MinAliasPriceStepsCount + MinAliasPriceStepsCount = 4 ) // MinPriceValue is the minimum value allowed for price configuration. var MinPriceValue = sdkmath.NewInt(1e18) const ( - // OpGasPlaceSellOrder is the gas consumed when a Dym-Name owner creates a Sell-Order for selling Dym-Name. + // OpGasPlaceSellOrder is the gas consumed when an asset owner creates a Sell-Order for selling the asset. OpGasPlaceSellOrder sdk.Gas = 25_000_000 - // OpGasCloseSellOrder is the gas consumed when Dym-Name owner closes Sell-Order. + // OpGasCloseSellOrder is the gas consumed when asset owner closes the Sell-Order. OpGasCloseSellOrder sdk.Gas = 5_000_000 + // OpGasCompleteSellOrder is the gas consumed when Sell-Order participant completes it. + OpGasCompleteSellOrder sdk.Gas = 5_000_000 - // OpGasPlaceBidOnSellOrder is the gas consumed when a buyer bids on a Dym-Name Sell-Order. + // OpGasPlaceBidOnSellOrder is the gas consumed when a buyer bids on a Sell-Order. OpGasPlaceBidOnSellOrder sdk.Gas = 20_000_000 // OpGasConfig is the gas consumed when Dym-Name controller updating Dym-Name configuration, @@ -42,15 +44,14 @@ const ( // We do not charge this fee on clear Contact operation. OpGasUpdateContact sdk.Gas = 1_000_000 - // OpGasPutBuyOrder is the gas consumed when a buyer placing a buy order, offer to buy Dym-Name. + // OpGasPutBuyOrder is the gas consumed when a buyer placing a buy order, offer to buy an asset. OpGasPutBuyOrder sdk.Gas = 25_000_000 // OpGasUpdateBuyOrder is the gas consumed when the buyer who placed the buy order, - // updating the offer to buy Dym-Name. + // updating the existing offer. OpGasUpdateBuyOrder sdk.Gas = 20_000_000 - // OpGasCloseBuyOrder is the gas consumed when the buyer who placed the buy order, - // closing the offer to buy Dym-Name. + // OpGasCloseBuyOrder is the gas consumed when the buyer who placed the buy order, closing it. OpGasCloseBuyOrder sdk.Gas = 5_000_000 ) diff --git a/x/dymns/types/dym_name.go b/x/dymns/types/dym_name.go index e5825248c..2a4012728 100644 --- a/x/dymns/types/dym_name.go +++ b/x/dymns/types/dym_name.go @@ -2,7 +2,6 @@ package types import ( "fmt" - "sort" "strings" errorsmod "cosmossdk.io/errors" @@ -128,21 +127,6 @@ func (m *DymNameConfig) Validate() error { return nil } -// Validate checks if the ReverseLookupDymNames record is valid. -func (m *ReverseLookupDymNames) Validate() error { - if m == nil { - return errorsmod.Wrap(gerrc.ErrInvalidArgument, "reverse lookup record is nil") - } - - for _, name := range m.DymNames { - if !dymnsutils.IsValidDymName(name) { - return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "invalid dym name: %s", name) - } - } - - return nil -} - // IsExpiredAtCtx returns true if the Dym-Name is expired at the given context. // It compares the expiry with the block time in context. func (m DymName) IsExpiredAtCtx(ctx sdk.Context) bool { @@ -288,33 +272,3 @@ func (m *DymName) GetAddressesForReverseMapping() ( return } - -// Distinct returns a new list of dym names with duplicates removed. -// Result will be sorted. -func (m ReverseLookupDymNames) Distinct() (distinct ReverseLookupDymNames) { - return ReverseLookupDymNames{ - DymNames: StringList(m.DymNames).Distinct(), - } -} - -// Combine merges the dym names from the current list and the other list. -// Result will be sorted distinct. -func (m ReverseLookupDymNames) Combine(other ReverseLookupDymNames) ReverseLookupDymNames { - return ReverseLookupDymNames{ - DymNames: StringList(m.DymNames).Combine(other.DymNames), - }.Distinct() -} - -// Exclude removes the dym names from the current list that are in the toBeExcluded list. -// Result will be sorted distinct. -func (m ReverseLookupDymNames) Exclude(toBeExcluded ReverseLookupDymNames) (afterExcluded ReverseLookupDymNames) { - return ReverseLookupDymNames{ - DymNames: StringList(m.DymNames).Exclude(toBeExcluded.DymNames), - }.Distinct() -} - -// Sort sorts the dym names in the list. -func (m ReverseLookupDymNames) Sort() ReverseLookupDymNames { - sort.Strings(m.DymNames) - return m -} diff --git a/x/dymns/types/dym_name_test.go b/x/dymns/types/dym_name_test.go index 8c7d8fa06..ef3bfdad2 100644 --- a/x/dymns/types/dym_name_test.go +++ b/x/dymns/types/dym_name_test.go @@ -581,57 +581,6 @@ func TestDymNameConfig_Validate(t *testing.T) { } } -func TestReverseLookupDymNames_Validate(t *testing.T) { - t.Run("nil obj", func(t *testing.T) { - m := (*ReverseLookupDymNames)(nil) - require.Error(t, m.Validate()) - }) - - tests := []struct { - name string - DymNames []string - wantErr bool - wantErrContains string - }{ - { - name: "pass - valid reverse lookup record", - DymNames: []string{"my-name", "not-bonded-pool"}, - }, - { - name: "pass - allow empty", - DymNames: []string{}, - }, - { - name: "fail - bad dym name", - DymNames: []string{"my-name", "-not-bonded-pool"}, - wantErr: true, - wantErrContains: "invalid dym name:", - }, - { - name: "fail - bad dym name", - DymNames: []string{"-a"}, - wantErr: true, - wantErrContains: "invalid dym name:", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := &ReverseLookupDymNames{ - DymNames: tt.DymNames, - } - - err := m.Validate() - if tt.wantErr { - require.NotEmpty(t, tt.wantErrContains, "mis-configured test") - require.Error(t, err) - require.Contains(t, err.Error(), tt.wantErrContains) - } else { - require.NoError(t, err) - } - }) - } -} - func TestDymName_IsExpiredAt(t *testing.T) { now := time.Now() tests := []struct { diff --git a/x/dymns/types/genesis.go b/x/dymns/types/genesis.go index 889b82e5f..ff9f965dc 100644 --- a/x/dymns/types/genesis.go +++ b/x/dymns/types/genesis.go @@ -21,10 +21,15 @@ func (m GenesisState) Validate() error { return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "params: %v", err) } + uniqueNames := make(map[string]struct{}) for _, dymName := range m.DymNames { if err := dymName.Validate(); err != nil { return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "Dym-Name '%s': %v", dymName.Name, err) } + if _, duplicated := uniqueNames[dymName.Name]; duplicated { + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "Dym-Name '%s': duplicate name", dymName.Name) + } + uniqueNames[dymName.Name] = struct{}{} } for _, soBid := range m.SellOrderBids { diff --git a/x/dymns/types/genesis_test.go b/x/dymns/types/genesis_test.go index 024d49627..8587783e8 100644 --- a/x/dymns/types/genesis_test.go +++ b/x/dymns/types/genesis_test.go @@ -114,6 +114,26 @@ func TestGenesisState_Validate(t *testing.T) { }).Validate()) }) + t.Run("fail - duplicated dym names", func(t *testing.T) { + require.Error(t, (GenesisState{ + Params: DefaultParams(), + DymNames: []DymName{ + { + Name: "my-name", + Owner: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + Controller: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + ExpireAt: time.Now().Unix(), + }, + { + Name: "my-name", + Owner: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + Controller: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + ExpireAt: time.Now().Unix(), + }, + }, + }).Validate()) + }) + t.Run("fail - invalid bid", func(t *testing.T) { require.Error(t, (GenesisState{ Params: DefaultParams(), diff --git a/x/dymns/types/keys.go b/x/dymns/types/keys.go index 3cc3310d4..f507a7fa6 100644 --- a/x/dymns/types/keys.go +++ b/x/dymns/types/keys.go @@ -25,7 +25,7 @@ const ( prefixRvlConfiguredAddressToDymNamesInclude // reverse lookup store prefixRvlFallbackAddressToDymNamesInclude // reverse lookup store prefixSellOrder - prefixActiveSellOrdersExpiration + prefixActiveSellOrdersExpiration // deprecated prefixCountBuyOrders prefixBuyOrder prefixRvlBuyerToBuyOrderIds // reverse lookup store @@ -83,14 +83,8 @@ var ( KeyPrefixRvlAliasToRollAppId = []byte{prefixRvlAliasToRollAppId} ) -var ( - KeyActiveSellOrdersExpirationOfDymName = []byte{prefixActiveSellOrdersExpiration, partialStoreAssetTypeDymName} - - KeyActiveSellOrdersExpirationOfAlias = []byte{prefixActiveSellOrdersExpiration, partialStoreAssetTypeAlias} - - // KeyCountBuyOrders is the key for the count of all-time buy orders - KeyCountBuyOrders = []byte{prefixCountBuyOrders} -) +// KeyCountBuyOrders is the key for the count of all-time buy orders +var KeyCountBuyOrders = []byte{prefixCountBuyOrders} // DymNameKey returns a key for specific Dym-Name func DymNameKey(name string) []byte { diff --git a/x/dymns/types/keys_test.go b/x/dymns/types/keys_test.go index 7c8950b90..7a2f5c212 100644 --- a/x/dymns/types/keys_test.go +++ b/x/dymns/types/keys_test.go @@ -25,8 +25,6 @@ func TestStorePrefixes(t *testing.T) { }) t.Run("ensure keys are not mistakenly modified", func(t *testing.T) { - require.Equal(t, []byte{0x06, partialStoreAssetTypeDymName}, KeyActiveSellOrdersExpirationOfDymName, "do not change it, will break the app") - require.Equal(t, []byte{0x06, partialStoreAssetTypeAlias}, KeyActiveSellOrdersExpirationOfAlias, "do not change it, will break the app") require.Equal(t, []byte{0x07}, KeyCountBuyOrders, "do not change it, will break the app") }) diff --git a/x/dymns/types/market.pb.go b/x/dymns/types/market.pb.go index cde0488f6..a27695b1d 100644 --- a/x/dymns/types/market.pb.go +++ b/x/dymns/types/market.pb.go @@ -56,8 +56,8 @@ func (AssetType) EnumDescriptor() ([]byte, []int) { // SellOrder defines a sell order, placed by owner, to sell a Dym-Name/Alias. // Sell-Order has an expiry date. // After expiry date, if no one has placed a bid, this Sell-Order will be closed, no change. -// If there is a bid, the highest bid will win, and the Dym-Name/Alias ownership will be transferred to the winner. -// If the bid matches the sell price, the Dym-Name/Alias ownership will be transferred to the bidder immediately. +// - If there is a bid, the highest bid will win, and the Dym-Name/Alias ownership will be transferred to the winner. +// - If the bid matches the sell price, the Dym-Name/Alias ownership will be transferred to the bidder immediately. type SellOrder struct { // asset_id is the Dym-Name/Alias being opened to be sold. AssetId string `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` @@ -150,107 +150,6 @@ func (m *SellOrder) GetHighestBid() *SellOrderBid { return nil } -// ActiveSellOrdersExpiration contains list of active SOs, store expiration date mapped by asset identity. -// Used by hook to find out expired SO instead of iterating through all records. -type ActiveSellOrdersExpiration struct { - Records []ActiveSellOrdersExpirationRecord `protobuf:"bytes,1,rep,name=records,proto3" json:"records"` -} - -func (m *ActiveSellOrdersExpiration) Reset() { *m = ActiveSellOrdersExpiration{} } -func (m *ActiveSellOrdersExpiration) String() string { return proto.CompactTextString(m) } -func (*ActiveSellOrdersExpiration) ProtoMessage() {} -func (*ActiveSellOrdersExpiration) Descriptor() ([]byte, []int) { - return fileDescriptor_ddf761d4919b968f, []int{1} -} -func (m *ActiveSellOrdersExpiration) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ActiveSellOrdersExpiration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ActiveSellOrdersExpiration.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ActiveSellOrdersExpiration) XXX_Merge(src proto.Message) { - xxx_messageInfo_ActiveSellOrdersExpiration.Merge(m, src) -} -func (m *ActiveSellOrdersExpiration) XXX_Size() int { - return m.Size() -} -func (m *ActiveSellOrdersExpiration) XXX_DiscardUnknown() { - xxx_messageInfo_ActiveSellOrdersExpiration.DiscardUnknown(m) -} - -var xxx_messageInfo_ActiveSellOrdersExpiration proto.InternalMessageInfo - -func (m *ActiveSellOrdersExpiration) GetRecords() []ActiveSellOrdersExpirationRecord { - if m != nil { - return m.Records - } - return nil -} - -// ActiveSellOrdersExpirationRecord contains the expiration date of an active Sell-Order. -type ActiveSellOrdersExpirationRecord struct { - // asset_id is the Dym-Name/Alias being opened to be sold. - AssetId string `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` - // expire_at is the last effective date of this Sell-Order. - ExpireAt int64 `protobuf:"varint,2,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty"` -} - -func (m *ActiveSellOrdersExpirationRecord) Reset() { *m = ActiveSellOrdersExpirationRecord{} } -func (m *ActiveSellOrdersExpirationRecord) String() string { return proto.CompactTextString(m) } -func (*ActiveSellOrdersExpirationRecord) ProtoMessage() {} -func (*ActiveSellOrdersExpirationRecord) Descriptor() ([]byte, []int) { - return fileDescriptor_ddf761d4919b968f, []int{2} -} -func (m *ActiveSellOrdersExpirationRecord) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ActiveSellOrdersExpirationRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ActiveSellOrdersExpirationRecord.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ActiveSellOrdersExpirationRecord) XXX_Merge(src proto.Message) { - xxx_messageInfo_ActiveSellOrdersExpirationRecord.Merge(m, src) -} -func (m *ActiveSellOrdersExpirationRecord) XXX_Size() int { - return m.Size() -} -func (m *ActiveSellOrdersExpirationRecord) XXX_DiscardUnknown() { - xxx_messageInfo_ActiveSellOrdersExpirationRecord.DiscardUnknown(m) -} - -var xxx_messageInfo_ActiveSellOrdersExpirationRecord proto.InternalMessageInfo - -func (m *ActiveSellOrdersExpirationRecord) GetAssetId() string { - if m != nil { - return m.AssetId - } - return "" -} - -func (m *ActiveSellOrdersExpirationRecord) GetExpireAt() int64 { - if m != nil { - return m.ExpireAt - } - return 0 -} - // SellOrderBid defines a bid placed by an account on a Sell-Order. type SellOrderBid struct { // bidder is the account address of the account which placed the bid. @@ -267,7 +166,7 @@ func (m *SellOrderBid) Reset() { *m = SellOrderBid{} } func (m *SellOrderBid) String() string { return proto.CompactTextString(m) } func (*SellOrderBid) ProtoMessage() {} func (*SellOrderBid) Descriptor() ([]byte, []int) { - return fileDescriptor_ddf761d4919b968f, []int{3} + return fileDescriptor_ddf761d4919b968f, []int{1} } func (m *SellOrderBid) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -347,7 +246,7 @@ func (m *BuyOrder) Reset() { *m = BuyOrder{} } func (m *BuyOrder) String() string { return proto.CompactTextString(m) } func (*BuyOrder) ProtoMessage() {} func (*BuyOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_ddf761d4919b968f, []int{4} + return fileDescriptor_ddf761d4919b968f, []int{2} } func (m *BuyOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -435,7 +334,7 @@ func (m *ReverseLookupBuyOrderIds) Reset() { *m = ReverseLookupBuyOrderI func (m *ReverseLookupBuyOrderIds) String() string { return proto.CompactTextString(m) } func (*ReverseLookupBuyOrderIds) ProtoMessage() {} func (*ReverseLookupBuyOrderIds) Descriptor() ([]byte, []int) { - return fileDescriptor_ddf761d4919b968f, []int{5} + return fileDescriptor_ddf761d4919b968f, []int{3} } func (m *ReverseLookupBuyOrderIds) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -474,8 +373,6 @@ func (m *ReverseLookupBuyOrderIds) GetOrderIds() []string { func init() { proto.RegisterEnum("dymensionxyz.dymension.dymns.AssetType", AssetType_name, AssetType_value) proto.RegisterType((*SellOrder)(nil), "dymensionxyz.dymension.dymns.SellOrder") - proto.RegisterType((*ActiveSellOrdersExpiration)(nil), "dymensionxyz.dymension.dymns.ActiveSellOrdersExpiration") - proto.RegisterType((*ActiveSellOrdersExpirationRecord)(nil), "dymensionxyz.dymension.dymns.ActiveSellOrdersExpirationRecord") proto.RegisterType((*SellOrderBid)(nil), "dymensionxyz.dymension.dymns.SellOrderBid") proto.RegisterType((*BuyOrder)(nil), "dymensionxyz.dymension.dymns.BuyOrder") proto.RegisterType((*ReverseLookupBuyOrderIds)(nil), "dymensionxyz.dymension.dymns.ReverseLookupBuyOrderIds") @@ -486,47 +383,43 @@ func init() { } var fileDescriptor_ddf761d4919b968f = []byte{ - // 626 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x4f, 0x4f, 0xdb, 0x4e, - 0x10, 0x8d, 0x1d, 0x08, 0xf1, 0x04, 0xf1, 0x43, 0x2b, 0x84, 0x0c, 0xbf, 0xca, 0xb5, 0x72, 0x69, - 0xca, 0xc1, 0x16, 0x41, 0x55, 0xab, 0xaa, 0xaa, 0xea, 0xb4, 0x54, 0x42, 0x40, 0xa8, 0x4c, 0xaa, - 0xaa, 0x1c, 0x6a, 0xf9, 0xcf, 0x12, 0x56, 0xc4, 0x5e, 0x6b, 0x77, 0x13, 0xe1, 0xaa, 0x1f, 0xa2, - 0x9f, 0xa9, 0x27, 0x8e, 0x1c, 0x7b, 0xaa, 0x2a, 0xf8, 0x22, 0x95, 0xbd, 0xc6, 0x0d, 0x07, 0x12, - 0xd4, 0xdb, 0x8c, 0x3d, 0xef, 0x69, 0xde, 0xbc, 0xa7, 0x85, 0xa7, 0x51, 0x16, 0xe3, 0x84, 0x13, - 0x9a, 0x5c, 0x64, 0x5f, 0xed, 0xaa, 0xc9, 0xab, 0x84, 0xdb, 0xb1, 0xcf, 0xce, 0xb1, 0xb0, 0x52, - 0x46, 0x05, 0x45, 0x8f, 0xa6, 0x47, 0xad, 0xaa, 0xb1, 0x8a, 0xd1, 0xcd, 0xb5, 0x21, 0x1d, 0xd2, - 0x62, 0xd0, 0xce, 0x2b, 0x89, 0xd9, 0x34, 0x42, 0xca, 0x63, 0xca, 0xed, 0xc0, 0xe7, 0xd8, 0x9e, - 0x6c, 0x07, 0x58, 0xf8, 0xdb, 0x76, 0x48, 0x49, 0x22, 0xff, 0xb7, 0xaf, 0x54, 0xd0, 0x8e, 0xf1, - 0x68, 0x74, 0xc4, 0x22, 0xcc, 0xd0, 0x06, 0x34, 0x7d, 0xce, 0xb1, 0xf0, 0x48, 0xa4, 0x2b, 0xa6, - 0xd2, 0xd1, 0xdc, 0xa5, 0xa2, 0xdf, 0x8b, 0xd0, 0x7b, 0x00, 0xf9, 0x4b, 0x64, 0x29, 0xd6, 0x55, - 0x53, 0xe9, 0xac, 0x74, 0x9f, 0x58, 0xb3, 0x36, 0xb2, 0x9c, 0x7c, 0x7e, 0x90, 0xa5, 0xd8, 0xd5, - 0xfc, 0xdb, 0x12, 0xfd, 0x0f, 0x1a, 0xbe, 0x48, 0x09, 0xc3, 0x9e, 0x2f, 0xf4, 0xba, 0xa9, 0x74, - 0xea, 0x6e, 0x53, 0x7e, 0x70, 0x04, 0x7a, 0x05, 0x5a, 0x4c, 0x12, 0x2f, 0x65, 0x24, 0xc4, 0xfa, - 0x82, 0xa9, 0x74, 0x5a, 0xdd, 0x0d, 0x4b, 0x2a, 0xb0, 0x72, 0x05, 0x56, 0xa9, 0xc0, 0x7a, 0x4b, - 0x49, 0xd2, 0x5b, 0xb8, 0xfc, 0xf5, 0xb8, 0xe6, 0x36, 0x63, 0x92, 0x7c, 0xc8, 0x01, 0xe8, 0x05, - 0x00, 0xc7, 0xa3, 0x51, 0x09, 0x5f, 0x9c, 0x03, 0x77, 0xb5, 0x7c, 0x58, 0x22, 0xf7, 0xa1, 0x75, - 0x46, 0x86, 0x67, 0x98, 0x0b, 0x2f, 0x20, 0x91, 0xde, 0x28, 0xa0, 0x5b, 0xb3, 0xd5, 0x55, 0x57, - 0xeb, 0x91, 0xc8, 0x85, 0x12, 0xde, 0x23, 0x51, 0xfb, 0x1b, 0x6c, 0x3a, 0xa1, 0x20, 0x13, 0x5c, - 0x4d, 0xf0, 0xdd, 0x5c, 0xa0, 0x2f, 0x08, 0x4d, 0xd0, 0x17, 0x58, 0x62, 0x38, 0xa4, 0x2c, 0xe2, - 0xba, 0x62, 0xd6, 0x3b, 0xad, 0xee, 0xeb, 0x39, 0x47, 0xbc, 0x97, 0xca, 0x2d, 0x68, 0xca, 0x2b, - 0xdc, 0x92, 0xb6, 0x4f, 0xc0, 0x9c, 0x07, 0x99, 0x65, 0xf3, 0x1d, 0x7b, 0xd4, 0xbb, 0xf6, 0xb4, - 0xc7, 0xb0, 0x3c, 0xad, 0x1a, 0xad, 0x43, 0x23, 0x20, 0x51, 0x84, 0x59, 0xc9, 0x52, 0x76, 0xe8, - 0x19, 0x2c, 0x4a, 0x0f, 0xd4, 0x87, 0x59, 0x28, 0xa7, 0x73, 0xba, 0xd4, 0x67, 0x7e, 0xcc, 0xf5, - 0xba, 0x59, 0xcf, 0xe9, 0x64, 0xd7, 0xfe, 0xa1, 0x42, 0xb3, 0x37, 0xce, 0x64, 0x44, 0x57, 0x40, - 0xad, 0xb6, 0x56, 0xc9, 0x5d, 0x2d, 0xea, 0xac, 0xc8, 0xd6, 0xff, 0x39, 0xb2, 0x7f, 0xf7, 0x5a, - 0x98, 0xde, 0x0b, 0xad, 0xc1, 0x62, 0x30, 0xce, 0x30, 0x2b, 0xa2, 0xa6, 0xb9, 0xb2, 0x41, 0x6f, - 0xa0, 0x45, 0x4f, 0x4f, 0x31, 0x2b, 0x63, 0xd8, 0x78, 0xd8, 0x09, 0xa0, 0xc0, 0xc8, 0x34, 0x1e, - 0x83, 0x1e, 0xd2, 0x71, 0x22, 0x30, 0x4b, 0x7d, 0x26, 0x32, 0x6f, 0x9a, 0x6e, 0x69, 0x5e, 0xaa, - 0xd7, 0xa7, 0xa1, 0x47, 0x15, 0x69, 0xfb, 0x39, 0xe8, 0x2e, 0x9e, 0x60, 0xc6, 0xf1, 0x01, 0xa5, - 0xe7, 0xe3, 0xf4, 0xf6, 0xa0, 0x7b, 0x11, 0xcf, 0x4d, 0xa7, 0x79, 0xed, 0x91, 0x32, 0x95, 0x9a, - 0xdb, 0xa4, 0xe5, 0xcf, 0xad, 0x97, 0xa0, 0x55, 0x57, 0x41, 0x2b, 0x00, 0xce, 0xc0, 0xfb, 0xd8, - 0xdf, 0xef, 0x1f, 0x7d, 0xea, 0xaf, 0xd6, 0xd0, 0x7f, 0xd0, 0x72, 0x06, 0xde, 0xbb, 0xcf, 0x87, - 0x5e, 0xdf, 0x39, 0xdc, 0x5d, 0x55, 0xd0, 0x32, 0x34, 0x9d, 0x81, 0xe7, 0x1c, 0xec, 0x39, 0xc7, - 0xab, 0x6a, 0xef, 0xe0, 0xf2, 0xda, 0x50, 0xae, 0xae, 0x0d, 0xe5, 0xf7, 0xb5, 0xa1, 0x7c, 0xbf, - 0x31, 0x6a, 0x57, 0x37, 0x46, 0xed, 0xe7, 0x8d, 0x51, 0x3b, 0xe9, 0x0e, 0x89, 0x38, 0x1b, 0x07, - 0x56, 0x48, 0x63, 0xfb, 0x9e, 0x17, 0x70, 0xb2, 0x63, 0x5f, 0x94, 0xcf, 0x60, 0x6e, 0x20, 0x0f, - 0x1a, 0xc5, 0x93, 0xb5, 0xf3, 0x27, 0x00, 0x00, 0xff, 0xff, 0x62, 0x23, 0xac, 0x0b, 0x33, 0x05, - 0x00, 0x00, + // 568 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0x8d, 0x9d, 0x36, 0xb5, 0x27, 0x55, 0xa9, 0x56, 0x55, 0xe5, 0x16, 0x64, 0xa2, 0x5c, 0x08, + 0x3d, 0xd8, 0x6a, 0x2b, 0x04, 0x42, 0x1c, 0x70, 0xf8, 0x90, 0xaa, 0xb6, 0x29, 0x72, 0x83, 0x10, + 0x5c, 0x2c, 0x3b, 0xbb, 0x4d, 0x56, 0x8d, 0xbd, 0xd6, 0xae, 0x1d, 0xc5, 0xfc, 0x0a, 0x7e, 0x13, + 0xa7, 0x1e, 0x73, 0xe4, 0x84, 0x50, 0xf2, 0x47, 0x90, 0xbd, 0xae, 0x09, 0x07, 0xd2, 0x8a, 0xdb, + 0xbc, 0xcc, 0x7b, 0xa3, 0x79, 0xf3, 0xe2, 0x85, 0xa7, 0x38, 0x0b, 0x49, 0x24, 0x28, 0x8b, 0xa6, + 0xd9, 0x57, 0xbb, 0x02, 0x79, 0x15, 0x09, 0x3b, 0xf4, 0xf9, 0x35, 0x49, 0xac, 0x98, 0xb3, 0x84, + 0xa1, 0x47, 0xcb, 0x54, 0xab, 0x02, 0x56, 0x41, 0xdd, 0xdf, 0x19, 0xb2, 0x21, 0x2b, 0x88, 0x76, + 0x5e, 0x49, 0xcd, 0xbe, 0x39, 0x60, 0x22, 0x64, 0xc2, 0x0e, 0x7c, 0x41, 0xec, 0xc9, 0x61, 0x40, + 0x12, 0xff, 0xd0, 0x1e, 0x30, 0x1a, 0xc9, 0x7e, 0x7b, 0xa6, 0x82, 0x7e, 0x49, 0xc6, 0xe3, 0x0b, + 0x8e, 0x09, 0x47, 0x7b, 0xa0, 0xf9, 0x42, 0x90, 0xc4, 0xa3, 0xd8, 0x50, 0x5a, 0x4a, 0x47, 0x77, + 0x37, 0x0a, 0x7c, 0x82, 0xd1, 0x7b, 0x00, 0xd9, 0x4a, 0xb2, 0x98, 0x18, 0x6a, 0x4b, 0xe9, 0x6c, + 0x1d, 0x3d, 0xb1, 0x56, 0x6d, 0x64, 0x39, 0x39, 0xbf, 0x9f, 0xc5, 0xc4, 0xd5, 0xfd, 0xdb, 0x12, + 0x3d, 0x04, 0x9d, 0x4c, 0x63, 0xca, 0x89, 0xe7, 0x27, 0x46, 0xbd, 0xa5, 0x74, 0xea, 0xae, 0x26, + 0x7f, 0x70, 0x12, 0xf4, 0x0a, 0xf4, 0x90, 0x46, 0x5e, 0xcc, 0xe9, 0x80, 0x18, 0x6b, 0x2d, 0xa5, + 0xd3, 0x3c, 0xda, 0xb3, 0xa4, 0x03, 0x2b, 0x77, 0x60, 0x95, 0x0e, 0xac, 0x37, 0x8c, 0x46, 0xdd, + 0xb5, 0x9b, 0x9f, 0x8f, 0x6b, 0xae, 0x16, 0xd2, 0xe8, 0x43, 0x2e, 0x40, 0x2f, 0x00, 0x04, 0x19, + 0x8f, 0x4b, 0xf9, 0xfa, 0x1d, 0x72, 0x57, 0xcf, 0xc9, 0x52, 0x79, 0x0a, 0xcd, 0x11, 0x1d, 0x8e, + 0x88, 0x48, 0xbc, 0x80, 0x62, 0xa3, 0x51, 0x48, 0x0f, 0x56, 0xbb, 0xab, 0xae, 0xd6, 0xa5, 0xd8, + 0x85, 0x52, 0xde, 0xa5, 0xb8, 0x9d, 0xc2, 0xe6, 0x72, 0x0f, 0xed, 0x42, 0x23, 0xa0, 0x18, 0x13, + 0x5e, 0x9e, 0xb4, 0x44, 0xe8, 0x19, 0xac, 0xcb, 0x4d, 0xd5, 0xfb, 0x19, 0x95, 0xec, 0x7c, 0x5c, + 0xec, 0x73, 0x3f, 0x14, 0x46, 0xbd, 0x55, 0xcf, 0xc7, 0x49, 0xd4, 0xfe, 0xae, 0x82, 0xd6, 0x4d, + 0x33, 0x19, 0xe4, 0x16, 0xa8, 0x55, 0x84, 0x2a, 0xc5, 0x7f, 0x05, 0xab, 0xae, 0x0a, 0xb6, 0xfe, + 0xdf, 0xc1, 0xfe, 0xd9, 0x6b, 0x6d, 0x79, 0x2f, 0xb4, 0x03, 0xeb, 0x41, 0x9a, 0x11, 0x5e, 0x04, + 0xa2, 0xbb, 0x12, 0xa0, 0xd7, 0xd0, 0x64, 0x57, 0x57, 0x84, 0x97, 0x61, 0x35, 0xee, 0x77, 0x02, + 0x28, 0x34, 0x32, 0xb3, 0x4b, 0x30, 0x06, 0x2c, 0x8d, 0x12, 0xc2, 0x63, 0x9f, 0x27, 0x99, 0xb7, + 0x3c, 0x6e, 0xe3, 0xae, 0xec, 0x77, 0x97, 0xa5, 0x17, 0xd5, 0xd0, 0xf6, 0x73, 0x30, 0x5c, 0x32, + 0x21, 0x5c, 0x90, 0x33, 0xc6, 0xae, 0xd3, 0xf8, 0xf6, 0xa0, 0x27, 0x58, 0xe4, 0xff, 0x5c, 0x96, + 0xd7, 0x1e, 0xc5, 0xc2, 0x50, 0x0a, 0x8f, 0x1a, 0x2b, 0x9b, 0x07, 0x2f, 0x41, 0xaf, 0xae, 0x82, + 0xb6, 0x00, 0x9c, 0xbe, 0xf7, 0xb1, 0x77, 0xda, 0xbb, 0xf8, 0xd4, 0xdb, 0xae, 0xa1, 0x07, 0xd0, + 0x74, 0xfa, 0xde, 0xdb, 0xcf, 0xe7, 0x5e, 0xcf, 0x39, 0x7f, 0xb7, 0xad, 0xa0, 0x4d, 0xd0, 0x9c, + 0xbe, 0xe7, 0x9c, 0x9d, 0x38, 0x97, 0xdb, 0x6a, 0xf7, 0xec, 0x66, 0x6e, 0x2a, 0xb3, 0xb9, 0xa9, + 0xfc, 0x9a, 0x9b, 0xca, 0xb7, 0x85, 0x59, 0x9b, 0x2d, 0xcc, 0xda, 0x8f, 0x85, 0x59, 0xfb, 0x72, + 0x34, 0xa4, 0xc9, 0x28, 0x0d, 0xac, 0x01, 0x0b, 0xed, 0x7f, 0xbc, 0x13, 0x93, 0x63, 0x7b, 0x5a, + 0x3e, 0x16, 0x79, 0x80, 0x22, 0x68, 0x14, 0x1f, 0xf6, 0xf1, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x44, 0xbd, 0x66, 0x08, 0x59, 0x04, 0x00, 0x00, } func (m *SellOrder) Marshal() (dAtA []byte, err error) { @@ -603,78 +496,6 @@ func (m *SellOrder) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ActiveSellOrdersExpiration) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ActiveSellOrdersExpiration) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ActiveSellOrdersExpiration) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Records) > 0 { - for iNdEx := len(m.Records) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Records[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintMarket(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *ActiveSellOrdersExpirationRecord) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ActiveSellOrdersExpirationRecord) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ActiveSellOrdersExpirationRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.ExpireAt != 0 { - i = encodeVarintMarket(dAtA, i, uint64(m.ExpireAt)) - i-- - dAtA[i] = 0x10 - } - if len(m.AssetId) > 0 { - i -= len(m.AssetId) - copy(dAtA[i:], m.AssetId) - i = encodeVarintMarket(dAtA, i, uint64(len(m.AssetId))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *SellOrderBid) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -876,37 +697,6 @@ func (m *SellOrder) Size() (n int) { return n } -func (m *ActiveSellOrdersExpiration) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Records) > 0 { - for _, e := range m.Records { - l = e.Size() - n += 1 + l + sovMarket(uint64(l)) - } - } - return n -} - -func (m *ActiveSellOrdersExpirationRecord) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AssetId) - if l > 0 { - n += 1 + l + sovMarket(uint64(l)) - } - if m.ExpireAt != 0 { - n += 1 + sovMarket(uint64(m.ExpireAt)) - } - return n -} - func (m *SellOrderBid) Size() (n int) { if m == nil { return 0 @@ -1210,191 +1000,6 @@ func (m *SellOrder) Unmarshal(dAtA []byte) error { } return nil } -func (m *ActiveSellOrdersExpiration) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowMarket - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ActiveSellOrdersExpiration: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ActiveSellOrdersExpiration: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowMarket - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthMarket - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthMarket - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Records = append(m.Records, ActiveSellOrdersExpirationRecord{}) - if err := m.Records[len(m.Records)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipMarket(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthMarket - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ActiveSellOrdersExpirationRecord) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowMarket - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ActiveSellOrdersExpirationRecord: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ActiveSellOrdersExpirationRecord: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AssetId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowMarket - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthMarket - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthMarket - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AssetId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ExpireAt", wireType) - } - m.ExpireAt = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowMarket - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.ExpireAt |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipMarket(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthMarket - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *SellOrderBid) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/dymns/types/msg_complete_sell_order.go b/x/dymns/types/msg_complete_sell_order.go new file mode 100644 index 000000000..bc436dc46 --- /dev/null +++ b/x/dymns/types/msg_complete_sell_order.go @@ -0,0 +1,56 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" + "github.com/dymensionxyz/gerr-cosmos/gerrc" +) + +var _ sdk.Msg = &MsgCompleteSellOrder{} + +// ValidateBasic performs basic validation for the MsgCompleteSellOrder. +func (m *MsgCompleteSellOrder) ValidateBasic() error { + if m.AssetType == TypeName { + if !dymnsutils.IsValidDymName(m.AssetId) { + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "name is not a valid dym name: %s", m.AssetId) + } + } else if m.AssetType == TypeAlias { + if !dymnsutils.IsValidAlias(m.AssetId) { + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "alias is not a valid alias: %s", m.AssetId) + } + } else { + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "invalid asset type: %s", m.AssetType) + } + + if _, err := sdk.AccAddressFromBech32(m.Participant); err != nil { + return errorsmod.Wrap(gerrc.ErrInvalidArgument, "participant is not a valid bech32 account address") + } + + return nil +} + +// GetSigners returns the required signers for the MsgCompleteSellOrder. +func (m *MsgCompleteSellOrder) GetSigners() []sdk.AccAddress { + participant, err := sdk.AccAddressFromBech32(m.Participant) + if err != nil { + panic(err) + } + return []sdk.AccAddress{participant} +} + +// Route returns the message router key for the MsgCompleteSellOrder. +func (m *MsgCompleteSellOrder) Route() string { + return RouterKey +} + +// Type returns the message type for the MsgCompleteSellOrder. +func (m *MsgCompleteSellOrder) Type() string { + return TypeMsgCompleteSellOrder +} + +// GetSignBytes returns the raw bytes for the MsgCompleteSellOrder. +func (m *MsgCompleteSellOrder) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(m) + return sdk.MustSortJSON(bz) +} diff --git a/x/dymns/types/msg_complete_sell_order_test.go b/x/dymns/types/msg_complete_sell_order_test.go new file mode 100644 index 000000000..7e34add8e --- /dev/null +++ b/x/dymns/types/msg_complete_sell_order_test.go @@ -0,0 +1,115 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMsgCompleteSellOrder_ValidateBasic(t *testing.T) { + //goland:noinspection SpellCheckingInspection + tests := []struct { + name string + assetId string + assetType AssetType + participant string + wantErr bool + wantErrContains string + }{ + { + name: "pass - (Name) valid", + assetId: "my-name", + assetType: TypeName, + participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + }, + { + name: "pass - (Alias) valid", + assetId: "alias", + assetType: TypeAlias, + participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + }, + { + name: "fail - (Name) not allow empty name", + assetId: "", + assetType: TypeName, + participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + wantErr: true, + wantErrContains: "name is not a valid dym name", + }, + { + name: "fail - (Alias) not allow empty alias", + assetId: "", + assetType: TypeAlias, + participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + wantErr: true, + wantErrContains: "alias is not a valid alias", + }, + { + name: "fail - (Name) not allow invalid name", + assetId: "-my-name", + assetType: TypeName, + participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + wantErr: true, + wantErrContains: "name is not a valid dym name", + }, + { + name: "fail - (Alias) not allow invalid alias", + assetId: "bad-alias", + assetType: TypeAlias, + participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + wantErr: true, + wantErrContains: "alias is not a valid alias", + }, + { + name: "fail - invalid participant", + assetId: "my-name", + assetType: TypeName, + participant: "dym1fl48vsnmsdzcv85q5", + wantErr: true, + wantErrContains: "participant is not a valid bech32 account address", + }, + { + name: "fail - missing participant", + assetId: "my-name", + assetType: TypeName, + participant: "", + wantErr: true, + wantErrContains: "participant is not a valid bech32 account address", + }, + { + name: "fail - participant must be dym1", + assetId: "my-name", + assetType: TypeName, + participant: "nim1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3pklgjx", + wantErr: true, + wantErrContains: "participant is not a valid bech32 account address", + }, + { + name: "fail - not supported asset type", + assetId: "asset", + assetType: AssetType_AT_UNKNOWN, + participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + wantErr: true, + wantErrContains: "invalid asset type", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgCompleteSellOrder{ + AssetId: tt.assetId, + AssetType: tt.assetType, + Participant: tt.participant, + } + + err := m.ValidateBasic() + if tt.wantErr { + require.NotEmpty(t, tt.wantErrContains, "mis-configured test case") + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErrContains) + return + } + + require.NoError(t, err) + }) + } +} diff --git a/x/dymns/types/msgs.go b/x/dymns/types/msgs.go index ca2b299ab..932c393d2 100644 --- a/x/dymns/types/msgs.go +++ b/x/dymns/types/msgs.go @@ -19,6 +19,8 @@ const ( TypeMsgPlaceSellOrder = "place_sell_order" // TypeMsgCancelSellOrder is type for MsgCancelSellOrder. TypeMsgCancelSellOrder = "cancel_sell_order" + // TypeMsgCompleteSellOrder is type for MsgCompleteSellOrder. + TypeMsgCompleteSellOrder = "complete_sell_order" // TypeMsgPurchaseOrder is type for MsgPurchaseOrder. TypeMsgPurchaseOrder = "purchase_order" diff --git a/x/dymns/types/msgs_test.go b/x/dymns/types/msgs_test.go index 37c768028..057c599a5 100644 --- a/x/dymns/types/msgs_test.go +++ b/x/dymns/types/msgs_test.go @@ -36,6 +36,9 @@ func TestMsgs_Signers(t *testing.T) { &MsgCancelSellOrder{ Owner: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", }, + &MsgCompleteSellOrder{ + Participant: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", + }, &MsgPurchaseOrder{ Buyer: "dym1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38x9fue", }, @@ -65,6 +68,7 @@ func TestMsgs_Signers(t *testing.T) { &MsgUpdateDetails{}, &MsgPlaceSellOrder{}, &MsgCancelSellOrder{}, + &MsgCompleteSellOrder{}, &MsgPurchaseOrder{}, &MsgPlaceBuyOrder{}, &MsgCancelBuyOrder{}, @@ -90,6 +94,7 @@ func TestMsgs_ImplementLegacyMsg(t *testing.T) { &MsgUpdateDetails{}, &MsgPlaceSellOrder{}, &MsgCancelSellOrder{}, + &MsgCompleteSellOrder{}, &MsgPurchaseOrder{}, &MsgPlaceBuyOrder{}, &MsgCancelBuyOrder{}, @@ -112,6 +117,7 @@ func TestMsgs_Type(t *testing.T) { require.Equal(t, "update_details", (&MsgUpdateDetails{}).Type()) require.Equal(t, "place_sell_order", (&MsgPlaceSellOrder{}).Type()) require.Equal(t, "cancel_sell_order", (&MsgCancelSellOrder{}).Type()) + require.Equal(t, "complete_sell_order", (&MsgCompleteSellOrder{}).Type()) require.Equal(t, "purchase_order", (&MsgPurchaseOrder{}).Type()) require.Equal(t, "place_buy_order", (&MsgPlaceBuyOrder{}).Type()) require.Equal(t, "cancel_buy_order", (&MsgCancelBuyOrder{}).Type()) diff --git a/x/dymns/types/params.go b/x/dymns/types/params.go index 5a30848cf..0bb88a665 100644 --- a/x/dymns/types/params.go +++ b/x/dymns/types/params.go @@ -71,9 +71,10 @@ func DefaultPriceParams() PriceParams { sdk.NewInt(5 /* DYM */).MulRaw(1e18), // 10+ letters }, - PriceExtends: sdk.NewInt(5 /* DYM */).MulRaw(1e18), - PriceDenom: params.BaseDenom, - MinOfferPrice: sdk.NewInt(10 /* DYM */).MulRaw(1e18), + PriceExtends: sdk.NewInt(5 /* DYM */).MulRaw(1e18), + PriceDenom: params.BaseDenom, + MinOfferPrice: sdk.NewInt(10 /* DYM */).MulRaw(1e18), + MinBidIncrementPercent: 1, } } @@ -91,7 +92,7 @@ func DefaultChainsParams() ChainsParams { }, { ChainId: "cosmoshub-4", - Aliases: []string{"cosmos"}, + Aliases: []string{"cosmos", "cosmoshub"}, }, { ChainId: "osmosis-1", @@ -272,6 +273,11 @@ func validatePriceParams(i interface{}) error { return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "min-offer-price is must be at least %s%s", MinPriceValue, m.PriceDenom) } + const maxMinBidIncrementPercent = 10 + if m.MinBidIncrementPercent > maxMinBidIncrementPercent { + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "min-bid-increment-percent cannot be more than %d: %d", maxMinBidIncrementPercent, m.MinBidIncrementPercent) + } + return nil } @@ -415,13 +421,19 @@ func validateMiscParams(i interface{}) error { return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "end epoch hook identifier: %v", err) } - const minGracePeriodDuration = 30 * 24 * time.Hour + const minGracePeriodDuration = 30 * // number of days + 24 * time.Hour // hours per day if m.GracePeriodDuration < minGracePeriodDuration { return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "grace period duration cannot be less than: %s", minGracePeriodDuration) } + const maxSellOrderDuration = 7 * // number of days + 24 * time.Hour // hours per day if m.SellOrderDuration <= 0 { return errorsmod.Wrap(gerrc.ErrInvalidArgument, "Sell Orders duration can not be zero") + } else if m.SellOrderDuration > maxSellOrderDuration { + // Sell Order duration cannot be too high because in increase the store size, potential causing DDoS + return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "Sell Orders duration cannot be more than: %s", maxSellOrderDuration) } return nil diff --git a/x/dymns/types/params.pb.go b/x/dymns/types/params.pb.go index 33fd3ec9b..be60ddee3 100644 --- a/x/dymns/types/params.pb.go +++ b/x/dymns/types/params.pb.go @@ -113,6 +113,9 @@ type PriceParams struct { // Mostly used to prevent spamming and abusing store with low price offers, // so the value should not be so low. MinOfferPrice github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=min_offer_price,json=minOfferPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"min_offer_price" yaml:"min_offer_price"` + // min_bid_increment_percent is the minimum percent raised compare to previous bid of a Sell-Order. + // The valid range from 0% to 100%, but capped at 10%. + MinBidIncrementPercent uint32 `protobuf:"varint,6,opt,name=min_bid_increment_percent,json=minBidIncrementPercent,proto3" json:"min_bid_increment_percent,omitempty" yaml:"min_bid_increment_percent"` } func (m *PriceParams) Reset() { *m = PriceParams{} } @@ -155,6 +158,13 @@ func (m *PriceParams) GetPriceDenom() string { return "" } +func (m *PriceParams) GetMinBidIncrementPercent() uint32 { + if m != nil { + return m.MinBidIncrementPercent + } + return 0 +} + // ChainsParams defines setting for prioritized aliases mapping. type ChainsParams struct { // aliases_of_chain_ids is set of chain-ids and their corresponding aliases, @@ -274,8 +284,7 @@ type MiscParams struct { // To be used to stop trading of Dym-Name when needed. EnableTradingName bool `protobuf:"varint,4,opt,name=enable_trading_name,json=enableTradingName,proto3" json:"enable_trading_name,omitempty"` // enable_trading_alias is the flag to enable trading of Alias. - // To be used in the future when Alias trading implementation is ready - // or disable trading of Alias when needed. + // To be used to stop trading of Alias when needed. EnableTradingAlias bool `protobuf:"varint,5,opt,name=enable_trading_alias,json=enableTradingAlias,proto3" json:"enable_trading_alias,omitempty"` } @@ -360,57 +369,60 @@ func init() { } var fileDescriptor_6097ac65688a2490 = []byte{ - // 794 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x95, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0xc7, 0xe3, 0xa6, 0xdb, 0x6d, 0x27, 0xed, 0x76, 0x33, 0xc9, 0x96, 0x6c, 0x59, 0xc5, 0xd5, - 0x80, 0x56, 0x59, 0x04, 0x36, 0xdb, 0x3d, 0x20, 0x71, 0x23, 0xec, 0xa2, 0x0d, 0x02, 0x5a, 0x0c, - 0x1c, 0xe0, 0x32, 0x72, 0xec, 0x49, 0x32, 0x4a, 0x3c, 0x63, 0x3c, 0x6e, 0x69, 0x38, 0x73, 0x45, - 0x42, 0x48, 0x48, 0xfc, 0x2f, 0xfc, 0x03, 0x3d, 0xf6, 0x88, 0x38, 0x18, 0xd4, 0xfe, 0x07, 0xf9, - 0x0b, 0x90, 0xdf, 0x8c, 0x53, 0x27, 0xb4, 0x41, 0x3d, 0x25, 0x6f, 0xde, 0x7b, 0x9f, 0xef, 0xfc, - 0x78, 0xef, 0x19, 0x3d, 0x0b, 0xa7, 0x11, 0x13, 0x8a, 0x4b, 0x71, 0x36, 0xfd, 0xd1, 0x9d, 0x1b, - 0xf9, 0x3f, 0xa1, 0xdc, 0xd8, 0x4f, 0xfc, 0x48, 0x39, 0x71, 0x22, 0x53, 0x89, 0x9f, 0x94, 0x43, - 0x9d, 0xb9, 0xe1, 0x40, 0xe8, 0x7e, 0x73, 0x28, 0x87, 0x12, 0x02, 0xdd, 0xfc, 0x9f, 0xce, 0xd9, - 0x6f, 0x07, 0x52, 0x45, 0x52, 0xb9, 0x7d, 0x5f, 0x31, 0xf7, 0xf4, 0x79, 0x9f, 0xa5, 0xfe, 0x73, - 0x37, 0x90, 0x5c, 0x14, 0xfe, 0xa1, 0x94, 0xc3, 0x09, 0x73, 0xc1, 0xea, 0x9f, 0x0c, 0xdc, 0xf0, - 0x24, 0xf1, 0xd3, 0x9c, 0x0a, 0x2b, 0xe4, 0xe7, 0x35, 0xb4, 0x71, 0x0c, 0x9b, 0xc0, 0xdf, 0xa0, - 0x7b, 0x71, 0xc2, 0x03, 0xd6, 0xb2, 0x0e, 0xac, 0x4e, 0xed, 0xf0, 0x99, 0xb3, 0x6a, 0x3b, 0xce, - 0x71, 0x1e, 0xaa, 0x33, 0xbb, 0xcd, 0xf3, 0xcc, 0xae, 0xcc, 0x32, 0x7b, 0x7b, 0xea, 0x47, 0x93, - 0x0f, 0x09, 0x50, 0x88, 0xa7, 0x69, 0xf8, 0x5b, 0xb4, 0x11, 0x8c, 0x7c, 0x2e, 0x54, 0x6b, 0x0d, - 0xb8, 0xef, 0xac, 0xe6, 0x7e, 0x0c, 0xb1, 0x06, 0xfc, 0xc8, 0x80, 0x77, 0x34, 0x58, 0x73, 0x88, - 0x67, 0x80, 0xf8, 0x4b, 0xb4, 0x1e, 0x71, 0x15, 0xb4, 0xaa, 0x00, 0xee, 0xac, 0x06, 0x7f, 0xce, - 0x55, 0x60, 0xb0, 0x0d, 0x83, 0xad, 0x69, 0x6c, 0xce, 0x20, 0x1e, 0xa0, 0xc8, 0xaf, 0xeb, 0xa8, - 0x56, 0x3a, 0x1a, 0x56, 0xe8, 0xa1, 0xf0, 0x23, 0x46, 0xe1, 0x2c, 0x54, 0xa5, 0x2c, 0x56, 0x2d, - 0xeb, 0xa0, 0xda, 0xd9, 0xea, 0xf6, 0x72, 0xc8, 0x5f, 0x99, 0xfd, 0x74, 0xc8, 0xd3, 0xd1, 0x49, - 0xdf, 0x09, 0x64, 0xe4, 0x9a, 0xc7, 0xd0, 0x3f, 0xef, 0xa9, 0x70, 0xec, 0xa6, 0xd3, 0x98, 0x29, - 0xa7, 0x27, 0xd2, 0x59, 0x66, 0xbf, 0xa1, 0xe5, 0x96, 0x79, 0xc4, 0x7b, 0x90, 0x2f, 0x81, 0xea, - 0x57, 0xf9, 0x02, 0x3e, 0x45, 0x75, 0x7f, 0xc2, 0x7d, 0xb5, 0xa0, 0xba, 0x06, 0xaa, 0x9f, 0xde, - 0x59, 0xb5, 0xa5, 0x55, 0xff, 0x03, 0x24, 0xde, 0x2e, 0xac, 0x95, 0x74, 0xc7, 0x68, 0x47, 0x07, - 0xb0, 0xb3, 0x94, 0x89, 0x50, 0xc1, 0xc5, 0x6e, 0x75, 0x3f, 0xb9, 0xb3, 0x66, 0xb3, 0x54, 0x08, - 0x05, 0x8c, 0x78, 0xdb, 0x60, 0xbf, 0xd2, 0x26, 0xfe, 0x00, 0xd5, 0xb4, 0x3f, 0x64, 0x42, 0x46, - 0xad, 0x75, 0x90, 0xda, 0x9b, 0x65, 0x36, 0x2e, 0x27, 0x83, 0x93, 0x78, 0x08, 0xac, 0x97, 0xb9, - 0x81, 0x63, 0xb4, 0x1b, 0x71, 0x41, 0xe5, 0x60, 0xc0, 0x12, 0x7d, 0xa0, 0xd6, 0x3d, 0x48, 0x7e, - 0x7d, 0xe7, 0x7d, 0xee, 0x15, 0x05, 0xb0, 0x80, 0x23, 0xde, 0x4e, 0xc4, 0xc5, 0x51, 0xbe, 0x00, - 0x97, 0x43, 0x7e, 0xb3, 0xd0, 0x76, 0xb9, 0x2e, 0xf1, 0x4f, 0x16, 0x6a, 0xc2, 0xe5, 0x31, 0x45, - 0xe5, 0x80, 0x42, 0x39, 0x52, 0x1e, 0xea, 0xd2, 0xa8, 0x1d, 0x3a, 0xab, 0x2b, 0xf1, 0x23, 0x9d, - 0x79, 0x34, 0x00, 0x66, 0x2f, 0xec, 0xbe, 0x65, 0xea, 0xf1, 0xcd, 0xd2, 0x53, 0x2d, 0x91, 0x89, - 0x57, 0xf7, 0x97, 0xd2, 0x14, 0x89, 0xd1, 0xc3, 0x65, 0x16, 0x76, 0xd0, 0x66, 0x91, 0x04, 0x8d, - 0xbc, 0xd5, 0x6d, 0xcc, 0x32, 0x7b, 0xb7, 0xd4, 0x40, 0x94, 0x87, 0xc4, 0xbb, 0x1f, 0x98, 0xf8, - 0x77, 0xd1, 0x7d, 0x03, 0x36, 0x15, 0x86, 0x67, 0x99, 0xfd, 0x60, 0x61, 0x23, 0xc4, 0x2b, 0x42, - 0xc8, 0x1f, 0x55, 0x84, 0xae, 0x1b, 0x09, 0x53, 0xf4, 0x98, 0x89, 0x90, 0xb2, 0x58, 0x06, 0x23, - 0x3a, 0x92, 0x72, 0x4c, 0x79, 0xc8, 0x44, 0xca, 0x07, 0x9c, 0x25, 0x46, 0xfd, 0xed, 0x59, 0x66, - 0x1f, 0x68, 0xdc, 0xad, 0xa1, 0xc4, 0xdb, 0x63, 0x22, 0x7c, 0x95, 0xbb, 0x5e, 0x4b, 0x39, 0xee, - 0xcd, 0x1d, 0xf8, 0x07, 0xf4, 0x68, 0x98, 0xf8, 0x01, 0xa3, 0x31, 0x4b, 0xb8, 0x0c, 0x69, 0x31, - 0xbd, 0xcc, 0x2c, 0x79, 0xec, 0xe8, 0xf1, 0xe6, 0x14, 0xe3, 0xcd, 0x79, 0x69, 0x02, 0xba, 0x1d, - 0x73, 0xa7, 0x4f, 0xb4, 0xf6, 0x8d, 0x14, 0xf2, 0xfb, 0xdf, 0xb6, 0xe5, 0x35, 0xc0, 0x77, 0x0c, - 0xae, 0x22, 0x1d, 0x7f, 0x8f, 0x1a, 0x8a, 0x4d, 0x26, 0x54, 0x26, 0x21, 0x4b, 0xae, 0x65, 0xab, - 0xff, 0x27, 0xfb, 0xd4, 0xc8, 0xee, 0x6b, 0xd9, 0x1b, 0x18, 0x5a, 0xb4, 0x9e, 0x7b, 0x8e, 0x72, - 0xc7, 0x5c, 0xd2, 0x41, 0x0d, 0x26, 0xfc, 0xfe, 0x84, 0xd1, 0x34, 0xf1, 0x43, 0x2e, 0x86, 0x34, - 0x1f, 0x0b, 0xd0, 0x18, 0x9b, 0x5e, 0x5d, 0xbb, 0xbe, 0xd6, 0x9e, 0x2f, 0xfc, 0x88, 0xe1, 0xf7, - 0x51, 0x73, 0x29, 0x1e, 0x5e, 0x09, 0x9a, 0x61, 0xd3, 0xc3, 0x0b, 0x09, 0x50, 0x26, 0xdd, 0xcf, - 0xce, 0x2f, 0xdb, 0xd6, 0xc5, 0x65, 0xdb, 0xfa, 0xe7, 0xb2, 0x6d, 0xfd, 0x72, 0xd5, 0xae, 0x5c, - 0x5c, 0xb5, 0x2b, 0x7f, 0x5e, 0xb5, 0x2b, 0xdf, 0x1d, 0x96, 0x5a, 0xe6, 0x96, 0x0f, 0xd6, 0xe9, - 0x0b, 0xf7, 0xcc, 0x7c, 0xb5, 0xa0, 0x85, 0xfa, 0x1b, 0x70, 0xfa, 0x17, 0xff, 0x06, 0x00, 0x00, - 0xff, 0xff, 0x7f, 0x92, 0xf0, 0x4d, 0xe2, 0x06, 0x00, 0x00, + // 834 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x95, 0x4f, 0x6f, 0x1c, 0x35, + 0x18, 0xc6, 0x33, 0x49, 0x9a, 0x26, 0xde, 0xa4, 0x69, 0xbc, 0xdb, 0xb0, 0x0d, 0xd5, 0x4e, 0x64, + 0x50, 0xb5, 0x45, 0x30, 0x43, 0xd3, 0x03, 0x12, 0x37, 0x86, 0x16, 0x75, 0x11, 0x90, 0x30, 0xc0, + 0x01, 0x2e, 0xd6, 0xec, 0xd8, 0xbb, 0xb1, 0xb2, 0x63, 0x0f, 0xe3, 0x49, 0x48, 0x38, 0x73, 0x45, + 0xe2, 0x82, 0xc4, 0x77, 0xe1, 0x0b, 0xf4, 0xd8, 0x23, 0xe2, 0x30, 0xa0, 0xe4, 0x1b, 0x2c, 0x5f, + 0x00, 0xf9, 0xb5, 0x27, 0x99, 0x2c, 0x49, 0x50, 0x4e, 0xbb, 0xaf, 0xdf, 0xe7, 0xfd, 0x3d, 0xfe, + 0xf3, 0xda, 0x83, 0x9e, 0xb0, 0x93, 0x8c, 0x4b, 0x2d, 0x94, 0x3c, 0x3e, 0xf9, 0x31, 0x3c, 0x0f, + 0xcc, 0x3f, 0xa9, 0xc3, 0x3c, 0x29, 0x92, 0x4c, 0x07, 0x79, 0xa1, 0x4a, 0x85, 0x1f, 0x35, 0xa5, + 0xc1, 0x79, 0x10, 0x80, 0x74, 0xab, 0x33, 0x56, 0x63, 0x05, 0xc2, 0xd0, 0xfc, 0xb3, 0x35, 0x5b, + 0xbd, 0x54, 0xe9, 0x4c, 0xe9, 0x70, 0x98, 0x68, 0x1e, 0x1e, 0x3d, 0x1d, 0xf2, 0x32, 0x79, 0x1a, + 0xa6, 0x4a, 0xc8, 0x3a, 0x3f, 0x56, 0x6a, 0x3c, 0xe1, 0x21, 0x44, 0xc3, 0xc3, 0x51, 0xc8, 0x0e, + 0x8b, 0xa4, 0x34, 0x54, 0x18, 0x21, 0x3f, 0xcf, 0xa3, 0xa5, 0x3d, 0x98, 0x04, 0xfe, 0x06, 0xdd, + 0xc9, 0x0b, 0x91, 0xf2, 0xae, 0xb7, 0xed, 0xf5, 0x5b, 0x3b, 0x4f, 0x82, 0x9b, 0xa6, 0x13, 0xec, + 0x19, 0xa9, 0xad, 0x8c, 0x3a, 0xaf, 0x2a, 0x7f, 0x6e, 0x5a, 0xf9, 0xab, 0x27, 0x49, 0x36, 0xf9, + 0x90, 0x00, 0x85, 0xc4, 0x96, 0x86, 0xbf, 0x45, 0x4b, 0xe9, 0x7e, 0x22, 0xa4, 0xee, 0xce, 0x03, + 0xf7, 0x9d, 0x9b, 0xb9, 0x1f, 0x83, 0xd6, 0x81, 0x1f, 0x38, 0xf0, 0x9a, 0x05, 0x5b, 0x0e, 0x89, + 0x1d, 0x10, 0x7f, 0x89, 0x16, 0x33, 0xa1, 0xd3, 0xee, 0x02, 0x80, 0xfb, 0x37, 0x83, 0x3f, 0x17, + 0x3a, 0x75, 0xd8, 0xb6, 0xc3, 0xb6, 0x2c, 0xd6, 0x30, 0x48, 0x0c, 0x28, 0xf2, 0xcf, 0x22, 0x6a, + 0x35, 0x96, 0x86, 0x35, 0xba, 0x2f, 0x93, 0x8c, 0x53, 0x58, 0x0b, 0xd5, 0x25, 0xcf, 0x75, 0xd7, + 0xdb, 0x5e, 0xe8, 0xaf, 0x44, 0x03, 0x03, 0xf9, 0xb3, 0xf2, 0x1f, 0x8f, 0x45, 0xb9, 0x7f, 0x38, + 0x0c, 0x52, 0x95, 0x85, 0xee, 0x30, 0xec, 0xcf, 0x7b, 0x9a, 0x1d, 0x84, 0xe5, 0x49, 0xce, 0x75, + 0x30, 0x90, 0xe5, 0xb4, 0xf2, 0xdf, 0xb0, 0x76, 0xb3, 0x3c, 0x12, 0xdf, 0x33, 0x43, 0xe0, 0xfa, + 0x95, 0x19, 0xc0, 0x47, 0x68, 0x23, 0x99, 0x88, 0x44, 0x5f, 0x72, 0x9d, 0x07, 0xd7, 0x4f, 0x6f, + 0xed, 0xda, 0xb5, 0xae, 0xff, 0x01, 0x92, 0x78, 0x1d, 0xc6, 0x1a, 0xbe, 0x07, 0x68, 0xcd, 0x0a, + 0xf8, 0x71, 0xc9, 0x25, 0xd3, 0xb0, 0xb1, 0x2b, 0xd1, 0x27, 0xb7, 0xf6, 0xec, 0x34, 0x1a, 0xa1, + 0x86, 0x91, 0x78, 0x15, 0xe2, 0x17, 0x36, 0xc4, 0x1f, 0xa0, 0x96, 0xcd, 0x33, 0x2e, 0x55, 0xd6, + 0x5d, 0x04, 0xab, 0xcd, 0x69, 0xe5, 0xe3, 0x66, 0x31, 0x24, 0x49, 0x8c, 0x20, 0x7a, 0x6e, 0x02, + 0x9c, 0xa3, 0xf5, 0x4c, 0x48, 0xaa, 0x46, 0x23, 0x5e, 0xd8, 0x05, 0x75, 0xef, 0x40, 0xf1, 0xcb, + 0x5b, 0xcf, 0x73, 0xb3, 0x6e, 0x80, 0x4b, 0x38, 0x12, 0xaf, 0x65, 0x42, 0xee, 0x9a, 0x01, 0xd8, + 0x1c, 0x4c, 0xd1, 0x43, 0x23, 0x19, 0x0a, 0x46, 0x85, 0x4c, 0x0b, 0x9e, 0x71, 0x59, 0xd2, 0x9c, + 0x17, 0x29, 0x97, 0x65, 0x77, 0x69, 0xdb, 0xeb, 0xaf, 0x45, 0x6f, 0x4f, 0x2b, 0x7f, 0xfb, 0x82, + 0x76, 0xa5, 0x94, 0xc4, 0x9b, 0x99, 0x90, 0x91, 0x60, 0x83, 0x3a, 0xb3, 0xe7, 0x12, 0xbf, 0x7a, + 0x68, 0xb5, 0xd9, 0xf8, 0xf8, 0x27, 0x0f, 0x75, 0xe0, 0x74, 0xb8, 0xa6, 0x6a, 0x44, 0xa1, 0xdf, + 0xa9, 0x60, 0xb6, 0xf7, 0x5a, 0x3b, 0xc1, 0xcd, 0xad, 0xfe, 0x91, 0xad, 0xdc, 0x1d, 0x01, 0x73, + 0xc0, 0xa2, 0xb7, 0x5c, 0xc3, 0xbf, 0xd9, 0xe8, 0x85, 0x19, 0x32, 0x89, 0x37, 0x92, 0x99, 0x32, + 0x4d, 0x72, 0x74, 0x7f, 0x96, 0x85, 0x03, 0xb4, 0x5c, 0x17, 0xc1, 0x4b, 0xb1, 0x12, 0xb5, 0xa7, + 0x95, 0xbf, 0xde, 0xb8, 0xa1, 0x54, 0x30, 0x12, 0xdf, 0x4d, 0x9d, 0xfe, 0x5d, 0x74, 0xd7, 0x81, + 0x5d, 0x0b, 0xe3, 0x69, 0xe5, 0xdf, 0xbb, 0x34, 0x11, 0x12, 0xd7, 0x12, 0xf2, 0xfb, 0x02, 0x42, + 0x17, 0x37, 0xd5, 0xec, 0x3c, 0x97, 0x8c, 0xf2, 0x5c, 0xa5, 0xfb, 0x74, 0x5f, 0xa9, 0x03, 0x2a, + 0x18, 0x97, 0xa5, 0x18, 0x09, 0x5e, 0x38, 0xf7, 0xc6, 0xce, 0x5f, 0x2b, 0x25, 0xf1, 0x26, 0x97, + 0xec, 0x85, 0x49, 0xbd, 0x54, 0xea, 0x60, 0x70, 0x9e, 0xc0, 0x3f, 0xa0, 0x07, 0xe3, 0x22, 0x49, + 0xb9, 0x39, 0x23, 0xa1, 0x18, 0xad, 0x9f, 0x47, 0xf7, 0x58, 0x3d, 0x0c, 0xec, 0xfb, 0x19, 0xd4, + 0xef, 0x67, 0xf0, 0xdc, 0x09, 0xa2, 0xbe, 0xdb, 0xd3, 0x47, 0xd6, 0xfb, 0x4a, 0x0a, 0xf9, 0xed, + 0x2f, 0xdf, 0x8b, 0xdb, 0x90, 0xdb, 0x83, 0x54, 0x5d, 0x8e, 0xbf, 0x47, 0x6d, 0xcd, 0x27, 0x13, + 0xaa, 0x0a, 0xc6, 0x8b, 0x0b, 0xdb, 0x85, 0xff, 0xb3, 0x7d, 0xec, 0x6c, 0xb7, 0xac, 0xed, 0x15, + 0x0c, 0x6b, 0xba, 0x61, 0x32, 0xbb, 0x26, 0x71, 0x6e, 0x19, 0xa0, 0x36, 0x97, 0xc9, 0x70, 0xc2, + 0x69, 0x59, 0x24, 0x4c, 0xc8, 0x31, 0x35, 0xef, 0x0e, 0xdc, 0xbc, 0xe5, 0x78, 0xc3, 0xa6, 0xbe, + 0xb6, 0x99, 0x2f, 0x92, 0x8c, 0xe3, 0xf7, 0x51, 0x67, 0x46, 0x0f, 0xa7, 0x04, 0xb7, 0x6d, 0x39, + 0xc6, 0x97, 0x0a, 0xa0, 0x4d, 0xa2, 0xcf, 0x5e, 0x9d, 0xf6, 0xbc, 0xd7, 0xa7, 0x3d, 0xef, 0xef, + 0xd3, 0x9e, 0xf7, 0xcb, 0x59, 0x6f, 0xee, 0xf5, 0x59, 0x6f, 0xee, 0x8f, 0xb3, 0xde, 0xdc, 0x77, + 0x3b, 0x8d, 0x3b, 0x79, 0xcd, 0x17, 0xf1, 0xe8, 0x59, 0x78, 0xec, 0x3e, 0x8b, 0x70, 0x47, 0x87, + 0x4b, 0xb0, 0xfa, 0x67, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xf1, 0x09, 0xc2, 0x5a, 0x43, 0x07, + 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -486,6 +498,11 @@ func (m *PriceParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MinBidIncrementPercent != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinBidIncrementPercent)) + i-- + dAtA[i] = 0x30 + } { size := m.MinOfferPrice.Size() i -= size @@ -738,6 +755,9 @@ func (m *PriceParams) Size() (n int) { } l = m.MinOfferPrice.Size() n += 1 + l + sovParams(uint64(l)) + if m.MinBidIncrementPercent != 0 { + n += 1 + sovParams(uint64(m.MinBidIncrementPercent)) + } return n } @@ -1154,6 +1174,25 @@ func (m *PriceParams) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinBidIncrementPercent", wireType) + } + m.MinBidIncrementPercent = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinBidIncrementPercent |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/dymns/types/params_test.go b/x/dymns/types/params_test.go index 072404640..3a2864642 100644 --- a/x/dymns/types/params_test.go +++ b/x/dymns/types/params_test.go @@ -68,6 +68,12 @@ func TestDefaultPriceParams(t *testing.T) { require.Fail(t, "price should be at least 1 DYM") } }) + + t.Run("ensure min-bid-increment-percent can not be greater than 100%", func(t *testing.T) { + priceParams := DefaultPriceParams() + priceParams.MinBidIncrementPercent = 101 + require.ErrorContains(t, priceParams.Validate(), "min-bid-increment-percent cannot be more than") + }) } func TestDefaultChainsParams(t *testing.T) { @@ -246,6 +252,16 @@ func TestPriceParams_Validate(t *testing.T) { ) }) + t.Run("fail - min_bid_increment_percent can not be greater than 100", func(t *testing.T) { + defaultPriceParams := DefaultPriceParams() + defaultPriceParams.MinBidIncrementPercent = 101 + + require.ErrorContains( + t, defaultPriceParams.Validate(), + "min-bid-increment-percent cannot be more than", + ) + }) + t.Run("fail - invalid type", func(t *testing.T) { require.Error(t, validatePriceParams("hello world")) require.Error(t, validatePriceParams(&PriceParams{}), "not accept pointer") @@ -488,6 +504,15 @@ func TestMiscParams_Validate(t *testing.T) { wantErr: true, wantErrContains: "Sell Orders duration can not be zero", }, + { + name: "fail - days SO duration can not be greater than 7 days", + modifier: func(p MiscParams) MiscParams { + p.SellOrderDuration = 7*24*time.Hour + time.Second + return p + }, + wantErr: true, + wantErrContains: "Sell Orders duration cannot be more than", + }, { name: "fail - days SO duration can not be negative", modifier: func(p MiscParams) MiscParams { diff --git a/x/dymns/types/reverse_lookup_offer_ids.go b/x/dymns/types/reverse_lookup_offer_ids.go deleted file mode 100644 index 0eebe68bc..000000000 --- a/x/dymns/types/reverse_lookup_offer_ids.go +++ /dev/null @@ -1,33 +0,0 @@ -package types - -import "sort" - -// Distinct returns a new list of offer-ids with duplicates removed. -// Result will be sorted. -func (m ReverseLookupBuyOrderIds) Distinct() (distinct ReverseLookupBuyOrderIds) { - return ReverseLookupBuyOrderIds{ - OrderIds: StringList(m.OrderIds).Distinct(), - } -} - -// Combine merges the offer-ids from the current list and the other list. -// Result will be sorted distinct. -func (m ReverseLookupBuyOrderIds) Combine(other ReverseLookupBuyOrderIds) ReverseLookupBuyOrderIds { - return ReverseLookupBuyOrderIds{ - OrderIds: StringList(m.OrderIds).Combine(other.OrderIds), - }.Distinct() -} - -// Exclude removes the offer-ids from the current list that are in the toBeExcluded list. -// Result will be sorted distinct. -func (m ReverseLookupBuyOrderIds) Exclude(toBeExcluded ReverseLookupBuyOrderIds) (afterExcluded ReverseLookupBuyOrderIds) { - return ReverseLookupBuyOrderIds{ - OrderIds: StringList(m.OrderIds).Exclude(toBeExcluded.OrderIds), - }.Distinct() -} - -// Sort sorts the offer-ids in the list. -func (m ReverseLookupBuyOrderIds) Sort() ReverseLookupBuyOrderIds { - sort.Strings(m.OrderIds) - return m -} diff --git a/x/dymns/types/sell_order.go b/x/dymns/types/sell_order.go index c5e822159..10cf8683c 100644 --- a/x/dymns/types/sell_order.go +++ b/x/dymns/types/sell_order.go @@ -2,8 +2,6 @@ package types import ( "fmt" - "slices" - "sort" errorsmod "cosmossdk.io/errors" @@ -13,11 +11,6 @@ import ( dymnsutils "github.com/dymensionxyz/dymension/v3/x/dymns/utils" ) -// GetIdentity returns the unique identity of the SO -func (m *SellOrder) GetIdentity() string { - return fmt.Sprintf("%s|%d|%d", m.AssetId, m.AssetType, m.ExpireAt) -} - // HasSetSellPrice returns true if the sell price is set func (m *SellOrder) HasSetSellPrice() bool { return m.SellPrice != nil && !m.SellPrice.Amount.IsNil() && !m.SellPrice.IsZero() @@ -185,72 +178,3 @@ func (m SellOrder) GetSdkEvent(actionName string) sdk.Event { sdk.NewAttribute(AttributeKeySoActionName, actionName), ) } - -func (m ActiveSellOrdersExpiration) Validate() error { - if len(m.Records) > 0 { - uniqueName := make(map[string]bool) - // Describe usage of Go Map: only used for validation - allNames := make([]string, len(m.Records)) - - for i, record := range m.Records { - if record.ExpireAt < 1 { - return errorsmod.Wrap(gerrc.ErrInvalidArgument, "active SO expiry is empty") - } - - if _, duplicated := uniqueName[record.AssetId]; duplicated { - return errorsmod.Wrapf(gerrc.ErrInvalidArgument, "active SO is not unique: %s", record.AssetId) - } - - uniqueName[record.AssetId] = true - allNames[i] = record.AssetId - } - - if !sort.StringsAreSorted(allNames) { - return errorsmod.Wrap(gerrc.ErrInvalidArgument, "active SO names are not sorted") - } - } - - return nil -} - -func (m *ActiveSellOrdersExpiration) Sort() { - if len(m.Records) < 2 { - return - } - - sort.Slice(m.Records, func(i, j int) bool { - return m.Records[i].AssetId < m.Records[j].AssetId - }) -} - -func (m *ActiveSellOrdersExpiration) Add(assetId string, expiry int64) { - newRecord := ActiveSellOrdersExpirationRecord{AssetId: assetId, ExpireAt: expiry} - - if len(m.Records) < 1 { - m.Records = []ActiveSellOrdersExpirationRecord{newRecord} - return - } - - foundAtIdx := -1 - - for i, record := range m.Records { - if record.AssetId == assetId { - foundAtIdx = i - break - } - } - - if foundAtIdx > -1 { - m.Records[foundAtIdx].ExpireAt = expiry - } else { - m.Records = append(m.Records, newRecord) - } - - m.Sort() -} - -func (m *ActiveSellOrdersExpiration) Remove(assetId string) { - m.Records = slices.DeleteFunc(m.Records, func(r ActiveSellOrdersExpirationRecord) bool { - return r.AssetId == assetId - }) -} diff --git a/x/dymns/types/sell_order_test.go b/x/dymns/types/sell_order_test.go index 210e3d835..eedb7f5b6 100644 --- a/x/dymns/types/sell_order_test.go +++ b/x/dymns/types/sell_order_test.go @@ -11,21 +11,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestSellOrder_GetIdentity(t *testing.T) { - nameSo := &SellOrder{ - AssetId: "my-name", - AssetType: TypeName, - ExpireAt: 1234, - } - require.Equal(t, "my-name|1|1234", nameSo.GetIdentity()) - aliasSo := &SellOrder{ - AssetId: "alias", - AssetType: TypeAlias, - ExpireAt: 1234, - } - require.Equal(t, "alias|2|1234", aliasSo.GetIdentity()) -} - func TestSellOrder_HasSetSellPrice(t *testing.T) { require.False(t, (&SellOrder{ SellPrice: nil, @@ -700,306 +685,6 @@ func TestSellOrder_GetSdkEvent(t *testing.T) { }) } -func TestActiveSellOrdersExpiration_Validate(t *testing.T) { - tests := []struct { - name string - records []ActiveSellOrdersExpirationRecord - wantErr bool - wantErrContains string - }{ - { - name: "pass", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, {AssetId: "b", ExpireAt: 1}, - }, - wantErr: false, - }, - { - name: "fail - name must be unique", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, {AssetId: "a", ExpireAt: 1}, - }, - wantErr: true, - wantErrContains: "active SO is not unique", - }, - { - name: "pass - expire at can be duplicated", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, {AssetId: "b", ExpireAt: 2}, - }, - wantErr: false, - }, - { - name: "fail - expire at must be > 0", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 0}, {AssetId: "b", ExpireAt: -1}, - }, - wantErr: true, - wantErrContains: "active SO expiry is empty", - }, - { - name: "fail - must be sorted", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 1}, {AssetId: "a", ExpireAt: 1}, - }, - wantErr: true, - wantErrContains: "active SO names are not sorted", - }, - { - name: "pass - empty list", - records: []ActiveSellOrdersExpirationRecord{}, - wantErr: false, - }, - { - name: "pass - nil list", - records: nil, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := ActiveSellOrdersExpiration{ - Records: tt.records, - } - - err := m.Validate() - - if tt.wantErr { - require.NotEmpty(t, tt.wantErrContains, "mis-configured test case") - require.Error(t, err) - require.Contains(t, err.Error(), tt.wantErrContains) - return - } - - require.NoError(t, err) - }) - } -} - -func TestActiveSellOrdersExpiration_Sort(t *testing.T) { - tests := []struct { - name string - records []ActiveSellOrdersExpirationRecord - want []ActiveSellOrdersExpirationRecord - }{ - { - name: "can sort", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 2}, {AssetId: "a", ExpireAt: 2}, - }, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, {AssetId: "b", ExpireAt: 2}, - }, - }, - { - name: "sort by name asc", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 1}, {AssetId: "a", ExpireAt: 2}, {AssetId: "c", ExpireAt: 3}, - }, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, {AssetId: "b", ExpireAt: 1}, {AssetId: "c", ExpireAt: 3}, - }, - }, - { - name: "can sort one", - records: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, - }, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, - }, - }, - { - name: "empty list", - records: []ActiveSellOrdersExpirationRecord{}, - want: []ActiveSellOrdersExpirationRecord{}, - }, - { - name: "nil list", - records: nil, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := ActiveSellOrdersExpiration{ - Records: tt.records, - } - - m.Sort() - - require.Equal(t, tt.want, m.Records) - }) - } -} - -func TestActiveSellOrdersExpiration_Add(t *testing.T) { - tests := []struct { - name string - existing []ActiveSellOrdersExpirationRecord - addName string - addExpiry int64 - want []ActiveSellOrdersExpirationRecord - }{ - { - name: "can add", - existing: []ActiveSellOrdersExpirationRecord{{AssetId: "a", ExpireAt: 1}}, - addName: "b", - addExpiry: 2, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, {AssetId: "b", ExpireAt: 2}, - }, - }, - { - name: "add will perform sort", - existing: []ActiveSellOrdersExpirationRecord{{AssetId: "b", ExpireAt: 1}}, - addName: "a", - addExpiry: 2, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 2}, {AssetId: "b", ExpireAt: 1}, - }, - }, - { - name: "add can override existing", - existing: []ActiveSellOrdersExpirationRecord{{AssetId: "b", ExpireAt: 1}}, - addName: "b", - addExpiry: 2, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 2}, - }, - }, - { - name: "add can override existing", - existing: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 1}, {AssetId: "c", ExpireAt: 1}, {AssetId: "d", ExpireAt: 1}, - }, - addName: "c", - addExpiry: 2, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 1}, {AssetId: "c", ExpireAt: 2}, {AssetId: "d", ExpireAt: 1}, - }, - }, - { - name: "can add to nil", - existing: nil, - addName: "a", - addExpiry: 1, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, - }, - }, - { - name: "can add to empty", - existing: []ActiveSellOrdersExpirationRecord{}, - addName: "a", - addExpiry: 1, - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := &ActiveSellOrdersExpiration{ - Records: tt.existing, - } - m.Add(tt.addName, tt.addExpiry) - - require.Equal(t, tt.want, m.Records) - }) - } -} - -func TestActiveSellOrdersExpiration_Remove(t *testing.T) { - tests := []struct { - name string - existing []ActiveSellOrdersExpirationRecord - removeName string - want []ActiveSellOrdersExpirationRecord - }{ - { - name: "can remove", - existing: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, {AssetId: "b", ExpireAt: 1}, - }, - removeName: "a", - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 1}, - }, - }, - { - name: "remove the last one", - existing: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, - }, - removeName: "a", - want: []ActiveSellOrdersExpirationRecord{}, - }, - { - name: "remove in head", - existing: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, {AssetId: "b", ExpireAt: 1}, {AssetId: "c", ExpireAt: 1}, - }, - removeName: "a", - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "b", ExpireAt: 1}, {AssetId: "c", ExpireAt: 1}, - }, - }, - { - name: "remove in middle", - existing: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, {AssetId: "b", ExpireAt: 1}, {AssetId: "c", ExpireAt: 1}, - }, - removeName: "b", - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, {AssetId: "c", ExpireAt: 1}, - }, - }, - { - name: "remove in tails", - existing: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, {AssetId: "b", ExpireAt: 1}, {AssetId: "c", ExpireAt: 1}, - }, - removeName: "c", - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "a", ExpireAt: 1}, {AssetId: "b", ExpireAt: 1}, - }, - }, - { - name: "remove keep order", - existing: []ActiveSellOrdersExpirationRecord{ - {AssetId: "c", ExpireAt: 1}, {AssetId: "b", ExpireAt: 1}, {AssetId: "a", ExpireAt: 1}, - }, - removeName: "b", - want: []ActiveSellOrdersExpirationRecord{ - {AssetId: "c", ExpireAt: 1}, {AssetId: "a", ExpireAt: 1}, - }, - }, - { - name: "can remove from nil", - existing: nil, - removeName: "a", - want: nil, - }, - { - name: "can remove from empty", - existing: []ActiveSellOrdersExpirationRecord{}, - removeName: "a", - want: []ActiveSellOrdersExpirationRecord{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := &ActiveSellOrdersExpiration{ - Records: tt.existing, - } - m.Remove(tt.removeName) - - require.Equal(t, tt.want, m.Records) - }) - } -} - func requireEventEquals(t *testing.T, event sdk.Event, wantType string, wantAttributePairs ...string) { require.NotNil(t, event) require.True(t, len(wantAttributePairs)%2 == 0, "size of expected attr pairs must be even") diff --git a/x/dymns/types/string_list.go b/x/dymns/types/string_list.go deleted file mode 100644 index 6276f6ac7..000000000 --- a/x/dymns/types/string_list.go +++ /dev/null @@ -1,75 +0,0 @@ -package types - -import "slices" - -// TODO: move this to a sdk-utils package - -// StringList is a list of strings. -// Used to add some operations on the list. -type StringList []string - -// Distinct returns a new list with duplicates removed. -// Result will be sorted. -func (m StringList) Distinct() (distinct StringList) { - uniqueElements := make(map[string]bool) - // Describe usage of Go Map: used to store unique elements, later result will be sorted - defer func() { - distinct.Sort() - }() - - for _, ele := range m { - uniqueElements[ele] = true - } - - distinctElements := make([]string, 0, len(uniqueElements)) - for name := range uniqueElements { - distinctElements = append(distinctElements, name) - } - - distinct = distinctElements - - return -} - -// Combine merges the elements from the current list and the other list. -// Result will be sorted distinct. -func (m StringList) Combine(other StringList) StringList { - return append(m, other...).Distinct() -} - -// Exclude removes the elements from the current list that are in the toBeExcluded list. -// Result will be sorted distinct. -func (m StringList) Exclude(toBeExcluded StringList) (afterExcluded StringList) { - var excludedElements map[string]bool - // Describe usage of Go Map: used to store unique elements, later result will be sorted - defer func() { - afterExcluded.Sort() - }() - - if len(toBeExcluded) > 0 { - excludedElements = make(map[string]bool) - - for _, element := range toBeExcluded { - excludedElements[element] = true - } - - filteredElements := make([]string, 0, len(m)) - for _, element := range m { - if !excludedElements[element] { - filteredElements = append(filteredElements, element) - } - } - - afterExcluded = StringList(filteredElements).Distinct() - } else { - afterExcluded = m - } - - return -} - -// Sort sorts the elements in the list. -func (m StringList) Sort() StringList { - slices.Sort(m) - return m -} diff --git a/x/dymns/types/string_list_test.go b/x/dymns/types/string_list_test.go deleted file mode 100644 index 73514a017..000000000 --- a/x/dymns/types/string_list_test.go +++ /dev/null @@ -1,358 +0,0 @@ -package types - -import ( - "sort" - "testing" - - "github.com/stretchr/testify/require" -) - -func Test_stringList_Distinct(t *testing.T) { - tests := []struct { - name string - provided StringList - wantDistinct StringList - }{ - { - name: "distinct", - provided: []string{"a", "b", "b", "a", "c", "d"}, - wantDistinct: []string{"a", "b", "c", "d"}, - }, - { - name: "distinct of single", - provided: []string{"a"}, - wantDistinct: []string{"a"}, - }, - { - name: "empty", - provided: []string{}, - wantDistinct: []string{}, - }, - { - name: "nil", - provided: nil, - wantDistinct: []string{}, - }, - { - name: "result must be sorted", - provided: []string{"d", "c", "a", "b", "a", "b", "e"}, - wantDistinct: []string{"a", "b", "c", "d", "e"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - distinct := tt.provided.Distinct() - want := tt.wantDistinct - - sort.Strings(distinct) - sort.Strings(want) - - require.Equal(t, want, distinct) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupDymNames{ - DymNames: tt.provided, - } - distinct := m.Distinct().DymNames - want := []string(tt.wantDistinct) - - sort.Strings(distinct) - sort.Strings(want) - - require.Equal(t, want, distinct) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupBuyOrderIds{ - OrderIds: tt.provided, - } - distinct := m.Distinct().OrderIds - want := []string(tt.wantDistinct) - - sort.Strings(distinct) - sort.Strings(want) - - require.Equal(t, want, distinct) - }) - } -} - -func Test_stringList_Combine(t *testing.T) { - tests := []struct { - name string - provided StringList - others StringList - wantCombined StringList - }{ - { - name: "combined", - provided: []string{"a", "b"}, - others: []string{"c", "d"}, - wantCombined: []string{"a", "b", "c", "d"}, - }, - { - name: "combined, distinct", - provided: []string{"a", "b"}, - others: []string{"b", "c", "d"}, - wantCombined: []string{"a", "b", "c", "d"}, - }, - { - name: "combined, distinct", - provided: []string{"a"}, - others: []string{"a"}, - wantCombined: []string{"a"}, - }, - { - name: "combine empty with other", - provided: nil, - others: []string{"a"}, - wantCombined: []string{"a"}, - }, - { - name: "combine empty with other", - provided: []string{"a"}, - others: nil, - wantCombined: []string{"a"}, - }, - { - name: "combine empty with other", - provided: nil, - others: []string{"a", "b"}, - wantCombined: []string{"a", "b"}, - }, - { - name: "combine with other empty", - provided: []string{"a", "b"}, - others: nil, - wantCombined: []string{"a", "b"}, - }, - { - name: "distinct source", - provided: []string{"a", "b", "a"}, - others: []string{"c", "c", "d"}, - wantCombined: []string{"a", "b", "c", "d"}, - }, - { - name: "both empty", - provided: []string{}, - others: []string{}, - wantCombined: []string{}, - }, - { - name: "both nil", - provided: nil, - others: nil, - wantCombined: []string{}, - }, - { - name: "result must be sorted", - provided: []string{"d", "c", "a", "b"}, - others: []string{"a", "b", "e"}, - wantCombined: []string{"a", "b", "c", "d", "e"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - combined := tt.provided.Combine(tt.others) - want := tt.wantCombined - - sort.Strings(combined) - sort.Strings(want) - - require.Equal(t, want, combined) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupDymNames{ - DymNames: tt.provided, - } - other := ReverseLookupDymNames{ - DymNames: tt.others, - } - combined := m.Combine(other).DymNames - want := []string(tt.wantCombined) - - sort.Strings(combined) - sort.Strings(want) - - require.Equal(t, want, combined) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupBuyOrderIds{ - OrderIds: tt.provided, - } - other := ReverseLookupBuyOrderIds{ - OrderIds: tt.others, - } - combined := m.Combine(other).OrderIds - want := []string(tt.wantCombined) - - sort.Strings(combined) - sort.Strings(want) - - require.Equal(t, want, combined) - }) - } -} - -func Test_stringList_Exclude(t *testing.T) { - tests := []struct { - name string - provided StringList - toBeExcluded StringList - want StringList - }{ - { - name: "exclude", - provided: []string{"a", "b", "c", "d"}, - toBeExcluded: []string{"b", "d"}, - want: []string{"a", "c"}, - }, - { - name: "exclude all", - provided: []string{"a", "b", "c", "d"}, - toBeExcluded: []string{"d", "c", "b", "a"}, - want: []string{}, - }, - { - name: "exclude none", - provided: []string{"a", "b", "c", "d"}, - toBeExcluded: []string{}, - want: []string{"a", "b", "c", "d"}, - }, - { - name: "exclude nil", - provided: []string{"a", "b", "c", "d"}, - toBeExcluded: []string{}, - want: []string{"a", "b", "c", "d"}, - }, - { - name: "none exclude", - provided: []string{}, - toBeExcluded: []string{"a", "b", "c", "d"}, - want: []string{}, - }, - { - name: "nil exclude", - provided: nil, - toBeExcluded: []string{"a", "b", "c", "d"}, - want: []string{}, - }, - { - name: "distinct after exclude", - provided: []string{"a", "a", "b"}, - toBeExcluded: []string{"b", "d"}, - want: []string{"a"}, - }, - { - name: "exclude partial", - provided: []string{"a", "b", "c"}, - toBeExcluded: []string{"b", "c", "d"}, - want: []string{"a"}, - }, - { - name: "result must be sorted", - provided: []string{"d", "c", "a", "b", "a", "b", "e"}, - toBeExcluded: []string{"e", "f"}, - want: []string{"a", "b", "c", "d"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - combined := tt.provided.Exclude(tt.toBeExcluded) - want := tt.want - - sort.Strings(combined) - sort.Strings(want) - - require.Equal(t, want, combined) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupDymNames{ - DymNames: tt.provided, - } - other := ReverseLookupDymNames{ - DymNames: tt.toBeExcluded, - } - combined := m.Exclude(other).DymNames - want := []string(tt.want) - - sort.Strings(combined) - sort.Strings(want) - - require.Equal(t, want, combined) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupBuyOrderIds{ - OrderIds: tt.provided, - } - other := ReverseLookupBuyOrderIds{ - OrderIds: tt.toBeExcluded, - } - combined := m.Exclude(other).OrderIds - want := []string(tt.want) - - sort.Strings(combined) - sort.Strings(want) - - require.Equal(t, want, combined) - }) - } -} - -func Test_stringList_Sort(t *testing.T) { - tests := []struct { - name string - provided StringList - want StringList - }{ - { - name: "can sort", - provided: []string{"b", "a", "c"}, - want: []string{"a", "b", "c"}, - }, - { - name: "can sort single", - provided: []string{"a"}, - want: []string{"a"}, - }, - { - name: "sort will not try distinct", - provided: []string{"b", "a", "c", "a"}, - want: []string{"a", "a", "b", "c"}, - }, - { - name: "nil", - provided: nil, - want: nil, - }, - { - name: "empty", - provided: []string{}, - want: []string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.want, tt.provided.Sort()) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupDymNames{ - DymNames: tt.provided, - } - require.Equal(t, []string(tt.want), m.Sort().DymNames) - }) - - t.Run(tt.name, func(t *testing.T) { - m := ReverseLookupBuyOrderIds{ - OrderIds: tt.provided, - } - require.Equal(t, []string(tt.want), m.Sort().OrderIds) - }) - } -} diff --git a/x/dymns/types/tx.pb.go b/x/dymns/types/tx.pb.go index bfcac46ae..8b39d8574 100644 --- a/x/dymns/types/tx.pb.go +++ b/x/dymns/types/tx.pb.go @@ -916,6 +916,107 @@ func (m *MsgCancelSellOrderResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgCancelSellOrderResponse proto.InternalMessageInfo +// MsgCompleteSellOrder defines the message used for user to complete a Sell-Order. +type MsgCompleteSellOrder struct { + // asset_id is the Dym-Name/Alias about to perform Sell Order completion action. + AssetId string `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + // asset_type is the type of the asset of the order, is Dym-Name/Alias. + AssetType AssetType `protobuf:"varint,2,opt,name=asset_type,json=assetType,proto3,enum=dymensionxyz.dymension.dymns.AssetType" json:"asset_type,omitempty"` + // participant is the bech32-encoded address of either asset owner or highest bidder account. + Participant string `protobuf:"bytes,3,opt,name=participant,proto3" json:"participant,omitempty"` +} + +func (m *MsgCompleteSellOrder) Reset() { *m = MsgCompleteSellOrder{} } +func (m *MsgCompleteSellOrder) String() string { return proto.CompactTextString(m) } +func (*MsgCompleteSellOrder) ProtoMessage() {} +func (*MsgCompleteSellOrder) Descriptor() ([]byte, []int) { + return fileDescriptor_88dd2f81468013c2, []int{16} +} +func (m *MsgCompleteSellOrder) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCompleteSellOrder) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCompleteSellOrder.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCompleteSellOrder) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCompleteSellOrder.Merge(m, src) +} +func (m *MsgCompleteSellOrder) XXX_Size() int { + return m.Size() +} +func (m *MsgCompleteSellOrder) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCompleteSellOrder.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCompleteSellOrder proto.InternalMessageInfo + +func (m *MsgCompleteSellOrder) GetAssetId() string { + if m != nil { + return m.AssetId + } + return "" +} + +func (m *MsgCompleteSellOrder) GetAssetType() AssetType { + if m != nil { + return m.AssetType + } + return AssetType_AT_UNKNOWN +} + +func (m *MsgCompleteSellOrder) GetParticipant() string { + if m != nil { + return m.Participant + } + return "" +} + +// MsgCompleteSellOrderResponse defines the response for the Sell-Order completion. +type MsgCompleteSellOrderResponse struct { +} + +func (m *MsgCompleteSellOrderResponse) Reset() { *m = MsgCompleteSellOrderResponse{} } +func (m *MsgCompleteSellOrderResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCompleteSellOrderResponse) ProtoMessage() {} +func (*MsgCompleteSellOrderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_88dd2f81468013c2, []int{17} +} +func (m *MsgCompleteSellOrderResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCompleteSellOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCompleteSellOrderResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCompleteSellOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCompleteSellOrderResponse.Merge(m, src) +} +func (m *MsgCompleteSellOrderResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCompleteSellOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCompleteSellOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCompleteSellOrderResponse proto.InternalMessageInfo + // MsgPurchaseOrder defines the message used for user to bid/purchase a Sell-Order. type MsgPurchaseOrder struct { // asset_id is the Dym-Name/Alias to be purchased for. @@ -936,7 +1037,7 @@ func (m *MsgPurchaseOrder) Reset() { *m = MsgPurchaseOrder{} } func (m *MsgPurchaseOrder) String() string { return proto.CompactTextString(m) } func (*MsgPurchaseOrder) ProtoMessage() {} func (*MsgPurchaseOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{16} + return fileDescriptor_88dd2f81468013c2, []int{18} } func (m *MsgPurchaseOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1008,7 +1109,7 @@ func (m *MsgPurchaseOrderResponse) Reset() { *m = MsgPurchaseOrderRespon func (m *MsgPurchaseOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgPurchaseOrderResponse) ProtoMessage() {} func (*MsgPurchaseOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{17} + return fileDescriptor_88dd2f81468013c2, []int{19} } func (m *MsgPurchaseOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1059,7 +1160,7 @@ func (m *MsgPlaceBuyOrder) Reset() { *m = MsgPlaceBuyOrder{} } func (m *MsgPlaceBuyOrder) String() string { return proto.CompactTextString(m) } func (*MsgPlaceBuyOrder) ProtoMessage() {} func (*MsgPlaceBuyOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{18} + return fileDescriptor_88dd2f81468013c2, []int{20} } func (m *MsgPlaceBuyOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1140,7 +1241,7 @@ func (m *MsgPlaceBuyOrderResponse) Reset() { *m = MsgPlaceBuyOrderRespon func (m *MsgPlaceBuyOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgPlaceBuyOrderResponse) ProtoMessage() {} func (*MsgPlaceBuyOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{19} + return fileDescriptor_88dd2f81468013c2, []int{21} } func (m *MsgPlaceBuyOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1188,7 +1289,7 @@ func (m *MsgCancelBuyOrder) Reset() { *m = MsgCancelBuyOrder{} } func (m *MsgCancelBuyOrder) String() string { return proto.CompactTextString(m) } func (*MsgCancelBuyOrder) ProtoMessage() {} func (*MsgCancelBuyOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{20} + return fileDescriptor_88dd2f81468013c2, []int{22} } func (m *MsgCancelBuyOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1239,7 +1340,7 @@ func (m *MsgCancelBuyOrderResponse) Reset() { *m = MsgCancelBuyOrderResp func (m *MsgCancelBuyOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgCancelBuyOrderResponse) ProtoMessage() {} func (*MsgCancelBuyOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{21} + return fileDescriptor_88dd2f81468013c2, []int{23} } func (m *MsgCancelBuyOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1285,7 +1386,7 @@ func (m *MsgAcceptBuyOrder) Reset() { *m = MsgAcceptBuyOrder{} } func (m *MsgAcceptBuyOrder) String() string { return proto.CompactTextString(m) } func (*MsgAcceptBuyOrder) ProtoMessage() {} func (*MsgAcceptBuyOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{22} + return fileDescriptor_88dd2f81468013c2, []int{24} } func (m *MsgAcceptBuyOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1345,7 +1446,7 @@ func (m *MsgAcceptBuyOrderResponse) Reset() { *m = MsgAcceptBuyOrderResp func (m *MsgAcceptBuyOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgAcceptBuyOrderResponse) ProtoMessage() {} func (*MsgAcceptBuyOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{23} + return fileDescriptor_88dd2f81468013c2, []int{25} } func (m *MsgAcceptBuyOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1398,7 +1499,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{24} + return fileDescriptor_88dd2f81468013c2, []int{26} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1462,7 +1563,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88dd2f81468013c2, []int{25} + return fileDescriptor_88dd2f81468013c2, []int{27} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1508,6 +1609,8 @@ func init() { proto.RegisterType((*MsgPlaceSellOrderResponse)(nil), "dymensionxyz.dymension.dymns.MsgPlaceSellOrderResponse") proto.RegisterType((*MsgCancelSellOrder)(nil), "dymensionxyz.dymension.dymns.MsgCancelSellOrder") proto.RegisterType((*MsgCancelSellOrderResponse)(nil), "dymensionxyz.dymension.dymns.MsgCancelSellOrderResponse") + proto.RegisterType((*MsgCompleteSellOrder)(nil), "dymensionxyz.dymension.dymns.MsgCompleteSellOrder") + proto.RegisterType((*MsgCompleteSellOrderResponse)(nil), "dymensionxyz.dymension.dymns.MsgCompleteSellOrderResponse") proto.RegisterType((*MsgPurchaseOrder)(nil), "dymensionxyz.dymension.dymns.MsgPurchaseOrder") proto.RegisterType((*MsgPurchaseOrderResponse)(nil), "dymensionxyz.dymension.dymns.MsgPurchaseOrderResponse") proto.RegisterType((*MsgPlaceBuyOrder)(nil), "dymensionxyz.dymension.dymns.MsgPlaceBuyOrder") @@ -1525,89 +1628,93 @@ func init() { } var fileDescriptor_88dd2f81468013c2 = []byte{ - // 1305 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xf7, 0xda, 0x49, 0x6b, 0x3f, 0x9a, 0xa4, 0x5d, 0x45, 0xd4, 0xd9, 0x16, 0x37, 0xb8, 0xaa, - 0x48, 0x2b, 0x75, 0x4d, 0x53, 0xa5, 0x2d, 0x15, 0x20, 0x25, 0xa9, 0x10, 0x91, 0x08, 0xb5, 0x36, - 0x85, 0x03, 0x97, 0xd5, 0x78, 0x3d, 0xd9, 0xac, 0xd8, 0x9d, 0x59, 0xed, 0xac, 0x93, 0x1a, 0x21, - 0x21, 0xc1, 0x1d, 0x55, 0x1c, 0x90, 0xe0, 0x53, 0x20, 0xc1, 0x77, 0xa0, 0xc7, 0x8a, 0x53, 0x4f, - 0x08, 0xb5, 0x12, 0x7c, 0x0d, 0x34, 0x7f, 0xbc, 0xde, 0x71, 0xfd, 0x67, 0x1d, 0x21, 0xe0, 0xe4, - 0x7d, 0x33, 0xef, 0xcf, 0xef, 0xfd, 0xe6, 0xbd, 0x99, 0x27, 0xc3, 0xb5, 0x6e, 0x3f, 0xc2, 0x84, - 0x05, 0x94, 0x3c, 0xee, 0x7f, 0xd1, 0xca, 0x04, 0xfe, 0x45, 0x58, 0x2b, 0x7d, 0x6c, 0xc7, 0x09, - 0x4d, 0xa9, 0x79, 0x39, 0xaf, 0x66, 0x67, 0x82, 0x2d, 0xd4, 0xac, 0x55, 0x9f, 0xfa, 0x54, 0x28, - 0xb6, 0xf8, 0x97, 0xb4, 0xb1, 0x1a, 0x1e, 0x65, 0x11, 0x65, 0xad, 0x0e, 0x62, 0xb8, 0x75, 0x7c, - 0xab, 0x83, 0x53, 0x74, 0xab, 0xe5, 0xd1, 0x80, 0xa8, 0xfd, 0x8b, 0x6a, 0x3f, 0x62, 0x7e, 0xeb, - 0xf8, 0x16, 0xff, 0x51, 0x1b, 0x6b, 0x72, 0xc3, 0x95, 0x1e, 0xa5, 0xa0, 0xb6, 0xae, 0x4f, 0x85, - 0x1b, 0xa1, 0xe4, 0x73, 0x9c, 0x16, 0x52, 0x8d, 0x51, 0x82, 0x22, 0xe5, 0xb5, 0xf9, 0xab, 0x01, - 0x2b, 0xfb, 0xcc, 0x77, 0xb0, 0x1f, 0xb0, 0x14, 0x27, 0x1f, 0xa3, 0x08, 0x9b, 0x26, 0x2c, 0x10, - 0x14, 0xe1, 0xba, 0xb1, 0x6e, 0x6c, 0xd4, 0x1c, 0xf1, 0x6d, 0xae, 0xc2, 0x22, 0x3d, 0x21, 0x38, - 0xa9, 0x97, 0xc5, 0xa2, 0x14, 0x4c, 0x0b, 0xaa, 0xdd, 0x5e, 0x82, 0xd2, 0x80, 0x92, 0x7a, 0x65, - 0xdd, 0xd8, 0xa8, 0x38, 0x99, 0x6c, 0x7e, 0x08, 0x2b, 0x1e, 0x25, 0x87, 0x41, 0x12, 0xb9, 0x31, - 0xe2, 0x10, 0xd2, 0xfa, 0xc2, 0xba, 0xb1, 0xf1, 0xda, 0xe6, 0x9a, 0xad, 0xf2, 0xe2, 0xec, 0xd8, - 0x8a, 0x1d, 0x7b, 0x97, 0x06, 0x64, 0x67, 0xe1, 0xe9, 0xef, 0x57, 0x4a, 0xce, 0xb2, 0xb2, 0x6b, - 0x4b, 0x33, 0xb3, 0x0e, 0x67, 0x3d, 0x4a, 0x52, 0xe4, 0xa5, 0xf5, 0x45, 0x11, 0x7d, 0x20, 0xde, - 0x87, 0xaf, 0xff, 0xfa, 0xe9, 0x86, 0xc4, 0xd2, 0x5c, 0x83, 0x8b, 0x23, 0x89, 0x38, 0x98, 0xc5, - 0x94, 0x30, 0xdc, 0xfc, 0xd9, 0x80, 0xf3, 0xb9, 0xbd, 0xed, 0x30, 0x40, 0x8c, 0x67, 0x84, 0xf8, - 0x87, 0x4a, 0x53, 0x0a, 0xe6, 0x1b, 0x00, 0x09, 0x0d, 0x43, 0x14, 0xc7, 0x6e, 0xd0, 0x55, 0xc9, - 0xd6, 0xd4, 0xca, 0x5e, 0x77, 0x48, 0x43, 0x25, 0x4f, 0xc3, 0x3f, 0x96, 0xaa, 0x96, 0x90, 0x05, - 0xf5, 0x51, 0xd0, 0x59, 0x46, 0x31, 0x5c, 0xda, 0x67, 0xfe, 0xa3, 0x04, 0x11, 0x76, 0x88, 0x93, - 0x07, 0xfd, 0x88, 0xe7, 0xfb, 0x90, 0x9b, 0xb1, 0xa3, 0x20, 0x9e, 0xe3, 0x04, 0x2f, 0x41, 0x8d, - 0xe0, 0x13, 0x37, 0x9f, 0x54, 0x95, 0xe0, 0x13, 0xe1, 0x4a, 0x43, 0x73, 0x0d, 0xae, 0x4e, 0x89, - 0x98, 0x01, 0x3b, 0x12, 0x4c, 0x1f, 0xe0, 0x74, 0x97, 0x92, 0x94, 0xf3, 0x86, 0x93, 0x39, 0xd0, - 0x34, 0x00, 0xbc, 0xcc, 0x4e, 0xc1, 0xc9, 0xad, 0x8c, 0xa1, 0x47, 0x8b, 0x94, 0x3f, 0x70, 0x5e, - 0x0c, 0x9f, 0xc4, 0x5d, 0x94, 0xf2, 0x32, 0xa0, 0xe1, 0x31, 0xde, 0xee, 0x76, 0x13, 0xcc, 0xd8, - 0x58, 0x34, 0x7a, 0xdc, 0xf2, 0x68, 0x5c, 0x73, 0x0d, 0xaa, 0xde, 0x11, 0x0a, 0x08, 0xaf, 0x89, - 0x8a, 0x2a, 0x41, 0x2e, 0xef, 0x75, 0xf9, 0x16, 0xeb, 0x75, 0x5c, 0xe1, 0x72, 0x41, 0x6e, 0xb1, - 0x5e, 0x47, 0xf4, 0x11, 0xaf, 0x25, 0x19, 0xdb, 0x4d, 0xa9, 0x2a, 0xdd, 0x9a, 0x5a, 0x79, 0x44, - 0xef, 0xaf, 0xf0, 0x64, 0x72, 0x51, 0x9a, 0x6f, 0xc2, 0x95, 0x09, 0xa0, 0xb3, 0xc4, 0xbe, 0x97, - 0x95, 0x2c, 0x75, 0x1e, 0xe0, 0x14, 0x05, 0xe1, 0xe9, 0x32, 0xca, 0xf5, 0x54, 0x45, 0xeb, 0x29, - 0xf3, 0x2a, 0x2c, 0x79, 0x21, 0x46, 0x89, 0x2b, 0x4a, 0xd3, 0x67, 0x22, 0xab, 0xaa, 0x73, 0x4e, - 0x2c, 0xee, 0xca, 0xb5, 0x57, 0xb1, 0xcb, 0xd3, 0xd0, 0x70, 0x65, 0xa0, 0x9f, 0x94, 0xe1, 0xc2, - 0x3e, 0xf3, 0xdb, 0x21, 0xf2, 0xf0, 0x01, 0x0e, 0xc3, 0x87, 0x49, 0x57, 0x72, 0x8a, 0x18, 0xc3, - 0x29, 0xe7, 0x54, 0x22, 0x3f, 0x2b, 0xe4, 0xbd, 0xae, 0xf9, 0x01, 0x80, 0xdc, 0x4a, 0xfb, 0x31, - 0x16, 0xe0, 0x97, 0x37, 0xdf, 0xb2, 0xa7, 0xdd, 0xc3, 0xf6, 0x36, 0xd7, 0x7f, 0xd4, 0x8f, 0xb1, - 0x53, 0x43, 0x83, 0xcf, 0x09, 0xdd, 0xfa, 0x2e, 0xd4, 0xa2, 0x80, 0xb8, 0x71, 0x12, 0x78, 0xb8, - 0x68, 0x9f, 0x56, 0xa3, 0x80, 0xb4, 0xb9, 0x81, 0x79, 0x0f, 0x80, 0xe1, 0x30, 0x54, 0xe6, 0x8b, - 0x33, 0xcc, 0x9d, 0x1a, 0x57, 0x16, 0x96, 0x5a, 0xf1, 0x5e, 0x82, 0xb5, 0x57, 0x18, 0xc9, 0xf8, - 0xfa, 0xc1, 0x00, 0x73, 0x9f, 0xf9, 0xbb, 0x88, 0x78, 0x38, 0xfc, 0xef, 0x09, 0xd3, 0x80, 0x5f, - 0x06, 0xeb, 0x55, 0x68, 0x19, 0xf2, 0x3f, 0x65, 0x79, 0xb6, 0x7b, 0x89, 0x77, 0x84, 0x18, 0xfe, - 0xd7, 0x70, 0xbf, 0x0e, 0x67, 0xe4, 0xab, 0x56, 0xaf, 0xac, 0x57, 0x36, 0x6a, 0x8e, 0x92, 0x78, - 0x3e, 0x9d, 0x5e, 0x1f, 0x27, 0xaa, 0x33, 0xa5, 0x60, 0x6e, 0xc1, 0x22, 0x3d, 0x3c, 0xc4, 0xc9, - 0xcc, 0xd3, 0x53, 0x87, 0x2f, 0xb5, 0x15, 0x0d, 0xc2, 0x85, 0x2a, 0x77, 0x2d, 0xcf, 0x8c, 0x84, - 0xef, 0xca, 0x92, 0x04, 0x7e, 0xb8, 0x3b, 0xbd, 0xfe, 0xff, 0x94, 0x84, 0x1b, 0x70, 0x81, 0xb7, - 0x6f, 0x40, 0x7a, 0xd8, 0xa5, 0x1c, 0x22, 0x47, 0x26, 0xef, 0xa8, 0x95, 0xc1, 0x86, 0x80, 0xbe, - 0xd7, 0x1d, 0x12, 0x76, 0xe6, 0xd4, 0x84, 0x6d, 0x49, 0xc2, 0xf2, 0x9c, 0x0c, 0x08, 0xe3, 0xdc, - 0x64, 0x08, 0x14, 0x37, 0x54, 0x46, 0x6e, 0xb6, 0xc5, 0xcd, 0x21, 0xcb, 0x2d, 0xcf, 0xe5, 0x04, - 0xfd, 0x61, 0xae, 0xe5, 0x5c, 0xae, 0x1a, 0x10, 0xd9, 0x79, 0xba, 0xc7, 0xe1, 0x4d, 0x65, 0x88, - 0x78, 0xdb, 0x9e, 0x87, 0xe3, 0xb4, 0x60, 0xbc, 0x31, 0xcf, 0xd8, 0xfb, 0x00, 0xfc, 0x86, 0x41, - 0xc2, 0x8d, 0xe8, 0xa5, 0x02, 0xa4, 0xf1, 0x4b, 0x49, 0x06, 0xd6, 0x1a, 0xee, 0xae, 0xc0, 0xab, - 0x23, 0xca, 0x98, 0xb3, 0xa0, 0x2a, 0x83, 0x60, 0x89, 0xac, 0xea, 0x64, 0x72, 0xf3, 0x79, 0x59, - 0x4c, 0x76, 0xf2, 0x4a, 0x6e, 0xcb, 0x52, 0xb8, 0x03, 0x35, 0xd4, 0x4b, 0x8f, 0x68, 0x12, 0xa4, - 0x7d, 0x99, 0xca, 0x4e, 0xfd, 0xb7, 0x5f, 0x6e, 0xae, 0x2a, 0x68, 0xea, 0xb5, 0x39, 0x48, 0x93, - 0x80, 0xf8, 0xce, 0x50, 0xd5, 0x3c, 0x80, 0xf3, 0x7c, 0x4a, 0x10, 0x77, 0x9e, 0xab, 0x8a, 0xac, - 0x2c, 0xd2, 0xba, 0x3e, 0xbd, 0x50, 0xc5, 0xcd, 0x27, 0x83, 0x3b, 0xcb, 0x04, 0x9f, 0xe4, 0x64, - 0xf3, 0x53, 0xb8, 0xc0, 0x9d, 0x8a, 0x87, 0x94, 0xb9, 0x59, 0xe9, 0x72, 0xaf, 0x37, 0xa6, 0x7b, - 0xdd, 0x15, 0x26, 0xca, 0xed, 0x0a, 0xc1, 0x27, 0xf9, 0x05, 0xb3, 0x0d, 0x7c, 0xc9, 0x8d, 0x02, - 0xe6, 0x0d, 0xbc, 0xca, 0x5b, 0x7e, 0x63, 0xba, 0xd7, 0xfd, 0x80, 0x79, 0xca, 0xe7, 0x12, 0xc1, - 0x27, 0x43, 0xf1, 0xfe, 0x32, 0x3f, 0x8f, 0x21, 0x1d, 0x6a, 0xd4, 0xcc, 0x33, 0x3b, 0x38, 0x91, - 0xcd, 0x6f, 0x96, 0xa0, 0xb2, 0xcf, 0x7c, 0xf3, 0x18, 0xce, 0x69, 0x33, 0xf5, 0xcd, 0x19, 0xb1, - 0xf5, 0xc9, 0xd5, 0xda, 0x9a, 0x4b, 0x3d, 0xab, 0xdf, 0x92, 0xd9, 0x87, 0x25, 0x7d, 0xcc, 0xb5, - 0x0b, 0x7b, 0x12, 0xfa, 0xd6, 0x9d, 0xf9, 0xf4, 0x73, 0xa1, 0x7f, 0x34, 0xa0, 0x3e, 0x71, 0x22, - 0x7d, 0x67, 0xa6, 0xdb, 0x49, 0xa6, 0xd6, 0xf6, 0xa9, 0x4d, 0x75, 0x5e, 0xf4, 0xa1, 0x74, 0x36, - 0x2f, 0x9a, 0x7e, 0x01, 0x5e, 0xc6, 0x8f, 0xa2, 0x25, 0xf3, 0x5b, 0x03, 0x56, 0xc7, 0x4e, 0xa2, - 0xb3, 0x0f, 0x79, 0x9c, 0x99, 0xf5, 0xde, 0xa9, 0xcc, 0x74, 0x2e, 0xf4, 0x01, 0xd2, 0x2e, 0xe8, - 0x51, 0xe9, 0x17, 0xe0, 0x62, 0xfc, 0x20, 0x58, 0x32, 0xbf, 0x84, 0xe5, 0x91, 0x31, 0xb0, 0x35, - 0xd3, 0x97, 0x6e, 0x60, 0xdd, 0x9d, 0xd3, 0x20, 0x17, 0xfd, 0x2b, 0x58, 0x19, 0x1d, 0xaa, 0xde, - 0x9e, 0xe9, 0x6d, 0xc4, 0xc2, 0xba, 0x37, 0xaf, 0x85, 0xce, 0xbc, 0x3e, 0x1b, 0xcd, 0x66, 0x5e, - 0xd3, 0x2f, 0xc0, 0xfc, 0xf8, 0x99, 0x44, 0x86, 0xd6, 0x26, 0x12, 0xbb, 0x18, 0x8f, 0x03, 0xfd, - 0x22, 0xa1, 0xc7, 0xbd, 0xee, 0xf2, 0xd0, 0x47, 0x5e, 0xf0, 0x56, 0x41, 0x0e, 0xb3, 0xe0, 0x77, - 0xe7, 0x34, 0xd0, 0xa3, 0x8f, 0xbc, 0xe7, 0xb3, 0xa3, 0xeb, 0x06, 0x05, 0xa2, 0x8f, 0x7f, 0x9f, - 0x9b, 0x25, 0x33, 0x85, 0x73, 0xda, 0x0b, 0x7c, 0xb3, 0x60, 0xeb, 0x48, 0x75, 0x6b, 0x6b, 0x2e, - 0xf5, 0x41, 0xdc, 0x9d, 0x8f, 0x9e, 0xbe, 0x68, 0x18, 0xcf, 0x5e, 0x34, 0x8c, 0x3f, 0x5e, 0x34, - 0x8c, 0x27, 0x2f, 0x1b, 0xa5, 0x67, 0x2f, 0x1b, 0xa5, 0xe7, 0x2f, 0x1b, 0xa5, 0xcf, 0x36, 0xfd, - 0x20, 0x3d, 0xea, 0x75, 0x6c, 0x8f, 0x46, 0xad, 0x09, 0xff, 0x12, 0x1d, 0xdf, 0x6e, 0x3d, 0x1e, - 0xfc, 0x09, 0xd6, 0x8f, 0x31, 0xeb, 0x9c, 0x11, 0x7f, 0x15, 0xdd, 0xfe, 0x3b, 0x00, 0x00, 0xff, - 0xff, 0x59, 0x79, 0xe6, 0x28, 0x31, 0x13, 0x00, 0x00, + // 1369 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4f, 0x6b, 0x1b, 0x47, + 0x14, 0xd7, 0x4a, 0x76, 0x22, 0xbd, 0x24, 0x76, 0xbc, 0x98, 0x46, 0xde, 0xa4, 0x8a, 0xab, 0x10, + 0xea, 0x04, 0x22, 0x35, 0x0e, 0x4e, 0x52, 0xd3, 0x16, 0x6c, 0x87, 0x52, 0x43, 0xdd, 0x08, 0x39, + 0xed, 0xa1, 0x97, 0x65, 0xb4, 0x1a, 0xaf, 0x97, 0xee, 0xce, 0x2c, 0x3b, 0x23, 0x3b, 0x2a, 0x85, + 0x42, 0xa1, 0xa7, 0x42, 0x09, 0x3d, 0x14, 0xda, 0xef, 0x50, 0x28, 0xb4, 0xdf, 0xa1, 0x39, 0x86, + 0x9e, 0x72, 0x2a, 0x25, 0x81, 0xf6, 0x6b, 0x94, 0xf9, 0xa3, 0xd5, 0x8e, 0x2c, 0x5b, 0x2b, 0x53, + 0xd2, 0x9e, 0xbc, 0x6f, 0xe6, 0xfd, 0xf9, 0xbd, 0xdf, 0xbc, 0x79, 0xf3, 0x2c, 0xb8, 0xde, 0xed, + 0x47, 0x98, 0xb0, 0x80, 0x92, 0xc7, 0xfd, 0xcf, 0x9b, 0xa9, 0x20, 0xbe, 0x08, 0x6b, 0xf2, 0xc7, + 0x8d, 0x38, 0xa1, 0x9c, 0xda, 0x57, 0xb2, 0x6a, 0x8d, 0x54, 0x68, 0x48, 0x35, 0x67, 0xd1, 0xa7, + 0x3e, 0x95, 0x8a, 0x4d, 0xf1, 0xa5, 0x6c, 0x9c, 0x9a, 0x47, 0x59, 0x44, 0x59, 0xb3, 0x83, 0x18, + 0x6e, 0x1e, 0xdc, 0xee, 0x60, 0x8e, 0x6e, 0x37, 0x3d, 0x1a, 0x10, 0xbd, 0x7f, 0x49, 0xef, 0x47, + 0xcc, 0x6f, 0x1e, 0xdc, 0x16, 0x7f, 0xf4, 0xc6, 0x92, 0xda, 0x70, 0x95, 0x47, 0x25, 0xe8, 0xad, + 0x1b, 0x27, 0xc2, 0x8d, 0x50, 0xf2, 0x19, 0xe6, 0xb9, 0x54, 0x63, 0x94, 0xa0, 0x48, 0x7b, 0xad, + 0xff, 0x66, 0xc1, 0xfc, 0x0e, 0xf3, 0xdb, 0xd8, 0x0f, 0x18, 0xc7, 0xc9, 0x47, 0x28, 0xc2, 0xb6, + 0x0d, 0x33, 0x04, 0x45, 0xb8, 0x6a, 0x2d, 0x5b, 0x2b, 0x95, 0xb6, 0xfc, 0xb6, 0x17, 0x61, 0x96, + 0x1e, 0x12, 0x9c, 0x54, 0x8b, 0x72, 0x51, 0x09, 0xb6, 0x03, 0xe5, 0x6e, 0x2f, 0x41, 0x3c, 0xa0, + 0xa4, 0x5a, 0x5a, 0xb6, 0x56, 0x4a, 0xed, 0x54, 0xb6, 0x3f, 0x80, 0x79, 0x8f, 0x92, 0xbd, 0x20, + 0x89, 0xdc, 0x18, 0x09, 0x08, 0xbc, 0x3a, 0xb3, 0x6c, 0xad, 0x9c, 0x5b, 0x5d, 0x6a, 0xe8, 0xbc, + 0x04, 0x3b, 0x0d, 0xcd, 0x4e, 0x63, 0x8b, 0x06, 0x64, 0x73, 0xe6, 0xe9, 0x1f, 0x57, 0x0b, 0xed, + 0x39, 0x6d, 0xd7, 0x52, 0x66, 0x76, 0x15, 0xce, 0x7a, 0x94, 0x70, 0xe4, 0xf1, 0xea, 0xac, 0x8c, + 0x3e, 0x10, 0xd7, 0xe1, 0xab, 0xbf, 0x7f, 0xbe, 0xa9, 0xb0, 0xd4, 0x97, 0xe0, 0xd2, 0x48, 0x22, + 0x6d, 0xcc, 0x62, 0x4a, 0x18, 0xae, 0xff, 0x62, 0xc1, 0xc5, 0xcc, 0xde, 0x46, 0x18, 0x20, 0x26, + 0x32, 0x42, 0xe2, 0x43, 0xa7, 0xa9, 0x04, 0xfb, 0x75, 0x80, 0x84, 0x86, 0x21, 0x8a, 0x63, 0x37, + 0xe8, 0xea, 0x64, 0x2b, 0x7a, 0x65, 0xbb, 0x3b, 0xa4, 0xa1, 0x94, 0xa5, 0xe1, 0x5f, 0x4b, 0xd5, + 0x48, 0xc8, 0x81, 0xea, 0x28, 0xe8, 0x34, 0xa3, 0x18, 0x2e, 0xef, 0x30, 0xff, 0x51, 0x82, 0x08, + 0xdb, 0xc3, 0xc9, 0x83, 0x7e, 0x24, 0xf2, 0x7d, 0x28, 0xcc, 0xd8, 0x7e, 0x10, 0x4f, 0x71, 0x82, + 0x97, 0xa1, 0x42, 0xf0, 0xa1, 0x9b, 0x4d, 0xaa, 0x4c, 0xf0, 0xa1, 0x74, 0x65, 0xa0, 0xb9, 0x0e, + 0xd7, 0x4e, 0x88, 0x98, 0x02, 0xdb, 0x97, 0x4c, 0xef, 0x62, 0xbe, 0x45, 0x09, 0x17, 0xbc, 0xe1, + 0x64, 0x0a, 0x34, 0x35, 0x00, 0x2f, 0xb5, 0xd3, 0x70, 0x32, 0x2b, 0x63, 0xe8, 0x31, 0x22, 0x65, + 0x0f, 0x5c, 0x14, 0xc3, 0xc7, 0x71, 0x17, 0x71, 0x51, 0x06, 0x34, 0x3c, 0xc0, 0x1b, 0xdd, 0x6e, + 0x82, 0x19, 0x1b, 0x8b, 0xc6, 0x8c, 0x5b, 0x1c, 0x8d, 0x6b, 0x2f, 0x41, 0xd9, 0xdb, 0x47, 0x01, + 0x11, 0x35, 0x51, 0xd2, 0x25, 0x28, 0xe4, 0xed, 0xae, 0xd8, 0x62, 0xbd, 0x8e, 0x2b, 0x5d, 0xce, + 0xa8, 0x2d, 0xd6, 0xeb, 0xc8, 0x7b, 0x24, 0x6a, 0x49, 0xc5, 0x76, 0x39, 0xd5, 0xa5, 0x5b, 0xd1, + 0x2b, 0x8f, 0xe8, 0xfa, 0xbc, 0x48, 0x26, 0x13, 0xa5, 0xfe, 0x06, 0x5c, 0x3d, 0x06, 0x74, 0x9a, + 0xd8, 0xf7, 0xaa, 0x92, 0x95, 0xce, 0x03, 0xcc, 0x51, 0x10, 0x9e, 0x2e, 0xa3, 0xcc, 0x9d, 0x2a, + 0x19, 0x77, 0xca, 0xbe, 0x06, 0x17, 0xbc, 0x10, 0xa3, 0xc4, 0x95, 0xa5, 0xe9, 0x33, 0x99, 0x55, + 0xb9, 0x7d, 0x5e, 0x2e, 0x6e, 0xa9, 0xb5, 0xa3, 0xd8, 0xd5, 0x69, 0x18, 0xb8, 0x52, 0xd0, 0x4f, + 0x8a, 0xb0, 0xb0, 0xc3, 0xfc, 0x56, 0x88, 0x3c, 0xbc, 0x8b, 0xc3, 0xf0, 0x61, 0xd2, 0x55, 0x9c, + 0x22, 0xc6, 0x30, 0x17, 0x9c, 0x2a, 0xe4, 0x67, 0xa5, 0xbc, 0xdd, 0xb5, 0xdf, 0x07, 0x50, 0x5b, + 0xbc, 0x1f, 0x63, 0x09, 0x7e, 0x6e, 0xf5, 0xcd, 0xc6, 0x49, 0x7d, 0xb8, 0xb1, 0x21, 0xf4, 0x1f, + 0xf5, 0x63, 0xdc, 0xae, 0xa0, 0xc1, 0xe7, 0x31, 0xb7, 0xf5, 0x1d, 0xa8, 0x44, 0x01, 0x71, 0xe3, + 0x24, 0xf0, 0x70, 0xde, 0x7b, 0x5a, 0x8e, 0x02, 0xd2, 0x12, 0x06, 0xf6, 0x7d, 0x00, 0x86, 0xc3, + 0x50, 0x9b, 0xcf, 0x4e, 0x30, 0x6f, 0x57, 0x84, 0xb2, 0xb4, 0x34, 0x8a, 0xf7, 0x32, 0x2c, 0x1d, + 0x61, 0x24, 0xe5, 0xeb, 0x07, 0x0b, 0xec, 0x1d, 0xe6, 0x6f, 0x21, 0xe2, 0xe1, 0xf0, 0xbf, 0x27, + 0xcc, 0x00, 0x7e, 0x05, 0x9c, 0xa3, 0xd0, 0x52, 0xe4, 0x3f, 0x59, 0xb0, 0x28, 0xb6, 0x69, 0x14, + 0x87, 0x98, 0xbf, 0xda, 0xc3, 0x5e, 0x86, 0x73, 0x31, 0x4a, 0x78, 0xe0, 0x05, 0x31, 0x22, 0x83, + 0xaa, 0xce, 0x2e, 0xad, 0x5f, 0x14, 0x79, 0x64, 0x57, 0xea, 0x35, 0xb8, 0x32, 0x0e, 0x6e, 0x9a, + 0xcf, 0x5f, 0xea, 0xba, 0xb5, 0x7a, 0x89, 0xb7, 0x8f, 0x18, 0x7e, 0x65, 0xb9, 0xbc, 0x06, 0x67, + 0xd4, 0x2b, 0x5d, 0x2d, 0x2d, 0x97, 0x56, 0x2a, 0x6d, 0x2d, 0x89, 0xf3, 0xe9, 0xf4, 0xfa, 0x38, + 0xd1, 0x9d, 0x46, 0x09, 0xf6, 0x1a, 0xcc, 0xd2, 0xbd, 0x3d, 0x9c, 0x4c, 0xac, 0x46, 0x5d, 0xcc, + 0x4a, 0x5b, 0x1f, 0xab, 0x74, 0xa1, 0xaf, 0xaf, 0x91, 0x67, 0x4a, 0xc2, 0x77, 0x45, 0x45, 0x82, + 0x28, 0xd6, 0xcd, 0x5e, 0xff, 0x7f, 0x4a, 0xc2, 0x4d, 0x58, 0x10, 0xed, 0x28, 0x20, 0x3d, 0xec, + 0x52, 0x01, 0x51, 0x20, 0x53, 0x3d, 0x77, 0x7e, 0xb0, 0x21, 0xa1, 0x6f, 0x77, 0x87, 0x84, 0x9d, + 0x39, 0x35, 0x61, 0x6b, 0x8a, 0xb0, 0x2c, 0x27, 0x03, 0xc2, 0x04, 0x37, 0x29, 0x02, 0xcd, 0x0d, + 0x55, 0x91, 0xeb, 0x2d, 0xd9, 0x09, 0xd5, 0xf5, 0xc9, 0x72, 0x79, 0x8c, 0xfe, 0x30, 0xd7, 0x62, + 0x26, 0x57, 0x03, 0x88, 0xea, 0x24, 0xa6, 0xc7, 0x61, 0xe7, 0xb5, 0x64, 0xbc, 0x0d, 0xcf, 0xc3, + 0x31, 0xcf, 0x19, 0x6f, 0xcc, 0xb3, 0xfc, 0x1e, 0x80, 0xe8, 0x98, 0x48, 0xba, 0x91, 0x37, 0x2b, + 0x07, 0x69, 0xa2, 0xc9, 0xaa, 0xc0, 0x46, 0x03, 0xb9, 0x27, 0xf1, 0x9a, 0x88, 0x52, 0xe6, 0x1c, + 0x28, 0xab, 0x20, 0x58, 0x21, 0x2b, 0xb7, 0x53, 0xb9, 0xfe, 0xbc, 0x28, 0x27, 0x55, 0xf5, 0xc4, + 0xb4, 0x54, 0x29, 0xdc, 0x85, 0x0a, 0xea, 0xf1, 0x7d, 0x9a, 0x04, 0xbc, 0xaf, 0x52, 0xd9, 0xac, + 0xfe, 0xfe, 0xeb, 0xad, 0x45, 0x0d, 0x4d, 0xbf, 0x9e, 0xbb, 0x3c, 0x09, 0x88, 0xdf, 0x1e, 0xaa, + 0xda, 0xbb, 0x70, 0x51, 0x4c, 0x3d, 0xb2, 0x87, 0xbb, 0xba, 0xc8, 0x8a, 0x32, 0xad, 0x1b, 0x27, + 0x17, 0xaa, 0xec, 0xe4, 0x2a, 0x78, 0x7b, 0x8e, 0xe0, 0xc3, 0x8c, 0x6c, 0x7f, 0x02, 0x0b, 0xc2, + 0xa9, 0x1c, 0x0c, 0x98, 0x9b, 0x96, 0xae, 0xf0, 0x7a, 0xf3, 0x64, 0xaf, 0x5b, 0xd2, 0x44, 0xbb, + 0x9d, 0x27, 0xf8, 0x30, 0xbb, 0x60, 0xb7, 0x40, 0x2c, 0xb9, 0x51, 0xc0, 0xbc, 0x81, 0x57, 0xf5, + 0x6a, 0xad, 0x9c, 0xec, 0x75, 0x27, 0x60, 0x9e, 0xf6, 0x79, 0x81, 0xe0, 0xc3, 0xa1, 0xb8, 0x3e, + 0x27, 0xce, 0x63, 0x48, 0x87, 0x1e, 0x9d, 0xb3, 0xcc, 0x0e, 0x4e, 0x64, 0xf5, 0x9b, 0x39, 0x28, + 0xed, 0x30, 0xdf, 0x3e, 0x80, 0xf3, 0xc6, 0xff, 0x08, 0xb7, 0x26, 0xc4, 0x36, 0x27, 0x71, 0x67, + 0x6d, 0x2a, 0xf5, 0xb4, 0x7e, 0x0b, 0x76, 0x1f, 0x2e, 0x98, 0x63, 0x7b, 0x23, 0xb7, 0x27, 0xa9, + 0xef, 0xdc, 0x9d, 0x4e, 0x3f, 0x13, 0xfa, 0x47, 0x0b, 0xaa, 0xc7, 0x4e, 0xd8, 0x6f, 0x4f, 0x74, + 0x7b, 0x9c, 0xa9, 0xb3, 0x71, 0x6a, 0x53, 0x93, 0x17, 0x73, 0xc8, 0x9e, 0xcc, 0x8b, 0xa1, 0x9f, + 0x83, 0x97, 0xf1, 0xa3, 0x75, 0xc1, 0xfe, 0xd6, 0x82, 0xc5, 0xb1, 0x93, 0xf5, 0xe4, 0x43, 0x1e, + 0x67, 0xe6, 0xbc, 0x7b, 0x2a, 0x33, 0x93, 0x0b, 0x73, 0x20, 0x6e, 0xe4, 0xf4, 0xa8, 0xf5, 0x73, + 0x70, 0x31, 0x7e, 0xb0, 0x2d, 0xd8, 0x5f, 0xc0, 0xdc, 0xc8, 0x58, 0xdb, 0x9c, 0xe8, 0xcb, 0x34, + 0x70, 0xee, 0x4d, 0x69, 0x90, 0x89, 0xfe, 0x25, 0xcc, 0x8f, 0x0e, 0x89, 0x6f, 0x4d, 0xf4, 0x36, + 0x62, 0xe1, 0xdc, 0x9f, 0xd6, 0x22, 0x03, 0xe0, 0x6b, 0x0b, 0x16, 0x8e, 0x0e, 0x7b, 0xab, 0x93, + 0x3d, 0x8e, 0xda, 0x38, 0xeb, 0xd3, 0xdb, 0x98, 0x15, 0x60, 0xce, 0x68, 0x93, 0x2b, 0xc0, 0xd0, + 0xcf, 0x51, 0x01, 0xe3, 0x67, 0x23, 0x15, 0xda, 0x98, 0x8c, 0x1a, 0xf9, 0xce, 0x73, 0xa0, 0x9f, + 0x27, 0xf4, 0xb8, 0x29, 0x43, 0x15, 0xdf, 0xc8, 0x24, 0xd1, 0xcc, 0x79, 0x96, 0x69, 0xf0, 0x7b, + 0x53, 0x1a, 0x98, 0xd1, 0x47, 0xe6, 0x8a, 0xc9, 0xd1, 0x4d, 0x83, 0x1c, 0xd1, 0xc7, 0xcf, 0x09, + 0xf5, 0x82, 0xcd, 0xe1, 0xbc, 0x31, 0x09, 0xdc, 0xca, 0x79, 0x85, 0x95, 0xba, 0xb3, 0x36, 0x95, + 0xfa, 0x20, 0xee, 0xe6, 0x87, 0x4f, 0x5f, 0xd4, 0xac, 0x67, 0x2f, 0x6a, 0xd6, 0x9f, 0x2f, 0x6a, + 0xd6, 0x93, 0x97, 0xb5, 0xc2, 0xb3, 0x97, 0xb5, 0xc2, 0xf3, 0x97, 0xb5, 0xc2, 0xa7, 0xab, 0x7e, + 0xc0, 0xf7, 0x7b, 0x9d, 0x86, 0x47, 0xa3, 0xe6, 0x31, 0xbf, 0xbe, 0x1d, 0xdc, 0x69, 0x3e, 0x1e, + 0xfc, 0xb8, 0xd8, 0x8f, 0x31, 0xeb, 0x9c, 0x91, 0x3f, 0xc1, 0xdd, 0xf9, 0x27, 0x00, 0x00, 0xff, + 0xff, 0xeb, 0x9e, 0x72, 0x0b, 0x89, 0x14, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1647,6 +1754,11 @@ type MsgClient interface { // This will stop the advertisement and remove the Dym-Name/Alias sale from the market. // Can only be performed if no one has placed a bid on the asset. CancelSellOrder(ctx context.Context, in *MsgCancelSellOrder, opts ...grpc.CallOption) (*MsgCancelSellOrderResponse, error) + // CompleteSellOrder is message handler, + // handles Sell-Order completion action, can be performed by either asset owner or the person who placed the highest bid. + // Can only be performed when Sell-Order expired and has a bid placed. + // If the asset was expired or prohibited trading, bid placed will be force to return to the bidder, ownership will not be transferred. + CompleteSellOrder(ctx context.Context, in *MsgCompleteSellOrder, opts ...grpc.CallOption) (*MsgCompleteSellOrderResponse, error) // PurchaseOrder is message handler, // handles purchasing a Dym-Name/Alias from a Sell-Order, performed by the buyer. PurchaseOrder(ctx context.Context, in *MsgPurchaseOrder, opts ...grpc.CallOption) (*MsgPurchaseOrderResponse, error) @@ -1744,6 +1856,15 @@ func (c *msgClient) CancelSellOrder(ctx context.Context, in *MsgCancelSellOrder, return out, nil } +func (c *msgClient) CompleteSellOrder(ctx context.Context, in *MsgCompleteSellOrder, opts ...grpc.CallOption) (*MsgCompleteSellOrderResponse, error) { + out := new(MsgCompleteSellOrderResponse) + err := c.cc.Invoke(ctx, "/dymensionxyz.dymension.dymns.Msg/CompleteSellOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) PurchaseOrder(ctx context.Context, in *MsgPurchaseOrder, opts ...grpc.CallOption) (*MsgPurchaseOrderResponse, error) { out := new(MsgPurchaseOrderResponse) err := c.cc.Invoke(ctx, "/dymensionxyz.dymension.dymns.Msg/PurchaseOrder", in, out, opts...) @@ -1816,6 +1937,11 @@ type MsgServer interface { // This will stop the advertisement and remove the Dym-Name/Alias sale from the market. // Can only be performed if no one has placed a bid on the asset. CancelSellOrder(context.Context, *MsgCancelSellOrder) (*MsgCancelSellOrderResponse, error) + // CompleteSellOrder is message handler, + // handles Sell-Order completion action, can be performed by either asset owner or the person who placed the highest bid. + // Can only be performed when Sell-Order expired and has a bid placed. + // If the asset was expired or prohibited trading, bid placed will be force to return to the bidder, ownership will not be transferred. + CompleteSellOrder(context.Context, *MsgCompleteSellOrder) (*MsgCompleteSellOrderResponse, error) // PurchaseOrder is message handler, // handles purchasing a Dym-Name/Alias from a Sell-Order, performed by the buyer. PurchaseOrder(context.Context, *MsgPurchaseOrder) (*MsgPurchaseOrderResponse, error) @@ -1861,6 +1987,9 @@ func (*UnimplementedMsgServer) PlaceSellOrder(ctx context.Context, req *MsgPlace func (*UnimplementedMsgServer) CancelSellOrder(ctx context.Context, req *MsgCancelSellOrder) (*MsgCancelSellOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CancelSellOrder not implemented") } +func (*UnimplementedMsgServer) CompleteSellOrder(ctx context.Context, req *MsgCompleteSellOrder) (*MsgCompleteSellOrderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CompleteSellOrder not implemented") +} func (*UnimplementedMsgServer) PurchaseOrder(ctx context.Context, req *MsgPurchaseOrder) (*MsgPurchaseOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PurchaseOrder not implemented") } @@ -2025,6 +2154,24 @@ func _Msg_CancelSellOrder_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _Msg_CompleteSellOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCompleteSellOrder) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CompleteSellOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dymensionxyz.dymension.dymns.Msg/CompleteSellOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CompleteSellOrder(ctx, req.(*MsgCompleteSellOrder)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_PurchaseOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgPurchaseOrder) if err := dec(in); err != nil { @@ -2151,6 +2298,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "CancelSellOrder", Handler: _Msg_CancelSellOrder_Handler, }, + { + MethodName: "CompleteSellOrder", + Handler: _Msg_CompleteSellOrder_Handler, + }, { MethodName: "PurchaseOrder", Handler: _Msg_PurchaseOrder_Handler, @@ -2779,6 +2930,71 @@ func (m *MsgCancelSellOrderResponse) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *MsgCompleteSellOrder) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCompleteSellOrder) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCompleteSellOrder) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Participant) > 0 { + i -= len(m.Participant) + copy(dAtA[i:], m.Participant) + i = encodeVarintTx(dAtA, i, uint64(len(m.Participant))) + i-- + dAtA[i] = 0x1a + } + if m.AssetType != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.AssetType)) + i-- + dAtA[i] = 0x10 + } + if len(m.AssetId) > 0 { + i -= len(m.AssetId) + copy(dAtA[i:], m.AssetId) + i = encodeVarintTx(dAtA, i, uint64(len(m.AssetId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgCompleteSellOrderResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCompleteSellOrderResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCompleteSellOrderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgPurchaseOrder) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3463,6 +3679,35 @@ func (m *MsgCancelSellOrderResponse) Size() (n int) { return n } +func (m *MsgCompleteSellOrder) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AssetId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.AssetType != 0 { + n += 1 + sovTx(uint64(m.AssetType)) + } + l = len(m.Participant) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCompleteSellOrderResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgPurchaseOrder) Size() (n int) { if m == nil { return 0 @@ -5422,6 +5667,189 @@ func (m *MsgCancelSellOrderResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgCompleteSellOrder) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCompleteSellOrder: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCompleteSellOrder: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetType", wireType) + } + m.AssetType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AssetType |= AssetType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Participant", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Participant = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCompleteSellOrderResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCompleteSellOrderResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCompleteSellOrderResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgPurchaseOrder) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0