xrpld
Loading...
Searching...
No Matches
InvariantsAMM_test.cpp
1#include <test/app/invariants/InvariantsBase.h>
2#include <test/jtx/AMM.h>
3#include <test/jtx/Account.h>
4#include <test/jtx/Env.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/mpt.h>
7#include <test/jtx/vault.h>
8#include <test/unit_test/SuiteJournal.h>
9
10#include <xrpl/basics/base_uint.h>
11#include <xrpl/beast/unit_test/suite.h>
12#include <xrpl/beast/utility/Journal.h>
13#include <xrpl/ledger/ApplyView.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Indexes.h>
17#include <xrpl/protocol/Issue.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/STLedgerEntry.h>
21#include <xrpl/protocol/STObject.h>
22#include <xrpl/protocol/STTx.h>
23#include <xrpl/protocol/TER.h>
24#include <xrpl/protocol/TxFormats.h>
25#include <xrpl/protocol/UintTypes.h>
26#include <xrpl/protocol/XRPAmount.h>
27#include <xrpl/tx/ApplyContext.h>
28#include <xrpl/tx/applySteps.h>
29#include <xrpl/tx/invariants/AMMInvariant.h>
30
31#include <cstdint>
32#include <functional>
33#include <initializer_list>
34#include <memory>
35#include <optional>
36#include <string>
37
38namespace xrpl::test {
39
41{
43
44 void
46 {
47 using namespace test::jtx;
48
49 bool const enforceAMMDelete = features[fixCleanup3_3_0];
50 testcase << "AMM delete invariants" + std::string(enforceAMMDelete ? " fix" : "");
51
52 Env env(*this, features);
53 Account const issuer{"issuer"};
54 Issue const lptIssue{Currency(0x4c50540000000000), issuer.id()};
55 STAmount const zeroLP{lptIssue, 0};
56 STAmount const nonZeroLP{lptIssue, 1};
57
58 auto const makeAMM = [](STAmount const& lptBalance) {
59 auto sleAMM = std::make_shared<SLE>(keylet::amm(UInt256(1)));
60 sleAMM->setFieldAmount(sfLPTokenBalance, lptBalance);
61 return sleAMM;
62 };
63
64 auto const checkInvariant = [&](TxType txType,
65 TER result,
66 std::optional<STAmount> const& deletedLPBalance,
67 bool expected,
68 std::string const& expectedLog) {
70 beast::Journal const jlog{sink};
71 ValidAMM invariant;
72
73 if (deletedLPBalance)
74 invariant.visitEntry(true, makeAMM(*deletedLPBalance), nullptr);
75
76 bool const actual = invariant.finalize(
77 STTx{txType, [](STObject&) {}}, result, XRPAmount{}, *env.current(), jlog);
78
79 BEAST_EXPECTS(actual == expected, "unexpected AMM delete invariant result");
80 auto const messages = sink.messages().str();
81 auto const expectedLogWhenEnforced = enforceAMMDelete ? expectedLog : "";
82 if (!expectedLogWhenEnforced.empty())
83 {
84 BEAST_EXPECTS(messages.contains(expectedLogWhenEnforced), expectedLogWhenEnforced);
85 }
86 else
87 {
88 BEAST_EXPECTS(messages.empty(), messages);
89 }
90 };
91
92 checkInvariant(
93 ttPAYMENT,
95 nonZeroLP,
96 !enforceAMMDelete,
97 "Invariant failed: AMM failed, unexpected AMM deletion by");
98 checkInvariant(
99 ttAMM_DELETE,
101 std::nullopt,
102 !enforceAMMDelete,
103 "Invariant failed: AMMDelete failed, AMM object remained on tesSUCCESS");
104 checkInvariant(
105 ttAMM_DELETE,
107 nonZeroLP,
108 !enforceAMMDelete,
109 "Invariant failed: AMMDelete failed, AMM object deleted with non-zero LP balance");
110 checkInvariant(
111 ttAMM_DELETE,
113 zeroLP,
114 !enforceAMMDelete,
115 "Invariant failed: AMMDelete failed, AMM object deleted when result is not tesSUCCESS");
116
117 checkInvariant(ttAMM_WITHDRAW, tesSUCCESS, nonZeroLP, true, "");
118 checkInvariant(ttAMM_CLAWBACK, tesSUCCESS, nonZeroLP, true, "");
119
120 checkInvariant(ttAMM_DELETE, tesSUCCESS, zeroLP, true, "");
121 checkInvariant(ttAMM_WITHDRAW, tesSUCCESS, zeroLP, true, "");
122 checkInvariant(ttAMM_CLAWBACK, tesSUCCESS, zeroLP, true, "");
123 }
124
125 void
127 {
128 testcase << "AMM";
129 using namespace jtx;
130
131 MPTID mptID{};
132 UInt256 ammID{};
133 AccountID ammAccountID{};
134 Account const gw{"gw"};
135 Issue lptIssue{};
136 PrettyAsset poolAsset{xrpIssue()};
137
138 auto deleteAMMAccount = [&](ApplyContext& ac, bool) {
139 auto sle = ac.view().peek(keylet::account(ammAccountID));
140 if (!sle)
141 return false;
142 ac.view().erase(sle);
143 return true;
144 };
145
146 auto updateLPTokensBalance = [&](ApplyContext& ac, std::int64_t amount) {
147 auto sle = ac.view().peek(keylet::amm(ammID));
148 if (!sle)
149 return false;
150 sle->setFieldAmount(sfLPTokenBalance, STAmount{lptIssue, amount});
151 ac.view().update(sle);
152 return true;
153 };
154 auto updateLPTokensBadAmount = [&](ApplyContext& ac, bool) {
155 return updateLPTokensBalance(ac, -1);
156 };
157 auto updateLPTokensBadBalance = [&](ApplyContext& ac, bool) {
158 return updateLPTokensBalance(ac, 200'000'000);
159 };
160 auto updateAMM = [&](ApplyContext& ac, bool) { return updateLPTokensBalance(ac, 10); };
161
162 auto updateAMMPool = [&](ApplyContext& ac, bool isMPT) {
163 if (isMPT)
164 {
165 auto sle = ac.view().peek(keylet::mptoken(mptID, ammAccountID));
166 if (!sle)
167 return false;
168 sle->setFieldU64(sfMPTAmount, 1);
169 ac.view().update(sle);
170 return true;
171 }
172 auto sle = ac.view().peek(keylet::account(ammAccountID));
173 if (!sle)
174 return false;
175 sle->setFieldAmount(sfBalance, XRP(1));
176 ac.view().update(sle);
177 return true;
178 };
179
180 auto test = [&](auto const txType,
181 auto&& update,
182 bool isMPT,
183 TER error = tecINVARIANT_FAILED) {
185 {{"AMM"}},
186 [&](Account const&, Account const&, ApplyContext& ac) { return update(ac, isMPT); },
187 XRPAmount{},
188 STTx{txType, [&](STObject& tx) {}},
189 {tecINVARIANT_FAILED, error},
190 [&](Account const&, Account const&, Env& env) {
191 env.fund(XRP(1'000), gw);
192 poolAsset = [&]() -> PrettyAsset {
193 if (isMPT)
194 {
195 MPT const mpt = MPTTester({.env = env, .issuer = gw});
196 mptID = mpt.issuanceID;
197 return mpt;
198 }
199 return gw["USD"];
200 }();
201 AMM const amm(env, gw, XRP(100), poolAsset(100));
202 ammAccountID = amm.ammAccount();
203 ammID = amm.ammID();
204 lptIssue = amm.lptIssue();
205 return true;
206 });
207 };
208
209 for (bool const isMPT : {false, true})
210 {
211 // Under fixCleanup3_4_0 the MPT balance invariants also fire on the
212 // second pass, so both IOU and MPT pools now escalate to tef.
213 auto const error = TER(tefINVARIANT_FAILED);
214 for (auto txType : {ttAMM_CREATE, ttAMM_DEPOSIT, ttAMM_CLAWBACK, ttAMM_WITHDRAW})
215 {
217 test(txType, updateLPTokensBadAmount, isMPT);
218 test(txType, updateLPTokensBadBalance, isMPT);
219 }
220 for (auto txType : {ttAMM_BID, ttAMM_VOTE})
221 {
222 test(txType, updateAMMPool, isMPT, error);
223 test(txType, updateLPTokensBadAmount, isMPT);
224 test(txType, updateLPTokensBadBalance, isMPT);
225 }
226 for (auto txType : {ttAMM_DELETE, ttCHECK_CASH, ttOFFER_CREATE, ttPAYMENT})
227 {
228 test(txType, updateAMM, isMPT);
229 }
230 }
231 }
232
233 // Test the invariant overwrite fix for both pre- and post-amendment
234 // behavior. With the fix enabled, |= accumulates violations across
235 // entries so a later valid entry cannot clear an earlier violation.
236 // Without the fix, = assignment means the last-visited entry wins.
237
238 void
239 run() override
240 {
242 testAMMDeleteInvariants(all_ - fixCleanup3_3_0);
243 testAMM();
244 }
245};
246
247BEAST_DEFINE_TESTSUITE(InvariantsAMM, app, xrpl);
248
249} // namespace xrpl::test
A generic endpoint for log messages.
Definition Journal.h:44
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
State information when applying a tx.
ApplyView & view()
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(SLE::Ref sle)=0
Remove a peeked SLE.
virtual void update(SLE::Ref sle)=0
Indicate changes to a peeked SLE.
A currency issued by an account.
Definition Issue.h:18
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, SLE::ConstRef, SLE::ConstRef)
void run() override
Runs the suite.
void testAMMDeleteInvariants(FeatureBitset features)
void doInvariantCheck(std::vector< std::string > const &expectLogs, Precheck const &precheck, XRPAmount fee=XRPAmount{}, STTx tx=STTx{ttACCOUNT_SET, [](STObject &) {}}, std::initializer_list< TER > ters={tecINVARIANT_FAILED, tefINVARIANT_FAILED}, Preclose const &preclose={}, TxAccount setTxAccount=TxAccount::None, std::source_location const &loc=std::source_location::current(), TER initialResult=tesSUCCESS)
Run a specific test case to put the ledger into a state that will be detected by an invariant.
std::stringstream const & messages() const
Convenience class to test AMM functionality.
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:512
Converts to MPT Issue or STAmount.
T make_shared(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:471
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:92
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
TxType
Transaction type identifiers.
Definition TxFormats.h:45
@ tefINVARIANT_FAILED
Definition TER.h:178
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
BaseUInt< 256 > UInt256
Definition base_uint.h:580
TER deleteAMMAccount(Sandbox &view, Asset const &asset, Asset const &asset2, beast::Journal j)
Delete trustlines to AMM.
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
TERSubset< CanCvtToTER > TER
Definition TER.h:654
@ tecINCOMPLETE
Definition TER.h:343
@ tecINVARIANT_FAILED
Definition TER.h:321
@ tesSUCCESS
Definition TER.h:250
T str(T... args)