#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl::test { class AMMInfo_test : public jtx::AMMTestBase { public: void testErrors() { testcase("Errors"); using namespace jtx; Account const bogie("bogie"); enum class TestAccount { None, Alice, Bogie }; auto accountId = [&](AMM const& ammAlice, TestAccount v) -> std::optional { if (v == TestAccount::Alice) { return ammAlice.ammAccount(); } if (v == TestAccount::Bogie) { return bogie; } return std::nullopt; }; // Invalid tokens pair testAMM([&](AMM& ammAlice, Env&) { Account const gw("gw"); auto const usd = gw["USD"]; auto const jv = ammAlice.ammRpcInfo({}, {}, usd, usd); BEAST_EXPECT(jv[jss::error_message] == "Account not found."); }); // Invalid LP account id testAMM([&](AMM& ammAlice, Env&) { auto const jv = ammAlice.ammRpcInfo(bogie.id()); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); // Account is not a string testAMM([&](AMM& ammAlice, Env&) { auto const jv = ammAlice.ammRpcInfo(json::Value{42}, std::nullopt, XRP, USD, std::nullopt, true, 3); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); // AMM Account is not a string testAMM([&](AMM& ammAlice, Env&) { auto const jv = ammAlice.ammRpcInfo( json::Value{to_string(ammAlice.ammAccount())}, std::nullopt, XRP, USD, json::Value{42}, false, 3); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); std::vector, std::optional, TestAccount, bool>> const invalidParams = { {xrpIssue(), std::nullopt, TestAccount::None, false}, {std::nullopt, USD, TestAccount::None, false}, {xrpIssue(), std::nullopt, TestAccount::Alice, false}, {std::nullopt, USD, TestAccount::Alice, false}, {xrpIssue(), USD, TestAccount::Alice, false}, {std::nullopt, std::nullopt, TestAccount::None, true}}; // Invalid parameters testAMM([&](AMM& ammAlice, Env&) { for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParams) { auto const jv = ammAlice.ammRpcInfo( std::nullopt, std::nullopt, iss1, iss2, accountId(ammAlice, acct), ignoreParams); BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters."); } }); // Invalid parameters *and* invalid LP account, default API version testAMM([&](AMM& ammAlice, Env&) { for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParams) { auto const jv = ammAlice.ammRpcInfo( bogie, // std::nullopt, iss1, iss2, accountId(ammAlice, acct), ignoreParams); BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters."); } }); // Invalid parameters *and* invalid LP account, API version 3 testAMM([&](AMM& ammAlice, Env&) { for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParams) { auto const jv = ammAlice.ammRpcInfo( bogie, // std::nullopt, iss1, iss2, accountId(ammAlice, acct), ignoreParams, 3); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); } }); // Invalid AMM account id testAMM([&](AMM& ammAlice, Env&) { auto const jv = ammAlice.ammRpcInfo( std::nullopt, std::nullopt, std::nullopt, std::nullopt, bogie.id()); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); std::vector, std::optional, TestAccount, bool>> const invalidParamsBadAccount = { {xrpIssue(), std::nullopt, TestAccount::None, false}, {std::nullopt, USD, TestAccount::None, false}, {xrpIssue(), std::nullopt, TestAccount::Bogie, false}, {std::nullopt, USD, TestAccount::Bogie, false}, {xrpIssue(), USD, TestAccount::Bogie, false}, {std::nullopt, std::nullopt, TestAccount::None, true}}; // Invalid parameters *and* invalid AMM account, default API version testAMM([&](AMM& ammAlice, Env&) { for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParamsBadAccount) { auto const jv = ammAlice.ammRpcInfo( std::nullopt, std::nullopt, iss1, iss2, accountId(ammAlice, acct), ignoreParams); BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters."); } }); // Invalid parameters *and* invalid AMM account, API version 3 testAMM([&](AMM& ammAlice, Env&) { for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParamsBadAccount) { auto const jv = ammAlice.ammRpcInfo( std::nullopt, std::nullopt, iss1, iss2, accountId(ammAlice, acct), ignoreParams, 3); BEAST_EXPECT( jv[jss::error_message] == (acct == TestAccount::Bogie ? std::string("Account malformed.") : std::string("Invalid parameters."))); } }); } void testSimpleRpc() { testcase("RPC simple"); using namespace jtx; testAMM([&](AMM& ammAlice, Env&) { BEAST_EXPECT(ammAlice.expectAmmRpcInfo( XRP(10000), USD(10000), IOUAmount{10000000, 0}, std::nullopt, std::nullopt, ammAlice.ammAccount())); }); { Env env{*this}; env.fund(XRP(1'000), gw_); MPTTester mptTester(env, gw_, {.fund = false}); mptTester.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); MPTTester mptTester1(env, gw_, {.fund = false}); mptTester1.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const mpt = mptTester["MPT"]; auto const mpt1 = mptTester1["MPT"]; std::vector> pools = { {XRP(100), mpt(100), IOUAmount{100'000}}, {USD(100), mpt(100), IOUAmount{100}}, {mpt(100), mpt1(100), IOUAmount{100}}}; for (auto& pool : pools) { AMM const amm(env, gw_, std::get<0>(pool), std::get<1>(pool)); BEAST_EXPECT(amm.expectAmmRpcInfo( std::get<0>(pool), std::get<1>(pool), std::get<2>(pool), std::nullopt, std::nullopt, amm.ammAccount())); } } } void 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 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)}); 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 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 { // 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 = votesCopy; // 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()); } 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 testFreeze() { using namespace jtx; testAMM([&](AMM& ammAlice, Env& env) { env(fset(gw_, asfGlobalFreeze)); env.close(); auto test = [&](bool freeze) { auto const info = ammAlice.ammRpcInfo(); BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool() == freeze); }; test(true); env(fclear(gw_, asfGlobalFreeze)); env.close(); test(false); }); } void testInvalidAmmField() { using namespace jtx; testcase("Invalid amm field"); testAMM([&](AMM& amm, Env&) { auto const resp = amm.ammRpcInfo( std::nullopt, jss::validated.cStr(), std::nullopt, std::nullopt, gw_); BEAST_EXPECT(resp.isMember("error") && resp["error"] == "actNotFound"); }); } void run() override { using namespace jtx; auto const all = testableAmendments(); testErrors(); testSimpleRpc(); testVoteAndBid(all); testVoteAndBid(all - fixAMMv1_3); testFreeze(); testInvalidAmmField(); } }; BEAST_DEFINE_TESTSUITE(AMMInfo, rpc, xrpl); } // namespace xrpl::test