Fix accounting calculation when multi crypto

This commit is contained in:
nicolas.dorier
2018-01-09 10:54:19 +09:00
parent 76d50b018b
commit 0c735f4e29
2 changed files with 105 additions and 16 deletions

View File

@@ -23,6 +23,7 @@ using Microsoft.EntityFrameworkCore;
using BTCPayServer.Services.Rates;
using Microsoft.Extensions.Caching.Memory;
using BTCPayServer.Eclair;
using System.Collections.Generic;
namespace BTCPayServer.Tests
{
@@ -75,6 +76,88 @@ namespace BTCPayServer.Tests
accounting = cryptoData.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
entity = new InvoiceEntity();
entity.ProductInformation = new ProductInformation() { Price = 5000 };
entity.SetCryptoData(new System.Collections.Generic.Dictionary<string, CryptoData>(new KeyValuePair<string, CryptoData>[] {
new KeyValuePair<string,CryptoData>("BTC", new CryptoData()
{
Rate = 1000,
TxFee = Money.Coins(0.1m)
}),
new KeyValuePair<string,CryptoData>("LTC", new CryptoData()
{
Rate = 500,
TxFee = Money.Coins(0.01m)
})
}));
entity.Payments = new List<PaymentEntity>();
cryptoData = entity.GetCryptoData("BTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(5.1m), accounting.Due);
cryptoData = entity.GetCryptoData("LTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true });
cryptoData = entity.GetCryptoData("BTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(4.2m), accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
Assert.Equal(Money.Coins(1.0m), accounting.Paid);
Assert.Equal(Money.Coins(5.2m), accounting.TotalDue);
Assert.Equal(2, accounting.TxCount);
cryptoData = entity.GetCryptoData("LTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 - 2.0m /* 8.21m */), accounting.Due);
Assert.Equal(Money.Coins(0.0m), accounting.CryptoPaid);
Assert.Equal(Money.Coins(2.0m), accounting.Paid);
Assert.Equal(Money.Coins(10.01m + 0.1m * 2), accounting.TotalDue);
entity.Payments.Add(new PaymentEntity() { CryptoCode = "LTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true });
cryptoData = entity.GetCryptoData("BTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(4.2m - 0.5m + 0.01m / 2), accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
Assert.Equal(Money.Coins(1.5m), accounting.Paid);
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); // The fee for LTC added
Assert.Equal(2, accounting.TxCount);
cryptoData = entity.GetCryptoData("LTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(8.21m - 1.0m + 0.01m), accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
Assert.Equal(Money.Coins(3.0m), accounting.Paid);
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.01m), accounting.TotalDue);
Assert.Equal(2, accounting.TxCount);
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2);
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(remaining, new Key()), Accounted = true });
cryptoData = entity.GetCryptoData("BTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Coins(1.0m) + remaining, accounting.CryptoPaid);
Assert.Equal(Money.Coins(1.5m) + remaining, accounting.Paid);
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue);
Assert.Equal(accounting.Paid, accounting.TotalDue);
Assert.Equal(2, accounting.TxCount);
cryptoData = entity.GetCryptoData("LTC");
accounting = cryptoData.Calculate();
Assert.Equal(Money.Zero, accounting.Due);
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
Assert.Equal(Money.Coins(3.0m) + remaining * 2, accounting.Paid);
// Paying 2 BTC fee, LTC fee removed because fully paid
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */), accounting.TotalDue);
Assert.Equal(1, accounting.TxCount);
Assert.Equal(accounting.Paid, accounting.TotalDue);
#pragma warning restore CS0618
}

View File

@@ -200,7 +200,7 @@ namespace BTCPayServer.Services.Invoices
internal void SetDerivationStrategies(IEnumerable<DerivationStrategy> derivationStrategies)
{
JObject obj = new JObject();
foreach(var strat in derivationStrategies)
foreach (var strat in derivationStrategies)
{
obj.Add(strat.Network.CryptoCode, new JValue(strat.DerivationStrategyBase.ToString()));
#pragma warning disable CS0618
@@ -482,33 +482,38 @@ namespace BTCPayServer.Services.Invoices
public CryptoDataAccounting Calculate()
{
var cryptoData = ParentEntity.GetCryptoData();
var totalDue = Money.Coins(ParentEntity.ProductInformation.Price / Rate) + TxFee;
var totalDue = Money.Coins(ParentEntity.ProductInformation.Price / Rate);
var paid = Money.Zero;
var cryptoPaid = Money.Zero;
int txCount = 1;
var paidTxFee = Money.Zero;
bool paidEnough = totalDue <= paid;
int txCount = 0;
var payments =
ParentEntity.Payments
.Where(p => p.Accounted)
.OrderByDescending(p => p.ReceivedTime)
.Select(_ =>
{
paidTxFee = _.GetValue(cryptoData, CryptoCode, cryptoData[_.GetCryptoCode()].TxFee);
paid += _.GetValue(cryptoData, CryptoCode);
if(!paidEnough)
totalDue += paidTxFee;
paidEnough |= totalDue <= paid;
if (CryptoCode == _.GetCryptoCode())
cryptoPaid += _.GetValue();
return _;
})
.TakeWhile(_ =>
{
var paidEnough = totalDue <= paid;
if (!paidEnough && _.GetCryptoCode() == CryptoCode)
{
cryptoPaid += _.GetValue();
txCount++;
totalDue += TxFee;
}
return !paidEnough;
return _;
})
.ToArray();
if (!paidEnough)
{
txCount++;
totalDue += TxFee;
}
var accounting = new CryptoDataAccounting();
accounting.TotalDue = totalDue;
accounting.Paid = paid;
@@ -573,20 +578,21 @@ namespace BTCPayServer.Services.Invoices
return Output.Value;
#pragma warning restore CS0618
}
public Money GetValue(Dictionary<string, CryptoData> cryptoData, string cryptoCode)
public Money GetValue(Dictionary<string, CryptoData> cryptoData, string cryptoCode, Money value = null)
{
#pragma warning disable CS0618
value = value ?? Output.Value;
#pragma warning restore CS0618
var to = cryptoCode;
var from = GetCryptoCode();
if (to == from)
return Output.Value;
return value;
var fromRate = cryptoData[from].Rate;
var toRate = cryptoData[to].Rate;
var fiatValue = fromRate * Output.Value.ToDecimal(MoneyUnit.BTC);
var fiatValue = fromRate * value.ToDecimal(MoneyUnit.BTC);
var otherCurrencyValue = toRate == 0 ? 0.0m : fiatValue / toRate;
return Money.Coins(otherCurrencyValue);
#pragma warning restore CS0618
}
public string GetCryptoCode()