Skip to content

Commit

Permalink
patch 5
Browse files Browse the repository at this point in the history
  • Loading branch information
VictorTrustyDev committed Sep 26, 2024
1 parent a53ee89 commit 2c3db77
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 125 deletions.
6 changes: 6 additions & 0 deletions proto/dymensionxyz/dymension/dymns/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
99 changes: 45 additions & 54 deletions x/dymns/keeper/msg_server_purchase_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (k msgServer) PurchaseOrder(goCtx context.Context, msg *dymnstypes.MsgPurch
return nil, err
}

priceParams := k.PriceParams(ctx)
miscParams := k.MiscParams(ctx)

var resp *dymnstypes.MsgPurchaseOrderResponse
Expand All @@ -28,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)
}
Expand All @@ -45,15 +46,12 @@ func (k msgServer) PurchaseOrder(goCtx context.Context, msg *dymnstypes.MsgPurch
}

// 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
}
Expand Down Expand Up @@ -99,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)
Expand All @@ -113,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
}
Expand Down Expand Up @@ -205,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) {
Expand Down Expand Up @@ -236,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
}
113 changes: 96 additions & 17 deletions x/dymns/keeper/msg_server_purchase_order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,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",
Expand Down Expand Up @@ -204,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,
Expand Down Expand Up @@ -371,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,
Expand All @@ -397,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 {
Expand Down Expand Up @@ -932,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},
Expand Down Expand Up @@ -1117,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},
Expand Down Expand Up @@ -1214,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,
Expand Down
12 changes: 9 additions & 3 deletions x/dymns/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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
}

Expand Down
Loading

0 comments on commit 2c3db77

Please sign in to comment.