compiling

This commit is contained in:
Richard Holland
2022-04-06 19:38:09 +00:00
parent dfc8503e56
commit ff19b91503
4 changed files with 242 additions and 423 deletions

View File

@@ -233,19 +233,18 @@ EscrowCreate::doApply()
// Check reserve and funds availability // Check reserve and funds availability
if (isXRP(amount) && balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp()) if (isXRP(amount) && balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
return tecUNFUNDED; return tecUNFUNDED;
else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) else
{ {
sleLine = view.peek(keylet::line(account, issuer, currency)); if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
// RH TODO: does doing a dry run here add any value? it should be done in return tefINTERNAL;
// preclaim but there is no preclaim in escrow.
sleLine = ctx_.view().peek(keylet::line(account, amount.getIssuer(), amount.getCurrency()));
// perform the lock as a dry run first // perform the lock as a dry run first
if (TER result = trustAdjustLockedBalance(ctx_.view(), sleLine, amount, true); if (TER result = trustAdjustLockedBalance(ctx_.view(), sleLine, amount, true);
result != tesSUCCESS) result != tesSUCCESS)
return result; return result;
} }
else
return tecINTERNAL; // should never happen
// Check destination account // Check destination account
{ {
@@ -306,7 +305,7 @@ EscrowCreate::doApply()
{ {
// do the lock-up for real now // do the lock-up for real now
TER result = TER result =
trustAdjustLockedBalance(ctx_.view(), sleLine, amount); trustAdjustLockedBalance(ctx_.view(), sleLine, amount, true);
if (result != tesSUCCESS) if (result != tesSUCCESS)
return result; return result;
} }
@@ -619,6 +618,29 @@ EscrowCancel::doApply()
} }
AccountID const account = (*slep)[sfAccount]; AccountID const account = (*slep)[sfAccount];
auto const sle = ctx_.view().peek(keylet::account(account));
auto amount = slep->getFieldAmount(sfAmount);
std::shared_ptr<SLE> sleLine;
if (!isXRP(amount))
{
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
sleLine =
ctx_.view().peek(
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
// dry run before we make any changes to ledger
if (TER result = trustAdjustLockedBalance(
ctx_.view(),
sleLine,
-amount,
true);
result != tesSUCCESS)
return result;
}
// Remove escrow from owner directory // Remove escrow from owner directory
{ {
@@ -645,20 +667,17 @@ EscrowCancel::doApply()
} }
} }
auto const sle = ctx_.view().peek(keylet::account(account));
auto amount = slep->getFieldAmount(sfAmount);
// Transfer amount back to the owner (or unlock it in TL case) // Transfer amount back to the owner (or unlock it in TL case)
if (isXRP(amount)) if (isXRP(amount))
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount]; (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
{ {
// unlock previously locked tokens from source line // unlock previously locked tokens from source line
auto line = view.peek(keylet::line(account, issuerAccID, currency));
TER result = trustAdjustLockedBalance( TER result = trustAdjustLockedBalance(
ctx_.view(), ctx_.view(),
line, sleLine,
-amount); -amount,
false);
if (result != tesSUCCESS) if (result != tesSUCCESS)
return result; return result;
} }

View File

@@ -156,19 +156,21 @@ closeChannel(
{ {
(*sle)[sfBalance] = (*sle)[sfBalance] + amount; (*sle)[sfBalance] = (*sle)[sfBalance] + amount;
} }
else if (view.rules().enabled(featurePaychanAndEscrowForTokens)) else
{ {
if (view.rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
auto line = view.peek(keylet::line(src, amount.getIssuer(), amount.getCurrency())); auto line = view.peek(keylet::line(src, amount.getIssuer(), amount.getCurrency()));
TER result = TER result =
trustAdjustLockedBalance( trustAdjustLockedBalance(
view, view,
line, line,
-amount); -amount,
false);
if (result != tesSUCCESS) if (result != tesSUCCESS)
return result; return result;
} }
else
return tefINTERNAL;
adjustOwnerCount(view, sle, -1, j); adjustOwnerCount(view, sle, -1, j);
view.update(sle); view.update(sle);
@@ -262,14 +264,15 @@ PayChanCreate::preclaim(PreclaimContext const& ctx)
trustXferAllowed( trustXferAllowed(
ctx.view, ctx.view,
{account, dst}, {account, dst},
amount.issue); amount.issue());
result != tesSUCCESS) result != tesSUCCESS)
return result; return result;
// check if the amount can be locked // check if the amount can be locked
auto sleLine = ctx.view.read(keylet::line(account, amount.issuer(), amount.currency())); std::shared_ptr<SLE const> sleLine =
ctx.view.read(keylet::line(account, amount.getIssuer(), amount.getCurrency()));
if (TER result = if (TER result =
trustAdjustLockedBalance( trustAdjustLockedBalance<ReadView const, std::shared_ptr<SLE const>>(
ctx.view, ctx.view,
sleLine, sleLine,
amount, amount,
@@ -363,7 +366,7 @@ PayChanCreate::doApply()
return tefINTERNAL; return tefINTERNAL;
auto sleLine = auto sleLine =
view.peek(keylet::line(account, amount.getIssuer(), amount.getCurrency())); ctx_.view().peek(keylet::line(account, amount.getIssuer(), amount.getCurrency()));
if (!sleLine) if (!sleLine)
return tecUNFUNDED_PAYMENT; return tecUNFUNDED_PAYMENT;
@@ -430,7 +433,6 @@ PayChanFund::preflight(PreflightContext const& ctx)
return preflight2(ctx); return preflight2(ctx);
} }
// RH UPTO
TER TER
PayChanFund::doApply() PayChanFund::doApply()
{ {
@@ -438,11 +440,33 @@ PayChanFund::doApply()
auto const slep = ctx_.view().peek(k); auto const slep = ctx_.view().peek(k);
if (!slep) if (!slep)
return tecNO_ENTRY; return tecNO_ENTRY;
STAmount const amount {ctx_.tx[sfAmount]};
std::shared_ptr<SLE> sleLine; // if XRP or featurePaychanAndEscrowForTokens
// not enabled this remains null
// if this is a Fund operation on an IOU then perform a dry run here
if (!isXRP(amount) &&
ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
sleLine = ctx_.view().peek(
keylet::line(
(*slep)[sfAccount],
amount.getIssuer(),
amount.getCurrency()));
if (TER result =
trustAdjustLockedBalance(
ctx_.view(),
sleLine,
amount,
true);
result != tesSUCCESS)
return result;
AccountID const src = (*slep)[sfAccount]; AccountID const src = (*slep)[sfAccount];
auto const txAccount = ctx_.tx[sfAccount]; auto const txAccount = ctx_.tx[sfAccount];
auto const expiration = (*slep)[~sfExpiration]; auto const expiration = (*slep)[~sfExpiration];
{ {
auto const cancelAfter = (*slep)[~sfCancelAfter]; auto const cancelAfter = (*slep)[~sfCancelAfter];
auto const closeTime = auto const closeTime =
@@ -490,7 +514,6 @@ PayChanFund::doApply()
if (balance < reserve) if (balance < reserve)
return tecINSUFFICIENT_RESERVE; return tecINSUFFICIENT_RESERVE;
STAmount const amount {ctx_.tx[sfAmount]};
if (isXRP(amount)) if (isXRP(amount))
{ {
@@ -500,47 +523,21 @@ PayChanFund::doApply()
(*sle)[sfBalance] = (*sle)[sfBalance] - amount; (*sle)[sfBalance] = (*sle)[sfBalance] - amount;
ctx_.view().update(sle); ctx_.view().update(sle);
} }
else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
{
// RH UPTO: add freeze checks for all featurePaychanAndEscrowForTokens applies on escrow and paychan
// find the user's trustline
auto const& account = src;
auto const currency = amount.getCurrency();
auto const issuer = amount.getIssuer();
auto& view = ctx_.view();
auto const chanAmount = slep->getFieldAmount(sfAmount);
if (chanAmount.getCurrency() != currency || chanAmount.getIssuer() != issuer)
return tecNO_AUTH;
auto sleLine = view.peek(keylet::line(account, issuer, currency));
if (!sleLine)
return tecUNFUNDED_PAYMENT;
bool high = account > issuer;
STAmount balance = (*sleLine)[sfBalance];
STAmount lockedBalance {sfLockedBalance, amount.issue()};
if (sleLine->isFieldPresent(sfLockedBalance))
lockedBalance = (*sleLine)[sfLockedBalance];
auto spendableBalance = balance - lockedBalance;
if (high)
spendableBalance = -spendableBalance;
if (amount > spendableBalance)
return tecUNFUNDED_PAYMENT;
lockedBalance += high ? -amount : amount;
sleLine->setFieldAmount(sfLockedBalance, lockedBalance);
view.update(sleLine);
}
else else
return tecINTERNAL; {
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
TER result =
trustAdjustLockedBalance(
ctx_.view(),
sleLine,
amount,
false);
if (result != tesSUCCESS)
return tefINTERNAL;
}
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount]; (*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
ctx_.view().update(slep); ctx_.view().update(slep);
@@ -574,7 +571,7 @@ PayChanClaim::preflight(PreflightContext const& ctx)
if (!isXRP(*amt) && !ctx.rules.enabled(featurePaychanAndEscrowForTokens)) if (!isXRP(*amt) && !ctx.rules.enabled(featurePaychanAndEscrowForTokens))
return temBAD_AMOUNT; return temBAD_AMOUNT;
if (*amt) if (*amt <= beast::zero)
return temBAD_AMOUNT; return temBAD_AMOUNT;
} }
@@ -682,6 +679,7 @@ PayChanClaim::doApply()
// featureDepositAuth to remove the bug. // featureDepositAuth to remove the bug.
bool const depositAuth{ctx_.view().rules().enabled(featureDepositAuth)}; bool const depositAuth{ctx_.view().rules().enabled(featureDepositAuth)};
if (!depositAuth && if (!depositAuth &&
// RH TODO: does this condition need to be changed for IOU paychans?
(txAccount == src && (sled->getFlags() & lsfDisallowXRP))) (txAccount == src && (sled->getFlags() & lsfDisallowXRP)))
return tecNO_TARGET; return tecNO_TARGET;
@@ -704,231 +702,26 @@ PayChanClaim::doApply()
assert(reqDelta >= beast::zero); assert(reqDelta >= beast::zero);
if (isXRP(reqDelta)) if (isXRP(reqDelta))
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta; (*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) else
{ {
// There are three involved accounts and two trustlines (at the end) // xfer locked tokens to satisfy claim
// Src - Account which created the paychan if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
// Dst - Account which will receive a payout in the event of success
// Issuer - Account which issued the IOU the escrow locks
auto const& amount = reqDelta;
auto issuerAccID = amount.getIssuer();
auto currency = amount.getCurrency();
Keylet klIssuer = keylet::account(issuerAccID);
auto& view = ctx_.view();
auto& srcAccID = src;
auto& dstAccID = dst;
bool dstLow = dstAccID < issuerAccID;
bool srcLow = srcAccID < issuerAccID;
auto sleSrcAcc = view.peek(keylet::account(srcAccID));
auto& sleDstAcc = sled;
auto& j = ctx_.journal;
if (!sleSrcAcc)
{
JLOG(j.warn())
<< "Src account not found in paychan claim";
return tecINTERNAL;
}
STAmount dstBalanceDrops = sleDstAcc->getFieldAmount(sfBalance);
// check if the destination has a trustline already
Keylet klDstLine = keylet::line(dstAccID, issuerAccID, currency);
auto sleSrcLine = view.peek(keylet::line(srcAccID, issuerAccID, currency));
auto sleDstLine = view.peek(klDstLine);
// check the issuer exists
auto sleIssuer = view.peek(klIssuer);
if (!sleIssuer)
{
JLOG(j.warn())
<< "Cannot paychan claim for token from non-existent issuer: "
<< to_string(issuerAccID);
return tecNO_ISSUER;
}
// check the trustline isn't frozen
if (isGlobalFrozen(view, issuerAccID))
{
JLOG(j.warn()) << "Cannot finish an escrow for frozen issuer";
return tecFROZEN;
}
// check the source line exists
if (!sleSrcLine)
{
JLOG(j.error())
<< "Cannot claim a paychan where the source line does not exist: "
<< "src: " << to_string(srcAccID) << " "
<< "iss: " << to_string(issuerAccID) << " "
<< "cur: " << to_string(currency);
return tefINTERNAL; return tefINTERNAL;
}
// check if rippling is allowed on the source line auto sleSrcAcc = ctx_.view().peek(keylet::account(src));
{ TER result =
auto const flag {srcLow ? lsfLowNoRipple : lsfHighNoRipple}; trustXferLockedBalance(
bool tlNoRipple = sleSrcLine->getFieldU32(sfFlags) & flag; ctx_.view(),
if (tlNoRipple) txAccount,
{ sleSrcAcc,
JLOG(j.warn()) << "PayChan claim would violate noripple status on issuer."; sled,
return tecPATH_DRY; reqDelta,
} ctx_.journal);
}
// dstLow XNOR srcLow tells us if we need to flip the balance amount
// on the destination line
bool flipDstAmt = !((dstLow && srcLow) || (!dstLow && !srcLow));
// 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 if the dest line exists, and if it doesn't create it if we're allowed to
if (!sleDstLine)
{
// if a line doesn't already exist between issuer and destination then
// such a line can now be created but only if either the person who signed
// for this txn is the destination account or the source and dest are the same
if (srcAccID != dstAccID && account_ != dstAccID)
return tecNO_PERMISSION;
// create trustline
if (std::uint32_t const ownerCount = {sleDstAcc->at(sfOwnerCount)};
dstBalanceDrops < view.fees().accountReserve(ownerCount + 1))
{
JLOG(j.trace()) << "Dest Trust line does not exist. "
"Insufficent reserve to create line.";
return tecNO_LINE_INSUF_RESERVE;
}
// clang-format off
if (TER const ter = trustCreate(
view,
dstLow, // 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, account_), // limit of zero
0, // quality in
0, // quality out
j); // journal
!isTesSuccess(ter))
{
return ter;
}
// clang-format on
}
else
{
// trustline already exists
// check is it's frozen
if (dstAccID != issuerAccID &&
sleDstLine->isFlag(dstLow ? lsfLowFreeze : lsfHighFreeze))
{
JLOG(j.warn())
<< "Finishing an escrow for destination frozen trustline.";
return tecFROZEN;
}
// increment dest balance
{
// if balance is higher than limit then only allow if dest is signer
STAmount dstLimit =
dstLow ? (*sleDstLine)[sfLowLimit] : (*sleDstLine)[sfHighLimit];
STAmount priorBalance =
dstLow ? (*sleDstLine)[sfBalance] : -((*sleDstLine)[sfBalance]);
STAmount finalBalance = priorBalance + (flipDstAmt ? -dstAmt : dstAmt);
if (finalBalance < priorBalance)
{
JLOG(j.warn())
<< "Escrow finish resulted in a lower final balance on dest line";
return tecINTERNAL;
}
if (finalBalance > dstLimit && account_ != dstAccID)
{
JLOG(j.trace())
<< "Escrow finish would increase dest line above limit without permission";
return tecPATH_DRY;
}
sleDstLine->setFieldAmount(sfBalance, dstLow ? finalBalance : -finalBalance);
}
}
// decrement source balance
{
STAmount priorBalance =
srcLow ? (*sleSrcLine)[sfBalance] : -((*sleSrcLine)[sfBalance]);
STAmount finalBalance = priorBalance - amount;
if (finalBalance < beast::zero)
{
JLOG(j.warn())
<< "Escrow finish results in a negative balance on source line";
return tecINTERNAL;
}
if (!sleSrcLine->isFieldPresent(sfLockedBalance))
{
JLOG(j.warn())
<< "Escrow finish could not find sfLockedBalance on source line";
return tecINTERNAL;
}
STAmount priorLockedBalance =
srcLow ? (*sleSrcLine)[sfLockedBalance] : -((*sleSrcLine)[sfLockedBalance]);
STAmount finalLockedBalance = priorLockedBalance - amount;
if (finalLockedBalance < beast::zero)
{
JLOG(j.warn())
<< "Escrow finish results in a negative locked balance on source line";
return tecINTERNAL;
}
sleSrcLine->setFieldAmount(sfBalance, srcLow ? finalBalance : -finalBalance);
if (finalLockedBalance == beast::zero)
sleSrcLine->makeFieldAbsent(sfLockedBalance);
else
sleSrcLine->setFieldAmount(sfLockedBalance, srcLow ? finalLockedBalance : -finalLockedBalance);
}
// update source and dest lines to reflect balance mutation
view.update(sleSrcLine);
if (sleDstLine)
view.update(sleDstLine);
if (result != tesSUCCESS)
return result;
} }
else
return tecINTERNAL;
ctx_.view().update(sled); ctx_.view().update(sled);
ctx_.view().update(slep); ctx_.view().update(slep);
} }

View File

@@ -299,6 +299,111 @@ trustDelete(
AccountID const& uHighAccountID, AccountID const& uHighAccountID,
beast::Journal j); beast::Journal j);
bool isTrustDefault(
std::shared_ptr<SLE> const& acc,
std::shared_ptr<SLE> const& line);
[[nodiscard]] TER
trustXferAllowed(
ReadView const& view,
std::vector<AccountID> const& parties,
Issue const& issue);
template<class V, class S>
[[nodiscard]] TER
trustAdjustLockedBalance(
V& view,
S& sleLine,
STAmount const& deltaAmt,
bool dryRun)
{
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));
constexpr bool bReadView = std::is_same<V, ReadView const>::value;
// dry runs are explicit in code, but really the view type determines
// what occurs here, so this combination is invalid.
if (bReadView && !dryRun)
return tefINTERNAL;
auto const currency = deltaAmt.getCurrency();
auto const issuer = deltaAmt.getIssuer();
STAmount lowLimit = sleLine->getFieldAmount(sfLowLimit);
// the account which is modifying the LockedBalance is always
// the side that isn't the issuer, so if the low side is the
// issuer then the high side is the account.
bool high = lowLimit.getIssuer() == issuer;
std::vector<AccountID> parties
{high ? sleLine->getFieldAmount(sfHighLimit).getIssuer(): lowLimit.getIssuer()};
// check for freezes & auth
if (TER result =
trustXferAllowed(
view,
parties,
deltaAmt.issue());
result != tesSUCCESS)
return result;
// pull the TL balance from the account's perspective
STAmount balance =
high ? -(*sleLine)[sfBalance] : (*sleLine)[sfBalance];
// this would mean somehow the issuer is trying to lock balance
if (balance < beast::zero)
return tecINTERNAL;
// can't lock or unlock a zero balance
if (balance == beast::zero)
return tecUNFUNDED_PAYMENT;
STAmount lockedBalance {sfLockedBalance, deltaAmt.issue()};
if (sleLine->isFieldPresent(sfLockedBalance))
lockedBalance =
high ? -(*sleLine)[sfLockedBalance] : (*sleLine)[sfLockedBalance];
lockedBalance += deltaAmt;
if (lockedBalance > balance)
return tecUNFUNDED_PAYMENT;
if (lockedBalance < beast::zero)
return tecINTERNAL;
// we won't update any SLEs if it is a dry run
if (dryRun)
return tesSUCCESS;
if constexpr(std::is_same<V, ApplyView>::value && std::is_same<S, std::shared_ptr<SLE>>::value)
{
if (lockedBalance == beast::zero)
sleLine->makeFieldAbsent(sfLockedBalance);
else
sleLine->
setFieldAmount(sfLockedBalance, high ? -lockedBalance : lockedBalance);
view.update(sleLine);
}
return tesSUCCESS;
}
[[nodiscard]] 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);
/** Delete an offer. /** Delete an offer.
Requirements: Requirements:

View File

@@ -913,19 +913,26 @@ trustDelete(
} }
bool isTrustDefault( bool isTrustDefault(
std::shared_ptr<SLE> acc, std::shared_ptr<SLE> const& acc,
std::shared_ptr<SLE> line) std::shared_ptr<SLE> const& line)
{ {
assert(acc && line); assert(acc && line);
uint32_t tlFlags = line->getFieldU32(sfFlags); uint32_t tlFlags = line->getFieldU32(sfFlags);
bool high =
acc == line->getFieldAmount(sfHighLimit).issuer(); AccountID highAccID = line->getFieldAmount(sfHighLimit).issue().account;
AccountID lowAccID = line->getFieldAmount(sfLowLimit ).issue().account;
AccountID accID = acc->getAccountID(sfAccount);
assert(accID == highAccID || accID == lowAccID);
bool high = accID == highAccID;
uint32_t acFlags = line->getFieldU32(sfFlags); uint32_t acFlags = line->getFieldU32(sfFlags);
const bool fNoRipple {high ? lsfHighNoRipple : lsfLowNoRipple}; const auto fNoRipple {high ? lsfHighNoRipple : lsfLowNoRipple};
const bool fFreeze {high ? lsfHighFreeze : lsfLowFreeze}; const auto fFreeze {high ? lsfHighFreeze : lsfLowFreeze};
if (tlFlags & fFreeze) if (tlFlags & fFreeze)
return false; return false;
@@ -942,13 +949,13 @@ bool isTrustDefault(
if (line->getFieldAmount(high ? sfHighLimit : sfLowLimit) != beast::zero) if (line->getFieldAmount(high ? sfHighLimit : sfLowLimit) != beast::zero)
return false; return false;
STAmount qualityIn = line->getFieldAmount(high ? sfHighQualityIn : sfLowQualityIn); uint32_t qualityIn = line->getFieldU32(high ? sfHighQualityIn : sfLowQualityIn);
STAmount qualityOut = line->getFieldAmount(high ? sfHighQualityOut : sfLowQualityOut); uint32_t qualityOut = line->getFieldU32(high ? sfHighQualityOut : sfLowQualityOut);
if (qualityIn != beast::zero && qualityIn != 1000000000) if (qualityIn && qualityIn != QUALITY_ONE)
return false; return false;
if (qualityOut != beast::zero && qualityOut != 1000000000) if (qualityOut && qualityOut != QUALITY_ONE)
return false; return false;
return true; return true;
@@ -961,15 +968,15 @@ bool isTrustDefault(
// Part of featurePaychanAndEscrowForTokens, but can be callled without guard // Part of featurePaychanAndEscrowForTokens, but can be callled without guard
TER TER
trustXferAllowed( trustXferAllowed(
ReadView& view, ReadView const& view,
std::vector<AccountID const> const& parties, std::vector<AccountID> const& parties,
Issue const& issue) Issue const& issue)
{ {
if (isFakeXRP(issue.currency)) if (isFakeXRP(issue.currency))
return tecNO_PERMISSION; return tecNO_PERMISSION;
auto const sleIssuerAcc = view.peek(keylet::account(issue.account)); auto const sleIssuerAcc = view.read(keylet::account(issue.account));
bool lockedBalanceAllowed = bool lockedBalanceAllowed =
view.rules().enabled(featurePaychanAndEscrowForTokens); view.rules().enabled(featurePaychanAndEscrowForTokens);
@@ -1012,7 +1019,8 @@ trustXferAllowed(
{ {
// these "strange" old lines, if they even exist anymore are // these "strange" old lines, if they even exist anymore are
// always a bar to xfer // always a bar to xfer
if (line->getFieldAccount(sfLowAccount) == line->getFieldAccount(sfHighAccount)) if (line->getFieldAmount(sfLowLimit).getIssuer() ==
line->getFieldAmount(sfHighLimit).getIssuer())
return tecINTERNAL; return tecINTERNAL;
if (line->isFieldPresent(sfLockedBalance)) if (line->isFieldPresent(sfLockedBalance))
@@ -1023,8 +1031,8 @@ trustXferAllowed(
STAmount lockedBalance = line->getFieldAmount(sfLockedBalance); STAmount lockedBalance = line->getFieldAmount(sfLockedBalance);
STAmount balance = line->getFieldAmount(sfBalance); STAmount balance = line->getFieldAmount(sfBalance);
if (lockedBalance.issuer() != balance.issuer() || if (lockedBalance.getIssuer() != balance.getIssuer() ||
lockedBalance.currency() != balance.currency()) lockedBalance.getCurrency() != balance.getCurrency())
return tecINTERNAL; return tecINTERNAL;
} }
} }
@@ -1061,121 +1069,14 @@ trustXferAllowed(
return tesSUCCESS; return tesSUCCESS;
} }
// Type juggling to allow dry runs of helper functions from preclaim
// context, without forcing the caller to recast or change their view.
// ApplyView is a subclass of ReadView so this logic works fine and
// allows a helper to be called explicitly and without confusion.
using ReadOrApplyView =
std::variant<
std::reference_wrapper<ReadView const>,
std::reference_wrapper<ApplyView>>;
inline ReadView const&
forceReadView(ReadOrApplyView& view)
{
return
*(std::holds_alternative<ReadViewRef>(view)
? &(std::get<ReadViewRef>(view).get())
: std::reintrepret_cast<ReadView const*>(
&(std::get<ReadViewRef>(view).get())));
}
// Modify a locked trustline balance, creating one if none exists
// or removing one if no longer needed. deltaAmt is in absolute terms
// positive means increment locked balance and negative decrement.
TER
trustAdjustLockedBalance(
ReadOrApplyView view_,
std::shared_ptr<SLE>& sleLine,
STAmount const& deltaAmt,
bool dryRun = false /* don't actually update, just try the delta */)
{
bool bReadView = std::holds_alternative<ReadViewRef>(view_);
// dry runs are explicit in code, but really the view type determines
// what occurs here, so this combination is invalid.
if (bReadView && !dryRun)
return tefINTERNAL;
ReadView const& view = forceReadView(view_);
if (!view.rules.enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL;
if (!sleLine)
return tecUNFUNDED_PAYMENT;
auto const currency = deltaAmt.getCurrency();
auto const issuer = deltaAmt.getIssuer();
STAmount lowLimit = sleLine->getFieldAmount(sfLowLimit);
// the account which is modifying the LockedBalance is always
// the side that isn't the issuer, so if the low side is the
// issuer then the high side is the account.
bool high = lowLimit.issuer() == issuer;
std::vector<AccountID> parties
{high ? sleLine->getFieldAmount(sfHighLimit).issuer(): lowLimit.issuer()};
// check for freezes & auth
if (TER result =
trustXferAllowed(
view,
parties,
deltaAmt.issue())
result != tesSUCCESS)
return result;
// pull the TL balance from the account's perspective
STAmount balance =
high ? -(*sleLine)[sfBalance] : (*sleLine)[sfBalance];
// this would mean somehow the issuer is trying to lock balance
if (balance < beast::zero)
return tecINTERNAL;
// can't lock or unlock a zero balance
if (balance == beast::zero)
return tecUNFUNDED_PAYMENT;
STAmount lockedBalance {sfLockedBalance, deltaAmt.issue()};
if (sleLine->isFieldPresent(sfLockedBalance))
lockedBalance =
high ? -(*sleLine)[sfLockedBalance] : (*sleLine)[sfLockedBalance];
lockedBalance += deltaAmt;
if (lockedBalance > balance
return tecUNFUNDED_PAYMENT;
if (lockedBalance < beast::zero)
return tecINTERNAL;
// we won't update any SLEs if it is a dry run
if (dryRun)
return tesSUCCESS;
if (lockedBalance == beast::zero)
sleLine->makeFieldAbsent(sfLockedBalance);
else
sleLine->setFieldAmount(sfLockedBalance, high ? -lockedBalance : lockedBalance);
// dryRun must be true if we have a ReadView in the variant
// therefore this is safe to execute
std::get<std::reference_wrapper<ApplyView>>(view_).get().update(sleLine);
return tesSUCCESS;
}
TER TER
trustXferLockedBalance( trustXferLockedBalance(
ApplyView& view, ApplyView& view,
AccountID const& actingAccID, // the account whose tx is actioning xfer AccountID const& actingAccID, // the account whose tx is actioning xfer
std::shared_ptr<SLE>& sleSrcAcc, std::shared_ptr<SLE> const& sleSrcAcc,
std::shared_ptr<SLE>& sleDstAcc, std::shared_ptr<SLE> const& sleDstAcc,
STAmount const& amount, // issuer, currency are in this field STAmount const& amount, // issuer, currency are in this field
Journal& j) beast::Journal const& j)
{ {
if (!view.rules().enabled(featurePaychanAndEscrowForTokens)) if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
return tefINTERNAL; return tefINTERNAL;
@@ -1196,11 +1097,11 @@ trustXferLockedBalance(
auto issuerAccID = amount.getIssuer(); auto issuerAccID = amount.getIssuer();
auto currency = amount.getCurrency(); auto currency = amount.getCurrency();
auto srcAccID = sleSrcAcc->getFieldAccount(sfAccount); auto srcAccID = sleSrcAcc->getAccountID(sfAccount);
auto dstAccID = sleDstAcc->getFieldAccount(sfAccount); auto dstAccID = sleDstAcc->getAccountID(sfAccount);
bool srcHigh = srcAccID > issuerAccID; bool srcHigh = srcAccID > issuerAccID;
bool dsthigh = dstAccID > issuerAccID; bool dstHigh = dstAccID > issuerAccID;
// check for freezing, auth, no ripple and TL sanity // check for freezing, auth, no ripple and TL sanity
if (TER result = if (TER result =
@@ -1234,7 +1135,7 @@ trustXferLockedBalance(
srcHigh ? -((*sleSrcLine)[sfBalance]) : (*sleSrcLine)[sfBalance]; srcHigh ? -((*sleSrcLine)[sfBalance]) : (*sleSrcLine)[sfBalance];
// ensure the currency/issuer in the locked balance matches the xfer amount // ensure the currency/issuer in the locked balance matches the xfer amount
if (priorBalance.issuer() != issuerAccID || priorBalance.currency() != currency) if (priorBalance.getIssuer() != issuerAccID || priorBalance.getCurrency() != currency)
return tecNO_PERMISSION; return tecNO_PERMISSION;
STAmount finalBalance = priorBalance - amount; STAmount finalBalance = priorBalance - amount;
@@ -1276,7 +1177,8 @@ trustXferLockedBalance(
: multiplyRound(amount, xferRate, amount.issue(), true); : multiplyRound(amount, xferRate, amount.issue(), true);
// check for a destination line // check for a destination line
auto sleDstLine = view.peek(keylet::line(dstAccID, issuerAccID, currency)); Keylet klDstLine = keylet::line(dstAccID, issuerAccID, currency);
auto sleDstLine = view.peek(klDstLine);
if (!sleDstLine) if (!sleDstLine)
{ {
@@ -1296,7 +1198,7 @@ trustXferLockedBalance(
// clang-format off // clang-format off
if (TER const ter = trustCreate( if (TER const ter = trustCreate(
view, view,
dstLow, // is dest low? !dstHigh, // is dest low?
issuerAccID, // source issuerAccID, // source
dstAccID, // destination dstAccID, // destination
klDstLine.key, // ledger index klDstLine.key, // ledger index
@@ -1305,7 +1207,7 @@ trustXferLockedBalance(
(sleDstAcc->getFlags() & lsfDefaultRipple) == 0, (sleDstAcc->getFlags() & lsfDefaultRipple) == 0,
false, // freeze trust line false, // freeze trust line
flipDstAmt ? -dstAmt : dstAmt, // initial balance flipDstAmt ? -dstAmt : dstAmt, // initial balance
Issue(currency, account_), // limit of zero Issue(currency, dstAccID), // limit of zero
0, // quality in 0, // quality in
0, // quality out 0, // quality out
j); // journal j); // journal
@@ -1343,19 +1245,19 @@ trustXferLockedBalance(
return tecPATH_DRY; return tecPATH_DRY;
} }
sleDstLine->setFieldAmount(sfBalance, dstLow ? finalBalance : -finalBalance); sleDstLine->setFieldAmount(sfBalance, dstHigh ? -finalBalance : finalBalance);
} }
// check if source line ended up in default state and adjust owner count if it did // check if source line ended up in default state and adjust owner count if it did
if (isTrustDefault(sleSrc, sleSrcLine)) if (isTrustDefault(sleSrcAcc, sleSrcLine))
{ {
uint32_t flags = sleSrcLine->getFieldU32(sfFlags); uint32_t flags = sleSrcLine->getFieldU32(sfFlags);
const bool fReserve { srcHigh ? lsfHighReserve : lsfLowReserve }; uint32_t fReserve { srcHigh ? lsfHighReserve : lsfLowReserve };
if (flags & fReserve) if (flags & fReserve)
{ {
sleSrcLine->setFieldU32(sfFlags, flags & ~fReserve); sleSrcLine->setFieldU32(sfFlags, flags & ~fReserve);
adjustOwnerCount(view, sleSrc, -1, j); adjustOwnerCount(view, sleSrcAcc, -1, j);
view.update(sleSrc); view.update(sleSrcAcc);
} }
} }