mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 14:05:51 +00:00
test: Add tests to raise coverage of AMM (#4971)
--------- Co-authored-by: Howard Hinnant <howard.hinnant@gmail.com> Co-authored-by: Mark Travis <mtravis@ripple.com> Co-authored-by: Bronek Kozicki <brok@incorrekt.com> Co-authored-by: Mayukha Vadari <mvadari@gmail.com> Co-authored-by: Chenna Keshava <ckeshavabs@gmail.com>
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include <ripple/app/misc/AMMHelpers.h>
|
||||
#include <ripple/app/paths/AMMContext.h>
|
||||
#include <ripple/app/paths/AMMOffer.h>
|
||||
#include <ripple/app/tx/impl/AMMBid.h>
|
||||
#include <ripple/protocol/AMMCore.h>
|
||||
#include <ripple/protocol/STParsedJSON.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
@@ -37,6 +38,10 @@
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* Basic tests of AMM that do not use offers.
|
||||
* Tests incorporating offers are in `AMMExtended_test`.
|
||||
*/
|
||||
struct AMM_test : public jtx::AMMTest
|
||||
{
|
||||
private:
|
||||
@@ -81,10 +86,8 @@ private:
|
||||
env.fund(XRP(30'000), gw, alice);
|
||||
env.close();
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env.trust(USD(30'000), alice);
|
||||
env.close();
|
||||
env(trust(gw, alice["USD"](30'000)), txflags(tfSetfAuth));
|
||||
env(trust(alice, gw["USD"](30'000), 0));
|
||||
env(trust(gw, alice["USD"](0), tfSetfAuth));
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10'000)));
|
||||
env.close();
|
||||
@@ -117,6 +120,14 @@ private:
|
||||
},
|
||||
std::nullopt,
|
||||
1'000);
|
||||
|
||||
// Make sure asset comparison works.
|
||||
BEAST_EXPECT(
|
||||
STIssue(sfAsset, STAmount(XRP(2'000)).issue()) ==
|
||||
STIssue(sfAsset, STAmount(XRP(2'000)).issue()));
|
||||
BEAST_EXPECT(
|
||||
STIssue(sfAsset, STAmount(XRP(2'000)).issue()) !=
|
||||
STIssue(sfAsset, STAmount({USD(2'000)}).issue()));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -599,7 +610,12 @@ private:
|
||||
USD(100),
|
||||
STAmount{USD, 1, -1},
|
||||
std::nullopt},
|
||||
};
|
||||
{tfTwoAssetIfEmpty | tfLPToken,
|
||||
std::nullopt,
|
||||
XRP(100),
|
||||
USD(100),
|
||||
STAmount{USD, 1, -1},
|
||||
std::nullopt}};
|
||||
for (auto const& it : invalidOptions)
|
||||
{
|
||||
ammAlice.deposit(
|
||||
@@ -615,6 +631,19 @@ private:
|
||||
ter(temMALFORMED));
|
||||
}
|
||||
|
||||
{
|
||||
// bad preflight1
|
||||
Json::Value jv = Json::objectValue;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::TransactionType] = jss::AMMDeposit;
|
||||
jv[jss::Asset] =
|
||||
STIssue(sfAsset, XRP).getJson(JsonOptions::none);
|
||||
jv[jss::Asset2] =
|
||||
STIssue(sfAsset, USD).getJson(JsonOptions::none);
|
||||
jv[jss::Fee] = "-1";
|
||||
env(jv, ter(temBAD_FEE));
|
||||
}
|
||||
|
||||
// Invalid tokens
|
||||
ammAlice.deposit(
|
||||
alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
|
||||
@@ -625,6 +654,33 @@ private:
|
||||
std::nullopt,
|
||||
ter(temBAD_AMM_TOKENS));
|
||||
|
||||
{
|
||||
Json::Value jv = Json::objectValue;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::TransactionType] = jss::AMMDeposit;
|
||||
jv[jss::Asset] =
|
||||
STIssue(sfAsset, XRP).getJson(JsonOptions::none);
|
||||
jv[jss::Asset2] =
|
||||
STIssue(sfAsset, USD).getJson(JsonOptions::none);
|
||||
jv[jss::LPTokenOut] =
|
||||
USD(100).value().getJson(JsonOptions::none);
|
||||
jv[jss::Flags] = tfLPToken;
|
||||
env(jv, ter(temBAD_AMM_TOKENS));
|
||||
}
|
||||
|
||||
// Invalid trading fee
|
||||
ammAlice.deposit(
|
||||
carol,
|
||||
std::nullopt,
|
||||
XRP(200),
|
||||
USD(200),
|
||||
std::nullopt,
|
||||
tfTwoAssetIfEmpty,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
10'000,
|
||||
ter(temBAD_FEE));
|
||||
|
||||
// Invalid tokens - bogus currency
|
||||
{
|
||||
auto const iss1 = Issue{Currency(0xabc), gw.id()};
|
||||
@@ -821,6 +877,13 @@ private:
|
||||
ter(tecFROZEN));
|
||||
ammAlice.deposit(
|
||||
carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
|
||||
ammAlice.deposit(
|
||||
carol,
|
||||
XRP(100),
|
||||
USD(100),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecFROZEN));
|
||||
});
|
||||
|
||||
// Individually frozen (AMM) account
|
||||
@@ -858,6 +921,50 @@ private:
|
||||
ter(tecFROZEN));
|
||||
});
|
||||
|
||||
// Individually frozen (AMM) account with IOU/IOU AMM
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
env(trust(gw, carol["USD"](0), tfSetFreeze));
|
||||
env(trust(gw, carol["BTC"](0), tfSetFreeze));
|
||||
env.close();
|
||||
ammAlice.deposit(
|
||||
carol,
|
||||
1'000'000,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecFROZEN));
|
||||
ammAlice.deposit(
|
||||
carol,
|
||||
USD(100),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecFROZEN));
|
||||
env(trust(gw, carol["USD"](0), tfClearFreeze));
|
||||
// Individually frozen AMM
|
||||
env(trust(
|
||||
gw,
|
||||
STAmount{
|
||||
Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
|
||||
tfSetFreeze));
|
||||
env.close();
|
||||
// Cannot deposit non-frozen token
|
||||
ammAlice.deposit(
|
||||
carol,
|
||||
1'000'000,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecFROZEN));
|
||||
ammAlice.deposit(
|
||||
carol,
|
||||
USD(100),
|
||||
BTC(0.01),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecFROZEN));
|
||||
},
|
||||
{{USD(20'000), BTC(0.5)}});
|
||||
|
||||
// Insufficient XRP balance
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
env.fund(XRP(1'000), bob);
|
||||
@@ -947,6 +1054,15 @@ private:
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecINSUF_RESERVE_LINE));
|
||||
|
||||
env(offer(carol, XRP(100), USD(103)));
|
||||
ammAlice.deposit(
|
||||
carol,
|
||||
USD(100),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecINSUF_RESERVE_LINE));
|
||||
}
|
||||
|
||||
// Insufficient reserve, IOU/IOU
|
||||
@@ -1359,6 +1475,48 @@ private:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
WithdrawArg args{
|
||||
.asset1Out = XRP(100),
|
||||
.err = ter(tecAMM_BALANCE),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
},
|
||||
{{XRP(99), USD(99)}});
|
||||
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
WithdrawArg args{
|
||||
.asset1Out = USD(100),
|
||||
.err = ter(tecAMM_BALANCE),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
},
|
||||
{{XRP(99), USD(99)}});
|
||||
|
||||
{
|
||||
Env env{*this};
|
||||
env.fund(XRP(30'000), gw, alice, bob);
|
||||
env.close();
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env(trust(alice, gw["USD"](30'000), 0));
|
||||
env(trust(gw, alice["USD"](0), tfSetfAuth));
|
||||
// Bob trusts Gateway to owe him USD...
|
||||
env(trust(bob, gw["USD"](30'000), 0));
|
||||
// ...but Gateway does not authorize Bob to hold its USD.
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10'000)));
|
||||
env.close();
|
||||
AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
|
||||
WithdrawArg args{
|
||||
.account = bob,
|
||||
.asset1Out = USD(100),
|
||||
.err = ter(tecNO_AUTH),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
}
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
// Invalid flags
|
||||
ammAlice.withdraw(
|
||||
@@ -2294,142 +2452,156 @@ private:
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// burn all the LPTokens through a AMMBid transaction
|
||||
{
|
||||
Env env(*this);
|
||||
fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
|
||||
AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
|
||||
|
||||
// auction slot is owned by the creator of the AMM i.e. gw
|
||||
BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
|
||||
|
||||
// gw attempts to burn all her LPTokens through a bid transaction
|
||||
// this transaction fails because AMMBid transaction can not burn
|
||||
// all the outstanding LPTokens
|
||||
env(amm.bid({
|
||||
.account = gw,
|
||||
.bidMin = 1'000'000,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
}
|
||||
|
||||
// burn all the LPTokens through a AMMBid transaction
|
||||
{
|
||||
Env env(*this);
|
||||
fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
|
||||
AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
|
||||
|
||||
// auction slot is owned by the creator of the AMM i.e. gw
|
||||
BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
|
||||
|
||||
// gw burns all but one of its LPTokens through a bid transaction
|
||||
// this transaction suceeds because the bid price is less than
|
||||
// the total outstanding LPToken balance
|
||||
env(amm.bid({
|
||||
.account = gw,
|
||||
.bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
|
||||
}),
|
||||
ter(tesSUCCESS))
|
||||
.close();
|
||||
|
||||
// gw must own the auction slot
|
||||
BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999}));
|
||||
|
||||
// 999'999 tokens are burned, only 1 LPToken is owned by gw
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(XRP(1'000), USD(1'000), IOUAmount{1}));
|
||||
|
||||
// gw owns only 1 LPToken in its balance
|
||||
BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1);
|
||||
|
||||
// gw attempts to burn the last of its LPTokens in an AMMBid
|
||||
// transaction. This transaction fails because it would burn all
|
||||
// the remaining LPTokens
|
||||
env(amm.bid({
|
||||
.account = gw,
|
||||
.bidMin = 1,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
}
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
// Invalid flags
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
0,
|
||||
std::nullopt,
|
||||
{},
|
||||
tfWithdrawAll,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 0,
|
||||
.flags = tfWithdrawAll,
|
||||
}),
|
||||
ter(temINVALID_FLAG));
|
||||
|
||||
ammAlice.deposit(carol, 1'000'000);
|
||||
// Invalid Bid price <= 0
|
||||
for (auto bid : {0, -100})
|
||||
{
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
bid,
|
||||
std::nullopt,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = bid,
|
||||
}),
|
||||
ter(temBAD_AMOUNT));
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
std::nullopt,
|
||||
bid,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMax = bid,
|
||||
}),
|
||||
ter(temBAD_AMOUNT));
|
||||
}
|
||||
|
||||
// Invlaid Min/Max combination
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
200,
|
||||
100,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 200,
|
||||
.bidMax = 100,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
|
||||
// Invalid Account
|
||||
Account bad("bad");
|
||||
env.memoize(bad);
|
||||
ammAlice.bid(
|
||||
bad,
|
||||
std::nullopt,
|
||||
100,
|
||||
{},
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = bad,
|
||||
.bidMax = 100,
|
||||
}),
|
||||
seq(1),
|
||||
std::nullopt,
|
||||
ter(terNO_ACCOUNT));
|
||||
|
||||
// Account is not LP
|
||||
Account const dan("dan");
|
||||
env.fund(XRP(1'000), dan);
|
||||
ammAlice.bid(
|
||||
dan,
|
||||
100,
|
||||
std::nullopt,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = dan,
|
||||
.bidMin = 100,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
ammAlice.bid(
|
||||
dan,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = dan,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
|
||||
// Auth account is invalid.
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
100,
|
||||
std::nullopt,
|
||||
{bob},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 100,
|
||||
.authAccounts = {bob},
|
||||
}),
|
||||
ter(terNO_ACCOUNT));
|
||||
|
||||
// Invalid Assets
|
||||
ammAlice.bid(
|
||||
alice,
|
||||
std::nullopt,
|
||||
100,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
{{USD, GBP}},
|
||||
env(ammAlice.bid({
|
||||
.account = alice,
|
||||
.bidMax = 100,
|
||||
.assets = {{USD, GBP}},
|
||||
}),
|
||||
ter(terNO_AMM));
|
||||
|
||||
// Invalid Min/Max issue
|
||||
ammAlice.bid(
|
||||
alice,
|
||||
std::nullopt,
|
||||
STAmount{USD, 100},
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = alice,
|
||||
.bidMax = STAmount{USD, 100},
|
||||
}),
|
||||
ter(temBAD_AMM_TOKENS));
|
||||
ammAlice.bid(
|
||||
alice,
|
||||
STAmount{USD, 100},
|
||||
std::nullopt,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = alice,
|
||||
.bidMin = STAmount{USD, 100},
|
||||
}),
|
||||
ter(temBAD_AMM_TOKENS));
|
||||
});
|
||||
|
||||
// Invalid AMM
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
ammAlice.withdrawAll(alice);
|
||||
ammAlice.bid(
|
||||
alice,
|
||||
std::nullopt,
|
||||
100,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = alice,
|
||||
.bidMax = 100,
|
||||
}),
|
||||
ter(terNO_AMM));
|
||||
});
|
||||
|
||||
@@ -2442,14 +2614,11 @@ private:
|
||||
env.fund(XRP(1'000), bob, ed, bill, scott, james);
|
||||
env.close();
|
||||
ammAlice.deposit(carol, 1'000'000);
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
100,
|
||||
std::nullopt,
|
||||
{bob, ed, bill, scott, james},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 100,
|
||||
.authAccounts = {bob, ed, bill, scott, james},
|
||||
}),
|
||||
ter(temMALFORMED));
|
||||
});
|
||||
|
||||
@@ -2458,35 +2627,25 @@ private:
|
||||
fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
|
||||
ammAlice.deposit(carol, 1'000'000);
|
||||
ammAlice.deposit(bob, 10);
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
1'000'001,
|
||||
std::nullopt,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 1'000'001,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
std::nullopt,
|
||||
1'000'001,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMax = 1'000'001,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
ammAlice.bid(carol, 1'000);
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 1'000,
|
||||
}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
|
||||
// Slot purchase price is more than 1000 but bob only has 10 tokens
|
||||
ammAlice.bid(
|
||||
bob,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = bob,
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
});
|
||||
|
||||
@@ -2500,17 +2659,13 @@ private:
|
||||
env.trust(STAmount{lpIssue, 50}, bob);
|
||||
env(pay(gw, alice, STAmount{lpIssue, 100}));
|
||||
env(pay(gw, bob, STAmount{lpIssue, 50}));
|
||||
amm.bid(alice, 100);
|
||||
env(amm.bid({.account = alice, .bidMin = 100}));
|
||||
// Alice doesn't have any more tokens, but
|
||||
// she still owns the slot.
|
||||
amm.bid(
|
||||
bob,
|
||||
std::nullopt,
|
||||
50,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(amm.bid({
|
||||
.account = bob,
|
||||
.bidMax = 50,
|
||||
}),
|
||||
ter(tecAMM_FAILED));
|
||||
}
|
||||
}
|
||||
@@ -2527,7 +2682,7 @@ private:
|
||||
// Bid 110 tokens. Pay bidMin.
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
ammAlice.deposit(carol, 1'000'000);
|
||||
ammAlice.bid(carol, 110);
|
||||
env(ammAlice.bid({.account = carol, .bidMin = 110}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
|
||||
// 110 tokens are burned.
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
@@ -2538,12 +2693,12 @@ private:
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
ammAlice.deposit(carol, 1'000'000);
|
||||
// Bid exactly 110. Pay 110 because the pay price is < 110.
|
||||
ammAlice.bid(carol, 110, 110);
|
||||
env(ammAlice.bid({.account = carol, .bidMin = 110, .bidMax = 110}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
|
||||
// Bid exactly 180-200. Pay 180 because the pay price is < 180.
|
||||
ammAlice.bid(alice, 180, 200);
|
||||
env(ammAlice.bid({.account = alice, .bidMin = 180, .bidMax = 200}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
|
||||
@@ -2553,43 +2708,36 @@ private:
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
ammAlice.deposit(carol, 1'000'000);
|
||||
// Bid, pay bidMin.
|
||||
ammAlice.bid(carol, 110);
|
||||
env(ammAlice.bid({.account = carol, .bidMin = 110}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
|
||||
|
||||
fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
|
||||
ammAlice.deposit(bob, 1'000'000);
|
||||
// Bid, pay the computed price.
|
||||
ammAlice.bid(bob);
|
||||
env(ammAlice.bid({.account = bob}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
|
||||
|
||||
// Bid bidMax fails because the computed price is higher.
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
std::nullopt,
|
||||
120,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMax = 120,
|
||||
}),
|
||||
ter(tecAMM_FAILED));
|
||||
// Bid MaxSlotPrice succeeds - pay computed price
|
||||
ammAlice.bid(carol, std::nullopt, 600);
|
||||
env(ammAlice.bid({.account = carol, .bidMax = 600}));
|
||||
BEAST_EXPECT(
|
||||
ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
|
||||
|
||||
// Bid Min/MaxSlotPrice fails because the computed price is not in
|
||||
// range
|
||||
ammAlice.bid(
|
||||
carol,
|
||||
10,
|
||||
100,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 10,
|
||||
.bidMax = 100,
|
||||
}),
|
||||
ter(tecAMM_FAILED));
|
||||
// Bid Min/MaxSlotPrice succeeds - pay computed price
|
||||
ammAlice.bid(carol, 100, 600);
|
||||
env(ammAlice.bid({.account = carol, .bidMin = 100, .bidMax = 600}));
|
||||
BEAST_EXPECT(
|
||||
ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
|
||||
});
|
||||
@@ -2604,23 +2752,23 @@ private:
|
||||
XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
|
||||
|
||||
// Initial state. Pay bidMin.
|
||||
ammAlice.bid(carol, 110);
|
||||
env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
|
||||
|
||||
// 1st Interval after close, price for 0th interval.
|
||||
ammAlice.bid(bob);
|
||||
env(ammAlice.bid({.account = bob}));
|
||||
env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
|
||||
BEAST_EXPECT(
|
||||
ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
|
||||
|
||||
// 10th Interval after close, price for 1st interval.
|
||||
ammAlice.bid(carol);
|
||||
env(ammAlice.bid({.account = carol}));
|
||||
env.close(seconds(10 * AUCTION_SLOT_INTERVAL_DURATION + 1));
|
||||
BEAST_EXPECT(
|
||||
ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
|
||||
|
||||
// 20th Interval (expired) after close, price for 10th interval.
|
||||
ammAlice.bid(bob);
|
||||
env(ammAlice.bid({.account = bob}));
|
||||
env.close(seconds(
|
||||
AUCTION_SLOT_TIME_INTERVALS * AUCTION_SLOT_INTERVAL_DURATION +
|
||||
1));
|
||||
@@ -2628,7 +2776,7 @@ private:
|
||||
0, std::nullopt, IOUAmount{127'33875, -5}));
|
||||
|
||||
// 0 Interval.
|
||||
ammAlice.bid(carol, 110);
|
||||
env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
|
||||
BEAST_EXPECT(
|
||||
ammAlice.expectAuctionSlot(0, std::nullopt, IOUAmount{110}));
|
||||
// ~321.09 tokens burnt on bidding fees.
|
||||
@@ -2650,7 +2798,11 @@ private:
|
||||
ammAlice.deposit(carol, 500'000);
|
||||
ammAlice.deposit(dan, 500'000);
|
||||
auto ammTokens = ammAlice.getLPTokensBalance();
|
||||
ammAlice.bid(carol, 120, std::nullopt, {bob, ed});
|
||||
env(ammAlice.bid({
|
||||
.account = carol,
|
||||
.bidMin = 120,
|
||||
.authAccounts = {bob, ed},
|
||||
}));
|
||||
auto const slotPrice = IOUAmount{5'200};
|
||||
ammTokens -= slotPrice;
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
|
||||
@@ -2760,10 +2912,10 @@ private:
|
||||
1'000);
|
||||
|
||||
// Bid tiny amount
|
||||
testAMM([&](AMM& ammAlice, Env&) {
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
// Bid a tiny amount
|
||||
auto const tiny = Number{STAmount::cMinValue, STAmount::cMinOffset};
|
||||
ammAlice.bid(alice, IOUAmount{tiny});
|
||||
env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{tiny}}));
|
||||
// Auction slot purchase price is equal to the tiny amount
|
||||
// since the minSlotPrice is 0 with no trading fee.
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
|
||||
@@ -2771,8 +2923,10 @@ private:
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
XRP(10'000), USD(10'000), ammAlice.tokens()));
|
||||
// Bid the tiny amount
|
||||
ammAlice.bid(
|
||||
alice, IOUAmount{STAmount::cMinValue, STAmount::cMinOffset});
|
||||
env(ammAlice.bid({
|
||||
.account = alice,
|
||||
.bidMin = IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
|
||||
}));
|
||||
// Pay slightly higher price
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(
|
||||
0, 0, IOUAmount{tiny * Number{105, -2}}));
|
||||
@@ -2783,14 +2937,22 @@ private:
|
||||
|
||||
// Reset auth account
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {carol});
|
||||
env(ammAlice.bid({
|
||||
.account = alice,
|
||||
.bidMin = IOUAmount{100},
|
||||
.authAccounts = {carol},
|
||||
}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
|
||||
ammAlice.bid(alice, IOUAmount{100});
|
||||
env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
|
||||
Account bob("bob");
|
||||
Account dan("dan");
|
||||
fund(env, {bob, dan}, XRP(1'000));
|
||||
ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {bob, dan});
|
||||
env(ammAlice.bid({
|
||||
.account = alice,
|
||||
.bidMin = IOUAmount{100},
|
||||
.authAccounts = {bob, dan},
|
||||
}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
|
||||
});
|
||||
|
||||
@@ -2805,7 +2967,7 @@ private:
|
||||
env(pay(gw, alice, STAmount{lpIssue, 500}));
|
||||
env(pay(gw, bob, STAmount{lpIssue, 50}));
|
||||
// Alice doesn't have anymore lp tokens
|
||||
amm.bid(alice, 500);
|
||||
env(amm.bid({.account = alice, .bidMin = 500}));
|
||||
BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
|
||||
BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
|
||||
// But trades with the discounted fee since she still owns the slot.
|
||||
@@ -2822,6 +2984,57 @@ private:
|
||||
STAmount{USD, UINT64_C(1'010'10090898081), -11},
|
||||
IOUAmount{1'004'487'562112089, -9}));
|
||||
}
|
||||
|
||||
// preflight tests
|
||||
{
|
||||
Env env(*this);
|
||||
fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
|
||||
AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
|
||||
Json::Value tx = amm.bid({.account = alice, .bidMin = 500});
|
||||
|
||||
{
|
||||
auto jtx = env.jt(tx, seq(1), fee(10));
|
||||
env.app().config().features.erase(featureAMM);
|
||||
PreflightContext pfctx(
|
||||
env.app(),
|
||||
*jtx.stx,
|
||||
env.current()->rules(),
|
||||
tapNONE,
|
||||
env.journal);
|
||||
auto pf = AMMBid::preflight(pfctx);
|
||||
BEAST_EXPECT(pf == temDISABLED);
|
||||
env.app().config().features.insert(featureAMM);
|
||||
}
|
||||
|
||||
{
|
||||
auto jtx = env.jt(tx, seq(1), fee(10));
|
||||
jtx.jv["TxnSignature"] = "deadbeef";
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext pfctx(
|
||||
env.app(),
|
||||
*jtx.stx,
|
||||
env.current()->rules(),
|
||||
tapNONE,
|
||||
env.journal);
|
||||
auto pf = AMMBid::preflight(pfctx);
|
||||
BEAST_EXPECT(pf != tesSUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
auto jtx = env.jt(tx, seq(1), fee(10));
|
||||
jtx.jv["Asset2"]["currency"] = "XRP";
|
||||
jtx.jv["Asset2"].removeMember("issuer");
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext pfctx(
|
||||
env.app(),
|
||||
*jtx.stx,
|
||||
env.current()->rules(),
|
||||
tapNONE,
|
||||
env.journal);
|
||||
auto pf = AMMBid::preflight(pfctx);
|
||||
BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -3601,7 +3814,7 @@ private:
|
||||
ammAlice.vote(carol, 0);
|
||||
BEAST_EXPECT(ammAlice.expectTradingFee(0));
|
||||
// Carol bids
|
||||
ammAlice.bid(carol, 100);
|
||||
env(ammAlice.bid({.account = carol, .bidMin = 100}));
|
||||
BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
|
||||
BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
|
||||
BEAST_EXPECT(accountBalance(env, carol) == "22499999960");
|
||||
@@ -3693,6 +3906,15 @@ private:
|
||||
Env env{*this, feature};
|
||||
fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
|
||||
AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
|
||||
|
||||
env(amm.bid({.bidMax = 1000}), ter(temMALFORMED));
|
||||
env(amm.bid({}), ter(temDISABLED));
|
||||
amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)});
|
||||
amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)});
|
||||
amm.withdraw(WithdrawArg{.err = ter(temDISABLED)});
|
||||
amm.deposit(
|
||||
DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)});
|
||||
amm.ammDelete(alice, ter(temDISABLED));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4312,14 +4534,10 @@ private:
|
||||
|
||||
// Bid,Vote,Deposit,Withdraw,SetTrust failing with
|
||||
// tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
|
||||
amm.bid(
|
||||
alice,
|
||||
1000,
|
||||
std::nullopt,
|
||||
{},
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
env(amm.bid({
|
||||
.account = alice,
|
||||
.bidMin = 1000,
|
||||
}),
|
||||
ter(tecAMM_EMPTY));
|
||||
amm.vote(
|
||||
std::nullopt,
|
||||
@@ -4391,6 +4609,9 @@ private:
|
||||
amm.ammDelete(alice);
|
||||
BEAST_EXPECT(!amm.ammExists());
|
||||
BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
|
||||
|
||||
// Try redundant delete
|
||||
amm.ammDelete(alice, ter(terNO_AMM));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4889,6 +5110,65 @@ private:
|
||||
false);
|
||||
}
|
||||
|
||||
void
|
||||
testMalformed()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
WithdrawArg args{
|
||||
.flags = tfSingleAsset,
|
||||
.err = ter(temMALFORMED),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
});
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
WithdrawArg args{
|
||||
.flags = tfOneAssetLPToken,
|
||||
.err = ter(temMALFORMED),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
});
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
WithdrawArg args{
|
||||
.flags = tfLimitLPToken,
|
||||
.err = ter(temMALFORMED),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
});
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
WithdrawArg args{
|
||||
.asset1Out = XRP(100),
|
||||
.asset2Out = XRP(100),
|
||||
.err = ter(temBAD_AMM_TOKENS),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
});
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
WithdrawArg args{
|
||||
.asset1Out = XRP(100),
|
||||
.asset2Out = BAD(100),
|
||||
.err = ter(temBAD_CURRENCY),
|
||||
};
|
||||
ammAlice.withdraw(args);
|
||||
});
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::AMMWithdraw;
|
||||
jv[jss::Flags] = tfLimitLPToken;
|
||||
jv[jss::Account] = alice.human();
|
||||
ammAlice.setTokens(jv);
|
||||
XRP(100).value().setJson(jv[jss::Amount]);
|
||||
USD(100).value().setJson(jv[jss::EPrice]);
|
||||
env(jv, ter(temBAD_AMM_TOKENS));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testFixOverflowOffer()
|
||||
{
|
||||
@@ -5164,7 +5444,7 @@ private:
|
||||
}
|
||||
|
||||
void
|
||||
testCore()
|
||||
testAll()
|
||||
{
|
||||
testInvalidInstance();
|
||||
testInstanceCreate();
|
||||
@@ -5190,13 +5470,14 @@ private:
|
||||
testAMMID();
|
||||
testSelection();
|
||||
testFixDefaultInnerObj();
|
||||
testMalformed();
|
||||
testFixOverflowOffer();
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testCore();
|
||||
testAll();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user