From 76b57a4e88799625919e64a546ce9a6484b18590 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Thu, 7 Apr 2022 16:28:30 +0000 Subject: [PATCH] templates --- src/ripple/app/tx/impl/Escrow.cpp | 60 ++++-- src/ripple/app/tx/impl/PayChan.cpp | 18 +- src/ripple/ledger/View.h | 300 +++++++++++++++++++++++++---- src/ripple/ledger/impl/View.cpp | 203 ------------------- 4 files changed, 316 insertions(+), 265 deletions(-) diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index fe8a7b13b..42275ed1e 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -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, diff --git a/src/ripple/app/tx/impl/PayChan.cpp b/src/ripple/app/tx/impl/PayChan.cpp index 3e56078b4..3fd401a66 100644 --- a/src/ripple/app/tx/impl/PayChan.cpp +++ b/src/ripple/app/tx/impl/PayChan.cpp @@ -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 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>( + 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; diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 254c31a00..6f37b69c6 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -34,11 +34,12 @@ #include #include #include +#include #include #include #include #include - +#include #include namespace ripple { @@ -314,14 +315,19 @@ trustAdjustLockedBalance( { static_assert( - (std::is_same::value && std::is_same>::value) || - (std::is_same::value && std::is_same>::value)); + (std::is_same::value && + std::is_same>::value) || + (std::is_same::value && + std::is_same>::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::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 -ReadView const& -forceReadView(V& view) -{ - static_assert( - std::is_same>::value || - std::is_same>::value || - std::is_same::value || - std::is_same::value); - - ReadView const* rv = NULL; - - if constexpr ( - std::is_same>::value || - std::is_same>::value) - rv = dynamic_cast(&(*view)); - else if constexpr( - std::is_same::value || - std::is_same::value) - rv = dynamic_cast(&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 [[nodiscard]]TER -trustXferAllowed( - V& view_, +trustTransferAllowed( + V& view, std::vector const& parties, Issue const& issue) { + static_assert( + std::is_same::value || + std::is_same::value); - ReadView const& view = forceReadView(view_); + typedef typename std::conditional< + std::is_same::value, + std::shared_ptr, + std::shared_ptr>::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 [[nodiscard]] TER -trustXferLockedBalance( - ApplyView& view, +trustTransferLockedBalance( + V& view, AccountID const& actingAccID, // the account whose tx is actioning xfer - std::shared_ptr const& sleSrcAcc, - std::shared_ptr 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::value, + std::shared_ptr, + std::shared_ptr>::type SLEPtr; + auto peek = [&](Keylet& k) + { + if constexpr (std::is_same::value) + return const_cast(view).peek(k); + else + return view.read(k); + }; + + assert(!(std::is_same::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::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::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: diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 60cd45d92..e447ea5d5 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -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 const& sleSrcAcc, - std::shared_ptr 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 const& sle, beast::Journal j) {