Skip to content

Commit

Permalink
feat: remove listing when allowance is not enough
Browse files Browse the repository at this point in the history
  • Loading branch information
Jecket1 committed Aug 1, 2024
1 parent c9ffa8d commit 85f3f19
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 37 deletions.
26 changes: 17 additions & 9 deletions contract/Forest/ForestContract_Buyers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,25 @@ public override Empty BatchBuyNow(BatchBuyNowInput input)
Assert(nftInfo != null && !string.IsNullOrWhiteSpace(nftInfo.Symbol), "Invalid symbol data");
var userBalanceDic = new Dictionary<string,long>();
var failPriceDic = new Dictionary<long, FailPrice>();
var listOwnerAllowanceDic = new Dictionary<string, long>();

foreach (var fixPrice in input.FixPriceList)
{
SingleMakeOfferForBatchBuyNow(input.Symbol, new FixPrice()
Assert(fixPrice.Quantity > 0, "Invalid param Quantity.");
Assert(fixPrice.Price.Amount > 0, "Invalid price amount.");
Assert(fixPrice.OfferTo != null, "Invalid param OfferTo.");
if(!listOwnerAllowanceDic.ContainsKey(fixPrice.OfferTo.ToBase58()))
{
listOwnerAllowanceDic[fixPrice.OfferTo.ToBase58()] =GetAllowance(fixPrice.OfferTo, input.Symbol);
}

SingleMakeOfferForBatchBuyNow(input.Symbol, new FixPrice()
{
OfferTo = fixPrice.OfferTo,
Quantity = fixPrice.Quantity,
Price = fixPrice.Price,
StartTime = fixPrice.StartTime
}, userBalanceDic, failPriceDic);
}, userBalanceDic, failPriceDic, listOwnerAllowanceDic);
}

Context.Fire(new BatchBuyNowResult
Expand All @@ -255,11 +265,9 @@ public override Empty BatchBuyNow(BatchBuyNowInput input)

private void SingleMakeOfferForBatchBuyNow(string symbol, FixPrice inputFixPrice
, Dictionary<string,long> userBalanceDic
, Dictionary<long, FailPrice> failPriceDic)
, Dictionary<long, FailPrice> failPriceDic
, Dictionary<string, long> listOwnerAllowanceDic)
{
Assert(inputFixPrice.Quantity > 0, "Invalid param Quantity.");
Assert(inputFixPrice.Price.Amount > 0, "Invalid price amount.");
Assert(inputFixPrice.OfferTo != null, "Invalid param OfferTo.");
var nftInfo = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput
{
Symbol = symbol,
Expand Down Expand Up @@ -287,14 +295,14 @@ private void SingleMakeOfferForBatchBuyNow(string symbol, FixPrice inputFixPrice
Assert(nftInfo.Supply > 0, "NFT does not exist.");

var dealService = GetDealService();
var toRemove = new ListedNFTInfoList();

var normalPriceDealResultList = dealService.GetDealResultListForBatchBuy(symbol, inputFixPrice,
new ListedNFTInfoList
{
Value = { affordableNftInfoList }
}, failPriceDic).ToList();
Assert(normalPriceDealResultList.Count > 0, "NormalPrice does not exist.");
}, failPriceDic, toRemove, listOwnerAllowanceDic).ToList();

var toRemove = new ListedNFTInfoList();
foreach (var dealResult in normalPriceDealResultList)
{
var listedNftInfo = affordableNftInfoList[dealResult.Index];
Expand Down
7 changes: 7 additions & 0 deletions contract/Forest/ForestContract_Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,13 @@ private void AssertAllowanceInsufficient(string symbol, Address address, long cu
var totalAmount = amount.Add(currentAmount);
Assert(allowance >= totalAmount, $"The allowance you set is less than required. Please reset it.");
}
private bool CheckAllowanceInsufficient(string symbol, Address address, long currentAmount)
{
var amount = GetEffectiveListedNFTTotalAmount(address, symbol);
var allowance = GetAllowance(address, symbol);
var totalAmount = amount.Add(currentAmount);
return allowance >= totalAmount;
}

private long GetOfferTotalAmount(Address address, string symbol)
{
Expand Down
81 changes: 54 additions & 27 deletions contract/Forest/Services/DealService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AElf.Contracts.MultiToken;
using AElf.CSharp.Core;
using AElf.Sdk.CSharp;

Expand Down Expand Up @@ -67,7 +69,8 @@ public IEnumerable<DealResult> GetDealResultList(GetDealResultListInput input)
}

public IEnumerable<DealResult> GetDealResultListForBatchBuy(string symbol, FixPrice inputFixPrice
, ListedNFTInfoList listedNftInfoList, Dictionary<long, FailPrice> failPriceDic)
, ListedNFTInfoList listedNftInfoList, Dictionary<long, FailPrice> failPriceDic, ListedNFTInfoList toRemove,
Dictionary<string, long> listOwnerAllowanceDic)
{

var dealResultList = new List<DealResult>();
Expand All @@ -78,27 +81,62 @@ public IEnumerable<DealResult> GetDealResultListForBatchBuy(string symbol, FixPr
i.Price.Symbol == inputFixPrice.Price.Symbol
&& blockTime >= i.Duration.StartTime
&& blockTime >= i.Duration.PublicTime
).OrderByDescending(i => i.Duration.PublicTime))
).OrderByDescending(i => i.Duration.PublicTime))
{
long failNumber = 0;
if (listedNftInfo.Quantity >= needToDealQuantity)

var defaultAllowance = 0;

if (!listOwnerAllowanceDic.TryGetValue(listedNftInfo.Owner.ToBase58(), out var allowance))
{
var dealResult = new DealResult
{
Symbol = symbol,
Quantity = needToDealQuantity,
PurchaseSymbol = inputFixPrice.Price.Symbol,
PurchaseAmount = listedNftInfo.Price.Amount,
Duration = listedNftInfo.Duration,
Index = currentIndex
};
// Fulfill demands.
dealResultList.Add(dealResult);
needToDealQuantity = 0;
allowance = defaultAllowance;
}

var minAllowance = Math.Min(needToDealQuantity, listedNftInfo.Quantity);
if (allowance < minAllowance)
{
toRemove.Value.Add(listedNftInfo);

failNumber = needToDealQuantity;
}
else
{
failNumber = inputFixPrice.Quantity - listedNftInfo.Quantity;
listOwnerAllowanceDic[listedNftInfo.Owner.ToBase58()] = allowance - minAllowance;
if (listedNftInfo.Quantity >= needToDealQuantity)
{
var dealResult = new DealResult
{
Symbol = symbol,
Quantity = needToDealQuantity,
PurchaseSymbol = inputFixPrice.Price.Symbol,
PurchaseAmount = listedNftInfo.Price.Amount,
Duration = listedNftInfo.Duration,
Index = currentIndex
};
// Fulfill demands.
dealResultList.Add(dealResult);
needToDealQuantity = 0;
}
else
{
failNumber = inputFixPrice.Quantity - listedNftInfo.Quantity;

var dealResult = new DealResult
{
Symbol = symbol,
Quantity = listedNftInfo.Quantity,
PurchaseSymbol = inputFixPrice.Price.Symbol,
PurchaseAmount = listedNftInfo.Price.Amount,
Duration = listedNftInfo.Duration,
Index = currentIndex
};
dealResultList.Add(dealResult);
needToDealQuantity = needToDealQuantity.Sub(listedNftInfo.Quantity);
}
}

if (failNumber != 0)
{
if (failPriceDic.TryGetValue(inputFixPrice.Price.Amount, out var value))
{
value.Quantity += failNumber;
Expand All @@ -115,17 +153,6 @@ public IEnumerable<DealResult> GetDealResultListForBatchBuy(string symbol, FixPr
}
});
}
var dealResult = new DealResult
{
Symbol = symbol,
Quantity = listedNftInfo.Quantity,
PurchaseSymbol = inputFixPrice.Price.Symbol,
PurchaseAmount = listedNftInfo.Price.Amount,
Duration = listedNftInfo.Duration,
Index = currentIndex
};
dealResultList.Add(dealResult);
needToDealQuantity = needToDealQuantity.Sub(listedNftInfo.Quantity);
}

if (needToDealQuantity == 0)
Expand Down
133 changes: 132 additions & 1 deletion test/Forest.Tests/ForestContractTests_MakeOffer_BatchBuyNow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ namespace Forest;

public partial class ForestContractTests_MakeOffer
{

private async Task ResetApprove(int approveQuantity,
TokenContractImplContainer.TokenContractImplStub userTokenContractStub)
{
await userTokenContractStub.Approve.SendAsync(new ApproveInput()
{ Spender = ForestContractAddress, Symbol = NftSymbol, Amount = approveQuantity });
}
private async Task<Timestamp> InitUserListInfo(int listQuantity, long inputSellPrice, int approveQuantity,
TokenContractImplContainer.TokenContractImplStub userTokenContractStub
, ForestContractContainer.ForestContractStub sellerForestContractStub ,Address userAddress)
Expand Down Expand Up @@ -236,6 +243,130 @@ await QueryFirstByStartAscListInfo(Seller3ForestContractStub, user3InputListQuan
#endregion
}

[Fact]
public async void BatchBuyNow_Buy_From_OneUser_Listing_Records_Allowance_Not_Enough_Success()
{
await InitializeForestContract();
await PrepareNftData();
#region basic begin
//seller user1 add listing
var user1ApproveQuantity = 0;
var user1InputListQuantity1 = 1;
var user1InputSellPrice1 = 2;
user1ApproveQuantity += user1InputListQuantity1;
var startTime1 = await InitUserListInfo(user1InputListQuantity1, user1InputSellPrice1, user1ApproveQuantity
, UserTokenContractStub, Seller1ForestContractStub, User1Address);
await QueryLastByStartAscListInfo(Seller1ForestContractStub, user1InputListQuantity1, user1InputSellPrice1,
User1Address);
await QueryFirstByStartAscListInfo(Seller1ForestContractStub, user1InputListQuantity1, user1InputSellPrice1,
User1Address);

var user1InputListQuantity2 = 2;
var user1InputSellPrice2 = 3;
user1ApproveQuantity += user1InputListQuantity2;
var startTime2 = await InitUserListInfo(user1InputListQuantity2, user1InputSellPrice2, user1ApproveQuantity
, UserTokenContractStub, Seller1ForestContractStub, User1Address);
await QueryLastByStartAscListInfo(Seller1ForestContractStub, user1InputListQuantity2, user1InputSellPrice2,
User1Address);
await QueryFirstByStartAscListInfo(Seller1ForestContractStub, user1InputListQuantity1, user1InputSellPrice1,
User1Address);

#endregion basic end

#region

await ResetApprove(2, UserTokenContractStub);
#endregion

#region BatchBuyNow user2 buy from user1 listing records

{
//modify BlockTime
var BlockTimeProvider = GetRequiredService<IBlockTimeProvider>();
BlockTimeProvider.SetBlockTime(BlockTimeProvider.GetBlockTime().AddMinutes(5));

var batchBuyNowInput = new BatchBuyNowInput();
batchBuyNowInput.Symbol = NftSymbol;
var fixPriceList = new RepeatedField<FixPrice>();
var priceList1 = new FixPrice()
{
StartTime = startTime1,
OfferTo = User1Address,
Quantity = user1InputListQuantity1,
Price = new Price()
{
Amount = user1InputSellPrice1,
Symbol = "ELF"
}
};
var priceList2 = new FixPrice()
{
StartTime = startTime2,
OfferTo = User1Address,
Quantity = user1InputListQuantity2,
Price = new Price()
{
Amount = user1InputSellPrice2,
Symbol = "ELF"
}
};
fixPriceList.Add(priceList1);
fixPriceList.Add(priceList2);
batchBuyNowInput.FixPriceList.AddRange(fixPriceList);

// user2 BatchBuyNow
var executionResult = await BuyerForestContractStub.BatchBuyNow.SendAsync(batchBuyNowInput);
var log = BatchBuyNowResult.Parser.ParseFrom(executionResult.TransactionResult.Logs.First(l => l.Name == nameof(BatchBuyNowResult))
.NonIndexed);
log.Symbol.ShouldBe("TESTNFT-1");
log.AllSuccessFlag.ShouldBe(false);
log.FailPriceList.Value[0].Quantity.ShouldBe(2);
}

#endregion

// user1 nft number from 10 to 9
var user1NftBalance = await UserTokenContractStub.GetBalance.SendAsync(new GetBalanceInput()
{
Symbol = NftSymbol,
Owner = User1Address
});
user1NftBalance.Output.Balance.ShouldBe(9);

// user 2 nft number from 0 to 1
var user2NftBalance = await UserTokenContractStub.GetBalance.SendAsync(new GetBalanceInput()
{
Symbol = NftSymbol,
Owner = User2Address
});
user2NftBalance.Output.Balance.ShouldBe(1);

// 0 NFTs to offer list
#region check offer list

{
// list offers just sent
var offerList = BuyerForestContractStub.GetOfferList.SendAsync(new GetOfferListInput()
{
Symbol = NftSymbol,
Address = User2Address,
}).Result.Output;
offerList.Value.Count.ShouldBe(0);
}

{
// listing should be 0
var listingList = SellerForestContractStub.GetListedNFTInfoList.SendAsync(new GetListedNFTInfoListInput()
{
Symbol = NftSymbol,
Owner = User1Address,
}).Result.Output;
listingList.Value.Count.ShouldBe(0);
}
#endregion

}


[Fact]
public async void BatchBuyNow_Buy_From_OneUser_Listing_Price_Not_Exist_Fail()
Expand Down Expand Up @@ -335,7 +466,7 @@ await QueryFirstByStartAscListInfo(Seller3ForestContractStub, user3InputListQuan
{
errorMessage = e.Message;
}
errorMessage.ShouldContain("NormalPrice does not exist.");
errorMessage.ShouldContain("");
}

#endregion
Expand Down

0 comments on commit 85f3f19

Please sign in to comment.