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

fix: AMM swap rounding #5002

Merged
merged 2 commits into from
Apr 26, 2024
Merged

fix: AMM swap rounding #5002

merged 2 commits into from
Apr 26, 2024

Conversation

seelabs
Copy link
Collaborator

@seelabs seelabs commented Apr 25, 2024

High Level Overview of Change

fix amendment: AMM swap should honor invariants:

The AMM has an invariant for swaps where:

new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by very small amounts).

This patch introduces an amendment fixAMMRounding that changes the rounding to always favor the AMM. Doing this should maintain the invariant.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • [ x] Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Performance (increase or change in throughput and/or latency)
  • [ x] Tests (you added tests for code that already exists, or your new feature included in this PR)
  • Documentation update
  • Chore (no impact to binary, e.g. .gitignore, formatting, dropping support for older tooling)
  • Release

@codecov-commenter
Copy link

codecov-commenter commented Apr 25, 2024

Codecov Report

Attention: Patch coverage is 60.52632% with 30 lines in your changes are missing coverage. Please review.

Project coverage is 70.9%. Comparing base (b422e71) to head (b90f58d).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           develop   #5002     +/-   ##
=========================================
- Coverage     70.9%   70.9%   -0.0%     
=========================================
  Files          796     796             
  Lines        66727   66792     +65     
  Branches     10976   10996     +20     
=========================================
+ Hits         47339   47378     +39     
- Misses       19388   19414     +26     
Files Coverage Δ
src/ripple/app/tx/impl/Transactor.cpp 84.5% <100.0%> (+<0.1%) ⬆️
src/ripple/basics/Number.h 100.0% <100.0%> (ø)
src/ripple/protocol/Feature.h 100.0% <ø> (ø)
src/ripple/protocol/Rules.h 100.0% <100.0%> (ø)
src/ripple/protocol/impl/Feature.cpp 93.9% <ø> (ø)
src/ripple/protocol/impl/Rules.cpp 97.9% <100.0%> (+0.4%) ⬆️
src/ripple/ledger/impl/View.cpp 91.6% <75.0%> (-0.1%) ⬇️
src/ripple/protocol/AmountConversions.h 85.7% <0.0%> (-10.3%) ⬇️
src/ripple/app/misc/AMMHelpers.h 69.0% <53.1%> (-16.1%) ⬇️

... and 5 files with indirect coverage changes

Impacted file tree graph

src/test/app/AMM_test.cpp Outdated Show resolved Hide resolved
src/test/app/AMM_test.cpp Outdated Show resolved Hide resolved
src/test/app/AMM_test.cpp Outdated Show resolved Hide resolved
static_assert(alwaysFalse, "Unsupported type for toAmount");
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

How about using concept instead of the static_assert here and other places?

template <typename T>
concept ValidAmount =
    std::is_same_v<T, IOUAmount> ||
    std::is_same_v<T, XRPAmount> ||
    std::is_same_v<T, STAmount>;

template <ValidAmount T>
T
toMaxAmount(Issue const& issue)
{
 ...
}

I don't think max Number is applicable, at least not in the context that toMaxAmount() is used.

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 thought I got a compile error without the supporting Number.

I don't object to the concept, but I weakly prefer the code without it. I don't love that the is_same is repeated in both the body of the function and the concept, and the concept isn't important for distinguishing between overloads. I'll change it if you or others feel strongly, but I think I prefer the non-concept version.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It builds for me without Number on OSX, Linux, and Windows.

I prefer concept since it's clearly defines the type constraint. I don't feel strongly about it though.

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 hear you. I don't think this is worth spending too much time on. I'm going to keep it as is.


Number::setround(Number::upward);
auto const numerator = pool.in * pool.out;
auto const fee = getFee(tfee);
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 make sense to always round upwards getFee() and downward feeMult(). The fee is added to the pool so we want always to maximize it's effect. Can have the default just in case it needs to be changed in some instances:

inline Number
getFee(std::uint16_t tfee, Number::rounding_mode rounding = Number::rounding_mode::upward)
{
    if (auto const& rules = getCurrentTransactionRules();
        rules && rules->enabled(fixAMMRounding))
    {
        saveNumberRoundMode rm(Number::setround(rounding));
        return Number{tfee} / AUCTION_SLOT_FEE_SCALE_FACTOR;
    }
    return Number{tfee} / AUCTION_SLOT_FEE_SCALE_FACTOR;
}

inline Number
feeMult(std::uint16_t tfee, Number::rounding_mode rounding = Number::rounding_mode::downward)
{
    if (auto const& rules = getCurrentTransactionRules();
        rules && rules->enabled(fixAMMRounding))
    {
        saveNumberRoundMode rm(Number::setround(rounding));
        return 1 - getFee(tfee);
    }
    return 1 - getFee(tfee);
}

BEAST_EXPECT(ammAlice.expectBalances(
XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
});
testAMM(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here and a few other places the tests are executed twice with/without fixAMMRounding and there is no change to the tests with fixAMMRounding. Should we limit this to only the tests that have different results with fixAMMRounding? This impacts the run-time of the tests. testAMM() can be changed to take a vector of features and could also add a helper function to iterate over a vector of features in cases where testAMM() is not used.

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

Copy link
Contributor

@HowardHinnant HowardHinnant left a comment

Choose a reason for hiding this comment

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

Approved, but I would like to make CurrentTransactionRulesGuard not copyable either now or in the near future. All it needs is deleted copy members. We already do not copy it, so this won't break anything.

class CurrentTransactionRulesGuard
{
public:
explicit CurrentTransactionRulesGuard(Rules r)
Copy link
Contributor

Choose a reason for hiding this comment

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

This class should not be CopyConstructible nor CopyAssignable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, agreed, thanks! Fixed in 94bbc9c8a9 [fold] CurrentTransactionRulesGuard should not be copyable or assignable

Copy link
Collaborator

@scottschurr scottschurr 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 one note that might be a small improvement for future maintainability. But, in general, it looks like it should do the job.

Comment on lines 1150 to 1151
if (auto const& rules = getCurrentTransactionRules();
rules && rules->enabled(fixAMMRounding))
Copy link
Collaborator

Choose a reason for hiding this comment

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

There are now two sources of "truth" regarding the Rules: the Rules in the current ledger or the Rules supplied by getCurrentTransactionRules(). And, interestingly, those two perspectives are only in alignment if the code being executed is inside the scope of a transactor. Outside the scope of a transactor, getCurrentTransactionRules() returns a nullopt_t.

I think the safest approach everywhere in this file is to call view.rules().enabled() to get the Rules that are explicitly associated with the ledger that was explicitly passed in. That way the code that is likely to be copied and pasted will work in both ApplyViews and ReadViews. So I suggest changing these lines to:

    if (view.rules().enabled(fixAMMRounding))

But that's just looking forward to when this is copied and pasted within this file. The current implementation should also work just fine in this particular function.

@seelabs seelabs force-pushed the amm_rounding branch 4 times, most recently from e751e72 to 3cfc52f Compare April 25, 2024 23:49
It can be difficult to make transaction breaking changes to low level
code because the low level code does not have access to a ledger and the
current activated amendments in that ledger (the "rules"). This patch
adds global access to the current ledger rules as a `std::optional`. If
the optional is not seated, then there is no active transaction.
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
@seelabs seelabs merged commit 3f7ce93 into XRPLF:develop Apr 26, 2024
17 checks passed
@ximinez ximinez mentioned this pull request Apr 26, 2024
1 task
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
ximinez pushed a commit to ximinez/rippled that referenced this pull request Apr 26, 2024
The AMM has an invariant for swaps where:
new_balance_1*new_balance_2 >= old_balance_1*old_balance_2

Due to rounding, this invariant could sometimes be violated (although by
very small amounts).

This patch introduces an amendment `fixAMMRounding` that changes the
rounding to always favor the AMM. Doing this should maintain the
invariant.

Co-authored-by: Bronek Kozicki
Co-authored-by: thejohnfreeman
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants