test: Unit test for AMM offer overflow (#4986)

This commit is contained in:
Bronek Kozicki
2024-04-19 01:30:18 +09:00
committed by GitHub
parent 659bd99a67
commit 8b0d049b9f

View File

@@ -4889,6 +4889,280 @@ private:
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()
{
@@ -4916,6 +5190,7 @@ private:
testAMMID();
testSelection();
testFixDefaultInnerObj();
testFixOverflowOffer();
}
void