rippled
Loading...
Searching...
No Matches
AMMInvariant.cpp
1#include <xrpl/tx/invariants/AMMInvariant.h>
2//
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/protocol/TxFormats.h>
6#include <xrpl/tx/transactors/dex/AMMHelpers.h>
7#include <xrpl/tx/transactors/dex/AMMUtils.h>
8
9namespace xrpl {
10
11void
13 bool isDelete,
14 std::shared_ptr<SLE const> const& before,
16{
17 if (isDelete)
18 return;
19
20 if (after)
21 {
22 auto const type = after->getType();
23 // AMM object changed
24 if (type == ltAMM)
25 {
26 ammAccount_ = after->getAccountID(sfAccount);
27 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
28 }
29 // AMM pool changed
30 else if (
31 (type == ltRIPPLE_STATE && ((after->getFlags() & lsfAMMNode) != 0u)) ||
32 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
33 {
34 ammPoolChanged_ = true;
35 }
36 }
37
38 if (before)
39 {
40 // AMM object changed
41 if (before->getType() == ltAMM)
42 {
43 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
44 }
45 }
46}
47
48static bool
50 STAmount const& amount,
51 STAmount const& amount2,
52 STAmount const& lptAMMBalance,
53 ValidAMM::ZeroAllowed zeroAllowed)
54{
55 bool const positive =
56 amount > beast::zero && amount2 > beast::zero && lptAMMBalance > beast::zero;
57 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
58 {
59 return positive ||
60 (amount == beast::zero && amount2 == beast::zero && lptAMMBalance == beast::zero);
61 }
62 return positive;
63}
64
65bool
66ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
67{
69 {
70 // LPTokens and the pool can not change on vote
71 // LCOV_EXCL_START
72 JLOG(j.error()) << "AMMVote invariant failed: " << lptAMMBalanceBefore_.value_or(STAmount{})
73 << " " << lptAMMBalanceAfter_.value_or(STAmount{}) << " "
75 if (enforce)
76 return false;
77 // LCOV_EXCL_STOP
78 }
79
80 return true;
81}
82
83bool
84ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
85{
87 {
88 // The pool can not change on bid
89 // LCOV_EXCL_START
90 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
91 if (enforce)
92 return false;
93 // LCOV_EXCL_STOP
94 }
95 // LPTokens are burnt, therefore there should be fewer LPTokens
96 else if (
99 {
100 // LCOV_EXCL_START
101 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ << " "
103 if (enforce)
104 return false;
105 // LCOV_EXCL_STOP
106 }
107
108 return true;
109}
110
111bool
113 STTx const& tx,
114 ReadView const& view,
115 bool enforce,
116 beast::Journal const& j) const
117{
118 if (!ammAccount_)
119 {
120 // LCOV_EXCL_START
121 JLOG(j.error()) << "AMMCreate invariant failed: AMM object is not created";
122 if (enforce)
123 return false;
124 // LCOV_EXCL_STOP
125 }
126 else
127 {
128 auto const [amount, amount2] = ammPoolHolds(
129 view,
131 tx[sfAmount].get<Issue>(),
132 tx[sfAmount2].get<Issue>(),
134 j);
135 // Create invariant:
136 // sqrt(amount * amount2) == LPTokens
137 // all balances are greater than zero
138 if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
139 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) != *lptAMMBalanceAfter_)
140 {
141 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " "
143 if (enforce)
144 return false;
145 }
146 }
147
148 return true;
149}
150
151bool
152ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
153{
154 if (ammAccount_)
155 {
156 // LCOV_EXCL_START
157 std::string const msg = (isTesSuccess(res)) ? "AMM object is not deleted on tesSUCCESS"
158 : "AMM object is changed on tecINCOMPLETE";
159 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
160 if (enforce)
161 return false;
162 // LCOV_EXCL_STOP
163 }
164
165 return true;
166}
167
168bool
169ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
170{
171 if (ammAccount_)
172 {
173 // LCOV_EXCL_START
174 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
175 if (enforce)
176 return false;
177 // LCOV_EXCL_STOP
178 }
179
180 return true;
181}
182
183bool
185 xrpl::STTx const& tx,
186 xrpl::ReadView const& view,
187 ZeroAllowed zeroAllowed,
188 beast::Journal const& j) const
189{
190 auto const [amount, amount2] = ammPoolHolds(
191 view,
193 tx[sfAsset].get<Issue>(),
194 tx[sfAsset2].get<Issue>(),
196 j);
197 // Deposit and Withdrawal invariant:
198 // sqrt(amount * amount2) >= LPTokens
199 // all balances are greater than zero
200 // unless on last withdrawal
201 auto const poolProductMean = root2(amount * amount2);
202 bool const nonNegativeBalances =
203 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
204 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
205 // Allow for a small relative error if strongInvariantCheck fails
206 auto weakInvariantCheck = [&]() {
207 return *lptAMMBalanceAfter_ != beast::zero &&
208 withinRelativeDistance(poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
209 };
210 if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck()))
211 {
212 JLOG(j.error()) << "AMM " << tx.getTxnType()
213 << " invariant failed: " << tx.getHash(HashPrefix::transactionID) << " "
214 << ammPoolChanged_ << " " << amount << " " << amount2 << " "
215 << poolProductMean << " " << lptAMMBalanceAfter_->getText() << " "
216 << ((*lptAMMBalanceAfter_ == beast::zero)
217 ? Number{1}
218 : ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean));
219 return false;
220 }
221
222 return true;
223}
224
225bool
227 xrpl::STTx const& tx,
228 xrpl::ReadView const& view,
229 bool enforce,
230 beast::Journal const& j) const
231{
232 if (!ammAccount_)
233 {
234 // LCOV_EXCL_START
235 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
236 if (enforce)
237 return false;
238 // LCOV_EXCL_STOP
239 }
240 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
241 {
242 return false;
243 }
244
245 return true;
246}
247
248bool
250 xrpl::STTx const& tx,
251 xrpl::ReadView const& view,
252 bool enforce,
253 beast::Journal const& j) const
254{
255 if (!ammAccount_)
256 {
257 // Last Withdraw or Clawback deleted AMM
258 }
259 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
260 {
261 if (enforce)
262 return false;
263 }
264
265 return true;
266}
267
268bool
270 STTx const& tx,
271 TER const result,
272 XRPAmount const,
273 ReadView const& view,
274 beast::Journal const& j)
275{
276 // Delete may return tecINCOMPLETE if there are too many
277 // trustlines to delete.
278 if (!isTesSuccess(result) && result != tecINCOMPLETE)
279 return true;
280
281 bool const enforce = view.rules().enabled(fixAMMv1_3);
282
283 switch (tx.getTxnType())
284 {
285 case ttAMM_CREATE:
286 return finalizeCreate(tx, view, enforce, j);
287 case ttAMM_DEPOSIT:
288 return finalizeDeposit(tx, view, enforce, j);
289 case ttAMM_CLAWBACK:
290 case ttAMM_WITHDRAW:
291 return finalizeWithdraw(tx, view, enforce, j);
292 case ttAMM_BID:
293 return finalizeBid(enforce, j);
294 case ttAMM_VOTE:
295 return finalizeVote(enforce, j);
296 case ttAMM_DELETE:
297 return finalizeDelete(enforce, result, j);
298 case ttCHECK_CASH:
299 case ttOFFER_CREATE:
300 case ttPAYMENT:
301 return finalizeDEX(enforce, j);
302 default:
303 break;
304 }
305
306 return true;
307}
308
309} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
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:120
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:367
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 finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeBid(bool enforce, beast::Journal const &) const
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeDEX(bool enforce, beast::Journal const &) const
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
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 > lptAMMBalanceAfter_
bool finalizeVote(bool enforce, beast::Journal const &) const
bool finalizeDelete(bool enforce, TER res, 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
@ fhIGNORE_FREEZE
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
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:523
Number root2(Number f)
Definition Number.cpp:1030
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue1, Issue const &issue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool balances.
Definition AMMUtils.cpp:12
@ transactionID
transaction plus signature to give transaction ID
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition AMMHelpers.h:106
@ tecINCOMPLETE
Definition TER.h:316
T value_or(T... args)