mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-02 08:25:55 +00:00
Introduce CheckCashMakesTrustLine amendment:
With this amendment, the CheckCash transaction creates a TrustLine if needed. The change is modeled after offer crossing. And, similar to offer crossing, cashing a check allows an account to exceed its trust line limit.
This commit is contained in:
committed by
manojsdoshi
parent
8d59c7dd40
commit
bf75094224
@@ -21,6 +21,7 @@
|
|||||||
#include <ripple/app/paths/Flow.h>
|
#include <ripple/app/paths/Flow.h>
|
||||||
#include <ripple/app/tx/impl/CashCheck.h>
|
#include <ripple/app/tx/impl/CashCheck.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/basics/scope.h>
|
||||||
#include <ripple/protocol/Feature.h>
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/Indexes.h>
|
#include <ripple/protocol/Indexes.h>
|
||||||
#include <ripple/protocol/STAccount.h>
|
#include <ripple/protocol/STAccount.h>
|
||||||
@@ -89,13 +90,13 @@ CashCheck::preclaim(PreclaimContext const& ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only cash a check with this account as the destination.
|
// Only cash a check with this account as the destination.
|
||||||
AccountID const dstId{(*sleCheck)[sfDestination]};
|
AccountID const dstId = sleCheck->at(sfDestination);
|
||||||
if (ctx.tx[sfAccount] != dstId)
|
if (ctx.tx[sfAccount] != dstId)
|
||||||
{
|
{
|
||||||
JLOG(ctx.j.warn()) << "Cashing a check with wrong Destination.";
|
JLOG(ctx.j.warn()) << "Cashing a check with wrong Destination.";
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
}
|
}
|
||||||
AccountID const srcId{(*sleCheck)[sfAccount]};
|
AccountID const srcId = sleCheck->at(sfAccount);
|
||||||
if (srcId == dstId)
|
if (srcId == dstId)
|
||||||
{
|
{
|
||||||
// They wrote a check to themselves. This should be caught when
|
// They wrote a check to themselves. This should be caught when
|
||||||
@@ -127,7 +128,7 @@ CashCheck::preclaim(PreclaimContext const& ctx)
|
|||||||
{
|
{
|
||||||
using duration = NetClock::duration;
|
using duration = NetClock::duration;
|
||||||
using timepoint = NetClock::time_point;
|
using timepoint = NetClock::time_point;
|
||||||
auto const optExpiry = (*sleCheck)[~sfExpiration];
|
auto const optExpiry = sleCheck->at(~sfExpiration);
|
||||||
|
|
||||||
// Expiration is defined in terms of the close time of the parent
|
// Expiration is defined in terms of the close time of the parent
|
||||||
// ledger, because we definitively know the time that it closed but
|
// ledger, because we definitively know the time that it closed but
|
||||||
@@ -148,7 +149,7 @@ CashCheck::preclaim(PreclaimContext const& ctx)
|
|||||||
return optAmount ? *optAmount : tx[sfDeliverMin];
|
return optAmount ? *optAmount : tx[sfDeliverMin];
|
||||||
}(ctx.tx)};
|
}(ctx.tx)};
|
||||||
|
|
||||||
STAmount const sendMax{(*sleCheck)[sfSendMax]};
|
STAmount const sendMax = sleCheck->at(sfSendMax);
|
||||||
Currency const currency{value.getCurrency()};
|
Currency const currency{value.getCurrency()};
|
||||||
if (currency != sendMax.getCurrency())
|
if (currency != sendMax.getCurrency())
|
||||||
{
|
{
|
||||||
@@ -172,7 +173,7 @@ CashCheck::preclaim(PreclaimContext const& ctx)
|
|||||||
{
|
{
|
||||||
STAmount availableFunds{accountFunds(
|
STAmount availableFunds{accountFunds(
|
||||||
ctx.view,
|
ctx.view,
|
||||||
(*sleCheck)[sfAccount],
|
sleCheck->at(sfAccount),
|
||||||
value,
|
value,
|
||||||
fhZERO_IF_FROZEN,
|
fhZERO_IF_FROZEN,
|
||||||
ctx.j)};
|
ctx.j)};
|
||||||
@@ -197,7 +198,9 @@ CashCheck::preclaim(PreclaimContext const& ctx)
|
|||||||
{
|
{
|
||||||
auto const sleTrustLine =
|
auto const sleTrustLine =
|
||||||
ctx.view.read(keylet::line(dstId, issuerId, currency));
|
ctx.view.read(keylet::line(dstId, issuerId, currency));
|
||||||
if (!sleTrustLine)
|
|
||||||
|
if (!sleTrustLine &&
|
||||||
|
!ctx.view.rules().enabled(featureCheckCashMakesTrustLine))
|
||||||
{
|
{
|
||||||
JLOG(ctx.j.warn())
|
JLOG(ctx.j.warn())
|
||||||
<< "Cannot cash check for IOU without trustline.";
|
<< "Cannot cash check for IOU without trustline.";
|
||||||
@@ -213,15 +216,22 @@ CashCheck::preclaim(PreclaimContext const& ctx)
|
|||||||
return tecNO_ISSUER;
|
return tecNO_ISSUER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((*sleIssuer)[sfFlags] & lsfRequireAuth)
|
if (sleIssuer->at(sfFlags) & lsfRequireAuth)
|
||||||
{
|
{
|
||||||
|
if (!sleTrustLine)
|
||||||
|
{
|
||||||
|
// We can only create a trust line if the issuer does not
|
||||||
|
// have requireAuth set.
|
||||||
|
return tecNO_AUTH;
|
||||||
|
}
|
||||||
|
|
||||||
// Entries have a canonical representation, determined by a
|
// Entries have a canonical representation, determined by a
|
||||||
// lexicographical "greater than" comparison employing strict
|
// lexicographical "greater than" comparison employing strict
|
||||||
// weak ordering. Determine which entry we need to access.
|
// weak ordering. Determine which entry we need to access.
|
||||||
bool const canonical_gt(dstId > issuerId);
|
bool const canonical_gt(dstId > issuerId);
|
||||||
|
|
||||||
bool const is_authorized(
|
bool const is_authorized(
|
||||||
(*sleTrustLine)[sfFlags] &
|
sleTrustLine->at(sfFlags) &
|
||||||
(canonical_gt ? lsfLowAuth : lsfHighAuth));
|
(canonical_gt ? lsfLowAuth : lsfHighAuth));
|
||||||
|
|
||||||
if (!is_authorized)
|
if (!is_authorized)
|
||||||
@@ -286,9 +296,10 @@ CashCheck::doApply()
|
|||||||
auto viewJ = ctx_.app.journal("View");
|
auto viewJ = ctx_.app.journal("View");
|
||||||
auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
|
auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
|
||||||
bool const doFix1623{ctx_.view().rules().enabled(fix1623)};
|
bool const doFix1623{ctx_.view().rules().enabled(fix1623)};
|
||||||
|
|
||||||
if (srcId != account_)
|
if (srcId != account_)
|
||||||
{
|
{
|
||||||
STAmount const sendMax{sleCheck->getFieldAmount(sfSendMax)};
|
STAmount const sendMax = sleCheck->at(sfSendMax);
|
||||||
|
|
||||||
// Flow() doesn't do XRP to XRP transfers.
|
// Flow() doesn't do XRP to XRP transfers.
|
||||||
if (sendMax.native())
|
if (sendMax.native())
|
||||||
@@ -334,19 +345,106 @@ CashCheck::doApply()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Let flow() do the heavy lifting on a check for an IOU.
|
|
||||||
//
|
|
||||||
// Note that for DeliverMin we don't know exactly how much
|
// Note that for DeliverMin we don't know exactly how much
|
||||||
// currency we want flow to deliver. We can't ask for the
|
// currency we want flow to deliver. We can't ask for the
|
||||||
// maximum possible currency because there might be a gateway
|
// maximum possible currency because there might be a gateway
|
||||||
// transfer rate to account for. Since the transfer rate cannot
|
// transfer rate to account for. Since the transfer rate cannot
|
||||||
// exceed 200%, we use 1/2 maxValue as our limit.
|
// exceed 200%, we use 1/2 maxValue as our limit.
|
||||||
STAmount const flowDeliver{
|
STAmount const flowDeliver{
|
||||||
optDeliverMin
|
optDeliverMin ? STAmount(
|
||||||
? STAmount{optDeliverMin->issue(), STAmount::cMaxValue / 2, STAmount::cMaxOffset}
|
optDeliverMin->issue(),
|
||||||
: static_cast<STAmount>(ctx_.tx[sfAmount])};
|
STAmount::cMaxValue / 2,
|
||||||
|
STAmount::cMaxOffset)
|
||||||
|
: ctx_.tx.getFieldAmount(sfAmount)};
|
||||||
|
|
||||||
// Call the payment engine's flow() to do the actual work.
|
// If a trust line does not exist yet create one.
|
||||||
|
Issue const& trustLineIssue = flowDeliver.issue();
|
||||||
|
AccountID const issuer = flowDeliver.getIssuer();
|
||||||
|
AccountID const truster = issuer == account_ ? srcId : account_;
|
||||||
|
Keylet const trustLineKey = keylet::line(truster, trustLineIssue);
|
||||||
|
bool const destLow = issuer > account_;
|
||||||
|
|
||||||
|
bool const checkCashMakesTrustLine =
|
||||||
|
psb.rules().enabled(featureCheckCashMakesTrustLine);
|
||||||
|
|
||||||
|
if (checkCashMakesTrustLine && !psb.exists(trustLineKey))
|
||||||
|
{
|
||||||
|
// 1. Can the check casher meet the reserve for the trust line?
|
||||||
|
// 2. Create trust line between destination (this) account
|
||||||
|
// and the issuer.
|
||||||
|
// 3. Apply correct noRipple settings on trust line. Use...
|
||||||
|
// a. this (destination) account and
|
||||||
|
// b. issuing account (not sending account).
|
||||||
|
|
||||||
|
// Can the account cover the trust line's reserve?
|
||||||
|
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
|
||||||
|
mPriorBalance < psb.fees().accountReserve(ownerCount + 1))
|
||||||
|
{
|
||||||
|
JLOG(j_.trace()) << "Trust line does not exist. "
|
||||||
|
"Insufficent reserve to create line.";
|
||||||
|
|
||||||
|
return tecNO_LINE_INSUF_RESERVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Currency const currency = flowDeliver.getCurrency();
|
||||||
|
STAmount initialBalance(flowDeliver.issue());
|
||||||
|
initialBalance.setIssuer(noAccount());
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
if (TER const ter = trustCreate(
|
||||||
|
psb, // payment sandbox
|
||||||
|
destLow, // is dest low?
|
||||||
|
issuer, // source
|
||||||
|
account_, // destination
|
||||||
|
trustLineKey.key, // ledger index
|
||||||
|
sleDst, // Account to add to
|
||||||
|
false, // authorize account
|
||||||
|
(sleDst->getFlags() & lsfDefaultRipple) == 0,
|
||||||
|
false, // freeze trust line
|
||||||
|
initialBalance, // zero initial balance
|
||||||
|
Issue(currency, account_), // limit of zero
|
||||||
|
0, // quality in
|
||||||
|
0, // quality out
|
||||||
|
viewJ); // journal
|
||||||
|
!isTesSuccess(ter))
|
||||||
|
{
|
||||||
|
return ter;
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Note that we _don't_ need to be careful about destroying
|
||||||
|
// the trust line if the check cashing fails. The transaction
|
||||||
|
// machinery will automatically clean it up.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the destination is signing the check, they clearly want
|
||||||
|
// the funds even if their new total funds would exceed the limit
|
||||||
|
// on their trust line. So we tweak the trust line limits before
|
||||||
|
// calling flow and then restore the trust line limits afterwards.
|
||||||
|
auto const sleTrustLine = psb.peek(trustLineKey);
|
||||||
|
if (!sleTrustLine)
|
||||||
|
return tecNO_LINE;
|
||||||
|
|
||||||
|
SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
|
||||||
|
STAmount const savedLimit = sleTrustLine->at(tweakedLimit);
|
||||||
|
|
||||||
|
// Make sure the tweaked limits are restored when we leave scope.
|
||||||
|
scope_exit fixup(
|
||||||
|
[&psb, &trustLineKey, &tweakedLimit, &savedLimit]() {
|
||||||
|
if (auto const sleTrustLine = psb.peek(trustLineKey))
|
||||||
|
sleTrustLine->at(tweakedLimit) = savedLimit;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (checkCashMakesTrustLine)
|
||||||
|
{
|
||||||
|
// Set the trust line limit to the highest possible value
|
||||||
|
// while flow runs.
|
||||||
|
STAmount const bigAmount(
|
||||||
|
trustLineIssue, STAmount::cMaxValue, STAmount::cMaxOffset);
|
||||||
|
sleTrustLine->at(tweakedLimit) = bigAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let flow() do the heavy lifting on a check for an IOU.
|
||||||
auto const result = flow(
|
auto const result = flow(
|
||||||
psb,
|
psb,
|
||||||
flowDeliver,
|
flowDeliver,
|
||||||
@@ -376,10 +474,14 @@ CashCheck::doApply()
|
|||||||
<< "flow did not produce DeliverMin.";
|
<< "flow did not produce DeliverMin.";
|
||||||
return tecPATH_PARTIAL;
|
return tecPATH_PARTIAL;
|
||||||
}
|
}
|
||||||
if (doFix1623)
|
if (doFix1623 && !checkCashMakesTrustLine)
|
||||||
// Set the delivered_amount metadata.
|
// Set the delivered_amount metadata.
|
||||||
ctx_.deliver(result.actualAmountOut);
|
ctx_.deliver(result.actualAmountOut);
|
||||||
}
|
}
|
||||||
|
// Set the delivered amount metadata in all cases, not just
|
||||||
|
// for DeliverMin.
|
||||||
|
if (checkCashMakesTrustLine)
|
||||||
|
ctx_.deliver(result.actualAmountOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +489,7 @@ CashCheck::doApply()
|
|||||||
// check link from destination directory.
|
// check link from destination directory.
|
||||||
if (srcId != account_)
|
if (srcId != account_)
|
||||||
{
|
{
|
||||||
std::uint64_t const page{(*sleCheck)[sfDestinationNode]};
|
std::uint64_t const page = {sleCheck->at(sfDestinationNode)};
|
||||||
if (!ctx_.view().dirRemove(
|
if (!ctx_.view().dirRemove(
|
||||||
keylet::ownerDir(account_), page, sleCheck->key(), true))
|
keylet::ownerDir(account_), page, sleCheck->key(), true))
|
||||||
{
|
{
|
||||||
@@ -397,7 +499,7 @@ CashCheck::doApply()
|
|||||||
}
|
}
|
||||||
// Remove check from check owner's directory.
|
// Remove check from check owner's directory.
|
||||||
{
|
{
|
||||||
std::uint64_t const page{(*sleCheck)[sfOwnerNode]};
|
std::uint64_t const page = {sleCheck->at(sfOwnerNode)};
|
||||||
if (!ctx_.view().dirRemove(
|
if (!ctx_.view().dirRemove(
|
||||||
keylet::ownerDir(srcId), page, sleCheck->key(), true))
|
keylet::ownerDir(srcId), page, sleCheck->key(), true))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ class FeatureCollections
|
|||||||
"FlowSortStrands",
|
"FlowSortStrands",
|
||||||
"fixSTAmountCanonicalize",
|
"fixSTAmountCanonicalize",
|
||||||
"fixRmSmallIncreasedQOffers",
|
"fixRmSmallIncreasedQOffers",
|
||||||
|
"CheckCashMakesTrustLine",
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<uint256> features;
|
std::vector<uint256> features;
|
||||||
@@ -378,6 +379,7 @@ extern uint256 const featureTicketBatch;
|
|||||||
extern uint256 const featureFlowSortStrands;
|
extern uint256 const featureFlowSortStrands;
|
||||||
extern uint256 const fixSTAmountCanonicalize;
|
extern uint256 const fixSTAmountCanonicalize;
|
||||||
extern uint256 const fixRmSmallIncreasedQOffers;
|
extern uint256 const fixRmSmallIncreasedQOffers;
|
||||||
|
extern uint256 const featureCheckCashMakesTrustLine;
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ detail::supportedAmendments()
|
|||||||
"FlowSortStrands",
|
"FlowSortStrands",
|
||||||
"fixSTAmountCanonicalize",
|
"fixSTAmountCanonicalize",
|
||||||
"fixRmSmallIncreasedQOffers",
|
"fixRmSmallIncreasedQOffers",
|
||||||
|
"CheckCashMakesTrustLine",
|
||||||
};
|
};
|
||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
@@ -192,7 +193,8 @@ uint256 const
|
|||||||
featureTicketBatch = *getRegisteredFeature("TicketBatch"),
|
featureTicketBatch = *getRegisteredFeature("TicketBatch"),
|
||||||
featureFlowSortStrands = *getRegisteredFeature("FlowSortStrands"),
|
featureFlowSortStrands = *getRegisteredFeature("FlowSortStrands"),
|
||||||
fixSTAmountCanonicalize = *getRegisteredFeature("fixSTAmountCanonicalize"),
|
fixSTAmountCanonicalize = *getRegisteredFeature("fixSTAmountCanonicalize"),
|
||||||
fixRmSmallIncreasedQOffers = *getRegisteredFeature("fixRmSmallIncreasedQOffers");
|
fixRmSmallIncreasedQOffers = *getRegisteredFeature("fixRmSmallIncreasedQOffers"),
|
||||||
|
featureCheckCashMakesTrustLine = *getRegisteredFeature("CheckCashMakesTrustLine");
|
||||||
|
|
||||||
// The following amendments have been active for at least two years. Their
|
// The following amendments have been active for at least two years. Their
|
||||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -348,8 +348,9 @@ Env::postconditions(JTx const& jt, TER ter, bool didApply)
|
|||||||
if (jt.ter &&
|
if (jt.ter &&
|
||||||
!test.expect(
|
!test.expect(
|
||||||
ter == *jt.ter,
|
ter == *jt.ter,
|
||||||
"apply: " + transToken(ter) + " (" + transHuman(ter) + ") != " +
|
"apply: Got " + transToken(ter) + " (" + transHuman(ter) +
|
||||||
transToken(*jt.ter) + " (" + transHuman(*jt.ter) + ")"))
|
"); Expected " + transToken(*jt.ter) + " (" +
|
||||||
|
transHuman(*jt.ter) + ")"))
|
||||||
{
|
{
|
||||||
test.log << pretty(jt.jv) << std::endl;
|
test.log << pretty(jt.jv) << std::endl;
|
||||||
// Don't check postconditions if
|
// Don't check postconditions if
|
||||||
|
|||||||
Reference in New Issue
Block a user