Compare commits

..
124 changed files with 3820 additions and 8958 deletions

No files matched your search

+3 -4
View File
@@ -30,10 +30,6 @@ Version 3.5.0 is not yet released.
- `subscribe`, `unsubscribe`: Added an optional `mpt_issuances` request field, an array of MPT issuance IDs (hex strings). Subscribers receive the same `transaction` message as the `transactions` stream for each validated transaction whose metadata affects a subscribed issuance. MPT issuance subscriptions count toward the per-connection subscription limit. An empty array, a non-array value, or an invalid ID returns `invalidParams`. ([#5671](https://github.com/XRPLF/rippled/pull/5671))
- `ledger_entry`: Add full support for checks, NFT offers, payment channels, and signer lists. ([#6319](https://github.com/XRPLF/rippled/pull/6319))
- `channel_authorize`, `channel_verify`: `amount` now also accepts the transaction `Amount` JSON object for a token payment channel (`currency`, `issuer` and `value` for a trust line token; `mpt_issuance_id` and `value` for an MPT), and the claim authorization is serialized for that token. A string of drops is still used for an XRP channel. An object that names XRP, is negative or is malformed returns `channelAmtMalformed`. On the command line, the amount argument may be that JSON object as a single argument. For a token channel the signed message is the `PaymentChannelClaim` hash prefix, the channel ID and the amount serialized as an `Amount` field value without its field header; the XRP message is unchanged. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `PaymentChannelCreate`, `PaymentChannelFund`, `PaymentChannelClaim`: `Amount` (and `Balance` on `PaymentChannelClaim`) now accept a trust line or MPT value in addition to XRP, gated by the `TokenPaychan` amendment. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `ledger_entry`, `account_objects`: The `PayChannel` ledger entry now includes optional `TransferRate` and `IssuerNode` fields, present on a channel funded with a trust line or MPT value. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `account_channels`: For a token payment channel, `amount` and `balance` are `Amount` JSON objects (`currency`, `issuer` and `value` for a trust line token; `mpt_issuance_id` and `value` for an MPT). An XRP channel still returns strings of drops. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
### Bugfixes in 3.5.0
@@ -42,6 +38,9 @@ Version 3.5.0 is not yet released.
- `account_currencies`, `account_nfts`, `account_offers`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before an invalid or missing `account`. An invalid `limit` or `marker` is reported before `lgrNotFound` or `actNotFound`, and for `account_offers` also before `actMalformed`. A request with a single problem returns the same error as before. [#8561](https://github.com/XRPLF/rippled/pull/8561)
- `deposit_authorized`: A `credentials` entry that is all zeros (for example `"0"` or 64 `0` characters) now returns a `badCredentials` error instead of looking up a credential that can never exist.
- `account_info`, `account_lines`, `account_channels`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before a missing or invalid `account`, or `ident` for `account_info`. For `account_lines`, a `peer` that is not a string, or an invalid `limit` or `marker`, is reported before `lgrNotFound`, `actNotFound` or a malformed `account`, and an invalid `limit` or `marker` also before a malformed `peer`. For `account_channels`, a malformed `account` is reported before `lgrNotFound`, and an invalid `destination_account`, `limit` or `marker` before `lgrNotFound` or `actNotFound`. A request with a single problem returns the same error as before. [#8573](https://github.com/XRPLF/rippled/pull/8573)
- `deposit_authorized`, `nft_buy_offers`, `nft_sell_offers`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before a missing or invalid `source_account`, `destination_account` or `nft_id`, and for `nft_buy_offers` / `nft_sell_offers` also before an invalid `limit`. For `deposit_authorized`, an invalid `credentials` array is reported before `lgrNotFound`, `srcActNotFound`, `dstActNotFound` or `badCredentials`. For `nft_buy_offers` and `nft_sell_offers`, an invalid `marker` is reported before `lgrNotFound` or `objectNotFound`. A request with a single problem returns the same error as before. [#8613](https://github.com/XRPLF/rippled/pull/8613)
- `nft_buy_offers`, `nft_sell_offers`: An `nft_id` that is an array or an object now returns an `invalidParams` error instead of an `internal` error. [#8613](https://github.com/XRPLF/rippled/pull/8613)
- `fee`: `current_ledger_size` and `expected_ledger_size` count each Batch transaction as 1 plus its inner transactions, so the open-ledger fee escalates on Batches the way it does on the same number of separate transactions. `open_ledger_fee`, and `load_factor_fee_escalation` in `server_info`, follow from the same counts.
### Bugfixes in 3.5.0
+2
View File
@@ -304,7 +304,9 @@ if(xrpld)
account_nfts
account_offers
book_changes
deposit_authorized
ledger
nft_offers_common
transaction_entry
)
target_sources(xrpld PRIVATE ${rpcspec_instantiations})
+2 -2
View File
@@ -3,11 +3,11 @@
"requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
"xrpl-rpc-spec/0.1.25#2daa763e2d86ccc4ad0a0fa4d1983e9f%1791907455.579992",
"xrpl-rpc-spec/0.1.29#852afc87cac32aca93cc50a985d73fe0%1792359643.19817",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
"secp256k1/0.7.1#b1f450b7f78a36fff75bb6934a356f3a%1782338841.3729",
"secp256k1/0.7.1#f4d16a4d907dd057598954476ca6f88c%1792234329.790275",
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1782392413.075713",
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
+1 -1
View File
@@ -40,7 +40,7 @@ class Xrpl(ConanFile):
"nudb/2.0.9",
"openssl/3.6.3",
"soci/4.0.3",
"xrpl-rpc-spec/0.1.25",
"xrpl-rpc-spec/0.1.29",
"zlib/1.3.2",
]
+26
View File
@@ -103,6 +103,13 @@ private:
*/
std::size_t baseTxCount_ = 0;
/**
* Inner transactions declared by the Batch transactions inserted into
* this view. The open ledger never applies them, so TxQ adds this to
* txCount().
*/
std::size_t batchInnerCount_ = 0;
bool open_ = true;
public:
@@ -198,12 +205,31 @@ public:
std::size_t
txCount() const;
/**
* Return the number of inner transactions declared by the Batch
* transactions inserted into this view.
*/
std::size_t
batchInnerCount() const;
/**
* Record that a Batch declaring `count` inner transactions was inserted.
*/
void
rawBatchInnerAdd(std::size_t count);
/**
* Apply changes.
*/
void
apply(TxsRawView& to) const;
/**
* Apply changes, including the Batch inner count.
*/
void
apply(OpenView& to) const;
// ReadView
LedgerHeader const&
+428 -17
View File
@@ -1,19 +1,32 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
@@ -40,22 +53,153 @@ escrowLockPreclaimHelper(
beast::Journal j);
template <>
TER
inline TER
escrowLockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
beast::Journal j)
{
auto const& issue = amount.get<Issue>();
auto const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(view, account, issue))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(view, dest, issue))
return tecFROZEN;
STAmount const spendableAmount =
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
TER
inline TER
escrowLockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
beast::Journal j)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(view, account, *sleIssuance))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(view, dest, *sleIssuance))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(
view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
template <ValidIssueType T>
TER
@@ -67,22 +211,43 @@ escrowLockApplyHelper(
beast::Journal journal);
template <>
TER
inline TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter =
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
inline TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter = lockEscrowMPT(view, sender, amount, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <ValidIssueType T>
TER
@@ -93,20 +258,62 @@ escrowUnlockPreclaimHelper(
bool checkFreeze = true);
template <>
TER
inline TER
escrowUnlockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze);
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (checkFreeze &&
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
return tesSUCCESS;
}
template <>
TER
inline TER
escrowUnlockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze);
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (checkFreeze && isFrozen(view, account, *sleIssuance))
return tecLOCKED;
return tesSUCCESS;
}
//------------------------------------------------------------------------------
@@ -125,7 +332,7 @@ escrowUnlockApplyHelper(
beast::Journal journal);
template <>
TER
inline TER
escrowUnlockApplyHelper<Issue>(
ApplyViewContext ctx,
Rate lockedRate,
@@ -136,10 +343,138 @@ escrowUnlockApplyHelper<Issue>(
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal);
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
if (senderIssuer)
return tecINTERNAL; // LCOV_EXCL_LINE
if (receiverIssuer)
return tesSUCCESS;
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx,
sleDest,
xrpBalance,
*sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return ret;
}
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
ctx.view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
!sleDest->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
ctx.view.update(sleDest);
}
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
return tecNO_LINE;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
// validate the line limit if the account submitting txn is not the receiver
// of the funds
if (!createAsset)
{
auto const sleRippleState = ctx.view.peek(trustLineKey);
if (!sleRippleState)
return tecINTERNAL; // LCOV_EXCL_LINE
// if the issuer is the high, then we use the low limit
// otherwise we use the high limit
STAmount const lineLimit =
sleRippleState->getFieldAmount(recvLow ? sfLowLimit : sfHighLimit);
STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance);
// flip the sign of the line balance if the issuer is not high
if (!recvLow)
lineBalance.negate();
// add the final amount to the line balance
lineBalance += finalAmt;
// if the transfer would exceed the line limit return tecLIMIT_EXCEEDED
if (lineLimit < lineBalance)
return tecLIMIT_EXCEEDED;
}
// if destination is not the issuer then transfer funds
if (!receiverIssuer)
{
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
template <>
TER
inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyViewContext ctx,
Rate lockedRate,
@@ -150,7 +485,83 @@ escrowUnlockApplyHelper<MPTIssue>(
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal);
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
}
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
if (ctx.view.rules().enabled(fixCleanup3_4_0))
{
XRPL_ASSERT(
lockedRate >= kParityRate,
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
// MPTs are integral, so round the delivered amount down and
// charge any fractional transfer fee to the escrowed amount.
auto const delivered =
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
finalAmt = STAmount(amount.asset(), delivered.value());
}
else
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
}
return unlockEscrowMPT(
ctx.view,
sender,
receiver,
finalAmt,
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
/**
* Smart escrow kill switches, driven by the voted FeeSettings.
@@ -3,98 +3,27 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
#include <optional>
namespace xrpl {
/**
* Check that a payment channel transaction may carry a token amount.
*
* Token channels rely on fixCleanup3_2_0 and fixTokenEscrowV1, so both must be
* enabled alongside featureTokenPaychan.
*
* @param rules The current ledger rules used to check amendment status.
* @return temBAD_AMOUNT if featureTokenPaychan is not enabled; temDISABLED if
* fixCleanup3_2_0 or fixTokenEscrowV1 is not enabled; tesSUCCESS otherwise.
*/
NotTEC
checkTokenPayChanRules(Rules const& rules);
/**
* Validate the token amount of a PaymentChannelCreate or PaymentChannelFund
* transaction during preflight.
*
* @param amount The channel or funding amount from the transaction.
* @return tesSUCCESS if the amount is valid; temBAD_AMOUNT or temBAD_CURRENCY
* otherwise.
*/
template <ValidIssueType T>
NotTEC
payChanAmountPreflightHelper(STAmount const& amount);
template <>
NotTEC
payChanAmountPreflightHelper<Issue>(STAmount const& amount);
template <>
NotTEC
payChanAmountPreflightHelper<MPTIssue>(STAmount const& amount);
/**
* Check that locking @p amount debits the source's trust line by exactly
* @p amount.
*
* @param view The ledger view holding the source's trust line.
* @param account The channel source whose balance is debited.
* @param amount The amount to lock in the channel.
* @param j Journal passed to the balance lookup.
* @return tecPRECISION_LOSS if @p amount is an IOU and the source's balance
* less @p amount is not exactly representable; tesSUCCESS otherwise,
* including for XRP and MPT amounts.
*/
TER
payChanLockPrecisionHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
beast::Journal j);
/**
* Close a payment channel and return its remaining funds to the channel owner.
*
* @param slep The SLE for the PayChannel object to close.
* @param ctx The apply view context (view and transaction) in which ledger
* state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param txAccount The account submitting the transaction that closes the
* channel.
* @param submitterPreFeeBalance The submitter's XRP balance before the
* transaction fee; the source's balance when the source closes its own
* channel and its trust line must be re-created for the refund.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
* @param slep The SLE for the PayChannel object to close.
* @param view The apply view in which ledger state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
*/
TER
closeChannel(
SLE::Ref slep,
ApplyViewContext ctx,
UInt256 const& key,
AccountID const& txAccount,
XRPAmount submitterPreFeeBalance,
beast::Journal j);
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal j);
/**
* Add two uint32_t values with saturation at UINT32_MAX.
-25
View File
@@ -2,17 +2,11 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
/**
* Serialize the PaymentChannelClaim authorization message:
* HashPrefix::PaymentChannelClaim, the channel key and the drops as a
* 64-bit integer.
*/
inline void
serializePayChanAuthorization(Serializer& msg, UInt256 const& key, XRPAmount const& amt)
{
@@ -21,23 +15,4 @@ serializePayChanAuthorization(Serializer& msg, UInt256 const& key, XRPAmount con
msg.add64(amt.drops());
}
/**
* Serialize the PaymentChannelClaim authorization message for any asset: a
* native amount uses the XRP layout; a token amount follows
* HashPrefix::PaymentChannelClaim and the channel key with STAmount::add, the
* Amount field value without its field header.
*/
inline void
serializePayChanAuthorization(Serializer& msg, UInt256 const& key, STAmount const& amt)
{
if (amt.native())
{
serializePayChanAuthorization(msg, key, amt.xrp());
return;
}
msg.add32(HashPrefix::PaymentChannelClaim);
msg.addBitString(key);
amt.add(msg);
}
} // namespace xrpl
-33
View File
@@ -805,39 +805,6 @@ canAdd(STAmount const& amt1, STAmount const& amt2);
bool
canSubtract(STAmount const& amt1, STAmount const& amt2);
/**
* Determine whether `amt1 + amt2` equals the mathematical sum.
*
* XRP and MPT sums are integer additions, so this is canAdd(). An IOU sum is
* rounded to the mantissa width and is exact only if subtracting each
* operand from it gives back the other.
*
* @param amt1 The first addend.
* @param amt2 The second addend.
* @return true if the sum is exact; false if it rounds, exceeds the largest
* amount the asset can represent, or the amounts are not comparable.
* Never throws.
*/
bool
isExactSum(STAmount const& amt1, STAmount const& amt2);
/**
* Determine whether `amt1 - amt2` equals the mathematical difference.
*
* XRP and MPT differences are integer subtractions, so this is canSubtract().
* An IOU difference is rounded to the mantissa width and is exact only if
* adding the subtrahend back gives the minuend and subtracting the difference
* from the minuend gives back the subtrahend.
*
* @param amt1 The minuend.
* @param amt2 The subtrahend.
* @return true if the difference is exact; false if it rounds, underflows,
* exceeds the largest amount the asset can represent, or the amounts are
* not comparable. Never throws.
*/
bool
isExactDifference(STAmount const& amt1, STAmount const& amt2);
/**
* Get the scale of a Number for a given asset.
*
@@ -23,7 +23,6 @@ XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultN
XRPL_FEATURE(ConfidentialMPTKeyRotation, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (BatchV1_2, Supported::Yes, VoteBehavior::DefaultYes)
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(TokenPaychan, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::Yes, VoteBehavior::DefaultNo)
@@ -376,8 +376,6 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfDestinationNode, SoeOptional},
{sfTransferRate, SoeOptional},
{sfIssuerNode, SoeOptional},
}))
/** The ledger object which tracks the AMM.
@@ -182,7 +182,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegation::Delegable}), ({
{sfDestination, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfAmount, SoeRequired},
{sfSettleDelay, SoeRequired},
{sfPublicKey, SoeRequired},
{sfCancelAfter, SoeOptional},
@@ -195,7 +195,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegatio
#endif
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::Delegable}), ({
{sfChannel, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfAmount, SoeRequired},
{sfExpiration, SoeOptional},
}))
@@ -205,8 +205,8 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::D
#endif
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({.delegable = Delegation::Delegable}), ({
{sfChannel, SoeRequired},
{sfAmount, SoeOptional, SoeMptSupported},
{sfBalance, SoeOptional, SoeMptSupported},
{sfAmount, SoeOptional},
{sfBalance, SoeOptional},
{sfSignature, SoeOptional},
{sfPublicKey, SoeOptional},
{sfCredentialIDs, SoeOptional},
@@ -287,54 +287,6 @@ public:
{
return this->sle_->isFieldPresent(sfDestinationNode);
}
/**
* @brief Get sfTransferRate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getTransferRate() const
{
if (hasTransferRate())
return this->sle_->at(sfTransferRate);
return std::nullopt;
}
/**
* @brief Check if sfTransferRate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTransferRate() const
{
return this->sle_->isFieldPresent(sfTransferRate);
}
/**
* @brief Get sfIssuerNode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT64::type::value_type>
getIssuerNode() const
{
if (hasIssuerNode())
return this->sle_->at(sfIssuerNode);
return std::nullopt;
}
/**
* @brief Check if sfIssuerNode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerNode() const
{
return this->sle_->isFieldPresent(sfIssuerNode);
}
};
/**
@@ -556,28 +508,6 @@ public:
return *this;
}
/**
* @brief Set sfTransferRate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setTransferRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfTransferRate] = value;
return *this;
}
/**
* @brief Set sfIssuerNode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setIssuerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfIssuerNode] = value;
return *this;
}
/**
* @brief Build and return the completed PayChannel wrapper.
* @param index The ledger entry index.
@@ -60,7 +60,6 @@ public:
/**
* @brief Get sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -87,7 +86,6 @@ public:
/**
* @brief Get sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -248,7 +246,6 @@ public:
/**
* @brief Set sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&
@@ -260,7 +257,6 @@ public:
/**
* @brief Set sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&
@@ -60,7 +60,6 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -208,7 +207,6 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelCreateBuilder&
@@ -60,7 +60,6 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -156,7 +155,6 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelFundBuilder&
@@ -27,8 +27,6 @@ class ValidMPTIssuance
std::uint32_t mptokensCreated_ = 0;
std::uint32_t mptokensDeleted_ = 0;
// holder of the last MPToken created
std::optional<AccountID> mptokenCreatedHolder_;
// non-MPT transactions may attempt to create
// MPToken by an issuer
bool mptCreatedByIssuer_ = false;
@@ -23,9 +23,6 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
@@ -23,15 +23,9 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
+3
View File
@@ -20,6 +20,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/TxMeta.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -275,6 +276,8 @@ ApplyStateTable::apply(
if (!isDryRun)
{
to.rawTxInsert(tx.getTransactionID(), sTx, sMeta);
if (tx.getTxnType() == ttBATCH)
to.rawBatchInnerAdd(tx.getFieldArray(sfRawTransactions).size());
apply(to);
}
return metadata;
+20
View File
@@ -87,6 +87,7 @@ OpenView::OpenView(OpenView const& rhs)
, base_{rhs.base_}
, items_{rhs.items_}
, hold_{rhs.hold_}
, batchInnerCount_{rhs.batchInnerCount_}
, open_{rhs.open_} {};
OpenView::OpenView(OpenLedgerT, ReadView const* base, Rules rules, std::shared_ptr<void const> hold)
@@ -125,6 +126,18 @@ OpenView::txCount() const
return baseTxCount_ + txs_.size();
}
std::size_t
OpenView::batchInnerCount() const
{
return batchInnerCount_;
}
void
OpenView::rawBatchInnerAdd(std::size_t count)
{
batchInnerCount_ += count;
}
void
OpenView::apply(TxsRawView& to) const
{
@@ -133,6 +146,13 @@ OpenView::apply(TxsRawView& to) const
to.rawTxInsert(item.first, item.second.txn, item.second.meta);
}
void
OpenView::apply(OpenView& to) const
{
apply(static_cast<TxsRawView&>(to));
to.rawBatchInnerAdd(batchInnerCount_);
}
//---
LedgerHeader const&
@@ -1,510 +0,0 @@
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
template <>
TER
escrowLockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
auto const& issue = amount.get<Issue>();
auto const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(view, account, issue))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(view, dest, issue))
return tecFROZEN;
STAmount const spendableAmount =
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
TER
escrowLockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(view, account, *sleIssuance))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(view, dest, *sleIssuance))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(
view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
template <>
TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter =
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter = lockEscrowMPT(view, sender, amount, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
escrowUnlockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (checkFreeze &&
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
return tesSUCCESS;
}
template <>
TER
escrowUnlockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (checkFreeze && isFrozen(view, account, *sleIssuance))
return tecLOCKED;
return tesSUCCESS;
}
template <>
TER
escrowUnlockApplyHelper<Issue>(
ApplyViewContext ctx,
Rate lockedRate,
SLE::Ref sleDest,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
if (senderIssuer)
return tecINTERNAL; // LCOV_EXCL_LINE
if (receiverIssuer)
return tesSUCCESS;
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx,
sleDest,
xrpBalance,
*sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return ret;
}
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
ctx.view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
!sleDest->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
ctx.view.update(sleDest);
}
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
return tecNO_LINE;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
// validate the line limit if the account submitting txn is not the receiver
// of the funds
if (!createAsset)
{
auto const sleRippleState = ctx.view.peek(trustLineKey);
if (!sleRippleState)
return tecINTERNAL; // LCOV_EXCL_LINE
// if the issuer is the high, then we use the low limit
// otherwise we use the high limit
STAmount const lineLimit =
sleRippleState->getFieldAmount(recvLow ? sfLowLimit : sfHighLimit);
STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance);
// flip the sign of the line balance if the issuer is not high
if (!recvLow)
lineBalance.negate();
// add the final amount to the line balance
lineBalance += finalAmt;
// if the transfer would exceed the line limit return tecLIMIT_EXCEEDED
if (lineLimit < lineBalance)
return tecLIMIT_EXCEEDED;
}
// if destination is not the issuer then transfer funds
if (!receiverIssuer)
{
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
template <>
TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyViewContext ctx,
Rate lockedRate,
SLE::Ref sleDest,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
}
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
if (ctx.view.rules().enabled(fixCleanup3_4_0))
{
XRPL_ASSERT(
lockedRate >= kParityRate,
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
// MPTs are integral, so round the delivered amount down and
// charge any fractional transfer fee to the escrowed amount.
auto const delivered =
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
finalAmt = STAmount(amount.asset(), delivered.value());
}
else
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
}
return unlockEscrowMPT(
ctx.view,
sender,
receiver,
finalAmt,
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
} // namespace xrpl
@@ -3,26 +3,16 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <algorithm>
#include <cstdint>
@@ -31,76 +21,9 @@
namespace xrpl {
NotTEC
checkTokenPayChanRules(Rules const& rules)
{
if (!rules.enabled(featureTokenPaychan))
return temBAD_AMOUNT;
if (!rules.enabled(fixCleanup3_2_0) || !rules.enabled(fixTokenEscrowV1))
return temDISABLED;
return tesSUCCESS;
}
template <>
NotTEC
payChanAmountPreflightHelper<Issue>(STAmount const& amount)
{
if (amount.native() || amount <= beast::kZero)
return temBAD_AMOUNT;
if (badCurrency() == amount.get<Issue>().currency)
return temBAD_CURRENCY;
return tesSUCCESS;
}
template <>
NotTEC
payChanAmountPreflightHelper<MPTIssue>(STAmount const& amount)
{
if (amount.native() || amount <= beast::kZero)
return temBAD_AMOUNT;
return tesSUCCESS;
}
TER
payChanLockPrecisionHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
beast::Journal j)
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal j)
{
if (!amount.holds<Issue>())
return tesSUCCESS;
// The trust line debit rounds to the mantissa, so require an exact
// difference rather than canAdd's relative tolerance.
STAmount const spendable = accountHolds(
view,
account,
amount.get<Issue>().currency,
amount.getIssuer(),
FreezeHandling::IgnoreFreeze,
j);
if (!isExactDifference(spendable, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
TER
closeChannel(
SLE::Ref slep,
ApplyViewContext ctx,
UInt256 const& key,
AccountID const& txAccount,
XRPAmount submitterPreFeeBalance,
beast::Journal j)
{
ApplyView& view = ctx.view;
AccountID const src = (*slep)[sfAccount];
// Remove PayChan from owner directory
{
@@ -115,9 +38,9 @@ closeChannel(
}
// Remove PayChan from recipient's owner directory, if present.
AccountID const dst = (*slep)[sfDestination];
if (auto const page = (*slep)[~sfDestinationNode])
{
auto const dst = (*slep)[sfDestination];
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
{
// LCOV_EXCL_START
@@ -127,69 +50,15 @@ closeChannel(
}
}
// Transfer amount back to owner, decrement owner count
auto const sle = view.peek(keylet::account(src));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Release the channel's reserve first: a token refund can re-create a
// holding the owner deleted while the channel was open.
decreaseOwnerCountForObject(view, sle, slep, 1, j);
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
XRPL_ASSERT(
isExactDifference((*slep)[sfAmount], (*slep)[sfBalance]),
"xrpl::closeChannel : exact refund");
auto const reqDelta = (*slep)[sfAmount] - (*slep)[sfBalance];
auto const& issuer = reqDelta.getIssuer();
// Only update the balance if there is a positive delta.
if (reqDelta > beast::kZero)
{
if (isXRP(reqDelta))
{
(*sle)[sfBalance] = (*sle)[sfBalance] + reqDelta;
}
else
{
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(view, src, reqDelta, false);
});
!isTesSuccess(ret))
return ret;
bool const createAsset = src == txAccount;
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx,
kParityRate,
sle,
submitterPreFeeBalance,
reqDelta,
issuer,
src,
src,
createAsset,
j);
});
!isTesSuccess(ret))
return ret;
}
}
// Remove PayChan from issuer's owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode])
{
if (!view.dirRemove(keylet::ownerDir(issuer), *optPage, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from issuer owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
decreaseOwnerCountForObject(view, sle, slep, 1, j);
view.update(sle);
// Remove PayChan from ledger
-58
View File
@@ -522,64 +522,6 @@ canAdd(STAmount const& a, STAmount const& b)
// LCOV_EXCL_STOP
}
/**
* Integral types defer to canAdd(). For IOU amounts the rounding error of the
* sum is a multiple of the finer operand's unit in the last place, so
* subtracting the coarser operand back from the sum returns the finer one
* only when that error is zero. Both differences are checked because either
* operand may be the finer. IOUAmount throws std::overflow_error when a sum
* or difference exceeds the largest IOU value; the exact result is then not
* representable, so the sum is not exact.
*/
bool
isExactSum(STAmount const& a, STAmount const& b)
{
if (!areComparable(a, b))
return false;
if (a.integral())
return canAdd(a, b);
try
{
STAmount const sum = a + b;
return sum - a == b && sum - b == a;
}
catch (std::overflow_error const&)
{
return false;
}
}
/**
* Integral types defer to canSubtract(). For IOU amounts each check misses
* the case where one operand is dust against the other. With a dust
* subtrahend, a - b rounds to a and adding b back rounds to a again, so only
* a - diff != b shows the loss. With a dust minuend, a - b rounds to -b and
* a - diff rounds to b, so only diff + b != a shows it. Both are checked.
* IOUAmount throws std::overflow_error when a step rounds past the largest
* IOU value; the difference is then not exact.
*/
bool
isExactDifference(STAmount const& a, STAmount const& b)
{
if (!areComparable(a, b))
return false;
if (a.integral())
return canSubtract(a, b);
try
{
STAmount const diff = a - b;
return diff + b == a && a - diff == b;
}
catch (std::overflow_error const&)
{
return false;
}
}
/**
* @brief Determines if it is safe to subtract one STAmount from another.
*
+2 -3
View File
@@ -150,8 +150,7 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef aft
drops_ -= (*before)[sfBalance].xrp().drops();
break;
case ltPAYCHAN:
if (isXRP((*before)[sfAmount]))
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
break;
case ltESCROW:
if (isXRP((*before)[sfAmount]))
@@ -184,7 +183,7 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef aft
drops_ += (*after)[sfBalance].xrp().drops();
break;
case ltPAYCHAN:
if (!isDelete && isXRP((*after)[sfAmount]))
if (!isDelete)
drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
break;
case ltESCROW:
@@ -125,7 +125,6 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef
else if (!before)
{
mptokensCreated_++;
mptokenCreatedHolder_ = after->at(sfAccount);
MPTIssue const mptIssue{after->at(sfMPTokenIssuanceID)};
if (mptIssue.getIssuer() == after->at(sfAccount))
mptCreatedByIssuer_ = true;
@@ -440,13 +439,6 @@ ValidMPTIssuance::finalize(
return true;
}
// A token paychan claim may auto-create the submitter's MPToken.
if (rules.enabled(featureTokenPaychan) && txnType == ttPAYCHAN_CLAIM &&
mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && mptokensDeleted_ == 0 &&
(mptokensCreated_ == 0 ||
(mptokensCreated_ == 1 && mptokenCreatedHolder_ == tx[sfAccount])))
return true;
if (hasPrivilege(tx, Privilege::MayDeleteMpt) &&
((txnType == ttAMM_DELETE && mptokensDeleted_ <= 2) || mptokensDeleted_ == 1) &&
mptokensCreated_ == 0 && mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0)
@@ -20,6 +20,8 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <variant>
namespace xrpl {
NotTEC
@@ -43,9 +45,11 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
if (!isXRP(amount))
{
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, account, amount, false);
});
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, account, amount, false);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
@@ -129,19 +133,21 @@ EscrowCancel::doApply()
auto const issuer = amount.getIssuer();
bool const createAsset = account == accountID_;
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : escrow.mutableRawSle(),
preFeeBalance_,
amount,
issuer,
account, // sender and receiver are the same
account,
createAsset,
j_);
});
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : escrow.mutableRawSle(),
preFeeBalance_,
amount,
issuer,
account, // sender and receiver are the same
account,
createAsset,
j_);
},
amount.asset().value());
!isTesSuccess(ret))
return ret; // LCOV_EXCL_LINE
@@ -37,6 +37,7 @@
#include <memory>
#include <system_error>
#include <variant>
namespace xrpl {
@@ -152,8 +153,9 @@ EscrowCreate::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureTokenEscrow))
return temBAD_AMOUNT;
if (auto const ret = amount.asset().visit(
[&]<typename T>(T const&) { return escrowCreatePreflightHelper<T>(ctx); });
if (auto const ret = std::visit(
[&]<typename T>(T const&) { return escrowCreatePreflightHelper<T>(ctx); },
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
@@ -254,9 +256,11 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
if (!ctx.view.rules().enabled(featureTokenEscrow))
return temDISABLED; // LCOV_EXCL_LINE
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
});
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
@@ -419,9 +423,11 @@ EscrowCreate::doApply()
}
else
{
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
});
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
},
amount.asset().value());
!isTesSuccess(ret))
{
return ret; // LCOV_EXCL_LINE
@@ -31,6 +31,7 @@
#include <cstdint>
#include <optional>
#include <system_error>
#include <variant>
namespace xrpl {
@@ -241,9 +242,11 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
if (!isXRP(amount))
{
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, amount);
});
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
@@ -428,19 +431,21 @@ EscrowFinish::doApply()
: kParityRate;
auto const issuer = amount.getIssuer();
bool const createAsset = destID == accountID_;
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
lockedRate,
sled,
preFeeBalance_,
amount,
issuer,
account,
destID,
createAsset,
j_);
});
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
lockedRate,
sled,
preFeeBalance_,
amount,
issuer,
account,
destID,
createAsset,
j_);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
@@ -4,18 +4,14 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/PayChan.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -50,29 +46,11 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
return temMALFORMED;
auto const bal = ctx.tx[~sfBalance];
if (bal && *bal <= beast::kZero)
if (bal && (!isXRP(*bal) || *bal <= beast::kZero))
return temBAD_AMOUNT;
auto const amt = ctx.tx[~sfAmount];
if (amt && *amt <= beast::kZero)
return temBAD_AMOUNT;
if ((bal && !isXRP(*bal)) || (amt && !isXRP(*amt)))
{
if (auto const ret = checkTokenPayChanRules(ctx.rules); !isTesSuccess(ret))
return ret;
// PaymentChannelCreate and PaymentChannelFund gate an MPT-denominated
// Amount on featureMPTokensV1 (checkExtraFeatures); apply the same
// gate here for sfBalance and sfAmount.
if (((bal && bal->holds<MPTIssue>()) || (amt && amt->holds<MPTIssue>())) &&
!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED;
}
// Both bal and amt must reference the same asset before comparing,
// otherwise STAmount comparison throws.
if (bal && amt && bal->asset() != amt->asset())
if (amt && (!isXRP(*amt) || *amt <= beast::kZero))
return temBAD_AMOUNT;
if (bal && amt && *bal > *amt)
@@ -92,8 +70,8 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
// The signature isn't needed if txAccount == src, but if it's
// present, check it
auto const reqBalance = *bal;
auto const authAmt = amt ? *amt : reqBalance;
auto const reqBalance = bal->xrp();
auto const authAmt = amt ? amt->xrp() : reqBalance;
if (reqBalance > authAmt)
return temBAD_AMOUNT;
@@ -118,42 +96,12 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
TER
PaymentChannelClaim::preclaim(PreclaimContext const& ctx)
{
if (ctx.view.rules().enabled(featureCredentials))
{
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
!isTesSuccess(err))
return err;
}
if (!ctx.view.rules().enabled(featureCredentials))
return Transactor::preclaim(ctx);
if (ctx.view.rules().enabled(featureTokenPaychan))
{
Keylet const k{ltPAYCHAN, ctx.tx[sfChannel]};
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
AccountID const dest = (*slep)[sfDestination];
auto const& chanFunds = slep->getFieldAmount(sfAmount);
// The requested balance and the optional amount must each match
// the channel's asset; otherwise the STAmount comparisons and
// subtractions in doApply would throw on mismatched issues. amount
// is checked independently of balance: a claim can carry Amount
// without Balance (the signature-authorized ceiling alone).
if (auto const bal = ctx.tx[~sfBalance]; bal && bal->asset() != chanFunds.asset())
return tecWRONG_ASSET;
if (auto const amt = ctx.tx[~sfAmount]; amt && amt->asset() != chanFunds.asset())
return tecWRONG_ASSET;
if (!isXRP(chanFunds) && ctx.tx.isFieldPresent(sfBalance))
{
if (auto const ret = chanFunds.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, chanFunds);
});
!isTesSuccess(ret))
return ret;
}
}
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
!isTesSuccess(err))
return err;
return tesSUCCESS;
}
@@ -168,36 +116,25 @@ PaymentChannelClaim::doApply()
AccountID const src = (*slep)[sfAccount];
AccountID const dst = (*slep)[sfDestination];
AccountID const txAccount = ctx_.tx[sfAccount];
auto const curExpiration = (*slep)[~sfExpiration];
if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) ||
isChannelExpired(ctx_.view(), curExpiration))
{
return closeChannel(
slep,
ctx_.getApplyViewContext(),
k.key,
accountID_,
preFeeBalance_,
ctx_.registry.get().getJournal("View"));
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
}
if (accountID_ != src && accountID_ != dst)
if (txAccount != src && txAccount != dst)
return tecNO_PERMISSION;
if (ctx_.tx[~sfBalance])
{
auto const chanBalance = slep->getFieldAmount(sfBalance);
auto const chanFunds = slep->getFieldAmount(sfAmount);
auto const reqBalance = ctx_.tx[sfBalance];
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
auto const reqBalance = ctx_.tx[sfBalance].xrp();
// Asset consistency between the requested balance/amount and the
// channel was validated in preclaim.
XRPL_ASSERT(
reqBalance.asset() == chanFunds.asset(),
"xrpl::PaymentChannelClaim::doApply : balance matches channel asset");
if (accountID_ == dst && !ctx_.tx[~sfSignature])
if (txAccount == dst && !ctx_.tx[~sfSignature])
{
return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION}
: TER{temBAD_SIGNATURE};
@@ -222,64 +159,27 @@ PaymentChannelClaim::doApply()
return tecUNFUNDED_PAYMENT;
}
// The payout reqBalance - sfBalance and the unclaimed remainder
// sfAmount - reqBalance must both be exact so that the amounts
// unlocked by this claim and by the close equal what sfBalance and
// sfAmount record.
if (!isExactDifference(chanFunds, reqBalance) ||
!isExactDifference(reqBalance, chanBalance))
return tecPRECISION_LOSS;
auto const sled = ctx_.view().peek(keylet::account(dst));
if (!sled)
return tecNO_DST;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, sled, ctx_.journal);
verifyDepositPreauth(ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal);
!isTesSuccess(err))
return err;
(*slep)[sfBalance] = ctx_.tx[sfBalance];
STAmount const reqDelta = reqBalance - chanBalance;
XRPAmount const reqDelta = reqBalance - chanBalance;
XRPL_ASSERT(
reqDelta >= beast::kZero, "xrpl::PaymentChannelClaim::doApply : minimum balance delta");
// Transfer amount to destination
if (isXRP(reqDelta))
{
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
}
else
{
Rate lockedRate = slep->isFieldPresent(sfTransferRate)
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
: kParityRate;
auto const& issuer = reqDelta.getIssuer();
bool const createAsset = dst == accountID_;
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
lockedRate,
sled,
preFeeBalance_,
reqDelta,
issuer,
src,
dst,
createAsset,
j_);
});
!isTesSuccess(ret))
return ret;
}
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
ctx_.view().update(sled);
ctx_.view().update(slep);
}
if (ctx_.tx.isFlag(tfRenew))
{
if (src != accountID_)
if (src != txAccount)
return tecNO_PERMISSION;
(*slep)[~sfExpiration] = std::nullopt;
ctx_.view().update(slep);
@@ -288,16 +188,8 @@ PaymentChannelClaim::doApply()
if (ctx_.tx.isFlag(tfClose))
{
// Channel will close immediately if dry or the receiver closes
if (dst == accountID_ || (*slep)[sfBalance] == (*slep)[sfAmount])
{
return closeChannel(
slep,
ctx_.getApplyViewContext(),
k.key,
accountID_,
preFeeBalance_,
ctx_.registry.get().getJournal("View"));
}
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
auto const settleExpiration = saturatingAdd(
ctx_.view().rules(),
@@ -1,25 +1,18 @@
#include <xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -58,37 +51,14 @@ namespace xrpl {
TxConsequences
PaymentChannelCreate::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::kZero};
}
bool
PaymentChannelCreate::checkExtraFeatures(PreflightContext const& ctx)
{
// Only require featureMPTokensV1 when the channel amount is an MPT and
// fixCleanup3_2_0 is active; XRP/IOU channels are unaffected by this gate.
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
return ctx.rules.enabled(featureMPTokensV1);
return true;
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
}
NotTEC
PaymentChannelCreate::preflight(PreflightContext const& ctx)
{
auto const amount = ctx.tx[sfAmount];
if (!isXRP(amount))
{
if (auto const ret = checkTokenPayChanRules(ctx.rules); !isTesSuccess(ret))
return ret;
if (auto const ret = amount.asset().visit(
[&]<typename T>(T const&) { return payChanAmountPreflightHelper<T>(amount); });
!isTesSuccess(ret))
return ret;
}
else if (amount <= beast::kZero)
{
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::kZero))
return temBAD_AMOUNT;
}
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
return temDST_IS_SRC;
@@ -107,8 +77,6 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
if (!sle)
return terNO_ACCOUNT;
auto const amount = ctx.tx[sfAmount];
// Check reserve and funds availability
if (!ctx.view.rules().enabled(featureSponsor))
{
@@ -118,15 +86,15 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
if (isXRP(amount) && balance < reserve + ctx.tx[sfAmount])
if (balance < reserve + ctx.tx[sfAmount])
return tecUNFUNDED;
}
auto const dest = ctx.tx[sfDestination];
auto const dst = ctx.tx[sfDestination];
{
// Check destination account
auto const sled = ctx.view.read(keylet::account(dest));
auto const sled = ctx.view.read(keylet::account(dst));
if (!sled)
return tecNO_DST;
@@ -147,24 +115,6 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
return tecNO_PERMISSION;
}
if (!isXRP(amount))
{
// Vault shares (an MPT issued by a pseudo-account) cannot be locked
// in a channel.
if (amount.holds<MPTIssue>() && isPseudoAccount(ctx.view, amount.getIssuer()))
return tecWRONG_ASSET;
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
});
!isTesSuccess(ret))
return ret;
if (auto const ret = payChanLockPrecisionHelper(ctx.view, account, amount, ctx.j);
!isTesSuccess(ret))
return ret;
}
return tesSUCCESS;
}
@@ -172,7 +122,6 @@ TER
PaymentChannelCreate::doApply()
{
auto const account = ctx_.tx[sfAccount];
auto const amount = ctx_.tx[sfAmount];
auto const sle = ctx_.view().peek(keylet::account(account));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -210,27 +159,25 @@ PaymentChannelCreate::doApply()
// - unsponsored: 1 — source owes reserve including the new increment.
auto const sourceReserve = accountReserve(
ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
// Token channels lock no XRP, so only an XRP channel checks the
// source's post-lock balance, as EscrowCreate does.
if (isXRP(amount) && preFeeBalance_ - amount.xrp() < sourceReserve)
if (preFeeBalance_ - ctx_.tx[sfAmount].xrp() < sourceReserve)
return tecUNFUNDED;
}
auto const dest = ctx_.tx[sfDestination];
auto const dst = ctx_.tx[sfDestination];
// Create PayChan in ledger.
//
// Note that we use the value from the sequence or ticket as the
// payChan sequence. For more explanation see comments in SeqProxy.h.
Keylet const payChanKeylet = keylet::payChannel(account, dest, ctx_.tx.getSeqProxy());
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqProxy());
auto const slep = std::make_shared<SLE>(payChanKeylet);
// Funds held in this channel
(*slep)[sfAmount] = amount;
(*slep)[sfAmount] = ctx_.tx[sfAmount];
// Amount channel has already paid
(*slep)[sfBalance] = amount.zeroed();
(*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed();
(*slep)[sfAccount] = account;
(*slep)[sfDestination] = dest;
(*slep)[sfDestination] = dst;
(*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay];
(*slep)[sfPublicKey] = ctx_.tx[sfPublicKey];
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
@@ -241,13 +188,6 @@ PaymentChannelCreate::doApply()
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
}
if (ctx_.view().rules().enabled(featureTokenPaychan) && !isXRP(amount))
{
auto const xferRate = transferRate(ctx_.view(), amount);
if (xferRate != kParityRate)
(*slep)[sfTransferRate] = xferRate.value;
}
ctx_.view().insert(slep);
// Add PayChan to owner directory
@@ -262,37 +202,14 @@ PaymentChannelCreate::doApply()
// Add PayChan to the recipient's owner directory
{
auto const page =
ctx_.view().dirInsert(keylet::ownerDir(dest), payChanKeylet, describeOwnerDir(dest));
ctx_.view().dirInsert(keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfDestinationNode] = *page;
}
// Add PayChan to the issuer's owner directory, if applicable
auto const& issuer = amount.getIssuer();
if (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds<MPTIssue>())
{
auto page = ctx_.view().dirInsert(
keylet::ownerDir(issuer), payChanKeylet, describeOwnerDir(issuer));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfIssuerNode] = *page;
}
// Deduct owner's balance, increment owner count
if (isXRP(amount))
{
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
}
else
{
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
});
!isTesSuccess(ret))
return ret;
}
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
ctx_.view().update(sle);
@@ -2,19 +2,15 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -24,24 +20,12 @@
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/applySteps.h>
#include <memory>
namespace xrpl {
TxConsequences
PaymentChannelFund::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::kZero};
}
bool
PaymentChannelFund::checkExtraFeatures(PreflightContext const& ctx)
{
// Only require featureMPTokensV1 when the funding amount is an MPT and
// fixCleanup3_2_0 is active; XRP/IOU channels are unaffected by this gate.
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
return ctx.rules.enabled(featureMPTokensV1);
return true;
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
}
NotTEC
@@ -50,40 +34,8 @@ PaymentChannelFund::preflight(PreflightContext const& ctx)
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfChannel] == beast::kZero)
return temMALFORMED;
auto const amount = ctx.tx[sfAmount];
if (!isXRP(amount))
{
if (auto const ret = checkTokenPayChanRules(ctx.rules); !isTesSuccess(ret))
return ret;
if (auto const ret = amount.asset().visit(
[&]<typename T>(T const&) { return payChanAmountPreflightHelper<T>(amount); });
!isTesSuccess(ret))
return ret;
}
else if (amount <= beast::kZero)
{
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::kZero))
return temBAD_AMOUNT;
}
return tesSUCCESS;
}
TER
PaymentChannelFund::preclaim(PreclaimContext const& ctx)
{
if (ctx.view.rules().enabled(featureTokenPaychan))
{
Keylet const k{ltPAYCHAN, ctx.tx[sfChannel]};
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_ENTRY;
// The funded asset must match the channel's asset; otherwise the
// STAmount accumulation in doApply would throw on mismatched issues.
if (ctx.tx[sfAmount].asset() != (*slep)[sfAmount].asset())
return tecWRONG_ASSET;
}
return tesSUCCESS;
}
@@ -97,22 +49,16 @@ PaymentChannelFund::doApply()
return tecNO_ENTRY;
AccountID const src = (*slep)[sfAccount];
AccountID const dst = (*slep)[sfDestination];
auto const txAccount = ctx_.tx[sfAccount];
auto const curExpiration = (*slep)[~sfExpiration];
if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) ||
isChannelExpired(ctx_.view(), curExpiration))
{
return closeChannel(
slep,
ctx_.getApplyViewContext(),
k.key,
accountID_,
preFeeBalance_,
ctx_.registry.get().getJournal("View"));
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
}
if (src != accountID_)
if (src != txAccount)
{
// only the owner can add funds or extend
return tecNO_PERMISSION;
@@ -136,22 +82,10 @@ PaymentChannelFund::doApply()
ctx_.view().update(slep);
}
auto const sle = ctx_.view().peek(keylet::account(accountID_));
auto const sle = ctx_.view().peek(keylet::account(txAccount));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const amount = ctx_.tx[sfAmount];
auto const& chanAmt = slep->getFieldAmount(sfAmount);
// Asset consistency between the funded amount and the channel was
// validated in preclaim.
XRPL_ASSERT(
amount.asset() == chanAmt.asset(),
"xrpl::PaymentChannelFund::doApply : amount matches channel asset");
// A token fund locks no XRP and adds no owned object, so only an XRP fund
// checks the source's XRP reserve.
if (isXRP(amount))
{
// Check reserve and funds availability
STAmount const balance = (*sle)[sfBalance];
@@ -166,59 +100,20 @@ PaymentChannelFund::doApply()
// when a sponsor is present and would ignore the source's post-lock
// balance entirely. Funding an existing channel adds no owned object,
// so there is no owner-count delta.
if (balance < accountReserve(ctx_.view(), sle, j_) + amount)
if (balance < accountReserve(ctx_.view(), sle, j_) + ctx_.tx[sfAmount])
return tecUNFUNDED;
}
// do not allow adding funds if dst does not exist
if (!ctx_.view().read(keylet::account(dst)))
if (AccountID const dst = (*slep)[sfDestination]; !ctx_.view().read(keylet::account(dst)))
{
return tecNO_DST;
}
if (!isXRP(amount))
{
// Funding is subject to the same issuer controls (locking opt-in,
// authorization, freeze/lock, transferability, spendable balance)
// as channel creation.
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockPreclaimHelper<T>(ctx_.view(), accountID_, dst, amount, j_);
});
!isTesSuccess(ret))
return ret;
if (auto const ret = payChanLockPrecisionHelper(ctx_.view(), accountID_, amount, j_);
!isTesSuccess(ret))
return ret;
// sfAmount must become exactly chanAmt + amount: an IOU sum that
// rounds or an MPT sum that overflows would record an increase other
// than what the source paid.
if (!isExactSum(chanAmt, amount))
return tecPRECISION_LOSS;
// The unclaimed remainder sfAmount - sfBalance must stay exact so
// that close refunds exactly what was not paid out.
if (!isExactDifference(chanAmt + amount, (*slep)[sfBalance]))
return tecPRECISION_LOSS;
}
if (isXRP(amount))
{
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
}
else
{
auto const& issuer = amount.getIssuer();
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
});
!isTesSuccess(ret))
return ret;
}
(*slep)[sfAmount] = chanAmt + amount;
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
ctx_.view().update(slep);
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
ctx_.view().update(sle);
return tesSUCCESS;
+1 -1
View File
@@ -4362,7 +4362,7 @@ private:
settleDelay,
pk,
cancelAfter),
Ter(tecNO_PERMISSION));
Ter(telENV_RPC_FAILED));
env(create(carol_, ammAlice.ammAccount(), XRP(1'000), settleDelay, pk, cancelAfter),
Ter(tecNO_PERMISSION));
+19 -12
View File
@@ -32,6 +32,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -894,8 +895,9 @@ public:
auto const jNoCred = credentials::ledgerEntry(env, becky, carol, credType);
BEAST_EXPECT(
jNoCred.isObject() && jNoCred.isMember(jss::result) &&
jNoCred[jss::result].isMember(jss::error) &&
jNoCred[jss::result][jss::error] == "entryNotFound");
jNoCred[jss::result][jss::error] == "entryNotFound" &&
jNoCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jNoCred[jss::result][jss::error_message] == "Entry not found.");
}
testcase("Credentials that aren't required");
@@ -918,8 +920,9 @@ public:
BEAST_EXPECT(
jNoCred.isObject() && jNoCred.isMember(jss::result) &&
jNoCred[jss::result].isMember(jss::error) &&
jNoCred[jss::result][jss::error] == "entryNotFound");
jNoCred[jss::result][jss::error] == "entryNotFound" &&
jNoCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jNoCred[jss::result][jss::error_message] == "Entry not found.");
}
{
@@ -957,8 +960,9 @@ public:
BEAST_EXPECT(
jNoCred.isObject() && jNoCred.isMember(jss::result) &&
jNoCred[jss::result].isMember(jss::error) &&
jNoCred[jss::result][jss::error] == "entryNotFound");
jNoCred[jss::result][jss::error] == "entryNotFound" &&
jNoCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jNoCred[jss::result][jss::error_message] == "Entry not found.");
}
testcase("Expired credentials");
@@ -994,8 +998,9 @@ public:
auto jv = credentials::ledgerEntry(env, john, carol, credType);
BEAST_EXPECT(
jv.isObject() && jv.isMember(jss::result) &&
jv[jss::result].isMember(jss::error) &&
jv[jss::result][jss::error] == "entryNotFound");
jv[jss::result][jss::error] == "entryNotFound" &&
jv[jss::result][jss::error_code] == RpcEntryNotFound &&
jv[jss::result][jss::error_message] == "Entry not found.");
}
}
}
@@ -1079,8 +1084,9 @@ public:
auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
BEAST_EXPECT(
jv.isObject() && jv.isMember(jss::result) &&
jv[jss::result].isMember(jss::error) &&
jv[jss::result][jss::error] == "entryNotFound");
jv[jss::result][jss::error] == "entryNotFound" &&
jv[jss::result][jss::error_code] == RpcEntryNotFound &&
jv[jss::result][jss::error_message] == "Entry not found.");
}
}
@@ -1121,8 +1127,9 @@ public:
auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
BEAST_EXPECT(
jv.isObject() && jv.isMember(jss::result) &&
jv[jss::result].isMember(jss::error) &&
jv[jss::result][jss::error] == "entryNotFound");
jv[jss::result][jss::error] == "entryNotFound" &&
jv[jss::result][jss::error_code] == RpcEntryNotFound &&
jv[jss::result][jss::error_message] == "Entry not found.");
}
}
}
+197 -5
View File
@@ -43,6 +43,7 @@
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Batch.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -2800,6 +2801,7 @@ class Batch_test : public beast::unit_test::Suite
auto const jrr = env.rpc("submit", strHex(slice))[jss::result];
expect(
jrr[jss::status] == "error" && jrr[jss::error] == "invalidTransaction" &&
!jrr.isMember(jss::error_code) && jrr[jss::error_message].isNull() &&
jrr[jss::error_exception] ==
"fails local checks: Batch inner transactions are never "
"considered validly signed.",
@@ -4894,7 +4896,7 @@ class Batch_test : public beast::unit_test::Suite
Ter(tesSUCCESS));
}
// inner batch transactions are counter towards the ledger tx count
// inner batch transactions are counted towards the ledger tx count
{
Env env{
*this,
@@ -4931,10 +4933,10 @@ class Batch_test : public beast::unit_test::Suite
Ter(tesSUCCESS));
}
checkMetrics(*this, env, 0, std::nullopt, 3, 2);
checkMetrics(*this, env, 0, std::nullopt, 5, 2);
env(noop(carol), Ter(terQUEUED));
checkMetrics(*this, env, 1, std::nullopt, 3, 2);
checkMetrics(*this, env, 1, std::nullopt, 5, 2);
}
// A Batch is never queued, so it also cannot sit behind the account's
@@ -4982,6 +4984,186 @@ class Batch_test : public beast::unit_test::Suite
}
}
void
testOpenViewBatchInnerCount(FeatureBitset features)
{
testcase("open view batch inner count");
using namespace test::jtx;
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
env.fund(XRP(10000), alice, bob, carol);
env.close();
// The open ledger holds the Batch but never its inner transactions.
auto const aliceSeq = env.seq(alice);
env(batch::outer(alice, aliceSeq, batch::calcBatchFee(env, 0, 2), tfAllOrNothing),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 2),
Ter(tesSUCCESS));
BEAST_EXPECT(env.current()->txCount() == 1);
BEAST_EXPECT(env.current()->batchInnerCount() == 2);
env(noop(bob));
BEAST_EXPECT(env.current()->txCount() == 2);
BEAST_EXPECT(env.current()->batchInnerCount() == 2);
// A copy keeps the count.
OpenView copy(*env.current());
BEAST_EXPECT(copy.batchInnerCount() == 2);
// A view applied into another OpenView adds its count to it.
auto const carolSeq = env.seq(carol);
auto const jt = env.jt(
batch::outer(carol, carolSeq, batch::calcBatchFee(env, 0, 3), tfAllOrNothing),
batch::Inner(pay(carol, bob, XRP(1)), carolSeq + 1),
batch::Inner(pay(carol, bob, XRP(1)), carolSeq + 2),
batch::Inner(pay(carol, bob, XRP(1)), carolSeq + 3));
OpenView sandbox(kOpenLedger, &copy, copy.rules());
auto const result = xrpl::apply(env.app(), sandbox, *jt.stx, TapNone, env.journal);
BEAST_EXPECT(result.applied && result.ter == tesSUCCESS);
BEAST_EXPECT(sandbox.txCount() == 1);
BEAST_EXPECT(sandbox.batchInnerCount() == 3);
sandbox.apply(copy);
BEAST_EXPECT(copy.txCount() == 3);
BEAST_EXPECT(copy.batchInnerCount() == 5);
// The next open ledger starts from zero.
env.close();
BEAST_EXPECT(env.current()->batchInnerCount() == 0);
}
void
testOpenLedgerCountsBatchInners(FeatureBitset features)
{
testcase("open ledger counts batch inners");
using namespace test::jtx;
using namespace std::literals;
Env env{
*this,
makeSmallQueueConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}}),
features,
nullptr,
beast::Severity::Error};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
// Fund across several ledgers so the TxQ metrics stay restricted.
env.fund(XRP(10000), noripple(alice, bob));
env.close(env.now() + 5s, 10000ms);
env.fund(XRP(10000), noripple(carol));
env.close(env.now() + 5s, 10000ms);
checkMetrics(*this, env, 0, std::nullopt, 0, 3);
// A Batch with two inner transactions reaches the target on its own.
auto const aliceSeq = env.seq(alice);
env(batch::outer(alice, aliceSeq, batch::calcBatchFee(env, 0, 2), tfAllOrNothing),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 2),
Ter(tesSUCCESS));
checkMetrics(*this, env, 0, std::nullopt, 3, 3);
env(noop(bob));
checkMetrics(*this, env, 0, std::nullopt, 4, 3);
// Past the target the fee escalates, so a base-fee transaction queues.
env(noop(carol), Ter(terQUEUED));
checkMetrics(*this, env, 1, std::nullopt, 4, 3);
}
void
testClosedLedgerCountsBatchInners(FeatureBitset features)
{
testcase("closed ledger counts batch inners");
using namespace test::jtx;
using namespace std::literals;
Env env{
*this,
makeSmallQueueConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}}),
features,
nullptr,
beast::Severity::Error};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
// Fund across several ledgers so the TxQ metrics stay restricted.
env.fund(XRP(10000), noripple(alice, bob));
env.close(env.now() + 5s, 10000ms);
env.fund(XRP(10000), noripple(carol));
env.close(env.now() + 5s, 10000ms);
// Both inner transactions apply.
auto const aliceSeq = env.seq(alice);
env(batch::outer(alice, aliceSeq, batch::calcBatchFee(env, 0, 2), tfAllOrNothing),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 2),
Ter(tesSUCCESS));
// The second inner transaction is unfunded, so neither applies.
auto const carolSeq = env.seq(carol);
env(batch::outer(carol, carolSeq, batch::calcBatchFee(env, 0, 2), tfAllOrNothing),
batch::Inner(pay(carol, bob, XRP(1)), carolSeq + 1),
batch::Inner(pay(carol, bob, XRP(100000)), carolSeq + 2),
Ter(tesSUCCESS));
checkMetrics(*this, env, 0, std::nullopt, 6, 3);
// The closed ledger holds 4 entries: alice's Batch, its two inner
// transactions and carol's Batch. Each Batch counts 1 + 2.
env.close();
checkMetrics(*this, env, 0, 12, 0, 6);
}
void
testClosedLedgerMedianSkipsInners(FeatureBitset features)
{
testcase("closed ledger median skips batch inners");
using namespace test::jtx;
using namespace std::literals;
Env env{
*this,
makeSmallQueueConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}}),
features,
nullptr,
beast::Severity::Error};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
// Fund across several ledgers so the TxQ metrics stay restricted.
env.fund(XRP(10000), noripple(alice, bob));
env.close(env.now() + 5s, 10000ms);
env.fund(XRP(10000), noripple(carol));
env.close(env.now() + 5s, 10000ms);
// Both pay 1000 times their base fee: fee level 256,000.
auto const aliceSeq = env.seq(alice);
env(batch::outer(alice, aliceSeq, batch::calcBatchFee(env, 0, 2) * 1000, tfAllOrNothing),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
batch::Inner(pay(alice, bob, XRP(1)), aliceSeq + 2),
Ter(tesSUCCESS));
env(noop(bob), Fee(env.current()->fees().base * 1000));
// The inner transactions pay no fee and stay out of the median.
env.close();
checkMetrics(*this, env, 0, 8, 0, 4, 256, 256000);
}
void
testBatchNetworkOps(FeatureBitset features)
{
@@ -5407,7 +5589,10 @@ class Batch_test : public beast::unit_test::Suite
// The delegated payment never applied.
BEAST_EXPECT(env.balance(mallory) == preMallory);
BEAST_EXPECT(env.rpc("tx", txIDs[1])[jss::result][jss::error] == "txnNotFound");
auto const txResult = env.rpc("tx", txIDs[1])[jss::result];
BEAST_EXPECT(txResult[jss::error] == "txnNotFound");
BEAST_EXPECT(txResult[jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(txResult[jss::error_message] == "Transaction not found.");
}
}
@@ -5818,7 +6003,10 @@ class Batch_test : public beast::unit_test::Suite
validateClosedLedger(env, testCases);
// verify jv2 is not present in the closed ledger.
BEAST_EXPECT(env.rpc("tx", txIDs[1])[jss::result][jss::error] == "txnNotFound");
auto const txResult = env.rpc("tx", txIDs[1])[jss::result];
BEAST_EXPECT(txResult[jss::error] == "txnNotFound");
BEAST_EXPECT(txResult[jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(txResult[jss::error_message] == "Transaction not found.");
}
}
@@ -6382,6 +6570,10 @@ class Batch_test : public beast::unit_test::Suite
testPseudoTxn(features);
testOpenLedger(features);
testBatchTxQueue(features);
testOpenViewBatchInnerCount(features);
testOpenLedgerCountsBatchInners(features);
testClosedLedgerCountsBatchInners(features);
testClosedLedgerMedianSkipsInners(features);
testBatchNetworkOps(features);
testBatchDelegate(features);
testBatchDelegateConsent(features);
@@ -20,6 +20,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
@@ -259,8 +260,9 @@ class ConfidentialTransferExtended_test : public ConfidentialTransferTestBase
auto const credentialDeleted = [&](Env& env, Account const& subject) -> bool {
auto const credentials = credentials::ledgerEntry(env, subject, dpIssuer, credType);
return credentials[jss::result].isMember(jss::error) &&
credentials[jss::result][jss::error] == "entryNotFound";
return credentials[jss::result][jss::error] == "entryNotFound" &&
credentials[jss::result][jss::error_code] == RpcEntryNotFound &&
credentials[jss::result][jss::error_message] == "Entry not found.";
};
// TEST 4: Expired credential with matching depositPreauth entry.
@@ -7944,6 +7944,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
// Signature verification fails — rejected before ZKP check
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
}
// Variant B: Re-signed with inflated ciphertext.
@@ -8051,6 +8053,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
}
// Variant B: Re-signed with all negated ciphertexts.
@@ -8154,6 +8158,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
}
// Variant B: Re-signed with combined ciphertext.
@@ -8278,6 +8284,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
// Signature verification fails
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
}
// Variant B: Re-signed C1 substitution.
@@ -8368,6 +8376,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
// signed — transaction rejected before ZKP verification.
auto const jr = env.rpc("submit", strHex(tampered.slice()));
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
}
// -----------------------------------------------------------------
+34 -22
View File
@@ -23,6 +23,7 @@
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -124,8 +125,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
}
@@ -173,8 +175,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, issuer, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
}
}
@@ -222,8 +225,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
// resurrection
@@ -259,8 +263,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
// resurrection
@@ -294,8 +299,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
// resurrection
@@ -331,8 +337,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
// resurrection
@@ -368,8 +375,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
}
@@ -390,8 +398,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
}
@@ -410,8 +419,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
jle[jss::result][jss::error] == "entryNotFound" &&
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
jle[jss::result][jss::error_message] == "Entry not found.");
}
}
}
@@ -744,8 +754,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jDelCred = credentials::ledgerEntry(env, subject, issuer, credType2);
BEAST_EXPECT(
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
jDelCred[jss::result].isMember(jss::error) &&
jDelCred[jss::result][jss::error] == "entryNotFound");
jDelCred[jss::result][jss::error] == "entryNotFound" &&
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jDelCred[jss::result][jss::error_message] == "Entry not found.");
BEAST_EXPECT(ownerCount(env, issuer) == 0);
BEAST_EXPECT(ownerCount(env, subject) == 1);
@@ -781,8 +792,9 @@ struct Credentials_test : public beast::unit_test::Suite
auto const jDelCred = credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
jDelCred[jss::result].isMember(jss::error) &&
jDelCred[jss::result][jss::error] == "entryNotFound");
jDelCred[jss::result][jss::error] == "entryNotFound" &&
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jDelCred[jss::result][jss::error_message] == "Entry not found.");
}
}
}
+9 -2
View File
@@ -38,6 +38,7 @@
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/Dir.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -159,6 +160,8 @@ class Delegate_test : public beast::unit_test::Suite
env.close();
auto const jle = delegate::entry(env, gw, alice);
BEAST_EXPECT(jle[jss::result][jss::error] == "entryNotFound");
BEAST_EXPECT(jle[jss::result][jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jle[jss::result][jss::error_message] == "Entry not found.");
// alice can delegate permissions to gw as well
env(delegate::set(alice, gw, permissions));
@@ -167,6 +170,8 @@ class Delegate_test : public beast::unit_test::Suite
auto const response = delegate::entry(env, gw, alice);
// alice has not been granted any permissions by gw
BEAST_EXPECT(response[jss::result][jss::error] == "entryNotFound");
BEAST_EXPECT(response[jss::result][jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(response[jss::result][jss::error_message] == "Entry not found.");
}
void
@@ -2780,9 +2785,11 @@ class Delegate_test : public beast::unit_test::Suite
jv[jss::secret] = bob.name();
auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message].asString().contains(
"A Signer may not be the transaction's Account"));
jrr[jss::error_message] ==
"A Signer may not be the transaction's Account (" + bob.human() + ").");
}
}
+13 -8
View File
@@ -28,6 +28,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -1143,8 +1144,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
ledgerEntryDepositPreauth(env, bob, {{.issuer = issuer, .credType = credType}});
BEAST_EXPECT(
jDP.isObject() && jDP.isMember(jss::result) &&
jDP[jss::result].isMember(jss::error) &&
jDP[jss::result][jss::error] == "entryNotFound");
jDP[jss::result][jss::error] == "entryNotFound" &&
jDP[jss::result][jss::error_code] == RpcEntryNotFound &&
jDP[jss::result][jss::error_message] == "Entry not found.");
}
}
}
@@ -1232,8 +1234,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
auto const jDelCred = credentials::ledgerEntry(env, alice, issuer, credType);
BEAST_EXPECT(
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
jDelCred[jss::result].isMember(jss::error) &&
jDelCred[jss::result][jss::error] == "entryNotFound");
jDelCred[jss::result][jss::error] == "entryNotFound" &&
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jDelCred[jss::result][jss::error_message] == "Entry not found.");
}
{
@@ -1283,8 +1286,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
auto const jDelCred = credentials::ledgerEntry(env, gw, issuer, credType);
BEAST_EXPECT(
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
jDelCred[jss::result].isMember(jss::error) &&
jDelCred[jss::result][jss::error] == "entryNotFound");
jDelCred[jss::result][jss::error] == "entryNotFound" &&
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jDelCred[jss::result][jss::error_message] == "Entry not found.");
BEAST_EXPECT(ownerCount(env, issuer) == 0);
BEAST_EXPECT(ownerCount(env, gw) == 0);
@@ -1358,8 +1362,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
auto const jDelCred = credentials::ledgerEntry(env, zelda, issuer, credType);
BEAST_EXPECT(
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
jDelCred[jss::result].isMember(jss::error) &&
jDelCred[jss::result][jss::error] == "entryNotFound");
jDelCred[jss::result][jss::error] == "entryNotFound" &&
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
jDelCred[jss::result][jss::error_message] == "Entry not found.");
}
}
-78
View File
@@ -2295,83 +2295,6 @@ struct EscrowToken_test : public beast::unit_test::Suite
}
}
void
testIOUUnlockCreditRoundsAtBalancePrecision(FeatureBitset features)
{
testcase("IOU Unlock Credit Rounds At Balance Precision");
using namespace test::jtx;
using namespace std::literals;
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account{"gateway"};
auto const usd = gw["USD"];
STAmount const limit{usd.issue(), 1, 21};
STAmount const large{usd.issue(), 1, 20};
// The unlock credits 1 to a balance of exactly 1e20; the sum keeps 16
// significant digits, so the balance stays 1e20
BEAST_EXPECT(large + usd(1) == large);
// EscrowCancel: the refund of 1 to alice is lost to rounding
{
Env env{*this, features};
env.fund(XRP(10'000), alice, bob, gw);
env(fset(gw, asfAllowTrustLineLocking));
env.close();
env.trust(limit, alice);
env.close();
env(pay(gw, alice, usd(1)));
env.close();
// The nonzero limit keeps alice's line after she escrows all of it
auto const seq = env.seq(alice);
env(escrow::create(alice, bob, usd(1)),
escrow::kFinishTime(env.now() + 1s),
escrow::kCancelTime(env.now() + 2s));
env.close();
BEAST_EXPECT(env.balance(alice, usd) == usd(0));
env(pay(gw, alice, large));
env.close();
env.close();
BEAST_EXPECT(env.balance(alice, usd) == large);
env(escrow::cancel(alice, alice, seq), Ter(tesSUCCESS));
env.close();
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), SeqProxy::rawSequence(seq))));
BEAST_EXPECT(env.balance(alice, usd) == large);
}
// EscrowFinish: the payout of 1 to bob is lost to rounding
{
Env env{*this, features};
env.fund(XRP(10'000), alice, bob, gw);
env(fset(gw, asfAllowTrustLineLocking));
env.close();
env.trust(usd(1), alice);
env.trust(limit, bob);
env.close();
env(pay(gw, alice, usd(1)));
env(pay(gw, bob, large));
env.close();
auto const seq = env.seq(alice);
env(escrow::create(alice, bob, usd(1)), escrow::kFinishTime(env.now() + 1s));
env.close();
env.close();
BEAST_EXPECT(env.balance(bob, usd) == large);
env(escrow::finish(bob, alice, seq), Ter(tesSUCCESS));
env.close();
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), SeqProxy::rawSequence(seq))));
BEAST_EXPECT(env.balance(alice, usd) == usd(0));
BEAST_EXPECT(env.balance(bob, usd) == large);
}
}
void
testMPTEnablement(FeatureBitset features)
{
@@ -4377,7 +4300,6 @@ struct EscrowToken_test : public beast::unit_test::Suite
testIOUFreeze(features);
testIOUInsufficientFunds(features);
testIOUPrecisionLoss(features);
testIOUUnlockCreditRoundsAtBalancePrecision(features);
}
void
+42
View File
@@ -39,6 +39,7 @@
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Indexes.h>
@@ -1020,6 +1021,7 @@ class MPToken_test : public beast::unit_test::Suite
payment[jss::build_path] = true;
auto jrr = env.rpc("json", "submit", to_string(payment));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::result][jss::error_message] ==
"Field 'build_path' not allowed in this context.");
@@ -1581,6 +1583,8 @@ class MPToken_test : public beast::unit_test::Suite
jv[jss::tx_json][jss::Amount][jss::value] = std::to_string(kMaxMpTokenAmount + 1);
auto const jrr = env.rpc("json", "submit", to_string(jv));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::result][jss::error_message] == "Invalid field 'tx_json.Amount'.");
}
// Pay maximum amount with the transfer fee, SendMax, and
@@ -1945,6 +1949,8 @@ class MPToken_test : public beast::unit_test::Suite
jtx.stx->add(s);
auto jrr = env.rpc("submit", strHex(s.slice()));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!jrr[jss::result].isMember(jss::error_code));
BEAST_EXPECT(jrr[jss::result][jss::error_message].isNull());
// tx is unsigned
json::Value jv1;
@@ -1952,9 +1958,13 @@ class MPToken_test : public beast::unit_test::Suite
jv1[jss::tx_json] = jv;
jrr = env.rpc("json", "submit", to_string(jv1));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::result][jss::error_message] == "Amount can not be MPT.");
jrr = env.rpc("json", "sign", to_string(jv1));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::result][jss::error_message] == "Amount can not be MPT.");
};
auto setMPTFields = [&](SField const& field, json::Value& jv, bool withAmount = true) {
jv[jss::Asset] = toJson(xrpIssue());
@@ -2010,6 +2020,35 @@ class MPToken_test : public beast::unit_test::Suite
};
ammBid(sfBidMin);
ammBid(sfBidMax);
// PaymentChannelCreate
{
json::Value jv;
jv[jss::TransactionType] = jss::PaymentChannelCreate;
jv[jss::Account] = alice.human();
jv[jss::Destination] = carol.human();
jv[jss::SettleDelay] = 1;
jv[sfPublicKey.fieldName] = strHex(alice.pk().slice());
jv[jss::Amount] = mpt.getJson(JsonOptions::Values::None);
test(jv, jss::Amount.cStr());
}
// PaymentChannelFund
{
json::Value jv;
jv[jss::TransactionType] = jss::PaymentChannelFund;
jv[jss::Account] = alice.human();
jv[sfChannel.fieldName] = to_string(UInt256{1});
jv[jss::Amount] = mpt.getJson(JsonOptions::Values::None);
test(jv, jss::Amount.cStr());
}
// PaymentChannelClaim
{
json::Value jv;
jv[jss::TransactionType] = jss::PaymentChannelClaim;
jv[jss::Account] = alice.human();
jv[sfChannel.fieldName] = to_string(UInt256{1});
jv[jss::Amount] = mpt.getJson(JsonOptions::Values::None);
test(jv, jss::Amount.cStr());
}
// NFTokenCreateOffer
{
json::Value jv;
@@ -3169,6 +3208,9 @@ class MPToken_test : public beast::unit_test::Suite
jv1[jss::tx_json] = jv;
auto const jrr = env.rpc("json", "submit", to_string(jv1));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::result][jss::error_message] == "Field 'tx_json.Amount' has invalid data.");
}
}
+44
View File
@@ -31,6 +31,7 @@
#include <xrpl/config/Constants.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/KeyType.h>
@@ -586,6 +587,7 @@ public:
auto jrr = env.rpc("json", "sign_for", to_string(jvOne))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] ==
"When multi-signing 'tx_json.SigningPubKey' must be empty.");
@@ -613,6 +615,7 @@ public:
jrr = env.rpc("json", "submit_multisigned", to_string(jvSubmit))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid Fee field. Fees must be greater than zero.");
}
@@ -639,6 +642,7 @@ public:
jrr = env.rpc("json", "submit_multisigned", to_string(jvSubmit))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "internal");
BEAST_EXPECT(jrr[jss::error_code] == RpcInternal);
BEAST_EXPECT(jrr[jss::error_message] == "Internal error.");
}
@@ -651,6 +655,8 @@ public:
auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "masterDisabled");
BEAST_EXPECT(jrr[jss::error_code] == RpcMasterDisabled);
BEAST_EXPECT(jrr[jss::error_message] == "Master key is disabled.");
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
@@ -690,12 +696,16 @@ public:
auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "srcActMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcActMalformed);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'tx_json.Account'.");
Account const jimmy{"jimmy"};
jv[jss::tx_json][jss::Account] = jimmy.human();
jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "srcActNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcActNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "Source account not found.");
}
{
@@ -706,6 +716,7 @@ public:
auto jrr = env.rpc("json", "submit_multisigned", to_string(jv))[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "tx_json.Signers array may not be empty.");
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
@@ -1068,6 +1079,9 @@ public:
STTx local = *tx.stx;
local.setFieldVL(sfSigningPubKey, Blob()); // Empty SigningPubKey
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Empty SigningPubKey.");
@@ -1082,6 +1096,9 @@ public:
local.setFieldVL(sfTxnSignature, badSig);
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid signature.");
@@ -1095,6 +1112,9 @@ public:
local.setFieldU32(sfSequence, seq + 1);
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid signature.");
@@ -1105,6 +1125,9 @@ public:
STTx local = *tx.stx;
local[sfSigningPubKey] = alice.pk(); // Insert sfSigningPubKey
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Cannot both single- and multi-sign.");
@@ -1116,6 +1139,9 @@ public:
local.sign(alice.pk(), alice.sk());
local.setFieldVL(sfSigningPubKey, Blob()); // Empty SigningPubKey
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Cannot both single- and multi-sign.");
@@ -1131,6 +1157,9 @@ public:
signer.setFieldVL(sfTxnSignature, badSig);
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception].asString().contains(
"Invalid signature on account r"));
@@ -1141,6 +1170,9 @@ public:
STTx local = *tx.stx;
local.peekFieldArray(sfSigners).clear(); // Empty Signers array.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid Signers array size.");
@@ -1186,6 +1218,9 @@ public:
bogie_));
STTx const local = *tx.stx;
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid Signers array size.");
@@ -1195,6 +1230,9 @@ public:
JTx const tx = env.jt(noop(alice), Fee(2 * baseFee), Msig(alice));
STTx const local = *tx.stx;
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Invalid multisigner.");
@@ -1204,6 +1242,9 @@ public:
JTx const tx = env.jt(noop(alice), Fee(2 * baseFee), Msig(bogie_, bogie_));
STTx const local = *tx.stx;
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Duplicate Signers not allowed.");
@@ -1217,6 +1258,9 @@ public:
std::ranges::reverse(signers);
// Signature should fail.
auto const info = submitSTTx(local);
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
info[jss::result][jss::error_exception] ==
"fails local checks: Unsorted Signers array.");
+203 -12
View File
@@ -19,17 +19,24 @@
#include <test/jtx/trust.h>
#include <test/jtx/txflags.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/RPCHandler.h>
#include <xrpld/rpc/Role.h>
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/random.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/Job.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
@@ -40,8 +47,12 @@
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/nft.h>
#include <xrpl/resource/Charge.h>
#include <xrpl/resource/Consumer.h>
#include <xrpl/resource/Fees.h>
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <functional>
@@ -4653,18 +4664,22 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite
if (expect(
nftOffers.isMember(jss::result), "expected \"result\"", __FILE__, line))
{
if (expect(
nftOffers[jss::result].isMember(jss::error),
"expected \"error\"",
__FILE__,
line))
{
expect(
nftOffers[jss::result][jss::error].asString() == "objectNotFound",
"expected \"objectNotFound\"",
__FILE__,
line);
}
json::Value const& result = nftOffers[jss::result];
expect(
result[jss::error].asString() == "objectNotFound",
"expected \"objectNotFound\"",
__FILE__,
line);
expect(
result[jss::error_code] == RpcObjectNotFound,
"expected RpcObjectNotFound",
__FILE__,
line);
expect(
result[jss::error_message] == "The requested object was not found.",
"expected \"The requested object was not found.\"",
__FILE__,
line);
}
break;
}
@@ -4860,11 +4875,15 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite
// Sell-side marker passed to nft_buy_offers.
json::Value const result = queryWithMarker("nft_buy_offers", sellOfferIndex);
BEAST_EXPECT(result[jss::error].asString() == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
{
// Buy-side marker passed to nft_sell_offers.
json::Value const result = queryWithMarker("nft_sell_offers", buyOfferIndex);
BEAST_EXPECT(result[jss::error].asString() == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
// A same-side marker is still accepted. With a single offer on each
@@ -4879,6 +4898,176 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite
}
}
void
testNftXxxOffersInvalidParams(FeatureBitset features)
{
testcase("nft_buy_offers and nft_sell_offers invalid params");
using namespace test::jtx;
Env env{*this, features};
Account const issuer{"issuer"};
Account const buyer{"buyer"};
env.fund(XRP(10000), issuer, buyer);
env.close();
UInt256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
env(token::mint(issuer, 0), Txflags(tfTransferable));
env.close();
env(token::createOffer(issuer, nftID, XRP(100)), Txflags(tfSellNFToken));
env(token::createOffer(issuer, nftID, XRP(200)), Txflags(tfSellNFToken));
env(token::createOffer(buyer, nftID, XRP(50)), token::Owner(issuer));
env(token::createOffer(buyer, nftID, XRP(60)), token::Owner(issuer));
env.close();
for (auto const* request : {"nft_buy_offers", "nft_sell_offers"})
{
auto const query = [&env, request](json::Value const& params) {
return env.rpc("json", request, to_string(params))[jss::result];
};
auto const withField = [&nftID](json::StaticString field, json::Value value) {
json::Value params;
params[jss::nft_id] = to_string(nftID);
params[field] = std::move(value);
return params;
};
auto const expectError = [this](json::Value const& result, std::string const& message) {
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_message] == message);
};
expectError(query(json::ValueType::Object), "Missing field 'nft_id'.");
for (json::Value const& nftId : {
json::Value{"NOTHEX"},
json::Value{json::ValueType::Array},
json::Value{json::ValueType::Object},
})
{
json::Value params;
params[jss::nft_id] = nftId;
expectError(query(params), "Invalid field 'nft_id'.");
}
expectError(
query(withField(jss::limit, "1")), "Invalid field 'limit', not unsigned integer.");
expectError(
query(withField(jss::limit, -1)), "Invalid field 'limit', not unsigned integer.");
expectError(query(withField(jss::limit, 0)), "Invalid field 'limit'.");
{
json::Value const result = query(withField(jss::limit, json::ValueType::Null));
BEAST_EXPECT(!result.isMember(jss::error));
BEAST_EXPECT(result[jss::offers].size() == 2);
}
{
json::Value const result = query(withField(jss::limit, 1));
BEAST_EXPECT(!result.isMember(jss::error));
BEAST_EXPECT(result[jss::offers].size() == 1);
BEAST_EXPECT(result[jss::limit] == 1);
BEAST_EXPECT(result.isMember(jss::marker));
}
expectError(query(withField(jss::marker, 5)), "Invalid field 'marker', not string.");
expectError(query(withField(jss::marker, "NOTHEX")), "Invalid parameters.");
}
}
void
testNftXxxOffersNonAdminLimit(FeatureBitset features)
{
testcase("nft_buy_offers and nft_sell_offers non-admin limit");
using namespace test::jtx;
Env env{*this, features};
Account const issuer{"issuer"};
Account const buyer{"buyer"};
env.fund(XRP(1000000), issuer, buyer);
env.close();
UInt256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
env(token::mint(issuer, 0), Txflags(tfTransferable));
env.close();
auto const& range = rpc::tuning::kNftOffers;
for (auto i = 1u; i <= range.rmax + 1; ++i)
{
env(token::createOffer(issuer, nftID, XRP(i)), Txflags(tfSellNFToken));
env(token::createOffer(buyer, nftID, XRP(i)), token::Owner(issuer));
if (i % 5 == 0)
env.close();
}
env.close();
auto const userQuery = [this, &env, &nftID](char const* request, unsigned int limit) {
auto& app = env.app();
resource::Charge loadType = resource::kFeeReferenceRpc;
resource::Consumer c;
rpc::JsonContext context{
{
.j = env.journal,
.app = app,
.loadType = loadType,
.netOps = app.getOPs(),
.ledgerMaster = app.getLedgerMaster(),
.consumer = c,
.role = Role::USER,
.coro = {},
.infoSub = {},
.apiVersion = rpc::kApiVersionIfUnspecified,
},
{},
// NOLINTNEXTLINE(readability-trailing-comma) fix-it drops comma after `{}`
{},
};
json::Value params;
params[jss::command] = request;
params[jss::nft_id] = to_string(nftID);
params[jss::limit] = limit;
json::Value result;
Gate g;
auto const job =
app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
context.params = std::move(params);
context.coro = coro;
rpc::doCommand(context, result);
g.signal();
});
if (!BEAST_EXPECT(job))
return result;
using namespace std::chrono_literals;
BEAST_EXPECT(g.waitFor(5s));
job->join();
return result;
};
for (auto const* request : {"nft_buy_offers", "nft_sell_offers"})
{
{
json::Value const result = userQuery(request, 1);
BEAST_EXPECT(!result.isMember(jss::error));
BEAST_EXPECT(result[jss::offers].size() == range.rmin);
BEAST_EXPECT(result[jss::limit] == range.rmin);
BEAST_EXPECT(result.isMember(jss::marker));
}
{
json::Value const result = userQuery(request, range.rmax * 2);
BEAST_EXPECT(!result.isMember(jss::error));
BEAST_EXPECT(result[jss::offers].size() == range.rmax);
BEAST_EXPECT(result[jss::limit] == range.rmax);
BEAST_EXPECT(result.isMember(jss::marker));
}
}
}
void
testNFTokenNegOffer(FeatureBitset features)
{
@@ -7601,6 +7790,8 @@ protected:
testNFTokenDeleteAccount(features);
testNftXxxOffers(features);
testNftXxxOffersMarkerWrongSide(features);
testNftXxxOffersInvalidParams(features);
testNftXxxOffersNonAdminLimit(features);
testNFTokenNegOffer(features);
testIOUWithTransferFee(features);
testBrokeredSaleToSelf(features);
+7
View File
@@ -34,6 +34,7 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -2755,6 +2756,8 @@ public:
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
env.require(Owners(alice, 1), Owners(bob, 1));
}
@@ -2788,6 +2791,8 @@ public:
auto jro = ledgerEntryOffer(env, bob, bobOfferSeq);
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
};
testHelper3TokensMix(test);
}
@@ -2847,6 +2852,8 @@ public:
// Verify the offer was consumed
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
// verify balances look right after the partial payment
// only 300 XRP should have been payed since that's all
+7
View File
@@ -30,6 +30,7 @@
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -1707,6 +1708,8 @@ public:
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
env.require(Owners(alice, 1), Owners(bob, 1));
}
@@ -1738,6 +1741,8 @@ public:
auto jro = ledgerEntryOffer(env, bob, bobOfferSeq);
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
}
void
@@ -1798,6 +1803,8 @@ public:
// Verify the offer was consumed
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
// verify balances look right after the partial payment
// only 300 XRP should be have been payed since that's all
+7 -2
View File
@@ -26,6 +26,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/PathAsset.h>
#include <xrpl/protocol/SField.h>
@@ -162,7 +163,9 @@ public:
g.signal();
});
BEAST_EXPECT(g.waitFor(5s));
BEAST_EXPECT(result.isMember(jss::error));
BEAST_EXPECT(result[jss::error] == "srcCurMalformed");
BEAST_EXPECT(result[jss::error_code] == RpcSrcCurMalformed);
BEAST_EXPECT(result[jss::error_message] == "Source currency is malformed.");
// Test rpc::tuning::max_auto_src_cur source currencies.
numSrc.clear();
@@ -189,7 +192,9 @@ public:
g.signal();
});
BEAST_EXPECT(g.waitFor(5s));
BEAST_EXPECT(result.isMember(jss::error));
BEAST_EXPECT(result[jss::error] == "internal");
BEAST_EXPECT(result[jss::error_code] == RpcInternal);
BEAST_EXPECT(result[jss::error_message] == "Internal error.");
}
void
+7 -2
View File
@@ -33,6 +33,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/SField.h>
@@ -318,7 +319,9 @@ public:
g.signal();
});
BEAST_EXPECT(g.waitFor(5s));
BEAST_EXPECT(result.isMember(jss::error));
BEAST_EXPECT(result[jss::error] == "srcCurMalformed");
BEAST_EXPECT(result[jss::error_code] == RpcSrcCurMalformed);
BEAST_EXPECT(result[jss::error_message] == "Source currency is malformed.");
// Test rpc::tuning::max_auto_src_cur source currencies.
for (auto i = 0; i < (rpc::tuning::kMaxAutoSrcCur - 1); ++i)
@@ -341,7 +344,9 @@ public:
g.signal();
});
BEAST_EXPECT(g.waitFor(5s));
BEAST_EXPECT(result.isMember(jss::error));
BEAST_EXPECT(result[jss::error] == "internal");
BEAST_EXPECT(result[jss::error_code] == RpcInternal);
BEAST_EXPECT(result[jss::error_message] == "Internal error.");
}
void
File diff suppressed because it is too large. Load diff
+186 -126
View File
@@ -99,31 +99,27 @@ struct PayChan_test : public beast::unit_test::Suite
env.fund(XRP(10000), alice, bob);
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), settleDelay, pk));
BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000));
{
auto const preAlice = env.balance(alice);
env(paychan::fund(alice, chan, XRP(1000)));
env(fund(alice, chan, XRP(1000)));
auto const feeDrops = env.current()->fees().base;
BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - feeDrops);
}
auto chanBal = paychan::channelBalance(*env.current(), chan);
auto const chanAmt = paychan::channelAmount(*env.current(), chan);
auto chanBal = channelBalance(*env.current(), chan);
auto const chanAmt = channelAmount(*env.current(), chan);
BEAST_EXPECT(chanBal == XRP(0));
BEAST_EXPECT(chanAmt == XRP(2000));
{
// bad amounts (non-xrp, negative amounts)
// A token amount needs TokenPaychan (temBAD_AMOUNT) and
// fixCleanup3_2_0, which this suite disables (temDISABLED).
auto const tokenResult =
features[featureTokenPaychan] ? Ter(temDISABLED) : Ter(temBAD_AMOUNT);
env(create(alice, bob, usda(1000), settleDelay, pk), tokenResult);
env(fund(alice, chan, usda(1000)), tokenResult);
env(create(alice, bob, usda(1000), settleDelay, pk), Ter(temBAD_AMOUNT));
env(fund(alice, chan, usda(1000)), Ter(temBAD_AMOUNT));
env(create(alice, bob, XRP(-1000), settleDelay, pk), Ter(temBAD_AMOUNT));
env(fund(alice, chan, XRP(-1000)), Ter(temBAD_AMOUNT));
}
@@ -144,13 +140,9 @@ struct PayChan_test : public beast::unit_test::Suite
auto const iou = usda(100).value();
auto const negXRP = XRP(-100).value();
auto const posXRP = XRP(100).value();
// A token Balance or Amount needs TokenPaychan (temBAD_AMOUNT) and
// fixCleanup3_2_0, which this suite disables (temDISABLED).
auto const tokenResult =
features[featureTokenPaychan] ? Ter(temDISABLED) : Ter(temBAD_AMOUNT);
env(claim(alice, chan, iou, iou), tokenResult);
env(claim(alice, chan, posXRP, iou), tokenResult);
env(claim(alice, chan, iou, posXRP), tokenResult);
env(claim(alice, chan, iou, iou), Ter(temBAD_AMOUNT));
env(claim(alice, chan, posXRP, iou), Ter(temBAD_AMOUNT));
env(claim(alice, chan, iou, posXRP), Ter(temBAD_AMOUNT));
env(claim(alice, chan, negXRP, negXRP), Ter(temBAD_AMOUNT));
env(claim(alice, chan, posXRP, negXRP), Ter(temBAD_AMOUNT));
env(claim(alice, chan, negXRP, posXRP), Ter(temBAD_AMOUNT));
@@ -170,9 +162,9 @@ struct PayChan_test : public beast::unit_test::Suite
auto const reqBal = chanBal + delta;
auto const authAmt = reqBal + XRP(100);
assert(reqBal <= chanAmt);
env(paychan::claim(alice, chan, reqBal, authAmt));
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == reqBal);
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt);
env(claim(alice, chan, reqBal, authAmt));
BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal);
BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(env.balance(bob) == preBob + delta);
chanBal = reqBal;
}
@@ -332,14 +324,14 @@ struct PayChan_test : public beast::unit_test::Suite
NetClock::time_point const cancelAfter =
env.current()->header().parentCloseTime + 3600s;
auto const channelFunds = XRP(1000);
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk, cancelAfter));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk, cancelAfter));
BEAST_EXPECT(channelExists(*env.current(), chan));
env.close(cancelAfter);
{
// dst cannot claim after cancelAfter
auto const chanBal = paychan::channelBalance(*env.current(), chan);
auto const chanAmt = paychan::channelAmount(*env.current(), chan);
auto const chanBal = channelBalance(*env.current(), chan);
auto const chanAmt = channelAmount(*env.current(), chan);
auto const preAlice = env.balance(alice);
auto const preBob = env.balance(bob);
auto const delta = XRP(500);
@@ -349,7 +341,7 @@ struct PayChan_test : public beast::unit_test::Suite
auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt);
env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()));
auto const feeDrops = env.current()->fees().base;
BEAST_EXPECT(!paychan::channelExists(*env.current(), chan));
BEAST_EXPECT(!channelExists(*env.current(), chan));
BEAST_EXPECT(env.balance(bob) == preBob - feeDrops);
BEAST_EXPECT(env.balance(alice) == preAlice + channelFunds);
}
@@ -363,9 +355,9 @@ struct PayChan_test : public beast::unit_test::Suite
NetClock::time_point const cancelAfter =
env.current()->header().parentCloseTime + 3600s;
auto const channelFunds = XRP(1000);
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk, cancelAfter));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk, cancelAfter));
BEAST_EXPECT(channelExists(*env.current(), chan));
// third party close before cancelAfter
env(claim(carol, chan), Txflags(tfClose), Ter(tecNO_PERMISSION));
BEAST_EXPECT(channelExists(*env.current(), chan));
@@ -432,9 +424,9 @@ struct PayChan_test : public beast::unit_test::Suite
auto const minExpiration = closeTime + settleDelay;
NetClock::time_point const cancelAfter = closeTime + 7200s;
auto const channelFunds = XRP(1000);
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk, cancelAfter));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk, cancelAfter));
BEAST_EXPECT(channelExists(*env.current(), chan));
BEAST_EXPECT(!channelExpiration(*env.current(), chan));
// Owner closes, will close after settleDelay
env(claim(alice, chan), Txflags(tfClose));
@@ -463,7 +455,7 @@ struct PayChan_test : public beast::unit_test::Suite
env(fund(alice, chan, XRP(1), NetClock::time_point{minExpiration - 50s}),
Ter(temBAD_EXPIRATION));
BEAST_EXPECT(!channelExpiration(*env.current(), chan));
env(paychan::fund(alice, chan, XRP(1), NetClock::time_point{minExpiration}));
env(fund(alice, chan, XRP(1), NetClock::time_point{minExpiration}));
env.close(minExpiration);
// Try to extend the expiration after the expiration has already passed
env(fund(alice, chan, XRP(1), NetClock::time_point{minExpiration + 1000s}));
@@ -485,17 +477,17 @@ struct PayChan_test : public beast::unit_test::Suite
NetClock::time_point const settleTimepoint =
env.current()->header().parentCloseTime + settleDelay;
auto const channelFunds = XRP(1000);
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(channelExists(*env.current(), chan));
// Owner closes, will close after settleDelay
env(claim(alice, chan), Txflags(tfClose));
BEAST_EXPECT(channelExists(*env.current(), chan));
env.close(settleTimepoint - (settleDelay / 2));
{
// receiver can still claim
auto const chanBal = paychan::channelBalance(*env.current(), chan);
auto const chanAmt = paychan::channelAmount(*env.current(), chan);
auto const chanBal = channelBalance(*env.current(), chan);
auto const chanAmt = channelAmount(*env.current(), chan);
auto const preBob = env.balance(bob);
auto const delta = XRP(500);
auto const reqBal = chanBal + delta;
@@ -511,8 +503,8 @@ struct PayChan_test : public beast::unit_test::Suite
env.close(settleTimepoint);
{
// past settleTime, channel will close
auto const chanBal = paychan::channelBalance(*env.current(), chan);
auto const chanAmt = paychan::channelAmount(*env.current(), chan);
auto const chanBal = channelBalance(*env.current(), chan);
auto const chanAmt = channelAmount(*env.current(), chan);
auto const preAlice = env.balance(alice);
auto const preBob = env.balance(bob);
auto const delta = XRP(500);
@@ -541,17 +533,17 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(channelExists(*env.current(), chan));
// Owner tries to close channel, but it will remain open (settle delay)
env(claim(alice, chan), Txflags(tfClose));
BEAST_EXPECT(channelExists(*env.current(), chan));
{
// claim the entire amount
auto const preBob = env.balance(bob);
env(paychan::claim(alice, chan, channelFunds.value(), channelFunds.value()));
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == channelFunds);
env(claim(alice, chan, channelFunds.value(), channelFunds.value()));
BEAST_EXPECT(channelBalance(*env.current(), chan) == channelFunds);
BEAST_EXPECT(env.balance(bob) == preBob + channelFunds);
}
auto const preAlice = env.balance(alice);
@@ -576,15 +568,15 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(channelExists(*env.current(), chan));
// Owner tries to close channel, but it will remain open (settle delay)
env(claim(alice, chan), Txflags(tfClose));
BEAST_EXPECT(channelExists(*env.current(), chan));
{
auto chanBal = paychan::channelBalance(*env.current(), chan);
auto const chanAmt = paychan::channelAmount(*env.current(), chan);
auto chanBal = channelBalance(*env.current(), chan);
auto const chanAmt = channelAmount(*env.current(), chan);
auto const preBob = env.balance(bob);
auto const delta = XRP(500);
@@ -599,8 +591,8 @@ struct PayChan_test : public beast::unit_test::Suite
}
{
// Claim again
auto chanBal = paychan::channelBalance(*env.current(), chan);
auto const chanAmt = paychan::channelAmount(*env.current(), chan);
auto chanBal = channelBalance(*env.current(), chan);
auto const chanAmt = channelAmount(*env.current(), chan);
auto const preBob = env.balance(bob);
auto const delta = XRP(500);
@@ -631,9 +623,9 @@ struct PayChan_test : public beast::unit_test::Suite
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env(fset(bob, asfDisallowXRP));
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), 3600s, alice.pk()));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), 3600s, alice.pk()));
BEAST_EXPECT(channelExists(*env.current(), chan));
}
{
@@ -642,13 +634,13 @@ struct PayChan_test : public beast::unit_test::Suite
// since it is just advisory.
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), 3600s, alice.pk()));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), 3600s, alice.pk()));
BEAST_EXPECT(channelExists(*env.current(), chan));
env(fset(bob, asfDisallowXRP));
auto const reqBal = XRP(500).value();
env(paychan::claim(alice, chan, reqBal, reqBal));
env(claim(alice, chan, reqBal, reqBal));
}
}
@@ -700,16 +692,16 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000));
BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000));
// alice can add more funds to the channel even though bob has
// asfDepositAuth set.
env(paychan::fund(alice, chan, XRP(1000)));
env(fund(alice, chan, XRP(1000)));
env.close();
// alice claims. Fails because bob's lsfDepositAuth flag is set.
@@ -737,7 +729,7 @@ struct PayChan_test : public beast::unit_test::Suite
// bob claims with signature. Succeeds even though bob's
// lsfDepositAuth flag is set since bob submitted the
// transaction.
env(paychan::claim(bob, chan, delta, delta, Slice(sig), pk));
env(claim(bob, chan, delta, delta, Slice(sig), pk));
env.close();
BEAST_EXPECT(env.balance(bob) == preBob + delta - baseFee);
}
@@ -768,7 +760,7 @@ struct PayChan_test : public beast::unit_test::Suite
env(deposit::auth(bob, alice));
env.close();
env(paychan::claim(alice, chan, delta, delta, Slice(sig), pk));
env(claim(alice, chan, delta, delta, Slice(sig), pk));
env.close();
BEAST_EXPECT(env.balance(bob) == preBob + delta - (3 * baseFee));
@@ -790,7 +782,7 @@ struct PayChan_test : public beast::unit_test::Suite
env.close();
// alice claims successfully.
env(paychan::claim(alice, chan, delta, delta));
env(claim(alice, chan, delta, delta));
env.close();
BEAST_EXPECT(env.balance(bob) == preBob + XRP(800) - (5 * baseFee));
}
@@ -818,12 +810,12 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
// alice add funds to the channel
env(paychan::fund(alice, chan, XRP(1000)));
env(fund(alice, chan, XRP(1000)));
env.close();
std::string const credBadIdx =
@@ -914,12 +906,12 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
// alice add funds to the channel
env(paychan::fund(alice, chan, XRP(1000)));
env(fund(alice, chan, XRP(1000)));
env.close();
auto const delta = XRP(500).value();
@@ -947,11 +939,11 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
env(paychan::fund(alice, chan, XRP(1000)));
env(fund(alice, chan, XRP(1000)));
env.close();
std::string const credIdx =
"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
@@ -983,12 +975,12 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
auto const chan1 = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan1));
auto const chan2 = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(paychan::channelExists(*env.current(), chan2));
auto const chan1 = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(channelExists(*env.current(), chan1));
auto const chan2 = channel(alice, bob, env.seq(alice));
env(create(alice, bob, channelFunds, settleDelay, pk));
BEAST_EXPECT(channelExists(*env.current(), chan2));
BEAST_EXPECT(chan1 != chan2);
}
@@ -1007,8 +999,8 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
auto const chan1Str = to_string(paychan::channel(alice, bob, env.seq(alice)));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
auto const chan1Str = to_string(channel(alice, bob, env.seq(alice)));
env(create(alice, bob, channelFunds, settleDelay, pk));
env.close();
{
// test account non-string
@@ -1066,6 +1058,7 @@ struct PayChan_test : public beast::unit_test::Suite
auto const jrr = env.rpc("json", "account_channels", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "actMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jrr[jss::error_message] == "Account malformed.");
BEAST_EXPECT(!jrr.isMember(jss::ledger_index));
BEAST_EXPECT(!jrr.isMember(jss::ledger_current_index));
}
@@ -1114,8 +1107,8 @@ struct PayChan_test : public beast::unit_test::Suite
BEAST_EXPECT(r[jss::result][jss::channels].size() == 0);
BEAST_EXPECT(r[jss::result][jss::validated]);
}
auto const chan2Str = to_string(paychan::channel(alice, bob, env.seq(alice)));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
auto const chan2Str = to_string(channel(alice, bob, env.seq(alice)));
env(create(alice, bob, channelFunds, settleDelay, pk));
env.close();
{
auto const r = env.rpc("account_channels", alice.human(), bob.human());
@@ -1165,7 +1158,7 @@ struct PayChan_test : public beast::unit_test::Suite
auto const channelFunds = XRP(1);
for (auto const& b : bobs)
{
env(paychan::create(alice, b, channelFunds, settleDelay, alice.pk()));
env(create(alice, b, channelFunds, settleDelay, alice.pk()));
}
}
@@ -1240,7 +1233,9 @@ struct PayChan_test : public beast::unit_test::Suite
{
// degenerate case
auto const r = testLimit(env, alice, 0);
BEAST_EXPECT(r.isMember(jss::error_message));
BEAST_EXPECT(r[jss::error] == "invalidParams");
BEAST_EXPECT(r[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(r[jss::error_message] == "Invalid field 'limit'.");
}
{
@@ -1289,8 +1284,8 @@ struct PayChan_test : public beast::unit_test::Suite
// channels where alice is the source, not the destination
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
env(paychan::create(alice, bob, channelFunds, settleDelay, alice.pk()));
env(paychan::create(bob, alice, channelFunds, settleDelay, bob.pk()));
env(create(alice, bob, channelFunds, settleDelay, alice.pk()));
env(create(bob, alice, channelFunds, settleDelay, bob.pk()));
auto const r = [&] {
json::Value jvc;
@@ -1317,8 +1312,8 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
auto const chan1Str = to_string(paychan::channel(alice, bob, env.seq(alice)));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
auto const chan1Str = to_string(channel(alice, bob, env.seq(alice)));
env(create(alice, bob, channelFunds, settleDelay, pk));
env.close();
json::Value args{json::ValueType::Object};
@@ -1334,7 +1329,12 @@ struct PayChan_test : public beast::unit_test::Suite
auto const rs =
env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
auto const error = apiVersion < 2u ? "invalidParams" : "badKeyType";
auto const errorCode = apiVersion < 2u ? RpcInvalidParams : RpcBadKeyType;
auto const errorMessage =
apiVersion < 2u ? "Invalid field 'key_type'." : "Bad key type.";
BEAST_EXPECT(rs[jss::error] == error);
BEAST_EXPECT(rs[jss::error_code] == errorCode);
BEAST_EXPECT(rs[jss::error_message] == errorMessage);
});
}
@@ -1352,8 +1352,8 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
auto const chan1Str = to_string(paychan::channel(alice, bob, env.seq(alice)));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
auto const chan1Str = to_string(channel(alice, bob, env.seq(alice)));
env(create(alice, bob, channelFunds, settleDelay, pk));
env.close();
std::string chan1PkStr;
{
@@ -1375,8 +1375,8 @@ struct PayChan_test : public beast::unit_test::Suite
BEAST_EXPECT(r[jss::result][jss::channels].size() == 0);
BEAST_EXPECT(r[jss::result][jss::validated]);
}
auto const chan2Str = to_string(paychan::channel(alice, bob, env.seq(alice)));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
auto const chan2Str = to_string(channel(alice, bob, env.seq(alice)));
env(create(alice, bob, channelFunds, settleDelay, pk));
env.close();
{
auto const r = env.rpc("account_channels", alice.human(), bob.human());
@@ -1423,22 +1423,40 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pkAsHex = sliceToHex(pk.slice());
auto rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000x", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000 ", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "x1000", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "x", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, " ", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000 1000", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1,000", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, " 1000", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "", sig);
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
}
{
// malformed channel
@@ -1447,22 +1465,34 @@ struct PayChan_test : public beast::unit_test::Suite
chan1StrBad.pop_back();
auto rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig);
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
rv = env.rpc("channel_authorize", "alice", chan1StrBad, "1000");
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
chan1StrBad = chan1Str;
chan1StrBad.push_back('0');
rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig);
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
rv = env.rpc("channel_authorize", "alice", chan1StrBad, "1000");
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
chan1StrBad = chan1Str;
chan1StrBad.back() = 'x';
rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig);
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
rv = env.rpc("channel_authorize", "alice", chan1StrBad, "1000");
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
}
{
// channel_id is not a string.
@@ -1475,6 +1505,8 @@ struct PayChan_test : public beast::unit_test::Suite
auto const ra =
env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(ra[jss::error] == "invalidParams");
BEAST_EXPECT(ra[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ra[jss::error_message] == "Invalid parameters.");
}
{
// channel_id is not a string.
@@ -1487,6 +1519,8 @@ struct PayChan_test : public beast::unit_test::Suite
auto const rv =
env.rpc("json", "channel_verify", args.toStyledString())[jss::result];
BEAST_EXPECT(rv[jss::error] == "invalidParams");
BEAST_EXPECT(rv[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(rv[jss::error_message] == "Invalid parameters.");
}
{
// give an ill formed base 58 public key
@@ -1545,6 +1579,8 @@ struct PayChan_test : public beast::unit_test::Suite
// Try to authorize using an unknown key type, except an error:
auto const rs1 = env.rpc("channel_authorize", "charlie", "nyx", chan, "1000");
BEAST_EXPECT(rs1[jss::error] == "badKeyType");
BEAST_EXPECT(rs1[jss::error_code] == RpcBadKeyType);
BEAST_EXPECT(rs1[jss::error_message] == "Bad key type.");
// Try to authorize using secp256k1; the authorization _should_
// succeed but the verification should fail:
@@ -1570,10 +1606,16 @@ struct PayChan_test : public beast::unit_test::Suite
// send malformed amounts rpc requests
auto rs = env.rpc("channel_authorize", "alice", chan1Str, "1000x");
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
rs = env.rpc("channel_authorize", "alice", chan1Str, "x1000");
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
rs = env.rpc("channel_authorize", "alice", chan1Str, "x");
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
{
// Missing channel_id
json::Value args{json::ValueType::Object};
@@ -1582,6 +1624,8 @@ struct PayChan_test : public beast::unit_test::Suite
args[jss::passphrase] = "passphrase_can_be_anything";
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(rs[jss::error] == "invalidParams");
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(rs[jss::error_message] == "Missing field 'channel_id'.");
}
{
// Missing amount
@@ -1591,6 +1635,8 @@ struct PayChan_test : public beast::unit_test::Suite
args[jss::passphrase] = "passphrase_can_be_anything";
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(rs[jss::error] == "invalidParams");
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(rs[jss::error_message] == "Missing field 'amount'.");
}
{
// Missing key_type and no secret.
@@ -1600,6 +1646,8 @@ struct PayChan_test : public beast::unit_test::Suite
args[jss::passphrase] = "passphrase_can_be_anything";
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(rs[jss::error] == "invalidParams");
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(rs[jss::error_message] == "Missing field 'secret'.");
}
{
// Both passphrase and seed specified.
@@ -1611,6 +1659,11 @@ struct PayChan_test : public beast::unit_test::Suite
args[jss::seed] = "seed can be anything";
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(rs[jss::error] == "invalidParams");
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
rs[jss::error_message] ==
"Exactly one of the following must be specified: passphrase, secret, seed or "
"seed_hex");
}
{
// channel_id is not exact hex.
@@ -1621,6 +1674,8 @@ struct PayChan_test : public beast::unit_test::Suite
args[jss::passphrase] = "passphrase_can_be_anything";
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(rs[jss::error] == "channelMalformed");
BEAST_EXPECT(rs[jss::error_code] == RpcChannelMalformed);
BEAST_EXPECT(rs[jss::error_message] == "Payment channel is malformed.");
}
{
// amount is not a string
@@ -1631,6 +1686,8 @@ struct PayChan_test : public beast::unit_test::Suite
args[jss::passphrase] = "passphrase_can_be_anything";
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
}
{
// Amount is not a decimal string.
@@ -1641,6 +1698,8 @@ struct PayChan_test : public beast::unit_test::Suite
args[jss::passphrase] = "passphrase_can_be_anything";
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
}
}
}
@@ -1844,8 +1903,8 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
auto jv = paychan::create(alice, bob, XRP(1000), settleDelay, pk);
auto const chan = channel(alice, bob, env.seq(alice));
auto jv = create(alice, bob, XRP(1000), settleDelay, pk);
auto const pkHex = strHex(pk.slice());
jv["PublicKey"] = pkHex.substr(2, pkHex.size() - 2);
env(jv, Ter(temMALFORMED));
@@ -1891,6 +1950,8 @@ struct PayChan_test : public beast::unit_test::Suite
)*";
auto const r = env.rpc("json", "channel_verify", txn);
BEAST_EXPECT(r["result"]["error"] == "publicMalformed");
BEAST_EXPECT(r["result"]["error_code"] == RpcPublicMalformed);
BEAST_EXPECT(r["result"]["error_message"] == "Public key is malformed.");
}
}
@@ -1923,7 +1984,7 @@ struct PayChan_test : public beast::unit_test::Suite
// Test with adding the paychan to the recipient's owner directory
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob);
BEAST_EXPECT(inOwnerDir(*env.current(), alice, chanSle));
@@ -1945,7 +2006,7 @@ struct PayChan_test : public beast::unit_test::Suite
Env env(*this, features);
env.fund(XRP(10000), alice, bob);
// create the channel before the amendment activates
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob);
BEAST_EXPECT(inOwnerDir(*env.current(), alice, chanSle));
@@ -1998,18 +2059,18 @@ struct PayChan_test : public beast::unit_test::Suite
// Create a channel from alice to bob
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(1000), settleDelay, pk));
auto const chan = channel(alice, bob, env.seq(alice));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000));
BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000));
rmAccount(env, alice, carol, tecHAS_OBLIGATIONS);
rmAccount(env, bob, carol, TER(tecHAS_OBLIGATIONS));
auto const feeDrops = env.current()->fees().base;
auto chanBal = paychan::channelBalance(*env.current(), chan);
auto chanAmt = paychan::channelAmount(*env.current(), chan);
auto chanBal = channelBalance(*env.current(), chan);
auto chanAmt = channelAmount(*env.current(), chan);
BEAST_EXPECT(chanBal == XRP(0));
BEAST_EXPECT(chanAmt == XRP(1000));
@@ -2073,15 +2134,15 @@ struct PayChan_test : public beast::unit_test::Suite
auto const pk = alice.pk();
auto const settleDelay = 100s;
auto const chan = paychan::channel(alice, bob, aliceTicketSeq);
auto const chan = channel(alice, bob, aliceTicketSeq);
env(create(alice, bob, XRP(1000), settleDelay, pk), ticket::Use(aliceTicketSeq++));
env.require(Tickets(alice, env.seq(alice) - aliceTicketSeq));
BEAST_EXPECT(env.seq(alice) == aliceSeq);
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == XRP(1000));
BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0));
BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000));
{
auto const preAlice = env.balance(alice);
@@ -2094,8 +2155,8 @@ struct PayChan_test : public beast::unit_test::Suite
BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - feeDrops);
}
auto chanBal = paychan::channelBalance(*env.current(), chan);
auto const chanAmt = paychan::channelAmount(*env.current(), chan);
auto chanBal = channelBalance(*env.current(), chan);
auto const chanAmt = channelAmount(*env.current(), chan);
BEAST_EXPECT(chanBal == XRP(0));
BEAST_EXPECT(chanAmt == XRP(2000));
@@ -2111,8 +2172,8 @@ struct PayChan_test : public beast::unit_test::Suite
env.require(Tickets(alice, env.seq(alice) - aliceTicketSeq));
BEAST_EXPECT(env.seq(alice) == aliceSeq);
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == reqBal);
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal);
BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(env.balance(bob) == preBob + delta);
chanBal = reqBal;
}
@@ -2130,8 +2191,8 @@ struct PayChan_test : public beast::unit_test::Suite
env.require(Tickets(bob, env.seq(bob) - bobTicketSeq));
BEAST_EXPECT(env.seq(bob) == bobSeq);
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == reqBal);
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal);
BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
auto const feeDrops = env.current()->fees().base;
BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops);
chanBal = reqBal;
@@ -2146,8 +2207,8 @@ struct PayChan_test : public beast::unit_test::Suite
env.require(Tickets(bob, env.seq(bob) - bobTicketSeq));
BEAST_EXPECT(env.seq(bob) == bobSeq);
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == chanBal);
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal);
BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(env.balance(bob) == preBob - feeDrops);
}
{
@@ -2166,8 +2227,8 @@ struct PayChan_test : public beast::unit_test::Suite
env.require(Tickets(bob, env.seq(bob) - bobTicketSeq));
BEAST_EXPECT(env.seq(bob) == bobSeq);
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == chanBal);
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal);
BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(env.balance(bob) == preBob);
}
@@ -2177,8 +2238,8 @@ struct PayChan_test : public beast::unit_test::Suite
env.require(Tickets(bob, env.seq(bob) - bobTicketSeq));
BEAST_EXPECT(env.seq(bob) == bobSeq);
BEAST_EXPECT(paychan::channelBalance(*env.current(), chan) == chanBal);
BEAST_EXPECT(paychan::channelAmount(*env.current(), chan) == chanAmt);
BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal);
BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
{
// Dst closes channel
@@ -2189,7 +2250,7 @@ struct PayChan_test : public beast::unit_test::Suite
env.require(Tickets(bob, env.seq(bob) - bobTicketSeq));
BEAST_EXPECT(env.seq(bob) == bobSeq);
BEAST_EXPECT(!paychan::channelExists(*env.current(), chan));
BEAST_EXPECT(!channelExists(*env.current(), chan));
auto const feeDrops = env.current()->fees().base;
auto const delta = chanAmt - chanBal;
assert(delta > beast::kZero);
@@ -2240,7 +2301,6 @@ public:
// pre-amendment behavior, so run it with the amendment disabled.
FeatureBitset const all{testableAmendments() - fixCleanup3_2_0};
testWithFeats(all);
testWithFeats(all - featureTokenPaychan);
testDepositAuthCreds();
testMetaAndOwnership(all - fixIncludeKeyletFields);
}
+4 -2
View File
@@ -13,6 +13,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/SField.h>
@@ -42,8 +43,9 @@ class ReducedOffer_test : public beast::unit_test::Suite
{
json::Value ledgerOffer = ledgerEntryOffer(env, acct, offerSeq);
return !(
ledgerOffer.isMember(jss::error) &&
ledgerOffer[jss::error].asString() == "entryNotFound");
ledgerOffer[jss::error].asString() == "entryNotFound" &&
ledgerOffer[jss::error_code] == RpcEntryNotFound &&
ledgerOffer[jss::error_message] == "Entry not found.");
}
// Common code to clean up unneeded offers.
+11 -7
View File
@@ -302,10 +302,14 @@ class SHAMapStore_test : public beast::unit_test::Suite
}
static bool
bad(json::Value const& json, ErrorCodeI error = RpcLgrNotFound)
bad(json::Value const& json,
std::string const& token,
ErrorCodeI error,
std::string const& message)
{
return json.isMember(jss::result) && rpc::containsError(json[jss::result]) &&
json[jss::result][jss::error_code] == error;
return json.isMember(jss::result) && json[jss::result][jss::error] == token &&
json[jss::result][jss::error_code] == error &&
json[jss::result][jss::error_message] == message;
}
std::string
@@ -883,7 +887,7 @@ public:
std::map<std::uint32_t, json::Value const> ledgers;
auto ledgerTmp = env.rpc("ledger", "0");
BEAST_EXPECT(bad(ledgerTmp));
BEAST_EXPECT(bad(ledgerTmp, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound"));
ledgers.emplace(1, env.rpc("ledger", "1"));
BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
@@ -895,10 +899,10 @@ public:
BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
ledgerTmp = env.rpc("ledger", "4");
BEAST_EXPECT(bad(ledgerTmp));
BEAST_EXPECT(bad(ledgerTmp, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound"));
ledgerTmp = env.rpc("ledger", "100");
BEAST_EXPECT(bad(ledgerTmp));
BEAST_EXPECT(bad(ledgerTmp, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound"));
auto const firstSeq = waitForReady(env);
auto lastRotated = firstSeq - 1;
@@ -987,7 +991,7 @@ public:
// Because advisory_delete is unset,
// "can_delete" is disabled.
auto const canDelete = env.rpc("can_delete");
BEAST_EXPECT(bad(canDelete, RpcNotEnabled));
BEAST_EXPECT(bad(canDelete, "notEnabled", RpcNotEnabled, "Not enabled in configuration."));
// Close ledgers without triggering a rotate
for (; ledgerSeq < lastRotated + kDeleteInterval; ++ledgerSeq)
-36
View File
@@ -4353,42 +4353,6 @@ public:
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
// A sponsored token channel locks no XRP, so a source below its own
// reserve before the fee may create one, as it may a token escrow.
if (cosigning)
{
Env env{*this, testableAmendments()};
Account const gw("gw");
auto const usd = gw["USD"];
env.fund(XRP(1000000), alice, bob, sponsor, gw);
env.close();
env(fset(gw, asfAllowTrustLineLocking));
env.trust(usd(10000), alice);
env.close();
env(pay(gw, alice, usd(1000)));
env.close();
adjustAccountXRPBalance(env, alice, reserve(env, ownerCount(env, alice)));
env(noop(alice));
env.close();
BEAST_EXPECT(env.balance(alice) < reserve(env, ownerCount(env, alice)));
env(escrow::create(alice, bob, usd(100)),
escrow::kFinishTime(env.now() + 100s),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor),
Ter(tesSUCCESS));
env.close();
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, usd(100), 10s, alice.pk()),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor),
Ter(tesSUCCESS));
env.close();
BEAST_EXPECT(paychan::channelExists(*env.current(), chan));
}
}
void
@@ -20,6 +20,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
@@ -1032,15 +1033,25 @@ struct TransactionProposalCancel_test : public beast::unit_test::Suite
{
json::Value noTicket;
noTicket[jss::account] = target.human();
BEAST_EXPECT(ledgerEntry(noTicket)[jss::error] == "malformedRequest");
auto const noTicketResult = ledgerEntry(noTicket);
BEAST_EXPECT(noTicketResult[jss::error] == "malformedRequest");
BEAST_EXPECT(noTicketResult[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(noTicketResult[jss::error_message] == "Missing field 'ticket_seq'.");
json::Value noAccount;
noAccount[jss::ticket_seq] = ticketSeq;
BEAST_EXPECT(ledgerEntry(noAccount)[jss::error] == "malformedRequest");
auto const noAccountResult = ledgerEntry(noAccount);
BEAST_EXPECT(noAccountResult[jss::error] == "malformedRequest");
BEAST_EXPECT(noAccountResult[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(noAccountResult[jss::error_message] == "Missing field 'account'.");
json::Value badAccount = byPair;
badAccount[jss::account] = "not an account";
BEAST_EXPECT(ledgerEntry(badAccount)[jss::error] == "malformedAddress");
auto const badAccountResult = ledgerEntry(badAccount);
BEAST_EXPECT(badAccountResult[jss::error] == "malformedAddress");
BEAST_EXPECT(badAccountResult[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
badAccountResult[jss::error_message] == "Invalid field 'account', not AccountID.");
}
// Listed for the proposer, who owns it, and not for the target.
@@ -1057,6 +1068,8 @@ struct TransactionProposalCancel_test : public beast::unit_test::Suite
{
auto const jrr = ledgerEntry(byPair);
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "Entry not found.");
BEAST_EXPECT(!jrr.isMember(jss::node));
BEAST_EXPECT(jrr[jss::index] == to_string(proposalID));
}
+5
View File
@@ -14,6 +14,7 @@
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
@@ -66,6 +67,8 @@ class TrustAndBalance_test : public beast::unit_test::Suite
// reflects this
auto jrr = ledgerEntryState(env, gw, alice, "USD");
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "Entry not found.");
// now create a credit limit
env(trust(alice, gw["USD"](800)));
@@ -100,6 +103,8 @@ class TrustAndBalance_test : public beast::unit_test::Suite
// ensure line is deleted
jrr = ledgerEntryState(env, gw, alice, "USD");
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcEntryNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "Entry not found.");
// TODO Check in both owner books.
+3 -1
View File
@@ -3231,7 +3231,9 @@ public:
{
auto const info = env.rpc("json", "account_info", to_string(prevLedgerWithQueue));
BEAST_EXPECT(info.isMember(jss::result) && rpc::containsError(info[jss::result]));
BEAST_EXPECT(info[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Invalid parameters.");
}
env.close();
@@ -970,48 +970,6 @@ class InvariantsMPT_test : public InvariantsBase
});
}
// A PaymentChannelClaim may create only the submitter's (A1) MPToken.
{
STTx const claim{ttPAYCHAN_CLAIM, [](STObject&) {}};
MPTID id;
Preclose const precloseIssuance = [&](Account const&, Account const&, Env& env) {
Account const gw("gw");
env.fund(XRP(1'000), gw);
MPTTester const mpt({.env = env, .issuer = gw});
id = mpt.issuanceID();
return true;
};
auto const insertToken = [&](Account const& holder, ApplyContext& ac) {
auto const sleNew = std::make_shared<SLE>(keylet::mptoken(id, holder));
(*sleNew)[sfAccount] = holder.id();
(*sleNew)[sfMPTokenIssuanceID] = id;
ac.view().insert(sleNew);
return true;
};
doInvariantCheck(
{{"a MPToken was created"}},
[&](Account const&, Account const& a2, ApplyContext& ac) {
return insertToken(a2, ac);
},
XRPAmount{},
claim,
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
precloseIssuance,
TxAccount::A1);
doInvariantCheck(
{},
[&](Account const& a1, Account const&, ApplyContext& ac) {
return insertToken(a1, ac);
},
XRPAmount{},
claim,
{tesSUCCESS, tesSUCCESS},
precloseIssuance,
TxAccount::A1);
}
// LoanSet / VaultWithdraw MayAuthorizeMpt caps (fixCleanup3_4_0) and
// the LoanBrokerDelete cap (fixCleanup3_5_0): LoanSet allows at most
// two creates and no deletes; VaultWithdraw and LoanBrokerDelete allow
+2
View File
@@ -1154,6 +1154,8 @@ class LoanBroker_test : public beast::unit_test::Suite
auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
// fails in doSubmit() on STTx construction
BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
BEAST_EXPECT(!jrr.isMember(jss::error_code));
BEAST_EXPECT(jrr[jss::error_message].isNull());
BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
}
@@ -190,6 +190,8 @@ private:
BEAST_EXPECT(jr.isMember(jss::result));
auto const jResult = jr[jss::result];
BEAST_EXPECT(jResult[jss::error] == "invalidTransaction");
BEAST_EXPECT(!jResult.isMember(jss::error_code));
BEAST_EXPECT(jResult[jss::error_message].isNull());
BEAST_EXPECT(
jResult[jss::error_exception] ==
"fails local checks: Transaction has bad signature.");
+4 -6
View File
@@ -18,6 +18,7 @@
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/KeyType.h>
@@ -126,12 +127,9 @@ private:
return params;
}();
auto const jSignBorrower = env.rpc("json", "sign", to_string(borrowerSignParams));
BEAST_EXPECT(
jSignBorrower.isMember(jss::result) &&
jSignBorrower[jss::result].isMember(jss::error) &&
jSignBorrower[jss::result][jss::error] == "invalidParams" &&
jSignBorrower[jss::result].isMember(jss::error_message) &&
jSignBorrower[jss::result][jss::error_message] == target);
BEAST_EXPECT(jSignBorrower[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jSignBorrower[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jSignBorrower[jss::result][jss::error_message] == target);
}
}
{
+2 -1
View File
@@ -196,7 +196,8 @@ private:
jvResult[jss::tx_blob] = strHex(local.getSerializer().slice());
auto res = env.rpc("json", "submit", to_string(jvResult))["result"];
BEAST_EXPECT(
res[jss::error] == "invalidTransaction" &&
res[jss::error] == "invalidTransaction" && !res.isMember(jss::error_code) &&
res[jss::error_message].isNull() &&
res[jss::error_exception] ==
"fails local checks: Counterparty: Invalid signature.");
});
+35 -11
View File
@@ -172,7 +172,7 @@ private:
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");
checkError(jvVault[jss::result], "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
@@ -182,7 +182,7 @@ private:
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");
checkError(jvVault[jss::result], "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
@@ -191,7 +191,11 @@ private:
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");
checkError(
jvVault[jss::result],
"malformedRequest",
RpcInvalidParams,
"Invalid field 'vault', not hex string or object.");
}
{
@@ -201,7 +205,11 @@ private:
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");
checkError(
jvVault[jss::result],
"malformedOwner",
RpcInvalidParams,
"Invalid field 'owner', not AccountID.");
}
{
@@ -211,7 +219,11 @@ private:
jvParams[jss::vault][jss::owner] = issuer.human();
jvParams[jss::vault][jss::seq] = "foo";
auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
BEAST_EXPECT(jvVault[jss::result][jss::error].asString() == "malformedRequest");
checkError(
jvVault[jss::result],
"malformedRequest",
RpcInvalidParams,
"Invalid field 'seq', not number.");
}
{
@@ -221,7 +233,11 @@ private:
jvParams[jss::vault][jss::owner] = issuer.human();
jvParams[jss::vault][jss::seq] = -1;
auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
BEAST_EXPECT(jvVault[jss::result][jss::error].asString() == "malformedRequest");
checkError(
jvVault[jss::result],
"malformedRequest",
RpcInvalidParams,
"Invalid field 'seq', not number.");
}
{
@@ -231,7 +247,11 @@ private:
jvParams[jss::vault][jss::owner] = issuer.human();
jvParams[jss::vault][jss::seq] = 1e20;
auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
BEAST_EXPECT(jvVault[jss::result][jss::error].asString() == "malformedRequest");
checkError(
jvVault[jss::result],
"malformedRequest",
RpcInvalidParams,
"Invalid field 'seq', not number.");
}
{
@@ -241,7 +261,11 @@ private:
jvParams[jss::vault][jss::owner] = issuer.human();
jvParams[jss::vault][jss::seq] = true;
auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
BEAST_EXPECT(jvVault[jss::result][jss::error].asString() == "malformedRequest");
checkError(
jvVault[jss::result],
"malformedRequest",
RpcInvalidParams,
"Invalid field 'seq', not number.");
}
{
@@ -500,8 +524,8 @@ private:
{
testcase("RPC vault_info command line invalid index");
json::Value jv = env.rpc("vault_info", "foobar", "validated");
BEAST_EXPECT(jv[jss::error].asString() == "invalidParams");
json::Value const jv = env.rpc("vault_info", "foobar", "validated");
checkError(jv, "invalidParams", RpcInvalidParams, "Invalid parameters.");
}
{
@@ -519,7 +543,7 @@ private:
{
testcase("RPC vault_info command line invalid ledger");
json::Value jv = env.rpc("vault_info", strHex(keylet.key), "0");
BEAST_EXPECT(jv[jss::result][jss::error].asString() == "lgrNotFound");
checkError(jv[jss::result], "lgrNotFound", RpcLgrNotFound, "ledgerNotFound");
}
}
+1 -21
View File
@@ -1,8 +1,5 @@
#include <test/jtx/paychan.h>
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
@@ -15,13 +12,11 @@
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/PayChan.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
@@ -44,7 +39,6 @@ create(
{
json::Value jv;
jv[jss::TransactionType] = jss::PaymentChannelCreate;
jv[jss::Flags] = tfFullyCanonicalSig;
jv[jss::Account] = to_string(account);
jv[jss::Destination] = to_string(to);
jv[jss::Amount] = amount.getJson(JsonOptions::Values::None);
@@ -66,7 +60,6 @@ fund(
{
json::Value jv;
jv[jss::TransactionType] = jss::PaymentChannelFund;
jv[jss::Flags] = tfFullyCanonicalSig;
jv[jss::Account] = to_string(account);
jv[sfChannel.fieldName] = to_string(channel);
jv[jss::Amount] = amount.getJson(JsonOptions::Values::None);
@@ -86,7 +79,6 @@ claim(
{
json::Value jv;
jv[jss::TransactionType] = jss::PaymentChannelClaim;
jv[jss::Flags] = tfFullyCanonicalSig;
jv[jss::Account] = to_string(account);
jv["Channel"] = to_string(channel);
if (amount)
@@ -141,20 +133,8 @@ signClaimAuth(
STAmount const& authAmt)
{
Serializer msg;
serializePayChanAuthorization(msg, channel, authAmt);
serializePayChanAuthorization(msg, channel, authAmt.xrp());
return sign(pk, sk, msg.slice());
}
Rate
rate(Env const& env, Account const& account, Account const& dest, std::uint32_t const& seq)
{
auto const sle =
env.le(keylet::payChannel(account.id(), dest.id(), SeqProxy::rawSequence(seq)));
if (!sle)
return Rate{0};
if (sle->isFieldPresent(sfTransferRate))
return xrpl::Rate((*sle)[sfTransferRate]);
return kParityRate;
}
} // namespace xrpl::test::jtx::paychan
-5
View File
@@ -1,7 +1,6 @@
#pragma once
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
@@ -11,7 +10,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SecretKey.h>
@@ -87,7 +85,4 @@ signClaimAuth(
uint256 const& channel,
STAmount const& authAmt);
Rate
rate(Env const& env, Account const& account, Account const& dest, std::uint32_t const& seq);
} // namespace xrpl::test::jtx::paychan
-218
View File
@@ -1001,222 +1001,6 @@ public:
}
}
void
testIsExactSum()
{
testcase("is exact sum");
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
// Exact IOU sum
{
STAmount const amt1(usd, 500);
STAmount const amt2(usd, 1500);
BEAST_EXPECT(isExactSum(amt1, amt2) == true);
}
// Exact IOU sum with a zero operand
{
STAmount const amt1(usd, 0);
STAmount const amt2(usd, 15, -19);
BEAST_EXPECT(isExactSum(amt1, amt2) == true);
}
// Exact IOU sum whose exponent exceeds both operands'
{
STAmount const amt1(usd, std::uint64_t{9999999999999999});
STAmount const amt2(usd, 1);
BEAST_EXPECT(isExactSum(amt1, amt2) == true);
}
// IOU sum that drops the finer operand: 1.5e-18 + 1 rounds to 1, and
// 1 - 1.5e-18 rounds back to 1, so only the difference with the
// coarser operand exposes the loss
{
STAmount const dust(usd, 15, -19);
STAmount const one(usd, 1);
BEAST_EXPECT(isExactSum(dust, one) == false);
BEAST_EXPECT(isExactSum(one, dust) == false);
}
// IOU sum that rounds the smaller operand away entirely
{
STAmount const amt1(usd, 1, 15);
STAmount const amt2(usd, 1, -3);
BEAST_EXPECT(isExactSum(amt1, amt2) == false);
}
// IOU sum that rounds to a different nonzero increase
{
STAmount const amt1(usd, std::uint64_t{1234567890123456});
STAmount const amt2(usd, 600025, -2);
BEAST_EXPECT(isExactSum(amt1, amt2) == false);
}
// IOU sum past the largest IOU value: the sum throws, so it is not
// exact
{
STAmount const max(usd, STAmount::kMaxValue, STAmount::kMaxOffset);
BEAST_EXPECT(max.mantissa() == STAmount::kMaxValue);
BEAST_EXPECT(max.exponent() == STAmount::kMaxOffset);
try
{
auto const _ = max + max;
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == std::string("value overflow"));
}
BEAST_EXPECT(isExactSum(max, max) == false);
}
// Exact XRP sum
{
STAmount const amt1(XRPAmount(500));
STAmount const amt2(XRPAmount(1500));
BEAST_EXPECT(isExactSum(amt1, amt2) == true);
}
// XRP overflow
{
STAmount const amt1(std::numeric_limits<XRPAmount::value_type>::max());
STAmount const amt2(XRPAmount(1));
BEAST_EXPECT(isExactSum(amt1, amt2) == false);
}
// Exact MPT sum
{
STAmount const amt1(mpt, 500);
STAmount const amt2(mpt, 1500);
BEAST_EXPECT(isExactSum(amt1, amt2) == true);
}
// MPT overflow
{
STAmount const amt1(mpt, std::numeric_limits<MPTAmount::value_type>::max());
STAmount const amt2(mpt, 1);
BEAST_EXPECT(isExactSum(amt1, amt2) == false);
}
// Not comparable
{
STAmount const amt1(XRPAmount(1));
STAmount const amt2(usd, 1);
BEAST_EXPECT(isExactSum(amt1, amt2) == false);
}
}
void
testIsExactDifference()
{
testcase("is exact difference");
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
// Exact IOU difference
{
STAmount const amt1(usd, 1500);
STAmount const amt2(usd, 500);
BEAST_EXPECT(isExactDifference(amt1, amt2) == true);
}
// Exact IOU difference with a zero subtrahend
{
STAmount const amt1(usd, 15, -19);
STAmount const amt2(usd, 0);
BEAST_EXPECT(isExactDifference(amt1, amt2) == true);
}
// Exact IOU difference of equal operands
{
STAmount const amt1(usd, 4000);
BEAST_EXPECT(isExactDifference(amt1, amt1) == true);
}
// IOU difference that rounds to a different nonzero decrease
{
STAmount const amt1(usd, std::uint64_t{1234567890123456});
STAmount const amt2(usd, 600025, -2);
BEAST_EXPECT(isExactDifference(amt1, amt2) == false);
}
// IOU difference that drops a dust subtrahend: 1 - 1.5e-18 rounds to
// 1 and 1 + 1.5e-18 rounds back to 1, so only the minuend minus the
// difference exposes the loss
{
STAmount const amt1(usd, 1);
STAmount const amt2(usd, 15, -19);
BEAST_EXPECT(isExactDifference(amt1, amt2) == false);
}
// IOU difference that drops a dust minuend: 1.5e-18 - 1 rounds to -1
// and 1.5e-18 - (-1) rounds back to 1, so only the difference plus
// the subtrahend exposes the loss
{
STAmount const amt1(usd, 15, -19);
STAmount const amt2(usd, 1);
BEAST_EXPECT(isExactDifference(amt1, amt2) == false);
}
// IOU difference whose check overflows: max - 1.5e80 rounds half to
// even to 9999999999999998e80, and adding 1.5e80 back rounds past the
// largest IOU value, so the difference is not exact
{
STAmount const max(usd, STAmount::kMaxValue, STAmount::kMaxOffset);
STAmount const claw(usd, 15, 79);
STAmount const diff = max - claw;
BEAST_EXPECT(diff == STAmount(usd, std::uint64_t{9999999999999998}, 80));
try
{
auto const _ = diff + claw;
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == std::string("value overflow"));
}
BEAST_EXPECT(isExactDifference(max, claw) == false);
}
// Exact XRP difference
{
STAmount const amt1(XRPAmount(1500));
STAmount const amt2(XRPAmount(500));
BEAST_EXPECT(isExactDifference(amt1, amt2) == true);
}
// XRP underflow
{
STAmount const amt1(XRPAmount(1));
STAmount const amt2(XRPAmount(2));
BEAST_EXPECT(isExactDifference(amt1, amt2) == false);
}
// Exact MPT difference
{
STAmount const amt1(mpt, 1500);
STAmount const amt2(mpt, 500);
BEAST_EXPECT(isExactDifference(amt1, amt2) == true);
}
// MPT underflow
{
STAmount const amt1(mpt, 1);
STAmount const amt2(mpt, 2);
BEAST_EXPECT(isExactDifference(amt1, amt2) == false);
}
// Not comparable
{
STAmount const amt1(XRPAmount(1));
STAmount const amt2(usd, 1);
BEAST_EXPECT(isExactDifference(amt1, amt2) == false);
}
}
void
testMPTRateRounding()
{
@@ -1793,8 +1577,6 @@ public:
testCanAddXRP();
testCanAddIOU();
testCanAddMPT();
testIsExactSum();
testIsExactDifference();
testMPTRateRounding();
testMPTOperandIOUResult();
testMPTGetRate();
+3
View File
@@ -12,6 +12,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
@@ -180,6 +181,7 @@ public:
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Field 'tx_json.Asset' has invalid data.");
}
@@ -253,6 +255,7 @@ public:
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Field 'tx_json.Asset' has invalid data.");
}
+29 -1
View File
@@ -8,6 +8,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/TxFlags.h>
@@ -56,12 +57,16 @@ public:
Account const gw("gw");
auto const usd = gw["USD"];
auto const jv = ammAlice.ammRpcInfo({}, {}, usd, usd);
BEAST_EXPECT(jv[jss::error] == "actNotFound");
BEAST_EXPECT(jv[jss::error_code] == RpcActNotFound);
BEAST_EXPECT(jv[jss::error_message] == "Account not found.");
});
// Invalid LP account id
testAMM([&](AMM& ammAlice, Env&) {
auto const jv = ammAlice.ammRpcInfo(bogie.id());
BEAST_EXPECT(jv[jss::error] == "actMalformed");
BEAST_EXPECT(jv[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jv[jss::error_message] == "Account malformed.");
});
@@ -69,6 +74,8 @@ public:
testAMM([&](AMM& ammAlice, Env&) {
auto const jv =
ammAlice.ammRpcInfo(json::Value{42}, std::nullopt, XRP, USD, std::nullopt, true, 3);
BEAST_EXPECT(jv[jss::error] == "actMalformed");
BEAST_EXPECT(jv[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jv[jss::error_message] == "Account malformed.");
});
@@ -82,6 +89,8 @@ public:
json::Value{42},
false,
3);
BEAST_EXPECT(jv[jss::error] == "actMalformed");
BEAST_EXPECT(jv[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jv[jss::error_message] == "Account malformed.");
});
@@ -106,6 +115,8 @@ public:
iss2,
accountId(ammAlice, acct),
ignoreParams);
BEAST_EXPECT(jv[jss::error] == "invalidParams");
BEAST_EXPECT(jv[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters.");
}
});
@@ -121,6 +132,8 @@ public:
iss2,
accountId(ammAlice, acct),
ignoreParams);
BEAST_EXPECT(jv[jss::error] == "invalidParams");
BEAST_EXPECT(jv[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters.");
}
});
@@ -137,6 +150,8 @@ public:
accountId(ammAlice, acct),
ignoreParams,
3);
BEAST_EXPECT(jv[jss::error] == "actMalformed");
BEAST_EXPECT(jv[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jv[jss::error_message] == "Account malformed.");
}
});
@@ -145,6 +160,8 @@ public:
testAMM([&](AMM& ammAlice, Env&) {
auto const jv = ammAlice.ammRpcInfo(
std::nullopt, std::nullopt, std::nullopt, std::nullopt, bogie.id());
BEAST_EXPECT(jv[jss::error] == "actMalformed");
BEAST_EXPECT(jv[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jv[jss::error_message] == "Account malformed.");
});
@@ -169,6 +186,8 @@ public:
iss2,
accountId(ammAlice, acct),
ignoreParams);
BEAST_EXPECT(jv[jss::error] == "invalidParams");
BEAST_EXPECT(jv[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters.");
}
});
@@ -185,6 +204,13 @@ public:
accountId(ammAlice, acct),
ignoreParams,
3);
BEAST_EXPECT(
jv[jss::error] ==
(acct == TestAccount::Bogie ? std::string("actMalformed")
: std::string("invalidParams")));
BEAST_EXPECT(
jv[jss::error_code] ==
(acct == TestAccount::Bogie ? RpcActMalformed : RpcInvalidParams));
BEAST_EXPECT(
jv[jss::error_message] ==
(acct == TestAccount::Bogie ? std::string("Account malformed.")
@@ -383,7 +409,9 @@ public:
testAMM([&](AMM& amm, Env&) {
auto const resp = amm.ammRpcInfo(
std::nullopt, jss::validated.cStr(), std::nullopt, std::nullopt, gw_);
BEAST_EXPECT(resp.isMember("error") && resp["error"] == "actNotFound");
BEAST_EXPECT(resp[jss::error] == "actNotFound");
BEAST_EXPECT(resp[jss::error_code] == RpcActNotFound);
BEAST_EXPECT(resp[jss::error_message] == "Account not found.");
});
}
+5
View File
@@ -35,6 +35,7 @@ public:
{
// account_info with no account.
auto const info = env.rpc("json", "account_info", "{ }");
BEAST_EXPECT(info[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Missing field 'account'.");
}
@@ -45,6 +46,7 @@ public:
"account_info",
"{\"account\": "
"\"n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV\"}");
BEAST_EXPECT(info[jss::result][jss::error] == "actMalformed");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Account malformed.");
BEAST_EXPECT(info[jss::result].isMember(jss::ledger_current_index));
@@ -56,6 +58,7 @@ public:
json::Value params;
params[jss::account] = bogie.human();
auto const info = env.rpc("json", "account_info", to_string(params));
BEAST_EXPECT(info[jss::result][jss::error] == "actNotFound");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcActNotFound);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Account not found.");
BEAST_EXPECT(info[jss::result][jss::account] == bogie.human());
@@ -65,6 +68,7 @@ public:
{
// Cannot use a seed as account
auto const info = env.rpc("json", "account_info", R"JSON({"account": "foo"})JSON");
BEAST_EXPECT(info[jss::result][jss::error] == "actMalformed");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Account malformed.");
}
@@ -334,6 +338,7 @@ public:
BEAST_EXPECT(info[jss::status] == "error");
BEAST_EXPECT(info[jss::error] == "invalidParams");
BEAST_EXPECT(info[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(info[jss::error_message] == "Invalid parameters.");
BEAST_EXPECT(info.isMember(jss::account_data));
BEAST_EXPECT(info[jss::account_data][sfAccount.jsonName] == alice.human());
BEAST_EXPECT(info.isMember(jss::ledger_current_index));
+26 -1
View File
@@ -55,6 +55,7 @@ public:
{
// account_lines with no account.
auto const lines = env.rpc("json", "account_lines", "{ }");
BEAST_EXPECT(lines[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -65,6 +66,7 @@ public:
json::Value params;
params[jss::account] = "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "actMalformed");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -118,6 +120,7 @@ public:
json::Value params;
params[jss::account] = alice.human();
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "actNotFound");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcActNotFound);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -161,6 +164,7 @@ public:
params[jss::account] = alice.human();
params[jss::ledger_index] = "nonsense";
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -172,6 +176,7 @@ public:
params[jss::account] = alice.human();
params[jss::ledger_index] = 50000;
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "lgrNotFound");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(lines[jss::result][jss::error_message] == "ledgerNotFound");
}
@@ -305,6 +310,7 @@ public:
params[jss::account] = alice.human();
params[jss::peer] = "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "actMalformed");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -344,6 +350,7 @@ public:
params[jss::account] = alice.human();
params[jss::limit] = -1;
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -382,6 +389,7 @@ public:
paramsD[jss::account] = alice.human();
paramsD[jss::marker] = marker;
auto const linesD = env.rpc("json", "account_lines", to_string(paramsD));
BEAST_EXPECT(linesD[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(linesD[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
linesD[jss::result][jss::error_message] ==
@@ -393,6 +401,7 @@ public:
params[jss::account] = alice.human();
params[jss::marker] = true;
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -404,6 +413,7 @@ public:
params[jss::account] = alice.human();
params[jss::marker] = "NOT_A_MARKER";
auto const lines = env.rpc("json", "account_lines", to_string(params));
BEAST_EXPECT(lines[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::result][jss::error_message] ==
@@ -548,7 +558,11 @@ public:
beckyLinesParams[jss::account] = becky.human();
beckyLinesParams[jss::marker] = aliceMarker;
auto const beckyLines = env.rpc("json", "account_lines", to_string(beckyLinesParams));
BEAST_EXPECT(beckyLines[jss::result].isMember(jss::error_message));
BEAST_EXPECT(beckyLines[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(beckyLines[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
beckyLines[jss::result][jss::error_message] ==
rpc::makeError(RpcInvalidParams)[jss::error_message]);
}
void
@@ -616,6 +630,7 @@ public:
linesEndParams[jss::account] = alice.human();
linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
auto const linesEnd = env.rpc("json", "account_lines", to_string(linesEndParams));
BEAST_EXPECT(linesEnd[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(linesEnd[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
linesEnd[jss::result][jss::error_message] ==
@@ -914,6 +929,7 @@ public:
request[jss::ripplerpc] = "2.0";
request[jss::id] = 5;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::error][jss::message] ==
@@ -933,6 +949,7 @@ public:
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "actMalformed");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(
lines[jss::error][jss::message] ==
@@ -953,6 +970,7 @@ public:
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "actNotFound");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcActNotFound);
BEAST_EXPECT(
lines[jss::error][jss::message] ==
@@ -1025,6 +1043,7 @@ public:
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::error][jss::message] ==
@@ -1045,6 +1064,7 @@ public:
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "lgrNotFound");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(lines[jss::error][jss::message] == "ledgerNotFound");
BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
@@ -1220,6 +1240,7 @@ public:
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "actMalformed");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(
lines[jss::error][jss::message] ==
@@ -1240,6 +1261,7 @@ public:
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::error][jss::message] ==
@@ -1314,6 +1336,7 @@ public:
requestD[jss::id] = 5;
requestD[jss::params] = paramsD;
auto const linesD = env.rpc("json2", to_string(requestD));
BEAST_EXPECT(linesD[jss::error][jss::error] == "invalidParams");
BEAST_EXPECT(linesD[jss::error][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
linesD[jss::error][jss::message] ==
@@ -1334,6 +1357,7 @@ public:
request[jss::id] = 5;
request[jss::params] = params;
auto const lines = env.rpc("json2", to_string(request));
BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::error][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::error][jss::message] ==
@@ -1492,6 +1516,7 @@ public:
linesEndRequest[jss::id] = 5;
linesEndRequest[jss::params] = linesEndParams;
auto const linesEnd = env.rpc("json2", to_string(linesEndRequest));
BEAST_EXPECT(linesEnd[jss::error][jss::error] == "invalidParams");
BEAST_EXPECT(linesEnd[jss::error][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
linesEnd[jss::error][jss::message] ==
+37 -3
View File
@@ -140,6 +140,8 @@ public:
{
json::Value const params;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Missing field 'account'.");
}
// test account non-string
@@ -149,6 +151,7 @@ public:
params[jss::account] = param;
auto jrr = env.rpc("json", "account_objects", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
};
@@ -164,6 +167,8 @@ public:
json::Value params;
params[jss::account] = "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV";
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "actMalformed");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Account malformed.");
}
// test error on account that's not in the ledger.
@@ -171,6 +176,8 @@ public:
json::Value params;
params[jss::account] = Account{"bogie"}.human();
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "actNotFound");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcActNotFound);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Account not found.");
}
Account const bob{"bob"};
@@ -180,6 +187,8 @@ public:
params[jss::account] = bob.human();
params[jss::ledger_index] = 10;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "lgrNotFound");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "ledgerNotFound");
}
@@ -190,6 +199,8 @@ public:
params[jss::account] = bob.human();
params[jss::type] = 10;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Invalid field 'type', not string.");
}
@@ -199,6 +210,8 @@ public:
params[jss::account] = bob.human();
params[jss::type] = "expedited";
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'type'.");
}
// test error on limit -ve
@@ -207,6 +220,8 @@ public:
params[jss::account] = bob.human();
params[jss::limit] = -1;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Invalid field 'limit', not unsigned integer.");
@@ -217,6 +232,8 @@ public:
params[jss::account] = bob.human();
params[jss::sponsored] = "true";
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Invalid field 'sponsored', not boolean.");
}
@@ -238,27 +255,39 @@ public:
std::string mark = to_string(resumeMarker);
params[jss::marker] = 10;
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Invalid field 'marker', not string.");
params[jss::marker] = "This is a string with no comma";
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = "This string has a comma, but is not hex";
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = std::string(&mark[1U], 64);
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = std::string(&mark[1U], 65);
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
params[jss::marker] = std::string(&mark[1U], 65) + "not hex";
resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
// Should this be an error?
@@ -603,7 +632,8 @@ public:
// Make a lambda that checks if the response has error for invalid type
auto const acctObjsTypeIsInvalid = [](json::Value const& resp) {
return resp[jss::result].isMember(jss::error) &&
return resp[jss::result][jss::error] == "invalidParams" &&
resp[jss::result][jss::error_code] == RpcInvalidParams &&
resp[jss::result][jss::error_message] == "Invalid field \'type\'.";
};
@@ -1174,7 +1204,9 @@ public:
params[jss::ledger_index] = jss::validated;
params[jss::marker] = marker;
json::Value const resp = env.rpc("json", "account_nfts", to_string(params));
return resp[jss::result][jss::error_message] == errorMessage;
return resp[jss::result][jss::error] == "invalidParams" &&
resp[jss::result][jss::error_code] == RpcInvalidParams &&
resp[jss::result][jss::error_message] == errorMessage;
};
// test an invalid marker that is not a string
@@ -1427,7 +1459,9 @@ public:
params[jss::ledger_index] = jss::validated;
params[jss::marker] = marker;
json::Value const resp = env.rpc("json", "account_objects", to_string(params));
return resp[jss::result][jss::error_message] == "Invalid field \'marker\'.";
return resp[jss::result][jss::error] == "invalidParams" &&
resp[jss::result][jss::error_code] == RpcInvalidParams &&
resp[jss::result][jss::error_message] == "Invalid field \'marker\'.";
};
auto const markerStr = marker.asString();
+10 -8
View File
@@ -173,7 +173,9 @@ public:
jvParams[jss::limit] = 0u;
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr.isMember(jss::error_message));
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit'.");
}
}
@@ -195,9 +197,9 @@ public:
{
// no account field
auto const jrr = env.rpc("account_offers");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "badSyntax");
BEAST_EXPECT(jrr[jss::error_code] == RpcBadSyntax);
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Syntax error.");
}
@@ -226,9 +228,9 @@ public:
jvParams[jss::account] = "";
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "actMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Account malformed.");
BEAST_EXPECT(jrr.isMember(jss::ledger_current_index));
BEAST_EXPECT(jrr[jss::validated] == false);
@@ -237,9 +239,9 @@ public:
{
// bogus account value
auto const jrr = env.rpc("account_offers", Account("bogus").human())[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "actNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcActNotFound);
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Account not found.");
}
@@ -250,9 +252,9 @@ public:
jvParams[jss::limit] = "0"; // NOT an integer
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not unsigned integer.");
}
@@ -263,9 +265,9 @@ public:
jvParams[jss::marker] = "NOT_A_MARKER";
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECTS(
jrr[jss::error_message] == "Invalid field 'marker'.", jrr.toStyledString());
}
@@ -277,9 +279,9 @@ public:
jvParams[jss::marker] = 1;
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not string.");
}
@@ -290,9 +292,9 @@ public:
jvParams[jss::ledger_index] = 10u;
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
+129 -62
View File
@@ -195,19 +195,27 @@ class AccountTx_test : public beast::unit_test::Suite
(j[jss::result][jss::transactions].size() == 0);
};
auto const isErr = [](json::Value const& j, ErrorCodeI code) {
return j.isMember(jss::result) && j[jss::result].isMember(jss::error) &&
j[jss::result][jss::error] == rpc::getErrorInfo(code).token;
auto const isErr = [](json::Value const& j, ErrorCodeI code, std::string const& message) {
return j.isMember(jss::result) &&
j[jss::result][jss::error] == rpc::getErrorInfo(code).token &&
j[jss::result][jss::error_code] == code &&
j[jss::result][jss::error_message] == message;
};
json::Value jParams;
jParams[jss::api_version] = apiVersion;
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(jParams)), RpcInvalidParams));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(jParams)),
RpcInvalidParams,
"Missing field 'account'."));
jParams[jss::account] = "0xDEADBEEF";
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(jParams)), RpcActMalformed));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(jParams)),
RpcActMalformed,
"Account malformed."));
jParams[jss::account] = a1.human();
BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(jParams))));
@@ -227,8 +235,10 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(
isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrIdxMalformed,
"Ledger index malformed."));
}
p[jss::ledger_index_min] = 1;
@@ -239,15 +249,18 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(
isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrIdxMalformed,
"Ledger index malformed."));
}
p[jss::ledger_index_min] = 2;
p[jss::ledger_index_max] = 1;
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
(apiVersion == 1 ? RpcLgrIdxsInvalid : RpcInvalidLgrRange)));
(apiVersion == 1 ? RpcLgrIdxsInvalid : RpcInvalidLgrRange),
(apiVersion == 1 ? "Ledger indexes invalid." : "Ledger range is invalid.")));
}
// Ledger index min only
{
@@ -262,14 +275,17 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(
isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrIdxMalformed,
"Ledger index malformed."));
}
p[jss::ledger_index_min] = env.current()->header().seq;
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
(apiVersion == 1 ? RpcLgrIdxsInvalid : RpcInvalidLgrRange)));
(apiVersion == 1 ? RpcLgrIdxsInvalid : RpcInvalidLgrRange),
(apiVersion == 1 ? "Ledger indexes invalid." : "Ledger range is invalid.")));
}
// Ledger index max only
@@ -285,8 +301,10 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(
isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrIdxMalformed,
"Ledger index malformed."));
}
p[jss::ledger_index_max] = 3;
@@ -310,10 +328,14 @@ class AccountTx_test : public beast::unit_test::Suite
BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
p[jss::ledger_index] = env.current()->header().seq;
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrNotValidated,
"Ledger not validated."));
p[jss::ledger_index] = env.current()->header().seq + 1;
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotFound));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)), RpcLgrNotFound, "ledgerNotFound"));
}
{
@@ -326,10 +348,16 @@ class AccountTx_test : public beast::unit_test::Suite
BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
p[jss::ledger_index] = "current";
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrNotValidated,
"Ledger not validated."));
p[jss::ledger_index] = "";
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrNotValidated,
"Ledger not validated."));
}
// Ledger Hash
@@ -360,7 +388,10 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
"invalidParams"));
}
}
@@ -374,8 +405,10 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(
isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcLgrIdxMalformed,
"Ledger index malformed."));
}
}
// test account non-string
@@ -385,6 +418,7 @@ class AccountTx_test : public beast::unit_test::Suite
params[jss::account] = param;
auto jrr = env.rpc("json", "account_tx", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
};
@@ -406,7 +440,10 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
"Invalid field 'binary'."));
}
p[jss::binary] = true;
@@ -420,7 +457,10 @@ class AccountTx_test : public beast::unit_test::Suite
}
else
{
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
"Invalid field 'forward'."));
}
p[jss::forward] = false;
@@ -433,70 +473,83 @@ class AccountTx_test : public beast::unit_test::Suite
// Test case: limit = 0 should fail (below minimum)
p[jss::limit] = 0;
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
"Invalid field 'limit'."));
// Test case: limit = 1.2 should fail (not an integer)
p[jss::limit] = 1.2;
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = "10" should fail (string instead of integer)
p[jss::limit] = "10";
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = true should fail (boolean instead of integer)
p[jss::limit] = true;
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = false should fail (boolean instead of integer)
p[jss::limit] = false;
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = -1 should fail (negative number)
p[jss::limit] = -1;
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = [] should fail (array instead of integer)
p[jss::limit] = json::Value(json::ValueType::Array);
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = {} should fail (object instead of integer)
p[jss::limit] = json::Value(json::ValueType::Object);
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = "malformed" should fail (malformed string)
p[jss::limit] = "malformed";
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = ["limit"] should fail (array with string)
p[jss::limit] = json::Value(json::ValueType::Array);
p[jss::limit].append("limit");
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = {"limit": 10} should fail (object with
// property)
p[jss::limit] = json::Value(json::ValueType::Object);
p[jss::limit][jss::limit] = 10;
BEAST_EXPECT(
env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
BEAST_EXPECT(isErr(
env.rpc("json", "account_tx", to_string(p)),
RpcInvalidParams,
rpc::expectedFieldMessage(jss::limit, "unsigned integer")));
// Test case: limit = 10 should succeed (valid integer)
p[jss::limit] = 10;
@@ -985,12 +1038,16 @@ class AccountTx_test : public beast::unit_test::Suite
};
auto const checkError = [&](json::Value const& delegateParams,
std::string const& errToken) {
std::string const& errToken,
ErrorCodeI code,
std::string const& errMessage) {
json::Value params;
params[jss::account] = alice.human();
params[jss::delegate] = delegateParams;
auto res = env.rpc("json", "account_tx", to_string(params));
BEAST_EXPECT(res[jss::result][jss::error] == errToken);
BEAST_EXPECT(res[jss::result][jss::error_code] == code);
BEAST_EXPECT(res[jss::result][jss::error_message] == errMessage);
};
// Filter: Delegatee. Expects TX #2 (Signed by Bob)
@@ -1061,27 +1118,27 @@ class AccountTx_test : public beast::unit_test::Suite
// "delegate" is not an object (e.g., string)
{
json::Value const p = "not_an_object";
checkError(p, "invalidParams");
checkError(p, "invalidParams", RpcInvalidParams, "Invalid field 'delegate'.");
}
// Missing "delegate_filter" inside object
{
json::Value const p(json::ValueType::Object);
checkError(p, "invalidParams");
checkError(p, "invalidParams", RpcInvalidParams, "Invalid field 'delegate_filter'.");
}
// "delegate_filter" is not a string (e.g., int)
{
json::Value p;
p[jss::delegate_filter] = 123;
checkError(p, "invalidParams");
checkError(p, "invalidParams", RpcInvalidParams, "Invalid field 'delegate_filter'.");
}
// "delegate_filter" has invalid value
{
json::Value p;
p[jss::delegate_filter] = "random_string";
checkError(p, "invalidParams");
checkError(p, "invalidParams", RpcInvalidParams, "Invalid field 'delegate_filter'.");
}
// "counterparty" is not a string
@@ -1089,7 +1146,7 @@ class AccountTx_test : public beast::unit_test::Suite
json::Value p;
p[jss::delegate_filter] = "actor";
p[jss::counter_party] = 123;
checkError(p, "invalidParams");
checkError(p, "invalidParams", RpcInvalidParams, "Invalid field 'counter_party'.");
}
// "counterparty" is malformed base58
@@ -1097,7 +1154,7 @@ class AccountTx_test : public beast::unit_test::Suite
json::Value p;
p[jss::delegate_filter] = "actor";
p[jss::counter_party] = "not_an_account";
checkError(p, "actMalformed");
checkError(p, "actMalformed", RpcActMalformed, "Account malformed.");
}
// Multi-signed non-delegated TX: Alice pays Carol via multi-sig.
@@ -1181,6 +1238,11 @@ class AccountTx_test : public beast::unit_test::Suite
p[jss::marker] = delegateMarker;
auto const r = env.rpc("json", "account_tx", to_string(p));
BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(r[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
r[jss::result][jss::error_message] ==
"Do not mix delegate and non-delegate pagination markers in account_tx; "
"repeat the same `delegate` object when using a delegate marker.");
}
// Obtain a non-delegate marker; it must not carry the flag.
@@ -1205,6 +1267,11 @@ class AccountTx_test : public beast::unit_test::Suite
p[jss::marker] = normalMarker;
auto const r = env.rpc("json", "account_tx", to_string(p));
BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(r[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
r[jss::result][jss::error_message] ==
"Do not mix delegate and non-delegate pagination markers in account_tx; "
"repeat the same `delegate` object when using a delegate marker.");
}
}
}
+19 -6
View File
@@ -14,6 +14,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/config/Constants.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/jss.h>
@@ -186,32 +187,42 @@ class AmendmentBlocked_test : public beast::unit_test::Suite
// ledger_accept
jr = env.rpc("ledger_accept")[jss::result];
BEAST_EXPECT(jr.isMember(jss::error) && jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::status] == "error");
BEAST_EXPECT(jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::error_code] == RpcAmendmentBlocked);
BEAST_EXPECT(jr[jss::error_message] == "Amendment blocked, need upgrade.");
BEAST_EXPECT(!jr.isMember(jss::warnings));
// ledger_current
jr = env.rpc("ledger_current")[jss::result];
BEAST_EXPECT(jr.isMember(jss::error) && jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::status] == "error");
BEAST_EXPECT(jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::error_code] == RpcAmendmentBlocked);
BEAST_EXPECT(jr[jss::error_message] == "Amendment blocked, need upgrade.");
BEAST_EXPECT(!jr.isMember(jss::warnings));
// owner_info
jr = env.rpc("owner_info", alice.human())[jss::result];
BEAST_EXPECT(jr.isMember(jss::error) && jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::status] == "error");
BEAST_EXPECT(jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::error_code] == RpcAmendmentBlocked);
BEAST_EXPECT(jr[jss::error_message] == "Amendment blocked, need upgrade.");
BEAST_EXPECT(!jr.isMember(jss::warnings));
// path_find
jr = wsc->invoke("path_find", pfReq)[jss::result];
BEAST_EXPECT(jr.isMember(jss::error) && jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::status] == "error");
BEAST_EXPECT(jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::error_code] == RpcAmendmentBlocked);
BEAST_EXPECT(jr[jss::error_message] == "Amendment blocked, need upgrade.");
BEAST_EXPECT(!jr.isMember(jss::warnings));
// submit
jr = env.rpc("submit", strHex(s.slice()))[jss::result];
BEAST_EXPECT(jr.isMember(jss::error) && jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::status] == "error");
BEAST_EXPECT(jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::error_code] == RpcAmendmentBlocked);
BEAST_EXPECT(jr[jss::error_message] == "Amendment blocked, need upgrade.");
BEAST_EXPECT(!jr.isMember(jss::warnings));
// submit_multisigned
@@ -221,7 +232,9 @@ class AmendmentBlocked_test : public beast::unit_test::Suite
BEAST_EXPECT(jr[jss::status] == "success");
msReq[jss::tx_json] = jr[jss::tx_json];
jr = env.rpc("json", "submit_multisigned", to_string(msReq))[jss::result];
BEAST_EXPECT(jr.isMember(jss::error) && jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::error] == "amendmentBlocked");
BEAST_EXPECT(jr[jss::error_code] == RpcAmendmentBlocked);
BEAST_EXPECT(jr[jss::error_message] == "Amendment blocked, need upgrade.");
BEAST_EXPECT(!jr.isMember(jss::warnings));
}
+7 -2
View File
@@ -19,6 +19,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/Ledger.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
@@ -77,8 +78,12 @@ public:
// non-conventional ledger input should throw an error
params["ledger"] = "non_conventional_ledger_input";
resp = env.rpc("json", "book_changes", to_string(params));
BEAST_EXPECT(resp[jss::result].isMember(jss::error));
BEAST_EXPECT(resp[jss::result][jss::status] != "success");
BEAST_EXPECT(resp[jss::result][jss::status] == "error");
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Invalid field 'ledger', not string or number.");
}
void
+28
View File
@@ -20,6 +20,7 @@
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -1225,6 +1226,7 @@ public:
jvParams[jss::ledger_index] = 10u;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
@@ -1233,6 +1235,7 @@ public:
jvParams[jss::ledger_index] = "validated";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Missing field 'taker_pays'.");
}
@@ -1242,6 +1245,7 @@ public:
jvParams[jss::taker_pays] = json::ValueType::Object;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Missing field 'taker_gets'.");
}
@@ -1252,6 +1256,7 @@ public:
jvParams[jss::taker_gets] = json::ValueType::Object;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'taker_pays', not object.");
}
@@ -1262,6 +1267,7 @@ public:
jvParams[jss::taker_gets] = "not an object";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'taker_gets', not object.");
}
@@ -1272,6 +1278,7 @@ public:
jvParams[jss::taker_gets] = json::ValueType::Object;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Missing field 'taker_pays.currency'.");
}
@@ -1282,6 +1289,7 @@ public:
jvParams[jss::taker_gets] = json::ValueType::Object;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_pays.currency', not string.");
}
@@ -1293,6 +1301,7 @@ public:
jvParams[jss::taker_gets] = json::ValueType::Object;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Missing field 'taker_gets.currency'.");
}
@@ -1303,6 +1312,7 @@ public:
jvParams[jss::taker_gets][jss::currency] = 1;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_gets.currency', not string.");
}
@@ -1314,6 +1324,7 @@ public:
jvParams[jss::taker_gets][jss::currency] = "XRP";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcCurMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcCurMalformed);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_pays.currency', bad currency.");
}
@@ -1325,6 +1336,7 @@ public:
jvParams[jss::taker_gets][jss::currency] = "NOT_VALID";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstAmtMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcDstAmtMalformed);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_gets.currency', bad currency.");
}
@@ -1337,6 +1349,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = 1;
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_gets.issuer', not string.");
}
@@ -1349,6 +1362,7 @@ public:
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_pays.issuer', not string.");
}
@@ -1361,6 +1375,7 @@ public:
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_pays.issuer', bad issuer.");
}
@@ -1373,6 +1388,7 @@ public:
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] ==
"Invalid field 'taker_pays.issuer', bad issuer account one.");
@@ -1386,6 +1402,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human() + "DEAD";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcDstIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'taker_gets.issuer', bad issuer.");
}
@@ -1398,6 +1415,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = toBase58(noAccount());
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcDstIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] ==
"Invalid field 'taker_gets.issuer', bad issuer account one.");
@@ -1412,6 +1430,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] ==
"Unneeded field 'taker_pays.issuer' "
@@ -1427,6 +1446,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] ==
"Invalid field 'taker_pays.issuer', expected non-XRP issuer.");
@@ -1441,6 +1461,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'taker', not string.");
}
@@ -1453,6 +1474,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'taker'.");
}
@@ -1466,6 +1488,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "badMarket");
BEAST_EXPECT(jrr[jss::error_code] == RpcBadMarket);
BEAST_EXPECT(jrr[jss::error_message] == "No such market.");
}
@@ -1479,6 +1502,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not unsigned integer.");
}
@@ -1492,6 +1516,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit'.");
}
@@ -1503,6 +1528,7 @@ public:
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcDstIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] ==
"Invalid field 'taker_gets.issuer', "
@@ -1518,6 +1544,7 @@ public:
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcDstIsrMalformed);
BEAST_EXPECT(
jrr[jss::error_message] ==
"Unneeded field 'taker_gets.issuer' "
@@ -1533,6 +1560,7 @@ public:
jvParams[jss::domain] = "badString";
auto const jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "domainMalformed");
BEAST_EXPECT(jrr[jss::error_code] == RpcDomainMalformed);
BEAST_EXPECT(jrr[jss::error_message] == "Unable to parse domain.");
}
}
+2 -1
View File
@@ -2,6 +2,7 @@
#include <test/jtx/Env.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
namespace xrpl {
@@ -22,8 +23,8 @@ class Connect_test : public beast::unit_test::Suite
auto const result = env.rpc("json", "connect", "{}");
BEAST_EXPECT(result[jss::result][jss::status] == "error");
BEAST_EXPECT(result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result][jss::error] == "notSynced");
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcNotSynced);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Not synced to the network.");
}
}
+133 -29
View File
@@ -8,6 +8,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Protocol.h> // IWYU pragma: keep
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TxFlags.h>
@@ -154,12 +155,20 @@ public:
Account const becky{"becky"};
// Lambda that checks the (error) result of deposit_authorized.
auto const verifyErr =
[this](json::Value const& result, char const* error, char const* errorMsg) {
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == error);
BEAST_EXPECT(result[jss::result][jss::error_message] == errorMsg);
};
auto const verifyErr = [this](
json::Value const& result,
char const* error,
ErrorCodeI code,
char const* errorMsg,
bool withLedgerFields = false) {
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == error);
BEAST_EXPECT(result[jss::result][jss::error_code] == code);
BEAST_EXPECT(result[jss::result][jss::error_message] == errorMsg);
BEAST_EXPECT(
result[jss::result].isMember(jss::ledger_current_index) == withLedgerFields);
BEAST_EXPECT(result[jss::result].isMember(jss::validated) == withLedgerFields);
};
Env env(*this);
{
@@ -167,28 +176,33 @@ public:
json::Value args{depositAuthArgs(alice, becky)};
args.removeMember(jss::source_account);
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "invalidParams", "Missing field 'source_account'.");
verifyErr(result, "invalidParams", RpcInvalidParams, "Missing field 'source_account'.");
}
{
// Non-string source_account field.
json::Value args{depositAuthArgs(alice, becky)};
args[jss::source_account] = 7.3;
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "invalidParams", "Invalid field 'source_account', not a string.");
verifyErr(
result,
"invalidParams",
RpcInvalidParams,
"Invalid field 'source_account', not a string.");
}
{
// Corrupt source_account field.
json::Value args{depositAuthArgs(alice, becky)};
args[jss::source_account] = "rG1QQv2nh2gr7RCZ!P8YYcBUKCCN633jCn";
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "actMalformed", "Account malformed.");
verifyErr(result, "actMalformed", RpcActMalformed, "Account malformed.");
}
{
// Missing destination_account field.
json::Value args{depositAuthArgs(alice, becky)};
args.removeMember(jss::destination_account);
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "invalidParams", "Missing field 'destination_account'.");
verifyErr(
result, "invalidParams", RpcInvalidParams, "Missing field 'destination_account'.");
}
{
// Non-string destination_account field.
@@ -196,40 +210,47 @@ public:
args[jss::destination_account] = 7.3;
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(
result, "invalidParams", "Invalid field 'destination_account', not a string.");
result,
"invalidParams",
RpcInvalidParams,
"Invalid field 'destination_account', not a string.");
}
{
// Corrupt destination_account field.
json::Value args{depositAuthArgs(alice, becky)};
args[jss::destination_account] = "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "actMalformed", "Account malformed.");
verifyErr(result, "actMalformed", RpcActMalformed, "Account malformed.");
}
{
// Request an invalid ledger.
json::Value const args{depositAuthArgs(alice, becky, "-1")};
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(
result, "invalidParams", "Invalid field 'ledger_index', not string or number.");
result,
"invalidParams",
RpcInvalidParams,
"Invalid field 'ledger_index', not string or number.");
}
{
// Request a ledger that doesn't exist yet as a string.
json::Value const args{depositAuthArgs(alice, becky, "17")};
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "lgrNotFound", "ledgerNotFound");
verifyErr(result, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound");
}
{
// Request a ledger that doesn't exist yet.
json::Value args{depositAuthArgs(alice, becky)};
args[jss::ledger_index] = 17;
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "lgrNotFound", "ledgerNotFound");
verifyErr(result, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound");
}
{
// alice is not yet funded.
json::Value const args{depositAuthArgs(alice, becky)};
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "srcActNotFound", "Source account not found.");
verifyErr(
result, "srcActNotFound", RpcSrcActNotFound, "Source account not found.", true);
}
env.fund(XRP(1000), alice);
env.close();
@@ -237,7 +258,12 @@ public:
// becky is not yet funded.
json::Value const args{depositAuthArgs(alice, becky)};
json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
verifyErr(result, "dstActNotFound", "Destination account not found.");
verifyErr(
result,
"dstActNotFound",
RpcDstActNotFound,
"Destination account not found.",
true);
}
env.fund(XRP(1000), becky);
env.close();
@@ -256,9 +282,11 @@ public:
jtx::Account const& dst,
bool authorized,
std::vector<std::string> credentialIDs = {},
std::string_view error = "")
std::string_view error = "",
ErrorCodeI code = RpcSuccess,
std::string_view errorMessage = "")
{
BEAST_EXPECT(result[jss::status] == authorized ? jss::success : jss::error);
BEAST_EXPECT(result[jss::status] == (authorized ? jss::success : jss::error));
if (result.isMember(jss::deposit_authorized))
BEAST_EXPECT(result[jss::deposit_authorized] == authorized);
if (authorized)
@@ -270,7 +298,16 @@ public:
BEAST_EXPECT(result.isMember(jss::error) == !error.empty());
if (!error.empty())
{
BEAST_EXPECT(result[jss::error].asString() == error);
BEAST_EXPECT(result[jss::error_code] == code);
BEAST_EXPECT(result[jss::error_message].asString() == errorMessage);
bool const withLedgerFields = error == "badCredentials";
BEAST_EXPECT(result.isMember(jss::ledger_hash) == withLedgerFields);
BEAST_EXPECT(result.isMember(jss::ledger_index) == withLedgerFields);
BEAST_EXPECT(result.isMember(jss::validated) == withLedgerFields);
}
if (authorized)
{
@@ -334,7 +371,15 @@ public:
args[jss::credentials] = json::ValueType::Array;
auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
checkCredentialsResponse(jv[jss::result], alice, becky, false, {}, "invalidParams");
checkCredentialsResponse(
jv[jss::result],
alice,
becky,
false,
{},
"invalidParams",
RpcInvalidParams,
"Invalid field 'credentials', not is non-empty array of CredentialID(hash256).");
}
{
@@ -348,7 +393,15 @@ public:
args[jss::credentials].append(3);
auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
checkCredentialsResponse(jv[jss::result], alice, becky, false, {}, "invalidParams");
checkCredentialsResponse(
jv[jss::result],
alice,
becky,
false,
{},
"invalidParams",
RpcInvalidParams,
"Invalid field 'credentials', not an array of CredentialID(hash256).");
}
{
@@ -362,7 +415,14 @@ public:
auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
checkCredentialsResponse(
jv[jss::result], alice, becky, false, {"hello world"}, "invalidParams");
jv[jss::result],
alice,
becky,
false,
{"hello world"},
"invalidParams",
RpcInvalidParams,
"Invalid field 'credentials', not an array of CredentialID(hash256).");
}
{
@@ -389,7 +449,9 @@ public:
"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
"473",
},
"badCredentials");
"badCredentials",
RpcBadCredentials,
"credentials don't exist");
}
{
@@ -403,7 +465,14 @@ public:
auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
checkCredentialsResponse(
jv[jss::result], alice, becky, false, {zero}, "badCredentials");
jv[jss::result],
alice,
becky,
false,
{zero},
"badCredentials",
RpcBadCredentials,
"credentials don't exist");
}
}
@@ -416,7 +485,14 @@ public:
"deposit_authorized",
depositAuthArgs(alice, becky, "validated", {credIdx}).toStyledString());
checkCredentialsResponse(
jv[jss::result], alice, becky, false, {credIdx}, "badCredentials");
jv[jss::result],
alice,
becky,
false,
{credIdx},
"badCredentials",
RpcBadCredentials,
"credentials aren't accepted");
}
// alice accept credentials
@@ -430,7 +506,14 @@ public:
"deposit_authorized",
depositAuthArgs(alice, becky, "validated", {credIdx, credIdx}).toStyledString());
checkCredentialsResponse(
jv[jss::result], alice, becky, false, {credIdx, credIdx}, "badCredentials");
jv[jss::result],
alice,
becky,
false,
{credIdx, credIdx},
"badCredentials",
RpcBadCredentials,
"duplicates in credentials");
}
{
@@ -453,7 +536,14 @@ public:
"deposit_authorized",
depositAuthArgs(alice, becky, "validated", kCredIds).toStyledString());
checkCredentialsResponse(
jv[jss::result], alice, becky, false, kCredIds, "invalidParams");
jv[jss::result],
alice,
becky,
false,
kCredIds,
"invalidParams",
RpcInvalidParams,
"Invalid field 'credentials', not array too long.");
}
{
@@ -500,7 +590,14 @@ public:
"deposit_authorized",
depositAuthArgs(becky, alice, "validated", {credDiana}).toStyledString());
checkCredentialsResponse(
jv[jss::result], becky, alice, false, {credDiana}, "badCredentials");
jv[jss::result],
becky,
alice,
false,
{credDiana},
"badCredentials",
RpcBadCredentials,
"credentials doesn't belong to the root account");
}
{
@@ -549,7 +646,14 @@ public:
depositAuthArgs(alice, becky, "validated", {credIdx2}).toStyledString());
checkCredentialsResponse(
jv[jss::result], alice, becky, false, {credIdx2}, "badCredentials");
jv[jss::result],
alice,
becky,
false,
{credIdx2},
"badCredentials",
RpcBadCredentials,
"credentials are expired");
}
}
}
+9
View File
@@ -11,6 +11,7 @@
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/AmendmentTable.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/jss.h>
@@ -206,6 +207,7 @@ class Feature_test : public beast::unit_test::Suite
// feature names are case-sensitive - expect error here
jrr = env.rpc("feature", "fMM")[jss::result];
BEAST_EXPECT(jrr[jss::error] == "badFeature");
BEAST_EXPECT(jrr[jss::error_code] == RpcBadFeature);
BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
// Test feature name size checks
@@ -253,6 +255,7 @@ class Feature_test : public beast::unit_test::Suite
params[jss::feature] = param;
auto jrr = env.rpc("json", "feature", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
};
@@ -266,6 +269,7 @@ class Feature_test : public beast::unit_test::Suite
{
auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
BEAST_EXPECT(jrr[jss::error] == "badFeature");
BEAST_EXPECT(jrr[jss::error_code] == RpcBadFeature);
BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
}
}
@@ -320,6 +324,7 @@ class Feature_test : public beast::unit_test::Suite
"EF";
auto const result = env.rpc("json", "feature", to_string(params))[jss::result];
BEAST_EXPECTS(result[jss::error] == "badFeature", result.toStyledString());
BEAST_EXPECT(result[jss::error_code] == RpcBadFeature);
BEAST_EXPECT(result[jss::error_message] == "Feature unknown or invalid.");
}
@@ -332,6 +337,7 @@ class Feature_test : public beast::unit_test::Suite
params[jss::vetoed] = true;
auto const result = env.rpc("json", "feature", to_string(params))[jss::result];
BEAST_EXPECTS(result[jss::error] == "noPermission", result[jss::error].asString());
BEAST_EXPECT(result[jss::error_code] == RpcNoPermission);
BEAST_EXPECT(
result[jss::error_message] == "You don't have permission for this command.");
}
@@ -513,6 +519,7 @@ class Feature_test : public beast::unit_test::Suite
auto const result = env.rpc("json", "feature", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
};
@@ -521,6 +528,7 @@ class Feature_test : public beast::unit_test::Suite
// anything other than accept or reject is an error
jrr = env.rpc("feature", kFeatureName, "maybe");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
}
@@ -584,6 +592,7 @@ class Feature_test : public beast::unit_test::Suite
// anything other than accept or reject is an error
jrr = env.rpc("feature", featureName, "maybe");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
}
+10
View File
@@ -10,6 +10,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TxFlags.h>
@@ -172,7 +173,12 @@ public:
auto response = jv[jss::result];
auto const error = apiVersion < 2u ? "invalidHotWallet" : "invalidParams";
auto const errorCode = apiVersion < 2u ? RpcInvalidHotwallet : RpcInvalidParams;
auto const errorMessage =
apiVersion < 2u ? "Invalid hotwallet." : "Invalid parameters.";
BEAST_EXPECT(response[jss::error] == error);
BEAST_EXPECT(response[jss::error_code] == errorCode);
BEAST_EXPECT(response[jss::error_message] == errorMessage);
});
}
@@ -201,6 +207,8 @@ public:
auto jv = wsc->invoke("gateway_balances", qry);
expect(jv[jss::status] == "error");
BEAST_EXPECT(jv[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jv[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jv[jss::result][jss::error_message] == "Invalid field 'account'.");
});
// The same applies to a non-string "ident".
@@ -212,6 +220,8 @@ public:
auto jv = wsc->invoke("gateway_balances", qry2);
expect(jv[jss::status] == "error");
BEAST_EXPECT(jv[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(jv[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jv[jss::result][jss::error_message] == "Invalid field 'ident'.");
});
}
+41
View File
@@ -12,6 +12,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
@@ -42,10 +43,14 @@ public:
auto const baseFee = env.current()->fees().base;
// missing base_asset
auto ret = Oracle::aggregatePrice(env, std::nullopt, "USD", kOracles);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Missing field 'base_asset'.");
// missing quote_asset
ret = Oracle::aggregatePrice(env, "XRP", std::nullopt, kOracles);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Missing field 'quote_asset'.");
// invalid base_asset, quote_asset
@@ -69,28 +74,44 @@ public:
{
ret = Oracle::aggregatePrice(env, "USD", v, kOracles);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Invalid parameters.");
ret = Oracle::aggregatePrice(env, v, "USD", kOracles);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Invalid parameters.");
ret = Oracle::aggregatePrice(env, v, v, kOracles);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Invalid parameters.");
}
// missing oracles array
ret = Oracle::aggregatePrice(env, "XRP", "USD");
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Missing field 'oracles'.");
// empty oracles array
ret = Oracle::aggregatePrice(env, "XRP", "USD", OraclesData{});
BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
BEAST_EXPECT(ret[jss::error_code] == RpcOracleMalformed);
BEAST_EXPECT(ret[jss::error_message].asString() == "Oracle request is malformed.");
// no token pairs found
ret = Oracle::aggregatePrice(env, "YAN", "USD", kOracles);
BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
BEAST_EXPECT(ret[jss::error_code] == RpcObjectNotFound);
BEAST_EXPECT(
ret[jss::error_message].asString() == "The requested object was not found.");
// invalid oracle document id
// id doesn't exist
ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, 2}}});
BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
BEAST_EXPECT(ret[jss::error_code] == RpcObjectNotFound);
BEAST_EXPECT(
ret[jss::error_message].asString() == "The requested object was not found.");
// invalid values
std::vector<AnyValue> const invalidDocument = {kNoneTag, 1.2, -1, "", "none", "1.2"};
for (auto const& v : invalidDocument)
@@ -99,17 +120,26 @@ public:
json::Value jv;
toJson(jv, v);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Invalid parameters.");
}
// missing document id
ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, std::nullopt}}});
BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
BEAST_EXPECT(ret[jss::error_code] == RpcOracleMalformed);
BEAST_EXPECT(ret[jss::error_message].asString() == "Oracle request is malformed.");
// invalid owner
ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{some, 1}}});
BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
BEAST_EXPECT(ret[jss::error_code] == RpcObjectNotFound);
BEAST_EXPECT(
ret[jss::error_message].asString() == "The requested object was not found.");
// missing account
ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{std::nullopt, 1}}});
BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
BEAST_EXPECT(ret[jss::error_code] == RpcOracleMalformed);
BEAST_EXPECT(ret[jss::error_message].asString() == "Oracle request is malformed.");
// oracles have wrong asset pair
env.fund(XRP(1'000), owner);
@@ -122,6 +152,9 @@ public:
});
ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, oracle.documentID()}}});
BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
BEAST_EXPECT(ret[jss::error_code] == RpcObjectNotFound);
BEAST_EXPECT(
ret[jss::error_message].asString() == "The requested object was not found.");
// invalid trim value
std::vector<AnyValue> const invalidTrim = {kNoneTag, 0, 26, -1, 1.2, "", "none", "1.2"};
@@ -130,6 +163,8 @@ public:
ret =
Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, oracle.documentID()}}}, v);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Invalid parameters.");
}
// invalid time threshold value
@@ -139,6 +174,8 @@ public:
ret = Oracle::aggregatePrice(
env, "XRP", "USD", {{{owner, oracle.documentID()}}}, std::nullopt, v);
BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message].asString() == "Invalid parameters.");
}
}
@@ -157,6 +194,8 @@ public:
}
auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
BEAST_EXPECT(ret[jss::error_code] == RpcOracleMalformed);
BEAST_EXPECT(ret[jss::error_message].asString() == "Oracle request is malformed.");
}
}
@@ -457,6 +496,8 @@ public:
// a nullptr dereference.
auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
BEAST_EXPECT(ret[jss::error_code] == RpcObjectNotFound);
BEAST_EXPECT(ret[jss::error_message].asString() == "The requested object was not found.");
}
void
File diff suppressed because it is too large. Load diff
+72 -34
View File
@@ -209,7 +209,12 @@ public:
params[jss::seed_hex] = strings.masterSeedHex;
// Secret fields are mutually exclusive.
BEAST_EXPECT(containsError(walletPropose(params)));
auto const result = walletPropose(params);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
result[jss::error_message] ==
"Exactly one of the following must be specified: passphrase, seed or seed_hex");
}
void
@@ -223,7 +228,8 @@ public:
params[jss::key_type] = "secp256k1";
params[jss::passphrase] = 20160506;
auto result = walletPropose(params);
BEAST_EXPECT(containsError(result));
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'passphrase', not string.");
}
@@ -232,7 +238,8 @@ public:
params[jss::key_type] = "secp256k1";
params[jss::seed] = json::ValueType::Object;
auto result = walletPropose(params);
BEAST_EXPECT(containsError(result));
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'seed', not string.");
}
@@ -241,7 +248,8 @@ public:
params[jss::key_type] = "ed25519";
params[jss::seed_hex] = json::ValueType::Array;
auto result = walletPropose(params);
BEAST_EXPECT(containsError(result));
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'seed_hex', not string.");
}
@@ -253,7 +261,8 @@ public:
params[jss::seed_hex] = common::gMasterSeedHex;
params[jss::seed] = common::gMasterSeed;
auto result = walletPropose(params);
BEAST_EXPECT(containsError(result));
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
result[jss::error_message] ==
"Exactly one of the following must be specified: passphrase, "
@@ -266,7 +275,8 @@ public:
params[jss::key_type] = "prime256v1";
params[jss::passphrase] = common::gMasterKey;
auto result = walletPropose(params);
BEAST_EXPECT(containsError(result));
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
@@ -275,7 +285,8 @@ public:
params[jss::key_type] = json::ValueType::Object;
params[jss::seed_hex] = common::gMasterSeedHex;
auto result = walletPropose(params);
BEAST_EXPECT(containsError(result));
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'key_type', not string.");
}
@@ -284,7 +295,8 @@ public:
params[jss::key_type] = json::ValueType::Array;
params[jss::seed] = common::gMasterSeed;
auto result = walletPropose(params);
BEAST_EXPECT(containsError(result));
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'key_type', not string.");
}
}
@@ -402,8 +414,9 @@ public:
json::Value error;
params[jss::secret] = 314159265;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'secret', not string.");
}
@@ -414,8 +427,9 @@ public:
params[jss::secret].append("array:0");
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'secret', not string.");
}
@@ -427,8 +441,9 @@ public:
params[jss::secret]["number"] = 702;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'secret', not string.");
}
@@ -440,8 +455,9 @@ public:
params[jss::secret] = common::gMasterSeed;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
error[jss::error_message] ==
"The secret field is not allowed if key_type is used.");
@@ -455,8 +471,9 @@ public:
params[jss::passphrase] = common::gMasterKey;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'key_type'.");
}
@@ -467,8 +484,9 @@ public:
params[jss::seed_hex] = common::gMasterSeedHex;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'key_type', not string.");
}
@@ -479,8 +497,9 @@ public:
params[jss::seed] = common::gMasterSeed;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'key_type', not string.");
}
@@ -492,8 +511,9 @@ public:
params[jss::passphrase] = 1234567890;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'passphrase', not string.");
}
@@ -504,8 +524,9 @@ public:
params[jss::passphrase] = json::ValueType::Object;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'passphrase', not string.");
}
@@ -516,8 +537,9 @@ public:
params[jss::passphrase] = json::ValueType::Array;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'passphrase', not string.");
}
@@ -528,8 +550,9 @@ public:
params[jss::passphrase] = "";
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
@@ -541,8 +564,9 @@ public:
params[jss::seed] = 443556;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'seed', not string.");
}
@@ -553,8 +577,9 @@ public:
params[jss::seed] = json::ValueType::Object;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'seed', not string.");
}
@@ -565,8 +590,9 @@ public:
params[jss::seed] = json::ValueType::Array;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'seed', not string.");
}
@@ -577,8 +603,9 @@ public:
params[jss::seed] = "";
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
@@ -589,8 +616,9 @@ public:
params[jss::seed] = "s M V s h z D F p t Z E m h s";
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
@@ -601,8 +629,9 @@ public:
params[jss::seed] = "pnnjkbnobnml43679nbvjdsklnbjs";
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
@@ -614,8 +643,9 @@ public:
params[jss::seed_hex] = 443556;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'seed_hex', not string.");
}
@@ -626,8 +656,9 @@ public:
params[jss::seed_hex] = json::ValueType::Object;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'seed_hex', not string.");
}
@@ -638,8 +669,9 @@ public:
params[jss::seed_hex] = json::ValueType::Array;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "invalidParams");
BEAST_EXPECT(error[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(error[jss::error_message] == "Invalid field 'seed_hex', not string.");
}
@@ -650,8 +682,9 @@ public:
params[jss::seed_hex] = "";
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
@@ -662,8 +695,9 @@ public:
params[jss::seed_hex] = "A670A19B";
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
@@ -674,8 +708,9 @@ public:
params[jss::seed_hex] = common::gPassphrase;
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
@@ -686,8 +721,9 @@ public:
params[jss::seed_hex] = "BE6A670A19B209E112146D0A7ED2AAD72567D0FC913";
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(!ret);
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(error[jss::error_message] == "Disallowed seed.");
}
}
@@ -723,7 +759,8 @@ public:
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(
error[jss::error_message] == "Specified seed is for an Ed25519 wallet.");
}
@@ -754,7 +791,8 @@ public:
auto const ret = keypairForSignature(params, error);
BEAST_EXPECT(containsError(error));
BEAST_EXPECT(error[jss::error] == "badSeed");
BEAST_EXPECT(error[jss::error_code] == RpcBadSeed);
BEAST_EXPECT(
error[jss::error_message] == "Specified seed is for an Ed25519 wallet.");
}
+12 -6
View File
@@ -17,6 +17,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/Serializer.h>
@@ -131,8 +132,9 @@ public:
json::Value jvParams;
jvParams[jss::limit] = "0"; // NOT an integer
auto const jrr = env.rpc("json", "ledger_data", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not integer.");
}
@@ -141,8 +143,9 @@ public:
json::Value jvParams;
jvParams[jss::marker] = "NOT_A_MARKER";
auto const jrr = env.rpc("json", "ledger_data", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not valid.");
}
@@ -151,8 +154,9 @@ public:
json::Value jvParams;
jvParams[jss::marker] = 1;
auto const jrr = env.rpc("json", "ledger_data", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not valid.");
}
@@ -161,8 +165,9 @@ public:
json::Value jvParams;
jvParams[jss::ledger_index] = 10u;
auto const jrr = env.rpc("json", "ledger_data", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
@@ -171,8 +176,9 @@ public:
json::Value jvParams;
jvParams[jss::binary] = "true";
auto const jrr = env.rpc("json", "ledger_data", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'binary', not boolean.");
}
}
@@ -459,8 +465,8 @@ public:
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "misspelling";
auto const jrr = env.rpc("json", "ledger_data", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember("error"));
BEAST_EXPECT(jrr["error"] == "invalidParams");
BEAST_EXPECT(jrr["error_code"] == RpcInvalidParams);
BEAST_EXPECT(jrr["error_message"] == "Invalid field 'type'.");
}
}
+179 -54
View File
@@ -153,6 +153,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
json::Value const& jv,
std::string const& err,
ErrorCodeI code,
std::string const& msg,
std::source_location const location = std::source_location::current())
{
@@ -165,6 +166,14 @@ class LedgerEntry_test : public beast::unit_test::Suite
"Expected error " + err + ", received " + jv[jss::error].asString() + ", at line " +
std::to_string(location.line()) + ", " + jv.toStyledString());
}
if (BEAST_EXPECT(jv.isMember(jss::error_code)))
{
BEAST_EXPECTS(
jv[jss::error_code] == code,
"Expected error code " + std::to_string(code) + ", received " +
jv[jss::error_code].asString() + ", at line " +
std::to_string(location.line()) + ", " + jv.toStyledString());
}
if (msg.empty())
{
BEAST_EXPECTS(
@@ -340,12 +349,21 @@ class LedgerEntry_test : public beast::unit_test::Suite
apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
if (apiVersion < 2u)
{
checkErrorValue(jrr, "unknownOption", "", location);
BEAST_EXPECTS(jrr[jss::status] == "error", std::to_string(location.line()));
BEAST_EXPECTS(
jrr[jss::error] == "unknownOption", std::to_string(location.line()));
BEAST_EXPECTS(!jrr.isMember(jss::error_code), std::to_string(location.line()));
BEAST_EXPECTS(
jrr[jss::error_message].isNull(), std::to_string(location.line()));
}
else
{
checkErrorValue(
jrr, "invalidParams", "No ledger_entry params provided.", location);
jrr,
"invalidParams",
RpcInvalidParams,
"No ledger_entry params provided.",
location);
}
}
auto const tryField = [&](json::Value fieldValue) -> void {
@@ -354,7 +372,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
auto const expectedErrMsg = rpc::expectedFieldMessage(
fieldName, typeNameOverride.value_or(getTypeName(typeID)));
checkErrorValue(jrr, expectedError, expectedErrMsg, location);
checkErrorValue(jrr, expectedError, RpcInvalidParams, expectedErrMsg, location);
};
auto const& badValues = getBadValues(typeID);
@@ -387,13 +405,21 @@ class LedgerEntry_test : public beast::unit_test::Suite
json::Value const jrr = env.rpc(
apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
checkErrorValue(
jrr, "malformedRequest", rpc::missingFieldMessage(fieldName.cStr()), location);
jrr,
"malformedRequest",
RpcInvalidParams,
rpc::missingFieldMessage(fieldName.cStr()),
location);
correctRequest[parentFieldName][fieldName] = json::ValueType::Null;
json::Value const jrr2 = env.rpc(
apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
checkErrorValue(
jrr2, "malformedRequest", rpc::missingFieldMessage(fieldName.cStr()), location);
jrr2,
"malformedRequest",
RpcInvalidParams,
rpc::missingFieldMessage(fieldName.cStr()),
location);
}
auto const tryField = [&](json::Value fieldValue) -> void {
correctRequest[parentFieldName][fieldName] = fieldValue;
@@ -403,6 +429,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr,
expectedError,
RpcInvalidParams,
rpc::expectedFieldMessage(fieldName, getTypeName(typeID)),
location);
};
@@ -497,7 +524,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AA";
auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
checkErrorValue(jrr, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound");
}
{
// Missing ledger_entry ledger_hash
@@ -511,7 +538,10 @@ class LedgerEntry_test : public beast::unit_test::Suite
json::Value const jrr = env.rpc(
apiVersion, "json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(
jrr, "invalidParams", "Invalid field 'ledger_hash', not hex string.");
jrr,
"invalidParams",
RpcInvalidParams,
"Invalid field 'ledger_hash', not hex string.");
};
auto const& badValues = getBadValues(typeId);
@@ -530,7 +560,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
"00000000000000000000000000000000000000000000000000000000000000"
"00";
auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
forAllApiVersions([&, this](unsigned apiVersion) {
@@ -546,11 +576,15 @@ class LedgerEntry_test : public beast::unit_test::Suite
if (apiVersion < 2u)
{
checkErrorValue(jrr, "unknownOption", "");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error] == "unknownOption");
BEAST_EXPECT(!jrr.isMember(jss::error_code));
BEAST_EXPECT(jrr[jss::error_message].isNull());
}
else
{
checkErrorValue(jrr, "invalidParams", "No ledger_entry params provided.");
checkErrorValue(
jrr, "invalidParams", RpcInvalidParams, "No ledger_entry params provided.");
}
}
});
@@ -655,7 +689,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
}
@@ -854,7 +888,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Request an index that is not a check. We'll use alice's
@@ -872,7 +906,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "unexpectedLedgerType", "Unexpected ledger type.");
checkErrorValue(
jrr, "unexpectedLedgerType", RpcUnexpectedLedgerType, "Unexpected ledger type.");
}
{
// Check malformed cases
@@ -931,7 +966,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
env,
"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
"E4");
checkErrorValue(jv[jss::result], "entryNotFound", "Entry not found.");
checkErrorValue(jv[jss::result], "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
@@ -1126,7 +1161,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
auto const expectedErrMsg = fieldValue.isNull()
? rpc::missingFieldMessage(jss::issuer.cStr())
: rpc::expectedFieldMessage(jss::issuer, "AccountID");
checkErrorValue(jrr, "malformedAuthorizedCredentials", expectedErrMsg);
checkErrorValue(
jrr, "malformedAuthorizedCredentials", RpcInvalidParams, expectedErrMsg);
};
auto const& badValues = getBadValues(FieldType::AccountField);
@@ -1155,6 +1191,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::authorized_credentials, "array"));
}
@@ -1177,7 +1214,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
auto const expectedErrMsg = fieldValue.isNull()
? rpc::missingFieldMessage(jss::credential_type.cStr())
: rpc::expectedFieldMessage(jss::credential_type, "hex string");
checkErrorValue(jrr, "malformedAuthorizedCredentials", expectedErrMsg);
checkErrorValue(
jrr, "malformedAuthorizedCredentials", RpcInvalidParams, expectedErrMsg);
};
auto const& badValues = getBadValues(FieldType::BlobField);
@@ -1207,6 +1245,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr[jss::result],
"malformedRequest",
RpcInvalidParams,
"Must have exactly one of `authorized` and "
"`authorized_credentials`.");
}
@@ -1239,6 +1278,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
RpcInvalidParams,
"Invalid field 'authorized_credentials', not array of objects.");
}
@@ -1257,6 +1297,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
RpcInvalidParams,
"Invalid field 'authorized_credentials', not array of objects.");
}
@@ -1271,6 +1312,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
RpcInvalidParams,
"Invalid field 'authorized_credentials', array empty.");
}
@@ -1308,6 +1350,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
RpcInvalidParams,
"Invalid field 'authorized_credentials', array too long.");
}
}
@@ -1443,7 +1486,10 @@ class LedgerEntry_test : public beast::unit_test::Suite
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(
jrr, "malformedRequest", "Must have exactly one of `owner` and `dir_root` fields.");
jrr,
"malformedRequest",
RpcInvalidParams,
"Must have exactly one of `owner` and `dir_root` fields.");
}
{
// Incomplete directory object. Missing both dir_root and owner.
@@ -1454,7 +1500,10 @@ class LedgerEntry_test : public beast::unit_test::Suite
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(
jrr, "malformedRequest", "Must have exactly one of `owner` and `dir_root` fields.");
jrr,
"malformedRequest",
RpcInvalidParams,
"Must have exactly one of `owner` and `dir_root` fields.");
}
}
@@ -1617,7 +1666,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::nft_offer][jss::seq] = offerSeq + 1000;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
// negative tests
@@ -1835,7 +1884,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Request an index that is not a payment channel.
@@ -1844,7 +1893,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
@@ -1919,6 +1968,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr,
"malformedRequest",
RpcInvalidParams,
"Invalid field 'accounts', not length-2 array of "
"Accounts.");
}
@@ -1937,6 +1987,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr,
"malformedRequest",
RpcInvalidParams,
"Invalid field 'accounts', not length-2 array of "
"Accounts.");
}
@@ -1957,6 +2008,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr,
"malformedAddress",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::accounts, "array of Accounts"));
}
@@ -1972,6 +2024,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
checkErrorValue(
jrr,
"malformedAddress",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::accounts, "array of Accounts"));
}
};
@@ -1994,7 +2047,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "malformedRequest", "Cannot have a trustline to self.");
checkErrorValue(
jrr, "malformedRequest", RpcInvalidParams, "Cannot have a trustline to self.");
}
}
}
@@ -2048,7 +2102,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Request an index that is not a signer list.
@@ -2057,7 +2111,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
@@ -2148,7 +2202,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// First real ticket requested by index.
@@ -2184,7 +2238,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Request a ticket using an account root entry.
@@ -2193,7 +2247,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "unexpectedLedgerType", "Unexpected ledger type.");
checkErrorValue(
jrr, "unexpectedLedgerType", RpcUnexpectedLedgerType, "Unexpected ledger type.");
}
{
@@ -2261,7 +2316,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::transaction_proposal][jss::ticket_seq] = ticketSeq + 1;
jvParams[jss::ledger_hash] = ledgerHash;
auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Lookup by an index of the wrong entry type.
@@ -2269,7 +2324,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::transaction_proposal] = to_string(keylet::account(target).key);
jvParams[jss::ledger_hash] = ledgerHash;
auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "unexpectedLedgerType", "Unexpected ledger type.");
checkErrorValue(
jrr, "unexpectedLedgerType", RpcUnexpectedLedgerType, "Unexpected ledger type.");
}
{
@@ -2333,7 +2389,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Malformed DID index
@@ -2455,7 +2511,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Request the MPToken using its owner + mptIssuanceID.
@@ -2478,7 +2534,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = ledgerHash;
json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// Malformed MPTIssuance index
@@ -2546,7 +2602,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
"12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
"DE";
auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
checkErrorValue(jrr[jss::result], "entryNotFound", "Entry not found.");
checkErrorValue(
jrr[jss::result], "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// test basic malformed scenarios
@@ -2592,7 +2649,9 @@ class LedgerEntry_test : public beast::unit_test::Suite
auto checkResult = [&](bool good,
json::Value const& jv,
json::StaticString const& expectedType,
std::optional<std::string> const& expectedError = {}) {
std::optional<std::string> const& expectedError = {},
ErrorCodeI expectedCode = RpcEntryNotFound,
std::string const& expectedMessage = "Entry not found.") {
if (good)
{
BEAST_EXPECTS(
@@ -2607,9 +2666,10 @@ class LedgerEntry_test : public beast::unit_test::Suite
{
BEAST_EXPECTS(
jv.isObject() && jv.isMember(jss::result) &&
jv[jss::result].isMember(jss::error) &&
!jv[jss::result].isMember(jss::node) &&
jv[jss::result][jss::error] == expectedError.value_or("entryNotFound"),
jv[jss::result][jss::error] == expectedError.value_or("entryNotFound") &&
jv[jss::result][jss::error_code] == expectedCode &&
jv[jss::result][jss::error_message] == expectedMessage,
to_string(jv));
}
};
@@ -2640,7 +2700,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[field] = json::ValueType::Null;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, expectedType, "malformedRequest");
checkResult(
false,
jv,
expectedType,
"malformedRequest",
RpcInvalidParams,
rpc::expectedFieldMessage(field, "hex string"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2650,7 +2716,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[field] = "arbitrary string";
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, expectedType, "malformedRequest");
checkResult(
false,
jv,
expectedType,
"malformedRequest",
RpcInvalidParams,
rpc::expectedFieldMessage(field, "hex string"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2660,7 +2732,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[field] = false;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, expectedType, "invalidParams");
checkResult(
false,
jv,
expectedType,
"invalidParams",
RpcInvalidParams,
rpc::expectedFieldMessage(field, "true"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2672,7 +2750,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[field] = badKey;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, expectedType, "entryNotFound");
checkResult(
false, jv, expectedType, "entryNotFound", RpcEntryNotFound, "Entry not found.");
BEAST_EXPECTS(jv[jss::result][jss::index] == badKey, to_string(jv));
}
@@ -2683,7 +2762,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::index] = field;
params[jss::api_version] = 2;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, expectedType, "malformedRequest");
checkResult(
false,
jv,
expectedType,
"malformedRequest",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::index, "hex string"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2722,7 +2807,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::index] = field;
params[jss::api_version] = 2;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, expectedType, "malformedRequest");
checkResult(
false,
jv,
expectedType,
"malformedRequest",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::index, "hex string"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2787,7 +2878,9 @@ class LedgerEntry_test : public beast::unit_test::Suite
auto checkResult = [&](bool good,
json::Value const& jv,
int expectedCount,
std::optional<std::string> const& expectedError = {}) {
std::optional<std::string> const& expectedError = {},
ErrorCodeI expectedCode = RpcEntryNotFound,
std::string const& expectedMessage = "Entry not found.") {
if (good)
{
BEAST_EXPECTS(
@@ -2807,9 +2900,10 @@ class LedgerEntry_test : public beast::unit_test::Suite
{
BEAST_EXPECTS(
jv.isObject() && jv.isMember(jss::result) &&
jv[jss::result].isMember(jss::error) &&
!jv[jss::result].isMember(jss::node) &&
jv[jss::result][jss::error] == expectedError.value_or("entryNotFound"),
jv[jss::result][jss::error] == expectedError.value_or("entryNotFound") &&
jv[jss::result][jss::error_code] == expectedCode &&
jv[jss::result][jss::error_message] == expectedMessage,
to_string(jv));
}
};
@@ -2839,7 +2933,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[jss::hashes] = json::ValueType::Null;
auto jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, 0, "malformedRequest");
checkResult(
false,
jv,
0,
"malformedRequest",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::hashes, "hex string"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2849,7 +2949,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[jss::hashes] = "arbitrary string";
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, 0, "malformedRequest");
checkResult(
false,
jv,
0,
"malformedRequest",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::hashes, "hex string"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2859,7 +2965,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[jss::hashes] = "10";
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, 0, "malformedRequest");
checkResult(
false,
jv,
0,
"malformedRequest",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::hashes, "hex string"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2869,7 +2981,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[jss::hashes] = false;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, 0, "invalidParams");
checkResult(
false,
jv,
0,
"invalidParams",
RpcInvalidParams,
rpc::expectedFieldMessage(jss::hashes, "true"));
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2879,7 +2997,7 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[jss::hashes] = -1;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, 0, "internal");
checkResult(false, jv, 0, "internal", RpcInternal, "Internal error.");
BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
}
@@ -2890,7 +3008,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
params[jss::ledger_index] = jss::validated;
params[jss::hashes] = badKey;
auto const jv = env.rpc("json", "ledger_entry", to_string(params));
checkResult(false, jv, 0, "entryNotFound");
checkResult(
false, jv, 0, "entryNotFound", RpcEntryNotFound, "Entry not found.");
BEAST_EXPECT(jv[jss::result][jss::index] == badKey);
}
@@ -3028,12 +3147,18 @@ 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)
checkErrorValue(
json::Value const& jv,
std::string const& err,
ErrorCodeI code,
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 (BEAST_EXPECT(jv.isMember(jss::error_code)))
BEAST_EXPECT(jv[jss::error_code] == code);
if (msg.empty())
{
BEAST_EXPECT(
@@ -3110,7 +3235,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::Suite,
json::Value const jrr =
mcEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
{
// create two claim ids and verify that the bridge counter was
@@ -3297,7 +3422,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::Suite,
[jss::xchain_owned_create_account_claim_id] = 1;
json::Value const jrr =
scEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
checkErrorValue(jrr, "entryNotFound", RpcEntryNotFound, "Entry not found.");
}
}
+4 -1
View File
@@ -3,6 +3,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
namespace xrpl {
@@ -53,8 +54,10 @@ class LedgerHeader_test : public beast::unit_test::Suite
json::Value params{json::ValueType::Object};
params[jss::api_version] = 2;
auto const result = env.client().invoke("ledger_header", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "unknownCmd");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "unknownCmd");
BEAST_EXPECT(result[jss::error_code] == RpcUnknownCommand);
BEAST_EXPECT(result[jss::error_message] == "Unknown method.");
}
public:
+32 -10
View File
@@ -37,12 +37,18 @@ namespace xrpl::test {
class LedgerRPC_test : public beast::unit_test::Suite
{
void
checkErrorValue(json::Value const& jv, std::string const& err, std::string const& msg)
checkErrorValue(
json::Value const& jv,
std::string const& err,
ErrorCodeI code,
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 (BEAST_EXPECT(jv.isMember(jss::error_code)))
BEAST_EXPECT(jv[jss::error_code] == code);
if (msg.empty())
{
BEAST_EXPECT(
@@ -139,7 +145,10 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger_index] = "potato";
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(
jrr, "invalidParams", "Invalid field 'ledger_index', not string or number.");
jrr,
"invalidParams",
RpcInvalidParams,
"Invalid field 'ledger_index', not string or number.");
}
{
@@ -148,7 +157,10 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger_index] = -1;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(
jrr, "invalidParams", "Invalid field 'ledger_index', not string or number.");
jrr,
"invalidParams",
RpcInvalidParams,
"Invalid field 'ledger_index', not string or number.");
}
{
@@ -156,13 +168,13 @@ class LedgerRPC_test : public beast::unit_test::Suite
json::Value jvParams;
jvParams[jss::ledger_index] = 10u;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
checkErrorValue(jrr, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound");
}
{
// unrecognized string arg -- error
auto const jrr = env.rpc("ledger", "arbitrary_text")[jss::result];
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
checkErrorValue(jrr, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound");
}
{
@@ -171,20 +183,21 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger_index] = "validated";
jvParams[jss::queue] = true;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
checkErrorValue(jrr, "invalidParams", RpcInvalidParams, "Invalid parameters.");
}
{
// Request a ledger with a very large (double) sequence.
auto const ret = env.rpc("json", "ledger", "{ \"ledger_index\" : 2e15 }");
BEAST_EXPECT(rpc::containsError(ret));
BEAST_EXPECT(ret[jss::error] == "invalidParams");
BEAST_EXPECT(ret[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(ret[jss::error_message] == "Invalid parameters.");
}
{
// Request a ledger with very large (integer) sequence.
auto const ret = env.rpc("json", "ledger", "{ \"ledger_index\" : 1000000000000000 }");
checkErrorValue(ret, "invalidParams", "Invalid parameters.");
checkErrorValue(ret, "invalidParams", RpcInvalidParams, "Invalid parameters.");
}
{
@@ -193,7 +206,7 @@ class LedgerRPC_test : public beast::unit_test::Suite
json::Value jvParams;
jvParams[field] = "blah";
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
checkErrorValue(jrr, "invalidParams", RpcInvalidParams, "Invalid parameters.");
};
testBooleanField(jss::full);
testBooleanField(jss::accounts);
@@ -255,7 +268,8 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger_index] = 1u;
jvParams[jss::full] = true;
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "noPermission", "You don't have permission for this command.");
checkErrorValue(
jrr, "noPermission", RpcNoPermission, "You don't have permission for this command.");
}
void
@@ -422,6 +436,7 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger] = "invalid";
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'ledger', not string or number.");
@@ -436,6 +451,7 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger] = 20;
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
@@ -455,12 +471,14 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger_hash] = "DEADBEEF" + hash3;
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
// request with non-string ledger_hash
jvParams[jss::ledger_hash] = 2;
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
// malformed (non hex) hash
@@ -469,6 +487,7 @@ class LedgerRPC_test : public beast::unit_test::Suite
"7F2775F2F7485BB37307984C3C0F2340";
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
// properly formed, but just doesn't exist
@@ -477,6 +496,7 @@ class LedgerRPC_test : public beast::unit_test::Suite
"5A75FD71FC3FD6F36FEB803C5F1D812D";
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
@@ -506,6 +526,7 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger_index] = "invalid";
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'ledger_index', not string or number.");
@@ -524,6 +545,7 @@ class LedgerRPC_test : public beast::unit_test::Suite
jvParams[jss::ledger_index] = 7;
jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
}
+20 -9
View File
@@ -43,21 +43,24 @@ public:
// arbitrary text is converted to 0.
auto const result = env.rpc("ledger_request", "arbitrary_text");
BEAST_EXPECT(
rpc::containsError(result[jss::result]) &&
result[jss::result][jss::error] == "invalidParams" &&
result[jss::result][jss::error_code] == RpcInvalidParams &&
result[jss::result][jss::error_message] == "Ledger index too small");
}
{
auto const result = env.rpc("ledger_request", "-1");
BEAST_EXPECT(
rpc::containsError(result[jss::result]) &&
result[jss::result][jss::error] == "invalidParams" &&
result[jss::result][jss::error_code] == RpcInvalidParams &&
result[jss::result][jss::error_message] == "Ledger index too small");
}
{
auto const result = env.rpc("ledger_request", "0");
BEAST_EXPECT(
rpc::containsError(result[jss::result]) &&
result[jss::result][jss::error] == "invalidParams" &&
result[jss::result][jss::error_code] == RpcInvalidParams &&
result[jss::result][jss::error_message] == "Ledger index too small");
}
@@ -112,7 +115,8 @@ public:
auto const result = env.rpc("ledger_request", ledgerHash);
BEAST_EXPECT(
rpc::containsError(result[jss::result]) &&
result[jss::result][jss::error] == "invalidParams" &&
result[jss::result][jss::error_code] == RpcInvalidParams &&
result[jss::result][jss::error_message] ==
"Invalid field 'ledger_hash', not hex string.");
}
@@ -130,14 +134,16 @@ public:
{
auto const result = env.rpc("ledger_request", "4");
BEAST_EXPECT(
rpc::containsError(result[jss::result]) &&
result[jss::result][jss::error] == "invalidParams" &&
result[jss::result][jss::error_code] == RpcInvalidParams &&
result[jss::result][jss::error_message] == "Ledger index too large");
}
{
auto const result = env.rpc("ledger_request", "5");
BEAST_EXPECT(
rpc::containsError(result[jss::result]) &&
result[jss::result][jss::error] == "invalidParams" &&
result[jss::result][jss::error_code] == RpcInvalidParams &&
result[jss::result][jss::error_message] == "Ledger index too large");
}
}
@@ -236,8 +242,9 @@ public:
"8FE8592EF22FBC2E8C774C7A1ED76AA3FCE64BED17D748CBA9AFDF7072FE36C7");
result = env.rpc("ledger_request", "6")[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Ledger index too large");
}
@@ -259,8 +266,9 @@ public:
jvParams[jss::ledger_index] = "1";
auto const result =
env.rpc("json", "ledger_request", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
result[jss::error_message] ==
"Exactly one of 'ledger_hash' or 'ledger_index' can be "
@@ -272,8 +280,9 @@ public:
jvParams[jss::ledger_index] = "index";
auto const result =
env.rpc("json", "ledger_request", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'ledger_index', not number.");
}
@@ -285,11 +294,13 @@ public:
if (apiVersion == 1)
{
BEAST_EXPECT(result[jss::error] == "noCurrent");
BEAST_EXPECT(result[jss::error_code] == RpcNoCurrent);
BEAST_EXPECT(result[jss::error_message] == "Current ledger is unavailable.");
}
else
{
BEAST_EXPECT(result[jss::error] == "notSynced");
BEAST_EXPECT(result[jss::error_code] == RpcNotSynced);
BEAST_EXPECT(result[jss::error_message] == "Not synced to the network.");
}
}
+5
View File
@@ -7,6 +7,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/config/Constants.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <memory>
@@ -27,6 +28,8 @@ public:
{
// manifest with no public key
auto const info = env.rpc("json", "manifest", "{ }");
BEAST_EXPECT(info[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Missing field 'public_key'.");
}
{
@@ -36,6 +39,8 @@ public:
"manifest",
"{ \"public_key\": "
"\"abcdef12345\"}");
BEAST_EXPECT(info[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(info[jss::result][jss::error_message] == "Invalid parameters.");
}
}
+11
View File
@@ -19,6 +19,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
@@ -49,6 +50,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
{ // missing account field
auto const result = env.rpc("json", "noripple_check", "{}")[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Missing field 'account'.");
}
@@ -57,6 +59,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::account] = alice.human();
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Missing field 'role'.");
}
@@ -68,6 +71,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::role] = "user";
auto jrr = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
};
@@ -85,6 +89,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::role] = "not_a_role";
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'role'.");
}
@@ -95,6 +100,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::limit] = -1;
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
result[jss::error_message] == "Invalid field 'limit', not unsigned integer.");
}
@@ -106,6 +112,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::ledger_hash] = 1;
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
result[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
}
@@ -117,6 +124,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::ledger] = "current";
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "actNotFound");
BEAST_EXPECT(result[jss::error_code] == RpcActNotFound);
BEAST_EXPECT(result[jss::error_message] == "Account not found.");
}
@@ -129,6 +137,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::transactions] = true;
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "actMalformed");
BEAST_EXPECT(result[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(result[jss::error_message] == "Account malformed.");
// The changelog promises malformed-account responses carry
// neither `transactions` nor any ledger metadata.
@@ -147,6 +156,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::ledger_hash] = "ABCDEF";
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
result[jss::error_message] ==
"Exactly one of 'ledger', 'ledger_hash', or 'ledger_index' can "
@@ -161,6 +171,7 @@ class NoRippleCheck_test : public beast::unit_test::Suite
params[jss::ledger] = json::ValueType::Object;
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
result[jss::error_message] == "Invalid field 'ledger', not string or number.");
}
+4
View File
@@ -14,6 +14,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
@@ -256,6 +257,9 @@ public:
else
{
BEAST_EXPECT(lines[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(lines[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
lines[jss::result][jss::error_message] == "Invalid field 'transactions'.");
}
}
}
+4
View File
@@ -10,6 +10,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/UintTypes.h>
@@ -34,6 +35,7 @@ class OwnerInfo_test : public beast::unit_test::Suite
{ // missing account field
auto const result = env.rpc("json", "owner_info", "{}")[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Missing field 'account'.");
}
@@ -44,8 +46,10 @@ class OwnerInfo_test : public beast::unit_test::Suite
if (BEAST_EXPECT(result.isMember(jss::accepted) && result.isMember(jss::current)))
{
BEAST_EXPECT(result[jss::accepted][jss::error] == "actMalformed");
BEAST_EXPECT(result[jss::accepted][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(result[jss::accepted][jss::error_message] == "Account malformed.");
BEAST_EXPECT(result[jss::current][jss::error] == "actMalformed");
BEAST_EXPECT(result[jss::current][jss::error_code] == RpcActMalformed);
BEAST_EXPECT(result[jss::current][jss::error_message] == "Account malformed.");
}
}
-74
View File
@@ -2157,53 +2157,6 @@ static RPCCallTestData const kRpcCallTestArray[] = {
]
})",
},
{
"channel_authorize: token amount.",
__LINE__,
{
"channel_authorize",
"secret_can_be_anything",
"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
R"({"currency":"USD","issuer":"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh","value":"10.5"})",
},
RPCCallTestData::Exception::NoException,
R"({
"method" : "channel_authorize",
"params" : [
{
"api_version" : %API_VER%,
"amount" : {
"currency" : "USD",
"issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value" : "10.5"
},
"channel_id" : "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
"secret" : "secret_can_be_anything"
}
]
})",
},
{
"channel_authorize: amount JSON not an object.",
__LINE__,
{
"channel_authorize",
"secret_can_be_anything",
"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
R"(["10","USD"])",
},
RPCCallTestData::Exception::NoException,
R"({
"method" : "channel_authorize",
"params" : [
{
"error" : "channelAmtMalformed",
"error_code" : 43,
"error_message" : "Payment channel amount is malformed."
}
]
})",
},
{
"channel_authorize: too few arguments.",
__LINE__,
@@ -2425,33 +2378,6 @@ static RPCCallTestData const kRpcCallTestArray[] = {
]
})",
},
{
"channel_verify: token amount.",
__LINE__,
{
"channel_verify",
"aB4BXXLuPu8DpVuyq1DBiu3SrPdtK9AYZisKhu8mvkoiUD8J9Gov",
"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
R"({"mpt_issuance_id":"00000001A407AF5856CCF3C42619DAA925813FC955C72983","value":"10"})",
"DEADBEEF",
},
RPCCallTestData::Exception::NoException,
R"({
"method" : "channel_verify",
"params" : [
{
"api_version" : %API_VER%,
"amount" : {
"mpt_issuance_id" : "00000001A407AF5856CCF3C42619DAA925813FC955C72983",
"value" : "10"
},
"channel_id" : "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
"public_key" : "aB4BXXLuPu8DpVuyq1DBiu3SrPdtK9AYZisKhu8mvkoiUD8J9Gov",
"signature" : "DEADBEEF"
}
]
})",
},
{
"channel_verify: too few arguments.",
__LINE__,
+59 -5
View File
@@ -183,6 +183,8 @@ class Simulate_test : public beast::unit_test::Suite
// No params
json::Value const params = json::ValueType::Object;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Neither `tx_blob` nor `tx_json` included.");
@@ -194,6 +196,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_blob] = "1200";
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Can only include one of `tx_blob` and `tx_json`.");
@@ -204,6 +208,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_blob] = "1200";
params[jss::binary] = "100";
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'binary'.");
}
{
@@ -212,6 +218,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_blob] = "12";
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx_blob'.");
}
{
@@ -220,6 +228,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = json::ValueType::Object;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Missing field 'tx.TransactionType'.");
}
@@ -231,6 +241,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Missing field 'tx.Account'.");
}
{
@@ -239,6 +251,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_blob] = "";
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx_blob'.");
}
{
@@ -247,6 +261,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_blob] = 1.1;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx_blob'.");
}
{
@@ -255,6 +271,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = "";
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Invalid field 'tx_json', not object.");
}
@@ -267,6 +285,8 @@ class Simulate_test : public beast::unit_test::Suite
txJson[jss::Account] = env.master.human();
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'seed'.");
}
{
@@ -278,6 +298,8 @@ class Simulate_test : public beast::unit_test::Suite
txJson[jss::Account] = env.master.human();
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'secret'.");
}
{
@@ -289,6 +311,8 @@ class Simulate_test : public beast::unit_test::Suite
txJson[jss::Account] = env.master.human();
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'seed_hex'.");
}
{
@@ -300,6 +324,8 @@ class Simulate_test : public beast::unit_test::Suite
txJson[jss::Account] = env.master.human();
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'passphrase'.");
}
{
@@ -311,6 +337,9 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidTransaction");
BEAST_EXPECT(!resp[jss::result].isMember(jss::error_code));
BEAST_EXPECT(resp[jss::result][jss::error_message].isNull());
BEAST_EXPECT(
resp[jss::result][jss::error_exception] ==
"Field 'Destination' is required but missing.");
@@ -327,6 +356,7 @@ class Simulate_test : public beast::unit_test::Suite
BEAST_EXPECTS(
resp[jss::result][jss::error] == "srcActMalformed",
resp[jss::result][jss::error].toStyledString());
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcSrcActMalformed);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Account'.");
}
{
@@ -338,6 +368,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "srcActNotFound");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcSrcActNotFound);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Source account not found.");
}
{
@@ -350,6 +382,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers'.");
}
{
@@ -363,6 +397,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers[0]'.");
}
{
@@ -375,6 +411,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Invalid field 'SponsorSignature', not object.");
@@ -391,6 +429,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECTS(
resp[jss::result][jss::error_message] ==
"Invalid field 'tx.SponsorSignature.Signers'.",
@@ -406,6 +446,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Field 'tx_json.foo' is unknown.");
}
@@ -415,6 +457,8 @@ class Simulate_test : public beast::unit_test::Suite
txJson[jss::TransactionType] = jss::AccountSet;
txJson[jss::Account] = alice.human();
auto const resp = env.rpc("simulate", to_string(txJson), "1");
BEAST_EXPECT(resp[jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(resp[jss::error_message] == "Invalid parameters.");
}
{
@@ -427,6 +471,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "transactionSigned");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcTxSigned);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Transaction should not be signed.");
}
@@ -449,6 +495,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "transactionSigned");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcTxSigned);
BEAST_EXPECT(
resp[jss::result][jss::error_message] == "Transaction should not be signed.");
}
@@ -462,6 +510,8 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"tfInnerBatchTxn flag is not allowed on top-level transactions.");
@@ -530,6 +580,7 @@ class Simulate_test : public beast::unit_test::Suite
{
auto const resp = simulate(env, tx);
BEAST_EXPECT(resp[jss::error] == "noPermission");
BEAST_EXPECT(resp[jss::error_code] == RpcNoPermission);
BEAST_EXPECT(
resp[jss::error_message] ==
"WASM transactions are only supported in admin mode.");
@@ -559,7 +610,10 @@ class Simulate_test : public beast::unit_test::Suite
using namespace jtx;
// Pin the reference fee so the asserted error message doesn't depend on
// UNIT_TEST_REFERENCE_FEE.
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->fees.referenceFee = 10;
cfg->section(Sections::kTransactionQueue).set(Keys::kMinimumTxnInLedgerStandalone, "3");
return cfg;
}));
@@ -579,11 +633,10 @@ class Simulate_test : public beast::unit_test::Suite
auto const resp = env.rpc("json", "simulate", to_string(params));
auto const result = resp[jss::result];
if (BEAST_EXPECT(result.isMember(jss::error)))
{
BEAST_EXPECT(result[jss::error] == "highFee");
BEAST_EXPECT(result[jss::error_code] == RpcHighFee);
}
BEAST_EXPECT(result[jss::error] == "highFee");
BEAST_EXPECT(result[jss::error_code] == RpcHighFee);
BEAST_EXPECT(
result[jss::error_message] == "Fee of 8889 exceeds the requested tx limit of 100");
}
}
@@ -613,6 +666,7 @@ class Simulate_test : public beast::unit_test::Suite
params[jss::tx_json] = jt.jv;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error] == "notImpl");
BEAST_EXPECT(resp[jss::result][jss::error_code] == RpcNotImpl);
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Not implemented.");
}
+3
View File
@@ -8,6 +8,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Seed.h>
#include <xrpl/protocol/jss.h>
@@ -39,6 +40,7 @@ public:
params[jss::fail_hard] = param;
auto const jrr = env.rpc("json", "submit", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'fail_hard', not boolean.");
}
@@ -50,6 +52,7 @@ public:
params[jss::fail_hard] = param;
auto const jrr = env.rpc("json", "submit", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'fail_hard', not boolean.");
}
};
+36
View File
@@ -31,6 +31,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
@@ -621,6 +622,7 @@ public:
{
auto const jr = env.rpc("json", method, "{}")[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -633,6 +635,7 @@ public:
if (subscribe)
{
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Failed to parse url.");
}
// else TODO: why isn't this an error for unsubscribe ?
@@ -646,6 +649,7 @@ public:
if (subscribe)
{
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Only http and https is supported.");
}
}
@@ -656,6 +660,7 @@ public:
jv[jss::url] = "no-url";
auto const jr = envNonadmin.rpc("json", method, to_string(jv))[jss::result];
BEAST_EXPECT(jr[jss::error] == "noPermission");
BEAST_EXPECT(jr[jss::error_code] == RpcNoPermission);
BEAST_EXPECT(jr[jss::error_message] == "You don't have permission for this command.");
}
@@ -677,6 +682,7 @@ public:
jv[f] = nonArray;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -685,6 +691,7 @@ public:
jv[f] = json::ValueType::Array;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "actMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
}
}
@@ -695,6 +702,7 @@ public:
jv[jss::mpt_issuances] = nonArray;
auto jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -704,6 +712,7 @@ public:
jv[jss::mpt_issuances][0u] = 1;
auto jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -713,6 +722,7 @@ public:
jv[jss::mpt_issuances][0u] = "not-an-mpt-issuance-id";
auto jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -722,6 +732,7 @@ public:
jv[jss::mpt_issuances][0u] = "0123456789ABCDEF";
auto jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -731,6 +742,7 @@ public:
jv[jss::books] = nonArray;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -740,6 +752,7 @@ public:
jv[jss::books][0u] = 1;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -752,6 +765,7 @@ public:
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcSrcCurMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Source currency is malformed.");
}
@@ -764,6 +778,7 @@ public:
jv[jss::books][0u][jss::taker_pays][jss::currency] = "ZZZZ";
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcSrcCurMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Source currency is malformed.");
}
@@ -777,6 +792,7 @@ public:
jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcSrcIsrMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Source issuer is malformed.");
}
@@ -790,6 +806,7 @@ public:
jv[jss::books][0u][jss::taker_pays][jss::issuer] = Account{"gateway"}.human() + "DEAD";
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcSrcIsrMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Source issuer is malformed.");
}
@@ -803,6 +820,7 @@ public:
auto const jr = wsc->invoke(method, jv)[jss::result];
// NOTE: this error is slightly incongruous with the equivalent source currency error
BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcDstAmtMalformed);
BEAST_EXPECT(
jr[jss::error_message] == "Destination amount/currency/issuer is malformed.");
}
@@ -818,6 +836,7 @@ public:
// NOTE: this error is slightly incongruous with the
// equivalent source currency error
BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcDstAmtMalformed);
BEAST_EXPECT(
jr[jss::error_message] == "Destination amount/currency/issuer is malformed.");
}
@@ -832,6 +851,7 @@ public:
jv[jss::books][0u][jss::taker_gets][jss::issuer] = 1;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcDstIsrMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Destination issuer is malformed.");
}
@@ -845,6 +865,7 @@ public:
jv[jss::books][0u][jss::taker_gets][jss::issuer] = Account{"gateway"}.human() + "DEAD";
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jr[jss::error_code] == RpcDstIsrMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Destination issuer is malformed.");
}
@@ -858,6 +879,7 @@ public:
Account{"gateway"}["USD"](1).value().getJson(JsonOptions::Values::IncludeDate);
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "badMarket");
BEAST_EXPECT(jr[jss::error_code] == RpcBadMarket);
BEAST_EXPECT(jr[jss::error_message] == "No such market.");
}
@@ -867,6 +889,7 @@ public:
jv[jss::streams] = nonArray;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
}
@@ -876,6 +899,7 @@ public:
jv[jss::streams][0u] = 1;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "malformedStream");
BEAST_EXPECT(jr[jss::error_code] == RpcStreamMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
}
@@ -885,6 +909,7 @@ public:
jv[jss::streams][0u] = "not_a_stream";
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECT(jr[jss::error] == "malformedStream");
BEAST_EXPECT(jr[jss::error_code] == RpcStreamMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
}
@@ -901,6 +926,7 @@ public:
jv[jss::books][0u][jss::taker] = 1;
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECTS(jr[jss::error] == "actMalformed", jr.toStyledString());
BEAST_EXPECT(jr[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
}
@@ -915,6 +941,7 @@ public:
jv[jss::books][0u][jss::taker] = "not_an_account";
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECTS(jr[jss::error] == "actMalformed", jr.toStyledString());
BEAST_EXPECT(jr[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
}
@@ -929,6 +956,7 @@ public:
jv[jss::books][0u][jss::taker] = Account{"alice"}.human() + "DEAD";
auto const jr = wsc->invoke(method, jv)[jss::result];
BEAST_EXPECTS(jr[jss::error] == "actMalformed", jr.toStyledString());
BEAST_EXPECT(jr[jss::error_code] == RpcActMalformed);
BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
}
}
@@ -1784,6 +1812,7 @@ public:
auto const jr =
wsc->invoke("subscribe", accountsRequest(makeAccountStrings(6)))[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Too many subscriptions for this connection.");
}
@@ -1866,6 +1895,7 @@ public:
{
auto const jr = wsc->invoke("subscribe", req)[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Too many subscriptions for this connection.");
}
@@ -1916,6 +1946,7 @@ public:
auto const jr =
wsc->invoke("subscribe", accountHistoryRequest(alice.human()))[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
BEAST_EXPECT(jr[jss::error_message] != "Too many subscriptions for this connection.");
}
@@ -1951,6 +1982,7 @@ public:
auto const jr =
wsc->invoke("subscribe", accountHistoryRequest(bob.human()))[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Too many subscriptions for this connection.");
}
}
@@ -1974,6 +2006,7 @@ public:
auto const jr = wsc->invoke(
"subscribe", mptIssuancesRequest(makeMPTIssuanceStrings(6)))[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Too many subscriptions for this connection.");
}
@@ -2017,6 +2050,7 @@ public:
auto const jr = wsc->invoke(
"subscribe", mptIssuancesRequest(makeMPTIssuanceStrings(1, 100)))[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Too many subscriptions for this connection.");
}
}
@@ -2041,6 +2075,7 @@ public:
auto const jr = wsc->invoke(
"subscribe", mptIssuancesRequest(makeMPTIssuanceStrings(3)))[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Too many subscriptions for this connection.");
// Two more issuances do fit the remaining allowance.
@@ -2062,6 +2097,7 @@ public:
auto const jr =
wsc->invoke("subscribe", accountsRequest(makeAccountStrings(1)))[jss::result];
BEAST_EXPECT(jr[jss::error] == "invalidParams");
BEAST_EXPECT(jr[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(jr[jss::error_message] == "Too many subscriptions for this connection.");
}
}
+42 -14
View File
@@ -38,16 +38,20 @@ class TransactionEntry_test : public beast::unit_test::Suite
{
// no params
auto const result = env.client().invoke("transaction_entry", {})[jss::result];
BEAST_EXPECT(result[jss::error] == "fieldNotFoundTransaction");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "fieldNotFoundTransaction");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
}
{
json::Value params{json::ValueType::Object};
params[jss::ledger] = 20;
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "lgrNotFound");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "lgrNotFound");
BEAST_EXPECT(result[jss::error_code] == RpcLgrNotFound);
BEAST_EXPECT(result[jss::error_message] == "ledgerNotFound");
}
{
@@ -55,8 +59,10 @@ class TransactionEntry_test : public beast::unit_test::Suite
params[jss::ledger] = "current";
params[jss::tx_hash] = "DEADBEEF";
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "notYetImplemented");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "notYetImplemented");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
}
{
@@ -65,8 +71,10 @@ class TransactionEntry_test : public beast::unit_test::Suite
params[jss::tx_hash] =
"E2FE8D4AF3FCC3944DDF6CD8CDDC5E3F0AD50863EF8919AFEF10CB6408CD4D05";
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "notYetImplemented");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "notYetImplemented");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
BEAST_EXPECT(result.isMember(jss::ledger_current_index));
BEAST_EXPECT(!result.isMember(jss::ledger_hash));
BEAST_EXPECT(result[jss::validated] == false);
@@ -78,8 +86,10 @@ class TransactionEntry_test : public beast::unit_test::Suite
params[jss::tx_hash] = "DEADBEEF";
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(!result[jss::ledger_hash].asString().empty());
BEAST_EXPECT(result[jss::error] == "malformedRequest");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "malformedRequest");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
}
for (auto const type : {json::ValueType::Object, json::ValueType::Array})
@@ -88,8 +98,10 @@ class TransactionEntry_test : public beast::unit_test::Suite
params[jss::ledger] = "closed";
params[jss::tx_hash] = json::Value{type};
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "malformedRequest");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "malformedRequest");
BEAST_EXPECT(!result.isMember(jss::error_code));
BEAST_EXPECT(result[jss::error_message].isNull());
}
std::string const txHash{
@@ -100,58 +112,74 @@ class TransactionEntry_test : public beast::unit_test::Suite
// No arguments
json::Value const result{env.rpc("transaction_entry")};
BEAST_EXPECT(result[jss::ledger_hash].asString().empty());
BEAST_EXPECT(result[jss::error] == "badSyntax");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "badSyntax");
BEAST_EXPECT(result[jss::error_code] == RpcBadSyntax);
BEAST_EXPECT(result[jss::error_message] == "Syntax error.");
}
{
// One argument
json::Value const result{env.rpc("transaction_entry", txHash)};
BEAST_EXPECT(result[jss::error] == "badSyntax");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "badSyntax");
BEAST_EXPECT(result[jss::error_code] == RpcBadSyntax);
BEAST_EXPECT(result[jss::error_message] == "Syntax error.");
}
{
// First argument with too few characters
json::Value const result{env.rpc("transaction_entry", txHash.substr(1), "closed")};
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
{
// First argument with too many characters
json::Value const result{env.rpc("transaction_entry", txHash + "A", "closed")};
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
{
// Second argument not valid
json::Value const result{env.rpc("transaction_entry", txHash, "closer")};
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
{
// Ledger index of 0 is not valid
json::Value const result{env.rpc("transaction_entry", txHash, "0")};
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
{
// Three arguments
json::Value const result{env.rpc("transaction_entry", txHash, "closed", "extra")};
BEAST_EXPECT(result[jss::error] == "badSyntax");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "badSyntax");
BEAST_EXPECT(result[jss::error_code] == RpcBadSyntax);
BEAST_EXPECT(result[jss::error_message] == "Syntax error.");
}
{
// Valid structure, but transaction not found.
json::Value const result{env.rpc("transaction_entry", txHash, "closed")};
BEAST_EXPECT(!result[jss::result][jss::ledger_hash].asString().empty());
BEAST_EXPECT(result[jss::result][jss::error] == "transactionNotFound");
BEAST_EXPECT(result[jss::result][jss::status] == "error");
BEAST_EXPECT(result[jss::result][jss::error] == "transactionNotFound");
BEAST_EXPECT(!result[jss::result].isMember(jss::error_code));
BEAST_EXPECT(result[jss::result][jss::error_message].isNull());
}
}
+11 -3
View File
@@ -7,6 +7,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/jss.h>
@@ -30,8 +31,10 @@ class TransactionHistory_test : public beast::unit_test::Suite
{
// no params
auto const result = env.client().invoke("tx_history", {})[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
}
{
@@ -39,8 +42,11 @@ class TransactionHistory_test : public beast::unit_test::Suite
json::Value params{json::ValueType::Object};
params[jss::start] = 10001; // limited to <= 10000 for non admin
auto const result = env.client().invoke("tx_history", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "noPermission");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "noPermission");
BEAST_EXPECT(result[jss::error_code] == RpcNoPermission);
BEAST_EXPECT(
result[jss::error_message] == "You don't have permission for this command.");
}
}
@@ -54,8 +60,10 @@ class TransactionHistory_test : public beast::unit_test::Suite
json::Value params{json::ValueType::Object};
params[jss::api_version] = 2;
auto const result = env.client().invoke("tx_history", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "unknownCmd");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(result[jss::error] == "unknownCmd");
BEAST_EXPECT(result[jss::error_code] == RpcUnknownCommand);
BEAST_EXPECT(result[jss::error_message] == "Unknown method.");
}
void
+76 -54
View File
@@ -110,9 +110,10 @@ class Transaction_test : public beast::unit_test::Suite
to_string(startLegSeq),
to_string(endLegSeq + deltaEndSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
if (deltaEndSeq != 0)
{
@@ -153,9 +154,10 @@ class Transaction_test : public beast::unit_test::Suite
to_string(startLegSeq),
to_string(endLegSeq + deltaEndSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
}
@@ -168,9 +170,10 @@ class Transaction_test : public beast::unit_test::Suite
to_string(startLegSeq),
to_string(endLegSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
}
@@ -184,9 +187,10 @@ class Transaction_test : public beast::unit_test::Suite
to_string(startLegSeq),
to_string(deletedLedger - 1));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
BEAST_EXPECT(result[jss::result][jss::searched_all].asBool());
}
@@ -213,9 +217,10 @@ class Transaction_test : public beast::unit_test::Suite
to_string(deletedLedger - 1),
to_string(startLegSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -229,9 +234,10 @@ class Transaction_test : public beast::unit_test::Suite
to_string(-1),
to_string(deletedLedger - 1));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -241,9 +247,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result = env.rpc(
command, to_string(tx->getTransactionID()), binary, to_string(-20), to_string(-10));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -253,9 +260,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result =
env.rpc(command, to_string(tx->getTransactionID()), binary, to_string(20));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -283,9 +291,10 @@ class Transaction_test : public beast::unit_test::Suite
to_string(startLegSeq),
to_string(startLegSeq + 1001));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == excessive);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == excessive);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcExcessiveLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range exceeds 1000.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -351,9 +360,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result = env.rpc(
command, ctid, binary, to_string(startLegSeq), to_string(endLegSeq + deltaEndSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
if (deltaEndSeq != 0)
{
@@ -394,9 +404,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result = env.rpc(
command, ctid, binary, to_string(startLegSeq), to_string(endLegSeq + deltaEndSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
}
@@ -406,9 +417,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result =
env.rpc(command, ctid, to_string(startLegSeq), to_string(endLegSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
}
@@ -419,9 +431,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result =
env.rpc(command, ctid, to_string(startLegSeq), to_string(deletedLedger - 1));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == notFound);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcTxnNotFound);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Transaction not found.");
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
}
@@ -447,9 +460,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result = env.rpc(
command, ctid, binary, to_string(deletedLedger - 1), to_string(startLegSeq));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -459,9 +473,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result =
env.rpc(command, ctid, binary, to_string(-1), to_string(deletedLedger - 1));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -470,9 +485,10 @@ class Transaction_test : public beast::unit_test::Suite
{
auto const result = env.rpc(command, ctid, binary, to_string(-20), to_string(-10));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -481,9 +497,10 @@ class Transaction_test : public beast::unit_test::Suite
{
auto const result = env.rpc(command, ctid, binary, to_string(20));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == invalid);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcInvalidLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range is invalid.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -507,9 +524,10 @@ class Transaction_test : public beast::unit_test::Suite
auto const result = env.rpc(
command, ctid, binary, to_string(startLegSeq), to_string(startLegSeq + 1001));
BEAST_EXPECT(
result[jss::result][jss::status] == jss::error &&
result[jss::result][jss::error] == excessive);
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
BEAST_EXPECT(result[jss::result][jss::error] == excessive);
BEAST_EXPECT(result[jss::result][jss::error_code] == RpcExcessiveLgrRange);
BEAST_EXPECT(result[jss::result][jss::error_message] == "Ledger range exceeds 1000.");
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
}
@@ -945,6 +963,7 @@ class Transaction_test : public beast::unit_test::Suite
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Missing field 'tx_json.NetworkID'.");
}
@@ -958,6 +977,7 @@ class Transaction_test : public beast::unit_test::Suite
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
}
@@ -971,6 +991,7 @@ class Transaction_test : public beast::unit_test::Suite
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
}
@@ -985,6 +1006,7 @@ class Transaction_test : public beast::unit_test::Suite
auto result = signFor(env, tx);
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
}
+3
View File
@@ -7,6 +7,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/config/Constants.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <memory>
@@ -27,6 +28,8 @@ public:
{
Env env(*this);
auto const info = env.rpc("validator_info");
BEAST_EXPECT(info[jss::result][jss::error] == "invalidParams");
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidParams);
BEAST_EXPECT(info[jss::result][jss::error_message] == "not a validator");
}
}
+57 -23
View File
@@ -306,33 +306,57 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En
// 1 - FAILS with wrong pass
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
jrr = makeAdminRequest(env, proto, *user, *password + "_")[jss::result];
BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
BEAST_EXPECT(
jrr["error_message"] == protoWs ? "Bad credentials."
: "You don't have permission for this command.");
if (protoWs)
{
BEAST_EXPECT(jrr[jss::error] == "forbidden");
BEAST_EXPECT(jrr[jss::error_code] == RpcForbidden);
BEAST_EXPECT(jrr[jss::error_message] == "Bad credentials.");
}
else
{
BEAST_EXPECT(jrr.isNull());
}
// 2 - FAILS with password in an object
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
jrr = makeAdminRequest(env, proto, *user, *password, true)[jss::result];
BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
BEAST_EXPECT(
jrr["error_message"] == protoWs ? "Bad credentials."
: "You don't have permission for this command.");
if (protoWs)
{
BEAST_EXPECT(jrr[jss::error] == "forbidden");
BEAST_EXPECT(jrr[jss::error_code] == RpcForbidden);
BEAST_EXPECT(jrr[jss::error_message] == "Bad credentials.");
}
else
{
BEAST_EXPECT(jrr.isNull());
}
// 3 - FAILS with wrong user
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
jrr = makeAdminRequest(env, proto, *user + "_", *password)[jss::result];
BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
BEAST_EXPECT(
jrr["error_message"] == protoWs ? "Bad credentials."
: "You don't have permission for this command.");
if (protoWs)
{
BEAST_EXPECT(jrr[jss::error] == "forbidden");
BEAST_EXPECT(jrr[jss::error_code] == RpcForbidden);
BEAST_EXPECT(jrr[jss::error_message] == "Bad credentials.");
}
else
{
BEAST_EXPECT(jrr.isNull());
}
// 4 - FAILS no credentials
jrr = makeAdminRequest(env, proto, "", "")[jss::result];
BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
BEAST_EXPECT(
jrr["error_message"] == protoWs ? "Bad credentials."
: "You don't have permission for this command.");
if (protoWs)
{
BEAST_EXPECT(jrr[jss::error] == "forbidden");
BEAST_EXPECT(jrr[jss::error_code] == RpcForbidden);
BEAST_EXPECT(jrr[jss::error_message] == "Bad credentials.");
}
else
{
BEAST_EXPECT(jrr.isNull());
}
// 5 - SUCCEEDS with proper credentials
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
@@ -353,10 +377,16 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En
{
// 1 - FAILS - admin disabled
jrr = makeAdminRequest(env, proto, "", "")[jss::result];
BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
BEAST_EXPECT(
jrr["error_message"] == protoWs ? "Bad credentials."
: "You don't have permission for this command.");
if (protoWs)
{
BEAST_EXPECT(jrr[jss::error] == "forbidden");
BEAST_EXPECT(jrr[jss::error_code] == RpcForbidden);
BEAST_EXPECT(jrr[jss::error_message] == "Bad credentials.");
}
else
{
BEAST_EXPECT(jrr.isNull());
}
}
}
@@ -730,7 +760,9 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En
{ // send invalid json
auto resp = sendAndParse("NOT JSON");
BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] == "jsonInvalid");
BEAST_EXPECT(resp[jss::error] == "jsonInvalid");
BEAST_EXPECT(!resp.isMember(jss::error_code));
BEAST_EXPECT(resp[jss::error_message].isNull());
BEAST_EXPECT(!resp.isMember(jss::status));
}
@@ -739,8 +771,10 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En
jv[jss::command] = "foo";
jv[jss::method] = "bar";
auto resp = sendAndParse(to_string(jv));
BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] == "missingCommand");
BEAST_EXPECT(resp.isMember(jss::status) && resp[jss::status] == "error");
BEAST_EXPECT(resp[jss::status] == "error");
BEAST_EXPECT(resp[jss::error] == "missingCommand");
BEAST_EXPECT(!resp.isMember(jss::error_code));
BEAST_EXPECT(resp[jss::error_message].isNull());
}
{ // send a ping (not an error)
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff. Show more