#include #include #include #include namespace ripple { namespace detail { auto DeferredCredits::makeKey( AccountID const& a1, AccountID const& a2, Currency const& c) -> Key { if (a1 < a2) return std::make_tuple(a1, a2, c); else return std::make_tuple(a2, a1, c); } void DeferredCredits::credit( AccountID const& sender, AccountID const& receiver, STAmount const& amount, STAmount const& preCreditSenderBalance) { XRPL_ASSERT( sender != receiver, "ripple::detail::DeferredCredits::credit : sender is not receiver"); XRPL_ASSERT( !amount.negative(), "ripple::detail::DeferredCredits::credit : positive amount"); auto const k = makeKey(sender, receiver, amount.getCurrency()); auto i = credits_.find(k); if (i == credits_.end()) { Value v; if (sender < receiver) { v.highAcctCredits = amount; v.lowAcctCredits = amount.zeroed(); v.lowAcctOrigBalance = preCreditSenderBalance; } else { v.highAcctCredits = amount.zeroed(); v.lowAcctCredits = amount; v.lowAcctOrigBalance = -preCreditSenderBalance; } credits_[k] = v; } else { // only record the balance the first time, do not record it here auto& v = i->second; if (sender < receiver) v.highAcctCredits += amount; else v.lowAcctCredits += amount; } } void DeferredCredits::ownerCount( AccountID const& id, std::uint32_t cur, std::uint32_t next) { auto const v = std::max(cur, next); auto r = ownerCounts_.emplace(std::make_pair(id, v)); if (!r.second) { auto& mapVal = r.first->second; mapVal = std::max(v, mapVal); } } std::optional DeferredCredits::ownerCount(AccountID const& id) const { auto i = ownerCounts_.find(id); if (i != ownerCounts_.end()) return i->second; return std::nullopt; } // Get the adjustments for the balance between main and other. auto DeferredCredits::adjustments( AccountID const& main, AccountID const& other, Currency const& currency) const -> std::optional { std::optional result; Key const k = makeKey(main, other, currency); auto i = credits_.find(k); if (i == credits_.end()) return result; auto const& v = i->second; if (main < other) { result.emplace( v.highAcctCredits, v.lowAcctCredits, v.lowAcctOrigBalance); return result; } else { result.emplace( v.lowAcctCredits, v.highAcctCredits, -v.lowAcctOrigBalance); return result; } } void DeferredCredits::apply(DeferredCredits& to) { for (auto const& i : credits_) { auto r = to.credits_.emplace(i); if (!r.second) { auto& toVal = r.first->second; auto const& fromVal = i.second; toVal.lowAcctCredits += fromVal.lowAcctCredits; toVal.highAcctCredits += fromVal.highAcctCredits; // Do not update the orig balance, it's already correct } } for (auto const& i : ownerCounts_) { auto r = to.ownerCounts_.emplace(i); if (!r.second) { auto& toVal = r.first->second; auto const& fromVal = i.second; toVal = std::max(toVal, fromVal); } } } } // namespace detail STAmount PaymentSandbox::balanceHook( AccountID const& account, AccountID const& issuer, STAmount const& amount) const { /* There are two algorithms here. The pre-switchover algorithm takes the current amount and subtracts the recorded credits. The post-switchover algorithm remembers the original balance, and subtracts the debits. The post-switchover algorithm should be more numerically stable. Consider a large credit with a small initial balance. The pre-switchover algorithm computes (B+C)-C (where B+C will the amount passed in). The post-switchover algorithm returns B. When B and C differ by large magnitudes, (B+C)-C may not equal B. */ auto const currency = amount.getCurrency(); auto delta = amount.zeroed(); auto lastBal = amount; auto minBal = amount; for (auto curSB = this; curSB; curSB = curSB->ps_) { if (auto adj = curSB->tab_.adjustments(account, issuer, currency)) { delta += adj->debits; lastBal = adj->origBalance; if (lastBal < minBal) minBal = lastBal; } } // The adjusted amount should never be larger than the balance. In // some circumstances, it is possible for the deferred credits table // to compute usable balance just slightly above what the ledger // calculates (but always less than the actual balance). auto adjustedAmt = std::min({amount, lastBal - delta, minBal}); adjustedAmt.setIssuer(amount.getIssuer()); if (isXRP(issuer) && adjustedAmt < beast::zero) // A calculated negative XRP balance is not an error case. Consider a // payment snippet that credits a large XRP amount and then debits the // same amount. The credit can't be used but we subtract the debit and // calculate a negative value. It's not an error case. adjustedAmt.clear(); return adjustedAmt; } std::uint32_t PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const { std::uint32_t result = count; for (auto curSB = this; curSB; curSB = curSB->ps_) { if (auto adj = curSB->tab_.ownerCount(account)) result = std::max(result, *adj); } return result; } void PaymentSandbox::creditHook( AccountID const& from, AccountID const& to, STAmount const& amount, STAmount const& preCreditBalance) { tab_.credit(from, to, amount, preCreditBalance); } void PaymentSandbox::adjustOwnerCountHook( AccountID const& account, std::uint32_t cur, std::uint32_t next) { tab_.ownerCount(account, cur, next); } void PaymentSandbox::apply(RawView& to) { XRPL_ASSERT(!ps_, "ripple::PaymentSandbox::apply : non-null sandbox"); items_.apply(to); } void PaymentSandbox::apply(PaymentSandbox& to) { XRPL_ASSERT(ps_ == &to, "ripple::PaymentSandbox::apply : matching sandbox"); items_.apply(to); tab_.apply(to.tab_); } std::map, STAmount> PaymentSandbox::balanceChanges(ReadView const& view) const { using key_t = std::tuple; // Map of delta trust lines. As a special case, when both ends of the trust // line are the same currency, then it's delta currency for that issuer. To // get the change in XRP balance, Account == root, issuer == root, currency // == XRP std::map result; // populate a dictionary with low/high/currency/delta. This can be // compared with the other versions payment code. auto each = [&result]( uint256 const& key, bool isDelete, std::shared_ptr const& before, std::shared_ptr const& after) { STAmount oldBalance; STAmount newBalance; AccountID lowID; AccountID highID; // before is read from prev view if (isDelete) { if (!before) return; auto const bt = before->getType(); switch (bt) { case ltACCOUNT_ROOT: lowID = xrpAccount(); highID = (*before)[sfAccount]; oldBalance = (*before)[sfBalance]; newBalance = oldBalance.zeroed(); break; case ltRIPPLE_STATE: lowID = (*before)[sfLowLimit].getIssuer(); highID = (*before)[sfHighLimit].getIssuer(); oldBalance = (*before)[sfBalance]; newBalance = oldBalance.zeroed(); break; case ltOFFER: // TBD break; default: break; } } else if (!before) { // insert auto const at = after->getType(); switch (at) { case ltACCOUNT_ROOT: lowID = xrpAccount(); highID = (*after)[sfAccount]; newBalance = (*after)[sfBalance]; oldBalance = newBalance.zeroed(); break; case ltRIPPLE_STATE: lowID = (*after)[sfLowLimit].getIssuer(); highID = (*after)[sfHighLimit].getIssuer(); newBalance = (*after)[sfBalance]; oldBalance = newBalance.zeroed(); break; case ltOFFER: // TBD break; default: break; } } else { // modify auto const at = after->getType(); XRPL_ASSERT( at == before->getType(), "ripple::PaymentSandbox::balanceChanges : after and before " "types matching"); switch (at) { case ltACCOUNT_ROOT: lowID = xrpAccount(); highID = (*after)[sfAccount]; oldBalance = (*before)[sfBalance]; newBalance = (*after)[sfBalance]; break; case ltRIPPLE_STATE: lowID = (*after)[sfLowLimit].getIssuer(); highID = (*after)[sfHighLimit].getIssuer(); oldBalance = (*before)[sfBalance]; newBalance = (*after)[sfBalance]; break; case ltOFFER: // TBD break; default: break; } } // The following are now set, put them in the map auto delta = newBalance - oldBalance; auto const cur = newBalance.getCurrency(); result[std::make_tuple(lowID, highID, cur)] = delta; auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta); if (r.second) { r.first->second += delta; } delta.negate(); r = result.emplace(std::make_tuple(highID, highID, cur), delta); if (r.second) { r.first->second += delta; } }; items_.visit(view, each); return result; } XRPAmount PaymentSandbox::xrpDestroyed() const { return items_.dropsDestroyed(); } } // namespace ripple