mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 02:25:53 +00:00
Add DepositPreauth ledger type and transaction (RIPD-1624):
The lsfDepositAuth flag limits the AccountIDs that can deposit into the account that has the flag set. The original design only allowed deposits to complete if the account with the flag set also signed the transaction that caused the deposit. The DepositPreauth ledger type allows an account with the lsfDepositAuth flag set to preauthorize additional accounts. This preauthorization allows them to sign deposits as well. An account can add DepositPreauth objects to the ledger (and remove them as well) using the DepositPreauth transaction.
This commit is contained in:
@@ -148,6 +148,7 @@ void printHelp (const po::options_description& desc)
|
|||||||
" channel_verify <public_key> <channel_id> <drops> <signature>\n"
|
" channel_verify <public_key> <channel_id> <drops> <signature>\n"
|
||||||
" connect <ip> [<port>]\n"
|
" connect <ip> [<port>]\n"
|
||||||
" consensus_info\n"
|
" consensus_info\n"
|
||||||
|
" deposit_authorized <source_account> <destination_account> [<ledger>]"
|
||||||
" feature [<feature> [accept|reject]]\n"
|
" feature [<feature> [accept|reject]]\n"
|
||||||
" fetch_info [clear]\n"
|
" fetch_info [clear]\n"
|
||||||
" gateway_balances [<ledger>] <issuer_account> [ <hotwallet> [ <hotwallet> ]]\n"
|
" gateway_balances [<ledger>] <issuer_account> [ <hotwallet> [ <hotwallet> ]]\n"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class CancelCheck
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CancelCheck (ApplyContext& ctx)
|
explicit CancelCheck (ApplyContext& ctx)
|
||||||
: Transactor (ctx)
|
: Transactor (ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class CancelOffer
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CancelOffer (ApplyContext& ctx)
|
explicit CancelOffer (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class CancelTicket
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CancelTicket (ApplyContext& ctx)
|
explicit CancelTicket (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class CashCheck
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CashCheck (ApplyContext& ctx)
|
explicit CashCheck (ApplyContext& ctx)
|
||||||
: Transactor (ctx)
|
: Transactor (ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class Change
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Change (ApplyContext& ctx)
|
explicit Change (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class CreateCheck
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CreateCheck (ApplyContext& ctx)
|
explicit CreateCheck (ApplyContext& ctx)
|
||||||
: Transactor (ctx)
|
: Transactor (ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ CreateOffer::preclaim(PreclaimContext const& ctx)
|
|||||||
//
|
//
|
||||||
// The return code change is attached to featureChecks as a convenience.
|
// The return code change is attached to featureChecks as a convenience.
|
||||||
// The change is not big enough to deserve its own amendment.
|
// The change is not big enough to deserve its own amendment.
|
||||||
return ctx.view.rules().enabled(featureChecks)
|
return ctx.view.rules().enabled(featureDepositPreauth)
|
||||||
? TER {tecEXPIRED}
|
? TER {tecEXPIRED}
|
||||||
: TER {tesSUCCESS};
|
: TER {tesSUCCESS};
|
||||||
}
|
}
|
||||||
@@ -237,10 +237,10 @@ CreateOffer::checkAcceptAsset(ReadView const& view,
|
|||||||
: TER {tecNO_ISSUER};
|
: TER {tecNO_ISSUER};
|
||||||
}
|
}
|
||||||
|
|
||||||
// This code is attached to the FlowCross amendment as a matter of
|
// This code is attached to the DepositPreauth amendment as a matter of
|
||||||
// convenience. The change is not significant enough to deserve its
|
// convenience. The change is not significant enough to deserve its
|
||||||
// own amendment.
|
// own amendment.
|
||||||
if (view.rules().enabled(featureFlowCross) && (issue.account == id))
|
if (view.rules().enabled(featureDepositPreauth) && (issue.account == id))
|
||||||
// An account can always accept its own issuance.
|
// An account can always accept its own issuance.
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
|
|
||||||
@@ -1106,10 +1106,10 @@ CreateOffer::applyGuts (Sandbox& sb, Sandbox& sbCancel)
|
|||||||
// If the offer has expired, the transaction has successfully
|
// If the offer has expired, the transaction has successfully
|
||||||
// done nothing, so short circuit from here.
|
// done nothing, so short circuit from here.
|
||||||
//
|
//
|
||||||
// The return code change is attached to featureChecks as a convenience.
|
// The return code change is attached to featureDepositPreauth as a
|
||||||
// The change is not big enough to deserve its own amendment.
|
// convenience. The change is not big enough to deserve a fix code.
|
||||||
TER const ter {ctx_.view().rules().enabled(
|
TER const ter {ctx_.view().rules().enabled(
|
||||||
featureChecks) ? TER {tecEXPIRED} : TER {tesSUCCESS}};
|
featureDepositPreauth) ? TER {tecEXPIRED} : TER {tesSUCCESS}};
|
||||||
return{ ter, true };
|
return{ ter, true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class CreateOffer
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/** Construct a Transactor subclass that creates an offer in the ledger. */
|
/** Construct a Transactor subclass that creates an offer in the ledger. */
|
||||||
CreateOffer (ApplyContext& ctx)
|
explicit CreateOffer (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
, stepCounter_ (1000, j_)
|
, stepCounter_ (1000, j_)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class CreateTicket
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CreateTicket (ApplyContext& ctx)
|
explicit CreateTicket (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
189
src/ripple/app/tx/impl/DepositPreauth.cpp
Normal file
189
src/ripple/app/tx/impl/DepositPreauth.cpp
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2018 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <ripple/app/tx/impl/DepositPreauth.h>
|
||||||
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/protocol/Feature.h>
|
||||||
|
#include <ripple/protocol/Indexes.h>
|
||||||
|
#include <ripple/protocol/st.h>
|
||||||
|
#include <ripple/protocol/TxFlags.h>
|
||||||
|
#include <ripple/ledger/View.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
NotTEC
|
||||||
|
DepositPreauth::preflight (PreflightContext const& ctx)
|
||||||
|
{
|
||||||
|
if (! ctx.rules.enabled (featureDepositPreauth))
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
|
auto const ret = preflight1 (ctx);
|
||||||
|
if (!isTesSuccess (ret))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
auto& tx = ctx.tx;
|
||||||
|
auto& j = ctx.j;
|
||||||
|
|
||||||
|
if (tx.getFlags() & tfUniversalMask)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) <<
|
||||||
|
"Malformed transaction: Invalid flags set.";
|
||||||
|
return temINVALID_FLAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const optAuth = ctx.tx[~sfAuthorize];
|
||||||
|
auto const optUnauth = ctx.tx[~sfUnauthorize];
|
||||||
|
if (static_cast<bool>(optAuth) == static_cast<bool>(optUnauth))
|
||||||
|
{
|
||||||
|
// Either both fields are present or neither field is present. In
|
||||||
|
// either case the transaction is malformed.
|
||||||
|
JLOG(j.trace()) <<
|
||||||
|
"Malformed transaction: "
|
||||||
|
"Invalid Authorize and Unauthorize field combination.";
|
||||||
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that the passed account is valid.
|
||||||
|
AccountID const target {optAuth ? *optAuth : *optUnauth};
|
||||||
|
if (target == beast::zero)
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) <<
|
||||||
|
"Malformed transaction: Authorized or Unauthorized field zeroed.";
|
||||||
|
return temINVALID_ACCOUNT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An account may not preauthorize itself.
|
||||||
|
if (optAuth && (target == ctx.tx[sfAccount]))
|
||||||
|
{
|
||||||
|
JLOG(j.trace()) <<
|
||||||
|
"Malformed transaction: Attempting to DepositPreauth self.";
|
||||||
|
return temCANNOT_PREAUTH_SELF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return preflight2 (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
DepositPreauth::preclaim(PreclaimContext const& ctx)
|
||||||
|
{
|
||||||
|
// Determine which operation we're performing: authorizing or unauthorizing.
|
||||||
|
if (ctx.tx.isFieldPresent (sfAuthorize))
|
||||||
|
{
|
||||||
|
// Verify that the Authorize account is present in the ledger.
|
||||||
|
AccountID const auth {ctx.tx[sfAuthorize]};
|
||||||
|
if (! ctx.view.exists (keylet::account (auth)))
|
||||||
|
return tecNO_TARGET;
|
||||||
|
|
||||||
|
// Verify that the Preauth entry they asked to add is not already
|
||||||
|
// in the ledger.
|
||||||
|
if (ctx.view.exists (
|
||||||
|
keylet::depositPreauth (ctx.tx[sfAccount], auth)))
|
||||||
|
return tecDUPLICATE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Verify that the Preauth entry they asked to remove is in the ledger.
|
||||||
|
AccountID const unauth {ctx.tx[sfUnauthorize]};
|
||||||
|
if (! ctx.view.exists (
|
||||||
|
keylet::depositPreauth (ctx.tx[sfAccount], unauth)))
|
||||||
|
return tecNO_ENTRY;
|
||||||
|
}
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
DepositPreauth::doApply ()
|
||||||
|
{
|
||||||
|
auto const sleOwner = view().peek (keylet::account (account_));
|
||||||
|
|
||||||
|
if (ctx_.tx.isFieldPresent (sfAuthorize))
|
||||||
|
{
|
||||||
|
// A preauth counts against the reserve of the issuing account, but we
|
||||||
|
// check the starting balance because we want to allow dipping into the
|
||||||
|
// reserve to pay fees.
|
||||||
|
{
|
||||||
|
STAmount const reserve {view().fees().accountReserve (
|
||||||
|
sleOwner->getFieldU32 (sfOwnerCount) + 1)};
|
||||||
|
|
||||||
|
if (mPriorBalance < reserve)
|
||||||
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preclaim already verified that the Preauth entry does not yet exist.
|
||||||
|
// Create and populate the Preauth entry.
|
||||||
|
AccountID const auth {ctx_.tx[sfAuthorize]};
|
||||||
|
auto slePreauth =
|
||||||
|
std::make_shared<SLE>(keylet::depositPreauth (account_, auth));
|
||||||
|
|
||||||
|
slePreauth->setAccountID (sfAccount, account_);
|
||||||
|
slePreauth->setAccountID (sfAuthorize, auth);
|
||||||
|
view().insert (slePreauth);
|
||||||
|
|
||||||
|
auto viewJ = ctx_.app.journal ("View");
|
||||||
|
auto const page = view().dirInsert (keylet::ownerDir (account_),
|
||||||
|
slePreauth->key(), describeOwnerDir (account_));
|
||||||
|
|
||||||
|
JLOG(j_.trace())
|
||||||
|
<< "Adding DepositPreauth to owner directory "
|
||||||
|
<< to_string (slePreauth->key())
|
||||||
|
<< ": " << (page ? "success" : "failure");
|
||||||
|
|
||||||
|
if (! page)
|
||||||
|
return tecDIR_FULL;
|
||||||
|
|
||||||
|
slePreauth->setFieldU64 (sfOwnerNode, *page);
|
||||||
|
|
||||||
|
// If we succeeded, the new entry counts against the creator's reserve.
|
||||||
|
adjustOwnerCount (view(), sleOwner, 1, viewJ);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Verify that the Preauth entry they asked to remove is
|
||||||
|
// in the ledger.
|
||||||
|
AccountID const unauth {ctx_.tx[sfUnauthorize]};
|
||||||
|
uint256 const preauthIndex {getDepositPreauthIndex (account_, unauth)};
|
||||||
|
auto slePreauth = view().peek (keylet::depositPreauth (preauthIndex));
|
||||||
|
|
||||||
|
if (! slePreauth)
|
||||||
|
{
|
||||||
|
// Error should have been caught in preclaim.
|
||||||
|
JLOG(j_.warn()) << "Selected DepositPreauth does not exist.";
|
||||||
|
return tecNO_ENTRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto viewJ = ctx_.app.journal ("View");
|
||||||
|
std::uint64_t const page {(*slePreauth)[sfOwnerNode]};
|
||||||
|
if (! view().dirRemove (
|
||||||
|
keylet::ownerDir (account_), page, preauthIndex, true))
|
||||||
|
{
|
||||||
|
JLOG(j_.warn()) << "Unable to delete DepositPreauth from owner.";
|
||||||
|
return tefBAD_LEDGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we succeeded, update the DepositPreauth owner's reserve.
|
||||||
|
auto const sleOwner = view().peek (keylet::account (account_));
|
||||||
|
adjustOwnerCount (view(), sleOwner, -1, viewJ);
|
||||||
|
|
||||||
|
// Remove DepositPreauth from ledger.
|
||||||
|
view().erase (slePreauth);
|
||||||
|
}
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ripple
|
||||||
50
src/ripple/app/tx/impl/DepositPreauth.h
Normal file
50
src/ripple/app/tx/impl/DepositPreauth.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#ifndef RIPPLE_TX_DEPOSIT_PREAUTH_H_INCLUDED
|
||||||
|
#define RIPPLE_TX_DEPOSIT_PREAUTH_H_INCLUDED
|
||||||
|
|
||||||
|
#include <ripple/app/tx/impl/Transactor.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
class DepositPreauth
|
||||||
|
: public Transactor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit DepositPreauth (ApplyContext& ctx)
|
||||||
|
: Transactor(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
NotTEC
|
||||||
|
preflight (PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static
|
||||||
|
TER
|
||||||
|
preclaim(PreclaimContext const& ctx);
|
||||||
|
|
||||||
|
TER doApply () override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // ripple
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@@ -446,8 +446,9 @@ EscrowFinish::doApply()
|
|||||||
return tecCRYPTOCONDITION_ERROR;
|
return tecCRYPTOCONDITION_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Escrow payments cannot be used to fund accounts
|
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||||
auto const sled = ctx_.view().peek(keylet::account((*slep)[sfDestination]));
|
AccountID const destID = (*slep)[sfDestination];
|
||||||
|
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||||
if (! sled)
|
if (! sled)
|
||||||
return tecNO_DST;
|
return tecNO_DST;
|
||||||
|
|
||||||
@@ -456,10 +457,15 @@ EscrowFinish::doApply()
|
|||||||
// Is EscrowFinished authorized?
|
// Is EscrowFinished authorized?
|
||||||
if (sled->getFlags() & lsfDepositAuth)
|
if (sled->getFlags() & lsfDepositAuth)
|
||||||
{
|
{
|
||||||
// Authorized if Destination == Account, otherwise no permission.
|
// A destination account that requires authorization has two
|
||||||
AccountID const destID = (*slep)[sfDestination];
|
// ways to get an EscrowFinished into the account:
|
||||||
if (ctx_.tx[sfAccount] != destID)
|
// 1. If Account == Destination, or
|
||||||
return tecNO_PERMISSION;
|
// 2. If Account is deposit preauthorized by destination.
|
||||||
|
if (account_ != destID)
|
||||||
|
{
|
||||||
|
if (! view().exists (keylet::depositPreauth (destID, account_)))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,7 +485,7 @@ EscrowFinish::doApply()
|
|||||||
if (ctx_.view ().rules().enabled(fix1523) && (*slep)[~sfDestinationNode])
|
if (ctx_.view ().rules().enabled(fix1523) && (*slep)[~sfDestinationNode])
|
||||||
{
|
{
|
||||||
TER const ter = dirDelete(ctx_.view(), true,
|
TER const ter = dirDelete(ctx_.view(), true,
|
||||||
(*slep)[sfDestinationNode], keylet::ownerDir((*slep)[sfDestination]),
|
(*slep)[sfDestinationNode], keylet::ownerDir(destID),
|
||||||
k.key, false, false, ctx_.app.journal ("View"));
|
k.key, false, false, ctx_.app.journal ("View"));
|
||||||
if (! isTesSuccess(ter))
|
if (! isTesSuccess(ter))
|
||||||
return ter;
|
return ter;
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ LedgerEntryTypesMatch::visitEntry(
|
|||||||
case ltESCROW:
|
case ltESCROW:
|
||||||
case ltPAYCHAN:
|
case ltPAYCHAN:
|
||||||
case ltCHECK:
|
case ltCHECK:
|
||||||
|
case ltDEPOSIT_PREAUTH:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
invalidTypeAdded_ = true;
|
invalidTypeAdded_ = true;
|
||||||
|
|||||||
@@ -472,12 +472,18 @@ PayChanClaim::doApply()
|
|||||||
(txAccount == src && (sled->getFlags() & lsfDisallowXRP)))
|
(txAccount == src && (sled->getFlags() & lsfDisallowXRP)))
|
||||||
return tecNO_TARGET;
|
return tecNO_TARGET;
|
||||||
|
|
||||||
// Check whether the destination account requires deposit authorization
|
// Check whether the destination account requires deposit authorization.
|
||||||
if (txAccount != dst)
|
if (depositAuth && (sled->getFlags() & lsfDepositAuth))
|
||||||
{
|
{
|
||||||
if (depositAuth &&
|
// A destination account that requires authorization has two
|
||||||
((sled->getFlags() & lsfDepositAuth) == lsfDepositAuth))
|
// ways to get a Payment Channel Claim into the account:
|
||||||
return tecNO_PERMISSION;
|
// 1. If Account == Destination, or
|
||||||
|
// 2. If Account is deposit preauthorized by destination.
|
||||||
|
if (txAccount != dst)
|
||||||
|
{
|
||||||
|
if (! view().exists (keylet::depositPreauth (dst, txAccount)))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||||
|
|||||||
@@ -350,12 +350,13 @@ Payment::doApply ()
|
|||||||
bool const reqDepositAuth = sleDst->getFlags() & lsfDepositAuth &&
|
bool const reqDepositAuth = sleDst->getFlags() & lsfDepositAuth &&
|
||||||
view().rules().enabled(featureDepositAuth);
|
view().rules().enabled(featureDepositAuth);
|
||||||
|
|
||||||
|
bool const depositPreauth = view().rules().enabled(featureDepositPreauth);
|
||||||
|
|
||||||
bool const bRipple = paths || sendMax || !saDstAmount.native ();
|
bool const bRipple = paths || sendMax || !saDstAmount.native ();
|
||||||
// XXX Should sendMax be sufficient to imply ripple?
|
|
||||||
|
|
||||||
// If the destination has lsfDepositAuth set, then only direct XRP
|
// If the destination has lsfDepositAuth set, then only direct XRP
|
||||||
// payments (no intermediate steps) are allowed to the destination.
|
// payments (no intermediate steps) are allowed to the destination.
|
||||||
if (bRipple && reqDepositAuth)
|
if (!depositPreauth && bRipple && reqDepositAuth)
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
|
|
||||||
if (bRipple)
|
if (bRipple)
|
||||||
@@ -363,6 +364,20 @@ Payment::doApply ()
|
|||||||
// Ripple payment with at least one intermediate step and uses
|
// Ripple payment with at least one intermediate step and uses
|
||||||
// transitive balances.
|
// transitive balances.
|
||||||
|
|
||||||
|
if (depositPreauth && reqDepositAuth)
|
||||||
|
{
|
||||||
|
// If depositPreauth is enabled, then an account that requires
|
||||||
|
// authorization has two ways to get an IOU Payment in:
|
||||||
|
// 1. If Account == Destination, or
|
||||||
|
// 2. If Account is deposit preauthorized by destination.
|
||||||
|
if (uDstAccountID != account_)
|
||||||
|
{
|
||||||
|
if (! view().exists (
|
||||||
|
keylet::depositPreauth (uDstAccountID, account_)))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copy paths into an editable class.
|
// Copy paths into an editable class.
|
||||||
STPathSet spsPaths = ctx_.tx.getFieldPathSet (sfPaths);
|
STPathSet spsPaths = ctx_.tx.getFieldPathSet (sfPaths);
|
||||||
|
|
||||||
@@ -450,15 +465,16 @@ Payment::doApply ()
|
|||||||
// source account has authority to deposit to the destination.
|
// source account has authority to deposit to the destination.
|
||||||
if (reqDepositAuth)
|
if (reqDepositAuth)
|
||||||
{
|
{
|
||||||
// Get the base reserve.
|
// If depositPreauth is enabled, then an account that requires
|
||||||
XRPAmount const dstReserve {view().fees().accountReserve (0)};
|
// authorization has three ways to get an XRP Payment in:
|
||||||
|
// 1. If Account == Destination, or
|
||||||
// If the destination's XRP balance is
|
// 2. If Account is deposit preauthorized by destination, or
|
||||||
// 1. below the base reserve and
|
// 3. If the destination's XRP balance is
|
||||||
// 2. the deposit amount is also below the base reserve,
|
// a. less than or equal to the base reserve and
|
||||||
|
// b. the deposit amount is less than or equal to the base reserve,
|
||||||
// then we allow the deposit.
|
// then we allow the deposit.
|
||||||
//
|
//
|
||||||
// This rule is designed to keep an account from getting wedged
|
// Rule 3 is designed to keep an account from getting wedged
|
||||||
// in an unusable state if it sets the lsfDepositAuth flag and
|
// in an unusable state if it sets the lsfDepositAuth flag and
|
||||||
// then consumes all of its XRP. Without the rule if an
|
// then consumes all of its XRP. Without the rule if an
|
||||||
// account with lsfDepositAuth set spent all of its XRP, it
|
// account with lsfDepositAuth set spent all of its XRP, it
|
||||||
@@ -467,9 +483,19 @@ Payment::doApply ()
|
|||||||
// We choose the base reserve as our bound because it is
|
// We choose the base reserve as our bound because it is
|
||||||
// a small number that seldom changes but is always sufficient
|
// a small number that seldom changes but is always sufficient
|
||||||
// to get the account un-wedged.
|
// to get the account un-wedged.
|
||||||
if (saDstAmount > dstReserve ||
|
if (uDstAccountID != account_)
|
||||||
sleDst->getFieldAmount (sfBalance) > dstReserve)
|
{
|
||||||
return tecNO_PERMISSION;
|
if (! view().exists (
|
||||||
|
keylet::depositPreauth (uDstAccountID, account_)))
|
||||||
|
{
|
||||||
|
// Get the base reserve.
|
||||||
|
XRPAmount const dstReserve {view().fees().accountReserve (0)};
|
||||||
|
|
||||||
|
if (saDstAmount > dstReserve ||
|
||||||
|
sleDst->getFieldAmount (sfBalance) > dstReserve)
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the arithmetic for the transfer and make the ledger change.
|
// Do the arithmetic for the transfer and make the ledger change.
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class Payment
|
|||||||
static std::size_t const MaxPathLength = 8;
|
static std::size_t const MaxPathLength = 8;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Payment (ApplyContext& ctx)
|
explicit Payment (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class SetAccount
|
|||||||
static std::size_t const DOMAIN_BYTES_MAX = 256;
|
static std::size_t const DOMAIN_BYTES_MAX = 256;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SetAccount (ApplyContext& ctx)
|
explicit SetAccount (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class SetRegularKey
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SetRegularKey (ApplyContext& ctx)
|
explicit SetRegularKey (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ private:
|
|||||||
std::vector<SignerEntries::SignerEntry> signers_;
|
std::vector<SignerEntries::SignerEntry> signers_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SetSignerList (ApplyContext& ctx)
|
explicit SetSignerList (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class SetTrust
|
|||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SetTrust (ApplyContext& ctx)
|
explicit SetTrust (ApplyContext& ctx)
|
||||||
: Transactor(ctx)
|
: Transactor(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <ripple/app/tx/impl/CreateCheck.h>
|
#include <ripple/app/tx/impl/CreateCheck.h>
|
||||||
#include <ripple/app/tx/impl/CreateOffer.h>
|
#include <ripple/app/tx/impl/CreateOffer.h>
|
||||||
#include <ripple/app/tx/impl/CreateTicket.h>
|
#include <ripple/app/tx/impl/CreateTicket.h>
|
||||||
|
#include <ripple/app/tx/impl/DepositPreauth.h>
|
||||||
#include <ripple/app/tx/impl/Escrow.h>
|
#include <ripple/app/tx/impl/Escrow.h>
|
||||||
#include <ripple/app/tx/impl/Payment.h>
|
#include <ripple/app/tx/impl/Payment.h>
|
||||||
#include <ripple/app/tx/impl/SetAccount.h>
|
#include <ripple/app/tx/impl/SetAccount.h>
|
||||||
@@ -47,6 +48,7 @@ invoke_preflight (PreflightContext const& ctx)
|
|||||||
case ttCHECK_CANCEL: return CancelCheck ::preflight(ctx);
|
case ttCHECK_CANCEL: return CancelCheck ::preflight(ctx);
|
||||||
case ttCHECK_CASH: return CashCheck ::preflight(ctx);
|
case ttCHECK_CASH: return CashCheck ::preflight(ctx);
|
||||||
case ttCHECK_CREATE: return CreateCheck ::preflight(ctx);
|
case ttCHECK_CREATE: return CreateCheck ::preflight(ctx);
|
||||||
|
case ttDEPOSIT_PREAUTH: return DepositPreauth ::preflight(ctx);
|
||||||
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
|
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
|
||||||
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
|
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
|
||||||
case ttESCROW_CREATE: return EscrowCreate ::preflight(ctx);
|
case ttESCROW_CREATE: return EscrowCreate ::preflight(ctx);
|
||||||
@@ -115,6 +117,7 @@ invoke_preclaim (PreclaimContext const& ctx)
|
|||||||
case ttCHECK_CANCEL: return invoke_preclaim<CancelCheck>(ctx);
|
case ttCHECK_CANCEL: return invoke_preclaim<CancelCheck>(ctx);
|
||||||
case ttCHECK_CASH: return invoke_preclaim<CashCheck>(ctx);
|
case ttCHECK_CASH: return invoke_preclaim<CashCheck>(ctx);
|
||||||
case ttCHECK_CREATE: return invoke_preclaim<CreateCheck>(ctx);
|
case ttCHECK_CREATE: return invoke_preclaim<CreateCheck>(ctx);
|
||||||
|
case ttDEPOSIT_PREAUTH: return invoke_preclaim<DepositPreauth>(ctx);
|
||||||
case ttOFFER_CANCEL: return invoke_preclaim<CancelOffer>(ctx);
|
case ttOFFER_CANCEL: return invoke_preclaim<CancelOffer>(ctx);
|
||||||
case ttOFFER_CREATE: return invoke_preclaim<CreateOffer>(ctx);
|
case ttOFFER_CREATE: return invoke_preclaim<CreateOffer>(ctx);
|
||||||
case ttESCROW_CREATE: return invoke_preclaim<EscrowCreate>(ctx);
|
case ttESCROW_CREATE: return invoke_preclaim<EscrowCreate>(ctx);
|
||||||
@@ -147,6 +150,7 @@ invoke_calculateBaseFee(PreclaimContext const& ctx)
|
|||||||
case ttCHECK_CANCEL: return CancelCheck::calculateBaseFee(ctx);
|
case ttCHECK_CANCEL: return CancelCheck::calculateBaseFee(ctx);
|
||||||
case ttCHECK_CASH: return CashCheck::calculateBaseFee(ctx);
|
case ttCHECK_CASH: return CashCheck::calculateBaseFee(ctx);
|
||||||
case ttCHECK_CREATE: return CreateCheck::calculateBaseFee(ctx);
|
case ttCHECK_CREATE: return CreateCheck::calculateBaseFee(ctx);
|
||||||
|
case ttDEPOSIT_PREAUTH: return DepositPreauth::calculateBaseFee(ctx);
|
||||||
case ttOFFER_CANCEL: return CancelOffer::calculateBaseFee(ctx);
|
case ttOFFER_CANCEL: return CancelOffer::calculateBaseFee(ctx);
|
||||||
case ttOFFER_CREATE: return CreateOffer::calculateBaseFee(ctx);
|
case ttOFFER_CREATE: return CreateOffer::calculateBaseFee(ctx);
|
||||||
case ttESCROW_CREATE: return EscrowCreate::calculateBaseFee(ctx);
|
case ttESCROW_CREATE: return EscrowCreate::calculateBaseFee(ctx);
|
||||||
@@ -192,6 +196,7 @@ invoke_calculateConsequences(STTx const& tx)
|
|||||||
case ttCHECK_CANCEL: return invoke_calculateConsequences<CancelCheck>(tx);
|
case ttCHECK_CANCEL: return invoke_calculateConsequences<CancelCheck>(tx);
|
||||||
case ttCHECK_CASH: return invoke_calculateConsequences<CashCheck>(tx);
|
case ttCHECK_CASH: return invoke_calculateConsequences<CashCheck>(tx);
|
||||||
case ttCHECK_CREATE: return invoke_calculateConsequences<CreateCheck>(tx);
|
case ttCHECK_CREATE: return invoke_calculateConsequences<CreateCheck>(tx);
|
||||||
|
case ttDEPOSIT_PREAUTH: return invoke_calculateConsequences<DepositPreauth>(tx);
|
||||||
case ttOFFER_CANCEL: return invoke_calculateConsequences<CancelOffer>(tx);
|
case ttOFFER_CANCEL: return invoke_calculateConsequences<CancelOffer>(tx);
|
||||||
case ttOFFER_CREATE: return invoke_calculateConsequences<CreateOffer>(tx);
|
case ttOFFER_CREATE: return invoke_calculateConsequences<CreateOffer>(tx);
|
||||||
case ttESCROW_CREATE: return invoke_calculateConsequences<EscrowCreate>(tx);
|
case ttESCROW_CREATE: return invoke_calculateConsequences<EscrowCreate>(tx);
|
||||||
@@ -222,26 +227,27 @@ invoke_apply (ApplyContext& ctx)
|
|||||||
{
|
{
|
||||||
switch(ctx.tx.getTxnType())
|
switch(ctx.tx.getTxnType())
|
||||||
{
|
{
|
||||||
case ttACCOUNT_SET: { SetAccount p(ctx); return p(); }
|
case ttACCOUNT_SET: { SetAccount p(ctx); return p(); }
|
||||||
case ttCHECK_CANCEL: { CancelCheck p(ctx); return p(); }
|
case ttCHECK_CANCEL: { CancelCheck p(ctx); return p(); }
|
||||||
case ttCHECK_CASH: { CashCheck p(ctx); return p(); }
|
case ttCHECK_CASH: { CashCheck p(ctx); return p(); }
|
||||||
case ttCHECK_CREATE: { CreateCheck p(ctx); return p(); }
|
case ttCHECK_CREATE: { CreateCheck p(ctx); return p(); }
|
||||||
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
|
case ttDEPOSIT_PREAUTH: { DepositPreauth p(ctx); return p(); }
|
||||||
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
|
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
|
||||||
case ttESCROW_CREATE: { EscrowCreate p(ctx); return p(); }
|
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
|
||||||
case ttESCROW_FINISH: { EscrowFinish p(ctx); return p(); }
|
case ttESCROW_CREATE: { EscrowCreate p(ctx); return p(); }
|
||||||
case ttESCROW_CANCEL: { EscrowCancel p(ctx); return p(); }
|
case ttESCROW_FINISH: { EscrowFinish p(ctx); return p(); }
|
||||||
case ttPAYCHAN_CLAIM: { PayChanClaim p(ctx); return p(); }
|
case ttESCROW_CANCEL: { EscrowCancel p(ctx); return p(); }
|
||||||
case ttPAYCHAN_CREATE: { PayChanCreate p(ctx); return p(); }
|
case ttPAYCHAN_CLAIM: { PayChanClaim p(ctx); return p(); }
|
||||||
case ttPAYCHAN_FUND: { PayChanFund p(ctx); return p(); }
|
case ttPAYCHAN_CREATE: { PayChanCreate p(ctx); return p(); }
|
||||||
case ttPAYMENT: { Payment p(ctx); return p(); }
|
case ttPAYCHAN_FUND: { PayChanFund p(ctx); return p(); }
|
||||||
case ttREGULAR_KEY_SET: { SetRegularKey p(ctx); return p(); }
|
case ttPAYMENT: { Payment p(ctx); return p(); }
|
||||||
case ttSIGNER_LIST_SET: { SetSignerList p(ctx); return p(); }
|
case ttREGULAR_KEY_SET: { SetRegularKey p(ctx); return p(); }
|
||||||
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }
|
case ttSIGNER_LIST_SET: { SetSignerList p(ctx); return p(); }
|
||||||
case ttTICKET_CREATE: { CreateTicket p(ctx); return p(); }
|
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }
|
||||||
case ttTRUST_SET: { SetTrust p(ctx); return p(); }
|
case ttTICKET_CREATE: { CreateTicket p(ctx); return p(); }
|
||||||
|
case ttTRUST_SET: { SetTrust p(ctx); return p(); }
|
||||||
case ttAMENDMENT:
|
case ttAMENDMENT:
|
||||||
case ttFEE: { Change p(ctx); return p(); }
|
case ttFEE: { Change p(ctx); return p(); }
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
return { temUNKNOWN, false };
|
return { temUNKNOWN, false };
|
||||||
|
|||||||
@@ -403,6 +403,19 @@ private:
|
|||||||
return jvRequest;
|
return jvRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deposit_authorized <source_account> <destination_account> [<ledger>]
|
||||||
|
Json::Value parseDepositAuthorized (Json::Value const& jvParams)
|
||||||
|
{
|
||||||
|
Json::Value jvRequest (Json::objectValue);
|
||||||
|
jvRequest[jss::source_account] = jvParams[0u].asString ();
|
||||||
|
jvRequest[jss::destination_account] = jvParams[1u].asString ();
|
||||||
|
|
||||||
|
if (jvParams.size () == 3)
|
||||||
|
jvParseLedger (jvRequest, jvParams[2u].asString ());
|
||||||
|
|
||||||
|
return jvRequest;
|
||||||
|
}
|
||||||
|
|
||||||
// Return an error for attemping to subscribe/unsubscribe via RPC.
|
// Return an error for attemping to subscribe/unsubscribe via RPC.
|
||||||
Json::Value parseEvented (Json::Value const& jvParams)
|
Json::Value parseEvented (Json::Value const& jvParams)
|
||||||
{
|
{
|
||||||
@@ -1071,9 +1084,10 @@ public:
|
|||||||
{ "channel_verify", &RPCParser::parseChannelVerify, 4, 4 },
|
{ "channel_verify", &RPCParser::parseChannelVerify, 4, 4 },
|
||||||
{ "connect", &RPCParser::parseConnect, 1, 2 },
|
{ "connect", &RPCParser::parseConnect, 1, 2 },
|
||||||
{ "consensus_info", &RPCParser::parseAsIs, 0, 0 },
|
{ "consensus_info", &RPCParser::parseAsIs, 0, 0 },
|
||||||
|
{ "deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 3 },
|
||||||
{ "feature", &RPCParser::parseFeature, 0, 2 },
|
{ "feature", &RPCParser::parseFeature, 0, 2 },
|
||||||
{ "fetch_info", &RPCParser::parseFetchInfo, 0, 1 },
|
{ "fetch_info", &RPCParser::parseFetchInfo, 0, 1 },
|
||||||
{ "gateway_balances", &RPCParser::parseGatewayBalances , 1, -1 },
|
{ "gateway_balances", &RPCParser::parseGatewayBalances, 1, -1 },
|
||||||
{ "get_counts", &RPCParser::parseGetCounts, 0, 1 },
|
{ "get_counts", &RPCParser::parseGetCounts, 0, 1 },
|
||||||
{ "json", &RPCParser::parseJson, 2, 2 },
|
{ "json", &RPCParser::parseJson, 2, 2 },
|
||||||
{ "json2", &RPCParser::parseJson2, 1, 1 },
|
{ "json2", &RPCParser::parseJson2, 1, 1 },
|
||||||
@@ -1109,12 +1123,12 @@ public:
|
|||||||
{ "validation_seed", &RPCParser::parseValidationSeed, 0, 1 },
|
{ "validation_seed", &RPCParser::parseValidationSeed, 0, 1 },
|
||||||
{ "version", &RPCParser::parseAsIs, 0, 0 },
|
{ "version", &RPCParser::parseAsIs, 0, 0 },
|
||||||
{ "wallet_propose", &RPCParser::parseWalletPropose, 0, 1 },
|
{ "wallet_propose", &RPCParser::parseWalletPropose, 0, 1 },
|
||||||
{ "internal", &RPCParser::parseInternal, 1, -1 },
|
{ "internal", &RPCParser::parseInternal, 1, -1 },
|
||||||
|
|
||||||
// Evented methods
|
// Evented methods
|
||||||
{ "path_find", &RPCParser::parseEvented, -1, -1 },
|
{ "path_find", &RPCParser::parseEvented, -1, -1 },
|
||||||
{ "subscribe", &RPCParser::parseEvented, -1, -1 },
|
{ "subscribe", &RPCParser::parseEvented, -1, -1 },
|
||||||
{ "unsubscribe", &RPCParser::parseEvented, -1, -1 },
|
{ "unsubscribe", &RPCParser::parseEvented, -1, -1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
auto const count = jvParams.size ();
|
auto const count = jvParams.size ();
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ class FeatureCollections
|
|||||||
"Checks",
|
"Checks",
|
||||||
"fix1571",
|
"fix1571",
|
||||||
"fix1543",
|
"fix1543",
|
||||||
"fix1623"
|
"fix1623",
|
||||||
|
"DepositPreauth"
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<uint256> features;
|
std::vector<uint256> features;
|
||||||
@@ -363,6 +364,7 @@ extern uint256 const featureChecks;
|
|||||||
extern uint256 const fix1571;
|
extern uint256 const fix1571;
|
||||||
extern uint256 const fix1543;
|
extern uint256 const fix1543;
|
||||||
extern uint256 const fix1623;
|
extern uint256 const fix1623;
|
||||||
|
extern uint256 const featureDepositPreauth;
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ getSignerListIndex (AccountID const& account);
|
|||||||
uint256
|
uint256
|
||||||
getCheckIndex (AccountID const& account, std::uint32_t uSequence);
|
getCheckIndex (AccountID const& account, std::uint32_t uSequence);
|
||||||
|
|
||||||
|
uint256
|
||||||
|
getDepositPreauthIndex (AccountID const& owner, AccountID const& preauthorized);
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
/* VFALCO TODO
|
/* VFALCO TODO
|
||||||
@@ -253,6 +256,21 @@ struct check_t
|
|||||||
};
|
};
|
||||||
static check_t const check {};
|
static check_t const check {};
|
||||||
|
|
||||||
|
/** A DepositPreauth */
|
||||||
|
struct depositPreauth_t
|
||||||
|
{
|
||||||
|
explicit depositPreauth_t() = default;
|
||||||
|
|
||||||
|
Keylet operator()(AccountID const& owner,
|
||||||
|
AccountID const& preauthorized) const;
|
||||||
|
|
||||||
|
Keylet operator()(uint256 const& key) const
|
||||||
|
{
|
||||||
|
return { ltDEPOSIT_PREAUTH, key };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static depositPreauth_t const depositPreauth {};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Any ledger entry */
|
/** Any ledger entry */
|
||||||
|
|||||||
@@ -141,6 +141,8 @@ JSS ( dbKBTotal ); // out: getCounts
|
|||||||
JSS ( dbKBTransaction ); // out: getCounts
|
JSS ( dbKBTransaction ); // out: getCounts
|
||||||
JSS ( debug_signing ); // in: TransactionSign
|
JSS ( debug_signing ); // in: TransactionSign
|
||||||
JSS ( delivered_amount ); // out: addPaymentDeliveredAmount
|
JSS ( delivered_amount ); // out: addPaymentDeliveredAmount
|
||||||
|
JSS ( deposit_authorized ); // out: deposit_authorized
|
||||||
|
JSS ( deposit_preauth ); // in: AccountObjects, LedgerData
|
||||||
JSS ( deprecated ); // out
|
JSS ( deprecated ); // out
|
||||||
JSS ( descending ); // in: AccountTx*
|
JSS ( descending ); // in: AccountTx*
|
||||||
JSS ( destination_account ); // in: PathRequest, RipplePathFind, account_lines
|
JSS ( destination_account ); // in: PathRequest, RipplePathFind, account_lines
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ enum LedgerEntryType
|
|||||||
|
|
||||||
ltCHECK = 'C',
|
ltCHECK = 'C',
|
||||||
|
|
||||||
|
ltDEPOSIT_PREAUTH = 'p',
|
||||||
|
|
||||||
// No longer used or supported. Left here to prevent accidental
|
// No longer used or supported. Left here to prevent accidental
|
||||||
// reassignment of the ledger type.
|
// reassignment of the ledger type.
|
||||||
ltNICKNAME = 'n',
|
ltNICKNAME = 'n',
|
||||||
@@ -114,6 +116,7 @@ enum LedgerNameSpace
|
|||||||
spaceSignerList = 'S',
|
spaceSignerList = 'S',
|
||||||
spaceXRPUChannel = 'x',
|
spaceXRPUChannel = 'x',
|
||||||
spaceCheck = 'C',
|
spaceCheck = 'C',
|
||||||
|
spaceDepositPreauth = 'p',
|
||||||
|
|
||||||
// No longer used or supported. Left here to reserve the space and
|
// No longer used or supported. Left here to reserve the space and
|
||||||
// avoid accidental reuse of the space.
|
// avoid accidental reuse of the space.
|
||||||
|
|||||||
@@ -464,6 +464,8 @@ extern SF_Account const sfAccount;
|
|||||||
extern SF_Account const sfOwner;
|
extern SF_Account const sfOwner;
|
||||||
extern SF_Account const sfDestination;
|
extern SF_Account const sfDestination;
|
||||||
extern SF_Account const sfIssuer;
|
extern SF_Account const sfIssuer;
|
||||||
|
extern SF_Account const sfAuthorize;
|
||||||
|
extern SF_Account const sfUnauthorize;
|
||||||
extern SF_Account const sfTarget;
|
extern SF_Account const sfTarget;
|
||||||
extern SF_Account const sfRegularKey;
|
extern SF_Account const sfRegularKey;
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ enum TEMcodes : TERUnderlyingType
|
|||||||
temBAD_QUORUM,
|
temBAD_QUORUM,
|
||||||
temBAD_WEIGHT,
|
temBAD_WEIGHT,
|
||||||
temBAD_TICK_SIZE,
|
temBAD_TICK_SIZE,
|
||||||
|
temINVALID_ACCOUNT_ID,
|
||||||
|
temCANNOT_PREAUTH_SELF,
|
||||||
|
|
||||||
// An intermediate result used internally, should never be returned.
|
// An intermediate result used internally, should never be returned.
|
||||||
temUNCERTAIN,
|
temUNCERTAIN,
|
||||||
@@ -259,7 +261,8 @@ enum TECcodes : TERUnderlyingType
|
|||||||
tecOVERSIZE = 145,
|
tecOVERSIZE = 145,
|
||||||
tecCRYPTOCONDITION_ERROR = 146,
|
tecCRYPTOCONDITION_ERROR = 146,
|
||||||
tecINVARIANT_FAILED = 147,
|
tecINVARIANT_FAILED = 147,
|
||||||
tecEXPIRED = 148
|
tecEXPIRED = 148,
|
||||||
|
tecDUPLICATE = 149,
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ enum TxType
|
|||||||
ttCHECK_CREATE = 16,
|
ttCHECK_CREATE = 16,
|
||||||
ttCHECK_CASH = 17,
|
ttCHECK_CASH = 17,
|
||||||
ttCHECK_CANCEL = 18,
|
ttCHECK_CANCEL = 18,
|
||||||
|
ttDEPOSIT_PREAUTH = 19,
|
||||||
ttTRUST_SET = 20,
|
ttTRUST_SET = 20,
|
||||||
|
|
||||||
ttAMENDMENT = 100,
|
ttAMENDMENT = 100,
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ detail::supportedAmendments ()
|
|||||||
{ "157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F97AE4B1F2D1 Checks" },
|
{ "157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F97AE4B1F2D1 Checks" },
|
||||||
{ "7117E2EC2DBF119CA55181D69819F1999ECEE1A0225A7FD2B9ED47940968479C fix1571" },
|
{ "7117E2EC2DBF119CA55181D69819F1999ECEE1A0225A7FD2B9ED47940968479C fix1571" },
|
||||||
{ "CA7C02118BA27599528543DFE77BA6838D1B0F43B447D4D7F53523CE6A0E9AC2 fix1543" },
|
{ "CA7C02118BA27599528543DFE77BA6838D1B0F43B447D4D7F53523CE6A0E9AC2 fix1543" },
|
||||||
{ "58BE9B5968C4DA7C59BA900961828B113E5490699B21877DEF9A31E9D0FE5D5F fix1623" }
|
{ "58BE9B5968C4DA7C59BA900961828B113E5490699B21877DEF9A31E9D0FE5D5F fix1623" },
|
||||||
|
{ "3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194 DepositPreauth"}
|
||||||
};
|
};
|
||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
@@ -163,5 +164,6 @@ uint256 const featureChecks = *getRegisteredFeature("Checks");
|
|||||||
uint256 const fix1571 = *getRegisteredFeature("fix1571");
|
uint256 const fix1571 = *getRegisteredFeature("fix1571");
|
||||||
uint256 const fix1543 = *getRegisteredFeature("fix1543");
|
uint256 const fix1543 = *getRegisteredFeature("fix1543");
|
||||||
uint256 const fix1623 = *getRegisteredFeature("fix1623");
|
uint256 const fix1623 = *getRegisteredFeature("fix1623");
|
||||||
|
uint256 const featureDepositPreauth = *getRegisteredFeature("DepositPreauth");
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
@@ -198,6 +198,15 @@ getCheckIndex (AccountID const& account, std::uint32_t uSequence)
|
|||||||
std::uint32_t(uSequence));
|
std::uint32_t(uSequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint256
|
||||||
|
getDepositPreauthIndex (AccountID const& owner, AccountID const& preauthorized)
|
||||||
|
{
|
||||||
|
return sha512Half(
|
||||||
|
std::uint16_t(spaceDepositPreauth),
|
||||||
|
owner,
|
||||||
|
preauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace keylet {
|
namespace keylet {
|
||||||
@@ -300,6 +309,13 @@ Keylet check_t::operator()(AccountID const& id,
|
|||||||
getCheckIndex(id, seq) };
|
getCheckIndex(id, seq) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Keylet depositPreauth_t::operator()(AccountID const& owner,
|
||||||
|
AccountID const& preauthorized) const
|
||||||
|
{
|
||||||
|
return { ltDEPOSIT_PREAUTH,
|
||||||
|
getDepositPreauthIndex(owner, preauthorized) };
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
Keylet unchecked (uint256 const& key)
|
Keylet unchecked (uint256 const& key)
|
||||||
|
|||||||
@@ -168,6 +168,14 @@ LedgerFormats::LedgerFormats ()
|
|||||||
<< SOElement (sfPreviousTxnID, SOE_REQUIRED)
|
<< SOElement (sfPreviousTxnID, SOE_REQUIRED)
|
||||||
<< SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
<< SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
add ("DepositPreauth", ltDEPOSIT_PREAUTH)
|
||||||
|
<< SOElement (sfAccount, SOE_REQUIRED)
|
||||||
|
<< SOElement (sfAuthorize, SOE_REQUIRED)
|
||||||
|
<< SOElement (sfOwnerNode, SOE_REQUIRED)
|
||||||
|
<< SOElement (sfPreviousTxnID, SOE_REQUIRED)
|
||||||
|
<< SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LedgerFormats::addCommonFields (Item& item)
|
void LedgerFormats::addCommonFields (Item& item)
|
||||||
|
|||||||
@@ -220,6 +220,8 @@ SF_Account const sfAccount = make::one<SF_Account::type>(&sfAccount, STI
|
|||||||
SF_Account const sfOwner = make::one<SF_Account::type>(&sfOwner, STI_ACCOUNT, 2, "Owner");
|
SF_Account const sfOwner = make::one<SF_Account::type>(&sfOwner, STI_ACCOUNT, 2, "Owner");
|
||||||
SF_Account const sfDestination = make::one<SF_Account::type>(&sfDestination, STI_ACCOUNT, 3, "Destination");
|
SF_Account const sfDestination = make::one<SF_Account::type>(&sfDestination, STI_ACCOUNT, 3, "Destination");
|
||||||
SF_Account const sfIssuer = make::one<SF_Account::type>(&sfIssuer, STI_ACCOUNT, 4, "Issuer");
|
SF_Account const sfIssuer = make::one<SF_Account::type>(&sfIssuer, STI_ACCOUNT, 4, "Issuer");
|
||||||
|
SF_Account const sfAuthorize = make::one<SF_Account::type>(&sfAuthorize, STI_ACCOUNT, 5, "Authorize");
|
||||||
|
SF_Account const sfUnauthorize = make::one<SF_Account::type>(&sfUnauthorize, STI_ACCOUNT, 6, "Unauthorize");
|
||||||
SF_Account const sfTarget = make::one<SF_Account::type>(&sfTarget, STI_ACCOUNT, 7, "Target");
|
SF_Account const sfTarget = make::one<SF_Account::type>(&sfTarget, STI_ACCOUNT, 7, "Target");
|
||||||
SF_Account const sfRegularKey = make::one<SF_Account::type>(&sfRegularKey, STI_ACCOUNT, 8, "RegularKey");
|
SF_Account const sfRegularKey = make::one<SF_Account::type>(&sfRegularKey, STI_ACCOUNT, 8, "RegularKey");
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ transResults()
|
|||||||
{ tecCRYPTOCONDITION_ERROR, { "tecCRYPTOCONDITION_ERROR", "Malformed, invalid, or mismatched conditional or fulfillment." } },
|
{ tecCRYPTOCONDITION_ERROR, { "tecCRYPTOCONDITION_ERROR", "Malformed, invalid, or mismatched conditional or fulfillment." } },
|
||||||
{ tecINVARIANT_FAILED, { "tecINVARIANT_FAILED", "One or more invariants for the transaction were not satisfied." } },
|
{ tecINVARIANT_FAILED, { "tecINVARIANT_FAILED", "One or more invariants for the transaction were not satisfied." } },
|
||||||
{ tecEXPIRED, { "tecEXPIRED", "Expiration time is passed." } },
|
{ tecEXPIRED, { "tecEXPIRED", "Expiration time is passed." } },
|
||||||
|
{ tecDUPLICATE, { "tecDUPLICATE", "Ledger object already exists." } },
|
||||||
|
|
||||||
{ tefALREADY, { "tefALREADY", "The exact transaction was already in this ledger." } },
|
{ tefALREADY, { "tefALREADY", "The exact transaction was already in this ledger." } },
|
||||||
{ tefBAD_ADD_AUTH, { "tefBAD_ADD_AUTH", "Not authorized to add account." } },
|
{ tefBAD_ADD_AUTH, { "tefBAD_ADD_AUTH", "Not authorized to add account." } },
|
||||||
@@ -138,6 +139,8 @@ transResults()
|
|||||||
{ temUNKNOWN, { "temUNKNOWN", "The transaction requires logic that is not implemented yet." } },
|
{ temUNKNOWN, { "temUNKNOWN", "The transaction requires logic that is not implemented yet." } },
|
||||||
{ temDISABLED, { "temDISABLED", "The transaction requires logic that is currently disabled." } },
|
{ temDISABLED, { "temDISABLED", "The transaction requires logic that is currently disabled." } },
|
||||||
{ temBAD_TICK_SIZE, { "temBAD_TICK_SIZE", "Malformed: Tick size out of range." } },
|
{ temBAD_TICK_SIZE, { "temBAD_TICK_SIZE", "Malformed: Tick size out of range." } },
|
||||||
|
{ temINVALID_ACCOUNT_ID, { "temINVALID_ACCOUNT_ID", "Malformed: A field contains an invalid account ID." } },
|
||||||
|
{ temCANNOT_PREAUTH_SELF, { "temCANNOT_PREAUTH_SELF", "Malformed: An account may not preauthorize itself." } },
|
||||||
|
|
||||||
{ terRETRY, { "terRETRY", "Retry transaction." } },
|
{ terRETRY, { "terRETRY", "Retry transaction." } },
|
||||||
{ terFUNDS_SPENT, { "terFUNDS_SPENT", "Can't set password, password set funds already spent." } },
|
{ terFUNDS_SPENT, { "terFUNDS_SPENT", "Can't set password, password set funds already spent." } },
|
||||||
|
|||||||
@@ -156,6 +156,11 @@ TxFormats::TxFormats ()
|
|||||||
add ("CheckCancel", ttCHECK_CANCEL)
|
add ("CheckCancel", ttCHECK_CANCEL)
|
||||||
<< SOElement (sfCheckID, SOE_REQUIRED)
|
<< SOElement (sfCheckID, SOE_REQUIRED)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
add ("DepositPreauth", ttDEPOSIT_PREAUTH)
|
||||||
|
<< SOElement (sfAuthorize, SOE_OPTIONAL)
|
||||||
|
<< SOElement (sfUnauthorize, SOE_OPTIONAL)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxFormats::addCommonFields (Item& item)
|
void TxFormats::addCommonFields (Item& item)
|
||||||
|
|||||||
110
src/ripple/rpc/handlers/DepositAuthorized.cpp
Normal file
110
src/ripple/rpc/handlers/DepositAuthorized.cpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2018 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
#include <ripple/ledger/ReadView.h>
|
||||||
|
#include <ripple/protocol/ErrorCodes.h>
|
||||||
|
#include <ripple/protocol/Indexes.h>
|
||||||
|
#include <ripple/protocol/JsonFields.h>
|
||||||
|
#include <ripple/rpc/Context.h>
|
||||||
|
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
// {
|
||||||
|
// source_account : <ident>
|
||||||
|
// destination_account : <ident>
|
||||||
|
// ledger_hash : <ledger>
|
||||||
|
// ledger_index : <ledger_index>
|
||||||
|
// }
|
||||||
|
|
||||||
|
Json::Value doDepositAuthorized (RPC::Context& context)
|
||||||
|
{
|
||||||
|
Json::Value const& params = context.params;
|
||||||
|
|
||||||
|
// Validate source_account.
|
||||||
|
if (! params.isMember (jss::source_account))
|
||||||
|
return RPC::missing_field_error (jss::source_account);
|
||||||
|
if (! params[jss::source_account].isString())
|
||||||
|
return RPC::make_error (rpcINVALID_PARAMS,
|
||||||
|
RPC::expected_field_message (jss::source_account,
|
||||||
|
"a string"));
|
||||||
|
|
||||||
|
AccountID srcAcct;
|
||||||
|
{
|
||||||
|
Json::Value const jvAccepted = RPC::accountFromString (
|
||||||
|
srcAcct, params[jss::source_account].asString(), true);
|
||||||
|
if (jvAccepted)
|
||||||
|
return jvAccepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate destination_account.
|
||||||
|
if (! params.isMember (jss::destination_account))
|
||||||
|
return RPC::missing_field_error (jss::destination_account);
|
||||||
|
if (! params[jss::destination_account].isString())
|
||||||
|
return RPC::make_error (rpcINVALID_PARAMS,
|
||||||
|
RPC::expected_field_message (jss::destination_account,
|
||||||
|
"a string"));
|
||||||
|
|
||||||
|
AccountID dstAcct;
|
||||||
|
{
|
||||||
|
Json::Value const jvAccepted = RPC::accountFromString (
|
||||||
|
dstAcct, params[jss::destination_account].asString(), true);
|
||||||
|
if (jvAccepted)
|
||||||
|
return jvAccepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ledger.
|
||||||
|
std::shared_ptr<ReadView const> ledger;
|
||||||
|
Json::Value result = RPC::lookupLedger (ledger, context);
|
||||||
|
|
||||||
|
if (!ledger)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// If destination account is not in the ledger you can't deposit to it, eh?
|
||||||
|
auto const sleDest = ledger->read (keylet::account(dstAcct));
|
||||||
|
if (! sleDest)
|
||||||
|
{
|
||||||
|
RPC::inject_error (rpcDST_ACT_MISSING, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the two accounts are the same, then the deposit should be fine.
|
||||||
|
bool depositAuthorized {true};
|
||||||
|
if (srcAcct != dstAcct)
|
||||||
|
{
|
||||||
|
// Check destination for the DepositAuth flag. If that flag is
|
||||||
|
// not set then a deposit should be just fine.
|
||||||
|
if (sleDest->getFlags() & lsfDepositAuth)
|
||||||
|
{
|
||||||
|
// See if a preauthorization entry is in the ledger.
|
||||||
|
auto const sleDepositAuth =
|
||||||
|
ledger->read(keylet::depositPreauth (dstAcct, srcAcct));
|
||||||
|
depositAuthorized = static_cast<bool>(sleDepositAuth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[jss::source_account] = params[jss::source_account].asString();
|
||||||
|
result[jss::destination_account] =
|
||||||
|
params[jss::destination_account].asString();
|
||||||
|
|
||||||
|
result[jss::deposit_authorized] = depositAuthorized;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // ripple
|
||||||
@@ -40,6 +40,7 @@ Json::Value doChannelAuthorize (RPC::Context&);
|
|||||||
Json::Value doChannelVerify (RPC::Context&);
|
Json::Value doChannelVerify (RPC::Context&);
|
||||||
Json::Value doConnect (RPC::Context&);
|
Json::Value doConnect (RPC::Context&);
|
||||||
Json::Value doConsensusInfo (RPC::Context&);
|
Json::Value doConsensusInfo (RPC::Context&);
|
||||||
|
Json::Value doDepositAuthorized (RPC::Context&);
|
||||||
Json::Value doFeature (RPC::Context&);
|
Json::Value doFeature (RPC::Context&);
|
||||||
Json::Value doFee (RPC::Context&);
|
Json::Value doFee (RPC::Context&);
|
||||||
Json::Value doFetchInfo (RPC::Context&);
|
Json::Value doFetchInfo (RPC::Context&);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Json::Value doLedgerEntry (RPC::Context& context)
|
|||||||
|
|
||||||
if (context.params.isMember (jss::index))
|
if (context.params.isMember (jss::index))
|
||||||
{
|
{
|
||||||
uNodeIndex.SetHex (context.params[jss::index].asString ());
|
uNodeIndex.SetHex (context.params[jss::index].asString());
|
||||||
bNodeBinary = true;
|
bNodeBinary = true;
|
||||||
}
|
}
|
||||||
else if (context.params.isMember (jss::account_root))
|
else if (context.params.isMember (jss::account_root))
|
||||||
@@ -65,7 +65,44 @@ Json::Value doLedgerEntry (RPC::Context& context)
|
|||||||
else if (context.params.isMember (jss::check))
|
else if (context.params.isMember (jss::check))
|
||||||
{
|
{
|
||||||
expectedType = ltCHECK;
|
expectedType = ltCHECK;
|
||||||
uNodeIndex.SetHex (context.params[jss::check].asString ());
|
uNodeIndex.SetHex (context.params[jss::check].asString());
|
||||||
|
}
|
||||||
|
else if (context.params.isMember (jss::deposit_preauth))
|
||||||
|
{
|
||||||
|
expectedType = ltDEPOSIT_PREAUTH;
|
||||||
|
|
||||||
|
if (!context.params[jss::deposit_preauth].isObject())
|
||||||
|
{
|
||||||
|
if (! context.params[jss::deposit_preauth].isString() ||
|
||||||
|
! uNodeIndex.SetHex (
|
||||||
|
context.params[jss::deposit_preauth].asString()))
|
||||||
|
{
|
||||||
|
uNodeIndex = zero;
|
||||||
|
jvResult[jss::error] = "malformedRequest";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!context.params[jss::deposit_preauth].isMember (jss::owner)
|
||||||
|
|| !context.params[jss::deposit_preauth][jss::owner].isString()
|
||||||
|
|| !context.params[jss::deposit_preauth].isMember (jss::authorized)
|
||||||
|
|| !context.params[jss::deposit_preauth][jss::authorized].isString())
|
||||||
|
{
|
||||||
|
jvResult[jss::error] = "malformedRequest";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const owner = parseBase58<AccountID>(context.params[
|
||||||
|
jss::deposit_preauth][jss::owner].asString());
|
||||||
|
|
||||||
|
auto const authorized = parseBase58<AccountID>(context.params[
|
||||||
|
jss::deposit_preauth][jss::authorized].asString());
|
||||||
|
|
||||||
|
if (! owner)
|
||||||
|
jvResult[jss::error] = "malformedOwner";
|
||||||
|
else if (! authorized)
|
||||||
|
jvResult[jss::error] = "malformedAuthorized";
|
||||||
|
else
|
||||||
|
uNodeIndex = keylet::depositPreauth (*owner, *authorized).key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (context.params.isMember (jss::directory))
|
else if (context.params.isMember (jss::directory))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ Handler const handlerArray[] {
|
|||||||
{ "channel_verify", byRef (&doChannelVerify), Role::USER, NO_CONDITION },
|
{ "channel_verify", byRef (&doChannelVerify), Role::USER, NO_CONDITION },
|
||||||
{ "connect", byRef (&doConnect), Role::ADMIN, NO_CONDITION },
|
{ "connect", byRef (&doConnect), Role::ADMIN, NO_CONDITION },
|
||||||
{ "consensus_info", byRef (&doConsensusInfo), Role::ADMIN, NO_CONDITION },
|
{ "consensus_info", byRef (&doConsensusInfo), Role::ADMIN, NO_CONDITION },
|
||||||
|
{ "deposit_authorized", byRef (&doDepositAuthorized), Role::USER, NO_CONDITION },
|
||||||
{ "gateway_balances", byRef (&doGatewayBalances), Role::USER, NO_CONDITION },
|
{ "gateway_balances", byRef (&doGatewayBalances), Role::USER, NO_CONDITION },
|
||||||
{ "get_counts", byRef (&doGetCounts), Role::ADMIN, NO_CONDITION },
|
{ "get_counts", byRef (&doGetCounts), Role::ADMIN, NO_CONDITION },
|
||||||
{ "feature", byRef (&doFeature), Role::ADMIN, NO_CONDITION },
|
{ "feature", byRef (&doFeature), Role::ADMIN, NO_CONDITION },
|
||||||
|
|||||||
@@ -688,11 +688,12 @@ chooseLedgerEntryType(Json::Value const& params)
|
|||||||
if (params.isMember(jss::type))
|
if (params.isMember(jss::type))
|
||||||
{
|
{
|
||||||
static
|
static
|
||||||
std::array<std::pair<char const *, LedgerEntryType>, 12> const types
|
std::array<std::pair<char const *, LedgerEntryType>, 13> const types
|
||||||
{ {
|
{ {
|
||||||
{ jss::account, ltACCOUNT_ROOT },
|
{ jss::account, ltACCOUNT_ROOT },
|
||||||
{ jss::amendments, ltAMENDMENTS },
|
{ jss::amendments, ltAMENDMENTS },
|
||||||
{ jss::check, ltCHECK },
|
{ jss::check, ltCHECK },
|
||||||
|
{ jss::deposit_preauth, ltDEPOSIT_PREAUTH },
|
||||||
{ jss::directory, ltDIR_NODE },
|
{ jss::directory, ltDIR_NODE },
|
||||||
{ jss::escrow, ltESCROW },
|
{ jss::escrow, ltESCROW },
|
||||||
{ jss::fee, ltFEE_SETTINGS },
|
{ jss::fee, ltFEE_SETTINGS },
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <ripple/app/tx/impl/CreateCheck.cpp>
|
#include <ripple/app/tx/impl/CreateCheck.cpp>
|
||||||
#include <ripple/app/tx/impl/CreateOffer.cpp>
|
#include <ripple/app/tx/impl/CreateOffer.cpp>
|
||||||
#include <ripple/app/tx/impl/CreateTicket.cpp>
|
#include <ripple/app/tx/impl/CreateTicket.cpp>
|
||||||
|
#include <ripple/app/tx/impl/DepositPreauth.cpp>
|
||||||
#include <ripple/app/tx/impl/Escrow.cpp>
|
#include <ripple/app/tx/impl/Escrow.cpp>
|
||||||
#include <ripple/app/tx/impl/InvariantCheck.cpp>
|
#include <ripple/app/tx/impl/InvariantCheck.cpp>
|
||||||
#include <ripple/app/tx/impl/OfferStream.cpp>
|
#include <ripple/app/tx/impl/OfferStream.cpp>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include <ripple/rpc/handlers/CanDelete.cpp>
|
#include <ripple/rpc/handlers/CanDelete.cpp>
|
||||||
#include <ripple/rpc/handlers/Connect.cpp>
|
#include <ripple/rpc/handlers/Connect.cpp>
|
||||||
#include <ripple/rpc/handlers/ConsensusInfo.cpp>
|
#include <ripple/rpc/handlers/ConsensusInfo.cpp>
|
||||||
|
#include <ripple/rpc/handlers/DepositAuthorized.cpp>
|
||||||
#include <ripple/rpc/handlers/Feature1.cpp>
|
#include <ripple/rpc/handlers/Feature1.cpp>
|
||||||
#include <ripple/rpc/handlers/Fee1.cpp>
|
#include <ripple/rpc/handlers/Fee1.cpp>
|
||||||
#include <ripple/rpc/handlers/FetchInfo.cpp>
|
#include <ripple/rpc/handlers/FetchInfo.cpp>
|
||||||
|
|||||||
@@ -17,37 +17,28 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <ripple/app/paths/Flow.h>
|
|
||||||
#include <ripple/app/paths/impl/Steps.h>
|
|
||||||
#include <ripple/basics/contract.h>
|
|
||||||
#include <ripple/core/Config.h>
|
|
||||||
#include <ripple/ledger/ApplyViewImpl.h>
|
|
||||||
#include <ripple/ledger/PaymentSandbox.h>
|
|
||||||
#include <ripple/ledger/Sandbox.h>
|
|
||||||
#include <ripple/protocol/Feature.h>
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/JsonFields.h>
|
|
||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
#include <test/jtx/PathSet.h>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
|
// Helper function that returns the reserve on an account based on
|
||||||
|
// the passed in number of owners.
|
||||||
|
static XRPAmount reserve (jtx::Env& env, std::uint32_t count)
|
||||||
|
{
|
||||||
|
return env.current()->fees().accountReserve (count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function that returns true if acct has the lsfDepostAuth flag set.
|
||||||
|
static bool hasDepositAuth (jtx::Env const& env, jtx::Account const& acct)
|
||||||
|
{
|
||||||
|
return ((*env.le(acct))[sfFlags] & lsfDepositAuth) == lsfDepositAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct DepositAuth_test : public beast::unit_test::suite
|
struct DepositAuth_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
// Helper function that returns the reserve on an account based on
|
|
||||||
// the passed in number of owners.
|
|
||||||
static XRPAmount reserve(jtx::Env& env, std::uint32_t count)
|
|
||||||
{
|
|
||||||
return env.current()->fees().accountReserve (count);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function that returns true if acct has the lsfDepostAuth flag set.
|
|
||||||
static bool hasDepositAuth (jtx::Env const& env, jtx::Account const& acct)
|
|
||||||
{
|
|
||||||
return ((*env.le(acct))[sfFlags] & lsfDepositAuth) == lsfDepositAuth;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void testEnable()
|
void testEnable()
|
||||||
{
|
{
|
||||||
testcase ("Enable");
|
testcase ("Enable");
|
||||||
@@ -389,7 +380,325 @@ struct DepositAuth_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DepositPreauth_test : public beast::unit_test::suite
|
||||||
|
{
|
||||||
|
void testEnable()
|
||||||
|
{
|
||||||
|
testcase ("Enable");
|
||||||
|
|
||||||
|
using namespace jtx;
|
||||||
|
Account const alice {"alice"};
|
||||||
|
Account const becky {"becky"};
|
||||||
|
{
|
||||||
|
// featureDepositPreauth is disabled.
|
||||||
|
Env env (*this, supported_amendments() - featureDepositPreauth);
|
||||||
|
env.fund (XRP (10000), alice, becky);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Should not be able to add a DepositPreauth to alice.
|
||||||
|
env (deposit::auth (alice, becky), ter (temDISABLED));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 0));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// Should not be able to remove a DepositPreauth from alice.
|
||||||
|
env (deposit::unauth (alice, becky), ter (temDISABLED));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 0));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// featureDepositPreauth is enabled. The valid case is really
|
||||||
|
// simple:
|
||||||
|
// o We should be able to add and remove an entry, and
|
||||||
|
// o That entry should cost one reserve.
|
||||||
|
// o The reserve should be returned when the entry is removed.
|
||||||
|
Env env (*this);
|
||||||
|
env.fund (XRP (10000), alice, becky);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Add a DepositPreauth to alice.
|
||||||
|
env (deposit::auth (alice, becky));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 1));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// Remove a DepositPreauth from alice.
|
||||||
|
env (deposit::unauth (alice, becky));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 0));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void testInvalid()
|
||||||
|
{
|
||||||
|
testcase ("Invalid");
|
||||||
|
|
||||||
|
using namespace jtx;
|
||||||
|
Account const alice {"alice"};
|
||||||
|
Account const becky {"becky"};
|
||||||
|
Account const carol {"carol"};
|
||||||
|
|
||||||
|
Env env (*this);
|
||||||
|
|
||||||
|
// Tell env about alice, becky and carol since they are not yet funded.
|
||||||
|
env.memoize (alice);
|
||||||
|
env.memoize (becky);
|
||||||
|
env.memoize (carol);
|
||||||
|
|
||||||
|
// Add DepositPreauth to an unfunded account.
|
||||||
|
env (deposit::auth (alice, becky), seq (1), ter (terNO_ACCOUNT));
|
||||||
|
|
||||||
|
env.fund (XRP (10000), alice, becky);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Bad fee.
|
||||||
|
env (deposit::auth (alice, becky), fee (drops(-10)), ter (temBAD_FEE));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Bad flags.
|
||||||
|
env (deposit::auth (alice, becky),
|
||||||
|
txflags (tfSell), ter (temINVALID_FLAG));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Neither auth not unauth.
|
||||||
|
Json::Value tx {deposit::auth (alice, becky)};
|
||||||
|
tx.removeMember (sfAuthorize.jsonName);
|
||||||
|
env (tx, ter (temMALFORMED));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Both auth and unauth.
|
||||||
|
Json::Value tx {deposit::auth (alice, becky)};
|
||||||
|
tx[sfUnauthorize.jsonName] = becky.human();
|
||||||
|
env (tx, ter (temMALFORMED));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Alice authorizes a zero account.
|
||||||
|
Json::Value tx {deposit::auth (alice, becky)};
|
||||||
|
tx[sfAuthorize.jsonName] = to_string (xrpAccount());
|
||||||
|
env (tx, ter (temINVALID_ACCOUNT_ID));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// alice authorizes herself.
|
||||||
|
env (deposit::auth (alice, alice), ter (temCANNOT_PREAUTH_SELF));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice authorizes an unfunded account.
|
||||||
|
env (deposit::auth (alice, carol), ter (tecNO_TARGET));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice successfully authorizes becky.
|
||||||
|
env.require (owners (alice, 0));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
env (deposit::auth (alice, becky));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 1));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// alice attempts to create a duplicate authorization.
|
||||||
|
env (deposit::auth (alice, becky), ter (tecDUPLICATE));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 1));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// carol attempts to preauthorize but doesn't have enough reserve.
|
||||||
|
env.fund (drops (249'999'999), carol);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (deposit::auth (carol, becky), ter (tecINSUFFICIENT_RESERVE));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (carol, 0));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// carol gets enough XRP to (barely) meet the reserve.
|
||||||
|
env (pay (alice, carol, drops (11)));
|
||||||
|
env.close();
|
||||||
|
env (deposit::auth (carol, becky));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (carol, 1));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// But carol can't meet the reserve for another preauthorization.
|
||||||
|
env (deposit::auth (carol, alice), ter (tecINSUFFICIENT_RESERVE));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (carol, 1));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
env.require (owners (alice, 1));
|
||||||
|
|
||||||
|
// alice attempts to remove an authorization she doesn't have.
|
||||||
|
env (deposit::unauth (alice, carol), ter (tecNO_ENTRY));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 1));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// alice successfully removes her authorization of becky.
|
||||||
|
env (deposit::unauth (alice, becky));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 0));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
|
||||||
|
// alice removes becky again and gets an error.
|
||||||
|
env (deposit::unauth (alice, becky), ter (tecNO_ENTRY));
|
||||||
|
env.close();
|
||||||
|
env.require (owners (alice, 0));
|
||||||
|
env.require (owners (becky, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void testPayment (FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase ("Payment");
|
||||||
|
|
||||||
|
using namespace jtx;
|
||||||
|
Account const alice {"alice"};
|
||||||
|
Account const becky {"becky"};
|
||||||
|
Account const gw {"gw"};
|
||||||
|
IOU const USD (gw["USD"]);
|
||||||
|
|
||||||
|
bool const supportsPreauth = {features[featureDepositPreauth]};
|
||||||
|
|
||||||
|
{
|
||||||
|
// The initial implementation of DepositAuth had a bug where an
|
||||||
|
// account with the DepositAuth flag set could not make a payment
|
||||||
|
// to itself. That bug was fixed in the DepositPreauth amendment.
|
||||||
|
Env env (*this, features);
|
||||||
|
env.fund (XRP (5000), alice, becky, gw);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env.trust (USD (1000), alice);
|
||||||
|
env.trust (USD (1000), becky);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (pay (gw, alice, USD (500)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (offer (alice, XRP (100), USD (100), tfPassive),
|
||||||
|
require (offers (alice, 1)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky pays herself USD (10) by consuming part of alice's offer.
|
||||||
|
// Make sure the payment works if PaymentAuth is not involved.
|
||||||
|
env (pay (becky, becky, USD (10)),
|
||||||
|
path (~USD), sendmax (XRP (10)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky decides to require authorization for deposits.
|
||||||
|
env(fset (becky, asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky pays herself again. Whether it succeeds depends on
|
||||||
|
// whether featureDepositPreauth is enabled.
|
||||||
|
TER const expect {
|
||||||
|
supportsPreauth ? TER {tesSUCCESS} : TER {tecNO_PERMISSION}};
|
||||||
|
|
||||||
|
env (pay (becky, becky, USD (10)),
|
||||||
|
path (~USD), sendmax (XRP (10)), ter (expect));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsPreauth)
|
||||||
|
{
|
||||||
|
// Make sure DepositPreauthorization works for payments.
|
||||||
|
|
||||||
|
Account const carol {"carol"};
|
||||||
|
|
||||||
|
Env env (*this, features);
|
||||||
|
env.fund (XRP (5000), alice, becky, carol, gw);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env.trust (USD (1000), alice);
|
||||||
|
env.trust (USD (1000), becky);
|
||||||
|
env.trust (USD (1000), carol);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (pay (gw, alice, USD (1000)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Make XRP and IOU payments from alice to becky. Should be fine.
|
||||||
|
env (pay (alice, becky, XRP (100)));
|
||||||
|
env (pay (alice, becky, USD (100)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky decides to require authorization for deposits.
|
||||||
|
env(fset (becky, asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice can no longer pay becky.
|
||||||
|
env (pay (alice, becky, XRP (100)), ter (tecNO_PERMISSION));
|
||||||
|
env (pay (alice, becky, USD (100)), ter (tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky preauthorizes carol for deposit, which doesn't provide
|
||||||
|
// authorization for alice.
|
||||||
|
env (deposit::auth (becky, carol));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice still can't pay becky.
|
||||||
|
env (pay (alice, becky, XRP (100)), ter (tecNO_PERMISSION));
|
||||||
|
env (pay (alice, becky, USD (100)), ter (tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky preauthorizes alice for deposit.
|
||||||
|
env (deposit::auth (becky, alice));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice can now pay becky.
|
||||||
|
env (pay (alice, becky, XRP (100)));
|
||||||
|
env (pay (alice, becky, USD (100)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice decides to require authorization for deposits.
|
||||||
|
env(fset (alice, asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Even though alice is authorized to pay becky, becky is not
|
||||||
|
// authorized to pay alice.
|
||||||
|
env (pay (becky, alice, XRP (100)), ter (tecNO_PERMISSION));
|
||||||
|
env (pay (becky, alice, USD (100)), ter (tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky unauthorizes carol. Should have no impact on alice.
|
||||||
|
env (deposit::unauth (becky, carol));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (pay (alice, becky, XRP (100)));
|
||||||
|
env (pay (alice, becky, USD (100)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky unauthorizes alice. alice now can't pay becky.
|
||||||
|
env (deposit::unauth (becky, alice));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (pay (alice, becky, XRP (100)), ter (tecNO_PERMISSION));
|
||||||
|
env (pay (alice, becky, USD (100)), ter (tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky decides to remove authorization for deposits. Now
|
||||||
|
// alice can pay becky again.
|
||||||
|
env(fclear (becky, asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (pay (alice, becky, XRP (100)));
|
||||||
|
env (pay (alice, becky, USD (100)));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() override
|
||||||
|
{
|
||||||
|
testEnable();
|
||||||
|
testInvalid();
|
||||||
|
testPayment (jtx::supported_amendments() - featureDepositPreauth);
|
||||||
|
testPayment (jtx::supported_amendments());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
BEAST_DEFINE_TESTSUITE(DepositAuth,app,ripple);
|
BEAST_DEFINE_TESTSUITE(DepositAuth,app,ripple);
|
||||||
|
BEAST_DEFINE_TESTSUITE(DepositPreauth,app,ripple);
|
||||||
|
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
void
|
void
|
||||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||||
{
|
{
|
||||||
jt.jv["FinishAfter"] = value_.time_since_epoch().count();
|
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
void
|
void
|
||||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||||
{
|
{
|
||||||
jt.jv["CancelAfter"] = value_.time_since_epoch().count();
|
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
void
|
void
|
||||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||||
{
|
{
|
||||||
jt.jv["Condition"] = value_;
|
jt.jv[sfCondition.jsonName] = value_;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
void
|
void
|
||||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||||
{
|
{
|
||||||
jt.jv["Fulfillment"] = value_;
|
jt.jv[sfFulfillment.jsonName] = value_;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,8 +188,8 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
jv[jss::TransactionType] = "EscrowFinish";
|
jv[jss::TransactionType] = "EscrowFinish";
|
||||||
jv[jss::Flags] = tfUniversal;
|
jv[jss::Flags] = tfUniversal;
|
||||||
jv[jss::Account] = account.human();
|
jv[jss::Account] = account.human();
|
||||||
jv["Owner"] = from.human();
|
jv[sfOwner.jsonName] = from.human();
|
||||||
jv["OfferSequence"] = seq;
|
jv[sfOfferSequence.jsonName] = seq;
|
||||||
return jv;
|
return jv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,8 +202,8 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
jv[jss::TransactionType] = "EscrowCancel";
|
jv[jss::TransactionType] = "EscrowCancel";
|
||||||
jv[jss::Flags] = tfUniversal;
|
jv[jss::Flags] = tfUniversal;
|
||||||
jv[jss::Account] = account.human();
|
jv[jss::Account] = account.human();
|
||||||
jv["Owner"] = from.human();
|
jv[sfOwner.jsonName] = from.human();
|
||||||
jv["OfferSequence"] = seq;
|
jv[sfOfferSequence.jsonName] = seq;
|
||||||
return jv;
|
return jv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,15 +587,15 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
{ // Unconditional
|
{
|
||||||
|
// Unconditional
|
||||||
Env env(*this);
|
Env env(*this);
|
||||||
env.fund(XRP(5000), "alice", "bob");
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
env(escrow("alice", "alice", XRP(1000)), finish_time(env.now() + 5s));
|
env(escrow("alice", "alice", XRP(1000)), finish_time(env.now() + 5s));
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
|
||||||
// Not enough time has elapsed for a finish and cancelling isn't
|
// Not enough time has elapsed for a finish and canceling isn't
|
||||||
// possible.
|
// possible.
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
@@ -609,37 +609,37 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
env.require(balance("alice", XRP(5000) - drops(10)));
|
env.require(balance("alice", XRP(5000) - drops(10)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Unconditionally pay from alice to bob. jack (neither source nor
|
// Unconditionally pay from Alice to Bob. Zelda (neither source nor
|
||||||
// destination) signs all cancels and finishes. This shows that
|
// destination) signs all cancels and finishes. This shows that
|
||||||
// Escrow will make a payment to bob with no intervention from bob.
|
// Escrow will make a payment to Bob with no intervention from Bob.
|
||||||
Env env(*this);
|
Env env(*this);
|
||||||
env.fund(XRP(5000), "alice", "bob", "jack");
|
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
|
||||||
// Not enough time has elapsed for a finish and cancelling isn't
|
// Not enough time has elapsed for a finish and canceling isn't
|
||||||
// possible.
|
// possible.
|
||||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("jack", "alice", seq), ter(tecNO_PERMISSION));
|
env(finish("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// Cancel continues to not be possible
|
// Cancel continues to not be possible
|
||||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
|
|
||||||
// Finish should succeed. Verify funds.
|
// Finish should succeed. Verify funds.
|
||||||
env(finish("jack", "alice", seq));
|
env(finish("zelda", "alice", seq));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
env.require(balance("bob", XRP(6000)));
|
env.require(balance("bob", XRP(6000)));
|
||||||
env.require(balance("jack", XRP(5000) - drops(40)));
|
env.require(balance("zelda", XRP(5000) - drops(40)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// bob sets PaymentAuth so only bob can finish the escrow.
|
// Bob sets DepositAuth so only Bob can finish the escrow.
|
||||||
Env env(*this);
|
Env env(*this);
|
||||||
|
|
||||||
env.fund(XRP(5000), "alice", "bob", "jack");
|
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||||
env(fset ("bob", asfDepositAuth));
|
env(fset ("bob", asfDepositAuth));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
@@ -647,22 +647,22 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
|
||||||
// Not enough time has elapsed for a finish and cancelling isn't
|
// Not enough time has elapsed for a finish and canceling isn't
|
||||||
// possible.
|
// possible.
|
||||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("jack", "alice", seq), ter(tecNO_PERMISSION));
|
env(finish("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// Cancel continues to not be possible. Finish will only succeed for
|
// Cancel continues to not be possible. Finish will only succeed for
|
||||||
// Bob, because of PaymentAuth.
|
// Bob, because of DepositAuth.
|
||||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("jack", "alice", seq), ter(tecNO_PERMISSION));
|
env(finish("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("bob", "alice", seq));
|
env(finish("bob", "alice", seq));
|
||||||
env.close();
|
env.close();
|
||||||
@@ -670,7 +670,35 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
auto const baseFee = env.current()->fees().base;
|
auto const baseFee = env.current()->fees().base;
|
||||||
env.require(balance("alice", XRP(4000) - (baseFee * 5)));
|
env.require(balance("alice", XRP(4000) - (baseFee * 5)));
|
||||||
env.require(balance("bob", XRP(6000) - (baseFee * 5)));
|
env.require(balance("bob", XRP(6000) - (baseFee * 5)));
|
||||||
env.require(balance("jack", XRP(5000) - (baseFee * 4)));
|
env.require(balance("zelda", XRP(5000) - (baseFee * 4)));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Bob sets DepositAuth but preauthorizes Zelda, so Zelda can
|
||||||
|
// finish the escrow.
|
||||||
|
Env env(*this);
|
||||||
|
|
||||||
|
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||||
|
env(fset ("bob", asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
env(deposit::auth ("bob", "zelda"));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
auto const seq = env.seq("alice");
|
||||||
|
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
||||||
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// DepositPreauth allows Finish to succeed for either Zelda or
|
||||||
|
// Bob. But Finish won't succeed for Alice since she is not
|
||||||
|
// preauthorized.
|
||||||
|
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
|
env(finish("zelda", "alice", seq));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
auto const baseFee = env.current()->fees().base;
|
||||||
|
env.require(balance("alice", XRP(4000) - (baseFee * 2)));
|
||||||
|
env.require(balance("bob", XRP(6000) - (baseFee * 2)));
|
||||||
|
env.require(balance("zelda", XRP(5000) - (baseFee * 1)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Conditional
|
// Conditional
|
||||||
@@ -680,7 +708,7 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
env(escrow("alice", "alice", XRP(1000)), condition(cb2), finish_time(env.now() + 5s));
|
env(escrow("alice", "alice", XRP(1000)), condition(cb2), finish_time(env.now() + 5s));
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
|
||||||
// Not enough time has elapsed for a finish and cancelling isn't
|
// Not enough time has elapsed for a finish and canceling isn't
|
||||||
// possible.
|
// possible.
|
||||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
@@ -704,45 +732,63 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
condition(cb2), fulfillment(fb2), fee(1500));
|
condition(cb2), fulfillment(fb2), fee(1500));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Self-escrowed conditional with PaymentAuth
|
// Self-escrowed conditional with DepositAuth.
|
||||||
Env env(*this);
|
Env env(*this);
|
||||||
|
|
||||||
env.fund(XRP(5000), "alice", "bob");
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
env(escrow("alice", "alice", XRP(1000)), condition(cb3), finish_time(env.now() + 5s));
|
env(escrow("alice", "alice", XRP(1000)), condition(cb3), finish_time(env.now() + 5s));
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
|
||||||
// Not enough time has elapsed for a finish and cancelling isn't
|
|
||||||
// possible.
|
|
||||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
env(finish("alice", "alice", seq),
|
|
||||||
condition(cb3), fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
|
||||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
env(finish("bob", "alice", seq),
|
|
||||||
condition(cb3), fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// Cancel continues to not be possible. Finish is now possible but
|
// Finish is now possible but requires the cryptocondition.
|
||||||
// requires the associated cryptocondition.
|
|
||||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
env(finish("alice", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
env(finish("alice", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
|
||||||
// Enable deposit authorization. After this, only Alice can finish
|
// Enable deposit authorization. After this only Alice can finish
|
||||||
// the escrow.
|
// the escrow.
|
||||||
env(fset ("alice", asfDepositAuth));
|
env(fset ("alice", asfDepositAuth));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
env(finish("bob", "alice", seq), condition(cb2),
|
env(finish("alice", "alice", seq), condition(cb2),
|
||||||
fulfillment(fb2), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
|
||||||
env(finish("alice", "alice", seq), condition(cb2),
|
|
||||||
fulfillment(fb2), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
fulfillment(fb2), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq), condition(cb3),
|
||||||
|
fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
||||||
env(finish("alice", "alice", seq), condition(cb3),
|
env(finish("alice", "alice", seq), condition(cb3),
|
||||||
fulfillment(fb3), fee(1500));
|
fulfillment(fb3), fee(1500));
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// Self-escrowed conditional with DepositAuth and DepositPreauth.
|
||||||
|
Env env(*this);
|
||||||
|
|
||||||
|
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||||
|
auto const seq = env.seq("alice");
|
||||||
|
env(escrow("alice", "alice", XRP(1000)), condition(cb3), finish_time(env.now() + 5s));
|
||||||
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Alice preauthorizes Zelda for deposit, even though Alice has not
|
||||||
|
// set the lsfDepositAuth flag (yet).
|
||||||
|
env(deposit::auth("alice", "zelda"));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Finish is now possible but requires the cryptocondition.
|
||||||
|
env(finish("alice", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("zelda", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
|
||||||
|
// Alice enables deposit authorization. After this only Alice or
|
||||||
|
// Zelda (because Zelda is preauthorized) can finish the escrow.
|
||||||
|
env(fset ("alice", asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env(finish("alice", "alice", seq), condition(cb2),
|
||||||
|
fulfillment(fb2), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq), condition(cb3),
|
||||||
|
fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
||||||
|
env(finish("zelda", "alice", seq), condition(cb3),
|
||||||
|
fulfillment(fb3), fee(1500));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -233,13 +233,13 @@ public:
|
|||||||
// an offer. Show that the attempt to remove the offer fails.
|
// an offer. Show that the attempt to remove the offer fails.
|
||||||
env.require (offers (alice, 2));
|
env.require (offers (alice, 2));
|
||||||
|
|
||||||
// featureChecks changes the return code on an expired Offer. Adapt
|
// featureDepositPreauths changes the return code on an expired Offer.
|
||||||
// to that.
|
// Adapt to that.
|
||||||
bool const featChecks {features[featureChecks]};
|
bool const featPreauth {features[featureDepositPreauth]};
|
||||||
env (offer (alice, XRP (5), USD (2)),
|
env (offer (alice, XRP (5), USD (2)),
|
||||||
json (sfExpiration.fieldName, lastClose(env)),
|
json (sfExpiration.fieldName, lastClose(env)),
|
||||||
json (jss::OfferSequence, offer2Seq),
|
json (jss::OfferSequence, offer2Seq),
|
||||||
ter (featChecks ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
ter (featPreauth ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
env.require (offers (alice, 2));
|
env.require (offers (alice, 2));
|
||||||
@@ -954,12 +954,12 @@ public:
|
|||||||
owners (alice, 1));
|
owners (alice, 1));
|
||||||
|
|
||||||
// Place an offer that should have already expired.
|
// Place an offer that should have already expired.
|
||||||
// The Checks amendment changes the return code; adapt to that.
|
// The DepositPreauth amendment changes the return code; adapt to that.
|
||||||
bool const featChecks {features[featureChecks]};
|
bool const featPreauth {features[featureDepositPreauth]};
|
||||||
|
|
||||||
env (offer (alice, xrpOffer, usdOffer),
|
env (offer (alice, xrpOffer, usdOffer),
|
||||||
json (sfExpiration.fieldName, lastClose(env)),
|
json (sfExpiration.fieldName, lastClose(env)),
|
||||||
ter (featChecks ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
ter (featPreauth ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
||||||
|
|
||||||
env.require (
|
env.require (
|
||||||
balance (alice, startBalance - f - f),
|
balance (alice, startBalance - f - f),
|
||||||
@@ -4390,20 +4390,20 @@ public:
|
|||||||
env(fset (gw, asfRequireAuth));
|
env(fset (gw, asfRequireAuth));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// The test behaves differently with or without FlowCross.
|
// The test behaves differently with or without DepositPreauth.
|
||||||
bool const flowCross = features[featureFlowCross];
|
bool const preauth = features[featureDepositPreauth];
|
||||||
|
|
||||||
// Before FlowCross an account with lsfRequireAuth set could not
|
// Before DepositPreauth an account with lsfRequireAuth set could not
|
||||||
// create an offer to buy their own currency. After FlowCross
|
// create an offer to buy their own currency. After DepositPreauth
|
||||||
// they can.
|
// they can.
|
||||||
env (offer (gw, gwUSD(40), XRP(4000)),
|
env (offer (gw, gwUSD(40), XRP(4000)),
|
||||||
ter (flowCross ? TER {tesSUCCESS} : TER {tecNO_LINE}));
|
ter (preauth ? TER {tesSUCCESS} : TER {tecNO_LINE}));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
env.require (offers (gw, flowCross ? 1 : 0));
|
env.require (offers (gw, preauth ? 1 : 0));
|
||||||
|
|
||||||
if (!flowCross)
|
if (!preauth)
|
||||||
// The rest of the test verifies FlowCross behavior.
|
// The rest of the test verifies DepositPreauth behavior.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Set up an authorized trust line and pay alice gwUSD 50.
|
// Set up an authorized trust line and pay alice gwUSD 50.
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ struct PayChan_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
// Create a channel where dst disallows XRP. Ignore that flag,
|
// Create a channel where dst disallows XRP. Ignore that flag,
|
||||||
// since it's just advisory.
|
// since it's just advisory.
|
||||||
Env env (*this);
|
Env env (*this, supported_amendments());
|
||||||
env.fund (XRP (10000), alice, bob);
|
env.fund (XRP (10000), alice, bob);
|
||||||
env (fset (bob, asfDisallowXRP));
|
env (fset (bob, asfDisallowXRP));
|
||||||
env (create (alice, bob, XRP (1000), 3600s, alice.pk()));
|
env (create (alice, bob, XRP (1000), 3600s, alice.pk()));
|
||||||
@@ -669,7 +669,7 @@ struct PayChan_test : public beast::unit_test::suite
|
|||||||
// Claim to a channel where dst disallows XRP (channel is
|
// Claim to a channel where dst disallows XRP (channel is
|
||||||
// created before disallow xrp is set). Ignore that flag
|
// created before disallow xrp is set). Ignore that flag
|
||||||
// since it is just advisory.
|
// since it is just advisory.
|
||||||
Env env (*this);
|
Env env (*this, supported_amendments());
|
||||||
env.fund (XRP (10000), alice, bob);
|
env.fund (XRP (10000), alice, bob);
|
||||||
env (create (alice, bob, XRP (1000), 3600s, alice.pk()));
|
env (create (alice, bob, XRP (1000), 3600s, alice.pk()));
|
||||||
auto const chan = channel (*env.current (), alice, bob);
|
auto const chan = channel (*env.current (), alice, bob);
|
||||||
@@ -716,10 +716,11 @@ struct PayChan_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
auto const alice = Account ("alice");
|
auto const alice = Account ("alice");
|
||||||
auto const bob = Account ("bob");
|
auto const bob = Account ("bob");
|
||||||
|
auto const carol = Account ("carol");
|
||||||
auto USDA = alice["USD"];
|
auto USDA = alice["USD"];
|
||||||
{
|
{
|
||||||
Env env (*this);
|
Env env (*this);
|
||||||
env.fund (XRP (10000), alice, bob);
|
env.fund (XRP (10000), alice, bob, carol);
|
||||||
|
|
||||||
env (fset (bob, asfDepositAuth));
|
env (fset (bob, asfDepositAuth));
|
||||||
env.close();
|
env.close();
|
||||||
@@ -757,22 +758,76 @@ struct PayChan_test : public beast::unit_test::suite
|
|||||||
env.close();
|
env.close();
|
||||||
BEAST_EXPECT (env.balance (bob) == preBob);
|
BEAST_EXPECT (env.balance (bob) == preBob);
|
||||||
|
|
||||||
|
// bob claims but omits the signature. Fails because only
|
||||||
|
// alice can claim without a signature.
|
||||||
|
env (claim (bob, chan, delta, delta), ter (temBAD_SIGNATURE));
|
||||||
|
env.close();
|
||||||
|
|
||||||
// bob claims with signature. Succeeds even though bob's
|
// bob claims with signature. Succeeds even though bob's
|
||||||
// lsfDepositAuth flag is set since bob signed the transaction.
|
// lsfDepositAuth flag is set since bob submitted the
|
||||||
|
// transaction.
|
||||||
env (claim (bob, chan, delta, delta, Slice (sig), pk));
|
env (claim (bob, chan, delta, delta, Slice (sig), pk));
|
||||||
env.close();
|
env.close();
|
||||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - baseFee);
|
BEAST_EXPECT (env.balance (bob) == preBob + delta - baseFee);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// Explore the limits of deposit preauthorization.
|
||||||
|
auto const delta = XRP (600).value();
|
||||||
|
auto const sig = signClaimAuth (pk, alice.sk (), chan, delta);
|
||||||
|
|
||||||
// bob clears lsfDepositAuth. Now alice can use an unsigned claim.
|
// carol claims and fails. Only channel participants (bob or
|
||||||
env (fclear (bob, asfDepositAuth));
|
// alice) may claim.
|
||||||
env.close();
|
env (claim (carol, chan,
|
||||||
|
delta, delta, Slice (sig), pk), ter (tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
// alice claims successfully.
|
// bob preauthorizes carol for deposit. But after that carol
|
||||||
env (claim (alice, chan, XRP (800).value(), XRP (800).value()));
|
// still can't claim since only channel participants may claim.
|
||||||
env.close();
|
env(deposit::auth (bob, carol));
|
||||||
BEAST_EXPECT (
|
env.close();
|
||||||
env.balance (bob) == preBob + XRP (800) - (2 * baseFee));
|
|
||||||
|
env (claim (carol, chan,
|
||||||
|
delta, delta, Slice (sig), pk), ter (tecNO_PERMISSION));
|
||||||
|
|
||||||
|
// Since alice is not preauthorized she also may not claim
|
||||||
|
// for bob.
|
||||||
|
env (claim (alice, chan, delta, delta,
|
||||||
|
Slice (sig), pk), ter (tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// However if bob preauthorizes alice for deposit then she can
|
||||||
|
// successfully submit a claim.
|
||||||
|
env(deposit::auth (bob, alice));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (claim (alice, chan, delta, delta, Slice (sig), pk));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
BEAST_EXPECT (
|
||||||
|
env.balance (bob) == preBob + delta - (3 * baseFee));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// bob removes preauthorization of alice. Once again she
|
||||||
|
// cannot submit a claim.
|
||||||
|
auto const delta = XRP (800).value();
|
||||||
|
|
||||||
|
env(deposit::unauth (bob, alice));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice claims and fails since she is no longer preauthorized.
|
||||||
|
env (claim (alice, chan, delta, delta), ter (tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// bob clears lsfDepositAuth. Now alice can claim.
|
||||||
|
env (fclear (bob, asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice claims successfully.
|
||||||
|
env (claim (alice, chan, delta, delta));
|
||||||
|
env.close();
|
||||||
|
BEAST_EXPECT (
|
||||||
|
env.balance (bob) == preBob + XRP (800) - (5 * baseFee));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <test/jtx/amount.h>
|
#include <test/jtx/amount.h>
|
||||||
#include <test/jtx/balance.h>
|
#include <test/jtx/balance.h>
|
||||||
#include <test/jtx/delivermin.h>
|
#include <test/jtx/delivermin.h>
|
||||||
|
#include <test/jtx/deposit.h>
|
||||||
#include <test/jtx/Env.h>
|
#include <test/jtx/Env.h>
|
||||||
#include <test/jtx/Env_ss.h>
|
#include <test/jtx/Env_ss.h>
|
||||||
#include <test/jtx/fee.h>
|
#include <test/jtx/fee.h>
|
||||||
|
|||||||
48
src/test/jtx/deposit.h
Normal file
48
src/test/jtx/deposit.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2018 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#ifndef RIPPLE_TEST_JTX_DEPOSIT_H_INCLUDED
|
||||||
|
#define RIPPLE_TEST_JTX_DEPOSIT_H_INCLUDED
|
||||||
|
|
||||||
|
#include <test/jtx/Env.h>
|
||||||
|
#include <test/jtx/Account.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace test {
|
||||||
|
namespace jtx {
|
||||||
|
|
||||||
|
/** Deposit preauthorize operations */
|
||||||
|
namespace deposit {
|
||||||
|
|
||||||
|
/** Preauthorize for deposit. Invoke as deposit::auth. */
|
||||||
|
Json::Value
|
||||||
|
auth (Account const& account, Account const& auth);
|
||||||
|
|
||||||
|
/** Remove preauthorization for deposit. Invoke as deposit::unauth. */
|
||||||
|
Json::Value
|
||||||
|
unauth (Account const& account, Account const& unauth);
|
||||||
|
|
||||||
|
} // deposit
|
||||||
|
|
||||||
|
} // jtx
|
||||||
|
|
||||||
|
} // test
|
||||||
|
} // ripple
|
||||||
|
|
||||||
|
#endif
|
||||||
55
src/test/jtx/impl/deposit.cpp
Normal file
55
src/test/jtx/impl/deposit.cpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2018 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <test/jtx/deposit.h>
|
||||||
|
#include <ripple/protocol/JsonFields.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace test {
|
||||||
|
namespace jtx {
|
||||||
|
|
||||||
|
namespace deposit {
|
||||||
|
|
||||||
|
// Add DepositPreauth.
|
||||||
|
Json::Value
|
||||||
|
auth (jtx::Account const& account, jtx::Account const& auth)
|
||||||
|
{
|
||||||
|
Json::Value jv;
|
||||||
|
jv[sfAccount.jsonName] = account.human();
|
||||||
|
jv[sfAuthorize.jsonName] = auth.human();
|
||||||
|
jv[sfTransactionType.jsonName] = "DepositPreauth";
|
||||||
|
return jv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove DepositPreauth.
|
||||||
|
Json::Value
|
||||||
|
unauth (jtx::Account const& account, jtx::Account const& unauth)
|
||||||
|
{
|
||||||
|
Json::Value jv;
|
||||||
|
jv[sfAccount.jsonName] = account.human();
|
||||||
|
jv[sfUnauthorize.jsonName] = unauth.human();
|
||||||
|
jv[sfTransactionType.jsonName] = "DepositPreauth";
|
||||||
|
return jv;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // deposit
|
||||||
|
|
||||||
|
} // jtx
|
||||||
|
} // test
|
||||||
|
} // ripple
|
||||||
@@ -355,6 +355,7 @@ public:
|
|||||||
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::account), 0));
|
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::account), 0));
|
||||||
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::amendments), 0));
|
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::amendments), 0));
|
||||||
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::check), 0));
|
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::check), 0));
|
||||||
|
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::deposit_preauth), 0));
|
||||||
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::directory), 0));
|
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::directory), 0));
|
||||||
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::escrow), 0));
|
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::escrow), 0));
|
||||||
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::fee), 0));
|
BEAST_EXPECT (acct_objs_is_size (acct_objs (gw, jss::fee), 0));
|
||||||
@@ -400,6 +401,18 @@ public:
|
|||||||
BEAST_EXPECT (check[sfDestination.jsonName] == alice.human());
|
BEAST_EXPECT (check[sfDestination.jsonName] == alice.human());
|
||||||
BEAST_EXPECT (check[sfSendMax.jsonName][jss::value].asUInt() == 10);
|
BEAST_EXPECT (check[sfSendMax.jsonName][jss::value].asUInt() == 10);
|
||||||
}
|
}
|
||||||
|
// gw preauthorizes payments from alice.
|
||||||
|
env (deposit::auth (gw, alice));
|
||||||
|
env.close();
|
||||||
|
{
|
||||||
|
// Find the preauthorization.
|
||||||
|
Json::Value const resp = acct_objs (gw, jss::deposit_preauth);
|
||||||
|
BEAST_EXPECT (acct_objs_is_size (resp, 1));
|
||||||
|
|
||||||
|
auto const& preauth = resp[jss::result][jss::account_objects][0u];
|
||||||
|
BEAST_EXPECT (preauth[sfAccount.jsonName] == gw.human());
|
||||||
|
BEAST_EXPECT (preauth[sfAuthorize.jsonName] == alice.human());
|
||||||
|
}
|
||||||
{
|
{
|
||||||
// gw creates an escrow that we can look for in the ledger.
|
// gw creates an escrow that we can look for in the ledger.
|
||||||
Json::Value jvEscrow;
|
Json::Value jvEscrow;
|
||||||
@@ -485,7 +498,7 @@ public:
|
|||||||
auto const& ticket = resp[jss::result][jss::account_objects][0u];
|
auto const& ticket = resp[jss::result][jss::account_objects][0u];
|
||||||
BEAST_EXPECT (ticket[sfAccount.jsonName] == gw.human());
|
BEAST_EXPECT (ticket[sfAccount.jsonName] == gw.human());
|
||||||
BEAST_EXPECT (ticket[sfLedgerEntryType.jsonName] == "Ticket");
|
BEAST_EXPECT (ticket[sfLedgerEntryType.jsonName] == "Ticket");
|
||||||
BEAST_EXPECT (ticket[sfSequence.jsonName].asUInt() == 8);
|
BEAST_EXPECT (ticket[sfSequence.jsonName].asUInt() == 9);
|
||||||
}
|
}
|
||||||
// Run up the number of directory entries so gw has two
|
// Run up the number of directory entries so gw has two
|
||||||
// directory nodes.
|
// directory nodes.
|
||||||
|
|||||||
230
src/test/rpc/DepositAuthorized_test.cpp
Normal file
230
src/test/rpc/DepositAuthorized_test.cpp
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2018 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <ripple/protocol/JsonFields.h> // jss:: definitions
|
||||||
|
#include <test/jtx.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
class DepositAuthorized_test : public beast::unit_test::suite
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Helper function that builds arguments for a deposit_authorized command.
|
||||||
|
static Json::Value depositAuthArgs (
|
||||||
|
jtx::Account const& source,
|
||||||
|
jtx::Account const& dest, std::string const& ledger = "")
|
||||||
|
{
|
||||||
|
Json::Value args {Json::objectValue};
|
||||||
|
args[jss::source_account] = source.human();
|
||||||
|
args[jss::destination_account] = dest.human();
|
||||||
|
if (! ledger.empty())
|
||||||
|
args[jss::ledger_index] = ledger;
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function that verifies a deposit_authorized request was
|
||||||
|
// successful and returned the expected value.
|
||||||
|
void validateDepositAuthResult (Json::Value const& result, bool authorized)
|
||||||
|
{
|
||||||
|
Json::Value const& results {result[jss::result]};
|
||||||
|
BEAST_EXPECT (results[jss::deposit_authorized] == authorized);
|
||||||
|
BEAST_EXPECT (results[jss::status] == jss::success);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test a variety of non-malformed cases.
|
||||||
|
void testValid()
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
Account const alice {"alice"};
|
||||||
|
Account const becky {"becky"};
|
||||||
|
Account const carol {"carol"};
|
||||||
|
|
||||||
|
Env env(*this);
|
||||||
|
env.fund(XRP(1000), alice, becky, carol);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky is authorized to deposit to herself.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (
|
||||||
|
becky, becky, "validated").toStyledString()), true);
|
||||||
|
|
||||||
|
// alice should currently be authorized to deposit to becky.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (
|
||||||
|
alice, becky, "validated").toStyledString()), true);
|
||||||
|
|
||||||
|
// becky sets the DepositAuth flag in the current ledger.
|
||||||
|
env (fset (becky, asfDepositAuth));
|
||||||
|
|
||||||
|
// alice is no longer authorized to deposit to becky in current ledger.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (alice, becky).toStyledString()), false);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// becky is still authorized to deposit to herself.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (
|
||||||
|
becky, becky, "validated").toStyledString()), true);
|
||||||
|
|
||||||
|
// It's not a reciprocal arrangement. becky can deposit to alice.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (
|
||||||
|
becky, alice, "current").toStyledString()), true);
|
||||||
|
|
||||||
|
// becky creates a deposit authorization for alice.
|
||||||
|
env (deposit::auth (becky, alice));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice is now authorized to deposit to becky.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (alice, becky, "closed").toStyledString()), true);
|
||||||
|
|
||||||
|
// carol is still not authorized to deposit to becky.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (carol, becky).toStyledString()), false);
|
||||||
|
|
||||||
|
// becky clears the the DepositAuth flag so carol becomes authorized.
|
||||||
|
env (fclear (becky, asfDepositAuth));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (carol, becky).toStyledString()), true);
|
||||||
|
|
||||||
|
// alice is still authorized to deposit to becky.
|
||||||
|
validateDepositAuthResult (env.rpc ("json", "deposit_authorized",
|
||||||
|
depositAuthArgs (alice, becky).toStyledString()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test malformed cases.
|
||||||
|
void testErrors()
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
Account const alice {"alice"};
|
||||||
|
Account const becky {"becky"};
|
||||||
|
|
||||||
|
// Lambda that checks the (error) result of deposit_authorized.
|
||||||
|
auto 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
Env env(*this);
|
||||||
|
{
|
||||||
|
// Missing source_account field.
|
||||||
|
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'.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 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.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 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.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 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'.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Non-string destination_account field.
|
||||||
|
Json::Value args {depositAuthArgs (alice, becky)};
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 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.");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Request an invalid ledger.
|
||||||
|
Json::Value args {depositAuthArgs (alice, becky, "17")};
|
||||||
|
Json::Value const result {env.rpc (
|
||||||
|
"json", "deposit_authorized", args.toStyledString())};
|
||||||
|
verifyErr (result, "invalidParams", "ledgerIndexMalformed");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// becky is not yet funded.
|
||||||
|
Json::Value args {depositAuthArgs (alice, becky)};
|
||||||
|
Json::Value const result {env.rpc (
|
||||||
|
"json", "deposit_authorized", args.toStyledString())};
|
||||||
|
verifyErr (result, "dstActMissing",
|
||||||
|
"Destination account does not exist.");
|
||||||
|
}
|
||||||
|
env.fund(XRP(1000), becky);
|
||||||
|
env.close();
|
||||||
|
{
|
||||||
|
// Once becky is funded try it again and see it succeed.
|
||||||
|
Json::Value args {depositAuthArgs (alice, becky)};
|
||||||
|
Json::Value const result {env.rpc (
|
||||||
|
"json", "deposit_authorized", args.toStyledString())};
|
||||||
|
validateDepositAuthResult (result, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
testValid();
|
||||||
|
testErrors();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BEAST_DEFINE_TESTSUITE(DepositAuthorized,app,ripple);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -325,9 +325,11 @@ public:
|
|||||||
env(jv);
|
env(jv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bob9 DepositPreauths bob4 and bob8.
|
||||||
|
env (deposit::auth (Account {"bob9"}, Account {"bob4"}));
|
||||||
|
env (deposit::auth (Account {"bob9"}, Account {"bob8"}));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
|
|
||||||
// Now fetch each type
|
// Now fetch each type
|
||||||
auto makeRequest = [&env](Json::StaticString t)
|
auto makeRequest = [&env](Json::StaticString t)
|
||||||
{
|
{
|
||||||
@@ -354,7 +356,7 @@ public:
|
|||||||
|
|
||||||
{ // jvParams[jss::type] = "directory";
|
{ // jvParams[jss::type] = "directory";
|
||||||
auto const jrr = makeRequest(jss::directory);
|
auto const jrr = makeRequest(jss::directory);
|
||||||
BEAST_EXPECT( checkArraySize(jrr[jss::state], 7) );
|
BEAST_EXPECT( checkArraySize(jrr[jss::state], 8) );
|
||||||
for (auto const& j : jrr[jss::state])
|
for (auto const& j : jrr[jss::state])
|
||||||
BEAST_EXPECT( j["LedgerEntryType"] == "DirectoryNode" );
|
BEAST_EXPECT( j["LedgerEntryType"] == "DirectoryNode" );
|
||||||
}
|
}
|
||||||
@@ -415,6 +417,13 @@ public:
|
|||||||
BEAST_EXPECT( j["LedgerEntryType"] == "PayChannel" );
|
BEAST_EXPECT( j["LedgerEntryType"] == "PayChannel" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // jvParams[jss::type] = "deposit_preauth";
|
||||||
|
auto const jrr = makeRequest(jss::deposit_preauth);
|
||||||
|
BEAST_EXPECT( checkArraySize(jrr[jss::state], 2) );
|
||||||
|
for (auto const& j : jrr[jss::state])
|
||||||
|
BEAST_EXPECT( j["LedgerEntryType"] == "DepositPreauth" );
|
||||||
|
}
|
||||||
|
|
||||||
{ // jvParams[jss::type] = "misspelling";
|
{ // jvParams[jss::type] = "misspelling";
|
||||||
Json::Value jvParams;
|
Json::Value jvParams;
|
||||||
jvParams[jss::ledger_index] = "current";
|
jvParams[jss::ledger_index] = "current";
|
||||||
|
|||||||
@@ -377,6 +377,132 @@ class LedgerRPC_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testLedgerEntryDepositPreauth()
|
||||||
|
{
|
||||||
|
testcase ("ledger_entry Request Directory");
|
||||||
|
using namespace test::jtx;
|
||||||
|
Env env {*this};
|
||||||
|
Account const alice {"alice"};
|
||||||
|
Account const becky {"becky"};
|
||||||
|
|
||||||
|
env.fund (XRP(10000), alice, becky);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env (deposit::auth (alice, becky));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
std::string const ledgerHash {to_string (env.closed()->info().hash)};
|
||||||
|
std::string depositPreauthIndex;
|
||||||
|
{
|
||||||
|
// Request a depositPreauth by owner and authorized.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
||||||
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
jrr[jss::node][sfLedgerEntryType.jsonName] == "DepositPreauth");
|
||||||
|
BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
|
||||||
|
BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
|
||||||
|
depositPreauthIndex = jrr[jss::node][jss::index].asString();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Request a depositPreauth by index.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth] = depositPreauthIndex;
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
jrr[jss::node][sfLedgerEntryType.jsonName] == "DepositPreauth");
|
||||||
|
BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
|
||||||
|
BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed request: deposit_preauth neither object nor string.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth] = -5;
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedRequest", "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed request: deposit_preauth not hex string.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth] = "0123456789ABCDEFG";
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedRequest", "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed request: missing [jss::deposit_preauth][jss::owner]
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedRequest", "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed request: [jss::deposit_preauth][jss::owner] not string.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth][jss::owner] = 7;
|
||||||
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedRequest", "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed: missing [jss::deposit_preauth][jss::authorized]
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedRequest", "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed: [jss::deposit_preauth][jss::authorized] not string.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
||||||
|
jvParams[jss::deposit_preauth][jss::authorized] = 47;
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedRequest", "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed: [jss::deposit_preauth][jss::owner] is malformed.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth][jss::owner] =
|
||||||
|
"rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
|
||||||
|
|
||||||
|
jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedOwner", "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Malformed: [jss::deposit_preauth][jss::authorized] is malformed.
|
||||||
|
Json::Value jvParams;
|
||||||
|
jvParams[jss::deposit_preauth][jss::owner] = alice.human();
|
||||||
|
jvParams[jss::deposit_preauth][jss::authorized] =
|
||||||
|
"rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
|
||||||
|
|
||||||
|
jvParams[jss::ledger_hash] = ledgerHash;
|
||||||
|
Json::Value const jrr = env.rpc (
|
||||||
|
"json", "ledger_entry", to_string (jvParams))[jss::result];
|
||||||
|
checkErrorValue (jrr, "malformedAuthorized", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void testLedgerEntryDirectory()
|
void testLedgerEntryDirectory()
|
||||||
{
|
{
|
||||||
testcase ("ledger_entry Request Directory");
|
testcase ("ledger_entry Request Directory");
|
||||||
@@ -1385,6 +1511,7 @@ public:
|
|||||||
testLedgerAccounts();
|
testLedgerAccounts();
|
||||||
testLedgerEntryAccountRoot();
|
testLedgerEntryAccountRoot();
|
||||||
testLedgerEntryCheck();
|
testLedgerEntryCheck();
|
||||||
|
testLedgerEntryDepositPreauth();
|
||||||
testLedgerEntryDirectory();
|
testLedgerEntryDirectory();
|
||||||
testLedgerEntryEscrow();
|
testLedgerEntryEscrow();
|
||||||
testLedgerEntryGenerator();
|
testLedgerEntryGenerator();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <test/jtx/impl/amount.cpp>
|
#include <test/jtx/impl/amount.cpp>
|
||||||
#include <test/jtx/impl/balance.cpp>
|
#include <test/jtx/impl/balance.cpp>
|
||||||
#include <test/jtx/impl/delivermin.cpp>
|
#include <test/jtx/impl/delivermin.cpp>
|
||||||
|
#include <test/jtx/impl/deposit.cpp>
|
||||||
#include <test/jtx/impl/Env.cpp>
|
#include <test/jtx/impl/Env.cpp>
|
||||||
#include <test/jtx/impl/envconfig.cpp>
|
#include <test/jtx/impl/envconfig.cpp>
|
||||||
#include <test/jtx/impl/fee.cpp>
|
#include <test/jtx/impl/fee.cpp>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <test/rpc/AccountTx_test.cpp>
|
#include <test/rpc/AccountTx_test.cpp>
|
||||||
#include <test/rpc/AmendmentBlocked_test.cpp>
|
#include <test/rpc/AmendmentBlocked_test.cpp>
|
||||||
#include <test/rpc/Book_test.cpp>
|
#include <test/rpc/Book_test.cpp>
|
||||||
|
#include <test/rpc/DepositAuthorized_test.cpp>
|
||||||
#include <test/rpc/Feature_test.cpp>
|
#include <test/rpc/Feature_test.cpp>
|
||||||
#include <test/rpc/GatewayBalances_test.cpp>
|
#include <test/rpc/GatewayBalances_test.cpp>
|
||||||
#include <test/rpc/GetCounts_test.cpp>
|
#include <test/rpc/GetCounts_test.cpp>
|
||||||
|
|||||||
Reference in New Issue
Block a user