2021-11-22 16:49:51 +01:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Runtime.CompilerServices ;
using System.Security ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Threading ;
using System.Threading.Tasks ;
2022-03-02 19:21:38 +01:00
using BTCPayServer.Abstractions.Extensions ;
2021-11-22 16:49:51 +01:00
using BTCPayServer.Client ;
using BTCPayServer.Client.Models ;
using BTCPayServer.Configuration ;
using BTCPayServer.Controllers ;
using BTCPayServer.Data ;
using BTCPayServer.HostedServices ;
2023-01-30 01:46:12 +01:00
using BTCPayServer.Hosting ;
2021-11-22 16:49:51 +01:00
using BTCPayServer.JsonConverters ;
using BTCPayServer.Payments ;
using BTCPayServer.Payments.Bitcoin ;
using BTCPayServer.Payments.Lightning ;
using BTCPayServer.Rating ;
using BTCPayServer.Services ;
2023-09-05 08:32:49 +02:00
using BTCPayServer.Services.Apps ;
2021-11-22 16:49:51 +01:00
using BTCPayServer.Services.Invoices ;
using BTCPayServer.Services.Labels ;
using BTCPayServer.Services.Rates ;
2023-05-27 05:51:48 +02:00
using BTCPayServer.Services.Stores ;
2021-11-22 16:49:51 +01:00
using BTCPayServer.Validation ;
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Configuration ;
using Microsoft.Extensions.Configuration.Memory ;
2023-01-30 01:46:12 +01:00
using Microsoft.Extensions.DependencyInjection ;
2021-11-22 16:49:51 +01:00
using Microsoft.Extensions.Options ;
using NBitcoin ;
using NBitcoin.DataEncoders ;
using NBitcoin.Scripting.Parser ;
2022-08-15 18:45:21 +02:00
using NBXplorer ;
2021-11-22 16:49:51 +01:00
using NBXplorer.DerivationStrategy ;
using NBXplorer.Models ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using Xunit ;
using Xunit.Abstractions ;
2022-08-15 18:45:21 +02:00
using StoreData = BTCPayServer . Data . StoreData ;
2021-11-22 16:49:51 +01:00
namespace BTCPayServer.Tests
{
[Trait("Fast", "Fast")]
public class FastTests : UnitTestBase
{
public FastTests ( ITestOutputHelper helper ) : base ( helper )
{
}
class DockerImage
{
public string User { get ; private set ; }
public string Name { get ; private set ; }
public string Tag { get ; private set ; }
public string Source { get ; set ; }
public static DockerImage Parse ( string str )
{
//${BTCPAY_IMAGE: -btcpayserver / btcpayserver:1.0.3.21}
var variableMatch = Regex . Match ( str , @"\$\{[^-]+-([^\}]+)\}" ) ;
if ( variableMatch . Success )
{
str = variableMatch . Groups [ 1 ] . Value ;
}
DockerImage img = new DockerImage ( ) ;
var match = Regex . Match ( str , "([^/]*/)?([^:]+):?(.*)" ) ;
if ( ! match . Success )
throw new FormatException ( ) ;
img . User = match . Groups [ 1 ] . Length = = 0 ? string . Empty : match . Groups [ 1 ] . Value . Substring ( 0 , match . Groups [ 1 ] . Value . Length - 1 ) ;
img . Name = match . Groups [ 2 ] . Value ;
img . Tag = match . Groups [ 3 ] . Value ;
if ( img . Tag = = string . Empty )
img . Tag = "latest" ;
return img ;
}
public override string ToString ( )
{
return ToString ( true ) ;
}
public string ToString ( bool includeTag )
{
StringBuilder builder = new StringBuilder ( ) ;
if ( ! String . IsNullOrWhiteSpace ( User ) )
builder . Append ( $"{User}/" ) ;
builder . Append ( $"{Name}" ) ;
if ( includeTag )
{
if ( ! String . IsNullOrWhiteSpace ( Tag ) )
builder . Append ( $":{Tag}" ) ;
}
return builder . ToString ( ) ;
}
}
/// <summary>
/// This test check that we don't forget to bump one image in both docker-compose.altcoins.yml and docker-compose.yml
/// </summary>
[Fact]
public void CheckDockerComposeUpToDate ( )
{
var compose1 = File . ReadAllText ( Path . Combine ( TestUtils . TryGetSolutionDirectoryInfo ( ) . FullName , "BTCPayServer.Tests" , "docker-compose.yml" ) ) ;
var compose2 = File . ReadAllText ( Path . Combine ( TestUtils . TryGetSolutionDirectoryInfo ( ) . FullName , "BTCPayServer.Tests" , "docker-compose.altcoins.yml" ) ) ;
List < DockerImage > GetImages ( string content )
{
List < DockerImage > images = new List < DockerImage > ( ) ;
foreach ( var line in content . Split ( new [ ] { "\n" , "\r\n" } , StringSplitOptions . RemoveEmptyEntries ) )
{
var l = line . Trim ( ) ;
if ( l . StartsWith ( "image:" , StringComparison . OrdinalIgnoreCase ) )
{
images . Add ( DockerImage . Parse ( l . Substring ( "image:" . Length ) . Trim ( ) ) ) ;
}
}
return images ;
}
var img1 = GetImages ( compose1 ) ;
var img2 = GetImages ( compose2 ) ;
var groups = img1 . Concat ( img2 ) . GroupBy ( g = > g . Name ) ;
foreach ( var g in groups )
{
var tags = new HashSet < String > ( g . Select ( o = > o . Tag ) ) ;
if ( tags . Count ! = 1 )
{
2023-11-28 15:20:03 +01:00
Assert . Fail ( $"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})" ) ;
2021-11-22 16:49:51 +01:00
}
}
}
2023-04-04 07:48:29 +02:00
[Fact]
public void CanParseDecimals ( )
{
CanParseDecimalsCore ( "{\"qty\": 1}" , 1.0 m ) ;
CanParseDecimalsCore ( "{\"qty\": \"1\"}" , 1.0 m ) ;
CanParseDecimalsCore ( "{\"qty\": 1.0}" , 1.0 m ) ;
CanParseDecimalsCore ( "{\"qty\": \"1.0\"}" , 1.0 m ) ;
CanParseDecimalsCore ( "{\"qty\": 6.1e-7}" , 6.1e-7 m ) ;
CanParseDecimalsCore ( "{\"qty\": \"6.1e-7\"}" , 6.1e-7 m ) ;
var data = JsonConvert . DeserializeObject < TradeRequestData > ( "{\"qty\": \"6.1e-7\", \"fromAsset\":\"Test\"}" ) ;
Assert . Equal ( 6.1e-7 m , data . Qty . Value ) ;
Assert . Equal ( "Test" , data . FromAsset ) ;
data = JsonConvert . DeserializeObject < TradeRequestData > ( "{\"fromAsset\":\"Test\", \"qty\": \"6.1e-7\"}" ) ;
Assert . Equal ( 6.1e-7 m , data . Qty . Value ) ;
Assert . Equal ( "Test" , data . FromAsset ) ;
}
private void CanParseDecimalsCore ( string str , decimal expected )
{
var d = JsonConvert . DeserializeObject < LedgerEntryData > ( str ) ;
Assert . Equal ( expected , d . Qty ) ;
var d2 = JsonConvert . DeserializeObject < TradeRequestData > ( str ) ;
Assert . Equal ( new TradeQuantity ( expected , TradeQuantity . ValueType . Exact ) , d2 . Qty ) ;
}
2021-11-22 16:49:51 +01:00
2022-07-06 14:14:55 +02:00
[Fact]
public void CanMergeReceiptOptions ( )
{
var r = InvoiceDataBase . ReceiptOptions . Merge ( null , null ) ;
Assert . True ( r ? . Enabled ) ;
Assert . True ( r ? . ShowPayments ) ;
Assert . True ( r ? . ShowQR ) ;
r = InvoiceDataBase . ReceiptOptions . Merge ( new InvoiceDataBase . ReceiptOptions ( ) , null ) ;
Assert . True ( r ? . Enabled ) ;
Assert . True ( r ? . ShowPayments ) ;
Assert . True ( r ? . ShowQR ) ;
r = InvoiceDataBase . ReceiptOptions . Merge ( new InvoiceDataBase . ReceiptOptions ( ) { Enabled = false } , null ) ;
Assert . False ( r ? . Enabled ) ;
Assert . True ( r ? . ShowPayments ) ;
Assert . True ( r ? . ShowQR ) ;
r = InvoiceDataBase . ReceiptOptions . Merge ( new InvoiceDataBase . ReceiptOptions ( ) { Enabled = false , ShowQR = false } , new InvoiceDataBase . ReceiptOptions ( ) { Enabled = true } ) ;
Assert . True ( r ? . Enabled ) ;
Assert . True ( r ? . ShowPayments ) ;
Assert . False ( r ? . ShowQR ) ;
StoreBlob blob = new StoreBlob ( ) ;
Assert . True ( blob . ReceiptOptions . Enabled ) ;
blob = JsonConvert . DeserializeObject < StoreBlob > ( "{}" ) ;
Assert . True ( blob . ReceiptOptions . Enabled ) ;
blob = JsonConvert . DeserializeObject < StoreBlob > ( "{\"receiptOptions\":{\"enabled\": false}}" ) ;
Assert . False ( blob . ReceiptOptions . Enabled ) ;
blob = JsonConvert . DeserializeObject < StoreBlob > ( JsonConvert . SerializeObject ( blob ) ) ;
Assert . False ( blob . ReceiptOptions . Enabled ) ;
}
2021-11-22 16:49:51 +01:00
[Fact]
public void CanParsePaymentMethodId ( )
{
var id = PaymentMethodId . Parse ( "BTC" ) ;
var id1 = PaymentMethodId . Parse ( "BTC-OnChain" ) ;
var id2 = PaymentMethodId . Parse ( "BTC-BTCLike" ) ;
Assert . Equal ( id , id1 ) ;
Assert . Equal ( id , id2 ) ;
Assert . Equal ( "BTC" , id . ToString ( ) ) ;
Assert . Equal ( "BTC" , id . ToString ( ) ) ;
id = PaymentMethodId . Parse ( "LTC" ) ;
Assert . Equal ( "LTC" , id . ToString ( ) ) ;
Assert . Equal ( "LTC" , id . ToStringNormalized ( ) ) ;
id = PaymentMethodId . Parse ( "LTC-offchain" ) ;
id1 = PaymentMethodId . Parse ( "LTC-OffChain" ) ;
id2 = PaymentMethodId . Parse ( "LTC-LightningLike" ) ;
Assert . Equal ( id , id1 ) ;
Assert . Equal ( id , id2 ) ;
Assert . Equal ( "LTC_LightningLike" , id . ToString ( ) ) ;
Assert . Equal ( "LTC-LightningNetwork" , id . ToStringNormalized ( ) ) ;
#if ALTCOINS
id = PaymentMethodId . Parse ( "XMR" ) ;
id1 = PaymentMethodId . Parse ( "XMR-MoneroLike" ) ;
Assert . Equal ( id , id1 ) ;
Assert . Equal ( "XMR_MoneroLike" , id . ToString ( ) ) ;
Assert . Equal ( "XMR" , id . ToStringNormalized ( ) ) ;
#endif
}
[Fact]
public async Task CheckExternalNoReferrerLinks ( )
{
var views = Path . Combine ( TestUtils . TryGetSolutionDirectoryInfo ( ) . FullName , "BTCPayServer" , "Views" ) ;
var viewFiles = Directory . EnumerateFiles ( views , "*.cshtml" , SearchOption . AllDirectories ) . ToArray ( ) ;
Assert . NotEmpty ( viewFiles ) ;
foreach ( var file in viewFiles )
{
var html = await File . ReadAllTextAsync ( file ) ;
CheckHtmlNodesForReferrer ( file , html , "a" , "href" ) ;
CheckHtmlNodesForReferrer ( file , html , "form" , "action" ) ;
}
}
private String GetAttributeValue ( String nodeHtml , string attribute )
{
Regex regex = new Regex ( "\\s" + attribute + "=\"(.*?)\"" ) ;
var match = regex . Match ( nodeHtml ) ;
if ( match . Success )
{
return match . Groups [ 1 ] . Value ;
}
return null ;
}
private void CheckHtmlNodesForReferrer ( string filePath , string html , string tagName , string attribute )
{
Regex aNodeRegex = new Regex ( "<" + tagName + "\\s.*?>" ) ;
var matches = aNodeRegex . Matches ( html ) . OfType < Match > ( ) ;
foreach ( var match in matches )
{
var node = match . Groups [ 0 ] . Value ;
var attributeValue = GetAttributeValue ( node , attribute ) ;
if ( attributeValue ! = null )
{
if ( attributeValue . Length = = 0 | | attributeValue . StartsWith ( "mailto:" ) | | attributeValue . StartsWith ( "/" ) | | attributeValue . StartsWith ( "~/" ) | | attributeValue . StartsWith ( "#" ) | | attributeValue . StartsWith ( "?" ) | | attributeValue . StartsWith ( "javascript:" ) | | attributeValue . StartsWith ( "@Url.Action(" ) )
{
// Local link, this is fine
}
2022-02-10 04:24:28 +01:00
else if ( attributeValue . StartsWith ( "http://" ) | | attributeValue . StartsWith ( "https://" ) )
2021-11-22 16:49:51 +01:00
{
// This can be an external link. Treating it as such.
var rel = GetAttributeValue ( node , "rel" ) ;
// Building the file path + line number helps us to navigate to the wrong HTML quickly!
var lineNumber = html . Substring ( 0 , html . IndexOf ( node , StringComparison . InvariantCulture ) ) . Split ( "\n" ) . Length ;
Assert . True ( rel ! = null , "Template file \"" + filePath + ":" + lineNumber + "\" contains a possibly external link (" + node + ") that is missing rel=\"noreferrer noopener\"" ) ;
if ( rel ! = null )
{
// All external links should have 'rel="noreferrer noopener"'
var relWords = rel . Split ( " " ) ;
Assert . Contains ( "noreferrer" , relWords ) ;
Assert . Contains ( "noopener" , relWords ) ;
}
}
}
}
}
[Fact]
public void CanHandleUriValidation ( )
{
var attribute = new UriAttribute ( ) ;
Assert . True ( attribute . IsValid ( "http://localhost" ) ) ;
Assert . True ( attribute . IsValid ( "http://localhost:1234" ) ) ;
Assert . True ( attribute . IsValid ( "https://localhost" ) ) ;
Assert . True ( attribute . IsValid ( "https://127.0.0.1" ) ) ;
Assert . True ( attribute . IsValid ( "http://127.0.0.1" ) ) ;
Assert . True ( attribute . IsValid ( "http://127.0.0.1:1234" ) ) ;
Assert . True ( attribute . IsValid ( "http://gozo.com" ) ) ;
Assert . True ( attribute . IsValid ( "https://gozo.com" ) ) ;
Assert . True ( attribute . IsValid ( "https://gozo.com:1234" ) ) ;
Assert . True ( attribute . IsValid ( "https://gozo.com:1234/test.css" ) ) ;
Assert . True ( attribute . IsValid ( "https://gozo.com:1234/test.png" ) ) ;
Assert . False ( attribute . IsValid (
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud e" ) ) ;
Assert . False ( attribute . IsValid ( 2 ) ) ;
Assert . False ( attribute . IsValid ( "http://" ) ) ;
Assert . False ( attribute . IsValid ( "httpdsadsa.com" ) ) ;
}
[Fact]
public void CanParseTorrc ( )
{
var nl = "\n" ;
var input = "# For the hidden service BTCPayServer" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.10:81" ;
nl = Environment . NewLine ;
var expected = "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"HiddenServicePort 80 172.19.0.10:81" + nl ;
Assert . True ( Torrc . TryParse ( input , out var torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
nl = "\r\n" ;
input = "# For the hidden service BTCPayServer" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.10:81" ;
Assert . True ( Torrc . TryParse ( input , out torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
input = "# For the hidden service BTCPayServer" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.10:80" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.11:80" ;
nl = Environment . NewLine ;
expected = "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"HiddenServicePort 80 172.19.0.10:80" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl +
"HiddenServicePort 80 172.19.0.11:80" + nl ;
Assert . True ( Torrc . TryParse ( input , out torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
}
2023-09-05 08:32:49 +02:00
[Fact]
public void CanParseCartItems ( )
{
Assert . True ( AppService . TryParsePosCartItems ( new JObject ( )
{
{ "cart" , new JArray ( )
{
new JObject ( )
{
{ "id" , "ddd" } ,
{ "price" , 4 } ,
{ "count" , 1 }
}
} }
} , out var items ) ) ;
Assert . Equal ( "ddd" , items [ 0 ] . Id ) ;
Assert . Equal ( 1 , items [ 0 ] . Count ) ;
Assert . Equal ( 4 , items [ 0 ] . Price ) ;
// Using legacy parsing
Assert . True ( AppService . TryParsePosCartItems ( new JObject ( )
{
{ "cart" , new JArray ( )
{
new JObject ( )
{
{ "id" , "ddd" } ,
{ "price" , new JObject ( )
{
{ "value" , 8.49 m }
}
} ,
{ "count" , 1 }
}
} }
} , out items ) ) ;
Assert . Equal ( "ddd" , items [ 0 ] . Id ) ;
Assert . Equal ( 1 , items [ 0 ] . Count ) ;
Assert . Equal ( 8.49 m , items [ 0 ] . Price ) ;
Assert . False ( AppService . TryParsePosCartItems ( new JObject ( )
{
{ "cart" , new JArray ( )
{
new JObject ( )
{
{ "id" , "ddd" } ,
{ "price" , new JObject ( )
{
{ "value" , "nocrahs" }
}
} ,
{ "count" , 1 }
}
} }
} , out items ) ) ;
}
2023-07-19 11:47:32 +02:00
[Fact]
public void CanCalculateDust ( )
{
var entity = new InvoiceEntity ( ) { Currency = "USD" } ;
entity . Networks = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
#pragma warning disable CS0618
entity . Payments = new System . Collections . Generic . List < PaymentEntity > ( ) ;
entity . SetPaymentMethod ( new PaymentMethod ( )
{
Currency = "BTC" ,
Rate = 34_000 m
} ) ;
entity . Price = 4000 ;
entity . UpdateTotals ( ) ;
var accounting = entity . GetPaymentMethods ( ) . First ( ) . Calculate ( ) ;
// Exact price should be 0.117647059..., but the payment method round up to one sat
Assert . Equal ( 0.11764706 m , accounting . Due ) ;
entity . Payments . Add ( new PaymentEntity ( )
{
Currency = "BTC" ,
Output = new TxOut ( Money . Coins ( 0.11764706 m ) , new Key ( ) ) ,
Accounted = true
} ) ;
entity . UpdateTotals ( ) ;
Assert . Equal ( 0.0 m , entity . NetDue ) ;
// The dust's value is below 1 sat
Assert . True ( entity . Dust > 0.0 m ) ;
Assert . True ( Money . Satoshis ( 1.0 m ) . ToDecimal ( MoneyUnit . BTC ) * entity . Rates [ "BTC" ] > entity . Dust ) ;
Assert . True ( ! entity . IsOverPaid ) ;
Assert . True ( ! entity . IsUnderPaid ) ;
// Now, imagine there is litecoin. It might seem from its
// perspecitve that there has been a slight over payment.
// However, Calculate() should just cap it to 0.0m
entity . SetPaymentMethod ( new PaymentMethod ( )
{
Currency = "LTC" ,
Rate = 3400 m
} ) ;
entity . UpdateTotals ( ) ;
var method = entity . GetPaymentMethods ( ) . First ( p = > p . Currency = = "LTC" ) ;
accounting = method . Calculate ( ) ;
Assert . Equal ( 0.0 m , accounting . DueUncapped ) ;
#pragma warning restore CS0618
}
2021-11-22 16:49:51 +01:00
#if ALTCOINS
[Fact]
public void CanCalculateCryptoDue ( )
{
var networkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
2023-07-19 11:47:32 +02:00
var entity = new InvoiceEntity ( ) { Currency = "USD" } ;
2021-11-22 16:49:51 +01:00
entity . Networks = networkProvider ;
#pragma warning disable CS0618
entity . Payments = new System . Collections . Generic . List < PaymentEntity > ( ) ;
entity . SetPaymentMethod ( new PaymentMethod ( )
{
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
Rate = 5000 ,
NextNetworkFee = Money . Coins ( 0.1 m )
} ) ;
entity . Price = 5000 ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
var paymentMethod = entity . GetPaymentMethods ( ) . TryGet ( "BTC" , PaymentTypes . BTCLike ) ;
var accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 1.0 m , accounting . ToSmallestUnit ( Money . Satoshis ( 1.0 m ) . ToDecimal ( MoneyUnit . BTC ) ) ) ;
Assert . Equal ( 1.1 m , accounting . Due ) ;
Assert . Equal ( 1.1 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . Payments . Add ( new PaymentEntity ( )
{
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
Output = new TxOut ( Money . Coins ( 0.5 m ) , new Key ( ) ) ,
2023-07-19 11:47:32 +02:00
Rate = 5000 ,
2021-11-22 16:49:51 +01:00
Accounted = true ,
NetworkFee = 0.1 m
} ) ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = paymentMethod . Calculate ( ) ;
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.7 m , accounting . Due ) ;
Assert . Equal ( 1.2 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . Payments . Add ( new PaymentEntity ( )
{
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
Output = new TxOut ( Money . Coins ( 0.2 m ) , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.1 m
} ) ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.6 m , accounting . Due ) ;
Assert . Equal ( 1.3 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . Payments . Add ( new PaymentEntity ( )
{
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
Output = new TxOut ( Money . Coins ( 0.6 m ) , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.1 m
} ) ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.0 m , accounting . Due ) ;
Assert . Equal ( 1.3 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . Payments . Add (
2023-07-19 11:47:32 +02:00
new PaymentEntity ( ) { Currency = "BTC" , Output = new TxOut ( Money . Coins ( 0.2 m ) , new Key ( ) ) , Accounted = true } ) ;
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.0 m , accounting . Due ) ;
Assert . Equal ( 1.3 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
entity = new InvoiceEntity ( ) ;
entity . Networks = networkProvider ;
entity . Price = 5000 ;
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary ( ) ;
paymentMethods . Add (
2023-07-19 11:47:32 +02:00
new PaymentMethod ( ) { Currency = "BTC" , Rate = 1000 , NextNetworkFee = Money . Coins ( 0.1 m ) } ) ;
2021-11-22 16:49:51 +01:00
paymentMethods . Add (
2023-07-19 11:47:32 +02:00
new PaymentMethod ( ) { Currency = "LTC" , Rate = 500 , NextNetworkFee = Money . Coins ( 0.01 m ) } ) ;
2021-11-22 16:49:51 +01:00
entity . SetPaymentMethods ( paymentMethods ) ;
entity . Payments = new List < PaymentEntity > ( ) ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 5.1 m , accounting . Due ) ;
2021-11-22 16:49:51 +01:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 10.01 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . Payments . Add ( new PaymentEntity ( )
{
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
Output = new TxOut ( Money . Coins ( 1.0 m ) , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.1 m
} ) ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 4.2 m , accounting . Due ) ;
Assert . Equal ( 1.0 m , accounting . CryptoPaid ) ;
Assert . Equal ( 1.0 m , accounting . Paid ) ;
Assert . Equal ( 5.2 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
Assert . Equal ( 2 , accounting . TxRequired ) ;
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 10.01 m + 0.1 m * 2 - 2.0 m /* 8.21m */ , accounting . Due ) ;
Assert . Equal ( 0.0 m , accounting . CryptoPaid ) ;
Assert . Equal ( 2.0 m , accounting . Paid ) ;
Assert . Equal ( 10.01 m + 0.1 m * 2 , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . Payments . Add ( new PaymentEntity ( )
{
2023-07-19 11:47:32 +02:00
Currency = "LTC" ,
2021-11-22 16:49:51 +01:00
Output = new TxOut ( Money . Coins ( 1.0 m ) , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.01 m
} ) ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 4.2 m - 0.5 m + 0.01 m / 2 , accounting . Due ) ;
Assert . Equal ( 1.0 m , accounting . CryptoPaid ) ;
Assert . Equal ( 1.5 m , accounting . Paid ) ;
Assert . Equal ( 5.2 m + 0.01 m / 2 , accounting . TotalDue ) ; // The fee for LTC added
2021-11-22 16:49:51 +01:00
Assert . Equal ( 2 , accounting . TxRequired ) ;
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 8.21 m - 1.0 m + 0.01 m , accounting . Due ) ;
Assert . Equal ( 1.0 m , accounting . CryptoPaid ) ;
Assert . Equal ( 3.0 m , accounting . Paid ) ;
Assert . Equal ( 10.01 m + 0.1 m * 2 + 0.01 m , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
Assert . Equal ( 2 , accounting . TxRequired ) ;
2023-07-19 11:47:32 +02:00
var remaining = Money . Coins ( 4.2 m - 0.5 m + 0.01 m / 2.0 m ) . ToDecimal ( MoneyUnit . BTC ) ;
2021-11-22 16:49:51 +01:00
entity . Payments . Add ( new PaymentEntity ( )
{
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
Output = new TxOut ( Money . Coins ( remaining ) , new Key ( ) ) ,
2021-11-22 16:49:51 +01:00
Accounted = true ,
NetworkFee = 0.1 m
} ) ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.0 m , accounting . Due ) ;
Assert . Equal ( 1.0 m + remaining , accounting . CryptoPaid ) ;
Assert . Equal ( 1.5 m + remaining , accounting . Paid ) ;
Assert . Equal ( 5.2 m + 0.01 m / 2 , accounting . TotalDue ) ;
2021-11-22 16:49:51 +01:00
Assert . Equal ( accounting . Paid , accounting . TotalDue ) ;
Assert . Equal ( 2 , accounting . TxRequired ) ;
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.0 m , accounting . Due ) ;
Assert . Equal ( 1.0 m , accounting . CryptoPaid ) ;
Assert . Equal ( 3.0 m + remaining * 2 , accounting . Paid ) ;
2021-11-22 16:49:51 +01:00
// Paying 2 BTC fee, LTC fee removed because fully paid
2023-07-19 11:47:32 +02:00
Assert . Equal ( 10.01 m + 0.1 m * 2 + 0.1 m * 2 /* + 0.01m no need to pay this fee anymore */ ,
2021-11-22 16:49:51 +01:00
accounting . TotalDue ) ;
Assert . Equal ( 1 , accounting . TxRequired ) ;
Assert . Equal ( accounting . Paid , accounting . TotalDue ) ;
#pragma warning restore CS0618
}
#endif
[Fact]
public void DeterministicUTXOSorter ( )
{
UTXO CreateRandomUTXO ( )
{
return new UTXO ( ) { Outpoint = new OutPoint ( RandomUtils . GetUInt256 ( ) , RandomUtils . GetUInt32 ( ) % 0xff ) } ;
}
var comparer = Payments . PayJoin . PayJoinEndpointController . UTXODeterministicComparer . Instance ;
var utxos = Enumerable . Range ( 0 , 100 ) . Select ( _ = > CreateRandomUTXO ( ) ) . ToArray ( ) ;
Array . Sort ( utxos , comparer ) ;
var utxo53 = utxos [ 53 ] ;
Array . Sort ( utxos , comparer ) ;
Assert . Equal ( utxo53 , utxos [ 53 ] ) ;
var utxo54 = utxos [ 54 ] ;
var utxo52 = utxos [ 52 ] ;
utxos = utxos . Where ( ( _ , i ) = > i ! = 53 ) . ToArray ( ) ;
Array . Sort ( utxos , comparer ) ;
Assert . Equal ( utxo52 , utxos [ 52 ] ) ;
Assert . Equal ( utxo54 , utxos [ 53 ] ) ;
}
[Fact]
public void CanAcceptInvoiceWithTolerance ( )
{
var networkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary ( new IPaymentMethodHandler [ ]
{
2023-03-09 21:36:11 +01:00
new BitcoinLikePaymentHandler ( null , networkProvider , null , null , null , null ) ,
2021-11-22 16:49:51 +01:00
new LightningLikePaymentHandler ( null , null , networkProvider , null , null , null ) ,
} ) ;
var entity = new InvoiceEntity ( ) ;
entity . Networks = networkProvider ;
#pragma warning disable CS0618
entity . Payments = new List < PaymentEntity > ( ) ;
entity . SetPaymentMethod ( new PaymentMethod ( )
{
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
Rate = 5000 ,
NextNetworkFee = Money . Coins ( 0.1 m )
} ) ;
entity . Price = 5000 ;
entity . PaymentTolerance = 0 ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
var paymentMethod = entity . GetPaymentMethods ( ) . TryGet ( "BTC" , PaymentTypes . BTCLike ) ;
var accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 1.1 m , accounting . Due ) ;
Assert . Equal ( 1.1 m , accounting . TotalDue ) ;
Assert . Equal ( 1.1 m , accounting . MinimumTotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . PaymentTolerance = 10 ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.99 m , accounting . MinimumTotalDue ) ;
2021-11-22 16:49:51 +01:00
entity . PaymentTolerance = 100 ;
2023-07-19 11:47:32 +02:00
entity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = paymentMethod . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.0000 _0001m , accounting . MinimumTotalDue ) ;
2021-11-22 16:49:51 +01:00
}
[Fact]
public void CanCalculatePeriod ( )
{
Data . PullPaymentData data = new Data . PullPaymentData ( ) ;
data . StartDate = Date ( 0 ) ;
data . EndDate = null ;
var period = data . GetPeriod ( Date ( 1 ) ) . Value ;
Assert . Equal ( Date ( 0 ) , period . Start ) ;
Assert . Null ( period . End ) ;
data . EndDate = Date ( 7 ) ;
period = data . GetPeriod ( Date ( 1 ) ) . Value ;
Assert . Equal ( Date ( 0 ) , period . Start ) ;
Assert . Equal ( Date ( 7 ) , period . End ) ;
data . Period = ( long ) TimeSpan . FromDays ( 2 ) . TotalSeconds ;
period = data . GetPeriod ( Date ( 1 ) ) . Value ;
Assert . Equal ( Date ( 0 ) , period . Start ) ;
Assert . Equal ( Date ( 2 ) , period . End ) ;
period = data . GetPeriod ( Date ( 2 ) ) . Value ;
Assert . Equal ( Date ( 2 ) , period . Start ) ;
Assert . Equal ( Date ( 4 ) , period . End ) ;
period = data . GetPeriod ( Date ( 6 ) ) . Value ;
Assert . Equal ( Date ( 6 ) , period . Start ) ;
Assert . Equal ( Date ( 7 ) , period . End ) ;
Assert . Null ( data . GetPeriod ( Date ( 7 ) ) ) ;
Assert . Null ( data . GetPeriod ( Date ( 8 ) ) ) ;
data . EndDate = null ;
period = data . GetPeriod ( Date ( 6 ) ) . Value ;
Assert . Equal ( Date ( 6 ) , period . Start ) ;
Assert . Equal ( Date ( 8 ) , period . End ) ;
Assert . Null ( data . GetPeriod ( Date ( - 1 ) ) ) ;
}
private DateTimeOffset Date ( int days )
{
return new DateTimeOffset ( 1970 , 1 , 1 , 0 , 0 , 0 , TimeSpan . Zero ) + TimeSpan . FromDays ( days ) ;
}
2023-02-14 09:03:12 +01:00
[Fact]
2023-07-24 15:57:24 +02:00
public void CanDetectFileType ( )
2023-02-14 09:03:12 +01:00
{
Assert . True ( FileTypeDetector . IsPicture ( new byte [ ] { 0x42 , 0x4D } , "test.bmp" ) ) ;
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { 0x42 , 0x4D } , ".bmp" ) ) ;
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { 0x42 , 0x4D } , "test.svg" ) ) ;
Assert . True ( FileTypeDetector . IsPicture ( new byte [ ] { 0xFF , 0xD8 , 0xFF , 0xD9 } , "test.jpg" ) ) ;
Assert . True ( FileTypeDetector . IsPicture ( new byte [ ] { 0xFF , 0xD8 , 0xFF , 0xD9 } , "test.jpeg" ) ) ;
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { 0xFF , 0xD8 , 0xFF , 0xDA } , "test.jpg" ) ) ;
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { 0xFF , 0xD8 , 0xFF } , "test.jpg" ) ) ;
Assert . True ( FileTypeDetector . IsPicture ( new byte [ ] { 0x3C , 0x73 , 0x76 , 0x67 } , "test.svg" ) ) ;
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { 0x3C , 0x73 , 0x76 , 0x67 } , "test.jpg" ) ) ;
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { 0xFF } , "e.jpg" ) ) ;
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { } , "empty.jpg" ) ) ;
2023-07-24 15:57:24 +02:00
Assert . False ( FileTypeDetector . IsPicture ( new byte [ ] { 0x49 , 0x44 , 0x33 , 0x04 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x23 } , "music.mp3" ) ) ;
Assert . True ( FileTypeDetector . IsAudio ( new byte [ ] { 0x49 , 0x44 , 0x33 , 0x04 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x23 } , "music.mp3" ) ) ;
Assert . True ( FileTypeDetector . IsAudio ( new byte [ ] { 0x52 , 0x49 , 0x46 , 0x46 , 0x24 , 0x9A , 0x08 , 0x00 , 0x57 , 0x41 } , "music.wav" ) ) ;
Assert . True ( FileTypeDetector . IsAudio ( new byte [ ] { 0xFF , 0xF1 , 0x50 , 0x80 , 0x1C , 0x3F , 0xFC , 0xDA , 0x00 , 0x4C } , "music.aac" ) ) ;
Assert . True ( FileTypeDetector . IsAudio ( new byte [ ] { 0x66 , 0x4C , 0x61 , 0x43 , 0x00 , 0x00 , 0x00 , 0x22 , 0x04 , 0x80 } , "music.flac" ) ) ;
Assert . True ( FileTypeDetector . IsAudio ( new byte [ ] { 0x4F , 0x67 , 0x67 , 0x53 , 0x00 , 0x02 , 0x00 , 0x00 , 0x00 , 0x00 } , "music.ogg" ) ) ;
Assert . True ( FileTypeDetector . IsAudio ( new byte [ ] { 0x1A , 0x45 , 0xDF , 0xA3 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } , "music.weba" ) ) ;
Assert . True ( FileTypeDetector . IsAudio ( new byte [ ] { 0xFF , 0xF3 , 0xE4 , 0x64 , 0x00 , 0x20 , 0xAD , 0xBD , 0x04 , 0x00 } , "music.mp3" ) ) ;
2023-02-14 09:03:12 +01:00
}
2021-11-22 16:49:51 +01:00
[Fact]
public void RoundupCurrenciesCorrectly ( )
{
2023-04-10 04:07:03 +02:00
DisplayFormatter displayFormatter = new ( CurrencyNameTable . Instance ) ;
2021-11-22 16:49:51 +01:00
foreach ( var test in new [ ]
{
2023-03-13 02:12:58 +01:00
( 0.0005 m , "0.0005 USD" , "USD" ) , ( 0.001 m , "0.001 USD" , "USD" ) , ( 0.01 m , "0.01 USD" , "USD" ) ,
( 0.1 m , "0.10 USD" , "USD" ) , ( 0.1 m , "0,10 EUR" , "EUR" ) , ( 1000 m , "1,000 JPY" , "JPY" ) ,
( 1000.0001 m , "1,000.00 INR" , "INR" ) ,
( 0.0 m , "0.00 USD" , "USD" )
2021-11-22 16:49:51 +01:00
} )
{
2023-03-13 02:12:58 +01:00
var actual = displayFormatter . Currency ( test . Item1 , test . Item3 ) ;
2021-11-22 16:49:51 +01:00
actual = actual . Replace ( "ï¿¥" , "Â¥" ) ; // Hack so JPY test pass on linux as well
Assert . Equal ( test . Item2 , actual ) ;
}
}
[Fact]
public async Task CanEnumerateTorServices ( )
{
var tor = new TorServices ( new BTCPayNetworkProvider ( ChainName . Regtest ) ,
new OptionsWrapper < BTCPayServerOptions > ( new BTCPayServerOptions ( )
{
TorrcFile = TestUtils . GetTestDataFullPath ( "Tor/torrc" )
2021-11-22 09:16:08 +01:00
} ) , BTCPayLogs ) ;
2021-11-22 16:49:51 +01:00
await tor . Refresh ( ) ;
Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . BTCPayServer ) ) ;
Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . P2P ) ) ;
Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . RPC ) ) ;
Assert . True ( tor . Services . Count ( t = > t . ServiceType = = TorServiceType . Other ) > 1 ) ;
tor = new TorServices ( new BTCPayNetworkProvider ( ChainName . Regtest ) ,
new OptionsWrapper < BTCPayServerOptions > ( new BTCPayServerOptions ( )
{
TorrcFile = null ,
TorServices = "btcpayserver:host.onion:80;btc-p2p:host2.onion:81,BTC-RPC:host3.onion:82,UNKNOWN:host4.onion:83,INVALID:ddd" . Split ( new [ ] { ';' , ',' } , StringSplitOptions . RemoveEmptyEntries )
2021-11-22 09:16:08 +01:00
} ) , BTCPayLogs ) ;
2021-11-22 16:49:51 +01:00
await Task . WhenAll ( tor . StartAsync ( CancellationToken . None ) ) ;
var btcpayS = Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . BTCPayServer ) ) ;
Assert . Null ( btcpayS . Network ) ;
Assert . Equal ( "host.onion" , btcpayS . OnionHost ) ;
Assert . Equal ( 80 , btcpayS . VirtualPort ) ;
var p2p = Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . P2P ) ) ;
Assert . NotNull ( p2p . Network ) ;
Assert . Equal ( "BTC" , p2p . Network . CryptoCode ) ;
Assert . Equal ( "host2.onion" , p2p . OnionHost ) ;
Assert . Equal ( 81 , p2p . VirtualPort ) ;
var rpc = Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . RPC ) ) ;
Assert . NotNull ( p2p . Network ) ;
Assert . Equal ( "BTC" , rpc . Network . CryptoCode ) ;
Assert . Equal ( "host3.onion" , rpc . OnionHost ) ;
Assert . Equal ( 82 , rpc . VirtualPort ) ;
var unknown = Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . Other ) ) ;
Assert . Null ( unknown . Network ) ;
Assert . Equal ( "host4.onion" , unknown . OnionHost ) ;
Assert . Equal ( 83 , unknown . VirtualPort ) ;
Assert . Equal ( "UNKNOWN" , unknown . Name ) ;
Assert . Equal ( 4 , tor . Services . Length ) ;
}
2022-12-07 12:18:17 +01:00
[Fact]
public void CanParseDerivationSchemes ( )
{
var networkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
var parser = new DerivationSchemeParser ( networkProvider . BTC ) ;
// xpub
var xpub = "xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw" ;
DerivationStrategyBase strategyBase = parser . Parse ( xpub ) ;
Assert . IsType < DirectDerivationStrategy > ( strategyBase ) ;
Assert . True ( ( ( DirectDerivationStrategy ) strategyBase ) . Segwit ) ;
Assert . Equal ( "tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS" , strategyBase . ToString ( ) ) ;
// Multisig
var multisig = "wsh(sortedmulti(2,[62a7956f/84'/1'/0']tpubDDXgATYzdQkHHhZZCMcNJj8BGDENvzMVou5v9NdxiP4rxDLj33nS233dGFW4htpVZSJ6zds9eVqAV9RyRHHiKtwQKX8eR4n4KN3Dwmj7A3h/0/*,[11312aa2/84'/1'/0']tpubDC8a54NFtQtMQAZ97VhoU9V6jVTvi9w4Y5SaAXJSBYETKg3AoX5CCKndznhPWxJUBToPCpT44s86QbKdGpKAnSjcMTGW4kE6UQ8vpBjcybW/0/*,[8f71b834/84'/1'/0']tpubDChjnP9LXNrJp43biqjY7FH93wgRRNrNxB4Q8pH7PPRy8UPcH2S6V46WGVJ47zVGF7SyBJNCpnaogsFbsybVQckGtVhCkng3EtFn8qmxptS/0/*))" ;
var expected = "2-of-tpubDDXgATYzdQkHHhZZCMcNJj8BGDENvzMVou5v9NdxiP4rxDLj33nS233dGFW4htpVZSJ6zds9eVqAV9RyRHHiKtwQKX8eR4n4KN3Dwmj7A3h-tpubDC8a54NFtQtMQAZ97VhoU9V6jVTvi9w4Y5SaAXJSBYETKg3AoX5CCKndznhPWxJUBToPCpT44s86QbKdGpKAnSjcMTGW4kE6UQ8vpBjcybW-tpubDChjnP9LXNrJp43biqjY7FH93wgRRNrNxB4Q8pH7PPRy8UPcH2S6V46WGVJ47zVGF7SyBJNCpnaogsFbsybVQckGtVhCkng3EtFn8qmxptS" ;
( strategyBase , RootedKeyPath [ ] rootedKeyPath ) = parser . ParseOutputDescriptor ( multisig ) ;
Assert . Equal ( 3 , rootedKeyPath . Length ) ;
Assert . IsType < P2WSHDerivationStrategy > ( strategyBase ) ;
Assert . IsType < MultisigDerivationStrategy > ( ( ( P2WSHDerivationStrategy ) strategyBase ) . Inner ) ;
Assert . Equal ( expected , strategyBase . ToString ( ) ) ;
var inner = ( MultisigDerivationStrategy ) ( ( P2WSHDerivationStrategy ) strategyBase ) . Inner ;
Assert . False ( inner . IsLegacy ) ;
Assert . Equal ( 3 , inner . Keys . Count ) ;
Assert . Equal ( 2 , inner . RequiredSignatures ) ;
Assert . Equal ( expected , inner . ToString ( ) ) ;
2023-01-06 14:18:07 +01:00
2022-12-07 12:18:17 +01:00
// Output Descriptor
networkProvider = new BTCPayNetworkProvider ( ChainName . Mainnet ) ;
parser = new DerivationSchemeParser ( networkProvider . BTC ) ;
var od = "wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48" ;
( strategyBase , rootedKeyPath ) = parser . ParseOutputDescriptor ( od ) ;
2022-12-13 01:09:25 +01:00
Assert . Single ( rootedKeyPath ) ;
2022-12-07 12:18:17 +01:00
Assert . IsType < DirectDerivationStrategy > ( strategyBase ) ;
Assert . True ( ( ( DirectDerivationStrategy ) strategyBase ) . Segwit ) ;
2023-01-06 14:18:07 +01:00
2022-12-07 12:18:17 +01:00
// Failure cases
Assert . Throws < FormatException > ( ( ) = > { parser . Parse ( "xpub 661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw" ) ; } ) ; // invalid format because of space
Assert . Throws < ParsingException > ( ( ) = > { parser . ParseOutputDescriptor ( "invalid" ) ; } ) ; // invalid in general
Assert . Throws < ParsingException > ( ( ) = > { parser . ParseOutputDescriptor ( "wpkh([8b60afd1/49h/0h/0h]xpub661MyMwAFXkMnyoBjyHndD3QwRbcGVBsTGeNZN6QGVHcfz4MPzBUxjSevweNFQx7SqmMHLdSA4FteGsRrEriu4pnVZMZWnruFFAYZATtcDw/0/*)#9x4vkw48" ) ; } ) ; // invalid checksum
}
2023-03-20 02:45:32 +01:00
[Fact]
public void ParseTradeQuantity ( )
{
Assert . Throws < FormatException > ( ( ) = > TradeQuantity . Parse ( "1.2345o" ) ) ;
Assert . Throws < FormatException > ( ( ) = > TradeQuantity . Parse ( "o" ) ) ;
Assert . Throws < FormatException > ( ( ) = > TradeQuantity . Parse ( "" ) ) ;
Assert . Throws < FormatException > ( ( ) = > TradeQuantity . Parse ( "1.353%%" ) ) ;
Assert . Throws < FormatException > ( ( ) = > TradeQuantity . Parse ( "1.353 %%" ) ) ;
Assert . Throws < FormatException > ( ( ) = > TradeQuantity . Parse ( "-1.353%" ) ) ;
Assert . Throws < FormatException > ( ( ) = > TradeQuantity . Parse ( "-1.353" ) ) ;
var qty = TradeQuantity . Parse ( "1.3%" ) ;
Assert . Equal ( 1.3 m , qty . Value ) ;
Assert . Equal ( TradeQuantity . ValueType . Percent , qty . Type ) ;
var qty2 = TradeQuantity . Parse ( "1.3" ) ;
Assert . Equal ( 1.3 m , qty2 . Value ) ;
Assert . Equal ( TradeQuantity . ValueType . Exact , qty2 . Type ) ;
Assert . NotEqual ( qty , qty2 ) ;
Assert . Equal ( qty , TradeQuantity . Parse ( "1.3%" ) ) ;
Assert . Equal ( qty2 , TradeQuantity . Parse ( "1.3" ) ) ;
Assert . Equal ( TradeQuantity . Parse ( qty . ToString ( ) ) , TradeQuantity . Parse ( "1.3%" ) ) ;
Assert . Equal ( TradeQuantity . Parse ( qty2 . ToString ( ) ) , TradeQuantity . Parse ( "1.3" ) ) ;
Assert . Equal ( TradeQuantity . Parse ( qty2 . ToString ( ) ) , TradeQuantity . Parse ( " 1.3 " ) ) ;
}
2021-11-22 16:49:51 +01:00
[Fact]
public void ParseDerivationSchemeSettings ( )
{
2023-03-17 14:35:30 +01:00
var testnet = new BTCPayNetworkProvider ( ChainName . Testnet ) . GetNetwork < BTCPayNetwork > ( "BTC" ) ;
2021-11-22 16:49:51 +01:00
var mainnet = new BTCPayNetworkProvider ( ChainName . Mainnet ) . GetNetwork < BTCPayNetwork > ( "BTC" ) ;
var root = new Mnemonic (
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage" )
. DeriveExtKey ( ) ;
2023-04-10 04:07:03 +02:00
2023-03-17 14:35:30 +01:00
// xpub
var tpub = "tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS" ;
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile ( tpub , testnet , out var settings , out var error ) ) ;
Assert . Null ( error ) ;
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy { Segwit : false } ) ;
Assert . Equal ( $"{tpub}-[legacy]" , ( ( DirectDerivationStrategy ) settings . AccountDerivation ) . ToString ( ) ) ;
// xpub with fingerprint and account
tpub = "tpubDCXK98mNrPWuoWweaoUkqwxQF5NMWpQLy7n7XJgDCpwYfoZRXGafPaVM7mYqD7UKhsbMxkN864JY2PniMkt1Uk4dNuAMnWFVqdquyvZNyca" ;
var vpub = "vpub5YVA1ZbrqkUVq8NZTtvRDrS2a1yoeBvHbG9NbxqJ6uRtpKGFwjQT11WEqKYsgoDF6gpqrDf8ddmPZe4yXWCjzqF8ad2Cw9xHiE8DSi3X3ik" ;
var fingerprint = "e5746fd9" ;
var account = "84'/1'/0'" ;
var str = $"[{fingerprint}/{account}]{vpub}" ;
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile ( str , testnet , out settings , out error ) ) ;
Assert . Null ( error ) ;
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy { Segwit : true } ) ;
Assert . Equal ( vpub , settings . AccountOriginal ) ;
Assert . Equal ( tpub , ( ( DirectDerivationStrategy ) settings . AccountDerivation ) . ToString ( ) ) ;
Assert . Equal ( HDFingerprint . TryParse ( fingerprint , out var hd ) ? hd : default , settings . AccountKeySettings [ 0 ] . RootFingerprint ) ;
Assert . Equal ( account , settings . AccountKeySettings [ 0 ] . AccountKeyPath . ToString ( ) ) ;
2021-11-22 16:49:51 +01:00
// ColdCard
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
2023-03-17 14:35:30 +01:00
mainnet , out settings , out error ) ) ;
2022-12-05 09:06:05 +01:00
Assert . Null ( error ) ;
2021-11-22 16:49:51 +01:00
Assert . Equal ( root . GetPublicKey ( ) . GetHDFingerPrint ( ) , settings . AccountKeySettings [ 0 ] . RootFingerprint ) ;
Assert . Equal ( settings . AccountKeySettings [ 0 ] . RootFingerprint ,
2023-03-17 14:35:30 +01:00
HDFingerprint . TryParse ( "8bafd160" , out hd ) ? hd : default ) ;
2021-11-22 16:49:51 +01:00
Assert . Equal ( "Coldcard Import 0x60d1af8b" , settings . Label ) ;
Assert . Equal ( "49'/0'/0'" , settings . AccountKeySettings [ 0 ] . AccountKeyPath . ToString ( ) ) ;
Assert . Equal (
"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD" ,
settings . AccountOriginal ) ;
Assert . Equal ( root . Derive ( new KeyPath ( "m/49'/0'/0'" ) ) . Neuter ( ) . PubKey . WitHash . ScriptPubKey . Hash . ScriptPubKey ,
settings . AccountDerivation . GetDerivation ( ) . ScriptPubKey ) ;
// Should be legacy
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"tpubDDWYqT3P24znfsaGX7kZcQhNc5LAjnQiKQvUCHF2jS6dsgJBRtymopEU5uGpMaR5YChjuiExZG1X2aTbqXkp82KqH5qnqwWHp6EWis9ZvKr\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/44'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
2022-12-05 09:06:05 +01:00
testnet , out settings , out error ) ) ;
2023-03-17 14:35:30 +01:00
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy { Segwit : false } ) ;
2022-12-05 09:06:05 +01:00
Assert . Null ( error ) ;
2021-11-22 16:49:51 +01:00
// Should be segwit p2sh
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DSddA9NoRUyJrQ4p86nsCiTSY7kLHrSxx3joEJXjHd4HPARhdXUATuk585FdWPVC2GdjsMePHb6BMDmf7c6KG4K4RPX6LVqBLtDcWpQJmh\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
2022-12-05 09:06:05 +01:00
testnet , out settings , out error ) ) ;
2023-03-17 14:35:30 +01:00
Assert . True ( settings . AccountDerivation is P2SHDerivationStrategy { Inner : DirectDerivationStrategy { Segwit : true } } ) ;
2022-12-05 09:06:05 +01:00
Assert . Null ( error ) ;
2021-11-22 16:49:51 +01:00
// Should be segwit
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
2022-12-05 09:06:05 +01:00
testnet , out settings , out error ) ) ;
2023-03-17 14:35:30 +01:00
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy { Segwit : true } ) ;
2022-12-05 09:06:05 +01:00
Assert . Null ( error ) ;
2021-11-22 16:49:51 +01:00
// Specter
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
"{\"label\": \"Specter\", \"blockheight\": 123456, \"descriptor\": \"wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48\"}" ,
2022-12-05 09:06:05 +01:00
mainnet , out var specter , out error ) ) ;
2021-11-22 16:49:51 +01:00
Assert . Equal ( root . GetPublicKey ( ) . GetHDFingerPrint ( ) , specter . AccountKeySettings [ 0 ] . RootFingerprint ) ;
Assert . Equal ( specter . AccountKeySettings [ 0 ] . RootFingerprint , hd ) ;
Assert . Equal ( "49'/0'/0'" , specter . AccountKeySettings [ 0 ] . AccountKeyPath . ToString ( ) ) ;
Assert . Equal ( "Specter" , specter . Label ) ;
2022-12-05 09:06:05 +01:00
Assert . Null ( error ) ;
2023-01-06 14:18:07 +01:00
2022-12-05 09:06:05 +01:00
// Failure case
Assert . False ( DerivationSchemeSettings . TryParseFromWalletFile (
"{\"keystore\": {\"ckcc_xpub\": \"tpubFailure\", \"xpub\": \"tpubFailure\", \"label\": \"Failure\"}, \"wallet_type\": \"standard\"}" ,
testnet , out settings , out error ) ) ;
Assert . Null ( settings ) ;
Assert . NotNull ( error ) ;
2021-11-22 16:49:51 +01:00
}
[Fact]
2023-11-28 15:20:03 +01:00
public async Task CheckRatesProvider ( )
2021-11-22 16:49:51 +01:00
{
var spy = new SpyRateProvider ( ) ;
RateRules . TryParse ( "X_X = bittrex(X_X);" , out var rateRules ) ;
var factory = CreateBTCPayRateFactory ( ) ;
factory . Providers . Clear ( ) ;
var fetcher = new RateFetcher ( factory ) ;
factory . Providers . Clear ( ) ;
var fetch = new BackgroundFetcherRateProvider ( spy ) ;
fetch . DoNotAutoFetchIfExpired = true ;
factory . Providers . Add ( "bittrex" , fetch ) ;
2023-11-28 15:20:03 +01:00
var fetchedRate = await fetcher . FetchRate ( CurrencyPair . Parse ( "BTC_USD" ) , rateRules , default ) ;
2021-11-22 16:49:51 +01:00
spy . AssertHit ( ) ;
2023-11-28 15:20:03 +01:00
fetchedRate = await fetcher . FetchRate ( CurrencyPair . Parse ( "BTC_USD" ) , rateRules , default ) ;
2021-11-22 16:49:51 +01:00
spy . AssertNotHit ( ) ;
2023-11-28 15:20:03 +01:00
await fetch . UpdateIfNecessary ( default ) ;
2021-11-22 16:49:51 +01:00
spy . AssertNotHit ( ) ;
fetch . RefreshRate = TimeSpan . FromSeconds ( 1.0 ) ;
Thread . Sleep ( 1020 ) ;
2023-11-28 15:20:03 +01:00
fetchedRate = await fetcher . FetchRate ( CurrencyPair . Parse ( "BTC_USD" ) , rateRules , default ) ;
2021-11-22 16:49:51 +01:00
spy . AssertNotHit ( ) ;
fetch . ValidatyTime = TimeSpan . FromSeconds ( 1.0 ) ;
2023-11-28 15:20:03 +01:00
await fetch . UpdateIfNecessary ( default ) ;
2021-11-22 16:49:51 +01:00
spy . AssertHit ( ) ;
2023-11-28 15:20:03 +01:00
await fetch . GetRatesAsync ( default ) ;
2021-11-22 16:49:51 +01:00
Thread . Sleep ( 1000 ) ;
2023-11-28 15:20:03 +01:00
await Assert . ThrowsAsync < InvalidOperationException > ( ( ) = > fetch . GetRatesAsync ( default ) ) ;
2021-11-22 16:49:51 +01:00
}
public static RateProviderFactory CreateBTCPayRateFactory ( )
{
2023-01-30 01:46:12 +01:00
ServiceCollection services = new ServiceCollection ( ) ;
services . AddHttpClient ( ) ;
BTCPayServerServices . RegisterRateSources ( services ) ;
var o = services . BuildServiceProvider ( ) ;
return new RateProviderFactory ( TestUtils . CreateHttpFactory ( ) , o . GetService < IEnumerable < IRateProvider > > ( ) ) ;
2021-11-22 16:49:51 +01:00
}
class SpyRateProvider : IRateProvider
{
public bool Hit { get ; set ; }
2023-01-30 01:46:12 +01:00
public RateSourceInfo RateSourceInfo = > new ( "spy" , "SPY" , "https://spy.org" ) ;
2021-11-22 16:49:51 +01:00
public Task < PairRate [ ] > GetRatesAsync ( CancellationToken cancellationToken )
{
Hit = true ;
var rates = new List < PairRate > ( ) ;
rates . Add ( new PairRate ( CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 5000 ) ) ) ;
return Task . FromResult ( rates . ToArray ( ) ) ;
}
public void AssertHit ( )
{
Assert . True ( Hit , "Should have hit the provider" ) ;
Hit = false ;
}
public void AssertNotHit ( )
{
Assert . False ( Hit , "Should have not hit the provider" ) ;
Hit = false ;
}
}
[Fact]
public async Task CanExpandExternalConnectionString ( )
{
var unusedUri = new Uri ( "https://toto.com" ) ;
Assert . True ( ExternalConnectionString . TryParse ( "server=/test" , out var connStr , out var error ) ) ;
var expanded = await connStr . Expand ( new Uri ( "https://toto.com" ) , ExternalServiceTypes . Charge ,
ChainName . Mainnet ) ;
Assert . Equal ( new Uri ( "https://toto.com/test" ) , expanded . Server ) ;
expanded = await connStr . Expand ( new Uri ( "http://toto.onion" ) , ExternalServiceTypes . Charge ,
ChainName . Mainnet ) ;
Assert . Equal ( new Uri ( "http://toto.onion/test" ) , expanded . Server ) ;
await Assert . ThrowsAsync < SecurityException > ( ( ) = >
connStr . Expand ( new Uri ( "http://toto.com" ) , ExternalServiceTypes . Charge , ChainName . Mainnet ) ) ;
await connStr . Expand ( new Uri ( "http://toto.com" ) , ExternalServiceTypes . Charge , ChainName . Testnet ) ;
// Make sure absolute paths are not expanded
Assert . True ( ExternalConnectionString . TryParse ( "server=https://tow/test" , out connStr , out error ) ) ;
expanded = await connStr . Expand ( new Uri ( "https://toto.com" ) , ExternalServiceTypes . Charge ,
ChainName . Mainnet ) ;
Assert . Equal ( new Uri ( "https://tow/test" ) , expanded . Server ) ;
// Error if directory not exists
Assert . True ( ExternalConnectionString . TryParse ( $"server={unusedUri};macaroondirectorypath=pouet" ,
out connStr , out error ) ) ;
await Assert . ThrowsAsync < DirectoryNotFoundException > ( ( ) = >
connStr . Expand ( unusedUri , ExternalServiceTypes . LNDGRPC , ChainName . Mainnet ) ) ;
await Assert . ThrowsAsync < DirectoryNotFoundException > ( ( ) = >
connStr . Expand ( unusedUri , ExternalServiceTypes . LNDRest , ChainName . Mainnet ) ) ;
await connStr . Expand ( unusedUri , ExternalServiceTypes . Charge , ChainName . Mainnet ) ;
var macaroonDirectory = CreateDirectory ( ) ;
Assert . True ( ExternalConnectionString . TryParse (
$"server={unusedUri};macaroondirectorypath={macaroonDirectory}" , out connStr , out error ) ) ;
await connStr . Expand ( unusedUri , ExternalServiceTypes . LNDGRPC , ChainName . Mainnet ) ;
expanded = await connStr . Expand ( unusedUri , ExternalServiceTypes . LNDRest , ChainName . Mainnet ) ;
Assert . NotNull ( expanded . Macaroons ) ;
Assert . Null ( expanded . MacaroonFilePath ) ;
Assert . Null ( expanded . Macaroons . AdminMacaroon ) ;
Assert . Null ( expanded . Macaroons . InvoiceMacaroon ) ;
Assert . Null ( expanded . Macaroons . ReadonlyMacaroon ) ;
File . WriteAllBytes ( $"{macaroonDirectory}/admin.macaroon" , new byte [ ] { 0xaa } ) ;
File . WriteAllBytes ( $"{macaroonDirectory}/invoice.macaroon" , new byte [ ] { 0xab } ) ;
File . WriteAllBytes ( $"{macaroonDirectory}/readonly.macaroon" , new byte [ ] { 0xac } ) ;
expanded = await connStr . Expand ( unusedUri , ExternalServiceTypes . LNDRest , ChainName . Mainnet ) ;
Assert . NotNull ( expanded . Macaroons . AdminMacaroon ) ;
Assert . NotNull ( expanded . Macaroons . InvoiceMacaroon ) ;
Assert . Equal ( "ab" , expanded . Macaroons . InvoiceMacaroon . Hex ) ;
Assert . Equal ( 0xab , expanded . Macaroons . InvoiceMacaroon . Bytes [ 0 ] ) ;
Assert . NotNull ( expanded . Macaroons . ReadonlyMacaroon ) ;
Assert . True ( ExternalConnectionString . TryParse (
$"server={unusedUri};cookiefilepath={macaroonDirectory}/charge.cookie" , out connStr , out error ) ) ;
File . WriteAllText ( $"{macaroonDirectory}/charge.cookie" , "apitoken" ) ;
expanded = await connStr . Expand ( unusedUri , ExternalServiceTypes . Charge , ChainName . Mainnet ) ;
Assert . Equal ( "apitoken" , expanded . APIToken ) ;
}
private string CreateDirectory ( [ CallerMemberName ] string caller = null )
{
var name = $"{caller}-{NBitcoin.RandomUtils.GetUInt32()}" ;
Directory . CreateDirectory ( name ) ;
return name ;
}
[Fact]
public void CanCheckFileNameValid ( )
{
var tests = new [ ]
{
( "test.com" , true ) ,
( "/test.com" , false ) ,
( "te/st.com" , false ) ,
( "\\test.com" , false ) ,
( "te\\st.com" , false )
} ;
foreach ( var t in tests )
{
Assert . Equal ( t . Item2 , t . Item1 . IsValidFileName ( ) ) ;
}
}
[Fact]
public void CanFixupWebhookEventPropertyName ( )
{
string legacy = "{\"orignalDeliveryId\":\"blahblah\"}" ;
var obj = JsonConvert . DeserializeObject < WebhookEvent > ( legacy , WebhookEvent . DefaultSerializerSettings ) ;
Assert . Equal ( "blahblah" , obj . OriginalDeliveryId ) ;
var serialized = JsonConvert . SerializeObject ( obj , WebhookEvent . DefaultSerializerSettings ) ;
Assert . DoesNotContain ( "orignalDeliveryId" , serialized ) ;
Assert . Contains ( "originalDeliveryId" , serialized ) ;
}
[Fact]
public async Task CanCreateSqlitedb ( )
{
if ( File . Exists ( "temp.db" ) )
File . Delete ( "temp.db" ) ;
// This test sqlite can migrate
var builder = new DbContextOptionsBuilder < ApplicationDbContext > ( ) ;
builder . UseSqlite ( "Data Source=temp.db" ) ;
await new ApplicationDbContext ( builder . Options ) . Database . MigrateAsync ( ) ;
}
[Fact]
public void CanUsePermission ( )
{
Assert . True ( Permission . Create ( Policies . CanModifyServerSettings )
. Contains ( Permission . Create ( Policies . CanModifyServerSettings ) ) ) ;
Assert . True ( Permission . Create ( Policies . CanModifyProfile )
. Contains ( Permission . Create ( Policies . CanViewProfile ) ) ) ;
Assert . True ( Permission . Create ( Policies . CanModifyStoreSettings )
. Contains ( Permission . Create ( Policies . CanViewStoreSettings ) ) ) ;
Assert . False ( Permission . Create ( Policies . CanViewStoreSettings )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
Assert . False ( Permission . Create ( Policies . CanModifyServerSettings )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
Assert . True ( Permission . Create ( Policies . Unrestricted )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
Assert . True ( Permission . Create ( Policies . Unrestricted )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings , "abc" ) ) ) ;
Assert . True ( Permission . Create ( Policies . CanViewStoreSettings )
. Contains ( Permission . Create ( Policies . CanViewStoreSettings , "abcd" ) ) ) ;
Assert . False ( Permission . Create ( Policies . CanModifyStoreSettings , "abcd" )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
}
[Fact]
public void CanParseFilter ( )
{
2023-05-19 03:42:09 +02:00
var storeId = "6DehZnc9S7qC6TUTNWuzJ1pFsHTHvES6An21r3MjvLey" ;
2021-11-22 16:49:51 +01:00
var filter = "storeid:abc, status:abed, blabhbalh " ;
var search = new SearchString ( filter ) ;
Assert . Equal ( "storeid:abc, status:abed, blabhbalh" , search . ToString ( ) ) ;
Assert . Equal ( "blabhbalh" , search . TextSearch ) ;
2023-05-19 03:42:09 +02:00
Assert . Single ( search . Filters [ "storeid" ] , "abc" ) ;
Assert . Single ( search . Filters [ "status" ] , "abed" ) ;
2021-11-22 16:49:51 +01:00
filter = "status:abed, status:abed2" ;
search = new SearchString ( filter ) ;
Assert . Equal ( "" , search . TextSearch ) ;
Assert . Equal ( "status:abed, status:abed2" , search . ToString ( ) ) ;
Assert . Throws < KeyNotFoundException > ( ( ) = > search . Filters [ "test" ] ) ;
Assert . Equal ( 2 , search . Filters [ "status" ] . Count ) ;
Assert . Equal ( "abed" , search . Filters [ "status" ] . First ( ) ) ;
Assert . Equal ( "abed2" , search . Filters [ "status" ] . Skip ( 1 ) . First ( ) ) ;
filter = "StartDate:2019-04-25 01:00 AM, hekki" ;
search = new SearchString ( filter ) ;
Assert . Equal ( "2019-04-25 01:00 AM" , search . Filters [ "startdate" ] . First ( ) ) ;
Assert . Equal ( "hekki" , search . TextSearch ) ;
2023-07-19 11:47:32 +02:00
2023-05-19 03:42:09 +02:00
// modify search
filter = $"status:settled,exceptionstatus:paidLate,unusual:true, fulltext searchterm, storeid:{storeId},startdate:2019-04-25 01:00:00" ;
search = new SearchString ( filter ) ;
Assert . Equal ( filter , search . ToString ( ) ) ;
Assert . Equal ( "fulltext searchterm" , search . TextSearch ) ;
Assert . Single ( search . Filters [ "storeid" ] , storeId ) ;
Assert . Single ( search . Filters [ "status" ] , "settled" ) ;
Assert . Single ( search . Filters [ "exceptionstatus" ] , "paidLate" ) ;
Assert . Single ( search . Filters [ "unusual" ] , "true" ) ;
2023-07-19 11:47:32 +02:00
2023-05-19 03:42:09 +02:00
// toggle off bool with same value
var modified = new SearchString ( search . Toggle ( "unusual" , "true" ) ) ;
Assert . Null ( modified . GetFilterBool ( "unusual" ) ) ;
2023-07-19 11:47:32 +02:00
2023-05-19 03:42:09 +02:00
// add to array
modified = new SearchString ( modified . Toggle ( "status" , "processing" ) ) ;
var statusArray = modified . GetFilterArray ( "status" ) ;
Assert . Equal ( 2 , statusArray . Length ) ;
Assert . Contains ( "processing" , statusArray ) ;
Assert . Contains ( "settled" , statusArray ) ;
2023-07-19 11:47:32 +02:00
2023-05-19 03:42:09 +02:00
// toggle off array with same value
modified = new SearchString ( modified . Toggle ( "status" , "settled" ) ) ;
statusArray = modified . GetFilterArray ( "status" ) ;
Assert . Single ( statusArray , "processing" ) ;
2023-07-19 11:47:32 +02:00
2023-05-19 03:42:09 +02:00
// toggle off array with null value
modified = new SearchString ( modified . Toggle ( "status" , null ) ) ;
Assert . Null ( modified . GetFilterArray ( "status" ) ) ;
2023-07-19 11:47:32 +02:00
2023-05-19 03:42:09 +02:00
// toggle off date with null value
modified = new SearchString ( modified . Toggle ( "startdate" , "-7d" ) ) ;
Assert . Single ( modified . GetFilterArray ( "startdate" ) , "-7d" ) ;
modified = new SearchString ( modified . Toggle ( "startdate" , null ) ) ;
Assert . Null ( modified . GetFilterArray ( "startdate" ) ) ;
2023-07-19 11:47:32 +02:00
2023-05-19 03:42:09 +02:00
// toggle off date with same value
modified = new SearchString ( modified . Toggle ( "enddate" , "-7d" ) ) ;
Assert . Single ( modified . GetFilterArray ( "enddate" ) , "-7d" ) ;
modified = new SearchString ( modified . Toggle ( "enddate" , "-7d" ) ) ;
Assert . Null ( modified . GetFilterArray ( "enddate" ) ) ;
2021-11-22 16:49:51 +01:00
}
[Fact]
public void CanParseFingerprint ( )
{
Assert . True ( SSH . SSHFingerprint . TryParse ( "4e343c6fc6cfbf9339c02d06a151e1dd" , out var unused ) ) ;
Assert . Equal ( "4e:34:3c:6f:c6:cf:bf:93:39:c0:2d:06:a1:51:e1:dd" , unused . ToString ( ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "4e:34:3c:6f:c6:cf:bf:93:39:c0:2d:06:a1:51:e1:dd" , out unused ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w" , out unused ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=" , out unused ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=" , out unused ) ) ;
Assert . Equal ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w" , unused . ToString ( ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=" , out var f1 ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w" , out var f2 ) ) ;
Assert . Equal ( f1 . ToString ( ) , f2 . ToString ( ) ) ;
}
[Fact]
public void HasCurrencyDataForNetworks ( )
{
var btcPayNetworkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
foreach ( var network in btcPayNetworkProvider . GetAll ( ) )
{
var cd = CurrencyNameTable . Instance . GetCurrencyData ( network . CryptoCode , false ) ;
Assert . NotNull ( cd ) ;
Assert . Equal ( network . Divisibility , cd . Divisibility ) ;
Assert . True ( cd . Crypto ) ;
}
}
[Fact]
public void SetOrderIdMetadataDoesntConvertInOctal ( )
{
var m = new InvoiceMetadata ( ) ;
m . OrderId = "000000161" ;
Assert . Equal ( "000000161" , m . OrderId ) ;
}
2023-07-11 08:32:01 +02:00
[Fact]
public void CanParseOldPosAppData ( )
{
var data = new JObject ( )
{
["price"] = 1.64 m
} . ToString ( ) ;
Assert . Equal ( 1.64 m , JsonConvert . DeserializeObject < PosAppCartItem > ( data ) . Price ) ;
data = new JObject ( )
{
["price"] = new JObject ( )
{
["value"] = 1.65 m
}
} . ToString ( ) ;
Assert . Equal ( 1.65 m , JsonConvert . DeserializeObject < PosAppCartItem > ( data ) . Price ) ;
2023-07-11 08:49:16 +02:00
data = new JObject ( )
{
["price"] = new JObject ( )
{
["value"] = "1.6305"
}
} . ToString ( ) ;
Assert . Equal ( 1.6305 m , JsonConvert . DeserializeObject < PosAppCartItem > ( data ) . Price ) ;
2023-07-11 08:32:01 +02:00
data = new JObject ( )
{
["price"] = new JObject ( )
{
["value"] = null
}
} . ToString ( ) ;
Assert . Equal ( 0.0 m , JsonConvert . DeserializeObject < PosAppCartItem > ( data ) . Price ) ;
var o = JObject . Parse ( JsonConvert . SerializeObject ( new PosAppCartItem ( ) { Price = 1.356 m } ) ) ;
Assert . Equal ( 1.356 m , o [ "price" ] . Value < decimal > ( ) ) ;
}
2021-11-22 16:49:51 +01:00
[Fact]
public void CanParseCurrencyValue ( )
{
Assert . True ( CurrencyValue . TryParse ( "1.50USD" , out var result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1.50 USD" , out result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1.50 usd" , out result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1 usd" , out result ) ) ;
Assert . Equal ( "1 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1usd" , out result ) ) ;
Assert . Equal ( "1 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1.501 usd" , out result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . False ( CurrencyValue . TryParse ( "1.501 WTFF" , out result ) ) ;
Assert . False ( CurrencyValue . TryParse ( "1,501 usd" , out result ) ) ;
Assert . False ( CurrencyValue . TryParse ( "1.501" , out result ) ) ;
}
2022-01-11 03:48:03 +01:00
[Fact]
public async Task MultiProcessingQueueTests ( )
{
MultiProcessingQueue q = new MultiProcessingQueue ( ) ;
var q10 = Enqueue ( q , "q1" ) ;
var q11 = Enqueue ( q , "q1" ) ;
var q20 = Enqueue ( q , "q2" ) ;
var q30 = Enqueue ( q , "q3" ) ;
q10 . AssertStarted ( ) ;
q11 . AssertStopped ( ) ;
q20 . AssertStarted ( ) ;
q30 . AssertStarted ( ) ;
Assert . Equal ( 3 , q . QueueCount ) ;
q10 . Done ( ) ;
q10 . AssertStopped ( ) ;
q11 . AssertStarted ( ) ;
q20 . AssertStarted ( ) ;
Assert . Equal ( 3 , q . QueueCount ) ;
q30 . Done ( ) ;
q30 . AssertStopped ( ) ;
TestUtils . Eventually ( ( ) = > Assert . Equal ( 2 , q . QueueCount ) , 1000 ) ;
await q . Abort ( default ) ;
q11 . AssertAborted ( ) ;
q20 . AssertAborted ( ) ;
Assert . Equal ( 0 , q . QueueCount ) ;
}
class MultiProcessingQueueTest
{
public bool Started ;
public bool Aborted ;
public TaskCompletionSource Tcs ;
public void Done ( ) { Tcs . TrySetResult ( ) ; }
public void AssertStarted ( )
{
TestUtils . Eventually ( ( ) = > Assert . True ( Started ) , 1000 ) ;
}
public void AssertStopped ( )
{
TestUtils . Eventually ( ( ) = > Assert . False ( Started ) , 1000 ) ;
}
public void AssertAborted ( )
{
TestUtils . Eventually ( ( ) = > Assert . True ( Aborted ) , 1000 ) ;
}
}
private static MultiProcessingQueueTest Enqueue ( MultiProcessingQueue q , string queueName )
{
MultiProcessingQueueTest t = new MultiProcessingQueueTest ( ) ;
t . Tcs = new TaskCompletionSource ( ) ;
2023-01-06 14:18:07 +01:00
q . Enqueue ( queueName , async ( cancellationToken ) = >
{
2022-01-11 03:48:03 +01:00
t . Started = true ;
try
{
await t . Tcs . Task . WaitAsync ( cancellationToken ) ;
}
catch { t . Aborted = true ; }
t . Started = false ;
} ) ;
return t ;
}
2021-11-22 16:49:51 +01:00
[Fact]
public async Task CanScheduleBackgroundTasks ( )
{
2021-11-22 09:16:08 +01:00
BackgroundJobClient client = new BackgroundJobClient ( BTCPayLogs ) ;
2021-11-22 16:49:51 +01:00
MockDelay mockDelay = new MockDelay ( ) ;
client . Delay = mockDelay ;
bool [ ] jobs = new bool [ 4 ] ;
TestLogs . LogInformation ( "Start Job[0] in 5 sec" ) ;
client . Schedule ( ( _ ) = >
{
TestLogs . LogInformation ( "Job[0]" ) ;
jobs [ 0 ] = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 5.0 ) ) ;
TestLogs . LogInformation ( "Start Job[1] in 2 sec" ) ;
client . Schedule ( ( _ ) = >
{
TestLogs . LogInformation ( "Job[1]" ) ;
jobs [ 1 ] = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 2.0 ) ) ;
TestLogs . LogInformation ( "Start Job[2] fails in 6 sec" ) ;
client . Schedule ( ( _ ) = >
{
jobs [ 2 ] = true ;
throw new Exception ( "Job[2]" ) ;
} , TimeSpan . FromSeconds ( 6.0 ) ) ;
TestLogs . LogInformation ( "Start Job[3] starts in in 7 sec" ) ;
client . Schedule ( ( _ ) = >
{
TestLogs . LogInformation ( "Job[3]" ) ;
jobs [ 3 ] = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 7.0 ) ) ;
Assert . True ( new [ ] { false , false , false , false } . SequenceEqual ( jobs ) ) ;
CancellationTokenSource cts = new CancellationTokenSource ( ) ;
var processing = client . ProcessJobs ( cts . Token ) ;
Assert . Equal ( 4 , client . GetExecutingCount ( ) ) ;
var waitJobsFinish = client . WaitAllRunning ( default ) ;
await mockDelay . Advance ( TimeSpan . FromSeconds ( 2.0 ) ) ;
Assert . True ( new [ ] { false , true , false , false } . SequenceEqual ( jobs ) ) ;
await mockDelay . Advance ( TimeSpan . FromSeconds ( 3.0 ) ) ;
Assert . True ( new [ ] { true , true , false , false } . SequenceEqual ( jobs ) ) ;
await mockDelay . Advance ( TimeSpan . FromSeconds ( 1.0 ) ) ;
Assert . True ( new [ ] { true , true , true , false } . SequenceEqual ( jobs ) ) ;
Assert . Equal ( 1 , client . GetExecutingCount ( ) ) ;
Assert . False ( waitJobsFinish . Wait ( 1 ) ) ;
Assert . False ( waitJobsFinish . IsCompletedSuccessfully ) ;
await mockDelay . Advance ( TimeSpan . FromSeconds ( 1.0 ) ) ;
Assert . True ( new [ ] { true , true , true , true } . SequenceEqual ( jobs ) ) ;
await waitJobsFinish ;
Assert . True ( waitJobsFinish . IsCompletedSuccessfully ) ;
Assert . True ( ! waitJobsFinish . IsFaulted ) ;
Assert . Equal ( 0 , client . GetExecutingCount ( ) ) ;
bool jobExecuted = false ;
TestLogs . LogInformation ( "This job will be cancelled" ) ;
client . Schedule ( ( _ ) = >
{
jobExecuted = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 1.0 ) ) ;
await mockDelay . Advance ( TimeSpan . FromSeconds ( 0.5 ) ) ;
Assert . False ( jobExecuted ) ;
TestUtils . Eventually ( ( ) = > Assert . Equal ( 1 , client . GetExecutingCount ( ) ) ) ;
waitJobsFinish = client . WaitAllRunning ( default ) ;
Assert . False ( waitJobsFinish . Wait ( 100 ) ) ;
cts . Cancel ( ) ;
await waitJobsFinish ;
Assert . True ( waitJobsFinish . Wait ( 1 ) ) ;
Assert . True ( waitJobsFinish . IsCompletedSuccessfully ) ;
Assert . False ( waitJobsFinish . IsFaulted ) ;
Assert . False ( jobExecuted ) ;
await mockDelay . Advance ( TimeSpan . FromSeconds ( 1.0 ) ) ;
Assert . False ( jobExecuted ) ;
Assert . Equal ( 0 , client . GetExecutingCount ( ) ) ;
await Assert . ThrowsAnyAsync < OperationCanceledException > ( async ( ) = > await processing ) ;
Assert . True ( processing . IsCanceled ) ;
Assert . True ( client . WaitAllRunning ( default ) . Wait ( 100 ) ) ;
}
[Fact]
public void PosDataParser_ParsesCorrectly ( )
{
var testCases =
new List < ( string input , Dictionary < string , object > expectedOutput ) > ( )
{
{ ( null , new Dictionary < string , object > ( ) ) } ,
{ ( "" , new Dictionary < string , object > ( ) ) } ,
{ ( "{}" , new Dictionary < string , object > ( ) ) } ,
{ ( "{ \"key\": \"value\"}" , new Dictionary < string , object > ( ) { { "key" , "value" } } ) } ,
// Duplicate keys should not crash things
{ ( "{ \"key\": true, \"key\": true}" , new Dictionary < string , object > ( ) { { "key" , "True" } } ) }
} ;
testCases . ForEach ( tuple = >
{
2023-02-25 15:34:49 +01:00
Assert . Equal ( tuple . expectedOutput , UIInvoiceController . PosDataParser . ParsePosData ( string . IsNullOrEmpty ( tuple . input ) ? null : JToken . Parse ( tuple . input ) ) ) ;
2021-11-22 16:49:51 +01:00
} ) ;
}
[Fact]
public void SecondDuplicatedRuleIsIgnored ( )
{
StringBuilder builder = new StringBuilder ( ) ;
builder . AppendLine ( "DOGE_X = 1.1" ) ;
builder . AppendLine ( "DOGE_X = 1.2" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out var rules ) ) ;
var rule = rules . GetRuleFor ( new CurrencyPair ( "DOGE" , "BTC" ) ) ;
rule . Reevaluate ( ) ;
Assert . True ( ! rule . HasError ) ;
Assert . Equal ( 1.1 m , rule . BidAsk . Ask ) ;
}
[Fact]
public void CanSerializeExchangeRatesCache ( )
{
HostedServices . RatesHostedService . ExchangeRatesCache cache = new HostedServices . RatesHostedService . ExchangeRatesCache ( ) ;
cache . Created = DateTimeOffset . UtcNow ;
cache . States = new List < Services . Rates . BackgroundFetcherState > ( ) ;
cache . States . Add ( new Services . Rates . BackgroundFetcherState ( )
{
ExchangeName = "Kraken" ,
LastRequested = DateTimeOffset . UtcNow ,
LastUpdated = DateTimeOffset . UtcNow ,
Rates = new List < Services . Rates . BackgroundFetcherRate > ( )
{
new Services . Rates . BackgroundFetcherRate ( )
{
Pair = new CurrencyPair ( "USD" , "BTC" ) ,
BidAsk = new BidAsk ( 1.0 m , 2.0 m )
}
}
} ) ;
var str = JsonConvert . SerializeObject ( cache , Formatting . Indented ) ;
var cache2 = JsonConvert . DeserializeObject < HostedServices . RatesHostedService . ExchangeRatesCache > ( str ) ;
Assert . Equal ( cache . Created . ToUnixTimeSeconds ( ) , cache2 . Created . ToUnixTimeSeconds ( ) ) ;
Assert . Equal ( cache . States [ 0 ] . Rates [ 0 ] . BidAsk , cache2 . States [ 0 ] . Rates [ 0 ] . BidAsk ) ;
Assert . Equal ( cache . States [ 0 ] . Rates [ 0 ] . Pair , cache2 . States [ 0 ] . Rates [ 0 ] . Pair ) ;
}
2023-05-27 05:51:48 +02:00
[Fact]
public void CanParseStoreRoleId ( )
{
var id = StoreRoleId . Parse ( "test::lol" ) ;
Assert . Equal ( "test" , id . StoreId ) ;
Assert . Equal ( "lol" , id . Role ) ;
Assert . Equal ( "test::lol" , id . ToString ( ) ) ;
Assert . Equal ( "test::lol" , id . Id ) ;
Assert . False ( id . IsServerRole ) ;
id = StoreRoleId . Parse ( "lol" ) ;
Assert . Null ( id . StoreId ) ;
Assert . Equal ( "lol" , id . Role ) ;
Assert . Equal ( "lol" , id . ToString ( ) ) ;
Assert . Equal ( "lol" , id . Id ) ;
Assert . True ( id . IsServerRole ) ;
}
2022-01-24 12:17:09 +01:00
[Fact]
public void KitchenSinkTest ( )
{
var b = JsonConvert . DeserializeObject < PullPaymentBlob > ( "{}" ) ;
Assert . Equal ( TimeSpan . FromDays ( 30.0 ) , b . BOLT11Expiration ) ;
var aaa = JsonConvert . SerializeObject ( b ) ;
}
2021-11-22 16:49:51 +01:00
[Fact]
public void CanParseRateRules ( )
{
2022-09-29 08:45:27 +02:00
var pair = CurrencyPair . Parse ( "USD_EMAT_IC" ) ;
Assert . Equal ( "USD" , pair . Left ) ;
Assert . Equal ( "EMAT_IC" , pair . Right ) ;
2021-11-22 16:49:51 +01:00
// Check happy path
StringBuilder builder = new StringBuilder ( ) ;
builder . AppendLine ( "// Some cool comments" ) ;
builder . AppendLine ( "DOGE_X = DOGE_BTC * BTC_X * 1.1" ) ;
builder . AppendLine ( "DOGE_BTC = Bittrex(DOGE_BTC)" ) ;
builder . AppendLine ( "// Some other cool comments" ) ;
builder . AppendLine ( "BTC_usd = kraken(BTC_USD)" ) ;
builder . AppendLine ( "BTC_X = Coinbase(BTC_X);" ) ;
builder . AppendLine ( "X_X = CoinAverage(X_X) * 1.02" ) ;
Assert . False ( RateRules . TryParse ( "DPW*&W&#hdi&#&3JJD" , out var rules ) ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
Assert . Equal (
"// Some cool comments\n" +
"DOGE_X = DOGE_BTC * BTC_X * 1.1;\n" +
"DOGE_BTC = bittrex(DOGE_BTC);\n" +
"// Some other cool comments\n" +
"BTC_USD = kraken(BTC_USD);\n" +
"BTC_X = coinbase(BTC_X);\n" +
"X_X = coinaverage(X_X) * 1.02;" ,
rules . ToString ( ) ) ;
var tests = new [ ]
{
( Pair : "DOGE_USD" , Expected : "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1" ) ,
( Pair : "BTC_USD" , Expected : "kraken(BTC_USD)" ) ,
( Pair : "BTC_CAD" , Expected : "coinbase(BTC_CAD)" ) ,
( Pair : "DOGE_CAD" , Expected : "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1" ) ,
( Pair : "LTC_CAD" , Expected : "coinaverage(LTC_CAD) * 1.02" ) ,
( Pair : "SATS_CAD" , Expected : "0.00000001 * coinbase(BTC_CAD)" ) ,
( Pair : "Sats_USD" , Expected : "0.00000001 * kraken(BTC_USD)" )
} ;
foreach ( var test in tests )
{
Assert . Equal ( test . Expected , rules . GetRuleFor ( CurrencyPair . Parse ( test . Pair ) ) . ToString ( ) ) ;
}
rules . Spread = 0.2 m ;
Assert . Equal ( "(bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1) * (0.8, 1.2)" , rules . GetRuleFor ( CurrencyPair . Parse ( "DOGE_USD" ) ) . ToString ( ) ) ;
////////////////
// Check errors conditions
builder = new StringBuilder ( ) ;
builder . AppendLine ( "DOGE_X = LTC_CAD * BTC_X * 1.1" ) ;
builder . AppendLine ( "DOGE_BTC = Bittrex(DOGE_BTC)" ) ;
builder . AppendLine ( "BTC_usd = kraken(BTC_USD)" ) ;
builder . AppendLine ( "LTC_CHF = LTC_CHF * 1.01" ) ;
builder . AppendLine ( "BTC_X = Coinbase(BTC_X)" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
tests = new [ ]
{
( Pair : "LTC_CAD" , Expected : "ERR_NO_RULE_MATCH(LTC_CAD)" ) ,
( Pair : "DOGE_USD" , Expected : "ERR_NO_RULE_MATCH(LTC_CAD) * kraken(BTC_USD) * 1.1" ) ,
( Pair : "LTC_CHF" , Expected : "ERR_TOO_MUCH_NESTED_CALLS(LTC_CHF) * 1.01" ) ,
} ;
foreach ( var test in tests )
{
Assert . Equal ( test . Expected , rules . GetRuleFor ( CurrencyPair . Parse ( test . Pair ) ) . ToString ( ) ) ;
}
//////////////////
// Check if we can resolve exchange rates
builder = new StringBuilder ( ) ;
builder . AppendLine ( "DOGE_X = DOGE_BTC * BTC_X * 1.1" ) ;
builder . AppendLine ( "DOGE_BTC = Bittrex(DOGE_BTC)" ) ;
builder . AppendLine ( "BTC_usd = kraken(BTC_USD)" ) ;
builder . AppendLine ( "BTC_X = Coinbase(BTC_X)" ) ;
builder . AppendLine ( "X_X = CoinAverage(X_X) * 1.02" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
var tests2 = new [ ]
{
( Pair : "DOGE_USD" , Expected : "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1" , ExpectedExchangeRates : "bittrex(DOGE_BTC),kraken(BTC_USD)" ) ,
( Pair : "BTC_USD" , Expected : "kraken(BTC_USD)" , ExpectedExchangeRates : "kraken(BTC_USD)" ) ,
( Pair : "BTC_CAD" , Expected : "coinbase(BTC_CAD)" , ExpectedExchangeRates : "coinbase(BTC_CAD)" ) ,
( Pair : "DOGE_CAD" , Expected : "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1" , ExpectedExchangeRates : "bittrex(DOGE_BTC),coinbase(BTC_CAD)" ) ,
( Pair : "LTC_CAD" , Expected : "coinaverage(LTC_CAD) * 1.02" , ExpectedExchangeRates : "coinaverage(LTC_CAD)" ) ,
( Pair : "SATS_USD" , Expected : "0.00000001 * kraken(BTC_USD)" , ExpectedExchangeRates : "kraken(BTC_USD)" ) ,
( Pair : "SATS_EUR" , Expected : "0.00000001 * coinbase(BTC_EUR)" , ExpectedExchangeRates : "coinbase(BTC_EUR)" )
} ;
foreach ( var test in tests2 )
{
var rule = rules . GetRuleFor ( CurrencyPair . Parse ( test . Pair ) ) ;
Assert . Equal ( test . Expected , rule . ToString ( ) ) ;
Assert . Equal ( test . ExpectedExchangeRates , string . Join ( ',' , rule . ExchangeRates . OfType < object > ( ) . ToArray ( ) ) ) ;
}
var rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "DOGE_CAD" ) ) ;
rule2 . ExchangeRates . SetRate ( "bittrex" , CurrencyPair . Parse ( "DOGE_BTC" ) , new BidAsk ( 5000 m ) ) ;
rule2 . Reevaluate ( ) ;
Assert . True ( rule2 . HasError ) ;
Assert . Equal ( "5000 * ERR_RATE_UNAVAILABLE(coinbase, BTC_CAD) * 1.1" , rule2 . ToString ( true ) ) ;
Assert . Equal ( "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1" , rule2 . ToString ( false ) ) ;
rule2 . ExchangeRates . SetRate ( "coinbase" , CurrencyPair . Parse ( "BTC_CAD" ) , new BidAsk ( 2000.4 m ) ) ;
rule2 . Reevaluate ( ) ;
Assert . False ( rule2 . HasError ) ;
Assert . Equal ( "5000 * 2000.4 * 1.1" , rule2 . ToString ( true ) ) ;
2022-10-07 08:05:11 +02:00
Assert . Equal ( 5000 m * 2000.4 m * 1.1 m , rule2 . BidAsk . Bid ) ;
2021-11-22 16:49:51 +01:00
////////
// Make sure parenthesis are correctly calculated
builder = new StringBuilder ( ) ;
builder . AppendLine ( "DOGE_X = DOGE_BTC * BTC_X" ) ;
builder . AppendLine ( "BTC_USD = -3 + coinbase(BTC_CAD) + 50 - 5" ) ;
builder . AppendLine ( "DOGE_BTC = 2000" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
rules . Spread = 0.1 m ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "DOGE_USD" ) ) ;
Assert . Equal ( "(2000 * (-3 + coinbase(BTC_CAD) + 50 - 5)) * (0.9, 1.1)" , rule2 . ToString ( ) ) ;
rule2 . ExchangeRates . SetRate ( "coinbase" , CurrencyPair . Parse ( "BTC_CAD" ) , new BidAsk ( 1000 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "(2000 * (-3 + 1000 + 50 - 5)) * (0.9, 1.1)" , rule2 . ToString ( true ) ) ;
Assert . Equal ( ( 2000 m * ( - 3 m + 1000 m + 50 m - 5 m ) ) * 0.9 m , rule2 . BidAsk . Bid ) ;
// Test inverse
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "USD_DOGE" ) ) ;
Assert . Equal ( "(1 / (2000 * (-3 + coinbase(BTC_CAD) + 50 - 5))) * (0.9, 1.1)" , rule2 . ToString ( ) ) ;
rule2 . ExchangeRates . SetRate ( "coinbase" , CurrencyPair . Parse ( "BTC_CAD" ) , new BidAsk ( 1000 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "(1 / (2000 * (-3 + 1000 + 50 - 5))) * (0.9, 1.1)" , rule2 . ToString ( true ) ) ;
Assert . Equal ( ( 1.0 m / ( 2000 m * ( - 3 m + 1000 m + 50 m - 5 m ) ) ) * 0.9 m , rule2 . BidAsk . Bid ) ;
////////
// Make sure kraken is not converted to CurrencyPair
builder = new StringBuilder ( ) ;
builder . AppendLine ( "BTC_USD = kraken(BTC_USD)" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "BTC_USD" ) ) ;
rule2 . ExchangeRates . SetRate ( "kraken" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 1000 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
// Make sure can handle pairs
builder = new StringBuilder ( ) ;
builder . AppendLine ( "BTC_USD = kraken(BTC_USD)" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "BTC_USD" ) ) ;
rule2 . ExchangeRates . SetRate ( "kraken" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 6000 m , 6100 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "(6000, 6100)" , rule2 . ToString ( true ) ) ;
Assert . Equal ( 6000 m , rule2 . BidAsk . Bid ) ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "USD_BTC" ) ) ;
rule2 . ExchangeRates . SetRate ( "kraken" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 6000 m , 6100 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "1 / (6000, 6100)" , rule2 . ToString ( true ) ) ;
Assert . Equal ( 1 m / 6100 m , rule2 . BidAsk . Bid ) ;
// Make sure the inverse has more priority than X_X or CDNT_X
builder = new StringBuilder ( ) ;
builder . AppendLine ( "EUR_CDNT = 10" ) ;
builder . AppendLine ( "CDNT_BTC = CDNT_EUR * EUR_BTC;" ) ;
builder . AppendLine ( "CDNT_X = CDNT_BTC * BTC_X;" ) ;
builder . AppendLine ( "X_X = coinaverage(X_X);" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "CDNT_EUR" ) ) ;
rule2 . ExchangeRates . SetRate ( "coinaverage" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 6000 m , 6100 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "1 / 10" , rule2 . ToString ( false ) ) ;
// Make sure an inverse can be solved on an exchange
builder = new StringBuilder ( ) ;
builder . AppendLine ( "X_X = coinaverage(X_X);" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "USD_BTC" ) ) ;
rule2 . ExchangeRates . SetRate ( "coinaverage" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 6000 m , 6100 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( $"({(1m / 6100m).ToString(CultureInfo.InvariantCulture)}, {(1m / 6000m).ToString(CultureInfo.InvariantCulture)})" , rule2 . ToString ( true ) ) ;
// Make sure defining value in sats works
builder = new StringBuilder ( ) ;
builder . AppendLine ( "BTC_USD = kraken(BTC_USD)" ) ;
builder . AppendLine ( "BTC_X = coinbase(BTC_X)" ) ;
Assert . True ( RateRules . TryParse ( builder . ToString ( ) , out rules ) ) ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "SATS_USD" ) ) ;
rule2 . ExchangeRates . SetRate ( "kraken" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 6000 m , 6100 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "0.00000001 * (6000, 6100)" , rule2 . ToString ( true ) ) ;
Assert . Equal ( 0.00006 m , rule2 . BidAsk . Bid ) ;
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "USD_SATS" ) ) ;
rule2 . ExchangeRates . SetRate ( "kraken" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 6000 m , 6100 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "1 / (0.00000001 * (6000, 6100))" , rule2 . ToString ( true ) ) ;
Assert . Equal ( 1 m / 0.000061 m , rule2 . BidAsk . Bid ) ;
// testing rounding
2023-03-09 21:36:11 +01:00
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "SATS_EUR" ) ) ;
2021-11-22 16:49:51 +01:00
rule2 . ExchangeRates . SetRate ( "coinbase" , CurrencyPair . Parse ( "BTC_EUR" ) , new BidAsk ( 1.23 m , 2.34 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "0.00000001 * (1.23, 2.34)" , rule2 . ToString ( true ) ) ;
Assert . Equal ( 0.0000000234 m , rule2 . BidAsk . Ask ) ;
Assert . Equal ( 0.0000000123 m , rule2 . BidAsk . Bid ) ;
2023-03-09 21:36:11 +01:00
rule2 = rules . GetRuleFor ( CurrencyPair . Parse ( "EUR_SATS" ) ) ;
2021-11-22 16:49:51 +01:00
rule2 . ExchangeRates . SetRate ( "coinbase" , CurrencyPair . Parse ( "BTC_EUR" ) , new BidAsk ( 1.23 m , 2.34 m ) ) ;
Assert . True ( rule2 . Reevaluate ( ) ) ;
Assert . Equal ( "1 / (0.00000001 * (1.23, 2.34))" , rule2 . ToString ( true ) ) ;
Assert . Equal ( 1 m / 0.0000000123 m , rule2 . BidAsk . Ask ) ;
Assert . Equal ( 1 m / 0.0000000234 m , rule2 . BidAsk . Bid ) ;
}
[Theory()]
[InlineData("DE-de")]
[InlineData("")]
public void NumericJsonConverterTests ( string culture )
{
System . Globalization . CultureInfo . CurrentCulture = System . Globalization . CultureInfo . GetCultureInfo ( culture ) ;
JsonReader Get ( string val )
{
return new JsonTextReader ( new StringReader ( val ) ) ;
}
var jsonConverter = new NumericStringJsonConverter ( ) ;
Assert . True ( jsonConverter . CanConvert ( typeof ( decimal ) ) ) ;
Assert . True ( jsonConverter . CanConvert ( typeof ( decimal? ) ) ) ;
Assert . True ( jsonConverter . CanConvert ( typeof ( double ) ) ) ;
Assert . True ( jsonConverter . CanConvert ( typeof ( double? ) ) ) ;
Assert . False ( jsonConverter . CanConvert ( typeof ( float ) ) ) ;
Assert . False ( jsonConverter . CanConvert ( typeof ( string ) ) ) ;
var numberJson = "1" ;
var numberDecimalJson = "1.2" ;
var stringJson = "\"1.2\"" ;
Assert . Equal ( 1 m , jsonConverter . ReadJson ( Get ( numberJson ) , typeof ( decimal ) , null , null ) ) ;
Assert . Equal ( 1.2 m , jsonConverter . ReadJson ( Get ( numberDecimalJson ) , typeof ( decimal ) , null , null ) ) ;
Assert . Null ( jsonConverter . ReadJson ( Get ( "null" ) , typeof ( decimal? ) , null , null ) ) ;
Assert . Equal ( ( double ) 1.0 , jsonConverter . ReadJson ( Get ( numberJson ) , typeof ( double ) , null , null ) ) ;
Assert . Equal ( ( double ) 1.2 , jsonConverter . ReadJson ( Get ( numberDecimalJson ) , typeof ( double ) , null , null ) ) ;
Assert . Null ( jsonConverter . ReadJson ( Get ( "null" ) , typeof ( double? ) , null , null ) ) ;
Assert . Throws < JsonSerializationException > ( ( ) = >
{
jsonConverter . ReadJson ( Get ( "null" ) , typeof ( decimal ) , null , null ) ;
} ) ;
Assert . Throws < JsonSerializationException > ( ( ) = >
{
jsonConverter . ReadJson ( Get ( "null" ) , typeof ( double ) , null , null ) ;
} ) ;
Assert . Equal ( 1.2 m , jsonConverter . ReadJson ( Get ( stringJson ) , typeof ( decimal ) , null , null ) ) ;
Assert . Equal ( 1.2 m , jsonConverter . ReadJson ( Get ( stringJson ) , typeof ( decimal? ) , null , null ) ) ;
Assert . Equal ( 1.2 , jsonConverter . ReadJson ( Get ( stringJson ) , typeof ( double ) , null , null ) ) ;
Assert . Equal ( 1.2 , jsonConverter . ReadJson ( Get ( stringJson ) , typeof ( double? ) , null , null ) ) ;
}
[Fact]
[Trait("Altcoins", "Altcoins")]
public void LoadSubChainsAlways ( )
{
var config = new ConfigurationRoot ( new List < IConfigurationProvider > ( )
{
new MemoryConfigurationProvider ( new MemoryConfigurationSource ( )
{
InitialData = new [ ] {
2021-12-11 06:31:41 +01:00
new KeyValuePair < string , string > ( "chains" , "usdt" ) }
2021-11-22 16:49:51 +01:00
} )
} ) ;
2021-11-22 09:16:08 +01:00
var networkProvider = config . ConfigureNetworkProvider ( BTCPayLogs ) ;
2021-11-22 16:49:51 +01:00
Assert . NotNull ( networkProvider . GetNetwork ( "LBTC" ) ) ;
Assert . NotNull ( networkProvider . GetNetwork ( "USDT" ) ) ;
}
[Fact]
[Trait("Altcoins", "Altcoins")]
public void CanParseDerivationScheme ( )
{
var testnetNetworkProvider = new BTCPayNetworkProvider ( ChainName . Testnet ) ;
var regtestNetworkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
var mainnetNetworkProvider = new BTCPayNetworkProvider ( ChainName . Mainnet ) ;
var testnetParser = new DerivationSchemeParser ( testnetNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
var mainnetParser = new DerivationSchemeParser ( mainnetNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
NBXplorer . DerivationStrategy . DerivationStrategyBase result ;
// Passing electrum stuff
// Passing a native segwit from mainnet to a testnet parser, means the testnet parser will try to convert it into segwit
result = testnetParser . Parse (
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t" ) ;
Assert . Equal (
"tpubD93CJNkmGjLXnsBqE2zGDqfEh1Q8iJ8wueordy3SeWt1RngbbuxXCsqASuVWFywmfoCwUE1rSfNJbaH4cBNcbp8WcyZgPiiRSTazLGL8U9w" ,
result . ToString ( ) ) ;
result = mainnetParser . Parse (
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t" ) ;
Assert . Equal (
"xpub68fZn8w5ZTP5X4zymr1B1vKsMtJUiudtN2DZHQzJJc87gW1tXh7S4SALCsQijUzXstg2reVyuZYFuPnTDKXNiNgDZNpNiC4BrVzaaGEaRHj" ,
result . ToString ( ) ) ;
// P2SH
result = testnetParser . Parse (
"upub57Wa4MvRPNyAipy1MCpERxcFpHR2ZatyikppkyeWkoRL6QJvLVMo39jYdcaJVxyvBURyRVmErBEA5oGicKBgk1j72GAXSPFH5tUDoGZ8nEu" ) ;
Assert . Equal (
"tpubD6NzVbkrYhZ4YWjDJUACG9E8fJx2NqNY1iynTiPKEjJrzzRKAgha3nNnwGXr2BtvCJKJHW4nmG7rRqc2AGGy2AECgt16seMyV2FZivUmaJg-[p2sh]" ,
result . ToString ( ) ) ;
result = mainnetParser . Parse (
"ypub6QqdH2c5z79681jUgdxjGJzGW9zpL4ryPCuhtZE4GpvrJoZqM823XQN6iSQeVbbbp2uCRQ9UgpeMcwiyV6qjvxTWVcxDn2XEAnioMUwsrQ5" ) ;
Assert . Equal (
"xpub661MyMwAqRbcGiYMrHB74DtmLBrNPSsUU6PV7ALAtpYyFhkc6TrUuLhxhET4VgwgQPnPfvYvEAHojf7QmQRj8imudHFoC7hju4f9xxri8wR-[p2sh]" ,
result . ToString ( ) ) ;
// if prefix not recognize, assume it is segwit
result = testnetParser . Parse (
"xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X" ) ;
Assert . Equal (
"tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu" ,
result . ToString ( ) ) ;
////////////////
var tpub =
"tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o" ;
result = testnetParser . Parse ( tpub ) ;
Assert . Equal ( tpub , result . ToString ( ) ) ;
var regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
var parsed =
regtestParser . Parse (
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]" ) ;
Assert . Equal (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]" ,
parsed . ToString ( ) ) ;
// Let's make sure we can't generate segwit with dogecoin
regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "DOGE" ) ) ;
parsed = regtestParser . Parse (
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]" ) ;
Assert . Equal (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]" ,
parsed . ToString ( ) ) ;
regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "DOGE" ) ) ;
parsed = regtestParser . Parse (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]" ) ;
Assert . Equal (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]" ,
parsed . ToString ( ) ) ;
//let's test output descriptor parsing support
//we don't support every descriptor, only the ones which represent an HD wallet with stndard derivation paths
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))" ) ) ;
//let's see what we actually support now
//standard legacy hd wallet
var parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( KeyPath . Parse ( "44'/0'/0'" ) , Assert . Single ( parsedDescriptor . Item2 ) . KeyPath ) ;
Assert . Equal ( HDFingerprint . Parse ( "d34db33f" ) , Assert . Single ( parsedDescriptor . Item2 ) . MasterFingerprint ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//masterfingerprint and key path are optional
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"pkh([d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//a master fingerprint must always be present if youre providing rooted path
Assert . Throws < ParsingException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pkh([44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)" ) ) ;
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//but a different deriv path from standard (0/*) is not supported
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)" ) ) ;
//p2sh-segwit hd wallet
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(wpkh([d34db33f/49'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( KeyPath . Parse ( "49'/0'/0'" ) , Assert . Single ( parsedDescriptor . Item2 ) . KeyPath ) ;
Assert . Equal ( HDFingerprint . Parse ( "d34db33f" ) , Assert . Single ( parsedDescriptor . Item2 ) . MasterFingerprint ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[p2sh]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//segwit hd wallet
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"wpkh([d34db33f/84'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( KeyPath . Parse ( "84'/0'/0'" ) , Assert . Single ( parsedDescriptor . Item2 ) . KeyPath ) ;
Assert . Equal ( HDFingerprint . Parse ( "d34db33f" ) , Assert . Single ( parsedDescriptor . Item2 ) . MasterFingerprint ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" , parsedDescriptor . Item1 . ToString ( ) ) ;
//multisig tests
//legacy
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(multi(1,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( 2 , parsedDescriptor . Item2 . Length ) ;
var strat = Assert . IsType < MultisigDerivationStrategy > ( Assert . IsType < P2SHDerivationStrategy > ( parsedDescriptor . Item1 ) . Inner ) ;
Assert . True ( strat . IsLegacy ) ;
Assert . Equal ( 1 , strat . RequiredSignatures ) ;
Assert . Equal ( 2 , strat . Keys . Count ( ) ) ;
Assert . False ( strat . LexicographicOrder ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]-[keeporder]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//segwit
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( 2 , parsedDescriptor . Item2 . Length ) ;
strat = Assert . IsType < MultisigDerivationStrategy > ( Assert . IsType < P2WSHDerivationStrategy > ( parsedDescriptor . Item1 ) . Inner ) ;
Assert . False ( strat . IsLegacy ) ;
Assert . Equal ( 1 , strat . RequiredSignatures ) ;
Assert . Equal ( 2 , strat . Keys . Count ( ) ) ;
Assert . False ( strat . LexicographicOrder ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//segwit-p2sh
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)))" ) ;
Assert . Equal ( 2 , parsedDescriptor . Item2 . Length ) ;
strat = Assert . IsType < MultisigDerivationStrategy > ( Assert . IsType < P2WSHDerivationStrategy > ( Assert . IsType < P2SHDerivationStrategy > ( parsedDescriptor . Item1 ) . Inner ) . Inner ) ;
Assert . False ( strat . IsLegacy ) ;
Assert . Equal ( 1 , strat . RequiredSignatures ) ;
Assert . Equal ( 2 , strat . Keys . Count ( ) ) ;
Assert . False ( strat . LexicographicOrder ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]-[p2sh]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//sorted
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(sortedmulti(1,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
}
[Fact]
[Trait("Altcoins", "Altcoins")]
public void CanCalculateCryptoDue2 ( )
{
#pragma warning disable CS0618
var dummy = new Key ( ) . PubKey . GetAddress ( ScriptPubKeyType . Legacy , Network . RegTest ) . ToString ( ) ;
var networkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
var networkBTC = networkProvider . GetNetwork ( "BTC" ) ;
var networkLTC = networkProvider . GetNetwork ( "LTC" ) ;
InvoiceEntity invoiceEntity = new InvoiceEntity ( ) ;
invoiceEntity . Networks = networkProvider ;
invoiceEntity . Payments = new System . Collections . Generic . List < PaymentEntity > ( ) ;
invoiceEntity . Price = 100 ;
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary ( ) ;
2023-07-19 11:47:32 +02:00
paymentMethods . Add ( new PaymentMethod ( ) { Network = networkBTC , Currency = "BTC" , Rate = 10513.44 m , }
2021-11-22 16:49:51 +01:00
. SetPaymentMethodDetails (
new BTCPayServer . Payments . Bitcoin . BitcoinLikeOnChainPaymentMethod ( )
{
NextNetworkFee = Money . Coins ( 0.00000100 m ) ,
DepositAddress = dummy
} ) ) ;
2023-07-19 11:47:32 +02:00
paymentMethods . Add ( new PaymentMethod ( ) { Network = networkLTC , Currency = "LTC" , Rate = 216.79 m }
2021-11-22 16:49:51 +01:00
. SetPaymentMethodDetails (
new BTCPayServer . Payments . Bitcoin . BitcoinLikeOnChainPaymentMethod ( )
{
NextNetworkFee = Money . Coins ( 0.00010000 m ) ,
DepositAddress = dummy
} ) ) ;
invoiceEntity . SetPaymentMethods ( paymentMethods ) ;
var btc = invoiceEntity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
var accounting = btc . Calculate ( ) ;
invoiceEntity . Payments . Add (
new PaymentEntity ( )
{
Accounted = true ,
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
NetworkFee = 0.00000100 m ,
Network = networkProvider . GetNetwork ( "BTC" ) ,
}
. SetCryptoPaymentData ( new BitcoinLikePaymentData ( )
{
Network = networkProvider . GetNetwork ( "BTC" ) ,
Output = new TxOut ( ) { Value = Money . Coins ( 0.00151263 m ) }
} ) ) ;
2023-07-19 11:47:32 +02:00
invoiceEntity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = btc . Calculate ( ) ;
invoiceEntity . Payments . Add (
new PaymentEntity ( )
{
Accounted = true ,
2023-07-19 11:47:32 +02:00
Currency = "BTC" ,
2021-11-22 16:49:51 +01:00
NetworkFee = 0.00000100 m ,
Network = networkProvider . GetNetwork ( "BTC" )
}
. SetCryptoPaymentData ( new BitcoinLikePaymentData ( )
{
Network = networkProvider . GetNetwork ( "BTC" ) ,
2023-07-19 11:47:32 +02:00
Output = new TxOut ( ) { Value = Money . Coins ( accounting . Due ) }
2021-11-22 16:49:51 +01:00
} ) ) ;
2023-07-19 11:47:32 +02:00
invoiceEntity . UpdateTotals ( ) ;
2021-11-22 16:49:51 +01:00
accounting = btc . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.0 m , accounting . Due ) ;
Assert . Equal ( 0.0 m , accounting . DueUncapped ) ;
2021-11-22 16:49:51 +01:00
var ltc = invoiceEntity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
accounting = ltc . Calculate ( ) ;
2023-07-19 11:47:32 +02:00
Assert . Equal ( 0.0 m , accounting . Due ) ;
// LTC might should be over paid due to BTC paying above what it should (round 1 satoshi up), but we handle this case
// and set DueUncapped to zero.
Assert . Equal ( 0.0 m , accounting . DueUncapped ) ;
2021-11-22 16:49:51 +01:00
}
2022-03-29 20:12:02 +02:00
[Fact]
public void AllPoliciesShowInUI ( )
{
foreach ( var policy in Policies . AllPolicies )
{
2023-01-06 14:18:07 +01:00
Assert . True ( UIManageController . AddApiKeyViewModel . PermissionValueItem . PermissionDescriptions . ContainsKey ( policy ) ) ;
if ( Policies . IsStorePolicy ( policy ) )
{
Assert . True ( UIManageController . AddApiKeyViewModel . PermissionValueItem . PermissionDescriptions . ContainsKey ( $"{policy}:" ) ) ;
}
2022-03-29 20:12:02 +02:00
}
}
2023-02-25 15:34:49 +01:00
[Fact]
public void CanParseMetadata ( )
{
var metadata = InvoiceMetadata . FromJObject ( JObject . Parse ( "{\"posData\": {\"test\":\"a\"}}" ) ) ;
Assert . Equal ( JObject . Parse ( "{\"test\":\"a\"}" ) . ToString ( ) , metadata . PosDataLegacy ) ;
Assert . Equal ( JObject . Parse ( "{\"test\":\"a\"}" ) . ToString ( ) , metadata . PosData . ToString ( ) ) ;
// Legacy, as string
metadata = InvoiceMetadata . FromJObject ( JObject . Parse ( "{\"posData\": \"{\\\"test\\\":\\\"a\\\"}\"}" ) ) ;
Assert . Equal ( "{\"test\":\"a\"}" , metadata . PosDataLegacy ) ;
Assert . Equal ( JObject . Parse ( "{\"test\":\"a\"}" ) . ToString ( ) , metadata . PosData . ToString ( ) ) ;
metadata = InvoiceMetadata . FromJObject ( JObject . Parse ( "{\"posData\": \"nobject\"}" ) ) ;
Assert . Equal ( "nobject" , metadata . PosDataLegacy ) ;
Assert . Null ( metadata . PosData ) ;
metadata = InvoiceMetadata . FromJObject ( JObject . Parse ( "{\"posData\": null}" ) ) ;
Assert . Null ( metadata . PosDataLegacy ) ;
Assert . Null ( metadata . PosData ) ;
metadata = InvoiceMetadata . FromJObject ( JObject . Parse ( "{}" ) ) ;
Assert . Null ( metadata . PosDataLegacy ) ;
Assert . Null ( metadata . PosData ) ;
}
[Fact]
public void CanParseInvoiceEntityDerivationStrategies ( )
{
// We have 3 ways of serializing the derivation strategies:
// through "derivationStrategy", through "derivationStrategies" as a string, through "derivationStrategies" as JObject
// Let's check that InvoiceEntity is similar in all cases.
var legacy = new JObject ( )
{
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
} ;
var scheme = DerivationSchemeSettings . Parse ( "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf" , new BTCPayNetworkProvider ( ChainName . Regtest ) . BTC ) ;
scheme . Source = "ManualDerivationScheme" ;
scheme . AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf" ;
var legacy2 = new JObject ( )
{
["derivationStrategies"] = scheme . ToJson ( )
} ;
var newformat = new JObject ( )
{
["derivationStrategies"] = JObject . Parse ( scheme . ToJson ( ) )
} ;
//new BTCPayNetworkProvider(ChainName.Regtest)
#pragma warning disable CS0618 // Type or member is obsolete
var formats = new [ ] { legacy , legacy2 , newformat }
. Select ( o = >
{
var entity = JsonConvert . DeserializeObject < InvoiceEntity > ( o . ToString ( ) ) ;
entity . Networks = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
return entity . DerivationStrategies . ToString ( ) ;
} )
. ToHashSet ( ) ;
#pragma warning restore CS0618 // Type or member is obsolete
2023-03-01 02:32:48 +01:00
Assert . Single ( formats ) ;
2023-02-25 15:34:49 +01:00
}
2022-08-15 18:45:21 +02:00
[Fact]
public void PaymentMethodIdConverterIsGraceful ( )
{
var pmi = "\"BTC_hasjdfhasjkfjlajn\"" ;
JsonTextReader reader = new ( new StringReader ( pmi ) ) ;
reader . Read ( ) ;
Assert . Null ( new PaymentMethodIdJsonConverter ( ) . ReadJson ( reader , typeof ( PaymentMethodId ) , null ,
JsonSerializer . CreateDefault ( ) ) ) ;
}
[Fact]
public void CanBeBracefulAfterObsoleteShitcoin ( )
{
var blob = new StoreBlob ( ) ;
blob . PaymentMethodCriteria = new List < PaymentMethodCriteria > ( )
{
new ( )
{
Above = true ,
Value = new CurrencyValue ( ) { Currency = "BTC" , Value = 0.1 m } ,
PaymentMethod = new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike )
}
} ;
2023-01-06 14:18:07 +01:00
var newBlob = new Serializer ( null ) . ToString ( blob ) . Replace ( "paymentMethod\":\"BTC\"" , "paymentMethod\":\"ETH_ZYC\"" ) ;
Assert . Empty ( StoreDataExtensions . GetStoreBlob ( new StoreData ( ) { StoreBlob = newBlob } ) . PaymentMethodCriteria ) ;
2022-08-15 18:45:21 +02:00
}
2021-11-22 16:49:51 +01:00
}
}