diff --git a/docs/How-To/cli/README.md b/docs/How-To/cli/README.md index 0ab9d2d0..b1ab9dd5 100644 --- a/docs/How-To/cli/README.md +++ b/docs/How-To/cli/README.md @@ -342,18 +342,18 @@ Example command: ```bash # Modify the bid price -fundraisingd tx fundraising modify-bid 1 1 0.45 10000000denom2 \ +fundraisingd tx fundraising modify-bid 2 1 0.38 10000000denom2 \ --chain-id fundraising \ ---from bob \ +--from steve \ --keyring-backend test \ --broadcast-mode block \ --yes \ --output json | jq # Modify the bid amount -fundraisingd tx fundraising modify-bid 1 2 0.35 25000000denom1 \ +fundraisingd tx fundraising modify-bid 2 2 0.4 15000000denom1 \ --chain-id fundraising \ ---from bob \ +--from steve \ --keyring-backend test \ --broadcast-mode block \ --yes \ diff --git a/x/fundraising/client/cli/tx.go b/x/fundraising/client/cli/tx.go index 68cb0594..ed3e7383 100644 --- a/x/fundraising/client/cli/tx.go +++ b/x/fundraising/client/cli/tx.go @@ -290,7 +290,7 @@ in our technical spec docs. https://github.com/tendermint/fundraising/blob/main/ return err } - bidType, err := parseBidType(args[1]) + bidType, err := ParseBidType(args[1]) if err != nil { return fmt.Errorf("parse order direction: %w", err) } diff --git a/x/fundraising/client/cli/utils.go b/x/fundraising/client/cli/utils.go index b61f8769..2c3455f5 100644 --- a/x/fundraising/client/cli/utils.go +++ b/x/fundraising/client/cli/utils.go @@ -81,8 +81,8 @@ func (req BatchAuctionRequest) String() string { return string(result) } -// parseBidType parses bid type string and returns types.BidType. -func parseBidType(s string) (types.BidType, error) { +// ParseBidType parses bid type string and returns types.BidType. +func ParseBidType(s string) (types.BidType, error) { switch strings.ToLower(s) { case "fixed-price", "fp", "f": return types.BidTypeFixedPrice, nil diff --git a/x/fundraising/client/cli/utils_test.go b/x/fundraising/client/cli/utils_test.go index 46d4e1e9..8f5938f0 100644 --- a/x/fundraising/client/cli/utils_test.go +++ b/x/fundraising/client/cli/utils_test.go @@ -1,6 +1,7 @@ package cli_test import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -121,3 +122,30 @@ func TestParseBatchAuction(t *testing.T) { require.Equal(t, sdk.MustNewDecFromStr("0.2"), auction.ExtendedRoundRate) require.EqualValues(t, expSchedules, auction.VestingSchedules) } + +func TestParseBidType(t *testing.T) { + for _, tc := range []struct { + bidType string + expectedErr error + }{ + {"fixed-price", nil}, + {"fp", nil}, + {"f", nil}, + {"batch-worth", nil}, + {"bw", nil}, + {"w", nil}, + {"batch-many", nil}, + {"bm", nil}, + {"m", nil}, + {"fixedprice", fmt.Errorf("invalid bid type: %s", "fixedprice")}, + {"batchworth", fmt.Errorf("invalid bid type: %s", "batchworth")}, + {"batchmany", fmt.Errorf("invalid bid type: %s", "batchmany")}, + } { + _, err := cli.ParseBidType(tc.bidType) + if tc.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } +} diff --git a/x/fundraising/handler.go b/x/fundraising/handler.go index 7d89fdde..9541fb73 100644 --- a/x/fundraising/handler.go +++ b/x/fundraising/handler.go @@ -10,6 +10,7 @@ import ( "github.com/tendermint/fundraising/x/fundraising/types" ) +// NewHandler returns a new msg handler. func NewHandler(k keeper.Keeper) sdk.Handler { msgServer := keeper.NewMsgServerImpl(k) diff --git a/x/fundraising/keeper/auction.go b/x/fundraising/keeper/auction.go index c85a8666..f20421e5 100644 --- a/x/fundraising/keeper/auction.go +++ b/x/fundraising/keeper/auction.go @@ -13,7 +13,7 @@ import ( "github.com/tendermint/fundraising/x/fundraising/types" ) -// GetNextAuctionIdWithUpdate increments auction id by one and set it. +// GetNextAuctionIdWithUpdate increments auction id by one and store it. func (k Keeper) GetNextAuctionIdWithUpdate(ctx sdk.Context) uint64 { id := k.GetLastAuctionId(ctx) + 1 k.SetAuctionId(ctx, id) @@ -64,7 +64,7 @@ func (k Keeper) CreateFixedPriceAuction(ctx sdk.Context, msg *types.MsgCreateFix auction := types.NewFixedPriceAuction(ba, msg.SellingCoin) - // Call the before auction created hook + // Call hook before storing an auction k.BeforeFixedPriceAuctionCreated( ctx, auction.Auctioneer, @@ -155,7 +155,7 @@ func (k Keeper) CreateBatchAuction(ctx sdk.Context, msg *types.MsgCreateBatchAuc msg.ExtendedRoundRate, ) - // Call the before auction created hook + // Call hook before storing an auction k.BeforeBatchAuctionCreated( ctx, auction.Auctioneer, @@ -195,7 +195,8 @@ func (k Keeper) CreateBatchAuction(ctx sdk.Context, msg *types.MsgCreateBatchAuc return auction, nil } -// CancelAuction cancels the auction. It can only be canceled when the auction has not started yet. +// CancelAuction handles types.MsgCancelAuction and cancels the auction. +// An auction can only be canceled when it is not started yet. func (k Keeper) CancelAuction(ctx sdk.Context, msg *types.MsgCancelAuction) error { auction, found := k.GetAuction(ctx, msg.AuctionId) if !found { @@ -220,7 +221,7 @@ func (k Keeper) CancelAuction(ctx sdk.Context, msg *types.MsgCancelAuction) erro return sdkerrors.Wrap(err, "failed to release the selling coin") } - // Call the before auction canceled hook + // Call hook before cancelling the auction k.BeforeAuctionCanceled(ctx, msg.AuctionId, msg.Auctioneer) if auction.GetType() == types.AuctionTypeFixedPrice { @@ -258,7 +259,7 @@ func (k Keeper) AddAllowedBidders(ctx sdk.Context, auctionId uint64, allowedBidd return sdkerrors.Wrapf(sdkerrors.ErrNotFound, "auction %d is not found", auctionId) } - // Call the before allowed bidders added hook + // Call hook before adding allowed bidders for the auction k.BeforeAllowedBiddersAdded(ctx, allowedBidders) // Store new allowed bidders @@ -266,11 +267,9 @@ func (k Keeper) AddAllowedBidders(ctx sdk.Context, auctionId uint64, allowedBidd if err := ab.Validate(); err != nil { return err } - if ab.MaxBidAmount.GT(auction.GetSellingCoin().Amount) { return types.ErrInsufficientRemainingAmount } - k.SetAllowedBidder(ctx, auctionId, ab) } @@ -299,7 +298,7 @@ func (k Keeper) UpdateAllowedBidder(ctx sdk.Context, auctionId uint64, bidder sd return err } - // Call the before allowed bidders updated hook + // Call hook before updating the allowed bidders for the auction k.BeforeAllowedBidderUpdated(ctx, auctionId, bidder, maxBidAmount) k.SetAllowedBidder(ctx, auctionId, allowedBidder) @@ -310,7 +309,7 @@ func (k Keeper) UpdateAllowedBidder(ctx sdk.Context, auctionId uint64, bidder sd // AllocateSellingCoin allocates allocated selling coin for all matched bids in MatchingInfo and // releases them from the selling reserve account. func (k Keeper) AllocateSellingCoin(ctx sdk.Context, auction types.AuctionI, mInfo MatchingInfo) error { - // Call the before selling coin distributed hook + // Call hook before selling coin allocation k.BeforeSellingCoinsAllocated(ctx, auction.GetId(), mInfo.AllocationMap, mInfo.RefundMap) sellingReserveAddr := auction.GetSellingReserveAddress() @@ -349,24 +348,24 @@ func (k Keeper) AllocateSellingCoin(ctx sdk.Context, auction types.AuctionI, mIn // ReleaseVestingPayingCoin releases the vested selling coin to the auctioneer from the vesting reserve account. func (k Keeper) ReleaseVestingPayingCoin(ctx sdk.Context, auction types.AuctionI) error { - vqs := k.GetVestingQueuesByAuctionId(ctx, auction.GetId()) - vqsLen := len(vqs) + vestingQueues := k.GetVestingQueuesByAuctionId(ctx, auction.GetId()) + vestingQueuesLen := len(vestingQueues) - for i, vq := range vqs { - if vq.ShouldRelease(ctx.BlockTime()) { + for i, vestingQueue := range vestingQueues { + if vestingQueue.ShouldRelease(ctx.BlockTime()) { vestingReserveAddr := auction.GetVestingReserveAddress() auctioneerAddr := auction.GetAuctioneer() - payingCoins := sdk.NewCoins(vq.PayingCoin) + payingCoins := sdk.NewCoins(vestingQueue.PayingCoin) if err := k.bankKeeper.SendCoins(ctx, vestingReserveAddr, auctioneerAddr, payingCoins); err != nil { return sdkerrors.Wrap(err, "failed to release paying coin to the auctioneer") } - vq.SetReleased(true) - k.SetVestingQueue(ctx, vq) + vestingQueue.SetReleased(true) + k.SetVestingQueue(ctx, vestingQueue) - // Update status to AuctionStatusFinished when all the amounts are released - if i == vqsLen-1 { + // Update status when all the amounts are released + if i == vestingQueuesLen-1 { _ = auction.SetStatus(types.AuctionStatusFinished) k.SetAuction(ctx, auction) } @@ -376,8 +375,7 @@ func (k Keeper) ReleaseVestingPayingCoin(ctx sdk.Context, auction types.AuctionI return nil } -// RefundRemainingSellingCoin refunds the remaining selling coin back to the auctioneer. -// This function is called right after the selling coin is sold. +// RefundRemainingSellingCoin refunds the remaining selling coin to the auctioneer. func (k Keeper) RefundRemainingSellingCoin(ctx sdk.Context, auction types.AuctionI) error { sellingReserveAddr := auction.GetSellingReserveAddress() sellingCoinDenom := auction.GetSellingCoin().Denom @@ -390,7 +388,7 @@ func (k Keeper) RefundRemainingSellingCoin(ctx sdk.Context, auction types.Auctio return nil } -// RefundPayingCoin refunds paying coin back to the bidders. +// RefundPayingCoin refunds paying coin to the corresponding bidders. func (k Keeper) RefundPayingCoin(ctx sdk.Context, auction types.AuctionI, mInfo MatchingInfo) error { payingReserveAddr := auction.GetPayingReserveAddress() payingCoinDenom := auction.GetPayingCoinDenom() @@ -432,16 +430,15 @@ func (k Keeper) RefundPayingCoin(ctx sdk.Context, auction types.AuctionI, mInfo // ExtendRound extends another round of ExtendedPeriod value for the auction. func (k Keeper) ExtendRound(ctx sdk.Context, ba *types.BatchAuction) { params := k.GetParams(ctx) - extendedPeriod := ctx.BlockTime().AddDate(0, 0, int(params.ExtendedPeriod)) - - endTimes := ba.GetEndTimes() - endTimes = append(endTimes, extendedPeriod) + extendedPeriod := params.ExtendedPeriod + nextEndTime := ba.GetEndTimes()[len(ba.GetEndTimes())-1].AddDate(0, 0, int(extendedPeriod)) + endTimes := append(ba.GetEndTimes(), nextEndTime) _ = ba.SetEndTimes(endTimes) k.SetAuction(ctx, ba) } -// CloseFixedPriceAuction finishes a fixed price auction. +// CloseFixedPriceAuction closes a fixed price auction. func (k Keeper) CloseFixedPriceAuction(ctx sdk.Context, auction types.AuctionI) { mInfo := k.CalculateFixedPriceAllocation(ctx, auction) @@ -458,13 +455,20 @@ func (k Keeper) CloseFixedPriceAuction(ctx sdk.Context, auction types.AuctionI) } } -// CloseBatchAuction finishes a batch auction. +// CloseBatchAuction closes a batch auction. func (k Keeper) CloseBatchAuction(ctx sdk.Context, auction types.AuctionI) { - ba := auction.(*types.BatchAuction) + ba, ok := auction.(*types.BatchAuction) + if !ok { + panic(fmt.Errorf("unable to close auction that is not a batch auction: %T", auction)) + } - if ba.MaxExtendedRound+1 == uint32(len(auction.GetEndTimes())) { - mInfo := k.CalculateBatchAllocation(ctx, auction) + // Extend round since there is no last matched length to compare with + lastMatchedLen := k.GetLastMatchedBidsLen(ctx, ba.GetId()) + mInfo := k.CalculateBatchAllocation(ctx, auction) + // Close the auction when maximum extended round + 1 is the same as the length of end times + // If the value of MaxExtendedRound is 0, it means that an auctioneer does not want have an extended round + if ba.MaxExtendedRound+1 == uint32(len(auction.GetEndTimes())) { if err := k.AllocateSellingCoin(ctx, auction, mInfo); err != nil { panic(err) } @@ -484,17 +488,11 @@ func (k Keeper) CloseBatchAuction(ctx sdk.Context, auction types.AuctionI) { return } - // TODO: what if no matched bids for the auction? - // Extend round since there is no last matched length to compare with - lastMatchedLen := k.GetLastMatchedBidsLen(ctx, ba.GetId()) if lastMatchedLen == 0 { k.ExtendRound(ctx, ba) return } - // TODO: add test case - mInfo := k.CalculateBatchAllocation(ctx, auction) - currDec := sdk.NewDec(mInfo.MatchedLen) lastDec := sdk.NewDec(lastMatchedLen) diff := sdk.OneDec().Sub(currDec.Quo(lastDec)) // 1 - (CurrentMatchedLenDec / LastMatchedLenDec) diff --git a/x/fundraising/keeper/auction_test.go b/x/fundraising/keeper/auction_test.go index 9f3ebce5..7663c96a 100644 --- a/x/fundraising/keeper/auction_test.go +++ b/x/fundraising/keeper/auction_test.go @@ -310,6 +310,95 @@ func (s *KeeperTestSuite) TestFixedPriceAuction_CancelAuction() { s.Require().True(s.getBalance(sellingReserveAddr, sellingCoinDenom).IsZero()) } +func (s *KeeperTestSuite) TestBatchAuction_AuctionStatus() { + standByAuction := s.createBatchAuction( + s.addr(0), + parseDec("1"), + parseDec("0.1"), + parseCoin("5000_000_000denom1"), + "denom2", + []types.VestingSchedule{}, + 1, + sdk.MustNewDecFromStr("0.2"), + time.Now().AddDate(0, 6, 0), + time.Now().AddDate(0, 6, 0).AddDate(0, 1, 0), + true, + ) + + auction, found := s.keeper.GetAuction(s.ctx, standByAuction.GetId()) + s.Require().True(found) + s.Require().Equal(types.AuctionStatusStandBy, auction.GetStatus()) + + feePool := s.app.DistrKeeper.GetFeePool(s.ctx) + auctionCreationFee := s.keeper.GetParams(s.ctx).AuctionCreationFee + s.Require().True(feePool.CommunityPool.IsEqual(sdk.NewDecCoinsFromCoins(auctionCreationFee...))) + + startedAuction := s.createBatchAuction( + s.addr(1), + parseDec("0.5"), + parseDec("0.1"), + parseCoin("5000_000_000denom3"), + "denom4", + []types.VestingSchedule{}, + 1, + sdk.MustNewDecFromStr("0.2"), + time.Now().AddDate(0, 0, -1), + time.Now().AddDate(0, 0, -1).AddDate(0, 2, 0), + true, + ) + + auction, found = s.keeper.GetAuction(s.ctx, startedAuction.GetId()) + s.Require().True(found) + s.Require().Equal(types.AuctionStatusStarted, auction.GetStatus()) +} + +func (s *KeeperTestSuite) TestBatchAuction_MaxNumVestingSchedules() { + batchAuction := types.NewMsgCreateBatchAuction( + s.addr(0).String(), + parseDec("1"), + parseDec("0.1"), + parseCoin("5000_000_000denom1"), + "denom2", + []types.VestingSchedule{}, + 1, + sdk.MustNewDecFromStr("0.2"), + time.Now().AddDate(0, 6, 0), + time.Now().AddDate(0, 6, 0).AddDate(0, 1, 0), + ) + + params := s.keeper.GetParams(s.ctx) + s.fundAddr(s.addr(0), params.AuctionCreationFee.Add(batchAuction.SellingCoin)) + + // Invalid max extended round + batchAuction.MaxExtendedRound = types.MaxExtendedRound + 1 + + _, err := s.keeper.CreateBatchAuction(s.ctx, batchAuction) + s.Require().EqualError(err, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "exceed maximum extended round").Error()) + + batchAuction.MaxExtendedRound = 1 + + // Invalid number of vesting schedules + numSchedules := types.MaxNumVestingSchedules + 1 + schedules := make([]types.VestingSchedule, numSchedules) + totalWeight := sdk.ZeroDec() + for i := range schedules { + var schedule types.VestingSchedule + if i == numSchedules-1 { + schedule.Weight = sdk.OneDec().Sub(totalWeight) + } else { + schedule.Weight = sdk.OneDec().Quo(sdk.NewDec(int64(numSchedules))) + } + schedule.ReleaseTime = time.Now().AddDate(0, 0, i) + + totalWeight = totalWeight.Add(schedule.Weight) + schedules[i] = schedule + } + batchAuction.VestingSchedules = schedules + + _, err = s.keeper.CreateBatchAuction(s.ctx, batchAuction) + s.Require().EqualError(err, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "exceed maximum number of vesting schedules").Error()) +} + func (s *KeeperTestSuite) TestFixedPriceAuction_MaxNumVestingSchedules() { fixedPriceAuction := types.NewMsgCreateFixedPriceAuction( s.addr(0).String(), @@ -345,6 +434,42 @@ func (s *KeeperTestSuite) TestFixedPriceAuction_MaxNumVestingSchedules() { _, err := s.keeper.CreateFixedPriceAuction(s.ctx, fixedPriceAuction) s.Require().EqualError(err, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "exceed maximum number of vesting schedules").Error()) } + +func (s *KeeperTestSuite) TestInvalidEndTime() { + params := s.keeper.GetParams(s.ctx) + + fixedPriceAuction := types.NewMsgCreateFixedPriceAuction( + s.addr(0).String(), + parseDec("0.5"), + parseCoin("500_000_000_000denom1"), + "denom2", + []types.VestingSchedule{}, + types.MustParseRFC3339("2022-03-01T00:00:00Z"), + types.MustParseRFC3339("2022-01-01T00:00:00Z"), + ) + s.fundAddr(s.addr(0), params.AuctionCreationFee.Add(fixedPriceAuction.SellingCoin)) + + _, err := s.keeper.CreateFixedPriceAuction(s.ctx, fixedPriceAuction) + s.Require().EqualError(err, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "end time must be set after the current time").Error()) + + batchAuction := types.NewMsgCreateBatchAuction( + s.addr(1).String(), + parseDec("1"), + parseDec("0.1"), + parseCoin("5000_000_000denom1"), + "denom2", + []types.VestingSchedule{}, + 1, + sdk.MustNewDecFromStr("0.2"), + types.MustParseRFC3339("2022-03-01T00:00:00Z"), + types.MustParseRFC3339("2022-01-01T00:00:00Z"), + ) + s.fundAddr(s.addr(1), params.AuctionCreationFee.Add(batchAuction.SellingCoin)) + + _, err = s.keeper.CreateBatchAuction(s.ctx, batchAuction) + s.Require().EqualError(err, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "end time must be set after the current time").Error()) +} + func (s *KeeperTestSuite) TestAddAllowedBidders() { startedAuction := s.createFixedPriceAuction( s.addr(0), @@ -362,6 +487,12 @@ func (s *KeeperTestSuite) TestAddAllowedBidders() { s.Require().Equal(types.AuctionStatusStarted, auction.GetStatus()) s.Require().Len(s.keeper.GetAllowedBiddersByAuction(s.ctx, startedAuction.Id), 0) + // Invalid auction id + err := s.keeper.AddAllowedBidders(s.ctx, 10, []types.AllowedBidder{ + {Bidder: s.addr(1).String(), MaxBidAmount: sdk.NewInt(100_000_000)}, + }) + s.Require().Error(err) + for _, tc := range []struct { name string bidders []types.AllowedBidder @@ -476,6 +607,10 @@ func (s *KeeperTestSuite) TestUpdateAllowedBidder() { s.Require().True(found) s.Require().Len(s.keeper.GetAllowedBiddersByAuction(s.ctx, startedAuction.Id), 0) + // Invalid auction id + err := s.keeper.UpdateAllowedBidder(s.ctx, 10, s.addr(1), sdk.NewInt(100_000_000)) + s.Require().Error(err) + // Add 5 bidders with different maximum bid amount s.Require().NoError(s.keeper.AddAllowedBidders(s.ctx, auction.GetId(), []types.AllowedBidder{ {Bidder: s.addr(1).String(), MaxBidAmount: sdk.NewInt(100_000_000)}, @@ -539,91 +674,194 @@ func (s *KeeperTestSuite) TestUpdateAllowedBidder() { } } -func (s *KeeperTestSuite) TestBatchAuction_AuctionStatus() { - standByAuction := s.createBatchAuction( +func (s *KeeperTestSuite) TestRefundPayingCoin() { + auction := s.createBatchAuction( s.addr(0), - parseDec("1"), - parseDec("0.1"), - parseCoin("5000_000_000denom1"), + parseDec("1.0"), + parseDec("0.5"), + parseCoin("100_000_000denom1"), "denom2", []types.VestingSchedule{}, 1, sdk.MustNewDecFromStr("0.2"), - time.Now().AddDate(0, 6, 0), - time.Now().AddDate(0, 6, 0).AddDate(0, 1, 0), + time.Now().AddDate(0, 0, -1), + time.Now().AddDate(0, 0, -1).AddDate(0, 2, 0), true, ) + s.Require().Equal(types.AuctionStatusStarted, auction.GetStatus()) - auction, found := s.keeper.GetAuction(s.ctx, standByAuction.GetId()) + s.placeBidBatchMany(auction.Id, s.addr(1), parseDec("1"), parseCoin("50_000_000denom1"), sdk.NewInt(1_000_000_000), true) + refundBid := s.placeBidBatchMany(auction.Id, s.addr(2), parseDec("0.9"), parseCoin("60_000_000denom1"), sdk.NewInt(1_000_000_000), true) + + a, found := s.keeper.GetAuction(s.ctx, auction.Id) s.Require().True(found) - s.Require().Equal(types.AuctionStatusStandBy, auction.GetStatus()) - feePool := s.app.DistrKeeper.GetFeePool(s.ctx) - auctionCreationFee := s.keeper.GetParams(s.ctx).AuctionCreationFee - s.Require().True(feePool.CommunityPool.IsEqual(sdk.NewDecCoinsFromCoins(auctionCreationFee...))) + mInfo := s.keeper.CalculateBatchAllocation(s.ctx, a) - startedAuction := s.createBatchAuction( - s.addr(1), + err := s.keeper.RefundPayingCoin(s.ctx, a, mInfo) + s.Require().NoError(err) + + expectedAmt := refundBid.ConvertToPayingAmount(auction.GetPayingCoinDenom()) + bidderBalance := s.getBalance(s.addr(2), auction.GetPayingCoinDenom()).Amount + s.Require().Equal(expectedAmt, bidderBalance) +} + +func (s *KeeperTestSuite) TestCloseFixedPriceAuction() { + auction := s.createFixedPriceAuction( + s.addr(0), + parseDec("1"), + parseCoin("1_000_000_000_000denom1"), + "denom2", + []types.VestingSchedule{ + {ReleaseTime: types.MustParseRFC3339("2023-01-01T00:00:00Z"), Weight: sdk.OneDec()}, + }, + time.Now().AddDate(0, 0, -1), + time.Now().AddDate(0, 0, -1).AddDate(0, 2, 0), + true, + ) + s.Require().Equal(types.AuctionStatusStarted, auction.GetStatus()) + + s.placeBidFixedPrice(auction.Id, s.addr(1), parseDec("1"), parseCoin("250_000_000denom1"), true) + s.placeBidFixedPrice(auction.Id, s.addr(2), parseDec("1"), parseCoin("250_000_000denom1"), true) + s.placeBidFixedPrice(auction.Id, s.addr(3), parseDec("1"), parseCoin("250_000_000denom1"), true) + s.placeBidFixedPrice(auction.Id, s.addr(4), parseDec("1"), parseCoin("250_000_000denom1"), true) + + a, found := s.keeper.GetAuction(s.ctx, auction.Id) + s.Require().True(found) + + s.keeper.CloseFixedPriceAuction(s.ctx, a) + + s.Require().Equal(parseCoin("999000000000denom1"), s.getBalance(s.addr(0), a.GetSellingCoin().Denom)) + s.Require().Equal(parseCoin("0denom2"), s.getBalance(s.addr(0), a.GetPayingCoinDenom())) + s.Require().Equal(parseCoin("250_000_000denom1"), s.getBalance(s.addr(1), a.GetSellingCoin().Denom)) + s.Require().Equal(parseCoin("250_000_000denom1"), s.getBalance(s.addr(2), a.GetSellingCoin().Denom)) + s.Require().Equal(parseCoin("250_000_000denom1"), s.getBalance(s.addr(3), a.GetSellingCoin().Denom)) + s.Require().Equal(parseCoin("250_000_000denom1"), s.getBalance(s.addr(4), a.GetSellingCoin().Denom)) + s.Require().Len(s.keeper.GetVestingQueues(s.ctx), len(a.GetVestingSchedules())) +} + +func (s *KeeperTestSuite) TestCloseBatchAuction() { + // Close a batch auction right away by setting MaxExtendedRound to 0 value + maxExtendedRound := uint32(0) + + auction := s.createBatchAuction( + s.addr(0), parseDec("0.5"), parseDec("0.1"), - parseCoin("5000_000_000denom3"), - "denom4", + parseCoin("10_000_000_000denom1"), + "denom2", []types.VestingSchedule{}, - 1, + maxExtendedRound, sdk.MustNewDecFromStr("0.2"), time.Now().AddDate(0, 0, -1), time.Now().AddDate(0, 0, -1).AddDate(0, 2, 0), true, ) + s.Require().Equal(types.AuctionStatusStarted, auction.GetStatus()) - auction, found = s.keeper.GetAuction(s.ctx, startedAuction.GetId()) + s.placeBidBatchMany(auction.Id, s.addr(1), parseDec("0.9"), parseCoin("200_000_000denom1"), sdk.NewInt(1_000_000_000), true) + s.placeBidBatchMany(auction.Id, s.addr(2), parseDec("0.8"), parseCoin("200_000_000denom1"), sdk.NewInt(1_000_000_000), true) + s.placeBidBatchMany(auction.Id, s.addr(3), parseDec("0.7"), parseCoin("100_000_000denom1"), sdk.NewInt(1_000_000_000), true) + + a, found := s.keeper.GetAuction(s.ctx, auction.Id) s.Require().True(found) + + s.keeper.CloseBatchAuction(s.ctx, a) + + s.Require().Equal(parseCoin("350000000denom2"), s.getBalance(s.addr(0), a.GetPayingCoinDenom())) + s.Require().Equal(parseCoin("9500000000denom1"), s.getBalance(s.addr(0), a.GetSellingCoin().Denom)) + s.Require().Equal(parseCoin("200_000_000denom1"), s.getBalance(s.addr(1), a.GetSellingCoin().Denom)) + s.Require().Equal(parseCoin("200_000_000denom1"), s.getBalance(s.addr(2), a.GetSellingCoin().Denom)) + s.Require().Equal(parseCoin("100_000_000denom1"), s.getBalance(s.addr(3), a.GetSellingCoin().Denom)) + s.Require().Len(s.keeper.GetVestingQueues(s.ctx), len(a.GetVestingSchedules())) +} + +func (s *KeeperTestSuite) TestCloseBatchAuction_ExtendRound() { + // Extend round for a batch auction by setting MaxExtendedRound to non zero value + maxExtendedRound := uint32(5) + extendedRoundRate := parseDec("0.2") + + auction := s.createBatchAuction( + s.addr(0), + parseDec("0.5"), + parseDec("0.1"), + parseCoin("10_000_000_000denom1"), + "denom2", + []types.VestingSchedule{}, + maxExtendedRound, + extendedRoundRate, + time.Now().AddDate(0, 0, -1), + time.Now().AddDate(0, 0, -1).AddDate(0, 2, 0), + true, + ) s.Require().Equal(types.AuctionStatusStarted, auction.GetStatus()) + + s.placeBidBatchMany(auction.Id, s.addr(1), parseDec("0.9"), parseCoin("200_000_000denom1"), sdk.NewInt(1_000_000_000), true) + s.placeBidBatchMany(auction.Id, s.addr(2), parseDec("0.8"), parseCoin("200_000_000denom1"), sdk.NewInt(1_000_000_000), true) + s.placeBidBatchMany(auction.Id, s.addr(3), parseDec("0.7"), parseCoin("100_000_000denom1"), sdk.NewInt(1_000_000_000), true) + + a, found := s.keeper.GetAuction(s.ctx, auction.Id) + s.Require().True(found) + s.Require().Len(a.GetEndTimes(), 1) + + s.keeper.CloseBatchAuction(s.ctx, auction) + + // Extended round must be triggered + a, found = s.keeper.GetAuction(s.ctx, auction.Id) + s.Require().True(found) + s.Require().Len(a.GetEndTimes(), 2) + + // Auction sniping occurs + s.placeBidBatchMany(auction.Id, s.addr(4), parseDec("0.85"), parseCoin("9_800_000_000denom1"), sdk.NewInt(100_000_000_000), true) + + s.keeper.CloseBatchAuction(s.ctx, a) + + a, found = s.keeper.GetAuction(s.ctx, auction.Id) + s.Require().True(found) + s.Require().Len(a.GetEndTimes(), 3) } -func (s *KeeperTestSuite) TestBatchAuction_MaxNumVestingSchedules() { - batchAuction := types.NewMsgCreateBatchAuction( - s.addr(0).String(), - parseDec("1"), +func (s *KeeperTestSuite) TestCloseBatchAuction_Valid() { + // Extend round for a batch auction by setting MaxExtendedRound to non zero value + maxExtendedRound := uint32(5) + extendedRoundRate := parseDec("0.2") + + auction := s.createBatchAuction( + s.addr(0), + parseDec("0.5"), parseDec("0.1"), - parseCoin("5000_000_000denom1"), + parseCoin("10_000_000_000denom1"), "denom2", []types.VestingSchedule{}, - 1, - sdk.MustNewDecFromStr("0.2"), - time.Now().AddDate(0, 6, 0), - time.Now().AddDate(0, 6, 0).AddDate(0, 1, 0), + maxExtendedRound, + extendedRoundRate, + time.Now().AddDate(0, 0, -1), + time.Now().AddDate(0, 0, -1).AddDate(0, 2, 0), + true, ) + s.Require().Equal(types.AuctionStatusStarted, auction.GetStatus()) - params := s.keeper.GetParams(s.ctx) - s.fundAddr(s.addr(0), params.AuctionCreationFee.Add(batchAuction.SellingCoin)) + s.placeBidBatchMany(auction.Id, s.addr(1), parseDec("0.9"), parseCoin("200_000_000denom1"), sdk.NewInt(1_000_000_000), true) + s.placeBidBatchMany(auction.Id, s.addr(2), parseDec("0.8"), parseCoin("200_000_000denom1"), sdk.NewInt(1_000_000_000), true) + s.placeBidBatchMany(auction.Id, s.addr(3), parseDec("0.7"), parseCoin("100_000_000denom1"), sdk.NewInt(1_000_000_000), true) - // Invalid max extended round - batchAuction.MaxExtendedRound = types.MaxExtendedRound + 1 + a, found := s.keeper.GetAuction(s.ctx, auction.Id) + s.Require().True(found) + s.Require().Len(a.GetEndTimes(), 1) - _, err := s.keeper.CreateBatchAuction(s.ctx, batchAuction) - s.Require().EqualError(err, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "exceed maximum extended round").Error()) + s.keeper.CloseBatchAuction(s.ctx, auction) - batchAuction.MaxExtendedRound = 1 + // Extended round must be triggered + a, found = s.keeper.GetAuction(s.ctx, auction.Id) + s.Require().True(found) + s.Require().Len(a.GetEndTimes(), 2) - // Invalid number of vesting schedules - numSchedules := types.MaxNumVestingSchedules + 1 - schedules := make([]types.VestingSchedule, numSchedules) - totalWeight := sdk.ZeroDec() - for i := range schedules { - var schedule types.VestingSchedule - if i == numSchedules-1 { - schedule.Weight = sdk.OneDec().Sub(totalWeight) - } else { - schedule.Weight = sdk.OneDec().Quo(sdk.NewDec(int64(numSchedules))) - } - schedule.ReleaseTime = time.Now().AddDate(0, 0, i) + // Auction sniping occurs + s.placeBidBatchMany(auction.Id, s.addr(4), parseDec("0.85"), parseCoin("9_500_000_000denom1"), sdk.NewInt(100_000_000_000), true) - totalWeight = totalWeight.Add(schedule.Weight) - schedules[i] = schedule - } - batchAuction.VestingSchedules = schedules + s.keeper.CloseBatchAuction(s.ctx, a) - _, err = s.keeper.CreateBatchAuction(s.ctx, batchAuction) - s.Require().EqualError(err, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "exceed maximum number of vesting schedules").Error()) + a, found = s.keeper.GetAuction(s.ctx, auction.Id) + s.Require().True(found) + s.Require().Len(a.GetEndTimes(), 2) } diff --git a/x/fundraising/keeper/genesis_test.go b/x/fundraising/keeper/genesis_test.go index e564c27a..605379d7 100644 --- a/x/fundraising/keeper/genesis_test.go +++ b/x/fundraising/keeper/genesis_test.go @@ -113,5 +113,3 @@ func (s *KeeperTestSuite) TestGenesisState() { }) s.Require().Equal(genState, s.keeper.ExportGenesis(s.ctx)) } - -// TODO: add cases for allowed bidders diff --git a/x/fundraising/keeper/store_test.go b/x/fundraising/keeper/store_test.go index 705cee90..31a1a949 100644 --- a/x/fundraising/keeper/store_test.go +++ b/x/fundraising/keeper/store_test.go @@ -157,6 +157,20 @@ func (s *KeeperTestSuite) TestIterateBids() { s.Require().Len(bidsByBidder, 2) } +func (s *KeeperTestSuite) TestVestingQueue() { + vestingQueue := types.NewVestingQueue( + 1, + s.addr(1), + parseCoin("100_000_000denom1"), + types.MustParseRFC3339("2023-01-01T00:00:00Z"), + false, + ) + s.keeper.SetVestingQueue(s.ctx, vestingQueue) + + vq := s.keeper.GetVestingQueue(s.ctx, 1, vestingQueue.ReleaseTime) + s.Require().EqualValues(vestingQueue, vq) +} + func (s *KeeperTestSuite) TestVestingQueueIterator() { payingReserveAddress := s.addr(0) payingCoinDenom := "denom1" diff --git a/x/fundraising/module.go b/x/fundraising/module.go index 1a48a6aa..3dad158d 100644 --- a/x/fundraising/module.go +++ b/x/fundraising/module.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "math/rand" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -14,10 +15,12 @@ import ( cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/fundraising/x/fundraising/client/cli" "github.com/tendermint/fundraising/x/fundraising/keeper" + "github.com/tendermint/fundraising/x/fundraising/simulation" "github.com/tendermint/fundraising/x/fundraising/types" ) @@ -179,3 +182,34 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +// ---------------------------------------------------------------------------- +// AppModuleSimulation +// ---------------------------------------------------------------------------- + +// GenerateGenesisState creates a randomized GenState of the module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { + return nil +} + +// RandomizedParams creates randomized param changes for the simulator. +func (am AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder registers store decoders. +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, + ) +} diff --git a/x/fundraising/module_simulation.go b/x/fundraising/module_simulation.go deleted file mode 100644 index 1704d8c1..00000000 --- a/x/fundraising/module_simulation.go +++ /dev/null @@ -1,43 +0,0 @@ -package fundraising - -import ( - "math/rand" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - - "github.com/tendermint/fundraising/x/fundraising/simulation" - "github.com/tendermint/fundraising/x/fundraising/types" -) - -// ---------------------------------------------------------------------------- -// AppModuleSimulation -// ---------------------------------------------------------------------------- - -// GenerateGenesisState creates a randomized GenState of the module. -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// ProposalContents doesn't return any content functions for governance proposals. -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { - return nil -} - -// RandomizedParams creates randomized param changes for the simulator. -func (am AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { - return simulation.ParamChanges(r) -} - -// RegisterStoreDecoder registers store decoders. -func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations( - simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, - ) -} diff --git a/x/fundraising/spec/02_state.md b/x/fundraising/spec/02_state.md index 53de32ab..2d1e8e6d 100644 --- a/x/fundraising/spec/02_state.md +++ b/x/fundraising/spec/02_state.md @@ -60,7 +60,7 @@ type AuctionI interface { } ``` -## Base Auction +## BaseAuction A base auction stores all requisite fields directly in a struct. @@ -89,7 +89,7 @@ type BaseAuction struct { // AllowedBidder defines a bidder who is allowed to bid with max number of bids. type AllowedBidder struct { Bidder string // a bidder who is allowed to bid - MaxBidAmount uint64 // a maximum amount of bids per bidder + MaxBidAmount sdk.Int // a maximum amount of bids per bidder } ``` diff --git a/x/fundraising/spec/07_params.md b/x/fundraising/spec/07_params.md index a0526146..43ba4144 100644 --- a/x/fundraising/spec/07_params.md +++ b/x/fundraising/spec/07_params.md @@ -28,7 +28,7 @@ There are some global constants defined in `x/fundraising/types/params.go`. ## MaxNumVestingSchedules -`MaxNumVestingSchedules` is the maximum number of vesting schedules for an auction to have. It is set to `50`. +`MaxNumVestingSchedules` is the maximum number of vesting schedules for an auction to have. It is set to `100`. ## MaxExtendedRound diff --git a/x/fundraising/types/allowed_bidder.go b/x/fundraising/types/allowed_bidder.go index ccedf87e..824689ad 100644 --- a/x/fundraising/types/allowed_bidder.go +++ b/x/fundraising/types/allowed_bidder.go @@ -22,6 +22,7 @@ func (ab AllowedBidder) GetBidder() sdk.AccAddress { return addr } +// Validate validates allowed bidder object. func (ab AllowedBidder) Validate() error { if _, err := sdk.AccAddressFromBech32(ab.Bidder); err != nil { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) diff --git a/x/fundraising/types/auction_test.go b/x/fundraising/types/auction_test.go index 3458c779..ef3508dc 100644 --- a/x/fundraising/types/auction_test.go +++ b/x/fundraising/types/auction_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/crypto" @@ -14,6 +15,100 @@ import ( ) func TestUnpackAuction(t *testing.T) { + auction := types.NewFixedPriceAuction( + types.NewBaseAuction( + 1, + types.AuctionTypeFixedPrice, + sdk.AccAddress(crypto.AddressHash([]byte("Auctioneer"))).String(), + types.SellingReserveAddress(1).String(), + types.PayingReserveAddress(1).String(), + sdk.MustNewDecFromStr("0.5"), + sdk.NewInt64Coin("denom3", 1_000_000_000_000), + "denom4", + types.VestingReserveAddress(1).String(), + []types.VestingSchedule{ + {ReleaseTime: types.MustParseRFC3339("2023-01-01T00:00:00Z"), Weight: sdk.OneDec()}, + }, + types.MustParseRFC3339("2022-01-01T00:00:00Z"), + []time.Time{types.MustParseRFC3339("2022-02-01T00:00:00Z")}, + types.AuctionStatusStarted, + ), + sdk.NewInt64Coin("denom3", 1_000_000_000_000), + ) + + any, err := types.PackAuction(auction) + require.NoError(t, err) + + marshaled, err := any.Marshal() + require.NoError(t, err) + + var any2 codectypes.Any + err = any2.Unmarshal(marshaled) + require.NoError(t, err) + + reMarshal, err := any2.Marshal() + require.NoError(t, err) + require.Equal(t, marshaled, reMarshal) + + auction2, err := types.UnpackAuction(&any2) + require.NoError(t, err) + + require.Equal(t, auction.Id, auction2.GetId()) + require.Equal(t, auction.Type, auction2.GetType()) + require.Equal(t, auction.Auctioneer, auction2.GetAuctioneer().String()) + require.Equal(t, auction.SellingCoin, auction2.GetSellingCoin()) + require.Equal(t, auction.PayingCoinDenom, auction2.GetPayingCoinDenom()) + require.Equal(t, auction.StartPrice, auction2.GetStartPrice()) + require.Equal(t, auction.SellingReserveAddress, auction2.GetSellingReserveAddress().String()) + require.Equal(t, auction.SellingReserveAddress, auction2.GetSellingReserveAddress().String()) + require.Equal(t, auction.PayingReserveAddress, auction2.GetPayingReserveAddress().String()) + require.Equal(t, auction.VestingReserveAddress, auction2.GetVestingReserveAddress().String()) + require.Equal(t, auction.VestingSchedules, auction2.GetVestingSchedules()) + require.Equal(t, auction.StartTime.UTC(), auction2.GetStartTime().UTC()) + require.Equal(t, auction.EndTimes[0].UTC(), auction2.GetEndTimes()[0].UTC()) + require.Equal(t, auction.Status, auction2.GetStatus()) +} + +func TestUnpackAuctionJSON(t *testing.T) { + auction := types.NewFixedPriceAuction( + types.NewBaseAuction( + 1, + types.AuctionTypeFixedPrice, + sdk.AccAddress(crypto.AddressHash([]byte("Auctioneer"))).String(), + types.SellingReserveAddress(1).String(), + types.PayingReserveAddress(1).String(), + sdk.MustNewDecFromStr("0.5"), + sdk.NewInt64Coin("denom1", 1_000_000_000_000), + "denom2", + types.VestingReserveAddress(1).String(), + []types.VestingSchedule{}, + time.Now().AddDate(0, 0, -1), + []time.Time{time.Now().AddDate(0, 1, -1)}, + types.AuctionStatusStarted, + ), + sdk.NewInt64Coin("denom2", 1_000_000_000_000), + ) + + any, err := types.PackAuction(auction) + require.NoError(t, err) + + registry := codectypes.NewInterfaceRegistry() + types.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) + + bz := cdc.MustMarshalJSON(any) + + var any2 codectypes.Any + err = cdc.UnmarshalJSON(bz, &any2) + require.NoError(t, err) + + auction2, err := types.UnpackAuction(&any2) + require.NoError(t, err) + + require.Equal(t, uint64(1), auction2.GetId()) +} + +func TestUnpackAuctions(t *testing.T) { auction := []types.AuctionI{ types.NewFixedPriceAuction( types.NewBaseAuction( @@ -23,6 +118,24 @@ func TestUnpackAuction(t *testing.T) { types.SellingReserveAddress(1).String(), types.PayingReserveAddress(1).String(), sdk.MustNewDecFromStr("0.5"), + sdk.NewInt64Coin("denom1", 1_000_000_000_000), + "denom2", + types.VestingReserveAddress(1).String(), + []types.VestingSchedule{}, + time.Now().AddDate(0, 0, -1), + []time.Time{time.Now().AddDate(0, 1, -1)}, + types.AuctionStatusStarted, + ), + sdk.NewInt64Coin("denom2", 1_000_000_000_000), + ), + types.NewBatchAuction( + types.NewBaseAuction( + 2, + types.AuctionTypeFixedPrice, + sdk.AccAddress(crypto.AddressHash([]byte("Auctioneer"))).String(), + types.SellingReserveAddress(1).String(), + types.PayingReserveAddress(1).String(), + sdk.MustNewDecFromStr("0.5"), sdk.NewInt64Coin("denom3", 1_000_000_000_000), "denom4", types.VestingReserveAddress(1).String(), @@ -31,26 +144,36 @@ func TestUnpackAuction(t *testing.T) { []time.Time{time.Now().AddDate(0, 1, -1)}, types.AuctionStatusStarted, ), - sdk.NewInt64Coin("denom3", 1_000_000_000_000), + sdk.MustNewDecFromStr("0.1"), + sdk.ZeroDec(), + uint32(3), + sdk.MustNewDecFromStr("0.15"), ), } any, err := types.PackAuction(auction[0]) require.NoError(t, err) - marshaled, err := any.Marshal() + any2, err := types.PackAuction(auction[1]) require.NoError(t, err) - var any2 codectypes.Any - err = any2.Unmarshal(marshaled) + anyAuctions := []*codectypes.Any{any, any2} + auctions, err := types.UnpackAuctions(anyAuctions) require.NoError(t, err) - reMarshal, err := any2.Marshal() - require.NoError(t, err) - require.Equal(t, marshaled, reMarshal) + registry := codectypes.NewInterfaceRegistry() + types.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) - _, err = types.UnpackAuction(&any2) - require.NoError(t, err) + bz1 := types.MustMarshalAuction(cdc, auctions[0]) + auction1 := types.MustUnmarshalAuction(cdc, bz1) + _, ok := auction1.(*types.FixedPriceAuction) + require.True(t, ok) + + bz2 := types.MustMarshalAuction(cdc, auctions[1]) + auction2 := types.MustUnmarshalAuction(cdc, bz2) + _, ok = auction2.(*types.BatchAuction) + require.True(t, ok) } func TestShouldAuctionStarted(t *testing.T) { diff --git a/x/fundraising/types/bid.go b/x/fundraising/types/bid.go index 3e01a6b5..f4cc5e06 100644 --- a/x/fundraising/types/bid.go +++ b/x/fundraising/types/bid.go @@ -4,6 +4,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// NewBid returns a new Bid. +func NewBid(auctionId uint64, bidder sdk.AccAddress, bidId uint64, bidType BidType, price sdk.Dec, coin sdk.Coin, isMatched bool) Bid { + return Bid{ + AuctionId: auctionId, + Bidder: bidder.String(), + Id: bidId, + Type: bidType, + Price: price, + Coin: coin, + IsMatched: isMatched, + } +} + func (b Bid) GetBidder() sdk.AccAddress { addr, err := sdk.AccAddressFromBech32(b.Bidder) if err != nil { diff --git a/x/fundraising/types/bid_test.go b/x/fundraising/types/bid_test.go index 6bd117d6..baee30fa 100644 --- a/x/fundraising/types/bid_test.go +++ b/x/fundraising/types/bid_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/fundraising/x/fundraising/types" + "github.com/tendermint/tendermint/crypto" ) func TestConvertToSellingAmount(t *testing.T) { @@ -108,3 +109,22 @@ func TestConvertToPayingAmount(t *testing.T) { require.Equal(t, tc.expectedAmt, payingAmt) } } + +func TestSetMatched(t *testing.T) { + bidder := sdk.AccAddress(crypto.AddressHash([]byte("Bidder"))) + + bid := types.NewBid( + 1, + bidder, + 1, + types.BidTypeFixedPrice, + sdk.MustNewDecFromStr("0.5"), + sdk.NewCoin("denom1", sdk.NewInt(100_000)), + false, + ) + require.False(t, bid.IsMatched) + require.Equal(t, bidder, bid.GetBidder()) + + bid.SetMatched(true) + require.True(t, bid.IsMatched) +} diff --git a/x/fundraising/types/keys_test.go b/x/fundraising/types/keys_test.go index 02fee1e0..52852cbd 100644 --- a/x/fundraising/types/keys_test.go +++ b/x/fundraising/types/keys_test.go @@ -20,12 +20,54 @@ func TestKeysTestSuite(t *testing.T) { suite.Run(t, new(keysTestSuite)) } +func (s *keysTestSuite) TestGetLastBidIdKey() { + s.Require().Equal([]byte{0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, types.GetLastBidIdKey(0)) + s.Require().Equal([]byte{0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}, types.GetLastBidIdKey(9)) + s.Require().Equal([]byte{0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, types.GetLastBidIdKey(10)) +} + func (s *keysTestSuite) TestGetAuctionKey() { s.Require().Equal([]byte{0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, types.GetAuctionKey(0)) s.Require().Equal([]byte{0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}, types.GetAuctionKey(9)) s.Require().Equal([]byte{0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, types.GetAuctionKey(10)) } +func (s *keysTestSuite) TestGetAllowedBiddersByAuctionKeyPrefix() { + s.Require().Equal([]byte{0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, types.GetAllowedBiddersByAuctionKeyPrefix(0)) + s.Require().Equal([]byte{0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}, types.GetAllowedBiddersByAuctionKeyPrefix(9)) + s.Require().Equal([]byte{0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, types.GetAllowedBiddersByAuctionKeyPrefix(10)) +} + +func (s *keysTestSuite) TestGetBidByAuctionIdPrefix() { + s.Require().Equal([]byte{0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, types.GetBidByAuctionIdPrefix(0)) + s.Require().Equal([]byte{0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}, types.GetBidByAuctionIdPrefix(9)) + s.Require().Equal([]byte{0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, types.GetBidByAuctionIdPrefix(10)) +} + +func (s *keysTestSuite) TestGetLastMatchedBidsLenKey() { + s.Require().Equal([]byte{0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, types.GetLastMatchedBidsLenKey(0)) + s.Require().Equal([]byte{0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}, types.GetLastMatchedBidsLenKey(9)) + s.Require().Equal([]byte{0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, types.GetLastMatchedBidsLenKey(10)) +} + +func (s *keysTestSuite) TestGetBidIndexByBidderPrefix() { + bidder1 := sdk.AccAddress(crypto.AddressHash([]byte("bidder1"))) + bidder2 := sdk.AccAddress(crypto.AddressHash([]byte("bidder2"))) + bidder3 := sdk.AccAddress(crypto.AddressHash([]byte("bidder3"))) + s.Require().Equal([]byte{0x32, 0x14, 0x20, 0x5c, 0xa, 0x82, 0xa, 0xf1, 0xed, + 0x98, 0x39, 0x6a, 0x35, 0xfe, 0xe3, 0x5d, 0x5, 0x2c, 0xd7, 0x96, 0x5a, 0x37}, types.GetBidIndexByBidderPrefix(bidder1)) + s.Require().Equal([]byte{0x32, 0x14, 0xa, 0xaf, 0x72, 0x3a, 0xd0, 0x8c, 0x17, + 0x88, 0x2e, 0xf6, 0x7a, 0x5, 0x31, 0xb3, 0x46, 0xdd, 0x22, 0xb3, 0x62, 0x1e}, types.GetBidIndexByBidderPrefix(bidder2)) + s.Require().Equal([]byte{0x32, 0x14, 0xe, 0x99, 0x7b, 0x9b, 0x5c, 0xef, 0x81, + 0x2f, 0xc6, 0x3f, 0xb6, 0x8b, 0x27, 0x42, 0x8a, 0xab, 0x7a, 0x58, 0xbc, 0x5e}, types.GetBidIndexByBidderPrefix(bidder3)) +} + +func (s *keysTestSuite) TestGetVestingQueueByAuctionIdPrefix() { + s.Require().Equal([]byte{0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, types.GetVestingQueueByAuctionIdPrefix(0)) + s.Require().Equal([]byte{0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}, types.GetVestingQueueByAuctionIdPrefix(9)) + s.Require().Equal([]byte{0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, types.GetVestingQueueByAuctionIdPrefix(10)) +} + func (s *keysTestSuite) TestGetAllowedBidderKey() { testCases := []struct { auctionId uint64 diff --git a/x/fundraising/types/match_test.go b/x/fundraising/types/match_test.go index 36b7be9a..e3125ae8 100644 --- a/x/fundraising/types/match_test.go +++ b/x/fundraising/types/match_test.go @@ -10,8 +10,8 @@ import ( "github.com/tendermint/fundraising/x/fundraising/types" ) -// TODO: these helper functions below are taken from keeper_test package -// move these to separate file +// These helper functions below are taken from keeper_test package +// move these to separate file func testAddr(addrNum int) sdk.AccAddress { addr := make(sdk.AccAddress, 20) diff --git a/x/fundraising/types/vesting.go b/x/fundraising/types/vesting.go index 64e14bcd..57398e9b 100644 --- a/x/fundraising/types/vesting.go +++ b/x/fundraising/types/vesting.go @@ -47,12 +47,24 @@ func ValidateVestingSchedules(schedules []VestingSchedule, endTime time.Time) er return nil } +// NewVestingQueue returns a new VestingQueue. +func NewVestingQueue(auctionId uint64, auctioneer sdk.AccAddress, payingCoin sdk.Coin, releaseTime time.Time, released bool) VestingQueue { + return VestingQueue{ + AuctionId: auctionId, + Auctioneer: auctioneer.String(), + PayingCoin: payingCoin, + ReleaseTime: releaseTime, + Released: released, + } +} + // ShouldRelease returns true when the vesting queue is ready to release the paying coin. // It checks if the release time is equal or before the given time t and released value is false. func (vq VestingQueue) ShouldRelease(t time.Time) bool { return !vq.GetReleaseTime().After(t) && !vq.Released } +// SetReleased sets released status of the vesting queue. func (vq *VestingQueue) SetReleased(status bool) { vq.Released = status } diff --git a/x/fundraising/types/vesting_test.go b/x/fundraising/types/vesting_test.go index 59222e26..c0c3ba57 100644 --- a/x/fundraising/types/vesting_test.go +++ b/x/fundraising/types/vesting_test.go @@ -135,3 +135,17 @@ func TestValidateVestingSchedules(t *testing.T) { }) } } + +func TestSetReleased(t *testing.T) { + vestingQueue := types.NewVestingQueue( + 1, + sdk.AccAddress(crypto.AddressHash([]byte("Auctioneer"))), + sdk.NewInt64Coin("denom1", 10000000), + types.MustParseRFC3339("2021-11-01T00:00:00Z"), + false, + ) + require.False(t, vestingQueue.Released) + + vestingQueue.SetReleased(true) + require.True(t, vestingQueue.Released) +}