xrpld
Loading...
Searching...
No Matches
AMMInvariant.cpp
1#include <xrpl/tx/invariants/AMMInvariant.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/ledger/ReadView.h>
8#include <xrpl/ledger/helpers/AMMHelpers.h>
9#include <xrpl/ledger/helpers/TokenHelpers.h>
10#include <xrpl/protocol/Feature.h>
11#include <xrpl/protocol/HashPrefix.h>
12#include <xrpl/protocol/Issue.h>
13#include <xrpl/protocol/LedgerFormats.h>
14#include <xrpl/protocol/SField.h>
15#include <xrpl/protocol/STAmount.h>
16#include <xrpl/protocol/STLedgerEntry.h>
17#include <xrpl/protocol/STTx.h>
18#include <xrpl/protocol/TER.h>
19#include <xrpl/protocol/TxFormats.h>
20#include <xrpl/protocol/XRPAmount.h>
21
22#include <string>
23
24namespace xrpl {
25
26void
28{
29 if (isDelete)
30 {
31 if (before && before->getType() == ltAMM)
32 {
33 ammDeleted_ = true;
34 lptAMMBalanceBeforeDeletion_ = before->getFieldAmount(sfLPTokenBalance);
35 }
36 return;
37 }
38
39 if (after)
40 {
41 auto const type = after->getType();
42 // AMM object changed
43 if (type == ltAMM)
44 {
45 ammAccount_ = after->getAccountID(sfAccount);
46 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
47 }
48 // AMM pool changed
49 else if (
50 (type == ltRIPPLE_STATE && after->isFlag(lsfAMMNode)) ||
51 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)) ||
52 (type == ltMPTOKEN && after->isFlag(lsfMPTAMM)))
53 {
54 ammPoolChanged_ = true;
55 }
56 }
57
58 if (before)
59 {
60 // AMM object changed
61 if (before->getType() == ltAMM)
62 {
63 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
64 }
65 }
66}
67
68static bool
70 STAmount const& amount,
71 STAmount const& amount2,
72 STAmount const& lptAMMBalance,
73 ValidAMM::ZeroAllowed zeroAllowed)
74{
75 bool const positive =
76 amount > beast::kZero && amount2 > beast::kZero && lptAMMBalance > beast::kZero;
77 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
78 {
79 return positive ||
80 (amount == beast::kZero && amount2 == beast::kZero && lptAMMBalance == beast::kZero);
81 }
82 return positive;
83}
84
85bool
86ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
87{
89 {
90 // LPTokens and the pool can not change on vote
91 // LCOV_EXCL_START
92 JLOG(j.error()) << "Invariant failed: AMMVote failed, "
93 << lptAMMBalanceBefore_.value_or(STAmount{}) << " "
94 << lptAMMBalanceAfter_.value_or(STAmount{}) << " " << ammPoolChanged_;
95 if (enforce)
96 return false;
97 // LCOV_EXCL_STOP
98 }
99
100 return true;
101}
102
103bool
104ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
105{
106 if (ammPoolChanged_)
107 {
108 // The pool can not change on bid
109 // LCOV_EXCL_START
110 JLOG(j.error()) << "Invariant failed: AMMBid failed, pool changed";
111 if (enforce)
112 return false;
113 // LCOV_EXCL_STOP
114 }
115 // LPTokens are burnt, therefore there should be fewer LPTokens
116 else if (
119 {
120 // LCOV_EXCL_START
121 JLOG(j.error()) << "Invariant failed: AMMBid failed, " << *lptAMMBalanceBefore_ << " "
123 if (enforce)
124 return false;
125 // LCOV_EXCL_STOP
126 }
127
128 return true;
129}
130
131bool
133 STTx const& tx,
134 ReadView const& view,
135 bool enforce,
136 beast::Journal const& j) const
137{
138 if (!ammAccount_)
139 {
140 // LCOV_EXCL_START
141 JLOG(j.error()) << "Invariant failed: AMMCreate failed, AMM object is not created";
142 if (enforce)
143 return false;
144 // LCOV_EXCL_STOP
145 }
146 else
147 {
148 auto const [amount, amount2] = ammPoolHolds(
149 view,
151 tx[sfAmount].asset(),
152 tx[sfAmount2].asset(),
155 j);
156 // Create invariant:
157 // sqrt(amount * amount2) == LPTokens
158 // all balances are greater than zero
159 // NOLINTBEGIN(bugprone-unchecked-optional-access) lptAMMBalanceAfter_ set with ammAccount_
160 // in visitEntry
161 if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
162 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->get<Issue>()) != *lptAMMBalanceAfter_)
163 {
164 JLOG(j.error()) << "Invariant failed: AMMCreate failed, " << amount << " " << amount2
165 << " " << *lptAMMBalanceAfter_;
166 if (enforce)
167 return false;
168 }
169 // NOLINTEND(bugprone-unchecked-optional-access)
170 }
171
172 return true;
173}
174
175bool
176ValidAMM::finalizeDelete(bool enforce, bool enforceAMMDelete, TER res, beast::Journal const& j)
177 const
178{
179 if (ammAccount_)
180 {
181 // LCOV_EXCL_START
182 std::string const msg = (isTesSuccess(res)) ? "AMM object remained on tesSUCCESS"
183 : "AMM object changed on tecINCOMPLETE";
184 JLOG(j.error()) << "Invariant failed: AMMDelete failed, " << msg;
185 if (enforce)
186 return false;
187 // LCOV_EXCL_STOP
188 }
189 if (enforceAMMDelete)
190 {
191 if (isTesSuccess(res))
192 {
193 if (!ammDeleted_)
194 {
195 // LCOV_EXCL_START
196 JLOG(j.error())
197 << "Invariant failed: AMMDelete failed, AMM object remained on tesSUCCESS";
198 return false;
199 // LCOV_EXCL_STOP
200 }
202 {
203 // LCOV_EXCL_START
204 JLOG(j.error())
205 << "Invariant failed: AMMDelete failed, AMM object deleted without LP balance";
206 return false;
207 // LCOV_EXCL_STOP
208 }
209 if (*lptAMMBalanceBeforeDeletion_ != beast::kZero)
210 {
211 // LCOV_EXCL_START
212 JLOG(j.error())
213 << "Invariant failed: AMMDelete failed, AMM object deleted with non-zero LP "
214 "balance: "
216 return false;
217 // LCOV_EXCL_STOP
218 }
219 }
220 else if (ammDeleted_)
221 {
222 // AMM should only be fully deleted when AMMDelete returns tesSUCCESS.
223 // LCOV_EXCL_START
224 JLOG(j.error()) << "Invariant failed: AMMDelete failed, AMM object deleted when result "
225 "is not tesSUCCESS";
226 return false;
227 // LCOV_EXCL_STOP
228 }
229 }
230
231 return true;
232}
233
234bool
235ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
236{
237 if (ammAccount_)
238 {
239 // LCOV_EXCL_START
240 JLOG(j.error()) << "Invariant failed: AMM swap failed, AMM object changed";
241 if (enforce)
242 return false;
243 // LCOV_EXCL_STOP
244 }
245
246 return true;
247}
248
249bool
251 xrpl::STTx const& tx,
252 xrpl::ReadView const& view,
253 ZeroAllowed zeroAllowed,
254 beast::Journal const& j) const
255{
256 // NOLINTBEGIN(bugprone-unchecked-optional-access) ammAccount_ and lptAMMBalanceAfter_ set
257 // together in visitEntry; callers only invoke this inside else-of-if(!ammAccount_)
258 auto const [amount, amount2] = ammPoolHolds(
259 view,
261 tx[sfAsset],
262 tx[sfAsset2],
265 j);
266 // Deposit and Withdrawal invariant:
267 // sqrt(amount * amount2) >= LPTokens
268 // all balances are greater than zero
269 // unless on last withdrawal
270 auto const poolProductMean = root2(amount * amount2);
271 bool const nonNegativeBalances =
272 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
273 auto const precisionLoss = checkAMMPrecisionLoss(poolProductMean, *lptAMMBalanceAfter_);
274 if (!nonNegativeBalances || !isTesSuccess(precisionLoss))
275 {
276 JLOG(j.error()) << "Invariant failed: AMM " << tx.getTxnType() << " "
278 << amount << " " << amount2 << " " << poolProductMean << " "
279 << lptAMMBalanceAfter_->getText() << " "
280 << ((*lptAMMBalanceAfter_ == beast::kZero)
281 ? Number{1}
282 : ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean));
283 return false;
284 }
285 // NOLINTEND(bugprone-unchecked-optional-access)
286
287 return true;
288}
289
290bool
292 xrpl::STTx const& tx,
293 xrpl::ReadView const& view,
294 bool enforce,
295 beast::Journal const& j) const
296{
297 if (!ammAccount_)
298 {
299 // LCOV_EXCL_START
300 JLOG(j.error()) << "Invariant failed: AMMDeposit failed, AMM object is deleted";
301 if (enforce)
302 return false;
303 // LCOV_EXCL_STOP
304 }
305 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
306 {
307 return false;
308 }
309
310 return true;
311}
312
313bool
315 xrpl::STTx const& tx,
316 xrpl::ReadView const& view,
317 bool enforce,
318 bool enforceAMMDelete,
319 beast::Journal const& j) const
320{
321 if (enforceAMMDelete && ammDeleted_)
322 {
323 // Last Withdraw or Clawback can delete the AMM. We don't have to check
324 // the LPToken balance because a final AMMWithdraw or AMMClawback can
325 // redeem the remaining LP tokens and delete the AMM entry in the same
326 // transaction.
327 return true;
328 }
329 if (ammAccount_ && !generalInvariant(tx, view, ZeroAllowed::Yes, j) && enforce)
330 {
331 return false;
332 }
333
334 return true;
335}
336
337bool
339 STTx const& tx,
340 TER const result,
341 XRPAmount const,
342 ReadView const& view,
343 beast::Journal const& j)
344{
345 // Delete may return tecINCOMPLETE if there are too many
346 // trustlines to delete.
347 if (!isTesSuccess(result) && result != tecINCOMPLETE)
348 return true;
349
350 bool const enforce = view.rules().enabled(fixAMMv1_3);
351 bool const enforceAMMDelete = view.rules().enabled(fixCleanup3_3_0);
352
353 // AMM can only be deleted by AMMWithdraw, AMMClawback, and AMMDelete
354 if (enforceAMMDelete && ammDeleted_)
355 {
356 switch (tx.getTxnType())
357 {
358 case ttAMM_WITHDRAW:
359 case ttAMM_CLAWBACK:
360 case ttAMM_DELETE:
361 break;
362 default:
363 // LCOV_EXCL_START
364 JLOG(j.error()) << "Invariant failed: AMM failed, unexpected AMM deletion by "
365 << tx.getTxnType();
366 return false;
367 // LCOV_EXCL_STOP
368 }
369 }
370
371 switch (tx.getTxnType())
372 {
373 case ttAMM_CREATE:
374 return finalizeCreate(tx, view, enforce, j);
375 case ttAMM_DEPOSIT:
376 return finalizeDeposit(tx, view, enforce, j);
377 case ttAMM_CLAWBACK:
378 case ttAMM_WITHDRAW:
379 return finalizeWithdraw(tx, view, enforce, enforceAMMDelete, j);
380 case ttAMM_BID:
381 return finalizeBid(enforce, j);
382 case ttAMM_VOTE:
383 return finalizeVote(enforce, j);
384 case ttAMM_DELETE:
385 return finalizeDelete(enforce, enforceAMMDelete, result, j);
386 case ttCHECK_CASH:
387 case ttOFFER_CREATE:
388 case ttPAYMENT:
389 return finalizeDEX(enforce, j);
390 default:
391 break;
392 }
393
394 return true;
395}
396
397} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream error() const
Definition Journal.h:315
A currency issued by an account.
Definition Issue.h:13
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
std::shared_ptr< STLedgerEntry const > const & const_ref
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:365
TxType getTxnType() const
Definition STTx.h:188
std::optional< AccountID > ammAccount_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalizeBid(bool enforce, beast::Journal const &) const
bool finalizeDelete(bool enforce, bool enforceAMMDelete, TER res, beast::Journal const &) const
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, bool enforceAMMDelete, beast::Journal const &) const
bool finalizeDEX(bool enforce, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceBeforeDeletion_
std::optional< STAmount > lptAMMBalanceAfter_
bool finalizeVote(bool enforce, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceBefore_
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Asset const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Asset const &asset1, Asset const &asset2, FreezeHandling freezeHandling, AuthHandling authHandling, beast::Journal const j)
Get AMM pool balances.
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:554
TER checkAMMPrecisionLoss(Number const &poolProductMean, STAmount const &newLPTokenBalance)
Check AMM pool product invariant after an AMM operation that changes LP tokens (deposit/withdraw/claw...
Number root2(Number f)
Definition Number.cpp:1275
@ TransactionId
transaction plus signature to give transaction ID
Definition HashPrefix.h:36
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecINCOMPLETE
Definition TER.h:333