templates

This commit is contained in:
Richard Holland
2022-04-07 16:28:30 +00:00
parent 1b80bf77a8
commit 76b57a4e88
4 changed files with 316 additions and 265 deletions

View File

@@ -243,7 +243,7 @@ EscrowCreate::doApply()
// check if the escrow is capable of being
// finished before we allow it to be created
if (TER result =
trustXferAllowed(
trustTransferAllowed(
ctx_.view(),
{account, ctx_.tx[sfDestination]},
amount.issue());
@@ -253,7 +253,12 @@ EscrowCreate::doApply()
// perform the lock as a dry run before
// we modify anything on-ledger
sleLine = ctx_.view().peek(keylet::line(account, amount.getIssuer(), amount.getCurrency()));
if (TER result = trustAdjustLockedBalance(ctx_.view(), sleLine, amount, true);
if (TER result =
trustAdjustLockedBalance(
ctx_.view(),
sleLine,
amount,
true);
result != tesSUCCESS)
return result;
}
@@ -320,7 +325,12 @@ EscrowCreate::doApply()
// do the lock-up for real now
TER result =
trustAdjustLockedBalance(ctx_.view(), sleLine, amount, false);
trustAdjustLockedBalance(
ctx_.view(),
sleLine,
amount,
false);
if (result != tesSUCCESS)
return result;
}
@@ -423,6 +433,10 @@ EscrowFinish::doApply()
if (!slep)
return tecNO_TARGET;
AccountID const account = (*slep)[sfAccount];
auto const sle = ctx_.view().peek(keylet::account(account));
auto amount = slep->getFieldAmount(sfAmount);
// If a cancel time is present, a finish operation should only succeed prior
// to that time. fix1571 corrects a logic error in the check that would make
// a finish only succeed strictly after the cancel time.
@@ -523,7 +537,28 @@ EscrowFinish::doApply()
}
}
AccountID const account = (*slep)[sfAccount];
if (!isXRP(amount))
{
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
// perform a dry run of the transfer before we
// change anything on-ledger
TER result =
trustTransferLockedBalance(
ctx_.view(),
account_, // txn signing account
sle, // src account
sled, // dst account
amount, // xfer amount
ctx_.journal,
true // dry run
);
if (result != tesSUCCESS)
return result;
}
// Remove escrow from owner directory
{
@@ -547,28 +582,25 @@ EscrowFinish::doApply()
}
}
auto const sle = ctx_.view().peek(keylet::account(account));
auto amount = slep->getFieldAmount(sfAmount);
if (isXRP(amount))
(*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
else
{
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
// all the significant complexity of checking the validity of this
// transfer and ensuring the lines exist etc is hidden away in this
// function, all we need to do is call it and return if unsuccessful.
TER result =
trustXferLockedBalance(
trustTransferLockedBalance(
ctx_.view(),
account_, // txn signing account
sle, // src account
sled, // dst account
amount, // xfer amount
ctx_.journal);
ctx_.journal,
false // wet run;
);
if (result != tesSUCCESS)
return result;
@@ -646,7 +678,8 @@ EscrowCancel::doApply()
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
// dry run before we make any changes to ledger
if (TER result = trustAdjustLockedBalance(
if (TER result =
trustAdjustLockedBalance(
ctx_.view(),
sleLine,
-amount,
@@ -689,7 +722,8 @@ EscrowCancel::doApply()
return tefINTERNAL;
// unlock previously locked tokens from source line
TER result = trustAdjustLockedBalance(
TER result =
trustAdjustLockedBalance(
ctx_.view(),
sleLine,
-amount,

View File

@@ -261,7 +261,7 @@ PayChanCreate::preclaim(PreclaimContext const& ctx)
// check for any possible bars to a channel existing
// between these accounts for this asset
if (TER result =
trustXferAllowed(
trustTransferAllowed(
ctx.view,
{account, dst},
amount.issue());
@@ -269,10 +269,12 @@ PayChanCreate::preclaim(PreclaimContext const& ctx)
return result;
// check if the amount can be locked
std::shared_ptr<SLE const> sleLine =
ctx.view.read(keylet::line(account, amount.getIssuer(), amount.getCurrency()));
auto sleLine =
ctx.view.read(
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
if (TER result =
trustAdjustLockedBalance<ReadView const, std::shared_ptr<SLE const>>(
trustAdjustLockedBalance(
ctx.view,
sleLine,
amount,
@@ -671,7 +673,7 @@ PayChanClaim::doApply()
// nothing requested
return tecUNFUNDED_PAYMENT;
auto const sled = ctx_.view().peek(keylet::account(dst));
auto sled = ctx_.view().peek(keylet::account(dst));
if (!sled)
return tecNO_DST;
@@ -710,13 +712,15 @@ PayChanClaim::doApply()
auto sleSrcAcc = ctx_.view().peek(keylet::account(src));
TER result =
trustXferLockedBalance(
trustTransferLockedBalance
(
ctx_.view(),
txAccount,
sleSrcAcc,
sled,
reqDelta,
ctx_.journal);
ctx_.journal,
false);
if (result != tesSUCCESS)
return result;

View File

@@ -34,11 +34,12 @@
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/Feature.h>
#include <ripple/basics/Log.h>
#include <functional>
#include <map>
#include <memory>
#include <utility>
#include <type_traits>
#include <vector>
namespace ripple {
@@ -314,14 +315,19 @@ trustAdjustLockedBalance(
{
static_assert(
(std::is_same<V, ReadView const>::value && std::is_same<S, std::shared_ptr<SLE const>>::value) ||
(std::is_same<V, ApplyView>::value && std::is_same<S, std::shared_ptr<SLE>>::value));
(std::is_same<V, ReadView const>::value &&
std::is_same<S, std::shared_ptr<SLE const>>::value) ||
(std::is_same<V, ApplyView>::value &&
std::is_same<S, std::shared_ptr<SLE>>::value));
// dry runs are explicit in code, but really the view type determines
// what occurs here, so this combination is invalid.
assert(!(std::is_same<V, ReadView const>::value && !dryRun));
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
auto const currency = deltaAmt.getCurrency();
auto const issuer = deltaAmt.getIssuer();
@@ -337,13 +343,13 @@ trustAdjustLockedBalance(
// check for freezes & auth
if (TER result =
trustXferAllowed(
trustTransferAllowed(
view,
parties,
deltaAmt.issue());
result != tesSUCCESS)
{
printf("trustXferAllowed failed on trustAdjustLockedBalance\n");
printf("trustTransferAllowed failed on trustAdjustLockedBalance\n");
return result;
}
@@ -391,30 +397,6 @@ trustAdjustLockedBalance(
}
template<class V>
ReadView const&
forceReadView(V& view)
{
static_assert(
std::is_same<V, std::shared_ptr<ReadView const>>::value ||
std::is_same<V, std::shared_ptr<ApplyView>>::value ||
std::is_same<V, ApplyView>::value ||
std::is_same<V, ReadView const>::value);
ReadView const* rv = NULL;
if constexpr (
std::is_same<V, std::shared_ptr<ReadView const>>::value ||
std::is_same<V, std::shared_ptr<ApplyView>>::value)
rv = dynamic_cast<ReadView const*>(&(*view));
else if constexpr(
std::is_same<V, ApplyView>::value ||
std::is_same<V, ReadView const>::value)
rv = dynamic_cast<ReadView const*>(&view);
return *rv;
}
// Check if movement of a particular token between 1 or more accounts
// (including unlocking) is forbidden by any flag or condition.
// If parties contains 1 entry then noRipple is not a bar to xfer.
@@ -422,13 +404,20 @@ forceReadView(V& view)
template<class V>
[[nodiscard]]TER
trustXferAllowed(
V& view_,
trustTransferAllowed(
V& view,
std::vector<AccountID> const& parties,
Issue const& issue)
{
static_assert(
std::is_same<V, ReadView const>::value ||
std::is_same<V, ApplyView>::value);
ReadView const& view = forceReadView(view_);
typedef typename std::conditional<
std::is_same<V, ApplyView>::value,
std::shared_ptr<SLE>,
std::shared_ptr<SLE const>>::type SLEPtr;
if (isFakeXRP(issue.currency))
return tecNO_PERMISSION;
@@ -452,7 +441,7 @@ trustXferAllowed(
for (AccountID const& p: parties)
{
auto line = view.read(keylet::line(p, issue.account, issue.currency));
auto const line = view.read(keylet::line(p, issue.account, issue.currency));
if (!line)
{
if (requireAuth)
@@ -467,7 +456,7 @@ trustXferAllowed(
// missing line is a line in default state, this is not
// a general bar to xfer, however additional conditions
// do attach to completing an xfer into a default line
// but these are checked in trustXferLockedBalance at
// but these are checked in trustTransferLockedBalance at
// the point of transfer.
continue;
}
@@ -514,7 +503,7 @@ trustXferAllowed(
if (flags & flagIssuerFreeze)
{
printf("trustXferAllowed: issuerFreeze\n");
printf("trustTransferAllowed: issuerFreeze\n");
return tecFROZEN;
}
@@ -523,7 +512,7 @@ trustXferAllowed(
// blocks any possible xfer
if (parties.size() > 1 && (flags & flagIssuerNoRipple))
{
printf("trustXferAllowed: issuerNoRipple\n");
printf("trustTransferAllowed: issuerNoRipple\n");
return tecPATH_DRY;
}
@@ -531,7 +520,7 @@ trustXferAllowed(
// the issuer has specified lsfRequireAuth
if (requireAuth && !(flags & flagIssuerAuth))
{
printf("trustXferAllowed: issuerRequireAuth\n");
printf("trustTransferAllowed: issuerRequireAuth\n");
return tecNO_AUTH;
}
}
@@ -540,15 +529,242 @@ trustXferAllowed(
return tesSUCCESS;
}
template <class V, class S>
[[nodiscard]] TER
trustXferLockedBalance(
ApplyView& view,
trustTransferLockedBalance(
V& view,
AccountID const& actingAccID, // the account whose tx is actioning xfer
std::shared_ptr<SLE> const& sleSrcAcc,
std::shared_ptr<SLE> const& sleDstAcc,
S& sleSrcAcc,
S& sleDstAcc,
STAmount const& amount, // issuer, currency are in this field
beast::Journal const& j);
beast::Journal const& j,
bool dryRun)
{
typedef typename std::conditional<
std::is_same<V, ApplyView>::value,
std::shared_ptr<SLE>,
std::shared_ptr<SLE const>>::type SLEPtr;
auto peek = [&](Keylet& k)
{
if constexpr (std::is_same<V, ApplyView>::value)
return const_cast<ApplyView&>(view).peek(k);
else
return view.read(k);
};
assert(!(std::is_same<V, ApplyView>::value && !dryRun));
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
if (!sleSrcAcc || !sleDstAcc)
{
JLOG(j.warn())
<< "trustTransferLockedBalance without sleSrc/sleDst";
return tecINTERNAL;
}
if (amount <= beast::zero)
{
JLOG(j.warn())
<< "trustTransferLockedBalance with non-positive amount";
return tecINTERNAL;
}
auto issuerAccID = amount.getIssuer();
auto currency = amount.getCurrency();
auto srcAccID = sleSrcAcc->getAccountID(sfAccount);
auto dstAccID = sleDstAcc->getAccountID(sfAccount);
bool srcHigh = srcAccID > issuerAccID;
bool dstHigh = dstAccID > issuerAccID;
// check for freezing, auth, no ripple and TL sanity
if (TER result =
trustTransferAllowed(view, {srcAccID, dstAccID}, {currency, issuerAccID});
result != tesSUCCESS)
return result;
// ensure source line exists
Keylet klSrcLine { keylet::line(srcAccID, issuerAccID, currency)};
SLEPtr sleSrcLine = peek(klSrcLine);
if (!sleSrcLine)
return tecNO_LINE;
// can't transfer a locked balance that does not exist
if (!sleSrcLine->isFieldPresent(sfLockedBalance))
{
JLOG(j.trace())
<< "trustTransferLockedBalance could not find sfLockedBalance on source line";
return tecUNFUNDED_PAYMENT;
}
STAmount lockedBalance = sleSrcLine->getFieldAmount(sfLockedBalance);
// check they have sufficient funds
if (amount > lockedBalance)
return tecUNFUNDED_PAYMENT;
// decrement source balance
{
STAmount priorBalance =
srcHigh ? -((*sleSrcLine)[sfBalance]) : (*sleSrcLine)[sfBalance];
// ensure the currency/issuer in the locked balance matches the xfer amount
if (priorBalance.getIssuer() != issuerAccID || priorBalance.getCurrency() != currency)
return tecNO_PERMISSION;
STAmount finalBalance = priorBalance - amount;
STAmount priorLockedBalance =
srcHigh ? -((*sleSrcLine)[sfLockedBalance]) : (*sleSrcLine)[sfLockedBalance];
STAmount finalLockedBalance = priorLockedBalance - amount;
// this should never happen but defensively check it here before updating sle
if (finalBalance < beast::zero || finalLockedBalance < beast::zero)
{
JLOG(j.warn())
<< "trustTransferLockedBalance results in a negative balance on source line";
return tecINTERNAL;
}
sleSrcLine->setFieldAmount(sfBalance, srcHigh ? -finalBalance : finalBalance);
if (finalLockedBalance == beast::zero)
sleSrcLine->makeFieldAbsent(sfLockedBalance);
else
sleSrcLine->setFieldAmount(sfLockedBalance, srcHigh ? -finalLockedBalance : finalLockedBalance);
}
// dstLow XNOR srcLow tells us if we need to flip the balance amount
// on the destination line
bool flipDstAmt = !((dstHigh && srcHigh) || (!dstHigh && !srcHigh));
// compute transfer fee, if any
auto xferRate = transferRate(view, issuerAccID);
// the destination will sometimes get less depending on xfer rate
// with any difference in tokens burned
auto dstAmt =
xferRate == parityRate
? amount
: multiplyRound(amount, xferRate, amount.issue(), true);
// check for a destination line
Keylet klDstLine = keylet::line(dstAccID, issuerAccID, currency);
SLEPtr sleDstLine = peek(klDstLine);
if (!sleDstLine)
{
// in most circumstances a missing destination line is a deal breaker
if (actingAccID != dstAccID && srcAccID != dstAccID)
return tecNO_PERMISSION;
STAmount dstBalanceDrops = sleDstAcc->getFieldAmount(sfBalance);
// no dst line exists, we might be able to create one...
if (std::uint32_t const ownerCount = {sleDstAcc->at(sfOwnerCount)};
dstBalanceDrops < view.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
// yes we can... we will
if (!dryRun)
{
if constexpr(std::is_same<V, ApplyView>::value)
{
// clang-format off
if (TER const ter = trustCreate(
view,
!dstHigh, // is dest low?
issuerAccID, // source
dstAccID, // destination
klDstLine.key, // ledger index
sleDstAcc, // Account to add to
false, // authorize account
(sleDstAcc->getFlags() & lsfDefaultRipple) == 0,
false, // freeze trust line
flipDstAmt ? -dstAmt : dstAmt, // initial balance
Issue(currency, dstAccID), // limit of zero
0, // quality in
0, // quality out
j); // journal
!isTesSuccess(ter))
{
return ter;
}
}
}
// clang-format on
}
else
{
// the dst line does exist, and it would have been checked above
// in trustTransferAllowed for NoRipple and Freeze flags
// check the limit
STAmount dstLimit =
dstHigh ? (*sleDstLine)[sfHighLimit] : (*sleDstLine)[sfLowLimit];
STAmount priorBalance =
dstHigh ? -((*sleDstLine)[sfBalance]) : (*sleDstLine)[sfBalance];
STAmount finalBalance = priorBalance + (flipDstAmt ? -dstAmt : dstAmt);
if (finalBalance < priorBalance)
{
JLOG(j.warn())
<< "trustTransferLockedBalance resulted in a lower final balance on dest line";
return tecINTERNAL;
}
if (finalBalance > dstLimit && actingAccID != dstAccID)
{
JLOG(j.trace())
<< "trustTransferLockedBalance would increase dest line above limit without permission";
return tecPATH_DRY;
}
sleDstLine->setFieldAmount(sfBalance, dstHigh ? -finalBalance : finalBalance);
}
if (dryRun)
return tesSUCCESS;
static_assert(std::is_same<V, ApplyView>::value);
// check if source line ended up in default state and adjust owner count if it did
if (isTrustDefault(sleSrcAcc, sleSrcLine))
{
uint32_t flags = sleSrcLine->getFieldU32(sfFlags);
uint32_t fReserve { srcHigh ? lsfHighReserve : lsfLowReserve };
if (flags & fReserve)
{
sleSrcLine->setFieldU32(sfFlags, flags & ~fReserve);
if (!dryRun)
{
adjustOwnerCount(view, sleSrcAcc, -1, j);
view.update(sleSrcAcc);
}
}
}
view.update(sleSrcLine);
if (sleDstLine)
{
// a destination line already existed and was updated
view.update(sleDstLine);
}
return tesSUCCESS;
}
/** Delete an offer.
Requirements:

View File

@@ -961,209 +961,6 @@ bool isTrustDefault(
return true;
}
TER
trustXferLockedBalance(
ApplyView& view,
AccountID const& actingAccID, // the account whose tx is actioning xfer
std::shared_ptr<SLE> const& sleSrcAcc,
std::shared_ptr<SLE> const& sleDstAcc,
STAmount const& amount, // issuer, currency are in this field
beast::Journal const& j)
{
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
if (!sleSrcAcc || !sleDstAcc)
{
JLOG(j.warn())
<< "trustXferLockedBalance without sleSrc/sleDst";
return tecINTERNAL;
}
if (amount <= beast::zero)
{
JLOG(j.warn())
<< "trustXferLockedBalance with non-positive amount";
return tecINTERNAL;
}
auto issuerAccID = amount.getIssuer();
auto currency = amount.getCurrency();
auto srcAccID = sleSrcAcc->getAccountID(sfAccount);
auto dstAccID = sleDstAcc->getAccountID(sfAccount);
bool srcHigh = srcAccID > issuerAccID;
bool dstHigh = dstAccID > issuerAccID;
// check for freezing, auth, no ripple and TL sanity
if (TER result =
trustXferAllowed(view, {srcAccID, dstAccID}, {currency, issuerAccID});
result != tesSUCCESS)
return result;
// ensure source line exists
auto sleSrcLine = view.peek(keylet::line(srcAccID, issuerAccID, currency));
if (!sleSrcLine)
return tecNO_LINE;
// can't transfer a locked balance that does not exist
if (!sleSrcLine->isFieldPresent(sfLockedBalance))
{
JLOG(j.trace())
<< "trustXferLockedBalance could not find sfLockedBalance on source line";
return tecUNFUNDED_PAYMENT;
}
STAmount lockedBalance = sleSrcLine->getFieldAmount(sfLockedBalance);
// check they have sufficient funds
if (amount > lockedBalance)
return tecUNFUNDED_PAYMENT;
// decrement source balance
{
STAmount priorBalance =
srcHigh ? -((*sleSrcLine)[sfBalance]) : (*sleSrcLine)[sfBalance];
// ensure the currency/issuer in the locked balance matches the xfer amount
if (priorBalance.getIssuer() != issuerAccID || priorBalance.getCurrency() != currency)
return tecNO_PERMISSION;
STAmount finalBalance = priorBalance - amount;
STAmount priorLockedBalance =
srcHigh ? -((*sleSrcLine)[sfLockedBalance]) : (*sleSrcLine)[sfLockedBalance];
STAmount finalLockedBalance = priorLockedBalance - amount;
// this should never happen but defensively check it here before updating sle
if (finalBalance < beast::zero || finalLockedBalance < beast::zero)
{
JLOG(j.warn())
<< "trustXferLockedBalance results in a negative balance on source line";
return tecINTERNAL;
}
sleSrcLine->setFieldAmount(sfBalance, srcHigh ? -finalBalance : finalBalance);
if (finalLockedBalance == beast::zero)
sleSrcLine->makeFieldAbsent(sfLockedBalance);
else
sleSrcLine->setFieldAmount(sfLockedBalance, srcHigh ? -finalLockedBalance : finalLockedBalance);
}
// dstLow XNOR srcLow tells us if we need to flip the balance amount
// on the destination line
bool flipDstAmt = !((dstHigh && srcHigh) || (!dstHigh && !srcHigh));
// compute transfer fee, if any
auto xferRate = transferRate(view, issuerAccID);
// the destination will sometimes get less depending on xfer rate
// with any difference in tokens burned
auto dstAmt =
xferRate == parityRate
? amount
: multiplyRound(amount, xferRate, amount.issue(), true);
// check for a destination line
Keylet klDstLine = keylet::line(dstAccID, issuerAccID, currency);
auto sleDstLine = view.peek(klDstLine);
if (!sleDstLine)
{
// in most circumstances a missing destination line is a deal breaker
if (actingAccID != dstAccID && srcAccID != dstAccID)
return tecNO_PERMISSION;
STAmount dstBalanceDrops = sleDstAcc->getFieldAmount(sfBalance);
// no dst line exists, we might be able to create one...
if (std::uint32_t const ownerCount = {sleDstAcc->at(sfOwnerCount)};
dstBalanceDrops < view.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
// yes we can... we will
// clang-format off
if (TER const ter = trustCreate(
view,
!dstHigh, // is dest low?
issuerAccID, // source
dstAccID, // destination
klDstLine.key, // ledger index
sleDstAcc, // Account to add to
false, // authorize account
(sleDstAcc->getFlags() & lsfDefaultRipple) == 0,
false, // freeze trust line
flipDstAmt ? -dstAmt : dstAmt, // initial balance
Issue(currency, dstAccID), // limit of zero
0, // quality in
0, // quality out
j); // journal
!isTesSuccess(ter))
{
return ter;
}
// clang-format on
}
else
{
// the dst line does exist, and it would have been checked above
// in trustXferAllowed for NoRipple and Freeze flags
// check the limit
STAmount dstLimit =
dstHigh ? (*sleDstLine)[sfHighLimit] : (*sleDstLine)[sfLowLimit];
STAmount priorBalance =
dstHigh ? -((*sleDstLine)[sfBalance]) : (*sleDstLine)[sfBalance];
STAmount finalBalance = priorBalance + (flipDstAmt ? -dstAmt : dstAmt);
if (finalBalance < priorBalance)
{
JLOG(j.warn())
<< "trustXferLockedBalance resulted in a lower final balance on dest line";
return tecINTERNAL;
}
if (finalBalance > dstLimit && actingAccID != dstAccID)
{
JLOG(j.trace())
<< "trustXferLockedBalance would increase dest line above limit without permission";
return tecPATH_DRY;
}
sleDstLine->setFieldAmount(sfBalance, dstHigh ? -finalBalance : finalBalance);
}
// check if source line ended up in default state and adjust owner count if it did
if (isTrustDefault(sleSrcAcc, sleSrcLine))
{
uint32_t flags = sleSrcLine->getFieldU32(sfFlags);
uint32_t fReserve { srcHigh ? lsfHighReserve : lsfLowReserve };
if (flags & fReserve)
{
sleSrcLine->setFieldU32(sfFlags, flags & ~fReserve);
adjustOwnerCount(view, sleSrcAcc, -1, j);
view.update(sleSrcAcc);
}
}
view.update(sleSrcLine);
if (sleDstLine)
{
// a destination line already existed and was updated
view.update(sleDstLine);
}
return tesSUCCESS;
}
TER
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
{