mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 22:30:57 +00:00
This PR splits out `ledger_entry` tests into its own file (`LedgerEntry_test.cpp`) and alphabetizes the helper functions in `LedgerEntry.cpp`. These commits were split out of #5237 to make that PR a little more manageable, since these basic trivial changes are most of the diff. There is no code change, just moving code around.
2608 lines
102 KiB
C++
2608 lines
102 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012-2025 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <test/jtx.h>
|
|
#include <test/jtx/Oracle.h>
|
|
#include <test/jtx/attester.h>
|
|
#include <test/jtx/multisign.h>
|
|
#include <test/jtx/xchain_bridge.h>
|
|
|
|
#include <xrpl/beast/unit_test.h>
|
|
#include <xrpl/json/json_value.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/ErrorCodes.h>
|
|
#include <xrpl/protocol/STXChainBridge.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
|
|
namespace ripple {
|
|
|
|
namespace test {
|
|
|
|
class LedgerEntry_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
checkErrorValue(
|
|
Json::Value const& jv,
|
|
std::string const& err,
|
|
std::string const& msg)
|
|
{
|
|
if (BEAST_EXPECT(jv.isMember(jss::status)))
|
|
BEAST_EXPECT(jv[jss::status] == "error");
|
|
if (BEAST_EXPECT(jv.isMember(jss::error)))
|
|
BEAST_EXPECT(jv[jss::error] == err);
|
|
if (msg.empty())
|
|
{
|
|
BEAST_EXPECT(
|
|
jv[jss::error_message] == Json::nullValue ||
|
|
jv[jss::error_message] == "");
|
|
}
|
|
else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
|
|
BEAST_EXPECT(jv[jss::error_message] == msg);
|
|
}
|
|
|
|
// Corrupt a valid address by replacing the 10th character with '!'.
|
|
// '!' is not part of the ripple alphabet.
|
|
std::string
|
|
makeBadAddress(std::string good)
|
|
{
|
|
std::string ret = std::move(good);
|
|
ret.replace(10, 1, 1, '!');
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
testLedgerEntryInvalid()
|
|
{
|
|
testcase("Invalid requests");
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
{
|
|
// Missing ledger_entry ledger_hash
|
|
Json::Value jvParams;
|
|
jvParams[jss::account_root] = alice.human();
|
|
jvParams[jss::ledger_hash] =
|
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
"AA";
|
|
auto const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
|
|
}
|
|
|
|
{
|
|
// ask for an zero index
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = "validated";
|
|
jvParams[jss::index] =
|
|
"00000000000000000000000000000000000000000000000000000000000000"
|
|
"0000";
|
|
auto const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryAccountRoot()
|
|
{
|
|
testcase("ledger_entry Request AccountRoot");
|
|
using namespace test::jtx;
|
|
|
|
auto cfg = envconfig();
|
|
cfg->FEES.reference_fee = 10;
|
|
Env env{*this, std::move(cfg)};
|
|
|
|
Account const alice{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
{
|
|
// Exercise ledger_closed along the way.
|
|
Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
|
|
BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
|
|
BEAST_EXPECT(jrr[jss::ledger_index] == 3);
|
|
}
|
|
|
|
std::string accountRootIndex;
|
|
{
|
|
// Request alice's account root.
|
|
Json::Value jvParams;
|
|
jvParams[jss::account_root] = alice.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
|
|
accountRootIndex = jrr[jss::index].asString();
|
|
}
|
|
{
|
|
constexpr char alicesAcctRootBinary[]{
|
|
"1100612200800000240000000425000000032D00000000559CE54C3B934E4"
|
|
"73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002"
|
|
"540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"};
|
|
|
|
// Request alice's account root, but with binary == true;
|
|
Json::Value jvParams;
|
|
jvParams[jss::account_root] = alice.human();
|
|
jvParams[jss::binary] = 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr.isMember(jss::node_binary));
|
|
BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary);
|
|
}
|
|
{
|
|
// Request alice's account root using the index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::index] = accountRootIndex;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(!jrr.isMember(jss::node_binary));
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
|
|
}
|
|
{
|
|
// Request alice's account root by index, but with binary == false.
|
|
Json::Value jvParams;
|
|
jvParams[jss::index] = accountRootIndex;
|
|
jvParams[jss::binary] = 0;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
|
|
}
|
|
{
|
|
// Request using a corrupted AccountID.
|
|
Json::Value jvParams;
|
|
jvParams[jss::account_root] = makeBadAddress(alice.human());
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedAddress", "");
|
|
}
|
|
{
|
|
// Request an account that is not in the ledger.
|
|
Json::Value jvParams;
|
|
jvParams[jss::account_root] = Account("bob").human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryCheck()
|
|
{
|
|
testcase("ledger_entry Request Check");
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
auto const checkId = keylet::check(env.master, env.seq(env.master));
|
|
|
|
env(check::create(env.master, alice, XRP(100)));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
{
|
|
// Request a check.
|
|
Json::Value jvParams;
|
|
jvParams[jss::check] = to_string(checkId.key);
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
|
|
BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
|
|
}
|
|
{
|
|
// Request an index that is not a check. We'll use alice's
|
|
// account root index.
|
|
std::string accountRootIndex;
|
|
{
|
|
Json::Value jvParams;
|
|
jvParams[jss::account_root] = alice.human();
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
accountRootIndex = jrr[jss::index].asString();
|
|
}
|
|
Json::Value jvParams;
|
|
jvParams[jss::check] = accountRootIndex;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "unexpectedLedgerType", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryCredentials()
|
|
{
|
|
testcase("ledger_entry credentials");
|
|
|
|
using namespace test::jtx;
|
|
|
|
Env env(*this);
|
|
Account const issuer{"issuer"};
|
|
Account const alice{"alice"};
|
|
Account const bob{"bob"};
|
|
const char credType[] = "abcde";
|
|
|
|
env.fund(XRP(5000), issuer, alice, bob);
|
|
env.close();
|
|
|
|
// Setup credentials with DepositAuth object for Alice and Bob
|
|
env(credentials::create(alice, issuer, credType));
|
|
env.close();
|
|
|
|
{
|
|
// Succeed
|
|
auto jv = credentials::ledgerEntry(env, alice, issuer, credType);
|
|
BEAST_EXPECT(
|
|
jv.isObject() && jv.isMember(jss::result) &&
|
|
!jv[jss::result].isMember(jss::error) &&
|
|
jv[jss::result].isMember(jss::node) &&
|
|
jv[jss::result][jss::node].isMember(
|
|
sfLedgerEntryType.jsonName) &&
|
|
jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
|
|
jss::Credential);
|
|
|
|
std::string const credIdx = jv[jss::result][jss::index].asString();
|
|
|
|
jv = credentials::ledgerEntry(env, credIdx);
|
|
BEAST_EXPECT(
|
|
jv.isObject() && jv.isMember(jss::result) &&
|
|
!jv[jss::result].isMember(jss::error) &&
|
|
jv[jss::result].isMember(jss::node) &&
|
|
jv[jss::result][jss::node].isMember(
|
|
sfLedgerEntryType.jsonName) &&
|
|
jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
|
|
jss::Credential);
|
|
}
|
|
|
|
{
|
|
// Fail, index not a hash
|
|
auto const jv = credentials::ledgerEntry(env, "");
|
|
checkErrorValue(jv[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, credential doesn't exist
|
|
auto const jv = credentials::ledgerEntry(
|
|
env,
|
|
"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
|
|
"E4");
|
|
checkErrorValue(jv[jss::result], "entryNotFound", "");
|
|
}
|
|
|
|
{
|
|
// Fail, invalid subject
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = 42;
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, invalid issuer
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::issuer] = 42;
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, invalid credentials type
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
jv[jss::credential][jss::credential_type] = 42;
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, empty subject
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = "";
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, empty issuer
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::issuer] = "";
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, empty credentials type
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
jv[jss::credential][jss::credential_type] = "";
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, no subject
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, no issuer
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, no credentials type
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, not AccountID subject
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = "wehsdbvasbdfvj";
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, not AccountID issuer
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::issuer] = "c4p93ugndfbsiu";
|
|
jv[jss::credential][jss::credential_type] =
|
|
strHex(std::string_view(credType));
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, credentials type isn't hex encoded
|
|
Json::Value jv;
|
|
jv[jss::ledger_index] = jss::validated;
|
|
jv[jss::credential][jss::subject] = alice.human();
|
|
jv[jss::credential][jss::issuer] = issuer.human();
|
|
jv[jss::credential][jss::credential_type] = "12KK";
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(jv));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryDepositPreauth()
|
|
{
|
|
testcase("ledger_entry Deposit Preauth");
|
|
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
Account const becky{"becky"};
|
|
|
|
env.fund(XRP(10000), alice, becky);
|
|
env.close();
|
|
|
|
env(deposit::auth(alice, becky));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
std::string depositPreauthIndex;
|
|
{
|
|
// Request a depositPreauth by owner and authorized.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfLedgerEntryType.jsonName] ==
|
|
jss::DepositPreauth);
|
|
BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
|
|
depositPreauthIndex = jrr[jss::node][jss::index].asString();
|
|
}
|
|
{
|
|
// Request a depositPreauth by index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth] = depositPreauthIndex;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfLedgerEntryType.jsonName] ==
|
|
jss::DepositPreauth);
|
|
BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
|
|
}
|
|
{
|
|
// Malformed request: deposit_preauth neither object nor string.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth] = -5;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed request: deposit_preauth not hex string.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth] = "0123456789ABCDEFG";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed request: missing [jss::deposit_preauth][jss::owner]
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed request: [jss::deposit_preauth][jss::owner] not string.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth][jss::owner] = 7;
|
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed: missing [jss::deposit_preauth][jss::authorized]
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed: [jss::deposit_preauth][jss::authorized] not string.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized] = 47;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed: [jss::deposit_preauth][jss::owner] is malformed.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth][jss::owner] =
|
|
"rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedOwner", "");
|
|
}
|
|
{
|
|
// Malformed: [jss::deposit_preauth][jss::authorized] is malformed.
|
|
Json::Value jvParams;
|
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized] =
|
|
"rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
|
|
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedAuthorized", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryDepositPreauthCred()
|
|
{
|
|
testcase("ledger_entry Deposit Preauth with credentials");
|
|
|
|
using namespace test::jtx;
|
|
|
|
Env env(*this);
|
|
Account const issuer{"issuer"};
|
|
Account const alice{"alice"};
|
|
Account const bob{"bob"};
|
|
const char credType[] = "abcde";
|
|
|
|
env.fund(XRP(5000), issuer, alice, bob);
|
|
env.close();
|
|
|
|
{
|
|
// Setup Bob with DepositAuth
|
|
env(fset(bob, asfDepositAuth));
|
|
env.close();
|
|
env(deposit::authCredentials(bob, {{issuer, credType}}));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// Succeed
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(std::move(jo));
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
|
|
BEAST_EXPECT(
|
|
jrr.isObject() && jrr.isMember(jss::result) &&
|
|
!jrr[jss::result].isMember(jss::error) &&
|
|
jrr[jss::result].isMember(jss::node) &&
|
|
jrr[jss::result][jss::node].isMember(
|
|
sfLedgerEntryType.jsonName) &&
|
|
jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
|
|
jss::DepositPreauth);
|
|
}
|
|
|
|
{
|
|
// Failed, invalid account
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = to_string(xrpAccount());
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(std::move(jo));
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, duplicates in credentials
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(jo);
|
|
arr.append(std::move(jo));
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, invalid credential_type
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
jo[jss::credential_type] = "";
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, authorized and authorized_credentials both present
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized] = alice.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Failed, authorized_credentials is not an array
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] = 42;
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Failed, authorized_credentials contains string data
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
arr.append("foobar");
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, authorized_credentials contains arrays
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
Json::Value payload = Json::arrayValue;
|
|
payload.append(42);
|
|
arr.append(std::move(payload));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, authorized_credentials is empty array
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, authorized_credentials is too long
|
|
|
|
static const std::string_view credTypes[] = {
|
|
"cred1",
|
|
"cred2",
|
|
"cred3",
|
|
"cred4",
|
|
"cred5",
|
|
"cred6",
|
|
"cred7",
|
|
"cred8",
|
|
"cred9"};
|
|
static_assert(
|
|
sizeof(credTypes) / sizeof(credTypes[0]) >
|
|
maxCredentialsArraySize);
|
|
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
for (unsigned i = 0; i < sizeof(credTypes) / sizeof(credTypes[0]);
|
|
++i)
|
|
{
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
jo[jss::credential_type] =
|
|
strHex(std::string_view(credTypes[i]));
|
|
arr.append(std::move(jo));
|
|
}
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, issuer is not set
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, issuer isn't string
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = 42;
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, issuer is an array
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
Json::Value payload = Json::arrayValue;
|
|
payload.append(42);
|
|
jo[jss::issuer] = std::move(payload);
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, issuer isn't valid encoded account
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = "invalid_account";
|
|
jo[jss::credential_type] = strHex(std::string_view(credType));
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, credential_type is not set
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, credential_type isn't string
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
jo[jss::credential_type] = 42;
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, credential_type is an array
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
Json::Value payload = Json::arrayValue;
|
|
payload.append(42);
|
|
jo[jss::credential_type] = std::move(payload);
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
|
|
{
|
|
// Failed, credential_type isn't hex encoded
|
|
Json::Value jvParams;
|
|
jvParams[jss::ledger_index] = jss::validated;
|
|
jvParams[jss::deposit_preauth][jss::owner] = bob.human();
|
|
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials] =
|
|
Json::arrayValue;
|
|
auto& arr(
|
|
jvParams[jss::deposit_preauth][jss::authorized_credentials]);
|
|
|
|
Json::Value jo;
|
|
jo[jss::issuer] = issuer.human();
|
|
jo[jss::credential_type] = "12KK";
|
|
arr.append(std::move(jo));
|
|
|
|
auto const jrr =
|
|
env.rpc("json", "ledger_entry", to_string(jvParams));
|
|
checkErrorValue(
|
|
jrr[jss::result], "malformedAuthorizedCredentials", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryDirectory()
|
|
{
|
|
testcase("ledger_entry Request Directory");
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
Account const gw{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(10000), alice, gw);
|
|
env.close();
|
|
|
|
env.trust(USD(1000), alice);
|
|
env.close();
|
|
|
|
// Run up the number of directory entries so alice has two
|
|
// directory nodes.
|
|
for (int d = 1'000'032; d >= 1'000'000; --d)
|
|
{
|
|
env(offer(alice, USD(1), drops(d)));
|
|
}
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
{
|
|
// Exercise ledger_closed along the way.
|
|
Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
|
|
BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
|
|
BEAST_EXPECT(jrr[jss::ledger_index] == 5);
|
|
}
|
|
|
|
std::string const dirRootIndex =
|
|
"A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D";
|
|
{
|
|
// Locate directory by index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = dirRootIndex;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32);
|
|
}
|
|
{
|
|
// Locate directory by directory root.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
jvParams[jss::directory][jss::dir_root] = dirRootIndex;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
|
|
}
|
|
{
|
|
// Locate directory by owner.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
jvParams[jss::directory][jss::owner] = alice.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
|
|
}
|
|
{
|
|
// Locate directory by directory root and sub_index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
jvParams[jss::directory][jss::dir_root] = dirRootIndex;
|
|
jvParams[jss::directory][jss::sub_index] = 1;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
|
|
BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
|
|
}
|
|
{
|
|
// Locate directory by owner and sub_index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
jvParams[jss::directory][jss::owner] = alice.human();
|
|
jvParams[jss::directory][jss::sub_index] = 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
|
|
BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
|
|
}
|
|
{
|
|
// Null directory argument.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::nullValue;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Non-integer sub_index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
jvParams[jss::directory][jss::dir_root] = dirRootIndex;
|
|
jvParams[jss::directory][jss::sub_index] = 1.5;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed owner entry.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
|
|
std::string const badAddress = makeBadAddress(alice.human());
|
|
jvParams[jss::directory][jss::owner] = badAddress;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedAddress", "");
|
|
}
|
|
{
|
|
// Malformed directory object. Specify both dir_root and owner.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
jvParams[jss::directory][jss::owner] = alice.human();
|
|
jvParams[jss::directory][jss::dir_root] = dirRootIndex;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Incomplete directory object. Missing both dir_root and owner.
|
|
Json::Value jvParams;
|
|
jvParams[jss::directory] = Json::objectValue;
|
|
jvParams[jss::directory][jss::sub_index] = 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryEscrow()
|
|
{
|
|
testcase("ledger_entry Request Escrow");
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
// Lambda to create an escrow.
|
|
auto escrowCreate = [](test::jtx::Account const& account,
|
|
test::jtx::Account const& to,
|
|
STAmount const& amount,
|
|
NetClock::time_point const& cancelAfter) {
|
|
Json::Value jv;
|
|
jv[jss::TransactionType] = jss::EscrowCreate;
|
|
jv[jss::Flags] = tfUniversal;
|
|
jv[jss::Account] = account.human();
|
|
jv[jss::Destination] = to.human();
|
|
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
|
jv[sfFinishAfter.jsonName] =
|
|
cancelAfter.time_since_epoch().count() + 2;
|
|
return jv;
|
|
};
|
|
|
|
using namespace std::chrono_literals;
|
|
env(escrowCreate(alice, alice, XRP(333), env.now() + 2s));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
std::string escrowIndex;
|
|
{
|
|
// Request the escrow using owner and sequence.
|
|
Json::Value jvParams;
|
|
jvParams[jss::escrow] = Json::objectValue;
|
|
jvParams[jss::escrow][jss::owner] = alice.human();
|
|
jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][jss::Amount] == XRP(333).value().getText());
|
|
escrowIndex = jrr[jss::index].asString();
|
|
}
|
|
{
|
|
// Request the escrow by index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::escrow] = escrowIndex;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][jss::Amount] == XRP(333).value().getText());
|
|
}
|
|
{
|
|
// Malformed owner entry.
|
|
Json::Value jvParams;
|
|
jvParams[jss::escrow] = Json::objectValue;
|
|
|
|
std::string const badAddress = makeBadAddress(alice.human());
|
|
jvParams[jss::escrow][jss::owner] = badAddress;
|
|
jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedOwner", "");
|
|
}
|
|
{
|
|
// Missing owner.
|
|
Json::Value jvParams;
|
|
jvParams[jss::escrow] = Json::objectValue;
|
|
jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Missing sequence.
|
|
Json::Value jvParams;
|
|
jvParams[jss::escrow] = Json::objectValue;
|
|
jvParams[jss::escrow][jss::owner] = alice.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Non-integer sequence.
|
|
Json::Value jvParams;
|
|
jvParams[jss::escrow] = Json::objectValue;
|
|
jvParams[jss::escrow][jss::owner] = alice.human();
|
|
jvParams[jss::escrow][jss::seq] =
|
|
std::to_string(env.seq(alice) - 1);
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryOffer()
|
|
{
|
|
testcase("ledger_entry Request Offer");
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
Account const gw{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(10000), alice, gw);
|
|
env.close();
|
|
|
|
env(offer(alice, USD(321), XRP(322)));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
std::string offerIndex;
|
|
{
|
|
// Request the offer using owner and sequence.
|
|
Json::Value jvParams;
|
|
jvParams[jss::offer] = Json::objectValue;
|
|
jvParams[jss::offer][jss::account] = alice.human();
|
|
jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
|
|
offerIndex = jrr[jss::index].asString();
|
|
}
|
|
{
|
|
// Request the offer using its index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::offer] = offerIndex;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
|
|
}
|
|
{
|
|
// Malformed account entry.
|
|
Json::Value jvParams;
|
|
jvParams[jss::offer] = Json::objectValue;
|
|
|
|
std::string const badAddress = makeBadAddress(alice.human());
|
|
jvParams[jss::offer][jss::account] = badAddress;
|
|
jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedAddress", "");
|
|
}
|
|
{
|
|
// Malformed offer object. Missing account member.
|
|
Json::Value jvParams;
|
|
jvParams[jss::offer] = Json::objectValue;
|
|
jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed offer object. Missing seq member.
|
|
Json::Value jvParams;
|
|
jvParams[jss::offer] = Json::objectValue;
|
|
jvParams[jss::offer][jss::account] = alice.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed offer object. Non-integral seq member.
|
|
Json::Value jvParams;
|
|
jvParams[jss::offer] = Json::objectValue;
|
|
jvParams[jss::offer][jss::account] = alice.human();
|
|
jvParams[jss::offer][jss::seq] = std::to_string(env.seq(alice) - 1);
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryPayChan()
|
|
{
|
|
testcase("ledger_entry Request Pay Chan");
|
|
using namespace test::jtx;
|
|
using namespace std::literals::chrono_literals;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
// Lambda to create a PayChan.
|
|
auto payChanCreate = [](test::jtx::Account const& account,
|
|
test::jtx::Account const& to,
|
|
STAmount const& amount,
|
|
NetClock::duration const& settleDelay,
|
|
PublicKey const& pk) {
|
|
Json::Value jv;
|
|
jv[jss::TransactionType] = jss::PaymentChannelCreate;
|
|
jv[jss::Account] = account.human();
|
|
jv[jss::Destination] = to.human();
|
|
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
|
jv[sfSettleDelay.jsonName] = settleDelay.count();
|
|
jv[sfPublicKey.jsonName] = strHex(pk.slice());
|
|
return jv;
|
|
};
|
|
|
|
env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
|
|
uint256 const payChanIndex{
|
|
keylet::payChan(alice, env.master, env.seq(alice) - 1).key};
|
|
{
|
|
// Request the payment channel using its index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::payment_channel] = to_string(payChanIndex);
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
|
|
BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
|
|
BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
|
|
}
|
|
{
|
|
// Request an index that is not a payment channel.
|
|
Json::Value jvParams;
|
|
jvParams[jss::payment_channel] = ledgerHash;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryRippleState()
|
|
{
|
|
testcase("ledger_entry Request RippleState");
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
Account const gw{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(10000), alice, gw);
|
|
env.close();
|
|
|
|
env.trust(USD(999), alice);
|
|
env.close();
|
|
|
|
env(pay(gw, alice, USD(97)));
|
|
env.close();
|
|
|
|
// check both aliases
|
|
for (auto const& fieldName : {jss::ripple_state, jss::state})
|
|
{
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
{
|
|
// Request the trust line using the accounts and currency.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::accounts][1u] = gw.human();
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfBalance.jsonName][jss::value] == "-97");
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999");
|
|
}
|
|
{
|
|
// ripple_state is not an object.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = "ripple_state";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state.currency is missing.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::accounts][1u] = gw.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state accounts is not an array.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = 2;
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state one of the accounts is missing.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state more than 2 accounts.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::accounts][1u] = gw.human();
|
|
jvParams[fieldName][jss::accounts][2u] = alice.human();
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state account[0] is not a string.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = 44;
|
|
jvParams[fieldName][jss::accounts][1u] = gw.human();
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state account[1] is not a string.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::accounts][1u] = 21;
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state account[0] == account[1].
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::accounts][1u] = alice.human();
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// ripple_state malformed account[0].
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] =
|
|
makeBadAddress(alice.human());
|
|
jvParams[fieldName][jss::accounts][1u] = gw.human();
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedAddress", "");
|
|
}
|
|
{
|
|
// ripple_state malformed account[1].
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::accounts][1u] =
|
|
makeBadAddress(gw.human());
|
|
jvParams[fieldName][jss::currency] = "USD";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedAddress", "");
|
|
}
|
|
{
|
|
// ripple_state malformed currency.
|
|
Json::Value jvParams;
|
|
jvParams[fieldName] = Json::objectValue;
|
|
jvParams[fieldName][jss::accounts] = Json::arrayValue;
|
|
jvParams[fieldName][jss::accounts][0u] = alice.human();
|
|
jvParams[fieldName][jss::accounts][1u] = gw.human();
|
|
jvParams[fieldName][jss::currency] = "USDollars";
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedCurrency", "");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryTicket()
|
|
{
|
|
testcase("ledger_entry Request Ticket");
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
env.close();
|
|
|
|
// Create two tickets.
|
|
std::uint32_t const tkt1{env.seq(env.master) + 1};
|
|
env(ticket::create(env.master, 2));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
// Request four tickets: one before the first one we created, the
|
|
// two created tickets, and the ticket that would come after the
|
|
// last created ticket.
|
|
{
|
|
// Not a valid ticket requested by index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] =
|
|
to_string(getTicketIndex(env.master, tkt1 - 1));
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
{
|
|
// First real ticket requested by index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1));
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket);
|
|
BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1);
|
|
}
|
|
{
|
|
// Second real ticket requested by account and sequence.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = Json::objectValue;
|
|
jvParams[jss::ticket][jss::account] = env.master.human();
|
|
jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][jss::index] ==
|
|
to_string(getTicketIndex(env.master, tkt1 + 1)));
|
|
}
|
|
{
|
|
// Not a valid ticket requested by account and sequence.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = Json::objectValue;
|
|
jvParams[jss::ticket][jss::account] = env.master.human();
|
|
jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
{
|
|
// Request a ticket using an account root entry.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = to_string(keylet::account(env.master).key);
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "unexpectedLedgerType", "");
|
|
}
|
|
{
|
|
// Malformed account entry.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = Json::objectValue;
|
|
|
|
std::string const badAddress = makeBadAddress(env.master.human());
|
|
jvParams[jss::ticket][jss::account] = badAddress;
|
|
jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedAddress", "");
|
|
}
|
|
{
|
|
// Malformed ticket object. Missing account member.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = Json::objectValue;
|
|
jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed ticket object. Missing seq member.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = Json::objectValue;
|
|
jvParams[jss::ticket][jss::account] = env.master.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
{
|
|
// Malformed ticket object. Non-integral seq member.
|
|
Json::Value jvParams;
|
|
jvParams[jss::ticket] = Json::objectValue;
|
|
jvParams[jss::ticket][jss::account] = env.master.human();
|
|
jvParams[jss::ticket][jss::ticket_seq] =
|
|
std::to_string(env.seq(env.master) - 1);
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryDID()
|
|
{
|
|
testcase("ledger_entry Request DID");
|
|
using namespace test::jtx;
|
|
using namespace std::literals::chrono_literals;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
// Lambda to create a DID.
|
|
auto didCreate = [](test::jtx::Account const& account) {
|
|
Json::Value jv;
|
|
jv[jss::TransactionType] = jss::DIDSet;
|
|
jv[jss::Account] = account.human();
|
|
jv[sfDIDDocument.jsonName] = strHex(std::string{"data"});
|
|
jv[sfURI.jsonName] = strHex(std::string{"uri"});
|
|
return jv;
|
|
};
|
|
|
|
env(didCreate(alice));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
|
|
{
|
|
// Request the DID using its index.
|
|
Json::Value jvParams;
|
|
jvParams[jss::did] = alice.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfDIDDocument.jsonName] ==
|
|
strHex(std::string{"data"}));
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"}));
|
|
}
|
|
{
|
|
// Request an index that is not a DID.
|
|
Json::Value jvParams;
|
|
jvParams[jss::did] = env.master.human();
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryInvalidParams(unsigned int apiVersion)
|
|
{
|
|
testcase(
|
|
"ledger_entry Request With Invalid Parameters v" +
|
|
std::to_string(apiVersion));
|
|
using namespace test::jtx;
|
|
Env env{*this};
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
|
|
auto makeParams = [&apiVersion](std::function<void(Json::Value&)> f) {
|
|
Json::Value params;
|
|
params[jss::api_version] = apiVersion;
|
|
f(params);
|
|
return params;
|
|
};
|
|
// "features" is not an option supported by ledger_entry.
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&ledgerHash](Json::Value& jvParams) {
|
|
jvParams[jss::features] = ledgerHash;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
});
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "unknownOption", "");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
Json::Value const injectObject = []() {
|
|
Json::Value obj(Json::objectValue);
|
|
obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
|
|
obj[jss::ledger_index] = "validated";
|
|
return obj;
|
|
}();
|
|
Json::Value const injectArray = []() {
|
|
Json::Value arr(Json::arrayValue);
|
|
arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
|
|
arr[1u] = "validated";
|
|
return arr;
|
|
}();
|
|
|
|
// invalid input for fields that can handle an object, but can't handle
|
|
// an array
|
|
for (auto const& field :
|
|
{jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm})
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&field, &injectArray](Json::Value& jvParams) {
|
|
jvParams[field] = injectArray;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "internal", "Internal error.");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
// Fields that can handle objects just fine
|
|
for (auto const& field :
|
|
{jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm})
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&field, &injectObject](Json::Value& jvParams) {
|
|
jvParams[field] = injectObject;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
|
|
for (auto const& inject : {injectObject, injectArray})
|
|
{
|
|
// invalid input for fields that can't handle an object or an array
|
|
for (auto const& field :
|
|
{jss::index,
|
|
jss::account_root,
|
|
jss::check,
|
|
jss::payment_channel})
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&field, &inject](Json::Value& jvParams) {
|
|
jvParams[field] = inject;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "internal", "Internal error.");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
// directory sub-fields
|
|
for (auto const& field : {jss::dir_root, jss::owner})
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&field, &inject](Json::Value& jvParams) {
|
|
jvParams[jss::directory][field] = inject;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "internal", "Internal error.");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
// escrow sub-fields
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&inject](Json::Value& jvParams) {
|
|
jvParams[jss::escrow][jss::owner] = inject;
|
|
jvParams[jss::escrow][jss::seq] = 99;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "internal", "Internal error.");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
// offer sub-fields
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&inject](Json::Value& jvParams) {
|
|
jvParams[jss::offer][jss::account] = inject;
|
|
jvParams[jss::offer][jss::seq] = 99;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "internal", "Internal error.");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
// ripple_state sub-fields
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&inject](Json::Value& jvParams) {
|
|
Json::Value rs(Json::objectValue);
|
|
rs[jss::currency] = "FOO";
|
|
rs[jss::accounts] = Json::Value(Json::arrayValue);
|
|
rs[jss::accounts][0u] =
|
|
"rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
|
|
rs[jss::accounts][1u] =
|
|
"rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
|
|
rs[jss::currency] = inject;
|
|
jvParams[jss::ripple_state] = std::move(rs);
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "internal", "Internal error.");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
// ticket sub-fields
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&inject](Json::Value& jvParams) {
|
|
jvParams[jss::ticket][jss::account] = inject;
|
|
jvParams[jss::ticket][jss::ticket_seq] = 99;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
if (apiVersion < 2u)
|
|
checkErrorValue(jrr, "internal", "Internal error.");
|
|
else
|
|
checkErrorValue(jrr, "invalidParams", "");
|
|
}
|
|
|
|
// Fields that can handle malformed inputs just fine
|
|
for (auto const& field : {jss::nft_page, jss::deposit_preauth})
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&field, &inject](Json::Value& jvParams) {
|
|
jvParams[field] = inject;
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
// Subfields of deposit_preauth that can handle malformed inputs
|
|
// fine
|
|
for (auto const& field : {jss::owner, jss::authorized})
|
|
{
|
|
auto const jvParams =
|
|
makeParams([&field, &inject](Json::Value& jvParams) {
|
|
auto pa = Json::Value(Json::objectValue);
|
|
pa[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
|
|
pa[jss::authorized] =
|
|
"rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
|
|
pa[field] = inject;
|
|
jvParams[jss::deposit_preauth] = std::move(pa);
|
|
});
|
|
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
checkErrorValue(jrr, "malformedRequest", "");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testInvalidOracleLedgerEntry()
|
|
{
|
|
testcase("Invalid Oracle Ledger Entry");
|
|
using namespace ripple::test::jtx;
|
|
using namespace ripple::test::jtx::oracle;
|
|
|
|
Env env(*this);
|
|
Account const owner("owner");
|
|
env.fund(XRP(1'000), owner);
|
|
Oracle oracle(
|
|
env,
|
|
{.owner = owner,
|
|
.fee = static_cast<int>(env.current()->fees().base.drops())});
|
|
|
|
// Malformed document id
|
|
auto res = Oracle::ledgerEntry(env, owner, NoneTag);
|
|
BEAST_EXPECT(res[jss::error].asString() == "invalidParams");
|
|
std::vector<AnyValue> invalid = {-1, 1.2, "", "Invalid"};
|
|
for (auto const& v : invalid)
|
|
{
|
|
auto const res = Oracle::ledgerEntry(env, owner, v);
|
|
BEAST_EXPECT(res[jss::error].asString() == "malformedDocumentID");
|
|
}
|
|
// Missing document id
|
|
res = Oracle::ledgerEntry(env, owner, std::nullopt);
|
|
BEAST_EXPECT(res[jss::error].asString() == "malformedRequest");
|
|
|
|
// Missing account
|
|
res = Oracle::ledgerEntry(env, std::nullopt, 1);
|
|
BEAST_EXPECT(res[jss::error].asString() == "malformedRequest");
|
|
|
|
// Malformed account
|
|
std::string malfAccount = to_string(owner.id());
|
|
malfAccount.replace(10, 1, 1, '!');
|
|
res = Oracle::ledgerEntry(env, malfAccount, 1);
|
|
BEAST_EXPECT(res[jss::error].asString() == "malformedAddress");
|
|
}
|
|
|
|
void
|
|
testOracleLedgerEntry()
|
|
{
|
|
testcase("Oracle Ledger Entry");
|
|
using namespace ripple::test::jtx;
|
|
using namespace ripple::test::jtx::oracle;
|
|
|
|
Env env(*this);
|
|
auto const baseFee =
|
|
static_cast<int>(env.current()->fees().base.drops());
|
|
std::vector<AccountID> accounts;
|
|
std::vector<std::uint32_t> oracles;
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
Account const owner(std::string("owner") + std::to_string(i));
|
|
env.fund(XRP(1'000), owner);
|
|
// different accounts can have the same asset pair
|
|
Oracle oracle(
|
|
env, {.owner = owner, .documentID = i, .fee = baseFee});
|
|
accounts.push_back(owner.id());
|
|
oracles.push_back(oracle.documentID());
|
|
// same account can have different asset pair
|
|
Oracle oracle1(
|
|
env, {.owner = owner, .documentID = i + 10, .fee = baseFee});
|
|
accounts.push_back(owner.id());
|
|
oracles.push_back(oracle1.documentID());
|
|
}
|
|
for (int i = 0; i < accounts.size(); ++i)
|
|
{
|
|
auto const jv = [&]() {
|
|
// document id is uint32
|
|
if (i % 2)
|
|
return Oracle::ledgerEntry(env, accounts[i], oracles[i]);
|
|
// document id is string
|
|
return Oracle::ledgerEntry(
|
|
env, accounts[i], std::to_string(oracles[i]));
|
|
}();
|
|
try
|
|
{
|
|
BEAST_EXPECT(
|
|
jv[jss::node][jss::Owner] == to_string(accounts[i]));
|
|
}
|
|
catch (...)
|
|
{
|
|
fail();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryMPT()
|
|
{
|
|
testcase("ledger_entry Request MPT");
|
|
using namespace test::jtx;
|
|
using namespace std::literals::chrono_literals;
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
Account const bob("bob");
|
|
|
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
|
mptAlice.create(
|
|
{.transferFee = 10,
|
|
.metadata = "123",
|
|
.ownerCount = 1,
|
|
.flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow |
|
|
tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback});
|
|
mptAlice.authorize({.account = bob, .holderCount = 1});
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
|
|
std::string const badMptID =
|
|
"00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315";
|
|
{
|
|
// Request the MPTIssuance using its MPTIssuanceID.
|
|
Json::Value jvParams;
|
|
jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID());
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfMPTokenMetadata.jsonName] ==
|
|
strHex(std::string{"123"}));
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][jss::mpt_issuance_id] ==
|
|
strHex(mptAlice.issuanceID()));
|
|
}
|
|
{
|
|
// Request an index that is not a MPTIssuance.
|
|
Json::Value jvParams;
|
|
jvParams[jss::mpt_issuance] = badMptID;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
{
|
|
// Request the MPToken using its owner + mptIssuanceID.
|
|
Json::Value jvParams;
|
|
jvParams[jss::mptoken] = Json::objectValue;
|
|
jvParams[jss::mptoken][jss::account] = bob.human();
|
|
jvParams[jss::mptoken][jss::mpt_issuance_id] =
|
|
strHex(mptAlice.issuanceID());
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfMPTokenIssuanceID.jsonName] ==
|
|
strHex(mptAlice.issuanceID()));
|
|
}
|
|
{
|
|
// Request the MPToken using a bad mptIssuanceID.
|
|
Json::Value jvParams;
|
|
jvParams[jss::mptoken] = Json::objectValue;
|
|
jvParams[jss::mptoken][jss::account] = bob.human();
|
|
jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = env.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryPermissionedDomain()
|
|
{
|
|
testcase("ledger_entry PermissionedDomain");
|
|
|
|
using namespace test::jtx;
|
|
|
|
Env env(*this, supported_amendments() | featurePermissionedDomains);
|
|
Account const issuer{"issuer"};
|
|
Account const alice{"alice"};
|
|
Account const bob{"bob"};
|
|
|
|
env.fund(XRP(5000), issuer, alice, bob);
|
|
env.close();
|
|
|
|
auto const seq = env.seq(alice);
|
|
env(pdomain::setTx(alice, {{alice, "first credential"}}));
|
|
env.close();
|
|
auto const objects = pdomain::getObjects(alice, env);
|
|
if (!BEAST_EXPECT(objects.size() == 1))
|
|
return;
|
|
|
|
{
|
|
// Succeed
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain][jss::account] = alice.human();
|
|
params[jss::permissioned_domain][jss::seq] = seq;
|
|
auto jv = env.rpc("json", "ledger_entry", to_string(params));
|
|
BEAST_EXPECT(
|
|
jv.isObject() && jv.isMember(jss::result) &&
|
|
!jv[jss::result].isMember(jss::error) &&
|
|
jv[jss::result].isMember(jss::node) &&
|
|
jv[jss::result][jss::node].isMember(
|
|
sfLedgerEntryType.jsonName) &&
|
|
jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
|
|
jss::PermissionedDomain);
|
|
|
|
std::string const pdIdx = jv[jss::result][jss::index].asString();
|
|
BEAST_EXPECT(
|
|
strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx);
|
|
|
|
params.clear();
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain] = pdIdx;
|
|
jv = env.rpc("json", "ledger_entry", to_string(params));
|
|
BEAST_EXPECT(
|
|
jv.isObject() && jv.isMember(jss::result) &&
|
|
!jv[jss::result].isMember(jss::error) &&
|
|
jv[jss::result].isMember(jss::node) &&
|
|
jv[jss::result][jss::node].isMember(
|
|
sfLedgerEntryType.jsonName) &&
|
|
jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
|
|
jss::PermissionedDomain);
|
|
}
|
|
|
|
{
|
|
// Fail, invalid permissioned domain index
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain] =
|
|
"12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
|
|
"DE";
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
|
|
checkErrorValue(jrr[jss::result], "entryNotFound", "");
|
|
}
|
|
|
|
{
|
|
// Fail, invalid permissioned domain index
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain] = "NotAHexString";
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, permissioned domain is not an object
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain] = 10;
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
|
|
{
|
|
// Fail, invalid account
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain][jss::account] = 1;
|
|
params[jss::permissioned_domain][jss::seq] = seq;
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
|
|
checkErrorValue(jrr[jss::result], "malformedAddress", "");
|
|
}
|
|
|
|
{
|
|
// Fail, account is an object
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain][jss::account] =
|
|
Json::Value{Json::ValueType::objectValue};
|
|
params[jss::permissioned_domain][jss::seq] = seq;
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
|
|
checkErrorValue(jrr[jss::result], "malformedAddress", "");
|
|
}
|
|
|
|
{
|
|
// Fail, no account
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain][jss::account] = "";
|
|
params[jss::permissioned_domain][jss::seq] = seq;
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
|
|
checkErrorValue(jrr[jss::result], "malformedAddress", "");
|
|
}
|
|
|
|
{
|
|
// Fail, invalid sequence
|
|
Json::Value params;
|
|
params[jss::ledger_index] = jss::validated;
|
|
params[jss::permissioned_domain][jss::account] = alice.human();
|
|
params[jss::permissioned_domain][jss::seq] = "12g";
|
|
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
|
|
checkErrorValue(jrr[jss::result], "malformedRequest", "");
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryCLI()
|
|
{
|
|
testcase("ledger_entry command-line");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
Account const alice{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
auto const checkId = keylet::check(env.master, env.seq(env.master));
|
|
|
|
env(check::create(env.master, alice, XRP(100)));
|
|
env.close();
|
|
|
|
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
|
{
|
|
// Request a check.
|
|
Json::Value const jrr =
|
|
env.rpc("ledger_entry", to_string(checkId.key))[jss::result];
|
|
BEAST_EXPECT(
|
|
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
|
|
BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
|
|
}
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
testLedgerEntryInvalid();
|
|
testLedgerEntryAccountRoot();
|
|
testLedgerEntryCheck();
|
|
testLedgerEntryCredentials();
|
|
testLedgerEntryDepositPreauth();
|
|
testLedgerEntryDepositPreauthCred();
|
|
testLedgerEntryDirectory();
|
|
testLedgerEntryEscrow();
|
|
testLedgerEntryOffer();
|
|
testLedgerEntryPayChan();
|
|
testLedgerEntryRippleState();
|
|
testLedgerEntryTicket();
|
|
testLedgerEntryDID();
|
|
testInvalidOracleLedgerEntry();
|
|
testOracleLedgerEntry();
|
|
testLedgerEntryMPT();
|
|
testLedgerEntryPermissionedDomain();
|
|
testLedgerEntryCLI();
|
|
|
|
forAllApiVersions(std::bind_front(
|
|
&LedgerEntry_test::testLedgerEntryInvalidParams, this));
|
|
}
|
|
};
|
|
|
|
class LedgerEntry_XChain_test : public beast::unit_test::suite,
|
|
public test::jtx::XChainBridgeObjects
|
|
{
|
|
void
|
|
checkErrorValue(
|
|
Json::Value const& jv,
|
|
std::string const& err,
|
|
std::string const& msg)
|
|
{
|
|
if (BEAST_EXPECT(jv.isMember(jss::status)))
|
|
BEAST_EXPECT(jv[jss::status] == "error");
|
|
if (BEAST_EXPECT(jv.isMember(jss::error)))
|
|
BEAST_EXPECT(jv[jss::error] == err);
|
|
if (msg.empty())
|
|
{
|
|
BEAST_EXPECT(
|
|
jv[jss::error_message] == Json::nullValue ||
|
|
jv[jss::error_message] == "");
|
|
}
|
|
else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
|
|
BEAST_EXPECT(jv[jss::error_message] == msg);
|
|
}
|
|
|
|
void
|
|
testLedgerEntryBridge()
|
|
{
|
|
testcase("ledger_entry: bridge");
|
|
using namespace test::jtx;
|
|
|
|
Env mcEnv{*this, features};
|
|
Env scEnv(*this, envconfig(), features);
|
|
|
|
createBridgeObjects(mcEnv, scEnv);
|
|
|
|
std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)};
|
|
std::string bridge_index;
|
|
Json::Value mcBridge;
|
|
{
|
|
// request the bridge via RPC
|
|
Json::Value jvParams;
|
|
jvParams[jss::bridge_account] = mcDoor.human();
|
|
jvParams[jss::bridge] = jvb;
|
|
Json::Value const jrr = mcEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
auto r = jrr[jss::node];
|
|
// std::cout << to_string(r) << '\n';
|
|
|
|
BEAST_EXPECT(r.isMember(jss::Account));
|
|
BEAST_EXPECT(r[jss::Account] == mcDoor.human());
|
|
|
|
BEAST_EXPECT(r.isMember(jss::Flags));
|
|
|
|
BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
|
|
BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
|
|
|
|
// we not created an account yet
|
|
BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
|
|
BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
|
|
|
|
// we have not claimed a locking chain tx yet
|
|
BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
|
|
BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
|
|
|
|
BEAST_EXPECT(r.isMember(jss::index));
|
|
bridge_index = r[jss::index].asString();
|
|
mcBridge = r;
|
|
}
|
|
{
|
|
// request the bridge via RPC by index
|
|
Json::Value jvParams;
|
|
jvParams[jss::index] = bridge_index;
|
|
Json::Value const jrr = mcEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
BEAST_EXPECT(jrr[jss::node] == mcBridge);
|
|
}
|
|
{
|
|
// swap door accounts and make sure we get an error value
|
|
Json::Value jvParams;
|
|
// Sidechain door account is "master", not scDoor
|
|
jvParams[jss::bridge_account] = Account::master.human();
|
|
jvParams[jss::bridge] = jvb;
|
|
jvParams[jss::ledger_hash] = ledgerHash;
|
|
Json::Value const jrr = mcEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
{
|
|
// create two claim ids and verify that the bridge counter was
|
|
// incremented
|
|
mcEnv(xchain_create_claim_id(mcAlice, jvb, reward, scAlice));
|
|
mcEnv.close();
|
|
mcEnv(xchain_create_claim_id(mcBob, jvb, reward, scBob));
|
|
mcEnv.close();
|
|
|
|
// request the bridge via RPC
|
|
Json::Value jvParams;
|
|
jvParams[jss::bridge_account] = mcDoor.human();
|
|
jvParams[jss::bridge] = jvb;
|
|
// std::cout << to_string(jvParams) << '\n';
|
|
Json::Value const jrr = mcEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
auto r = jrr[jss::node];
|
|
|
|
// we executed two create claim id txs
|
|
BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
|
|
BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryClaimID()
|
|
{
|
|
testcase("ledger_entry: xchain_claim_id");
|
|
using namespace test::jtx;
|
|
|
|
Env mcEnv{*this, features};
|
|
Env scEnv(*this, envconfig(), features);
|
|
|
|
createBridgeObjects(mcEnv, scEnv);
|
|
|
|
scEnv(xchain_create_claim_id(scAlice, jvb, reward, mcAlice));
|
|
scEnv.close();
|
|
scEnv(xchain_create_claim_id(scBob, jvb, reward, mcBob));
|
|
scEnv.close();
|
|
|
|
std::string bridge_index;
|
|
{
|
|
// request the xchain_claim_id via RPC
|
|
Json::Value jvParams;
|
|
jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
|
|
jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
|
|
1;
|
|
// std::cout << to_string(jvParams) << '\n';
|
|
Json::Value const jrr = scEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
auto r = jrr[jss::node];
|
|
// std::cout << to_string(r) << '\n';
|
|
|
|
BEAST_EXPECT(r.isMember(jss::Account));
|
|
BEAST_EXPECT(r[jss::Account] == scAlice.human());
|
|
BEAST_EXPECT(
|
|
r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
|
|
BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
|
|
BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
|
|
}
|
|
|
|
{
|
|
// request the xchain_claim_id via RPC
|
|
Json::Value jvParams;
|
|
jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
|
|
jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
|
|
2;
|
|
Json::Value const jrr = scEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
auto r = jrr[jss::node];
|
|
// std::cout << to_string(r) << '\n';
|
|
|
|
BEAST_EXPECT(r.isMember(jss::Account));
|
|
BEAST_EXPECT(r[jss::Account] == scBob.human());
|
|
BEAST_EXPECT(
|
|
r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
|
|
BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
|
|
BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
testLedgerEntryCreateAccountClaimID()
|
|
{
|
|
testcase("ledger_entry: xchain_create_account_claim_id");
|
|
using namespace test::jtx;
|
|
|
|
Env mcEnv{*this, features};
|
|
Env scEnv(*this, envconfig(), features);
|
|
|
|
// note: signers.size() and quorum are both 5 in createBridgeObjects
|
|
createBridgeObjects(mcEnv, scEnv);
|
|
|
|
auto scCarol =
|
|
Account("scCarol"); // Don't fund it - it will be created with the
|
|
// xchain transaction
|
|
auto const amt = XRP(1000);
|
|
mcEnv(sidechain_xchain_account_create(
|
|
mcAlice, jvb, scCarol, amt, reward));
|
|
mcEnv.close();
|
|
|
|
// send less than quorum of attestations (otherwise funds are
|
|
// immediately transferred and no "claim" object is created)
|
|
size_t constexpr num_attest = 3;
|
|
auto attestations = create_account_attestations(
|
|
scAttester,
|
|
jvb,
|
|
mcAlice,
|
|
amt,
|
|
reward,
|
|
payee,
|
|
/*wasLockingChainSend*/ true,
|
|
1,
|
|
scCarol,
|
|
signers,
|
|
UT_XCHAIN_DEFAULT_NUM_SIGNERS);
|
|
for (size_t i = 0; i < num_attest; ++i)
|
|
{
|
|
scEnv(attestations[i]);
|
|
}
|
|
scEnv.close();
|
|
|
|
{
|
|
// request the create account claim_id via RPC
|
|
Json::Value jvParams;
|
|
jvParams[jss::xchain_owned_create_account_claim_id] =
|
|
jvXRPBridgeRPC;
|
|
jvParams[jss::xchain_owned_create_account_claim_id]
|
|
[jss::xchain_owned_create_account_claim_id] = 1;
|
|
// std::cout << to_string(jvParams) << '\n';
|
|
Json::Value const jrr = scEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
// std::cout << to_string(jrr) << '\n';
|
|
|
|
BEAST_EXPECT(jrr.isMember(jss::node));
|
|
auto r = jrr[jss::node];
|
|
|
|
BEAST_EXPECT(r.isMember(jss::Account));
|
|
BEAST_EXPECT(r[jss::Account] == Account::master.human());
|
|
|
|
BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
|
|
BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
|
|
|
|
BEAST_EXPECT(
|
|
r.isMember(sfXChainCreateAccountAttestations.jsonName));
|
|
auto attest = r[sfXChainCreateAccountAttestations.jsonName];
|
|
BEAST_EXPECT(attest.isArray());
|
|
BEAST_EXPECT(attest.size() == 3);
|
|
BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(
|
|
sfXChainCreateAccountProofSig.jsonName));
|
|
Json::Value a[num_attest];
|
|
for (size_t i = 0; i < num_attest; ++i)
|
|
{
|
|
a[i] = attest[Json::Value::UInt(0)]
|
|
[sfXChainCreateAccountProofSig.jsonName];
|
|
BEAST_EXPECT(
|
|
a[i].isMember(jss::Amount) &&
|
|
a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
|
|
BEAST_EXPECT(
|
|
a[i].isMember(jss::Destination) &&
|
|
a[i][jss::Destination] == scCarol.human());
|
|
BEAST_EXPECT(
|
|
a[i].isMember(sfAttestationSignerAccount.jsonName) &&
|
|
std::any_of(
|
|
signers.begin(), signers.end(), [&](signer const& s) {
|
|
return a[i][sfAttestationSignerAccount.jsonName] ==
|
|
s.account.human();
|
|
}));
|
|
BEAST_EXPECT(
|
|
a[i].isMember(sfAttestationRewardAccount.jsonName) &&
|
|
std::any_of(
|
|
payee.begin(),
|
|
payee.end(),
|
|
[&](Account const& account) {
|
|
return a[i][sfAttestationRewardAccount.jsonName] ==
|
|
account.human();
|
|
}));
|
|
BEAST_EXPECT(
|
|
a[i].isMember(sfWasLockingChainSend.jsonName) &&
|
|
a[i][sfWasLockingChainSend.jsonName] == 1);
|
|
BEAST_EXPECT(
|
|
a[i].isMember(sfSignatureReward.jsonName) &&
|
|
a[i][sfSignatureReward.jsonName].asInt() ==
|
|
1 * drop_per_xrp);
|
|
}
|
|
}
|
|
|
|
// complete attestations quorum - CreateAccountClaimID should not be
|
|
// present anymore
|
|
for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
|
|
{
|
|
scEnv(attestations[i]);
|
|
}
|
|
scEnv.close();
|
|
{
|
|
// request the create account claim_id via RPC
|
|
Json::Value jvParams;
|
|
jvParams[jss::xchain_owned_create_account_claim_id] =
|
|
jvXRPBridgeRPC;
|
|
jvParams[jss::xchain_owned_create_account_claim_id]
|
|
[jss::xchain_owned_create_account_claim_id] = 1;
|
|
// std::cout << to_string(jvParams) << '\n';
|
|
Json::Value const jrr = scEnv.rpc(
|
|
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
|
checkErrorValue(jrr, "entryNotFound", "");
|
|
}
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
testLedgerEntryBridge();
|
|
testLedgerEntryClaimID();
|
|
testLedgerEntryCreateAccountClaimID();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(LedgerEntry, app, ripple);
|
|
BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, app, ripple);
|
|
|
|
} // namespace test
|
|
} // namespace ripple
|