diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index 3947aac81..fe8a7b13b 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -235,12 +235,24 @@ EscrowCreate::doApply() return tecUNFUNDED; else { + // preflight will prevent this ever firing, included + // defensively for completeness if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) return tefINTERNAL; - sleLine = ctx_.view().peek(keylet::line(account, amount.getIssuer(), amount.getCurrency())); + // check if the escrow is capable of being + // finished before we allow it to be created + if (TER result = + trustXferAllowed( + ctx_.view(), + {account, ctx_.tx[sfDestination]}, + amount.issue()); + result != tesSUCCESS) + return result; - // perform the lock as a dry run first + // 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); result != tesSUCCESS) return result; @@ -301,16 +313,17 @@ EscrowCreate::doApply() // Deduct owner's balance, increment owner count if (isXRP(amount)) (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; - else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens) && sleLine) + else { + if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens) || !sleLine) + return tefINTERNAL; + // do the lock-up for real now TER result = - trustAdjustLockedBalance(ctx_.view(), sleLine, amount, true); + trustAdjustLockedBalance(ctx_.view(), sleLine, amount, false); if (result != tesSUCCESS) return result; } - else - return tecINTERNAL; // should never happen adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); ctx_.view().update(sle); @@ -540,8 +553,10 @@ EscrowFinish::doApply() if (isXRP(amount)) (*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount]; - else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) + 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 @@ -558,8 +573,6 @@ EscrowFinish::doApply() if (result != tesSUCCESS) return result; } - else - return tecINTERNAL; // should never happen ctx_.view().update(sled); @@ -670,8 +683,11 @@ EscrowCancel::doApply() // Transfer amount back to the owner (or unlock it in TL case) if (isXRP(amount)) (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount]; - else if (ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) + else { + if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) + return tefINTERNAL; + // unlock previously locked tokens from source line TER result = trustAdjustLockedBalance( ctx_.view(), @@ -681,8 +697,6 @@ EscrowCancel::doApply() if (result != tesSUCCESS) return result; } - else - return tecINTERNAL; // Decrement owner count adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 9835ede72..254c31a00 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -303,12 +304,6 @@ bool isTrustDefault( std::shared_ptr const& acc, std::shared_ptr const& line); -[[nodiscard]] TER -trustXferAllowed( - ReadView const& view, - std::vector const& parties, - Issue const& issue); - template [[nodiscard]] TER trustAdjustLockedBalance( @@ -322,13 +317,10 @@ trustAdjustLockedBalance( (std::is_same::value && std::is_same>::value) || (std::is_same::value && std::is_same>::value)); - constexpr bool bReadView = std::is_same::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; + assert(!(std::is_same::value && !dryRun)); auto const currency = deltaAmt.getCurrency(); auto const issuer = deltaAmt.getIssuer(); @@ -350,7 +342,10 @@ trustAdjustLockedBalance( parties, deltaAmt.issue()); result != tesSUCCESS) - return result; + { + printf("trustXferAllowed failed on trustAdjustLockedBalance\n"); + return result; + } // pull the TL balance from the account's perspective STAmount balance = @@ -395,6 +390,156 @@ trustAdjustLockedBalance( return tesSUCCESS; } + +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. +// Part of featurePaychanAndEscrowForTokens, but can be callled without guard + +template +[[nodiscard]]TER +trustXferAllowed( + V& view_, + std::vector const& parties, + Issue const& issue) +{ + + ReadView const& view = forceReadView(view_); + + if (isFakeXRP(issue.currency)) + return tecNO_PERMISSION; + + auto const sleIssuerAcc = view.read(keylet::account(issue.account)); + + bool lockedBalanceAllowed = + view.rules().enabled(featurePaychanAndEscrowForTokens); + + // missing issuer is always a bar to xfer + if (!sleIssuerAcc) + return tecNO_ISSUER; + + // issuer global freeze is always a bar to xfer + if (isGlobalFrozen(view, issue.account)) + return tecFROZEN; + + uint32_t issuerFlags = sleIssuerAcc->getFieldU32(sfFlags); + + bool requireAuth = issuerFlags & lsfRequireAuth; + + for (AccountID const& p: parties) + { + auto line = view.read(keylet::line(p, issue.account, issue.currency)); + if (!line) + { + if (requireAuth) + { + // the line doesn't exist, i.e. it is in default state + // default state means the line has not been authed + // therefore if auth is required by issuer then + // this is now a bar to xfer + return tecNO_AUTH; + } + + // 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 + // the point of transfer. + continue; + } + + // sanity check the line, insane lines are a bar to xfer + { + // these "strange" old lines, if they even exist anymore are + // always a bar to xfer + if (line->getFieldAmount(sfLowLimit).getIssuer() == + line->getFieldAmount(sfHighLimit).getIssuer()) + return tecINTERNAL; + + if (line->isFieldPresent(sfLockedBalance)) + { + if (!lockedBalanceAllowed) + { + printf("lockedBalanceAllowed was false\n"); + return tecINTERNAL; + } + + STAmount lockedBalance = line->getFieldAmount(sfLockedBalance); + STAmount balance = line->getFieldAmount(sfBalance); + + if (lockedBalance.getCurrency() != balance.getCurrency()) + { + printf("lockedBalance issuer/currency did not match balance issuer/currency\n"); + return tecINTERNAL; + } + } + } + + // check the bars to xfer ... these are: + // any TL in the set has noRipple on the issuer's side + // any TL in the set has a freeze on the issuer's side + // any TL in the set has RequireAuth and the TL lacks lsf*Auth + { + bool pHigh = p > issue.account; + + auto const flagIssuerNoRipple { pHigh ? lsfLowNoRipple : lsfHighNoRipple }; + auto const flagIssuerFreeze { pHigh ? lsfLowFreeze : lsfHighFreeze }; + auto const flagIssuerAuth { pHigh ? lsfLowAuth : lsfHighAuth }; + + uint32_t flags = line->getFieldU32(sfFlags); + + if (flags & flagIssuerFreeze) + { + printf("trustXferAllowed: issuerFreeze\n"); + return tecFROZEN; + } + + // if called with more than one party then any party + // that has a noripple on the issuer side of their tl + // blocks any possible xfer + if (parties.size() > 1 && (flags & flagIssuerNoRipple)) + { + printf("trustXferAllowed: issuerNoRipple\n"); + return tecPATH_DRY; + } + + // every party involved must be on an authed trustline if + // the issuer has specified lsfRequireAuth + if (requireAuth && !(flags & flagIssuerAuth)) + { + printf("trustXferAllowed: issuerRequireAuth\n"); + return tecNO_AUTH; + } + } + } + + return tesSUCCESS; +} + [[nodiscard]] TER trustXferLockedBalance( ApplyView& view, diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 0c5375ddc..60cd45d92 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -961,114 +961,6 @@ bool isTrustDefault( return true; } - -// 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. -// Part of featurePaychanAndEscrowForTokens, but can be callled without guard -TER -trustXferAllowed( - ReadView const& view, - std::vector const& parties, - Issue const& issue) -{ - - if (isFakeXRP(issue.currency)) - return tecNO_PERMISSION; - - auto const sleIssuerAcc = view.read(keylet::account(issue.account)); - - bool lockedBalanceAllowed = - view.rules().enabled(featurePaychanAndEscrowForTokens); - - // missing issuer is always a bar to xfer - if (!sleIssuerAcc) - return tecNO_ISSUER; - - // issuer global freeze is always a bar to xfer - if (isGlobalFrozen(view, issue.account)) - return tecFROZEN; - - uint32_t issuerFlags = sleIssuerAcc->getFieldU32(sfFlags); - - bool requireAuth = issuerFlags & lsfRequireAuth; - - for (AccountID const& p: parties) - { - auto line = view.read(keylet::line(p, issue.account, issue.currency)); - if (!line) - { - if (requireAuth) - { - // the line doesn't exist, i.e. it is in default state - // default state means the line has not been authed - // therefore if auth is required by issuer then - // this is now a bar to xfer - return tecNO_AUTH; - } - - // 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 - // the point of transfer. - continue; - } - - // sanity check the line, insane lines are a bar to xfer - { - // these "strange" old lines, if they even exist anymore are - // always a bar to xfer - if (line->getFieldAmount(sfLowLimit).getIssuer() == - line->getFieldAmount(sfHighLimit).getIssuer()) - return tecINTERNAL; - - if (line->isFieldPresent(sfLockedBalance)) - { - if (!lockedBalanceAllowed) - return tecINTERNAL; - - STAmount lockedBalance = line->getFieldAmount(sfLockedBalance); - STAmount balance = line->getFieldAmount(sfBalance); - - if (lockedBalance.getIssuer() != balance.getIssuer() || - lockedBalance.getCurrency() != balance.getCurrency()) - return tecINTERNAL; - } - } - - // check the bars to xfer ... these are: - // any TL in the set has noRipple on the issuer's side - // any TL in the set has a freeze on the issuer's side - // any TL in the set has RequireAuth and the TL lacks lsf*Auth - { - bool pHigh = p > issue.account; - - auto const flagIssuerNoRipple { pHigh ? lsfLowNoRipple : lsfHighNoRipple }; - auto const flagIssuerFreeze { pHigh ? lsfLowFreeze : lsfHighFreeze }; - auto const flagIssuerAuth { pHigh ? lsfLowAuth : lsfHighAuth }; - - uint32_t flags = line->getFieldU32(sfFlags); - - if (flags & flagIssuerFreeze) - return tecFROZEN; - - // if called with more than one party then any party - // that has a noripple on the issuer side of their tl - // blocks any possible xfer - if (parties.size() > 1 && (flags & flagIssuerNoRipple)) - return tecPATH_DRY; - - // every party involved must be on an authed trustline if - // the issuer has specified lsfRequireAuth - if (requireAuth && !(flags & flagIssuerAuth)) - return tecNO_AUTH; - } - } - - return tesSUCCESS; -} - TER trustXferLockedBalance( ApplyView& view,