Skip to content

Commit

Permalink
test: Unit test for AMM offer overflow (XRPLF#4986)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bronek authored and sophiax851 committed Jun 12, 2024
1 parent b941e24 commit ea278da
Showing 1 changed file with 275 additions and 0 deletions.
275 changes: 275 additions & 0 deletions src/test/app/AMM_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4889,6 +4889,280 @@ struct AMM_test : public jtx::AMMTest
false);
}

void
testFixOverflowOffer()
{
using namespace jtx;
using namespace std::chrono;
FeatureBitset const all{supported_amendments()};

Account const gatehub{"gatehub"};
Account const bitstamp{"bitstamp"};
Account const trader{"trader"};
auto const usdGH = gatehub["USD"];
auto const btcGH = gatehub["BTC"];
auto const usdBIT = bitstamp["USD"];

struct InputSet
{
char const* testCase;
double const poolUsdBIT;
double const poolUsdGH;
sendmax const sendMaxUsdBIT;
STAmount const sendUsdGH;
STAmount const failUsdGH;
STAmount const failUsdBIT;
STAmount const goodUsdGH;
STAmount const goodUsdBIT;
IOUAmount const lpTokenBalance;
double const offer1BtcGH = 0.1;
double const offer2BtcGH = 0.1;
double const offer2UsdGH = 1;
double const rateBIT = 0.0;
double const rateGH = 0.0;
};

using uint64_t = std::uint64_t;

for (auto const& input : {
InputSet{
.testCase = "Test Fix Overflow Offer", //
.poolUsdBIT = 3, //
.poolUsdGH = 273, //
.sendMaxUsdBIT{usdBIT(50)}, //
.sendUsdGH{usdGH, uint64_t(272'455089820359), -12}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(46'47826086956522), -14}, //
.goodUsdGH{usdGH, uint64_t(96'7543114220382), -13}, //
.goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, //
.lpTokenBalance = {28'61817604250837, -14}, //
.offer1BtcGH = 0.1, //
.offer2BtcGH = 0.1, //
.offer2UsdGH = 1, //
.rateBIT = 1.15, //
.rateGH = 1.2, //
},
InputSet{
.testCase = "Overflow test {1, 100, 0.111}", //
.poolUsdBIT = 1, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(0.111)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(1'111), -3}, //
.goodUsdGH{usdGH, uint64_t(90'04347888284115), -14}, //
.goodUsdBIT{usdBIT, uint64_t(1'111), -3}, //
.lpTokenBalance{10, 0}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
InputSet{
.testCase = "Overflow test {1, 100, 1.00}", //
.poolUsdBIT = 1, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(1.00)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(2), 0}, //
.goodUsdGH{usdGH, uint64_t(52'94379354424079), -14}, //
.goodUsdBIT{usdBIT, uint64_t(2), 0}, //
.lpTokenBalance{10, 0}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
InputSet{
.testCase = "Overflow test {1, 100, 4.6432}", //
.poolUsdBIT = 1, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(4.6432)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(5'6432), -4}, //
.goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
.goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
.lpTokenBalance{10, 0}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
InputSet{
.testCase = "Overflow test {1, 100, 10}", //
.poolUsdBIT = 1, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(10)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(11), 0}, //
.goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
.goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
.lpTokenBalance{10, 0}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
InputSet{
.testCase = "Overflow test {50, 100, 5.55}", //
.poolUsdBIT = 50, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(5.55)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(55'55), -2}, //
.goodUsdGH{usdGH, uint64_t(90'04347888284113), -14}, //
.goodUsdBIT{usdBIT, uint64_t(55'55), -2}, //
.lpTokenBalance{uint64_t(70'71067811865475), -14}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
InputSet{
.testCase = "Overflow test {50, 100, 50.00}", //
.poolUsdBIT = 50, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(50.00)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
.failUsdBIT{usdBIT, uint64_t(100), 0}, //
.goodUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
.goodUsdBIT{usdBIT, uint64_t(100), 0}, //
.lpTokenBalance{uint64_t(70'71067811865475), -14}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
InputSet{
.testCase = "Overflow test {50, 100, 232.16}", //
.poolUsdBIT = 50, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(232.16)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(282'16), -2}, //
.goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
.goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
.lpTokenBalance{70'71067811865475, -14}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
InputSet{
.testCase = "Overflow test {50, 100, 500}", //
.poolUsdBIT = 50, //
.poolUsdGH = 100, //
.sendMaxUsdBIT{usdBIT(500)}, //
.sendUsdGH{usdGH, 100}, //
.failUsdGH = STAmount{0}, //
.failUsdBIT{usdBIT, uint64_t(550), 0}, //
.goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
.goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
.lpTokenBalance{70'71067811865475, -14}, //
.offer1BtcGH = 1e-5, //
.offer2BtcGH = 1, //
.offer2UsdGH = 1e-5, //
.rateBIT = 0, //
.rateGH = 0, //
},
})
{
testcase(input.testCase);
for (auto const& features :
{all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
{
Env env(*this, features);

env.fund(XRP(5'000), gatehub, bitstamp, trader);
env.close();

if (input.rateGH != 0.0)
env(rate(gatehub, input.rateGH));
if (input.rateBIT != 0.0)
env(rate(bitstamp, input.rateBIT));

env(trust(trader, usdGH(10'000'000)));
env(trust(trader, usdBIT(10'000'000)));
env(trust(trader, btcGH(10'000'000)));
env.close();

env(pay(gatehub, trader, usdGH(100'000)));
env(pay(gatehub, trader, btcGH(100'000)));
env(pay(bitstamp, trader, usdBIT(100'000)));
env.close();

AMM amm{
env,
trader,
usdGH(input.poolUsdGH),
usdBIT(input.poolUsdBIT)};
env.close();

IOUAmount const preSwapLPTokenBalance =
amm.getLPTokensBalance();

env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
env(offer(
trader,
btcGH(input.offer2BtcGH),
usdGH(input.offer2UsdGH)));
env.close();

env(pay(trader, trader, input.sendUsdGH),
path(~usdGH),
path(~btcGH, ~usdGH),
sendmax(input.sendMaxUsdBIT),
txflags(tfPartialPayment));
env.close();
if (!features[fixAMMOverflowOffer])
BEAST_EXPECT(amm.expectBalances(
input.failUsdGH,
input.failUsdBIT,
input.lpTokenBalance));
else
{
BEAST_EXPECT(amm.expectBalances(
input.goodUsdGH,
input.goodUsdBIT,
input.lpTokenBalance));

// Invariant: LPToken balance must not change in a payment
// or a swap transaction
BEAST_EXPECT(
amm.getLPTokensBalance() == preSwapLPTokenBalance);

// Invariant: The square root of (product of the pool
// balances) must be at least the LPTokenBalance
Number const sqrtPoolProduct =
root2(input.goodUsdGH * input.goodUsdBIT);

// Include a tiny tolerance for the test cases using
// .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14},
// .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15},
// These two values multiply
// to 99.99999999999994227040383754105 which gets internally
// rounded to 100, due to representation error.
BEAST_EXPECT(
(sqrtPoolProduct + Number{1, -14} >=
input.lpTokenBalance));
}
}
}
}

void
testCore()
{
Expand Down Expand Up @@ -4916,6 +5190,7 @@ struct AMM_test : public jtx::AMMTest
testAMMID();
testSelection();
testFixDefaultInnerObj();
testFixOverflowOffer();
}

void
Expand Down

0 comments on commit ea278da

Please sign in to comment.