diff --git a/contract/Forest/ForestContract.cs b/contract/Forest/ForestContract.cs index cd8346bc..4ce475de 100755 --- a/contract/Forest/ForestContract.cs +++ b/contract/Forest/ForestContract.cs @@ -239,5 +239,23 @@ public override Empty CreateArt(CreateArtInput input) }); return new Empty(); } + + public override Empty SetMaxBatchCancelOfferCount(Int32Value input) + { + AssertSenderIsAdmin(); + Assert(input is { Value: > 0 }, "Invalid input."); + + State.MaxBatchCancelOfferCount.Value = input.Value; + return new Empty(); + } + + public override Empty SetMaxBatchCancelListCount(Int32Value input) + { + AssertSenderIsAdmin(); + Assert(input is { Value: > 0 }, "Invalid input."); + + State.MaxBatchCancelListCount.Value = input.Value; + return new Empty(); + } } } \ No newline at end of file diff --git a/contract/Forest/ForestContractConstants.cs b/contract/Forest/ForestContractConstants.cs index 9c495d0d..561026f1 100644 --- a/contract/Forest/ForestContractConstants.cs +++ b/contract/Forest/ForestContractConstants.cs @@ -22,5 +22,7 @@ public partial class ForestContract public const string DefaultAIImageSize1024 = "1024x1024"; public const string DefaultAIImageSize512 = "512x512"; public const string DefaultAIImageSize256 = "256x256"; + public const int DefaultMaxBatchCancelOfferCount= 20; + public const int DefaultMaxBatchCancelListCount= 20; } \ No newline at end of file diff --git a/contract/Forest/ForestContractState.cs b/contract/Forest/ForestContractState.cs index 1bd12a4a..c21a6ff0 100755 --- a/contract/Forest/ForestContractState.cs +++ b/contract/Forest/ForestContractState.cs @@ -61,6 +61,9 @@ public partial class ForestContractState : ContractState /// Symbol -> Address TxId -> CreateArtInfo /// public MappedState CreateArtInfoMap { get; set; } + public SingletonState MaxBatchCancelOfferCount { get; set; } + + public SingletonState MaxBatchCancelListCount { get; set; } } } \ No newline at end of file diff --git a/contract/Forest/ForestContract_Buyers.cs b/contract/Forest/ForestContract_Buyers.cs index d9a976df..dd167c35 100644 --- a/contract/Forest/ForestContract_Buyers.cs +++ b/contract/Forest/ForestContract_Buyers.cs @@ -534,6 +534,12 @@ public override Empty CancelOffer(CancelOfferInput input) public override Empty CancelOfferListByExpireTime(CancelOfferListByExpireTimeInput input) { AssertContractInitialized(); + CancelOfferList(input); + return new Empty(); + } + + private Empty CancelOfferList(CancelOfferListByExpireTimeInput input) + { Assert(Context.Sender != null, "Invalid input data : Context.Sender"); Assert(input != null, "Invalid input data"); Assert(input.Symbol != null, "Invalid input data : Symbol"); @@ -548,15 +554,26 @@ public override Empty CancelOfferListByExpireTime(CancelOfferListByExpireTimeInp Assert(offerList?.Value?.Count > 0, "Offer not exists"); - var cancelOfferList = offerList?.Value.Where(existOffer => input.CancelOfferList.Any(cannelOffer => - AreOffersEqual(existOffer, cannelOffer) - )).ToList(); + var cancelOfferList = new List(); + var remainOfferList = new List(); + foreach (var existOffer in offerList?.Value) + { + foreach (var cannelOffer in input.CancelOfferList) + { + if (AreOffersEqual(existOffer, cannelOffer)) + { + cancelOfferList.Add(existOffer); + } + if (!AreOffersEqual(existOffer, cannelOffer)) + { + remainOfferList.Add(existOffer); + } + } + } Assert(cancelOfferList?.Count > 0, "Cannel Offer not exists"); var newOfferList = new OfferList(); - var remainOfferList = offerList?.Value.Where(existOffer => !input.CancelOfferList.Any(cannelOffer => - AreOffersEqual(existOffer, cannelOffer) - )).ToList(); + newOfferList.Value.Add(remainOfferList); var cancelOfferMap = new Dictionary(); @@ -599,7 +616,7 @@ public override Empty CancelOfferListByExpireTime(CancelOfferListByExpireTimeInp return new Empty(); } - + private bool AreOffersEqual(Offer existOffer, CancelOffer cancelOffer) { return existOffer.To == cancelOffer.OfferTo && @@ -757,4 +774,35 @@ private void PerformMakeOffer(MakeOfferInput input, GetBalanceOutput originGetBa Quantity = input.Quantity }); } + + public override Empty BatchCancelOfferList(BatchCancelOfferListInput input) + { + AssertContractInitialized(); + RequireMaxBatchCancelOfferCountSet(); + Assert(Context.Sender != null, "Invalid input data : Context.Sender"); + Assert(input != null, "Invalid input data"); + Assert(input.BatchCancelOfferInfo != null, "Invalid input data : BatchCancelOfferInfo"); + Assert(input.BatchCancelOfferInfo.CancelOfferList != null, "Invalid input data : CancelOfferList"); + var maxBatchCancelOfferCount = State.MaxBatchCancelOfferCount.Value; + Assert(input.BatchCancelOfferInfo.CancelOfferList.Count <= maxBatchCancelOfferCount, "Invalid cancel list count."); + foreach (var cancelOffer in input.BatchCancelOfferInfo.CancelOfferList) + { + var cancelInput = new CancelOfferListByExpireTimeInput() + { + Symbol = cancelOffer.Symbol, + CancelOfferList = {new CancelOffer() + { + ExpireTime = cancelOffer.ExpireTime, + OfferTo = cancelOffer.OfferTo, + Price = new Price() + { + Symbol = cancelOffer.Price.Symbol, + Amount = cancelOffer.Price.Amount + } + }} + }; + CancelOfferList(cancelInput); + } + return new Empty(); + } } \ No newline at end of file diff --git a/contract/Forest/ForestContract_Helpers.cs b/contract/Forest/ForestContract_Helpers.cs index 26661f7b..a6955b1f 100644 --- a/contract/Forest/ForestContract_Helpers.cs +++ b/contract/Forest/ForestContract_Helpers.cs @@ -342,4 +342,22 @@ private void CheckCreateArtParams(CreateArtInput input) var size = State.AIImageSizeList.Value.Value; Assert(State.AIImageSizeList.Value.Value.Contains(input.Size), $"Invalid input size"); } + + private void RequireMaxBatchCancelOfferCountSet() + { + var maxCount = State.MaxBatchCancelOfferCount.Value; + if (maxCount <= 0) + { + State.MaxBatchCancelOfferCount.Value = DefaultMaxBatchCancelOfferCount; + } + } + + private void RequireMaxBatchCancelListCountSet() + { + var maxCount = State.MaxBatchCancelListCount.Value; + if (maxCount <= 0) + { + State.MaxBatchCancelListCount.Value = DefaultMaxBatchCancelListCount; + } + } } \ No newline at end of file diff --git a/contract/Forest/ForestContract_Sellers.cs b/contract/Forest/ForestContract_Sellers.cs index da12a289..d816d548 100644 --- a/contract/Forest/ForestContract_Sellers.cs +++ b/contract/Forest/ForestContract_Sellers.cs @@ -138,6 +138,12 @@ public override Empty ListWithFixedPrice(ListWithFixedPriceInput input) } public override Empty Delist(DelistInput input) + { + SingleDelist(input); + return new Empty(); + } + + private Empty SingleDelist(DelistInput input) { Assert(input.Quantity > 0, "Quantity must be a positive integer."); var listedNftInfoList = State.ListedNFTInfoListMap[input.Symbol][Context.Sender]; @@ -147,10 +153,18 @@ public override Empty Delist(DelistInput input) } Assert(input.Price != null, "Need to specific list record."); - var listedNftInfo = listedNftInfoList.Value.FirstOrDefault(i => - i.Price.Amount == input.Price.Amount && i.Price.Symbol == input.Price.Symbol && - i.Owner == Context.Sender && - (input.StartTime == null ? true : input.StartTime.Seconds == i.Duration.StartTime.Seconds)); + ListedNFTInfo listedNftInfo = null; + + foreach (var i in listedNftInfoList.Value) + { + if (i.Price.Amount == input.Price.Amount && i.Price.Symbol == input.Price.Symbol && + i.Owner == Context.Sender && + (input.StartTime == null ? true : input.StartTime.Seconds == i.Duration.StartTime.Seconds)) + { + listedNftInfo = i; + break; + } + } if (listedNftInfo == null) { @@ -370,4 +384,33 @@ public override Empty Deal(DealInput input) }); return new Empty(); } + + public override Empty BatchCancelList(BatchCancelListInput input) + { + AssertContractInitialized(); + RequireMaxBatchCancelListCountSet(); + Assert(Context.Sender != null, "Invalid input data : Context.Sender"); + Assert(input != null, "Invalid input data"); + Assert(input.BatchCancelListInfo != null, "Invalid input data : Symbol"); + Assert(input.BatchCancelListInfo.CancelList != null, "Invalid input data : CancelList"); + var maxBatchCancelCount = State.MaxBatchCancelListCount.Value; + Assert(input.BatchCancelListInfo.CancelList.Count <= maxBatchCancelCount, "Invalid cancel list count."); + foreach (var cancelInfo in input.BatchCancelListInfo.CancelList) + { + var cancelInput = new DelistInput() + { + Symbol = cancelInfo.Symbol, + Quantity = cancelInfo.Quantity, + Price = new Price() + { + Symbol = cancelInfo.Price.Symbol, + Amount = cancelInfo.Price.Amount + }, + StartTime = cancelInfo.StartTime + }; + SingleDelist(cancelInput); + } + return new Empty(); + + } } \ No newline at end of file diff --git a/contract/Forest/ForestContract_Views.cs b/contract/Forest/ForestContract_Views.cs index 0edbe95e..e947dfc9 100644 --- a/contract/Forest/ForestContract_Views.cs +++ b/contract/Forest/ForestContract_Views.cs @@ -147,4 +147,14 @@ public override CreateArtInfo GetCreateArtInfo(GetCreateArtInfoInput input) Assert(input != null, "Invalid TransactionId"); return State.CreateArtInfoMap[input.Address ?? Context.Sender][input.TransactionId]; } + + public override Int32Value GetMaxBatchCancelOfferCount(Empty input) + { + return new Int32Value { Value = State.MaxBatchCancelOfferCount.Value }; + } + + public override Int32Value GetMaxBatchCancelListCount(Empty input) + { + return new Int32Value { Value = State.MaxBatchCancelListCount.Value }; + } } \ No newline at end of file diff --git a/protobuf/forest_contract.proto b/protobuf/forest_contract.proto index 05cf01ea..9aa64e87 100755 --- a/protobuf/forest_contract.proto +++ b/protobuf/forest_contract.proto @@ -39,6 +39,11 @@ service ForestContract { } rpc CancelOfferListByExpireTime (CancelOfferListByExpireTimeInput) returns (google.protobuf.Empty) { } + + rpc BatchCancelOfferList (BatchCancelOfferListInput) returns (google.protobuf.Empty) { + } + rpc BatchCancelList (BatchCancelListInput) returns (google.protobuf.Empty){ + } rpc BatchBuyNow (BatchBuyNowInput) returns (google.protobuf.Empty){ } @@ -74,7 +79,13 @@ service ForestContract { rpc RemoveAIImageSize (google.protobuf.StringValue) returns (google.protobuf.Empty){ } + + rpc SetMaxBatchCancelOfferCount(google.protobuf.Int32Value) returns (google.protobuf.Empty){ + } + rpc SetMaxBatchCancelListCount(google.protobuf.Int32Value) returns (google.protobuf.Empty){ + } + // Views. rpc GetListedNFTInfoList (GetListedNFTInfoListInput) returns (ListedNFTInfoList) { option (aelf.is_view) = true; @@ -124,6 +135,14 @@ service ForestContract { rpc GetCreateArtInfo (GetCreateArtInfoInput) returns (CreateArtInfo){ option (aelf.is_view) = true; } + + rpc GetMaxBatchCancelOfferCount(google.protobuf.Empty) returns (google.protobuf.Int32Value){ + option (aelf.is_view) = true; + } + + rpc GetMaxBatchCancelListCount(google.protobuf.Empty) returns (google.protobuf.Int32Value){ + option (aelf.is_view) = true; + } } // Structs. @@ -141,7 +160,6 @@ message ListedNFTInfoList { repeated ListedNFTInfo value = 1; } - message StringList { repeated string value = 1; } @@ -269,6 +287,30 @@ message CancelOfferListByExpireTimeInput { repeated CancelOffer cancel_offer_list =2; } +message BatchCancelOfferListInput { + BatchCancelOfferInfo batch_cancel_offer_info = 1; +} + +message BatchCancelOfferInfo{ + repeated CancelOfferList cancel_offer_list =1; +} + +message CancelOfferList{ + string symbol = 1; + google.protobuf.Timestamp expire_time = 2; + aelf.Address offer_to = 3; + Price price = 4; +} + +message BatchCancelListInput{ + BatchCancelListInfo batch_cancel_list_info = 1; +} + +message BatchCancelListInfo{ + repeated DelistInput cancel_list =1; +} + + message CancelOffer{ google.protobuf.Timestamp expire_time = 1; aelf.Address offer_to = 2; diff --git a/src/AElf.Boilerplate.MainChain/AElf.Boilerplate.MainChain.csproj b/src/AElf.Boilerplate.MainChain/AElf.Boilerplate.MainChain.csproj index 34f47b89..f04a49e6 100755 --- a/src/AElf.Boilerplate.MainChain/AElf.Boilerplate.MainChain.csproj +++ b/src/AElf.Boilerplate.MainChain/AElf.Boilerplate.MainChain.csproj @@ -43,7 +43,7 @@ Contract PreserveNewest - + false Contract PreserveNewest diff --git a/src/AElf.Boilerplate.TestBase/AElf.Boilerplate.TestBase.csproj b/src/AElf.Boilerplate.TestBase/AElf.Boilerplate.TestBase.csproj index 22e32d0d..5e0b734c 100755 --- a/src/AElf.Boilerplate.TestBase/AElf.Boilerplate.TestBase.csproj +++ b/src/AElf.Boilerplate.TestBase/AElf.Boilerplate.TestBase.csproj @@ -56,7 +56,7 @@ Contract PreserveNewest - + false Contract PreserveNewest diff --git a/test/Forest.Contracts.Auction.Tests/AuctionContractTests.cs b/test/Forest.Contracts.Auction.Tests/AuctionContractTests.cs index 720e481e..06efefaa 100644 --- a/test/Forest.Contracts.Auction.Tests/AuctionContractTests.cs +++ b/test/Forest.Contracts.Auction.Tests/AuctionContractTests.cs @@ -347,7 +347,7 @@ public async Task CreateAuctionTests_Fail() { Symbol = "SEED-2" }); - result.TransactionResult.Error.ShouldContain("Only support 721 type NFT."); + result.TransactionResult.Error.ShouldContain("Invalid input auction type."); result = await AuctionContractStub.CreateAuction.SendWithExceptionAsync(new CreateAuctionInput { @@ -904,7 +904,7 @@ await TokenContractStub.Create.SendAsync(new CreateInput Issuer = DefaultAddress, Symbol = "SEED-2", TokenName = "SEED-TEST", - TotalSupply = 2, + TotalSupply = 1, Decimals = 0, IsBurnable = true, LockWhiteList = { TokenContractAddress }, diff --git a/test/Forest.Tests/Forest.Tests.csproj b/test/Forest.Tests/Forest.Tests.csproj index 93f107f6..6b4ba21b 100755 --- a/test/Forest.Tests/Forest.Tests.csproj +++ b/test/Forest.Tests/Forest.Tests.csproj @@ -97,8 +97,4 @@ - - - - \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTests_CancelOffer.cs b/test/Forest.Tests/ForestContractTests_CancelOffer.cs index a65edd9b..65f0e08d 100644 --- a/test/Forest.Tests/ForestContractTests_CancelOffer.cs +++ b/test/Forest.Tests/ForestContractTests_CancelOffer.cs @@ -1251,4 +1251,114 @@ await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput() #endregion } + [Fact] + public async void BatchCancelOffer_Test() + { + await InitializeForestContract(); + await PrepareNftData(); + + var sellPrice = Elf(5_0000_0000); + var offerPrice = Elf(5_0000_0000); + var offerQuantity = 1; + var dealQuantity = 1; + var serviceFee = dealQuantity * sellPrice.Amount * ServiceFeeRate / 10000; + + var expireTime = Timestamp.FromDateTime(DateTime.UtcNow.AddMinutes(5)); + #region user buy + { + // user2 make offer to user1 + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput() + { + Symbol = NftSymbol, + OfferTo = User1Address, + Quantity = offerQuantity, + Price = offerPrice, + ExpireTime = expireTime + }); + + // user2 make offer to user1 + await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput() + { + Symbol = NftSymbol2, + OfferTo = User1Address, + Quantity = offerQuantity, + Price = offerPrice, + ExpireTime = expireTime + }); + } + #endregion + + #region check offer list + { + // list offers just sent + var offerList1 = await BuyerForestContractStub.GetOfferList.SendAsync(new GetOfferListInput() + { + Symbol = NftSymbol, + Address = User2Address, + }); + offerList1.Output.Value.Count.ShouldBeGreaterThan(0); + offerList1.Output.Value[0].To.ShouldBe(User1Address); + offerList1.Output.Value[0].From.ShouldBe(User2Address); + offerList1.Output.Value[0].Quantity.ShouldBe(offerQuantity); + + var offerList2 = await BuyerForestContractStub.GetOfferList.SendAsync(new GetOfferListInput() + { + Symbol = NftSymbol2, + Address = User2Address, + }); + offerList2.Output.Value.Count.ShouldBeGreaterThan(0); + offerList2.Output.Value[0].To.ShouldBe(User1Address); + offerList2.Output.Value[0].From.ShouldBe(User2Address); + offerList2.Output.Value[0].Quantity.ShouldBe(offerQuantity); + } + #endregion + + #region Admin Cancel order + { + var batchCancelOfferInput = new BatchCancelOfferListInput() + { + BatchCancelOfferInfo = new BatchCancelOfferInfo() + { + CancelOfferList = { new CancelOfferList() + { + Symbol = NftSymbol, + ExpireTime = expireTime, + OfferTo = User1Address, + Price = offerPrice + }, + new CancelOfferList() + { + Symbol = NftSymbol2, + ExpireTime = expireTime, + OfferTo = User1Address, + Price = offerPrice + } + } + } + }; + await BuyerForestContractStub.BatchCancelOfferList.SendAsync(batchCancelOfferInput); + } + #endregion + + #region check offer list + { + var offerList1 = await BuyerForestContractStub.GetOfferList.SendAsync(new GetOfferListInput() + { + Symbol = NftSymbol, + Address = User2Address, + }); + offerList1.Output.Value.Count.ShouldBe(0); + + + var offerList2 = await BuyerForestContractStub.GetOfferList.SendAsync(new GetOfferListInput() + { + Symbol = NftSymbol2, + Address = User2Address, + }); + offerList2.Output.Value.Count.ShouldBe(0); + } + #endregion + } + + } \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTests_List.cs b/test/Forest.Tests/ForestContractTests_List.cs index 79d2684c..93cdfc3e 100644 --- a/test/Forest.Tests/ForestContractTests_List.cs +++ b/test/Forest.Tests/ForestContractTests_List.cs @@ -13,6 +13,8 @@ namespace Forest; public class ForestContractListTests : ForestContractTestBase { private const string NftSymbol = "TESTNFT-1"; + private const string NftSymbol2 = "TESTNFT-2"; + private const string ElfSymbol = "ELF"; private const int ServiceFeeRate = 1000; // 10% private const long InitializeElfAmount = 10000_0000_0000; @@ -106,6 +108,28 @@ private async Task PrepareNftData() log2.Amount.ShouldBe(10); log2.Memo.ShouldBe(""); log2.To.ShouldBe(User1Address); + + //create and issue nft2 + { + // create NFT via MULTI-TOKEN-CONTRACT + await UserTokenContractStub.Create.SendAsync(new CreateInput + { + Symbol = NftSymbol2, + TokenName = NftSymbol2, + TotalSupply = 100, + Decimals = 0, + Issuer = User1Address, + IsBurnable = false, + IssueChainId = 0, + ExternalInfo = new ExternalInfo() + }); + await UserTokenContractStub.Issue.SendAsync(new IssueInput() + { + Symbol = NftSymbol2, + Amount = 10, + To = User1Address + }); + } // got 100-totalSupply and 10-supply var tokenInfo = await UserTokenContractStub.GetTokenInfo.SendAsync(new GetTokenInfoInput() { @@ -1221,22 +1245,22 @@ await Seller1ForestContractStub.Delist.SendAsync(new DelistInput } - private async Task InitListInfo(int listQuantity, int inputSellPrice, int approveQuantity) + private async Task InitListInfo(int listQuantity, int inputSellPrice, int approveQuantity, string symbol = null, Timestamp startTime = null) { var sellPrice = Elf(inputSellPrice); { await UserTokenContractStub.Approve.SendAsync(new ApproveInput() - { Spender = ForestContractAddress, Symbol = NftSymbol, Amount = approveQuantity }); + { Spender = ForestContractAddress, Symbol = symbol ?? NftSymbol, Amount = approveQuantity }); var executionResult = await Seller1ForestContractStub.ListWithFixedPrice.SendAsync( new ListWithFixedPriceInput { - Symbol = NftSymbol, + Symbol = symbol ?? NftSymbol, Quantity = listQuantity, IsWhitelistAvailable = true, Price = sellPrice, Duration = new ListWithFixedPriceDuration() { - StartTime = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddSeconds(approveQuantity)), + StartTime = startTime?? Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddSeconds(approveQuantity)), PublicTime = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddSeconds(approveQuantity)), } }); @@ -1245,7 +1269,7 @@ await UserTokenContractStub.Approve.SendAsync(new ApproveInput() .NonIndexed); log.Owner.ShouldBe(User1Address); log.Quantity.ShouldBe(listQuantity); - log.Symbol.ShouldBe(NftSymbol); + log.Symbol.ShouldBe( symbol ?? NftSymbol); log.Price.Symbol.ShouldBe(ElfSymbol); log.Price.Amount.ShouldBe(inputSellPrice); log.Duration.StartTime.ShouldNotBeNull(); @@ -1255,7 +1279,7 @@ await UserTokenContractStub.Approve.SendAsync(new ApproveInput() var listedNftInfo = (await Seller1ForestContractStub.GetListedNFTInfoList.CallAsync( new GetListedNFTInfoListInput { - Symbol = NftSymbol, + Symbol = symbol ?? NftSymbol, Owner = User1Address })).Value.Last(); listedNftInfo.Price.Symbol.ShouldBe("ELF"); @@ -1268,13 +1292,13 @@ await UserTokenContractStub.Approve.SendAsync(new ApproveInput() } } - private async Task QueryLastByStartAscListInfo(int intpuListQuantity, int inputSellPrice) + private async Task QueryLastByStartAscListInfo(int intpuListQuantity, int inputSellPrice, string symbol = null) { { var listedNftInfo = (await Seller1ForestContractStub.GetListedNFTInfoList.CallAsync( new GetListedNFTInfoListInput { - Symbol = NftSymbol, + Symbol = symbol ?? NftSymbol, Owner = User1Address })).Value.Last(); listedNftInfo.Price.Symbol.ShouldBe("ELF"); @@ -3138,4 +3162,102 @@ await UserTokenContractStub.Approve.SendAsync(new ApproveInput() } } + + [Fact] + public async void BatchCancelListTest() + { + //basic begin + int approveQuantity = 0; + await InitializeForestContract(); + await PrepareNftData(); + + int inputListQuantity1 = 1; + int inputSellPrice1 = 2; + var startTime1 = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddSeconds(approveQuantity)); + + approveQuantity += inputListQuantity1; + await InitListInfo(inputListQuantity1, inputSellPrice1, approveQuantity, NftSymbol, startTime1); + await QueryLastByStartAscListInfo(inputListQuantity1, inputSellPrice1, NftSymbol); + await QueryFirstByStartAscListInfo(inputListQuantity1, inputSellPrice1); + + int inputListQuantity2 = 2; + int inputSellPrice2 = 2; + var startTime2 = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddSeconds(approveQuantity)); + + approveQuantity += inputListQuantity2; + await InitListInfo(inputListQuantity2, inputSellPrice2, approveQuantity, NftSymbol2, startTime2); + await QueryLastByStartAscListInfo(inputListQuantity2, inputSellPrice2, NftSymbol2); + await QueryFirstByStartAscListInfo(inputListQuantity1, inputSellPrice1); + + var batchInput = new BatchCancelListInput() + { + BatchCancelListInfo = new BatchCancelListInfo() + { + CancelList = { new DelistInput() + { + Symbol = NftSymbol, + Quantity = inputListQuantity1, + Price = new Price() + { + Symbol = ElfSymbol, + Amount = inputSellPrice1, + }, + StartTime = startTime1 + },new DelistInput() + { + Symbol = NftSymbol2, + Quantity = inputListQuantity2, + Price = new Price() + { + Symbol = ElfSymbol, + Amount = inputSellPrice2, + }, + StartTime = startTime2 + } } + } + }; + + { + var listedNftInfo1 = (await Seller1ForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = NftSymbol, + Owner = User1Address + })).Value.First(); + listedNftInfo1.ShouldNotBeNull(); + listedNftInfo1.Symbol.ShouldBe(NftSymbol); + + var listedNftInfo2 = (await Seller1ForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = NftSymbol2, + Owner = User1Address + })).Value.First(); + listedNftInfo2.ShouldNotBeNull(); + listedNftInfo2.Symbol.ShouldBe(NftSymbol2); + + } + + { + //batch cancel list + await Seller1ForestContractStub.BatchCancelList.SendAsync(batchInput); + + var listedNftInfo1 = (await Seller1ForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = NftSymbol, + Owner = User1Address + })).Value; + listedNftInfo1.ShouldBeEmpty(); + + var listedNftInfo2 = (await Seller1ForestContractStub.GetListedNFTInfoList.CallAsync( + new GetListedNFTInfoListInput + { + Symbol = NftSymbol2, + Owner = User1Address + })).Value; + listedNftInfo1.ShouldBeEmpty(); + + } + } } \ No newline at end of file diff --git a/test/Forest.Tests/ForestContractTests_MakeOffer_whiteUser.cs b/test/Forest.Tests/ForestContractTests_MakeOffer_whiteUser.cs index e18ef6c7..963b2c95 100644 --- a/test/Forest.Tests/ForestContractTests_MakeOffer_whiteUser.cs +++ b/test/Forest.Tests/ForestContractTests_MakeOffer_whiteUser.cs @@ -1936,7 +1936,7 @@ await Seller1ForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPr Duration = new ListWithFixedPriceDuration() { // start 5min ago - StartTime = Timestamp.FromDateTime(DateTime.UtcNow).AddSeconds(-2), + StartTime = Timestamp.FromDateTime(DateTime.UtcNow).AddSeconds(-20), // public 10min after PublicTime = Timestamp.FromDateTime(DateTime.UtcNow).AddSeconds(-2), DurationMinutes = 1 * 60, @@ -2192,7 +2192,7 @@ await Seller1ForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPr try { await UserTokenContractStub.Approve.SendAsync(new ApproveInput() { Spender = ForestContractAddress, Symbol = NftSymbol, Amount = 1 }); - await User2TokenContractStub.Approve.SendAsync(new ApproveInput() { Spender = ForestContractAddress, Symbol = whitePrice.Symbol, Amount = sellPrice.Amount*2 }); + await User2TokenContractStub.Approve.SendAsync(new ApproveInput() { Spender = ForestContractAddress, Symbol = whitePrice.Symbol, Amount = sellPrice.Amount-1 }); var executionResult = await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput() { Symbol = NftSymbol, @@ -2206,7 +2206,7 @@ await Seller1ForestContractStub.ListWithFixedPrice.SendAsync(new ListWithFixedPr { errorMessage = e.Message; } - errorMessage.ShouldContain("[TransferFrom]Insufficient allowance"); + errorMessage.ShouldContain("The allowance you set is less than required"); } #endregion diff --git a/test/Forest.Tests/ForestContractTests_Security.cs b/test/Forest.Tests/ForestContractTests_Security.cs index e5c3b2b5..1e0bb344 100644 --- a/test/Forest.Tests/ForestContractTests_Security.cs +++ b/test/Forest.Tests/ForestContractTests_Security.cs @@ -1259,6 +1259,14 @@ await UserTokenContractStub.Approve.SendAsync(new ApproveInput() Amount = issueAmount, Spender = ForestContractAddress }); + await AdminForestContractStub.SetBizConfig.SendAsync(new BizConfig() + { + MaxListCount = 3, + MaxOfferCount = 3, + MaxOfferDealCount = 3, + MaxTokenWhitelistCount = 3 + + }); var bizConfig = await Seller1ForestContractStub.GetBizConfig.SendAsync(new Empty()); @@ -1336,6 +1344,13 @@ await UserTokenContractStub.Issue.SendAsync(new IssueInput() #region create offer reach maxCount + await AdminForestContractStub.SetBizConfig.SendAsync(new BizConfig() + { + MaxListCount = 3, + MaxOfferCount = 3, + MaxOfferDealCount = 3, + MaxTokenWhitelistCount = 3 + }); var bizConfig = await Seller1ForestContractStub.GetBizConfig.SendAsync(new Empty()); await UserTokenContractStub.Approve.SendAsync(new ApproveInput() diff --git a/test/Forest.Tests/ForestContractTests_Views.cs b/test/Forest.Tests/ForestContractTests_Views.cs index 87d9de34..6417a2d0 100644 --- a/test/Forest.Tests/ForestContractTests_Views.cs +++ b/test/Forest.Tests/ForestContractTests_Views.cs @@ -1191,4 +1191,28 @@ await BuyerForestContractStub.MakeOffer.SendAsync(new MakeOfferInput() } + [Fact] + public async void GetMaxBatchCancelOfferListCount_Test() + { + await InitializeForestContract(); + var count = await AdminForestContractStub.GetMaxBatchCancelOfferCount.CallAsync(new Empty()); + count.Value.ShouldBe(0); + + await AdminForestContractStub.SetMaxBatchCancelOfferCount.SendAsync(new Int32Value(){Value = 10}); + count = await AdminForestContractStub.GetMaxBatchCancelOfferCount.CallAsync(new Empty()); + count.Value.ShouldBe(10); + } + + [Fact] + public async void GetMaxBatchCancelListCount_Test() + { + await InitializeForestContract(); + var count = await AdminForestContractStub.GetMaxBatchCancelListCount.CallAsync(new Empty()); + count.Value.ShouldBe(0); + + await AdminForestContractStub.SetMaxBatchCancelListCount.SendAsync(new Int32Value(){Value = 10}); + count = await AdminForestContractStub.GetMaxBatchCancelListCount.CallAsync(new Empty()); + count.Value.ShouldBe(10); + } + } \ No newline at end of file