-
Notifications
You must be signed in to change notification settings - Fork 664
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
Add Amount type #252
Add Amount type #252
Conversation
eca2175
to
cb387f0
Compare
576975d
to
610efd8
Compare
Rebased this on top of #250 so I could use |
610efd8
to
7ff1150
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR feels much cleaner than the previous one. But I wonder if we could save ourselves some pain by declaring parsing and serialization of amounts with suffixes an application responsibility. That would probably avoid the custom parsing and the pressure to acommodate all kinds of weird scaling suffixes (sat
, satoshi
, $
as some people proposed).
src/util/amount.rs
Outdated
/// | ||
/// Note: This only parses the value string. If you want to parse a value with denomination, | ||
/// use `FromStr`. | ||
pub fn parse_denom(mut s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this feels like it should be fuzz tested against a regex implementation doing the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder if we couldn't use f64
here if we defined the maximum valid range as -21*10^14
sat till 21*10^14 sat
. log(21*10^(6+8), 2) ≈ 50.9
and a f64
has 52 mantissa bits, that should be enough to not have any losses imo (but I'd need to test this as well, but with only 2^51
combinations even exhaustive testing seems feasible).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I never touched the fuzz tests. I can try to look into this. Could also create random amounts (uniform i64 values) and string and destring for all denominations etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f64
in principle has enough precision to represent any bitcoin amount, yes. (This is probably one reason for Satoshi choosing the "max 21mil with 8 decimal places" limits he did.) But in practice it's hard to write a parser that doesn't do things like dividing by 10 that create rounding errors and result in wrong numbers being parsed anyway.
d7f64e7
to
0e7ba7c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with the history of this PR but I've rewritten some code with it and basic functionality like converting denominations and from and to f64's works fine for me.
Travis prints a few unused
warnings.
4913e3c
to
cc8c556
Compare
I added |
24ead71
to
162706b
Compare
162706b
to
20950bf
Compare
Ok so I updated the PR to use The one last thing I'm not fully content with is the |
20950bf
to
7ff0572
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update. The from_float_denom
doesn't seem to work, for example
assert_eq!(f(0.0001234, D::Bitcoin), Err(ParseAmountError::TooPrecise));
should be 12340 satoshi instead.
I see that you removed Nit, but "A trait used to" is redundant. Reader sees that it is a trait. :) "Can be converted to ..." is shorter and to the point. This "into btc" is confusing. Shouldn't this be just "TryIntoAmount"? Also - shouldn't the error be associated type? Why would it always had to be "Parse"? That would imply using for just parseable stuff like strings.
|
Lol meta-discussion. The But well, it's removed because it was not super valuable and caused confusion apparently. |
3a419d0
to
172fb78
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can confirm that I can use this module in my code without issues.
4f6c114
to
e7df221
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some final, mostly minor, nits. Looks good otherwise.
e7df221
to
e7920d7
Compare
What else is blocking this? |
/// | ||
/// Note: This only parses the value string. If you want to parse a value | ||
/// with denomination, use [FromStr]. | ||
pub fn from_str_in(mut s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I began verifying the parsing code by trying to build a DFA from it to derive a regular expression. The regex seems ok: [0-9]*.[0-9]{0,precision_diff}
the only weird case this allows is parsing .
as 0
.
But the precision verification is broken, e.g. the following test case in fn parsing()
assert_eq!(p("12.000", Denomination::MilliSatoshi), Err(ParseAmountError::TooPrecise));
fails with
left: `Ok(Amount(12 satoshi))`,
right: `Err(TooPrecise)`', src/util/amount.rs:712:9
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good catch! I fixed that in an extra commit. The string roundtrip test didn't catch this because the to_string_in
was also broken :/
4ed8fb0
to
80eeb2d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you agree feel free to address the one clarity nit I left. Other than that this seems ready to be merged to me.
concept ACK |
d0b3767
to
adcc546
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not looked at the string parsing nor serde but the rest works for me. ACK adcc546
@stevenroose serde tests fail according to travis |
} | ||
|
||
/// Get the number of satoshis in this [Amount]. | ||
pub fn as_sat(self) -> i64 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have an as_usat
or something that returns a u64? I see in @jonasnick's code using this that there are zillions of casts to u64.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One that panics on negative or one that Result
s? I'd personally do try_as_usat() -> Option<u64>
and as_usat() -> u64
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:( in my usage negative amounts never ever exist. It's really unergonomic to have these extra error paths and also have all my amounts potentially being negative. It also prevents computing fees using checked_sub
to catch unbalanced transactions as a special case.
Please see #270 for a replacement PR that has explicit |
Fix link in README
(Retake of #192 with a simpler structure for simple satoshi-precision amounts.)
Rationale
There are a lot of places and ways in which Bitcoin amounts are used: displaying, converting denominations, arithmetic, de/serializing in different ways. It's not always clear what type to use for a Bitcoin amount:
i64
?u64
?f64
? All three of those are common choices and are confusing when used together.The
Amount
type has the following functions:functions
checked_*
methods for checked arithmetic andstd::ops
trait implementations for common operations that panic on over/underflowserde
serialization asi64
satoshi or asf64
bitcoin (extensible with other options, f.e. as a string)The underlying type is
i64
. There was a hard discussion about whether to usei64
oru64
, i.e. whether or not to allow negative amounts. One of the main deciding factors was the fact that the Bitcoin Core API has several wallet-related calls where negative amounts are used. Several utility functions are added to aid those that want to avoid negative amounts:abs
,is_negative
andpositive_sub
(functions likechecked_sub
as if the underlying type wasu64
, i.e. returnsNone
if the result or either of the operands of the subtraction would be negative).Superseded by #270