xrpld
Loading...
Searching...
No Matches
PaymentSandbox.cpp
1#include <xrpl/ledger/PaymentSandbox.h>
2
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/beast/utility/Zero.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/ledger/OwnerCounts.h>
7#include <xrpl/ledger/RawView.h>
8#include <xrpl/protocol/AccountID.h>
9#include <xrpl/protocol/Issue.h>
10#include <xrpl/protocol/MPTIssue.h>
11#include <xrpl/protocol/SField.h>
12#include <xrpl/protocol/UintTypes.h>
13#include <xrpl/protocol/XRPAmount.h>
14
15#include <algorithm>
16#include <cstdint>
17#include <map>
18#include <optional>
19#include <tuple>
20#include <utility>
21
22namespace xrpl {
23
24namespace detail {
25
26auto
28{
29 if (a1 < a2)
30 {
31 return std::make_tuple(a1, a2, c);
32 }
33
34 return std::make_tuple(a2, a1, c);
35}
36
37void
39 AccountID const& sender,
40 AccountID const& receiver,
41 STAmount const& amount,
42 STAmount const& preCreditSenderBalance)
43{
44 XRPL_ASSERT(
45 sender != receiver, "xrpl::detail::DeferredCredits::creditIOU : sender is not receiver");
46 XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditIOU : positive amount");
47 XRPL_ASSERT(
48 amount.holds<Issue>(), "xrpl::detail::DeferredCredits::creditIOU : amount is for Issue");
49
50 auto const k = makeKeyIOU(sender, receiver, amount.get<Issue>().currency);
51 auto i = creditsIOU_.find(k);
52 if (i == creditsIOU_.end())
53 {
54 ValueIOU v;
55
56 if (sender < receiver)
57 {
58 v.lowAcctDebits = amount;
59 v.highAcctDebits = amount.zeroed();
60 v.lowAcctOrigBalance = preCreditSenderBalance;
61 }
62 else
63 {
64 v.lowAcctDebits = amount.zeroed();
65 v.highAcctDebits = amount;
66 v.lowAcctOrigBalance = -preCreditSenderBalance;
67 }
68
69 creditsIOU_[k] = v;
70 }
71 else
72 {
73 // only record the balance the first time, do not record it here
74 auto& v = i->second;
75 if (sender < receiver)
76 {
77 v.lowAcctDebits += amount;
78 }
79 else
80 {
81 v.highAcctDebits += amount;
82 }
83 }
84}
85
86void
88 AccountID const& sender,
89 AccountID const& receiver,
90 STAmount const& amount,
91 std::uint64_t preCreditBalanceHolder,
92 std::int64_t preCreditBalanceIssuer)
93{
94 XRPL_ASSERT(
95 amount.holds<MPTIssue>(),
96 "xrpl::detail::DeferredCredits::creditMPT : amount is for MPTIssue");
97 XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditMPT : positive amount");
98 XRPL_ASSERT(
99 sender != receiver, "xrpl::detail::DeferredCredits::creditMPT : sender is not receiver");
100
101 auto const mptAmtVal = amount.mpt().value();
102 auto const& issuer = amount.getIssuer();
103 auto const& mptIssue = amount.get<MPTIssue>();
104 auto const& mptID = mptIssue.getMptID();
105 bool const isSenderIssuer = sender == issuer;
106
107 auto i = creditsMPT_.find(mptID);
108 if (i == creditsMPT_.end())
109 {
111 if (isSenderIssuer)
112 {
113 v.credit = mptAmtVal;
114 v.holders[receiver].origBalance = preCreditBalanceHolder;
115 }
116 else
117 {
118 v.holders[sender].debit = mptAmtVal;
119 v.holders[sender].origBalance = preCreditBalanceHolder;
120 }
121 v.origBalance = preCreditBalanceIssuer;
122 creditsMPT_.emplace(mptID, std::move(v));
123 }
124 else
125 {
126 // only record the balance the first time, do not record it here
127 auto& v = i->second;
128 if (isSenderIssuer)
129 {
130 v.credit += mptAmtVal;
131 if (!v.holders.contains(receiver))
132 {
133 v.holders[receiver].origBalance = preCreditBalanceHolder;
134 }
135 }
136 else
137 {
138 if (!v.holders.contains(sender))
139 {
140 v.holders[sender].debit = mptAmtVal;
141 v.holders[sender].origBalance = preCreditBalanceHolder;
142 }
143 else
144 {
145 v.holders[sender].debit += mptAmtVal;
146 }
147 }
148 }
149}
150
151void
153 MPTIssue const& issue,
154 std::uint64_t amount,
155 std::int64_t origBalance)
156{
157 auto const& mptID = issue.getMptID();
158 auto i = creditsMPT_.find(mptID);
159
160 if (i == creditsMPT_.end())
161 {
163 v.origBalance = origBalance;
164 v.selfDebit = amount;
165 creditsMPT_.emplace(mptID, std::move(v));
166 }
167 else
168 {
169 i->second.selfDebit += amount;
170 }
171}
172
173void
175{
176 auto const v = std::max(cur, next);
177 auto r = ownerCounts_.emplace(id, v);
178 if (!r.second)
179 {
180 auto& mapVal = r.first->second;
181 mapVal = std::max(v, mapVal);
182 }
183}
184
187{
188 auto i = ownerCounts_.find(id);
189 if (i != ownerCounts_.end())
190 return i->second;
191 return std::nullopt;
192}
193
194// Get the adjustments for the balance between main and other.
195auto
197 AccountID const& main,
198 AccountID const& other,
199 Currency const& currency) const -> std::optional<AdjustmentIOU>
200{
202
203 KeyIOU const k = makeKeyIOU(main, other, currency);
204 auto i = creditsIOU_.find(k);
205 if (i == creditsIOU_.end())
206 return result;
207
208 auto const& v = i->second;
209
210 if (main < other)
211 {
212 result.emplace(v.lowAcctDebits, v.highAcctDebits, v.lowAcctOrigBalance);
213 return result;
214 }
215
216 result.emplace(v.highAcctDebits, v.lowAcctDebits, -v.lowAcctOrigBalance);
217 return result;
218}
219
220auto
222{
223 auto i = creditsMPT_.find(mptID);
224 if (i == creditsMPT_.end())
225 return std::nullopt;
226 return i->second;
227}
228
229void
231{
232 for (auto const& i : creditsIOU_)
233 {
234 auto r = to.creditsIOU_.emplace(i);
235 if (!r.second)
236 {
237 auto& toVal = r.first->second;
238 auto const& fromVal = i.second;
239 toVal.lowAcctDebits += fromVal.lowAcctDebits;
240 toVal.highAcctDebits += fromVal.highAcctDebits;
241 // Do not update the orig balance, it's already correct
242 }
243 }
244
245 for (auto const& i : creditsMPT_)
246 {
247 auto r = to.creditsMPT_.emplace(i);
248 if (!r.second)
249 {
250 auto& toVal = r.first->second;
251 auto const& fromVal = i.second;
252 toVal.credit += fromVal.credit;
253 toVal.selfDebit += fromVal.selfDebit;
254 for (auto& [k, v] : fromVal.holders)
255 {
256 if (!toVal.holders.contains(k))
257 {
258 toVal.holders[k] = v;
259 }
260 else
261 {
262 toVal.holders[k].debit += v.debit;
263 }
264 }
265 // Do not update the orig balance, it's already correct
266 }
267 }
268
269 for (auto const& i : ownerCounts_)
270 {
271 auto r = to.ownerCounts_.emplace(i);
272 if (!r.second)
273 {
274 auto& toVal = r.first->second;
275 auto const& fromVal = i.second;
276 toVal = std::max(toVal, fromVal);
277 }
278 }
279}
280
281} // namespace detail
282
285 AccountID const& account,
286 AccountID const& issuer,
287 STAmount const& amount) const
288{
289 XRPL_ASSERT(amount.holds<Issue>(), "balanceHookIOU: amount is for Issue");
290
291 /*
292 There are two algorithms here. The pre-switchover algorithm takes the
293 current amount and subtracts the recorded credits. The post-switchover
294 algorithm remembers the original balance, and subtracts the debits. The
295 post-switchover algorithm should be more numerically stable. Consider a
296 large credit with a small initial balance. The pre-switchover algorithm
297 computes (B+C)-C (where B+C will the amount passed in). The
298 post-switchover algorithm returns B. When B and C differ by large
299 magnitudes, (B+C)-C may not equal B.
300 */
301
302 auto const& currency = amount.get<Issue>().currency;
303
304 auto delta = amount.zeroed();
305 auto lastBal = amount;
306 auto minBal = amount;
307 for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
308 {
309 if (auto adj = curSB->tab_.adjustmentsIOU(account, issuer, currency))
310 {
311 delta += adj->debits;
312 lastBal = adj->origBalance;
313 if (lastBal < minBal)
314 minBal = lastBal;
315 }
316 }
317
318 // The adjusted amount should never be larger than the balance. In
319 // some circumstances, it is possible for the deferred credits table
320 // to compute usable balance just slightly above what the ledger
321 // calculates (but always less than the actual balance).
322 auto adjustedAmt = std::min({amount, lastBal - delta, minBal});
323 adjustedAmt.get<Issue>().account = amount.getIssuer();
324
325 if (isXRP(issuer) && adjustedAmt < beast::kZero)
326 {
327 // A calculated negative XRP balance is not an error case. Consider a
328 // payment snippet that credits a large XRP amount and then debits the
329 // same amount. The credit can't be used, but we subtract the debit and
330 // calculate a negative value. It's not an error case.
331 adjustedAmt.clear();
332 }
333
334 return adjustedAmt;
335}
336
339 const
340{
341 auto const& issuer = issue.getIssuer();
342 bool const accountIsHolder = account != issuer;
343
344 std::int64_t delta = 0;
345 std::int64_t lastBal = amount;
346 std::int64_t minBal = amount;
347 for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
348 {
349 if (auto adj = curSB->tab_.adjustmentsMPT(issue))
350 {
351 if (accountIsHolder)
352 {
353 if (auto const i = adj->holders.find(account); i != adj->holders.end())
354 {
355 delta += i->second.debit;
356 lastBal = i->second.origBalance;
357 }
358 }
359 else
360 {
361 delta += adj->credit;
362 lastBal = adj->origBalance;
363 }
364 minBal = std::min(lastBal, minBal);
365 }
366 }
367
368 // The adjusted amount should never be larger than the balance.
369
370 auto const adjustedAmt = std::min({amount, lastBal - delta, minBal});
371
372 return adjustedAmt > 0 ? STAmount{issue, adjustedAmt} : STAmount{issue};
373}
374
377{
378 std::int64_t selfDebited = 0;
379 std::int64_t lastBal = amount;
380 for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
381 {
382 if (auto adj = curSB->tab_.adjustmentsMPT(issue))
383 {
384 selfDebited += adj->selfDebit;
385 lastBal = adj->origBalance;
386 }
387 }
388
389 if (lastBal > selfDebited)
390 return STAmount{issue, lastBal - selfDebited};
391
392 return STAmount{issue};
393}
394
396PaymentSandbox::ownerCountHook(AccountID const& account, OwnerCounts const& count) const
397{
398 OwnerCounts result = count;
399 for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
400 {
401 if (auto adj = curSB->tab_.ownerCount(account))
402 result = std::max(result, *adj);
403 }
404 return result;
405}
406
407void
409 AccountID const& from,
410 AccountID const& to,
411 STAmount const& amount,
412 STAmount const& preCreditBalance)
413{
414 XRPL_ASSERT(amount.holds<Issue>(), "creditHookIOU: amount is for Issue");
415
416 tab_.creditIOU(from, to, amount, preCreditBalance);
417}
418
419void
421 AccountID const& from,
422 AccountID const& to,
423 STAmount const& amount,
424 std::uint64_t preCreditBalanceHolder,
425 std::int64_t preCreditBalanceIssuer)
426{
427 XRPL_ASSERT(amount.holds<MPTIssue>(), "creditHookMPT: amount is for MPTIssue");
428
429 tab_.creditMPT(from, to, amount, preCreditBalanceHolder, preCreditBalanceIssuer);
430}
431
432void
434 MPTIssue const& issue,
435 std::uint64_t amount,
436 std::int64_t origBalance)
437{
438 XRPL_ASSERT(amount > 0, "PaymentSandbox::issuerSelfDebitHookMPT: amount must be > 0");
439
440 tab_.issuerSelfDebitMPT(issue, amount, origBalance);
441}
442
443void
445 AccountID const& account,
446 OwnerCounts const& cur,
447 OwnerCounts const& next)
448{
449 tab_.ownerCount(account, cur, next);
450}
451
452void
454{
455 XRPL_ASSERT(!ps_, "xrpl::PaymentSandbox::apply : non-null sandbox");
456 items_.apply(to);
457}
458
459void
461{
462 XRPL_ASSERT(ps_ == &to, "xrpl::PaymentSandbox::apply : matching sandbox");
463 items_.apply(to);
464 tab_.apply(to.tab_);
465}
466
469{
470 return items_.dropsDestroyed();
471}
472
473} // namespace xrpl
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:134
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:43
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
STAmount balanceHookMPT(AccountID const &account, MPTIssue const &issue, std::int64_t amount) const override
void creditHookMPT(AccountID const &from, AccountID const &to, STAmount const &amount, std::uint64_t preCreditBalanceHolder, std::int64_t preCreditBalanceIssuer) override
STAmount balanceHookIOU(AccountID const &account, AccountID const &issuer, STAmount const &amount) const override
void creditHookIOU(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance) override
void adjustOwnerCountHook(AccountID const &account, OwnerCounts const &cur, OwnerCounts const &next) override
PaymentSandbox const * ps_
void issuerSelfDebitHookMPT(MPTIssue const &issue, std::uint64_t amount, std::int64_t origBalance) override
Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
void apply(RawView &to)
Apply changes to base view.
detail::DeferredCredits tab_
OwnerCounts ownerCountHook(AccountID const &account, OwnerCounts const &count) const override
STAmount balanceHookSelfIssueMPT(MPTIssue const &issue, std::int64_t amount) const override
XRPAmount xrpDestroyed() const
Interface for ledger entry changes.
Definition RawView.h:18
constexpr bool holds() const noexcept
Definition STAmount.h:478
constexpr TIss const & get() const
bool negative() const noexcept
Definition STAmount.h:484
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:530
MPTAmount mpt() const
Definition STAmount.cpp:301
AccountID const & getIssuer() const
Definition STAmount.h:516
detail::ApplyStateTable items_
std::optional< AdjustmentIOU > adjustmentsIOU(AccountID const &main, AccountID const &other, Currency const &currency) const
std::map< MPTID, IssuerValueMPT > creditsMPT_
void ownerCount(AccountID const &id, OwnerCounts const &cur, OwnerCounts const &next)
void apply(DeferredCredits &to)
static KeyIOU makeKeyIOU(AccountID const &a1, AccountID const &a2, Currency const &currency)
void creditIOU(AccountID const &sender, AccountID const &receiver, STAmount const &amount, STAmount const &preCreditSenderBalance)
std::map< KeyIOU, ValueIOU > creditsIOU_
void creditMPT(AccountID const &sender, AccountID const &receiver, STAmount const &amount, std::uint64_t preCreditBalanceHolder, std::int64_t preCreditBalanceIssuer)
std::map< AccountID, OwnerCounts > ownerCounts_
std::tuple< AccountID, AccountID, Currency > KeyIOU
std::optional< AdjustmentMPT > adjustmentsMPT(MPTID const &mptID) const
void issuerSelfDebitMPT(MPTIssue const &issue, std::uint64_t amount, std::int64_t origBalance)
T emplace(T... args)
T make_tuple(T... args)
T max(T... args)
T min(T... args)
constexpr Zero kZero
Definition Zero.h:30
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool isXRP(AccountID const &c)
Definition AccountID.h:84
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
std::map< AccountID, HolderValueMPT > holders