mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 18:45:52 +00:00
fix: Add AMMv1_3 amendment (#5203)
* Add AMM bid/create/deposit/swap/withdraw/vote invariants:
- Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`.
- Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change.
- Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`.
- Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore`
and `LPTokens` don't change.
- Vote: `LPTokens` and pool balances don't change.
- All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens
are withdrawn.
* Add AMM deposit and withdraw rounding to ensure AMM invariant:
- On deposit, tokens out are rounded downward and deposit amount is rounded upward.
- On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward.
* Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer.
* Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account.
This commit is contained in:
committed by
GitHub
parent
0a34b5c691
commit
621df422a7
@@ -98,6 +98,12 @@ public:
|
||||
|
||||
static IOUAmount
|
||||
minPositiveAmount();
|
||||
|
||||
friend std::ostream&
|
||||
operator<<(std::ostream& os, IOUAmount const& x)
|
||||
{
|
||||
return os << to_string(x);
|
||||
}
|
||||
};
|
||||
|
||||
inline IOUAmount::IOUAmount(beast::Zero)
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
bool
|
||||
isFeatureEnabled(uint256 const& feature);
|
||||
|
||||
class DigestAwareReadView;
|
||||
|
||||
/** Rules controlling protocol behavior. */
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -161,4 +161,12 @@ Rules::operator!=(Rules const& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool
|
||||
isFeatureEnabled(uint256 const& feature)
|
||||
{
|
||||
auto const& rules = getCurrentTransactionRules();
|
||||
return rules && rules->enabled(feature);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -581,8 +581,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
|
||||
|
||||
// gw clawback 1000 USD from the AMM pool
|
||||
env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
|
||||
@@ -601,12 +605,20 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the
|
||||
// current balance is 3000 USD and 3750 EUR.
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(3000), EUR(3750), IOUAmount{3354101966249684, -12}));
|
||||
|
||||
// Alice has 3/4 of its initial lptokens Left.
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{3354101966249685, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{3354101966249685, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{3354101966249684, -12}));
|
||||
|
||||
// gw clawback another 500 USD from the AMM pool.
|
||||
env(amm::ammClawback(gw, alice, USD, EUR, USD(500)),
|
||||
@@ -617,14 +629,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// AMM pool.
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2500000000000001), -12},
|
||||
STAmount{EUR, UINT64_C(3125000000000001), -12},
|
||||
IOUAmount{2795084971874738, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), EUR(3125), IOUAmount{2795084971874737, -12}));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, EUR) ==
|
||||
STAmount(EUR, UINT64_C(2874999999999999), -12));
|
||||
else
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875));
|
||||
|
||||
// gw clawback small amount, 1 USD.
|
||||
env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS));
|
||||
@@ -633,14 +652,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// Another 1 USD / 1.25 EUR was withdrawn.
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2499000000000002), -12},
|
||||
STAmount{EUR, UINT64_C(3123750000000002), -12},
|
||||
IOUAmount{2793966937885989, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12}));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, EUR) ==
|
||||
STAmount(EUR, UINT64_C(2876249999999998), -12));
|
||||
STAmount(EUR, UINT64_C(2'876'249999999998), -12));
|
||||
else
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25));
|
||||
|
||||
// gw clawback 4000 USD, exceeding the current balance. We
|
||||
// will clawback all.
|
||||
@@ -713,14 +739,26 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR.
|
||||
AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
|
||||
amm2.deposit(alice, EUR(1000), XRP(3000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
|
||||
amm2.deposit(bob, EUR(1000), XRP(3000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9}));
|
||||
env.close();
|
||||
|
||||
auto aliceXrpBalance = env.balance(alice, XRP);
|
||||
@@ -743,10 +781,18 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), XRP(5000), IOUAmount{3535533905932737, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865480, -10}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865474, -10}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9}));
|
||||
|
||||
@@ -760,14 +806,26 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// Bob gets 20 XRP back.
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2490000000000001), -12},
|
||||
XRP(4980),
|
||||
IOUAmount{3521391770309008, -9}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865480, -10}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865474, -10}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
|
||||
|
||||
// gw2 clawback 200 EUR from amm2.
|
||||
env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)),
|
||||
@@ -780,12 +838,24 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// Alice gets 600 XRP back.
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(1000) + XRP(600)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
|
||||
BEAST_EXPECT(
|
||||
amm2.expectLPTokens(alice, IOUAmount{1385640646055103, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectLPTokens(
|
||||
alice, IOUAmount{1385640646055103, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectLPTokens(
|
||||
alice, IOUAmount{1385640646055102, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9}));
|
||||
|
||||
// gw claw back 1000 USD from alice in amm, which exceeds alice's
|
||||
// balance. This will clawback all the remaining LP tokens of alice
|
||||
@@ -798,17 +868,34 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env.require(balance(bob, gw["USD"](4000)));
|
||||
|
||||
// Alice gets 1000 XRP back.
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
|
||||
XRPAmount{1}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(1990000000000001), -12},
|
||||
XRP(3980),
|
||||
IOUAmount{2814284989122460, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(1'990),
|
||||
XRPAmount{3'980'000'001},
|
||||
IOUAmount{2814284989122459, -9}));
|
||||
|
||||
// gw clawback 1000 USD from bob in amm, which also exceeds bob's
|
||||
// balance in amm. All bob's lptoken in amm will be consumed, which
|
||||
@@ -820,10 +907,17 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env.require(balance(alice, gw["USD"](5000)));
|
||||
env.require(balance(bob, gw["USD"](4000)));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
|
||||
XRPAmount{1}));
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
|
||||
|
||||
@@ -843,21 +937,32 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// Alice gets another 2400 XRP back, bob's XRP balance remains the
|
||||
// same.
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400) - XRPAmount{1}));
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
|
||||
|
||||
// Alice now does not have any lptoken in amm2
|
||||
BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
|
||||
|
||||
// gw2 claw back 2000 EUR from bib in amm2, which exceeds bob's
|
||||
// gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's
|
||||
// balance. All bob's lptokens will be consumed, which corresponds
|
||||
// to 1000EUR / 3000 XRP.
|
||||
env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)),
|
||||
@@ -869,11 +974,18 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// Bob gets another 3000 XRP back. Alice's XRP balance remains the
|
||||
// same.
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400) - XRPAmount{1}));
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000)));
|
||||
|
||||
@@ -881,8 +993,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
|
||||
BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0)));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,21 +1056,45 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
|
||||
amm.deposit(bob, USD(2000), EUR(2500));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(6000), EUR(7500), IOUAmount{6708203932499368, -12}));
|
||||
amm.deposit(carol, USD(1000), EUR(1250));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(7000), EUR(8750), IOUAmount{7826237921249262, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999579, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
|
||||
amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749895, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749894, -12}));
|
||||
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
env.require(balance(alice, gw2["EUR"](1000)));
|
||||
@@ -968,16 +1108,30 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(4999999999999999), -12},
|
||||
STAmount{EUR, UINT64_C(6249999999999999), -12},
|
||||
IOUAmount{5590169943749475, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(5000000000000001), -12},
|
||||
STAmount{EUR, UINT64_C(6250000000000001), -12},
|
||||
IOUAmount{5590169943749473, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999579, -12}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749895, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749894, -12}));
|
||||
|
||||
// Bob will get 2500 EUR back.
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
@@ -986,9 +1140,14 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env.balance(bob, USD) ==
|
||||
STAmount(USD, UINT64_C(3000000000000000), -12));
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, EUR) ==
|
||||
STAmount(EUR, UINT64_C(5000000000000001), -12));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, EUR) ==
|
||||
STAmount(EUR, UINT64_C(4999999999999999), -12));
|
||||
env.require(balance(carol, gw["USD"](3000)));
|
||||
env.require(balance(carol, gw2["EUR"](2750)));
|
||||
|
||||
@@ -996,13 +1155,23 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(3999999999999999), -12},
|
||||
STAmount{EUR, UINT64_C(4999999999999999), -12},
|
||||
IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(4000000000000001), -12},
|
||||
STAmount{EUR, UINT64_C(5000000000000002), -12},
|
||||
IOUAmount{4472135954999579, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999579, -12}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
|
||||
BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0)));
|
||||
|
||||
@@ -1041,14 +1210,26 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
|
||||
AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999579, -9}));
|
||||
amm.deposit(alice, USD(1000), XRP(200));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(11000), XRP(2200), IOUAmount{4919349550499536, -9}));
|
||||
amm.deposit(bob, USD(2000), XRP(400));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(13000), XRP(2600), IOUAmount{5813776741499451, -9}));
|
||||
env.close();
|
||||
|
||||
auto aliceXrpBalance = env.balance(alice, XRP);
|
||||
@@ -1058,18 +1239,34 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(200)));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(12000),
|
||||
XRPAmount(2400000001),
|
||||
IOUAmount{5366563145999494, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(200)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
|
||||
// gw clawback all bob's USD in amm. (2000 USD / 400 XRP)
|
||||
env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000),
|
||||
XRPAmount(2000000001),
|
||||
IOUAmount{4472135954999579, -9}));
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400)));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
@@ -1125,10 +1322,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
amm.deposit(bob, USD(4000), EUR(1000));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
|
||||
if (!features[fixAMMv1_3])
|
||||
amm.deposit(carol, USD(2000), EUR(500));
|
||||
else
|
||||
amm.deposit(carol, USD(2000.25), EUR(500));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
|
||||
|
||||
// gw clawback 1000 USD from carol.
|
||||
env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
@@ -1142,7 +1341,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
// 250 EUR goes back to carol.
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
@@ -1164,7 +1368,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
// 250 EUR did not go back to bob because tfClawTwoAssets is set.
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
// gw clawback all USD from alice and set tfClawTwoAssets.
|
||||
@@ -1181,7 +1390,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
}
|
||||
|
||||
@@ -1366,12 +1580,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// gw2 claws back 1000 EUR from gw.
|
||||
env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4500),
|
||||
STAmount(EUR, UINT64_C(9000000000000001), -12),
|
||||
IOUAmount{6363961030678928, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4500), EUR(9000), IOUAmount{6363961030678928, -12}));
|
||||
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
|
||||
@@ -1384,12 +1607,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// gw2 claws back 4000 EUR from alice.
|
||||
env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500),
|
||||
STAmount(EUR, UINT64_C(5000000000000001), -12),
|
||||
IOUAmount{3535533905932738, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), EUR(5000), IOUAmount{3535533905932738, -12}));
|
||||
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12}));
|
||||
@@ -1653,7 +1885,10 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
amm.deposit(bob, USD(4000), EUR(1000));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
|
||||
if (!features[fixAMMv1_3])
|
||||
amm.deposit(carol, USD(2000), EUR(500));
|
||||
else
|
||||
amm.deposit(carol, USD(2000.25), EUR(500));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
|
||||
|
||||
@@ -1675,7 +1910,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
// 250 EUR goes back to carol.
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
@@ -1697,7 +1937,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
// 250 EUR did not go back to bob because tfClawTwoAssets is set.
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
// gw clawback all USD from alice and set tfClawTwoAssets.
|
||||
@@ -1715,7 +1960,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
}
|
||||
}
|
||||
@@ -1763,13 +2013,23 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount(USD, UINT64_C(5656854249492380), -13),
|
||||
XRP(70.710678),
|
||||
IOUAmount(200000)));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount(USD, UINT64_C(565'685424949238), -12),
|
||||
XRP(70.710679),
|
||||
IOUAmount(200000)));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(29.289322)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(29.289321)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1780,13 +2040,18 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
testFeatureDisabled(all - featureAMMClawback);
|
||||
testAMMClawbackSpecificAmount(all);
|
||||
testAMMClawbackExceedBalance(all);
|
||||
testAMMClawbackExceedBalance(all - fixAMMv1_3);
|
||||
testAMMClawbackAll(all);
|
||||
testAMMClawbackAll(all - fixAMMv1_3);
|
||||
testAMMClawbackSameIssuerAssets(all);
|
||||
testAMMClawbackSameIssuerAssets(all - fixAMMv1_3);
|
||||
testAMMClawbackSameCurrency(all);
|
||||
testAMMClawbackIssuesEachOther(all);
|
||||
testNotHoldingLptoken(all);
|
||||
testAssetFrozen(all);
|
||||
testAssetFrozen(all - fixAMMv1_3);
|
||||
testSingleDepositAndClawback(all);
|
||||
testSingleDepositAndClawback(all - fixAMMv1_3);
|
||||
}
|
||||
};
|
||||
BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple);
|
||||
|
||||
@@ -1451,7 +1451,7 @@ private:
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testRmFundedOffer(all);
|
||||
testRmFundedOffer(all - fixAMMv1_1);
|
||||
testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testEnforceNoRipple(all);
|
||||
testFillModes(all);
|
||||
testOfferCrossWithXRP(all);
|
||||
@@ -1465,7 +1465,7 @@ private:
|
||||
testOfferCreateThenCross(all);
|
||||
testSellFlagExceedLimit(all);
|
||||
testGatewayCrossCurrency(all);
|
||||
testGatewayCrossCurrency(all - fixAMMv1_1);
|
||||
testGatewayCrossCurrency(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testBridgedCross(all);
|
||||
testSellWithFillOrKill(all);
|
||||
testTransferRateOffer(all);
|
||||
@@ -1473,7 +1473,7 @@ private:
|
||||
testBadPathAssert(all);
|
||||
testSellFlagBasic(all);
|
||||
testDirectToDirectPath(all);
|
||||
testDirectToDirectPath(all - fixAMMv1_1);
|
||||
testDirectToDirectPath(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testRequireAuth(all);
|
||||
testMissingAuth(all);
|
||||
}
|
||||
@@ -4063,9 +4063,9 @@ private:
|
||||
testBookStep(all);
|
||||
testBookStep(all | ownerPaysFee);
|
||||
testTransferRate(all | ownerPaysFee);
|
||||
testTransferRate((all - fixAMMv1_1) | ownerPaysFee);
|
||||
testTransferRate((all - fixAMMv1_1 - fixAMMv1_3) | ownerPaysFee);
|
||||
testTransferRateNoOwnerFee(all);
|
||||
testTransferRateNoOwnerFee(all - fixAMMv1_1);
|
||||
testTransferRateNoOwnerFee(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testLimitQuality();
|
||||
testXRPPathLoop();
|
||||
}
|
||||
@@ -4076,7 +4076,7 @@ private:
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testStepLimit(all);
|
||||
testStepLimit(all - fixAMMv1_1);
|
||||
testStepLimit(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -4085,7 +4085,7 @@ private:
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
test_convert_all_of_an_asset(all);
|
||||
test_convert_all_of_an_asset(all - fixAMMv1_1);
|
||||
test_convert_all_of_an_asset(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -127,7 +127,6 @@ class AMM
|
||||
STAmount const asset1_;
|
||||
STAmount const asset2_;
|
||||
uint256 const ammID_;
|
||||
IOUAmount const initialLPTokens_;
|
||||
bool log_;
|
||||
bool doClose_;
|
||||
// Predict next purchase price
|
||||
@@ -140,6 +139,7 @@ class AMM
|
||||
std::uint32_t const fee_;
|
||||
AccountID const ammAccount_;
|
||||
Issue const lptIssue_;
|
||||
IOUAmount const initialLPTokens_;
|
||||
|
||||
public:
|
||||
AMM(Env& env,
|
||||
@@ -196,6 +196,12 @@ public:
|
||||
Issue const& issue2,
|
||||
std::optional<AccountID> const& account = std::nullopt) const;
|
||||
|
||||
std::tuple<STAmount, STAmount, STAmount>
|
||||
balances(std::optional<AccountID> const& account = std::nullopt) const
|
||||
{
|
||||
return balances(asset1_.get<Issue>(), asset2_.get<Issue>(), account);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
expectLPTokens(AccountID const& account, IOUAmount const& tokens) const;
|
||||
|
||||
@@ -430,6 +436,9 @@ private:
|
||||
|
||||
[[nodiscard]] bool
|
||||
expectAuctionSlot(auto&& cb) const;
|
||||
|
||||
IOUAmount
|
||||
initialTokens();
|
||||
};
|
||||
|
||||
namespace amm {
|
||||
|
||||
@@ -35,6 +35,15 @@ class AMM;
|
||||
|
||||
enum class Fund { All, Acct, Gw, IOUOnly };
|
||||
|
||||
struct TestAMMArg
|
||||
{
|
||||
std::optional<std::pair<STAmount, STAmount>> pool = std::nullopt;
|
||||
std::uint16_t tfee = 0;
|
||||
std::optional<jtx::ter> ter = std::nullopt;
|
||||
std::vector<FeatureBitset> features = {supported_amendments()};
|
||||
bool noLog = false;
|
||||
};
|
||||
|
||||
void
|
||||
fund(
|
||||
jtx::Env& env,
|
||||
@@ -87,6 +96,11 @@ protected:
|
||||
std::uint16_t tfee = 0,
|
||||
std::optional<jtx::ter> const& ter = std::nullopt,
|
||||
std::vector<FeatureBitset> const& features = {supported_amendments()});
|
||||
|
||||
void
|
||||
testAMM(
|
||||
std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
|
||||
TestAMMArg const& arg);
|
||||
};
|
||||
|
||||
class AMMTest : public jtx::AMMTestBase
|
||||
|
||||
@@ -622,6 +622,12 @@ public:
|
||||
void
|
||||
disableFeature(uint256 const feature);
|
||||
|
||||
bool
|
||||
enabled(uint256 feature) const
|
||||
{
|
||||
return current()->rules().enabled(feature);
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
fund(bool setDefaultRipple, STAmount const& amount, Account const& account);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/misc/AMMHelpers.h>
|
||||
#include <xrpld/app/misc/AMMUtils.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
|
||||
@@ -39,12 +40,16 @@ number(STAmount const& a)
|
||||
return a;
|
||||
}
|
||||
|
||||
static IOUAmount
|
||||
initialTokens(STAmount const& asset1, STAmount const& asset2)
|
||||
IOUAmount
|
||||
AMM::initialTokens()
|
||||
{
|
||||
auto const product = number(asset1) * number(asset2);
|
||||
if (!env_.enabled(fixAMMv1_3))
|
||||
{
|
||||
auto const product = number(asset1_) * number(asset2_);
|
||||
return (IOUAmount)(product.mantissa() >= 0 ? root2(product)
|
||||
: root2(-product));
|
||||
}
|
||||
return getLPTokensBalance();
|
||||
}
|
||||
|
||||
AMM::AMM(
|
||||
@@ -65,7 +70,6 @@ AMM::AMM(
|
||||
, asset1_(asset1)
|
||||
, asset2_(asset2)
|
||||
, ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key)
|
||||
, initialLPTokens_(initialTokens(asset1, asset2))
|
||||
, log_(log)
|
||||
, doClose_(close)
|
||||
, lastPurchasePrice_(0)
|
||||
@@ -78,6 +82,7 @@ AMM::AMM(
|
||||
asset1_.issue().currency,
|
||||
asset2_.issue().currency,
|
||||
ammAccount_))
|
||||
, initialLPTokens_(initialTokens())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <test/jtx/AMMTest.h>
|
||||
#include <test/jtx/CaptureLogs.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/pay.h>
|
||||
|
||||
@@ -105,15 +106,31 @@ AMMTestBase::testAMM(
|
||||
std::uint16_t tfee,
|
||||
std::optional<jtx::ter> const& ter,
|
||||
std::vector<FeatureBitset> const& vfeatures)
|
||||
{
|
||||
testAMM(
|
||||
std::move(cb),
|
||||
TestAMMArg{
|
||||
.pool = pool, .tfee = tfee, .ter = ter, .features = vfeatures});
|
||||
}
|
||||
|
||||
void
|
||||
AMMTestBase::testAMM(
|
||||
std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
|
||||
TestAMMArg const& arg)
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
for (auto const& features : vfeatures)
|
||||
std::string logs;
|
||||
|
||||
for (auto const& features : arg.features)
|
||||
{
|
||||
Env env{*this, features};
|
||||
Env env{
|
||||
*this,
|
||||
features,
|
||||
arg.noLog ? std::make_unique<CaptureLogs>(&logs) : nullptr};
|
||||
|
||||
auto const [asset1, asset2] =
|
||||
pool ? *pool : std::make_pair(XRP(10000), USD(10000));
|
||||
arg.pool ? *arg.pool : std::make_pair(XRP(10000), USD(10000));
|
||||
auto tofund = [&](STAmount const& a) -> STAmount {
|
||||
if (a.native())
|
||||
{
|
||||
@@ -143,7 +160,7 @@ AMMTestBase::testAMM(
|
||||
alice,
|
||||
asset1,
|
||||
asset2,
|
||||
CreateArg{.log = false, .tfee = tfee, .err = ter});
|
||||
CreateArg{.log = false, .tfee = arg.tfee, .err = arg.ter});
|
||||
if (BEAST_EXPECT(
|
||||
ammAlice.expectBalances(asset1, asset2, ammAlice.tokens())))
|
||||
cb(ammAlice, env);
|
||||
|
||||
@@ -203,12 +203,13 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testVoteAndBid()
|
||||
testVoteAndBid(FeatureBitset features)
|
||||
{
|
||||
testcase("Vote and Bid");
|
||||
|
||||
using namespace jtx;
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
|
||||
XRP(10000), USD(10000), IOUAmount{10000000, 0}));
|
||||
std::unordered_map<std::string, std::uint16_t> votes;
|
||||
@@ -217,7 +218,10 @@ public:
|
||||
{
|
||||
Account a(std::to_string(i));
|
||||
votes.insert({a.human(), 50 * (i + 1)});
|
||||
if (!features[fixAMMv1_3])
|
||||
fund(env, gw, {a}, {USD(10000)}, Fund::Acct);
|
||||
else
|
||||
fund(env, gw, {a}, {USD(10001)}, Fund::Acct);
|
||||
ammAlice.deposit(a, 10000000);
|
||||
ammAlice.vote(a, 50 * (i + 1));
|
||||
}
|
||||
@@ -227,6 +231,7 @@ public:
|
||||
env.fund(XRP(1000), bob, ed, bill);
|
||||
env(ammAlice.bid(
|
||||
{.bidMin = 100, .authAccounts = {carol, bob, ed, bill}}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
|
||||
XRP(80000),
|
||||
USD(80000),
|
||||
@@ -234,6 +239,14 @@ public:
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ammAlice.ammAccount()));
|
||||
else
|
||||
BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
|
||||
XRPAmount(80000000005),
|
||||
STAmount{USD, UINT64_C(80'000'00000000005), -11},
|
||||
IOUAmount{79994400},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ammAlice.ammAccount()));
|
||||
for (auto i = 0; i < 2; ++i)
|
||||
{
|
||||
std::unordered_set<std::string> authAccounts = {
|
||||
@@ -254,8 +267,10 @@ public:
|
||||
for (std::uint8_t i = 0; i < 8; ++i)
|
||||
{
|
||||
if (!BEAST_EXPECT(
|
||||
votes[voteSlots[i][jss::account].asString()] ==
|
||||
voteSlots[i][jss::trading_fee].asUInt() &&
|
||||
votes[voteSlots[i][jss::account]
|
||||
.asString()] ==
|
||||
voteSlots[i][jss::trading_fee]
|
||||
.asUInt() &&
|
||||
voteSlots[i][jss::vote_weight].asUInt() ==
|
||||
12500))
|
||||
return;
|
||||
@@ -270,7 +285,8 @@ public:
|
||||
for (std::uint8_t i = 0; i < 4; ++i)
|
||||
{
|
||||
if (!BEAST_EXPECT(authAccounts.contains(
|
||||
auctionSlot[jss::auth_accounts][i][jss::account]
|
||||
auctionSlot[jss::auth_accounts][i]
|
||||
[jss::account]
|
||||
.asString())))
|
||||
return;
|
||||
authAccounts.erase(
|
||||
@@ -280,7 +296,8 @@ public:
|
||||
if (!BEAST_EXPECT(authAccounts.empty()))
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
auctionSlot[jss::account].asString() == alice.human() &&
|
||||
auctionSlot[jss::account].asString() ==
|
||||
alice.human() &&
|
||||
auctionSlot[jss::discounted_fee].asUInt() == 17 &&
|
||||
auctionSlot[jss::price][jss::value].asString() ==
|
||||
"5600" &&
|
||||
@@ -294,7 +311,11 @@ public:
|
||||
fail(e.what(), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
std::nullopt,
|
||||
0,
|
||||
std::nullopt,
|
||||
{features});
|
||||
}
|
||||
|
||||
void
|
||||
@@ -337,9 +358,12 @@ public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
testErrors();
|
||||
testSimpleRpc();
|
||||
testVoteAndBid();
|
||||
testVoteAndBid(all);
|
||||
testVoteAndBid(all - fixAMMv1_3);
|
||||
testFreeze();
|
||||
testInvalidAmmField();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ reduceOffer(auto const& amount)
|
||||
|
||||
} // namespace detail
|
||||
|
||||
enum class IsDeposit : bool { No = false, Yes = true };
|
||||
|
||||
/** Calculate LP Tokens given AMM pool reserves.
|
||||
* @param asset1 AMM one side of the pool reserve
|
||||
* @param asset2 AMM another side of the pool reserve
|
||||
@@ -67,7 +69,7 @@ ammLPTokens(
|
||||
* @return tokens
|
||||
*/
|
||||
STAmount
|
||||
lpTokensIn(
|
||||
lpTokensOut(
|
||||
STAmount const& asset1Balance,
|
||||
STAmount const& asset1Deposit,
|
||||
STAmount const& lptAMMBalance,
|
||||
@@ -96,7 +98,7 @@ ammAssetIn(
|
||||
* @return tokens out amount
|
||||
*/
|
||||
STAmount
|
||||
lpTokensOut(
|
||||
lpTokensIn(
|
||||
STAmount const& asset1Balance,
|
||||
STAmount const& asset1Withdraw,
|
||||
STAmount const& lptAMMBalance,
|
||||
@@ -110,7 +112,7 @@ lpTokensOut(
|
||||
* @return calculated asset amount
|
||||
*/
|
||||
STAmount
|
||||
withdrawByTokens(
|
||||
ammAssetOut(
|
||||
STAmount const& assetBalance,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokens,
|
||||
@@ -608,13 +610,13 @@ square(Number const& n);
|
||||
* withdraw to cancel out the precision loss.
|
||||
* @param lptAMMBalance LPT AMM Balance
|
||||
* @param lpTokens LP tokens to deposit or withdraw
|
||||
* @param isDeposit true if deposit, false if withdraw
|
||||
* @param isDeposit Yes if deposit, No if withdraw
|
||||
*/
|
||||
STAmount
|
||||
adjustLPTokens(
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokens,
|
||||
bool isDeposit);
|
||||
IsDeposit isDeposit);
|
||||
|
||||
/** Calls adjustLPTokens() and adjusts deposit or withdraw amounts if
|
||||
* the adjusted LP tokens are less than the provided LP tokens.
|
||||
@@ -624,7 +626,7 @@ adjustLPTokens(
|
||||
* @param lptAMMBalance LPT AMM Balance
|
||||
* @param lpTokens LP tokens to deposit or withdraw
|
||||
* @param tfee trading fee in basis points
|
||||
* @param isDeposit true if deposit, false if withdraw
|
||||
* @param isDeposit Yes if deposit, No if withdraw
|
||||
* @return
|
||||
*/
|
||||
std::tuple<STAmount, std::optional<STAmount>, STAmount>
|
||||
@@ -635,7 +637,7 @@ adjustAmountsByLPTokens(
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokens,
|
||||
std::uint16_t tfee,
|
||||
bool isDeposit);
|
||||
IsDeposit isDeposit);
|
||||
|
||||
/** Positive solution for quadratic equation:
|
||||
* x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
|
||||
@@ -643,6 +645,141 @@ adjustAmountsByLPTokens(
|
||||
Number
|
||||
solveQuadraticEq(Number const& a, Number const& b, Number const& c);
|
||||
|
||||
STAmount
|
||||
multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm);
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline Number::rounding_mode
|
||||
getLPTokenRounding(IsDeposit isDeposit)
|
||||
{
|
||||
// Minimize on deposit, maximize on withdraw to ensure
|
||||
// AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
|
||||
return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward;
|
||||
}
|
||||
|
||||
inline Number::rounding_mode
|
||||
getAssetRounding(IsDeposit isDeposit)
|
||||
{
|
||||
// Maximize on deposit, minimize on withdraw to ensure
|
||||
// AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
|
||||
return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas
|
||||
* calculate the amount as a fractional value of the pool balance. The rounding
|
||||
* takes place on the last step of multiplying the balance by the fraction if
|
||||
* AMMv1_3 is enabled.
|
||||
*/
|
||||
template <typename A>
|
||||
STAmount
|
||||
getRoundedAsset(
|
||||
Rules const& rules,
|
||||
STAmount const& balance,
|
||||
A const& frac,
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
{
|
||||
if constexpr (std::is_same_v<A, STAmount>)
|
||||
return multiply(balance, frac, balance.issue());
|
||||
else
|
||||
return toSTAmount(balance.issue(), balance * frac);
|
||||
}
|
||||
auto const rm = detail::getAssetRounding(isDeposit);
|
||||
return multiply(balance, frac, rm);
|
||||
}
|
||||
|
||||
/** Round AMM single deposit/withdrawal amount.
|
||||
* The lambda's are used to delay evaluation until the function
|
||||
* is executed so that the calculation is not done twice. noRoundCb() is
|
||||
* called if AMMv1_3 is disabled. Otherwise, the rounding is set and
|
||||
* the amount is:
|
||||
* isDeposit is Yes - the balance multiplied by productCb()
|
||||
* isDeposit is No - the result of productCb(). The rounding is
|
||||
* the same for all calculations in productCb()
|
||||
*/
|
||||
STAmount
|
||||
getRoundedAsset(
|
||||
Rules const& rules,
|
||||
std::function<Number()>&& noRoundCb,
|
||||
STAmount const& balance,
|
||||
std::function<Number()>&& productCb,
|
||||
IsDeposit isDeposit);
|
||||
|
||||
/** Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas
|
||||
* calculate the lptokens as a fractional value of the AMM total lptokens.
|
||||
* The rounding takes place on the last step of multiplying the balance by
|
||||
* the fraction if AMMv1_3 is enabled. The tokens are then
|
||||
* adjusted to factor in the loss in precision (we only keep 16 significant
|
||||
* digits) when adding the lptokens to the balance.
|
||||
*/
|
||||
STAmount
|
||||
getRoundedLPTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& balance,
|
||||
Number const& frac,
|
||||
IsDeposit isDeposit);
|
||||
|
||||
/** Round AMM single deposit/withdrawal LPToken amount.
|
||||
* The lambda's are used to delay evaluation until the function is executed
|
||||
* so that the calculations are not done twice.
|
||||
* noRoundCb() is called if AMMv1_3 is disabled. Otherwise, the rounding is set
|
||||
* and the lptokens are:
|
||||
* if isDeposit is Yes - the result of productCb(). The rounding is
|
||||
* the same for all calculations in productCb()
|
||||
* if isDeposit is No - the balance multiplied by productCb()
|
||||
* The lptokens are then adjusted to factor in the loss in precision
|
||||
* (we only keep 16 significant digits) when adding the lptokens to the balance.
|
||||
*/
|
||||
STAmount
|
||||
getRoundedLPTokens(
|
||||
Rules const& rules,
|
||||
std::function<Number()>&& noRoundCb,
|
||||
STAmount const& lptAMMBalance,
|
||||
std::function<Number()>&& productCb,
|
||||
IsDeposit isDeposit);
|
||||
|
||||
/* Next two functions adjust asset in/out amount to factor in the adjusted
|
||||
* lptokens. The lptokens are calculated from the asset in/out. The lptokens are
|
||||
* then adjusted to factor in the loss in precision. The adjusted lptokens might
|
||||
* be less than the initially calculated tokens. Therefore, the asset in/out
|
||||
* must be adjusted. The rounding might result in the adjusted amount being
|
||||
* greater than the original asset in/out amount. If this happens,
|
||||
* then the original amount is reduced by the difference in the adjusted amount
|
||||
* and the original amount. The actual tokens and the actual adjusted amount
|
||||
* are then recalculated. The minimum of the original and the actual
|
||||
* adjusted amount is returned.
|
||||
*/
|
||||
std::pair<STAmount, STAmount>
|
||||
adjustAssetInByTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& balance,
|
||||
STAmount const& amount,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& tokens,
|
||||
std::uint16_t tfee);
|
||||
std::pair<STAmount, STAmount>
|
||||
adjustAssetOutByTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& balance,
|
||||
STAmount const& amount,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& tokens,
|
||||
std::uint16_t tfee);
|
||||
|
||||
/** Find a fraction of tokens after the tokens are adjusted. The fraction
|
||||
* is used to adjust equal deposit/withdraw amount.
|
||||
*/
|
||||
Number
|
||||
adjustFracByTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& tokens,
|
||||
Number const& frac);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED
|
||||
|
||||
@@ -27,6 +27,10 @@ ammLPTokens(
|
||||
STAmount const& asset2,
|
||||
Issue const& lptIssue)
|
||||
{
|
||||
// AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance
|
||||
auto const rounding =
|
||||
isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround();
|
||||
NumberRoundModeGuard g(rounding);
|
||||
auto const tokens = root2(asset1 * asset2);
|
||||
return toSTAmount(lptIssue, tokens);
|
||||
}
|
||||
@@ -38,7 +42,7 @@ ammLPTokens(
|
||||
* where f1 = 1 - tfee, f2 = (1 - tfee/2)/f1
|
||||
*/
|
||||
STAmount
|
||||
lpTokensIn(
|
||||
lpTokensOut(
|
||||
STAmount const& asset1Balance,
|
||||
STAmount const& asset1Deposit,
|
||||
STAmount const& lptAMMBalance,
|
||||
@@ -48,8 +52,17 @@ lpTokensIn(
|
||||
auto const f2 = feeMultHalf(tfee) / f1;
|
||||
Number const r = asset1Deposit / asset1Balance;
|
||||
auto const c = root2(f2 * f2 + r / f1) - f2;
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
auto const t = lptAMMBalance * (r - c) / (1 + c);
|
||||
return toSTAmount(lptAMMBalance.issue(), t);
|
||||
}
|
||||
else
|
||||
{
|
||||
// minimize tokens out
|
||||
auto const frac = (r - c) / (1 + c);
|
||||
return multiply(lptAMMBalance, frac, Number::downward);
|
||||
}
|
||||
}
|
||||
|
||||
/* Equation 4 solves equation 3 for b:
|
||||
@@ -78,8 +91,17 @@ ammAssetIn(
|
||||
auto const a = 1 / (t2 * t2);
|
||||
auto const b = 2 * d / t2 - 1 / f1;
|
||||
auto const c = d * d - f2 * f2;
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
return toSTAmount(
|
||||
asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c));
|
||||
}
|
||||
else
|
||||
{
|
||||
// maximize deposit
|
||||
auto const frac = solveQuadraticEq(a, b, c);
|
||||
return multiply(asset1Balance, frac, Number::upward);
|
||||
}
|
||||
}
|
||||
|
||||
/* Equation 7:
|
||||
@@ -87,7 +109,7 @@ ammAssetIn(
|
||||
* where R = b/B, c = R*fee + 2 - fee
|
||||
*/
|
||||
STAmount
|
||||
lpTokensOut(
|
||||
lpTokensIn(
|
||||
STAmount const& asset1Balance,
|
||||
STAmount const& asset1Withdraw,
|
||||
STAmount const& lptAMMBalance,
|
||||
@@ -96,8 +118,17 @@ lpTokensOut(
|
||||
Number const fr = asset1Withdraw / asset1Balance;
|
||||
auto const f1 = getFee(tfee);
|
||||
auto const c = fr * f1 + 2 - f1;
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2;
|
||||
return toSTAmount(lptAMMBalance.issue(), t);
|
||||
}
|
||||
else
|
||||
{
|
||||
// maximize tokens in
|
||||
auto const frac = (c - root2(c * c - 4 * fr)) / 2;
|
||||
return multiply(lptAMMBalance, frac, Number::upward);
|
||||
}
|
||||
}
|
||||
|
||||
/* Equation 8 solves equation 7 for b:
|
||||
@@ -111,7 +142,7 @@ lpTokensOut(
|
||||
* R = (t1**2 + t1*(f - 2)) / (t1*f - 1)
|
||||
*/
|
||||
STAmount
|
||||
withdrawByTokens(
|
||||
ammAssetOut(
|
||||
STAmount const& assetBalance,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokens,
|
||||
@@ -119,8 +150,17 @@ withdrawByTokens(
|
||||
{
|
||||
auto const f = getFee(tfee);
|
||||
Number const t1 = lpTokens / lptAMMBalance;
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
|
||||
return toSTAmount(assetBalance.issue(), b);
|
||||
}
|
||||
else
|
||||
{
|
||||
// minimize withdraw
|
||||
auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
|
||||
return multiply(assetBalance, frac, Number::downward);
|
||||
}
|
||||
}
|
||||
|
||||
Number
|
||||
@@ -133,12 +173,12 @@ STAmount
|
||||
adjustLPTokens(
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokens,
|
||||
bool isDeposit)
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
// Force rounding downward to ensure adjusted tokens are less or equal
|
||||
// to requested tokens.
|
||||
saveNumberRoundMode rm(Number::setround(Number::rounding_mode::downward));
|
||||
if (isDeposit)
|
||||
if (isDeposit == IsDeposit::Yes)
|
||||
return (lptAMMBalance + lpTokens) - lptAMMBalance;
|
||||
return (lpTokens - lptAMMBalance) + lptAMMBalance;
|
||||
}
|
||||
@@ -151,8 +191,12 @@ adjustAmountsByLPTokens(
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokens,
|
||||
std::uint16_t tfee,
|
||||
bool isDeposit)
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
// AMMv1_3 amendment adjusts tokens and amounts in deposit/withdraw
|
||||
if (isFeatureEnabled(fixAMMv1_3))
|
||||
return std::make_tuple(amount, amount2, lpTokens);
|
||||
|
||||
auto const lpTokensActual =
|
||||
adjustLPTokens(lptAMMBalance, lpTokens, isDeposit);
|
||||
|
||||
@@ -191,14 +235,14 @@ adjustAmountsByLPTokens(
|
||||
|
||||
// Single trade
|
||||
auto const amountActual = [&]() {
|
||||
if (isDeposit)
|
||||
if (isDeposit == IsDeposit::Yes)
|
||||
return ammAssetIn(
|
||||
amountBalance, lptAMMBalance, lpTokensActual, tfee);
|
||||
else if (!ammRoundingEnabled)
|
||||
return withdrawByTokens(
|
||||
return ammAssetOut(
|
||||
amountBalance, lptAMMBalance, lpTokens, tfee);
|
||||
else
|
||||
return withdrawByTokens(
|
||||
return ammAssetOut(
|
||||
amountBalance, lptAMMBalance, lpTokensActual, tfee);
|
||||
}();
|
||||
if (!ammRoundingEnabled)
|
||||
@@ -237,4 +281,132 @@ solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c)
|
||||
return (2 * c) / (-b + root2(d));
|
||||
}
|
||||
|
||||
STAmount
|
||||
multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm)
|
||||
{
|
||||
NumberRoundModeGuard g(rm);
|
||||
auto const t = amount * frac;
|
||||
return toSTAmount(amount.issue(), t, rm);
|
||||
}
|
||||
|
||||
STAmount
|
||||
getRoundedAsset(
|
||||
Rules const& rules,
|
||||
std::function<Number()>&& noRoundCb,
|
||||
STAmount const& balance,
|
||||
std::function<Number()>&& productCb,
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return toSTAmount(balance.issue(), noRoundCb());
|
||||
|
||||
auto const rm = detail::getAssetRounding(isDeposit);
|
||||
if (isDeposit == IsDeposit::Yes)
|
||||
return multiply(balance, productCb(), rm);
|
||||
NumberRoundModeGuard g(rm);
|
||||
return toSTAmount(balance.issue(), productCb(), rm);
|
||||
}
|
||||
|
||||
STAmount
|
||||
getRoundedLPTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& balance,
|
||||
Number const& frac,
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return toSTAmount(balance.issue(), balance * frac);
|
||||
|
||||
auto const rm = detail::getLPTokenRounding(isDeposit);
|
||||
auto const tokens = multiply(balance, frac, rm);
|
||||
return adjustLPTokens(balance, tokens, isDeposit);
|
||||
}
|
||||
|
||||
STAmount
|
||||
getRoundedLPTokens(
|
||||
Rules const& rules,
|
||||
std::function<Number()>&& noRoundCb,
|
||||
STAmount const& lptAMMBalance,
|
||||
std::function<Number()>&& productCb,
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return toSTAmount(lptAMMBalance.issue(), noRoundCb());
|
||||
|
||||
auto const tokens = [&] {
|
||||
auto const rm = detail::getLPTokenRounding(isDeposit);
|
||||
if (isDeposit == IsDeposit::Yes)
|
||||
{
|
||||
NumberRoundModeGuard g(rm);
|
||||
return toSTAmount(lptAMMBalance.issue(), productCb(), rm);
|
||||
}
|
||||
return multiply(lptAMMBalance, productCb(), rm);
|
||||
}();
|
||||
return adjustLPTokens(lptAMMBalance, tokens, isDeposit);
|
||||
}
|
||||
|
||||
std::pair<STAmount, STAmount>
|
||||
adjustAssetInByTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& balance,
|
||||
STAmount const& amount,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& tokens,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return {tokens, amount};
|
||||
auto assetAdj = ammAssetIn(balance, lptAMMBalance, tokens, tfee);
|
||||
auto tokensAdj = tokens;
|
||||
// Rounding didn't work the right way.
|
||||
// Try to adjust the original deposit amount by difference
|
||||
// in adjust and original amount. Then adjust tokens and deposit amount.
|
||||
if (assetAdj > amount)
|
||||
{
|
||||
auto const adjAmount = amount - (assetAdj - amount);
|
||||
auto const t = lpTokensOut(balance, adjAmount, lptAMMBalance, tfee);
|
||||
tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::Yes);
|
||||
assetAdj = ammAssetIn(balance, lptAMMBalance, tokensAdj, tfee);
|
||||
}
|
||||
return {tokensAdj, std::min(amount, assetAdj)};
|
||||
}
|
||||
|
||||
std::pair<STAmount, STAmount>
|
||||
adjustAssetOutByTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& balance,
|
||||
STAmount const& amount,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& tokens,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return {tokens, amount};
|
||||
auto assetAdj = ammAssetOut(balance, lptAMMBalance, tokens, tfee);
|
||||
auto tokensAdj = tokens;
|
||||
// Rounding didn't work the right way.
|
||||
// Try to adjust the original deposit amount by difference
|
||||
// in adjust and original amount. Then adjust tokens and deposit amount.
|
||||
if (assetAdj > amount)
|
||||
{
|
||||
auto const adjAmount = amount - (assetAdj - amount);
|
||||
auto const t = lpTokensIn(balance, adjAmount, lptAMMBalance, tfee);
|
||||
tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::No);
|
||||
assetAdj = ammAssetOut(balance, lptAMMBalance, tokensAdj, tfee);
|
||||
}
|
||||
return {tokensAdj, std::min(amount, assetAdj)};
|
||||
}
|
||||
|
||||
Number
|
||||
adjustFracByTokens(
|
||||
Rules const& rules,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& tokens,
|
||||
Number const& frac)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return frac;
|
||||
return tokens / lptAMMBalance;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -78,6 +78,21 @@ AMMBid::preflight(PreflightContext const& ctx)
|
||||
JLOG(ctx.j.debug()) << "AMM Bid: Invalid number of AuthAccounts.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
else if (ctx.rules.enabled(fixAMMv1_3))
|
||||
{
|
||||
AccountID account = ctx.tx[sfAccount];
|
||||
std::set<AccountID> unique;
|
||||
for (auto const& obj : authAccounts)
|
||||
{
|
||||
auto authAccount = obj[sfAccount];
|
||||
if (authAccount == account || unique.contains(authAccount))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Bid: Invalid auth.account.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
unique.insert(authAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
@@ -232,7 +247,9 @@ applyBid(
|
||||
auctionSlot.makeFieldAbsent(sfAuthAccounts);
|
||||
// Burn the remaining bid amount
|
||||
auto const saBurn = adjustLPTokens(
|
||||
lptAMMBalance, toSTAmount(lptAMMBalance.issue(), burn), false);
|
||||
lptAMMBalance,
|
||||
toSTAmount(lptAMMBalance.issue(), burn),
|
||||
IsDeposit::No);
|
||||
if (saBurn >= lptAMMBalance)
|
||||
{
|
||||
// This error case should never occur.
|
||||
|
||||
@@ -542,7 +542,7 @@ AMMDeposit::deposit(
|
||||
lptAMMBalance,
|
||||
lpTokensDeposit,
|
||||
tfee,
|
||||
true);
|
||||
IsDeposit::Yes);
|
||||
|
||||
if (lpTokensDepositActual <= beast::zero)
|
||||
{
|
||||
@@ -625,6 +625,17 @@ AMMDeposit::deposit(
|
||||
return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
|
||||
}
|
||||
|
||||
static STAmount
|
||||
adjustLPTokensOut(
|
||||
Rules const& rules,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokensDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return lpTokensDeposit;
|
||||
return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes);
|
||||
}
|
||||
|
||||
/** Proportional deposit of pools assets in exchange for the specified
|
||||
* amount of LPTokens.
|
||||
*/
|
||||
@@ -642,16 +653,25 @@ AMMDeposit::equalDepositTokens(
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const tokensAdj =
|
||||
adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
auto const frac =
|
||||
divide(lpTokensDeposit, lptAMMBalance, lptAMMBalance.issue());
|
||||
divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue());
|
||||
// amounts factor in the adjusted tokens
|
||||
auto const amountDeposit =
|
||||
getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
|
||||
auto const amount2Deposit =
|
||||
getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
|
||||
return deposit(
|
||||
view,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
multiply(amountBalance, frac, amountBalance.issue()),
|
||||
multiply(amount2Balance, frac, amount2Balance.issue()),
|
||||
amountDeposit,
|
||||
amount2Deposit,
|
||||
lptAMMBalance,
|
||||
lpTokensDeposit,
|
||||
tokensAdj,
|
||||
depositMin,
|
||||
deposit2Min,
|
||||
std::nullopt,
|
||||
@@ -708,37 +728,55 @@ AMMDeposit::equalDepositLimit(
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto frac = Number{amount} / amountBalance;
|
||||
auto tokens = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
|
||||
if (tokens == beast::zero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
auto const amount2Deposit = amount2Balance * frac;
|
||||
auto tokensAdj =
|
||||
getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
|
||||
if (tokensAdj == beast::zero)
|
||||
{
|
||||
if (!view.rules().enabled(fixAMMv1_3))
|
||||
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
||||
else
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
}
|
||||
// factor in the adjusted tokens
|
||||
frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
|
||||
auto const amount2Deposit =
|
||||
getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
|
||||
if (amount2Deposit <= amount2)
|
||||
return deposit(
|
||||
view,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amount,
|
||||
toSTAmount(amount2Balance.issue(), amount2Deposit),
|
||||
amount2Deposit,
|
||||
lptAMMBalance,
|
||||
tokens,
|
||||
tokensAdj,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
lpTokensDepositMin,
|
||||
tfee);
|
||||
frac = Number{amount2} / amount2Balance;
|
||||
tokens = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
|
||||
if (tokens == beast::zero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
auto const amountDeposit = amountBalance * frac;
|
||||
tokensAdj =
|
||||
getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
|
||||
if (tokensAdj == beast::zero)
|
||||
{
|
||||
if (!view.rules().enabled(fixAMMv1_3))
|
||||
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
||||
else
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
||||
}
|
||||
// factor in the adjusted tokens
|
||||
frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
|
||||
auto const amountDeposit =
|
||||
getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
|
||||
if (amountDeposit <= amount)
|
||||
return deposit(
|
||||
view,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
toSTAmount(amountBalance.issue(), amountDeposit),
|
||||
amountDeposit,
|
||||
amount2,
|
||||
lptAMMBalance,
|
||||
tokens,
|
||||
tokensAdj,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
lpTokensDepositMin,
|
||||
@@ -764,17 +802,30 @@ AMMDeposit::singleDeposit(
|
||||
std::optional<STAmount> const& lpTokensDepositMin,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const tokens = lpTokensIn(amountBalance, amount, lptAMMBalance, tfee);
|
||||
auto const tokens = adjustLPTokensOut(
|
||||
view.rules(),
|
||||
lptAMMBalance,
|
||||
lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
|
||||
if (tokens == beast::zero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
{
|
||||
if (!view.rules().enabled(fixAMMv1_3))
|
||||
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
||||
else
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
}
|
||||
// factor in the adjusted tokens
|
||||
auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
|
||||
view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
||||
return deposit(
|
||||
view,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amount,
|
||||
amountDepositAdj,
|
||||
std::nullopt,
|
||||
lptAMMBalance,
|
||||
tokens,
|
||||
tokensAdj,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
lpTokensDepositMin,
|
||||
@@ -798,8 +849,13 @@ AMMDeposit::singleDepositTokens(
|
||||
STAmount const& lpTokensDeposit,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const tokensAdj =
|
||||
adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
// the adjusted tokens are factored in
|
||||
auto const amountDeposit =
|
||||
ammAssetIn(amountBalance, lptAMMBalance, lpTokensDeposit, tfee);
|
||||
ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
|
||||
if (amountDeposit > amount)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
return deposit(
|
||||
@@ -809,7 +865,7 @@ AMMDeposit::singleDepositTokens(
|
||||
amountDeposit,
|
||||
std::nullopt,
|
||||
lptAMMBalance,
|
||||
lpTokensDeposit,
|
||||
tokensAdj,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
@@ -853,20 +909,32 @@ AMMDeposit::singleDepositEPrice(
|
||||
{
|
||||
if (amount != beast::zero)
|
||||
{
|
||||
auto const tokens =
|
||||
lpTokensIn(amountBalance, amount, lptAMMBalance, tfee);
|
||||
auto const tokens = adjustLPTokensOut(
|
||||
view.rules(),
|
||||
lptAMMBalance,
|
||||
lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
|
||||
if (tokens <= beast::zero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
auto const ep = Number{amount} / tokens;
|
||||
{
|
||||
if (!view.rules().enabled(fixAMMv1_3))
|
||||
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
||||
else
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
}
|
||||
// factor in the adjusted tokens
|
||||
auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
|
||||
view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
||||
auto const ep = Number{amountDepositAdj} / tokensAdj;
|
||||
if (ep <= ePrice)
|
||||
return deposit(
|
||||
view,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amount,
|
||||
amountDepositAdj,
|
||||
std::nullopt,
|
||||
lptAMMBalance,
|
||||
tokens,
|
||||
tokensAdj,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
@@ -897,21 +965,37 @@ AMMDeposit::singleDepositEPrice(
|
||||
auto const a1 = c * c;
|
||||
auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
|
||||
auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
|
||||
auto const amountDeposit = toSTAmount(
|
||||
amountBalance.issue(),
|
||||
f1 * amountBalance * solveQuadraticEq(a1, b1, c1));
|
||||
auto amtNoRoundCb = [&] {
|
||||
return f1 * amountBalance * solveQuadraticEq(a1, b1, c1);
|
||||
};
|
||||
auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); };
|
||||
auto const amountDeposit = getRoundedAsset(
|
||||
view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes);
|
||||
if (amountDeposit <= beast::zero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
auto const tokens =
|
||||
toSTAmount(lptAMMBalance.issue(), amountDeposit / ePrice);
|
||||
auto tokNoRoundCb = [&] { return amountDeposit / ePrice; };
|
||||
auto tokProdCb = [&] { return amountDeposit / ePrice; };
|
||||
auto const tokens = getRoundedLPTokens(
|
||||
view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes);
|
||||
// factor in the adjusted tokens
|
||||
auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
|
||||
view.rules(),
|
||||
amountBalance,
|
||||
amountDeposit,
|
||||
lptAMMBalance,
|
||||
tokens,
|
||||
tfee);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
||||
|
||||
return deposit(
|
||||
view,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amountDeposit,
|
||||
amountDepositAdj,
|
||||
std::nullopt,
|
||||
lptAMMBalance,
|
||||
tokens,
|
||||
tokensAdj,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
|
||||
@@ -522,7 +522,7 @@ AMMWithdraw::withdraw(
|
||||
lpTokensAMMBalance,
|
||||
lpTokensWithdraw,
|
||||
tfee,
|
||||
false);
|
||||
IsDeposit::No);
|
||||
return std::make_tuple(
|
||||
amountWithdraw, amount2Withdraw, lpTokensWithdraw);
|
||||
}();
|
||||
@@ -683,6 +683,20 @@ AMMWithdraw::withdraw(
|
||||
amount2WithdrawActual);
|
||||
}
|
||||
|
||||
static STAmount
|
||||
adjustLPTokensIn(
|
||||
Rules const& rules,
|
||||
STAmount const& lptAMMBalance,
|
||||
STAmount const& lpTokensWithdraw,
|
||||
WithdrawAll withdrawAll)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
|
||||
return lpTokensWithdraw;
|
||||
return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
|
||||
}
|
||||
|
||||
/** Proportional withdrawal of pool assets for the amount of LPTokens.
|
||||
*/
|
||||
std::pair<TER, STAmount>
|
||||
AMMWithdraw::equalWithdrawTokens(
|
||||
Sandbox& view,
|
||||
@@ -786,16 +800,22 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
journal);
|
||||
}
|
||||
|
||||
auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue());
|
||||
auto const withdrawAmount =
|
||||
multiply(amountBalance, frac, amountBalance.issue());
|
||||
auto const withdraw2Amount =
|
||||
multiply(amount2Balance, frac, amount2Balance.issue());
|
||||
auto const tokensAdj = adjustLPTokensIn(
|
||||
view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {
|
||||
tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt};
|
||||
// the adjusted tokens are factored in
|
||||
auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
|
||||
auto const amountWithdraw =
|
||||
getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
|
||||
auto const amount2Withdraw =
|
||||
getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
|
||||
// LP is making equal withdrawal by tokens but the requested amount
|
||||
// of LP tokens is likely too small and results in one-sided pool
|
||||
// withdrawal due to round off. Fail so the user withdraws
|
||||
// more tokens.
|
||||
if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero)
|
||||
if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
|
||||
return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
|
||||
|
||||
return withdraw(
|
||||
@@ -804,10 +824,10 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
ammAccount,
|
||||
account,
|
||||
amountBalance,
|
||||
withdrawAmount,
|
||||
withdraw2Amount,
|
||||
amountWithdraw,
|
||||
amount2Withdraw,
|
||||
lptAMMBalance,
|
||||
lpTokensWithdraw,
|
||||
tokensAdj,
|
||||
tfee,
|
||||
freezeHanding,
|
||||
withdrawAll,
|
||||
@@ -862,7 +882,16 @@ AMMWithdraw::equalWithdrawLimit(
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto frac = Number{amount} / amountBalance;
|
||||
auto const amount2Withdraw = amount2Balance * frac;
|
||||
auto amount2Withdraw =
|
||||
getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
|
||||
auto tokensAdj =
|
||||
getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
// factor in the adjusted tokens
|
||||
frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
|
||||
amount2Withdraw =
|
||||
getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
|
||||
if (amount2Withdraw <= amount2)
|
||||
{
|
||||
return withdraw(
|
||||
@@ -871,26 +900,42 @@ AMMWithdraw::equalWithdrawLimit(
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amount,
|
||||
toSTAmount(amount2.issue(), amount2Withdraw),
|
||||
amount2Withdraw,
|
||||
lptAMMBalance,
|
||||
toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
|
||||
tokensAdj,
|
||||
tfee);
|
||||
}
|
||||
|
||||
frac = Number{amount2} / amount2Balance;
|
||||
auto const amountWithdraw = amountBalance * frac;
|
||||
auto amountWithdraw =
|
||||
getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
|
||||
tokensAdj =
|
||||
getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
||||
// factor in the adjusted tokens
|
||||
frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
|
||||
amountWithdraw =
|
||||
getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
|
||||
if (!view.rules().enabled(fixAMMv1_3))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
XRPL_ASSERT(
|
||||
amountWithdraw <= amount,
|
||||
"ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
else if (amountWithdraw > amount)
|
||||
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
||||
return withdraw(
|
||||
view,
|
||||
ammSle,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
toSTAmount(amount.issue(), amountWithdraw),
|
||||
amountWithdraw,
|
||||
amount2,
|
||||
lptAMMBalance,
|
||||
toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
|
||||
tokensAdj,
|
||||
tfee);
|
||||
}
|
||||
|
||||
@@ -909,19 +954,32 @@ AMMWithdraw::singleWithdraw(
|
||||
STAmount const& amount,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee);
|
||||
auto const tokens = adjustLPTokensIn(
|
||||
view.rules(),
|
||||
lptAMMBalance,
|
||||
lpTokensIn(amountBalance, amount, lptAMMBalance, tfee),
|
||||
isWithdrawAll(ctx_.tx));
|
||||
if (tokens == beast::zero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
|
||||
{
|
||||
if (!view.rules().enabled(fixAMMv1_3))
|
||||
return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
|
||||
else
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
}
|
||||
// factor in the adjusted tokens
|
||||
auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens(
|
||||
view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
|
||||
return withdraw(
|
||||
view,
|
||||
ammSle,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amount,
|
||||
amountWithdrawAdj,
|
||||
std::nullopt,
|
||||
lptAMMBalance,
|
||||
tokens,
|
||||
tokensAdj,
|
||||
tfee);
|
||||
}
|
||||
|
||||
@@ -946,8 +1004,13 @@ AMMWithdraw::singleWithdrawTokens(
|
||||
STAmount const& lpTokensWithdraw,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const tokensAdj = adjustLPTokensIn(
|
||||
view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
|
||||
if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
// the adjusted tokens are factored in
|
||||
auto const amountWithdraw =
|
||||
withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee);
|
||||
ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
|
||||
if (amount == beast::zero || amountWithdraw >= amount)
|
||||
{
|
||||
return withdraw(
|
||||
@@ -958,7 +1021,7 @@ AMMWithdraw::singleWithdrawTokens(
|
||||
amountWithdraw,
|
||||
std::nullopt,
|
||||
lptAMMBalance,
|
||||
lpTokensWithdraw,
|
||||
tokensAdj,
|
||||
tfee);
|
||||
}
|
||||
|
||||
@@ -1007,11 +1070,27 @@ AMMWithdraw::singleWithdrawEPrice(
|
||||
// t = T*(T + A*E*(f - 2))/(T*f - A*E)
|
||||
Number const ae = amountBalance * ePrice;
|
||||
auto const f = getFee(tfee);
|
||||
auto const tokens = lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
|
||||
auto tokNoRoundCb = [&] {
|
||||
return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
|
||||
(lptAMMBalance * f - ae);
|
||||
if (tokens <= 0)
|
||||
};
|
||||
auto tokProdCb = [&] {
|
||||
return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
|
||||
};
|
||||
auto const tokensAdj = getRoundedLPTokens(
|
||||
view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
|
||||
if (tokensAdj <= beast::zero)
|
||||
{
|
||||
if (!view.rules().enabled(fixAMMv1_3))
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice);
|
||||
else
|
||||
return {tecAMM_INVALID_TOKENS, STAmount{}};
|
||||
}
|
||||
auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
|
||||
auto amtProdCb = [&] { return tokensAdj / ePrice; };
|
||||
// the adjusted tokens are factored in
|
||||
auto const amountWithdraw = getRoundedAsset(
|
||||
view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
|
||||
if (amount == beast::zero || amountWithdraw >= amount)
|
||||
{
|
||||
return withdraw(
|
||||
@@ -1022,7 +1101,7 @@ AMMWithdraw::singleWithdrawEPrice(
|
||||
amountWithdraw,
|
||||
std::nullopt,
|
||||
lptAMMBalance,
|
||||
toSTAmount(lptAMMBalance.issue(), tokens),
|
||||
tokensAdj,
|
||||
tfee);
|
||||
}
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ private:
|
||||
std::uint16_t tfee);
|
||||
|
||||
/** Check from the flags if it's withdraw all */
|
||||
WithdrawAll
|
||||
static WithdrawAll
|
||||
isWithdrawAll(STTx const& tx);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/AMMHelpers.h>
|
||||
#include <xrpld/app/misc/AMMUtils.h>
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/tx/detail/InvariantCheck.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
@@ -1663,4 +1665,309 @@ ValidPermissionedDEX::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ValidAMM::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (isDelete)
|
||||
return;
|
||||
|
||||
if (after)
|
||||
{
|
||||
auto const type = after->getType();
|
||||
// AMM object changed
|
||||
if (type == ltAMM)
|
||||
{
|
||||
ammAccount_ = after->getAccountID(sfAccount);
|
||||
lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
|
||||
}
|
||||
// AMM pool changed
|
||||
else if (
|
||||
(type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
|
||||
(type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
|
||||
{
|
||||
ammPoolChanged_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (before)
|
||||
{
|
||||
// AMM object changed
|
||||
if (before->getType() == ltAMM)
|
||||
{
|
||||
lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
validBalances(
|
||||
STAmount const& amount,
|
||||
STAmount const& amount2,
|
||||
STAmount const& lptAMMBalance,
|
||||
ValidAMM::ZeroAllowed zeroAllowed)
|
||||
{
|
||||
bool const positive = amount > beast::zero && amount2 > beast::zero &&
|
||||
lptAMMBalance > beast::zero;
|
||||
if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
|
||||
return positive ||
|
||||
(amount == beast::zero && amount2 == beast::zero &&
|
||||
lptAMMBalance == beast::zero);
|
||||
return positive;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
if (lptAMMBalanceAfter_ != lptAMMBalanceBefore_ || ammPoolChanged_)
|
||||
{
|
||||
// LPTokens and the pool can not change on vote
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMVote invariant failed: "
|
||||
<< lptAMMBalanceBefore_.value_or(STAmount{}) << " "
|
||||
<< lptAMMBalanceAfter_.value_or(STAmount{}) << " "
|
||||
<< ammPoolChanged_;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
if (ammPoolChanged_)
|
||||
{
|
||||
// The pool can not change on bid
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMBid invariant failed: pool changed";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// LPTokens are burnt, therefore there should be fewer LPTokens
|
||||
else if (
|
||||
lptAMMBalanceBefore_ && lptAMMBalanceAfter_ &&
|
||||
(*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ ||
|
||||
*lptAMMBalanceAfter_ <= beast::zero))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
|
||||
<< " " << *lptAMMBalanceAfter_;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeCreate(
|
||||
STTx const& tx,
|
||||
ReadView const& view,
|
||||
bool enforce,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error())
|
||||
<< "AMMCreate invariant failed: AMM object is not created";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const [amount, amount2] = ammPoolHolds(
|
||||
view,
|
||||
*ammAccount_,
|
||||
tx[sfAmount].get<Issue>(),
|
||||
tx[sfAmount2].get<Issue>(),
|
||||
fhIGNORE_FREEZE,
|
||||
j);
|
||||
// Create invariant:
|
||||
// sqrt(amount * amount2) == LPTokens
|
||||
// all balances are greater than zero
|
||||
if (!validBalances(
|
||||
amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
|
||||
ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
|
||||
*lptAMMBalanceAfter_)
|
||||
{
|
||||
JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
|
||||
<< amount2 << " " << *lptAMMBalanceAfter_;
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
|
||||
{
|
||||
if (ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
std::string const msg = (res == tesSUCCESS)
|
||||
? "AMM object is not deleted on tesSUCCESS"
|
||||
: "AMM object is changed on tecINCOMPLETE";
|
||||
JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
if (ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::generalInvariant(
|
||||
ripple::STTx const& tx,
|
||||
ripple::ReadView const& view,
|
||||
ZeroAllowed zeroAllowed,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
auto const [amount, amount2] = ammPoolHolds(
|
||||
view,
|
||||
*ammAccount_,
|
||||
tx[sfAsset].get<Issue>(),
|
||||
tx[sfAsset2].get<Issue>(),
|
||||
fhIGNORE_FREEZE,
|
||||
j);
|
||||
// Deposit and Withdrawal invariant:
|
||||
// sqrt(amount * amount2) >= LPTokens
|
||||
// all balances are greater than zero
|
||||
// unless on last withdrawal
|
||||
auto const poolProductMean = root2(amount * amount2);
|
||||
bool const nonNegativeBalances =
|
||||
validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
|
||||
bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
|
||||
// Allow for a small relative error if strongInvariantCheck fails
|
||||
auto weakInvariantCheck = [&]() {
|
||||
return *lptAMMBalanceAfter_ != beast::zero &&
|
||||
withinRelativeDistance(
|
||||
poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
|
||||
};
|
||||
if (!nonNegativeBalances ||
|
||||
(!strongInvariantCheck && !weakInvariantCheck()))
|
||||
{
|
||||
JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
|
||||
<< tx.getHash(HashPrefix::transactionID) << " "
|
||||
<< ammPoolChanged_ << " " << amount << " " << amount2
|
||||
<< " " << poolProductMean << " "
|
||||
<< lptAMMBalanceAfter_->getText() << " "
|
||||
<< ((*lptAMMBalanceAfter_ == beast::zero)
|
||||
? Number{1}
|
||||
: ((*lptAMMBalanceAfter_ - poolProductMean) /
|
||||
poolProductMean));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeDeposit(
|
||||
ripple::STTx const& tx,
|
||||
ripple::ReadView const& view,
|
||||
bool enforce,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeWithdraw(
|
||||
ripple::STTx const& tx,
|
||||
ripple::ReadView const& view,
|
||||
bool enforce,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// Last Withdraw or Clawback deleted AMM
|
||||
}
|
||||
else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
|
||||
{
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Delete may return tecINCOMPLETE if there are too many
|
||||
// trustlines to delete.
|
||||
if (result != tesSUCCESS && result != tecINCOMPLETE)
|
||||
return true;
|
||||
|
||||
bool const enforce = view.rules().enabled(fixAMMv1_3);
|
||||
|
||||
switch (tx.getTxnType())
|
||||
{
|
||||
case ttAMM_CREATE:
|
||||
return finalizeCreate(tx, view, enforce, j);
|
||||
case ttAMM_DEPOSIT:
|
||||
return finalizeDeposit(tx, view, enforce, j);
|
||||
case ttAMM_CLAWBACK:
|
||||
case ttAMM_WITHDRAW:
|
||||
return finalizeWithdraw(tx, view, enforce, j);
|
||||
case ttAMM_BID:
|
||||
return finalizeBid(enforce, j);
|
||||
case ttAMM_VOTE:
|
||||
return finalizeVote(enforce, j);
|
||||
case ttAMM_DELETE:
|
||||
return finalizeDelete(enforce, result, j);
|
||||
case ttCHECK_CASH:
|
||||
case ttOFFER_CREATE:
|
||||
case ttPAYMENT:
|
||||
return finalizeDEX(enforce, j);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -641,6 +641,69 @@ public:
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
class ValidAMM
|
||||
{
|
||||
std::optional<AccountID> ammAccount_;
|
||||
std::optional<STAmount> lptAMMBalanceAfter_;
|
||||
std::optional<STAmount> lptAMMBalanceBefore_;
|
||||
bool ammPoolChanged_;
|
||||
|
||||
public:
|
||||
enum class ZeroAllowed : bool { No = false, Yes = true };
|
||||
|
||||
ValidAMM() : ammPoolChanged_{false}
|
||||
{
|
||||
}
|
||||
void
|
||||
visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const&);
|
||||
|
||||
private:
|
||||
bool
|
||||
finalizeBid(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeVote(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeCreate(
|
||||
STTx const&,
|
||||
ReadView const&,
|
||||
bool enforce,
|
||||
beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDelete(bool enforce, TER res, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDeposit(
|
||||
STTx const&,
|
||||
ReadView const&,
|
||||
bool enforce,
|
||||
beast::Journal const&) const;
|
||||
// Includes clawback
|
||||
bool
|
||||
finalizeWithdraw(
|
||||
STTx const&,
|
||||
ReadView const&,
|
||||
bool enforce,
|
||||
beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDEX(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
generalInvariant(
|
||||
STTx const&,
|
||||
ReadView const&,
|
||||
ZeroAllowed zeroAllowed,
|
||||
beast::Journal const&) const;
|
||||
};
|
||||
|
||||
// additional invariant checks can be declared above and then added to this
|
||||
// tuple
|
||||
using InvariantChecks = std::tuple<
|
||||
@@ -661,7 +724,8 @@ using InvariantChecks = std::tuple<
|
||||
ValidClawback,
|
||||
ValidMPTIssuance,
|
||||
ValidPermissionedDomain,
|
||||
ValidPermissionedDEX>;
|
||||
ValidPermissionedDEX,
|
||||
ValidAMM>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
@@ -170,8 +171,24 @@ public:
|
||||
* always returns true.
|
||||
*/
|
||||
bool
|
||||
checkInvariant(TAmounts<TIn, TOut> const&, beast::Journal j) const
|
||||
checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::Journal j) const
|
||||
{
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
return true;
|
||||
|
||||
if (consumed.in > m_amounts.in || consumed.out > m_amounts.out)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error())
|
||||
<< "AMMOffer::checkInvariant failed: consumed "
|
||||
<< to_string(consumed.in) << " " << to_string(consumed.out)
|
||||
<< " amounts " << to_string(m_amounts.in) << " "
|
||||
<< to_string(m_amounts.out);
|
||||
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user