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

Add a new serialized type: STNumber #5121

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from

Conversation

thejohnfreeman
Copy link
Collaborator

@thejohnfreeman thejohnfreeman commented Sep 6, 2024

This type should let objects and transactions contain multiple fields for quantities of XRP, IOU, or MPT without duplicating information about the "issue" (represented by STIssue). It is a straightforward serialization of our internal Number type that uniformly represents those quantities.

In this change, STNumber is unused anywhere except a couple example tests. I wanted to get a gut-check on the implementation before going much further. Happy to take suggestions for more tests too.

Copy link

codecov bot commented Sep 6, 2024

Codecov Report

Attention: Patch coverage is 80.00000% with 17 lines in your changes missing coverage. Please review.

Project coverage is 77.5%. Comparing base (0d887ad) to head (3f4254b).
Report is 4 commits behind head on develop.

Files with missing lines Patch % Lines
src/libxrpl/protocol/STNumber.cpp 76.5% 8 Missing ⚠️
src/libxrpl/protocol/STObject.cpp 0.0% 5 Missing ⚠️
src/libxrpl/protocol/Serializer.cpp 87.5% 2 Missing ⚠️
include/xrpl/protocol/STNumber.h 66.7% 1 Missing ⚠️
src/xrpld/app/tx/detail/Payment.cpp 88.9% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           develop   #5121     +/-   ##
=========================================
- Coverage     77.5%   77.5%   -0.0%     
=========================================
  Files          777     779      +2     
  Lines        65831   65890     +59     
  Branches      8184    8186      +2     
=========================================
+ Hits         51026   51071     +45     
- Misses       14805   14819     +14     
Files with missing lines Coverage Δ
include/xrpl/basics/Number.h 100.0% <ø> (ø)
include/xrpl/protocol/SField.h 100.0% <ø> (ø)
include/xrpl/protocol/STObject.h 92.9% <ø> (ø)
include/xrpl/protocol/Serializer.h 94.8% <100.0%> (+1.0%) ⬆️
src/libxrpl/protocol/STVar.cpp 97.3% <ø> (ø)
include/xrpl/protocol/STNumber.h 66.7% <66.7%> (ø)
src/xrpld/app/tx/detail/Payment.cpp 90.7% <88.9%> (ø)
src/libxrpl/protocol/Serializer.cpp 85.7% <87.5%> (-0.9%) ⬇️
src/libxrpl/protocol/STObject.cpp 85.0% <0.0%> (-1.0%) ⬇️
src/libxrpl/protocol/STNumber.cpp 76.5% <76.5%> (ø)

... and 4 files with indirect coverage changes

Impacted file tree graph

@dangell7
Copy link
Collaborator

dangell7 commented Sep 9, 2024

I like this. I had all sorts of fun when doing sfQuantity on the options proposal. I think I just ended up using an i32. Some things I would be curious about.

  1. Will there be a getFieldNumber(sfQuantity)? (Assuming yes)
  2. Also how can one multiply / divide / add / subtract an STNumber with an STAmount. That might be good to show in tests if its not straight forward. For example I have 100 USD and I want to multiply by 100.

I ended up with STAmount const totalValue = STAmount(strikePrice.issue(), (strikePrice.mantissa() * quantity)); but maybe I took a wrong turn.

Thank you

src/libxrpl/protocol/Serializer.cpp Outdated Show resolved Hide resolved
include/xrpl/protocol/STNumber.h Outdated Show resolved Hide resolved
include/xrpl/protocol/STNumber.h Outdated Show resolved Hide resolved
src/test/protocol/STNumber_test.cpp Show resolved Hide resolved
@thejohnfreeman
Copy link
Collaborator Author

@dangell7 I added getFieldNumber. I generally add methods as-needed, and the getField* methods seem incomplete (there is no getFieldIssue, for example). Let me know if there are others you want.

I added a test to demonstrate multiplication. The general pattern is to convert everything to Number, compute your arithmetic, and then reconstruct final types like STAmount. We can add arithmetic helpers for those final types where it makes sense. I'm a little disappointed that conversion from STAmount to Number must first pass through constructing either an XRPAmount or an IOUAmount, though I guess it is inlined.

src/libxrpl/protocol/Serializer.cpp Outdated Show resolved Hide resolved
src/libxrpl/protocol/Serializer.cpp Outdated Show resolved Hide resolved
@@ -71,6 +72,32 @@ Serializer::add64(std::uint64_t i)
return ret;
}

int
Serializer::addi32(std::int32_t i)
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a private common (template?) function for 32 and 64 bit serialization. They are practically the same except for the most significant byte handling.

include/xrpl/protocol/STNumber.h Outdated Show resolved Hide resolved
#include <xrpl/protocol/STBase.h>

namespace ripple {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a brief description of the class.

include/xrpl/protocol/STNumber.h Show resolved Hide resolved
void
run() override
{
std::initializer_list<std::int64_t> const values = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it also make sense to add int32 tests?

BEAST_EXPECT(stnum.value() == Number{0});
}

std::initializer_list<std::int64_t> const values = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should add int32 tests?

IOUAmount totalValue{iouValue * factor};
STAmount const totalAmount{totalValue, strikePrice.issue()};
BEAST_EXPECT(totalAmount == Number{10'000});
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we test arithmetics with some large/small fractional values to make sure STAmount and STNumber produce the same results?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I welcome all tests written by others, but I don't want to spend my time designing those tests right now.

src/libxrpl/protocol/STNumber.cpp Outdated Show resolved Hide resolved
Copy link
Collaborator

@gregtatcam gregtatcam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM
I left a couple of minor comments to consider.

add32(std::uint32_t i); // ledger indexes, account sequence, timestamps

template <typename T>
requires(sizeof(T) == sizeof(std::uint32_t)) int add32(T i)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't something like this be more accurate?

 requires(std::is_same_v<std::make_unsigned_t<T>, std::uint32_t>)

add64(std::uint64_t i); // native currency amounts

template <typename T>
requires(sizeof(T) == sizeof(std::uint64_t)) int add64(T i)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to above:

 requires(std::is_same_v<std::make_unsigned_t<T>, std::uint64_t>)

@HowardHinnant
Copy link
Contributor

One thing that bothers me is the ability to destruct a STNumber via a reference or pointer to a Number. This would call Number's non-virtual destructor and not call STNumber's destructor.

That error can be prevented at compile-time with this refactor: HowardHinnant@05ecfc6

Only very lightly tested. So if accepted, this does need further scrutiny.

@gregtatcam
Copy link
Collaborator

One thing that bothers me is the ability to destruct a STNumber via a reference or pointer to a Number. This would call Number's non-virtual destructor and not call STNumber's destructor.

That error can be prevented at compile-time with this refactor: HowardHinnant@05ecfc6

Only very lightly tested. So if accepted, this does need further scrutiny.

@HowardHinnant Why not adding a virtual destructor? If STNumber is a subclass of Number then the math just works.

@HowardHinnant
Copy link
Contributor

@HowardHinnant Why not adding a virtual destructor? If STNumber is a subclass of Number then the math just works.

The math also just works with the way I refactored it. Note that I did not have to change any use cases of STNumber, in code or in tests.

Adding a virtual destructor to Number adds an expense to every client of Number whether they use it or not. My refactor adds no expense to Number at all. It does not touch the declaration or implementation of Number. That makes it a cleaner separation of duties. And the resulting API that clients see is identical, minus the ability to implicitly convert STNumber* to Number* (and STNumber& to Number&), which is both unneeded and undesirable.

HowardHinnant and others added 2 commits October 28, 2024 21:08
* This prevents code trying to delete a STNumber via a
  pointer to the base class using a non-virtual destructor.
@thejohnfreeman thejohnfreeman added the Passed Passed code review & PR owner thinks it's ready to merge. Perf sign-off may still be required. label Nov 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Passed Passed code review & PR owner thinks it's ready to merge. Perf sign-off may still be required.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants