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:
Gregory Tsipenyuk
2025-06-02 09:52:10 -04:00
committed by GitHub
parent 0a34b5c691
commit 621df422a7
22 changed files with 2515 additions and 574 deletions

View File

@@ -581,8 +581,12 @@ class AMMClawback_test : public jtx::AMMTest
AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(amm.expectBalances(
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
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.
BEAST_EXPECT(amm.expectBalances(
USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
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)));
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(2500000000000001), -12},
STAmount{EUR, UINT64_C(3125000000000001), -12},
IOUAmount{2795084971874738, -12}));
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}));
BEAST_EXPECT(
env.balance(alice, EUR) ==
STAmount(EUR, UINT64_C(2874999999999999), -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)));
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(2499000000000002), -12},
STAmount{EUR, UINT64_C(3123750000000002), -12},
IOUAmount{2793966937885989, -12}));
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}));
BEAST_EXPECT(
env.balance(alice, EUR) ==
STAmount(EUR, UINT64_C(2876249999999998), -12));
if (!features[fixAMMv1_3])
BEAST_EXPECT(
env.balance(alice, EUR) ==
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));
BEAST_EXPECT(amm2.expectBalances(
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
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));
BEAST_EXPECT(amm2.expectBalances(
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
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));
BEAST_EXPECT(amm2.expectBalances(
EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
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)));
BEAST_EXPECT(amm.expectBalances(
USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
BEAST_EXPECT(
amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
if (!features[fixAMMv1_3])
BEAST_EXPECT(amm.expectBalances(
USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
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)));
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(2490000000000001), -12},
XRP(4980),
IOUAmount{3521391770309008, -9}));
BEAST_EXPECT(
amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
BEAST_EXPECT(
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
if (!features[fixAMMv1_3])
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(2490000000000001), -12},
XRP(4980),
IOUAmount{3521391770309008, -9}));
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)));
BEAST_EXPECT(amm2.expectBalances(
EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
BEAST_EXPECT(
amm2.expectLPTokens(alice, IOUAmount{1385640646055103, -9}));
BEAST_EXPECT(
amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
if (!features[fixAMMv1_3])
BEAST_EXPECT(amm2.expectBalances(
EUR(2800), XRP(8400), IOUAmount{4849742261192859, -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.
BEAST_EXPECT(expectLedgerEntryRoot(
env,
alice,
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
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)));
BEAST_EXPECT(
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(1990000000000001), -12},
XRP(3980),
IOUAmount{2814284989122460, -9}));
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)));
BEAST_EXPECT(expectLedgerEntryRoot(
env,
alice,
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
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.
BEAST_EXPECT(expectLedgerEntryRoot(
env,
alice,
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
XRP(2400)));
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)));
BEAST_EXPECT(amm2.expectBalances(
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
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.
BEAST_EXPECT(expectLedgerEntryRoot(
env,
alice,
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
XRP(2400)));
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)));
BEAST_EXPECT(amm2.expectBalances(
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
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();
BEAST_EXPECT(amm.expectBalances(
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
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));
BEAST_EXPECT(amm.expectBalances(
USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
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));
BEAST_EXPECT(amm.expectBalances(
USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
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}));
BEAST_EXPECT(
amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
BEAST_EXPECT(
amm.expectLPTokens(carol, IOUAmount{1118033988749895, -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(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();
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(4999999999999999), -12},
STAmount{EUR, UINT64_C(6249999999999999), -12},
IOUAmount{5590169943749475, -12}));
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));
BEAST_EXPECT(
env.balance(bob, EUR) ==
STAmount(EUR, UINT64_C(5000000000000001), -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();
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(3999999999999999), -12},
STAmount{EUR, UINT64_C(4999999999999999), -12},
IOUAmount{4472135954999580, -12}));
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));
BEAST_EXPECT(amm.expectBalances(
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
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));
BEAST_EXPECT(amm.expectBalances(
USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
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));
BEAST_EXPECT(amm.expectBalances(
USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
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();
BEAST_EXPECT(amm.expectBalances(
USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
BEAST_EXPECT(
expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(200)));
if (!features[fixAMMv1_3])
BEAST_EXPECT(amm.expectBalances(
USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
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();
BEAST_EXPECT(amm.expectBalances(
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
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)));
amm.deposit(carol, USD(2000), EUR(500));
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));
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
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));
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
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));
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
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();
BEAST_EXPECT(amm.expectBalances(
USD(4500),
STAmount(EUR, UINT64_C(9000000000000001), -12),
IOUAmount{6363961030678928, -12}));
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();
BEAST_EXPECT(amm.expectBalances(
USD(2500),
STAmount(EUR, UINT64_C(5000000000000001), -12),
IOUAmount{3535533905932738, -12}));
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)));
amm.deposit(carol, USD(2000), EUR(500));
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));
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
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));
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
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));
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
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();
BEAST_EXPECT(amm.expectBalances(
STAmount(USD, UINT64_C(5656854249492380), -13),
XRP(70.710678),
IOUAmount(200000)));
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)));
BEAST_EXPECT(expectLedgerEntryRoot(
env, alice, aliceXrpBalance + XRP(29.289322)));
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);

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
return (IOUAmount)(product.mantissa() >= 0 ? root2(product)
: root2(-product));
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())
{
}

View File

@@ -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);

View File

@@ -203,98 +203,119 @@ public:
}
void
testVoteAndBid()
testVoteAndBid(FeatureBitset features)
{
testcase("Vote and Bid");
using namespace jtx;
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;
votes.insert({alice.human(), 0});
for (int i = 0; i < 7; ++i)
{
Account a(std::to_string(i));
votes.insert({a.human(), 50 * (i + 1)});
fund(env, gw, {a}, {USD(10000)}, Fund::Acct);
ammAlice.deposit(a, 10000000);
ammAlice.vote(a, 50 * (i + 1));
}
BEAST_EXPECT(ammAlice.expectTradingFee(175));
Account ed("ed");
Account bill("bill");
env.fund(XRP(1000), bob, ed, bill);
env(ammAlice.bid(
{.bidMin = 100, .authAccounts = {carol, bob, ed, bill}}));
BEAST_EXPECT(ammAlice.expectAmmRpcInfo(
XRP(80000),
USD(80000),
IOUAmount{79994400},
std::nullopt,
std::nullopt,
ammAlice.ammAccount()));
for (auto i = 0; i < 2; ++i)
{
std::unordered_set<std::string> authAccounts = {
carol.human(), bob.human(), ed.human(), bill.human()};
auto const ammInfo = i ? ammAlice.ammRpcInfo()
: ammAlice.ammRpcInfo(
std::nullopt,
std::nullopt,
std::nullopt,
std::nullopt,
ammAlice.ammAccount());
auto const& amm = ammInfo[jss::amm];
try
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;
votes.insert({alice.human(), 0});
for (int i = 0; i < 7; ++i)
{
// votes
auto const voteSlots = amm[jss::vote_slots];
auto votesCopy = votes;
for (std::uint8_t i = 0; i < 8; ++i)
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));
}
BEAST_EXPECT(ammAlice.expectTradingFee(175));
Account ed("ed");
Account bill("bill");
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),
IOUAmount{79994400},
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 = {
carol.human(), bob.human(), ed.human(), bill.human()};
auto const ammInfo = i ? ammAlice.ammRpcInfo()
: ammAlice.ammRpcInfo(
std::nullopt,
std::nullopt,
std::nullopt,
std::nullopt,
ammAlice.ammAccount());
auto const& amm = ammInfo[jss::amm];
try
{
if (!BEAST_EXPECT(
votes[voteSlots[i][jss::account].asString()] ==
voteSlots[i][jss::trading_fee].asUInt() &&
voteSlots[i][jss::vote_weight].asUInt() ==
12500))
// votes
auto const voteSlots = amm[jss::vote_slots];
auto votesCopy = votes;
for (std::uint8_t i = 0; i < 8; ++i)
{
if (!BEAST_EXPECT(
votes[voteSlots[i][jss::account]
.asString()] ==
voteSlots[i][jss::trading_fee]
.asUInt() &&
voteSlots[i][jss::vote_weight].asUInt() ==
12500))
return;
votes.erase(voteSlots[i][jss::account].asString());
}
if (!BEAST_EXPECT(votes.empty()))
return;
votes.erase(voteSlots[i][jss::account].asString());
}
if (!BEAST_EXPECT(votes.empty()))
return;
votes = votesCopy;
votes = votesCopy;
// bid
auto const auctionSlot = amm[jss::auction_slot];
for (std::uint8_t i = 0; i < 4; ++i)
{
if (!BEAST_EXPECT(authAccounts.contains(
// bid
auto const auctionSlot = amm[jss::auction_slot];
for (std::uint8_t i = 0; i < 4; ++i)
{
if (!BEAST_EXPECT(authAccounts.contains(
auctionSlot[jss::auth_accounts][i]
[jss::account]
.asString())))
return;
authAccounts.erase(
auctionSlot[jss::auth_accounts][i][jss::account]
.asString())))
.asString());
}
if (!BEAST_EXPECT(authAccounts.empty()))
return;
authAccounts.erase(
auctionSlot[jss::auth_accounts][i][jss::account]
.asString());
BEAST_EXPECT(
auctionSlot[jss::account].asString() ==
alice.human() &&
auctionSlot[jss::discounted_fee].asUInt() == 17 &&
auctionSlot[jss::price][jss::value].asString() ==
"5600" &&
auctionSlot[jss::price][jss::currency].asString() ==
to_string(ammAlice.lptIssue().currency) &&
auctionSlot[jss::price][jss::issuer].asString() ==
to_string(ammAlice.lptIssue().account));
}
catch (std::exception const& e)
{
fail(e.what(), __FILE__, __LINE__);
}
if (!BEAST_EXPECT(authAccounts.empty()))
return;
BEAST_EXPECT(
auctionSlot[jss::account].asString() == alice.human() &&
auctionSlot[jss::discounted_fee].asUInt() == 17 &&
auctionSlot[jss::price][jss::value].asString() ==
"5600" &&
auctionSlot[jss::price][jss::currency].asString() ==
to_string(ammAlice.lptIssue().currency) &&
auctionSlot[jss::price][jss::issuer].asString() ==
to_string(ammAlice.lptIssue().account));
}
catch (std::exception const& e)
{
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();
}