Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[VDG] Fix Amount Paste #12661

Merged
merged 7 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 11 additions & 12 deletions WalletWasabi.Fluent/Controls/CurrencyEntryBox.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;

Check notice on line 1 in WalletWasabi.Fluent/Controls/CurrencyEntryBox.axaml.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

ℹ Getting worse: Overall Code Complexity

The mean cyclomatic complexity increases from 4.20 to 4.27, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
Expand All @@ -15,6 +15,7 @@
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Infrastructure;
using WalletWasabi.Helpers;
using WalletWasabi.Userfacing;
using static WalletWasabi.Userfacing.CurrencyInput;

namespace WalletWasabi.Fluent.Controls;
Expand Down Expand Up @@ -208,9 +209,6 @@
[GeneratedRegex($"^(?<Whole>[0-9{GroupSeparator}]*)(\\{DecimalSeparator}?(?<Frac>[0-9{GroupSeparator}]*))$")]
private static partial Regex RegexBtcFormat();

[GeneratedRegex($"^[0-9{GroupSeparator}{DecimalSeparator}]*$")]
private static partial Regex RegexDecimalCharsOnly();

[GeneratedRegex($"{GroupSeparator}{{2,}}")]
private static partial Regex RegexConsecutiveSpaces();

Expand Down Expand Up @@ -238,7 +236,7 @@
var rule2 = whole >= 8 && (preComposedText.EndsWith(GroupSeparator) || wholeStr.EndsWith(GroupSeparator));

// Check for non-numeric chars.
var rule3 = !RegexDecimalCharsOnly().IsMatch(preComposedText);
var rule3 = !CurrencyInput.RegexDecimalCharsOnly().IsMatch(preComposedText);
if (rule1 || rule2 || rule3)
{
return false;
Expand Down Expand Up @@ -308,9 +306,15 @@
return;
}

// Ignore paste if there are invalid characters
if (!CurrencyInput.RegexDecimalCharsOnly().IsMatch(text))
{
return;
}

text = text.Replace("\r", "").Replace("\n", "").Trim();

if (!TryParse(text, out text))
if (!TryParsePastedValue(text, out text))
{
return;
}
Expand All @@ -327,7 +331,7 @@
}
}

private bool TryParse(string text, [NotNullWhen(true)] out string? result)
private bool TryParsePastedValue(string text, [NotNullWhen(true)] out string? result)
{
if (!IsFiat)
{
Expand All @@ -336,12 +340,7 @@
: ClipboardObserver.ParseToMoney(text);
if (money is not null)
{
var fractionalCount =
text.Contains('.')
? text.Skip(text.LastIndexOf('.')).Where(char.IsDigit).Count()
: 0;

result = money.ToDecimal(MoneyUnit.BTC).FormattedBtcExactFractional(fractionalCount);
result = money.ToDecimal(MoneyUnit.BTC).FormattedBtcExactFractional(text);
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion WalletWasabi.Fluent/Controls/DualCurrencyEntryBox.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ private void InputText(string? text)
}
else
{
if (CurrencyInput.TryCorrectBitcoinAmount(text, out var better) && better != Constants.MaximumNumberOfBitcoins.ToString())
if (CurrencyInput.TryCorrectBitcoinAmount(text, out var better))
{
text = better;
}
Expand Down
10 changes: 10 additions & 0 deletions WalletWasabi.Fluent/Extensions/CurrencyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Globalization;

Check notice on line 1 in WalletWasabi.Fluent/Extensions/CurrencyExtensions.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

ℹ Getting worse: Primitive Obsession

The ratio of primitive types in function arguments increases from 70.00% to 72.73%, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.
using System.Linq;
using NBitcoin;
using WalletWasabi.Blockchain.TransactionBuilding;
Expand Down Expand Up @@ -57,6 +57,16 @@
return string.Format(FormatInfo, fullFormat, amount).Trim();
}

public static string FormattedBtcExactFractional(this decimal amount, string originalText)
{
var fractionalCount =
originalText.Contains('.')
? originalText.Skip(originalText.LastIndexOf('.')).Where(char.IsDigit).Count()
: 0;

return FormattedBtcExactFractional(amount, fractionalCount);
}

public static string FormattedFiat(this decimal amount, string format = "N2")
{
return amount.ToString(format, FormatInfo).Trim();
Expand Down
14 changes: 10 additions & 4 deletions WalletWasabi.Fluent/Infrastructure/ClipboardObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ public ClipboardObserver(IObservable<Amount> balances)
public IObservable<string?> ClipboardBtcContentChanged(IScheduler scheduler)
{
return ApplicationHelper.ClipboardTextChanged(scheduler)
.CombineLatest(_balances.Select(x => x.Btc), ParseToMoney)
.Select(money => money?.ToDecimal(MoneyUnit.BTC).FormattedBtc());
.CombineLatest(_balances.Select(x => x.Btc), ParseToMoney);
}

public static decimal? ParseToUsd(string? text)
Expand Down Expand Up @@ -70,8 +69,15 @@ public ClipboardObserver(IObservable<Amount> balances)
return Money.TryParse(text, out var n) ? n : default;
}

public static Money? ParseToMoney(string? text, Money balance)
public static string? ParseToMoney(string? text, Money balance)
{
return ParseToMoney(text).Ensure(m => m <= balance);
// Ignore paste if there are invalid characters
if (text is null || !CurrencyInput.RegexDecimalCharsOnly().IsMatch(text))
{
return null;
}

var money = ParseToMoney(text).Ensure(m => m <= balance);
return money?.ToDecimal(MoneyUnit.BTC).FormattedBtcExactFractional(text);
}
}
2 changes: 1 addition & 1 deletion WalletWasabi.Fluent/Views/Wallets/Send/SendView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
IsConversionReversed="{Binding ConversionReversed, Mode=TwoWay}"
BalanceBtc="{Binding Balance^.Btc}"
BalanceUsd="{Binding Balance^.Usd^}"
ValidatePasteBalance="True">
ValidatePasteBalance="False">
<DualCurrencyEntryBox.Resources>

<converters:BooleanConverter x:Key="SuggestionPlacementConverter"
Expand Down
14 changes: 7 additions & 7 deletions WalletWasabi.Tests/UnitTests/Userfacing/CurrencyInputTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ public BitcoinTestData()
{
Add("1.000000000", true, "1.00000000");
Add("1.111111119", true, "1.11111111");
Add("20999999.97690001", true, "20999999.9769");
Add("30999999", true, "20999999.9769");
Add("303333333333333333999999", true, "20999999.9769");
Add("20999999.977", true, "20999999.9769");
Add("209999990.9769", true, "20999999.9769");
Add("20999999.976910000000000", true, "20999999.9769");
Add("209999990000000000.97000000000069", true, "20999999.9769");
Add("20999999.97690001", false, null);
Add("30999999", false, null);
Add("303333333333333333999999", false, null);
Add("20999999.977", false, null);
Add("209999990.9769", false, null);
Add("20999999.976910000000000", true, "20999999.97691000");
Add("209999990000000000.97000000000069", true, "209999990000000000.97000000");
Add("1.000000001", true, "1.00000000");
Add("20999999.97000000000069", true, "20999999.97000000");
}
Expand Down
11 changes: 4 additions & 7 deletions WalletWasabi/Userfacing/CurrencyInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace WalletWasabi.Userfacing;

public static class CurrencyInput
public static partial class CurrencyInput
{
public const string DecimalSeparator = ".";
public const string GroupSeparator = " ";
Expand All @@ -19,6 +19,9 @@ public static class CurrencyInput
NumberDecimalSeparator = DecimalSeparator
};

[GeneratedRegex($"^[0-9{GroupSeparator}{DecimalSeparator}]*$")]
public static partial Regex RegexDecimalCharsOnly();

public static bool TryCorrectAmount(string original, [NotNullWhen(true)] out string? best)
{
var corrected = original;
Expand Down Expand Up @@ -104,12 +107,6 @@ public static bool TryCorrectBitcoinAmount(string original, [NotNullWhen(true)]
corrected = corrected[..(dotIndex + 1 + 8)];
}

// Make sure you don't send more bitcoins than how much there is in existence.
if (corrected.Length != 0 && corrected != "." && (!decimal.TryParse(corrected, out decimal btcDecimal) || btcDecimal > Constants.MaximumNumberOfBitcoins))
{
corrected = Constants.MaximumNumberOfBitcoins.ToString();
}

if (corrected != original)
{
best = corrected;
Expand Down