From 83582b67313d270fafd6ca0ffc1d0f41c0761952 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Mon, 31 Mar 2025 13:51:00 +0100 Subject: [PATCH] Add RPC unit test --- src/libxrpl/protocol/STLedgerEntry.cpp | 4 - src/test/app/Vault_test.cpp | 245 +++++++++++++++++++++---- 2 files changed, 211 insertions(+), 38 deletions(-) diff --git a/src/libxrpl/protocol/STLedgerEntry.cpp b/src/libxrpl/protocol/STLedgerEntry.cpp index f39181b953..c490fab533 100644 --- a/src/libxrpl/protocol/STLedgerEntry.cpp +++ b/src/libxrpl/protocol/STLedgerEntry.cpp @@ -143,11 +143,7 @@ STLedgerEntry::getJson(JsonOptions options) const ret[jss::mpt_issuance_id] = to_string( makeMptID(getFieldU32(sfSequence), getAccountID(sfIssuer))); else if (getType() == ltVAULT) - { - // Replace "MPTokenIssuanceID" with functionally equivalent "Share" - ret.removeMember(sfMPTokenIssuanceID.fieldName.c_str()); ret[jss::Share] = to_json(MPTIssue(getFieldH192(sfMPTokenIssuanceID))); - } return ret; } diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index c54ab57715..e8ac73daeb 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -32,9 +32,13 @@ #include #include #include +#include +#include #include #include #include +#include +#include #include #include #include @@ -1081,43 +1085,14 @@ class Vault_test : public beast::unit_test::suite env(tx); env.close(); - auto const [vaultAccount, ownerAccount, mptID] = // - [&env, &owner, key = keylet.key, this]() // - -> std::tuple { - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::vault] = strHex(key); - auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); - - // Vault pseudo-account - auto const vAcct = - parseBase58( - jvVault[jss::result][jss::node][jss::Account].asString()) - .value(); - - // Owner account - auto const oAcct = - parseBase58( - jvVault[jss::result][jss::node][jss::Owner].asString()) - .value(); - BEAST_EXPECT(oAcct == owner.id()); - - // MPTID of the vault shares - auto const strMpt = jvVault[jss::result][jss::node][jss::Share] - [jss::mpt_issuance_id] - .asString(); - uint192 mpt; - BEAST_EXPECT(mpt.parseHex(strMpt)); - - return {vAcct, oAcct, mpt}; + auto const vaultAccount = [&env, keylet = keylet, this]() -> AccountID { + auto const vault = env.le(keylet); + BEAST_EXPECT(vault != nullptr); + return vault->at(sfAccount); }(); - BEAST_EXPECT(makeMptID(1, vaultAccount) == mptID); - BEAST_EXPECT( - keylet::vault(ownerAccount, env.seq(owner) - 1).key == keylet.key); - auto const vaultBalance = // - [&, account = vaultAccount]() -> PrettyAmount { + [&, account = vaultAccount, this]() -> PrettyAmount { auto const sle = env.le(keylet::line(account, issue)); BEAST_EXPECT(sle != nullptr); auto amount = sle->getFieldAmount(sfBalance); @@ -1191,6 +1166,207 @@ class Vault_test : public beast::unit_test::suite env(tx, ter{terADDRESS_COLLISION}); } + void + testRPC() + { + testcase("RPC"); + Env env{*this}; + Account const owner{"owner"}; + Account const issuer{"issuer"}; + Vault vault{env}; + env.fund(XRP(1000), issuer, owner); + env.close(); + + PrettyAsset asset = issuer["IOU"]; + env.trust(asset(1000), owner); + env(pay(issuer, owner, asset(200))); + env.close(); + + auto const sequence = env.seq(owner); + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + // Set some fields + { + auto tx1 = vault.deposit( + {.depositor = owner, .id = keylet.key, .amount = asset(50)}); + env(tx1); + + auto tx2 = vault.set({.owner = owner, .id = keylet.key}); + tx2[sfAssetsMaximum] = asset(1000).number(); + env(tx2); + env.close(); + } + + auto const sleVault = [&env, keylet = keylet, this]() { + auto const vault = env.le(keylet); + BEAST_EXPECT(vault != nullptr); + return vault; + }(); + + auto const check = [&, keylet = keylet, sle = sleVault, this]( + Json::Value const& node) { + BEAST_EXPECT(node.isObject()); + + auto checkString = + [&node](SField const& field, std::string expected) -> bool { + return node.isMember(field.fieldName) && + node[field.fieldName].isString() && + node[field.fieldName] == expected; + }; + auto checkObject = + [&node](SField const& field, Json::Value expected) -> bool { + return node.isMember(field.fieldName) && + node[field.fieldName].isObject() && + node[field.fieldName] == expected; + }; + auto checkInt = [&node](SField const& field, int expected) -> bool { + return node.isMember(field.fieldName) && + ((node[field.fieldName].isInt() && + node[field.fieldName] == Json::Int(expected)) || + (node[field.fieldName].isUInt() && + node[field.fieldName] == Json::UInt(expected))); + }; + + BEAST_EXPECT(node["LedgerEntryType"].asString() == "Vault"); + BEAST_EXPECT(node[jss::index].asString() == strHex(keylet.key)); + BEAST_EXPECT(checkInt(sfFlags, 0)); + // Ignore all other standard fields, this test doesn't care + + BEAST_EXPECT(checkString(sfAccount, toBase58(sle->at(sfAccount)))); + BEAST_EXPECT(checkObject(sfAsset, to_json(sle->at(sfAsset)))); + BEAST_EXPECT(checkString(sfAssetsAvailable, "50")); + BEAST_EXPECT(checkString(sfAssetsMaximum, "1000")); + BEAST_EXPECT(checkString(sfAssetsTotal, "50")); + BEAST_EXPECT(checkString(sfLossUnrealized, "0")); + BEAST_EXPECT(checkString( + sfMPTokenIssuanceID, strHex(sle->at(sfMPTokenIssuanceID)))); + BEAST_EXPECT(checkString(sfOwner, toBase58(owner.id()))); + BEAST_EXPECT(checkInt(sfSequence, sequence)); + BEAST_EXPECT( + checkInt(sfWithdrawalPolicy, vaultStrategyFirstComeFirstServe)); + + // This field is injected in STLedgerEntry::getJson + BEAST_EXPECT( + node.isMember(jss::Share) && node[jss::Share].isObject() && + node[jss::Share] == to_json(sle->at(sfMPTokenIssuanceID))); + + // This field is injected in RPC::supplementJson + BEAST_EXPECT( + node.isMember(jss::SharesTotal) && + node[jss::SharesTotal].isString() && + node[jss::SharesTotal] == "50"); + }; + + { + testcase("RPC ledger_entry selected by key"); + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::vault] = strHex(keylet.key); + auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); + + BEAST_EXPECT(!jvVault[jss::result].isMember(jss::error)); + BEAST_EXPECT(jvVault[jss::result].isMember(jss::node)); + check(jvVault[jss::result][jss::node]); + } + + { + testcase("RPC ledger_entry selected by owner and seq"); + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::vault][jss::owner] = owner.human(); + jvParams[jss::vault][jss::seq] = sequence; + auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); + + BEAST_EXPECT(!jvVault[jss::result].isMember(jss::error)); + BEAST_EXPECT(jvVault[jss::result].isMember(jss::node)); + check(jvVault[jss::result][jss::node]); + } + + { + testcase("RPC ledger_entry cannot find vault by key"); + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::vault] = to_string(uint256(42)); + auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + jvVault[jss::result][jss::error].asString() == "entryNotFound"); + } + + { + testcase("RPC ledger_entry cannot find vault by owner and seq"); + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::vault][jss::owner] = issuer.human(); + jvParams[jss::vault][jss::seq] = 1'000'000; + auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + jvVault[jss::result][jss::error].asString() == "entryNotFound"); + } + + { + testcase("RPC ledger_entry malformed key"); + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::vault] = 42; + auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + jvVault[jss::result][jss::error].asString() == + "malformedRequest"); + } + + { + testcase("RPC ledger_entry malformed owner"); + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::vault][jss::owner] = 42; + jvParams[jss::vault][jss::seq] = sequence; + auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + jvVault[jss::result][jss::error].asString() == + "malformedOwner"); + } + + { + testcase("RPC ledger_entry malformed seq"); + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::vault][jss::owner] = issuer.human(); + jvParams[jss::vault][jss::seq] = "42"; + auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + jvVault[jss::result][jss::error].asString() == + "malformedRequest"); + } + + { + testcase("RPC account_objects"); + + Json::Value jvParams; + jvParams[jss::account] = owner.human(); + jvParams[jss::type] = jss::vault; + auto jv = env.rpc( + "json", "account_objects", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jv[jss::account_objects].size() == 1); + check(jv[jss::account_objects][0u]); + } + + { + testcase("RPC ledger_data"); + + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::binary] = false; + jvParams[jss::type] = jss::vault; + Json::Value jv = + env.rpc("json", "ledger_data", to_string(jvParams)); + BEAST_EXPECT(jv[jss::result][jss::state].size() == 1); + check(jv[jss::result][jss::state][0u]); + } + } + public: void run() override @@ -1204,6 +1380,7 @@ public: testWithDomainCheck(); testNonTransferableShares(); testFailedPseudoAccount(); + testRPC(); } };