From 7c375d897380477e2a6308c95f2984d248247702 Mon Sep 17 00:00:00 2001 From: bthomee Date: Tue, 25 Mar 2025 14:35:36 +0000 Subject: [PATCH] deploy: 2bc5cb240f250dfe9d1f47691cc063341f720d59 --- AMMExtended__test_8cpp_source.html | 7930 ++++---- AMM__test_8cpp_source.html | 11651 ++++++------ AccountTx__test_8cpp_source.html | 1277 +- BookDirs__test_8cpp_source.html | 154 +- Check__test_8cpp_source.html | 4989 ++--- DID__test_8cpp_source.html | 659 +- DeliverMin__test_8cpp_source.html | 218 +- DeliveredAmount__test_8cpp_source.html | 299 +- DepositAuth__test_8cpp_source.html | 2887 +-- Env__test_8cpp_source.html | 1521 +- Escrow__test_8cpp_source.html | 3147 ++-- Flow__test_8cpp_source.html | 2804 +-- GatewayBalances__test_8cpp_source.html | 419 +- GetAggregatePrice__test_8cpp_source.html | 594 +- JSONRPC__test_8cpp_source.html | 1315 +- LedgerClosed__test_8cpp_source.html | 76 +- LedgerMaster__test_8cpp_source.html | 212 +- LedgerRPC__test_8cpp_source.html | 5280 +++--- LedgerRequestRPC__test_8cpp_source.html | 413 +- MPToken__test_8cpp_source.html | 901 +- MultiSign__test_8cpp_source.html | 3145 ++-- NFToken__test_8cpp_source.html | 15271 ++++++++-------- Offer__test_8cpp_source.html | 10543 +++++------ Oracle__test_8cpp_source.html | 1449 +- OwnerInfo__test_8cpp_source.html | 257 +- Path__test_8cpp_source.html | 2362 +-- PayChan__test_8cpp_source.html | 68 +- ReducedOffer__test_8cpp_source.html | 2 +- Regression__test_8cpp_source.html | 343 +- RobustTransaction__test_8cpp_source.html | 110 +- SetAuth__test_8cpp_source.html | 56 +- Simulate__test_8cpp_source.html | 1285 +- Subscribe__test_8cpp_source.html | 2276 +-- TransactionEntry__test_8cpp_source.html | 729 +- Transaction__test_8cpp_source.html | 336 +- View__test_8cpp_source.html | 721 +- XChain__test_8cpp_source.html | 5491 +++--- classripple_1_1Check__test.html | 30 +- classripple_1_1LedgerClosed__test.html | 2 +- classripple_1_1LedgerRPC__test.html | 44 +- classripple_1_1NFTokenAllFeatures__test.html | 74 +- classripple_1_1NFTokenBaseUtil__test.html | 72 +- ...pple_1_1NFTokenDisallowIncoming__test.html | 74 +- classripple_1_1NFTokenWOMintOffer__test.html | 74 +- classripple_1_1NFTokenWOModify__test.html | 74 +- ...sripple_1_1NFTokenWOTokenRemint__test.html | 74 +- ...ripple_1_1NFTokenWOTokenReserve__test.html | 74 +- classripple_1_1NFTokenWOfixV1__test.html | 74 +- classripple_1_1OwnerInfo__test.html | 2 +- classripple_1_1RPC_1_1JSONRPC__test.html | 10 +- ...pple_1_1RPC_1_1LedgerRequestRPC__test.html | 8 +- classripple_1_1TransactionEntry__test.html | 4 +- classripple_1_1Transaction__test.html | 6 +- classripple_1_1test_1_1AccountTx__test.html | 6 +- classripple_1_1test_1_1DeliverMin__test.html | 2 +- ...pple_1_1test_1_1DeliveredAmount__test.html | 4 +- classripple_1_1test_1_1Env__test.html | 40 +- ...pple_1_1test_1_1GatewayBalances__test.html | 6 +- ...ripple_1_1test_1_1GetAmendments__test.html | 6 +- ...sripple_1_1test_1_1LedgerMaster__test.html | 6 +- classripple_1_1test_1_1MPToken__test.html | 10 +- classripple_1_1test_1_1MultiSign__test.html | 40 +- ...ple_1_1test_1_1OfferAllFeatures__test.html | 132 +- ...ripple_1_1test_1_1OfferBaseUtil__test.html | 130 +- ...le_1_1test_1_1OfferWOFillOrKill__test.html | 132 +- ...ple_1_1test_1_1OfferWOFlowCross__test.html | 132 +- ..._1_1test_1_1OfferWOSmallQOffers__test.html | 132 +- ..._1_1test_1_1OfferWTakerDryOffer__test.html | 132 +- ...ripple_1_1test_1_1Offer__manual__test.html | 132 +- classripple_1_1test_1_1Path__test.html | 52 +- ...le_1_1test_1_1RobustTransaction__test.html | 2 +- classripple_1_1test_1_1Simulate__test.html | 18 +- classripple_1_1test_1_1Subscribe__test.html | 14 +- classripple_1_1test_1_1View__test.html | 8 +- ..._1_1test_1_1XChainSim__test_1_1SmBase.html | 16 +- ...1_1XChainSim__test_1_1SmCreateAccount.html | 30 +- ...test_1_1XChainSim__test_1_1SmTransfer.html | 36 +- ..._1_1oracle_1_1GetAggregatePrice__test.html | 4 +- namespaceripple_1_1test.html | 2 +- ...ctripple_1_1test_1_1AMMExtended__test.html | 110 +- structripple_1_1test_1_1AMM__test.html | 60 +- structripple_1_1test_1_1BookDirs__test.html | 2 +- structripple_1_1test_1_1DID__test.html | 10 +- ...ctripple_1_1test_1_1DepositAuth__test.html | 6 +- ...ipple_1_1test_1_1DepositPreauth__test.html | 18 +- structripple_1_1test_1_1Env__test_1_1UDT.html | 2 +- structripple_1_1test_1_1Escrow__test.html | 24 +- ...tripple_1_1test_1_1Flow__manual__test.html | 42 +- structripple_1_1test_1_1Flow__test.html | 40 +- structripple_1_1test_1_1Regression__test.html | 8 +- structripple_1_1test_1_1SetAuth__test.html | 2 +- structripple_1_1test_1_1XChainSim__test.html | 30 +- ...t_1_1XChainSim__test_1_1AccountCreate.html | 16 +- ...1XChainSim__test_1_1AccountStateTrack.html | 10 +- ...1_1XChainSim__test_1_1ChainStateTrack.html | 42 +- ..._1_1ChainStateTrack_1_1BridgeCounters.html | 16 +- ...im__test_1_1ChainStateTrack_1_1Claims.html | 6 +- ...1XChainSim__test_1_1ChainStateTracker.html | 14 +- ..._1test_1_1XChainSim__test_1_1Transfer.html | 18 +- structripple_1_1test_1_1XChain__test.html | 14 +- ...test_1_1jtx_1_1oracle_1_1Oracle__test.html | 14 +- 101 files changed, 47027 insertions(+), 46587 deletions(-) diff --git a/AMMExtended__test_8cpp_source.html b/AMMExtended__test_8cpp_source.html index bbe0a1e12a..5f978ad104 100644 --- a/AMMExtended__test_8cpp_source.html +++ b/AMMExtended__test_8cpp_source.html @@ -202,1644 +202,1644 @@ $(function() {
127 auto const USD2 = gw2["USD"];
128
129 env.fund(XRP(20'000), alice, noripple(bob), carol, dan, gw1, gw2);
-
130 env.trust(USD1(20'000), alice, carol, dan);
-
131 env(trust(bob, USD1(1'000), tfSetNoRipple));
-
132 env.trust(USD2(1'000), alice, carol, dan);
-
133 env(trust(bob, USD2(1'000), tfSetNoRipple));
-
134
-
135 env(pay(gw1, dan, USD1(10'000)));
-
136 env(pay(gw1, bob, USD1(50)));
-
137 env(pay(gw2, bob, USD2(50)));
-
138
-
139 AMM ammDan(env, dan, XRP(10'000), USD1(10'000));
-
140
-
141 env(pay(alice, carol, USD2(50)),
-
142 path(~USD1, bob),
-
143 sendmax(XRP(50)),
-
144 txflags(tfNoRippleDirect),
-
145 ter(tecPATH_DRY));
-
146 }
-
147
-
148 {
-
149 // Make sure payment works with default flags
-
150 Env env{*this, features};
-
151
-
152 Account const dan("dan");
-
153 Account const gw1("gw1");
-
154 Account const gw2("gw2");
-
155 auto const USD1 = gw1["USD"];
-
156 auto const USD2 = gw2["USD"];
-
157
-
158 env.fund(XRP(20'000), alice, bob, carol, gw1, gw2);
-
159 env.fund(XRP(20'000), dan);
-
160 env.trust(USD1(20'000), alice, bob, carol, dan);
-
161 env.trust(USD2(1'000), alice, bob, carol, dan);
-
162
-
163 env(pay(gw1, dan, USD1(10'050)));
-
164 env(pay(gw1, bob, USD1(50)));
-
165 env(pay(gw2, bob, USD2(50)));
-
166
-
167 AMM ammDan(env, dan, XRP(10'000), USD1(10'050));
-
168
-
169 env(pay(alice, carol, USD2(50)),
-
170 path(~USD1, bob),
-
171 sendmax(XRP(50)),
-
172 txflags(tfNoRippleDirect));
-
173 BEAST_EXPECT(ammDan.expectBalances(
-
174 XRP(10'050), USD1(10'000), ammDan.tokens()));
-
175
-
176 BEAST_EXPECT(expectLedgerEntryRoot(
-
177 env, alice, XRP(20'000) - XRP(50) - txfee(env, 1)));
-
178 BEAST_EXPECT(expectLine(env, bob, USD1(100)));
-
179 BEAST_EXPECT(expectLine(env, bob, USD2(0)));
-
180 BEAST_EXPECT(expectLine(env, carol, USD2(50)));
-
181 }
-
182 }
-
183
-
184 void
-
185 testFillModes(FeatureBitset features)
-
186 {
-
187 testcase("Fill Modes");
-
188 using namespace jtx;
+
130 env.close();
+
131 env.trust(USD1(20'000), alice, carol, dan);
+
132 env(trust(bob, USD1(1'000), tfSetNoRipple));
+
133 env.trust(USD2(1'000), alice, carol, dan);
+
134 env(trust(bob, USD2(1'000), tfSetNoRipple));
+
135 env.close();
+
136
+
137 env(pay(gw1, dan, USD1(10'000)));
+
138 env(pay(gw1, bob, USD1(50)));
+
139 env(pay(gw2, bob, USD2(50)));
+
140 env.close();
+
141
+
142 AMM ammDan(env, dan, XRP(10'000), USD1(10'000));
+
143
+
144 env(pay(alice, carol, USD2(50)),
+
145 path(~USD1, bob),
+
146 sendmax(XRP(50)),
+
147 txflags(tfNoRippleDirect),
+
148 ter(tecPATH_DRY));
+
149 }
+
150
+
151 {
+
152 // Make sure payment works with default flags
+
153 Env env{*this, features};
+
154
+
155 Account const dan("dan");
+
156 Account const gw1("gw1");
+
157 Account const gw2("gw2");
+
158 auto const USD1 = gw1["USD"];
+
159 auto const USD2 = gw2["USD"];
+
160
+
161 env.fund(XRP(20'000), alice, bob, carol, gw1, gw2);
+
162 env.fund(XRP(20'000), dan);
+
163 env.close();
+
164 env.trust(USD1(20'000), alice, bob, carol, dan);
+
165 env.trust(USD2(1'000), alice, bob, carol, dan);
+
166 env.close();
+
167
+
168 env(pay(gw1, dan, USD1(10'050)));
+
169 env(pay(gw1, bob, USD1(50)));
+
170 env(pay(gw2, bob, USD2(50)));
+
171 env.close();
+
172
+
173 AMM ammDan(env, dan, XRP(10'000), USD1(10'050));
+
174
+
175 env(pay(alice, carol, USD2(50)),
+
176 path(~USD1, bob),
+
177 sendmax(XRP(50)),
+
178 txflags(tfNoRippleDirect));
+
179 BEAST_EXPECT(ammDan.expectBalances(
+
180 XRP(10'050), USD1(10'000), ammDan.tokens()));
+
181
+
182 BEAST_EXPECT(expectLedgerEntryRoot(
+
183 env, alice, XRP(20'000) - XRP(50) - txfee(env, 1)));
+
184 BEAST_EXPECT(expectLine(env, bob, USD1(100)));
+
185 BEAST_EXPECT(expectLine(env, bob, USD2(0)));
+
186 BEAST_EXPECT(expectLine(env, carol, USD2(50)));
+
187 }
+
188 }
189
-
190 auto const startBalance = XRP(1'000'000);
-
191
-
192 // Fill or Kill - unless we fully cross, just charge a fee and don't
-
193 // place the offer on the books. But also clean up expired offers
-
194 // that are discovered along the way.
-
195 //
-
196 // fix1578 changes the return code. Verify expected behavior
-
197 // without and with fix1578.
-
198 for (auto const& tweakedFeatures :
-
199 {features - fix1578, features | fix1578})
-
200 {
-
201 testAMM(
-
202 [&](AMM& ammAlice, Env& env) {
-
203 // Order that can't be filled
-
204 TER const killedCode{
-
205 tweakedFeatures[fix1578] ? TER{tecKILLED}
-
206 : TER{tesSUCCESS}};
-
207 env(offer(carol, USD(100), XRP(100)),
-
208 txflags(tfFillOrKill),
-
209 ter(killedCode));
-
210 env.close();
-
211 BEAST_EXPECT(ammAlice.expectBalances(
-
212 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
213 // fee = AMM
-
214 BEAST_EXPECT(expectLedgerEntryRoot(
-
215 env, carol, XRP(30'000) - (txfee(env, 1))));
-
216 BEAST_EXPECT(expectOffers(env, carol, 0));
-
217 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
218
-
219 // Order that can be filled
-
220 env(offer(carol, XRP(100), USD(100)),
-
221 txflags(tfFillOrKill),
-
222 ter(tesSUCCESS));
-
223 BEAST_EXPECT(ammAlice.expectBalances(
-
224 XRP(10'000), USD(10'100), ammAlice.tokens()));
-
225 BEAST_EXPECT(expectLedgerEntryRoot(
-
226 env, carol, XRP(30'000) + XRP(100) - txfee(env, 2)));
-
227 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
-
228 BEAST_EXPECT(expectOffers(env, carol, 0));
-
229 },
-
230 {{XRP(10'100), USD(10'000)}},
-
231 0,
-
232 std::nullopt,
-
233 {tweakedFeatures});
-
234
-
235 // Immediate or Cancel - cross as much as possible
-
236 // and add nothing on the books.
-
237 testAMM(
-
238 [&](AMM& ammAlice, Env& env) {
-
239 env(offer(carol, XRP(200), USD(200)),
-
240 txflags(tfImmediateOrCancel),
-
241 ter(tesSUCCESS));
-
242
-
243 // AMM generates a synthetic offer of 100USD/100XRP
-
244 // to match the CLOB offer quality.
-
245 BEAST_EXPECT(ammAlice.expectBalances(
-
246 XRP(10'000), USD(10'100), ammAlice.tokens()));
-
247 // +AMM - offer * fee
-
248 BEAST_EXPECT(expectLedgerEntryRoot(
-
249 env, carol, XRP(30'000) + XRP(100) - txfee(env, 1)));
-
250 // AMM
-
251 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
-
252 BEAST_EXPECT(expectOffers(env, carol, 0));
-
253 },
-
254 {{XRP(10'100), USD(10'000)}},
-
255 0,
-
256 std::nullopt,
-
257 {tweakedFeatures});
-
258
-
259 // tfPassive -- place the offer without crossing it.
-
260 testAMM(
-
261 [&](AMM& ammAlice, Env& env) {
-
262 // Carol creates a passive offer that could cross AMM.
-
263 // Carol's offer should stay in the ledger.
-
264 env(offer(carol, XRP(100), USD(100), tfPassive));
-
265 env.close();
-
266 BEAST_EXPECT(ammAlice.expectBalances(
-
267 XRP(10'100), STAmount{USD, 10'000}, ammAlice.tokens()));
-
268 BEAST_EXPECT(expectOffers(
-
269 env, carol, 1, {{{XRP(100), STAmount{USD, 100}}}}));
-
270 },
-
271 {{XRP(10'100), USD(10'000)}},
-
272 0,
-
273 std::nullopt,
-
274 {tweakedFeatures});
-
275
-
276 // tfPassive -- cross only offers of better quality.
-
277 testAMM(
-
278 [&](AMM& ammAlice, Env& env) {
-
279 env(offer(alice, USD(110), XRP(100)));
-
280 env.close();
+
190 void
+
191 testFillModes(FeatureBitset features)
+
192 {
+
193 testcase("Fill Modes");
+
194 using namespace jtx;
+
195
+
196 auto const startBalance = XRP(1'000'000);
+
197
+
198 // Fill or Kill - unless we fully cross, just charge a fee and don't
+
199 // place the offer on the books. But also clean up expired offers
+
200 // that are discovered along the way.
+
201 //
+
202 // fix1578 changes the return code. Verify expected behavior
+
203 // without and with fix1578.
+
204 for (auto const& tweakedFeatures :
+
205 {features - fix1578, features | fix1578})
+
206 {
+
207 testAMM(
+
208 [&](AMM& ammAlice, Env& env) {
+
209 // Order that can't be filled
+
210 TER const killedCode{
+
211 tweakedFeatures[fix1578] ? TER{tecKILLED}
+
212 : TER{tesSUCCESS}};
+
213 env(offer(carol, USD(100), XRP(100)),
+
214 txflags(tfFillOrKill),
+
215 ter(killedCode));
+
216 env.close();
+
217 BEAST_EXPECT(ammAlice.expectBalances(
+
218 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
219 // fee = AMM
+
220 BEAST_EXPECT(expectLedgerEntryRoot(
+
221 env, carol, XRP(30'000) - (txfee(env, 1))));
+
222 BEAST_EXPECT(expectOffers(env, carol, 0));
+
223 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
224
+
225 // Order that can be filled
+
226 env(offer(carol, XRP(100), USD(100)),
+
227 txflags(tfFillOrKill),
+
228 ter(tesSUCCESS));
+
229 BEAST_EXPECT(ammAlice.expectBalances(
+
230 XRP(10'000), USD(10'100), ammAlice.tokens()));
+
231 BEAST_EXPECT(expectLedgerEntryRoot(
+
232 env, carol, XRP(30'000) + XRP(100) - txfee(env, 2)));
+
233 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
+
234 BEAST_EXPECT(expectOffers(env, carol, 0));
+
235 },
+
236 {{XRP(10'100), USD(10'000)}},
+
237 0,
+
238 std::nullopt,
+
239 {tweakedFeatures});
+
240
+
241 // Immediate or Cancel - cross as much as possible
+
242 // and add nothing on the books.
+
243 testAMM(
+
244 [&](AMM& ammAlice, Env& env) {
+
245 env(offer(carol, XRP(200), USD(200)),
+
246 txflags(tfImmediateOrCancel),
+
247 ter(tesSUCCESS));
+
248
+
249 // AMM generates a synthetic offer of 100USD/100XRP
+
250 // to match the CLOB offer quality.
+
251 BEAST_EXPECT(ammAlice.expectBalances(
+
252 XRP(10'000), USD(10'100), ammAlice.tokens()));
+
253 // +AMM - offer * fee
+
254 BEAST_EXPECT(expectLedgerEntryRoot(
+
255 env, carol, XRP(30'000) + XRP(100) - txfee(env, 1)));
+
256 // AMM
+
257 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
+
258 BEAST_EXPECT(expectOffers(env, carol, 0));
+
259 },
+
260 {{XRP(10'100), USD(10'000)}},
+
261 0,
+
262 std::nullopt,
+
263 {tweakedFeatures});
+
264
+
265 // tfPassive -- place the offer without crossing it.
+
266 testAMM(
+
267 [&](AMM& ammAlice, Env& env) {
+
268 // Carol creates a passive offer that could cross AMM.
+
269 // Carol's offer should stay in the ledger.
+
270 env(offer(carol, XRP(100), USD(100), tfPassive));
+
271 env.close();
+
272 BEAST_EXPECT(ammAlice.expectBalances(
+
273 XRP(10'100), STAmount{USD, 10'000}, ammAlice.tokens()));
+
274 BEAST_EXPECT(expectOffers(
+
275 env, carol, 1, {{{XRP(100), STAmount{USD, 100}}}}));
+
276 },
+
277 {{XRP(10'100), USD(10'000)}},
+
278 0,
+
279 std::nullopt,
+
280 {tweakedFeatures});
281
-
282 // Carol creates a passive offer. That offer should cross
-
283 // AMM and leave Alice's offer untouched.
-
284 env(offer(carol, XRP(100), USD(100), tfPassive));
-
285 env.close();
-
286 BEAST_EXPECT(ammAlice.expectBalances(
-
287 XRP(10'900),
-
288 STAmount{USD, UINT64_C(9'082'56880733945), -11},
-
289 ammAlice.tokens()));
-
290 BEAST_EXPECT(expectOffers(env, carol, 0));
-
291 BEAST_EXPECT(expectOffers(env, alice, 1));
-
292 },
-
293 {{XRP(11'000), USD(9'000)}},
-
294 0,
-
295 std::nullopt,
-
296 {tweakedFeatures});
-
297 }
-
298 }
-
299
-
300 void
-
301 testOfferCrossWithXRP(FeatureBitset features)
-
302 {
-
303 testcase("Offer Crossing with XRP, Normal order");
-
304
-
305 using namespace jtx;
-
306
-
307 Env env{*this, features};
-
308
-
309 fund(env, gw, {bob, alice}, XRP(300'000), {USD(100)}, Fund::All);
+
282 // tfPassive -- cross only offers of better quality.
+
283 testAMM(
+
284 [&](AMM& ammAlice, Env& env) {
+
285 env(offer(alice, USD(110), XRP(100)));
+
286 env.close();
+
287
+
288 // Carol creates a passive offer. That offer should cross
+
289 // AMM and leave Alice's offer untouched.
+
290 env(offer(carol, XRP(100), USD(100), tfPassive));
+
291 env.close();
+
292 BEAST_EXPECT(ammAlice.expectBalances(
+
293 XRP(10'900),
+
294 STAmount{USD, UINT64_C(9'082'56880733945), -11},
+
295 ammAlice.tokens()));
+
296 BEAST_EXPECT(expectOffers(env, carol, 0));
+
297 BEAST_EXPECT(expectOffers(env, alice, 1));
+
298 },
+
299 {{XRP(11'000), USD(9'000)}},
+
300 0,
+
301 std::nullopt,
+
302 {tweakedFeatures});
+
303 }
+
304 }
+
305
+
306 void
+
307 testOfferCrossWithXRP(FeatureBitset features)
+
308 {
+
309 testcase("Offer Crossing with XRP, Normal order");
310
-
311 AMM ammAlice(env, alice, XRP(150'000), USD(50));
+
311 using namespace jtx;
312
-
313 // Existing offer pays better than this wants.
-
314 // Partially consume existing offer.
-
315 // Pay 1 USD, get 3061224490 Drops.
-
316 auto const xrpTransferred = XRPAmount{3'061'224'490};
-
317 env(offer(bob, USD(1), XRP(4'000)));
+
313 Env env{*this, features};
+
314
+
315 fund(env, gw, {bob, alice}, XRP(300'000), {USD(100)}, Fund::All);
+
316
+
317 AMM ammAlice(env, alice, XRP(150'000), USD(50));
318
-
319 BEAST_EXPECT(ammAlice.expectBalances(
-
320 XRP(150'000) + xrpTransferred,
-
321 USD(49),
-
322 IOUAmount{273'861'278752583, -8}));
-
323
-
324 BEAST_EXPECT(expectLine(env, bob, STAmount{USD, 101}));
-
325 BEAST_EXPECT(expectLedgerEntryRoot(
-
326 env, bob, XRP(300'000) - xrpTransferred - txfee(env, 1)));
-
327 BEAST_EXPECT(expectOffers(env, bob, 0));
-
328 }
+
319 // Existing offer pays better than this wants.
+
320 // Partially consume existing offer.
+
321 // Pay 1 USD, get 3061224490 Drops.
+
322 auto const xrpTransferred = XRPAmount{3'061'224'490};
+
323 env(offer(bob, USD(1), XRP(4'000)));
+
324
+
325 BEAST_EXPECT(ammAlice.expectBalances(
+
326 XRP(150'000) + xrpTransferred,
+
327 USD(49),
+
328 IOUAmount{273'861'278752583, -8}));
329
-
330 void
-
331 testOfferCrossWithLimitOverride(FeatureBitset features)
-
332 {
-
333 testcase("Offer Crossing with Limit Override");
-
334
-
335 using namespace jtx;
-
336
-
337 Env env{*this, features};
-
338
-
339 env.fund(XRP(200'000), gw, alice, bob);
+
330 BEAST_EXPECT(expectLine(env, bob, STAmount{USD, 101}));
+
331 BEAST_EXPECT(expectLedgerEntryRoot(
+
332 env, bob, XRP(300'000) - xrpTransferred - txfee(env, 1)));
+
333 BEAST_EXPECT(expectOffers(env, bob, 0));
+
334 }
+
335
+
336 void
+
337 testOfferCrossWithLimitOverride(FeatureBitset features)
+
338 {
+
339 testcase("Offer Crossing with Limit Override");
340
-
341 env(trust(alice, USD(1'000)));
+
341 using namespace jtx;
342
-
343 env(pay(gw, alice, alice["USD"](500)));
+
343 Env env{*this, features};
344
-
345 AMM ammAlice(env, alice, XRP(150'000), USD(51));
-
346 env(offer(bob, USD(1), XRP(3'000)));
+
345 env.fund(XRP(200'000), gw, alice, bob);
+
346 env.close();
347
-
348 BEAST_EXPECT(
-
349 ammAlice.expectBalances(XRP(153'000), USD(50), ammAlice.tokens()));
-
350
-
351 auto jrr = ledgerEntryState(env, bob, gw, "USD");
-
352 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-1");
-
353 jrr = ledgerEntryRoot(env, bob);
-
354 BEAST_EXPECT(
-
355 jrr[jss::node][sfBalance.fieldName] ==
-
356 to_string(
-
357 (XRP(200'000) - XRP(3'000) - env.current()->fees().base * 1)
-
358 .xrp()));
-
359 }
-
360
-
361 void
-
362 testCurrencyConversionEntire(FeatureBitset features)
-
363 {
-
364 testcase("Currency Conversion: Entire Offer");
-
365
-
366 using namespace jtx;
+
348 env(trust(alice, USD(1'000)));
+
349
+
350 env(pay(gw, alice, alice["USD"](500)));
+
351
+
352 AMM ammAlice(env, alice, XRP(150'000), USD(51));
+
353 env(offer(bob, USD(1), XRP(3'000)));
+
354
+
355 BEAST_EXPECT(
+
356 ammAlice.expectBalances(XRP(153'000), USD(50), ammAlice.tokens()));
+
357
+
358 auto jrr = ledgerEntryState(env, bob, gw, "USD");
+
359 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-1");
+
360 jrr = ledgerEntryRoot(env, bob);
+
361 BEAST_EXPECT(
+
362 jrr[jss::node][sfBalance.fieldName] ==
+
363 to_string(
+
364 (XRP(200'000) - XRP(3'000) - env.current()->fees().base * 1)
+
365 .xrp()));
+
366 }
367
-
368 Env env{*this, features};
-
369
-
370 fund(env, gw, {alice, bob}, XRP(10'000));
-
371 env.require(owners(bob, 0));
+
368 void
+
369 testCurrencyConversionEntire(FeatureBitset features)
+
370 {
+
371 testcase("Currency Conversion: Entire Offer");
372
-
373 env(trust(alice, USD(100)));
-
374 env(trust(bob, USD(1'000)));
-
375 env(pay(gw, bob, USD(1'000)));
+
373 using namespace jtx;
+
374
+
375 Env env{*this, features};
376
-
377 env.require(owners(alice, 1), owners(bob, 1));
-
378
-
379 env(pay(gw, alice, alice["USD"](100)));
-
380 AMM ammBob(env, bob, USD(200), XRP(1'500));
-
381
-
382 env(pay(alice, alice, XRP(500)), sendmax(USD(100)));
+
377 fund(env, gw, {alice, bob}, XRP(10'000));
+
378 env.require(owners(bob, 0));
+
379
+
380 env(trust(alice, USD(100)));
+
381 env(trust(bob, USD(1'000)));
+
382 env(pay(gw, bob, USD(1'000)));
383
-
384 BEAST_EXPECT(
-
385 ammBob.expectBalances(USD(300), XRP(1'000), ammBob.tokens()));
-
386 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
387
-
388 auto jrr = ledgerEntryRoot(env, alice);
-
389 BEAST_EXPECT(
-
390 jrr[jss::node][sfBalance.fieldName] ==
-
391 to_string((XRP(10'000) + XRP(500) - env.current()->fees().base * 2)
-
392 .xrp()));
-
393 }
+
384 env.require(owners(alice, 1), owners(bob, 1));
+
385
+
386 env(pay(gw, alice, alice["USD"](100)));
+
387 AMM ammBob(env, bob, USD(200), XRP(1'500));
+
388
+
389 env(pay(alice, alice, XRP(500)), sendmax(USD(100)));
+
390
+
391 BEAST_EXPECT(
+
392 ammBob.expectBalances(USD(300), XRP(1'000), ammBob.tokens()));
+
393 BEAST_EXPECT(expectLine(env, alice, USD(0)));
394
-
395 void
-
396 testCurrencyConversionInParts(FeatureBitset features)
-
397 {
-
398 testcase("Currency Conversion: In Parts");
-
399
-
400 using namespace jtx;
+
395 auto jrr = ledgerEntryRoot(env, alice);
+
396 BEAST_EXPECT(
+
397 jrr[jss::node][sfBalance.fieldName] ==
+
398 to_string((XRP(10'000) + XRP(500) - env.current()->fees().base * 2)
+
399 .xrp()));
+
400 }
401
-
402 testAMM(
-
403 [&](AMM& ammAlice, Env& env) {
-
404 // Alice converts USD to XRP which should fail
-
405 // due to PartialPayment.
-
406 env(pay(alice, alice, XRP(100)),
-
407 sendmax(USD(100)),
-
408 ter(tecPATH_PARTIAL));
-
409
-
410 // Alice converts USD to XRP, should succeed because
-
411 // we permit partial payment
-
412 env(pay(alice, alice, XRP(100)),
-
413 sendmax(USD(100)),
-
414 txflags(tfPartialPayment));
-
415 env.close();
-
416 BEAST_EXPECT(ammAlice.expectBalances(
-
417 XRPAmount{9'900'990'100}, USD(10'100), ammAlice.tokens()));
-
418 // initial 30,000 - 10,000AMM - 100pay
-
419 BEAST_EXPECT(expectLine(env, alice, USD(19'900)));
-
420 // initial 30,000 - 10,0000AMM + 99.009900pay - fee*3
-
421 BEAST_EXPECT(expectLedgerEntryRoot(
-
422 env,
-
423 alice,
-
424 XRP(30'000) - XRP(10'000) + XRPAmount{99'009'900} -
-
425 ammCrtFee(env) - txfee(env, 2)));
-
426 },
-
427 {{XRP(10'000), USD(10'000)}},
-
428 0,
-
429 std::nullopt,
-
430 {features});
-
431 }
-
432
-
433 void
-
434 testCrossCurrencyStartXRP(FeatureBitset features)
-
435 {
-
436 testcase("Cross Currency Payment: Start with XRP");
-
437
-
438 using namespace jtx;
+
402 void
+
403 testCurrencyConversionInParts(FeatureBitset features)
+
404 {
+
405 testcase("Currency Conversion: In Parts");
+
406
+
407 using namespace jtx;
+
408
+
409 testAMM(
+
410 [&](AMM& ammAlice, Env& env) {
+
411 // Alice converts USD to XRP which should fail
+
412 // due to PartialPayment.
+
413 env(pay(alice, alice, XRP(100)),
+
414 sendmax(USD(100)),
+
415 ter(tecPATH_PARTIAL));
+
416
+
417 // Alice converts USD to XRP, should succeed because
+
418 // we permit partial payment
+
419 env(pay(alice, alice, XRP(100)),
+
420 sendmax(USD(100)),
+
421 txflags(tfPartialPayment));
+
422 env.close();
+
423 BEAST_EXPECT(ammAlice.expectBalances(
+
424 XRPAmount{9'900'990'100}, USD(10'100), ammAlice.tokens()));
+
425 // initial 30,000 - 10,000AMM - 100pay
+
426 BEAST_EXPECT(expectLine(env, alice, USD(19'900)));
+
427 // initial 30,000 - 10,0000AMM + 99.009900pay - fee*3
+
428 BEAST_EXPECT(expectLedgerEntryRoot(
+
429 env,
+
430 alice,
+
431 XRP(30'000) - XRP(10'000) + XRPAmount{99'009'900} -
+
432 ammCrtFee(env) - txfee(env, 2)));
+
433 },
+
434 {{XRP(10'000), USD(10'000)}},
+
435 0,
+
436 std::nullopt,
+
437 {features});
+
438 }
439
-
440 testAMM(
-
441 [&](AMM& ammAlice, Env& env) {
-
442 env.fund(XRP(1'000), bob);
-
443 env(trust(bob, USD(100)));
-
444 env.close();
-
445 env(pay(alice, bob, USD(100)), sendmax(XRP(100)));
-
446 BEAST_EXPECT(ammAlice.expectBalances(
-
447 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
448 BEAST_EXPECT(expectLine(env, bob, USD(100)));
-
449 },
-
450 {{XRP(10'000), USD(10'100)}},
-
451 0,
-
452 std::nullopt,
-
453 {features});
-
454 }
-
455
-
456 void
-
457 testCrossCurrencyEndXRP(FeatureBitset features)
-
458 {
-
459 testcase("Cross Currency Payment: End with XRP");
-
460
-
461 using namespace jtx;
-
462
-
463 testAMM(
-
464 [&](AMM& ammAlice, Env& env) {
-
465 env.fund(XRP(1'000), bob);
-
466 env(trust(bob, USD(100)));
-
467 env.close();
-
468 env(pay(alice, bob, XRP(100)), sendmax(USD(100)));
-
469 BEAST_EXPECT(ammAlice.expectBalances(
-
470 XRP(10'000), USD(10'100), ammAlice.tokens()));
-
471 BEAST_EXPECT(expectLedgerEntryRoot(
-
472 env, bob, XRP(1'000) + XRP(100) - txfee(env, 1)));
-
473 },
-
474 {{XRP(10'100), USD(10'000)}},
-
475 0,
-
476 std::nullopt,
-
477 {features});
-
478 }
-
479
-
480 void
-
481 testCrossCurrencyBridged(FeatureBitset features)
-
482 {
-
483 testcase("Cross Currency Payment: Bridged");
-
484
-
485 using namespace jtx;
-
486
-
487 Env env{*this, features};
+
440 void
+
441 testCrossCurrencyStartXRP(FeatureBitset features)
+
442 {
+
443 testcase("Cross Currency Payment: Start with XRP");
+
444
+
445 using namespace jtx;
+
446
+
447 testAMM(
+
448 [&](AMM& ammAlice, Env& env) {
+
449 env.fund(XRP(1'000), bob);
+
450 env.close();
+
451 env(trust(bob, USD(100)));
+
452 env.close();
+
453 env(pay(alice, bob, USD(100)), sendmax(XRP(100)));
+
454 BEAST_EXPECT(ammAlice.expectBalances(
+
455 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
456 BEAST_EXPECT(expectLine(env, bob, USD(100)));
+
457 },
+
458 {{XRP(10'000), USD(10'100)}},
+
459 0,
+
460 std::nullopt,
+
461 {features});
+
462 }
+
463
+
464 void
+
465 testCrossCurrencyEndXRP(FeatureBitset features)
+
466 {
+
467 testcase("Cross Currency Payment: End with XRP");
+
468
+
469 using namespace jtx;
+
470
+
471 testAMM(
+
472 [&](AMM& ammAlice, Env& env) {
+
473 env.fund(XRP(1'000), bob);
+
474 env.close();
+
475 env(trust(bob, USD(100)));
+
476 env.close();
+
477 env(pay(alice, bob, XRP(100)), sendmax(USD(100)));
+
478 BEAST_EXPECT(ammAlice.expectBalances(
+
479 XRP(10'000), USD(10'100), ammAlice.tokens()));
+
480 BEAST_EXPECT(expectLedgerEntryRoot(
+
481 env, bob, XRP(1'000) + XRP(100) - txfee(env, 1)));
+
482 },
+
483 {{XRP(10'100), USD(10'000)}},
+
484 0,
+
485 std::nullopt,
+
486 {features});
+
487 }
488
-
489 auto const gw1 = Account{"gateway_1"};
-
490 auto const gw2 = Account{"gateway_2"};
-
491 auto const dan = Account{"dan"};
-
492 auto const USD1 = gw1["USD"];
-
493 auto const EUR1 = gw2["EUR"];
-
494
-
495 fund(env, gw1, {gw2, alice, bob, carol, dan}, XRP(60'000));
-
496
-
497 env(trust(alice, USD1(1'000)));
-
498 env.close();
-
499 env(trust(bob, EUR1(1'000)));
-
500 env.close();
-
501 env(trust(carol, USD1(10'000)));
-
502 env.close();
-
503 env(trust(dan, EUR1(1'000)));
-
504 env.close();
-
505
-
506 env(pay(gw1, alice, alice["USD"](500)));
-
507 env.close();
-
508 env(pay(gw1, carol, carol["USD"](6'000)));
-
509 env(pay(gw2, dan, dan["EUR"](400)));
+
489 void
+
490 testCrossCurrencyBridged(FeatureBitset features)
+
491 {
+
492 testcase("Cross Currency Payment: Bridged");
+
493
+
494 using namespace jtx;
+
495
+
496 Env env{*this, features};
+
497
+
498 auto const gw1 = Account{"gateway_1"};
+
499 auto const gw2 = Account{"gateway_2"};
+
500 auto const dan = Account{"dan"};
+
501 auto const USD1 = gw1["USD"];
+
502 auto const EUR1 = gw2["EUR"];
+
503
+
504 fund(env, gw1, {gw2, alice, bob, carol, dan}, XRP(60'000));
+
505 env(trust(alice, USD1(1'000)));
+
506 env.close();
+
507 env(trust(bob, EUR1(1'000)));
+
508 env.close();
+
509 env(trust(carol, USD1(10'000)));
510 env.close();
-
511
-
512 AMM ammCarol(env, carol, USD1(5'000), XRP(50'000));
+
511 env(trust(dan, EUR1(1'000)));
+
512 env.close();
513
-
514 env(offer(dan, XRP(500), EUR1(50)));
+
514 env(pay(gw1, alice, alice["USD"](500)));
515 env.close();
-
516
-
517 Json::Value jtp{Json::arrayValue};
-
518 jtp[0u][0u][jss::currency] = "XRP";
-
519 env(pay(alice, bob, EUR1(30)),
-
520 json(jss::Paths, jtp),
-
521 sendmax(USD1(333)));
-
522 env.close();
-
523 BEAST_EXPECT(ammCarol.expectBalances(
-
524 XRP(49'700),
-
525 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
-
526 ammCarol.tokens()));
-
527 BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), EUR(20)}}}));
-
528 BEAST_EXPECT(expectLine(env, bob, STAmount{EUR1, 30}));
-
529 }
-
530
-
531 void
-
532 testOfferFeesConsumeFunds(FeatureBitset features)
-
533 {
-
534 testcase("Offer Fees Consume Funds");
-
535
-
536 using namespace jtx;
-
537
-
538 Env env{*this, features};
-
539
-
540 auto const gw1 = Account{"gateway_1"};
-
541 auto const gw2 = Account{"gateway_2"};
-
542 auto const gw3 = Account{"gateway_3"};
-
543 auto const alice = Account{"alice"};
-
544 auto const bob = Account{"bob"};
-
545 auto const USD1 = gw1["USD"];
-
546 auto const USD2 = gw2["USD"];
-
547 auto const USD3 = gw3["USD"];
-
548
-
549 // Provide micro amounts to compensate for fees to make results round
-
550 // nice.
-
551 // reserve: Alice has 3 entries in the ledger, via trust lines
-
552 // fees:
-
553 // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) +
-
554 // 1 for payment == 4
-
555 auto const starting_xrp = XRP(100) +
-
556 env.current()->fees().accountReserve(3) +
-
557 env.current()->fees().base * 4;
-
558
-
559 env.fund(starting_xrp, gw1, gw2, gw3, alice);
-
560 env.fund(XRP(2'000), bob);
-
561
-
562 env(trust(alice, USD1(1'000)));
-
563 env(trust(alice, USD2(1'000)));
-
564 env(trust(alice, USD3(1'000)));
-
565 env(trust(bob, USD1(1'200)));
-
566 env(trust(bob, USD2(1'100)));
-
567
-
568 env(pay(gw1, bob, bob["USD"](1'200)));
-
569
-
570 AMM ammBob(env, bob, XRP(1'000), USD1(1'200));
-
571 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
-
572 // Ask for more than available to prove reserve works.
-
573 env(offer(alice, USD1(200), XRP(200)));
-
574
-
575 // The pool gets only 100XRP for ~109.09USD, even though
-
576 // it can exchange more.
-
577 BEAST_EXPECT(ammBob.expectBalances(
-
578 XRP(1'100),
-
579 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
-
580 ammBob.tokens()));
-
581
-
582 auto jrr = ledgerEntryState(env, alice, gw1, "USD");
-
583 BEAST_EXPECT(
-
584 jrr[jss::node][sfBalance.fieldName][jss::value] ==
-
585 "109.090909090909");
-
586 jrr = ledgerEntryRoot(env, alice);
-
587 BEAST_EXPECT(
-
588 jrr[jss::node][sfBalance.fieldName] == XRP(350).value().getText());
-
589 }
+
516 env(pay(gw1, carol, carol["USD"](6'000)));
+
517 env(pay(gw2, dan, dan["EUR"](400)));
+
518 env.close();
+
519
+
520 AMM ammCarol(env, carol, USD1(5'000), XRP(50'000));
+
521
+
522 env(offer(dan, XRP(500), EUR1(50)));
+
523 env.close();
+
524
+
525 Json::Value jtp{Json::arrayValue};
+
526 jtp[0u][0u][jss::currency] = "XRP";
+
527 env(pay(alice, bob, EUR1(30)),
+
528 json(jss::Paths, jtp),
+
529 sendmax(USD1(333)));
+
530 env.close();
+
531 BEAST_EXPECT(ammCarol.expectBalances(
+
532 XRP(49'700),
+
533 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
+
534 ammCarol.tokens()));
+
535 BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), EUR(20)}}}));
+
536 BEAST_EXPECT(expectLine(env, bob, STAmount{EUR1, 30}));
+
537 }
+
538
+
539 void
+
540 testOfferFeesConsumeFunds(FeatureBitset features)
+
541 {
+
542 testcase("Offer Fees Consume Funds");
+
543
+
544 using namespace jtx;
+
545
+
546 Env env{*this, features};
+
547
+
548 auto const gw1 = Account{"gateway_1"};
+
549 auto const gw2 = Account{"gateway_2"};
+
550 auto const gw3 = Account{"gateway_3"};
+
551 auto const alice = Account{"alice"};
+
552 auto const bob = Account{"bob"};
+
553 auto const USD1 = gw1["USD"];
+
554 auto const USD2 = gw2["USD"];
+
555 auto const USD3 = gw3["USD"];
+
556
+
557 // Provide micro amounts to compensate for fees to make results round
+
558 // nice.
+
559 // reserve: Alice has 3 entries in the ledger, via trust lines
+
560 // fees:
+
561 // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) +
+
562 // 1 for payment == 4
+
563 auto const starting_xrp = XRP(100) +
+
564 env.current()->fees().accountReserve(3) +
+
565 env.current()->fees().base * 4;
+
566
+
567 env.fund(starting_xrp, gw1, gw2, gw3, alice);
+
568 env.fund(XRP(2'000), bob);
+
569 env.close();
+
570
+
571 env(trust(alice, USD1(1'000)));
+
572 env(trust(alice, USD2(1'000)));
+
573 env(trust(alice, USD3(1'000)));
+
574 env(trust(bob, USD1(1'200)));
+
575 env(trust(bob, USD2(1'100)));
+
576
+
577 env(pay(gw1, bob, bob["USD"](1'200)));
+
578
+
579 AMM ammBob(env, bob, XRP(1'000), USD1(1'200));
+
580 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
+
581 // Ask for more than available to prove reserve works.
+
582 env(offer(alice, USD1(200), XRP(200)));
+
583
+
584 // The pool gets only 100XRP for ~109.09USD, even though
+
585 // it can exchange more.
+
586 BEAST_EXPECT(ammBob.expectBalances(
+
587 XRP(1'100),
+
588 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
+
589 ammBob.tokens()));
590
-
591 void
-
592 testOfferCreateThenCross(FeatureBitset features)
-
593 {
-
594 testcase("Offer Create, then Cross");
-
595
-
596 using namespace jtx;
-
597
-
598 Env env{*this, features};
+
591 auto jrr = ledgerEntryState(env, alice, gw1, "USD");
+
592 BEAST_EXPECT(
+
593 jrr[jss::node][sfBalance.fieldName][jss::value] ==
+
594 "109.090909090909");
+
595 jrr = ledgerEntryRoot(env, alice);
+
596 BEAST_EXPECT(
+
597 jrr[jss::node][sfBalance.fieldName] == XRP(350).value().getText());
+
598 }
599
-
600 fund(env, gw, {alice, bob}, XRP(200'000));
-
601
-
602 env(rate(gw, 1.005));
-
603
-
604 env(trust(alice, USD(1'000)));
-
605 env(trust(bob, USD(1'000)));
+
600 void
+
601 testOfferCreateThenCross(FeatureBitset features)
+
602 {
+
603 testcase("Offer Create, then Cross");
+
604
+
605 using namespace jtx;
606
-
607 env(pay(gw, bob, USD(1)));
-
608 env(pay(gw, alice, USD(200)));
-
609
-
610 AMM ammAlice(env, alice, USD(150), XRP(150'100));
-
611 env(offer(bob, XRP(100), USD(0.1)));
+
607 Env env{*this, features};
+
608
+
609 fund(env, gw, {alice, bob}, XRP(200'000));
+
610
+
611 env(rate(gw, 1.005));
612
-
613 BEAST_EXPECT(ammAlice.expectBalances(
-
614 USD(150.1), XRP(150'000), ammAlice.tokens()));
+
613 env(trust(alice, USD(1'000)));
+
614 env(trust(bob, USD(1'000)));
615
-
616 auto const jrr = ledgerEntryState(env, bob, gw, "USD");
-
617 // Bob pays 0.005 transfer fee. Note 10**-10 round-off.
-
618 BEAST_EXPECT(
-
619 jrr[jss::node][sfBalance.fieldName][jss::value] == "-0.8995000001");
-
620 }
+
616 env(pay(gw, bob, USD(1)));
+
617 env(pay(gw, alice, USD(200)));
+
618
+
619 AMM ammAlice(env, alice, USD(150), XRP(150'100));
+
620 env(offer(bob, XRP(100), USD(0.1)));
621
-
622 void
-
623 testSellFlagBasic(FeatureBitset features)
-
624 {
-
625 testcase("Offer tfSell: Basic Sell");
-
626
-
627 using namespace jtx;
-
628
-
629 testAMM(
-
630 [&](AMM& ammAlice, Env& env) {
-
631 env(offer(carol, USD(100), XRP(100)), json(jss::Flags, tfSell));
-
632 env.close();
-
633 BEAST_EXPECT(ammAlice.expectBalances(
-
634 XRP(10'000), USD(9'999), ammAlice.tokens()));
-
635 BEAST_EXPECT(expectOffers(env, carol, 0));
-
636 BEAST_EXPECT(expectLine(env, carol, USD(30'101)));
-
637 BEAST_EXPECT(expectLedgerEntryRoot(
-
638 env, carol, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
639 },
-
640 {{XRP(9'900), USD(10'100)}},
-
641 0,
-
642 std::nullopt,
-
643 {features});
-
644 }
-
645
-
646 void
-
647 testSellFlagExceedLimit(FeatureBitset features)
-
648 {
-
649 testcase("Offer tfSell: 2x Sell Exceed Limit");
-
650
-
651 using namespace jtx;
-
652
-
653 Env env{*this, features};
+
622 BEAST_EXPECT(ammAlice.expectBalances(
+
623 USD(150.1), XRP(150'000), ammAlice.tokens()));
+
624
+
625 auto const jrr = ledgerEntryState(env, bob, gw, "USD");
+
626 // Bob pays 0.005 transfer fee. Note 10**-10 round-off.
+
627 BEAST_EXPECT(
+
628 jrr[jss::node][sfBalance.fieldName][jss::value] == "-0.8995000001");
+
629 }
+
630
+
631 void
+
632 testSellFlagBasic(FeatureBitset features)
+
633 {
+
634 testcase("Offer tfSell: Basic Sell");
+
635
+
636 using namespace jtx;
+
637
+
638 testAMM(
+
639 [&](AMM& ammAlice, Env& env) {
+
640 env(offer(carol, USD(100), XRP(100)), json(jss::Flags, tfSell));
+
641 env.close();
+
642 BEAST_EXPECT(ammAlice.expectBalances(
+
643 XRP(10'000), USD(9'999), ammAlice.tokens()));
+
644 BEAST_EXPECT(expectOffers(env, carol, 0));
+
645 BEAST_EXPECT(expectLine(env, carol, USD(30'101)));
+
646 BEAST_EXPECT(expectLedgerEntryRoot(
+
647 env, carol, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
648 },
+
649 {{XRP(9'900), USD(10'100)}},
+
650 0,
+
651 std::nullopt,
+
652 {features});
+
653 }
654
-
655 auto const starting_xrp =
-
656 XRP(100) + reserve(env, 1) + env.current()->fees().base * 2;
-
657
-
658 env.fund(starting_xrp, gw, alice);
-
659 env.fund(XRP(2'000), bob);
-
660
-
661 env(trust(alice, USD(150)));
-
662 env(trust(bob, USD(4'000)));
+
655 void
+
656 testSellFlagExceedLimit(FeatureBitset features)
+
657 {
+
658 testcase("Offer tfSell: 2x Sell Exceed Limit");
+
659
+
660 using namespace jtx;
+
661
+
662 Env env{*this, features};
663
-
664 env(pay(gw, bob, bob["USD"](2'200)));
-
665
-
666 AMM ammBob(env, bob, XRP(1'000), USD(2'200));
-
667 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
-
668 // Ask for more than available to prove reserve works.
-
669 // Taker pays 100 USD for 100 XRP.
-
670 // Selling XRP.
-
671 // Will sell all 100 XRP and get more USD than asked for.
-
672 env(offer(alice, USD(100), XRP(200)), json(jss::Flags, tfSell));
-
673 BEAST_EXPECT(
-
674 ammBob.expectBalances(XRP(1'100), USD(2'000), ammBob.tokens()));
-
675 BEAST_EXPECT(expectLine(env, alice, USD(200)));
-
676 BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(250)));
-
677 BEAST_EXPECT(expectOffers(env, alice, 0));
-
678 }
-
679
-
680 void
-
681 testGatewayCrossCurrency(FeatureBitset features)
-
682 {
-
683 testcase("Client Issue: Gateway Cross Currency");
-
684
-
685 using namespace jtx;
-
686
-
687 Env env{*this, features};
-
688
-
689 auto const XTS = gw["XTS"];
-
690 auto const XXX = gw["XXX"];
-
691
-
692 auto const starting_xrp =
-
693 XRP(100.1) + reserve(env, 1) + env.current()->fees().base * 2;
-
694 fund(
-
695 env,
-
696 gw,
-
697 {alice, bob},
-
698 starting_xrp,
-
699 {XTS(100), XXX(100)},
-
700 Fund::All);
+
664 auto const starting_xrp =
+
665 XRP(100) + reserve(env, 1) + env.current()->fees().base * 2;
+
666
+
667 env.fund(starting_xrp, gw, alice);
+
668 env.fund(XRP(2'000), bob);
+
669 env.close();
+
670
+
671 env(trust(alice, USD(150)));
+
672 env(trust(bob, USD(4'000)));
+
673
+
674 env(pay(gw, bob, bob["USD"](2'200)));
+
675
+
676 AMM ammBob(env, bob, XRP(1'000), USD(2'200));
+
677 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
+
678 // Ask for more than available to prove reserve works.
+
679 // Taker pays 100 USD for 100 XRP.
+
680 // Selling XRP.
+
681 // Will sell all 100 XRP and get more USD than asked for.
+
682 env(offer(alice, USD(100), XRP(200)), json(jss::Flags, tfSell));
+
683 BEAST_EXPECT(
+
684 ammBob.expectBalances(XRP(1'100), USD(2'000), ammBob.tokens()));
+
685 BEAST_EXPECT(expectLine(env, alice, USD(200)));
+
686 BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(250)));
+
687 BEAST_EXPECT(expectOffers(env, alice, 0));
+
688 }
+
689
+
690 void
+
691 testGatewayCrossCurrency(FeatureBitset features)
+
692 {
+
693 testcase("Client Issue: Gateway Cross Currency");
+
694
+
695 using namespace jtx;
+
696
+
697 Env env{*this, features};
+
698
+
699 auto const XTS = gw["XTS"];
+
700 auto const XXX = gw["XXX"];
701
-
702 AMM ammAlice(env, alice, XTS(100), XXX(100));
-
703
-
704 Json::Value payment;
-
705 payment[jss::secret] = toBase58(generateSeed("bob"));
-
706 payment[jss::id] = env.seq(bob);
-
707 payment[jss::build_path] = true;
-
708 payment[jss::tx_json] = pay(bob, bob, bob["XXX"](1));
-
709 payment[jss::tx_json][jss::Sequence] =
-
710 env.current()
-
711 ->read(keylet::account(bob.id()))
-
712 ->getFieldU32(sfSequence);
-
713 payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
-
714 payment[jss::tx_json][jss::SendMax] =
-
715 bob["XTS"](1.5).value().getJson(JsonOptions::none);
-
716 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
-
717 auto const jrr = env.rpc("json", "submit", to_string(payment));
-
718 BEAST_EXPECT(jrr[jss::result][jss::status] == "success");
-
719 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
-
720 if (!features[fixAMMv1_1])
-
721 {
-
722 BEAST_EXPECT(ammAlice.expectBalances(
-
723 STAmount(XTS, UINT64_C(101'010101010101), -12),
-
724 XXX(99),
-
725 ammAlice.tokens()));
-
726 BEAST_EXPECT(expectLine(
-
727 env, bob, STAmount{XTS, UINT64_C(98'989898989899), -12}));
-
728 }
-
729 else
-
730 {
-
731 BEAST_EXPECT(ammAlice.expectBalances(
-
732 STAmount(XTS, UINT64_C(101'0101010101011), -13),
-
733 XXX(99),
-
734 ammAlice.tokens()));
-
735 BEAST_EXPECT(expectLine(
-
736 env, bob, STAmount{XTS, UINT64_C(98'9898989898989), -13}));
-
737 }
-
738 BEAST_EXPECT(expectLine(env, bob, XXX(101)));
-
739 }
-
740
-
741 void
-
742 testBridgedCross(FeatureBitset features)
-
743 {
-
744 testcase("Bridged Crossing");
-
745
-
746 using namespace jtx;
-
747
-
748 {
-
749 Env env{*this, features};
+
702 auto const starting_xrp =
+
703 XRP(100.1) + reserve(env, 1) + env.current()->fees().base * 2;
+
704 fund(
+
705 env,
+
706 gw,
+
707 {alice, bob},
+
708 starting_xrp,
+
709 {XTS(100), XXX(100)},
+
710 Fund::All);
+
711
+
712 AMM ammAlice(env, alice, XTS(100), XXX(100));
+
713
+
714 Json::Value payment;
+
715 payment[jss::secret] = toBase58(generateSeed("bob"));
+
716 payment[jss::id] = env.seq(bob);
+
717 payment[jss::build_path] = true;
+
718 payment[jss::tx_json] = pay(bob, bob, bob["XXX"](1));
+
719 payment[jss::tx_json][jss::Sequence] =
+
720 env.current()
+
721 ->read(keylet::account(bob.id()))
+
722 ->getFieldU32(sfSequence);
+
723 payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
+
724 payment[jss::tx_json][jss::SendMax] =
+
725 bob["XTS"](1.5).value().getJson(JsonOptions::none);
+
726 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
+
727 auto const jrr = env.rpc("json", "submit", to_string(payment));
+
728 BEAST_EXPECT(jrr[jss::result][jss::status] == "success");
+
729 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
+
730 if (!features[fixAMMv1_1])
+
731 {
+
732 BEAST_EXPECT(ammAlice.expectBalances(
+
733 STAmount(XTS, UINT64_C(101'010101010101), -12),
+
734 XXX(99),
+
735 ammAlice.tokens()));
+
736 BEAST_EXPECT(expectLine(
+
737 env, bob, STAmount{XTS, UINT64_C(98'989898989899), -12}));
+
738 }
+
739 else
+
740 {
+
741 BEAST_EXPECT(ammAlice.expectBalances(
+
742 STAmount(XTS, UINT64_C(101'0101010101011), -13),
+
743 XXX(99),
+
744 ammAlice.tokens()));
+
745 BEAST_EXPECT(expectLine(
+
746 env, bob, STAmount{XTS, UINT64_C(98'9898989898989), -13}));
+
747 }
+
748 BEAST_EXPECT(expectLine(env, bob, XXX(101)));
+
749 }
750
-
751 fund(
-
752 env,
-
753 gw,
-
754 {alice, bob, carol},
-
755 {USD(15'000), EUR(15'000)},
-
756 Fund::All);
+
751 void
+
752 testBridgedCross(FeatureBitset features)
+
753 {
+
754 testcase("Bridged Crossing");
+
755
+
756 using namespace jtx;
757
-
758 // The scenario:
-
759 // o USD/XRP AMM is created.
-
760 // o EUR/XRP AMM is created.
-
761 // o carol has EUR but wants USD.
-
762 // Note that carol's offer must come last. If carol's offer is
-
763 // placed before AMM is created, then autobridging will not occur.
-
764 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
765 AMM ammBob(env, bob, EUR(10'000), XRP(10'100));
-
766
-
767 // Carol makes an offer that consumes AMM liquidity and
-
768 // fully consumes Carol's offer.
-
769 env(offer(carol, USD(100), EUR(100)));
-
770 env.close();
-
771
-
772 BEAST_EXPECT(ammAlice.expectBalances(
-
773 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
774 BEAST_EXPECT(ammBob.expectBalances(
-
775 XRP(10'000), EUR(10'100), ammBob.tokens()));
-
776 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
-
777 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
-
778 BEAST_EXPECT(expectOffers(env, carol, 0));
-
779 }
-
780
-
781 {
-
782 Env env{*this, features};
-
783
-
784 fund(
-
785 env,
-
786 gw,
-
787 {alice, bob, carol},
-
788 {USD(15'000), EUR(15'000)},
-
789 Fund::All);
+
758 {
+
759 Env env{*this, features};
+
760
+
761 fund(
+
762 env,
+
763 gw,
+
764 {alice, bob, carol},
+
765 {USD(15'000), EUR(15'000)},
+
766 Fund::All);
+
767
+
768 // The scenario:
+
769 // o USD/XRP AMM is created.
+
770 // o EUR/XRP AMM is created.
+
771 // o carol has EUR but wants USD.
+
772 // Note that carol's offer must come last. If carol's offer is
+
773 // placed before AMM is created, then autobridging will not occur.
+
774 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
775 AMM ammBob(env, bob, EUR(10'000), XRP(10'100));
+
776
+
777 // Carol makes an offer that consumes AMM liquidity and
+
778 // fully consumes Carol's offer.
+
779 env(offer(carol, USD(100), EUR(100)));
+
780 env.close();
+
781
+
782 BEAST_EXPECT(ammAlice.expectBalances(
+
783 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
784 BEAST_EXPECT(ammBob.expectBalances(
+
785 XRP(10'000), EUR(10'100), ammBob.tokens()));
+
786 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
+
787 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
+
788 BEAST_EXPECT(expectOffers(env, carol, 0));
+
789 }
790
-
791 // The scenario:
-
792 // o USD/XRP AMM is created.
-
793 // o EUR/XRP offer is created.
-
794 // o carol has EUR but wants USD.
-
795 // Note that carol's offer must come last. If carol's offer is
-
796 // placed before AMM and bob's offer are created, then autobridging
-
797 // will not occur.
-
798 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
799 env(offer(bob, EUR(100), XRP(100)));
-
800 env.close();
-
801
-
802 // Carol makes an offer that consumes AMM liquidity and
-
803 // fully consumes Carol's offer.
-
804 env(offer(carol, USD(100), EUR(100)));
-
805 env.close();
-
806
-
807 BEAST_EXPECT(ammAlice.expectBalances(
-
808 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
809 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
-
810 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
-
811 BEAST_EXPECT(expectOffers(env, carol, 0));
-
812 BEAST_EXPECT(expectOffers(env, bob, 0));
-
813 }
-
814
-
815 {
-
816 Env env{*this, features};
-
817
-
818 fund(
-
819 env,
-
820 gw,
-
821 {alice, bob, carol},
-
822 {USD(15'000), EUR(15'000)},
-
823 Fund::All);
+
791 {
+
792 Env env{*this, features};
+
793
+
794 fund(
+
795 env,
+
796 gw,
+
797 {alice, bob, carol},
+
798 {USD(15'000), EUR(15'000)},
+
799 Fund::All);
+
800
+
801 // The scenario:
+
802 // o USD/XRP AMM is created.
+
803 // o EUR/XRP offer is created.
+
804 // o carol has EUR but wants USD.
+
805 // Note that carol's offer must come last. If carol's offer is
+
806 // placed before AMM and bob's offer are created, then autobridging
+
807 // will not occur.
+
808 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
809 env(offer(bob, EUR(100), XRP(100)));
+
810 env.close();
+
811
+
812 // Carol makes an offer that consumes AMM liquidity and
+
813 // fully consumes Carol's offer.
+
814 env(offer(carol, USD(100), EUR(100)));
+
815 env.close();
+
816
+
817 BEAST_EXPECT(ammAlice.expectBalances(
+
818 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
819 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
+
820 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
+
821 BEAST_EXPECT(expectOffers(env, carol, 0));
+
822 BEAST_EXPECT(expectOffers(env, bob, 0));
+
823 }
824
-
825 // The scenario:
-
826 // o USD/XRP offer is created.
-
827 // o EUR/XRP AMM is created.
-
828 // o carol has EUR but wants USD.
-
829 // Note that carol's offer must come last. If carol's offer is
-
830 // placed before AMM and alice's offer are created, then
-
831 // autobridging will not occur.
-
832 env(offer(alice, XRP(100), USD(100)));
-
833 env.close();
-
834 AMM ammBob(env, bob, EUR(10'000), XRP(10'100));
-
835
-
836 // Carol makes an offer that consumes AMM liquidity and
-
837 // fully consumes Carol's offer.
-
838 env(offer(carol, USD(100), EUR(100)));
-
839 env.close();
-
840
-
841 BEAST_EXPECT(ammBob.expectBalances(
-
842 XRP(10'000), EUR(10'100), ammBob.tokens()));
-
843 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
-
844 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
-
845 BEAST_EXPECT(expectOffers(env, carol, 0));
-
846 BEAST_EXPECT(expectOffers(env, alice, 0));
-
847 }
-
848 }
-
849
-
850 void
-
851 testSellWithFillOrKill(FeatureBitset features)
-
852 {
-
853 // Test a number of different corner cases regarding offer crossing
-
854 // when both the tfSell flag and tfFillOrKill flags are set.
-
855 testcase("Combine tfSell with tfFillOrKill");
-
856
-
857 using namespace jtx;
-
858
-
859 // Code returned if an offer is killed.
-
860 TER const killedCode{
-
861 features[fix1578] ? TER{tecKILLED} : TER{tesSUCCESS}};
-
862
-
863 {
-
864 Env env{*this, features};
-
865 fund(env, gw, {alice, bob}, {USD(20'000)}, Fund::All);
-
866 AMM ammBob(env, bob, XRP(20'000), USD(200));
-
867 // alice submits a tfSell | tfFillOrKill offer that does not cross.
-
868 env(offer(alice, USD(2.1), XRP(210), tfSell | tfFillOrKill),
-
869 ter(killedCode));
-
870
-
871 BEAST_EXPECT(
-
872 ammBob.expectBalances(XRP(20'000), USD(200), ammBob.tokens()));
-
873 BEAST_EXPECT(expectOffers(env, bob, 0));
-
874 }
-
875 {
-
876 Env env{*this, features};
-
877 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
-
878 AMM ammBob(env, bob, XRP(20'000), USD(200));
-
879 // alice submits a tfSell | tfFillOrKill offer that crosses.
-
880 // Even though tfSell is present it doesn't matter this time.
-
881 env(offer(alice, USD(2), XRP(220), tfSell | tfFillOrKill));
-
882 env.close();
-
883 BEAST_EXPECT(ammBob.expectBalances(
-
884 XRP(20'220),
-
885 STAmount{USD, UINT64_C(197'8239366963403), -13},
-
886 ammBob.tokens()));
-
887 BEAST_EXPECT(expectLine(
-
888 env, alice, STAmount{USD, UINT64_C(1'002'17606330366), -11}));
-
889 BEAST_EXPECT(expectOffers(env, alice, 0));
-
890 }
-
891 {
-
892 // alice submits a tfSell | tfFillOrKill offer that crosses and
-
893 // returns more than was asked for (because of the tfSell flag).
-
894 Env env{*this, features};
-
895 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
-
896 AMM ammBob(env, bob, XRP(20'000), USD(200));
-
897
-
898 env(offer(alice, USD(10), XRP(1'500), tfSell | tfFillOrKill));
-
899 env.close();
-
900
-
901 BEAST_EXPECT(ammBob.expectBalances(
-
902 XRP(21'500),
-
903 STAmount{USD, UINT64_C(186'046511627907), -12},
-
904 ammBob.tokens()));
-
905 BEAST_EXPECT(expectLine(
-
906 env, alice, STAmount{USD, UINT64_C(1'013'953488372093), -12}));
-
907 BEAST_EXPECT(expectOffers(env, alice, 0));
-
908 }
-
909 {
-
910 // alice submits a tfSell | tfFillOrKill offer that doesn't cross.
-
911 // This would have succeeded with a regular tfSell, but the
-
912 // fillOrKill prevents the transaction from crossing since not
-
913 // all of the offer is consumed because AMM generated offer,
-
914 // which matches alice's offer quality is ~ 10XRP/0.01996USD.
-
915 Env env{*this, features};
-
916 fund(env, gw, {alice, bob}, {USD(10'000)}, Fund::All);
-
917 AMM ammBob(env, bob, XRP(5000), USD(10));
-
918
-
919 env(offer(alice, USD(1), XRP(501), tfSell | tfFillOrKill),
-
920 ter(tecKILLED));
-
921 env.close();
-
922 BEAST_EXPECT(expectOffers(env, alice, 0));
-
923 BEAST_EXPECT(expectOffers(env, bob, 0));
-
924 }
-
925 }
-
926
-
927 void
-
928 testTransferRateOffer(FeatureBitset features)
-
929 {
-
930 testcase("Transfer Rate Offer");
-
931
-
932 using namespace jtx;
-
933
-
934 // AMM XRP/USD. Alice places USD/XRP offer.
-
935 testAMM(
-
936 [&](AMM& ammAlice, Env& env) {
-
937 env(rate(gw, 1.25));
-
938 env.close();
-
939
-
940 env(offer(carol, USD(100), XRP(100)));
-
941 env.close();
-
942
-
943 // AMM doesn't pay the transfer fee
-
944 BEAST_EXPECT(ammAlice.expectBalances(
-
945 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
946 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
947 BEAST_EXPECT(expectOffers(env, carol, 0));
-
948 },
-
949 {{XRP(10'000), USD(10'100)}},
-
950 0,
-
951 std::nullopt,
-
952 {features});
-
953
-
954 // Reverse the order, so the offer in the books is to sell XRP
-
955 // in return for USD.
-
956 testAMM(
-
957 [&](AMM& ammAlice, Env& env) {
-
958 env(rate(gw, 1.25));
-
959 env.close();
-
960
-
961 env(offer(carol, XRP(100), USD(100)));
-
962 env.close();
+
825 {
+
826 Env env{*this, features};
+
827
+
828 fund(
+
829 env,
+
830 gw,
+
831 {alice, bob, carol},
+
832 {USD(15'000), EUR(15'000)},
+
833 Fund::All);
+
834
+
835 // The scenario:
+
836 // o USD/XRP offer is created.
+
837 // o EUR/XRP AMM is created.
+
838 // o carol has EUR but wants USD.
+
839 // Note that carol's offer must come last. If carol's offer is
+
840 // placed before AMM and alice's offer are created, then
+
841 // autobridging will not occur.
+
842 env(offer(alice, XRP(100), USD(100)));
+
843 env.close();
+
844 AMM ammBob(env, bob, EUR(10'000), XRP(10'100));
+
845
+
846 // Carol makes an offer that consumes AMM liquidity and
+
847 // fully consumes Carol's offer.
+
848 env(offer(carol, USD(100), EUR(100)));
+
849 env.close();
+
850
+
851 BEAST_EXPECT(ammBob.expectBalances(
+
852 XRP(10'000), EUR(10'100), ammBob.tokens()));
+
853 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
+
854 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
+
855 BEAST_EXPECT(expectOffers(env, carol, 0));
+
856 BEAST_EXPECT(expectOffers(env, alice, 0));
+
857 }
+
858 }
+
859
+
860 void
+
861 testSellWithFillOrKill(FeatureBitset features)
+
862 {
+
863 // Test a number of different corner cases regarding offer crossing
+
864 // when both the tfSell flag and tfFillOrKill flags are set.
+
865 testcase("Combine tfSell with tfFillOrKill");
+
866
+
867 using namespace jtx;
+
868
+
869 // Code returned if an offer is killed.
+
870 TER const killedCode{
+
871 features[fix1578] ? TER{tecKILLED} : TER{tesSUCCESS}};
+
872
+
873 {
+
874 Env env{*this, features};
+
875 fund(env, gw, {alice, bob}, {USD(20'000)}, Fund::All);
+
876 AMM ammBob(env, bob, XRP(20'000), USD(200));
+
877 // alice submits a tfSell | tfFillOrKill offer that does not cross.
+
878 env(offer(alice, USD(2.1), XRP(210), tfSell | tfFillOrKill),
+
879 ter(killedCode));
+
880
+
881 BEAST_EXPECT(
+
882 ammBob.expectBalances(XRP(20'000), USD(200), ammBob.tokens()));
+
883 BEAST_EXPECT(expectOffers(env, bob, 0));
+
884 }
+
885 {
+
886 Env env{*this, features};
+
887 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
+
888 AMM ammBob(env, bob, XRP(20'000), USD(200));
+
889 // alice submits a tfSell | tfFillOrKill offer that crosses.
+
890 // Even though tfSell is present it doesn't matter this time.
+
891 env(offer(alice, USD(2), XRP(220), tfSell | tfFillOrKill));
+
892 env.close();
+
893 BEAST_EXPECT(ammBob.expectBalances(
+
894 XRP(20'220),
+
895 STAmount{USD, UINT64_C(197'8239366963403), -13},
+
896 ammBob.tokens()));
+
897 BEAST_EXPECT(expectLine(
+
898 env, alice, STAmount{USD, UINT64_C(1'002'17606330366), -11}));
+
899 BEAST_EXPECT(expectOffers(env, alice, 0));
+
900 }
+
901 {
+
902 // alice submits a tfSell | tfFillOrKill offer that crosses and
+
903 // returns more than was asked for (because of the tfSell flag).
+
904 Env env{*this, features};
+
905 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
+
906 AMM ammBob(env, bob, XRP(20'000), USD(200));
+
907
+
908 env(offer(alice, USD(10), XRP(1'500), tfSell | tfFillOrKill));
+
909 env.close();
+
910
+
911 BEAST_EXPECT(ammBob.expectBalances(
+
912 XRP(21'500),
+
913 STAmount{USD, UINT64_C(186'046511627907), -12},
+
914 ammBob.tokens()));
+
915 BEAST_EXPECT(expectLine(
+
916 env, alice, STAmount{USD, UINT64_C(1'013'953488372093), -12}));
+
917 BEAST_EXPECT(expectOffers(env, alice, 0));
+
918 }
+
919 {
+
920 // alice submits a tfSell | tfFillOrKill offer that doesn't cross.
+
921 // This would have succeeded with a regular tfSell, but the
+
922 // fillOrKill prevents the transaction from crossing since not
+
923 // all of the offer is consumed because AMM generated offer,
+
924 // which matches alice's offer quality is ~ 10XRP/0.01996USD.
+
925 Env env{*this, features};
+
926 fund(env, gw, {alice, bob}, {USD(10'000)}, Fund::All);
+
927 AMM ammBob(env, bob, XRP(5000), USD(10));
+
928
+
929 env(offer(alice, USD(1), XRP(501), tfSell | tfFillOrKill),
+
930 ter(tecKILLED));
+
931 env.close();
+
932 BEAST_EXPECT(expectOffers(env, alice, 0));
+
933 BEAST_EXPECT(expectOffers(env, bob, 0));
+
934 }
+
935 }
+
936
+
937 void
+
938 testTransferRateOffer(FeatureBitset features)
+
939 {
+
940 testcase("Transfer Rate Offer");
+
941
+
942 using namespace jtx;
+
943
+
944 // AMM XRP/USD. Alice places USD/XRP offer.
+
945 testAMM(
+
946 [&](AMM& ammAlice, Env& env) {
+
947 env(rate(gw, 1.25));
+
948 env.close();
+
949
+
950 env(offer(carol, USD(100), XRP(100)));
+
951 env.close();
+
952
+
953 // AMM doesn't pay the transfer fee
+
954 BEAST_EXPECT(ammAlice.expectBalances(
+
955 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
956 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
957 BEAST_EXPECT(expectOffers(env, carol, 0));
+
958 },
+
959 {{XRP(10'000), USD(10'100)}},
+
960 0,
+
961 std::nullopt,
+
962 {features});
963
-
964 BEAST_EXPECT(ammAlice.expectBalances(
-
965 XRP(10'000), USD(10'100), ammAlice.tokens()));
-
966 // Carol pays 25% transfer fee
-
967 BEAST_EXPECT(expectLine(env, carol, USD(29'875)));
-
968 BEAST_EXPECT(expectOffers(env, carol, 0));
-
969 },
-
970 {{XRP(10'100), USD(10'000)}},
-
971 0,
-
972 std::nullopt,
-
973 {features});
-
974
-
975 {
-
976 // Bridged crossing.
-
977 Env env{*this, features};
-
978 fund(
-
979 env,
-
980 gw,
-
981 {alice, bob, carol},
-
982 {USD(15'000), EUR(15'000)},
-
983 Fund::All);
-
984 env(rate(gw, 1.25));
-
985
-
986 // The scenario:
-
987 // o USD/XRP AMM is created.
-
988 // o EUR/XRP Offer is created.
-
989 // o carol has EUR but wants USD.
-
990 // Note that Carol's offer must come last. If Carol's offer is
-
991 // placed before AMM is created, then autobridging will not occur.
-
992 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
993 env(offer(bob, EUR(100), XRP(100)));
-
994 env.close();
+
964 // Reverse the order, so the offer in the books is to sell XRP
+
965 // in return for USD.
+
966 testAMM(
+
967 [&](AMM& ammAlice, Env& env) {
+
968 env(rate(gw, 1.25));
+
969 env.close();
+
970
+
971 env(offer(carol, XRP(100), USD(100)));
+
972 env.close();
+
973
+
974 BEAST_EXPECT(ammAlice.expectBalances(
+
975 XRP(10'000), USD(10'100), ammAlice.tokens()));
+
976 // Carol pays 25% transfer fee
+
977 BEAST_EXPECT(expectLine(env, carol, USD(29'875)));
+
978 BEAST_EXPECT(expectOffers(env, carol, 0));
+
979 },
+
980 {{XRP(10'100), USD(10'000)}},
+
981 0,
+
982 std::nullopt,
+
983 {features});
+
984
+
985 {
+
986 // Bridged crossing.
+
987 Env env{*this, features};
+
988 fund(
+
989 env,
+
990 gw,
+
991 {alice, bob, carol},
+
992 {USD(15'000), EUR(15'000)},
+
993 Fund::All);
+
994 env(rate(gw, 1.25));
995
-
996 // Carol makes an offer that consumes AMM liquidity and
-
997 // fully consumes Bob's offer.
-
998 env(offer(carol, USD(100), EUR(100)));
-
999 env.close();
-
1000
-
1001 // AMM doesn't pay the transfer fee
-
1002 BEAST_EXPECT(ammAlice.expectBalances(
-
1003 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
1004 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
-
1005 // Carol pays 25% transfer fee.
-
1006 BEAST_EXPECT(expectLine(env, carol, EUR(14'875)));
-
1007 BEAST_EXPECT(expectOffers(env, carol, 0));
-
1008 BEAST_EXPECT(expectOffers(env, bob, 0));
-
1009 }
+
996 // The scenario:
+
997 // o USD/XRP AMM is created.
+
998 // o EUR/XRP Offer is created.
+
999 // o carol has EUR but wants USD.
+
1000 // Note that Carol's offer must come last. If Carol's offer is
+
1001 // placed before AMM is created, then autobridging will not occur.
+
1002 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
1003 env(offer(bob, EUR(100), XRP(100)));
+
1004 env.close();
+
1005
+
1006 // Carol makes an offer that consumes AMM liquidity and
+
1007 // fully consumes Bob's offer.
+
1008 env(offer(carol, USD(100), EUR(100)));
+
1009 env.close();
1010
-
1011 {
-
1012 // Bridged crossing. The transfer fee is paid on the step not
-
1013 // involving AMM as src/dst.
-
1014 Env env{*this, features};
-
1015 fund(
-
1016 env,
-
1017 gw,
-
1018 {alice, bob, carol},
-
1019 {USD(15'000), EUR(15'000)},
-
1020 Fund::All);
-
1021 env(rate(gw, 1.25));
-
1022
-
1023 // The scenario:
-
1024 // o USD/XRP AMM is created.
-
1025 // o EUR/XRP Offer is created.
-
1026 // o carol has EUR but wants USD.
-
1027 // Note that Carol's offer must come last. If Carol's offer is
-
1028 // placed before AMM is created, then autobridging will not occur.
-
1029 AMM ammAlice(env, alice, XRP(10'000), USD(10'050));
-
1030 env(offer(bob, EUR(100), XRP(100)));
-
1031 env.close();
+
1011 // AMM doesn't pay the transfer fee
+
1012 BEAST_EXPECT(ammAlice.expectBalances(
+
1013 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
1014 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
+
1015 // Carol pays 25% transfer fee.
+
1016 BEAST_EXPECT(expectLine(env, carol, EUR(14'875)));
+
1017 BEAST_EXPECT(expectOffers(env, carol, 0));
+
1018 BEAST_EXPECT(expectOffers(env, bob, 0));
+
1019 }
+
1020
+
1021 {
+
1022 // Bridged crossing. The transfer fee is paid on the step not
+
1023 // involving AMM as src/dst.
+
1024 Env env{*this, features};
+
1025 fund(
+
1026 env,
+
1027 gw,
+
1028 {alice, bob, carol},
+
1029 {USD(15'000), EUR(15'000)},
+
1030 Fund::All);
+
1031 env(rate(gw, 1.25));
1032
-
1033 // Carol makes an offer that consumes AMM liquidity and
-
1034 // partially consumes Bob's offer.
-
1035 env(offer(carol, USD(50), EUR(50)));
-
1036 env.close();
-
1037 // This test verifies that the amount removed from an offer
-
1038 // accounts for the transfer fee that is removed from the
-
1039 // account but not from the remaining offer.
-
1040
-
1041 // AMM doesn't pay the transfer fee
-
1042 BEAST_EXPECT(ammAlice.expectBalances(
-
1043 XRP(10'050), USD(10'000), ammAlice.tokens()));
-
1044 BEAST_EXPECT(expectLine(env, carol, USD(15'050)));
-
1045 // Carol pays 25% transfer fee.
-
1046 BEAST_EXPECT(expectLine(env, carol, EUR(14'937.5)));
-
1047 BEAST_EXPECT(expectOffers(env, carol, 0));
-
1048 BEAST_EXPECT(
-
1049 expectOffers(env, bob, 1, {{Amounts{EUR(50), XRP(50)}}}));
-
1050 }
-
1051
-
1052 {
-
1053 // A trust line's QualityIn should not affect offer crossing.
-
1054 // Bridged crossing. The transfer fee is paid on the step not
-
1055 // involving AMM as src/dst.
-
1056 Env env{*this, features};
-
1057 fund(env, gw, {alice, carol, bob}, XRP(30'000));
-
1058 env(rate(gw, 1.25));
-
1059 env(trust(alice, USD(15'000)));
-
1060 env(trust(bob, EUR(15'000)));
-
1061 env(trust(carol, EUR(15'000)), qualityInPercent(80));
-
1062 env(trust(bob, USD(15'000)));
-
1063 env(trust(carol, USD(15'000)));
-
1064 env.close();
-
1065
-
1066 env(pay(gw, alice, USD(11'000)));
-
1067 env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000)));
-
1068 env.close();
-
1069 // 1000 / 0.8
-
1070 BEAST_EXPECT(expectLine(env, carol, EUR(1'250)));
-
1071 // The scenario:
-
1072 // o USD/XRP AMM is created.
-
1073 // o EUR/XRP Offer is created.
-
1074 // o carol has EUR but wants USD.
-
1075 // Note that Carol's offer must come last. If Carol's offer is
-
1076 // placed before AMM is created, then autobridging will not occur.
-
1077 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
1078 env(offer(bob, EUR(100), XRP(100)));
-
1079 env.close();
-
1080
-
1081 // Carol makes an offer that consumes AMM liquidity and
-
1082 // fully consumes Bob's offer.
-
1083 env(offer(carol, USD(100), EUR(100)));
-
1084 env.close();
-
1085
-
1086 // AMM doesn't pay the transfer fee
-
1087 BEAST_EXPECT(ammAlice.expectBalances(
-
1088 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
1089 BEAST_EXPECT(expectLine(env, carol, USD(100)));
-
1090 // Carol pays 25% transfer fee: 1250 - 100(offer) - 25(transfer fee)
-
1091 BEAST_EXPECT(expectLine(env, carol, EUR(1'125)));
-
1092 BEAST_EXPECT(expectOffers(env, carol, 0));
-
1093 BEAST_EXPECT(expectOffers(env, bob, 0));
-
1094 }
+
1033 // The scenario:
+
1034 // o USD/XRP AMM is created.
+
1035 // o EUR/XRP Offer is created.
+
1036 // o carol has EUR but wants USD.
+
1037 // Note that Carol's offer must come last. If Carol's offer is
+
1038 // placed before AMM is created, then autobridging will not occur.
+
1039 AMM ammAlice(env, alice, XRP(10'000), USD(10'050));
+
1040 env(offer(bob, EUR(100), XRP(100)));
+
1041 env.close();
+
1042
+
1043 // Carol makes an offer that consumes AMM liquidity and
+
1044 // partially consumes Bob's offer.
+
1045 env(offer(carol, USD(50), EUR(50)));
+
1046 env.close();
+
1047 // This test verifies that the amount removed from an offer
+
1048 // accounts for the transfer fee that is removed from the
+
1049 // account but not from the remaining offer.
+
1050
+
1051 // AMM doesn't pay the transfer fee
+
1052 BEAST_EXPECT(ammAlice.expectBalances(
+
1053 XRP(10'050), USD(10'000), ammAlice.tokens()));
+
1054 BEAST_EXPECT(expectLine(env, carol, USD(15'050)));
+
1055 // Carol pays 25% transfer fee.
+
1056 BEAST_EXPECT(expectLine(env, carol, EUR(14'937.5)));
+
1057 BEAST_EXPECT(expectOffers(env, carol, 0));
+
1058 BEAST_EXPECT(
+
1059 expectOffers(env, bob, 1, {{Amounts{EUR(50), XRP(50)}}}));
+
1060 }
+
1061
+
1062 {
+
1063 // A trust line's QualityIn should not affect offer crossing.
+
1064 // Bridged crossing. The transfer fee is paid on the step not
+
1065 // involving AMM as src/dst.
+
1066 Env env{*this, features};
+
1067 fund(env, gw, {alice, carol, bob}, XRP(30'000));
+
1068 env(rate(gw, 1.25));
+
1069 env(trust(alice, USD(15'000)));
+
1070 env(trust(bob, EUR(15'000)));
+
1071 env(trust(carol, EUR(15'000)), qualityInPercent(80));
+
1072 env(trust(bob, USD(15'000)));
+
1073 env(trust(carol, USD(15'000)));
+
1074 env.close();
+
1075
+
1076 env(pay(gw, alice, USD(11'000)));
+
1077 env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000)));
+
1078 env.close();
+
1079 // 1000 / 0.8
+
1080 BEAST_EXPECT(expectLine(env, carol, EUR(1'250)));
+
1081 // The scenario:
+
1082 // o USD/XRP AMM is created.
+
1083 // o EUR/XRP Offer is created.
+
1084 // o carol has EUR but wants USD.
+
1085 // Note that Carol's offer must come last. If Carol's offer is
+
1086 // placed before AMM is created, then autobridging will not occur.
+
1087 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
1088 env(offer(bob, EUR(100), XRP(100)));
+
1089 env.close();
+
1090
+
1091 // Carol makes an offer that consumes AMM liquidity and
+
1092 // fully consumes Bob's offer.
+
1093 env(offer(carol, USD(100), EUR(100)));
+
1094 env.close();
1095
-
1096 {
-
1097 // A trust line's QualityOut should not affect offer crossing.
-
1098 // Bridged crossing. The transfer fee is paid on the step not
-
1099 // involving AMM as src/dst.
-
1100 Env env{*this, features};
-
1101 fund(env, gw, {alice, carol, bob}, XRP(30'000));
-
1102 env(rate(gw, 1.25));
-
1103 env(trust(alice, USD(15'000)));
-
1104 env(trust(bob, EUR(15'000)));
-
1105 env(trust(carol, EUR(15'000)), qualityOutPercent(120));
-
1106 env(trust(bob, USD(15'000)));
-
1107 env(trust(carol, USD(15'000)));
-
1108 env.close();
-
1109
-
1110 env(pay(gw, alice, USD(11'000)));
-
1111 env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000)));
-
1112 env.close();
-
1113 BEAST_EXPECT(expectLine(env, carol, EUR(1'000)));
-
1114 // The scenario:
-
1115 // o USD/XRP AMM is created.
-
1116 // o EUR/XRP Offer is created.
-
1117 // o carol has EUR but wants USD.
-
1118 // Note that Carol's offer must come last. If Carol's offer is
-
1119 // placed before AMM is created, then autobridging will not occur.
-
1120 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
1121 env(offer(bob, EUR(100), XRP(100)));
+
1096 // AMM doesn't pay the transfer fee
+
1097 BEAST_EXPECT(ammAlice.expectBalances(
+
1098 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
1099 BEAST_EXPECT(expectLine(env, carol, USD(100)));
+
1100 // Carol pays 25% transfer fee: 1250 - 100(offer) - 25(transfer fee)
+
1101 BEAST_EXPECT(expectLine(env, carol, EUR(1'125)));
+
1102 BEAST_EXPECT(expectOffers(env, carol, 0));
+
1103 BEAST_EXPECT(expectOffers(env, bob, 0));
+
1104 }
+
1105
+
1106 {
+
1107 // A trust line's QualityOut should not affect offer crossing.
+
1108 // Bridged crossing. The transfer fee is paid on the step not
+
1109 // involving AMM as src/dst.
+
1110 Env env{*this, features};
+
1111 fund(env, gw, {alice, carol, bob}, XRP(30'000));
+
1112 env(rate(gw, 1.25));
+
1113 env(trust(alice, USD(15'000)));
+
1114 env(trust(bob, EUR(15'000)));
+
1115 env(trust(carol, EUR(15'000)), qualityOutPercent(120));
+
1116 env(trust(bob, USD(15'000)));
+
1117 env(trust(carol, USD(15'000)));
+
1118 env.close();
+
1119
+
1120 env(pay(gw, alice, USD(11'000)));
+
1121 env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000)));
1122 env.close();
-
1123
-
1124 // Carol makes an offer that consumes AMM liquidity and
-
1125 // fully consumes Bob's offer.
-
1126 env(offer(carol, USD(100), EUR(100)));
-
1127 env.close();
-
1128
-
1129 // AMM pay doesn't transfer fee
-
1130 BEAST_EXPECT(ammAlice.expectBalances(
-
1131 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
1132 BEAST_EXPECT(expectLine(env, carol, USD(100)));
-
1133 // Carol pays 25% transfer fee: 1000 - 100(offer) - 25(transfer fee)
-
1134 BEAST_EXPECT(expectLine(env, carol, EUR(875)));
-
1135 BEAST_EXPECT(expectOffers(env, carol, 0));
-
1136 BEAST_EXPECT(expectOffers(env, bob, 0));
-
1137 }
-
1138 }
-
1139
-
1140 void
-
1141 testSelfIssueOffer(FeatureBitset features)
-
1142 {
-
1143 // This test is not the same as corresponding testSelfIssueOffer()
-
1144 // in the Offer_test. It simply tests AMM with self issue and
-
1145 // offer crossing.
-
1146 using namespace jtx;
-
1147
-
1148 Env env{*this, features};
+
1123 BEAST_EXPECT(expectLine(env, carol, EUR(1'000)));
+
1124 // The scenario:
+
1125 // o USD/XRP AMM is created.
+
1126 // o EUR/XRP Offer is created.
+
1127 // o carol has EUR but wants USD.
+
1128 // Note that Carol's offer must come last. If Carol's offer is
+
1129 // placed before AMM is created, then autobridging will not occur.
+
1130 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
1131 env(offer(bob, EUR(100), XRP(100)));
+
1132 env.close();
+
1133
+
1134 // Carol makes an offer that consumes AMM liquidity and
+
1135 // fully consumes Bob's offer.
+
1136 env(offer(carol, USD(100), EUR(100)));
+
1137 env.close();
+
1138
+
1139 // AMM pay doesn't transfer fee
+
1140 BEAST_EXPECT(ammAlice.expectBalances(
+
1141 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
1142 BEAST_EXPECT(expectLine(env, carol, USD(100)));
+
1143 // Carol pays 25% transfer fee: 1000 - 100(offer) - 25(transfer fee)
+
1144 BEAST_EXPECT(expectLine(env, carol, EUR(875)));
+
1145 BEAST_EXPECT(expectOffers(env, carol, 0));
+
1146 BEAST_EXPECT(expectOffers(env, bob, 0));
+
1147 }
+
1148 }
1149
-
1150 auto const USD_bob = bob["USD"];
-
1151 auto const f = env.current()->fees().base;
-
1152
-
1153 env.fund(XRP(30'000) + f, alice, bob);
-
1154 env.close();
-
1155 AMM ammBob(env, bob, XRP(10'000), USD_bob(10'100));
-
1156
-
1157 env(offer(alice, USD_bob(100), XRP(100)));
-
1158 env.close();
+
1150 void
+
1151 testSelfIssueOffer(FeatureBitset features)
+
1152 {
+
1153 // This test is not the same as corresponding testSelfIssueOffer()
+
1154 // in the Offer_test. It simply tests AMM with self issue and
+
1155 // offer crossing.
+
1156 using namespace jtx;
+
1157
+
1158 Env env{*this, features};
1159
-
1160 BEAST_EXPECT(ammBob.expectBalances(
-
1161 XRP(10'100), USD_bob(10'000), ammBob.tokens()));
-
1162 BEAST_EXPECT(expectOffers(env, alice, 0));
-
1163 BEAST_EXPECT(expectLine(env, alice, USD_bob(100)));
-
1164 }
-
1165
-
1166 void
-
1167 testBadPathAssert(FeatureBitset features)
-
1168 {
-
1169 // At one point in the past this invalid path caused assert. It
-
1170 // should not be possible for user-supplied data to cause assert.
-
1171 // Make sure assert is gone.
-
1172 testcase("Bad path assert");
-
1173
-
1174 using namespace jtx;
+
1160 auto const USD_bob = bob["USD"];
+
1161 auto const f = env.current()->fees().base;
+
1162
+
1163 env.fund(XRP(30'000) + f, alice, bob);
+
1164 env.close();
+
1165 AMM ammBob(env, bob, XRP(10'000), USD_bob(10'100));
+
1166
+
1167 env(offer(alice, USD_bob(100), XRP(100)));
+
1168 env.close();
+
1169
+
1170 BEAST_EXPECT(ammBob.expectBalances(
+
1171 XRP(10'100), USD_bob(10'000), ammBob.tokens()));
+
1172 BEAST_EXPECT(expectOffers(env, alice, 0));
+
1173 BEAST_EXPECT(expectLine(env, alice, USD_bob(100)));
+
1174 }
1175
-
1176 // The problem was identified when featureOwnerPaysFee was enabled,
-
1177 // so make sure that gets included.
-
1178 Env env{*this, features | featureOwnerPaysFee};
-
1179
-
1180 // The fee that's charged for transactions.
-
1181 auto const fee = env.current()->fees().base;
-
1182 {
-
1183 // A trust line's QualityOut should not affect offer crossing.
-
1184 auto const ann = Account("ann");
-
1185 auto const A_BUX = ann["BUX"];
-
1186 auto const bob = Account("bob");
-
1187 auto const cam = Account("cam");
-
1188 auto const dan = Account("dan");
-
1189 auto const D_BUX = dan["BUX"];
-
1190
-
1191 // Verify trust line QualityOut affects payments.
-
1192 env.fund(reserve(env, 4) + (fee * 4), ann, bob, cam, dan);
-
1193 env.close();
-
1194
-
1195 env(trust(bob, A_BUX(400)));
-
1196 env(trust(bob, D_BUX(200)), qualityOutPercent(120));
-
1197 env(trust(cam, D_BUX(100)));
-
1198 env.close();
-
1199 env(pay(dan, bob, D_BUX(100)));
-
1200 env.close();
-
1201 BEAST_EXPECT(expectLine(env, bob, D_BUX(100)));
-
1202
-
1203 env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200)));
-
1204 env.close();
-
1205
-
1206 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
-
1207 BEAST_EXPECT(expectLine(env, ann, D_BUX(none)));
-
1208 BEAST_EXPECT(expectLine(env, bob, A_BUX(72)));
-
1209 BEAST_EXPECT(expectLine(env, bob, D_BUX(40)));
-
1210 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
-
1211 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
-
1212 BEAST_EXPECT(expectLine(env, dan, A_BUX(none)));
-
1213 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
-
1214
-
1215 AMM ammBob(env, bob, A_BUX(30), D_BUX(30));
-
1216
-
1217 env(trust(ann, D_BUX(100)));
-
1218 env.close();
-
1219
-
1220 // This payment caused the assert.
-
1221 env(pay(ann, ann, D_BUX(30)),
-
1222 path(A_BUX, D_BUX),
-
1223 sendmax(A_BUX(30)),
-
1224 ter(temBAD_PATH));
-
1225 env.close();
+
1176 void
+
1177 testBadPathAssert(FeatureBitset features)
+
1178 {
+
1179 // At one point in the past this invalid path caused assert. It
+
1180 // should not be possible for user-supplied data to cause assert.
+
1181 // Make sure assert is gone.
+
1182 testcase("Bad path assert");
+
1183
+
1184 using namespace jtx;
+
1185
+
1186 // The problem was identified when featureOwnerPaysFee was enabled,
+
1187 // so make sure that gets included.
+
1188 Env env{*this, features | featureOwnerPaysFee};
+
1189
+
1190 // The fee that's charged for transactions.
+
1191 auto const fee = env.current()->fees().base;
+
1192 {
+
1193 // A trust line's QualityOut should not affect offer crossing.
+
1194 auto const ann = Account("ann");
+
1195 auto const A_BUX = ann["BUX"];
+
1196 auto const bob = Account("bob");
+
1197 auto const cam = Account("cam");
+
1198 auto const dan = Account("dan");
+
1199 auto const D_BUX = dan["BUX"];
+
1200
+
1201 // Verify trust line QualityOut affects payments.
+
1202 env.fund(reserve(env, 4) + (fee * 4), ann, bob, cam, dan);
+
1203 env.close();
+
1204
+
1205 env(trust(bob, A_BUX(400)));
+
1206 env(trust(bob, D_BUX(200)), qualityOutPercent(120));
+
1207 env(trust(cam, D_BUX(100)));
+
1208 env.close();
+
1209 env(pay(dan, bob, D_BUX(100)));
+
1210 env.close();
+
1211 BEAST_EXPECT(expectLine(env, bob, D_BUX(100)));
+
1212
+
1213 env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200)));
+
1214 env.close();
+
1215
+
1216 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
+
1217 BEAST_EXPECT(expectLine(env, ann, D_BUX(none)));
+
1218 BEAST_EXPECT(expectLine(env, bob, A_BUX(72)));
+
1219 BEAST_EXPECT(expectLine(env, bob, D_BUX(40)));
+
1220 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
+
1221 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
+
1222 BEAST_EXPECT(expectLine(env, dan, A_BUX(none)));
+
1223 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
+
1224
+
1225 AMM ammBob(env, bob, A_BUX(30), D_BUX(30));
1226
-
1227 BEAST_EXPECT(
-
1228 ammBob.expectBalances(A_BUX(30), D_BUX(30), ammBob.tokens()));
-
1229 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
-
1230 BEAST_EXPECT(expectLine(env, ann, D_BUX(0)));
-
1231 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
-
1232 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
-
1233 BEAST_EXPECT(expectLine(env, dan, A_BUX(0)));
-
1234 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
-
1235 }
-
1236 }
-
1237
-
1238 void
-
1239 testDirectToDirectPath(FeatureBitset features)
-
1240 {
-
1241 // The offer crossing code expects that a DirectStep is always
-
1242 // preceded by a BookStep. In one instance the default path
-
1243 // was not matching that assumption. Here we recreate that case
-
1244 // so we can prove the bug stays fixed.
-
1245 testcase("Direct to Direct path");
-
1246
-
1247 using namespace jtx;
-
1248
-
1249 Env env{*this, features};
-
1250
-
1251 auto const ann = Account("ann");
-
1252 auto const bob = Account("bob");
-
1253 auto const cam = Account("cam");
-
1254 auto const carol = Account("carol");
-
1255 auto const A_BUX = ann["BUX"];
-
1256 auto const B_BUX = bob["BUX"];
-
1257
-
1258 auto const fee = env.current()->fees().base;
-
1259 env.fund(XRP(1'000), carol);
-
1260 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
-
1261 env.close();
-
1262
-
1263 env(trust(ann, B_BUX(40)));
-
1264 env(trust(cam, A_BUX(40)));
-
1265 env(trust(bob, A_BUX(30)));
-
1266 env(trust(cam, B_BUX(40)));
-
1267 env(trust(carol, B_BUX(400)));
-
1268 env(trust(carol, A_BUX(400)));
-
1269 env.close();
-
1270
-
1271 env(pay(ann, cam, A_BUX(35)));
-
1272 env(pay(bob, cam, B_BUX(35)));
-
1273 env(pay(bob, carol, B_BUX(400)));
-
1274 env(pay(ann, carol, A_BUX(400)));
-
1275
-
1276 AMM ammCarol(env, carol, A_BUX(300), B_BUX(330));
-
1277
-
1278 // cam puts an offer on the books that her upcoming offer could cross.
-
1279 // But this offer should be deleted, not crossed, by her upcoming
-
1280 // offer.
-
1281 env(offer(cam, A_BUX(29), B_BUX(30), tfPassive));
-
1282 env.close();
-
1283 env.require(balance(cam, A_BUX(35)));
-
1284 env.require(balance(cam, B_BUX(35)));
-
1285 env.require(offers(cam, 1));
-
1286
-
1287 // This offer caused the assert.
-
1288 env(offer(cam, B_BUX(30), A_BUX(30)));
-
1289
-
1290 // AMM is consumed up to the first cam Offer quality
-
1291 if (!features[fixAMMv1_1])
-
1292 {
-
1293 BEAST_EXPECT(ammCarol.expectBalances(
-
1294 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
-
1295 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
-
1296 ammCarol.tokens()));
-
1297 BEAST_EXPECT(expectOffers(
-
1298 env,
-
1299 cam,
-
1300 1,
-
1301 {{Amounts{
-
1302 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
-
1303 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
-
1304 }
-
1305 else
-
1306 {
-
1307 BEAST_EXPECT(ammCarol.expectBalances(
-
1308 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
-
1309 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
-
1310 ammCarol.tokens()));
-
1311 BEAST_EXPECT(expectOffers(
-
1312 env,
-
1313 cam,
-
1314 1,
-
1315 {{Amounts{
-
1316 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
-
1317 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
-
1318 }
-
1319 }
-
1320
-
1321 void
-
1322 testRequireAuth(FeatureBitset features)
-
1323 {
-
1324 testcase("lsfRequireAuth");
-
1325
-
1326 using namespace jtx;
-
1327
-
1328 Env env{*this, features};
-
1329
-
1330 auto const aliceUSD = alice["USD"];
-
1331 auto const bobUSD = bob["USD"];
-
1332
-
1333 env.fund(XRP(400'000), gw, alice, bob);
-
1334 env.close();
+
1227 env(trust(ann, D_BUX(100)));
+
1228 env.close();
+
1229
+
1230 // This payment caused the assert.
+
1231 env(pay(ann, ann, D_BUX(30)),
+
1232 path(A_BUX, D_BUX),
+
1233 sendmax(A_BUX(30)),
+
1234 ter(temBAD_PATH));
+
1235 env.close();
+
1236
+
1237 BEAST_EXPECT(
+
1238 ammBob.expectBalances(A_BUX(30), D_BUX(30), ammBob.tokens()));
+
1239 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
+
1240 BEAST_EXPECT(expectLine(env, ann, D_BUX(0)));
+
1241 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
+
1242 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
+
1243 BEAST_EXPECT(expectLine(env, dan, A_BUX(0)));
+
1244 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
+
1245 }
+
1246 }
+
1247
+
1248 void
+
1249 testDirectToDirectPath(FeatureBitset features)
+
1250 {
+
1251 // The offer crossing code expects that a DirectStep is always
+
1252 // preceded by a BookStep. In one instance the default path
+
1253 // was not matching that assumption. Here we recreate that case
+
1254 // so we can prove the bug stays fixed.
+
1255 testcase("Direct to Direct path");
+
1256
+
1257 using namespace jtx;
+
1258
+
1259 Env env{*this, features};
+
1260
+
1261 auto const ann = Account("ann");
+
1262 auto const bob = Account("bob");
+
1263 auto const cam = Account("cam");
+
1264 auto const carol = Account("carol");
+
1265 auto const A_BUX = ann["BUX"];
+
1266 auto const B_BUX = bob["BUX"];
+
1267
+
1268 auto const fee = env.current()->fees().base;
+
1269 env.fund(XRP(1'000), carol);
+
1270 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
+
1271 env.close();
+
1272
+
1273 env(trust(ann, B_BUX(40)));
+
1274 env(trust(cam, A_BUX(40)));
+
1275 env(trust(bob, A_BUX(30)));
+
1276 env(trust(cam, B_BUX(40)));
+
1277 env(trust(carol, B_BUX(400)));
+
1278 env(trust(carol, A_BUX(400)));
+
1279 env.close();
+
1280
+
1281 env(pay(ann, cam, A_BUX(35)));
+
1282 env(pay(bob, cam, B_BUX(35)));
+
1283 env(pay(bob, carol, B_BUX(400)));
+
1284 env(pay(ann, carol, A_BUX(400)));
+
1285
+
1286 AMM ammCarol(env, carol, A_BUX(300), B_BUX(330));
+
1287
+
1288 // cam puts an offer on the books that her upcoming offer could cross.
+
1289 // But this offer should be deleted, not crossed, by her upcoming
+
1290 // offer.
+
1291 env(offer(cam, A_BUX(29), B_BUX(30), tfPassive));
+
1292 env.close();
+
1293 env.require(balance(cam, A_BUX(35)));
+
1294 env.require(balance(cam, B_BUX(35)));
+
1295 env.require(offers(cam, 1));
+
1296
+
1297 // This offer caused the assert.
+
1298 env(offer(cam, B_BUX(30), A_BUX(30)));
+
1299
+
1300 // AMM is consumed up to the first cam Offer quality
+
1301 if (!features[fixAMMv1_1])
+
1302 {
+
1303 BEAST_EXPECT(ammCarol.expectBalances(
+
1304 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
+
1305 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
+
1306 ammCarol.tokens()));
+
1307 BEAST_EXPECT(expectOffers(
+
1308 env,
+
1309 cam,
+
1310 1,
+
1311 {{Amounts{
+
1312 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
+
1313 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
+
1314 }
+
1315 else
+
1316 {
+
1317 BEAST_EXPECT(ammCarol.expectBalances(
+
1318 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
+
1319 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
+
1320 ammCarol.tokens()));
+
1321 BEAST_EXPECT(expectOffers(
+
1322 env,
+
1323 cam,
+
1324 1,
+
1325 {{Amounts{
+
1326 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
+
1327 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
+
1328 }
+
1329 }
+
1330
+
1331 void
+
1332 testRequireAuth(FeatureBitset features)
+
1333 {
+
1334 testcase("lsfRequireAuth");
1335
-
1336 // GW requires authorization for holders of its IOUs
-
1337 env(fset(gw, asfRequireAuth));
-
1338 env.close();
+
1336 using namespace jtx;
+
1337
+
1338 Env env{*this, features};
1339
-
1340 // Properly set trust and have gw authorize bob and alice
-
1341 env(trust(gw, bobUSD(100)), txflags(tfSetfAuth));
-
1342 env(trust(bob, USD(100)));
-
1343 env(trust(gw, aliceUSD(100)), txflags(tfSetfAuth));
-
1344 env(trust(alice, USD(2'000)));
-
1345 env(pay(gw, alice, USD(1'000)));
-
1346 env.close();
-
1347 // Alice is able to create AMM since the GW has authorized her
-
1348 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
+
1340 auto const aliceUSD = alice["USD"];
+
1341 auto const bobUSD = bob["USD"];
+
1342
+
1343 env.fund(XRP(400'000), gw, alice, bob);
+
1344 env.close();
+
1345
+
1346 // GW requires authorization for holders of its IOUs
+
1347 env(fset(gw, asfRequireAuth));
+
1348 env.close();
1349
-
1350 // Set up authorized trust line for AMM.
-
1351 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
-
1352 txflags(tfSetfAuth));
-
1353 env.close();
-
1354
-
1355 env(pay(gw, bob, USD(50)));
+
1350 // Properly set trust and have gw authorize bob and alice
+
1351 env(trust(gw, bobUSD(100)), txflags(tfSetfAuth));
+
1352 env(trust(bob, USD(100)));
+
1353 env(trust(gw, aliceUSD(100)), txflags(tfSetfAuth));
+
1354 env(trust(alice, USD(2'000)));
+
1355 env(pay(gw, alice, USD(1'000)));
1356 env.close();
-
1357
-
1358 BEAST_EXPECT(expectLine(env, bob, USD(50)));
+
1357 // Alice is able to create AMM since the GW has authorized her
+
1358 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
1359
-
1360 // Bob's offer should cross Alice's AMM
-
1361 env(offer(bob, XRP(50), USD(50)));
-
1362 env.close();
-
1363
-
1364 BEAST_EXPECT(
-
1365 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
-
1366 BEAST_EXPECT(expectOffers(env, bob, 0));
-
1367 BEAST_EXPECT(expectLine(env, bob, USD(0)));
-
1368 }
+
1360 // Set up authorized trust line for AMM.
+
1361 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
+
1362 txflags(tfSetfAuth));
+
1363 env.close();
+
1364
+
1365 env(pay(gw, bob, USD(50)));
+
1366 env.close();
+
1367
+
1368 BEAST_EXPECT(expectLine(env, bob, USD(50)));
1369
-
1370 void
-
1371 testMissingAuth(FeatureBitset features)
-
1372 {
-
1373 testcase("Missing Auth");
-
1374
-
1375 using namespace jtx;
-
1376
-
1377 Env env{*this, features};
-
1378
-
1379 env.fund(XRP(400'000), gw, alice, bob);
-
1380 env.close();
-
1381
-
1382 // Alice doesn't have the funds
-
1383 {
-
1384 AMM ammAlice(
-
1385 env, alice, USD(1'000), XRP(1'000), ter(tecUNFUNDED_AMM));
-
1386 }
-
1387
-
1388 env(fset(gw, asfRequireAuth));
-
1389 env.close();
-
1390
-
1391 env(trust(gw, bob["USD"](50)), txflags(tfSetfAuth));
-
1392 env.close();
-
1393 env(trust(bob, USD(50)));
-
1394 env.close();
-
1395
-
1396 env(pay(gw, bob, USD(50)));
-
1397 env.close();
-
1398 BEAST_EXPECT(expectLine(env, bob, USD(50)));
-
1399
-
1400 // Alice should not be able to create AMM without authorization.
-
1401 {
-
1402 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_LINE));
-
1403 }
-
1404
-
1405 // Set up a trust line for Alice, but don't authorize it. Alice
-
1406 // should still not be able to create AMM for USD/gw.
-
1407 env(trust(gw, alice["USD"](2'000)));
-
1408 env.close();
+
1370 // Bob's offer should cross Alice's AMM
+
1371 env(offer(bob, XRP(50), USD(50)));
+
1372 env.close();
+
1373
+
1374 BEAST_EXPECT(
+
1375 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
+
1376 BEAST_EXPECT(expectOffers(env, bob, 0));
+
1377 BEAST_EXPECT(expectLine(env, bob, USD(0)));
+
1378 }
+
1379
+
1380 void
+
1381 testMissingAuth(FeatureBitset features)
+
1382 {
+
1383 testcase("Missing Auth");
+
1384
+
1385 using namespace jtx;
+
1386
+
1387 Env env{*this, features};
+
1388
+
1389 env.fund(XRP(400'000), gw, alice, bob);
+
1390 env.close();
+
1391
+
1392 // Alice doesn't have the funds
+
1393 {
+
1394 AMM ammAlice(
+
1395 env, alice, USD(1'000), XRP(1'000), ter(tecUNFUNDED_AMM));
+
1396 }
+
1397
+
1398 env(fset(gw, asfRequireAuth));
+
1399 env.close();
+
1400
+
1401 env(trust(gw, bob["USD"](50)), txflags(tfSetfAuth));
+
1402 env.close();
+
1403 env(trust(bob, USD(50)));
+
1404 env.close();
+
1405
+
1406 env(pay(gw, bob, USD(50)));
+
1407 env.close();
+
1408 BEAST_EXPECT(expectLine(env, bob, USD(50)));
1409
-
1410 {
-
1411 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_AUTH));
-
1412 }
-
1413
-
1414 // Finally, set up an authorized trust line for Alice. Now Alice's
-
1415 // AMM create should succeed.
-
1416 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
-
1417 env(trust(alice, USD(2'000)));
-
1418 env(pay(gw, alice, USD(1'000)));
-
1419 env.close();
-
1420
-
1421 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
-
1422
-
1423 // Set up authorized trust line for AMM.
-
1424 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
-
1425 txflags(tfSetfAuth));
-
1426 env.close();
-
1427
-
1428 // Now bob creates his offer again, which crosses with alice's AMM.
-
1429 env(offer(bob, XRP(50), USD(50)));
-
1430 env.close();
-
1431
-
1432 BEAST_EXPECT(
-
1433 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
-
1434 BEAST_EXPECT(expectOffers(env, bob, 0));
-
1435 BEAST_EXPECT(expectLine(env, bob, USD(0)));
-
1436 }
+
1410 // Alice should not be able to create AMM without authorization.
+
1411 {
+
1412 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_LINE));
+
1413 }
+
1414
+
1415 // Set up a trust line for Alice, but don't authorize it. Alice
+
1416 // should still not be able to create AMM for USD/gw.
+
1417 env(trust(gw, alice["USD"](2'000)));
+
1418 env.close();
+
1419
+
1420 {
+
1421 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_AUTH));
+
1422 }
+
1423
+
1424 // Finally, set up an authorized trust line for Alice. Now Alice's
+
1425 // AMM create should succeed.
+
1426 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
+
1427 env(trust(alice, USD(2'000)));
+
1428 env(pay(gw, alice, USD(1'000)));
+
1429 env.close();
+
1430
+
1431 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
+
1432
+
1433 // Set up authorized trust line for AMM.
+
1434 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
+
1435 txflags(tfSetfAuth));
+
1436 env.close();
1437
-
1438 void
-
1439 testOffers()
-
1440 {
-
1441 using namespace jtx;
-
1442 FeatureBitset const all{supported_amendments()};
-
1443 testRmFundedOffer(all);
-
1444 testRmFundedOffer(all - fixAMMv1_1);
-
1445 testEnforceNoRipple(all);
-
1446 testFillModes(all);
-
1447 testOfferCrossWithXRP(all);
-
1448 testOfferCrossWithLimitOverride(all);
-
1449 testCurrencyConversionEntire(all);
-
1450 testCurrencyConversionInParts(all);
-
1451 testCrossCurrencyStartXRP(all);
-
1452 testCrossCurrencyEndXRP(all);
-
1453 testCrossCurrencyBridged(all);
-
1454 testOfferFeesConsumeFunds(all);
-
1455 testOfferCreateThenCross(all);
-
1456 testSellFlagExceedLimit(all);
-
1457 testGatewayCrossCurrency(all);
-
1458 testGatewayCrossCurrency(all - fixAMMv1_1);
-
1459 testBridgedCross(all);
-
1460 testSellWithFillOrKill(all);
-
1461 testTransferRateOffer(all);
-
1462 testSelfIssueOffer(all);
-
1463 testBadPathAssert(all);
-
1464 testSellFlagBasic(all);
-
1465 testDirectToDirectPath(all);
-
1466 testDirectToDirectPath(all - fixAMMv1_1);
-
1467 testRequireAuth(all);
-
1468 testMissingAuth(all);
-
1469 }
-
1470
-
1471 void
-
1472 path_find_consume_all()
-
1473 {
-
1474 testcase("path find consume all");
-
1475 using namespace jtx;
-
1476
-
1477 Env env = pathTestEnv();
-
1478 env.fund(XRP(100'000'250), alice);
-
1479 fund(env, gw, {carol, bob}, {USD(100)}, Fund::All);
-
1480 fund(env, gw, {alice}, {USD(100)}, Fund::IOUOnly);
-
1481 AMM ammCarol(env, carol, XRP(100), USD(100));
-
1482
-
1483 STPathSet st;
-
1484 STAmount sa;
-
1485 STAmount da;
-
1486 std::tie(st, sa, da) = find_paths(
-
1487 env,
-
1488 alice,
-
1489 bob,
-
1490 bob["AUD"](-1),
-
1491 std::optional<STAmount>(XRP(100'000'000)));
-
1492 BEAST_EXPECT(st.empty());
-
1493 std::tie(st, sa, da) = find_paths(
-
1494 env,
-
1495 alice,
-
1496 bob,
-
1497 bob["USD"](-1),
-
1498 std::optional<STAmount>(XRP(100'000'000)));
-
1499 // Alice sends all requested 100,000,000XRP
-
1500 BEAST_EXPECT(sa == XRP(100'000'000));
-
1501 // Bob gets ~99.99USD. This is the amount Bob
-
1502 // can get out of AMM for 100,000,000XRP.
-
1503 BEAST_EXPECT(equal(
-
1504 da, STAmount{bob["USD"].issue(), UINT64_C(99'9999000001), -10}));
-
1505 }
-
1506
-
1507 // carol holds gateway AUD, sells gateway AUD for XRP
-
1508 // bob will hold gateway AUD
-
1509 // alice pays bob gateway AUD using XRP
-
1510 void
-
1511 via_offers_via_gateway()
-
1512 {
-
1513 testcase("via gateway");
-
1514 using namespace jtx;
-
1515
-
1516 Env env = pathTestEnv();
-
1517 auto const AUD = gw["AUD"];
-
1518 env.fund(XRP(10'000), alice, bob, carol, gw);
-
1519 env(rate(gw, 1.1));
-
1520 env.trust(AUD(2'000), bob, carol);
-
1521 env(pay(gw, carol, AUD(51)));
-
1522 env.close();
-
1523 AMM ammCarol(env, carol, XRP(40), AUD(51));
-
1524 env(pay(alice, bob, AUD(10)), sendmax(XRP(100)), paths(XRP));
-
1525 env.close();
-
1526 // AMM offer is 51.282052XRP/11AUD, 11AUD/1.1 = 10AUD to bob
-
1527 BEAST_EXPECT(
-
1528 ammCarol.expectBalances(XRP(51), AUD(40), ammCarol.tokens()));
-
1529 BEAST_EXPECT(expectLine(env, bob, AUD(10)));
-
1530
-
1531 auto const result =
-
1532 find_paths(env, alice, bob, Account(bob)["USD"](25));
-
1533 BEAST_EXPECT(std::get<0>(result).empty());
-
1534 }
-
1535
-
1536 void
-
1537 receive_max()
-
1538 {
-
1539 testcase("Receive max");
-
1540 using namespace jtx;
-
1541 auto const charlie = Account("charlie");
-
1542 {
-
1543 // XRP -> IOU receive max
-
1544 Env env = pathTestEnv();
-
1545 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
-
1546 AMM ammCharlie(env, charlie, XRP(10), USD(11));
-
1547 auto [st, sa, da] =
-
1548 find_paths(env, alice, bob, USD(-1), XRP(1).value());
-
1549 BEAST_EXPECT(sa == XRP(1));
-
1550 BEAST_EXPECT(equal(da, USD(1)));
-
1551 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
-
1552 {
-
1553 auto const& pathElem = st[0][0];
-
1554 BEAST_EXPECT(
-
1555 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
-
1556 pathElem.getCurrency() == USD.currency);
-
1557 }
-
1558 }
-
1559 {
-
1560 // IOU -> XRP receive max
-
1561 Env env = pathTestEnv();
-
1562 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
-
1563 AMM ammCharlie(env, charlie, XRP(11), USD(10));
-
1564 env.close();
-
1565 auto [st, sa, da] =
-
1566 find_paths(env, alice, bob, drops(-1), USD(1).value());
-
1567 BEAST_EXPECT(sa == USD(1));
-
1568 BEAST_EXPECT(equal(da, XRP(1)));
-
1569 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
-
1570 {
-
1571 auto const& pathElem = st[0][0];
-
1572 BEAST_EXPECT(
-
1573 pathElem.isOffer() &&
-
1574 pathElem.getIssuerID() == xrpAccount() &&
-
1575 pathElem.getCurrency() == xrpCurrency());
-
1576 }
-
1577 }
-
1578 }
-
1579
-
1580 void
-
1581 path_find_01()
-
1582 {
-
1583 testcase("Path Find: XRP -> XRP and XRP -> IOU");
-
1584 using namespace jtx;
-
1585 Env env = pathTestEnv();
-
1586 Account A1{"A1"};
-
1587 Account A2{"A2"};
-
1588 Account A3{"A3"};
-
1589 Account G1{"G1"};
-
1590 Account G2{"G2"};
-
1591 Account G3{"G3"};
-
1592 Account M1{"M1"};
-
1593
-
1594 env.fund(XRP(100'000), A1);
-
1595 env.fund(XRP(10'000), A2);
-
1596 env.fund(XRP(1'000), A3, G1, G2, G3);
-
1597 env.fund(XRP(20'000), M1);
-
1598 env.close();
-
1599
-
1600 env.trust(G1["XYZ"](5'000), A1);
-
1601 env.trust(G3["ABC"](5'000), A1);
-
1602 env.trust(G2["XYZ"](5'000), A2);
-
1603 env.trust(G3["ABC"](5'000), A2);
-
1604 env.trust(A2["ABC"](1'000), A3);
-
1605 env.trust(G1["XYZ"](100'000), M1);
-
1606 env.trust(G2["XYZ"](100'000), M1);
-
1607 env.trust(G3["ABC"](100'000), M1);
-
1608 env.close();
-
1609
-
1610 env(pay(G1, A1, G1["XYZ"](3'500)));
-
1611 env(pay(G3, A1, G3["ABC"](1'200)));
-
1612 env(pay(G1, M1, G1["XYZ"](25'000)));
-
1613 env(pay(G2, M1, G2["XYZ"](25'000)));
-
1614 env(pay(G3, M1, G3["ABC"](25'000)));
-
1615 env.close();
-
1616
-
1617 AMM ammM1_G1_G2(env, M1, G1["XYZ"](1'000), G2["XYZ"](1'000));
-
1618 AMM ammM1_XRP_G3(env, M1, XRP(10'000), G3["ABC"](1'000));
-
1619
-
1620 STPathSet st;
-
1621 STAmount sa, da;
-
1622
-
1623 {
-
1624 auto const& send_amt = XRP(10);
-
1625 std::tie(st, sa, da) =
-
1626 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
-
1627 BEAST_EXPECT(equal(da, send_amt));
-
1628 BEAST_EXPECT(st.empty());
-
1629 }
+
1438 // Now bob creates his offer again, which crosses with alice's AMM.
+
1439 env(offer(bob, XRP(50), USD(50)));
+
1440 env.close();
+
1441
+
1442 BEAST_EXPECT(
+
1443 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
+
1444 BEAST_EXPECT(expectOffers(env, bob, 0));
+
1445 BEAST_EXPECT(expectLine(env, bob, USD(0)));
+
1446 }
+
1447
+
1448 void
+
1449 testOffers()
+
1450 {
+
1451 using namespace jtx;
+
1452 FeatureBitset const all{supported_amendments()};
+
1453 testRmFundedOffer(all);
+
1454 testRmFundedOffer(all - fixAMMv1_1);
+
1455 testEnforceNoRipple(all);
+
1456 testFillModes(all);
+
1457 testOfferCrossWithXRP(all);
+
1458 testOfferCrossWithLimitOverride(all);
+
1459 testCurrencyConversionEntire(all);
+
1460 testCurrencyConversionInParts(all);
+
1461 testCrossCurrencyStartXRP(all);
+
1462 testCrossCurrencyEndXRP(all);
+
1463 testCrossCurrencyBridged(all);
+
1464 testOfferFeesConsumeFunds(all);
+
1465 testOfferCreateThenCross(all);
+
1466 testSellFlagExceedLimit(all);
+
1467 testGatewayCrossCurrency(all);
+
1468 testGatewayCrossCurrency(all - fixAMMv1_1);
+
1469 testBridgedCross(all);
+
1470 testSellWithFillOrKill(all);
+
1471 testTransferRateOffer(all);
+
1472 testSelfIssueOffer(all);
+
1473 testBadPathAssert(all);
+
1474 testSellFlagBasic(all);
+
1475 testDirectToDirectPath(all);
+
1476 testDirectToDirectPath(all - fixAMMv1_1);
+
1477 testRequireAuth(all);
+
1478 testMissingAuth(all);
+
1479 }
+
1480
+
1481 void
+
1482 path_find_consume_all()
+
1483 {
+
1484 testcase("path find consume all");
+
1485 using namespace jtx;
+
1486
+
1487 Env env = pathTestEnv();
+
1488 env.fund(XRP(100'000'250), alice);
+
1489 fund(env, gw, {carol, bob}, {USD(100)}, Fund::All);
+
1490 fund(env, gw, {alice}, {USD(100)}, Fund::IOUOnly);
+
1491 AMM ammCarol(env, carol, XRP(100), USD(100));
+
1492
+
1493 STPathSet st;
+
1494 STAmount sa;
+
1495 STAmount da;
+
1496 std::tie(st, sa, da) = find_paths(
+
1497 env,
+
1498 alice,
+
1499 bob,
+
1500 bob["AUD"](-1),
+
1501 std::optional<STAmount>(XRP(100'000'000)));
+
1502 BEAST_EXPECT(st.empty());
+
1503 std::tie(st, sa, da) = find_paths(
+
1504 env,
+
1505 alice,
+
1506 bob,
+
1507 bob["USD"](-1),
+
1508 std::optional<STAmount>(XRP(100'000'000)));
+
1509 // Alice sends all requested 100,000,000XRP
+
1510 BEAST_EXPECT(sa == XRP(100'000'000));
+
1511 // Bob gets ~99.99USD. This is the amount Bob
+
1512 // can get out of AMM for 100,000,000XRP.
+
1513 BEAST_EXPECT(equal(
+
1514 da, STAmount{bob["USD"].issue(), UINT64_C(99'9999000001), -10}));
+
1515 }
+
1516
+
1517 // carol holds gateway AUD, sells gateway AUD for XRP
+
1518 // bob will hold gateway AUD
+
1519 // alice pays bob gateway AUD using XRP
+
1520 void
+
1521 via_offers_via_gateway()
+
1522 {
+
1523 testcase("via gateway");
+
1524 using namespace jtx;
+
1525
+
1526 Env env = pathTestEnv();
+
1527 auto const AUD = gw["AUD"];
+
1528 env.fund(XRP(10'000), alice, bob, carol, gw);
+
1529 env.close();
+
1530 env(rate(gw, 1.1));
+
1531 env.trust(AUD(2'000), bob, carol);
+
1532 env(pay(gw, carol, AUD(51)));
+
1533 env.close();
+
1534 AMM ammCarol(env, carol, XRP(40), AUD(51));
+
1535 env(pay(alice, bob, AUD(10)), sendmax(XRP(100)), paths(XRP));
+
1536 env.close();
+
1537 // AMM offer is 51.282052XRP/11AUD, 11AUD/1.1 = 10AUD to bob
+
1538 BEAST_EXPECT(
+
1539 ammCarol.expectBalances(XRP(51), AUD(40), ammCarol.tokens()));
+
1540 BEAST_EXPECT(expectLine(env, bob, AUD(10)));
+
1541
+
1542 auto const result =
+
1543 find_paths(env, alice, bob, Account(bob)["USD"](25));
+
1544 BEAST_EXPECT(std::get<0>(result).empty());
+
1545 }
+
1546
+
1547 void
+
1548 receive_max()
+
1549 {
+
1550 testcase("Receive max");
+
1551 using namespace jtx;
+
1552 auto const charlie = Account("charlie");
+
1553 {
+
1554 // XRP -> IOU receive max
+
1555 Env env = pathTestEnv();
+
1556 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
+
1557 AMM ammCharlie(env, charlie, XRP(10), USD(11));
+
1558 auto [st, sa, da] =
+
1559 find_paths(env, alice, bob, USD(-1), XRP(1).value());
+
1560 BEAST_EXPECT(sa == XRP(1));
+
1561 BEAST_EXPECT(equal(da, USD(1)));
+
1562 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
+
1563 {
+
1564 auto const& pathElem = st[0][0];
+
1565 BEAST_EXPECT(
+
1566 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
+
1567 pathElem.getCurrency() == USD.currency);
+
1568 }
+
1569 }
+
1570 {
+
1571 // IOU -> XRP receive max
+
1572 Env env = pathTestEnv();
+
1573 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
+
1574 AMM ammCharlie(env, charlie, XRP(11), USD(10));
+
1575 env.close();
+
1576 auto [st, sa, da] =
+
1577 find_paths(env, alice, bob, drops(-1), USD(1).value());
+
1578 BEAST_EXPECT(sa == USD(1));
+
1579 BEAST_EXPECT(equal(da, XRP(1)));
+
1580 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
+
1581 {
+
1582 auto const& pathElem = st[0][0];
+
1583 BEAST_EXPECT(
+
1584 pathElem.isOffer() &&
+
1585 pathElem.getIssuerID() == xrpAccount() &&
+
1586 pathElem.getCurrency() == xrpCurrency());
+
1587 }
+
1588 }
+
1589 }
+
1590
+
1591 void
+
1592 path_find_01()
+
1593 {
+
1594 testcase("Path Find: XRP -> XRP and XRP -> IOU");
+
1595 using namespace jtx;
+
1596 Env env = pathTestEnv();
+
1597 Account A1{"A1"};
+
1598 Account A2{"A2"};
+
1599 Account A3{"A3"};
+
1600 Account G1{"G1"};
+
1601 Account G2{"G2"};
+
1602 Account G3{"G3"};
+
1603 Account M1{"M1"};
+
1604
+
1605 env.fund(XRP(100'000), A1);
+
1606 env.fund(XRP(10'000), A2);
+
1607 env.fund(XRP(1'000), A3, G1, G2, G3);
+
1608 env.fund(XRP(20'000), M1);
+
1609 env.close();
+
1610
+
1611 env.trust(G1["XYZ"](5'000), A1);
+
1612 env.trust(G3["ABC"](5'000), A1);
+
1613 env.trust(G2["XYZ"](5'000), A2);
+
1614 env.trust(G3["ABC"](5'000), A2);
+
1615 env.trust(A2["ABC"](1'000), A3);
+
1616 env.trust(G1["XYZ"](100'000), M1);
+
1617 env.trust(G2["XYZ"](100'000), M1);
+
1618 env.trust(G3["ABC"](100'000), M1);
+
1619 env.close();
+
1620
+
1621 env(pay(G1, A1, G1["XYZ"](3'500)));
+
1622 env(pay(G3, A1, G3["ABC"](1'200)));
+
1623 env(pay(G1, M1, G1["XYZ"](25'000)));
+
1624 env(pay(G2, M1, G2["XYZ"](25'000)));
+
1625 env(pay(G3, M1, G3["ABC"](25'000)));
+
1626 env.close();
+
1627
+
1628 AMM ammM1_G1_G2(env, M1, G1["XYZ"](1'000), G2["XYZ"](1'000));
+
1629 AMM ammM1_XRP_G3(env, M1, XRP(10'000), G3["ABC"](1'000));
1630
-
1631 {
-
1632 // no path should exist for this since dest account
-
1633 // does not exist.
-
1634 auto const& send_amt = XRP(200);
-
1635 std::tie(st, sa, da) = find_paths(
-
1636 env, A1, Account{"A0"}, send_amt, std::nullopt, xrpCurrency());
-
1637 BEAST_EXPECT(equal(da, send_amt));
-
1638 BEAST_EXPECT(st.empty());
-
1639 }
-
1640
-
1641 {
-
1642 auto const& send_amt = G3["ABC"](10);
-
1643 std::tie(st, sa, da) =
-
1644 find_paths(env, A2, G3, send_amt, std::nullopt, xrpCurrency());
-
1645 BEAST_EXPECT(equal(da, send_amt));
-
1646 BEAST_EXPECT(equal(sa, XRPAmount{101'010'102}));
-
1647 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]))));
-
1648 }
-
1649
-
1650 {
-
1651 auto const& send_amt = A2["ABC"](1);
-
1652 std::tie(st, sa, da) =
-
1653 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
-
1654 BEAST_EXPECT(equal(da, send_amt));
-
1655 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
-
1656 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3)));
-
1657 }
-
1658
-
1659 {
-
1660 auto const& send_amt = A3["ABC"](1);
-
1661 std::tie(st, sa, da) =
-
1662 find_paths(env, A1, A3, send_amt, std::nullopt, xrpCurrency());
-
1663 BEAST_EXPECT(equal(da, send_amt));
-
1664 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
-
1665 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3, A2)));
-
1666 }
-
1667 }
-
1668
-
1669 void
-
1670 path_find_02()
-
1671 {
-
1672 testcase("Path Find: non-XRP -> XRP");
-
1673 using namespace jtx;
-
1674 Env env = pathTestEnv();
-
1675 Account A1{"A1"};
-
1676 Account A2{"A2"};
-
1677 Account G3{"G3"};
-
1678 Account M1{"M1"};
+
1631 STPathSet st;
+
1632 STAmount sa, da;
+
1633
+
1634 {
+
1635 auto const& send_amt = XRP(10);
+
1636 std::tie(st, sa, da) =
+
1637 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
+
1638 BEAST_EXPECT(equal(da, send_amt));
+
1639 BEAST_EXPECT(st.empty());
+
1640 }
+
1641
+
1642 {
+
1643 // no path should exist for this since dest account
+
1644 // does not exist.
+
1645 auto const& send_amt = XRP(200);
+
1646 std::tie(st, sa, da) = find_paths(
+
1647 env, A1, Account{"A0"}, send_amt, std::nullopt, xrpCurrency());
+
1648 BEAST_EXPECT(equal(da, send_amt));
+
1649 BEAST_EXPECT(st.empty());
+
1650 }
+
1651
+
1652 {
+
1653 auto const& send_amt = G3["ABC"](10);
+
1654 std::tie(st, sa, da) =
+
1655 find_paths(env, A2, G3, send_amt, std::nullopt, xrpCurrency());
+
1656 BEAST_EXPECT(equal(da, send_amt));
+
1657 BEAST_EXPECT(equal(sa, XRPAmount{101'010'102}));
+
1658 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]))));
+
1659 }
+
1660
+
1661 {
+
1662 auto const& send_amt = A2["ABC"](1);
+
1663 std::tie(st, sa, da) =
+
1664 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
+
1665 BEAST_EXPECT(equal(da, send_amt));
+
1666 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
+
1667 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3)));
+
1668 }
+
1669
+
1670 {
+
1671 auto const& send_amt = A3["ABC"](1);
+
1672 std::tie(st, sa, da) =
+
1673 find_paths(env, A1, A3, send_amt, std::nullopt, xrpCurrency());
+
1674 BEAST_EXPECT(equal(da, send_amt));
+
1675 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
+
1676 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3, A2)));
+
1677 }
+
1678 }
1679
-
1680 env.fund(XRP(1'000), A1, A2, G3);
-
1681 env.fund(XRP(11'000), M1);
-
1682 env.close();
-
1683
-
1684 env.trust(G3["ABC"](1'000), A1, A2);
-
1685 env.trust(G3["ABC"](100'000), M1);
-
1686 env.close();
-
1687
-
1688 env(pay(G3, A1, G3["ABC"](1'000)));
-
1689 env(pay(G3, A2, G3["ABC"](1'000)));
-
1690 env(pay(G3, M1, G3["ABC"](1'200)));
-
1691 env.close();
-
1692
-
1693 AMM ammM1(env, M1, G3["ABC"](1'000), XRP(10'010));
+
1680 void
+
1681 path_find_02()
+
1682 {
+
1683 testcase("Path Find: non-XRP -> XRP");
+
1684 using namespace jtx;
+
1685 Env env = pathTestEnv();
+
1686 Account A1{"A1"};
+
1687 Account A2{"A2"};
+
1688 Account G3{"G3"};
+
1689 Account M1{"M1"};
+
1690
+
1691 env.fund(XRP(1'000), A1, A2, G3);
+
1692 env.fund(XRP(11'000), M1);
+
1693 env.close();
1694
-
1695 STPathSet st;
-
1696 STAmount sa, da;
-
1697
-
1698 auto const& send_amt = XRP(10);
-
1699 std::tie(st, sa, da) =
-
1700 find_paths(env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
-
1701 BEAST_EXPECT(equal(da, send_amt));
-
1702 BEAST_EXPECT(equal(sa, A1["ABC"](1)));
-
1703 BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
-
1704 }
+
1695 env.trust(G3["ABC"](1'000), A1, A2);
+
1696 env.trust(G3["ABC"](100'000), M1);
+
1697 env.close();
+
1698
+
1699 env(pay(G3, A1, G3["ABC"](1'000)));
+
1700 env(pay(G3, A2, G3["ABC"](1'000)));
+
1701 env(pay(G3, M1, G3["ABC"](1'200)));
+
1702 env.close();
+
1703
+
1704 AMM ammM1(env, M1, G3["ABC"](1'000), XRP(10'010));
1705
-
1706 void
-
1707 path_find_05()
-
1708 {
-
1709 testcase("Path Find: non-XRP -> non-XRP, same currency");
-
1710 using namespace jtx;
-
1711 Env env = pathTestEnv();
-
1712 Account A1{"A1"};
-
1713 Account A2{"A2"};
-
1714 Account A3{"A3"};
-
1715 Account A4{"A4"};
-
1716 Account G1{"G1"};
-
1717 Account G2{"G2"};
-
1718 Account G3{"G3"};
-
1719 Account G4{"G4"};
-
1720 Account M1{"M1"};
-
1721 Account M2{"M2"};
-
1722
-
1723 env.fund(XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
-
1724 env.fund(XRP(10'000), A4);
-
1725 env.fund(XRP(21'000), M1, M2);
-
1726 env.close();
-
1727
-
1728 env.trust(G1["HKD"](2'000), A1);
-
1729 env.trust(G2["HKD"](2'000), A2);
-
1730 env.trust(G1["HKD"](2'000), A3);
-
1731 env.trust(G1["HKD"](100'000), M1);
-
1732 env.trust(G2["HKD"](100'000), M1);
-
1733 env.trust(G1["HKD"](100'000), M2);
-
1734 env.trust(G2["HKD"](100'000), M2);
-
1735 env.close();
-
1736
-
1737 env(pay(G1, A1, G1["HKD"](1'000)));
-
1738 env(pay(G2, A2, G2["HKD"](1'000)));
-
1739 env(pay(G1, A3, G1["HKD"](1'000)));
-
1740 env(pay(G1, M1, G1["HKD"](1'200)));
-
1741 env(pay(G2, M1, G2["HKD"](5'000)));
-
1742 env(pay(G1, M2, G1["HKD"](1'200)));
-
1743 env(pay(G2, M2, G2["HKD"](5'000)));
-
1744 env.close();
-
1745
-
1746 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
-
1747 AMM ammM2XRP_G2(env, M2, XRP(10'000), G2["HKD"](1'010));
-
1748 AMM ammM2G1_XRP(env, M2, G1["HKD"](1'010), XRP(10'000));
-
1749
-
1750 STPathSet st;
-
1751 STAmount sa, da;
-
1752
-
1753 {
-
1754 // A) Borrow or repay --
-
1755 // Source -> Destination (repay source issuer)
-
1756 auto const& send_amt = G1["HKD"](10);
-
1757 std::tie(st, sa, da) = find_paths(
-
1758 env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
-
1759 BEAST_EXPECT(st.empty());
-
1760 BEAST_EXPECT(equal(da, send_amt));
-
1761 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
-
1762 }
+
1706 STPathSet st;
+
1707 STAmount sa, da;
+
1708
+
1709 auto const& send_amt = XRP(10);
+
1710 std::tie(st, sa, da) =
+
1711 find_paths(env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
+
1712 BEAST_EXPECT(equal(da, send_amt));
+
1713 BEAST_EXPECT(equal(sa, A1["ABC"](1)));
+
1714 BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
+
1715 }
+
1716
+
1717 void
+
1718 path_find_05()
+
1719 {
+
1720 testcase("Path Find: non-XRP -> non-XRP, same currency");
+
1721 using namespace jtx;
+
1722 Env env = pathTestEnv();
+
1723 Account A1{"A1"};
+
1724 Account A2{"A2"};
+
1725 Account A3{"A3"};
+
1726 Account A4{"A4"};
+
1727 Account G1{"G1"};
+
1728 Account G2{"G2"};
+
1729 Account G3{"G3"};
+
1730 Account G4{"G4"};
+
1731 Account M1{"M1"};
+
1732 Account M2{"M2"};
+
1733
+
1734 env.fund(XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
+
1735 env.fund(XRP(10'000), A4);
+
1736 env.fund(XRP(21'000), M1, M2);
+
1737 env.close();
+
1738
+
1739 env.trust(G1["HKD"](2'000), A1);
+
1740 env.trust(G2["HKD"](2'000), A2);
+
1741 env.trust(G1["HKD"](2'000), A3);
+
1742 env.trust(G1["HKD"](100'000), M1);
+
1743 env.trust(G2["HKD"](100'000), M1);
+
1744 env.trust(G1["HKD"](100'000), M2);
+
1745 env.trust(G2["HKD"](100'000), M2);
+
1746 env.close();
+
1747
+
1748 env(pay(G1, A1, G1["HKD"](1'000)));
+
1749 env(pay(G2, A2, G2["HKD"](1'000)));
+
1750 env(pay(G1, A3, G1["HKD"](1'000)));
+
1751 env(pay(G1, M1, G1["HKD"](1'200)));
+
1752 env(pay(G2, M1, G2["HKD"](5'000)));
+
1753 env(pay(G1, M2, G1["HKD"](1'200)));
+
1754 env(pay(G2, M2, G2["HKD"](5'000)));
+
1755 env.close();
+
1756
+
1757 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
+
1758 AMM ammM2XRP_G2(env, M2, XRP(10'000), G2["HKD"](1'010));
+
1759 AMM ammM2G1_XRP(env, M2, G1["HKD"](1'010), XRP(10'000));
+
1760
+
1761 STPathSet st;
+
1762 STAmount sa, da;
1763
1764 {
-
1765 // A2) Borrow or repay --
-
1766 // Source -> Destination (repay destination issuer)
-
1767 auto const& send_amt = A1["HKD"](10);
+
1765 // A) Borrow or repay --
+
1766 // Source -> Destination (repay source issuer)
+
1767 auto const& send_amt = G1["HKD"](10);
1768 std::tie(st, sa, da) = find_paths(
1769 env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
1770 BEAST_EXPECT(st.empty());
@@ -1848,2402 +1848,2377 @@ $(function() {
1773 }
1774
1775 {
-
1776 // B) Common gateway --
-
1777 // Source -> AC -> Destination
-
1778 auto const& send_amt = A3["HKD"](10);
+
1776 // A2) Borrow or repay --
+
1777 // Source -> Destination (repay destination issuer)
+
1778 auto const& send_amt = A1["HKD"](10);
1779 std::tie(st, sa, da) = find_paths(
-
1780 env, A1, A3, send_amt, std::nullopt, G1["HKD"].currency);
-
1781 BEAST_EXPECT(equal(da, send_amt));
-
1782 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
-
1783 BEAST_EXPECT(same(st, stpath(G1)));
+
1780 env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
+
1781 BEAST_EXPECT(st.empty());
+
1782 BEAST_EXPECT(equal(da, send_amt));
+
1783 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1784 }
1785
1786 {
-
1787 // C) Gateway to gateway --
-
1788 // Source -> OB -> Destination
-
1789 auto const& send_amt = G2["HKD"](10);
+
1787 // B) Common gateway --
+
1788 // Source -> AC -> Destination
+
1789 auto const& send_amt = A3["HKD"](10);
1790 std::tie(st, sa, da) = find_paths(
-
1791 env, G1, G2, send_amt, std::nullopt, G1["HKD"].currency);
+
1791 env, A1, A3, send_amt, std::nullopt, G1["HKD"].currency);
1792 BEAST_EXPECT(equal(da, send_amt));
-
1793 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
-
1794 BEAST_EXPECT(same(
-
1795 st,
-
1796 stpath(IPE(G2["HKD"])),
-
1797 stpath(M1),
-
1798 stpath(M2),
-
1799 stpath(IPE(xrpIssue()), IPE(G2["HKD"]))));
-
1800 }
-
1801
-
1802 {
-
1803 // D) User to unlinked gateway via order book --
-
1804 // Source -> AC -> OB -> Destination
-
1805 auto const& send_amt = G2["HKD"](10);
-
1806 std::tie(st, sa, da) = find_paths(
-
1807 env, A1, G2, send_amt, std::nullopt, G1["HKD"].currency);
-
1808 BEAST_EXPECT(equal(da, send_amt));
-
1809 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
-
1810 BEAST_EXPECT(same(
-
1811 st,
-
1812 stpath(G1, M1),
-
1813 stpath(G1, M2),
-
1814 stpath(G1, IPE(G2["HKD"])),
-
1815 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]))));
-
1816 }
-
1817
-
1818 {
-
1819 // I4) XRP bridge" --
-
1820 // Source -> AC -> OB to XRP -> OB from XRP -> AC ->
-
1821 // Destination
-
1822 auto const& send_amt = A2["HKD"](10);
-
1823 std::tie(st, sa, da) = find_paths(
-
1824 env, A1, A2, send_amt, std::nullopt, G1["HKD"].currency);
-
1825 BEAST_EXPECT(equal(da, send_amt));
-
1826 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
-
1827 BEAST_EXPECT(same(
-
1828 st,
-
1829 stpath(G1, M1, G2),
-
1830 stpath(G1, M2, G2),
-
1831 stpath(G1, IPE(G2["HKD"]), G2),
-
1832 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]), G2)));
-
1833 }
-
1834 }
-
1835
-
1836 void
-
1837 path_find_06()
-
1838 {
-
1839 testcase("Path Find: non-XRP -> non-XRP, same currency");
-
1840 using namespace jtx;
-
1841 Env env = pathTestEnv();
-
1842 Account A1{"A1"};
-
1843 Account A2{"A2"};
-
1844 Account A3{"A3"};
-
1845 Account G1{"G1"};
-
1846 Account G2{"G2"};
-
1847 Account M1{"M1"};
-
1848
-
1849 env.fund(XRP(11'000), M1);
-
1850 env.fund(XRP(1'000), A1, A2, A3, G1, G2);
-
1851 env.close();
-
1852
-
1853 env.trust(G1["HKD"](2'000), A1);
-
1854 env.trust(G2["HKD"](2'000), A2);
-
1855 env.trust(A2["HKD"](2'000), A3);
-
1856 env.trust(G1["HKD"](100'000), M1);
-
1857 env.trust(G2["HKD"](100'000), M1);
-
1858 env.close();
+
1793 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
+
1794 BEAST_EXPECT(same(st, stpath(G1)));
+
1795 }
+
1796
+
1797 {
+
1798 // C) Gateway to gateway --
+
1799 // Source -> OB -> Destination
+
1800 auto const& send_amt = G2["HKD"](10);
+
1801 std::tie(st, sa, da) = find_paths(
+
1802 env, G1, G2, send_amt, std::nullopt, G1["HKD"].currency);
+
1803 BEAST_EXPECT(equal(da, send_amt));
+
1804 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
+
1805 BEAST_EXPECT(same(
+
1806 st,
+
1807 stpath(IPE(G2["HKD"])),
+
1808 stpath(M1),
+
1809 stpath(M2),
+
1810 stpath(IPE(xrpIssue()), IPE(G2["HKD"]))));
+
1811 }
+
1812
+
1813 {
+
1814 // D) User to unlinked gateway via order book --
+
1815 // Source -> AC -> OB -> Destination
+
1816 auto const& send_amt = G2["HKD"](10);
+
1817 std::tie(st, sa, da) = find_paths(
+
1818 env, A1, G2, send_amt, std::nullopt, G1["HKD"].currency);
+
1819 BEAST_EXPECT(equal(da, send_amt));
+
1820 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
+
1821 BEAST_EXPECT(same(
+
1822 st,
+
1823 stpath(G1, M1),
+
1824 stpath(G1, M2),
+
1825 stpath(G1, IPE(G2["HKD"])),
+
1826 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]))));
+
1827 }
+
1828
+
1829 {
+
1830 // I4) XRP bridge" --
+
1831 // Source -> AC -> OB to XRP -> OB from XRP -> AC ->
+
1832 // Destination
+
1833 auto const& send_amt = A2["HKD"](10);
+
1834 std::tie(st, sa, da) = find_paths(
+
1835 env, A1, A2, send_amt, std::nullopt, G1["HKD"].currency);
+
1836 BEAST_EXPECT(equal(da, send_amt));
+
1837 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
+
1838 BEAST_EXPECT(same(
+
1839 st,
+
1840 stpath(G1, M1, G2),
+
1841 stpath(G1, M2, G2),
+
1842 stpath(G1, IPE(G2["HKD"]), G2),
+
1843 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]), G2)));
+
1844 }
+
1845 }
+
1846
+
1847 void
+
1848 path_find_06()
+
1849 {
+
1850 testcase("Path Find: non-XRP -> non-XRP, same currency");
+
1851 using namespace jtx;
+
1852 Env env = pathTestEnv();
+
1853 Account A1{"A1"};
+
1854 Account A2{"A2"};
+
1855 Account A3{"A3"};
+
1856 Account G1{"G1"};
+
1857 Account G2{"G2"};
+
1858 Account M1{"M1"};
1859
-
1860 env(pay(G1, A1, G1["HKD"](1'000)));
-
1861 env(pay(G2, A2, G2["HKD"](1'000)));
-
1862 env(pay(G1, M1, G1["HKD"](5'000)));
-
1863 env(pay(G2, M1, G2["HKD"](5'000)));
-
1864 env.close();
-
1865
-
1866 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
-
1867
-
1868 // E) Gateway to user
-
1869 // Source -> OB -> AC -> Destination
-
1870 auto const& send_amt = A2["HKD"](10);
-
1871 STPathSet st;
-
1872 STAmount sa, da;
-
1873 std::tie(st, sa, da) =
-
1874 find_paths(env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency);
-
1875 BEAST_EXPECT(equal(da, send_amt));
-
1876 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
-
1877 BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
-
1878 }
-
1879
-
1880 void
-
1881 testFalseDry(FeatureBitset features)
-
1882 {
-
1883 testcase("falseDryChanges");
-
1884
-
1885 using namespace jtx;
-
1886
-
1887 Env env(*this, features);
-
1888
-
1889 env.fund(XRP(10'000), alice, gw);
-
1890 // This removes no ripple for carol,
-
1891 // different from the original test
-
1892 fund(env, gw, {carol}, XRP(10'000), {}, Fund::Acct);
-
1893 auto const AMMXRPPool = env.current()->fees().increment * 2;
-
1894 env.fund(reserve(env, 5) + ammCrtFee(env) + AMMXRPPool, bob);
-
1895 env.trust(USD(1'000), alice, bob, carol);
-
1896 env.trust(EUR(1'000), alice, bob, carol);
+
1860 env.fund(XRP(11'000), M1);
+
1861 env.fund(XRP(1'000), A1, A2, A3, G1, G2);
+
1862 env.close();
+
1863
+
1864 env.trust(G1["HKD"](2'000), A1);
+
1865 env.trust(G2["HKD"](2'000), A2);
+
1866 env.trust(A2["HKD"](2'000), A3);
+
1867 env.trust(G1["HKD"](100'000), M1);
+
1868 env.trust(G2["HKD"](100'000), M1);
+
1869 env.close();
+
1870
+
1871 env(pay(G1, A1, G1["HKD"](1'000)));
+
1872 env(pay(G2, A2, G2["HKD"](1'000)));
+
1873 env(pay(G1, M1, G1["HKD"](5'000)));
+
1874 env(pay(G2, M1, G2["HKD"](5'000)));
+
1875 env.close();
+
1876
+
1877 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
+
1878
+
1879 // E) Gateway to user
+
1880 // Source -> OB -> AC -> Destination
+
1881 auto const& send_amt = A2["HKD"](10);
+
1882 STPathSet st;
+
1883 STAmount sa, da;
+
1884 std::tie(st, sa, da) =
+
1885 find_paths(env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency);
+
1886 BEAST_EXPECT(equal(da, send_amt));
+
1887 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
+
1888 BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
+
1889 }
+
1890
+
1891 void
+
1892 testFalseDry(FeatureBitset features)
+
1893 {
+
1894 testcase("falseDryChanges");
+
1895
+
1896 using namespace jtx;
1897
-
1898 env(pay(gw, alice, EUR(50)));
-
1899 env(pay(gw, bob, USD(150)));
-
1900
-
1901 // Bob has _just_ slightly less than 50 xrp available
-
1902 // If his owner count changes, he will have more liquidity.
-
1903 // This is one error case to test (when Flow is used).
-
1904 // Computing the incoming xrp to the XRP/USD offer will require two
-
1905 // recursive calls to the EUR/XRP offer. The second call will return
-
1906 // tecPATH_DRY, but the entire path should not be marked as dry.
-
1907 // This is the second error case to test (when flowV1 is used).
-
1908 env(offer(bob, EUR(50), XRP(50)));
-
1909 AMM ammBob(env, bob, AMMXRPPool, USD(150));
-
1910
-
1911 env(pay(alice, carol, USD(1'000'000)),
-
1912 path(~XRP, ~USD),
-
1913 sendmax(EUR(500)),
-
1914 txflags(tfNoRippleDirect | tfPartialPayment));
-
1915
-
1916 auto const carolUSD = env.balance(carol, USD).value();
-
1917 BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50));
-
1918 }
-
1919
-
1920 void
-
1921 testBookStep(FeatureBitset features)
-
1922 {
-
1923 testcase("Book Step");
-
1924
-
1925 using namespace jtx;
-
1926
-
1927 {
-
1928 // simple IOU/IOU offer
-
1929 Env env(*this, features);
-
1930
-
1931 fund(
-
1932 env,
-
1933 gw,
-
1934 {alice, bob, carol},
-
1935 XRP(10'000),
-
1936 {BTC(100), USD(150)},
-
1937 Fund::All);
+
1898 Env env(*this, features);
+
1899
+
1900 env.fund(XRP(10'000), alice, gw);
+
1901 // This removes no ripple for carol,
+
1902 // different from the original test
+
1903 fund(env, gw, {carol}, XRP(10'000), {}, Fund::Acct);
+
1904 auto const AMMXRPPool = env.current()->fees().increment * 2;
+
1905 env.fund(reserve(env, 5) + ammCrtFee(env) + AMMXRPPool, bob);
+
1906 env.close();
+
1907 env.trust(USD(1'000), alice, bob, carol);
+
1908 env.trust(EUR(1'000), alice, bob, carol);
+
1909
+
1910 env(pay(gw, alice, EUR(50)));
+
1911 env(pay(gw, bob, USD(150)));
+
1912
+
1913 // Bob has _just_ slightly less than 50 xrp available
+
1914 // If his owner count changes, he will have more liquidity.
+
1915 // This is one error case to test (when Flow is used).
+
1916 // Computing the incoming xrp to the XRP/USD offer will require two
+
1917 // recursive calls to the EUR/XRP offer. The second call will return
+
1918 // tecPATH_DRY, but the entire path should not be marked as dry.
+
1919 // This is the second error case to test (when flowV1 is used).
+
1920 env(offer(bob, EUR(50), XRP(50)));
+
1921 AMM ammBob(env, bob, AMMXRPPool, USD(150));
+
1922
+
1923 env(pay(alice, carol, USD(1'000'000)),
+
1924 path(~XRP, ~USD),
+
1925 sendmax(EUR(500)),
+
1926 txflags(tfNoRippleDirect | tfPartialPayment));
+
1927
+
1928 auto const carolUSD = env.balance(carol, USD).value();
+
1929 BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50));
+
1930 }
+
1931
+
1932 void
+
1933 testBookStep(FeatureBitset features)
+
1934 {
+
1935 testcase("Book Step");
+
1936
+
1937 using namespace jtx;
1938
-
1939 AMM ammBob(env, bob, BTC(100), USD(150));
-
1940
-
1941 env(pay(alice, carol, USD(50)), path(~USD), sendmax(BTC(50)));
+
1939 {
+
1940 // simple IOU/IOU offer
+
1941 Env env(*this, features);
1942
-
1943 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
-
1944 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
-
1945 BEAST_EXPECT(expectLine(env, bob, USD(0)));
-
1946 BEAST_EXPECT(expectLine(env, carol, USD(200)));
-
1947 BEAST_EXPECT(
-
1948 ammBob.expectBalances(BTC(150), USD(100), ammBob.tokens()));
-
1949 }
-
1950 {
-
1951 // simple IOU/XRP XRP/IOU offer
-
1952 Env env(*this, features);
-
1953
-
1954 fund(
-
1955 env,
-
1956 gw,
-
1957 {alice, carol, bob},
-
1958 XRP(10'000),
-
1959 {BTC(100), USD(150)},
-
1960 Fund::All);
-
1961
-
1962 AMM ammBobBTC_XRP(env, bob, BTC(100), XRP(150));
-
1963 AMM ammBobXRP_USD(env, bob, XRP(100), USD(150));
-
1964
-
1965 env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50)));
-
1966
-
1967 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
-
1968 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
-
1969 BEAST_EXPECT(expectLine(env, bob, USD(0)));
-
1970 BEAST_EXPECT(expectLine(env, carol, USD(200)));
-
1971 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
-
1972 BTC(150), XRP(100), ammBobBTC_XRP.tokens()));
-
1973 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
-
1974 XRP(150), USD(100), ammBobXRP_USD.tokens()));
-
1975 }
-
1976 {
-
1977 // simple XRP -> USD through offer and sendmax
-
1978 Env env(*this, features);
-
1979
-
1980 fund(
-
1981 env,
-
1982 gw,
-
1983 {alice, carol, bob},
-
1984 XRP(10'000),
-
1985 {USD(150)},
-
1986 Fund::All);
-
1987
-
1988 AMM ammBob(env, bob, XRP(100), USD(150));
-
1989
-
1990 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
+
1943 fund(
+
1944 env,
+
1945 gw,
+
1946 {alice, bob, carol},
+
1947 XRP(10'000),
+
1948 {BTC(100), USD(150)},
+
1949 Fund::All);
+
1950
+
1951 AMM ammBob(env, bob, BTC(100), USD(150));
+
1952
+
1953 env(pay(alice, carol, USD(50)), path(~USD), sendmax(BTC(50)));
+
1954
+
1955 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
+
1956 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
+
1957 BEAST_EXPECT(expectLine(env, bob, USD(0)));
+
1958 BEAST_EXPECT(expectLine(env, carol, USD(200)));
+
1959 BEAST_EXPECT(
+
1960 ammBob.expectBalances(BTC(150), USD(100), ammBob.tokens()));
+
1961 }
+
1962 {
+
1963 // simple IOU/XRP XRP/IOU offer
+
1964 Env env(*this, features);
+
1965
+
1966 fund(
+
1967 env,
+
1968 gw,
+
1969 {alice, carol, bob},
+
1970 XRP(10'000),
+
1971 {BTC(100), USD(150)},
+
1972 Fund::All);
+
1973
+
1974 AMM ammBobBTC_XRP(env, bob, BTC(100), XRP(150));
+
1975 AMM ammBobXRP_USD(env, bob, XRP(100), USD(150));
+
1976
+
1977 env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50)));
+
1978
+
1979 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
+
1980 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
+
1981 BEAST_EXPECT(expectLine(env, bob, USD(0)));
+
1982 BEAST_EXPECT(expectLine(env, carol, USD(200)));
+
1983 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
+
1984 BTC(150), XRP(100), ammBobBTC_XRP.tokens()));
+
1985 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
+
1986 XRP(150), USD(100), ammBobXRP_USD.tokens()));
+
1987 }
+
1988 {
+
1989 // simple XRP -> USD through offer and sendmax
+
1990 Env env(*this, features);
1991
-
1992 BEAST_EXPECT(expectLedgerEntryRoot(
-
1993 env, alice, xrpMinusFee(env, 10'000 - 50)));
-
1994 BEAST_EXPECT(expectLedgerEntryRoot(
-
1995 env, bob, XRP(10'000) - XRP(100) - ammCrtFee(env)));
-
1996 BEAST_EXPECT(expectLine(env, bob, USD(0)));
-
1997 BEAST_EXPECT(expectLine(env, carol, USD(200)));
-
1998 BEAST_EXPECT(
-
1999 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
-
2000 }
-
2001 {
-
2002 // simple USD -> XRP through offer and sendmax
-
2003 Env env(*this, features);
-
2004
-
2005 fund(
-
2006 env,
-
2007 gw,
-
2008 {alice, carol, bob},
-
2009 XRP(10'000),
-
2010 {USD(100)},
-
2011 Fund::All);
-
2012
-
2013 AMM ammBob(env, bob, USD(100), XRP(150));
-
2014
-
2015 env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50)));
+
1992 fund(
+
1993 env,
+
1994 gw,
+
1995 {alice, carol, bob},
+
1996 XRP(10'000),
+
1997 {USD(150)},
+
1998 Fund::All);
+
1999
+
2000 AMM ammBob(env, bob, XRP(100), USD(150));
+
2001
+
2002 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
+
2003
+
2004 BEAST_EXPECT(expectLedgerEntryRoot(
+
2005 env, alice, xrpMinusFee(env, 10'000 - 50)));
+
2006 BEAST_EXPECT(expectLedgerEntryRoot(
+
2007 env, bob, XRP(10'000) - XRP(100) - ammCrtFee(env)));
+
2008 BEAST_EXPECT(expectLine(env, bob, USD(0)));
+
2009 BEAST_EXPECT(expectLine(env, carol, USD(200)));
+
2010 BEAST_EXPECT(
+
2011 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
+
2012 }
+
2013 {
+
2014 // simple USD -> XRP through offer and sendmax
+
2015 Env env(*this, features);
2016
-
2017 BEAST_EXPECT(expectLine(env, alice, USD(50)));
-
2018 BEAST_EXPECT(expectLedgerEntryRoot(
-
2019 env, bob, XRP(10'000) - XRP(150) - ammCrtFee(env)));
-
2020 BEAST_EXPECT(expectLine(env, bob, USD(0)));
-
2021 BEAST_EXPECT(expectLedgerEntryRoot(env, carol, XRP(10'000 + 50)));
-
2022 BEAST_EXPECT(
-
2023 ammBob.expectBalances(USD(150), XRP(100), ammBob.tokens()));
-
2024 }
-
2025 {
-
2026 // test unfunded offers are removed when payment succeeds
-
2027 Env env(*this, features);
+
2017 fund(
+
2018 env,
+
2019 gw,
+
2020 {alice, carol, bob},
+
2021 XRP(10'000),
+
2022 {USD(100)},
+
2023 Fund::All);
+
2024
+
2025 AMM ammBob(env, bob, USD(100), XRP(150));
+
2026
+
2027 env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50)));
2028
-
2029 env.fund(XRP(10'000), alice, carol, gw);
-
2030 env.fund(XRP(10'000), bob);
-
2031 env.trust(USD(1'000), alice, bob, carol);
-
2032 env.trust(BTC(1'000), alice, bob, carol);
-
2033 env.trust(EUR(1'000), alice, bob, carol);
-
2034
-
2035 env(pay(gw, alice, BTC(60)));
-
2036 env(pay(gw, bob, USD(200)));
-
2037 env(pay(gw, bob, EUR(150)));
-
2038
-
2039 env(offer(bob, BTC(50), USD(50)));
-
2040 env(offer(bob, BTC(40), EUR(50)));
-
2041 AMM ammBob(env, bob, EUR(100), USD(150));
-
2042
-
2043 // unfund offer
-
2044 env(pay(bob, gw, EUR(50)));
-
2045 BEAST_EXPECT(isOffer(env, bob, BTC(50), USD(50)));
-
2046 BEAST_EXPECT(isOffer(env, bob, BTC(40), EUR(50)));
-
2047
-
2048 env(pay(alice, carol, USD(50)),
-
2049 path(~USD),
-
2050 path(~EUR, ~USD),
-
2051 sendmax(BTC(60)));
-
2052
-
2053 env.require(balance(alice, BTC(10)));
-
2054 env.require(balance(bob, BTC(50)));
-
2055 env.require(balance(bob, USD(0)));
-
2056 env.require(balance(bob, EUR(0)));
-
2057 env.require(balance(carol, USD(50)));
-
2058 // used in the payment
-
2059 BEAST_EXPECT(!isOffer(env, bob, BTC(50), USD(50)));
-
2060 // found unfunded
-
2061 BEAST_EXPECT(!isOffer(env, bob, BTC(40), EUR(50)));
-
2062 // unchanged
-
2063 BEAST_EXPECT(
-
2064 ammBob.expectBalances(EUR(100), USD(150), ammBob.tokens()));
-
2065 }
-
2066 {
-
2067 // test unfunded offers are removed when the payment fails.
-
2068 // bob makes two offers: a funded 50 USD for 50 BTC and an
-
2069 // unfunded 50 EUR for 60 BTC. alice pays carol 61 USD with 61
-
2070 // BTC. alice only has 60 BTC, so the payment will fail. The
-
2071 // payment uses two paths: one through bob's funded offer and
-
2072 // one through his unfunded offer. When the payment fails `flow`
-
2073 // should return the unfunded offer. This test is intentionally
-
2074 // similar to the one that removes unfunded offers when the
-
2075 // payment succeeds.
-
2076 Env env(*this, features);
-
2077
-
2078 env.fund(XRP(10'000), bob, carol, gw);
-
2079 // Sets rippling on, this is different from
-
2080 // the original test
-
2081 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
-
2082 env.trust(USD(1'000), alice, bob, carol);
-
2083 env.trust(BTC(1'000), alice, bob, carol);
-
2084 env.trust(EUR(1'000), alice, bob, carol);
-
2085
-
2086 env(pay(gw, alice, BTC(60)));
-
2087 env(pay(gw, bob, BTC(100)));
-
2088 env(pay(gw, bob, USD(100)));
-
2089 env(pay(gw, bob, EUR(50)));
-
2090 env(pay(gw, carol, EUR(1)));
-
2091
-
2092 // This is multiplath, which generates limited # of offers
-
2093 AMM ammBobBTC_USD(env, bob, BTC(50), USD(50));
-
2094 env(offer(bob, BTC(60), EUR(50)));
-
2095 env(offer(carol, BTC(1'000), EUR(1)));
-
2096 env(offer(bob, EUR(50), USD(50)));
-
2097
-
2098 // unfund offer
-
2099 env(pay(bob, gw, EUR(50)));
-
2100 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
-
2101 BTC(50), USD(50), ammBobBTC_USD.tokens()));
-
2102 BEAST_EXPECT(isOffer(env, bob, BTC(60), EUR(50)));
-
2103 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
-
2104 BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50)));
-
2105
-
2106 auto flowJournal = env.app().logs().journal("Flow");
-
2107 auto const flowResult = [&] {
-
2108 STAmount deliver(USD(51));
-
2109 STAmount smax(BTC(61));
-
2110 PaymentSandbox sb(env.current().get(), tapNONE);
-
2111 STPathSet paths;
-
2112 auto IPE = [](Issue const& iss) {
-
2113 return STPathElement(
-
2114 STPathElement::typeCurrency | STPathElement::typeIssuer,
-
2115 xrpAccount(),
-
2116 iss.currency,
-
2117 iss.account);
-
2118 };
-
2119 {
-
2120 // BTC -> USD
-
2121 STPath p1({IPE(USD.issue())});
-
2122 paths.push_back(p1);
-
2123 // BTC -> EUR -> USD
-
2124 STPath p2({IPE(EUR.issue()), IPE(USD.issue())});
-
2125 paths.push_back(p2);
-
2126 }
-
2127
-
2128 return flow(
-
2129 sb,
-
2130 deliver,
-
2131 alice,
-
2132 carol,
-
2133 paths,
-
2134 false,
-
2135 false,
-
2136 true,
-
2137 OfferCrossing::no,
-
2138 std::nullopt,
-
2139 smax,
-
2140 flowJournal);
-
2141 }();
-
2142
-
2143 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
-
2144 env.app().openLedger().modify(
-
2145 [&](OpenView& view, beast::Journal j) {
-
2146 if (flowResult.removableOffers.empty())
-
2147 return false;
-
2148 Sandbox sb(&view, tapNONE);
-
2149 for (auto const& o : flowResult.removableOffers)
-
2150 if (auto ok = sb.peek(keylet::offer(o)))
-
2151 offerDelete(sb, ok, flowJournal);
-
2152 sb.apply(view);
-
2153 return true;
-
2154 });
-
2155
-
2156 // used in payment, but since payment failed should be untouched
-
2157 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
-
2158 BTC(50), USD(50), ammBobBTC_USD.tokens()));
-
2159 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
-
2160 // found unfunded
-
2161 BEAST_EXPECT(!isOffer(env, bob, BTC(60), EUR(50)));
-
2162 }
-
2163 {
-
2164 // Do not produce more in the forward pass than the reverse pass
-
2165 // This test uses a path that whose reverse pass will compute a
-
2166 // 0.5 USD input required for a 1 EUR output. It sets a sendmax
-
2167 // of 0.4 USD, so the payment engine will need to do a forward
-
2168 // pass. Without limits, the 0.4 USD would produce 1000 EUR in
-
2169 // the forward pass. This test checks that the payment produces
-
2170 // 1 EUR, as expected.
-
2171
-
2172 Env env(*this, features);
-
2173 env.fund(XRP(10'000), bob, carol, gw);
-
2174 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
-
2175 env.trust(USD(1'000), alice, bob, carol);
-
2176 env.trust(EUR(1'000), alice, bob, carol);
-
2177
-
2178 env(pay(gw, alice, USD(1'000)));
-
2179 env(pay(gw, bob, EUR(1'000)));
-
2180 env(pay(gw, bob, USD(1'000)));
-
2181
-
2182 // env(offer(bob, USD(1), drops(2)), txflags(tfPassive));
-
2183 AMM ammBob(env, bob, USD(8), XRPAmount{21});
-
2184 env(offer(bob, drops(1), EUR(1'000)), txflags(tfPassive));
-
2185
-
2186 env(pay(alice, carol, EUR(1)),
-
2187 path(~XRP, ~EUR),
-
2188 sendmax(USD(0.4)),
-
2189 txflags(tfNoRippleDirect | tfPartialPayment));
+
2029 BEAST_EXPECT(expectLine(env, alice, USD(50)));
+
2030 BEAST_EXPECT(expectLedgerEntryRoot(
+
2031 env, bob, XRP(10'000) - XRP(150) - ammCrtFee(env)));
+
2032 BEAST_EXPECT(expectLine(env, bob, USD(0)));
+
2033 BEAST_EXPECT(expectLedgerEntryRoot(env, carol, XRP(10'000 + 50)));
+
2034 BEAST_EXPECT(
+
2035 ammBob.expectBalances(USD(150), XRP(100), ammBob.tokens()));
+
2036 }
+
2037 {
+
2038 // test unfunded offers are removed when payment succeeds
+
2039 Env env(*this, features);
+
2040
+
2041 env.fund(XRP(10'000), alice, carol, gw);
+
2042 env.fund(XRP(10'000), bob);
+
2043 env.close();
+
2044 env.trust(USD(1'000), alice, bob, carol);
+
2045 env.trust(BTC(1'000), alice, bob, carol);
+
2046 env.trust(EUR(1'000), alice, bob, carol);
+
2047 env.close();
+
2048
+
2049 env(pay(gw, alice, BTC(60)));
+
2050 env(pay(gw, bob, USD(200)));
+
2051 env(pay(gw, bob, EUR(150)));
+
2052 env.close();
+
2053
+
2054 env(offer(bob, BTC(50), USD(50)));
+
2055 env(offer(bob, BTC(40), EUR(50)));
+
2056 env.close();
+
2057 AMM ammBob(env, bob, EUR(100), USD(150));
+
2058
+
2059 // unfund offer
+
2060 env(pay(bob, gw, EUR(50)));
+
2061 BEAST_EXPECT(isOffer(env, bob, BTC(50), USD(50)));
+
2062 BEAST_EXPECT(isOffer(env, bob, BTC(40), EUR(50)));
+
2063
+
2064 env(pay(alice, carol, USD(50)),
+
2065 path(~USD),
+
2066 path(~EUR, ~USD),
+
2067 sendmax(BTC(60)));
+
2068
+
2069 env.require(balance(alice, BTC(10)));
+
2070 env.require(balance(bob, BTC(50)));
+
2071 env.require(balance(bob, USD(0)));
+
2072 env.require(balance(bob, EUR(0)));
+
2073 env.require(balance(carol, USD(50)));
+
2074 // used in the payment
+
2075 BEAST_EXPECT(!isOffer(env, bob, BTC(50), USD(50)));
+
2076 // found unfunded
+
2077 BEAST_EXPECT(!isOffer(env, bob, BTC(40), EUR(50)));
+
2078 // unchanged
+
2079 BEAST_EXPECT(
+
2080 ammBob.expectBalances(EUR(100), USD(150), ammBob.tokens()));
+
2081 }
+
2082 {
+
2083 // test unfunded offers are removed when the payment fails.
+
2084 // bob makes two offers: a funded 50 USD for 50 BTC and an
+
2085 // unfunded 50 EUR for 60 BTC. alice pays carol 61 USD with 61
+
2086 // BTC. alice only has 60 BTC, so the payment will fail. The
+
2087 // payment uses two paths: one through bob's funded offer and
+
2088 // one through his unfunded offer. When the payment fails `flow`
+
2089 // should return the unfunded offer. This test is intentionally
+
2090 // similar to the one that removes unfunded offers when the
+
2091 // payment succeeds.
+
2092 Env env(*this, features);
+
2093
+
2094 env.fund(XRP(10'000), bob, carol, gw);
+
2095 env.close();
+
2096 // Sets rippling on, this is different from
+
2097 // the original test
+
2098 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
+
2099 env.trust(USD(1'000), alice, bob, carol);
+
2100 env.trust(BTC(1'000), alice, bob, carol);
+
2101 env.trust(EUR(1'000), alice, bob, carol);
+
2102 env.close();
+
2103
+
2104 env(pay(gw, alice, BTC(60)));
+
2105 env(pay(gw, bob, BTC(100)));
+
2106 env(pay(gw, bob, USD(100)));
+
2107 env(pay(gw, bob, EUR(50)));
+
2108 env(pay(gw, carol, EUR(1)));
+
2109 env.close();
+
2110
+
2111 // This is multiplath, which generates limited # of offers
+
2112 AMM ammBobBTC_USD(env, bob, BTC(50), USD(50));
+
2113 env(offer(bob, BTC(60), EUR(50)));
+
2114 env(offer(carol, BTC(1'000), EUR(1)));
+
2115 env(offer(bob, EUR(50), USD(50)));
+
2116
+
2117 // unfund offer
+
2118 env(pay(bob, gw, EUR(50)));
+
2119 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
+
2120 BTC(50), USD(50), ammBobBTC_USD.tokens()));
+
2121 BEAST_EXPECT(isOffer(env, bob, BTC(60), EUR(50)));
+
2122 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
+
2123 BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50)));
+
2124
+
2125 auto flowJournal = env.app().logs().journal("Flow");
+
2126 auto const flowResult = [&] {
+
2127 STAmount deliver(USD(51));
+
2128 STAmount smax(BTC(61));
+
2129 PaymentSandbox sb(env.current().get(), tapNONE);
+
2130 STPathSet paths;
+
2131 auto IPE = [](Issue const& iss) {
+
2132 return STPathElement(
+
2133 STPathElement::typeCurrency | STPathElement::typeIssuer,
+
2134 xrpAccount(),
+
2135 iss.currency,
+
2136 iss.account);
+
2137 };
+
2138 {
+
2139 // BTC -> USD
+
2140 STPath p1({IPE(USD.issue())});
+
2141 paths.push_back(p1);
+
2142 // BTC -> EUR -> USD
+
2143 STPath p2({IPE(EUR.issue()), IPE(USD.issue())});
+
2144 paths.push_back(p2);
+
2145 }
+
2146
+
2147 return flow(
+
2148 sb,
+
2149 deliver,
+
2150 alice,
+
2151 carol,
+
2152 paths,
+
2153 false,
+
2154 false,
+
2155 true,
+
2156 OfferCrossing::no,
+
2157 std::nullopt,
+
2158 smax,
+
2159 flowJournal);
+
2160 }();
+
2161
+
2162 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
+
2163 env.app().openLedger().modify(
+
2164 [&](OpenView& view, beast::Journal j) {
+
2165 if (flowResult.removableOffers.empty())
+
2166 return false;
+
2167 Sandbox sb(&view, tapNONE);
+
2168 for (auto const& o : flowResult.removableOffers)
+
2169 if (auto ok = sb.peek(keylet::offer(o)))
+
2170 offerDelete(sb, ok, flowJournal);
+
2171 sb.apply(view);
+
2172 return true;
+
2173 });
+
2174
+
2175 // used in payment, but since payment failed should be untouched
+
2176 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
+
2177 BTC(50), USD(50), ammBobBTC_USD.tokens()));
+
2178 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
+
2179 // found unfunded
+
2180 BEAST_EXPECT(!isOffer(env, bob, BTC(60), EUR(50)));
+
2181 }
+
2182 {
+
2183 // Do not produce more in the forward pass than the reverse pass
+
2184 // This test uses a path that whose reverse pass will compute a
+
2185 // 0.5 USD input required for a 1 EUR output. It sets a sendmax
+
2186 // of 0.4 USD, so the payment engine will need to do a forward
+
2187 // pass. Without limits, the 0.4 USD would produce 1000 EUR in
+
2188 // the forward pass. This test checks that the payment produces
+
2189 // 1 EUR, as expected.
2190
-
2191 BEAST_EXPECT(expectLine(env, carol, EUR(1)));
-
2192 BEAST_EXPECT(ammBob.expectBalances(
-
2193 USD(8.4), XRPAmount{20}, ammBob.tokens()));
-
2194 }
-
2195 }
-
2196
-
2197 void
-
2198 testTransferRate(FeatureBitset features)
-
2199 {
-
2200 testcase("Transfer Rate");
-
2201
-
2202 using namespace jtx;
+
2191 Env env(*this, features);
+
2192 env.fund(XRP(10'000), bob, carol, gw);
+
2193 env.close();
+
2194 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
+
2195 env.trust(USD(1'000), alice, bob, carol);
+
2196 env.trust(EUR(1'000), alice, bob, carol);
+
2197 env.close();
+
2198
+
2199 env(pay(gw, alice, USD(1'000)));
+
2200 env(pay(gw, bob, EUR(1'000)));
+
2201 env(pay(gw, bob, USD(1'000)));
+
2202 env.close();
2203
-
2204 {
-
2205 // transfer fee on AMM
-
2206 Env env(*this, features);
+
2204 // env(offer(bob, USD(1), drops(2)), txflags(tfPassive));
+
2205 AMM ammBob(env, bob, USD(8), XRPAmount{21});
+
2206 env(offer(bob, drops(1), EUR(1'000)), txflags(tfPassive));
2207
-
2208 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(1'000)});
-
2209 env(rate(gw, 1.25));
-
2210 env.close();
-
2211
-
2212 AMM ammBob(env, bob, XRP(100), USD(150));
-
2213 // no transfer fee on create
-
2214 BEAST_EXPECT(expectLine(env, bob, USD(1000 - 150)));
-
2215
-
2216 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
-
2217 env.close();
+
2208 env(pay(alice, carol, EUR(1)),
+
2209 path(~XRP, ~EUR),
+
2210 sendmax(USD(0.4)),
+
2211 txflags(tfNoRippleDirect | tfPartialPayment));
+
2212
+
2213 BEAST_EXPECT(expectLine(env, carol, EUR(1)));
+
2214 BEAST_EXPECT(ammBob.expectBalances(
+
2215 USD(8.4), XRPAmount{20}, ammBob.tokens()));
+
2216 }
+
2217 }
2218
-
2219 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 150)));
-
2220 BEAST_EXPECT(
-
2221 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
-
2222 BEAST_EXPECT(expectLedgerEntryRoot(
-
2223 env, alice, xrpMinusFee(env, 10'000 - 50)));
-
2224 BEAST_EXPECT(expectLine(env, carol, USD(1'050)));
-
2225 }
-
2226
-
2227 {
-
2228 // Transfer fee AMM and offer
-
2229 Env env(*this, features);
-
2230
-
2231 fund(
-
2232 env,
-
2233 gw,
-
2234 {alice, bob, carol},
-
2235 XRP(10'000),
-
2236 {USD(1'000), EUR(1'000)});
-
2237 env(rate(gw, 1.25));
-
2238 env.close();
-
2239
-
2240 AMM ammBob(env, bob, XRP(100), USD(140));
-
2241 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
-
2242
-
2243 env(offer(bob, USD(50), EUR(50)));
-
2244
-
2245 // alice buys 40EUR with 40XRP
-
2246 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
-
2247
-
2248 // 40XRP is swapped in for 40USD
-
2249 BEAST_EXPECT(
-
2250 ammBob.expectBalances(XRP(140), USD(100), ammBob.tokens()));
-
2251 // 40USD buys 40EUR via bob's offer. 40EUR delivered to carol
-
2252 // and bob pays 25% on 40EUR, 40EUR*0.25=10EUR
-
2253 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 40 - 40 * 0.25)));
-
2254 // bob gets 40USD back from the offer
-
2255 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 + 40)));
-
2256 BEAST_EXPECT(expectLedgerEntryRoot(
-
2257 env, alice, xrpMinusFee(env, 10'000 - 40)));
-
2258 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
-
2259 BEAST_EXPECT(expectOffers(env, bob, 1, {{USD(10), EUR(10)}}));
-
2260 }
+
2219 void
+
2220 testTransferRate(FeatureBitset features)
+
2221 {
+
2222 testcase("Transfer Rate");
+
2223
+
2224 using namespace jtx;
+
2225
+
2226 {
+
2227 // transfer fee on AMM
+
2228 Env env(*this, features);
+
2229
+
2230 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(1'000)});
+
2231 env(rate(gw, 1.25));
+
2232 env.close();
+
2233
+
2234 AMM ammBob(env, bob, XRP(100), USD(150));
+
2235 // no transfer fee on create
+
2236 BEAST_EXPECT(expectLine(env, bob, USD(1000 - 150)));
+
2237
+
2238 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
+
2239 env.close();
+
2240
+
2241 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 150)));
+
2242 BEAST_EXPECT(
+
2243 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
+
2244 BEAST_EXPECT(expectLedgerEntryRoot(
+
2245 env, alice, xrpMinusFee(env, 10'000 - 50)));
+
2246 BEAST_EXPECT(expectLine(env, carol, USD(1'050)));
+
2247 }
+
2248
+
2249 {
+
2250 // Transfer fee AMM and offer
+
2251 Env env(*this, features);
+
2252
+
2253 fund(
+
2254 env,
+
2255 gw,
+
2256 {alice, bob, carol},
+
2257 XRP(10'000),
+
2258 {USD(1'000), EUR(1'000)});
+
2259 env(rate(gw, 1.25));
+
2260 env.close();
2261
-
2262 {
-
2263 // Transfer fee two consecutive AMM
-
2264 Env env(*this, features);
-
2265
-
2266 fund(
-
2267 env,
-
2268 gw,
-
2269 {alice, bob, carol},
-
2270 XRP(10'000),
-
2271 {USD(1'000), EUR(1'000)});
-
2272 env(rate(gw, 1.25));
-
2273 env.close();
-
2274
-
2275 AMM ammBobXRP_USD(env, bob, XRP(100), USD(140));
-
2276 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
-
2277
-
2278 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(140));
-
2279 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
-
2280 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
-
2281
-
2282 // alice buys 40EUR with 40XRP
-
2283 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
-
2284
-
2285 // 40XRP is swapped in for 40USD
-
2286 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
-
2287 XRP(140), USD(100), ammBobXRP_USD.tokens()));
-
2288 // 40USD is swapped in for 40EUR
-
2289 BEAST_EXPECT(ammBobUSD_EUR.expectBalances(
-
2290 USD(140), EUR(100), ammBobUSD_EUR.tokens()));
-
2291 // no other charges on bob
-
2292 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
-
2293 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
-
2294 BEAST_EXPECT(expectLedgerEntryRoot(
-
2295 env, alice, xrpMinusFee(env, 10'000 - 40)));
-
2296 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
-
2297 }
-
2298
-
2299 {
-
2300 // Payment via AMM with limit quality, deliver less
-
2301 // than requested
-
2302 Env env(*this, features);
+
2262 AMM ammBob(env, bob, XRP(100), USD(140));
+
2263 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
+
2264
+
2265 env(offer(bob, USD(50), EUR(50)));
+
2266
+
2267 // alice buys 40EUR with 40XRP
+
2268 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
+
2269
+
2270 // 40XRP is swapped in for 40USD
+
2271 BEAST_EXPECT(
+
2272 ammBob.expectBalances(XRP(140), USD(100), ammBob.tokens()));
+
2273 // 40USD buys 40EUR via bob's offer. 40EUR delivered to carol
+
2274 // and bob pays 25% on 40EUR, 40EUR*0.25=10EUR
+
2275 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 40 - 40 * 0.25)));
+
2276 // bob gets 40USD back from the offer
+
2277 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 + 40)));
+
2278 BEAST_EXPECT(expectLedgerEntryRoot(
+
2279 env, alice, xrpMinusFee(env, 10'000 - 40)));
+
2280 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
+
2281 BEAST_EXPECT(expectOffers(env, bob, 1, {{USD(10), EUR(10)}}));
+
2282 }
+
2283
+
2284 {
+
2285 // Transfer fee two consecutive AMM
+
2286 Env env(*this, features);
+
2287
+
2288 fund(
+
2289 env,
+
2290 gw,
+
2291 {alice, bob, carol},
+
2292 XRP(10'000),
+
2293 {USD(1'000), EUR(1'000)});
+
2294 env(rate(gw, 1.25));
+
2295 env.close();
+
2296
+
2297 AMM ammBobXRP_USD(env, bob, XRP(100), USD(140));
+
2298 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
+
2299
+
2300 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(140));
+
2301 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
+
2302 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
2303
-
2304 fund(
-
2305 env,
-
2306 gw,
-
2307 {alice, bob, carol},
-
2308 XRP(1'000),
-
2309 {USD(1'200), GBP(1'200)});
-
2310 env(rate(gw, 1.25));
-
2311 env.close();
-
2312
-
2313 AMM amm(env, bob, GBP(1'000), USD(1'100));
-
2314
-
2315 // requested quality limit is 90USD/110GBP = 0.8181
-
2316 // trade quality is 77.2727USD/94.4444GBP = 0.8181
-
2317 env(pay(alice, carol, USD(90)),
-
2318 path(~USD),
-
2319 sendmax(GBP(110)),
-
2320 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
2321 env.close();
-
2322
-
2323 if (!features[fixAMMv1_1])
-
2324 {
-
2325 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
-
2326 // on 75.5555GBP
-
2327 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
-
2328 BEAST_EXPECT(expectLine(
-
2329 env,
-
2330 alice,
-
2331 STAmount{GBP, UINT64_C(1'105'555555555555), -12}));
-
2332 // 75.5555GBP is swapped in for 77.7272USD
-
2333 BEAST_EXPECT(amm.expectBalances(
-
2334 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
-
2335 STAmount{USD, UINT64_C(1'022'727272727272), -12},
-
2336 amm.tokens()));
-
2337 }
-
2338 else
-
2339 {
-
2340 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
-
2341 // on 75.5555GBP
-
2342 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
-
2343 BEAST_EXPECT(expectLine(
-
2344 env,
-
2345 alice,
-
2346 STAmount{GBP, UINT64_C(1'105'555555555554), -12}));
-
2347 // 75.5555GBP is swapped in for 77.7272USD
-
2348 BEAST_EXPECT(amm.expectBalances(
-
2349 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
-
2350 STAmount{USD, UINT64_C(1'022'727272727272), -12},
-
2351 amm.tokens()));
-
2352 }
-
2353 BEAST_EXPECT(expectLine(
-
2354 env, carol, STAmount{USD, UINT64_C(1'277'272727272728), -12}));
-
2355 }
-
2356
-
2357 {
-
2358 // AMM offer crossing
-
2359 Env env(*this, features);
-
2360
-
2361 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'200), EUR(1'200)});
-
2362 env(rate(gw, 1.25));
-
2363 env.close();
-
2364
-
2365 AMM amm(env, bob, USD(1'000), EUR(1'150));
-
2366
-
2367 env(offer(alice, EUR(100), USD(100)));
-
2368 env.close();
-
2369
-
2370 if (!features[fixAMMv1_1])
-
2371 {
-
2372 // 95.2380USD is swapped in for 100EUR
-
2373 BEAST_EXPECT(amm.expectBalances(
-
2374 STAmount{USD, UINT64_C(1'095'238095238095), -12},
-
2375 EUR(1'050),
-
2376 amm.tokens()));
-
2377 // alice pays 25% tr fee on 95.2380USD
-
2378 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
-
2379 BEAST_EXPECT(expectLine(
-
2380 env,
-
2381 alice,
-
2382 STAmount{USD, UINT64_C(1'080'952380952381), -12},
-
2383 EUR(1'300)));
-
2384 }
-
2385 else
-
2386 {
-
2387 // 95.2380USD is swapped in for 100EUR
-
2388 BEAST_EXPECT(amm.expectBalances(
-
2389 STAmount{USD, UINT64_C(1'095'238095238096), -12},
-
2390 EUR(1'050),
-
2391 amm.tokens()));
-
2392 // alice pays 25% tr fee on 95.2380USD
-
2393 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
-
2394 BEAST_EXPECT(expectLine(
-
2395 env,
-
2396 alice,
-
2397 STAmount{USD, UINT64_C(1'080'95238095238), -11},
-
2398 EUR(1'300)));
-
2399 }
-
2400 BEAST_EXPECT(expectOffers(env, alice, 0));
-
2401 }
-
2402
-
2403 {
-
2404 // First pass through a strand redeems, second pass issues,
-
2405 // through an offer limiting step is not an endpoint
-
2406 Env env(*this, features);
-
2407 auto const USDA = alice["USD"];
-
2408 auto const USDB = bob["USD"];
-
2409 Account const dan("dan");
-
2410
-
2411 env.fund(XRP(10'000), bob, carol, dan, gw);
-
2412 fund(env, {alice}, XRP(10'000));
-
2413 env(rate(gw, 1.25));
-
2414 env.trust(USD(2'000), alice, bob, carol, dan);
-
2415 env.trust(EUR(2'000), carol, dan);
-
2416 env.trust(USDA(1'000), bob);
-
2417 env.trust(USDB(1'000), gw);
-
2418 env(pay(gw, bob, USD(50)));
-
2419 env(pay(gw, dan, EUR(1'050)));
-
2420 env(pay(gw, dan, USD(1'000)));
-
2421 AMM ammDan(env, dan, USD(1'000), EUR(1'050));
-
2422
-
2423 if (!features[fixAMMv1_1])
-
2424 {
-
2425 // alice -> bob -> gw -> carol. $50 should have transfer fee;
-
2426 // $10, no fee
-
2427 env(pay(alice, carol, EUR(50)),
-
2428 path(bob, gw, ~EUR),
-
2429 sendmax(USDA(60)),
-
2430 txflags(tfNoRippleDirect));
-
2431 BEAST_EXPECT(ammDan.expectBalances(
-
2432 USD(1'050), EUR(1'000), ammDan.tokens()));
-
2433 BEAST_EXPECT(expectLine(env, dan, USD(0)));
-
2434 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
-
2435 BEAST_EXPECT(expectLine(env, bob, USD(-10)));
-
2436 BEAST_EXPECT(expectLine(env, bob, USDA(60)));
-
2437 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
-
2438 }
-
2439 else
-
2440 {
-
2441 // alice -> bob -> gw -> carol. $50 should have transfer fee;
-
2442 // $10, no fee
-
2443 env(pay(alice, carol, EUR(50)),
-
2444 path(bob, gw, ~EUR),
-
2445 sendmax(USDA(60.1)),
-
2446 txflags(tfNoRippleDirect));
-
2447 BEAST_EXPECT(ammDan.expectBalances(
-
2448 STAmount{USD, UINT64_C(1'050'000000000001), -12},
-
2449 EUR(1'000),
-
2450 ammDan.tokens()));
-
2451 BEAST_EXPECT(expectLine(env, dan, USD(0)));
-
2452 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
-
2453 BEAST_EXPECT(expectLine(
-
2454 env, bob, STAmount{USD, INT64_C(-10'000000000001), -12}));
-
2455 BEAST_EXPECT(expectLine(
-
2456 env, bob, STAmount{USDA, UINT64_C(60'000000000001), -12}));
-
2457 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
-
2458 }
-
2459 }
-
2460 }
-
2461
-
2462 void
-
2463 testTransferRateNoOwnerFee(FeatureBitset features)
-
2464 {
-
2465 testcase("No Owner Fee");
-
2466 using namespace jtx;
-
2467
-
2468 {
-
2469 // payment via AMM
-
2470 Env env(*this, features);
-
2471
-
2472 fund(
-
2473 env,
-
2474 gw,
-
2475 {alice, bob, carol},
-
2476 XRP(1'000),
-
2477 {USD(1'000), GBP(1'000)});
-
2478 env(rate(gw, 1.25));
-
2479 env.close();
-
2480
-
2481 AMM amm(env, bob, GBP(1'000), USD(1'000));
-
2482
-
2483 env(pay(alice, carol, USD(100)),
-
2484 path(~USD),
-
2485 sendmax(GBP(150)),
-
2486 txflags(tfNoRippleDirect | tfPartialPayment));
-
2487 env.close();
-
2488
-
2489 // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
-
2490 // 1,000 - 120*1.25 = 850GBP
-
2491 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
-
2492 if (!features[fixAMMv1_1])
-
2493 {
-
2494 // 120GBP is swapped in for 107.1428USD
-
2495 BEAST_EXPECT(amm.expectBalances(
-
2496 GBP(1'120),
-
2497 STAmount{USD, UINT64_C(892'8571428571428), -13},
-
2498 amm.tokens()));
-
2499 }
-
2500 else
-
2501 {
-
2502 BEAST_EXPECT(amm.expectBalances(
-
2503 GBP(1'120),
-
2504 STAmount{USD, UINT64_C(892'8571428571429), -13},
-
2505 amm.tokens()));
-
2506 }
-
2507 // 25% of 85.7142USD is paid in tr fee
-
2508 // 85.7142*1.25 = 107.1428USD
-
2509 BEAST_EXPECT(expectLine(
-
2510 env, carol, STAmount(USD, UINT64_C(1'085'714285714286), -12)));
-
2511 }
-
2512
-
2513 {
-
2514 // Payment via offer and AMM
-
2515 Env env(*this, features);
-
2516 Account const ed("ed");
-
2517
-
2518 fund(
-
2519 env,
-
2520 gw,
-
2521 {alice, bob, carol, ed},
-
2522 XRP(1'000),
-
2523 {USD(1'000), EUR(1'000), GBP(1'000)});
-
2524 env(rate(gw, 1.25));
-
2525 env.close();
-
2526
-
2527 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
-
2528 env.close();
-
2529
-
2530 AMM amm(env, bob, EUR(1'000), USD(1'000));
-
2531
-
2532 env(pay(alice, carol, USD(100)),
-
2533 path(~EUR, ~USD),
-
2534 sendmax(GBP(150)),
-
2535 txflags(tfNoRippleDirect | tfPartialPayment));
-
2536 env.close();
-
2537
-
2538 // alice buys 120EUR with 120GBP via the offer
-
2539 // and pays 25% tr fee on 120GBP
-
2540 // 1,000 - 120*1.25 = 850GBP
-
2541 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
-
2542 // consumed offer is 120GBP/120EUR
-
2543 // ed doesn't pay tr fee
-
2544 BEAST_EXPECT(expectLine(env, ed, EUR(880), GBP(1'120)));
-
2545 BEAST_EXPECT(
-
2546 expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}}));
-
2547 // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR
-
2548 // 96EUR is swapped in for 87.5912USD
-
2549 BEAST_EXPECT(amm.expectBalances(
-
2550 EUR(1'096),
-
2551 STAmount{USD, UINT64_C(912'4087591240876), -13},
-
2552 amm.tokens()));
-
2553 // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD
-
2554 BEAST_EXPECT(expectLine(
-
2555 env, carol, STAmount(USD, UINT64_C(1'070'07299270073), -11)));
-
2556 }
-
2557 {
-
2558 // Payment via AMM, AMM
-
2559 Env env(*this, features);
-
2560 Account const ed("ed");
-
2561
-
2562 fund(
-
2563 env,
-
2564 gw,
-
2565 {alice, bob, carol, ed},
-
2566 XRP(1'000),
-
2567 {USD(1'000), EUR(1'000), GBP(1'000)});
-
2568 env(rate(gw, 1.25));
-
2569 env.close();
-
2570
-
2571 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
-
2572 AMM amm2(env, ed, EUR(1'000), USD(1'000));
-
2573
-
2574 env(pay(alice, carol, USD(100)),
-
2575 path(~EUR, ~USD),
-
2576 sendmax(GBP(150)),
-
2577 txflags(tfNoRippleDirect | tfPartialPayment));
-
2578 env.close();
-
2579
-
2580 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
-
2581 if (!features[fixAMMv1_1])
-
2582 {
-
2583 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
-
2584 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
-
2585 // 107.1428EUR
-
2586 BEAST_EXPECT(amm1.expectBalances(
-
2587 GBP(1'120),
-
2588 STAmount{EUR, UINT64_C(892'8571428571428), -13},
-
2589 amm1.tokens()));
-
2590 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
-
2591 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
-
2592 BEAST_EXPECT(amm2.expectBalances(
-
2593 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
-
2594 STAmount{USD, UINT64_C(921'0526315789471), -13},
-
2595 amm2.tokens()));
-
2596 }
-
2597 else
-
2598 {
-
2599 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
-
2600 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
-
2601 // 107.1428EUR
-
2602 BEAST_EXPECT(amm1.expectBalances(
-
2603 GBP(1'120),
-
2604 STAmount{EUR, UINT64_C(892'8571428571429), -13},
-
2605 amm1.tokens()));
-
2606 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
-
2607 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
-
2608 BEAST_EXPECT(amm2.expectBalances(
-
2609 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
-
2610 STAmount{USD, UINT64_C(921'052631578948), -12},
-
2611 amm2.tokens()));
-
2612 }
-
2613 // 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD
-
2614 BEAST_EXPECT(expectLine(
-
2615 env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12)));
-
2616 }
-
2617 {
-
2618 // AMM offer crossing
-
2619 Env env(*this, features);
-
2620
-
2621 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'100), EUR(1'100)});
-
2622 env(rate(gw, 1.25));
-
2623 env.close();
-
2624
-
2625 AMM amm(env, bob, USD(1'000), EUR(1'100));
-
2626 env(offer(alice, EUR(100), USD(100)));
-
2627 env.close();
-
2628
-
2629 // 100USD is swapped in for 100EUR
-
2630 BEAST_EXPECT(
-
2631 amm.expectBalances(USD(1'100), EUR(1'000), amm.tokens()));
-
2632 // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD
-
2633 BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200)));
-
2634 BEAST_EXPECT(expectOffers(env, alice, 0));
-
2635 }
-
2636
-
2637 {
-
2638 // Payment via AMM with limit quality
-
2639 Env env(*this, features);
-
2640
-
2641 fund(
-
2642 env,
-
2643 gw,
-
2644 {alice, bob, carol},
-
2645 XRP(1'000),
-
2646 {USD(1'000), GBP(1'000)});
-
2647 env(rate(gw, 1.25));
-
2648 env.close();
-
2649
-
2650 AMM amm(env, bob, GBP(1'000), USD(1'000));
-
2651
-
2652 // requested quality limit is 100USD/178.58GBP = 0.55997
-
2653 // trade quality is 100USD/178.5714 = 0.55999
-
2654 env(pay(alice, carol, USD(100)),
-
2655 path(~USD),
-
2656 sendmax(GBP(178.58)),
-
2657 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
2658 env.close();
-
2659
-
2660 // alice buys 125USD with 142.8571GBP and pays 25% tr fee
-
2661 // on 142.8571GBP
-
2662 // 1,000 - 142.8571*1.25 = 821.4285GBP
-
2663 BEAST_EXPECT(expectLine(
-
2664 env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13)));
-
2665 // 142.8571GBP is swapped in for 125USD
-
2666 BEAST_EXPECT(amm.expectBalances(
-
2667 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
-
2668 USD(875),
-
2669 amm.tokens()));
-
2670 // 25% on 100USD is paid in tr fee
-
2671 // 100*1.25 = 125USD
-
2672 BEAST_EXPECT(expectLine(env, carol, USD(1'100)));
-
2673 }
-
2674 {
-
2675 // Payment via AMM with limit quality, deliver less
-
2676 // than requested
-
2677 Env env(*this, features);
-
2678
-
2679 fund(
-
2680 env,
-
2681 gw,
-
2682 {alice, bob, carol},
-
2683 XRP(1'000),
-
2684 {USD(1'200), GBP(1'200)});
-
2685 env(rate(gw, 1.25));
-
2686 env.close();
-
2687
-
2688 AMM amm(env, bob, GBP(1'000), USD(1'200));
-
2689
-
2690 // requested quality limit is 90USD/120GBP = 0.75
-
2691 // trade quality is 22.5USD/30GBP = 0.75
-
2692 env(pay(alice, carol, USD(90)),
-
2693 path(~USD),
-
2694 sendmax(GBP(120)),
-
2695 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
2696 env.close();
-
2697
-
2698 if (!features[fixAMMv1_1])
-
2699 {
-
2700 // alice buys 28.125USD with 24GBP and pays 25% tr fee
-
2701 // on 24GBP
-
2702 // 1,200 - 24*1.25 = 1,170GBP
-
2703 BEAST_EXPECT(expectLine(env, alice, GBP(1'170)));
-
2704 // 24GBP is swapped in for 28.125USD
-
2705 BEAST_EXPECT(amm.expectBalances(
-
2706 GBP(1'024), USD(1'171.875), amm.tokens()));
-
2707 }
-
2708 else
-
2709 {
-
2710 // alice buys 28.125USD with 24GBP and pays 25% tr fee
-
2711 // on 24GBP
-
2712 // 1,200 - 24*1.25 =~ 1,170GBP
-
2713 BEAST_EXPECT(expectLine(
-
2714 env,
-
2715 alice,
-
2716 STAmount{GBP, UINT64_C(1'169'999999999999), -12}));
-
2717 // 24GBP is swapped in for 28.125USD
-
2718 BEAST_EXPECT(amm.expectBalances(
-
2719 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
-
2720 USD(1'171.875),
-
2721 amm.tokens()));
-
2722 }
-
2723 // 25% on 22.5USD is paid in tr fee
-
2724 // 22.5*1.25 = 28.125USD
-
2725 BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
-
2726 }
-
2727 {
-
2728 // Payment via offer and AMM with limit quality, deliver less
-
2729 // than requested
-
2730 Env env(*this, features);
-
2731 Account const ed("ed");
-
2732
-
2733 fund(
-
2734 env,
-
2735 gw,
-
2736 {alice, bob, carol, ed},
-
2737 XRP(1'000),
-
2738 {USD(1'400), EUR(1'400), GBP(1'400)});
-
2739 env(rate(gw, 1.25));
-
2740 env.close();
-
2741
-
2742 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
-
2743 env.close();
-
2744
-
2745 AMM amm(env, bob, EUR(1'000), USD(1'400));
-
2746
-
2747 // requested quality limit is 95USD/140GBP = 0.6785
-
2748 // trade quality is 59.7321USD/88.0262GBP = 0.6785
-
2749 env(pay(alice, carol, USD(95)),
-
2750 path(~EUR, ~USD),
-
2751 sendmax(GBP(140)),
-
2752 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
2753 env.close();
+
2304 // alice buys 40EUR with 40XRP
+
2305 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
+
2306
+
2307 // 40XRP is swapped in for 40USD
+
2308 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
+
2309 XRP(140), USD(100), ammBobXRP_USD.tokens()));
+
2310 // 40USD is swapped in for 40EUR
+
2311 BEAST_EXPECT(ammBobUSD_EUR.expectBalances(
+
2312 USD(140), EUR(100), ammBobUSD_EUR.tokens()));
+
2313 // no other charges on bob
+
2314 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
+
2315 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
+
2316 BEAST_EXPECT(expectLedgerEntryRoot(
+
2317 env, alice, xrpMinusFee(env, 10'000 - 40)));
+
2318 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
+
2319 }
+
2320
+
2321 {
+
2322 // Payment via AMM with limit quality, deliver less
+
2323 // than requested
+
2324 Env env(*this, features);
+
2325
+
2326 fund(
+
2327 env,
+
2328 gw,
+
2329 {alice, bob, carol},
+
2330 XRP(1'000),
+
2331 {USD(1'200), GBP(1'200)});
+
2332 env(rate(gw, 1.25));
+
2333 env.close();
+
2334
+
2335 AMM amm(env, bob, GBP(1'000), USD(1'100));
+
2336
+
2337 // requested quality limit is 90USD/110GBP = 0.8181
+
2338 // trade quality is 77.2727USD/94.4444GBP = 0.8181
+
2339 env(pay(alice, carol, USD(90)),
+
2340 path(~USD),
+
2341 sendmax(GBP(110)),
+
2342 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
2343 env.close();
+
2344
+
2345 if (!features[fixAMMv1_1])
+
2346 {
+
2347 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
+
2348 // on 75.5555GBP
+
2349 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
+
2350 BEAST_EXPECT(expectLine(
+
2351 env,
+
2352 alice,
+
2353 STAmount{GBP, UINT64_C(1'105'555555555555), -12}));
+
2354 // 75.5555GBP is swapped in for 77.7272USD
+
2355 BEAST_EXPECT(amm.expectBalances(
+
2356 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
+
2357 STAmount{USD, UINT64_C(1'022'727272727272), -12},
+
2358 amm.tokens()));
+
2359 }
+
2360 else
+
2361 {
+
2362 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
+
2363 // on 75.5555GBP
+
2364 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
+
2365 BEAST_EXPECT(expectLine(
+
2366 env,
+
2367 alice,
+
2368 STAmount{GBP, UINT64_C(1'105'555555555554), -12}));
+
2369 // 75.5555GBP is swapped in for 77.7272USD
+
2370 BEAST_EXPECT(amm.expectBalances(
+
2371 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
+
2372 STAmount{USD, UINT64_C(1'022'727272727272), -12},
+
2373 amm.tokens()));
+
2374 }
+
2375 BEAST_EXPECT(expectLine(
+
2376 env, carol, STAmount{USD, UINT64_C(1'277'272727272728), -12}));
+
2377 }
+
2378
+
2379 {
+
2380 // AMM offer crossing
+
2381 Env env(*this, features);
+
2382
+
2383 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'200), EUR(1'200)});
+
2384 env(rate(gw, 1.25));
+
2385 env.close();
+
2386
+
2387 AMM amm(env, bob, USD(1'000), EUR(1'150));
+
2388
+
2389 env(offer(alice, EUR(100), USD(100)));
+
2390 env.close();
+
2391
+
2392 if (!features[fixAMMv1_1])
+
2393 {
+
2394 // 95.2380USD is swapped in for 100EUR
+
2395 BEAST_EXPECT(amm.expectBalances(
+
2396 STAmount{USD, UINT64_C(1'095'238095238095), -12},
+
2397 EUR(1'050),
+
2398 amm.tokens()));
+
2399 // alice pays 25% tr fee on 95.2380USD
+
2400 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
+
2401 BEAST_EXPECT(expectLine(
+
2402 env,
+
2403 alice,
+
2404 STAmount{USD, UINT64_C(1'080'952380952381), -12},
+
2405 EUR(1'300)));
+
2406 }
+
2407 else
+
2408 {
+
2409 // 95.2380USD is swapped in for 100EUR
+
2410 BEAST_EXPECT(amm.expectBalances(
+
2411 STAmount{USD, UINT64_C(1'095'238095238096), -12},
+
2412 EUR(1'050),
+
2413 amm.tokens()));
+
2414 // alice pays 25% tr fee on 95.2380USD
+
2415 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
+
2416 BEAST_EXPECT(expectLine(
+
2417 env,
+
2418 alice,
+
2419 STAmount{USD, UINT64_C(1'080'95238095238), -11},
+
2420 EUR(1'300)));
+
2421 }
+
2422 BEAST_EXPECT(expectOffers(env, alice, 0));
+
2423 }
+
2424
+
2425 {
+
2426 // First pass through a strand redeems, second pass issues,
+
2427 // through an offer limiting step is not an endpoint
+
2428 Env env(*this, features);
+
2429 auto const USDA = alice["USD"];
+
2430 auto const USDB = bob["USD"];
+
2431 Account const dan("dan");
+
2432
+
2433 env.fund(XRP(10'000), bob, carol, dan, gw);
+
2434 fund(env, {alice}, XRP(10'000));
+
2435 env(rate(gw, 1.25));
+
2436 env.trust(USD(2'000), alice, bob, carol, dan);
+
2437 env.trust(EUR(2'000), carol, dan);
+
2438 env.trust(USDA(1'000), bob);
+
2439 env.trust(USDB(1'000), gw);
+
2440 env(pay(gw, bob, USD(50)));
+
2441 env(pay(gw, dan, EUR(1'050)));
+
2442 env(pay(gw, dan, USD(1'000)));
+
2443 AMM ammDan(env, dan, USD(1'000), EUR(1'050));
+
2444
+
2445 if (!features[fixAMMv1_1])
+
2446 {
+
2447 // alice -> bob -> gw -> carol. $50 should have transfer fee;
+
2448 // $10, no fee
+
2449 env(pay(alice, carol, EUR(50)),
+
2450 path(bob, gw, ~EUR),
+
2451 sendmax(USDA(60)),
+
2452 txflags(tfNoRippleDirect));
+
2453 BEAST_EXPECT(ammDan.expectBalances(
+
2454 USD(1'050), EUR(1'000), ammDan.tokens()));
+
2455 BEAST_EXPECT(expectLine(env, dan, USD(0)));
+
2456 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
+
2457 BEAST_EXPECT(expectLine(env, bob, USD(-10)));
+
2458 BEAST_EXPECT(expectLine(env, bob, USDA(60)));
+
2459 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
+
2460 }
+
2461 else
+
2462 {
+
2463 // alice -> bob -> gw -> carol. $50 should have transfer fee;
+
2464 // $10, no fee
+
2465 env(pay(alice, carol, EUR(50)),
+
2466 path(bob, gw, ~EUR),
+
2467 sendmax(USDA(60.1)),
+
2468 txflags(tfNoRippleDirect));
+
2469 BEAST_EXPECT(ammDan.expectBalances(
+
2470 STAmount{USD, UINT64_C(1'050'000000000001), -12},
+
2471 EUR(1'000),
+
2472 ammDan.tokens()));
+
2473 BEAST_EXPECT(expectLine(env, dan, USD(0)));
+
2474 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
+
2475 BEAST_EXPECT(expectLine(
+
2476 env, bob, STAmount{USD, INT64_C(-10'000000000001), -12}));
+
2477 BEAST_EXPECT(expectLine(
+
2478 env, bob, STAmount{USDA, UINT64_C(60'000000000001), -12}));
+
2479 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
+
2480 }
+
2481 }
+
2482 }
+
2483
+
2484 void
+
2485 testTransferRateNoOwnerFee(FeatureBitset features)
+
2486 {
+
2487 testcase("No Owner Fee");
+
2488 using namespace jtx;
+
2489
+
2490 {
+
2491 // payment via AMM
+
2492 Env env(*this, features);
+
2493
+
2494 fund(
+
2495 env,
+
2496 gw,
+
2497 {alice, bob, carol},
+
2498 XRP(1'000),
+
2499 {USD(1'000), GBP(1'000)});
+
2500 env(rate(gw, 1.25));
+
2501 env.close();
+
2502
+
2503 AMM amm(env, bob, GBP(1'000), USD(1'000));
+
2504
+
2505 env(pay(alice, carol, USD(100)),
+
2506 path(~USD),
+
2507 sendmax(GBP(150)),
+
2508 txflags(tfNoRippleDirect | tfPartialPayment));
+
2509 env.close();
+
2510
+
2511 // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
+
2512 // 1,000 - 120*1.25 = 850GBP
+
2513 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
+
2514 if (!features[fixAMMv1_1])
+
2515 {
+
2516 // 120GBP is swapped in for 107.1428USD
+
2517 BEAST_EXPECT(amm.expectBalances(
+
2518 GBP(1'120),
+
2519 STAmount{USD, UINT64_C(892'8571428571428), -13},
+
2520 amm.tokens()));
+
2521 }
+
2522 else
+
2523 {
+
2524 BEAST_EXPECT(amm.expectBalances(
+
2525 GBP(1'120),
+
2526 STAmount{USD, UINT64_C(892'8571428571429), -13},
+
2527 amm.tokens()));
+
2528 }
+
2529 // 25% of 85.7142USD is paid in tr fee
+
2530 // 85.7142*1.25 = 107.1428USD
+
2531 BEAST_EXPECT(expectLine(
+
2532 env, carol, STAmount(USD, UINT64_C(1'085'714285714286), -12)));
+
2533 }
+
2534
+
2535 {
+
2536 // Payment via offer and AMM
+
2537 Env env(*this, features);
+
2538 Account const ed("ed");
+
2539
+
2540 fund(
+
2541 env,
+
2542 gw,
+
2543 {alice, bob, carol, ed},
+
2544 XRP(1'000),
+
2545 {USD(1'000), EUR(1'000), GBP(1'000)});
+
2546 env(rate(gw, 1.25));
+
2547 env.close();
+
2548
+
2549 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
+
2550 env.close();
+
2551
+
2552 AMM amm(env, bob, EUR(1'000), USD(1'000));
+
2553
+
2554 env(pay(alice, carol, USD(100)),
+
2555 path(~EUR, ~USD),
+
2556 sendmax(GBP(150)),
+
2557 txflags(tfNoRippleDirect | tfPartialPayment));
+
2558 env.close();
+
2559
+
2560 // alice buys 120EUR with 120GBP via the offer
+
2561 // and pays 25% tr fee on 120GBP
+
2562 // 1,000 - 120*1.25 = 850GBP
+
2563 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
+
2564 // consumed offer is 120GBP/120EUR
+
2565 // ed doesn't pay tr fee
+
2566 BEAST_EXPECT(expectLine(env, ed, EUR(880), GBP(1'120)));
+
2567 BEAST_EXPECT(
+
2568 expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}}));
+
2569 // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR
+
2570 // 96EUR is swapped in for 87.5912USD
+
2571 BEAST_EXPECT(amm.expectBalances(
+
2572 EUR(1'096),
+
2573 STAmount{USD, UINT64_C(912'4087591240876), -13},
+
2574 amm.tokens()));
+
2575 // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD
+
2576 BEAST_EXPECT(expectLine(
+
2577 env, carol, STAmount(USD, UINT64_C(1'070'07299270073), -11)));
+
2578 }
+
2579 {
+
2580 // Payment via AMM, AMM
+
2581 Env env(*this, features);
+
2582 Account const ed("ed");
+
2583
+
2584 fund(
+
2585 env,
+
2586 gw,
+
2587 {alice, bob, carol, ed},
+
2588 XRP(1'000),
+
2589 {USD(1'000), EUR(1'000), GBP(1'000)});
+
2590 env(rate(gw, 1.25));
+
2591 env.close();
+
2592
+
2593 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
+
2594 AMM amm2(env, ed, EUR(1'000), USD(1'000));
+
2595
+
2596 env(pay(alice, carol, USD(100)),
+
2597 path(~EUR, ~USD),
+
2598 sendmax(GBP(150)),
+
2599 txflags(tfNoRippleDirect | tfPartialPayment));
+
2600 env.close();
+
2601
+
2602 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
+
2603 if (!features[fixAMMv1_1])
+
2604 {
+
2605 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
+
2606 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
+
2607 // 107.1428EUR
+
2608 BEAST_EXPECT(amm1.expectBalances(
+
2609 GBP(1'120),
+
2610 STAmount{EUR, UINT64_C(892'8571428571428), -13},
+
2611 amm1.tokens()));
+
2612 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
+
2613 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
+
2614 BEAST_EXPECT(amm2.expectBalances(
+
2615 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
+
2616 STAmount{USD, UINT64_C(921'0526315789471), -13},
+
2617 amm2.tokens()));
+
2618 }
+
2619 else
+
2620 {
+
2621 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
+
2622 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
+
2623 // 107.1428EUR
+
2624 BEAST_EXPECT(amm1.expectBalances(
+
2625 GBP(1'120),
+
2626 STAmount{EUR, UINT64_C(892'8571428571429), -13},
+
2627 amm1.tokens()));
+
2628 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
+
2629 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
+
2630 BEAST_EXPECT(amm2.expectBalances(
+
2631 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
+
2632 STAmount{USD, UINT64_C(921'052631578948), -12},
+
2633 amm2.tokens()));
+
2634 }
+
2635 // 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD
+
2636 BEAST_EXPECT(expectLine(
+
2637 env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12)));
+
2638 }
+
2639 {
+
2640 // AMM offer crossing
+
2641 Env env(*this, features);
+
2642
+
2643 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'100), EUR(1'100)});
+
2644 env(rate(gw, 1.25));
+
2645 env.close();
+
2646
+
2647 AMM amm(env, bob, USD(1'000), EUR(1'100));
+
2648 env(offer(alice, EUR(100), USD(100)));
+
2649 env.close();
+
2650
+
2651 // 100USD is swapped in for 100EUR
+
2652 BEAST_EXPECT(
+
2653 amm.expectBalances(USD(1'100), EUR(1'000), amm.tokens()));
+
2654 // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD
+
2655 BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200)));
+
2656 BEAST_EXPECT(expectOffers(env, alice, 0));
+
2657 }
+
2658
+
2659 {
+
2660 // Payment via AMM with limit quality
+
2661 Env env(*this, features);
+
2662
+
2663 fund(
+
2664 env,
+
2665 gw,
+
2666 {alice, bob, carol},
+
2667 XRP(1'000),
+
2668 {USD(1'000), GBP(1'000)});
+
2669 env(rate(gw, 1.25));
+
2670 env.close();
+
2671
+
2672 AMM amm(env, bob, GBP(1'000), USD(1'000));
+
2673
+
2674 // requested quality limit is 100USD/178.58GBP = 0.55997
+
2675 // trade quality is 100USD/178.5714 = 0.55999
+
2676 env(pay(alice, carol, USD(100)),
+
2677 path(~USD),
+
2678 sendmax(GBP(178.58)),
+
2679 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
2680 env.close();
+
2681
+
2682 // alice buys 125USD with 142.8571GBP and pays 25% tr fee
+
2683 // on 142.8571GBP
+
2684 // 1,000 - 142.8571*1.25 = 821.4285GBP
+
2685 BEAST_EXPECT(expectLine(
+
2686 env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13)));
+
2687 // 142.8571GBP is swapped in for 125USD
+
2688 BEAST_EXPECT(amm.expectBalances(
+
2689 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
+
2690 USD(875),
+
2691 amm.tokens()));
+
2692 // 25% on 100USD is paid in tr fee
+
2693 // 100*1.25 = 125USD
+
2694 BEAST_EXPECT(expectLine(env, carol, USD(1'100)));
+
2695 }
+
2696 {
+
2697 // Payment via AMM with limit quality, deliver less
+
2698 // than requested
+
2699 Env env(*this, features);
+
2700
+
2701 fund(
+
2702 env,
+
2703 gw,
+
2704 {alice, bob, carol},
+
2705 XRP(1'000),
+
2706 {USD(1'200), GBP(1'200)});
+
2707 env(rate(gw, 1.25));
+
2708 env.close();
+
2709
+
2710 AMM amm(env, bob, GBP(1'000), USD(1'200));
+
2711
+
2712 // requested quality limit is 90USD/120GBP = 0.75
+
2713 // trade quality is 22.5USD/30GBP = 0.75
+
2714 env(pay(alice, carol, USD(90)),
+
2715 path(~USD),
+
2716 sendmax(GBP(120)),
+
2717 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
2718 env.close();
+
2719
+
2720 if (!features[fixAMMv1_1])
+
2721 {
+
2722 // alice buys 28.125USD with 24GBP and pays 25% tr fee
+
2723 // on 24GBP
+
2724 // 1,200 - 24*1.25 = 1,170GBP
+
2725 BEAST_EXPECT(expectLine(env, alice, GBP(1'170)));
+
2726 // 24GBP is swapped in for 28.125USD
+
2727 BEAST_EXPECT(amm.expectBalances(
+
2728 GBP(1'024), USD(1'171.875), amm.tokens()));
+
2729 }
+
2730 else
+
2731 {
+
2732 // alice buys 28.125USD with 24GBP and pays 25% tr fee
+
2733 // on 24GBP
+
2734 // 1,200 - 24*1.25 =~ 1,170GBP
+
2735 BEAST_EXPECT(expectLine(
+
2736 env,
+
2737 alice,
+
2738 STAmount{GBP, UINT64_C(1'169'999999999999), -12}));
+
2739 // 24GBP is swapped in for 28.125USD
+
2740 BEAST_EXPECT(amm.expectBalances(
+
2741 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
+
2742 USD(1'171.875),
+
2743 amm.tokens()));
+
2744 }
+
2745 // 25% on 22.5USD is paid in tr fee
+
2746 // 22.5*1.25 = 28.125USD
+
2747 BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
+
2748 }
+
2749 {
+
2750 // Payment via offer and AMM with limit quality, deliver less
+
2751 // than requested
+
2752 Env env(*this, features);
+
2753 Account const ed("ed");
2754
-
2755 if (!features[fixAMMv1_1])
-
2756 {
-
2757 // alice buys 70.4210EUR with 70.4210GBP via the offer
-
2758 // and pays 25% tr fee on 70.4210GBP
-
2759 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
-
2760 BEAST_EXPECT(expectLine(
-
2761 env,
-
2762 alice,
-
2763 STAmount{GBP, UINT64_C(1'311'973684210527), -12}));
-
2764 // ed doesn't pay tr fee, the balances reflect consumed offer
-
2765 // 70.4210GBP/70.4210EUR
-
2766 BEAST_EXPECT(expectLine(
-
2767 env,
-
2768 ed,
-
2769 STAmount{EUR, UINT64_C(1'329'578947368421), -12},
-
2770 STAmount{GBP, UINT64_C(1'470'421052631579), -12}));
-
2771 BEAST_EXPECT(expectOffers(
-
2772 env,
-
2773 ed,
-
2774 1,
-
2775 {Amounts{
-
2776 STAmount{GBP, UINT64_C(929'5789473684212), -13},
-
2777 STAmount{EUR, UINT64_C(929'5789473684212), -13}}}));
-
2778 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
-
2779 // 56.3368EUR is swapped in for 74.6651USD
-
2780 BEAST_EXPECT(amm.expectBalances(
-
2781 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
-
2782 STAmount{USD, UINT64_C(1'325'334821428571), -12},
-
2783 amm.tokens()));
-
2784 }
-
2785 else
-
2786 {
-
2787 // alice buys 70.4210EUR with 70.4210GBP via the offer
-
2788 // and pays 25% tr fee on 70.4210GBP
-
2789 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
-
2790 BEAST_EXPECT(expectLine(
-
2791 env,
-
2792 alice,
-
2793 STAmount{GBP, UINT64_C(1'311'973684210525), -12}));
-
2794 // ed doesn't pay tr fee, the balances reflect consumed offer
-
2795 // 70.4210GBP/70.4210EUR
-
2796 BEAST_EXPECT(expectLine(
-
2797 env,
-
2798 ed,
-
2799 STAmount{EUR, UINT64_C(1'329'57894736842), -11},
-
2800 STAmount{GBP, UINT64_C(1'470'42105263158), -11}));
-
2801 BEAST_EXPECT(expectOffers(
-
2802 env,
-
2803 ed,
-
2804 1,
-
2805 {Amounts{
-
2806 STAmount{GBP, UINT64_C(929'57894736842), -11},
-
2807 STAmount{EUR, UINT64_C(929'57894736842), -11}}}));
-
2808 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
-
2809 // 56.3368EUR is swapped in for 74.6651USD
-
2810 BEAST_EXPECT(amm.expectBalances(
-
2811 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
-
2812 STAmount{USD, UINT64_C(1'325'334821428571), -12},
-
2813 amm.tokens()));
-
2814 }
-
2815 // 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD
-
2816 BEAST_EXPECT(expectLine(
-
2817 env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12)));
-
2818 }
-
2819 {
-
2820 // Payment via AMM and offer with limit quality, deliver less
-
2821 // than requested
-
2822 Env env(*this, features);
-
2823 Account const ed("ed");
-
2824
-
2825 fund(
-
2826 env,
-
2827 gw,
-
2828 {alice, bob, carol, ed},
-
2829 XRP(1'000),
-
2830 {USD(1'400), EUR(1'400), GBP(1'400)});
-
2831 env(rate(gw, 1.25));
-
2832 env.close();
-
2833
-
2834 AMM amm(env, bob, GBP(1'000), EUR(1'000));
-
2835
-
2836 env(offer(ed, EUR(1'000), USD(1'400)), txflags(tfPassive));
-
2837 env.close();
-
2838
-
2839 // requested quality limit is 95USD/140GBP = 0.6785
-
2840 // trade quality is 47.7857USD/70.4210GBP = 0.6785
-
2841 env(pay(alice, carol, USD(95)),
-
2842 path(~EUR, ~USD),
-
2843 sendmax(GBP(140)),
-
2844 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
2845 env.close();
+
2755 fund(
+
2756 env,
+
2757 gw,
+
2758 {alice, bob, carol, ed},
+
2759 XRP(1'000),
+
2760 {USD(1'400), EUR(1'400), GBP(1'400)});
+
2761 env(rate(gw, 1.25));
+
2762 env.close();
+
2763
+
2764 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
+
2765 env.close();
+
2766
+
2767 AMM amm(env, bob, EUR(1'000), USD(1'400));
+
2768
+
2769 // requested quality limit is 95USD/140GBP = 0.6785
+
2770 // trade quality is 59.7321USD/88.0262GBP = 0.6785
+
2771 env(pay(alice, carol, USD(95)),
+
2772 path(~EUR, ~USD),
+
2773 sendmax(GBP(140)),
+
2774 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
2775 env.close();
+
2776
+
2777 if (!features[fixAMMv1_1])
+
2778 {
+
2779 // alice buys 70.4210EUR with 70.4210GBP via the offer
+
2780 // and pays 25% tr fee on 70.4210GBP
+
2781 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
+
2782 BEAST_EXPECT(expectLine(
+
2783 env,
+
2784 alice,
+
2785 STAmount{GBP, UINT64_C(1'311'973684210527), -12}));
+
2786 // ed doesn't pay tr fee, the balances reflect consumed offer
+
2787 // 70.4210GBP/70.4210EUR
+
2788 BEAST_EXPECT(expectLine(
+
2789 env,
+
2790 ed,
+
2791 STAmount{EUR, UINT64_C(1'329'578947368421), -12},
+
2792 STAmount{GBP, UINT64_C(1'470'421052631579), -12}));
+
2793 BEAST_EXPECT(expectOffers(
+
2794 env,
+
2795 ed,
+
2796 1,
+
2797 {Amounts{
+
2798 STAmount{GBP, UINT64_C(929'5789473684212), -13},
+
2799 STAmount{EUR, UINT64_C(929'5789473684212), -13}}}));
+
2800 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
+
2801 // 56.3368EUR is swapped in for 74.6651USD
+
2802 BEAST_EXPECT(amm.expectBalances(
+
2803 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
+
2804 STAmount{USD, UINT64_C(1'325'334821428571), -12},
+
2805 amm.tokens()));
+
2806 }
+
2807 else
+
2808 {
+
2809 // alice buys 70.4210EUR with 70.4210GBP via the offer
+
2810 // and pays 25% tr fee on 70.4210GBP
+
2811 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
+
2812 BEAST_EXPECT(expectLine(
+
2813 env,
+
2814 alice,
+
2815 STAmount{GBP, UINT64_C(1'311'973684210525), -12}));
+
2816 // ed doesn't pay tr fee, the balances reflect consumed offer
+
2817 // 70.4210GBP/70.4210EUR
+
2818 BEAST_EXPECT(expectLine(
+
2819 env,
+
2820 ed,
+
2821 STAmount{EUR, UINT64_C(1'329'57894736842), -11},
+
2822 STAmount{GBP, UINT64_C(1'470'42105263158), -11}));
+
2823 BEAST_EXPECT(expectOffers(
+
2824 env,
+
2825 ed,
+
2826 1,
+
2827 {Amounts{
+
2828 STAmount{GBP, UINT64_C(929'57894736842), -11},
+
2829 STAmount{EUR, UINT64_C(929'57894736842), -11}}}));
+
2830 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
+
2831 // 56.3368EUR is swapped in for 74.6651USD
+
2832 BEAST_EXPECT(amm.expectBalances(
+
2833 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
+
2834 STAmount{USD, UINT64_C(1'325'334821428571), -12},
+
2835 amm.tokens()));
+
2836 }
+
2837 // 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD
+
2838 BEAST_EXPECT(expectLine(
+
2839 env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12)));
+
2840 }
+
2841 {
+
2842 // Payment via AMM and offer with limit quality, deliver less
+
2843 // than requested
+
2844 Env env(*this, features);
+
2845 Account const ed("ed");
2846
-
2847 if (!features[fixAMMv1_1])
-
2848 {
-
2849 // alice buys 53.3322EUR with 56.3368GBP via the amm
-
2850 // and pays 25% tr fee on 56.3368GBP
-
2851 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
-
2852 BEAST_EXPECT(expectLine(
-
2853 env,
-
2854 alice,
-
2855 STAmount{GBP, UINT64_C(1'329'578947368421), -12}));
-
2858 // 56.3368GBP is swapped in for 53.3322EUR
-
2859 BEAST_EXPECT(amm.expectBalances(
-
2860 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
-
2861 STAmount{EUR, UINT64_C(946'6677295918366), -13},
-
2862 amm.tokens()));
-
2863 }
-
2864 else
-
2865 {
-
2866 // alice buys 53.3322EUR with 56.3368GBP via the amm
-
2867 // and pays 25% tr fee on 56.3368GBP
-
2868 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
-
2869 BEAST_EXPECT(expectLine(
-
2870 env,
-
2871 alice,
-
2872 STAmount{GBP, UINT64_C(1'329'57894736842), -11}));
-
2875 // 56.3368GBP is swapped in for 53.3322EUR
-
2876 BEAST_EXPECT(amm.expectBalances(
-
2877 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
-
2878 STAmount{EUR, UINT64_C(946'6677295918366), -13},
-
2879 amm.tokens()));
-
2880 }
-
2881 // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
-
2882 // 42.6658EUR/59.7321USD
-
2883 BEAST_EXPECT(expectLine(
-
2884 env,
-
2885 ed,
-
2886 STAmount{USD, UINT64_C(1'340'267857142857), -12},
-
2887 STAmount{EUR, UINT64_C(1'442'665816326531), -12}));
-
2888 BEAST_EXPECT(expectOffers(
-
2889 env,
-
2890 ed,
-
2891 1,
-
2892 {Amounts{
-
2893 STAmount{EUR, UINT64_C(957'3341836734693), -13},
-
2894 STAmount{USD, UINT64_C(1'340'267857142857), -12}}}));
-
2895 // 25% on 47.7857USD is paid in tr fee 47.7857*1.25 = 59.7321USD
-
2896 BEAST_EXPECT(expectLine(
-
2897 env, carol, STAmount(USD, UINT64_C(1'447'785714285714), -12)));
-
2898 }
-
2899 {
-
2900 // Payment via AMM, AMM with limit quality, deliver less
-
2901 // than requested
-
2902 Env env(*this, features);
-
2903 Account const ed("ed");
-
2904
-
2905 fund(
+
2847 fund(
+
2848 env,
+
2849 gw,
+
2850 {alice, bob, carol, ed},
+
2851 XRP(1'000),
+
2852 {USD(1'400), EUR(1'400), GBP(1'400)});
+
2853 env(rate(gw, 1.25));
+
2854 env.close();
+
2855
+
2856 AMM amm(env, bob, GBP(1'000), EUR(1'000));
+
2857
+
2858 env(offer(ed, EUR(1'000), USD(1'400)), txflags(tfPassive));
+
2859 env.close();
+
2860
+
2861 // requested quality limit is 95USD/140GBP = 0.6785
+
2862 // trade quality is 47.7857USD/70.4210GBP = 0.6785
+
2863 env(pay(alice, carol, USD(95)),
+
2864 path(~EUR, ~USD),
+
2865 sendmax(GBP(140)),
+
2866 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
2867 env.close();
+
2868
+
2869 if (!features[fixAMMv1_1])
+
2870 {
+
2871 // alice buys 53.3322EUR with 56.3368GBP via the amm
+
2872 // and pays 25% tr fee on 56.3368GBP
+
2873 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
+
2874 BEAST_EXPECT(expectLine(
+
2875 env,
+
2876 alice,
+
2877 STAmount{GBP, UINT64_C(1'329'578947368421), -12}));
+
2880 // 56.3368GBP is swapped in for 53.3322EUR
+
2881 BEAST_EXPECT(amm.expectBalances(
+
2882 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
+
2883 STAmount{EUR, UINT64_C(946'6677295918366), -13},
+
2884 amm.tokens()));
+
2885 }
+
2886 else
+
2887 {
+
2888 // alice buys 53.3322EUR with 56.3368GBP via the amm
+
2889 // and pays 25% tr fee on 56.3368GBP
+
2890 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
+
2891 BEAST_EXPECT(expectLine(
+
2892 env,
+
2893 alice,
+
2894 STAmount{GBP, UINT64_C(1'329'57894736842), -11}));
+
2897 // 56.3368GBP is swapped in for 53.3322EUR
+
2898 BEAST_EXPECT(amm.expectBalances(
+
2899 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
+
2900 STAmount{EUR, UINT64_C(946'6677295918366), -13},
+
2901 amm.tokens()));
+
2902 }
+
2903 // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
+
2904 // 42.6658EUR/59.7321USD
+
2905 BEAST_EXPECT(expectLine(
2906 env,
-
2907 gw,
-
2908 {alice, bob, carol, ed},
-
2909 XRP(1'000),
-
2910 {USD(1'400), EUR(1'400), GBP(1'400)});
-
2911 env(rate(gw, 1.25));
-
2912 env.close();
-
2913
-
2914 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
-
2915 AMM amm2(env, ed, EUR(1'000), USD(1'400));
-
2916
-
2917 // requested quality limit is 90USD/145GBP = 0.6206
-
2918 // trade quality is 66.7432USD/107.5308GBP = 0.6206
-
2919 env(pay(alice, carol, USD(90)),
-
2920 path(~EUR, ~USD),
-
2921 sendmax(GBP(145)),
-
2922 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
2923 env.close();
-
2924
-
2925 if (!features[fixAMMv1_1])
-
2926 {
-
2927 // alice buys 53.3322EUR with 107.5308GBP
-
2928 // 25% on 86.0246GBP is paid in tr fee
-
2929 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
-
2930 BEAST_EXPECT(expectLine(
-
2931 env,
-
2932 alice,
-
2933 STAmount{GBP, UINT64_C(1'292'469135802469), -12}));
-
2934 // 86.0246GBP is swapped in for 79.2106EUR
-
2935 BEAST_EXPECT(amm1.expectBalances(
-
2936 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
-
2937 STAmount{EUR, UINT64_C(920'78937795562), -11},
-
2938 amm1.tokens()));
-
2939 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
-
2940 // 63.3684EUR is swapped in for 83.4291USD
-
2941 BEAST_EXPECT(amm2.expectBalances(
-
2942 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
-
2943 STAmount{USD, UINT64_C(1'316'570881226053), -12},
-
2944 amm2.tokens()));
-
2945 }
-
2946 else
-
2947 {
-
2948 // alice buys 53.3322EUR with 107.5308GBP
-
2949 // 25% on 86.0246GBP is paid in tr fee
-
2950 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
-
2951 BEAST_EXPECT(expectLine(
-
2952 env,
-
2953 alice,
-
2954 STAmount{GBP, UINT64_C(1'292'469135802466), -12}));
-
2955 // 86.0246GBP is swapped in for 79.2106EUR
-
2956 BEAST_EXPECT(amm1.expectBalances(
-
2957 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
-
2958 STAmount{EUR, UINT64_C(920'7893779556188), -13},
-
2959 amm1.tokens()));
-
2960 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
-
2961 // 63.3684EUR is swapped in for 83.4291USD
-
2962 BEAST_EXPECT(amm2.expectBalances(
-
2963 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
-
2964 STAmount{USD, UINT64_C(1'316'570881226053), -12},
-
2965 amm2.tokens()));
-
2966 }
-
2967 // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
-
2968 BEAST_EXPECT(expectLine(
-
2969 env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12)));
-
2970 }
-
2971 {
-
2972 // Payment by the issuer via AMM, AMM with limit quality,
-
2973 // deliver less than requested
-
2974 Env env(*this, features);
-
2975
-
2976 fund(
-
2977 env,
-
2978 gw,
-
2979 {alice, bob, carol},
-
2980 XRP(1'000),
-
2981 {USD(1'400), EUR(1'400), GBP(1'400)});
-
2982 env(rate(gw, 1.25));
-
2983 env.close();
-
2984
-
2985 AMM amm1(env, alice, GBP(1'000), EUR(1'000));
-
2986 AMM amm2(env, bob, EUR(1'000), USD(1'400));
-
2987
-
2988 // requested quality limit is 90USD/120GBP = 0.75
-
2989 // trade quality is 81.1111USD/108.1481GBP = 0.75
-
2990 env(pay(gw, carol, USD(90)),
-
2991 path(~EUR, ~USD),
-
2992 sendmax(GBP(120)),
-
2993 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
2994 env.close();
-
2995
-
2996 if (!features[fixAMMv1_1])
-
2997 {
-
2998 // 108.1481GBP is swapped in for 97.5935EUR
-
2999 BEAST_EXPECT(amm1.expectBalances(
-
3000 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
-
3001 STAmount{EUR, UINT64_C(902'4064171122988), -13},
-
3002 amm1.tokens()));
-
3003 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
-
3004 // 78.0748EUR is swapped in for 101.3888USD
-
3005 BEAST_EXPECT(amm2.expectBalances(
-
3006 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
-
3007 STAmount{USD, UINT64_C(1'298'611111111111), -12},
-
3008 amm2.tokens()));
-
3009 }
-
3010 else
-
3011 {
-
3012 // 108.1481GBP is swapped in for 97.5935EUR
-
3013 BEAST_EXPECT(amm1.expectBalances(
-
3014 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
-
3015 STAmount{EUR, UINT64_C(902'4064171122975), -13},
-
3016 amm1.tokens()));
-
3017 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
-
3018 // 78.0748EUR is swapped in for 101.3888USD
-
3019 BEAST_EXPECT(amm2.expectBalances(
-
3020 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
-
3021 STAmount{USD, UINT64_C(1'298'611111111111), -12},
-
3022 amm2.tokens()));
-
3023 }
-
3024 // 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD
-
3025 BEAST_EXPECT(expectLine(
-
3026 env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12}));
-
3027 }
-
3028 }
-
3029
-
3030 void
-
3031 testLimitQuality()
-
3032 {
-
3033 // Single path with amm, offer, and limit quality. The quality limit
-
3034 // is such that the first offer should be taken but the second
-
3035 // should not. The total amount delivered should be the sum of the
-
3036 // two offers and sendMax should be more than the first offer.
-
3037 testcase("limitQuality");
-
3038 using namespace jtx;
-
3039
-
3040 {
-
3041 Env env(*this);
-
3042
-
3043 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
-
3044
-
3045 AMM ammBob(env, bob, XRP(1'000), USD(1'050));
-
3046 env(offer(bob, XRP(100), USD(50)));
-
3047
-
3048 env(pay(alice, carol, USD(100)),
-
3049 path(~USD),
-
3050 sendmax(XRP(100)),
-
3051 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
3052
-
3053 BEAST_EXPECT(
-
3054 ammBob.expectBalances(XRP(1'050), USD(1'000), ammBob.tokens()));
-
3055 BEAST_EXPECT(expectLine(env, carol, USD(2'050)));
-
3056 BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}}));
-
3057 }
-
3058 }
-
3059
-
3060 void
-
3061 testXRPPathLoop()
-
3062 {
-
3063 testcase("Circular XRP");
+
2907 ed,
+
2908 STAmount{USD, UINT64_C(1'340'267857142857), -12},
+
2909 STAmount{EUR, UINT64_C(1'442'665816326531), -12}));
+
2910 BEAST_EXPECT(expectOffers(
+
2911 env,
+
2912 ed,
+
2913 1,
+
2914 {Amounts{
+
2915 STAmount{EUR, UINT64_C(957'3341836734693), -13},
+
2916 STAmount{USD, UINT64_C(1'340'267857142857), -12}}}));
+
2917 // 25% on 47.7857USD is paid in tr fee 47.7857*1.25 = 59.7321USD
+
2918 BEAST_EXPECT(expectLine(
+
2919 env, carol, STAmount(USD, UINT64_C(1'447'785714285714), -12)));
+
2920 }
+
2921 {
+
2922 // Payment via AMM, AMM with limit quality, deliver less
+
2923 // than requested
+
2924 Env env(*this, features);
+
2925 Account const ed("ed");
+
2926
+
2927 fund(
+
2928 env,
+
2929 gw,
+
2930 {alice, bob, carol, ed},
+
2931 XRP(1'000),
+
2932 {USD(1'400), EUR(1'400), GBP(1'400)});
+
2933 env(rate(gw, 1.25));
+
2934 env.close();
+
2935
+
2936 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
+
2937 AMM amm2(env, ed, EUR(1'000), USD(1'400));
+
2938
+
2939 // requested quality limit is 90USD/145GBP = 0.6206
+
2940 // trade quality is 66.7432USD/107.5308GBP = 0.6206
+
2941 env(pay(alice, carol, USD(90)),
+
2942 path(~EUR, ~USD),
+
2943 sendmax(GBP(145)),
+
2944 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
2945 env.close();
+
2946
+
2947 if (!features[fixAMMv1_1])
+
2948 {
+
2949 // alice buys 53.3322EUR with 107.5308GBP
+
2950 // 25% on 86.0246GBP is paid in tr fee
+
2951 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
+
2952 BEAST_EXPECT(expectLine(
+
2953 env,
+
2954 alice,
+
2955 STAmount{GBP, UINT64_C(1'292'469135802469), -12}));
+
2956 // 86.0246GBP is swapped in for 79.2106EUR
+
2957 BEAST_EXPECT(amm1.expectBalances(
+
2958 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
+
2959 STAmount{EUR, UINT64_C(920'78937795562), -11},
+
2960 amm1.tokens()));
+
2961 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
+
2962 // 63.3684EUR is swapped in for 83.4291USD
+
2963 BEAST_EXPECT(amm2.expectBalances(
+
2964 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
+
2965 STAmount{USD, UINT64_C(1'316'570881226053), -12},
+
2966 amm2.tokens()));
+
2967 }
+
2968 else
+
2969 {
+
2970 // alice buys 53.3322EUR with 107.5308GBP
+
2971 // 25% on 86.0246GBP is paid in tr fee
+
2972 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
+
2973 BEAST_EXPECT(expectLine(
+
2974 env,
+
2975 alice,
+
2976 STAmount{GBP, UINT64_C(1'292'469135802466), -12}));
+
2977 // 86.0246GBP is swapped in for 79.2106EUR
+
2978 BEAST_EXPECT(amm1.expectBalances(
+
2979 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
+
2980 STAmount{EUR, UINT64_C(920'7893779556188), -13},
+
2981 amm1.tokens()));
+
2982 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
+
2983 // 63.3684EUR is swapped in for 83.4291USD
+
2984 BEAST_EXPECT(amm2.expectBalances(
+
2985 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
+
2986 STAmount{USD, UINT64_C(1'316'570881226053), -12},
+
2987 amm2.tokens()));
+
2988 }
+
2989 // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
+
2990 BEAST_EXPECT(expectLine(
+
2991 env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12)));
+
2992 }
+
2993 {
+
2994 // Payment by the issuer via AMM, AMM with limit quality,
+
2995 // deliver less than requested
+
2996 Env env(*this, features);
+
2997
+
2998 fund(
+
2999 env,
+
3000 gw,
+
3001 {alice, bob, carol},
+
3002 XRP(1'000),
+
3003 {USD(1'400), EUR(1'400), GBP(1'400)});
+
3004 env(rate(gw, 1.25));
+
3005 env.close();
+
3006
+
3007 AMM amm1(env, alice, GBP(1'000), EUR(1'000));
+
3008 AMM amm2(env, bob, EUR(1'000), USD(1'400));
+
3009
+
3010 // requested quality limit is 90USD/120GBP = 0.75
+
3011 // trade quality is 81.1111USD/108.1481GBP = 0.75
+
3012 env(pay(gw, carol, USD(90)),
+
3013 path(~EUR, ~USD),
+
3014 sendmax(GBP(120)),
+
3015 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3016 env.close();
+
3017
+
3018 if (!features[fixAMMv1_1])
+
3019 {
+
3020 // 108.1481GBP is swapped in for 97.5935EUR
+
3021 BEAST_EXPECT(amm1.expectBalances(
+
3022 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
+
3023 STAmount{EUR, UINT64_C(902'4064171122988), -13},
+
3024 amm1.tokens()));
+
3025 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
+
3026 // 78.0748EUR is swapped in for 101.3888USD
+
3027 BEAST_EXPECT(amm2.expectBalances(
+
3028 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
+
3029 STAmount{USD, UINT64_C(1'298'611111111111), -12},
+
3030 amm2.tokens()));
+
3031 }
+
3032 else
+
3033 {
+
3034 // 108.1481GBP is swapped in for 97.5935EUR
+
3035 BEAST_EXPECT(amm1.expectBalances(
+
3036 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
+
3037 STAmount{EUR, UINT64_C(902'4064171122975), -13},
+
3038 amm1.tokens()));
+
3039 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
+
3040 // 78.0748EUR is swapped in for 101.3888USD
+
3041 BEAST_EXPECT(amm2.expectBalances(
+
3042 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
+
3043 STAmount{USD, UINT64_C(1'298'611111111111), -12},
+
3044 amm2.tokens()));
+
3045 }
+
3046 // 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD
+
3047 BEAST_EXPECT(expectLine(
+
3048 env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12}));
+
3049 }
+
3050 }
+
3051
+
3052 void
+
3053 testLimitQuality()
+
3054 {
+
3055 // Single path with amm, offer, and limit quality. The quality limit
+
3056 // is such that the first offer should be taken but the second
+
3057 // should not. The total amount delivered should be the sum of the
+
3058 // two offers and sendMax should be more than the first offer.
+
3059 testcase("limitQuality");
+
3060 using namespace jtx;
+
3061
+
3062 {
+
3063 Env env(*this);
3064
-
3065 using namespace jtx;
+
3065 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
3066
-
3067 for (auto const withFix : {true, false})
-
3068 {
-
3069 auto const feats = withFix
-
3070 ? supported_amendments()
-
3071 : supported_amendments() - FeatureBitset{fix1781};
-
3072
-
3073 // Payment path starting with XRP
-
3074 Env env(*this, feats);
-
3075 // Note, if alice doesn't have default ripple, then pay
-
3076 // fails with tecPATH_DRY.
-
3077 fund(
-
3078 env,
-
3079 gw,
-
3080 {alice, bob},
-
3081 XRP(10'000),
-
3082 {USD(200), EUR(200)},
-
3083 Fund::All);
-
3084
-
3085 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(101));
-
3086 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(101));
-
3087 env.close();
+
3067 AMM ammBob(env, bob, XRP(1'000), USD(1'050));
+
3068 env(offer(bob, XRP(100), USD(50)));
+
3069
+
3070 env(pay(alice, carol, USD(100)),
+
3071 path(~USD),
+
3072 sendmax(XRP(100)),
+
3073 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3074
+
3075 BEAST_EXPECT(
+
3076 ammBob.expectBalances(XRP(1'050), USD(1'000), ammBob.tokens()));
+
3077 BEAST_EXPECT(expectLine(env, carol, USD(2'050)));
+
3078 BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}}));
+
3079 }
+
3080 }
+
3081
+
3082 void
+
3083 testXRPPathLoop()
+
3084 {
+
3085 testcase("Circular XRP");
+
3086
+
3087 using namespace jtx;
3088
-
3089 TER const expectedTer =
-
3090 withFix ? TER{temBAD_PATH_LOOP} : TER{tesSUCCESS};
-
3091 env(pay(alice, bob, EUR(1)),
-
3092 path(~USD, ~XRP, ~EUR),
-
3093 sendmax(XRP(1)),
-
3094 txflags(tfNoRippleDirect),
-
3095 ter(expectedTer));
-
3096 }
-
3097 {
-
3098 // Payment path ending with XRP
-
3099 Env env(*this);
-
3100 // Note, if alice doesn't have default ripple, then pay fails
-
3101 // with tecPATH_DRY.
-
3102 fund(
-
3103 env,
-
3104 gw,
-
3105 {alice, bob},
-
3106 XRP(10'000),
-
3107 {USD(200), EUR(200)},
-
3108 Fund::All);
-
3109
-
3110 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
-
3111 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
-
3112 // EUR -> //XRP -> //USD ->XRP
-
3113 env(pay(alice, bob, XRP(1)),
-
3114 path(~XRP, ~USD, ~XRP),
-
3115 sendmax(EUR(1)),
+
3089 for (auto const withFix : {true, false})
+
3090 {
+
3091 auto const feats = withFix
+
3092 ? supported_amendments()
+
3093 : supported_amendments() - FeatureBitset{fix1781};
+
3094
+
3095 // Payment path starting with XRP
+
3096 Env env(*this, feats);
+
3097 // Note, if alice doesn't have default ripple, then pay
+
3098 // fails with tecPATH_DRY.
+
3099 fund(
+
3100 env,
+
3101 gw,
+
3102 {alice, bob},
+
3103 XRP(10'000),
+
3104 {USD(200), EUR(200)},
+
3105 Fund::All);
+
3106
+
3107 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(101));
+
3108 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(101));
+
3109 env.close();
+
3110
+
3111 TER const expectedTer =
+
3112 withFix ? TER{temBAD_PATH_LOOP} : TER{tesSUCCESS};
+
3113 env(pay(alice, bob, EUR(1)),
+
3114 path(~USD, ~XRP, ~EUR),
+
3115 sendmax(XRP(1)),
3116 txflags(tfNoRippleDirect),
-
3117 ter(temBAD_PATH_LOOP));
+
3117 ter(expectedTer));
3118 }
3119 {
-
3120 // Payment where loop is formed in the middle of the path, not
-
3121 // on an endpoint
-
3122 auto const JPY = gw["JPY"];
-
3123 Env env(*this);
-
3124 // Note, if alice doesn't have default ripple, then pay fails
-
3125 // with tecPATH_DRY.
-
3126 fund(
-
3127 env,
-
3128 gw,
-
3129 {alice, bob},
-
3130 XRP(10'000),
-
3131 {USD(200), EUR(200), JPY(200)},
-
3132 Fund::All);
-
3133
-
3134 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
-
3135 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
-
3136 AMM ammAliceXRP_JPY(env, alice, XRP(100), JPY(100));
-
3137
-
3138 env(pay(alice, bob, JPY(1)),
-
3139 path(~XRP, ~EUR, ~XRP, ~JPY),
-
3140 sendmax(USD(1)),
-
3141 txflags(tfNoRippleDirect),
-
3142 ter(temBAD_PATH_LOOP));
-
3143 }
-
3144 }
-
3145
-
3146 void
-
3147 testStepLimit(FeatureBitset features)
-
3148 {
-
3149 testcase("Step Limit");
-
3150
-
3151 using namespace jtx;
-
3152 Env env(*this, features);
-
3153 auto const dan = Account("dan");
-
3154 auto const ed = Account("ed");
+
3120 // Payment path ending with XRP
+
3121 Env env(*this);
+
3122 // Note, if alice doesn't have default ripple, then pay fails
+
3123 // with tecPATH_DRY.
+
3124 fund(
+
3125 env,
+
3126 gw,
+
3127 {alice, bob},
+
3128 XRP(10'000),
+
3129 {USD(200), EUR(200)},
+
3130 Fund::All);
+
3131
+
3132 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
+
3133 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
+
3134 // EUR -> //XRP -> //USD ->XRP
+
3135 env(pay(alice, bob, XRP(1)),
+
3136 path(~XRP, ~USD, ~XRP),
+
3137 sendmax(EUR(1)),
+
3138 txflags(tfNoRippleDirect),
+
3139 ter(temBAD_PATH_LOOP));
+
3140 }
+
3141 {
+
3142 // Payment where loop is formed in the middle of the path, not
+
3143 // on an endpoint
+
3144 auto const JPY = gw["JPY"];
+
3145 Env env(*this);
+
3146 // Note, if alice doesn't have default ripple, then pay fails
+
3147 // with tecPATH_DRY.
+
3148 fund(
+
3149 env,
+
3150 gw,
+
3151 {alice, bob},
+
3152 XRP(10'000),
+
3153 {USD(200), EUR(200), JPY(200)},
+
3154 Fund::All);
3155
-
3156 fund(env, gw, {ed}, XRP(100'000'000), {USD(11)});
-
3157 env.fund(XRP(100'000'000), alice, bob, carol, dan);
-
3158 env.trust(USD(1), bob);
-
3159 env(pay(gw, bob, USD(1)));
-
3160 env.trust(USD(1), dan);
-
3161 env(pay(gw, dan, USD(1)));
-
3162 n_offers(env, 2'000, bob, XRP(1), USD(1));
-
3163 n_offers(env, 1, dan, XRP(1), USD(1));
-
3164 AMM ammEd(env, ed, XRP(9), USD(11));
-
3165
-
3166 // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
-
3167 // offer, removes 999 more as unfunded, then hits the step limit.
-
3168 env(offer(alice, USD(1'000), XRP(1'000)));
-
3169 if (!features[fixAMMv1_1])
-
3170 env.require(balance(
-
3171 alice, STAmount{USD, UINT64_C(2'050126257867561), -15}));
-
3172 else
-
3173 env.require(balance(
-
3174 alice, STAmount{USD, UINT64_C(2'050125257867587), -15}));
-
3175 env.require(owners(alice, 2));
-
3176 env.require(balance(bob, USD(0)));
-
3177 env.require(owners(bob, 1'001));
-
3178 env.require(balance(dan, USD(1)));
-
3179 env.require(owners(dan, 2));
-
3180
-
3181 // Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
-
3182 // 1000 offers as unfunded and hits the step limit.
-
3183 env(offer(carol, USD(1'000), XRP(1'000)));
-
3184 env.require(balance(carol, USD(none)));
-
3185 env.require(owners(carol, 1));
-
3186 env.require(balance(bob, USD(0)));
-
3187 env.require(owners(bob, 1));
-
3188 env.require(balance(dan, USD(1)));
-
3189 env.require(owners(dan, 2));
-
3190 }
-
3191
-
3192 void
-
3193 test_convert_all_of_an_asset(FeatureBitset features)
-
3194 {
-
3195 testcase("Convert all of an asset using DeliverMin");
-
3196
-
3197 using namespace jtx;
-
3198
-
3199 {
-
3200 Env env(*this, features);
-
3201 fund(env, gw, {alice, bob, carol}, XRP(10'000));
-
3202 env.trust(USD(100), alice, bob, carol);
-
3203 env(pay(alice, bob, USD(10)),
-
3204 delivermin(USD(10)),
-
3205 ter(temBAD_AMOUNT));
-
3206 env(pay(alice, bob, USD(10)),
-
3207 delivermin(USD(-5)),
-
3208 txflags(tfPartialPayment),
-
3209 ter(temBAD_AMOUNT));
-
3210 env(pay(alice, bob, USD(10)),
-
3211 delivermin(XRP(5)),
-
3212 txflags(tfPartialPayment),
-
3213 ter(temBAD_AMOUNT));
-
3214 env(pay(alice, bob, USD(10)),
-
3215 delivermin(Account(carol)["USD"](5)),
-
3216 txflags(tfPartialPayment),
-
3217 ter(temBAD_AMOUNT));
-
3218 env(pay(alice, bob, USD(10)),
-
3219 delivermin(USD(15)),
-
3220 txflags(tfPartialPayment),
-
3221 ter(temBAD_AMOUNT));
-
3222 env(pay(gw, carol, USD(50)));
-
3223 AMM ammCarol(env, carol, XRP(10), USD(15));
-
3224 env(pay(alice, bob, USD(10)),
-
3225 paths(XRP),
-
3226 delivermin(USD(7)),
-
3227 txflags(tfPartialPayment),
-
3228 sendmax(XRP(5)),
-
3229 ter(tecPATH_PARTIAL));
-
3230 env.require(balance(alice, XRP(9'999.99999)));
-
3231 env.require(balance(bob, XRP(10'000)));
-
3232 }
-
3233
-
3234 {
-
3235 Env env(*this, features);
-
3236 fund(env, gw, {alice, bob}, XRP(10'000));
-
3237 env.trust(USD(1'100), alice, bob);
-
3238 env(pay(gw, bob, USD(1'100)));
-
3239 AMM ammBob(env, bob, XRP(1'000), USD(1'100));
-
3240 env(pay(alice, alice, USD(10'000)),
-
3241 paths(XRP),
-
3242 delivermin(USD(100)),
+
3156 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
+
3157 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
+
3158 AMM ammAliceXRP_JPY(env, alice, XRP(100), JPY(100));
+
3159
+
3160 env(pay(alice, bob, JPY(1)),
+
3161 path(~XRP, ~EUR, ~XRP, ~JPY),
+
3162 sendmax(USD(1)),
+
3163 txflags(tfNoRippleDirect),
+
3164 ter(temBAD_PATH_LOOP));
+
3165 }
+
3166 }
+
3167
+
3168 void
+
3169 testStepLimit(FeatureBitset features)
+
3170 {
+
3171 testcase("Step Limit");
+
3172
+
3173 using namespace jtx;
+
3174 Env env(*this, features);
+
3175 auto const dan = Account("dan");
+
3176 auto const ed = Account("ed");
+
3177
+
3178 fund(env, gw, {ed}, XRP(100'000'000), {USD(11)});
+
3179 env.fund(XRP(100'000'000), alice, bob, carol, dan);
+
3180 env.close();
+
3181 env.trust(USD(1), bob);
+
3182 env(pay(gw, bob, USD(1)));
+
3183 env.trust(USD(1), dan);
+
3184 env(pay(gw, dan, USD(1)));
+
3185 n_offers(env, 2'000, bob, XRP(1), USD(1));
+
3186 n_offers(env, 1, dan, XRP(1), USD(1));
+
3187 AMM ammEd(env, ed, XRP(9), USD(11));
+
3188
+
3189 // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
+
3190 // offer, removes 999 more as unfunded, then hits the step limit.
+
3191 env(offer(alice, USD(1'000), XRP(1'000)));
+
3192 if (!features[fixAMMv1_1])
+
3193 env.require(balance(
+
3194 alice, STAmount{USD, UINT64_C(2'050126257867561), -15}));
+
3195 else
+
3196 env.require(balance(
+
3197 alice, STAmount{USD, UINT64_C(2'050125257867587), -15}));
+
3198 env.require(owners(alice, 2));
+
3199 env.require(balance(bob, USD(0)));
+
3200 env.require(owners(bob, 1'001));
+
3201 env.require(balance(dan, USD(1)));
+
3202 env.require(owners(dan, 2));
+
3203
+
3204 // Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
+
3205 // 1000 offers as unfunded and hits the step limit.
+
3206 env(offer(carol, USD(1'000), XRP(1'000)));
+
3207 env.require(balance(carol, USD(none)));
+
3208 env.require(owners(carol, 1));
+
3209 env.require(balance(bob, USD(0)));
+
3210 env.require(owners(bob, 1));
+
3211 env.require(balance(dan, USD(1)));
+
3212 env.require(owners(dan, 2));
+
3213 }
+
3214
+
3215 void
+
3216 test_convert_all_of_an_asset(FeatureBitset features)
+
3217 {
+
3218 testcase("Convert all of an asset using DeliverMin");
+
3219
+
3220 using namespace jtx;
+
3221
+
3222 {
+
3223 Env env(*this, features);
+
3224 fund(env, gw, {alice, bob, carol}, XRP(10'000));
+
3225 env.trust(USD(100), alice, bob, carol);
+
3226 env(pay(alice, bob, USD(10)),
+
3227 delivermin(USD(10)),
+
3228 ter(temBAD_AMOUNT));
+
3229 env(pay(alice, bob, USD(10)),
+
3230 delivermin(USD(-5)),
+
3231 txflags(tfPartialPayment),
+
3232 ter(temBAD_AMOUNT));
+
3233 env(pay(alice, bob, USD(10)),
+
3234 delivermin(XRP(5)),
+
3235 txflags(tfPartialPayment),
+
3236 ter(temBAD_AMOUNT));
+
3237 env(pay(alice, bob, USD(10)),
+
3238 delivermin(Account(carol)["USD"](5)),
+
3239 txflags(tfPartialPayment),
+
3240 ter(temBAD_AMOUNT));
+
3241 env(pay(alice, bob, USD(10)),
+
3242 delivermin(USD(15)),
3243 txflags(tfPartialPayment),
-
3244 sendmax(XRP(100)));
-
3245 env.require(balance(alice, USD(100)));
-
3246 }
-
3247
-
3248 {
-
3249 Env env(*this, features);
-
3250 fund(env, gw, {alice, bob, carol}, XRP(10'000));
-
3251 env.trust(USD(1'200), bob, carol);
-
3252 env(pay(gw, bob, USD(1'200)));
-
3253 AMM ammBob(env, bob, XRP(5'500), USD(1'200));
-
3254 env(pay(alice, carol, USD(10'000)),
-
3255 paths(XRP),
-
3256 delivermin(USD(200)),
-
3257 txflags(tfPartialPayment),
-
3258 sendmax(XRP(1'000)),
-
3259 ter(tecPATH_PARTIAL));
-
3260 env(pay(alice, carol, USD(10'000)),
-
3261 paths(XRP),
-
3262 delivermin(USD(200)),
-
3263 txflags(tfPartialPayment),
-
3264 sendmax(XRP(1'100)));
-
3265 BEAST_EXPECT(
-
3266 ammBob.expectBalances(XRP(6'600), USD(1'000), ammBob.tokens()));
-
3267 env.require(balance(carol, USD(200)));
-
3268 }
-
3269
-
3270 {
-
3271 auto const dan = Account("dan");
-
3272 Env env(*this, features);
-
3273 fund(env, gw, {alice, bob, carol, dan}, XRP(10'000));
-
3274 env.trust(USD(1'100), bob, carol, dan);
-
3275 env(pay(gw, bob, USD(100)));
-
3276 env(pay(gw, dan, USD(1'100)));
-
3277 env(offer(bob, XRP(100), USD(100)));
-
3278 env(offer(bob, XRP(1'000), USD(100)));
-
3279 AMM ammDan(env, dan, XRP(1'000), USD(1'100));
-
3280 if (!features[fixAMMv1_1])
-
3281 {
-
3282 env(pay(alice, carol, USD(10'000)),
-
3283 paths(XRP),
-
3284 delivermin(USD(200)),
-
3285 txflags(tfPartialPayment),
-
3286 sendmax(XRP(200)));
-
3287 env.require(balance(bob, USD(0)));
-
3288 env.require(balance(carol, USD(200)));
-
3289 BEAST_EXPECT(ammDan.expectBalances(
-
3290 XRP(1'100), USD(1'000), ammDan.tokens()));
-
3291 }
-
3292 else
-
3293 {
-
3294 env(pay(alice, carol, USD(10'000)),
-
3295 paths(XRP),
-
3296 delivermin(USD(200)),
-
3297 txflags(tfPartialPayment),
-
3298 sendmax(XRPAmount(200'000'001)));
-
3299 env.require(balance(bob, USD(0)));
-
3300 env.require(balance(
-
3301 carol, STAmount{USD, UINT64_C(200'00000090909), -11}));
-
3302 BEAST_EXPECT(ammDan.expectBalances(
-
3303 XRPAmount{1'100'000'001},
-
3304 STAmount{USD, UINT64_C(999'99999909091), -11},
-
3305 ammDan.tokens()));
-
3306 }
-
3307 }
-
3308 }
-
3309
-
3310 void
-
3311 testPayment(FeatureBitset features)
-
3312 {
-
3313 testcase("Payment");
-
3314
-
3315 using namespace jtx;
-
3316 Account const becky{"becky"};
-
3317
-
3318 bool const supportsPreauth = {features[featureDepositPreauth]};
-
3319
-
3320 // The initial implementation of DepositAuth had a bug where an
-
3321 // account with the DepositAuth flag set could not make a payment
-
3322 // to itself. That bug was fixed in the DepositPreauth amendment.
-
3323 Env env(*this, features);
-
3324 fund(env, gw, {alice, becky}, XRP(5'000));
-
3325 env.close();
-
3326
-
3327 env.trust(USD(1'000), alice);
-
3328 env.trust(USD(1'000), becky);
-
3329 env.close();
-
3330
-
3331 env(pay(gw, alice, USD(500)));
-
3332 env.close();
-
3333
-
3334 AMM ammAlice(env, alice, XRP(100), USD(140));
+
3244 ter(temBAD_AMOUNT));
+
3245 env(pay(gw, carol, USD(50)));
+
3246 AMM ammCarol(env, carol, XRP(10), USD(15));
+
3247 env(pay(alice, bob, USD(10)),
+
3248 paths(XRP),
+
3249 delivermin(USD(7)),
+
3250 txflags(tfPartialPayment),
+
3251 sendmax(XRP(5)),
+
3252 ter(tecPATH_PARTIAL));
+
3253 env.require(balance(
+
3254 alice,
+
3255 drops(10'000'000'000 - env.current()->fees().base.drops())));
+
3256 env.require(balance(bob, XRP(10'000)));
+
3257 }
+
3258
+
3259 {
+
3260 Env env(*this, features);
+
3261 fund(env, gw, {alice, bob}, XRP(10'000));
+
3262 env.trust(USD(1'100), alice, bob);
+
3263 env(pay(gw, bob, USD(1'100)));
+
3264 AMM ammBob(env, bob, XRP(1'000), USD(1'100));
+
3265 env(pay(alice, alice, USD(10'000)),
+
3266 paths(XRP),
+
3267 delivermin(USD(100)),
+
3268 txflags(tfPartialPayment),
+
3269 sendmax(XRP(100)));
+
3270 env.require(balance(alice, USD(100)));
+
3271 }
+
3272
+
3273 {
+
3274 Env env(*this, features);
+
3275 fund(env, gw, {alice, bob, carol}, XRP(10'000));
+
3276 env.trust(USD(1'200), bob, carol);
+
3277 env(pay(gw, bob, USD(1'200)));
+
3278 AMM ammBob(env, bob, XRP(5'500), USD(1'200));
+
3279 env(pay(alice, carol, USD(10'000)),
+
3280 paths(XRP),
+
3281 delivermin(USD(200)),
+
3282 txflags(tfPartialPayment),
+
3283 sendmax(XRP(1'000)),
+
3284 ter(tecPATH_PARTIAL));
+
3285 env(pay(alice, carol, USD(10'000)),
+
3286 paths(XRP),
+
3287 delivermin(USD(200)),
+
3288 txflags(tfPartialPayment),
+
3289 sendmax(XRP(1'100)));
+
3290 BEAST_EXPECT(
+
3291 ammBob.expectBalances(XRP(6'600), USD(1'000), ammBob.tokens()));
+
3292 env.require(balance(carol, USD(200)));
+
3293 }
+
3294
+
3295 {
+
3296 auto const dan = Account("dan");
+
3297 Env env(*this, features);
+
3298 fund(env, gw, {alice, bob, carol, dan}, XRP(10'000));
+
3299 env.close();
+
3300 env.trust(USD(1'100), bob, carol, dan);
+
3301 env(pay(gw, bob, USD(100)));
+
3302 env(pay(gw, dan, USD(1'100)));
+
3303 env(offer(bob, XRP(100), USD(100)));
+
3304 env(offer(bob, XRP(1'000), USD(100)));
+
3305 AMM ammDan(env, dan, XRP(1'000), USD(1'100));
+
3306 if (!features[fixAMMv1_1])
+
3307 {
+
3308 env(pay(alice, carol, USD(10'000)),
+
3309 paths(XRP),
+
3310 delivermin(USD(200)),
+
3311 txflags(tfPartialPayment),
+
3312 sendmax(XRP(200)));
+
3313 env.require(balance(bob, USD(0)));
+
3314 env.require(balance(carol, USD(200)));
+
3315 BEAST_EXPECT(ammDan.expectBalances(
+
3316 XRP(1'100), USD(1'000), ammDan.tokens()));
+
3317 }
+
3318 else
+
3319 {
+
3320 env(pay(alice, carol, USD(10'000)),
+
3321 paths(XRP),
+
3322 delivermin(USD(200)),
+
3323 txflags(tfPartialPayment),
+
3324 sendmax(XRPAmount(200'000'001)));
+
3325 env.require(balance(bob, USD(0)));
+
3326 env.require(balance(
+
3327 carol, STAmount{USD, UINT64_C(200'00000090909), -11}));
+
3328 BEAST_EXPECT(ammDan.expectBalances(
+
3329 XRPAmount{1'100'000'001},
+
3330 STAmount{USD, UINT64_C(999'99999909091), -11},
+
3331 ammDan.tokens()));
+
3332 }
+
3333 }
+
3334 }
3335
-
3336 // becky pays herself USD (10) by consuming part of alice's offer.
-
3337 // Make sure the payment works if PaymentAuth is not involved.
-
3338 env(pay(becky, becky, USD(10)), path(~USD), sendmax(XRP(10)));
-
3339 env.close();
-
3340 BEAST_EXPECT(ammAlice.expectBalances(
-
3341 XRPAmount(107'692'308), USD(130), ammAlice.tokens()));
-
3342
-
3343 // becky decides to require authorization for deposits.
-
3344 env(fset(becky, asfDepositAuth));
-
3345 env.close();
-
3346
-
3347 // becky pays herself again. Whether it succeeds depends on
-
3348 // whether featureDepositPreauth is enabled.
-
3349 TER const expect{
-
3350 supportsPreauth ? TER{tesSUCCESS} : TER{tecNO_PERMISSION}};
-
3351
-
3352 env(pay(becky, becky, USD(10)),
-
3353 path(~USD),
-
3354 sendmax(XRP(10)),
-
3355 ter(expect));
+
3336 void
+
3337 testPayment(FeatureBitset features)
+
3338 {
+
3339 testcase("Payment");
+
3340
+
3341 using namespace jtx;
+
3342 Account const becky{"becky"};
+
3343
+
3344 bool const supportsPreauth = {features[featureDepositPreauth]};
+
3345
+
3346 // The initial implementation of DepositAuth had a bug where an
+
3347 // account with the DepositAuth flag set could not make a payment
+
3348 // to itself. That bug was fixed in the DepositPreauth amendment.
+
3349 Env env(*this, features);
+
3350 fund(env, gw, {alice, becky}, XRP(5'000));
+
3351 env.close();
+
3352
+
3353 env.trust(USD(1'000), alice);
+
3354 env.trust(USD(1'000), becky);
+
3355 env.close();
3356
-
3357 env.close();
-
3358 }
+
3357 env(pay(gw, alice, USD(500)));
+
3358 env.close();
3359
-
3360 void
-
3361 testPayIOU()
-
3362 {
-
3363 // Exercise IOU payments and non-direct XRP payments to an account
-
3364 // that has the lsfDepositAuth flag set.
-
3365 testcase("Pay IOU");
-
3366
-
3367 using namespace jtx;
+
3360 AMM ammAlice(env, alice, XRP(100), USD(140));
+
3361
+
3362 // becky pays herself USD (10) by consuming part of alice's offer.
+
3363 // Make sure the payment works if PaymentAuth is not involved.
+
3364 env(pay(becky, becky, USD(10)), path(~USD), sendmax(XRP(10)));
+
3365 env.close();
+
3366 BEAST_EXPECT(ammAlice.expectBalances(
+
3367 XRPAmount(107'692'308), USD(130), ammAlice.tokens()));
3368
-
3369 Env env(*this);
-
3370
-
3371 fund(env, gw, {alice, bob, carol}, XRP(10'000));
-
3372 env.trust(USD(1'000), alice, bob, carol);
-
3373 env.close();
-
3374
-
3375 env(pay(gw, alice, USD(150)));
-
3376 env(pay(gw, carol, USD(150)));
-
3377 AMM ammCarol(env, carol, USD(100), XRPAmount(101));
-
3378
-
3379 // Make sure bob's trust line is all set up so he can receive USD.
-
3380 env(pay(alice, bob, USD(50)));
-
3381 env.close();
+
3369 // becky decides to require authorization for deposits.
+
3370 env(fset(becky, asfDepositAuth));
+
3371 env.close();
+
3372
+
3373 // becky pays herself again. Whether it succeeds depends on
+
3374 // whether featureDepositPreauth is enabled.
+
3375 TER const expect{
+
3376 supportsPreauth ? TER{tesSUCCESS} : TER{tecNO_PERMISSION}};
+
3377
+
3378 env(pay(becky, becky, USD(10)),
+
3379 path(~USD),
+
3380 sendmax(XRP(10)),
+
3381 ter(expect));
3382
-
3383 // bob sets the lsfDepositAuth flag.
-
3384 env(fset(bob, asfDepositAuth), require(flags(bob, asfDepositAuth)));
-
3385 env.close();
-
3386
-
3387 // None of the following payments should succeed.
-
3388 auto failedIouPayments = [this, &env]() {
-
3389 env.require(flags(bob, asfDepositAuth));
-
3390
-
3391 // Capture bob's balances before hand to confirm they don't
-
3392 // change.
-
3393 PrettyAmount const bobXrpBalance{env.balance(bob, XRP)};
-
3394 PrettyAmount const bobUsdBalance{env.balance(bob, USD)};
-
3395
-
3396 env(pay(alice, bob, USD(50)), ter(tecNO_PERMISSION));
-
3397 env.close();
-
3398
-
3399 // Note that even though alice is paying bob in XRP, the payment
-
3400 // is still not allowed since the payment passes through an
-
3401 // offer.
-
3402 env(pay(alice, bob, drops(1)),
-
3403 sendmax(USD(1)),
-
3404 ter(tecNO_PERMISSION));
-
3405 env.close();
-
3406
-
3407 BEAST_EXPECT(bobXrpBalance == env.balance(bob, XRP));
-
3408 BEAST_EXPECT(bobUsdBalance == env.balance(bob, USD));
-
3409 };
-
3410
-
3411 // Test when bob has an XRP balance > base reserve.
-
3412 failedIouPayments();
-
3413
-
3414 // Set bob's XRP balance == base reserve. Also demonstrate that
-
3415 // bob can make payments while his lsfDepositAuth flag is set.
-
3416 env(pay(bob, alice, USD(25)));
-
3417 env.close();
-
3418
-
3419 {
-
3420 STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)};
-
3421 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
-
3422 env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee));
+
3383 env.close();
+
3384 }
+
3385
+
3386 void
+
3387 testPayIOU()
+
3388 {
+
3389 // Exercise IOU payments and non-direct XRP payments to an account
+
3390 // that has the lsfDepositAuth flag set.
+
3391 testcase("Pay IOU");
+
3392
+
3393 using namespace jtx;
+
3394
+
3395 Env env(*this);
+
3396
+
3397 fund(env, gw, {alice, bob, carol}, XRP(10'000));
+
3398 env.trust(USD(1'000), alice, bob, carol);
+
3399 env.close();
+
3400
+
3401 env(pay(gw, alice, USD(150)));
+
3402 env(pay(gw, carol, USD(150)));
+
3403 AMM ammCarol(env, carol, USD(100), XRPAmount(101));
+
3404
+
3405 // Make sure bob's trust line is all set up so he can receive USD.
+
3406 env(pay(alice, bob, USD(50)));
+
3407 env.close();
+
3408
+
3409 // bob sets the lsfDepositAuth flag.
+
3410 env(fset(bob, asfDepositAuth), require(flags(bob, asfDepositAuth)));
+
3411 env.close();
+
3412
+
3413 // None of the following payments should succeed.
+
3414 auto failedIouPayments = [this, &env]() {
+
3415 env.require(flags(bob, asfDepositAuth));
+
3416
+
3417 // Capture bob's balances before hand to confirm they don't
+
3418 // change.
+
3419 PrettyAmount const bobXrpBalance{env.balance(bob, XRP)};
+
3420 PrettyAmount const bobUsdBalance{env.balance(bob, USD)};
+
3421
+
3422 env(pay(alice, bob, USD(50)), ter(tecNO_PERMISSION));
3423 env.close();
-
3424 }
-
3425
-
3426 // Test when bob's XRP balance == base reserve.
-
3427 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
-
3428 BEAST_EXPECT(env.balance(bob, USD) == USD(25));
-
3429 failedIouPayments();
-
3430
-
3431 // Test when bob has an XRP balance == 0.
-
3432 env(noop(bob), fee(reserve(env, 0)));
-
3433 env.close();
-
3434
-
3435 BEAST_EXPECT(env.balance(bob, XRP) == XRP(0));
-
3436 failedIouPayments();
-
3437
-
3438 // Give bob enough XRP for the fee to clear the lsfDepositAuth flag.
-
3439 env(pay(alice, bob, drops(env.current()->fees().base)));
-
3440
-
3441 // bob clears the lsfDepositAuth and the next payment succeeds.
-
3442 env(fclear(bob, asfDepositAuth));
+
3424
+
3425 // Note that even though alice is paying bob in XRP, the payment
+
3426 // is still not allowed since the payment passes through an
+
3427 // offer.
+
3428 env(pay(alice, bob, drops(1)),
+
3429 sendmax(USD(1)),
+
3430 ter(tecNO_PERMISSION));
+
3431 env.close();
+
3432
+
3433 BEAST_EXPECT(bobXrpBalance == env.balance(bob, XRP));
+
3434 BEAST_EXPECT(bobUsdBalance == env.balance(bob, USD));
+
3435 };
+
3436
+
3437 // Test when bob has an XRP balance > base reserve.
+
3438 failedIouPayments();
+
3439
+
3440 // Set bob's XRP balance == base reserve. Also demonstrate that
+
3441 // bob can make payments while his lsfDepositAuth flag is set.
+
3442 env(pay(bob, alice, USD(25)));
3443 env.close();
3444
-
3445 env(pay(alice, bob, USD(50)));
-
3446 env.close();
-
3447
-
3448 env(pay(alice, bob, drops(1)), sendmax(USD(1)));
-
3449 env.close();
-
3450 BEAST_EXPECT(ammCarol.expectBalances(
-
3451 USD(101), XRPAmount(100), ammCarol.tokens()));
-
3452 }
-
3453
-
3454 void
-
3455 testRippleState(FeatureBitset features)
-
3456 {
-
3457 testcase("RippleState Freeze");
-
3458
-
3459 using namespace test::jtx;
-
3460 Env env(*this, features);
-
3461
-
3462 Account const G1{"G1"};
-
3463 Account const alice{"alice"};
-
3464 Account const bob{"bob"};
-
3465
-
3466 env.fund(XRP(1'000), G1, alice, bob);
-
3467 env.close();
-
3468
-
3469 env.trust(G1["USD"](100), bob);
-
3470 env.trust(G1["USD"](205), alice);
-
3471 env.close();
-
3472
-
3473 env(pay(G1, bob, G1["USD"](10)));
-
3474 env(pay(G1, alice, G1["USD"](205)));
+
3445 {
+
3446 STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)};
+
3447 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
+
3448 env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee));
+
3449 env.close();
+
3450 }
+
3451
+
3452 // Test when bob's XRP balance == base reserve.
+
3453 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
+
3454 BEAST_EXPECT(env.balance(bob, USD) == USD(25));
+
3455 failedIouPayments();
+
3456
+
3457 // Test when bob has an XRP balance == 0.
+
3458 env(noop(bob), fee(reserve(env, 0)));
+
3459 env.close();
+
3460
+
3461 BEAST_EXPECT(env.balance(bob, XRP) == XRP(0));
+
3462 failedIouPayments();
+
3463
+
3464 // Give bob enough XRP for the fee to clear the lsfDepositAuth flag.
+
3465 env(pay(alice, bob, drops(env.current()->fees().base)));
+
3466
+
3467 // bob clears the lsfDepositAuth and the next payment succeeds.
+
3468 env(fclear(bob, asfDepositAuth));
+
3469 env.close();
+
3470
+
3471 env(pay(alice, bob, USD(50)));
+
3472 env.close();
+
3473
+
3474 env(pay(alice, bob, drops(1)), sendmax(USD(1)));
3475 env.close();
-
3476
-
3477 AMM ammAlice(env, alice, XRP(500), G1["USD"](105));
-
3478
-
3479 {
-
3480 auto lines = getAccountLines(env, bob);
-
3481 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
-
3482 return;
-
3483 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
-
3484 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100");
-
3485 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10");
-
3486 }
+
3476 BEAST_EXPECT(ammCarol.expectBalances(
+
3477 USD(101), XRPAmount(100), ammCarol.tokens()));
+
3478 }
+
3479
+
3480 void
+
3481 testRippleState(FeatureBitset features)
+
3482 {
+
3483 testcase("RippleState Freeze");
+
3484
+
3485 using namespace test::jtx;
+
3486 Env env(*this, features);
3487
-
3488 {
-
3489 auto lines = getAccountLines(env, alice, G1["USD"]);
-
3490 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
-
3491 return;
-
3492 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
-
3493 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205");
-
3494 // 105 transferred to AMM
-
3495 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100");
-
3496 }
-
3497
-
3498 {
-
3499 // Account with line unfrozen (proving operations normally work)
-
3500 // test: can make Payment on that line
-
3501 env(pay(alice, bob, G1["USD"](1)));
+
3488 Account const G1{"G1"};
+
3489 Account const alice{"alice"};
+
3490 Account const bob{"bob"};
+
3491
+
3492 env.fund(XRP(1'000), G1, alice, bob);
+
3493 env.close();
+
3494
+
3495 env.trust(G1["USD"](100), bob);
+
3496 env.trust(G1["USD"](205), alice);
+
3497 env.close();
+
3498
+
3499 env(pay(G1, bob, G1["USD"](10)));
+
3500 env(pay(G1, alice, G1["USD"](205)));
+
3501 env.close();
3502
-
3503 // test: can receive Payment on that line
-
3504 env(pay(bob, alice, G1["USD"](1)));
-
3505 env.close();
-
3506 }
-
3507
-
3508 {
-
3509 // Is created via a TrustSet with SetFreeze flag
-
3510 // test: sets LowFreeze | HighFreeze flags
-
3511 env(trust(G1, bob["USD"](0), tfSetFreeze));
-
3512 auto affected = env.meta()->getJson(
-
3513 JsonOptions::none)[sfAffectedNodes.fieldName];
-
3514 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
-
3515 return;
-
3516 auto ff =
-
3517 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
-
3518 BEAST_EXPECT(
-
3519 ff[sfLowLimit.fieldName] ==
-
3520 G1["USD"](0).value().getJson(JsonOptions::none));
-
3521 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze);
-
3522 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
-
3523 env.close();
-
3524 }
-
3525
-
3526 {
-
3527 // Account with line frozen by issuer
-
3528 // test: can buy more assets on that line
-
3529 env(offer(bob, G1["USD"](5), XRP(25)));
-
3530 auto affected = env.meta()->getJson(
-
3531 JsonOptions::none)[sfAffectedNodes.fieldName];
-
3532 if (!BEAST_EXPECT(checkArraySize(affected, 4u)))
-
3533 return;
-
3534 auto ff =
-
3535 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
-
3536 BEAST_EXPECT(
-
3537 ff[sfHighLimit.fieldName] ==
-
3538 bob["USD"](100).value().getJson(JsonOptions::none));
-
3539 auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15}
-
3540 .value()
-
3541 .getJson(JsonOptions::none);
-
3542 BEAST_EXPECT(ff[sfBalance.fieldName] == amt);
-
3543 env.close();
-
3544 BEAST_EXPECT(ammAlice.expectBalances(
-
3545 XRP(525), G1["USD"](100), ammAlice.tokens()));
-
3546 }
-
3547
-
3548 {
-
3549 // test: can not sell assets from that line
-
3550 env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER));
-
3551
-
3552 // test: can receive Payment on that line
-
3553 env(pay(alice, bob, G1["USD"](1)));
-
3554
-
3555 // test: can not make Payment from that line
-
3556 env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY));
-
3557 }
-
3558
-
3559 {
-
3560 // check G1 account lines
-
3561 // test: shows freeze
-
3562 auto lines = getAccountLines(env, G1);
-
3563 Json::Value bobLine;
-
3564 for (auto const& it : lines[jss::lines])
-
3565 {
-
3566 if (it[jss::account] == bob.human())
-
3567 {
-
3568 bobLine = it;
-
3569 break;
-
3570 }
-
3571 }
-
3572 if (!BEAST_EXPECT(bobLine))
-
3573 return;
-
3574 BEAST_EXPECT(bobLine[jss::freeze] == true);
-
3575 BEAST_EXPECT(bobLine[jss::balance] == "-16");
-
3576 }
-
3577
-
3578 {
-
3579 // test: shows freeze peer
-
3580 auto lines = getAccountLines(env, bob);
-
3581 Json::Value g1Line;
-
3582 for (auto const& it : lines[jss::lines])
-
3583 {
-
3584 if (it[jss::account] == G1.human())
-
3585 {
-
3586 g1Line = it;
-
3587 break;
-
3588 }
-
3589 }
-
3590 if (!BEAST_EXPECT(g1Line))
-
3591 return;
-
3592 BEAST_EXPECT(g1Line[jss::freeze_peer] == true);
-
3593 BEAST_EXPECT(g1Line[jss::balance] == "16");
-
3594 }
-
3595
-
3596 {
-
3597 // Is cleared via a TrustSet with ClearFreeze flag
-
3598 // test: sets LowFreeze | HighFreeze flags
-
3599 env(trust(G1, bob["USD"](0), tfClearFreeze));
-
3600 auto affected = env.meta()->getJson(
-
3601 JsonOptions::none)[sfAffectedNodes.fieldName];
-
3602 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
-
3603 return;
-
3604 auto ff =
-
3605 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
-
3606 BEAST_EXPECT(
-
3607 ff[sfLowLimit.fieldName] ==
-
3608 G1["USD"](0).value().getJson(JsonOptions::none));
-
3609 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
-
3610 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
-
3611 env.close();
-
3612 }
-
3613 }
-
3614
-
3615 void
-
3616 testGlobalFreeze(FeatureBitset features)
-
3617 {
-
3618 testcase("Global Freeze");
-
3619
-
3620 using namespace test::jtx;
-
3621 Env env(*this, features);
-
3622
-
3623 Account G1{"G1"};
-
3624 Account A1{"A1"};
-
3625 Account A2{"A2"};
-
3626 Account A3{"A3"};
-
3627 Account A4{"A4"};
-
3628
-
3629 env.fund(XRP(12'000), G1);
-
3630 env.fund(XRP(1'000), A1);
-
3631 env.fund(XRP(20'000), A2, A3, A4);
-
3632 env.close();
-
3633
-
3634 env.trust(G1["USD"](1'200), A1);
-
3635 env.trust(G1["USD"](200), A2);
-
3636 env.trust(G1["BTC"](100), A3);
-
3637 env.trust(G1["BTC"](100), A4);
-
3638 env.close();
-
3639
-
3640 env(pay(G1, A1, G1["USD"](1'000)));
-
3641 env(pay(G1, A2, G1["USD"](100)));
-
3642 env(pay(G1, A3, G1["BTC"](100)));
-
3643 env(pay(G1, A4, G1["BTC"](100)));
-
3644 env.close();
-
3645
-
3646 AMM ammG1(env, G1, XRP(10'000), G1["USD"](100));
-
3647 env(offer(A1, XRP(10'000), G1["USD"](100)), txflags(tfPassive));
-
3648 env(offer(A2, G1["USD"](100), XRP(10'000)), txflags(tfPassive));
-
3649 env.close();
-
3650
-
3651 {
-
3652 // Account without GlobalFreeze (proving operations normally
-
3653 // work)
-
3654 // test: visible offers where taker_pays is unfrozen issuer
-
3655 auto offers = env.rpc(
-
3656 "book_offers",
-
3657 std::string("USD/") + G1.human(),
-
3658 "XRP")[jss::result][jss::offers];
-
3659 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
-
3660 return;
-
3661 std::set<std::string> accounts;
-
3662 for (auto const& offer : offers)
-
3663 {
-
3664 accounts.insert(offer[jss::Account].asString());
-
3665 }
-
3666 BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts));
-
3667
-
3668 // test: visible offers where taker_gets is unfrozen issuer
-
3669 offers = env.rpc(
-
3670 "book_offers",
-
3671 "XRP",
-
3672 std::string("USD/") + G1.human())[jss::result][jss::offers];
-
3673 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
-
3674 return;
-
3675 accounts.clear();
-
3676 for (auto const& offer : offers)
-
3677 {
-
3678 accounts.insert(offer[jss::Account].asString());
-
3679 }
-
3680 BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts));
-
3681 }
-
3682
-
3683 {
-
3684 // Offers/Payments
-
3685 // test: assets can be bought on the market
-
3686 // env(offer(A3, G1["BTC"](1), XRP(1)));
-
3687 AMM ammA3(env, A3, G1["BTC"](1), XRP(1));
-
3688
-
3689 // test: assets can be sold on the market
-
3690 // AMM is bidirectional
-
3691
-
3692 // test: direct issues can be sent
-
3693 env(pay(G1, A2, G1["USD"](1)));
-
3694
-
3695 // test: direct redemptions can be sent
-
3696 env(pay(A2, G1, G1["USD"](1)));
-
3697
-
3698 // test: via rippling can be sent
-
3699 env(pay(A2, A1, G1["USD"](1)));
-
3700
-
3701 // test: via rippling can be sent back
-
3702 env(pay(A1, A2, G1["USD"](1)));
-
3703 ammA3.withdrawAll(std::nullopt);
-
3704 }
-
3705
-
3706 {
-
3707 // Account with GlobalFreeze
-
3708 // set GlobalFreeze first
-
3709 // test: SetFlag GlobalFreeze will toggle back to freeze
-
3710 env.require(nflags(G1, asfGlobalFreeze));
-
3711 env(fset(G1, asfGlobalFreeze));
-
3712 env.require(flags(G1, asfGlobalFreeze));
-
3713 env.require(nflags(G1, asfNoFreeze));
-
3714
-
3715 // test: assets can't be bought on the market
-
3716 AMM ammA3(env, A3, G1["BTC"](1), XRP(1), ter(tecFROZEN));
-
3717
-
3718 // test: assets can't be sold on the market
-
3719 // AMM is bidirectional
-
3720 }
-
3721
-
3722 {
-
3723 // test: book_offers shows offers
-
3724 // (should these actually be filtered?)
-
3725 auto offers = env.rpc(
-
3726 "book_offers",
-
3727 "XRP",
-
3728 std::string("USD/") + G1.human())[jss::result][jss::offers];
-
3729 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
-
3730 return;
-
3731
-
3732 offers = env.rpc(
-
3733 "book_offers",
-
3734 std::string("USD/") + G1.human(),
-
3735 "XRP")[jss::result][jss::offers];
-
3736 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
-
3737 return;
-
3738 }
-
3739
-
3740 {
-
3741 // Payments
-
3742 // test: direct issues can be sent
-
3743 env(pay(G1, A2, G1["USD"](1)));
-
3744
-
3745 // test: direct redemptions can be sent
-
3746 env(pay(A2, G1, G1["USD"](1)));
-
3747
-
3748 // test: via rippling cant be sent
-
3749 env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY));
-
3750 }
-
3751 }
-
3752
-
3753 void
-
3754 testOffersWhenFrozen(FeatureBitset features)
-
3755 {
-
3756 testcase("Offers for Frozen Trust Lines");
-
3757
-
3758 using namespace test::jtx;
-
3759 Env env(*this, features);
-
3760
-
3761 Account G1{"G1"};
-
3762 Account A2{"A2"};
-
3763 Account A3{"A3"};
-
3764 Account A4{"A4"};
-
3765
-
3766 env.fund(XRP(2'000), G1, A3, A4);
-
3767 env.fund(XRP(2'000), A2);
-
3768 env.close();
-
3769
-
3770 env.trust(G1["USD"](1'000), A2);
-
3771 env.trust(G1["USD"](2'000), A3);
-
3772 env.trust(G1["USD"](2'001), A4);
-
3773 env.close();
-
3774
-
3775 env(pay(G1, A3, G1["USD"](2'000)));
-
3776 env(pay(G1, A4, G1["USD"](2'001)));
-
3777 env.close();
+
3503 AMM ammAlice(env, alice, XRP(500), G1["USD"](105));
+
3504
+
3505 {
+
3506 auto lines = getAccountLines(env, bob);
+
3507 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
+
3508 return;
+
3509 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
+
3510 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100");
+
3511 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10");
+
3512 }
+
3513
+
3514 {
+
3515 auto lines = getAccountLines(env, alice, G1["USD"]);
+
3516 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
+
3517 return;
+
3518 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
+
3519 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205");
+
3520 // 105 transferred to AMM
+
3521 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100");
+
3522 }
+
3523
+
3524 // Account with line unfrozen (proving operations normally work)
+
3525 // test: can make Payment on that line
+
3526 env(pay(alice, bob, G1["USD"](1)));
+
3527
+
3528 // test: can receive Payment on that line
+
3529 env(pay(bob, alice, G1["USD"](1)));
+
3530 env.close();
+
3531
+
3532 // Is created via a TrustSet with SetFreeze flag
+
3533 // test: sets LowFreeze | HighFreeze flags
+
3534 env(trust(G1, bob["USD"](0), tfSetFreeze));
+
3535 env.close();
+
3536
+
3537 {
+
3538 // Account with line frozen by issuer
+
3539 // test: can buy more assets on that line
+
3540 env(offer(bob, G1["USD"](5), XRP(25)));
+
3541 env.close();
+
3542 BEAST_EXPECT(ammAlice.expectBalances(
+
3543 XRP(525), G1["USD"](100), ammAlice.tokens()));
+
3544 }
+
3545
+
3546 {
+
3547 // test: can not sell assets from that line
+
3548 env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER));
+
3549
+
3550 // test: can receive Payment on that line
+
3551 env(pay(alice, bob, G1["USD"](1)));
+
3552
+
3553 // test: can not make Payment from that line
+
3554 env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY));
+
3555 }
+
3556
+
3557 {
+
3558 // check G1 account lines
+
3559 // test: shows freeze
+
3560 auto lines = getAccountLines(env, G1);
+
3561 Json::Value bobLine;
+
3562 for (auto const& it : lines[jss::lines])
+
3563 {
+
3564 if (it[jss::account] == bob.human())
+
3565 {
+
3566 bobLine = it;
+
3567 break;
+
3568 }
+
3569 }
+
3570 if (!BEAST_EXPECT(bobLine))
+
3571 return;
+
3572 BEAST_EXPECT(bobLine[jss::freeze] == true);
+
3573 BEAST_EXPECT(bobLine[jss::balance] == "-16");
+
3574 }
+
3575
+
3576 {
+
3577 // test: shows freeze peer
+
3578 auto lines = getAccountLines(env, bob);
+
3579 Json::Value g1Line;
+
3580 for (auto const& it : lines[jss::lines])
+
3581 {
+
3582 if (it[jss::account] == G1.human())
+
3583 {
+
3584 g1Line = it;
+
3585 break;
+
3586 }
+
3587 }
+
3588 if (!BEAST_EXPECT(g1Line))
+
3589 return;
+
3590 BEAST_EXPECT(g1Line[jss::freeze_peer] == true);
+
3591 BEAST_EXPECT(g1Line[jss::balance] == "16");
+
3592 }
+
3593
+
3594 {
+
3595 // Is cleared via a TrustSet with ClearFreeze flag
+
3596 // test: sets LowFreeze | HighFreeze flags
+
3597 env(trust(G1, bob["USD"](0), tfClearFreeze));
+
3598 auto affected = env.meta()->getJson(
+
3599 JsonOptions::none)[sfAffectedNodes.fieldName];
+
3600 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
+
3601 return;
+
3602 auto ff =
+
3603 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
+
3604 BEAST_EXPECT(
+
3605 ff[sfLowLimit.fieldName] ==
+
3606 G1["USD"](0).value().getJson(JsonOptions::none));
+
3607 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
+
3608 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
+
3609 env.close();
+
3610 }
+
3611 }
+
3612
+
3613 void
+
3614 testGlobalFreeze(FeatureBitset features)
+
3615 {
+
3616 testcase("Global Freeze");
+
3617
+
3618 using namespace test::jtx;
+
3619 Env env(*this, features);
+
3620
+
3621 Account G1{"G1"};
+
3622 Account A1{"A1"};
+
3623 Account A2{"A2"};
+
3624 Account A3{"A3"};
+
3625 Account A4{"A4"};
+
3626
+
3627 env.fund(XRP(12'000), G1);
+
3628 env.fund(XRP(1'000), A1);
+
3629 env.fund(XRP(20'000), A2, A3, A4);
+
3630 env.close();
+
3631
+
3632 env.trust(G1["USD"](1'200), A1);
+
3633 env.trust(G1["USD"](200), A2);
+
3634 env.trust(G1["BTC"](100), A3);
+
3635 env.trust(G1["BTC"](100), A4);
+
3636 env.close();
+
3637
+
3638 env(pay(G1, A1, G1["USD"](1'000)));
+
3639 env(pay(G1, A2, G1["USD"](100)));
+
3640 env(pay(G1, A3, G1["BTC"](100)));
+
3641 env(pay(G1, A4, G1["BTC"](100)));
+
3642 env.close();
+
3643
+
3644 AMM ammG1(env, G1, XRP(10'000), G1["USD"](100));
+
3645 env(offer(A1, XRP(10'000), G1["USD"](100)), txflags(tfPassive));
+
3646 env(offer(A2, G1["USD"](100), XRP(10'000)), txflags(tfPassive));
+
3647 env.close();
+
3648
+
3649 {
+
3650 // Account without GlobalFreeze (proving operations normally
+
3651 // work)
+
3652 // test: visible offers where taker_pays is unfrozen issuer
+
3653 auto offers = env.rpc(
+
3654 "book_offers",
+
3655 std::string("USD/") + G1.human(),
+
3656 "XRP")[jss::result][jss::offers];
+
3657 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
+
3658 return;
+
3659 std::set<std::string> accounts;
+
3660 for (auto const& offer : offers)
+
3661 {
+
3662 accounts.insert(offer[jss::Account].asString());
+
3663 }
+
3664 BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts));
+
3665
+
3666 // test: visible offers where taker_gets is unfrozen issuer
+
3667 offers = env.rpc(
+
3668 "book_offers",
+
3669 "XRP",
+
3670 std::string("USD/") + G1.human())[jss::result][jss::offers];
+
3671 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
+
3672 return;
+
3673 accounts.clear();
+
3674 for (auto const& offer : offers)
+
3675 {
+
3676 accounts.insert(offer[jss::Account].asString());
+
3677 }
+
3678 BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts));
+
3679 }
+
3680
+
3681 {
+
3682 // Offers/Payments
+
3683 // test: assets can be bought on the market
+
3684 // env(offer(A3, G1["BTC"](1), XRP(1)));
+
3685 AMM ammA3(env, A3, G1["BTC"](1), XRP(1));
+
3686
+
3687 // test: assets can be sold on the market
+
3688 // AMM is bidirectional
+
3689
+
3690 // test: direct issues can be sent
+
3691 env(pay(G1, A2, G1["USD"](1)));
+
3692
+
3693 // test: direct redemptions can be sent
+
3694 env(pay(A2, G1, G1["USD"](1)));
+
3695
+
3696 // test: via rippling can be sent
+
3697 env(pay(A2, A1, G1["USD"](1)));
+
3698
+
3699 // test: via rippling can be sent back
+
3700 env(pay(A1, A2, G1["USD"](1)));
+
3701 ammA3.withdrawAll(std::nullopt);
+
3702 }
+
3703
+
3704 {
+
3705 // Account with GlobalFreeze
+
3706 // set GlobalFreeze first
+
3707 // test: SetFlag GlobalFreeze will toggle back to freeze
+
3708 env.require(nflags(G1, asfGlobalFreeze));
+
3709 env(fset(G1, asfGlobalFreeze));
+
3710 env.require(flags(G1, asfGlobalFreeze));
+
3711 env.require(nflags(G1, asfNoFreeze));
+
3712
+
3713 // test: assets can't be bought on the market
+
3714 AMM ammA3(env, A3, G1["BTC"](1), XRP(1), ter(tecFROZEN));
+
3715
+
3716 // test: assets can't be sold on the market
+
3717 // AMM is bidirectional
+
3718 }
+
3719
+
3720 {
+
3721 // test: book_offers shows offers
+
3722 // (should these actually be filtered?)
+
3723 auto offers = env.rpc(
+
3724 "book_offers",
+
3725 "XRP",
+
3726 std::string("USD/") + G1.human())[jss::result][jss::offers];
+
3727 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
+
3728 return;
+
3729
+
3730 offers = env.rpc(
+
3731 "book_offers",
+
3732 std::string("USD/") + G1.human(),
+
3733 "XRP")[jss::result][jss::offers];
+
3734 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
+
3735 return;
+
3736 }
+
3737
+
3738 {
+
3739 // Payments
+
3740 // test: direct issues can be sent
+
3741 env(pay(G1, A2, G1["USD"](1)));
+
3742
+
3743 // test: direct redemptions can be sent
+
3744 env(pay(A2, G1, G1["USD"](1)));
+
3745
+
3746 // test: via rippling cant be sent
+
3747 env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY));
+
3748 }
+
3749 }
+
3750
+
3751 void
+
3752 testOffersWhenFrozen(FeatureBitset features)
+
3753 {
+
3754 testcase("Offers for Frozen Trust Lines");
+
3755
+
3756 using namespace test::jtx;
+
3757 Env env(*this, features);
+
3758
+
3759 Account G1{"G1"};
+
3760 Account A2{"A2"};
+
3761 Account A3{"A3"};
+
3762 Account A4{"A4"};
+
3763
+
3764 env.fund(XRP(2'000), G1, A3, A4);
+
3765 env.fund(XRP(2'000), A2);
+
3766 env.close();
+
3767
+
3768 env.trust(G1["USD"](1'000), A2);
+
3769 env.trust(G1["USD"](2'000), A3);
+
3770 env.trust(G1["USD"](2'001), A4);
+
3771 env.close();
+
3772
+
3773 env(pay(G1, A3, G1["USD"](2'000)));
+
3774 env(pay(G1, A4, G1["USD"](2'001)));
+
3775 env.close();
+
3776
+
3777 AMM ammA3(env, A3, XRP(1'000), G1["USD"](1'001));
3778
-
3779 AMM ammA3(env, A3, XRP(1'000), G1["USD"](1'001));
-
3780
-
3781 // removal after successful payment
-
3782 // test: make a payment with partially consuming offer
-
3783 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
-
3784 env.close();
-
3785
-
3786 BEAST_EXPECT(
-
3787 ammA3.expectBalances(XRP(1'001), G1["USD"](1'000), ammA3.tokens()));
-
3788
-
3789 // test: someone else creates an offer providing liquidity
-
3790 env(offer(A4, XRP(999), G1["USD"](999)));
-
3791 env.close();
-
3792 // The offer consumes AMM offer
-
3793 BEAST_EXPECT(
-
3794 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
-
3795
-
3796 // test: AMM line is frozen
-
3797 auto const a3am =
-
3798 STAmount{Issue{to_currency("USD"), ammA3.ammAccount()}, 0};
-
3799 env(trust(G1, a3am, tfSetFreeze));
-
3800 auto const info = ammA3.ammRpcInfo();
-
3801 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
-
3802 auto affected =
-
3803 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
-
3804 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
-
3805 return;
-
3806 auto ff =
-
3807 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
-
3808 BEAST_EXPECT(
-
3809 ff[sfHighLimit.fieldName] ==
-
3810 G1["USD"](0).value().getJson(JsonOptions::none));
-
3811 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
-
3812 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfHighFreeze);
-
3813 env.close();
-
3814
-
3815 // test: Can make a payment via the new offer
-
3816 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
-
3817 env.close();
-
3818 // AMM is not consumed
-
3819 BEAST_EXPECT(
-
3820 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
-
3821
-
3822 // removal buy successful OfferCreate
-
3823 // test: freeze the new offer
-
3824 env(trust(G1, A4["USD"](0), tfSetFreeze));
-
3825 affected =
-
3826 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
-
3827 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
-
3828 return;
-
3829 ff = affected[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
-
3830 BEAST_EXPECT(
-
3831 ff[sfLowLimit.fieldName] ==
-
3832 G1["USD"](0).value().getJson(JsonOptions::none));
-
3833 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze);
-
3834 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
-
3835 env.close();
+
3779 // removal after successful payment
+
3780 // test: make a payment with partially consuming offer
+
3781 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
+
3782 env.close();
+
3783
+
3784 BEAST_EXPECT(
+
3785 ammA3.expectBalances(XRP(1'001), G1["USD"](1'000), ammA3.tokens()));
+
3786
+
3787 // test: someone else creates an offer providing liquidity
+
3788 env(offer(A4, XRP(999), G1["USD"](999)));
+
3789 env.close();
+
3790 // The offer consumes AMM offer
+
3791 BEAST_EXPECT(
+
3792 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
+
3793
+
3794 // test: AMM line is frozen
+
3795 auto const a3am =
+
3796 STAmount{Issue{to_currency("USD"), ammA3.ammAccount()}, 0};
+
3797 env(trust(G1, a3am, tfSetFreeze));
+
3798 auto const info = ammA3.ammRpcInfo();
+
3799 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
+
3800 env.close();
+
3801
+
3802 // test: Can make a payment via the new offer
+
3803 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
+
3804 env.close();
+
3805 // AMM is not consumed
+
3806 BEAST_EXPECT(
+
3807 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
+
3808
+
3809 // removal buy successful OfferCreate
+
3810 // test: freeze the new offer
+
3811 env(trust(G1, A4["USD"](0), tfSetFreeze));
+
3812 env.close();
+
3813
+
3814 // test: can no longer create a crossing offer
+
3815 env(offer(A2, G1["USD"](999), XRP(999)));
+
3816 env.close();
+
3817
+
3818 // test: offer was removed by offer_create
+
3819 auto offers = getAccountOffers(env, A4)[jss::offers];
+
3820 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
+
3821 return;
+
3822 }
+
3823
+
3824 void
+
3825 testTxMultisign(FeatureBitset features)
+
3826 {
+
3827 testcase("Multisign AMM Transactions");
+
3828
+
3829 using namespace jtx;
+
3830 Env env{*this, features};
+
3831 Account const bogie{"bogie", KeyType::secp256k1};
+
3832 Account const alice{"alice", KeyType::secp256k1};
+
3833 Account const becky{"becky", KeyType::ed25519};
+
3834 Account const zelda{"zelda", KeyType::secp256k1};
+
3835 fund(env, gw, {alice, becky, zelda}, XRP(20'000), {USD(20'000)});
3836
-
3837 // test: can no longer create a crossing offer
-
3838 env(offer(A2, G1["USD"](999), XRP(999)));
-
3839 affected =
-
3840 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
-
3841 if (!BEAST_EXPECT(checkArraySize(affected, 8u)))
-
3842 return;
-
3843 auto created = affected[0u][sfCreatedNode.fieldName];
-
3844 BEAST_EXPECT(
-
3845 created[sfNewFields.fieldName][jss::Account] == A2.human());
-
3846 env.close();
+
3837 // alice uses a regular key with the master disabled.
+
3838 Account const alie{"alie", KeyType::secp256k1};
+
3839 env(regkey(alice, alie));
+
3840 env(fset(alice, asfDisableMaster), sig(alice));
+
3841
+
3842 // Attach signers to alice.
+
3843 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
+
3844 env.close();
+
3845 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
+
3846 env.require(owners(alice, signerListOwners + 0));
3847
-
3848 // test: offer was removed by offer_create
-
3849 auto offers = getAccountOffers(env, A4)[jss::offers];
-
3850 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
-
3851 return;
-
3852 }
-
3853
-
3854 void
-
3855 testTxMultisign(FeatureBitset features)
-
3856 {
-
3857 testcase("Multisign AMM Transactions");
-
3858
-
3859 using namespace jtx;
-
3860 Env env{*this, features};
-
3861 Account const bogie{"bogie", KeyType::secp256k1};
-
3862 Account const alice{"alice", KeyType::secp256k1};
-
3863 Account const becky{"becky", KeyType::ed25519};
-
3864 Account const zelda{"zelda", KeyType::secp256k1};
-
3865 fund(env, gw, {alice, becky, zelda}, XRP(20'000), {USD(20'000)});
-
3866
-
3867 // alice uses a regular key with the master disabled.
-
3868 Account const alie{"alie", KeyType::secp256k1};
-
3869 env(regkey(alice, alie));
-
3870 env(fset(alice, asfDisableMaster), sig(alice));
-
3871
-
3872 // Attach signers to alice.
-
3873 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
-
3874 env.close();
-
3875 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
-
3876 env.require(owners(alice, signerListOwners + 0));
-
3877
-
3878 const msig ms{becky, bogie};
-
3879
-
3880 // Multisign all AMM transactions
-
3881 AMM ammAlice(
-
3882 env,
-
3883 alice,
-
3884 XRP(10'000),
-
3885 USD(10'000),
-
3886 false,
-
3887 0,
-
3888 ammCrtFee(env).drops(),
-
3889 std::nullopt,
-
3890 std::nullopt,
-
3891 ms,
-
3892 ter(tesSUCCESS));
-
3893 BEAST_EXPECT(ammAlice.expectBalances(
-
3894 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3895
-
3896 ammAlice.deposit(alice, 1'000'000);
-
3897 BEAST_EXPECT(ammAlice.expectBalances(
-
3898 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
3899
-
3900 ammAlice.withdraw(alice, 1'000'000);
-
3901 BEAST_EXPECT(ammAlice.expectBalances(
-
3902 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3903
-
3904 ammAlice.vote({}, 1'000);
-
3905 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
3906
-
3907 env(ammAlice.bid({.account = alice, .bidMin = 100}), ms).close();
-
3908 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
-
3909 // 4000 tokens burnt
-
3910 BEAST_EXPECT(ammAlice.expectBalances(
-
3911 XRP(10'000), USD(10'000), IOUAmount{9'996'000, 0}));
-
3912 }
-
3913
-
3914 void
-
3915 testToStrand(FeatureBitset features)
-
3916 {
-
3917 testcase("To Strand");
+
3848 const msig ms{becky, bogie};
+
3849
+
3850 // Multisign all AMM transactions
+
3851 AMM ammAlice(
+
3852 env,
+
3853 alice,
+
3854 XRP(10'000),
+
3855 USD(10'000),
+
3856 false,
+
3857 0,
+
3858 ammCrtFee(env).drops(),
+
3859 std::nullopt,
+
3860 std::nullopt,
+
3861 ms,
+
3862 ter(tesSUCCESS));
+
3863 BEAST_EXPECT(ammAlice.expectBalances(
+
3864 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3865
+
3866 ammAlice.deposit(alice, 1'000'000);
+
3867 BEAST_EXPECT(ammAlice.expectBalances(
+
3868 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
3869
+
3870 ammAlice.withdraw(alice, 1'000'000);
+
3871 BEAST_EXPECT(ammAlice.expectBalances(
+
3872 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3873
+
3874 ammAlice.vote({}, 1'000);
+
3875 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
3876
+
3877 env(ammAlice.bid({.account = alice, .bidMin = 100}), ms).close();
+
3878 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
+
3879 // 4000 tokens burnt
+
3880 BEAST_EXPECT(ammAlice.expectBalances(
+
3881 XRP(10'000), USD(10'000), IOUAmount{9'996'000, 0}));
+
3882 }
+
3883
+
3884 void
+
3885 testToStrand(FeatureBitset features)
+
3886 {
+
3887 testcase("To Strand");
+
3888
+
3889 using namespace jtx;
+
3890
+
3891 // cannot have more than one offer with the same output issue
+
3892
+
3893 Env env(*this, features);
+
3894
+
3895 fund(
+
3896 env,
+
3897 gw,
+
3898 {alice, bob, carol},
+
3899 XRP(10'000),
+
3900 {USD(2'000), EUR(1'000)});
+
3901
+
3902 AMM bobXRP_USD(env, bob, XRP(1'000), USD(1'000));
+
3903 AMM bobUSD_EUR(env, bob, USD(1'000), EUR(1'000));
+
3904
+
3905 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
+
3906 env(pay(alice, carol, USD(100)),
+
3907 path(~USD, ~EUR, ~USD),
+
3908 sendmax(XRP(200)),
+
3909 txflags(tfNoRippleDirect),
+
3910 ter(temBAD_PATH_LOOP));
+
3911 }
+
3912
+
3913 void
+
3914 testRIPD1373(FeatureBitset features)
+
3915 {
+
3916 using namespace jtx;
+
3917 testcase("RIPD1373");
3918
-
3919 using namespace jtx;
-
3920
-
3921 // cannot have more than one offer with the same output issue
-
3922
-
3923 Env env(*this, features);
-
3924
-
3925 fund(
-
3926 env,
-
3927 gw,
-
3928 {alice, bob, carol},
-
3929 XRP(10'000),
-
3930 {USD(2'000), EUR(1'000)});
-
3931
-
3932 AMM bobXRP_USD(env, bob, XRP(1'000), USD(1'000));
-
3933 AMM bobUSD_EUR(env, bob, USD(1'000), EUR(1'000));
+
3919 {
+
3920 Env env(*this, features);
+
3921 auto const BobUSD = bob["USD"];
+
3922 auto const BobEUR = bob["EUR"];
+
3923 fund(env, gw, {alice, bob}, XRP(10'000));
+
3924 env.trust(USD(1'000), alice, bob);
+
3925 env.trust(EUR(1'000), alice, bob);
+
3926 env.close();
+
3927 fund(
+
3928 env,
+
3929 bob,
+
3930 {alice, gw},
+
3931 {BobUSD(100), BobEUR(100)},
+
3932 Fund::IOUOnly);
+
3933 env.close();
3934
-
3935 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
-
3936 env(pay(alice, carol, USD(100)),
-
3937 path(~USD, ~EUR, ~USD),
-
3938 sendmax(XRP(200)),
-
3939 txflags(tfNoRippleDirect),
-
3940 ter(temBAD_PATH_LOOP));
-
3941 }
-
3942
-
3943 void
-
3944 testRIPD1373(FeatureBitset features)
-
3945 {
-
3946 using namespace jtx;
-
3947 testcase("RIPD1373");
-
3948
-
3949 {
-
3950 Env env(*this, features);
-
3951 auto const BobUSD = bob["USD"];
-
3952 auto const BobEUR = bob["EUR"];
-
3953 fund(env, gw, {alice, bob}, XRP(10'000));
-
3954 env.trust(USD(1'000), alice, bob);
-
3955 env.trust(EUR(1'000), alice, bob);
-
3956 fund(
-
3957 env,
-
3958 bob,
-
3959 {alice, gw},
-
3960 {BobUSD(100), BobEUR(100)},
-
3961 Fund::IOUOnly);
-
3962
-
3963 AMM ammBobXRP_USD(env, bob, XRP(100), BobUSD(100));
-
3964 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
-
3965
-
3966 AMM ammBobUSD_EUR(env, bob, BobUSD(100), BobEUR(100));
-
3967 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
-
3968
-
3969 Path const p = [&] {
-
3970 Path result;
-
3971 result.push_back(allpe(gw, BobUSD));
-
3972 result.push_back(cpe(EUR.currency));
-
3973 return result;
-
3974 }();
+
3935 AMM ammBobXRP_USD(env, bob, XRP(100), BobUSD(100));
+
3936 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
+
3937
+
3938 AMM ammBobUSD_EUR(env, bob, BobUSD(100), BobEUR(100));
+
3939 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
+
3940
+
3941 Path const p = [&] {
+
3942 Path result;
+
3943 result.push_back(allpe(gw, BobUSD));
+
3944 result.push_back(cpe(EUR.currency));
+
3945 return result;
+
3946 }();
+
3947
+
3948 PathSet paths(p);
+
3949
+
3950 env(pay(alice, alice, EUR(1)),
+
3951 json(paths.json()),
+
3952 sendmax(XRP(10)),
+
3953 txflags(tfNoRippleDirect | tfPartialPayment),
+
3954 ter(temBAD_PATH));
+
3955 }
+
3956
+
3957 {
+
3958 Env env(*this, features);
+
3959
+
3960 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
+
3961
+
3962 AMM ammBob(env, bob, XRP(100), USD(100));
+
3963
+
3964 // payment path: XRP -> XRP/USD -> USD/XRP
+
3965 env(pay(alice, carol, XRP(100)),
+
3966 path(~USD, ~XRP),
+
3967 txflags(tfNoRippleDirect),
+
3968 ter(temBAD_SEND_XRP_PATHS));
+
3969 }
+
3970
+
3971 {
+
3972 Env env(*this, features);
+
3973
+
3974 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
3975
-
3976 PathSet paths(p);
+
3976 AMM ammBob(env, bob, XRP(100), USD(100));
3977
-
3978 env(pay(alice, alice, EUR(1)),
-
3979 json(paths.json()),
-
3980 sendmax(XRP(10)),
-
3981 txflags(tfNoRippleDirect | tfPartialPayment),
-
3982 ter(temBAD_PATH));
-
3983 }
-
3984
-
3985 {
-
3986 Env env(*this, features);
-
3987
-
3988 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
-
3989
-
3990 AMM ammBob(env, bob, XRP(100), USD(100));
-
3991
-
3992 // payment path: XRP -> XRP/USD -> USD/XRP
-
3993 env(pay(alice, carol, XRP(100)),
-
3994 path(~USD, ~XRP),
-
3995 txflags(tfNoRippleDirect),
-
3996 ter(temBAD_SEND_XRP_PATHS));
-
3997 }
-
3998
-
3999 {
-
4000 Env env(*this, features);
-
4001
-
4002 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
-
4003
-
4004 AMM ammBob(env, bob, XRP(100), USD(100));
+
3978 // payment path: XRP -> XRP/USD -> USD/XRP
+
3979 env(pay(alice, carol, XRP(100)),
+
3980 path(~USD, ~XRP),
+
3981 sendmax(XRP(200)),
+
3982 txflags(tfNoRippleDirect),
+
3983 ter(temBAD_SEND_XRP_MAX));
+
3984 }
+
3985 }
+
3986
+
3987 void
+
3988 testLoop(FeatureBitset features)
+
3989 {
+
3990 testcase("test loop");
+
3991 using namespace jtx;
+
3992
+
3993 auto const CNY = gw["CNY"];
+
3994
+
3995 {
+
3996 Env env(*this, features);
+
3997
+
3998 env.fund(XRP(10'000), alice, bob, carol, gw);
+
3999 env.close();
+
4000 env.trust(USD(10'000), alice, bob, carol);
+
4001 env.close();
+
4002 env(pay(gw, bob, USD(100)));
+
4003 env(pay(gw, alice, USD(100)));
+
4004 env.close();
4005
-
4006 // payment path: XRP -> XRP/USD -> USD/XRP
-
4007 env(pay(alice, carol, XRP(100)),
-
4008 path(~USD, ~XRP),
-
4009 sendmax(XRP(200)),
-
4010 txflags(tfNoRippleDirect),
-
4011 ter(temBAD_SEND_XRP_MAX));
-
4012 }
-
4013 }
-
4014
-
4015 void
-
4016 testLoop(FeatureBitset features)
-
4017 {
-
4018 testcase("test loop");
-
4019 using namespace jtx;
-
4020
-
4021 auto const CNY = gw["CNY"];
-
4022
-
4023 {
-
4024 Env env(*this, features);
-
4025
-
4026 env.fund(XRP(10'000), alice, bob, carol, gw);
-
4027 env.trust(USD(10'000), alice, bob, carol);
+
4006 AMM ammBob(env, bob, XRP(100), USD(100));
+
4007
+
4008 // payment path: USD -> USD/XRP -> XRP/USD
+
4009 env(pay(alice, carol, USD(100)),
+
4010 sendmax(USD(100)),
+
4011 path(~XRP, ~USD),
+
4012 txflags(tfNoRippleDirect),
+
4013 ter(temBAD_PATH_LOOP));
+
4014 }
+
4015
+
4016 {
+
4017 Env env(*this, features);
+
4018
+
4019 env.fund(XRP(10'000), alice, bob, carol, gw);
+
4020 env.close();
+
4021 env.trust(USD(10'000), alice, bob, carol);
+
4022 env.trust(EUR(10'000), alice, bob, carol);
+
4023 env.trust(CNY(10'000), alice, bob, carol);
+
4024
+
4025 env(pay(gw, bob, USD(200)));
+
4026 env(pay(gw, bob, EUR(200)));
+
4027 env(pay(gw, bob, CNY(100)));
4028
-
4029 env(pay(gw, bob, USD(100)));
-
4030 env(pay(gw, alice, USD(100)));
-
4031
-
4032 AMM ammBob(env, bob, XRP(100), USD(100));
-
4033
-
4034 // payment path: USD -> USD/XRP -> XRP/USD
-
4035 env(pay(alice, carol, USD(100)),
-
4036 sendmax(USD(100)),
-
4037 path(~XRP, ~USD),
-
4038 txflags(tfNoRippleDirect),
-
4039 ter(temBAD_PATH_LOOP));
-
4040 }
+
4029 AMM ammBobXRP_USD(env, bob, XRP(100), USD(100));
+
4030 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(100));
+
4031 AMM ammBobEUR_CNY(env, bob, EUR(100), CNY(100));
+
4032
+
4033 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
+
4034 env(pay(alice, carol, CNY(100)),
+
4035 sendmax(XRP(100)),
+
4036 path(~USD, ~EUR, ~USD, ~CNY),
+
4037 txflags(tfNoRippleDirect),
+
4038 ter(temBAD_PATH_LOOP));
+
4039 }
+
4040 }
4041
-
4042 {
-
4043 Env env(*this, features);
-
4044
-
4045 env.fund(XRP(10'000), alice, bob, carol, gw);
-
4046 env.trust(USD(10'000), alice, bob, carol);
-
4047 env.trust(EUR(10'000), alice, bob, carol);
-
4048 env.trust(CNY(10'000), alice, bob, carol);
-
4049
-
4050 env(pay(gw, bob, USD(200)));
-
4051 env(pay(gw, bob, EUR(200)));
-
4052 env(pay(gw, bob, CNY(100)));
+
4042 void
+
4043 testPaths()
+
4044 {
+
4045 path_find_consume_all();
+
4046 via_offers_via_gateway();
+
4047 receive_max();
+
4048 path_find_01();
+
4049 path_find_02();
+
4050 path_find_05();
+
4051 path_find_06();
+
4052 }
4053
-
4054 AMM ammBobXRP_USD(env, bob, XRP(100), USD(100));
-
4055 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(100));
-
4056 AMM ammBobEUR_CNY(env, bob, EUR(100), CNY(100));
-
4057
-
4058 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
-
4059 env(pay(alice, carol, CNY(100)),
-
4060 sendmax(XRP(100)),
-
4061 path(~USD, ~EUR, ~USD, ~CNY),
-
4062 txflags(tfNoRippleDirect),
-
4063 ter(temBAD_PATH_LOOP));
-
4064 }
-
4065 }
-
4066
-
4067 void
-
4068 testPaths()
-
4069 {
-
4070 path_find_consume_all();
-
4071 via_offers_via_gateway();
-
4072 receive_max();
-
4073 path_find_01();
-
4074 path_find_02();
-
4075 path_find_05();
-
4076 path_find_06();
-
4077 }
-
4078
-
4079 void
-
4080 testFlow()
-
4081 {
-
4082 using namespace jtx;
-
4083 FeatureBitset const all{supported_amendments()};
-
4084 FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
-
4085
-
4086 testFalseDry(all);
-
4087 testBookStep(all);
-
4088 testBookStep(all | ownerPaysFee);
-
4089 testTransferRate(all | ownerPaysFee);
-
4090 testTransferRate((all - fixAMMv1_1) | ownerPaysFee);
-
4091 testTransferRateNoOwnerFee(all);
-
4092 testTransferRateNoOwnerFee(all - fixAMMv1_1);
-
4093 testLimitQuality();
-
4094 testXRPPathLoop();
-
4095 }
-
4096
-
4097 void
-
4098 testCrossingLimits()
-
4099 {
-
4100 using namespace jtx;
-
4101 FeatureBitset const all{supported_amendments()};
-
4102 testStepLimit(all);
-
4103 testStepLimit(all - fixAMMv1_1);
-
4104 }
-
4105
-
4106 void
-
4107 testDeliverMin()
-
4108 {
-
4109 using namespace jtx;
-
4110 FeatureBitset const all{supported_amendments()};
-
4111 test_convert_all_of_an_asset(all);
-
4112 test_convert_all_of_an_asset(all - fixAMMv1_1);
-
4113 }
+
4054 void
+
4055 testFlow()
+
4056 {
+
4057 using namespace jtx;
+
4058 FeatureBitset const all{supported_amendments()};
+
4059 FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
+
4060
+
4061 testFalseDry(all);
+
4062 testBookStep(all);
+
4063 testBookStep(all | ownerPaysFee);
+
4064 testTransferRate(all | ownerPaysFee);
+
4065 testTransferRate((all - fixAMMv1_1) | ownerPaysFee);
+
4066 testTransferRateNoOwnerFee(all);
+
4067 testTransferRateNoOwnerFee(all - fixAMMv1_1);
+
4068 testLimitQuality();
+
4069 testXRPPathLoop();
+
4070 }
+
4071
+
4072 void
+
4073 testCrossingLimits()
+
4074 {
+
4075 using namespace jtx;
+
4076 FeatureBitset const all{supported_amendments()};
+
4077 testStepLimit(all);
+
4078 testStepLimit(all - fixAMMv1_1);
+
4079 }
+
4080
+
4081 void
+
4082 testDeliverMin()
+
4083 {
+
4084 using namespace jtx;
+
4085 FeatureBitset const all{supported_amendments()};
+
4086 test_convert_all_of_an_asset(all);
+
4087 test_convert_all_of_an_asset(all - fixAMMv1_1);
+
4088 }
+
4089
+
4090 void
+
4091 testDepositAuth()
+
4092 {
+
4093 auto const supported{jtx::supported_amendments()};
+
4094 testPayment(supported - featureDepositPreauth);
+
4095 testPayment(supported);
+
4096 testPayIOU();
+
4097 }
+
4098
+
4099 void
+
4100 testFreeze()
+
4101 {
+
4102 using namespace test::jtx;
+
4103 auto const sa = supported_amendments();
+
4104 testRippleState(sa);
+
4105 testGlobalFreeze(sa);
+
4106 testOffersWhenFrozen(sa);
+
4107 }
+
4108
+
4109 void
+
4110 testMultisign()
+
4111 {
+
4112 using namespace jtx;
+
4113 auto const all = supported_amendments();
4114
-
4115 void
-
4116 testDepositAuth()
-
4117 {
-
4118 auto const supported{jtx::supported_amendments()};
-
4119 testPayment(supported - featureDepositPreauth);
-
4120 testPayment(supported);
-
4121 testPayIOU();
-
4122 }
-
4123
-
4124 void
-
4125 testFreeze()
-
4126 {
-
4127 using namespace test::jtx;
-
4128 auto const sa = supported_amendments();
-
4129 testRippleState(sa);
-
4130 testGlobalFreeze(sa);
-
4131 testOffersWhenFrozen(sa);
-
4132 }
-
4133
-
4134 void
-
4135 testMultisign()
-
4136 {
-
4137 using namespace jtx;
-
4138 auto const all = supported_amendments();
-
4139
-
4140 testTxMultisign(
-
4141 all - featureMultiSignReserve - featureExpandedSignerList);
-
4142 testTxMultisign(all - featureExpandedSignerList);
-
4143 testTxMultisign(all);
+
4115 testTxMultisign(
+
4116 all - featureMultiSignReserve - featureExpandedSignerList);
+
4117 testTxMultisign(all - featureExpandedSignerList);
+
4118 testTxMultisign(all);
+
4119 }
+
4120
+
4121 void
+
4122 testPayStrand()
+
4123 {
+
4124 using namespace jtx;
+
4125 auto const all = supported_amendments();
+
4126
+
4127 testToStrand(all);
+
4128 testRIPD1373(all);
+
4129 testLoop(all);
+
4130 }
+
4131
+
4132 void
+
4133 run() override
+
4134 {
+
4135 testOffers();
+
4136 testPaths();
+
4137 testFlow();
+
4138 testCrossingLimits();
+
4139 testDeliverMin();
+
4140 testDepositAuth();
+
4141 testFreeze();
+
4142 testMultisign();
+
4143 testPayStrand();
4144 }
-
4145
-
4146 void
-
4147 testPayStrand()
-
4148 {
-
4149 using namespace jtx;
-
4150 auto const all = supported_amendments();
-
4151
-
4152 testToStrand(all);
-
4153 testRIPD1373(all);
-
4154 testLoop(all);
-
4155 }
-
4156
-
4157 void
-
4158 run() override
-
4159 {
-
4160 testOffers();
-
4161 testPaths();
-
4162 testFlow();
-
4163 testCrossingLimits();
-
4164 testDeliverMin();
-
4165 testDepositAuth();
-
4166 testFreeze();
-
4167 testMultisign();
-
4168 testPayStrand();
-
4169 }
-
4170};
-
4171
-
4172BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app, ripple, 1);
-
4173
-
4174} // namespace test
-
4175} // namespace ripple
+
4145};
+
4146
+
4147BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app, ripple, 1);
+
4148
+
4149} // namespace test
+
4150} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
A generic endpoint for log messages.
Definition: Journal.h:60
@@ -4373,7 +4348,6 @@ $(function() {
@ noripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
-
AccountID const & noAccount()
A placeholder for empty accounts.
Definition: AccountID.cpp:185
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
constexpr std::uint32_t asfGlobalFreeze
Definition: TxFlags.h:82
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:84
@@ -4424,63 +4398,63 @@ $(function() {
@ none
Definition: STBase.h:44
Tests of AMM that use offers too.
-
void testGlobalFreeze(FeatureBitset features)
-
void testFlow()
-
void testPayStrand()
-
void testOfferCrossWithXRP(FeatureBitset features)
-
void testCurrencyConversionEntire(FeatureBitset features)
-
void testTransferRate(FeatureBitset features)
-
void testCrossingLimits()
-
void testFalseDry(FeatureBitset features)
-
void receive_max()
-
void testOfferCrossWithLimitOverride(FeatureBitset features)
-
void testTransferRateOffer(FeatureBitset features)
-
void testDeliverMin()
-
void path_find_05()
-
void testBookStep(FeatureBitset features)
-
void path_find_01()
-
void testBridgedCross(FeatureBitset features)
-
void test_convert_all_of_an_asset(FeatureBitset features)
-
void testGatewayCrossCurrency(FeatureBitset features)
-
void testRequireAuth(FeatureBitset features)
-
void path_find_06()
-
void testPayment(FeatureBitset features)
-
void testFreeze()
-
void testOfferFeesConsumeFunds(FeatureBitset features)
-
void path_find_02()
-
void testMultisign()
-
void testOffersWhenFrozen(FeatureBitset features)
-
void testSellFlagExceedLimit(FeatureBitset features)
-
void testCrossCurrencyBridged(FeatureBitset features)
-
void testBadPathAssert(FeatureBitset features)
-
void testLoop(FeatureBitset features)
-
void via_offers_via_gateway()
-
void testOfferCreateThenCross(FeatureBitset features)
-
void testToStrand(FeatureBitset features)
-
void run() override
Runs the suite.
-
void testFillModes(FeatureBitset features)
-
void testPaths()
-
void testMissingAuth(FeatureBitset features)
-
void path_find_consume_all()
-
void testRIPD1373(FeatureBitset features)
-
void testCrossCurrencyEndXRP(FeatureBitset features)
-
void testXRPPathLoop()
-
void testCurrencyConversionInParts(FeatureBitset features)
-
void testOffers()
-
void testTransferRateNoOwnerFee(FeatureBitset features)
-
void testRippleState(FeatureBitset features)
-
void testPayIOU()
+
void testGlobalFreeze(FeatureBitset features)
+
void testFlow()
+
void testPayStrand()
+
void testOfferCrossWithXRP(FeatureBitset features)
+
void testCurrencyConversionEntire(FeatureBitset features)
+
void testTransferRate(FeatureBitset features)
+
void testCrossingLimits()
+
void testFalseDry(FeatureBitset features)
+
void receive_max()
+
void testOfferCrossWithLimitOverride(FeatureBitset features)
+
void testTransferRateOffer(FeatureBitset features)
+
void testDeliverMin()
+
void path_find_05()
+
void testBookStep(FeatureBitset features)
+
void path_find_01()
+
void testBridgedCross(FeatureBitset features)
+
void test_convert_all_of_an_asset(FeatureBitset features)
+
void testGatewayCrossCurrency(FeatureBitset features)
+
void testRequireAuth(FeatureBitset features)
+
void path_find_06()
+
void testPayment(FeatureBitset features)
+
void testFreeze()
+
void testOfferFeesConsumeFunds(FeatureBitset features)
+
void path_find_02()
+
void testMultisign()
+
void testOffersWhenFrozen(FeatureBitset features)
+
void testSellFlagExceedLimit(FeatureBitset features)
+
void testCrossCurrencyBridged(FeatureBitset features)
+
void testBadPathAssert(FeatureBitset features)
+
void testLoop(FeatureBitset features)
+
void via_offers_via_gateway()
+
void testOfferCreateThenCross(FeatureBitset features)
+
void testToStrand(FeatureBitset features)
+
void run() override
Runs the suite.
+
void testFillModes(FeatureBitset features)
+
void testPaths()
+
void testMissingAuth(FeatureBitset features)
+
void path_find_consume_all()
+
void testRIPD1373(FeatureBitset features)
+
void testCrossCurrencyEndXRP(FeatureBitset features)
+
void testXRPPathLoop()
+
void testCurrencyConversionInParts(FeatureBitset features)
+
void testOffers()
+
void testTransferRateNoOwnerFee(FeatureBitset features)
+
void testRippleState(FeatureBitset features)
+
void testPayIOU()
void testRmFundedOffer(FeatureBitset features)
-
void testSelfIssueOffer(FeatureBitset features)
-
void testDirectToDirectPath(FeatureBitset features)
-
void testDepositAuth()
-
void testStepLimit(FeatureBitset features)
-
void testLimitQuality()
+
void testSelfIssueOffer(FeatureBitset features)
+
void testDirectToDirectPath(FeatureBitset features)
+
void testDepositAuth()
+
void testStepLimit(FeatureBitset features)
+
void testLimitQuality()
void testEnforceNoRipple(FeatureBitset features)
-
void testCrossCurrencyStartXRP(FeatureBitset features)
-
void testSellWithFillOrKill(FeatureBitset features)
-
void testTxMultisign(FeatureBitset features)
-
void testSellFlagBasic(FeatureBitset features)
+
void testCrossCurrencyStartXRP(FeatureBitset features)
+
void testSellWithFillOrKill(FeatureBitset features)
+
void testTxMultisign(FeatureBitset features)
+
void testSellFlagBasic(FeatureBitset features)
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...
STAmount const & value() const
T tie(T... args)
diff --git a/AMM__test_8cpp_source.html b/AMM__test_8cpp_source.html index c6c94cb748..5dac1d3633 100644 --- a/AMM__test_8cpp_source.html +++ b/AMM__test_8cpp_source.html @@ -1382,5869 +1382,5892 @@ $(function() {
1308
1309 // Equal deposit: 1000000 tokens, 10% of the current pool
1310 testAMM([&](AMM& ammAlice, Env& env) {
-
1311 ammAlice.deposit(carol, 1'000'000);
-
1312 BEAST_EXPECT(ammAlice.expectBalances(
-
1313 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1314 // 30,000 less deposited 1,000
-
1315 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
-
1316 // 30,000 less deposited 1,000 and 10 drops tx fee
-
1317 BEAST_EXPECT(
-
1318 expectLedgerEntryRoot(env, carol, XRPAmount{28'999'999'990}));
-
1319 });
-
1320
-
1321 // equal asset deposit: unit test to exercise the rounding-down of
-
1322 // LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
-
1323 // The LPTokens need to have 16 significant digits and a fractional part
-
1324 for (const Number deltaLPTokens :
-
1325 {Number{UINT64_C(100000'0000000009), -10},
-
1326 Number{UINT64_C(100000'0000000001), -10}})
-
1327 {
-
1328 testAMM([&](AMM& ammAlice, Env& env) {
-
1329 // initial LPToken balance
-
1330 IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
-
1331 const IOUAmount newLPTokens{
-
1332 deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
-
1333
-
1334 // carol performs a two-asset deposit
-
1335 ammAlice.deposit(
-
1336 DepositArg{.account = carol, .tokens = newLPTokens});
-
1337
-
1338 IOUAmount const finalLPToken = ammAlice.getLPTokensBalance();
-
1339
-
1340 // Change in behavior due to rounding down of LPTokens:
-
1341 // there is a decrease in the observed return of LPTokens --
-
1342 // Inputs Number{UINT64_C(100000'0000000001), -10} and
-
1343 // Number{UINT64_C(100000'0000000009), -10} are both rounded
-
1344 // down to 1e5
-
1345 BEAST_EXPECT((finalLPToken - initLPToken == IOUAmount{1, 5}));
-
1346 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
-
1347
-
1348 // fraction of newLPTokens/(existing LPToken balance). The
-
1349 // existing LPToken balance is 1e7
-
1350 const Number fr = deltaLPTokens / 1e7;
-
1351
-
1352 // The below equations are based on Equation 1, 2 from XLS-30d
-
1353 // specification, Section: 2.3.1.2
-
1354 const Number deltaXRP = fr * 1e10;
-
1355 const Number deltaUSD = fr * 1e4;
-
1356
-
1357 const STAmount depositUSD =
-
1358 STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
-
1359
-
1360 const STAmount depositXRP =
-
1361 STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
-
1362
-
1363 // initial LPTokens (1e7) + newLPTokens
-
1364 BEAST_EXPECT(ammAlice.expectBalances(
-
1365 XRP(10'000) + depositXRP,
-
1366 USD(10'000) + depositUSD,
-
1367 IOUAmount{1, 7} + newLPTokens));
-
1368
-
1369 // 30,000 less deposited depositUSD
-
1370 BEAST_EXPECT(expectLine(env, carol, USD(30'000) - depositUSD));
-
1371 // 30,000 less deposited depositXRP and 10 drops tx fee
-
1372 BEAST_EXPECT(expectLedgerEntryRoot(
-
1373 env, carol, XRP(30'000) - depositXRP - txfee(env, 1)));
-
1374 });
-
1375 }
-
1376
-
1377 // Equal limit deposit: deposit USD100 and XRP proportionally
-
1378 // to the pool composition not to exceed 100XRP. If the amount
-
1379 // exceeds 100XRP then deposit 100XRP and USD proportionally
-
1380 // to the pool composition not to exceed 100USD. Fail if exceeded.
-
1381 // Deposit 100USD/100XRP
-
1382 testAMM([&](AMM& ammAlice, Env&) {
-
1383 ammAlice.deposit(carol, USD(100), XRP(100));
-
1384 BEAST_EXPECT(ammAlice.expectBalances(
-
1385 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1386 });
-
1387
-
1388 // Equal limit deposit.
-
1389 // Try to deposit 200USD/100XRP. Is truncated to 100USD/100XRP.
-
1390 testAMM([&](AMM& ammAlice, Env&) {
-
1391 ammAlice.deposit(carol, USD(200), XRP(100));
-
1392 BEAST_EXPECT(ammAlice.expectBalances(
-
1393 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1394 });
-
1395 // Try to deposit 100USD/200XRP. Is truncated to 100USD/100XRP.
-
1396 testAMM([&](AMM& ammAlice, Env&) {
-
1397 ammAlice.deposit(carol, USD(100), XRP(200));
-
1398 BEAST_EXPECT(ammAlice.expectBalances(
-
1399 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
-
1400 });
-
1401
-
1402 // Single deposit: 1000 USD
-
1403 testAMM([&](AMM& ammAlice, Env&) {
-
1404 ammAlice.deposit(carol, USD(1'000));
-
1405 BEAST_EXPECT(ammAlice.expectBalances(
-
1406 XRP(10'000),
-
1407 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1408 IOUAmount{10'488'088'48170151, -8}));
-
1409 });
-
1410
-
1411 // Single deposit: 1000 XRP
-
1412 testAMM([&](AMM& ammAlice, Env&) {
-
1413 ammAlice.deposit(carol, XRP(1'000));
-
1414 BEAST_EXPECT(ammAlice.expectBalances(
-
1415 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
-
1416 });
-
1417
-
1418 // Single deposit: 100000 tokens worth of USD
-
1419 testAMM([&](AMM& ammAlice, Env&) {
-
1420 ammAlice.deposit(carol, 100000, USD(205));
-
1421 BEAST_EXPECT(ammAlice.expectBalances(
-
1422 XRP(10'000), USD(10'201), IOUAmount{10'100'000, 0}));
-
1423 });
-
1424
-
1425 // Single deposit: 100000 tokens worth of XRP
-
1426 testAMM([&](AMM& ammAlice, Env&) {
-
1427 ammAlice.deposit(carol, 100'000, XRP(205));
-
1428 BEAST_EXPECT(ammAlice.expectBalances(
-
1429 XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0}));
-
1430 });
-
1431
-
1432 // Single deposit with EP not exceeding specified:
-
1433 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut)
-
1434 testAMM([&](AMM& ammAlice, Env&) {
-
1435 ammAlice.deposit(
-
1436 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
-
1437 BEAST_EXPECT(ammAlice.expectBalances(
-
1438 XRP(10'000),
-
1439 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1440 IOUAmount{10'488'088'48170151, -8}));
-
1441 });
-
1442
-
1443 // Single deposit with EP not exceeding specified:
-
1444 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
-
1445 testAMM([&](AMM& ammAlice, Env&) {
-
1446 ammAlice.deposit(
-
1447 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
-
1448 BEAST_EXPECT(ammAlice.expectBalances(
-
1449 XRP(10'000),
-
1450 STAmount{USD, 10'080'16, -2},
-
1451 IOUAmount{10'040'000, 0}));
-
1452 });
-
1453
-
1454 // Single deposit with EP not exceeding specified:
-
1455 // 0USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
-
1456 testAMM([&](AMM& ammAlice, Env&) {
-
1457 ammAlice.deposit(
-
1458 carol, USD(0), std::nullopt, STAmount{USD, 2004, -6});
-
1459 BEAST_EXPECT(ammAlice.expectBalances(
-
1460 XRP(10'000),
-
1461 STAmount{USD, 10'080'16, -2},
-
1462 IOUAmount{10'040'000, 0}));
-
1463 });
-
1464
-
1465 // IOU to IOU + transfer fee
-
1466 {
-
1467 Env env{*this};
-
1468 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
1469 env(rate(gw, 1.25));
-
1470 env.close();
-
1471 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
1472 BEAST_EXPECT(ammAlice.expectBalances(
-
1473 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
1474 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
1475 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
1476 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
-
1477 // no transfer fee on deposit
-
1478 ammAlice.deposit(carol, 10);
-
1479 BEAST_EXPECT(ammAlice.expectBalances(
-
1480 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
-
1481 BEAST_EXPECT(expectLine(env, carol, USD(0)));
-
1482 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
-
1483 }
-
1484
-
1485 // Tiny deposits
-
1486 testAMM([&](AMM& ammAlice, Env&) {
-
1487 ammAlice.deposit(carol, IOUAmount{1, -3});
-
1488 BEAST_EXPECT(ammAlice.expectBalances(
-
1489 XRPAmount{10'000'000'001},
-
1490 STAmount{USD, UINT64_C(10'000'000001), -6},
-
1491 IOUAmount{10'000'000'001, -3}));
-
1492 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1, -3}));
-
1493 });
-
1494 testAMM([&](AMM& ammAlice, Env&) {
-
1495 ammAlice.deposit(carol, XRPAmount{1});
-
1496 BEAST_EXPECT(ammAlice.expectBalances(
-
1497 XRPAmount{10'000'000'001},
-
1498 USD(10'000),
-
1499 IOUAmount{1'000'000'000049999, -8}));
-
1500 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
-
1501 });
-
1502 testAMM([&](AMM& ammAlice, Env&) {
-
1503 ammAlice.deposit(carol, STAmount{USD, 1, -10});
-
1504 BEAST_EXPECT(ammAlice.expectBalances(
-
1505 XRP(10'000),
-
1506 STAmount{USD, UINT64_C(10'000'00000000008), -11},
-
1507 IOUAmount{10'000'000'00000004, -8}));
-
1508 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
-
1509 });
-
1510
-
1511 // Issuer create/deposit
-
1512 {
-
1513 Env env(*this);
-
1514 env.fund(XRP(30000), gw);
-
1515 AMM ammGw(env, gw, XRP(10'000), USD(10'000));
-
1516 BEAST_EXPECT(
-
1517 ammGw.expectBalances(XRP(10'000), USD(10'000), ammGw.tokens()));
-
1518 ammGw.deposit(gw, 1'000'000);
-
1519 BEAST_EXPECT(ammGw.expectBalances(
-
1520 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
-
1521 ammGw.deposit(gw, USD(1'000));
-
1522 BEAST_EXPECT(ammGw.expectBalances(
-
1523 XRP(11'000),
-
1524 STAmount{USD, UINT64_C(11'999'99999999998), -11},
-
1525 IOUAmount{11'489'125'29307605, -8}));
-
1526 }
-
1527
-
1528 // Issuer deposit
-
1529 testAMM([&](AMM& ammAlice, Env& env) {
-
1530 ammAlice.deposit(gw, 1'000'000);
-
1531 BEAST_EXPECT(ammAlice.expectBalances(
-
1532 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
-
1533 ammAlice.deposit(gw, USD(1'000));
-
1534 BEAST_EXPECT(ammAlice.expectBalances(
-
1535 XRP(11'000),
-
1536 STAmount{USD, UINT64_C(11'999'99999999998), -11},
-
1537 IOUAmount{11'489'125'29307605, -8}));
-
1538 });
-
1539
-
1540 // Min deposit
-
1541 testAMM([&](AMM& ammAlice, Env& env) {
-
1542 // Equal deposit by tokens
-
1543 ammAlice.deposit(
-
1544 carol,
-
1545 1'000'000,
-
1546 XRP(1'000),
-
1547 USD(1'000),
-
1548 std::nullopt,
-
1549 tfLPToken,
-
1550 std::nullopt,
-
1551 std::nullopt);
-
1552 BEAST_EXPECT(ammAlice.expectBalances(
-
1553 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1554 });
-
1555 testAMM([&](AMM& ammAlice, Env& env) {
-
1556 // Equal deposit by asset
-
1557 ammAlice.deposit(
-
1558 carol,
-
1559 1'000'000,
-
1560 XRP(1'000),
-
1561 USD(1'000),
-
1562 std::nullopt,
-
1563 tfTwoAsset,
-
1564 std::nullopt,
-
1565 std::nullopt);
-
1566 BEAST_EXPECT(ammAlice.expectBalances(
-
1567 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1568 });
-
1569 testAMM([&](AMM& ammAlice, Env& env) {
-
1570 // Single deposit by asset
-
1571 ammAlice.deposit(
-
1572 carol,
-
1573 488'088,
-
1574 XRP(1'000),
-
1575 std::nullopt,
+
1311 auto const baseFee = env.current()->fees().base;
+
1312 ammAlice.deposit(carol, 1'000'000);
+
1313 BEAST_EXPECT(ammAlice.expectBalances(
+
1314 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1315 // 30,000 less deposited 1,000
+
1316 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
+
1317 // 30,000 less deposited 1,000 and 10 drops tx fee
+
1318 BEAST_EXPECT(expectLedgerEntryRoot(
+
1319 env, carol, XRPAmount{29'000'000'000 - baseFee}));
+
1320 });
+
1321
+
1322 // equal asset deposit: unit test to exercise the rounding-down of
+
1323 // LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
+
1324 // The LPTokens need to have 16 significant digits and a fractional part
+
1325 for (const Number deltaLPTokens :
+
1326 {Number{UINT64_C(100000'0000000009), -10},
+
1327 Number{UINT64_C(100000'0000000001), -10}})
+
1328 {
+
1329 testAMM([&](AMM& ammAlice, Env& env) {
+
1330 // initial LPToken balance
+
1331 IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
+
1332 const IOUAmount newLPTokens{
+
1333 deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
+
1334
+
1335 // carol performs a two-asset deposit
+
1336 ammAlice.deposit(
+
1337 DepositArg{.account = carol, .tokens = newLPTokens});
+
1338
+
1339 IOUAmount const finalLPToken = ammAlice.getLPTokensBalance();
+
1340
+
1341 // Change in behavior due to rounding down of LPTokens:
+
1342 // there is a decrease in the observed return of LPTokens --
+
1343 // Inputs Number{UINT64_C(100000'0000000001), -10} and
+
1344 // Number{UINT64_C(100000'0000000009), -10} are both rounded
+
1345 // down to 1e5
+
1346 BEAST_EXPECT((finalLPToken - initLPToken == IOUAmount{1, 5}));
+
1347 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
+
1348
+
1349 // fraction of newLPTokens/(existing LPToken balance). The
+
1350 // existing LPToken balance is 1e7
+
1351 const Number fr = deltaLPTokens / 1e7;
+
1352
+
1353 // The below equations are based on Equation 1, 2 from XLS-30d
+
1354 // specification, Section: 2.3.1.2
+
1355 const Number deltaXRP = fr * 1e10;
+
1356 const Number deltaUSD = fr * 1e4;
+
1357
+
1358 const STAmount depositUSD =
+
1359 STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
+
1360
+
1361 const STAmount depositXRP =
+
1362 STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
+
1363
+
1364 // initial LPTokens (1e7) + newLPTokens
+
1365 BEAST_EXPECT(ammAlice.expectBalances(
+
1366 XRP(10'000) + depositXRP,
+
1367 USD(10'000) + depositUSD,
+
1368 IOUAmount{1, 7} + newLPTokens));
+
1369
+
1370 // 30,000 less deposited depositUSD
+
1371 BEAST_EXPECT(expectLine(env, carol, USD(30'000) - depositUSD));
+
1372 // 30,000 less deposited depositXRP and 10 drops tx fee
+
1373 BEAST_EXPECT(expectLedgerEntryRoot(
+
1374 env, carol, XRP(30'000) - depositXRP - txfee(env, 1)));
+
1375 });
+
1376 }
+
1377
+
1378 // Equal limit deposit: deposit USD100 and XRP proportionally
+
1379 // to the pool composition not to exceed 100XRP. If the amount
+
1380 // exceeds 100XRP then deposit 100XRP and USD proportionally
+
1381 // to the pool composition not to exceed 100USD. Fail if exceeded.
+
1382 // Deposit 100USD/100XRP
+
1383 testAMM([&](AMM& ammAlice, Env&) {
+
1384 ammAlice.deposit(carol, USD(100), XRP(100));
+
1385 BEAST_EXPECT(ammAlice.expectBalances(
+
1386 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1387 });
+
1388
+
1389 // Equal limit deposit.
+
1390 // Try to deposit 200USD/100XRP. Is truncated to 100USD/100XRP.
+
1391 testAMM([&](AMM& ammAlice, Env&) {
+
1392 ammAlice.deposit(carol, USD(200), XRP(100));
+
1393 BEAST_EXPECT(ammAlice.expectBalances(
+
1394 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1395 });
+
1396 // Try to deposit 100USD/200XRP. Is truncated to 100USD/100XRP.
+
1397 testAMM([&](AMM& ammAlice, Env&) {
+
1398 ammAlice.deposit(carol, USD(100), XRP(200));
+
1399 BEAST_EXPECT(ammAlice.expectBalances(
+
1400 XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
+
1401 });
+
1402
+
1403 // Single deposit: 1000 USD
+
1404 testAMM([&](AMM& ammAlice, Env&) {
+
1405 ammAlice.deposit(carol, USD(1'000));
+
1406 BEAST_EXPECT(ammAlice.expectBalances(
+
1407 XRP(10'000),
+
1408 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1409 IOUAmount{10'488'088'48170151, -8}));
+
1410 });
+
1411
+
1412 // Single deposit: 1000 XRP
+
1413 testAMM([&](AMM& ammAlice, Env&) {
+
1414 ammAlice.deposit(carol, XRP(1'000));
+
1415 BEAST_EXPECT(ammAlice.expectBalances(
+
1416 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
+
1417 });
+
1418
+
1419 // Single deposit: 100000 tokens worth of USD
+
1420 testAMM([&](AMM& ammAlice, Env&) {
+
1421 ammAlice.deposit(carol, 100000, USD(205));
+
1422 BEAST_EXPECT(ammAlice.expectBalances(
+
1423 XRP(10'000), USD(10'201), IOUAmount{10'100'000, 0}));
+
1424 });
+
1425
+
1426 // Single deposit: 100000 tokens worth of XRP
+
1427 testAMM([&](AMM& ammAlice, Env&) {
+
1428 ammAlice.deposit(carol, 100'000, XRP(205));
+
1429 BEAST_EXPECT(ammAlice.expectBalances(
+
1430 XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0}));
+
1431 });
+
1432
+
1433 // Single deposit with EP not exceeding specified:
+
1434 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut)
+
1435 testAMM([&](AMM& ammAlice, Env&) {
+
1436 ammAlice.deposit(
+
1437 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
+
1438 BEAST_EXPECT(ammAlice.expectBalances(
+
1439 XRP(10'000),
+
1440 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1441 IOUAmount{10'488'088'48170151, -8}));
+
1442 });
+
1443
+
1444 // Single deposit with EP not exceeding specified:
+
1445 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
+
1446 testAMM([&](AMM& ammAlice, Env&) {
+
1447 ammAlice.deposit(
+
1448 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
+
1449 BEAST_EXPECT(ammAlice.expectBalances(
+
1450 XRP(10'000),
+
1451 STAmount{USD, 10'080'16, -2},
+
1452 IOUAmount{10'040'000, 0}));
+
1453 });
+
1454
+
1455 // Single deposit with EP not exceeding specified:
+
1456 // 0USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
+
1457 testAMM([&](AMM& ammAlice, Env&) {
+
1458 ammAlice.deposit(
+
1459 carol, USD(0), std::nullopt, STAmount{USD, 2004, -6});
+
1460 BEAST_EXPECT(ammAlice.expectBalances(
+
1461 XRP(10'000),
+
1462 STAmount{USD, 10'080'16, -2},
+
1463 IOUAmount{10'040'000, 0}));
+
1464 });
+
1465
+
1466 // IOU to IOU + transfer fee
+
1467 {
+
1468 Env env{*this};
+
1469 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
1470 env(rate(gw, 1.25));
+
1471 env.close();
+
1472 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
+
1473 BEAST_EXPECT(ammAlice.expectBalances(
+
1474 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
1475 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
1476 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
1477 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
+
1478 // no transfer fee on deposit
+
1479 ammAlice.deposit(carol, 10);
+
1480 BEAST_EXPECT(ammAlice.expectBalances(
+
1481 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
+
1482 BEAST_EXPECT(expectLine(env, carol, USD(0)));
+
1483 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
+
1484 }
+
1485
+
1486 // Tiny deposits
+
1487 testAMM([&](AMM& ammAlice, Env&) {
+
1488 ammAlice.deposit(carol, IOUAmount{1, -3});
+
1489 BEAST_EXPECT(ammAlice.expectBalances(
+
1490 XRPAmount{10'000'000'001},
+
1491 STAmount{USD, UINT64_C(10'000'000001), -6},
+
1492 IOUAmount{10'000'000'001, -3}));
+
1493 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1, -3}));
+
1494 });
+
1495 testAMM([&](AMM& ammAlice, Env&) {
+
1496 ammAlice.deposit(carol, XRPAmount{1});
+
1497 BEAST_EXPECT(ammAlice.expectBalances(
+
1498 XRPAmount{10'000'000'001},
+
1499 USD(10'000),
+
1500 IOUAmount{1'000'000'000049999, -8}));
+
1501 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
+
1502 });
+
1503 testAMM([&](AMM& ammAlice, Env&) {
+
1504 ammAlice.deposit(carol, STAmount{USD, 1, -10});
+
1505 BEAST_EXPECT(ammAlice.expectBalances(
+
1506 XRP(10'000),
+
1507 STAmount{USD, UINT64_C(10'000'00000000008), -11},
+
1508 IOUAmount{10'000'000'00000004, -8}));
+
1509 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
+
1510 });
+
1511
+
1512 // Issuer create/deposit
+
1513 {
+
1514 Env env(*this);
+
1515 env.fund(XRP(30000), gw);
+
1516 AMM ammGw(env, gw, XRP(10'000), USD(10'000));
+
1517 BEAST_EXPECT(
+
1518 ammGw.expectBalances(XRP(10'000), USD(10'000), ammGw.tokens()));
+
1519 ammGw.deposit(gw, 1'000'000);
+
1520 BEAST_EXPECT(ammGw.expectBalances(
+
1521 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
+
1522 ammGw.deposit(gw, USD(1'000));
+
1523 BEAST_EXPECT(ammGw.expectBalances(
+
1524 XRP(11'000),
+
1525 STAmount{USD, UINT64_C(11'999'99999999998), -11},
+
1526 IOUAmount{11'489'125'29307605, -8}));
+
1527 }
+
1528
+
1529 // Issuer deposit
+
1530 testAMM([&](AMM& ammAlice, Env& env) {
+
1531 ammAlice.deposit(gw, 1'000'000);
+
1532 BEAST_EXPECT(ammAlice.expectBalances(
+
1533 XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
+
1534 ammAlice.deposit(gw, USD(1'000));
+
1535 BEAST_EXPECT(ammAlice.expectBalances(
+
1536 XRP(11'000),
+
1537 STAmount{USD, UINT64_C(11'999'99999999998), -11},
+
1538 IOUAmount{11'489'125'29307605, -8}));
+
1539 });
+
1540
+
1541 // Min deposit
+
1542 testAMM([&](AMM& ammAlice, Env& env) {
+
1543 // Equal deposit by tokens
+
1544 ammAlice.deposit(
+
1545 carol,
+
1546 1'000'000,
+
1547 XRP(1'000),
+
1548 USD(1'000),
+
1549 std::nullopt,
+
1550 tfLPToken,
+
1551 std::nullopt,
+
1552 std::nullopt);
+
1553 BEAST_EXPECT(ammAlice.expectBalances(
+
1554 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1555 });
+
1556 testAMM([&](AMM& ammAlice, Env& env) {
+
1557 // Equal deposit by asset
+
1558 ammAlice.deposit(
+
1559 carol,
+
1560 1'000'000,
+
1561 XRP(1'000),
+
1562 USD(1'000),
+
1563 std::nullopt,
+
1564 tfTwoAsset,
+
1565 std::nullopt,
+
1566 std::nullopt);
+
1567 BEAST_EXPECT(ammAlice.expectBalances(
+
1568 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1569 });
+
1570 testAMM([&](AMM& ammAlice, Env& env) {
+
1571 // Single deposit by asset
+
1572 ammAlice.deposit(
+
1573 carol,
+
1574 488'088,
+
1575 XRP(1'000),
1576 std::nullopt,
-
1577 tfSingleAsset,
-
1578 std::nullopt,
-
1579 std::nullopt);
-
1580 BEAST_EXPECT(ammAlice.expectBalances(
-
1581 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
-
1582 });
-
1583 testAMM([&](AMM& ammAlice, Env& env) {
-
1584 // Single deposit by asset
-
1585 ammAlice.deposit(
-
1586 carol,
-
1587 488'088,
-
1588 USD(1'000),
-
1589 std::nullopt,
+
1577 std::nullopt,
+
1578 tfSingleAsset,
+
1579 std::nullopt,
+
1580 std::nullopt);
+
1581 BEAST_EXPECT(ammAlice.expectBalances(
+
1582 XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
+
1583 });
+
1584 testAMM([&](AMM& ammAlice, Env& env) {
+
1585 // Single deposit by asset
+
1586 ammAlice.deposit(
+
1587 carol,
+
1588 488'088,
+
1589 USD(1'000),
1590 std::nullopt,
-
1591 tfSingleAsset,
-
1592 std::nullopt,
-
1593 std::nullopt);
-
1594 BEAST_EXPECT(ammAlice.expectBalances(
-
1595 XRP(10'000),
-
1596 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
1597 IOUAmount{10'488'088'48170151, -8}));
-
1598 });
-
1599 }
-
1600
-
1601 void
-
1602 testInvalidWithdraw()
-
1603 {
-
1604 testcase("Invalid Withdraw");
-
1605
-
1606 using namespace jtx;
-
1607
-
1608 testAMM(
-
1609 [&](AMM& ammAlice, Env& env) {
-
1610 WithdrawArg args{
-
1611 .asset1Out = XRP(100),
-
1612 .err = ter(tecAMM_BALANCE),
-
1613 };
-
1614 ammAlice.withdraw(args);
-
1615 },
-
1616 {{XRP(99), USD(99)}});
-
1617
-
1618 testAMM(
-
1619 [&](AMM& ammAlice, Env& env) {
-
1620 WithdrawArg args{
-
1621 .asset1Out = USD(100),
-
1622 .err = ter(tecAMM_BALANCE),
-
1623 };
-
1624 ammAlice.withdraw(args);
-
1625 },
-
1626 {{XRP(99), USD(99)}});
-
1627
-
1628 {
-
1629 Env env{*this};
-
1630 env.fund(XRP(30'000), gw, alice, bob);
-
1631 env.close();
-
1632 env(fset(gw, asfRequireAuth));
-
1633 env(trust(alice, gw["USD"](30'000), 0));
-
1634 env(trust(gw, alice["USD"](0), tfSetfAuth));
-
1635 // Bob trusts Gateway to owe him USD...
-
1636 env(trust(bob, gw["USD"](30'000), 0));
-
1637 // ...but Gateway does not authorize Bob to hold its USD.
-
1638 env.close();
-
1639 env(pay(gw, alice, USD(10'000)));
+
1591 std::nullopt,
+
1592 tfSingleAsset,
+
1593 std::nullopt,
+
1594 std::nullopt);
+
1595 BEAST_EXPECT(ammAlice.expectBalances(
+
1596 XRP(10'000),
+
1597 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
1598 IOUAmount{10'488'088'48170151, -8}));
+
1599 });
+
1600 }
+
1601
+
1602 void
+
1603 testInvalidWithdraw()
+
1604 {
+
1605 testcase("Invalid Withdraw");
+
1606
+
1607 using namespace jtx;
+
1608
+
1609 testAMM(
+
1610 [&](AMM& ammAlice, Env& env) {
+
1611 WithdrawArg args{
+
1612 .asset1Out = XRP(100),
+
1613 .err = ter(tecAMM_BALANCE),
+
1614 };
+
1615 ammAlice.withdraw(args);
+
1616 },
+
1617 {{XRP(99), USD(99)}});
+
1618
+
1619 testAMM(
+
1620 [&](AMM& ammAlice, Env& env) {
+
1621 WithdrawArg args{
+
1622 .asset1Out = USD(100),
+
1623 .err = ter(tecAMM_BALANCE),
+
1624 };
+
1625 ammAlice.withdraw(args);
+
1626 },
+
1627 {{XRP(99), USD(99)}});
+
1628
+
1629 {
+
1630 Env env{*this};
+
1631 env.fund(XRP(30'000), gw, alice, bob);
+
1632 env.close();
+
1633 env(fset(gw, asfRequireAuth));
+
1634 env.close();
+
1635 env(trust(alice, gw["USD"](30'000), 0));
+
1636 env(trust(gw, alice["USD"](0), tfSetfAuth));
+
1637 // Bob trusts Gateway to owe him USD...
+
1638 env(trust(bob, gw["USD"](30'000), 0));
+
1639 // ...but Gateway does not authorize Bob to hold its USD.
1640 env.close();
-
1641 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
-
1642 WithdrawArg args{
-
1643 .account = bob,
-
1644 .asset1Out = USD(100),
-
1645 .err = ter(tecNO_AUTH),
-
1646 };
-
1647 ammAlice.withdraw(args);
-
1648 }
-
1649
-
1650 testAMM([&](AMM& ammAlice, Env& env) {
-
1651 // Invalid flags
-
1652 ammAlice.withdraw(
-
1653 alice,
-
1654 1'000'000,
-
1655 std::nullopt,
-
1656 std::nullopt,
+
1641 env(pay(gw, alice, USD(10'000)));
+
1642 env.close();
+
1643 AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
+
1644 WithdrawArg args{
+
1645 .account = bob,
+
1646 .asset1Out = USD(100),
+
1647 .err = ter(tecNO_AUTH),
+
1648 };
+
1649 ammAlice.withdraw(args);
+
1650 }
+
1651
+
1652 testAMM([&](AMM& ammAlice, Env& env) {
+
1653 // Invalid flags
+
1654 ammAlice.withdraw(
+
1655 alice,
+
1656 1'000'000,
1657 std::nullopt,
-
1658 tfBurnable,
+
1658 std::nullopt,
1659 std::nullopt,
-
1660 std::nullopt,
-
1661 ter(temINVALID_FLAG));
-
1662 ammAlice.withdraw(
-
1663 alice,
-
1664 1'000'000,
-
1665 std::nullopt,
-
1666 std::nullopt,
+
1660 tfBurnable,
+
1661 std::nullopt,
+
1662 std::nullopt,
+
1663 ter(temINVALID_FLAG));
+
1664 ammAlice.withdraw(
+
1665 alice,
+
1666 1'000'000,
1667 std::nullopt,
-
1668 tfTwoAssetIfEmpty,
+
1668 std::nullopt,
1669 std::nullopt,
-
1670 std::nullopt,
-
1671 ter(temINVALID_FLAG));
-
1672
-
1673 // Invalid options
-
1674 std::vector<std::tuple<
-
1675 std::optional<std::uint32_t>,
-
1676 std::optional<STAmount>,
-
1677 std::optional<STAmount>,
-
1678 std::optional<IOUAmount>,
-
1679 std::optional<std::uint32_t>,
-
1680 NotTEC>>
-
1681 invalidOptions = {
-
1682 // tokens, asset1Out, asset2Out, EPrice, flags, ter
-
1683 {std::nullopt,
-
1684 std::nullopt,
-
1685 std::nullopt,
+
1670 tfTwoAssetIfEmpty,
+
1671 std::nullopt,
+
1672 std::nullopt,
+
1673 ter(temINVALID_FLAG));
+
1674
+
1675 // Invalid options
+
1676 std::vector<std::tuple<
+
1677 std::optional<std::uint32_t>,
+
1678 std::optional<STAmount>,
+
1679 std::optional<STAmount>,
+
1680 std::optional<IOUAmount>,
+
1681 std::optional<std::uint32_t>,
+
1682 NotTEC>>
+
1683 invalidOptions = {
+
1684 // tokens, asset1Out, asset2Out, EPrice, flags, ter
+
1685 {std::nullopt,
1686 std::nullopt,
1687 std::nullopt,
-
1688 temMALFORMED},
-
1689 {std::nullopt,
-
1690 std::nullopt,
-
1691 std::nullopt,
+
1688 std::nullopt,
+
1689 std::nullopt,
+
1690 temMALFORMED},
+
1691 {std::nullopt,
1692 std::nullopt,
-
1693 tfSingleAsset | tfTwoAsset,
-
1694 temMALFORMED},
-
1695 {1'000,
-
1696 std::nullopt,
-
1697 std::nullopt,
+
1693 std::nullopt,
+
1694 std::nullopt,
+
1695 tfSingleAsset | tfTwoAsset,
+
1696 temMALFORMED},
+
1697 {1'000,
1698 std::nullopt,
-
1699 tfWithdrawAll,
-
1700 temMALFORMED},
-
1701 {std::nullopt,
-
1702 USD(0),
-
1703 XRP(100),
-
1704 std::nullopt,
-
1705 tfWithdrawAll | tfLPToken,
-
1706 temMALFORMED},
-
1707 {std::nullopt,
-
1708 std::nullopt,
-
1709 USD(100),
+
1699 std::nullopt,
+
1700 std::nullopt,
+
1701 tfWithdrawAll,
+
1702 temMALFORMED},
+
1703 {std::nullopt,
+
1704 USD(0),
+
1705 XRP(100),
+
1706 std::nullopt,
+
1707 tfWithdrawAll | tfLPToken,
+
1708 temMALFORMED},
+
1709 {std::nullopt,
1710 std::nullopt,
-
1711 tfWithdrawAll,
-
1712 temMALFORMED},
-
1713 {std::nullopt,
-
1714 std::nullopt,
-
1715 std::nullopt,
+
1711 USD(100),
+
1712 std::nullopt,
+
1713 tfWithdrawAll,
+
1714 temMALFORMED},
+
1715 {std::nullopt,
1716 std::nullopt,
-
1717 tfWithdrawAll | tfOneAssetWithdrawAll,
-
1718 temMALFORMED},
-
1719 {std::nullopt,
-
1720 USD(100),
-
1721 std::nullopt,
-
1722 std::nullopt,
-
1723 tfWithdrawAll,
-
1724 temMALFORMED},
-
1725 {std::nullopt,
-
1726 std::nullopt,
-
1727 std::nullopt,
+
1717 std::nullopt,
+
1718 std::nullopt,
+
1719 tfWithdrawAll | tfOneAssetWithdrawAll,
+
1720 temMALFORMED},
+
1721 {std::nullopt,
+
1722 USD(100),
+
1723 std::nullopt,
+
1724 std::nullopt,
+
1725 tfWithdrawAll,
+
1726 temMALFORMED},
+
1727 {std::nullopt,
1728 std::nullopt,
-
1729 tfOneAssetWithdrawAll,
-
1730 temMALFORMED},
-
1731 {1'000,
-
1732 std::nullopt,
-
1733 USD(100),
+
1729 std::nullopt,
+
1730 std::nullopt,
+
1731 tfOneAssetWithdrawAll,
+
1732 temMALFORMED},
+
1733 {1'000,
1734 std::nullopt,
-
1735 std::nullopt,
-
1736 temMALFORMED},
-
1737 {std::nullopt,
-
1738 std::nullopt,
-
1739 std::nullopt,
-
1740 IOUAmount{250, 0},
-
1741 tfWithdrawAll,
-
1742 temMALFORMED},
-
1743 {1'000,
-
1744 std::nullopt,
-
1745 std::nullopt,
-
1746 IOUAmount{250, 0},
+
1735 USD(100),
+
1736 std::nullopt,
+
1737 std::nullopt,
+
1738 temMALFORMED},
+
1739 {std::nullopt,
+
1740 std::nullopt,
+
1741 std::nullopt,
+
1742 IOUAmount{250, 0},
+
1743 tfWithdrawAll,
+
1744 temMALFORMED},
+
1745 {1'000,
+
1746 std::nullopt,
1747 std::nullopt,
-
1748 temMALFORMED},
-
1749 {std::nullopt,
-
1750 std::nullopt,
-
1751 USD(100),
-
1752 IOUAmount{250, 0},
-
1753 std::nullopt,
-
1754 temMALFORMED},
-
1755 {std::nullopt,
-
1756 XRP(100),
-
1757 USD(100),
-
1758 IOUAmount{250, 0},
-
1759 std::nullopt,
-
1760 temMALFORMED},
-
1761 {1'000,
-
1762 XRP(100),
-
1763 USD(100),
-
1764 std::nullopt,
-
1765 std::nullopt,
-
1766 temMALFORMED},
-
1767 {std::nullopt,
-
1768 XRP(100),
-
1769 USD(100),
-
1770 std::nullopt,
-
1771 tfWithdrawAll,
-
1772 temMALFORMED}};
-
1773 for (auto const& it : invalidOptions)
-
1774 {
-
1775 ammAlice.withdraw(
-
1776 alice,
-
1777 std::get<0>(it),
-
1778 std::get<1>(it),
-
1779 std::get<2>(it),
-
1780 std::get<3>(it),
-
1781 std::get<4>(it),
-
1782 std::nullopt,
-
1783 std::nullopt,
-
1784 ter(std::get<5>(it)));
-
1785 }
-
1786
-
1787 // Invalid tokens
-
1788 ammAlice.withdraw(
-
1789 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
+
1748 IOUAmount{250, 0},
+
1749 std::nullopt,
+
1750 temMALFORMED},
+
1751 {std::nullopt,
+
1752 std::nullopt,
+
1753 USD(100),
+
1754 IOUAmount{250, 0},
+
1755 std::nullopt,
+
1756 temMALFORMED},
+
1757 {std::nullopt,
+
1758 XRP(100),
+
1759 USD(100),
+
1760 IOUAmount{250, 0},
+
1761 std::nullopt,
+
1762 temMALFORMED},
+
1763 {1'000,
+
1764 XRP(100),
+
1765 USD(100),
+
1766 std::nullopt,
+
1767 std::nullopt,
+
1768 temMALFORMED},
+
1769 {std::nullopt,
+
1770 XRP(100),
+
1771 USD(100),
+
1772 std::nullopt,
+
1773 tfWithdrawAll,
+
1774 temMALFORMED}};
+
1775 for (auto const& it : invalidOptions)
+
1776 {
+
1777 ammAlice.withdraw(
+
1778 alice,
+
1779 std::get<0>(it),
+
1780 std::get<1>(it),
+
1781 std::get<2>(it),
+
1782 std::get<3>(it),
+
1783 std::get<4>(it),
+
1784 std::nullopt,
+
1785 std::nullopt,
+
1786 ter(std::get<5>(it)));
+
1787 }
+
1788
+
1789 // Invalid tokens
1790 ammAlice.withdraw(
-
1791 alice,
-
1792 IOUAmount{-1},
-
1793 std::nullopt,
-
1794 std::nullopt,
-
1795 ter(temBAD_AMM_TOKENS));
-
1796
-
1797 // Mismatched token, invalid Asset1Out issue
-
1798 ammAlice.withdraw(
-
1799 alice,
-
1800 GBP(100),
-
1801 std::nullopt,
-
1802 std::nullopt,
-
1803 ter(temBAD_AMM_TOKENS));
-
1804
-
1805 // Mismatched token, invalid Asset2Out issue
-
1806 ammAlice.withdraw(
-
1807 alice,
-
1808 USD(100),
-
1809 GBP(100),
-
1810 std::nullopt,
-
1811 ter(temBAD_AMM_TOKENS));
-
1812
-
1813 // Mismatched token, Asset1Out.issue == Asset2Out.issue
-
1814 ammAlice.withdraw(
-
1815 alice,
-
1816 USD(100),
-
1817 USD(100),
-
1818 std::nullopt,
-
1819 ter(temBAD_AMM_TOKENS));
-
1820
-
1821 // Invalid amount value
-
1822 ammAlice.withdraw(
-
1823 alice, USD(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT));
+
1791 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
+
1792 ammAlice.withdraw(
+
1793 alice,
+
1794 IOUAmount{-1},
+
1795 std::nullopt,
+
1796 std::nullopt,
+
1797 ter(temBAD_AMM_TOKENS));
+
1798
+
1799 // Mismatched token, invalid Asset1Out issue
+
1800 ammAlice.withdraw(
+
1801 alice,
+
1802 GBP(100),
+
1803 std::nullopt,
+
1804 std::nullopt,
+
1805 ter(temBAD_AMM_TOKENS));
+
1806
+
1807 // Mismatched token, invalid Asset2Out issue
+
1808 ammAlice.withdraw(
+
1809 alice,
+
1810 USD(100),
+
1811 GBP(100),
+
1812 std::nullopt,
+
1813 ter(temBAD_AMM_TOKENS));
+
1814
+
1815 // Mismatched token, Asset1Out.issue == Asset2Out.issue
+
1816 ammAlice.withdraw(
+
1817 alice,
+
1818 USD(100),
+
1819 USD(100),
+
1820 std::nullopt,
+
1821 ter(temBAD_AMM_TOKENS));
+
1822
+
1823 // Invalid amount value
1824 ammAlice.withdraw(
-
1825 alice,
-
1826 USD(-100),
-
1827 std::nullopt,
-
1828 std::nullopt,
-
1829 ter(temBAD_AMOUNT));
-
1830 ammAlice.withdraw(
-
1831 alice,
-
1832 USD(10),
-
1833 std::nullopt,
-
1834 IOUAmount{-1},
-
1835 ter(temBAD_AMOUNT));
-
1836
-
1837 // Invalid amount/token value, withdraw all tokens from one side
-
1838 // of the pool.
-
1839 ammAlice.withdraw(
-
1840 alice,
-
1841 USD(10'000),
-
1842 std::nullopt,
-
1843 std::nullopt,
-
1844 ter(tecAMM_BALANCE));
-
1845 ammAlice.withdraw(
-
1846 alice,
-
1847 XRP(10'000),
-
1848 std::nullopt,
-
1849 std::nullopt,
-
1850 ter(tecAMM_BALANCE));
-
1851 ammAlice.withdraw(
-
1852 alice,
-
1853 std::nullopt,
-
1854 USD(0),
+
1825 alice, USD(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT));
+
1826 ammAlice.withdraw(
+
1827 alice,
+
1828 USD(-100),
+
1829 std::nullopt,
+
1830 std::nullopt,
+
1831 ter(temBAD_AMOUNT));
+
1832 ammAlice.withdraw(
+
1833 alice,
+
1834 USD(10),
+
1835 std::nullopt,
+
1836 IOUAmount{-1},
+
1837 ter(temBAD_AMOUNT));
+
1838
+
1839 // Invalid amount/token value, withdraw all tokens from one side
+
1840 // of the pool.
+
1841 ammAlice.withdraw(
+
1842 alice,
+
1843 USD(10'000),
+
1844 std::nullopt,
+
1845 std::nullopt,
+
1846 ter(tecAMM_BALANCE));
+
1847 ammAlice.withdraw(
+
1848 alice,
+
1849 XRP(10'000),
+
1850 std::nullopt,
+
1851 std::nullopt,
+
1852 ter(tecAMM_BALANCE));
+
1853 ammAlice.withdraw(
+
1854 alice,
1855 std::nullopt,
-
1856 std::nullopt,
-
1857 tfOneAssetWithdrawAll,
+
1856 USD(0),
+
1857 std::nullopt,
1858 std::nullopt,
-
1859 std::nullopt,
-
1860 ter(tecAMM_BALANCE));
-
1861
-
1862 // Bad currency
-
1863 ammAlice.withdraw(
-
1864 alice,
-
1865 BAD(100),
-
1866 std::nullopt,
-
1867 std::nullopt,
-
1868 ter(temBAD_CURRENCY));
-
1869
-
1870 // Invalid Account
-
1871 Account bad("bad");
-
1872 env.memoize(bad);
-
1873 ammAlice.withdraw(
-
1874 bad,
-
1875 1'000'000,
-
1876 std::nullopt,
-
1877 std::nullopt,
+
1859 tfOneAssetWithdrawAll,
+
1860 std::nullopt,
+
1861 std::nullopt,
+
1862 ter(tecAMM_BALANCE));
+
1863
+
1864 // Bad currency
+
1865 ammAlice.withdraw(
+
1866 alice,
+
1867 BAD(100),
+
1868 std::nullopt,
+
1869 std::nullopt,
+
1870 ter(temBAD_CURRENCY));
+
1871
+
1872 // Invalid Account
+
1873 Account bad("bad");
+
1874 env.memoize(bad);
+
1875 ammAlice.withdraw(
+
1876 bad,
+
1877 1'000'000,
1878 std::nullopt,
1879 std::nullopt,
1880 std::nullopt,
-
1881 seq(1),
-
1882 ter(terNO_ACCOUNT));
-
1883
-
1884 // Invalid AMM
-
1885 ammAlice.withdraw(
-
1886 alice,
-
1887 1'000,
-
1888 std::nullopt,
-
1889 std::nullopt,
+
1881 std::nullopt,
+
1882 std::nullopt,
+
1883 seq(1),
+
1884 ter(terNO_ACCOUNT));
+
1885
+
1886 // Invalid AMM
+
1887 ammAlice.withdraw(
+
1888 alice,
+
1889 1'000,
1890 std::nullopt,
1891 std::nullopt,
-
1892 {{USD, GBP}},
+
1892 std::nullopt,
1893 std::nullopt,
-
1894 ter(terNO_AMM));
-
1895
-
1896 // Carol is not a Liquidity Provider
-
1897 ammAlice.withdraw(
-
1898 carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
-
1899
-
1900 // Withdraw entire one side of the pool.
-
1901 // Equal withdraw but due to XRP precision limit,
-
1902 // this results in full withdraw of XRP pool only,
-
1903 // while leaving a tiny amount in USD pool.
-
1904 ammAlice.withdraw(
-
1905 alice,
-
1906 IOUAmount{9'999'999'9999, -4},
-
1907 std::nullopt,
-
1908 std::nullopt,
-
1909 ter(tecAMM_BALANCE));
-
1910 // Withdrawing from one side.
-
1911 // XRP by tokens
-
1912 ammAlice.withdraw(
-
1913 alice,
-
1914 IOUAmount(9'999'999'9999, -4),
-
1915 XRP(0),
-
1916 std::nullopt,
-
1917 ter(tecAMM_BALANCE));
-
1918 // USD by tokens
-
1919 ammAlice.withdraw(
-
1920 alice,
-
1921 IOUAmount(9'999'999'9, -1),
-
1922 USD(0),
-
1923 std::nullopt,
-
1924 ter(tecAMM_BALANCE));
-
1925 // XRP
-
1926 ammAlice.withdraw(
-
1927 alice,
-
1928 XRP(10'000),
-
1929 std::nullopt,
-
1930 std::nullopt,
-
1931 ter(tecAMM_BALANCE));
-
1932 // USD
-
1933 ammAlice.withdraw(
-
1934 alice,
-
1935 STAmount{USD, UINT64_C(9'999'9999999999999), -13},
-
1936 std::nullopt,
-
1937 std::nullopt,
-
1938 ter(tecAMM_BALANCE));
-
1939 });
-
1940
-
1941 // Invalid AMM
-
1942 testAMM([&](AMM& ammAlice, Env& env) {
-
1943 ammAlice.withdrawAll(alice);
-
1944 ammAlice.withdraw(
-
1945 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
-
1946 });
-
1947
-
1948 // Globally frozen asset
-
1949 testAMM([&](AMM& ammAlice, Env& env) {
-
1950 env(fset(gw, asfGlobalFreeze));
-
1951 env.close();
-
1952 // Can withdraw non-frozen token
-
1953 ammAlice.withdraw(alice, XRP(100));
-
1954 ammAlice.withdraw(
-
1955 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
1894 {{USD, GBP}},
+
1895 std::nullopt,
+
1896 ter(terNO_AMM));
+
1897
+
1898 // Carol is not a Liquidity Provider
+
1899 ammAlice.withdraw(
+
1900 carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
+
1901
+
1902 // Withdraw entire one side of the pool.
+
1903 // Equal withdraw but due to XRP precision limit,
+
1904 // this results in full withdraw of XRP pool only,
+
1905 // while leaving a tiny amount in USD pool.
+
1906 ammAlice.withdraw(
+
1907 alice,
+
1908 IOUAmount{9'999'999'9999, -4},
+
1909 std::nullopt,
+
1910 std::nullopt,
+
1911 ter(tecAMM_BALANCE));
+
1912 // Withdrawing from one side.
+
1913 // XRP by tokens
+
1914 ammAlice.withdraw(
+
1915 alice,
+
1916 IOUAmount(9'999'999'9999, -4),
+
1917 XRP(0),
+
1918 std::nullopt,
+
1919 ter(tecAMM_BALANCE));
+
1920 // USD by tokens
+
1921 ammAlice.withdraw(
+
1922 alice,
+
1923 IOUAmount(9'999'999'9, -1),
+
1924 USD(0),
+
1925 std::nullopt,
+
1926 ter(tecAMM_BALANCE));
+
1927 // XRP
+
1928 ammAlice.withdraw(
+
1929 alice,
+
1930 XRP(10'000),
+
1931 std::nullopt,
+
1932 std::nullopt,
+
1933 ter(tecAMM_BALANCE));
+
1934 // USD
+
1935 ammAlice.withdraw(
+
1936 alice,
+
1937 STAmount{USD, UINT64_C(9'999'9999999999999), -13},
+
1938 std::nullopt,
+
1939 std::nullopt,
+
1940 ter(tecAMM_BALANCE));
+
1941 });
+
1942
+
1943 // Invalid AMM
+
1944 testAMM([&](AMM& ammAlice, Env& env) {
+
1945 ammAlice.withdrawAll(alice);
+
1946 ammAlice.withdraw(
+
1947 alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
+
1948 });
+
1949
+
1950 // Globally frozen asset
+
1951 testAMM([&](AMM& ammAlice, Env& env) {
+
1952 env(fset(gw, asfGlobalFreeze));
+
1953 env.close();
+
1954 // Can withdraw non-frozen token
+
1955 ammAlice.withdraw(alice, XRP(100));
1956 ammAlice.withdraw(
-
1957 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-
1958 });
-
1959
-
1960 // Individually frozen (AMM) account
-
1961 testAMM([&](AMM& ammAlice, Env& env) {
-
1962 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
1963 env.close();
-
1964 // Can withdraw non-frozen token
-
1965 ammAlice.withdraw(alice, XRP(100));
-
1966 ammAlice.withdraw(
-
1967 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1957 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
1958 ammAlice.withdraw(
+
1959 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1960 });
+
1961
+
1962 // Individually frozen (AMM) account
+
1963 testAMM([&](AMM& ammAlice, Env& env) {
+
1964 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
1965 env.close();
+
1966 // Can withdraw non-frozen token
+
1967 ammAlice.withdraw(alice, XRP(100));
1968 ammAlice.withdraw(
-
1969 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
1970 env(trust(gw, alice["USD"](0), tfClearFreeze));
-
1971 // Individually frozen AMM
-
1972 env(trust(
-
1973 gw,
-
1974 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
1975 tfSetFreeze));
-
1976 // Can withdraw non-frozen token
-
1977 ammAlice.withdraw(alice, XRP(100));
-
1978 ammAlice.withdraw(
-
1979 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1969 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1970 ammAlice.withdraw(
+
1971 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
1972 env(trust(gw, alice["USD"](0), tfClearFreeze));
+
1973 // Individually frozen AMM
+
1974 env(trust(
+
1975 gw,
+
1976 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
1977 tfSetFreeze));
+
1978 // Can withdraw non-frozen token
+
1979 ammAlice.withdraw(alice, XRP(100));
1980 ammAlice.withdraw(
-
1981 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
-
1982 });
-
1983
-
1984 // Carol withdraws more than she owns
-
1985 testAMM([&](AMM& ammAlice, Env&) {
-
1986 // Single deposit of 100000 worth of tokens,
-
1987 // which is 10% of the pool. Carol is LP now.
-
1988 ammAlice.deposit(carol, 1'000'000);
-
1989 BEAST_EXPECT(ammAlice.expectBalances(
-
1990 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
1991
-
1992 ammAlice.withdraw(
-
1993 carol,
-
1994 2'000'000,
-
1995 std::nullopt,
-
1996 std::nullopt,
-
1997 ter(tecAMM_INVALID_TOKENS));
-
1998 BEAST_EXPECT(ammAlice.expectBalances(
-
1999 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
2000 });
-
2001
-
2002 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
-
2003 // to withdraw are 0.
-
2004 testAMM([&](AMM& ammAlice, Env&) {
-
2005 ammAlice.deposit(carol, 1'000'000);
-
2006 ammAlice.withdraw(
-
2007 carol,
-
2008 USD(100),
-
2009 std::nullopt,
-
2010 IOUAmount{500, 0},
-
2011 ter(tecAMM_FAILED));
-
2012 });
-
2013
-
2014 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
-
2015 // to withdraw are greater than the LP shares.
-
2016 testAMM([&](AMM& ammAlice, Env&) {
-
2017 ammAlice.deposit(carol, 1'000'000);
-
2018 ammAlice.withdraw(
-
2019 carol,
-
2020 USD(100),
-
2021 std::nullopt,
-
2022 IOUAmount{600, 0},
-
2023 ter(tecAMM_INVALID_TOKENS));
-
2024 });
-
2025
-
2026 // Withdraw with EPrice limit. Fails to withdraw, amount1
-
2027 // to withdraw is less than 1700USD.
-
2028 testAMM([&](AMM& ammAlice, Env&) {
-
2029 ammAlice.deposit(carol, 1'000'000);
-
2030 ammAlice.withdraw(
-
2031 carol,
-
2032 USD(1'700),
-
2033 std::nullopt,
-
2034 IOUAmount{520, 0},
-
2035 ter(tecAMM_FAILED));
-
2036 });
-
2037
-
2038 // Deposit/Withdraw the same amount with the trading fee
-
2039 testAMM(
-
2040 [&](AMM& ammAlice, Env&) {
-
2041 ammAlice.deposit(carol, USD(1'000));
-
2042 ammAlice.withdraw(
-
2043 carol,
-
2044 USD(1'000),
-
2045 std::nullopt,
-
2046 std::nullopt,
-
2047 ter(tecAMM_INVALID_TOKENS));
-
2048 },
-
2049 std::nullopt,
-
2050 1'000);
-
2051 testAMM(
-
2052 [&](AMM& ammAlice, Env&) {
-
2053 ammAlice.deposit(carol, XRP(1'000));
-
2054 ammAlice.withdraw(
-
2055 carol,
-
2056 XRP(1'000),
-
2057 std::nullopt,
-
2058 std::nullopt,
-
2059 ter(tecAMM_INVALID_TOKENS));
-
2060 },
-
2061 std::nullopt,
-
2062 1'000);
-
2063
-
2064 // Deposit/Withdraw the same amount fails due to the tokens adjustment
-
2065 testAMM([&](AMM& ammAlice, Env&) {
-
2066 ammAlice.deposit(carol, STAmount{USD, 1, -6});
-
2067 ammAlice.withdraw(
-
2068 carol,
-
2069 STAmount{USD, 1, -6},
-
2070 std::nullopt,
-
2071 std::nullopt,
-
2072 ter(tecAMM_INVALID_TOKENS));
-
2073 });
-
2074
-
2075 // Withdraw close to one side of the pool. Account's LP tokens
-
2076 // are rounded to all LP tokens.
-
2077 testAMM([&](AMM& ammAlice, Env&) {
-
2078 ammAlice.withdraw(
-
2079 alice,
-
2080 STAmount{USD, UINT64_C(9'999'999999999999), -12},
-
2081 std::nullopt,
-
2082 std::nullopt,
-
2083 ter(tecAMM_BALANCE));
-
2084 });
-
2085
-
2086 // Tiny withdraw
-
2087 testAMM([&](AMM& ammAlice, Env&) {
-
2088 // XRP amount to withdraw is 0
-
2089 ammAlice.withdraw(
-
2090 alice,
-
2091 IOUAmount{1, -5},
-
2092 std::nullopt,
-
2093 std::nullopt,
-
2094 ter(tecAMM_FAILED));
-
2095 // Calculated tokens to withdraw are 0
-
2096 ammAlice.withdraw(
-
2097 alice,
-
2098 std::nullopt,
-
2099 STAmount{USD, 1, -11},
+
1981 alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
+
1982 ammAlice.withdraw(
+
1983 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
+
1984 });
+
1985
+
1986 // Carol withdraws more than she owns
+
1987 testAMM([&](AMM& ammAlice, Env&) {
+
1988 // Single deposit of 100000 worth of tokens,
+
1989 // which is 10% of the pool. Carol is LP now.
+
1990 ammAlice.deposit(carol, 1'000'000);
+
1991 BEAST_EXPECT(ammAlice.expectBalances(
+
1992 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
1993
+
1994 ammAlice.withdraw(
+
1995 carol,
+
1996 2'000'000,
+
1997 std::nullopt,
+
1998 std::nullopt,
+
1999 ter(tecAMM_INVALID_TOKENS));
+
2000 BEAST_EXPECT(ammAlice.expectBalances(
+
2001 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
2002 });
+
2003
+
2004 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
+
2005 // to withdraw are 0.
+
2006 testAMM([&](AMM& ammAlice, Env&) {
+
2007 ammAlice.deposit(carol, 1'000'000);
+
2008 ammAlice.withdraw(
+
2009 carol,
+
2010 USD(100),
+
2011 std::nullopt,
+
2012 IOUAmount{500, 0},
+
2013 ter(tecAMM_FAILED));
+
2014 });
+
2015
+
2016 // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
+
2017 // to withdraw are greater than the LP shares.
+
2018 testAMM([&](AMM& ammAlice, Env&) {
+
2019 ammAlice.deposit(carol, 1'000'000);
+
2020 ammAlice.withdraw(
+
2021 carol,
+
2022 USD(100),
+
2023 std::nullopt,
+
2024 IOUAmount{600, 0},
+
2025 ter(tecAMM_INVALID_TOKENS));
+
2026 });
+
2027
+
2028 // Withdraw with EPrice limit. Fails to withdraw, amount1
+
2029 // to withdraw is less than 1700USD.
+
2030 testAMM([&](AMM& ammAlice, Env&) {
+
2031 ammAlice.deposit(carol, 1'000'000);
+
2032 ammAlice.withdraw(
+
2033 carol,
+
2034 USD(1'700),
+
2035 std::nullopt,
+
2036 IOUAmount{520, 0},
+
2037 ter(tecAMM_FAILED));
+
2038 });
+
2039
+
2040 // Deposit/Withdraw the same amount with the trading fee
+
2041 testAMM(
+
2042 [&](AMM& ammAlice, Env&) {
+
2043 ammAlice.deposit(carol, USD(1'000));
+
2044 ammAlice.withdraw(
+
2045 carol,
+
2046 USD(1'000),
+
2047 std::nullopt,
+
2048 std::nullopt,
+
2049 ter(tecAMM_INVALID_TOKENS));
+
2050 },
+
2051 std::nullopt,
+
2052 1'000);
+
2053 testAMM(
+
2054 [&](AMM& ammAlice, Env&) {
+
2055 ammAlice.deposit(carol, XRP(1'000));
+
2056 ammAlice.withdraw(
+
2057 carol,
+
2058 XRP(1'000),
+
2059 std::nullopt,
+
2060 std::nullopt,
+
2061 ter(tecAMM_INVALID_TOKENS));
+
2062 },
+
2063 std::nullopt,
+
2064 1'000);
+
2065
+
2066 // Deposit/Withdraw the same amount fails due to the tokens adjustment
+
2067 testAMM([&](AMM& ammAlice, Env&) {
+
2068 ammAlice.deposit(carol, STAmount{USD, 1, -6});
+
2069 ammAlice.withdraw(
+
2070 carol,
+
2071 STAmount{USD, 1, -6},
+
2072 std::nullopt,
+
2073 std::nullopt,
+
2074 ter(tecAMM_INVALID_TOKENS));
+
2075 });
+
2076
+
2077 // Withdraw close to one side of the pool. Account's LP tokens
+
2078 // are rounded to all LP tokens.
+
2079 testAMM([&](AMM& ammAlice, Env&) {
+
2080 ammAlice.withdraw(
+
2081 alice,
+
2082 STAmount{USD, UINT64_C(9'999'999999999999), -12},
+
2083 std::nullopt,
+
2084 std::nullopt,
+
2085 ter(tecAMM_BALANCE));
+
2086 });
+
2087
+
2088 // Tiny withdraw
+
2089 testAMM([&](AMM& ammAlice, Env&) {
+
2090 // XRP amount to withdraw is 0
+
2091 ammAlice.withdraw(
+
2092 alice,
+
2093 IOUAmount{1, -5},
+
2094 std::nullopt,
+
2095 std::nullopt,
+
2096 ter(tecAMM_FAILED));
+
2097 // Calculated tokens to withdraw are 0
+
2098 ammAlice.withdraw(
+
2099 alice,
2100 std::nullopt,
-
2101 ter(tecAMM_INVALID_TOKENS));
-
2102 ammAlice.deposit(carol, STAmount{USD, 1, -10});
-
2103 ammAlice.withdraw(
-
2104 carol,
-
2105 std::nullopt,
-
2106 STAmount{USD, 1, -9},
+
2101 STAmount{USD, 1, -11},
+
2102 std::nullopt,
+
2103 ter(tecAMM_INVALID_TOKENS));
+
2104 ammAlice.deposit(carol, STAmount{USD, 1, -10});
+
2105 ammAlice.withdraw(
+
2106 carol,
2107 std::nullopt,
-
2108 ter(tecAMM_INVALID_TOKENS));
-
2109 ammAlice.withdraw(
-
2110 carol,
-
2111 std::nullopt,
-
2112 XRPAmount{1},
+
2108 STAmount{USD, 1, -9},
+
2109 std::nullopt,
+
2110 ter(tecAMM_INVALID_TOKENS));
+
2111 ammAlice.withdraw(
+
2112 carol,
2113 std::nullopt,
-
2114 ter(tecAMM_INVALID_TOKENS));
-
2115 });
-
2116 }
-
2117
-
2118 void
-
2119 testWithdraw()
-
2120 {
-
2121 testcase("Withdraw");
-
2122
-
2123 using namespace jtx;
+
2114 XRPAmount{1},
+
2115 std::nullopt,
+
2116 ter(tecAMM_INVALID_TOKENS));
+
2117 });
+
2118 }
+
2119
+
2120 void
+
2121 testWithdraw()
+
2122 {
+
2123 testcase("Withdraw");
2124
-
2125 // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
-
2126 // pool
-
2127 testAMM([&](AMM& ammAlice, Env& env) {
-
2128 // Single deposit of 100000 worth of tokens,
-
2129 // which is 10% of the pool. Carol is LP now.
-
2130 ammAlice.deposit(carol, 1'000'000);
-
2131 BEAST_EXPECT(ammAlice.expectBalances(
-
2132 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
-
2133 BEAST_EXPECT(
-
2134 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
2135 // 30,000 less deposited 1,000
-
2136 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
-
2137 // 30,000 less deposited 1,000 and 10 drops tx fee
-
2138 BEAST_EXPECT(
-
2139 expectLedgerEntryRoot(env, carol, XRPAmount{28'999'999'990}));
-
2140
-
2141 // Carol withdraws all tokens
-
2142 ammAlice.withdraw(carol, 1'000'000);
-
2143 BEAST_EXPECT(
-
2144 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
-
2145 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
2125 using namespace jtx;
+
2126
+
2127 // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
+
2128 // pool
+
2129 testAMM([&](AMM& ammAlice, Env& env) {
+
2130 auto const baseFee = env.current()->fees().base.drops();
+
2131 // Single deposit of 100000 worth of tokens,
+
2132 // which is 10% of the pool. Carol is LP now.
+
2133 ammAlice.deposit(carol, 1'000'000);
+
2134 BEAST_EXPECT(ammAlice.expectBalances(
+
2135 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
+
2136 BEAST_EXPECT(
+
2137 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
2138 // 30,000 less deposited 1,000
+
2139 BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
+
2140 // 30,000 less deposited 1,000 and 10 drops tx fee
+
2141 BEAST_EXPECT(expectLedgerEntryRoot(
+
2142 env, carol, XRPAmount{29'000'000'000 - baseFee}));
+
2143
+
2144 // Carol withdraws all tokens
+
2145 ammAlice.withdraw(carol, 1'000'000);
2146 BEAST_EXPECT(
-
2147 expectLedgerEntryRoot(env, carol, XRPAmount{29'999'999'980}));
-
2148 });
-
2149
-
2150 // Equal withdrawal by tokens 1000000, 10%
-
2151 // of the current pool
-
2152 testAMM([&](AMM& ammAlice, Env&) {
-
2153 ammAlice.withdraw(alice, 1'000'000);
-
2154 BEAST_EXPECT(ammAlice.expectBalances(
-
2155 XRP(9'000), USD(9'000), IOUAmount{9'000'000, 0}));
-
2156 });
-
2157
-
2158 // Equal withdrawal with a limit. Withdraw XRP200.
-
2159 // If proportional withdraw of USD is less than 100
-
2160 // then withdraw that amount, otherwise withdraw USD100
-
2161 // and proportionally withdraw XRP. It's the latter
-
2162 // in this case - XRP100/USD100.
-
2163 testAMM([&](AMM& ammAlice, Env&) {
-
2164 ammAlice.withdraw(alice, XRP(200), USD(100));
-
2165 BEAST_EXPECT(ammAlice.expectBalances(
-
2166 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
-
2167 });
-
2168
-
2169 // Equal withdrawal with a limit. XRP100/USD100.
-
2170 testAMM([&](AMM& ammAlice, Env&) {
-
2171 ammAlice.withdraw(alice, XRP(100), USD(200));
-
2172 BEAST_EXPECT(ammAlice.expectBalances(
-
2173 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
-
2174 });
-
2175
-
2176 // Single withdrawal by amount XRP1000
-
2177 testAMM([&](AMM& ammAlice, Env&) {
-
2178 ammAlice.withdraw(alice, XRP(1'000));
-
2179 BEAST_EXPECT(ammAlice.expectBalances(
-
2180 XRP(9'000), USD(10'000), IOUAmount{9'486'832'98050514, -8}));
-
2181 });
-
2182
-
2183 // Single withdrawal by tokens 10000.
-
2184 testAMM([&](AMM& ammAlice, Env&) {
-
2185 ammAlice.withdraw(alice, 10'000, USD(0));
-
2186 BEAST_EXPECT(ammAlice.expectBalances(
-
2187 XRP(10'000), USD(9980.01), IOUAmount{9'990'000, 0}));
-
2188 });
-
2189
-
2190 // Withdraw all tokens.
-
2191 testAMM([&](AMM& ammAlice, Env& env) {
-
2192 env(trust(carol, STAmount{ammAlice.lptIssue(), 10'000}));
-
2193 // Can SetTrust only for AMM LP tokens
-
2194 env(trust(
-
2195 carol,
-
2196 STAmount{
-
2197 Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}),
-
2198 ter(tecNO_PERMISSION));
-
2199 env.close();
-
2200 ammAlice.withdrawAll(alice);
-
2201 BEAST_EXPECT(!ammAlice.ammExists());
-
2202
-
2203 BEAST_EXPECT(!env.le(keylet::ownerDir(ammAlice.ammAccount())));
-
2204
-
2205 // Can create AMM for the XRP/USD pair
-
2206 AMM ammCarol(env, carol, XRP(10'000), USD(10'000));
-
2207 BEAST_EXPECT(ammCarol.expectBalances(
-
2208 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2209 });
-
2210
-
2211 // Single deposit 1000USD, withdraw all tokens in USD
-
2212 testAMM([&](AMM& ammAlice, Env& env) {
-
2213 ammAlice.deposit(carol, USD(1'000));
-
2214 ammAlice.withdrawAll(carol, USD(0));
-
2215 BEAST_EXPECT(ammAlice.expectBalances(
-
2216 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2217 BEAST_EXPECT(
-
2218 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
-
2219 });
-
2220
-
2221 // Single deposit 1000USD, withdraw all tokens in XRP
-
2222 testAMM([&](AMM& ammAlice, Env&) {
-
2223 ammAlice.deposit(carol, USD(1'000));
-
2224 ammAlice.withdrawAll(carol, XRP(0));
-
2225 BEAST_EXPECT(ammAlice.expectBalances(
-
2226 XRPAmount(9'090'909'091),
-
2227 STAmount{USD, UINT64_C(10'999'99999999999), -11},
-
2228 IOUAmount{10'000'000, 0}));
-
2229 });
-
2230
-
2231 // Single deposit/withdraw by the same account
-
2232 testAMM([&](AMM& ammAlice, Env&) {
-
2233 // Since a smaller amount might be deposited due to
-
2234 // the lp tokens adjustment, withdrawing by tokens
-
2235 // is generally preferred to withdrawing by amount.
-
2236 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
-
2237 ammAlice.withdraw(carol, lpTokens, USD(0));
-
2238 lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6));
-
2239 ammAlice.withdraw(carol, lpTokens, USD(0));
-
2240 lpTokens = ammAlice.deposit(carol, XRPAmount(1));
-
2241 ammAlice.withdraw(carol, lpTokens, XRPAmount(0));
-
2242 BEAST_EXPECT(ammAlice.expectBalances(
-
2243 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
2244 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2245 });
-
2246
-
2247 // Single deposit by different accounts and then withdraw
-
2248 // in reverse.
-
2249 testAMM([&](AMM& ammAlice, Env&) {
-
2250 auto const carolTokens = ammAlice.deposit(carol, USD(1'000));
-
2251 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
-
2252 ammAlice.withdraw(alice, aliceTokens, USD(0));
-
2253 ammAlice.withdraw(carol, carolTokens, USD(0));
-
2254 BEAST_EXPECT(ammAlice.expectBalances(
-
2255 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
2256 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2257 BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens()));
-
2258 });
-
2259
-
2260 // Equal deposit 10%, withdraw all tokens
-
2261 testAMM([&](AMM& ammAlice, Env&) {
-
2262 ammAlice.deposit(carol, 1'000'000);
-
2263 ammAlice.withdrawAll(carol);
-
2264 BEAST_EXPECT(ammAlice.expectBalances(
-
2265 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
-
2266 });
-
2267
-
2268 // Equal deposit 10%, withdraw all tokens in USD
-
2269 testAMM([&](AMM& ammAlice, Env&) {
-
2270 ammAlice.deposit(carol, 1'000'000);
-
2271 ammAlice.withdrawAll(carol, USD(0));
-
2272 BEAST_EXPECT(ammAlice.expectBalances(
-
2273 XRP(11'000),
-
2274 STAmount{USD, UINT64_C(9'090'909090909092), -12},
-
2275 IOUAmount{10'000'000, 0}));
-
2276 });
-
2277
-
2278 // Equal deposit 10%, withdraw all tokens in XRP
-
2279 testAMM([&](AMM& ammAlice, Env&) {
-
2280 ammAlice.deposit(carol, 1'000'000);
-
2281 ammAlice.withdrawAll(carol, XRP(0));
-
2282 BEAST_EXPECT(ammAlice.expectBalances(
-
2283 XRPAmount(9'090'909'091),
-
2284 USD(11'000),
-
2285 IOUAmount{10'000'000, 0}));
-
2286 });
-
2287
-
2288 auto const all = supported_amendments();
-
2289 // Withdraw with EPrice limit.
-
2290 testAMM(
-
2291 [&](AMM& ammAlice, Env& env) {
-
2292 ammAlice.deposit(carol, 1'000'000);
-
2293 ammAlice.withdraw(
-
2294 carol, USD(100), std::nullopt, IOUAmount{520, 0});
-
2295 if (!env.current()->rules().enabled(fixAMMv1_1))
-
2296 BEAST_EXPECT(
-
2297 ammAlice.expectBalances(
-
2298 XRPAmount(11'000'000'000),
-
2299 STAmount{USD, UINT64_C(9'372'781065088757), -12},
-
2300 IOUAmount{10'153'846'15384616, -8}) &&
-
2301 ammAlice.expectLPTokens(
-
2302 carol, IOUAmount{153'846'15384616, -8}));
-
2303 else
-
2304 BEAST_EXPECT(
-
2305 ammAlice.expectBalances(
-
2306 XRPAmount(11'000'000'000),
-
2307 STAmount{USD, UINT64_C(9'372'781065088769), -12},
-
2308 IOUAmount{10'153'846'15384616, -8}) &&
-
2309 ammAlice.expectLPTokens(
-
2310 carol, IOUAmount{153'846'15384616, -8}));
-
2311 ammAlice.withdrawAll(carol);
-
2312 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
2313 },
-
2314 std::nullopt,
-
2315 0,
-
2316 std::nullopt,
-
2317 {all, all - fixAMMv1_1});
-
2318
-
2319 // Withdraw with EPrice limit. AssetOut is 0.
-
2320 testAMM(
-
2321 [&](AMM& ammAlice, Env& env) {
-
2322 ammAlice.deposit(carol, 1'000'000);
-
2323 ammAlice.withdraw(
-
2324 carol, USD(0), std::nullopt, IOUAmount{520, 0});
-
2325 if (!env.current()->rules().enabled(fixAMMv1_1))
-
2326 BEAST_EXPECT(
-
2327 ammAlice.expectBalances(
-
2328 XRPAmount(11'000'000'000),
-
2329 STAmount{USD, UINT64_C(9'372'781065088757), -12},
-
2330 IOUAmount{10'153'846'15384616, -8}) &&
-
2331 ammAlice.expectLPTokens(
-
2332 carol, IOUAmount{153'846'15384616, -8}));
-
2333 else
-
2334 BEAST_EXPECT(
-
2335 ammAlice.expectBalances(
-
2336 XRPAmount(11'000'000'000),
-
2337 STAmount{USD, UINT64_C(9'372'781065088769), -12},
-
2338 IOUAmount{10'153'846'15384616, -8}) &&
-
2339 ammAlice.expectLPTokens(
-
2340 carol, IOUAmount{153'846'15384616, -8}));
-
2341 },
-
2342 std::nullopt,
-
2343 0,
-
2344 std::nullopt,
-
2345 {all, all - fixAMMv1_1});
-
2346
-
2347 // IOU to IOU + transfer fee
-
2348 {
-
2349 Env env{*this};
-
2350 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
-
2351 env(rate(gw, 1.25));
-
2352 env.close();
-
2353 // no transfer fee on create
-
2354 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
-
2355 BEAST_EXPECT(ammAlice.expectBalances(
-
2356 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
2357 BEAST_EXPECT(expectLine(env, alice, USD(0)));
-
2358 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
-
2359 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
-
2360 // no transfer fee on deposit
-
2361 ammAlice.deposit(carol, 10);
-
2362 BEAST_EXPECT(ammAlice.expectBalances(
-
2363 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
-
2364 BEAST_EXPECT(expectLine(env, carol, USD(0)));
-
2365 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
-
2366 // no transfer fee on withdraw
-
2367 ammAlice.withdraw(carol, 10);
-
2368 BEAST_EXPECT(ammAlice.expectBalances(
-
2369 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
-
2370 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
-
2371 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
-
2372 BEAST_EXPECT(expectLine(env, carol, BTC(0.05)));
-
2373 }
-
2374
-
2375 // Tiny withdraw
-
2376 testAMM([&](AMM& ammAlice, Env&) {
-
2377 // By tokens
-
2378 ammAlice.withdraw(alice, IOUAmount{1, -3});
-
2379 BEAST_EXPECT(ammAlice.expectBalances(
-
2380 XRPAmount{9'999'999'999},
-
2381 STAmount{USD, UINT64_C(9'999'999999), -6},
-
2382 IOUAmount{9'999'999'999, -3}));
-
2383 });
-
2384 testAMM([&](AMM& ammAlice, Env&) {
-
2385 // Single XRP pool
-
2386 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
-
2387 BEAST_EXPECT(ammAlice.expectBalances(
-
2388 XRPAmount{9'999'999'999},
-
2389 USD(10'000),
-
2390 IOUAmount{9'999'999'9995, -4}));
-
2391 });
-
2392 testAMM([&](AMM& ammAlice, Env&) {
-
2393 // Single USD pool
-
2394 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
-
2395 BEAST_EXPECT(ammAlice.expectBalances(
-
2396 XRP(10'000),
-
2397 STAmount{USD, UINT64_C(9'999'9999999999), -10},
-
2398 IOUAmount{9'999'999'99999995, -8}));
-
2399 });
-
2400
-
2401 // Withdraw close to entire pool
-
2402 // Equal by tokens
-
2403 testAMM([&](AMM& ammAlice, Env&) {
-
2404 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
-
2405 BEAST_EXPECT(ammAlice.expectBalances(
-
2406 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
-
2407 });
-
2408 // USD by tokens
-
2409 testAMM([&](AMM& ammAlice, Env&) {
-
2410 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
-
2411 BEAST_EXPECT(ammAlice.expectBalances(
-
2412 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
-
2413 });
-
2414 // XRP by tokens
-
2415 testAMM([&](AMM& ammAlice, Env&) {
-
2416 ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0));
-
2417 BEAST_EXPECT(ammAlice.expectBalances(
-
2418 XRPAmount{1}, USD(10'000), IOUAmount{100}));
-
2419 });
-
2420 // USD
-
2421 testAMM([&](AMM& ammAlice, Env&) {
-
2422 ammAlice.withdraw(
-
2423 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
-
2424 BEAST_EXPECT(ammAlice.expectBalances(
-
2425 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
-
2426 });
-
2427 // XRP
-
2428 testAMM([&](AMM& ammAlice, Env&) {
-
2429 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
-
2430 BEAST_EXPECT(ammAlice.expectBalances(
-
2431 XRPAmount{1}, USD(10'000), IOUAmount{100}));
-
2432 });
-
2433 }
-
2434
-
2435 void
-
2436 testInvalidFeeVote()
-
2437 {
-
2438 testcase("Invalid Fee Vote");
-
2439 using namespace jtx;
-
2440
-
2441 testAMM([&](AMM& ammAlice, Env& env) {
-
2442 // Invalid flags
-
2443 ammAlice.vote(
-
2444 std::nullopt,
-
2445 1'000,
-
2446 tfWithdrawAll,
+
2147 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
+
2148 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
2149 BEAST_EXPECT(expectLedgerEntryRoot(
+
2150 env, carol, XRPAmount{30'000'000'000 - 2 * baseFee}));
+
2151 });
+
2152
+
2153 // Equal withdrawal by tokens 1000000, 10%
+
2154 // of the current pool
+
2155 testAMM([&](AMM& ammAlice, Env&) {
+
2156 ammAlice.withdraw(alice, 1'000'000);
+
2157 BEAST_EXPECT(ammAlice.expectBalances(
+
2158 XRP(9'000), USD(9'000), IOUAmount{9'000'000, 0}));
+
2159 });
+
2160
+
2161 // Equal withdrawal with a limit. Withdraw XRP200.
+
2162 // If proportional withdraw of USD is less than 100
+
2163 // then withdraw that amount, otherwise withdraw USD100
+
2164 // and proportionally withdraw XRP. It's the latter
+
2165 // in this case - XRP100/USD100.
+
2166 testAMM([&](AMM& ammAlice, Env&) {
+
2167 ammAlice.withdraw(alice, XRP(200), USD(100));
+
2168 BEAST_EXPECT(ammAlice.expectBalances(
+
2169 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
+
2170 });
+
2171
+
2172 // Equal withdrawal with a limit. XRP100/USD100.
+
2173 testAMM([&](AMM& ammAlice, Env&) {
+
2174 ammAlice.withdraw(alice, XRP(100), USD(200));
+
2175 BEAST_EXPECT(ammAlice.expectBalances(
+
2176 XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
+
2177 });
+
2178
+
2179 // Single withdrawal by amount XRP1000
+
2180 testAMM([&](AMM& ammAlice, Env&) {
+
2181 ammAlice.withdraw(alice, XRP(1'000));
+
2182 BEAST_EXPECT(ammAlice.expectBalances(
+
2183 XRP(9'000), USD(10'000), IOUAmount{9'486'832'98050514, -8}));
+
2184 });
+
2185
+
2186 // Single withdrawal by tokens 10000.
+
2187 testAMM([&](AMM& ammAlice, Env&) {
+
2188 ammAlice.withdraw(alice, 10'000, USD(0));
+
2189 BEAST_EXPECT(ammAlice.expectBalances(
+
2190 XRP(10'000), USD(9980.01), IOUAmount{9'990'000, 0}));
+
2191 });
+
2192
+
2193 // Withdraw all tokens.
+
2194 testAMM([&](AMM& ammAlice, Env& env) {
+
2195 env(trust(carol, STAmount{ammAlice.lptIssue(), 10'000}));
+
2196 // Can SetTrust only for AMM LP tokens
+
2197 env(trust(
+
2198 carol,
+
2199 STAmount{
+
2200 Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}),
+
2201 ter(tecNO_PERMISSION));
+
2202 env.close();
+
2203 ammAlice.withdrawAll(alice);
+
2204 BEAST_EXPECT(!ammAlice.ammExists());
+
2205
+
2206 BEAST_EXPECT(!env.le(keylet::ownerDir(ammAlice.ammAccount())));
+
2207
+
2208 // Can create AMM for the XRP/USD pair
+
2209 AMM ammCarol(env, carol, XRP(10'000), USD(10'000));
+
2210 BEAST_EXPECT(ammCarol.expectBalances(
+
2211 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2212 });
+
2213
+
2214 // Single deposit 1000USD, withdraw all tokens in USD
+
2215 testAMM([&](AMM& ammAlice, Env& env) {
+
2216 ammAlice.deposit(carol, USD(1'000));
+
2217 ammAlice.withdrawAll(carol, USD(0));
+
2218 BEAST_EXPECT(ammAlice.expectBalances(
+
2219 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2220 BEAST_EXPECT(
+
2221 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
+
2222 });
+
2223
+
2224 // Single deposit 1000USD, withdraw all tokens in XRP
+
2225 testAMM([&](AMM& ammAlice, Env&) {
+
2226 ammAlice.deposit(carol, USD(1'000));
+
2227 ammAlice.withdrawAll(carol, XRP(0));
+
2228 BEAST_EXPECT(ammAlice.expectBalances(
+
2229 XRPAmount(9'090'909'091),
+
2230 STAmount{USD, UINT64_C(10'999'99999999999), -11},
+
2231 IOUAmount{10'000'000, 0}));
+
2232 });
+
2233
+
2234 // Single deposit/withdraw by the same account
+
2235 testAMM([&](AMM& ammAlice, Env&) {
+
2236 // Since a smaller amount might be deposited due to
+
2237 // the lp tokens adjustment, withdrawing by tokens
+
2238 // is generally preferred to withdrawing by amount.
+
2239 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
+
2240 ammAlice.withdraw(carol, lpTokens, USD(0));
+
2241 lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6));
+
2242 ammAlice.withdraw(carol, lpTokens, USD(0));
+
2243 lpTokens = ammAlice.deposit(carol, XRPAmount(1));
+
2244 ammAlice.withdraw(carol, lpTokens, XRPAmount(0));
+
2245 BEAST_EXPECT(ammAlice.expectBalances(
+
2246 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
2247 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2248 });
+
2249
+
2250 // Single deposit by different accounts and then withdraw
+
2251 // in reverse.
+
2252 testAMM([&](AMM& ammAlice, Env&) {
+
2253 auto const carolTokens = ammAlice.deposit(carol, USD(1'000));
+
2254 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
+
2255 ammAlice.withdraw(alice, aliceTokens, USD(0));
+
2256 ammAlice.withdraw(carol, carolTokens, USD(0));
+
2257 BEAST_EXPECT(ammAlice.expectBalances(
+
2258 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
2259 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2260 BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens()));
+
2261 });
+
2262
+
2263 // Equal deposit 10%, withdraw all tokens
+
2264 testAMM([&](AMM& ammAlice, Env&) {
+
2265 ammAlice.deposit(carol, 1'000'000);
+
2266 ammAlice.withdrawAll(carol);
+
2267 BEAST_EXPECT(ammAlice.expectBalances(
+
2268 XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
+
2269 });
+
2270
+
2271 // Equal deposit 10%, withdraw all tokens in USD
+
2272 testAMM([&](AMM& ammAlice, Env&) {
+
2273 ammAlice.deposit(carol, 1'000'000);
+
2274 ammAlice.withdrawAll(carol, USD(0));
+
2275 BEAST_EXPECT(ammAlice.expectBalances(
+
2276 XRP(11'000),
+
2277 STAmount{USD, UINT64_C(9'090'909090909092), -12},
+
2278 IOUAmount{10'000'000, 0}));
+
2279 });
+
2280
+
2281 // Equal deposit 10%, withdraw all tokens in XRP
+
2282 testAMM([&](AMM& ammAlice, Env&) {
+
2283 ammAlice.deposit(carol, 1'000'000);
+
2284 ammAlice.withdrawAll(carol, XRP(0));
+
2285 BEAST_EXPECT(ammAlice.expectBalances(
+
2286 XRPAmount(9'090'909'091),
+
2287 USD(11'000),
+
2288 IOUAmount{10'000'000, 0}));
+
2289 });
+
2290
+
2291 auto const all = supported_amendments();
+
2292 // Withdraw with EPrice limit.
+
2293 testAMM(
+
2294 [&](AMM& ammAlice, Env& env) {
+
2295 ammAlice.deposit(carol, 1'000'000);
+
2296 ammAlice.withdraw(
+
2297 carol, USD(100), std::nullopt, IOUAmount{520, 0});
+
2298 if (!env.current()->rules().enabled(fixAMMv1_1))
+
2299 BEAST_EXPECT(
+
2300 ammAlice.expectBalances(
+
2301 XRPAmount(11'000'000'000),
+
2302 STAmount{USD, UINT64_C(9'372'781065088757), -12},
+
2303 IOUAmount{10'153'846'15384616, -8}) &&
+
2304 ammAlice.expectLPTokens(
+
2305 carol, IOUAmount{153'846'15384616, -8}));
+
2306 else
+
2307 BEAST_EXPECT(
+
2308 ammAlice.expectBalances(
+
2309 XRPAmount(11'000'000'000),
+
2310 STAmount{USD, UINT64_C(9'372'781065088769), -12},
+
2311 IOUAmount{10'153'846'15384616, -8}) &&
+
2312 ammAlice.expectLPTokens(
+
2313 carol, IOUAmount{153'846'15384616, -8}));
+
2314 ammAlice.withdrawAll(carol);
+
2315 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
2316 },
+
2317 std::nullopt,
+
2318 0,
+
2319 std::nullopt,
+
2320 {all, all - fixAMMv1_1});
+
2321
+
2322 // Withdraw with EPrice limit. AssetOut is 0.
+
2323 testAMM(
+
2324 [&](AMM& ammAlice, Env& env) {
+
2325 ammAlice.deposit(carol, 1'000'000);
+
2326 ammAlice.withdraw(
+
2327 carol, USD(0), std::nullopt, IOUAmount{520, 0});
+
2328 if (!env.current()->rules().enabled(fixAMMv1_1))
+
2329 BEAST_EXPECT(
+
2330 ammAlice.expectBalances(
+
2331 XRPAmount(11'000'000'000),
+
2332 STAmount{USD, UINT64_C(9'372'781065088757), -12},
+
2333 IOUAmount{10'153'846'15384616, -8}) &&
+
2334 ammAlice.expectLPTokens(
+
2335 carol, IOUAmount{153'846'15384616, -8}));
+
2336 else
+
2337 BEAST_EXPECT(
+
2338 ammAlice.expectBalances(
+
2339 XRPAmount(11'000'000'000),
+
2340 STAmount{USD, UINT64_C(9'372'781065088769), -12},
+
2341 IOUAmount{10'153'846'15384616, -8}) &&
+
2342 ammAlice.expectLPTokens(
+
2343 carol, IOUAmount{153'846'15384616, -8}));
+
2344 },
+
2345 std::nullopt,
+
2346 0,
+
2347 std::nullopt,
+
2348 {all, all - fixAMMv1_1});
+
2349
+
2350 // IOU to IOU + transfer fee
+
2351 {
+
2352 Env env{*this};
+
2353 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
+
2354 env(rate(gw, 1.25));
+
2355 env.close();
+
2356 // no transfer fee on create
+
2357 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
+
2358 BEAST_EXPECT(ammAlice.expectBalances(
+
2359 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
2360 BEAST_EXPECT(expectLine(env, alice, USD(0)));
+
2361 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
+
2362 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
+
2363 // no transfer fee on deposit
+
2364 ammAlice.deposit(carol, 10);
+
2365 BEAST_EXPECT(ammAlice.expectBalances(
+
2366 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
+
2367 BEAST_EXPECT(expectLine(env, carol, USD(0)));
+
2368 BEAST_EXPECT(expectLine(env, carol, BTC(0)));
+
2369 // no transfer fee on withdraw
+
2370 ammAlice.withdraw(carol, 10);
+
2371 BEAST_EXPECT(ammAlice.expectBalances(
+
2372 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
+
2373 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
+
2374 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
+
2375 BEAST_EXPECT(expectLine(env, carol, BTC(0.05)));
+
2376 }
+
2377
+
2378 // Tiny withdraw
+
2379 testAMM([&](AMM& ammAlice, Env&) {
+
2380 // By tokens
+
2381 ammAlice.withdraw(alice, IOUAmount{1, -3});
+
2382 BEAST_EXPECT(ammAlice.expectBalances(
+
2383 XRPAmount{9'999'999'999},
+
2384 STAmount{USD, UINT64_C(9'999'999999), -6},
+
2385 IOUAmount{9'999'999'999, -3}));
+
2386 });
+
2387 testAMM([&](AMM& ammAlice, Env&) {
+
2388 // Single XRP pool
+
2389 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
+
2390 BEAST_EXPECT(ammAlice.expectBalances(
+
2391 XRPAmount{9'999'999'999},
+
2392 USD(10'000),
+
2393 IOUAmount{9'999'999'9995, -4}));
+
2394 });
+
2395 testAMM([&](AMM& ammAlice, Env&) {
+
2396 // Single USD pool
+
2397 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
+
2398 BEAST_EXPECT(ammAlice.expectBalances(
+
2399 XRP(10'000),
+
2400 STAmount{USD, UINT64_C(9'999'9999999999), -10},
+
2401 IOUAmount{9'999'999'99999995, -8}));
+
2402 });
+
2403
+
2404 // Withdraw close to entire pool
+
2405 // Equal by tokens
+
2406 testAMM([&](AMM& ammAlice, Env&) {
+
2407 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
+
2408 BEAST_EXPECT(ammAlice.expectBalances(
+
2409 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
+
2410 });
+
2411 // USD by tokens
+
2412 testAMM([&](AMM& ammAlice, Env&) {
+
2413 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
+
2414 BEAST_EXPECT(ammAlice.expectBalances(
+
2415 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
+
2416 });
+
2417 // XRP by tokens
+
2418 testAMM([&](AMM& ammAlice, Env&) {
+
2419 ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0));
+
2420 BEAST_EXPECT(ammAlice.expectBalances(
+
2421 XRPAmount{1}, USD(10'000), IOUAmount{100}));
+
2422 });
+
2423 // USD
+
2424 testAMM([&](AMM& ammAlice, Env&) {
+
2425 ammAlice.withdraw(
+
2426 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
+
2427 BEAST_EXPECT(ammAlice.expectBalances(
+
2428 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
+
2429 });
+
2430 // XRP
+
2431 testAMM([&](AMM& ammAlice, Env&) {
+
2432 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
+
2433 BEAST_EXPECT(ammAlice.expectBalances(
+
2434 XRPAmount{1}, USD(10'000), IOUAmount{100}));
+
2435 });
+
2436 }
+
2437
+
2438 void
+
2439 testInvalidFeeVote()
+
2440 {
+
2441 testcase("Invalid Fee Vote");
+
2442 using namespace jtx;
+
2443
+
2444 testAMM([&](AMM& ammAlice, Env& env) {
+
2445 // Invalid flags
+
2446 ammAlice.vote(
2447 std::nullopt,
-
2448 std::nullopt,
-
2449 ter(temINVALID_FLAG));
-
2450
-
2451 // Invalid fee.
-
2452 ammAlice.vote(
-
2453 std::nullopt,
-
2454 1'001,
-
2455 std::nullopt,
+
2448 1'000,
+
2449 tfWithdrawAll,
+
2450 std::nullopt,
+
2451 std::nullopt,
+
2452 ter(temINVALID_FLAG));
+
2453
+
2454 // Invalid fee.
+
2455 ammAlice.vote(
2456 std::nullopt,
-
2457 std::nullopt,
-
2458 ter(temBAD_FEE));
-
2459 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
2460
-
2461 // Invalid Account
-
2462 Account bad("bad");
-
2463 env.memoize(bad);
-
2464 ammAlice.vote(
-
2465 bad,
-
2466 1'000,
-
2467 std::nullopt,
-
2468 seq(1),
-
2469 std::nullopt,
-
2470 ter(terNO_ACCOUNT));
-
2471
-
2472 // Invalid AMM
-
2473 ammAlice.vote(
-
2474 alice,
-
2475 1'000,
-
2476 std::nullopt,
-
2477 std::nullopt,
-
2478 {{USD, GBP}},
-
2479 ter(terNO_AMM));
-
2480
-
2481 // Account is not LP
-
2482 ammAlice.vote(
-
2483 carol,
-
2484 1'000,
-
2485 std::nullopt,
-
2486 std::nullopt,
-
2487 std::nullopt,
-
2488 ter(tecAMM_INVALID_TOKENS));
-
2489 });
-
2490
-
2491 // Invalid AMM
-
2492 testAMM([&](AMM& ammAlice, Env& env) {
-
2493 ammAlice.withdrawAll(alice);
-
2494 ammAlice.vote(
-
2495 alice,
-
2496 1'000,
-
2497 std::nullopt,
-
2498 std::nullopt,
-
2499 std::nullopt,
-
2500 ter(terNO_AMM));
-
2501 });
-
2502 }
-
2503
-
2504 void
-
2505 testFeeVote()
-
2506 {
-
2507 testcase("Fee Vote");
-
2508 using namespace jtx;
-
2509
-
2510 // One vote sets fee to 1%.
-
2511 testAMM([&](AMM& ammAlice, Env& env) {
-
2512 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0}));
-
2513 ammAlice.vote({}, 1'000);
-
2514 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
2515 // Discounted fee is 1/10 of trading fee.
-
2516 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0}));
-
2517 });
-
2518
-
2519 auto vote = [&](AMM& ammAlice,
-
2520 Env& env,
-
2521 int i,
-
2522 int fundUSD = 100'000,
-
2523 std::uint32_t tokens = 10'000'000,
-
2524 std::vector<Account>* accounts = nullptr) {
-
2525 Account a(std::to_string(i));
-
2526 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
-
2527 ammAlice.deposit(a, tokens);
-
2528 ammAlice.vote(a, 50 * (i + 1));
-
2529 if (accounts)
-
2530 accounts->push_back(std::move(a));
-
2531 };
-
2532
-
2533 // Eight votes fill all voting slots, set fee 0.175%.
-
2534 testAMM([&](AMM& ammAlice, Env& env) {
-
2535 for (int i = 0; i < 7; ++i)
-
2536 vote(ammAlice, env, i, 10'000);
-
2537 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2538 });
-
2539
-
2540 // Eight votes fill all voting slots, set fee 0.175%.
-
2541 // New vote, same account, sets fee 0.225%
-
2542 testAMM([&](AMM& ammAlice, Env& env) {
-
2543 for (int i = 0; i < 7; ++i)
-
2544 vote(ammAlice, env, i);
-
2545 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2546 Account const a("0");
-
2547 ammAlice.vote(a, 450);
-
2548 BEAST_EXPECT(ammAlice.expectTradingFee(225));
-
2549 });
-
2550
-
2551 // Eight votes fill all voting slots, set fee 0.175%.
-
2552 // New vote, new account, higher vote weight, set higher fee 0.244%
-
2553 testAMM([&](AMM& ammAlice, Env& env) {
-
2554 for (int i = 0; i < 7; ++i)
-
2555 vote(ammAlice, env, i);
-
2556 BEAST_EXPECT(ammAlice.expectTradingFee(175));
-
2557 vote(ammAlice, env, 7, 100'000, 20'000'000);
-
2558 BEAST_EXPECT(ammAlice.expectTradingFee(244));
-
2559 });
-
2560
-
2561 // Eight votes fill all voting slots, set fee 0.219%.
-
2562 // New vote, new account, higher vote weight, set smaller fee 0.206%
-
2563 testAMM([&](AMM& ammAlice, Env& env) {
-
2564 for (int i = 7; i > 0; --i)
-
2565 vote(ammAlice, env, i);
-
2566 BEAST_EXPECT(ammAlice.expectTradingFee(219));
-
2567 vote(ammAlice, env, 0, 100'000, 20'000'000);
-
2568 BEAST_EXPECT(ammAlice.expectTradingFee(206));
-
2569 });
-
2570
-
2571 // Eight votes fill all voting slots. The accounts then withdraw all
-
2572 // tokens. An account sets a new fee and the previous slots are
-
2573 // deleted.
-
2574 testAMM([&](AMM& ammAlice, Env& env) {
-
2575 std::vector<Account> accounts;
-
2576 for (int i = 0; i < 7; ++i)
-
2577 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
-
2578 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2457 1'001,
+
2458 std::nullopt,
+
2459 std::nullopt,
+
2460 std::nullopt,
+
2461 ter(temBAD_FEE));
+
2462 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
2463
+
2464 // Invalid Account
+
2465 Account bad("bad");
+
2466 env.memoize(bad);
+
2467 ammAlice.vote(
+
2468 bad,
+
2469 1'000,
+
2470 std::nullopt,
+
2471 seq(1),
+
2472 std::nullopt,
+
2473 ter(terNO_ACCOUNT));
+
2474
+
2475 // Invalid AMM
+
2476 ammAlice.vote(
+
2477 alice,
+
2478 1'000,
+
2479 std::nullopt,
+
2480 std::nullopt,
+
2481 {{USD, GBP}},
+
2482 ter(terNO_AMM));
+
2483
+
2484 // Account is not LP
+
2485 ammAlice.vote(
+
2486 carol,
+
2487 1'000,
+
2488 std::nullopt,
+
2489 std::nullopt,
+
2490 std::nullopt,
+
2491 ter(tecAMM_INVALID_TOKENS));
+
2492 });
+
2493
+
2494 // Invalid AMM
+
2495 testAMM([&](AMM& ammAlice, Env& env) {
+
2496 ammAlice.withdrawAll(alice);
+
2497 ammAlice.vote(
+
2498 alice,
+
2499 1'000,
+
2500 std::nullopt,
+
2501 std::nullopt,
+
2502 std::nullopt,
+
2503 ter(terNO_AMM));
+
2504 });
+
2505 }
+
2506
+
2507 void
+
2508 testFeeVote()
+
2509 {
+
2510 testcase("Fee Vote");
+
2511 using namespace jtx;
+
2512
+
2513 // One vote sets fee to 1%.
+
2514 testAMM([&](AMM& ammAlice, Env& env) {
+
2515 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0}));
+
2516 ammAlice.vote({}, 1'000);
+
2517 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
2518 // Discounted fee is 1/10 of trading fee.
+
2519 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2520 });
+
2521
+
2522 auto vote = [&](AMM& ammAlice,
+
2523 Env& env,
+
2524 int i,
+
2525 int fundUSD = 100'000,
+
2526 std::uint32_t tokens = 10'000'000,
+
2527 std::vector<Account>* accounts = nullptr) {
+
2528 Account a(std::to_string(i));
+
2529 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
+
2530 ammAlice.deposit(a, tokens);
+
2531 ammAlice.vote(a, 50 * (i + 1));
+
2532 if (accounts)
+
2533 accounts->push_back(std::move(a));
+
2534 };
+
2535
+
2536 // Eight votes fill all voting slots, set fee 0.175%.
+
2537 testAMM([&](AMM& ammAlice, Env& env) {
+
2538 for (int i = 0; i < 7; ++i)
+
2539 vote(ammAlice, env, i, 10'000);
+
2540 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2541 });
+
2542
+
2543 // Eight votes fill all voting slots, set fee 0.175%.
+
2544 // New vote, same account, sets fee 0.225%
+
2545 testAMM([&](AMM& ammAlice, Env& env) {
+
2546 for (int i = 0; i < 7; ++i)
+
2547 vote(ammAlice, env, i);
+
2548 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2549 Account const a("0");
+
2550 ammAlice.vote(a, 450);
+
2551 BEAST_EXPECT(ammAlice.expectTradingFee(225));
+
2552 });
+
2553
+
2554 // Eight votes fill all voting slots, set fee 0.175%.
+
2555 // New vote, new account, higher vote weight, set higher fee 0.244%
+
2556 testAMM([&](AMM& ammAlice, Env& env) {
+
2557 for (int i = 0; i < 7; ++i)
+
2558 vote(ammAlice, env, i);
+
2559 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2560 vote(ammAlice, env, 7, 100'000, 20'000'000);
+
2561 BEAST_EXPECT(ammAlice.expectTradingFee(244));
+
2562 });
+
2563
+
2564 // Eight votes fill all voting slots, set fee 0.219%.
+
2565 // New vote, new account, higher vote weight, set smaller fee 0.206%
+
2566 testAMM([&](AMM& ammAlice, Env& env) {
+
2567 for (int i = 7; i > 0; --i)
+
2568 vote(ammAlice, env, i);
+
2569 BEAST_EXPECT(ammAlice.expectTradingFee(219));
+
2570 vote(ammAlice, env, 0, 100'000, 20'000'000);
+
2571 BEAST_EXPECT(ammAlice.expectTradingFee(206));
+
2572 });
+
2573
+
2574 // Eight votes fill all voting slots. The accounts then withdraw all
+
2575 // tokens. An account sets a new fee and the previous slots are
+
2576 // deleted.
+
2577 testAMM([&](AMM& ammAlice, Env& env) {
+
2578 std::vector<Account> accounts;
2579 for (int i = 0; i < 7; ++i)
-
2580 ammAlice.withdrawAll(accounts[i]);
-
2581 ammAlice.deposit(carol, 10'000'000);
-
2582 ammAlice.vote(carol, 1'000);
-
2583 // The initial LP set the fee to 1000. Carol gets 50% voting
-
2584 // power, and the new fee is 500.
-
2585 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
2586 });
-
2587
-
2588 // Eight votes fill all voting slots. The accounts then withdraw some
-
2589 // tokens. The new vote doesn't get the voting power but
-
2590 // the slots are refreshed and the fee is updated.
-
2591 testAMM([&](AMM& ammAlice, Env& env) {
-
2592 std::vector<Account> accounts;
-
2593 for (int i = 0; i < 7; ++i)
-
2594 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
-
2595 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2580 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
+
2581 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2582 for (int i = 0; i < 7; ++i)
+
2583 ammAlice.withdrawAll(accounts[i]);
+
2584 ammAlice.deposit(carol, 10'000'000);
+
2585 ammAlice.vote(carol, 1'000);
+
2586 // The initial LP set the fee to 1000. Carol gets 50% voting
+
2587 // power, and the new fee is 500.
+
2588 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
2589 });
+
2590
+
2591 // Eight votes fill all voting slots. The accounts then withdraw some
+
2592 // tokens. The new vote doesn't get the voting power but
+
2593 // the slots are refreshed and the fee is updated.
+
2594 testAMM([&](AMM& ammAlice, Env& env) {
+
2595 std::vector<Account> accounts;
2596 for (int i = 0; i < 7; ++i)
-
2597 ammAlice.withdraw(accounts[i], 9'000'000);
-
2598 ammAlice.deposit(carol, 1'000);
-
2599 // The vote is not added to the slots
-
2600 ammAlice.vote(carol, 1'000);
-
2601 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
-
2602 for (std::uint16_t i = 0; i < info.size(); ++i)
-
2603 BEAST_EXPECT(info[i][jss::account] != carol.human());
-
2604 // But the slots are refreshed and the fee is changed
-
2605 BEAST_EXPECT(ammAlice.expectTradingFee(82));
-
2606 });
-
2607 }
-
2608
-
2609 void
-
2610 testInvalidBid()
-
2611 {
-
2612 testcase("Invalid Bid");
-
2613 using namespace jtx;
-
2614 using namespace std::chrono;
-
2615
-
2616 // burn all the LPTokens through a AMMBid transaction
-
2617 {
-
2618 Env env(*this);
-
2619 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
-
2620 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
-
2621
-
2622 // auction slot is owned by the creator of the AMM i.e. gw
-
2623 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2597 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
+
2598 BEAST_EXPECT(ammAlice.expectTradingFee(175));
+
2599 for (int i = 0; i < 7; ++i)
+
2600 ammAlice.withdraw(accounts[i], 9'000'000);
+
2601 ammAlice.deposit(carol, 1'000);
+
2602 // The vote is not added to the slots
+
2603 ammAlice.vote(carol, 1'000);
+
2604 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
+
2605 for (std::uint16_t i = 0; i < info.size(); ++i)
+
2606 BEAST_EXPECT(info[i][jss::account] != carol.human());
+
2607 // But the slots are refreshed and the fee is changed
+
2608 BEAST_EXPECT(ammAlice.expectTradingFee(82));
+
2609 });
+
2610 }
+
2611
+
2612 void
+
2613 testInvalidBid()
+
2614 {
+
2615 testcase("Invalid Bid");
+
2616 using namespace jtx;
+
2617 using namespace std::chrono;
+
2618
+
2619 // burn all the LPTokens through a AMMBid transaction
+
2620 {
+
2621 Env env(*this);
+
2622 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
+
2623 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
2624
-
2625 // gw attempts to burn all her LPTokens through a bid transaction
-
2626 // this transaction fails because AMMBid transaction can not burn
-
2627 // all the outstanding LPTokens
-
2628 env(amm.bid({
-
2629 .account = gw,
-
2630 .bidMin = 1'000'000,
-
2631 }),
-
2632 ter(tecAMM_INVALID_TOKENS));
-
2633 }
-
2634
-
2635 // burn all the LPTokens through a AMMBid transaction
-
2636 {
-
2637 Env env(*this);
-
2638 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
-
2639 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
-
2640
-
2641 // auction slot is owned by the creator of the AMM i.e. gw
-
2642 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2625 // auction slot is owned by the creator of the AMM i.e. gw
+
2626 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2627
+
2628 // gw attempts to burn all her LPTokens through a bid transaction
+
2629 // this transaction fails because AMMBid transaction can not burn
+
2630 // all the outstanding LPTokens
+
2631 env(amm.bid({
+
2632 .account = gw,
+
2633 .bidMin = 1'000'000,
+
2634 }),
+
2635 ter(tecAMM_INVALID_TOKENS));
+
2636 }
+
2637
+
2638 // burn all the LPTokens through a AMMBid transaction
+
2639 {
+
2640 Env env(*this);
+
2641 fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
+
2642 AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
2643
-
2644 // gw burns all but one of its LPTokens through a bid transaction
-
2645 // this transaction suceeds because the bid price is less than
-
2646 // the total outstanding LPToken balance
-
2647 env(amm.bid({
-
2648 .account = gw,
-
2649 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
-
2650 }),
-
2651 ter(tesSUCCESS))
-
2652 .close();
-
2653
-
2654 // gw must own the auction slot
-
2655 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999}));
+
2644 // auction slot is owned by the creator of the AMM i.e. gw
+
2645 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
2646
+
2647 // gw burns all but one of its LPTokens through a bid transaction
+
2648 // this transaction suceeds because the bid price is less than
+
2649 // the total outstanding LPToken balance
+
2650 env(amm.bid({
+
2651 .account = gw,
+
2652 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
+
2653 }),
+
2654 ter(tesSUCCESS))
+
2655 .close();
2656
-
2657 // 999'999 tokens are burned, only 1 LPToken is owned by gw
-
2658 BEAST_EXPECT(
-
2659 amm.expectBalances(XRP(1'000), USD(1'000), IOUAmount{1}));
-
2660
-
2661 // gw owns only 1 LPToken in its balance
-
2662 BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1);
+
2657 // gw must own the auction slot
+
2658 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999}));
+
2659
+
2660 // 999'999 tokens are burned, only 1 LPToken is owned by gw
+
2661 BEAST_EXPECT(
+
2662 amm.expectBalances(XRP(1'000), USD(1'000), IOUAmount{1}));
2663
-
2664 // gw attempts to burn the last of its LPTokens in an AMMBid
-
2665 // transaction. This transaction fails because it would burn all
-
2666 // the remaining LPTokens
-
2667 env(amm.bid({
-
2668 .account = gw,
-
2669 .bidMin = 1,
-
2670 }),
-
2671 ter(tecAMM_INVALID_TOKENS));
-
2672 }
-
2673
-
2674 testAMM([&](AMM& ammAlice, Env& env) {
-
2675 // Invalid flags
-
2676 env(ammAlice.bid({
-
2677 .account = carol,
-
2678 .bidMin = 0,
-
2679 .flags = tfWithdrawAll,
-
2680 }),
-
2681 ter(temINVALID_FLAG));
-
2682
-
2683 ammAlice.deposit(carol, 1'000'000);
-
2684 // Invalid Bid price <= 0
-
2685 for (auto bid : {0, -100})
-
2686 {
-
2687 env(ammAlice.bid({
-
2688 .account = carol,
-
2689 .bidMin = bid,
-
2690 }),
-
2691 ter(temBAD_AMOUNT));
-
2692 env(ammAlice.bid({
-
2693 .account = carol,
-
2694 .bidMax = bid,
-
2695 }),
-
2696 ter(temBAD_AMOUNT));
-
2697 }
-
2698
-
2699 // Invlaid Min/Max combination
-
2700 env(ammAlice.bid({
-
2701 .account = carol,
-
2702 .bidMin = 200,
-
2703 .bidMax = 100,
-
2704 }),
-
2705 ter(tecAMM_INVALID_TOKENS));
-
2706
-
2707 // Invalid Account
-
2708 Account bad("bad");
-
2709 env.memoize(bad);
-
2710 env(ammAlice.bid({
-
2711 .account = bad,
-
2712 .bidMax = 100,
-
2713 }),
-
2714 seq(1),
-
2715 ter(terNO_ACCOUNT));
-
2716
-
2717 // Account is not LP
-
2718 Account const dan("dan");
-
2719 env.fund(XRP(1'000), dan);
-
2720 env(ammAlice.bid({
-
2721 .account = dan,
-
2722 .bidMin = 100,
-
2723 }),
-
2724 ter(tecAMM_INVALID_TOKENS));
-
2725 env(ammAlice.bid({
-
2726 .account = dan,
-
2727 }),
-
2728 ter(tecAMM_INVALID_TOKENS));
-
2729
-
2730 // Auth account is invalid.
-
2731 env(ammAlice.bid({
-
2732 .account = carol,
-
2733 .bidMin = 100,
-
2734 .authAccounts = {bob},
-
2735 }),
-
2736 ter(terNO_ACCOUNT));
-
2737
-
2738 // Invalid Assets
-
2739 env(ammAlice.bid({
-
2740 .account = alice,
-
2741 .bidMax = 100,
-
2742 .assets = {{USD, GBP}},
-
2743 }),
-
2744 ter(terNO_AMM));
-
2745
-
2746 // Invalid Min/Max issue
-
2747 env(ammAlice.bid({
-
2748 .account = alice,
-
2749 .bidMax = STAmount{USD, 100},
-
2750 }),
-
2751 ter(temBAD_AMM_TOKENS));
-
2752 env(ammAlice.bid({
-
2753 .account = alice,
-
2754 .bidMin = STAmount{USD, 100},
-
2755 }),
-
2756 ter(temBAD_AMM_TOKENS));
-
2757 });
-
2758
-
2759 // Invalid AMM
-
2760 testAMM([&](AMM& ammAlice, Env& env) {
-
2761 ammAlice.withdrawAll(alice);
-
2762 env(ammAlice.bid({
-
2763 .account = alice,
-
2764 .bidMax = 100,
-
2765 }),
-
2766 ter(terNO_AMM));
-
2767 });
-
2768
-
2769 // More than four Auth accounts.
-
2770 testAMM([&](AMM& ammAlice, Env& env) {
-
2771 Account ed("ed");
-
2772 Account bill("bill");
-
2773 Account scott("scott");
-
2774 Account james("james");
-
2775 env.fund(XRP(1'000), bob, ed, bill, scott, james);
-
2776 env.close();
-
2777 ammAlice.deposit(carol, 1'000'000);
-
2778 env(ammAlice.bid({
-
2779 .account = carol,
-
2780 .bidMin = 100,
-
2781 .authAccounts = {bob, ed, bill, scott, james},
-
2782 }),
-
2783 ter(temMALFORMED));
-
2784 });
-
2785
-
2786 // Bid price exceeds LP owned tokens
-
2787 testAMM([&](AMM& ammAlice, Env& env) {
-
2788 fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
-
2789 ammAlice.deposit(carol, 1'000'000);
-
2790 ammAlice.deposit(bob, 10);
-
2791 env(ammAlice.bid({
-
2792 .account = carol,
-
2793 .bidMin = 1'000'001,
-
2794 }),
-
2795 ter(tecAMM_INVALID_TOKENS));
-
2796 env(ammAlice.bid({
-
2797 .account = carol,
-
2798 .bidMax = 1'000'001,
-
2799 }),
-
2800 ter(tecAMM_INVALID_TOKENS));
-
2801 env(ammAlice.bid({
-
2802 .account = carol,
-
2803 .bidMin = 1'000,
-
2804 }));
-
2805 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
-
2806 // Slot purchase price is more than 1000 but bob only has 10 tokens
-
2807 env(ammAlice.bid({
-
2808 .account = bob,
-
2809 }),
-
2810 ter(tecAMM_INVALID_TOKENS));
-
2811 });
-
2812
-
2813 // Bid all tokens, still own the slot
-
2814 {
-
2815 Env env(*this);
-
2816 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
-
2817 AMM amm(env, gw, XRP(10), USD(1'000));
-
2818 auto const lpIssue = amm.lptIssue();
-
2819 env.trust(STAmount{lpIssue, 100}, alice);
-
2820 env.trust(STAmount{lpIssue, 50}, bob);
-
2821 env(pay(gw, alice, STAmount{lpIssue, 100}));
-
2822 env(pay(gw, bob, STAmount{lpIssue, 50}));
-
2823 env(amm.bid({.account = alice, .bidMin = 100}));
-
2824 // Alice doesn't have any more tokens, but
-
2825 // she still owns the slot.
-
2826 env(amm.bid({
-
2827 .account = bob,
-
2828 .bidMax = 50,
-
2829 }),
-
2830 ter(tecAMM_FAILED));
-
2831 }
-
2832 }
-
2833
-
2834 void
-
2835 testBid(FeatureBitset features)
-
2836 {
-
2837 testcase("Bid");
-
2838 using namespace jtx;
-
2839 using namespace std::chrono;
-
2840
-
2841 // Auction slot initially is owned by AMM creator, who pays 0 price.
-
2842
-
2843 // Bid 110 tokens. Pay bidMin.
-
2844 testAMM(
-
2845 [&](AMM& ammAlice, Env& env) {
-
2846 ammAlice.deposit(carol, 1'000'000);
-
2847 env(ammAlice.bid({.account = carol, .bidMin = 110}));
-
2848 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2849 // 110 tokens are burned.
-
2850 BEAST_EXPECT(ammAlice.expectBalances(
-
2851 XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
-
2852 },
-
2853 std::nullopt,
-
2854 0,
-
2855 std::nullopt,
-
2856 {features});
-
2857
-
2858 // Bid with min/max when the pay price is less than min.
-
2859 testAMM(
-
2860 [&](AMM& ammAlice, Env& env) {
-
2861 ammAlice.deposit(carol, 1'000'000);
-
2862 // Bid exactly 110. Pay 110 because the pay price is < 110.
-
2863 env(ammAlice.bid(
-
2864 {.account = carol, .bidMin = 110, .bidMax = 110}));
-
2865 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2866 BEAST_EXPECT(ammAlice.expectBalances(
-
2867 XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
-
2868 // Bid exactly 180-200. Pay 180 because the pay price is < 180.
-
2869 env(ammAlice.bid(
-
2870 {.account = alice, .bidMin = 180, .bidMax = 200}));
-
2871 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
-
2872 BEAST_EXPECT(ammAlice.expectBalances(
-
2873 XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
-
2874 },
-
2875 std::nullopt,
-
2876 0,
-
2877 std::nullopt,
-
2878 {features});
-
2879
-
2880 // Start bid at bidMin 110.
-
2881 testAMM(
-
2882 [&](AMM& ammAlice, Env& env) {
-
2883 ammAlice.deposit(carol, 1'000'000);
-
2884 // Bid, pay bidMin.
-
2885 env(ammAlice.bid({.account = carol, .bidMin = 110}));
-
2886 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2887
-
2888 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
-
2889 ammAlice.deposit(bob, 1'000'000);
-
2890 // Bid, pay the computed price.
-
2891 env(ammAlice.bid({.account = bob}));
-
2892 BEAST_EXPECT(
-
2893 ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
-
2894
-
2895 // Bid bidMax fails because the computed price is higher.
-
2896 env(ammAlice.bid({
-
2897 .account = carol,
-
2898 .bidMax = 120,
-
2899 }),
-
2900 ter(tecAMM_FAILED));
-
2901 // Bid MaxSlotPrice succeeds - pay computed price
-
2902 env(ammAlice.bid({.account = carol, .bidMax = 600}));
-
2903 BEAST_EXPECT(
-
2904 ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
-
2905
-
2906 // Bid Min/MaxSlotPrice fails because the computed price is not
-
2907 // in range
-
2908 env(ammAlice.bid({
-
2909 .account = carol,
-
2910 .bidMin = 10,
-
2911 .bidMax = 100,
-
2912 }),
-
2913 ter(tecAMM_FAILED));
-
2914 // Bid Min/MaxSlotPrice succeeds - pay computed price
-
2915 env(ammAlice.bid(
-
2916 {.account = carol, .bidMin = 100, .bidMax = 600}));
-
2917 BEAST_EXPECT(
-
2918 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
-
2919 },
-
2920 std::nullopt,
-
2921 0,
-
2922 std::nullopt,
-
2923 {features});
-
2924
-
2925 // Slot states.
-
2926 testAMM(
-
2927 [&](AMM& ammAlice, Env& env) {
-
2928 ammAlice.deposit(carol, 1'000'000);
-
2929
-
2930 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
-
2931 ammAlice.deposit(bob, 1'000'000);
-
2932 BEAST_EXPECT(ammAlice.expectBalances(
-
2933 XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
-
2934
-
2935 // Initial state. Pay bidMin.
-
2936 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
-
2937 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
-
2938
-
2939 // 1st Interval after close, price for 0th interval.
-
2940 env(ammAlice.bid({.account = bob}));
-
2941 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
-
2942 BEAST_EXPECT(
-
2943 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
-
2944
-
2945 // 10th Interval after close, price for 1st interval.
-
2946 env(ammAlice.bid({.account = carol}));
-
2947 env.close(seconds(10 * AUCTION_SLOT_INTERVAL_DURATION + 1));
-
2948 BEAST_EXPECT(
-
2949 ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
-
2950
-
2951 // 20th Interval (expired) after close, price for 10th interval.
-
2952 env(ammAlice.bid({.account = bob}));
-
2953 env.close(seconds(
-
2954 AUCTION_SLOT_TIME_INTERVALS *
-
2955 AUCTION_SLOT_INTERVAL_DURATION +
-
2956 1));
-
2957 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
2958 0, std::nullopt, IOUAmount{127'33875, -5}));
-
2959
-
2960 // 0 Interval.
-
2961 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
-
2962 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
2963 0, std::nullopt, IOUAmount{110}));
-
2964 // ~321.09 tokens burnt on bidding fees.
-
2965 BEAST_EXPECT(ammAlice.expectBalances(
-
2966 XRP(12'000), USD(12'000), IOUAmount{11'999'678'91, -2}));
-
2967 },
-
2968 std::nullopt,
-
2969 0,
-
2970 std::nullopt,
-
2971 {features});
-
2972
-
2973 // Pool's fee 1%. Bid bidMin.
-
2974 // Auction slot owner and auth account trade at discounted fee -
-
2975 // 1/10 of the trading fee.
-
2976 // Other accounts trade at 1% fee.
-
2977 testAMM(
-
2978 [&](AMM& ammAlice, Env& env) {
-
2979 Account const dan("dan");
-
2980 Account const ed("ed");
-
2981 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
-
2982 ammAlice.deposit(bob, 1'000'000);
-
2983 ammAlice.deposit(ed, 1'000'000);
-
2984 ammAlice.deposit(carol, 500'000);
-
2985 ammAlice.deposit(dan, 500'000);
-
2986 auto ammTokens = ammAlice.getLPTokensBalance();
-
2987 env(ammAlice.bid({
-
2988 .account = carol,
-
2989 .bidMin = 120,
-
2990 .authAccounts = {bob, ed},
-
2991 }));
-
2992 auto const slotPrice = IOUAmount{5'200};
-
2993 ammTokens -= slotPrice;
-
2994 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
-
2995 BEAST_EXPECT(ammAlice.expectBalances(
-
2996 XRP(13'000), USD(13'000), ammTokens));
-
2997 // Discounted trade
-
2998 for (int i = 0; i < 10; ++i)
-
2999 {
-
3000 auto tokens = ammAlice.deposit(carol, USD(100));
-
3001 ammAlice.withdraw(carol, tokens, USD(0));
-
3002 tokens = ammAlice.deposit(bob, USD(100));
-
3003 ammAlice.withdraw(bob, tokens, USD(0));
-
3004 tokens = ammAlice.deposit(ed, USD(100));
-
3005 ammAlice.withdraw(ed, tokens, USD(0));
-
3006 }
-
3007 // carol, bob, and ed pay ~0.99USD in fees.
-
3008 if (!features[fixAMMv1_1])
-
3009 {
-
3010 BEAST_EXPECT(
-
3011 env.balance(carol, USD) ==
-
3012 STAmount(USD, UINT64_C(29'499'00572620545), -11));
+
2664 // gw owns only 1 LPToken in its balance
+
2665 BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1);
+
2666
+
2667 // gw attempts to burn the last of its LPTokens in an AMMBid
+
2668 // transaction. This transaction fails because it would burn all
+
2669 // the remaining LPTokens
+
2670 env(amm.bid({
+
2671 .account = gw,
+
2672 .bidMin = 1,
+
2673 }),
+
2674 ter(tecAMM_INVALID_TOKENS));
+
2675 }
+
2676
+
2677 testAMM([&](AMM& ammAlice, Env& env) {
+
2678 // Invalid flags
+
2679 env(ammAlice.bid({
+
2680 .account = carol,
+
2681 .bidMin = 0,
+
2682 .flags = tfWithdrawAll,
+
2683 }),
+
2684 ter(temINVALID_FLAG));
+
2685
+
2686 ammAlice.deposit(carol, 1'000'000);
+
2687 // Invalid Bid price <= 0
+
2688 for (auto bid : {0, -100})
+
2689 {
+
2690 env(ammAlice.bid({
+
2691 .account = carol,
+
2692 .bidMin = bid,
+
2693 }),
+
2694 ter(temBAD_AMOUNT));
+
2695 env(ammAlice.bid({
+
2696 .account = carol,
+
2697 .bidMax = bid,
+
2698 }),
+
2699 ter(temBAD_AMOUNT));
+
2700 }
+
2701
+
2702 // Invlaid Min/Max combination
+
2703 env(ammAlice.bid({
+
2704 .account = carol,
+
2705 .bidMin = 200,
+
2706 .bidMax = 100,
+
2707 }),
+
2708 ter(tecAMM_INVALID_TOKENS));
+
2709
+
2710 // Invalid Account
+
2711 Account bad("bad");
+
2712 env.memoize(bad);
+
2713 env(ammAlice.bid({
+
2714 .account = bad,
+
2715 .bidMax = 100,
+
2716 }),
+
2717 seq(1),
+
2718 ter(terNO_ACCOUNT));
+
2719
+
2720 // Account is not LP
+
2721 Account const dan("dan");
+
2722 env.fund(XRP(1'000), dan);
+
2723 env(ammAlice.bid({
+
2724 .account = dan,
+
2725 .bidMin = 100,
+
2726 }),
+
2727 ter(tecAMM_INVALID_TOKENS));
+
2728 env(ammAlice.bid({
+
2729 .account = dan,
+
2730 }),
+
2731 ter(tecAMM_INVALID_TOKENS));
+
2732
+
2733 // Auth account is invalid.
+
2734 env(ammAlice.bid({
+
2735 .account = carol,
+
2736 .bidMin = 100,
+
2737 .authAccounts = {bob},
+
2738 }),
+
2739 ter(terNO_ACCOUNT));
+
2740
+
2741 // Invalid Assets
+
2742 env(ammAlice.bid({
+
2743 .account = alice,
+
2744 .bidMax = 100,
+
2745 .assets = {{USD, GBP}},
+
2746 }),
+
2747 ter(terNO_AMM));
+
2748
+
2749 // Invalid Min/Max issue
+
2750 env(ammAlice.bid({
+
2751 .account = alice,
+
2752 .bidMax = STAmount{USD, 100},
+
2753 }),
+
2754 ter(temBAD_AMM_TOKENS));
+
2755 env(ammAlice.bid({
+
2756 .account = alice,
+
2757 .bidMin = STAmount{USD, 100},
+
2758 }),
+
2759 ter(temBAD_AMM_TOKENS));
+
2760 });
+
2761
+
2762 // Invalid AMM
+
2763 testAMM([&](AMM& ammAlice, Env& env) {
+
2764 ammAlice.withdrawAll(alice);
+
2765 env(ammAlice.bid({
+
2766 .account = alice,
+
2767 .bidMax = 100,
+
2768 }),
+
2769 ter(terNO_AMM));
+
2770 });
+
2771
+
2772 // More than four Auth accounts.
+
2773 testAMM([&](AMM& ammAlice, Env& env) {
+
2774 Account ed("ed");
+
2775 Account bill("bill");
+
2776 Account scott("scott");
+
2777 Account james("james");
+
2778 env.fund(XRP(1'000), bob, ed, bill, scott, james);
+
2779 env.close();
+
2780 ammAlice.deposit(carol, 1'000'000);
+
2781 env(ammAlice.bid({
+
2782 .account = carol,
+
2783 .bidMin = 100,
+
2784 .authAccounts = {bob, ed, bill, scott, james},
+
2785 }),
+
2786 ter(temMALFORMED));
+
2787 });
+
2788
+
2789 // Bid price exceeds LP owned tokens
+
2790 testAMM([&](AMM& ammAlice, Env& env) {
+
2791 fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
+
2792 ammAlice.deposit(carol, 1'000'000);
+
2793 ammAlice.deposit(bob, 10);
+
2794 env(ammAlice.bid({
+
2795 .account = carol,
+
2796 .bidMin = 1'000'001,
+
2797 }),
+
2798 ter(tecAMM_INVALID_TOKENS));
+
2799 env(ammAlice.bid({
+
2800 .account = carol,
+
2801 .bidMax = 1'000'001,
+
2802 }),
+
2803 ter(tecAMM_INVALID_TOKENS));
+
2804 env(ammAlice.bid({
+
2805 .account = carol,
+
2806 .bidMin = 1'000,
+
2807 }));
+
2808 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
+
2809 // Slot purchase price is more than 1000 but bob only has 10 tokens
+
2810 env(ammAlice.bid({
+
2811 .account = bob,
+
2812 }),
+
2813 ter(tecAMM_INVALID_TOKENS));
+
2814 });
+
2815
+
2816 // Bid all tokens, still own the slot
+
2817 {
+
2818 Env env(*this);
+
2819 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
+
2820 AMM amm(env, gw, XRP(10), USD(1'000));
+
2821 auto const lpIssue = amm.lptIssue();
+
2822 env.trust(STAmount{lpIssue, 100}, alice);
+
2823 env.trust(STAmount{lpIssue, 50}, bob);
+
2824 env(pay(gw, alice, STAmount{lpIssue, 100}));
+
2825 env(pay(gw, bob, STAmount{lpIssue, 50}));
+
2826 env(amm.bid({.account = alice, .bidMin = 100}));
+
2827 // Alice doesn't have any more tokens, but
+
2828 // she still owns the slot.
+
2829 env(amm.bid({
+
2830 .account = bob,
+
2831 .bidMax = 50,
+
2832 }),
+
2833 ter(tecAMM_FAILED));
+
2834 }
+
2835 }
+
2836
+
2837 void
+
2838 testBid(FeatureBitset features)
+
2839 {
+
2840 testcase("Bid");
+
2841 using namespace jtx;
+
2842 using namespace std::chrono;
+
2843
+
2844 // Auction slot initially is owned by AMM creator, who pays 0 price.
+
2845
+
2846 // Bid 110 tokens. Pay bidMin.
+
2847 testAMM(
+
2848 [&](AMM& ammAlice, Env& env) {
+
2849 ammAlice.deposit(carol, 1'000'000);
+
2850 env(ammAlice.bid({.account = carol, .bidMin = 110}));
+
2851 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2852 // 110 tokens are burned.
+
2853 BEAST_EXPECT(ammAlice.expectBalances(
+
2854 XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
+
2855 },
+
2856 std::nullopt,
+
2857 0,
+
2858 std::nullopt,
+
2859 {features});
+
2860
+
2861 // Bid with min/max when the pay price is less than min.
+
2862 testAMM(
+
2863 [&](AMM& ammAlice, Env& env) {
+
2864 ammAlice.deposit(carol, 1'000'000);
+
2865 // Bid exactly 110. Pay 110 because the pay price is < 110.
+
2866 env(ammAlice.bid(
+
2867 {.account = carol, .bidMin = 110, .bidMax = 110}));
+
2868 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2869 BEAST_EXPECT(ammAlice.expectBalances(
+
2870 XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
+
2871 // Bid exactly 180-200. Pay 180 because the pay price is < 180.
+
2872 env(ammAlice.bid(
+
2873 {.account = alice, .bidMin = 180, .bidMax = 200}));
+
2874 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
+
2875 BEAST_EXPECT(ammAlice.expectBalances(
+
2876 XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
+
2877 },
+
2878 std::nullopt,
+
2879 0,
+
2880 std::nullopt,
+
2881 {features});
+
2882
+
2883 // Start bid at bidMin 110.
+
2884 testAMM(
+
2885 [&](AMM& ammAlice, Env& env) {
+
2886 ammAlice.deposit(carol, 1'000'000);
+
2887 // Bid, pay bidMin.
+
2888 env(ammAlice.bid({.account = carol, .bidMin = 110}));
+
2889 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2890
+
2891 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
+
2892 ammAlice.deposit(bob, 1'000'000);
+
2893 // Bid, pay the computed price.
+
2894 env(ammAlice.bid({.account = bob}));
+
2895 BEAST_EXPECT(
+
2896 ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
+
2897
+
2898 // Bid bidMax fails because the computed price is higher.
+
2899 env(ammAlice.bid({
+
2900 .account = carol,
+
2901 .bidMax = 120,
+
2902 }),
+
2903 ter(tecAMM_FAILED));
+
2904 // Bid MaxSlotPrice succeeds - pay computed price
+
2905 env(ammAlice.bid({.account = carol, .bidMax = 600}));
+
2906 BEAST_EXPECT(
+
2907 ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
+
2908
+
2909 // Bid Min/MaxSlotPrice fails because the computed price is not
+
2910 // in range
+
2911 env(ammAlice.bid({
+
2912 .account = carol,
+
2913 .bidMin = 10,
+
2914 .bidMax = 100,
+
2915 }),
+
2916 ter(tecAMM_FAILED));
+
2917 // Bid Min/MaxSlotPrice succeeds - pay computed price
+
2918 env(ammAlice.bid(
+
2919 {.account = carol, .bidMin = 100, .bidMax = 600}));
+
2920 BEAST_EXPECT(
+
2921 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
+
2922 },
+
2923 std::nullopt,
+
2924 0,
+
2925 std::nullopt,
+
2926 {features});
+
2927
+
2928 // Slot states.
+
2929 testAMM(
+
2930 [&](AMM& ammAlice, Env& env) {
+
2931 ammAlice.deposit(carol, 1'000'000);
+
2932
+
2933 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
+
2934 ammAlice.deposit(bob, 1'000'000);
+
2935 BEAST_EXPECT(ammAlice.expectBalances(
+
2936 XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
+
2937
+
2938 // Initial state. Pay bidMin.
+
2939 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
+
2940 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
+
2941
+
2942 // 1st Interval after close, price for 0th interval.
+
2943 env(ammAlice.bid({.account = bob}));
+
2944 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
+
2945 BEAST_EXPECT(
+
2946 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
+
2947
+
2948 // 10th Interval after close, price for 1st interval.
+
2949 env(ammAlice.bid({.account = carol}));
+
2950 env.close(seconds(10 * AUCTION_SLOT_INTERVAL_DURATION + 1));
+
2951 BEAST_EXPECT(
+
2952 ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
+
2953
+
2954 // 20th Interval (expired) after close, price for 10th interval.
+
2955 env(ammAlice.bid({.account = bob}));
+
2956 env.close(seconds(
+
2957 AUCTION_SLOT_TIME_INTERVALS *
+
2958 AUCTION_SLOT_INTERVAL_DURATION +
+
2959 1));
+
2960 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
2961 0, std::nullopt, IOUAmount{127'33875, -5}));
+
2962
+
2963 // 0 Interval.
+
2964 env(ammAlice.bid({.account = carol, .bidMin = 110})).close();
+
2965 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
2966 0, std::nullopt, IOUAmount{110}));
+
2967 // ~321.09 tokens burnt on bidding fees.
+
2968 BEAST_EXPECT(ammAlice.expectBalances(
+
2969 XRP(12'000), USD(12'000), IOUAmount{11'999'678'91, -2}));
+
2970 },
+
2971 std::nullopt,
+
2972 0,
+
2973 std::nullopt,
+
2974 {features});
+
2975
+
2976 // Pool's fee 1%. Bid bidMin.
+
2977 // Auction slot owner and auth account trade at discounted fee -
+
2978 // 1/10 of the trading fee.
+
2979 // Other accounts trade at 1% fee.
+
2980 testAMM(
+
2981 [&](AMM& ammAlice, Env& env) {
+
2982 Account const dan("dan");
+
2983 Account const ed("ed");
+
2984 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
+
2985 ammAlice.deposit(bob, 1'000'000);
+
2986 ammAlice.deposit(ed, 1'000'000);
+
2987 ammAlice.deposit(carol, 500'000);
+
2988 ammAlice.deposit(dan, 500'000);
+
2989 auto ammTokens = ammAlice.getLPTokensBalance();
+
2990 env(ammAlice.bid({
+
2991 .account = carol,
+
2992 .bidMin = 120,
+
2993 .authAccounts = {bob, ed},
+
2994 }));
+
2995 auto const slotPrice = IOUAmount{5'200};
+
2996 ammTokens -= slotPrice;
+
2997 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
+
2998 BEAST_EXPECT(ammAlice.expectBalances(
+
2999 XRP(13'000), USD(13'000), ammTokens));
+
3000 // Discounted trade
+
3001 for (int i = 0; i < 10; ++i)
+
3002 {
+
3003 auto tokens = ammAlice.deposit(carol, USD(100));
+
3004 ammAlice.withdraw(carol, tokens, USD(0));
+
3005 tokens = ammAlice.deposit(bob, USD(100));
+
3006 ammAlice.withdraw(bob, tokens, USD(0));
+
3007 tokens = ammAlice.deposit(ed, USD(100));
+
3008 ammAlice.withdraw(ed, tokens, USD(0));
+
3009 }
+
3010 // carol, bob, and ed pay ~0.99USD in fees.
+
3011 if (!features[fixAMMv1_1])
+
3012 {
3013 BEAST_EXPECT(
-
3014 env.balance(bob, USD) ==
-
3015 STAmount(USD, UINT64_C(18'999'00572616195), -11));
+
3014 env.balance(carol, USD) ==
+
3015 STAmount(USD, UINT64_C(29'499'00572620545), -11));
3016 BEAST_EXPECT(
-
3017 env.balance(ed, USD) ==
-
3018 STAmount(USD, UINT64_C(18'999'00572611841), -11));
-
3019 // USD pool is slightly higher because of the fees.
-
3020 BEAST_EXPECT(ammAlice.expectBalances(
-
3021 XRP(13'000),
-
3022 STAmount(USD, UINT64_C(13'002'98282151419), -11),
-
3023 ammTokens));
-
3024 }
-
3025 else
-
3026 {
-
3027 BEAST_EXPECT(
-
3028 env.balance(carol, USD) ==
-
3029 STAmount(USD, UINT64_C(29'499'00572620544), -11));
+
3017 env.balance(bob, USD) ==
+
3018 STAmount(USD, UINT64_C(18'999'00572616195), -11));
+
3019 BEAST_EXPECT(
+
3020 env.balance(ed, USD) ==
+
3021 STAmount(USD, UINT64_C(18'999'00572611841), -11));
+
3022 // USD pool is slightly higher because of the fees.
+
3023 BEAST_EXPECT(ammAlice.expectBalances(
+
3024 XRP(13'000),
+
3025 STAmount(USD, UINT64_C(13'002'98282151419), -11),
+
3026 ammTokens));
+
3027 }
+
3028 else
+
3029 {
3030 BEAST_EXPECT(
-
3031 env.balance(bob, USD) ==
-
3032 STAmount(USD, UINT64_C(18'999'00572616194), -11));
+
3031 env.balance(carol, USD) ==
+
3032 STAmount(USD, UINT64_C(29'499'00572620544), -11));
3033 BEAST_EXPECT(
-
3034 env.balance(ed, USD) ==
-
3035 STAmount(USD, UINT64_C(18'999'0057261184), -10));
-
3036 // USD pool is slightly higher because of the fees.
-
3037 BEAST_EXPECT(ammAlice.expectBalances(
-
3038 XRP(13'000),
-
3039 STAmount(USD, UINT64_C(13'002'98282151422), -11),
-
3040 ammTokens));
-
3041 }
-
3042 ammTokens = ammAlice.getLPTokensBalance();
-
3043 // Trade with the fee
-
3044 for (int i = 0; i < 10; ++i)
-
3045 {
-
3046 auto const tokens = ammAlice.deposit(dan, USD(100));
-
3047 ammAlice.withdraw(dan, tokens, USD(0));
-
3048 }
-
3049 // dan pays ~9.94USD, which is ~10 times more in fees than
-
3050 // carol, bob, ed. the discounted fee is 10 times less
-
3051 // than the trading fee.
-
3052 if (!features[fixAMMv1_1])
-
3053 {
-
3054 BEAST_EXPECT(
-
3055 env.balance(dan, USD) ==
-
3056 STAmount(USD, UINT64_C(19'490'056722744), -9));
-
3057 // USD pool gains more in dan's fees.
-
3058 BEAST_EXPECT(ammAlice.expectBalances(
-
3059 XRP(13'000),
-
3060 STAmount{USD, UINT64_C(13'012'92609877019), -11},
-
3061 ammTokens));
-
3062 // Discounted fee payment
-
3063 ammAlice.deposit(carol, USD(100));
-
3064 ammTokens = ammAlice.getLPTokensBalance();
-
3065 BEAST_EXPECT(ammAlice.expectBalances(
-
3066 XRP(13'000),
-
3067 STAmount{USD, UINT64_C(13'112'92609877019), -11},
-
3068 ammTokens));
-
3069 env(pay(carol, bob, USD(100)),
-
3070 path(~USD),
-
3071 sendmax(XRP(110)));
-
3072 env.close();
-
3073 // carol pays 100000 drops in fees
-
3074 // 99900668XRP swapped in for 100USD
-
3075 BEAST_EXPECT(ammAlice.expectBalances(
-
3076 XRPAmount{13'100'000'668},
-
3077 STAmount{USD, UINT64_C(13'012'92609877019), -11},
-
3078 ammTokens));
-
3079 }
-
3080 else
-
3081 {
-
3082 BEAST_EXPECT(
-
3083 env.balance(dan, USD) ==
-
3084 STAmount(USD, UINT64_C(19'490'05672274399), -11));
-
3085 // USD pool gains more in dan's fees.
-
3086 BEAST_EXPECT(ammAlice.expectBalances(
-
3087 XRP(13'000),
-
3088 STAmount{USD, UINT64_C(13'012'92609877023), -11},
-
3089 ammTokens));
-
3090 // Discounted fee payment
-
3091 ammAlice.deposit(carol, USD(100));
-
3092 ammTokens = ammAlice.getLPTokensBalance();
-
3093 BEAST_EXPECT(ammAlice.expectBalances(
-
3094 XRP(13'000),
-
3095 STAmount{USD, UINT64_C(13'112'92609877023), -11},
-
3096 ammTokens));
-
3097 env(pay(carol, bob, USD(100)),
-
3098 path(~USD),
-
3099 sendmax(XRP(110)));
-
3100 env.close();
-
3101 // carol pays 100000 drops in fees
-
3102 // 99900668XRP swapped in for 100USD
-
3103 BEAST_EXPECT(ammAlice.expectBalances(
-
3104 XRPAmount{13'100'000'668},
-
3105 STAmount{USD, UINT64_C(13'012'92609877023), -11},
-
3106 ammTokens));
-
3107 }
-
3108 // Payment with the trading fee
-
3109 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
-
3110 env.close();
-
3111 // alice pays ~1.011USD in fees, which is ~10 times more
-
3112 // than carol's fee
-
3113 // 100.099431529USD swapped in for 100XRP
-
3114 if (!features[fixAMMv1_1])
-
3115 {
-
3116 BEAST_EXPECT(ammAlice.expectBalances(
-
3117 XRPAmount{13'000'000'668},
-
3118 STAmount{USD, UINT64_C(13'114'03663047264), -11},
-
3119 ammTokens));
-
3120 }
-
3121 else
-
3122 {
-
3123 BEAST_EXPECT(ammAlice.expectBalances(
-
3124 XRPAmount{13'000'000'668},
-
3125 STAmount{USD, UINT64_C(13'114'03663047269), -11},
-
3126 ammTokens));
-
3127 }
-
3128 // Auction slot expired, no discounted fee
-
3129 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
-
3130 // clock is parent's based
-
3131 env.close();
-
3132 if (!features[fixAMMv1_1])
-
3133 BEAST_EXPECT(
-
3134 env.balance(carol, USD) ==
-
3135 STAmount(USD, UINT64_C(29'399'00572620545), -11));
-
3136 else
-
3137 BEAST_EXPECT(
-
3138 env.balance(carol, USD) ==
-
3139 STAmount(USD, UINT64_C(29'399'00572620544), -11));
-
3140 ammTokens = ammAlice.getLPTokensBalance();
-
3141 for (int i = 0; i < 10; ++i)
-
3142 {
-
3143 auto const tokens = ammAlice.deposit(carol, USD(100));
-
3144 ammAlice.withdraw(carol, tokens, USD(0));
-
3145 }
-
3146 // carol pays ~9.94USD in fees, which is ~10 times more in
-
3147 // trading fees vs discounted fee.
-
3148 if (!features[fixAMMv1_1])
-
3149 {
-
3150 BEAST_EXPECT(
-
3151 env.balance(carol, USD) ==
-
3152 STAmount(USD, UINT64_C(29'389'06197177128), -11));
-
3153 BEAST_EXPECT(ammAlice.expectBalances(
-
3154 XRPAmount{13'000'000'668},
-
3155 STAmount{USD, UINT64_C(13'123'98038490681), -11},
-
3156 ammTokens));
-
3157 }
-
3158 else
-
3159 {
-
3160 BEAST_EXPECT(
-
3161 env.balance(carol, USD) ==
-
3162 STAmount(USD, UINT64_C(29'389'06197177124), -11));
-
3163 BEAST_EXPECT(ammAlice.expectBalances(
-
3164 XRPAmount{13'000'000'668},
-
3165 STAmount{USD, UINT64_C(13'123'98038490689), -11},
-
3166 ammTokens));
-
3167 }
-
3168 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
-
3169 env.close();
-
3170 // carol pays ~1.008XRP in trading fee, which is
-
3171 // ~10 times more than the discounted fee.
-
3172 // 99.815876XRP is swapped in for 100USD
-
3173 if (!features[fixAMMv1_1])
-
3174 {
-
3175 BEAST_EXPECT(ammAlice.expectBalances(
-
3176 XRPAmount(13'100'824'790),
-
3177 STAmount{USD, UINT64_C(13'023'98038490681), -11},
-
3178 ammTokens));
-
3179 }
-
3180 else
-
3181 {
-
3182 BEAST_EXPECT(ammAlice.expectBalances(
-
3183 XRPAmount(13'100'824'790),
-
3184 STAmount{USD, UINT64_C(13'023'98038490689), -11},
-
3185 ammTokens));
-
3186 }
-
3187 },
-
3188 std::nullopt,
-
3189 1'000,
-
3190 std::nullopt,
-
3191 {features});
-
3192
-
3193 // Bid tiny amount
-
3194 testAMM(
-
3195 [&](AMM& ammAlice, Env& env) {
-
3196 // Bid a tiny amount
-
3197 auto const tiny =
-
3198 Number{STAmount::cMinValue, STAmount::cMinOffset};
-
3199 env(ammAlice.bid(
-
3200 {.account = alice, .bidMin = IOUAmount{tiny}}));
-
3201 // Auction slot purchase price is equal to the tiny amount
-
3202 // since the minSlotPrice is 0 with no trading fee.
-
3203 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
-
3204 // The purchase price is too small to affect the total tokens
-
3205 BEAST_EXPECT(ammAlice.expectBalances(
-
3206 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3207 // Bid the tiny amount
-
3208 env(ammAlice.bid({
-
3209 .account = alice,
-
3210 .bidMin =
-
3211 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
-
3212 }));
-
3213 // Pay slightly higher price
-
3214 BEAST_EXPECT(ammAlice.expectAuctionSlot(
-
3215 0, 0, IOUAmount{tiny * Number{105, -2}}));
-
3216 // The purchase price is still too small to affect the total
-
3217 // tokens
-
3218 BEAST_EXPECT(ammAlice.expectBalances(
-
3219 XRP(10'000), USD(10'000), ammAlice.tokens()));
-
3220 },
-
3221 std::nullopt,
-
3222 0,
-
3223 std::nullopt,
-
3224 {features});
-
3225
-
3226 // Reset auth account
-
3227 testAMM(
-
3228 [&](AMM& ammAlice, Env& env) {
-
3229 env(ammAlice.bid({
-
3230 .account = alice,
-
3231 .bidMin = IOUAmount{100},
-
3232 .authAccounts = {carol},
-
3233 }));
-
3234 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
-
3235 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
-
3236 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
-
3237 Account bob("bob");
-
3238 Account dan("dan");
-
3239 fund(env, {bob, dan}, XRP(1'000));
-
3240 env(ammAlice.bid({
-
3241 .account = alice,
-
3242 .bidMin = IOUAmount{100},
-
3243 .authAccounts = {bob, dan},
-
3244 }));
-
3245 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
-
3246 },
-
3247 std::nullopt,
-
3248 0,
-
3249 std::nullopt,
-
3250 {features});
-
3251
-
3252 // Bid all tokens, still own the slot and trade at a discount
-
3253 {
-
3254 Env env(*this, features);
-
3255 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
-
3256 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
-
3257 auto const lpIssue = amm.lptIssue();
-
3258 env.trust(STAmount{lpIssue, 500}, alice);
-
3259 env.trust(STAmount{lpIssue, 50}, bob);
-
3260 env(pay(gw, alice, STAmount{lpIssue, 500}));
-
3261 env(pay(gw, bob, STAmount{lpIssue, 50}));
-
3262 // Alice doesn't have anymore lp tokens
-
3263 env(amm.bid({.account = alice, .bidMin = 500}));
-
3264 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
-
3265 BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
-
3266 // But trades with the discounted fee since she still owns the slot.
-
3267 // Alice pays 10011 drops in fees
-
3268 env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11)));
-
3269 BEAST_EXPECT(amm.expectBalances(
-
3270 XRPAmount{1'010'010'011},
-
3271 USD(1'000),
-
3272 IOUAmount{1'004'487'562112089, -9}));
-
3273 // Bob pays the full fee ~0.1USD
-
3274 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
-
3275 if (!features[fixAMMv1_1])
-
3276 {
-
3277 BEAST_EXPECT(amm.expectBalances(
-
3278 XRPAmount{1'000'010'011},
-
3279 STAmount{USD, UINT64_C(1'010'10090898081), -11},
-
3280 IOUAmount{1'004'487'562112089, -9}));
-
3281 }
-
3282 else
-
3283 {
-
3284 BEAST_EXPECT(amm.expectBalances(
-
3285 XRPAmount{1'000'010'011},
-
3286 STAmount{USD, UINT64_C(1'010'100908980811), -12},
-
3287 IOUAmount{1'004'487'562112089, -9}));
-
3288 }
-
3289 }
-
3290
-
3291 // preflight tests
-
3292 {
-
3293 Env env(*this, features);
-
3294 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
-
3295 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
-
3296 Json::Value tx = amm.bid({.account = alice, .bidMin = 500});
-
3297
-
3298 {
-
3299 auto jtx = env.jt(tx, seq(1), fee(10));
-
3300 env.app().config().features.erase(featureAMM);
-
3301 PreflightContext pfctx(
-
3302 env.app(),
-
3303 *jtx.stx,
-
3304 env.current()->rules(),
-
3305 tapNONE,
-
3306 env.journal);
-
3307 auto pf = AMMBid::preflight(pfctx);
-
3308 BEAST_EXPECT(pf == temDISABLED);
-
3309 env.app().config().features.insert(featureAMM);
-
3310 }
-
3311
-
3312 {
-
3313 auto jtx = env.jt(tx, seq(1), fee(10));
-
3314 jtx.jv["TxnSignature"] = "deadbeef";
-
3315 jtx.stx = env.ust(jtx);
-
3316 PreflightContext pfctx(
-
3317 env.app(),
-
3318 *jtx.stx,
-
3319 env.current()->rules(),
-
3320 tapNONE,
-
3321 env.journal);
-
3322 auto pf = AMMBid::preflight(pfctx);
-
3323 BEAST_EXPECT(pf != tesSUCCESS);
-
3324 }
-
3325
-
3326 {
-
3327 auto jtx = env.jt(tx, seq(1), fee(10));
-
3328 jtx.jv["Asset2"]["currency"] = "XRP";
-
3329 jtx.jv["Asset2"].removeMember("issuer");
-
3330 jtx.stx = env.ust(jtx);
-
3331 PreflightContext pfctx(
-
3332 env.app(),
-
3333 *jtx.stx,
-
3334 env.current()->rules(),
-
3335 tapNONE,
-
3336 env.journal);
-
3337 auto pf = AMMBid::preflight(pfctx);
-
3338 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
-
3339 }
-
3340 }
-
3341 }
-
3342
-
3343 void
-
3344 testInvalidAMMPayment()
-
3345 {
-
3346 testcase("Invalid AMM Payment");
-
3347 using namespace jtx;
-
3348 using namespace std::chrono;
-
3349 using namespace std::literals::chrono_literals;
-
3350
-
3351 // Can't pay into AMM account.
-
3352 // Can't pay out since there is no keys
-
3353 for (auto const& acct : {gw, alice})
-
3354 {
-
3355 {
-
3356 Env env(*this);
-
3357 fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
-
3358 // XRP balance is below reserve
-
3359 AMM ammAlice(env, acct, XRP(10), USD(10));
-
3360 // Pay below reserve
-
3361 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
-
3362 ter(tecNO_PERMISSION));
-
3363 // Pay above reserve
-
3364 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
-
3365 ter(tecNO_PERMISSION));
-
3366 // Pay IOU
-
3367 env(pay(carol, ammAlice.ammAccount(), USD(10)),
-
3368 ter(tecNO_PERMISSION));
-
3369 }
-
3370 {
-
3371 Env env(*this);
-
3372 fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
-
3373 // XRP balance is above reserve
-
3374 AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
-
3375 // Pay below reserve
-
3376 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
-
3377 ter(tecNO_PERMISSION));
-
3378 // Pay above reserve
-
3379 env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)),
-
3380 ter(tecNO_PERMISSION));
-
3381 }
-
3382 }
-
3383
-
3384 // Can't pay into AMM with escrow.
-
3385 testAMM([&](AMM& ammAlice, Env& env) {
-
3386 env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
-
3387 condition(cb1),
-
3388 finish_time(env.now() + 1s),
-
3389 cancel_time(env.now() + 2s),
-
3390 fee(1'500),
-
3391 ter(tecNO_PERMISSION));
-
3392 });
-
3393
-
3394 // Can't pay into AMM with paychan.
-
3395 testAMM([&](AMM& ammAlice, Env& env) {
-
3396 auto const pk = carol.pk();
-
3397 auto const settleDelay = 100s;
-
3398 NetClock::time_point const cancelAfter =
-
3399 env.current()->info().parentCloseTime + 200s;
-
3400 env(create(
-
3401 carol,
-
3402 ammAlice.ammAccount(),
-
3403 XRP(1'000),
-
3404 settleDelay,
-
3405 pk,
-
3406 cancelAfter),
-
3407 ter(tecNO_PERMISSION));
-
3408 });
-
3409
-
3410 // Can't pay into AMM with checks.
-
3411 testAMM([&](AMM& ammAlice, Env& env) {
-
3412 env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)),
+
3034 env.balance(bob, USD) ==
+
3035 STAmount(USD, UINT64_C(18'999'00572616194), -11));
+
3036 BEAST_EXPECT(
+
3037 env.balance(ed, USD) ==
+
3038 STAmount(USD, UINT64_C(18'999'0057261184), -10));
+
3039 // USD pool is slightly higher because of the fees.
+
3040 BEAST_EXPECT(ammAlice.expectBalances(
+
3041 XRP(13'000),
+
3042 STAmount(USD, UINT64_C(13'002'98282151422), -11),
+
3043 ammTokens));
+
3044 }
+
3045 ammTokens = ammAlice.getLPTokensBalance();
+
3046 // Trade with the fee
+
3047 for (int i = 0; i < 10; ++i)
+
3048 {
+
3049 auto const tokens = ammAlice.deposit(dan, USD(100));
+
3050 ammAlice.withdraw(dan, tokens, USD(0));
+
3051 }
+
3052 // dan pays ~9.94USD, which is ~10 times more in fees than
+
3053 // carol, bob, ed. the discounted fee is 10 times less
+
3054 // than the trading fee.
+
3055 if (!features[fixAMMv1_1])
+
3056 {
+
3057 BEAST_EXPECT(
+
3058 env.balance(dan, USD) ==
+
3059 STAmount(USD, UINT64_C(19'490'056722744), -9));
+
3060 // USD pool gains more in dan's fees.
+
3061 BEAST_EXPECT(ammAlice.expectBalances(
+
3062 XRP(13'000),
+
3063 STAmount{USD, UINT64_C(13'012'92609877019), -11},
+
3064 ammTokens));
+
3065 // Discounted fee payment
+
3066 ammAlice.deposit(carol, USD(100));
+
3067 ammTokens = ammAlice.getLPTokensBalance();
+
3068 BEAST_EXPECT(ammAlice.expectBalances(
+
3069 XRP(13'000),
+
3070 STAmount{USD, UINT64_C(13'112'92609877019), -11},
+
3071 ammTokens));
+
3072 env(pay(carol, bob, USD(100)),
+
3073 path(~USD),
+
3074 sendmax(XRP(110)));
+
3075 env.close();
+
3076 // carol pays 100000 drops in fees
+
3077 // 99900668XRP swapped in for 100USD
+
3078 BEAST_EXPECT(ammAlice.expectBalances(
+
3079 XRPAmount{13'100'000'668},
+
3080 STAmount{USD, UINT64_C(13'012'92609877019), -11},
+
3081 ammTokens));
+
3082 }
+
3083 else
+
3084 {
+
3085 BEAST_EXPECT(
+
3086 env.balance(dan, USD) ==
+
3087 STAmount(USD, UINT64_C(19'490'05672274399), -11));
+
3088 // USD pool gains more in dan's fees.
+
3089 BEAST_EXPECT(ammAlice.expectBalances(
+
3090 XRP(13'000),
+
3091 STAmount{USD, UINT64_C(13'012'92609877023), -11},
+
3092 ammTokens));
+
3093 // Discounted fee payment
+
3094 ammAlice.deposit(carol, USD(100));
+
3095 ammTokens = ammAlice.getLPTokensBalance();
+
3096 BEAST_EXPECT(ammAlice.expectBalances(
+
3097 XRP(13'000),
+
3098 STAmount{USD, UINT64_C(13'112'92609877023), -11},
+
3099 ammTokens));
+
3100 env(pay(carol, bob, USD(100)),
+
3101 path(~USD),
+
3102 sendmax(XRP(110)));
+
3103 env.close();
+
3104 // carol pays 100000 drops in fees
+
3105 // 99900668XRP swapped in for 100USD
+
3106 BEAST_EXPECT(ammAlice.expectBalances(
+
3107 XRPAmount{13'100'000'668},
+
3108 STAmount{USD, UINT64_C(13'012'92609877023), -11},
+
3109 ammTokens));
+
3110 }
+
3111 // Payment with the trading fee
+
3112 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
+
3113 env.close();
+
3114 // alice pays ~1.011USD in fees, which is ~10 times more
+
3115 // than carol's fee
+
3116 // 100.099431529USD swapped in for 100XRP
+
3117 if (!features[fixAMMv1_1])
+
3118 {
+
3119 BEAST_EXPECT(ammAlice.expectBalances(
+
3120 XRPAmount{13'000'000'668},
+
3121 STAmount{USD, UINT64_C(13'114'03663047264), -11},
+
3122 ammTokens));
+
3123 }
+
3124 else
+
3125 {
+
3126 BEAST_EXPECT(ammAlice.expectBalances(
+
3127 XRPAmount{13'000'000'668},
+
3128 STAmount{USD, UINT64_C(13'114'03663047269), -11},
+
3129 ammTokens));
+
3130 }
+
3131 // Auction slot expired, no discounted fee
+
3132 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
+
3133 // clock is parent's based
+
3134 env.close();
+
3135 if (!features[fixAMMv1_1])
+
3136 BEAST_EXPECT(
+
3137 env.balance(carol, USD) ==
+
3138 STAmount(USD, UINT64_C(29'399'00572620545), -11));
+
3139 else
+
3140 BEAST_EXPECT(
+
3141 env.balance(carol, USD) ==
+
3142 STAmount(USD, UINT64_C(29'399'00572620544), -11));
+
3143 ammTokens = ammAlice.getLPTokensBalance();
+
3144 for (int i = 0; i < 10; ++i)
+
3145 {
+
3146 auto const tokens = ammAlice.deposit(carol, USD(100));
+
3147 ammAlice.withdraw(carol, tokens, USD(0));
+
3148 }
+
3149 // carol pays ~9.94USD in fees, which is ~10 times more in
+
3150 // trading fees vs discounted fee.
+
3151 if (!features[fixAMMv1_1])
+
3152 {
+
3153 BEAST_EXPECT(
+
3154 env.balance(carol, USD) ==
+
3155 STAmount(USD, UINT64_C(29'389'06197177128), -11));
+
3156 BEAST_EXPECT(ammAlice.expectBalances(
+
3157 XRPAmount{13'000'000'668},
+
3158 STAmount{USD, UINT64_C(13'123'98038490681), -11},
+
3159 ammTokens));
+
3160 }
+
3161 else
+
3162 {
+
3163 BEAST_EXPECT(
+
3164 env.balance(carol, USD) ==
+
3165 STAmount(USD, UINT64_C(29'389'06197177124), -11));
+
3166 BEAST_EXPECT(ammAlice.expectBalances(
+
3167 XRPAmount{13'000'000'668},
+
3168 STAmount{USD, UINT64_C(13'123'98038490689), -11},
+
3169 ammTokens));
+
3170 }
+
3171 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
+
3172 env.close();
+
3173 // carol pays ~1.008XRP in trading fee, which is
+
3174 // ~10 times more than the discounted fee.
+
3175 // 99.815876XRP is swapped in for 100USD
+
3176 if (!features[fixAMMv1_1])
+
3177 {
+
3178 BEAST_EXPECT(ammAlice.expectBalances(
+
3179 XRPAmount(13'100'824'790),
+
3180 STAmount{USD, UINT64_C(13'023'98038490681), -11},
+
3181 ammTokens));
+
3182 }
+
3183 else
+
3184 {
+
3185 BEAST_EXPECT(ammAlice.expectBalances(
+
3186 XRPAmount(13'100'824'790),
+
3187 STAmount{USD, UINT64_C(13'023'98038490689), -11},
+
3188 ammTokens));
+
3189 }
+
3190 },
+
3191 std::nullopt,
+
3192 1'000,
+
3193 std::nullopt,
+
3194 {features});
+
3195
+
3196 // Bid tiny amount
+
3197 testAMM(
+
3198 [&](AMM& ammAlice, Env& env) {
+
3199 // Bid a tiny amount
+
3200 auto const tiny =
+
3201 Number{STAmount::cMinValue, STAmount::cMinOffset};
+
3202 env(ammAlice.bid(
+
3203 {.account = alice, .bidMin = IOUAmount{tiny}}));
+
3204 // Auction slot purchase price is equal to the tiny amount
+
3205 // since the minSlotPrice is 0 with no trading fee.
+
3206 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
+
3207 // The purchase price is too small to affect the total tokens
+
3208 BEAST_EXPECT(ammAlice.expectBalances(
+
3209 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3210 // Bid the tiny amount
+
3211 env(ammAlice.bid({
+
3212 .account = alice,
+
3213 .bidMin =
+
3214 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
+
3215 }));
+
3216 // Pay slightly higher price
+
3217 BEAST_EXPECT(ammAlice.expectAuctionSlot(
+
3218 0, 0, IOUAmount{tiny * Number{105, -2}}));
+
3219 // The purchase price is still too small to affect the total
+
3220 // tokens
+
3221 BEAST_EXPECT(ammAlice.expectBalances(
+
3222 XRP(10'000), USD(10'000), ammAlice.tokens()));
+
3223 },
+
3224 std::nullopt,
+
3225 0,
+
3226 std::nullopt,
+
3227 {features});
+
3228
+
3229 // Reset auth account
+
3230 testAMM(
+
3231 [&](AMM& ammAlice, Env& env) {
+
3232 env(ammAlice.bid({
+
3233 .account = alice,
+
3234 .bidMin = IOUAmount{100},
+
3235 .authAccounts = {carol},
+
3236 }));
+
3237 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
+
3238 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
+
3239 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
+
3240 Account bob("bob");
+
3241 Account dan("dan");
+
3242 fund(env, {bob, dan}, XRP(1'000));
+
3243 env(ammAlice.bid({
+
3244 .account = alice,
+
3245 .bidMin = IOUAmount{100},
+
3246 .authAccounts = {bob, dan},
+
3247 }));
+
3248 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
+
3249 },
+
3250 std::nullopt,
+
3251 0,
+
3252 std::nullopt,
+
3253 {features});
+
3254
+
3255 // Bid all tokens, still own the slot and trade at a discount
+
3256 {
+
3257 Env env(*this, features);
+
3258 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
+
3259 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
+
3260 auto const lpIssue = amm.lptIssue();
+
3261 env.trust(STAmount{lpIssue, 500}, alice);
+
3262 env.trust(STAmount{lpIssue, 50}, bob);
+
3263 env(pay(gw, alice, STAmount{lpIssue, 500}));
+
3264 env(pay(gw, bob, STAmount{lpIssue, 50}));
+
3265 // Alice doesn't have anymore lp tokens
+
3266 env(amm.bid({.account = alice, .bidMin = 500}));
+
3267 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
+
3268 BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
+
3269 // But trades with the discounted fee since she still owns the slot.
+
3270 // Alice pays 10011 drops in fees
+
3271 env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11)));
+
3272 BEAST_EXPECT(amm.expectBalances(
+
3273 XRPAmount{1'010'010'011},
+
3274 USD(1'000),
+
3275 IOUAmount{1'004'487'562112089, -9}));
+
3276 // Bob pays the full fee ~0.1USD
+
3277 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
+
3278 if (!features[fixAMMv1_1])
+
3279 {
+
3280 BEAST_EXPECT(amm.expectBalances(
+
3281 XRPAmount{1'000'010'011},
+
3282 STAmount{USD, UINT64_C(1'010'10090898081), -11},
+
3283 IOUAmount{1'004'487'562112089, -9}));
+
3284 }
+
3285 else
+
3286 {
+
3287 BEAST_EXPECT(amm.expectBalances(
+
3288 XRPAmount{1'000'010'011},
+
3289 STAmount{USD, UINT64_C(1'010'100908980811), -12},
+
3290 IOUAmount{1'004'487'562112089, -9}));
+
3291 }
+
3292 }
+
3293
+
3294 // preflight tests
+
3295 {
+
3296 Env env(*this, features);
+
3297 auto const baseFee = env.current()->fees().base;
+
3298
+
3299 fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
+
3300 AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
+
3301 Json::Value tx = amm.bid({.account = alice, .bidMin = 500});
+
3302
+
3303 {
+
3304 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3305 env.app().config().features.erase(featureAMM);
+
3306 PreflightContext pfctx(
+
3307 env.app(),
+
3308 *jtx.stx,
+
3309 env.current()->rules(),
+
3310 tapNONE,
+
3311 env.journal);
+
3312 auto pf = AMMBid::preflight(pfctx);
+
3313 BEAST_EXPECT(pf == temDISABLED);
+
3314 env.app().config().features.insert(featureAMM);
+
3315 }
+
3316
+
3317 {
+
3318 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3319 jtx.jv["TxnSignature"] = "deadbeef";
+
3320 jtx.stx = env.ust(jtx);
+
3321 PreflightContext pfctx(
+
3322 env.app(),
+
3323 *jtx.stx,
+
3324 env.current()->rules(),
+
3325 tapNONE,
+
3326 env.journal);
+
3327 auto pf = AMMBid::preflight(pfctx);
+
3328 BEAST_EXPECT(pf != tesSUCCESS);
+
3329 }
+
3330
+
3331 {
+
3332 auto jtx = env.jt(tx, seq(1), fee(baseFee));
+
3333 jtx.jv["Asset2"]["currency"] = "XRP";
+
3334 jtx.jv["Asset2"].removeMember("issuer");
+
3335 jtx.stx = env.ust(jtx);
+
3336 PreflightContext pfctx(
+
3337 env.app(),
+
3338 *jtx.stx,
+
3339 env.current()->rules(),
+
3340 tapNONE,
+
3341 env.journal);
+
3342 auto pf = AMMBid::preflight(pfctx);
+
3343 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
+
3344 }
+
3345 }
+
3346 }
+
3347
+
3348 void
+
3349 testInvalidAMMPayment()
+
3350 {
+
3351 testcase("Invalid AMM Payment");
+
3352 using namespace jtx;
+
3353 using namespace std::chrono;
+
3354 using namespace std::literals::chrono_literals;
+
3355
+
3356 // Can't pay into AMM account.
+
3357 // Can't pay out since there is no keys
+
3358 for (auto const& acct : {gw, alice})
+
3359 {
+
3360 {
+
3361 Env env(*this);
+
3362 fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
+
3363 // XRP balance is below reserve
+
3364 AMM ammAlice(env, acct, XRP(10), USD(10));
+
3365 // Pay below reserve
+
3366 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
+
3367 ter(tecNO_PERMISSION));
+
3368 // Pay above reserve
+
3369 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
+
3370 ter(tecNO_PERMISSION));
+
3371 // Pay IOU
+
3372 env(pay(carol, ammAlice.ammAccount(), USD(10)),
+
3373 ter(tecNO_PERMISSION));
+
3374 }
+
3375 {
+
3376 Env env(*this);
+
3377 fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
+
3378 // XRP balance is above reserve
+
3379 AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
+
3380 // Pay below reserve
+
3381 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
+
3382 ter(tecNO_PERMISSION));
+
3383 // Pay above reserve
+
3384 env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)),
+
3385 ter(tecNO_PERMISSION));
+
3386 }
+
3387 }
+
3388
+
3389 // Can't pay into AMM with escrow.
+
3390 testAMM([&](AMM& ammAlice, Env& env) {
+
3391 auto const baseFee = env.current()->fees().base;
+
3392 env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
+
3393 condition(cb1),
+
3394 finish_time(env.now() + 1s),
+
3395 cancel_time(env.now() + 2s),
+
3396 fee(baseFee * 150),
+
3397 ter(tecNO_PERMISSION));
+
3398 });
+
3399
+
3400 // Can't pay into AMM with paychan.
+
3401 testAMM([&](AMM& ammAlice, Env& env) {
+
3402 auto const pk = carol.pk();
+
3403 auto const settleDelay = 100s;
+
3404 NetClock::time_point const cancelAfter =
+
3405 env.current()->info().parentCloseTime + 200s;
+
3406 env(create(
+
3407 carol,
+
3408 ammAlice.ammAccount(),
+
3409 XRP(1'000),
+
3410 settleDelay,
+
3411 pk,
+
3412 cancelAfter),
3413 ter(tecNO_PERMISSION));
3414 });
3415
-
3416 // Pay amounts close to one side of the pool
-
3417 testAMM(
-
3418 [&](AMM& ammAlice, Env& env) {
-
3419 // Can't consume whole pool
-
3420 env(pay(alice, carol, USD(100)),
-
3421 path(~USD),
-
3422 sendmax(XRP(1'000'000'000)),
-
3423 ter(tecPATH_PARTIAL));
-
3424 env(pay(alice, carol, XRP(100)),
-
3425 path(~XRP),
-
3426 sendmax(USD(1'000'000'000)),
-
3427 ter(tecPATH_PARTIAL));
-
3428 // Overflow
-
3429 env(pay(alice,
-
3430 carol,
-
3431 STAmount{USD, UINT64_C(99'999999999), -9}),
-
3432 path(~USD),
-
3433 sendmax(XRP(1'000'000'000)),
-
3434 ter(tecPATH_PARTIAL));
+
3416 // Can't pay into AMM with checks.
+
3417 testAMM([&](AMM& ammAlice, Env& env) {
+
3418 env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)),
+
3419 ter(tecNO_PERMISSION));
+
3420 });
+
3421
+
3422 // Pay amounts close to one side of the pool
+
3423 testAMM(
+
3424 [&](AMM& ammAlice, Env& env) {
+
3425 // Can't consume whole pool
+
3426 env(pay(alice, carol, USD(100)),
+
3427 path(~USD),
+
3428 sendmax(XRP(1'000'000'000)),
+
3429 ter(tecPATH_PARTIAL));
+
3430 env(pay(alice, carol, XRP(100)),
+
3431 path(~XRP),
+
3432 sendmax(USD(1'000'000'000)),
+
3433 ter(tecPATH_PARTIAL));
+
3434 // Overflow
3435 env(pay(alice,
3436 carol,
-
3437 STAmount{USD, UINT64_C(999'99999999), -8}),
+
3437 STAmount{USD, UINT64_C(99'999999999), -9}),
3438 path(~USD),
3439 sendmax(XRP(1'000'000'000)),
3440 ter(tecPATH_PARTIAL));
-
3441 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'999}),
-
3442 path(~XRP),
-
3443 sendmax(USD(1'000'000'000)),
-
3444 ter(tecPATH_PARTIAL));
-
3445 // Sender doesn't have enough funds
-
3446 env(pay(alice, carol, USD(99.99)),
-
3447 path(~USD),
-
3448 sendmax(XRP(1'000'000'000)),
-
3449 ter(tecPATH_PARTIAL));
-
3450 env(pay(alice, carol, STAmount{xrpIssue(), 99'990'000}),
-
3451 path(~XRP),
-
3452 sendmax(USD(1'000'000'000)),
-
3453 ter(tecPATH_PARTIAL));
-
3454 },
-
3455 {{XRP(100), USD(100)}});
-
3456
-
3457 // Globally frozen
-
3458 testAMM([&](AMM& ammAlice, Env& env) {
-
3459 env(fset(gw, asfGlobalFreeze));
-
3460 env.close();
-
3461 env(pay(alice, carol, USD(1)),
-
3462 path(~USD),
-
3463 txflags(tfPartialPayment | tfNoRippleDirect),
-
3464 sendmax(XRP(10)),
-
3465 ter(tecPATH_DRY));
-
3466 env(pay(alice, carol, XRP(1)),
-
3467 path(~XRP),
-
3468 txflags(tfPartialPayment | tfNoRippleDirect),
-
3469 sendmax(USD(10)),
-
3470 ter(tecPATH_DRY));
-
3471 });
-
3472
-
3473 // Individually frozen AMM
-
3474 testAMM([&](AMM& ammAlice, Env& env) {
-
3475 env(trust(
-
3476 gw,
-
3477 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-
3478 tfSetFreeze));
-
3479 env.close();
-
3480 env(pay(alice, carol, USD(1)),
-
3481 path(~USD),
-
3482 txflags(tfPartialPayment | tfNoRippleDirect),
-
3483 sendmax(XRP(10)),
-
3484 ter(tecPATH_DRY));
-
3485 env(pay(alice, carol, XRP(1)),
-
3486 path(~XRP),
-
3487 txflags(tfPartialPayment | tfNoRippleDirect),
-
3488 sendmax(USD(10)),
-
3489 ter(tecPATH_DRY));
-
3490 });
-
3491
-
3492 // Individually frozen accounts
-
3493 testAMM([&](AMM& ammAlice, Env& env) {
-
3494 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
3495 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
3496 env.close();
-
3497 env(pay(alice, carol, XRP(1)),
-
3498 path(~XRP),
-
3499 sendmax(USD(10)),
-
3500 txflags(tfNoRippleDirect | tfPartialPayment),
-
3501 ter(tecPATH_DRY));
-
3502 });
-
3503 }
-
3504
-
3505 void
-
3506 testBasicPaymentEngine(FeatureBitset features)
-
3507 {
-
3508 testcase("Basic Payment");
-
3509 using namespace jtx;
+
3441 env(pay(alice,
+
3442 carol,
+
3443 STAmount{USD, UINT64_C(999'99999999), -8}),
+
3444 path(~USD),
+
3445 sendmax(XRP(1'000'000'000)),
+
3446 ter(tecPATH_PARTIAL));
+
3447 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'999}),
+
3448 path(~XRP),
+
3449 sendmax(USD(1'000'000'000)),
+
3450 ter(tecPATH_PARTIAL));
+
3451 // Sender doesn't have enough funds
+
3452 env(pay(alice, carol, USD(99.99)),
+
3453 path(~USD),
+
3454 sendmax(XRP(1'000'000'000)),
+
3455 ter(tecPATH_PARTIAL));
+
3456 env(pay(alice, carol, STAmount{xrpIssue(), 99'990'000}),
+
3457 path(~XRP),
+
3458 sendmax(USD(1'000'000'000)),
+
3459 ter(tecPATH_PARTIAL));
+
3460 },
+
3461 {{XRP(100), USD(100)}});
+
3462
+
3463 // Globally frozen
+
3464 testAMM([&](AMM& ammAlice, Env& env) {
+
3465 env(fset(gw, asfGlobalFreeze));
+
3466 env.close();
+
3467 env(pay(alice, carol, USD(1)),
+
3468 path(~USD),
+
3469 txflags(tfPartialPayment | tfNoRippleDirect),
+
3470 sendmax(XRP(10)),
+
3471 ter(tecPATH_DRY));
+
3472 env(pay(alice, carol, XRP(1)),
+
3473 path(~XRP),
+
3474 txflags(tfPartialPayment | tfNoRippleDirect),
+
3475 sendmax(USD(10)),
+
3476 ter(tecPATH_DRY));
+
3477 });
+
3478
+
3479 // Individually frozen AMM
+
3480 testAMM([&](AMM& ammAlice, Env& env) {
+
3481 env(trust(
+
3482 gw,
+
3483 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+
3484 tfSetFreeze));
+
3485 env.close();
+
3486 env(pay(alice, carol, USD(1)),
+
3487 path(~USD),
+
3488 txflags(tfPartialPayment | tfNoRippleDirect),
+
3489 sendmax(XRP(10)),
+
3490 ter(tecPATH_DRY));
+
3491 env(pay(alice, carol, XRP(1)),
+
3492 path(~XRP),
+
3493 txflags(tfPartialPayment | tfNoRippleDirect),
+
3494 sendmax(USD(10)),
+
3495 ter(tecPATH_DRY));
+
3496 });
+
3497
+
3498 // Individually frozen accounts
+
3499 testAMM([&](AMM& ammAlice, Env& env) {
+
3500 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
3501 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
3502 env.close();
+
3503 env(pay(alice, carol, XRP(1)),
+
3504 path(~XRP),
+
3505 sendmax(USD(10)),
+
3506 txflags(tfNoRippleDirect | tfPartialPayment),
+
3507 ter(tecPATH_DRY));
+
3508 });
+
3509 }
3510
-
3511 // Payment 100USD for 100XRP.
-
3512 // Force one path with tfNoRippleDirect.
-
3513 testAMM(
-
3514 [&](AMM& ammAlice, Env& env) {
-
3515 env.fund(jtx::XRP(30'000), bob);
-
3516 env.close();
-
3517 env(pay(bob, carol, USD(100)),
-
3518 path(~USD),
-
3519 sendmax(XRP(100)),
-
3520 txflags(tfNoRippleDirect));
-
3521 env.close();
-
3522 BEAST_EXPECT(ammAlice.expectBalances(
-
3523 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3524 // Initial balance 30,000 + 100
-
3525 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3526 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3527 BEAST_EXPECT(expectLedgerEntryRoot(
-
3528 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3529 },
-
3530 {{XRP(10'000), USD(10'100)}},
-
3531 0,
-
3532 std::nullopt,
-
3533 {features});
-
3534
-
3535 // Payment 100USD for 100XRP, use default path.
-
3536 testAMM(
-
3537 [&](AMM& ammAlice, Env& env) {
-
3538 env.fund(jtx::XRP(30'000), bob);
-
3539 env.close();
-
3540 env(pay(bob, carol, USD(100)), sendmax(XRP(100)));
-
3541 env.close();
-
3542 BEAST_EXPECT(ammAlice.expectBalances(
-
3543 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3544 // Initial balance 30,000 + 100
-
3545 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3546 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3547 BEAST_EXPECT(expectLedgerEntryRoot(
-
3548 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3549 },
-
3550 {{XRP(10'000), USD(10'100)}},
-
3551 0,
-
3552 std::nullopt,
-
3553 {features});
-
3554
-
3555 // This payment is identical to above. While it has
-
3556 // both default path and path, activeStrands has one path.
-
3557 testAMM(
-
3558 [&](AMM& ammAlice, Env& env) {
-
3559 env.fund(jtx::XRP(30'000), bob);
-
3560 env.close();
-
3561 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
-
3562 env.close();
-
3563 BEAST_EXPECT(ammAlice.expectBalances(
-
3564 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3565 // Initial balance 30,000 + 100
-
3566 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
3567 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
-
3568 BEAST_EXPECT(expectLedgerEntryRoot(
-
3569 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3570 },
-
3571 {{XRP(10'000), USD(10'100)}},
-
3572 0,
-
3573 std::nullopt,
-
3574 {features});
-
3575
-
3576 // Payment with limitQuality set.
-
3577 testAMM(
-
3578 [&](AMM& ammAlice, Env& env) {
-
3579 env.fund(jtx::XRP(30'000), bob);
-
3580 env.close();
-
3581 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
-
3582 // would have been sent has it not been for limitQuality.
-
3583 env(pay(bob, carol, USD(100)),
-
3584 path(~USD),
-
3585 sendmax(XRP(100)),
-
3586 txflags(
-
3587 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
3588 env.close();
-
3589 BEAST_EXPECT(ammAlice.expectBalances(
-
3590 XRP(10'010), USD(10'000), ammAlice.tokens()));
-
3591 // Initial balance 30,000 + 10(limited by limitQuality)
-
3592 BEAST_EXPECT(expectLine(env, carol, USD(30'010)));
-
3593 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
-
3594 // fee)
-
3595 BEAST_EXPECT(expectLedgerEntryRoot(
-
3596 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
-
3597
-
3598 // Fails because of limitQuality. Would have sent
-
3599 // ~98.91USD/110XRP has it not been for limitQuality.
-
3600 env(pay(bob, carol, USD(100)),
-
3601 path(~USD),
-
3602 sendmax(XRP(100)),
-
3603 txflags(
-
3604 tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
-
3605 ter(tecPATH_DRY));
-
3606 env.close();
-
3607 },
-
3608 {{XRP(10'000), USD(10'010)}},
-
3609 0,
-
3610 std::nullopt,
-
3611 {features});
-
3612
-
3613 // Payment with limitQuality and transfer fee set.
-
3614 testAMM(
-
3615 [&](AMM& ammAlice, Env& env) {
-
3616 env(rate(gw, 1.1));
-
3617 env.close();
-
3618 env.fund(jtx::XRP(30'000), bob);
-
3619 env.close();
-
3620 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
-
3621 // would have been sent has it not been for limitQuality and
-
3622 // the transfer fee.
-
3623 env(pay(bob, carol, USD(100)),
-
3624 path(~USD),
-
3625 sendmax(XRP(110)),
-
3626 txflags(
-
3627 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
-
3628 env.close();
-
3629 BEAST_EXPECT(ammAlice.expectBalances(
-
3630 XRP(10'010), USD(10'000), ammAlice.tokens()));
-
3631 // 10USD - 10% transfer fee
-
3632 BEAST_EXPECT(expectLine(
-
3633 env,
-
3634 carol,
-
3635 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
-
3636 BEAST_EXPECT(expectLedgerEntryRoot(
-
3637 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
-
3638 },
-
3639 {{XRP(10'000), USD(10'010)}},
-
3640 0,
-
3641 std::nullopt,
-
3642 {features});
-
3643
-
3644 // Fail when partial payment is not set.
-
3645 testAMM(
-
3646 [&](AMM& ammAlice, Env& env) {
-
3647 env.fund(jtx::XRP(30'000), bob);
-
3648 env.close();
-
3649 env(pay(bob, carol, USD(100)),
-
3650 path(~USD),
-
3651 sendmax(XRP(100)),
-
3652 txflags(tfNoRippleDirect),
-
3653 ter(tecPATH_PARTIAL));
-
3654 },
-
3655 {{XRP(10'000), USD(10'000)}},
-
3656 0,
-
3657 std::nullopt,
-
3658 {features});
-
3659
-
3660 // Non-default path (with AMM) has a better quality than default path.
-
3661 // The max possible liquidity is taken out of non-default
-
3662 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
-
3663 // is taken from the offer.
-
3664 {
-
3665 Env env(*this, features);
-
3666 fund(
-
3667 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
-
3668 env.close();
-
3669 env.fund(XRP(1'000), bob);
-
3670 env.close();
-
3671 auto ammEUR_XRP = AMM(env, alice, XRP(10'000), EUR(10'000));
-
3672 auto ammUSD_EUR = AMM(env, alice, EUR(10'000), USD(10'000));
-
3673 env(offer(alice, XRP(101), USD(100)), txflags(tfPassive));
+
3511 void
+
3512 testBasicPaymentEngine(FeatureBitset features)
+
3513 {
+
3514 testcase("Basic Payment");
+
3515 using namespace jtx;
+
3516
+
3517 // Payment 100USD for 100XRP.
+
3518 // Force one path with tfNoRippleDirect.
+
3519 testAMM(
+
3520 [&](AMM& ammAlice, Env& env) {
+
3521 env.fund(jtx::XRP(30'000), bob);
+
3522 env.close();
+
3523 env(pay(bob, carol, USD(100)),
+
3524 path(~USD),
+
3525 sendmax(XRP(100)),
+
3526 txflags(tfNoRippleDirect));
+
3527 env.close();
+
3528 BEAST_EXPECT(ammAlice.expectBalances(
+
3529 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3530 // Initial balance 30,000 + 100
+
3531 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3532 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3533 BEAST_EXPECT(expectLedgerEntryRoot(
+
3534 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3535 },
+
3536 {{XRP(10'000), USD(10'100)}},
+
3537 0,
+
3538 std::nullopt,
+
3539 {features});
+
3540
+
3541 // Payment 100USD for 100XRP, use default path.
+
3542 testAMM(
+
3543 [&](AMM& ammAlice, Env& env) {
+
3544 env.fund(jtx::XRP(30'000), bob);
+
3545 env.close();
+
3546 env(pay(bob, carol, USD(100)), sendmax(XRP(100)));
+
3547 env.close();
+
3548 BEAST_EXPECT(ammAlice.expectBalances(
+
3549 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3550 // Initial balance 30,000 + 100
+
3551 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3552 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3553 BEAST_EXPECT(expectLedgerEntryRoot(
+
3554 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3555 },
+
3556 {{XRP(10'000), USD(10'100)}},
+
3557 0,
+
3558 std::nullopt,
+
3559 {features});
+
3560
+
3561 // This payment is identical to above. While it has
+
3562 // both default path and path, activeStrands has one path.
+
3563 testAMM(
+
3564 [&](AMM& ammAlice, Env& env) {
+
3565 env.fund(jtx::XRP(30'000), bob);
+
3566 env.close();
+
3567 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
+
3568 env.close();
+
3569 BEAST_EXPECT(ammAlice.expectBalances(
+
3570 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3571 // Initial balance 30,000 + 100
+
3572 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
3573 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
+
3574 BEAST_EXPECT(expectLedgerEntryRoot(
+
3575 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3576 },
+
3577 {{XRP(10'000), USD(10'100)}},
+
3578 0,
+
3579 std::nullopt,
+
3580 {features});
+
3581
+
3582 // Payment with limitQuality set.
+
3583 testAMM(
+
3584 [&](AMM& ammAlice, Env& env) {
+
3585 env.fund(jtx::XRP(30'000), bob);
+
3586 env.close();
+
3587 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
+
3588 // would have been sent has it not been for limitQuality.
+
3589 env(pay(bob, carol, USD(100)),
+
3590 path(~USD),
+
3591 sendmax(XRP(100)),
+
3592 txflags(
+
3593 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3594 env.close();
+
3595 BEAST_EXPECT(ammAlice.expectBalances(
+
3596 XRP(10'010), USD(10'000), ammAlice.tokens()));
+
3597 // Initial balance 30,000 + 10(limited by limitQuality)
+
3598 BEAST_EXPECT(expectLine(env, carol, USD(30'010)));
+
3599 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
+
3600 // fee)
+
3601 BEAST_EXPECT(expectLedgerEntryRoot(
+
3602 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
+
3603
+
3604 // Fails because of limitQuality. Would have sent
+
3605 // ~98.91USD/110XRP has it not been for limitQuality.
+
3606 env(pay(bob, carol, USD(100)),
+
3607 path(~USD),
+
3608 sendmax(XRP(100)),
+
3609 txflags(
+
3610 tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
+
3611 ter(tecPATH_DRY));
+
3612 env.close();
+
3613 },
+
3614 {{XRP(10'000), USD(10'010)}},
+
3615 0,
+
3616 std::nullopt,
+
3617 {features});
+
3618
+
3619 // Payment with limitQuality and transfer fee set.
+
3620 testAMM(
+
3621 [&](AMM& ammAlice, Env& env) {
+
3622 env(rate(gw, 1.1));
+
3623 env.close();
+
3624 env.fund(jtx::XRP(30'000), bob);
+
3625 env.close();
+
3626 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
+
3627 // would have been sent has it not been for limitQuality and
+
3628 // the transfer fee.
+
3629 env(pay(bob, carol, USD(100)),
+
3630 path(~USD),
+
3631 sendmax(XRP(110)),
+
3632 txflags(
+
3633 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
+
3634 env.close();
+
3635 BEAST_EXPECT(ammAlice.expectBalances(
+
3636 XRP(10'010), USD(10'000), ammAlice.tokens()));
+
3637 // 10USD - 10% transfer fee
+
3638 BEAST_EXPECT(expectLine(
+
3639 env,
+
3640 carol,
+
3641 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
+
3642 BEAST_EXPECT(expectLedgerEntryRoot(
+
3643 env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
+
3644 },
+
3645 {{XRP(10'000), USD(10'010)}},
+
3646 0,
+
3647 std::nullopt,
+
3648 {features});
+
3649
+
3650 // Fail when partial payment is not set.
+
3651 testAMM(
+
3652 [&](AMM& ammAlice, Env& env) {
+
3653 env.fund(jtx::XRP(30'000), bob);
+
3654 env.close();
+
3655 env(pay(bob, carol, USD(100)),
+
3656 path(~USD),
+
3657 sendmax(XRP(100)),
+
3658 txflags(tfNoRippleDirect),
+
3659 ter(tecPATH_PARTIAL));
+
3660 },
+
3661 {{XRP(10'000), USD(10'000)}},
+
3662 0,
+
3663 std::nullopt,
+
3664 {features});
+
3665
+
3666 // Non-default path (with AMM) has a better quality than default path.
+
3667 // The max possible liquidity is taken out of non-default
+
3668 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
+
3669 // is taken from the offer.
+
3670 {
+
3671 Env env(*this, features);
+
3672 fund(
+
3673 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3674 env.close();
-
3675 env(pay(bob, carol, USD(100)),
-
3676 path(~EUR, ~USD),
-
3677 sendmax(XRP(102)),
-
3678 txflags(tfPartialPayment));
-
3679 env.close();
-
3680 BEAST_EXPECT(ammEUR_XRP.expectBalances(
-
3681 XRPAmount(10'030'082'730),
-
3682 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
-
3683 ammEUR_XRP.tokens()));
-
3684 if (!features[fixAMMv1_1])
-
3685 {
-
3686 BEAST_EXPECT(ammUSD_EUR.expectBalances(
-
3687 STAmount(USD, UINT64_C(9'970'097277662122), -12),
-
3688 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
-
3689 ammUSD_EUR.tokens()));
-
3690
-
3691 // fixReducedOffersV2 changes the expected results slightly.
-
3692 Amounts const expectedAmounts =
-
3693 env.closed()->rules().enabled(fixReducedOffersV2)
-
3694 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233787816), -14)}
-
3695 : Amounts{
-
3696 XRPAmount(30'201'749),
-
3697 STAmount(USD, UINT64_C(29'90272233787818), -14)};
-
3698
-
3699 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
-
3700 }
-
3701 else
-
3702 {
-
3703 BEAST_EXPECT(ammUSD_EUR.expectBalances(
-
3704 STAmount(USD, UINT64_C(9'970'097277662172), -12),
-
3705 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
-
3706 ammUSD_EUR.tokens()));
-
3707
-
3708 // fixReducedOffersV2 changes the expected results slightly.
-
3709 Amounts const expectedAmounts =
-
3710 env.closed()->rules().enabled(fixReducedOffersV2)
-
3711 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233782839), -14)}
-
3712 : Amounts{
-
3713 XRPAmount(30'201'749),
-
3714 STAmount(USD, UINT64_C(29'90272233782840), -14)};
-
3715
-
3716 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
-
3717 }
-
3718 // Initial 30,000 + 100
-
3719 BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100}));
-
3720 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
-
3721 BEAST_EXPECT(expectLedgerEntryRoot(
-
3722 env,
-
3723 bob,
-
3724 XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} -
-
3725 txfee(env, 1)));
-
3726 }
-
3727
-
3728 // Default path (with AMM) has a better quality than a non-default path.
-
3729 // The max possible liquidity is taken out of default
-
3730 // path ~49XRP/49USD. The rest is taken from the offer.
-
3731 testAMM(
-
3732 [&](AMM& ammAlice, Env& env) {
-
3733 env.fund(XRP(1'000), bob);
-
3734 env.close();
-
3735 env.trust(EUR(2'000), alice);
-
3736 env.close();
-
3737 env(pay(gw, alice, EUR(1'000)));
-
3738 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
-
3739 env.close();
-
3740 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
-
3741 env.close();
-
3742 env(pay(bob, carol, USD(100)),
-
3743 path(~EUR, ~USD),
-
3744 sendmax(XRP(102)),
-
3745 txflags(tfPartialPayment));
-
3746 env.close();
-
3747 BEAST_EXPECT(ammAlice.expectBalances(
-
3748 XRPAmount(10'050'238'637),
-
3749 STAmount(USD, UINT64_C(9'950'01249687578), -11),
-
3750 ammAlice.tokens()));
-
3751 BEAST_EXPECT(expectOffers(
-
3752 env,
-
3753 alice,
-
3754 2,
-
3755 {{Amounts{
-
3756 XRPAmount(50'487'378),
-
3757 STAmount(EUR, UINT64_C(49'98750312422), -11)},
-
3758 Amounts{
-
3759 STAmount(EUR, UINT64_C(49'98750312422), -11),
-
3760 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
-
3761 // Initial 30,000 + 99.99999999999
-
3762 BEAST_EXPECT(expectLine(
-
3763 env,
-
3764 carol,
-
3765 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
-
3766 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
-
3767 // fee)
-
3768 BEAST_EXPECT(expectLedgerEntryRoot(
+
3675 env.fund(XRP(1'000), bob);
+
3676 env.close();
+
3677 auto ammEUR_XRP = AMM(env, alice, XRP(10'000), EUR(10'000));
+
3678 auto ammUSD_EUR = AMM(env, alice, EUR(10'000), USD(10'000));
+
3679 env(offer(alice, XRP(101), USD(100)), txflags(tfPassive));
+
3680 env.close();
+
3681 env(pay(bob, carol, USD(100)),
+
3682 path(~EUR, ~USD),
+
3683 sendmax(XRP(102)),
+
3684 txflags(tfPartialPayment));
+
3685 env.close();
+
3686 BEAST_EXPECT(ammEUR_XRP.expectBalances(
+
3687 XRPAmount(10'030'082'730),
+
3688 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
+
3689 ammEUR_XRP.tokens()));
+
3690 if (!features[fixAMMv1_1])
+
3691 {
+
3692 BEAST_EXPECT(ammUSD_EUR.expectBalances(
+
3693 STAmount(USD, UINT64_C(9'970'097277662122), -12),
+
3694 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
+
3695 ammUSD_EUR.tokens()));
+
3696
+
3697 // fixReducedOffersV2 changes the expected results slightly.
+
3698 Amounts const expectedAmounts =
+
3699 env.closed()->rules().enabled(fixReducedOffersV2)
+
3700 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233787816), -14)}
+
3701 : Amounts{
+
3702 XRPAmount(30'201'749),
+
3703 STAmount(USD, UINT64_C(29'90272233787818), -14)};
+
3704
+
3705 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
+
3706 }
+
3707 else
+
3708 {
+
3709 BEAST_EXPECT(ammUSD_EUR.expectBalances(
+
3710 STAmount(USD, UINT64_C(9'970'097277662172), -12),
+
3711 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
+
3712 ammUSD_EUR.tokens()));
+
3713
+
3714 // fixReducedOffersV2 changes the expected results slightly.
+
3715 Amounts const expectedAmounts =
+
3716 env.closed()->rules().enabled(fixReducedOffersV2)
+
3717 ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233782839), -14)}
+
3718 : Amounts{
+
3719 XRPAmount(30'201'749),
+
3720 STAmount(USD, UINT64_C(29'90272233782840), -14)};
+
3721
+
3722 BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}}));
+
3723 }
+
3724 // Initial 30,000 + 100
+
3725 BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100}));
+
3726 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
+
3727 BEAST_EXPECT(expectLedgerEntryRoot(
+
3728 env,
+
3729 bob,
+
3730 XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} -
+
3731 txfee(env, 1)));
+
3732 }
+
3733
+
3734 // Default path (with AMM) has a better quality than a non-default path.
+
3735 // The max possible liquidity is taken out of default
+
3736 // path ~49XRP/49USD. The rest is taken from the offer.
+
3737 testAMM(
+
3738 [&](AMM& ammAlice, Env& env) {
+
3739 env.fund(XRP(1'000), bob);
+
3740 env.close();
+
3741 env.trust(EUR(2'000), alice);
+
3742 env.close();
+
3743 env(pay(gw, alice, EUR(1'000)));
+
3744 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
+
3745 env.close();
+
3746 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
+
3747 env.close();
+
3748 env(pay(bob, carol, USD(100)),
+
3749 path(~EUR, ~USD),
+
3750 sendmax(XRP(102)),
+
3751 txflags(tfPartialPayment));
+
3752 env.close();
+
3753 BEAST_EXPECT(ammAlice.expectBalances(
+
3754 XRPAmount(10'050'238'637),
+
3755 STAmount(USD, UINT64_C(9'950'01249687578), -11),
+
3756 ammAlice.tokens()));
+
3757 BEAST_EXPECT(expectOffers(
+
3758 env,
+
3759 alice,
+
3760 2,
+
3761 {{Amounts{
+
3762 XRPAmount(50'487'378),
+
3763 STAmount(EUR, UINT64_C(49'98750312422), -11)},
+
3764 Amounts{
+
3765 STAmount(EUR, UINT64_C(49'98750312422), -11),
+
3766 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
+
3767 // Initial 30,000 + 99.99999999999
+
3768 BEAST_EXPECT(expectLine(
3769 env,
-
3770 bob,
-
3771 XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} -
-
3772 txfee(env, 1)));
-
3773 },
-
3774 std::nullopt,
-
3775 0,
-
3776 std::nullopt,
-
3777 {features});
-
3778
-
3779 // Default path with AMM and Order Book offer. AMM is consumed first,
-
3780 // remaining amount is consumed by the offer.
-
3781 testAMM(
-
3782 [&](AMM& ammAlice, Env& env) {
-
3783 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
-
3784 env.close();
-
3785 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
-
3786 env.close();
-
3787 env(pay(alice, carol, USD(200)),
-
3788 sendmax(XRP(200)),
-
3789 txflags(tfPartialPayment));
+
3770 carol,
+
3771 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
+
3772 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
+
3773 // fee)
+
3774 BEAST_EXPECT(expectLedgerEntryRoot(
+
3775 env,
+
3776 bob,
+
3777 XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} -
+
3778 txfee(env, 1)));
+
3779 },
+
3780 std::nullopt,
+
3781 0,
+
3782 std::nullopt,
+
3783 {features});
+
3784
+
3785 // Default path with AMM and Order Book offer. AMM is consumed first,
+
3786 // remaining amount is consumed by the offer.
+
3787 testAMM(
+
3788 [&](AMM& ammAlice, Env& env) {
+
3789 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3790 env.close();
-
3791 if (!features[fixAMMv1_1])
-
3792 {
-
3793 BEAST_EXPECT(ammAlice.expectBalances(
-
3794 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3795 // Initial 30,000 + 200
-
3796 BEAST_EXPECT(expectLine(env, carol, USD(30'200)));
-
3797 }
-
3798 else
-
3799 {
-
3800 BEAST_EXPECT(ammAlice.expectBalances(
-
3801 XRP(10'100),
-
3802 STAmount(USD, UINT64_C(10'000'00000000001), -11),
-
3803 ammAlice.tokens()));
-
3804 BEAST_EXPECT(expectLine(
-
3805 env,
-
3806 carol,
-
3807 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
-
3808 }
-
3809 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
-
3810 // - 100(offer) - 10(tx fee) - one reserve
-
3811 BEAST_EXPECT(expectLedgerEntryRoot(
-
3812 env,
-
3813 alice,
-
3814 XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) -
-
3815 ammCrtFee(env) - txfee(env, 1)));
-
3816 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3817 },
-
3818 {{XRP(10'000), USD(10'100)}},
-
3819 0,
-
3820 std::nullopt,
-
3821 {features});
-
3822
-
3823 // Default path with AMM and Order Book offer.
-
3824 // Order Book offer is consumed first.
-
3825 // Remaining amount is consumed by AMM.
-
3826 {
-
3827 Env env(*this, features);
-
3828 fund(env, gw, {alice, bob, carol}, XRP(20'000), {USD(2'000)});
-
3829 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
-
3830 AMM ammAlice(env, alice, XRP(1'000), USD(1'050));
-
3831 env(pay(alice, carol, USD(200)),
-
3832 sendmax(XRP(200)),
-
3833 txflags(tfPartialPayment));
-
3834 BEAST_EXPECT(ammAlice.expectBalances(
-
3835 XRP(1'050), USD(1'000), ammAlice.tokens()));
-
3836 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
-
3837 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3838 }
-
3839
-
3840 // Offer crossing XRP/IOU
-
3841 testAMM(
-
3842 [&](AMM& ammAlice, Env& env) {
-
3843 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
-
3844 env.close();
-
3845 env(offer(bob, USD(100), XRP(100)));
-
3846 env.close();
-
3847 BEAST_EXPECT(ammAlice.expectBalances(
-
3848 XRP(10'100), USD(10'000), ammAlice.tokens()));
-
3849 // Initial 1,000 + 100
-
3850 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
-
3851 // Initial 30,000 - 100(offer) - 10(tx fee)
-
3852 BEAST_EXPECT(expectLedgerEntryRoot(
-
3853 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
-
3854 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3855 },
-
3856 {{XRP(10'000), USD(10'100)}},
-
3857 0,
-
3858 std::nullopt,
-
3859 {features});
-
3860
-
3861 // Offer crossing IOU/IOU and transfer rate
-
3862 // Single path AMM offer
-
3863 testAMM(
-
3864 [&](AMM& ammAlice, Env& env) {
-
3865 env(rate(gw, 1.25));
-
3866 env.close();
-
3867 // This offer succeeds to cross pre- and post-amendment
-
3868 // because the strand's out amount is small enough to match
-
3869 // limitQuality value and limitOut() function in StrandFlow
-
3870 // doesn't require an adjustment to out value.
-
3871 env(offer(carol, EUR(100), GBP(100)));
-
3872 env.close();
-
3873 // No transfer fee
-
3874 BEAST_EXPECT(ammAlice.expectBalances(
-
3875 GBP(1'100), EUR(1'000), ammAlice.tokens()));
-
3876 // Initial 30,000 - 100(offer) - 25% transfer fee
-
3877 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
-
3878 // Initial 30,000 + 100(offer)
-
3879 BEAST_EXPECT(expectLine(env, carol, EUR(30'100)));
-
3880 BEAST_EXPECT(expectOffers(env, bob, 0));
-
3881 },
-
3882 {{GBP(1'000), EUR(1'100)}},
-
3883 0,
-
3884 std::nullopt,
-
3885 {features});
-
3886 // Single-path AMM offer
-
3887 testAMM(
-
3888 [&](AMM& amm, Env& env) {
-
3889 env(rate(gw, 1.001));
-
3890 env.close();
-
3891 env(offer(carol, XRP(100), USD(55)));
-
3892 env.close();
-
3893 if (!features[fixAMMv1_1])
-
3894 {
-
3895 // Pre-amendment the transfer fee is not taken into
-
3896 // account when calculating the limit out based on
-
3897 // limitQuality. Carol pays 0.1% on the takerGets, which
-
3898 // lowers the overall quality. AMM offer is generated based
-
3899 // on higher limit out, which generates a larger offer
-
3900 // with lower quality. Consequently, the offer fails
-
3901 // to cross.
-
3902 BEAST_EXPECT(
-
3903 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
-
3904 BEAST_EXPECT(expectOffers(
-
3905 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
-
3906 }
-
3907 else
-
3908 {
-
3909 // Post-amendment the transfer fee is taken into account
-
3910 // when calculating the limit out based on limitQuality.
-
3911 // This increases the limitQuality and decreases
-
3912 // the limit out. Consequently, AMM offer size is decreased,
-
3913 // and the quality is increased, matching the overall
-
3914 // quality.
-
3915 // AMM offer ~50USD/91XRP
-
3916 BEAST_EXPECT(amm.expectBalances(
-
3917 XRPAmount(909'090'909),
-
3918 STAmount{USD, UINT64_C(550'000000055), -9},
-
3919 amm.tokens()));
-
3920 // Offer ~91XRP/49.99USD
-
3921 BEAST_EXPECT(expectOffers(
-
3922 env,
-
3923 carol,
-
3924 1,
-
3925 {{Amounts{
-
3926 XRPAmount{9'090'909},
-
3927 STAmount{USD, 4'99999995, -8}}}}));
-
3928 // Carol pays 0.1% fee on ~50USD =~ 0.05USD
-
3929 BEAST_EXPECT(
-
3930 env.balance(carol, USD) ==
-
3931 STAmount(USD, UINT64_C(29'949'94999999494), -11));
-
3932 }
-
3933 },
-
3934 {{XRP(1'000), USD(500)}},
-
3935 0,
-
3936 std::nullopt,
-
3937 {features});
-
3938 testAMM(
-
3939 [&](AMM& amm, Env& env) {
-
3940 env(rate(gw, 1.001));
-
3941 env.close();
-
3942 env(offer(carol, XRP(10), USD(5.5)));
-
3943 env.close();
-
3944 if (!features[fixAMMv1_1])
-
3945 {
-
3946 BEAST_EXPECT(amm.expectBalances(
-
3947 XRP(990),
-
3948 STAmount{USD, UINT64_C(505'050505050505), -12},
-
3949 amm.tokens()));
-
3950 BEAST_EXPECT(expectOffers(env, carol, 0));
-
3951 }
-
3952 else
-
3953 {
-
3954 BEAST_EXPECT(amm.expectBalances(
-
3955 XRP(990),
-
3956 STAmount{USD, UINT64_C(505'0505050505051), -13},
-
3957 amm.tokens()));
-
3958 BEAST_EXPECT(expectOffers(env, carol, 0));
-
3959 }
-
3960 },
-
3961 {{XRP(1'000), USD(500)}},
-
3962 0,
-
3963 std::nullopt,
-
3964 {features});
-
3965 // Multi-path AMM offer
-
3966 testAMM(
-
3967 [&](AMM& ammAlice, Env& env) {
-
3968 Account const ed("ed");
-
3969 fund(
-
3970 env,
-
3971 gw,
-
3972 {bob, ed},
-
3973 XRP(30'000),
-
3974 {GBP(2'000), EUR(2'000)},
-
3975 Fund::Acct);
-
3976 env(rate(gw, 1.25));
-
3977 env.close();
-
3978 // The auto-bridge is worse quality than AMM, is not consumed
-
3979 // first and initially forces multi-path AMM offer generation.
-
3980 // Multi-path AMM offers are consumed until their quality
-
3981 // is less than the auto-bridge offers quality. Auto-bridge
-
3982 // offers are consumed afterward. Then the behavior is
-
3983 // different pre-amendment and post-amendment.
-
3984 env(offer(bob, GBP(10), XRP(10)), txflags(tfPassive));
-
3985 env(offer(ed, XRP(10), EUR(10)), txflags(tfPassive));
+
3791 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
+
3792 env.close();
+
3793 env(pay(alice, carol, USD(200)),
+
3794 sendmax(XRP(200)),
+
3795 txflags(tfPartialPayment));
+
3796 env.close();
+
3797 if (!features[fixAMMv1_1])
+
3798 {
+
3799 BEAST_EXPECT(ammAlice.expectBalances(
+
3800 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3801 // Initial 30,000 + 200
+
3802 BEAST_EXPECT(expectLine(env, carol, USD(30'200)));
+
3803 }
+
3804 else
+
3805 {
+
3806 BEAST_EXPECT(ammAlice.expectBalances(
+
3807 XRP(10'100),
+
3808 STAmount(USD, UINT64_C(10'000'00000000001), -11),
+
3809 ammAlice.tokens()));
+
3810 BEAST_EXPECT(expectLine(
+
3811 env,
+
3812 carol,
+
3813 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
+
3814 }
+
3815 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
+
3816 // - 100(offer) - 10(tx fee) - one reserve
+
3817 BEAST_EXPECT(expectLedgerEntryRoot(
+
3818 env,
+
3819 alice,
+
3820 XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) -
+
3821 ammCrtFee(env) - txfee(env, 1)));
+
3822 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3823 },
+
3824 {{XRP(10'000), USD(10'100)}},
+
3825 0,
+
3826 std::nullopt,
+
3827 {features});
+
3828
+
3829 // Default path with AMM and Order Book offer.
+
3830 // Order Book offer is consumed first.
+
3831 // Remaining amount is consumed by AMM.
+
3832 {
+
3833 Env env(*this, features);
+
3834 fund(env, gw, {alice, bob, carol}, XRP(20'000), {USD(2'000)});
+
3835 env.close();
+
3836 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
+
3837 env.close();
+
3838 AMM ammAlice(env, alice, XRP(1'000), USD(1'050));
+
3839 env(pay(alice, carol, USD(200)),
+
3840 sendmax(XRP(200)),
+
3841 txflags(tfPartialPayment));
+
3842 env.close();
+
3843 BEAST_EXPECT(ammAlice.expectBalances(
+
3844 XRP(1'050), USD(1'000), ammAlice.tokens()));
+
3845 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
+
3846 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3847 }
+
3848
+
3849 // Offer crossing XRP/IOU
+
3850 testAMM(
+
3851 [&](AMM& ammAlice, Env& env) {
+
3852 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
+
3853 env.close();
+
3854 env(offer(bob, USD(100), XRP(100)));
+
3855 env.close();
+
3856 BEAST_EXPECT(ammAlice.expectBalances(
+
3857 XRP(10'100), USD(10'000), ammAlice.tokens()));
+
3858 // Initial 1,000 + 100
+
3859 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
+
3860 // Initial 30,000 - 100(offer) - 10(tx fee)
+
3861 BEAST_EXPECT(expectLedgerEntryRoot(
+
3862 env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
+
3863 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3864 },
+
3865 {{XRP(10'000), USD(10'100)}},
+
3866 0,
+
3867 std::nullopt,
+
3868 {features});
+
3869
+
3870 // Offer crossing IOU/IOU and transfer rate
+
3871 // Single path AMM offer
+
3872 testAMM(
+
3873 [&](AMM& ammAlice, Env& env) {
+
3874 env(rate(gw, 1.25));
+
3875 env.close();
+
3876 // This offer succeeds to cross pre- and post-amendment
+
3877 // because the strand's out amount is small enough to match
+
3878 // limitQuality value and limitOut() function in StrandFlow
+
3879 // doesn't require an adjustment to out value.
+
3880 env(offer(carol, EUR(100), GBP(100)));
+
3881 env.close();
+
3882 // No transfer fee
+
3883 BEAST_EXPECT(ammAlice.expectBalances(
+
3884 GBP(1'100), EUR(1'000), ammAlice.tokens()));
+
3885 // Initial 30,000 - 100(offer) - 25% transfer fee
+
3886 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
+
3887 // Initial 30,000 + 100(offer)
+
3888 BEAST_EXPECT(expectLine(env, carol, EUR(30'100)));
+
3889 BEAST_EXPECT(expectOffers(env, bob, 0));
+
3890 },
+
3891 {{GBP(1'000), EUR(1'100)}},
+
3892 0,
+
3893 std::nullopt,
+
3894 {features});
+
3895 // Single-path AMM offer
+
3896 testAMM(
+
3897 [&](AMM& amm, Env& env) {
+
3898 env(rate(gw, 1.001));
+
3899 env.close();
+
3900 env(offer(carol, XRP(100), USD(55)));
+
3901 env.close();
+
3902 if (!features[fixAMMv1_1])
+
3903 {
+
3904 // Pre-amendment the transfer fee is not taken into
+
3905 // account when calculating the limit out based on
+
3906 // limitQuality. Carol pays 0.1% on the takerGets, which
+
3907 // lowers the overall quality. AMM offer is generated based
+
3908 // on higher limit out, which generates a larger offer
+
3909 // with lower quality. Consequently, the offer fails
+
3910 // to cross.
+
3911 BEAST_EXPECT(
+
3912 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
+
3913 BEAST_EXPECT(expectOffers(
+
3914 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
+
3915 }
+
3916 else
+
3917 {
+
3918 // Post-amendment the transfer fee is taken into account
+
3919 // when calculating the limit out based on limitQuality.
+
3920 // This increases the limitQuality and decreases
+
3921 // the limit out. Consequently, AMM offer size is decreased,
+
3922 // and the quality is increased, matching the overall
+
3923 // quality.
+
3924 // AMM offer ~50USD/91XRP
+
3925 BEAST_EXPECT(amm.expectBalances(
+
3926 XRPAmount(909'090'909),
+
3927 STAmount{USD, UINT64_C(550'000000055), -9},
+
3928 amm.tokens()));
+
3929 // Offer ~91XRP/49.99USD
+
3930 BEAST_EXPECT(expectOffers(
+
3931 env,
+
3932 carol,
+
3933 1,
+
3934 {{Amounts{
+
3935 XRPAmount{9'090'909},
+
3936 STAmount{USD, 4'99999995, -8}}}}));
+
3937 // Carol pays 0.1% fee on ~50USD =~ 0.05USD
+
3938 BEAST_EXPECT(
+
3939 env.balance(carol, USD) ==
+
3940 STAmount(USD, UINT64_C(29'949'94999999494), -11));
+
3941 }
+
3942 },
+
3943 {{XRP(1'000), USD(500)}},
+
3944 0,
+
3945 std::nullopt,
+
3946 {features});
+
3947 testAMM(
+
3948 [&](AMM& amm, Env& env) {
+
3949 env(rate(gw, 1.001));
+
3950 env.close();
+
3951 env(offer(carol, XRP(10), USD(5.5)));
+
3952 env.close();
+
3953 if (!features[fixAMMv1_1])
+
3954 {
+
3955 BEAST_EXPECT(amm.expectBalances(
+
3956 XRP(990),
+
3957 STAmount{USD, UINT64_C(505'050505050505), -12},
+
3958 amm.tokens()));
+
3959 BEAST_EXPECT(expectOffers(env, carol, 0));
+
3960 }
+
3961 else
+
3962 {
+
3963 BEAST_EXPECT(amm.expectBalances(
+
3964 XRP(990),
+
3965 STAmount{USD, UINT64_C(505'0505050505051), -13},
+
3966 amm.tokens()));
+
3967 BEAST_EXPECT(expectOffers(env, carol, 0));
+
3968 }
+
3969 },
+
3970 {{XRP(1'000), USD(500)}},
+
3971 0,
+
3972 std::nullopt,
+
3973 {features});
+
3974 // Multi-path AMM offer
+
3975 testAMM(
+
3976 [&](AMM& ammAlice, Env& env) {
+
3977 Account const ed("ed");
+
3978 fund(
+
3979 env,
+
3980 gw,
+
3981 {bob, ed},
+
3982 XRP(30'000),
+
3983 {GBP(2'000), EUR(2'000)},
+
3984 Fund::Acct);
+
3985 env(rate(gw, 1.25));
3986 env.close();
-
3987 env(offer(carol, EUR(100), GBP(100)));
-
3988 env.close();
-
3989 if (!features[fixAMMv1_1])
-
3990 {
-
3991 // After the auto-bridge offers are consumed, single path
-
3992 // AMM offer is generated with the limit out not taking
-
3993 // into consideration the transfer fee. This results
-
3994 // in an overall lower quality offer than the limit quality
-
3995 // and the single path AMM offer fails to consume.
-
3996 // Total consumed ~37.06GBP/39.32EUR
-
3997 BEAST_EXPECT(ammAlice.expectBalances(
-
3998 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
-
3999 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
-
4000 ammAlice.tokens()));
-
4001 // Consumed offer ~49.32EUR/49.32GBP
-
4002 BEAST_EXPECT(expectOffers(
-
4003 env,
-
4004 carol,
-
4005 1,
-
4006 {Amounts{
-
4007 STAmount{EUR, UINT64_C(50'684828792831), -12},
-
4008 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
-
4009 BEAST_EXPECT(expectOffers(env, bob, 0));
-
4010 BEAST_EXPECT(expectOffers(env, ed, 0));
-
4011
-
4012 // Initial 30,000 - ~47.06(offers = 37.06(AMM) + 10(LOB))
-
4013 // * 1.25
-
4014 // = 58.825 = ~29941.17
-
4015 // carol bought ~72.93EUR at the cost of ~70.68GBP
-
4016 // the offer is partially consumed
-
4017 BEAST_EXPECT(expectLine(
-
4018 env,
-
4019 carol,
-
4020 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
-
4021 // Initial 30,000 + ~49.3(offers = 39.3(AMM) + 10(LOB))
-
4022 BEAST_EXPECT(expectLine(
-
4023 env,
-
4024 carol,
-
4025 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
-
4026 }
-
4027 else
-
4028 {
-
4029 // After the auto-bridge offers are consumed, single path
-
4030 // AMM offer is generated with the limit out taking
-
4031 // into consideration the transfer fee. This results
-
4032 // in an overall quality offer matching the limit quality
-
4033 // and the single path AMM offer is consumed. More
-
4034 // liquidity is consumed overall in post-amendment.
-
4035 // Total consumed ~60.68GBP/62.93EUR
-
4036 BEAST_EXPECT(ammAlice.expectBalances(
-
4037 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
-
4038 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
-
4039 ammAlice.tokens()));
-
4040 // Consumed offer ~72.93EUR/72.93GBP
-
4041 BEAST_EXPECT(expectOffers(
-
4042 env,
-
4043 carol,
-
4044 1,
-
4045 {Amounts{
-
4046 STAmount{EUR, UINT64_C(27'06583722134028), -14},
-
4047 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
-
4048 BEAST_EXPECT(expectOffers(env, bob, 0));
-
4049 BEAST_EXPECT(expectOffers(env, ed, 0));
-
4050
-
4051 // Initial 30,000 - ~70.68(offers = 60.68(AMM) + 10(LOB))
-
4052 // * 1.25
-
4053 // = 88.35 = ~29911.64
-
4054 // carol bought ~72.93EUR at the cost of ~70.68GBP
-
4055 // the offer is partially consumed
-
4056 BEAST_EXPECT(expectLine(
-
4057 env,
-
4058 carol,
-
4059 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
-
4060 // Initial 30,000 + ~72.93(offers = 62.93(AMM) + 10(LOB))
-
4061 BEAST_EXPECT(expectLine(
-
4062 env,
-
4063 carol,
-
4064 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
-
4065 }
-
4066 // Initial 2000 + 10 = 2010
-
4067 BEAST_EXPECT(expectLine(env, bob, GBP(2'010)));
-
4068 // Initial 2000 - 10 * 1.25 = 1987.5
-
4069 BEAST_EXPECT(expectLine(env, ed, EUR(1'987.5)));
-
4070 },
-
4071 {{GBP(1'000), EUR(1'100)}},
-
4072 0,
-
4073 std::nullopt,
-
4074 {features});
-
4075
-
4076 // Payment and transfer fee
-
4077 // Scenario:
-
4078 // Bob sends 125GBP to pay 80EUR to Carol
-
4079 // Payment execution:
-
4080 // bob's 125GBP/1.25 = 100GBP
-
4081 // 100GBP/100EUR AMM offer
-
4082 // 100EUR/1.25 = 80EUR paid to carol
-
4083 testAMM(
-
4084 [&](AMM& ammAlice, Env& env) {
-
4085 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
-
4086 env(rate(gw, 1.25));
-
4087 env.close();
-
4088 env(pay(bob, carol, EUR(100)),
-
4089 path(~EUR),
-
4090 sendmax(GBP(125)),
-
4091 txflags(tfPartialPayment));
-
4092 env.close();
-
4093 BEAST_EXPECT(ammAlice.expectBalances(
-
4094 GBP(1'100), EUR(1'000), ammAlice.tokens()));
-
4095 BEAST_EXPECT(expectLine(env, bob, GBP(75)));
-
4096 BEAST_EXPECT(expectLine(env, carol, EUR(30'080)));
-
4097 },
-
4098 {{GBP(1'000), EUR(1'100)}},
-
4099 0,
-
4100 std::nullopt,
-
4101 {features});
-
4102
-
4103 // Payment and transfer fee, multiple steps
-
4104 // Scenario:
-
4105 // Dan's offer 200CAN/200GBP
-
4106 // AMM 1000GBP/10125EUR
-
4107 // Ed's offer 200EUR/200USD
-
4108 // Bob sends 195.3125CAN to pay 100USD to Carol
-
4109 // Payment execution:
-
4110 // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer
-
4111 // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer
-
4112 // 125GBP/125EUR 125EUR/1.25 = 100EUR -> ed's offer
-
4113 // 100EUR/100USD 100USD/1.25 = 80USD paid to carol
-
4114 testAMM(
-
4115 [&](AMM& ammAlice, Env& env) {
-
4116 Account const dan("dan");
-
4117 Account const ed("ed");
-
4118 auto const CAN = gw["CAN"];
-
4119 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
-
4120 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
-
4121 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
-
4122 env(trust(carol, USD(100)));
-
4123 env(rate(gw, 1.25));
-
4124 env.close();
-
4125 env(offer(dan, CAN(200), GBP(200)));
-
4126 env(offer(ed, EUR(200), USD(200)));
-
4127 env.close();
-
4128 env(pay(bob, carol, USD(100)),
-
4129 path(~GBP, ~EUR, ~USD),
-
4130 sendmax(CAN(195.3125)),
-
4131 txflags(tfPartialPayment));
-
4132 env.close();
-
4133 BEAST_EXPECT(expectLine(env, bob, CAN(0)));
-
4134 BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75)));
-
4135 BEAST_EXPECT(ammAlice.expectBalances(
-
4136 GBP(10'125), EUR(10'000), ammAlice.tokens()));
-
4137 BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100)));
-
4138 BEAST_EXPECT(expectLine(env, carol, USD(80)));
-
4139 },
-
4140 {{GBP(10'000), EUR(10'125)}},
-
4141 0,
-
4142 std::nullopt,
-
4143 {features});
-
4144
-
4145 // Pay amounts close to one side of the pool
-
4146 testAMM(
-
4147 [&](AMM& ammAlice, Env& env) {
-
4148 env(pay(alice, carol, USD(99.99)),
-
4149 path(~USD),
-
4150 sendmax(XRP(1)),
-
4151 txflags(tfPartialPayment),
-
4152 ter(tesSUCCESS));
-
4153 env(pay(alice, carol, USD(100)),
-
4154 path(~USD),
-
4155 sendmax(XRP(1)),
-
4156 txflags(tfPartialPayment),
-
4157 ter(tesSUCCESS));
-
4158 env(pay(alice, carol, XRP(100)),
-
4159 path(~XRP),
-
4160 sendmax(USD(1)),
-
4161 txflags(tfPartialPayment),
-
4162 ter(tesSUCCESS));
-
4163 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}),
-
4164 path(~XRP),
-
4165 sendmax(USD(1)),
-
4166 txflags(tfPartialPayment),
-
4167 ter(tesSUCCESS));
-
4168 },
-
4169 {{XRP(100), USD(100)}},
-
4170 0,
-
4171 std::nullopt,
-
4172 {features});
-
4173
-
4174 // Multiple paths/steps
-
4175 {
-
4176 Env env(*this, features);
-
4177 auto const ETH = gw["ETH"];
-
4178 fund(
-
4179 env,
-
4180 gw,
-
4181 {alice},
-
4182 XRP(100'000),
-
4183 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
-
4184 fund(env, gw, {carol, bob}, XRP(1'000), {USD(200)}, Fund::Acct);
-
4185 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
-
4186 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
-
4187 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
-
4188 AMM xrp_usd(env, alice, XRP(10'150), USD(10'200));
-
4189 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
-
4190 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
-
4191 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
-
4192 env(pay(bob, carol, USD(100)),
-
4193 path(~EUR, ~BTC, ~USD),
-
4194 path(~USD),
-
4195 path(~ETH, ~EUR, ~USD),
-
4196 sendmax(XRP(200)));
-
4197 if (!features[fixAMMv1_1])
-
4198 {
-
4199 // XRP-ETH-EUR-USD
-
4200 // This path provides ~26.06USD/26.2XRP
-
4201 BEAST_EXPECT(xrp_eth.expectBalances(
-
4202 XRPAmount(10'026'208'900),
-
4203 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
-
4204 xrp_eth.tokens()));
-
4205 BEAST_EXPECT(eth_eur.expectBalances(
-
4206 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
-
4207 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
-
4208 eth_eur.tokens()));
-
4209 BEAST_EXPECT(eur_usd.expectBalances(
-
4210 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
-
4211 STAmount{USD, UINT64_C(9'973'93151712086), -11},
-
4212 eur_usd.tokens()));
-
4213 // XRP-USD path
-
4214 // This path provides ~73.9USD/74.1XRP
-
4215 BEAST_EXPECT(xrp_usd.expectBalances(
-
4216 XRPAmount(10'224'106'246),
-
4217 STAmount{USD, UINT64_C(10'126'06848287914), -11},
-
4218 xrp_usd.tokens()));
-
4219 }
-
4220 else
-
4221 {
-
4222 BEAST_EXPECT(xrp_eth.expectBalances(
-
4223 XRPAmount(10'026'208'900),
-
4224 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
-
4225 xrp_eth.tokens()));
-
4226 BEAST_EXPECT(eth_eur.expectBalances(
-
4227 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
-
4228 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
-
4229 eth_eur.tokens()));
-
4230 BEAST_EXPECT(eur_usd.expectBalances(
-
4231 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
-
4232 STAmount{USD, UINT64_C(9'973'93151712057), -11},
-
4233 eur_usd.tokens()));
-
4234 // XRP-USD path
-
4235 // This path provides ~73.9USD/74.1XRP
-
4236 BEAST_EXPECT(xrp_usd.expectBalances(
-
4237 XRPAmount(10'224'106'246),
-
4238 STAmount{USD, UINT64_C(10'126'06848287943), -11},
-
4239 xrp_usd.tokens()));
-
4240 }
-
4241
-
4242 // XRP-EUR-BTC-USD
-
4243 // This path doesn't provide any liquidity due to how
-
4244 // offers are generated in multi-path. Analytical solution
-
4245 // shows a different distribution:
-
4246 // XRP-EUR-BTC-USD 11.6USD/11.64XRP, XRP-USD 60.7USD/60.8XRP,
-
4247 // XRP-ETH-EUR-USD 27.6USD/27.6XRP
-
4248 BEAST_EXPECT(xrp_eur.expectBalances(
-
4249 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
-
4250 BEAST_EXPECT(eur_btc.expectBalances(
-
4251 EUR(10'000), BTC(10'200), eur_btc.tokens()));
-
4252 BEAST_EXPECT(btc_usd.expectBalances(
-
4253 BTC(10'100), USD(10'000), btc_usd.tokens()));
-
4254
-
4255 BEAST_EXPECT(expectLine(env, carol, USD(300)));
-
4256 }
-
4257
-
4258 // Dependent AMM
-
4259 {
-
4260 Env env(*this, features);
-
4261 auto const ETH = gw["ETH"];
-
4262 fund(
-
4263 env,
-
4264 gw,
-
4265 {alice},
-
4266 XRP(40'000),
-
4267 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
-
4268 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
-
4269 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
-
4270 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
-
4271 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
-
4272 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
-
4273 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
-
4274 env(pay(bob, carol, USD(100)),
-
4275 path(~EUR, ~BTC, ~USD),
-
4276 path(~ETH, ~EUR, ~BTC, ~USD),
-
4277 sendmax(XRP(200)));
-
4278 if (!features[fixAMMv1_1])
-
4279 {
-
4280 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
-
4281 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
-
4282 BEAST_EXPECT(xrp_eur.expectBalances(
-
4283 XRPAmount(10'118'738'472),
-
4284 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
-
4285 xrp_eur.tokens()));
-
4286 BEAST_EXPECT(eur_btc.expectBalances(
-
4287 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
-
4288 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
-
4289 eur_btc.tokens()));
-
4290 BEAST_EXPECT(btc_usd.expectBalances(
-
4291 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
-
4292 USD(9'900),
-
4293 btc_usd.tokens()));
-
4294 BEAST_EXPECT(xrp_eth.expectBalances(
-
4295 XRPAmount(10'082'446'397),
-
4296 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
-
4297 xrp_eth.tokens()));
-
4298 BEAST_EXPECT(eth_eur.expectBalances(
-
4299 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
-
4300 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
-
4301 eth_eur.tokens()));
-
4302 }
-
4303 else
-
4304 {
-
4305 BEAST_EXPECT(xrp_eur.expectBalances(
-
4306 XRPAmount(10'118'738'472),
-
4307 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
-
4308 xrp_eur.tokens()));
-
4309 BEAST_EXPECT(eur_btc.expectBalances(
-
4310 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
-
4311 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
-
4312 eur_btc.tokens()));
-
4313 BEAST_EXPECT(btc_usd.expectBalances(
-
4314 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
-
4315 USD(9'900),
-
4316 btc_usd.tokens()));
-
4317 BEAST_EXPECT(xrp_eth.expectBalances(
-
4318 XRPAmount(10'082'446'397),
-
4319 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
-
4320 xrp_eth.tokens()));
-
4321 BEAST_EXPECT(eth_eur.expectBalances(
-
4322 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
-
4323 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
-
4324 eth_eur.tokens()));
-
4325 }
-
4326 BEAST_EXPECT(expectLine(env, carol, USD(300)));
-
4327 }
-
4328
-
4329 // AMM offers limit
-
4330 // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit.
-
4331 testAMM(
-
4332 [&](AMM& ammAlice, Env& env) {
-
4333 env.fund(XRP(1'000), bob);
-
4334 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
-
4335 env(trust(alice, EUR(200)));
-
4336 for (int i = 0; i < 30; ++i)
-
4337 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
-
4338 // This is worse quality offer than 30 offers above.
-
4339 // It will not be consumed because of AMM offers limit.
-
4340 env(offer(alice, EUR(140), XRP(100)));
-
4341 env(pay(bob, carol, USD(100)),
-
4342 path(~XRP, ~USD),
-
4343 sendmax(EUR(400)),
-
4344 txflags(tfPartialPayment | tfNoRippleDirect));
-
4345 if (!features[fixAMMv1_1])
-
4346 {
-
4347 // Carol gets ~29.91USD because of the AMM offers limit
-
4348 BEAST_EXPECT(ammAlice.expectBalances(
-
4349 XRP(10'030),
-
4350 STAmount{USD, UINT64_C(9'970'089730807577), -12},
-
4351 ammAlice.tokens()));
-
4352 BEAST_EXPECT(expectLine(
-
4353 env,
-
4354 carol,
-
4355 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
-
4356 }
-
4357 else
-
4358 {
-
4359 BEAST_EXPECT(ammAlice.expectBalances(
-
4360 XRP(10'030),
-
4361 STAmount{USD, UINT64_C(9'970'089730807827), -12},
-
4362 ammAlice.tokens()));
-
4363 BEAST_EXPECT(expectLine(
-
4364 env,
-
4365 carol,
-
4366 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
-
4367 }
-
4368 BEAST_EXPECT(
-
4369 expectOffers(env, alice, 1, {{{EUR(140), XRP(100)}}}));
-
4370 },
-
4371 std::nullopt,
-
4372 0,
-
4373 std::nullopt,
-
4374 {features});
-
4375 // This payment is fulfilled
-
4376 testAMM(
-
4377 [&](AMM& ammAlice, Env& env) {
-
4378 env.fund(XRP(1'000), bob);
-
4379 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
-
4380 env(trust(alice, EUR(200)));
-
4381 for (int i = 0; i < 29; ++i)
-
4382 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
-
4383 // This is worse quality offer than 30 offers above.
-
4384 // It will not be consumed because of AMM offers limit.
-
4385 env(offer(alice, EUR(140), XRP(100)));
-
4386 env(pay(bob, carol, USD(100)),
-
4387 path(~XRP, ~USD),
-
4388 sendmax(EUR(400)),
-
4389 txflags(tfPartialPayment | tfNoRippleDirect));
-
4390 BEAST_EXPECT(ammAlice.expectBalances(
-
4391 XRPAmount{10'101'010'102}, USD(9'900), ammAlice.tokens()));
-
4392 if (!features[fixAMMv1_1])
-
4393 {
-
4394 // Carol gets ~100USD
-
4395 BEAST_EXPECT(expectLine(
-
4396 env,
-
4397 carol,
-
4398 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
-
4399 }
-
4400 else
-
4401 {
-
4402 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
4403 }
-
4404 BEAST_EXPECT(expectOffers(
-
4405 env,
-
4406 alice,
-
4407 1,
-
4408 {{{STAmount{EUR, UINT64_C(39'1858572), -7},
-
4409 XRPAmount{27'989'898}}}}));
-
4410 },
-
4411 std::nullopt,
-
4412 0,
-
4413 std::nullopt,
-
4414 {features});
-
4415
-
4416 // Offer crossing with AMM and another offer. AMM has a better
-
4417 // quality and is consumed first.
-
4418 {
-
4419 Env env(*this, features);
-
4420 fund(env, gw, {alice, carol, bob}, XRP(30'000), {USD(30'000)});
-
4421 env(offer(bob, XRP(100), USD(100.001)));
-
4422 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
-
4423 env(offer(carol, USD(100), XRP(100)));
-
4424 if (!features[fixAMMv1_1])
-
4425 {
-
4426 BEAST_EXPECT(ammAlice.expectBalances(
-
4427 XRPAmount{10'049'825'373},
-
4428 STAmount{USD, UINT64_C(10'049'92586949302), -11},
-
4429 ammAlice.tokens()));
-
4430 BEAST_EXPECT(expectOffers(
-
4431 env,
-
4432 bob,
-
4433 1,
-
4434 {{{XRPAmount{50'074'629},
-
4435 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
-
4436 }
-
4437 else
-
4438 {
-
4439 BEAST_EXPECT(ammAlice.expectBalances(
-
4440 XRPAmount{10'049'825'372},
-
4441 STAmount{USD, UINT64_C(10'049'92587049303), -11},
-
4442 ammAlice.tokens()));
-
4443 BEAST_EXPECT(expectOffers(
-
4444 env,
-
4445 bob,
-
4446 1,
-
4447 {{{XRPAmount{50'074'628},
-
4448 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
-
4449 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
-
4450 }
-
4451 }
-
4452
-
4453 // Individually frozen account
-
4454 testAMM(
-
4455 [&](AMM& ammAlice, Env& env) {
-
4456 env(trust(gw, carol["USD"](0), tfSetFreeze));
-
4457 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
4458 env.close();
-
4459 env(pay(alice, carol, USD(1)),
-
4460 path(~USD),
-
4461 sendmax(XRP(10)),
-
4462 txflags(tfNoRippleDirect | tfPartialPayment),
-
4463 ter(tesSUCCESS));
-
4464 },
-
4465 std::nullopt,
-
4466 0,
-
4467 std::nullopt,
-
4468 {features});
-
4469 }
-
4470
-
4471 void
-
4472 testAMMTokens()
-
4473 {
-
4474 testcase("AMM Tokens");
-
4475 using namespace jtx;
-
4476
-
4477 // Offer crossing with AMM LPTokens and XRP.
-
4478 testAMM([&](AMM& ammAlice, Env& env) {
-
4479 auto const token1 = ammAlice.lptIssue();
-
4480 auto priceXRP = withdrawByTokens(
-
4481 STAmount{XRPAmount{10'000'000'000}},
-
4482 STAmount{token1, 10'000'000},
-
4483 STAmount{token1, 5'000'000},
-
4484 0);
-
4485 // Carol places an order to buy LPTokens
-
4486 env(offer(carol, STAmount{token1, 5'000'000}, priceXRP));
-
4487 // Alice places an order to sell LPTokens
-
4488 env(offer(alice, priceXRP, STAmount{token1, 5'000'000}));
-
4489 // Pool's LPTokens balance doesn't change
-
4490 BEAST_EXPECT(ammAlice.expectBalances(
-
4491 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
4492 // Carol is Liquidity Provider
-
4493 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000}));
-
4494 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
-
4495 // Carol votes
-
4496 ammAlice.vote(carol, 1'000);
-
4497 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
4498 ammAlice.vote(carol, 0);
-
4499 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4500 // Carol bids
-
4501 env(ammAlice.bid({.account = carol, .bidMin = 100}));
-
4502 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
-
4503 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
-
4504 BEAST_EXPECT(accountBalance(env, carol) == "22499999960");
-
4505 priceXRP = withdrawByTokens(
-
4506 STAmount{XRPAmount{10'000'000'000}},
-
4507 STAmount{token1, 9'999'900},
-
4508 STAmount{token1, 4'999'900},
-
4509 0);
-
4510 // Carol withdraws
-
4511 ammAlice.withdrawAll(carol, XRP(0));
-
4512 BEAST_EXPECT(accountBalance(env, carol) == "29999949949");
-
4513 BEAST_EXPECT(ammAlice.expectBalances(
-
4514 XRPAmount{10'000'000'000} - priceXRP,
-
4515 USD(10'000),
-
4516 IOUAmount{5'000'000}));
-
4517 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
-
4518 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
4519 });
-
4520
-
4521 // Offer crossing with two AMM LPTokens.
-
4522 testAMM([&](AMM& ammAlice, Env& env) {
-
4523 ammAlice.deposit(carol, 1'000'000);
-
4524 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
-
4525 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
-
4526 ammAlice1.deposit(carol, 1'000'000);
-
4527 auto const token1 = ammAlice.lptIssue();
-
4528 auto const token2 = ammAlice1.lptIssue();
-
4529 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
-
4530 txflags(tfPassive));
-
4531 env.close();
-
4532 BEAST_EXPECT(expectOffers(env, alice, 1));
-
4533 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
-
4534 env.close();
-
4535 BEAST_EXPECT(
-
4536 expectLine(env, alice, STAmount{token1, 10'000'100}) &&
-
4537 expectLine(env, alice, STAmount{token2, 9'999'900}));
-
4538 BEAST_EXPECT(
-
4539 expectLine(env, carol, STAmount{token2, 1'000'100}) &&
-
4540 expectLine(env, carol, STAmount{token1, 999'900}));
-
4541 BEAST_EXPECT(
-
4542 expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
-
4543 });
-
4544
-
4545 // LPs pay LPTokens directly. Must trust set because the trust line
-
4546 // is checked for the limit, which is 0 in the AMM auto-created
-
4547 // trust line.
-
4548 testAMM([&](AMM& ammAlice, Env& env) {
-
4549 auto const token1 = ammAlice.lptIssue();
-
4550 env.trust(STAmount{token1, 2'000'000}, carol);
-
4551 env.close();
-
4552 ammAlice.deposit(carol, 1'000'000);
-
4553 BEAST_EXPECT(
-
4554 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
-
4555 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
4556 // Pool balance doesn't change, only tokens moved from
-
4557 // one line to another.
-
4558 env(pay(alice, carol, STAmount{token1, 100}));
-
4559 env.close();
-
4560 BEAST_EXPECT(
-
4561 // Alice initial token1 10,000,000 - 100
-
4562 ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) &&
-
4563 // Carol initial token1 1,000,000 + 100
-
4564 ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0}));
-
4565
-
4566 env.trust(STAmount{token1, 20'000'000}, alice);
-
4567 env.close();
-
4568 env(pay(carol, alice, STAmount{token1, 100}));
-
4569 env.close();
-
4570 // Back to the original balance
-
4571 BEAST_EXPECT(
-
4572 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
-
4573 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
-
4574 });
-
4575 }
-
4576
-
4577 void
-
4578 testAmendment()
-
4579 {
-
4580 testcase("Amendment");
-
4581 using namespace jtx;
-
4582 FeatureBitset const all{supported_amendments()};
-
4583 FeatureBitset const noAMM{all - featureAMM};
-
4584 FeatureBitset const noNumber{all - fixUniversalNumber};
-
4585 FeatureBitset const noAMMAndNumber{
-
4586 all - featureAMM - fixUniversalNumber};
-
4587
-
4588 for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
-
4589 {
-
4590 Env env{*this, feature};
-
4591 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
-
4592 AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
-
4593
-
4594 env(amm.bid({.bidMax = 1000}), ter(temMALFORMED));
-
4595 env(amm.bid({}), ter(temDISABLED));
-
4596 amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)});
-
4597 amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)});
-
4598 amm.withdraw(WithdrawArg{.err = ter(temDISABLED)});
-
4599 amm.deposit(
-
4600 DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)});
-
4601 amm.ammDelete(alice, ter(temDISABLED));
-
4602 }
-
4603 }
-
4604
-
4605 void
-
4606 testFlags()
-
4607 {
-
4608 testcase("Flags");
-
4609 using namespace jtx;
-
4610
-
4611 testAMM([&](AMM& ammAlice, Env& env) {
-
4612 auto const info = env.rpc(
-
4613 "json",
-
4614 "account_info",
-
4615 std::string(
-
4616 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
-
4617 "\"}"));
-
4618 auto const flags =
-
4619 info[jss::result][jss::account_data][jss::Flags].asUInt();
-
4620 BEAST_EXPECT(
-
4621 flags ==
-
4622 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
-
4623 });
-
4624 }
-
4625
-
4626 void
-
4627 testRippling()
-
4628 {
-
4629 testcase("Rippling");
-
4630 using namespace jtx;
-
4631
-
4632 // Rippling via AMM fails because AMM trust line has 0 limit.
-
4633 // Set up two issuers, A and B. Have each issue a token called TST.
-
4634 // Have another account C hold TST from both issuers,
-
4635 // and create an AMM for this pair.
-
4636 // Have a fourth account, D, create a trust line to the AMM for TST.
-
4637 // Send a payment delivering TST.AMM from C to D, using SendMax in
-
4638 // TST.A (or B) and a path through the AMM account. By normal
-
4639 // rippling rules, this would have caused the AMM's balances
-
4640 // to shift at a 1:1 rate with no fee applied has it not been
-
4641 // for 0 limit.
-
4642 {
-
4643 Env env(*this);
-
4644 auto const A = Account("A");
-
4645 auto const B = Account("B");
-
4646 auto const TSTA = A["TST"];
-
4647 auto const TSTB = B["TST"];
-
4648 auto const C = Account("C");
-
4649 auto const D = Account("D");
-
4650
-
4651 env.fund(XRP(10'000), A);
-
4652 env.fund(XRP(10'000), B);
-
4653 env.fund(XRP(10'000), C);
-
4654 env.fund(XRP(10'000), D);
-
4655
-
4656 env.trust(TSTA(10'000), C);
-
4657 env.trust(TSTB(10'000), C);
-
4658 env(pay(A, C, TSTA(10'000)));
-
4659 env(pay(B, C, TSTB(10'000)));
-
4660 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
-
4661 auto const ammIss = Issue(TSTA.currency, amm.ammAccount());
-
4662
-
4663 // Can SetTrust only for AMM LP tokens
-
4664 env(trust(D, STAmount{ammIss, 10'000}), ter(tecNO_PERMISSION));
-
4665 env.close();
-
4666
-
4667 // The payment would fail because of above, but check just in case
-
4668 env(pay(C, D, STAmount{ammIss, 10}),
-
4669 sendmax(TSTA(100)),
-
4670 path(amm.ammAccount()),
-
4671 txflags(tfPartialPayment | tfNoRippleDirect),
-
4672 ter(tecPATH_DRY));
-
4673 }
-
4674 }
-
4675
-
4676 void
-
4677 testAMMAndCLOB(FeatureBitset features)
-
4678 {
-
4679 testcase("AMMAndCLOB, offer quality change");
-
4680 using namespace jtx;
-
4681 auto const gw = Account("gw");
-
4682 auto const TST = gw["TST"];
-
4683 auto const LP1 = Account("LP1");
-
4684 auto const LP2 = Account("LP2");
-
4685
-
4686 auto prep = [&](auto const& offerCb, auto const& expectCb) {
-
4687 Env env(*this, features);
-
4688 env.fund(XRP(30'000'000'000), gw);
-
4689 env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000)));
-
4690
-
4691 env.fund(XRP(10'000), LP1);
-
4692 env.fund(XRP(10'000), LP2);
-
4693 env(offer(LP1, TST(25), XRPAmount(287'500'000)));
-
4694
-
4695 // Either AMM or CLOB offer
-
4696 offerCb(env);
-
4697
-
4698 env(offer(LP2, TST(25), XRPAmount(287'500'000)));
+
3987 // The auto-bridge is worse quality than AMM, is not consumed
+
3988 // first and initially forces multi-path AMM offer generation.
+
3989 // Multi-path AMM offers are consumed until their quality
+
3990 // is less than the auto-bridge offers quality. Auto-bridge
+
3991 // offers are consumed afterward. Then the behavior is
+
3992 // different pre-amendment and post-amendment.
+
3993 env(offer(bob, GBP(10), XRP(10)), txflags(tfPassive));
+
3994 env(offer(ed, XRP(10), EUR(10)), txflags(tfPassive));
+
3995 env.close();
+
3996 env(offer(carol, EUR(100), GBP(100)));
+
3997 env.close();
+
3998 if (!features[fixAMMv1_1])
+
3999 {
+
4000 // After the auto-bridge offers are consumed, single path
+
4001 // AMM offer is generated with the limit out not taking
+
4002 // into consideration the transfer fee. This results
+
4003 // in an overall lower quality offer than the limit quality
+
4004 // and the single path AMM offer fails to consume.
+
4005 // Total consumed ~37.06GBP/39.32EUR
+
4006 BEAST_EXPECT(ammAlice.expectBalances(
+
4007 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
+
4008 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
+
4009 ammAlice.tokens()));
+
4010 // Consumed offer ~49.32EUR/49.32GBP
+
4011 BEAST_EXPECT(expectOffers(
+
4012 env,
+
4013 carol,
+
4014 1,
+
4015 {Amounts{
+
4016 STAmount{EUR, UINT64_C(50'684828792831), -12},
+
4017 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
+
4018 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4019 BEAST_EXPECT(expectOffers(env, ed, 0));
+
4020
+
4021 // Initial 30,000 - ~47.06(offers = 37.06(AMM) + 10(LOB))
+
4022 // * 1.25
+
4023 // = 58.825 = ~29941.17
+
4024 // carol bought ~72.93EUR at the cost of ~70.68GBP
+
4025 // the offer is partially consumed
+
4026 BEAST_EXPECT(expectLine(
+
4027 env,
+
4028 carol,
+
4029 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
+
4030 // Initial 30,000 + ~49.3(offers = 39.3(AMM) + 10(LOB))
+
4031 BEAST_EXPECT(expectLine(
+
4032 env,
+
4033 carol,
+
4034 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
+
4035 }
+
4036 else
+
4037 {
+
4038 // After the auto-bridge offers are consumed, single path
+
4039 // AMM offer is generated with the limit out taking
+
4040 // into consideration the transfer fee. This results
+
4041 // in an overall quality offer matching the limit quality
+
4042 // and the single path AMM offer is consumed. More
+
4043 // liquidity is consumed overall in post-amendment.
+
4044 // Total consumed ~60.68GBP/62.93EUR
+
4045 BEAST_EXPECT(ammAlice.expectBalances(
+
4046 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
+
4047 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
+
4048 ammAlice.tokens()));
+
4049 // Consumed offer ~72.93EUR/72.93GBP
+
4050 BEAST_EXPECT(expectOffers(
+
4051 env,
+
4052 carol,
+
4053 1,
+
4054 {Amounts{
+
4055 STAmount{EUR, UINT64_C(27'06583722134028), -14},
+
4056 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
+
4057 BEAST_EXPECT(expectOffers(env, bob, 0));
+
4058 BEAST_EXPECT(expectOffers(env, ed, 0));
+
4059
+
4060 // Initial 30,000 - ~70.68(offers = 60.68(AMM) + 10(LOB))
+
4061 // * 1.25
+
4062 // = 88.35 = ~29911.64
+
4063 // carol bought ~72.93EUR at the cost of ~70.68GBP
+
4064 // the offer is partially consumed
+
4065 BEAST_EXPECT(expectLine(
+
4066 env,
+
4067 carol,
+
4068 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
+
4069 // Initial 30,000 + ~72.93(offers = 62.93(AMM) + 10(LOB))
+
4070 BEAST_EXPECT(expectLine(
+
4071 env,
+
4072 carol,
+
4073 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
+
4074 }
+
4075 // Initial 2000 + 10 = 2010
+
4076 BEAST_EXPECT(expectLine(env, bob, GBP(2'010)));
+
4077 // Initial 2000 - 10 * 1.25 = 1987.5
+
4078 BEAST_EXPECT(expectLine(env, ed, EUR(1'987.5)));
+
4079 },
+
4080 {{GBP(1'000), EUR(1'100)}},
+
4081 0,
+
4082 std::nullopt,
+
4083 {features});
+
4084
+
4085 // Payment and transfer fee
+
4086 // Scenario:
+
4087 // Bob sends 125GBP to pay 80EUR to Carol
+
4088 // Payment execution:
+
4089 // bob's 125GBP/1.25 = 100GBP
+
4090 // 100GBP/100EUR AMM offer
+
4091 // 100EUR/1.25 = 80EUR paid to carol
+
4092 testAMM(
+
4093 [&](AMM& ammAlice, Env& env) {
+
4094 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
+
4095 env(rate(gw, 1.25));
+
4096 env.close();
+
4097 env(pay(bob, carol, EUR(100)),
+
4098 path(~EUR),
+
4099 sendmax(GBP(125)),
+
4100 txflags(tfPartialPayment));
+
4101 env.close();
+
4102 BEAST_EXPECT(ammAlice.expectBalances(
+
4103 GBP(1'100), EUR(1'000), ammAlice.tokens()));
+
4104 BEAST_EXPECT(expectLine(env, bob, GBP(75)));
+
4105 BEAST_EXPECT(expectLine(env, carol, EUR(30'080)));
+
4106 },
+
4107 {{GBP(1'000), EUR(1'100)}},
+
4108 0,
+
4109 std::nullopt,
+
4110 {features});
+
4111
+
4112 // Payment and transfer fee, multiple steps
+
4113 // Scenario:
+
4114 // Dan's offer 200CAN/200GBP
+
4115 // AMM 1000GBP/10125EUR
+
4116 // Ed's offer 200EUR/200USD
+
4117 // Bob sends 195.3125CAN to pay 100USD to Carol
+
4118 // Payment execution:
+
4119 // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer
+
4120 // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer
+
4121 // 125GBP/125EUR 125EUR/1.25 = 100EUR -> ed's offer
+
4122 // 100EUR/100USD 100USD/1.25 = 80USD paid to carol
+
4123 testAMM(
+
4124 [&](AMM& ammAlice, Env& env) {
+
4125 Account const dan("dan");
+
4126 Account const ed("ed");
+
4127 auto const CAN = gw["CAN"];
+
4128 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
+
4129 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
+
4130 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
+
4131 env(trust(carol, USD(100)));
+
4132 env(rate(gw, 1.25));
+
4133 env.close();
+
4134 env(offer(dan, CAN(200), GBP(200)));
+
4135 env(offer(ed, EUR(200), USD(200)));
+
4136 env.close();
+
4137 env(pay(bob, carol, USD(100)),
+
4138 path(~GBP, ~EUR, ~USD),
+
4139 sendmax(CAN(195.3125)),
+
4140 txflags(tfPartialPayment));
+
4141 env.close();
+
4142 BEAST_EXPECT(expectLine(env, bob, CAN(0)));
+
4143 BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75)));
+
4144 BEAST_EXPECT(ammAlice.expectBalances(
+
4145 GBP(10'125), EUR(10'000), ammAlice.tokens()));
+
4146 BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100)));
+
4147 BEAST_EXPECT(expectLine(env, carol, USD(80)));
+
4148 },
+
4149 {{GBP(10'000), EUR(10'125)}},
+
4150 0,
+
4151 std::nullopt,
+
4152 {features});
+
4153
+
4154 // Pay amounts close to one side of the pool
+
4155 testAMM(
+
4156 [&](AMM& ammAlice, Env& env) {
+
4157 env(pay(alice, carol, USD(99.99)),
+
4158 path(~USD),
+
4159 sendmax(XRP(1)),
+
4160 txflags(tfPartialPayment),
+
4161 ter(tesSUCCESS));
+
4162 env(pay(alice, carol, USD(100)),
+
4163 path(~USD),
+
4164 sendmax(XRP(1)),
+
4165 txflags(tfPartialPayment),
+
4166 ter(tesSUCCESS));
+
4167 env(pay(alice, carol, XRP(100)),
+
4168 path(~XRP),
+
4169 sendmax(USD(1)),
+
4170 txflags(tfPartialPayment),
+
4171 ter(tesSUCCESS));
+
4172 env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}),
+
4173 path(~XRP),
+
4174 sendmax(USD(1)),
+
4175 txflags(tfPartialPayment),
+
4176 ter(tesSUCCESS));
+
4177 },
+
4178 {{XRP(100), USD(100)}},
+
4179 0,
+
4180 std::nullopt,
+
4181 {features});
+
4182
+
4183 // Multiple paths/steps
+
4184 {
+
4185 Env env(*this, features);
+
4186 auto const ETH = gw["ETH"];
+
4187 fund(
+
4188 env,
+
4189 gw,
+
4190 {alice},
+
4191 XRP(100'000),
+
4192 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
+
4193 fund(env, gw, {carol, bob}, XRP(1'000), {USD(200)}, Fund::Acct);
+
4194 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
+
4195 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
+
4196 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
+
4197 AMM xrp_usd(env, alice, XRP(10'150), USD(10'200));
+
4198 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
+
4199 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
+
4200 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
+
4201 env(pay(bob, carol, USD(100)),
+
4202 path(~EUR, ~BTC, ~USD),
+
4203 path(~USD),
+
4204 path(~ETH, ~EUR, ~USD),
+
4205 sendmax(XRP(200)));
+
4206 if (!features[fixAMMv1_1])
+
4207 {
+
4208 // XRP-ETH-EUR-USD
+
4209 // This path provides ~26.06USD/26.2XRP
+
4210 BEAST_EXPECT(xrp_eth.expectBalances(
+
4211 XRPAmount(10'026'208'900),
+
4212 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
+
4213 xrp_eth.tokens()));
+
4214 BEAST_EXPECT(eth_eur.expectBalances(
+
4215 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
+
4216 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
+
4217 eth_eur.tokens()));
+
4218 BEAST_EXPECT(eur_usd.expectBalances(
+
4219 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
+
4220 STAmount{USD, UINT64_C(9'973'93151712086), -11},
+
4221 eur_usd.tokens()));
+
4222 // XRP-USD path
+
4223 // This path provides ~73.9USD/74.1XRP
+
4224 BEAST_EXPECT(xrp_usd.expectBalances(
+
4225 XRPAmount(10'224'106'246),
+
4226 STAmount{USD, UINT64_C(10'126'06848287914), -11},
+
4227 xrp_usd.tokens()));
+
4228 }
+
4229 else
+
4230 {
+
4231 BEAST_EXPECT(xrp_eth.expectBalances(
+
4232 XRPAmount(10'026'208'900),
+
4233 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
+
4234 xrp_eth.tokens()));
+
4235 BEAST_EXPECT(eth_eur.expectBalances(
+
4236 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
+
4237 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
+
4238 eth_eur.tokens()));
+
4239 BEAST_EXPECT(eur_usd.expectBalances(
+
4240 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
+
4241 STAmount{USD, UINT64_C(9'973'93151712057), -11},
+
4242 eur_usd.tokens()));
+
4243 // XRP-USD path
+
4244 // This path provides ~73.9USD/74.1XRP
+
4245 BEAST_EXPECT(xrp_usd.expectBalances(
+
4246 XRPAmount(10'224'106'246),
+
4247 STAmount{USD, UINT64_C(10'126'06848287943), -11},
+
4248 xrp_usd.tokens()));
+
4249 }
+
4250
+
4251 // XRP-EUR-BTC-USD
+
4252 // This path doesn't provide any liquidity due to how
+
4253 // offers are generated in multi-path. Analytical solution
+
4254 // shows a different distribution:
+
4255 // XRP-EUR-BTC-USD 11.6USD/11.64XRP, XRP-USD 60.7USD/60.8XRP,
+
4256 // XRP-ETH-EUR-USD 27.6USD/27.6XRP
+
4257 BEAST_EXPECT(xrp_eur.expectBalances(
+
4258 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
+
4259 BEAST_EXPECT(eur_btc.expectBalances(
+
4260 EUR(10'000), BTC(10'200), eur_btc.tokens()));
+
4261 BEAST_EXPECT(btc_usd.expectBalances(
+
4262 BTC(10'100), USD(10'000), btc_usd.tokens()));
+
4263
+
4264 BEAST_EXPECT(expectLine(env, carol, USD(300)));
+
4265 }
+
4266
+
4267 // Dependent AMM
+
4268 {
+
4269 Env env(*this, features);
+
4270 auto const ETH = gw["ETH"];
+
4271 fund(
+
4272 env,
+
4273 gw,
+
4274 {alice},
+
4275 XRP(40'000),
+
4276 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
+
4277 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
+
4278 AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
+
4279 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
+
4280 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
+
4281 AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
+
4282 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
+
4283 env(pay(bob, carol, USD(100)),
+
4284 path(~EUR, ~BTC, ~USD),
+
4285 path(~ETH, ~EUR, ~BTC, ~USD),
+
4286 sendmax(XRP(200)));
+
4287 if (!features[fixAMMv1_1])
+
4288 {
+
4289 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
+
4290 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
+
4291 BEAST_EXPECT(xrp_eur.expectBalances(
+
4292 XRPAmount(10'118'738'472),
+
4293 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
+
4294 xrp_eur.tokens()));
+
4295 BEAST_EXPECT(eur_btc.expectBalances(
+
4296 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
+
4297 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
+
4298 eur_btc.tokens()));
+
4299 BEAST_EXPECT(btc_usd.expectBalances(
+
4300 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
+
4301 USD(9'900),
+
4302 btc_usd.tokens()));
+
4303 BEAST_EXPECT(xrp_eth.expectBalances(
+
4304 XRPAmount(10'082'446'397),
+
4305 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
+
4306 xrp_eth.tokens()));
+
4307 BEAST_EXPECT(eth_eur.expectBalances(
+
4308 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
+
4309 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
+
4310 eth_eur.tokens()));
+
4311 }
+
4312 else
+
4313 {
+
4314 BEAST_EXPECT(xrp_eur.expectBalances(
+
4315 XRPAmount(10'118'738'472),
+
4316 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
+
4317 xrp_eur.tokens()));
+
4318 BEAST_EXPECT(eur_btc.expectBalances(
+
4319 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
+
4320 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
+
4321 eur_btc.tokens()));
+
4322 BEAST_EXPECT(btc_usd.expectBalances(
+
4323 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
+
4324 USD(9'900),
+
4325 btc_usd.tokens()));
+
4326 BEAST_EXPECT(xrp_eth.expectBalances(
+
4327 XRPAmount(10'082'446'397),
+
4328 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
+
4329 xrp_eth.tokens()));
+
4330 BEAST_EXPECT(eth_eur.expectBalances(
+
4331 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
+
4332 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
+
4333 eth_eur.tokens()));
+
4334 }
+
4335 BEAST_EXPECT(expectLine(env, carol, USD(300)));
+
4336 }
+
4337
+
4338 // AMM offers limit
+
4339 // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit.
+
4340 testAMM(
+
4341 [&](AMM& ammAlice, Env& env) {
+
4342 env.fund(XRP(1'000), bob);
+
4343 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
+
4344 env(trust(alice, EUR(200)));
+
4345 for (int i = 0; i < 30; ++i)
+
4346 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
+
4347 // This is worse quality offer than 30 offers above.
+
4348 // It will not be consumed because of AMM offers limit.
+
4349 env(offer(alice, EUR(140), XRP(100)));
+
4350 env(pay(bob, carol, USD(100)),
+
4351 path(~XRP, ~USD),
+
4352 sendmax(EUR(400)),
+
4353 txflags(tfPartialPayment | tfNoRippleDirect));
+
4354 if (!features[fixAMMv1_1])
+
4355 {
+
4356 // Carol gets ~29.91USD because of the AMM offers limit
+
4357 BEAST_EXPECT(ammAlice.expectBalances(
+
4358 XRP(10'030),
+
4359 STAmount{USD, UINT64_C(9'970'089730807577), -12},
+
4360 ammAlice.tokens()));
+
4361 BEAST_EXPECT(expectLine(
+
4362 env,
+
4363 carol,
+
4364 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
+
4365 }
+
4366 else
+
4367 {
+
4368 BEAST_EXPECT(ammAlice.expectBalances(
+
4369 XRP(10'030),
+
4370 STAmount{USD, UINT64_C(9'970'089730807827), -12},
+
4371 ammAlice.tokens()));
+
4372 BEAST_EXPECT(expectLine(
+
4373 env,
+
4374 carol,
+
4375 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
+
4376 }
+
4377 BEAST_EXPECT(
+
4378 expectOffers(env, alice, 1, {{{EUR(140), XRP(100)}}}));
+
4379 },
+
4380 std::nullopt,
+
4381 0,
+
4382 std::nullopt,
+
4383 {features});
+
4384 // This payment is fulfilled
+
4385 testAMM(
+
4386 [&](AMM& ammAlice, Env& env) {
+
4387 env.fund(XRP(1'000), bob);
+
4388 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
+
4389 env(trust(alice, EUR(200)));
+
4390 for (int i = 0; i < 29; ++i)
+
4391 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
+
4392 // This is worse quality offer than 30 offers above.
+
4393 // It will not be consumed because of AMM offers limit.
+
4394 env(offer(alice, EUR(140), XRP(100)));
+
4395 env(pay(bob, carol, USD(100)),
+
4396 path(~XRP, ~USD),
+
4397 sendmax(EUR(400)),
+
4398 txflags(tfPartialPayment | tfNoRippleDirect));
+
4399 BEAST_EXPECT(ammAlice.expectBalances(
+
4400 XRPAmount{10'101'010'102}, USD(9'900), ammAlice.tokens()));
+
4401 if (!features[fixAMMv1_1])
+
4402 {
+
4403 // Carol gets ~100USD
+
4404 BEAST_EXPECT(expectLine(
+
4405 env,
+
4406 carol,
+
4407 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
+
4408 }
+
4409 else
+
4410 {
+
4411 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
4412 }
+
4413 BEAST_EXPECT(expectOffers(
+
4414 env,
+
4415 alice,
+
4416 1,
+
4417 {{{STAmount{EUR, UINT64_C(39'1858572), -7},
+
4418 XRPAmount{27'989'898}}}}));
+
4419 },
+
4420 std::nullopt,
+
4421 0,
+
4422 std::nullopt,
+
4423 {features});
+
4424
+
4425 // Offer crossing with AMM and another offer. AMM has a better
+
4426 // quality and is consumed first.
+
4427 {
+
4428 Env env(*this, features);
+
4429 fund(env, gw, {alice, carol, bob}, XRP(30'000), {USD(30'000)});
+
4430 env(offer(bob, XRP(100), USD(100.001)));
+
4431 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
+
4432 env(offer(carol, USD(100), XRP(100)));
+
4433 if (!features[fixAMMv1_1])
+
4434 {
+
4435 BEAST_EXPECT(ammAlice.expectBalances(
+
4436 XRPAmount{10'049'825'373},
+
4437 STAmount{USD, UINT64_C(10'049'92586949302), -11},
+
4438 ammAlice.tokens()));
+
4439 BEAST_EXPECT(expectOffers(
+
4440 env,
+
4441 bob,
+
4442 1,
+
4443 {{{XRPAmount{50'074'629},
+
4444 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
+
4445 }
+
4446 else
+
4447 {
+
4448 BEAST_EXPECT(ammAlice.expectBalances(
+
4449 XRPAmount{10'049'825'372},
+
4450 STAmount{USD, UINT64_C(10'049'92587049303), -11},
+
4451 ammAlice.tokens()));
+
4452 BEAST_EXPECT(expectOffers(
+
4453 env,
+
4454 bob,
+
4455 1,
+
4456 {{{XRPAmount{50'074'628},
+
4457 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
+
4458 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
+
4459 }
+
4460 }
+
4461
+
4462 // Individually frozen account
+
4463 testAMM(
+
4464 [&](AMM& ammAlice, Env& env) {
+
4465 env(trust(gw, carol["USD"](0), tfSetFreeze));
+
4466 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
4467 env.close();
+
4468 env(pay(alice, carol, USD(1)),
+
4469 path(~USD),
+
4470 sendmax(XRP(10)),
+
4471 txflags(tfNoRippleDirect | tfPartialPayment),
+
4472 ter(tesSUCCESS));
+
4473 },
+
4474 std::nullopt,
+
4475 0,
+
4476 std::nullopt,
+
4477 {features});
+
4478 }
+
4479
+
4480 void
+
4481 testAMMTokens()
+
4482 {
+
4483 testcase("AMM Tokens");
+
4484 using namespace jtx;
+
4485
+
4486 // Offer crossing with AMM LPTokens and XRP.
+
4487 testAMM([&](AMM& ammAlice, Env& env) {
+
4488 auto const baseFee = env.current()->fees().base.drops();
+
4489 auto const token1 = ammAlice.lptIssue();
+
4490 auto priceXRP = withdrawByTokens(
+
4491 STAmount{XRPAmount{10'000'000'000}},
+
4492 STAmount{token1, 10'000'000},
+
4493 STAmount{token1, 5'000'000},
+
4494 0);
+
4495 // Carol places an order to buy LPTokens
+
4496 env(offer(carol, STAmount{token1, 5'000'000}, priceXRP));
+
4497 // Alice places an order to sell LPTokens
+
4498 env(offer(alice, priceXRP, STAmount{token1, 5'000'000}));
+
4499 // Pool's LPTokens balance doesn't change
+
4500 BEAST_EXPECT(ammAlice.expectBalances(
+
4501 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
4502 // Carol is Liquidity Provider
+
4503 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000}));
+
4504 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
+
4505 // Carol votes
+
4506 ammAlice.vote(carol, 1'000);
+
4507 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
4508 ammAlice.vote(carol, 0);
+
4509 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4510 // Carol bids
+
4511 env(ammAlice.bid({.account = carol, .bidMin = 100}));
+
4512 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
+
4513 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
+
4514 BEAST_EXPECT(
+
4515 accountBalance(env, carol) ==
+
4516 std::to_string(22500000000 - 4 * baseFee));
+
4517 priceXRP = withdrawByTokens(
+
4518 STAmount{XRPAmount{10'000'000'000}},
+
4519 STAmount{token1, 9'999'900},
+
4520 STAmount{token1, 4'999'900},
+
4521 0);
+
4522 // Carol withdraws
+
4523 ammAlice.withdrawAll(carol, XRP(0));
+
4524 BEAST_EXPECT(
+
4525 accountBalance(env, carol) ==
+
4526 std::to_string(29999949999 - 5 * baseFee));
+
4527 BEAST_EXPECT(ammAlice.expectBalances(
+
4528 XRPAmount{10'000'000'000} - priceXRP,
+
4529 USD(10'000),
+
4530 IOUAmount{5'000'000}));
+
4531 BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
+
4532 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
4533 });
+
4534
+
4535 // Offer crossing with two AMM LPTokens.
+
4536 testAMM([&](AMM& ammAlice, Env& env) {
+
4537 ammAlice.deposit(carol, 1'000'000);
+
4538 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
+
4539 AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
+
4540 ammAlice1.deposit(carol, 1'000'000);
+
4541 auto const token1 = ammAlice.lptIssue();
+
4542 auto const token2 = ammAlice1.lptIssue();
+
4543 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
+
4544 txflags(tfPassive));
+
4545 env.close();
+
4546 BEAST_EXPECT(expectOffers(env, alice, 1));
+
4547 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
+
4548 env.close();
+
4549 BEAST_EXPECT(
+
4550 expectLine(env, alice, STAmount{token1, 10'000'100}) &&
+
4551 expectLine(env, alice, STAmount{token2, 9'999'900}));
+
4552 BEAST_EXPECT(
+
4553 expectLine(env, carol, STAmount{token2, 1'000'100}) &&
+
4554 expectLine(env, carol, STAmount{token1, 999'900}));
+
4555 BEAST_EXPECT(
+
4556 expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
+
4557 });
+
4558
+
4559 // LPs pay LPTokens directly. Must trust set because the trust line
+
4560 // is checked for the limit, which is 0 in the AMM auto-created
+
4561 // trust line.
+
4562 testAMM([&](AMM& ammAlice, Env& env) {
+
4563 auto const token1 = ammAlice.lptIssue();
+
4564 env.trust(STAmount{token1, 2'000'000}, carol);
+
4565 env.close();
+
4566 ammAlice.deposit(carol, 1'000'000);
+
4567 BEAST_EXPECT(
+
4568 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
+
4569 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
4570 // Pool balance doesn't change, only tokens moved from
+
4571 // one line to another.
+
4572 env(pay(alice, carol, STAmount{token1, 100}));
+
4573 env.close();
+
4574 BEAST_EXPECT(
+
4575 // Alice initial token1 10,000,000 - 100
+
4576 ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) &&
+
4577 // Carol initial token1 1,000,000 + 100
+
4578 ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0}));
+
4579
+
4580 env.trust(STAmount{token1, 20'000'000}, alice);
+
4581 env.close();
+
4582 env(pay(carol, alice, STAmount{token1, 100}));
+
4583 env.close();
+
4584 // Back to the original balance
+
4585 BEAST_EXPECT(
+
4586 ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
+
4587 ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
+
4588 });
+
4589 }
+
4590
+
4591 void
+
4592 testAmendment()
+
4593 {
+
4594 testcase("Amendment");
+
4595 using namespace jtx;
+
4596 FeatureBitset const all{supported_amendments()};
+
4597 FeatureBitset const noAMM{all - featureAMM};
+
4598 FeatureBitset const noNumber{all - fixUniversalNumber};
+
4599 FeatureBitset const noAMMAndNumber{
+
4600 all - featureAMM - fixUniversalNumber};
+
4601
+
4602 for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
+
4603 {
+
4604 Env env{*this, feature};
+
4605 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
+
4606 AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
+
4607
+
4608 env(amm.bid({.bidMax = 1000}), ter(temMALFORMED));
+
4609 env(amm.bid({}), ter(temDISABLED));
+
4610 amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)});
+
4611 amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)});
+
4612 amm.withdraw(WithdrawArg{.err = ter(temDISABLED)});
+
4613 amm.deposit(
+
4614 DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)});
+
4615 amm.ammDelete(alice, ter(temDISABLED));
+
4616 }
+
4617 }
+
4618
+
4619 void
+
4620 testFlags()
+
4621 {
+
4622 testcase("Flags");
+
4623 using namespace jtx;
+
4624
+
4625 testAMM([&](AMM& ammAlice, Env& env) {
+
4626 auto const info = env.rpc(
+
4627 "json",
+
4628 "account_info",
+
4629 std::string(
+
4630 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
+
4631 "\"}"));
+
4632 auto const flags =
+
4633 info[jss::result][jss::account_data][jss::Flags].asUInt();
+
4634 BEAST_EXPECT(
+
4635 flags ==
+
4636 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
+
4637 });
+
4638 }
+
4639
+
4640 void
+
4641 testRippling()
+
4642 {
+
4643 testcase("Rippling");
+
4644 using namespace jtx;
+
4645
+
4646 // Rippling via AMM fails because AMM trust line has 0 limit.
+
4647 // Set up two issuers, A and B. Have each issue a token called TST.
+
4648 // Have another account C hold TST from both issuers,
+
4649 // and create an AMM for this pair.
+
4650 // Have a fourth account, D, create a trust line to the AMM for TST.
+
4651 // Send a payment delivering TST.AMM from C to D, using SendMax in
+
4652 // TST.A (or B) and a path through the AMM account. By normal
+
4653 // rippling rules, this would have caused the AMM's balances
+
4654 // to shift at a 1:1 rate with no fee applied has it not been
+
4655 // for 0 limit.
+
4656 {
+
4657 Env env(*this);
+
4658 auto const A = Account("A");
+
4659 auto const B = Account("B");
+
4660 auto const TSTA = A["TST"];
+
4661 auto const TSTB = B["TST"];
+
4662 auto const C = Account("C");
+
4663 auto const D = Account("D");
+
4664
+
4665 env.fund(XRP(10'000), A);
+
4666 env.fund(XRP(10'000), B);
+
4667 env.fund(XRP(10'000), C);
+
4668 env.fund(XRP(10'000), D);
+
4669
+
4670 env.trust(TSTA(10'000), C);
+
4671 env.trust(TSTB(10'000), C);
+
4672 env(pay(A, C, TSTA(10'000)));
+
4673 env(pay(B, C, TSTB(10'000)));
+
4674 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
+
4675 auto const ammIss = Issue(TSTA.currency, amm.ammAccount());
+
4676
+
4677 // Can SetTrust only for AMM LP tokens
+
4678 env(trust(D, STAmount{ammIss, 10'000}), ter(tecNO_PERMISSION));
+
4679 env.close();
+
4680
+
4681 // The payment would fail because of above, but check just in case
+
4682 env(pay(C, D, STAmount{ammIss, 10}),
+
4683 sendmax(TSTA(100)),
+
4684 path(amm.ammAccount()),
+
4685 txflags(tfPartialPayment | tfNoRippleDirect),
+
4686 ter(tecPATH_DRY));
+
4687 }
+
4688 }
+
4689
+
4690 void
+
4691 testAMMAndCLOB(FeatureBitset features)
+
4692 {
+
4693 testcase("AMMAndCLOB, offer quality change");
+
4694 using namespace jtx;
+
4695 auto const gw = Account("gw");
+
4696 auto const TST = gw["TST"];
+
4697 auto const LP1 = Account("LP1");
+
4698 auto const LP2 = Account("LP2");
4699
-
4700 expectCb(env);
-
4701 };
-
4702
-
4703 // If we replace AMM with an equivalent CLOB offer, which AMM generates
-
4704 // when it is consumed, then the result must be equivalent, too.
-
4705 std::string lp2TSTBalance;
-
4706 std::string lp2TakerGets;
-
4707 std::string lp2TakerPays;
-
4708 // Execute with AMM first
-
4709 prep(
-
4710 [&](Env& env) { AMM amm(env, LP1, TST(25), XRP(250)); },
-
4711 [&](Env& env) {
-
4712 lp2TSTBalance =
-
4713 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
-
4714 .asString();
-
4715 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
-
4716 lp2TakerGets = offer["taker_gets"].asString();
-
4717 lp2TakerPays = offer["taker_pays"]["value"].asString();
-
4718 });
-
4719 // Execute with CLOB offer
-
4720 prep(
-
4721 [&](Env& env) {
-
4722 if (!features[fixAMMv1_1])
-
4723 env(offer(
-
4724 LP1,
-
4725 XRPAmount{18'095'133},
-
4726 STAmount{TST, UINT64_C(1'68737984885388), -14}),
-
4727 txflags(tfPassive));
-
4728 else
-
4729 env(offer(
-
4730 LP1,
-
4731 XRPAmount{18'095'132},
-
4732 STAmount{TST, UINT64_C(1'68737976189735), -14}),
-
4733 txflags(tfPassive));
-
4734 },
+
4700 auto prep = [&](auto const& offerCb, auto const& expectCb) {
+
4701 Env env(*this, features);
+
4702 env.fund(XRP(30'000'000'000), gw);
+
4703 env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000)));
+
4704
+
4705 env.fund(XRP(10'000), LP1);
+
4706 env.fund(XRP(10'000), LP2);
+
4707 env(offer(LP1, TST(25), XRPAmount(287'500'000)));
+
4708
+
4709 // Either AMM or CLOB offer
+
4710 offerCb(env);
+
4711
+
4712 env(offer(LP2, TST(25), XRPAmount(287'500'000)));
+
4713
+
4714 expectCb(env);
+
4715 };
+
4716
+
4717 // If we replace AMM with an equivalent CLOB offer, which AMM generates
+
4718 // when it is consumed, then the result must be equivalent, too.
+
4719 std::string lp2TSTBalance;
+
4720 std::string lp2TakerGets;
+
4721 std::string lp2TakerPays;
+
4722 // Execute with AMM first
+
4723 prep(
+
4724 [&](Env& env) { AMM amm(env, LP1, TST(25), XRP(250)); },
+
4725 [&](Env& env) {
+
4726 lp2TSTBalance =
+
4727 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
+
4728 .asString();
+
4729 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
+
4730 lp2TakerGets = offer["taker_gets"].asString();
+
4731 lp2TakerPays = offer["taker_pays"]["value"].asString();
+
4732 });
+
4733 // Execute with CLOB offer
+
4734 prep(
4735 [&](Env& env) {
-
4736 BEAST_EXPECT(
-
4737 lp2TSTBalance ==
-
4738 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
-
4739 .asString());
-
4740 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
-
4741 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
-
4742 BEAST_EXPECT(
-
4743 lp2TakerPays == offer["taker_pays"]["value"].asString());
-
4744 });
-
4745 }
-
4746
-
4747 void
-
4748 testTradingFee(FeatureBitset features)
-
4749 {
-
4750 testcase("Trading Fee");
-
4751 using namespace jtx;
-
4752
-
4753 // Single Deposit, 1% fee
-
4754 testAMM(
-
4755 [&](AMM& ammAlice, Env& env) {
-
4756 // No fee
-
4757 ammAlice.deposit(carol, USD(3'000));
-
4758 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
-
4759 ammAlice.withdrawAll(carol, USD(3'000));
-
4760 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
-
4761 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4762 // Set fee to 1%
-
4763 ammAlice.vote(alice, 1'000);
-
4764 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4765 // Carol gets fewer LPToken ~994, because of the single deposit
-
4766 // fee
-
4767 ammAlice.deposit(carol, USD(3'000));
-
4768 BEAST_EXPECT(ammAlice.expectLPTokens(
-
4769 carol, IOUAmount{994'981155689671, -12}));
-
4770 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
-
4771 // Set fee to 0
-
4772 ammAlice.vote(alice, 0);
-
4773 ammAlice.withdrawAll(carol, USD(0));
-
4774 // Carol gets back less than the original deposit
-
4775 BEAST_EXPECT(expectLine(
-
4776 env,
-
4777 carol,
-
4778 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
-
4779 },
-
4780 {{USD(1'000), EUR(1'000)}},
-
4781 0,
-
4782 std::nullopt,
-
4783 {features});
-
4784
-
4785 // Single deposit with EP not exceeding specified:
-
4786 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee.
-
4787 testAMM(
-
4788 [&](AMM& ammAlice, Env& env) {
-
4789 auto const balance = env.balance(carol, USD);
-
4790 auto tokensFee = ammAlice.deposit(
-
4791 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
-
4792 auto const deposit = balance - env.balance(carol, USD);
-
4793 ammAlice.withdrawAll(carol, USD(0));
-
4794 ammAlice.vote(alice, 0);
-
4795 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4796 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
-
4797 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
-
4798 // LPTokens
-
4799 BEAST_EXPECT(tokensFee == IOUAmount(485'636'0611129, -7));
-
4800 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644'85901109, -8));
-
4801 },
-
4802 std::nullopt,
-
4803 1'000,
-
4804 std::nullopt,
-
4805 {features});
-
4806
-
4807 // Single deposit with EP not exceeding specified:
-
4808 // 200USD with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee
-
4809 testAMM(
-
4810 [&](AMM& ammAlice, Env& env) {
-
4811 auto const balance = env.balance(carol, USD);
-
4812 auto const tokensFee = ammAlice.deposit(
-
4813 carol, USD(200), std::nullopt, STAmount{USD, 2020, -6});
-
4814 auto const deposit = balance - env.balance(carol, USD);
-
4815 ammAlice.withdrawAll(carol, USD(0));
-
4816 ammAlice.vote(alice, 0);
-
4817 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4818 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
-
4819 // carol pays ~475 LPTokens in fees or ~0.5% of the no-fee
-
4820 // LPTokens
-
4821 BEAST_EXPECT(tokensFee == IOUAmount(98'000'00000002, -8));
-
4822 BEAST_EXPECT(tokensNoFee == IOUAmount(98'475'81871545, -8));
-
4823 },
-
4824 std::nullopt,
-
4825 1'000,
-
4826 std::nullopt,
-
4827 {features});
-
4828
-
4829 // Single Withdrawal, 1% fee
-
4830 testAMM(
-
4831 [&](AMM& ammAlice, Env& env) {
-
4832 // No fee
-
4833 ammAlice.deposit(carol, USD(3'000));
-
4834
-
4835 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
-
4836 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
-
4837 // Set fee to 1%
-
4838 ammAlice.vote(alice, 1'000);
-
4839 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4840 // Single withdrawal. Carol gets ~5USD less than deposited.
-
4841 ammAlice.withdrawAll(carol, USD(0));
-
4842 BEAST_EXPECT(expectLine(
-
4843 env,
-
4844 carol,
-
4845 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
-
4846 },
-
4847 {{USD(1'000), EUR(1'000)}},
-
4848 0,
-
4849 std::nullopt,
-
4850 {features});
-
4851
-
4852 // Withdraw with EPrice limit, 1% fee.
-
4853 testAMM(
-
4854 [&](AMM& ammAlice, Env& env) {
-
4855 ammAlice.deposit(carol, 1'000'000);
-
4856 auto const tokensFee = ammAlice.withdraw(
-
4857 carol, USD(100), std::nullopt, IOUAmount{520, 0});
-
4858 // carol withdraws ~1,443.44USD
-
4859 auto const balanceAfterWithdraw = [&]() {
-
4860 if (!features[fixAMMv1_1])
-
4861 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
-
4862 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
-
4863 }();
-
4864 BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw);
-
4865 // Set to original pool size
-
4866 auto const deposit = balanceAfterWithdraw - USD(29'000);
-
4867 ammAlice.deposit(carol, deposit);
-
4868 // fee 0%
-
4869 ammAlice.vote(alice, 0);
-
4870 BEAST_EXPECT(ammAlice.expectTradingFee(0));
-
4871 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
-
4872 if (!features[fixAMMv1_1])
-
4873 BEAST_EXPECT(
-
4874 env.balance(carol, USD) ==
-
4875 STAmount(USD, UINT64_C(30'443'43891402717), -11));
-
4876 else
-
4877 BEAST_EXPECT(
-
4878 env.balance(carol, USD) ==
-
4879 STAmount(USD, UINT64_C(30'443'43891402716), -11));
-
4880 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
-
4881 // LPTokens
-
4882 if (!features[fixAMMv1_1])
-
4883 BEAST_EXPECT(
-
4884 tokensNoFee == IOUAmount(746'579'80779913, -8));
-
4885 else
-
4886 BEAST_EXPECT(
-
4887 tokensNoFee == IOUAmount(746'579'80779912, -8));
-
4888 BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8));
-
4889 },
-
4890 std::nullopt,
-
4891 1'000,
-
4892 std::nullopt,
-
4893 {features});
-
4894
-
4895 // Payment, 1% fee
-
4896 testAMM(
-
4897 [&](AMM& ammAlice, Env& env) {
-
4898 fund(
-
4899 env,
-
4900 gw,
-
4901 {bob},
-
4902 XRP(1'000),
-
4903 {USD(1'000), EUR(1'000)},
-
4904 Fund::Acct);
-
4905 // Alice contributed 1010EUR and 1000USD to the pool
-
4906 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
-
4907 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
-
4908 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4909 // Carol pays to Alice with no fee
-
4910 env(pay(carol, alice, EUR(10)),
-
4911 path(~EUR),
-
4912 sendmax(USD(10)),
-
4913 txflags(tfNoRippleDirect));
-
4914 env.close();
-
4915 // Alice has 10EUR more and Carol has 10USD less
-
4916 BEAST_EXPECT(expectLine(env, alice, EUR(29'000)));
-
4917 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
-
4918 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
-
4919
-
4920 // Set fee to 1%
-
4921 ammAlice.vote(alice, 1'000);
-
4922 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
-
4923 // Bob pays to Carol with 1% fee
-
4924 env(pay(bob, carol, USD(10)),
-
4925 path(~USD),
-
4926 sendmax(EUR(15)),
+
4736 if (!features[fixAMMv1_1])
+
4737 env(offer(
+
4738 LP1,
+
4739 XRPAmount{18'095'133},
+
4740 STAmount{TST, UINT64_C(1'68737984885388), -14}),
+
4741 txflags(tfPassive));
+
4742 else
+
4743 env(offer(
+
4744 LP1,
+
4745 XRPAmount{18'095'132},
+
4746 STAmount{TST, UINT64_C(1'68737976189735), -14}),
+
4747 txflags(tfPassive));
+
4748 },
+
4749 [&](Env& env) {
+
4750 BEAST_EXPECT(
+
4751 lp2TSTBalance ==
+
4752 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
+
4753 .asString());
+
4754 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
+
4755 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
+
4756 BEAST_EXPECT(
+
4757 lp2TakerPays == offer["taker_pays"]["value"].asString());
+
4758 });
+
4759 }
+
4760
+
4761 void
+
4762 testTradingFee(FeatureBitset features)
+
4763 {
+
4764 testcase("Trading Fee");
+
4765 using namespace jtx;
+
4766
+
4767 // Single Deposit, 1% fee
+
4768 testAMM(
+
4769 [&](AMM& ammAlice, Env& env) {
+
4770 // No fee
+
4771 ammAlice.deposit(carol, USD(3'000));
+
4772 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
+
4773 ammAlice.withdrawAll(carol, USD(3'000));
+
4774 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
+
4775 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
4776 // Set fee to 1%
+
4777 ammAlice.vote(alice, 1'000);
+
4778 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
4779 // Carol gets fewer LPToken ~994, because of the single deposit
+
4780 // fee
+
4781 ammAlice.deposit(carol, USD(3'000));
+
4782 BEAST_EXPECT(ammAlice.expectLPTokens(
+
4783 carol, IOUAmount{994'981155689671, -12}));
+
4784 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
+
4785 // Set fee to 0
+
4786 ammAlice.vote(alice, 0);
+
4787 ammAlice.withdrawAll(carol, USD(0));
+
4788 // Carol gets back less than the original deposit
+
4789 BEAST_EXPECT(expectLine(
+
4790 env,
+
4791 carol,
+
4792 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
+
4793 },
+
4794 {{USD(1'000), EUR(1'000)}},
+
4795 0,
+
4796 std::nullopt,
+
4797 {features});
+
4798
+
4799 // Single deposit with EP not exceeding specified:
+
4800 // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee.
+
4801 testAMM(
+
4802 [&](AMM& ammAlice, Env& env) {
+
4803 auto const balance = env.balance(carol, USD);
+
4804 auto tokensFee = ammAlice.deposit(
+
4805 carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
+
4806 auto const deposit = balance - env.balance(carol, USD);
+
4807 ammAlice.withdrawAll(carol, USD(0));
+
4808 ammAlice.vote(alice, 0);
+
4809 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4810 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
+
4811 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
+
4812 // LPTokens
+
4813 BEAST_EXPECT(tokensFee == IOUAmount(485'636'0611129, -7));
+
4814 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644'85901109, -8));
+
4815 },
+
4816 std::nullopt,
+
4817 1'000,
+
4818 std::nullopt,
+
4819 {features});
+
4820
+
4821 // Single deposit with EP not exceeding specified:
+
4822 // 200USD with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee
+
4823 testAMM(
+
4824 [&](AMM& ammAlice, Env& env) {
+
4825 auto const balance = env.balance(carol, USD);
+
4826 auto const tokensFee = ammAlice.deposit(
+
4827 carol, USD(200), std::nullopt, STAmount{USD, 2020, -6});
+
4828 auto const deposit = balance - env.balance(carol, USD);
+
4829 ammAlice.withdrawAll(carol, USD(0));
+
4830 ammAlice.vote(alice, 0);
+
4831 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4832 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
+
4833 // carol pays ~475 LPTokens in fees or ~0.5% of the no-fee
+
4834 // LPTokens
+
4835 BEAST_EXPECT(tokensFee == IOUAmount(98'000'00000002, -8));
+
4836 BEAST_EXPECT(tokensNoFee == IOUAmount(98'475'81871545, -8));
+
4837 },
+
4838 std::nullopt,
+
4839 1'000,
+
4840 std::nullopt,
+
4841 {features});
+
4842
+
4843 // Single Withdrawal, 1% fee
+
4844 testAMM(
+
4845 [&](AMM& ammAlice, Env& env) {
+
4846 // No fee
+
4847 ammAlice.deposit(carol, USD(3'000));
+
4848
+
4849 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
+
4850 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
+
4851 // Set fee to 1%
+
4852 ammAlice.vote(alice, 1'000);
+
4853 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
4854 // Single withdrawal. Carol gets ~5USD less than deposited.
+
4855 ammAlice.withdrawAll(carol, USD(0));
+
4856 BEAST_EXPECT(expectLine(
+
4857 env,
+
4858 carol,
+
4859 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
+
4860 },
+
4861 {{USD(1'000), EUR(1'000)}},
+
4862 0,
+
4863 std::nullopt,
+
4864 {features});
+
4865
+
4866 // Withdraw with EPrice limit, 1% fee.
+
4867 testAMM(
+
4868 [&](AMM& ammAlice, Env& env) {
+
4869 ammAlice.deposit(carol, 1'000'000);
+
4870 auto const tokensFee = ammAlice.withdraw(
+
4871 carol, USD(100), std::nullopt, IOUAmount{520, 0});
+
4872 // carol withdraws ~1,443.44USD
+
4873 auto const balanceAfterWithdraw = [&]() {
+
4874 if (!features[fixAMMv1_1])
+
4875 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
+
4876 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
+
4877 }();
+
4878 BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw);
+
4879 // Set to original pool size
+
4880 auto const deposit = balanceAfterWithdraw - USD(29'000);
+
4881 ammAlice.deposit(carol, deposit);
+
4882 // fee 0%
+
4883 ammAlice.vote(alice, 0);
+
4884 BEAST_EXPECT(ammAlice.expectTradingFee(0));
+
4885 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
+
4886 if (!features[fixAMMv1_1])
+
4887 BEAST_EXPECT(
+
4888 env.balance(carol, USD) ==
+
4889 STAmount(USD, UINT64_C(30'443'43891402717), -11));
+
4890 else
+
4891 BEAST_EXPECT(
+
4892 env.balance(carol, USD) ==
+
4893 STAmount(USD, UINT64_C(30'443'43891402716), -11));
+
4894 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
+
4895 // LPTokens
+
4896 if (!features[fixAMMv1_1])
+
4897 BEAST_EXPECT(
+
4898 tokensNoFee == IOUAmount(746'579'80779913, -8));
+
4899 else
+
4900 BEAST_EXPECT(
+
4901 tokensNoFee == IOUAmount(746'579'80779912, -8));
+
4902 BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8));
+
4903 },
+
4904 std::nullopt,
+
4905 1'000,
+
4906 std::nullopt,
+
4907 {features});
+
4908
+
4909 // Payment, 1% fee
+
4910 testAMM(
+
4911 [&](AMM& ammAlice, Env& env) {
+
4912 fund(
+
4913 env,
+
4914 gw,
+
4915 {bob},
+
4916 XRP(1'000),
+
4917 {USD(1'000), EUR(1'000)},
+
4918 Fund::Acct);
+
4919 // Alice contributed 1010EUR and 1000USD to the pool
+
4920 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
+
4921 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
+
4922 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
4923 // Carol pays to Alice with no fee
+
4924 env(pay(carol, alice, EUR(10)),
+
4925 path(~EUR),
+
4926 sendmax(USD(10)),
4927 txflags(tfNoRippleDirect));
4928 env.close();
-
4929 // Bob sends 10.1~EUR to pay 10USD
-
4930 BEAST_EXPECT(expectLine(
-
4931 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
-
4932 // Carol got 10USD
-
4933 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
4934 BEAST_EXPECT(ammAlice.expectBalances(
-
4935 USD(1'000),
-
4936 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
-
4937 ammAlice.tokens()));
-
4938 },
-
4939 {{USD(1'000), EUR(1'010)}},
-
4940 0,
-
4941 std::nullopt,
-
4942 {features});
-
4943
-
4944 // Offer crossing, 0.5% fee
-
4945 testAMM(
-
4946 [&](AMM& ammAlice, Env& env) {
-
4947 // No fee
-
4948 env(offer(carol, EUR(10), USD(10)));
-
4949 env.close();
-
4950 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
-
4951 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
-
4952 // Change pool composition back
-
4953 env(offer(carol, USD(10), EUR(10)));
-
4954 env.close();
-
4955 // Set fee to 0.5%
-
4956 ammAlice.vote(alice, 500);
-
4957 BEAST_EXPECT(ammAlice.expectTradingFee(500));
-
4958 env(offer(carol, EUR(10), USD(10)));
-
4959 env.close();
-
4960 // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes
-
4961 // to the pool
-
4962 BEAST_EXPECT(expectLine(
-
4963 env,
-
4964 carol,
-
4965 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
-
4966 BEAST_EXPECT(expectLine(
-
4967 env,
-
4968 carol,
-
4969 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
-
4970 BEAST_EXPECT(expectOffers(
-
4971 env,
-
4972 carol,
-
4973 1,
-
4974 {{Amounts{
-
4975 STAmount{EUR, UINT64_C(5'025125628140703), -15},
-
4976 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
-
4977 if (!features[fixAMMv1_1])
-
4978 {
-
4979 BEAST_EXPECT(ammAlice.expectBalances(
-
4980 STAmount{USD, UINT64_C(1'004'974874371859), -12},
-
4981 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
-
4982 ammAlice.tokens()));
-
4983 }
-
4984 else
-
4985 {
-
4986 BEAST_EXPECT(ammAlice.expectBalances(
-
4987 STAmount{USD, UINT64_C(1'004'97487437186), -11},
-
4988 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
-
4989 ammAlice.tokens()));
-
4990 }
-
4991 },
-
4992 {{USD(1'000), EUR(1'010)}},
-
4993 0,
-
4994 std::nullopt,
-
4995 {features});
-
4996
-
4997 // Payment with AMM and CLOB offer, 0 fee
-
4998 // AMM liquidity is consumed first up to CLOB offer quality
-
4999 // CLOB offer is fully consumed next
-
5000 // Remaining amount is consumed via AMM liquidity
-
5001 {
-
5002 Env env(*this, features);
-
5003 Account const ed("ed");
-
5004 fund(
-
5005 env,
-
5006 gw,
-
5007 {alice, bob, carol, ed},
-
5008 XRP(1'000),
-
5009 {USD(2'000), EUR(2'000)});
-
5010 env(offer(carol, EUR(5), USD(5)));
-
5011 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
-
5012 env(pay(bob, ed, USD(10)),
-
5013 path(~USD),
-
5014 sendmax(EUR(15)),
-
5015 txflags(tfNoRippleDirect));
-
5016 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5017 if (!features[fixAMMv1_1])
-
5018 {
-
5019 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
-
5020 BEAST_EXPECT(ammAlice.expectBalances(
-
5021 USD(1'000), EUR(1'005), ammAlice.tokens()));
-
5022 }
-
5023 else
-
5024 {
-
5025 BEAST_EXPECT(expectLine(
-
5026 env, bob, STAmount(EUR, UINT64_C(1989'999999999999), -12)));
-
5027 BEAST_EXPECT(ammAlice.expectBalances(
-
5028 USD(1'000),
-
5029 STAmount(EUR, UINT64_C(1005'000000000001), -12),
-
5030 ammAlice.tokens()));
-
5031 }
-
5032 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5033 }
-
5034
-
5035 // Payment with AMM and CLOB offer. Same as above but with 0.25%
-
5036 // fee.
-
5037 {
-
5038 Env env(*this, features);
-
5039 Account const ed("ed");
-
5040 fund(
-
5041 env,
-
5042 gw,
-
5043 {alice, bob, carol, ed},
-
5044 XRP(1'000),
-
5045 {USD(2'000), EUR(2'000)});
-
5046 env(offer(carol, EUR(5), USD(5)));
-
5047 // Set 0.25% fee
-
5048 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 250);
-
5049 env(pay(bob, ed, USD(10)),
-
5050 path(~USD),
-
5051 sendmax(EUR(15)),
-
5052 txflags(tfNoRippleDirect));
-
5053 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5054 if (!features[fixAMMv1_1])
-
5055 {
-
5056 BEAST_EXPECT(expectLine(
-
5057 env,
-
5058 bob,
-
5059 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
-
5060 BEAST_EXPECT(ammAlice.expectBalances(
-
5061 USD(1'000),
-
5062 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
-
5063 ammAlice.tokens()));
-
5064 }
-
5065 else
-
5066 {
-
5067 BEAST_EXPECT(expectLine(
-
5068 env,
-
5069 bob,
-
5070 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
-
5071 BEAST_EXPECT(ammAlice.expectBalances(
-
5072 USD(1'000),
-
5073 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
-
5074 ammAlice.tokens()));
-
5075 }
-
5076 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5077 }
-
5078
-
5079 // Payment with AMM and CLOB offer. AMM has a better
-
5080 // spot price quality, but 1% fee offsets that. As the result
-
5081 // the entire trade is executed via LOB.
-
5082 {
-
5083 Env env(*this, features);
-
5084 Account const ed("ed");
-
5085 fund(
-
5086 env,
-
5087 gw,
-
5088 {alice, bob, carol, ed},
-
5089 XRP(1'000),
-
5090 {USD(2'000), EUR(2'000)});
-
5091 env(offer(carol, EUR(10), USD(10)));
-
5092 // Set 1% fee
-
5093 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
-
5094 env(pay(bob, ed, USD(10)),
-
5095 path(~USD),
-
5096 sendmax(EUR(15)),
-
5097 txflags(tfNoRippleDirect));
-
5098 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5099 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
-
5100 BEAST_EXPECT(ammAlice.expectBalances(
-
5101 USD(1'005), EUR(1'000), ammAlice.tokens()));
-
5102 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5103 }
-
5104
-
5105 // Payment with AMM and CLOB offer. AMM has a better
-
5106 // spot price quality, but 1% fee offsets that.
-
5107 // The CLOB offer is consumed first and the remaining
-
5108 // amount is consumed via AMM liquidity.
-
5109 {
-
5110 Env env(*this, features);
-
5111 Account const ed("ed");
-
5112 fund(
-
5113 env,
-
5114 gw,
-
5115 {alice, bob, carol, ed},
-
5116 XRP(1'000),
-
5117 {USD(2'000), EUR(2'000)});
-
5118 env(offer(carol, EUR(9), USD(9)));
-
5119 // Set 1% fee
-
5120 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
-
5121 env(pay(bob, ed, USD(10)),
-
5122 path(~USD),
-
5123 sendmax(EUR(15)),
-
5124 txflags(tfNoRippleDirect));
-
5125 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
-
5126 BEAST_EXPECT(expectLine(
-
5127 env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
-
5128 BEAST_EXPECT(ammAlice.expectBalances(
-
5129 USD(1'004),
-
5130 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
-
5131 ammAlice.tokens()));
-
5132 BEAST_EXPECT(expectOffers(env, carol, 0));
-
5133 }
-
5134 }
-
5135
-
5136 void
-
5137 testAdjustedTokens(FeatureBitset features)
-
5138 {
-
5139 testcase("Adjusted Deposit/Withdraw Tokens");
-
5140
-
5141 using namespace jtx;
-
5142
-
5143 // Deposit/Withdraw in USD
-
5144 testAMM(
-
5145 [&](AMM& ammAlice, Env& env) {
-
5146 Account const bob("bob");
-
5147 Account const ed("ed");
-
5148 Account const paul("paul");
-
5149 Account const dan("dan");
-
5150 Account const chris("chris");
-
5151 Account const simon("simon");
-
5152 Account const ben("ben");
-
5153 Account const nataly("nataly");
-
5154 fund(
-
5155 env,
-
5156 gw,
-
5157 {bob, ed, paul, dan, chris, simon, ben, nataly},
-
5158 {USD(1'500'000)},
-
5159 Fund::Acct);
-
5160 for (int i = 0; i < 10; ++i)
-
5161 {
-
5162 ammAlice.deposit(ben, STAmount{USD, 1, -10});
-
5163 ammAlice.withdrawAll(ben, USD(0));
-
5164 ammAlice.deposit(simon, USD(0.1));
-
5165 ammAlice.withdrawAll(simon, USD(0));
-
5166 ammAlice.deposit(chris, USD(1));
-
5167 ammAlice.withdrawAll(chris, USD(0));
-
5168 ammAlice.deposit(dan, USD(10));
-
5169 ammAlice.withdrawAll(dan, USD(0));
-
5170 ammAlice.deposit(bob, USD(100));
-
5171 ammAlice.withdrawAll(bob, USD(0));
-
5172 ammAlice.deposit(carol, USD(1'000));
-
5173 ammAlice.withdrawAll(carol, USD(0));
-
5174 ammAlice.deposit(ed, USD(10'000));
-
5175 ammAlice.withdrawAll(ed, USD(0));
-
5176 ammAlice.deposit(paul, USD(100'000));
-
5177 ammAlice.withdrawAll(paul, USD(0));
-
5178 ammAlice.deposit(nataly, USD(1'000'000));
-
5179 ammAlice.withdrawAll(nataly, USD(0));
-
5180 }
-
5181 // Due to round off some accounts have a tiny gain, while
-
5182 // other have a tiny loss. The last account to withdraw
-
5183 // gets everything in the pool.
-
5184 if (!features[fixAMMv1_1])
-
5185 BEAST_EXPECT(ammAlice.expectBalances(
-
5186 XRP(10'000),
-
5187 STAmount{USD, UINT64_C(10'000'0000000013), -10},
-
5188 IOUAmount{10'000'000}));
-
5189 else
-
5190 BEAST_EXPECT(ammAlice.expectBalances(
-
5191 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
5192 BEAST_EXPECT(expectLine(env, ben, USD(1'500'000)));
-
5193 BEAST_EXPECT(expectLine(env, simon, USD(1'500'000)));
-
5194 BEAST_EXPECT(expectLine(env, chris, USD(1'500'000)));
-
5195 BEAST_EXPECT(expectLine(env, dan, USD(1'500'000)));
-
5196 if (!features[fixAMMv1_1])
-
5197 BEAST_EXPECT(expectLine(
-
5198 env,
-
5199 carol,
-
5200 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
-
5201 else
-
5202 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
-
5203 BEAST_EXPECT(expectLine(env, ed, USD(1'500'000)));
-
5204 BEAST_EXPECT(expectLine(env, paul, USD(1'500'000)));
-
5205 if (!features[fixAMMv1_1])
-
5206 BEAST_EXPECT(expectLine(
-
5207 env,
-
5208 nataly,
-
5209 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
-
5210 else
+
4929 // Alice has 10EUR more and Carol has 10USD less
+
4930 BEAST_EXPECT(expectLine(env, alice, EUR(29'000)));
+
4931 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
+
4932 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
+
4933
+
4934 // Set fee to 1%
+
4935 ammAlice.vote(alice, 1'000);
+
4936 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
+
4937 // Bob pays to Carol with 1% fee
+
4938 env(pay(bob, carol, USD(10)),
+
4939 path(~USD),
+
4940 sendmax(EUR(15)),
+
4941 txflags(tfNoRippleDirect));
+
4942 env.close();
+
4943 // Bob sends 10.1~EUR to pay 10USD
+
4944 BEAST_EXPECT(expectLine(
+
4945 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
+
4946 // Carol got 10USD
+
4947 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
4948 BEAST_EXPECT(ammAlice.expectBalances(
+
4949 USD(1'000),
+
4950 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
+
4951 ammAlice.tokens()));
+
4952 },
+
4953 {{USD(1'000), EUR(1'010)}},
+
4954 0,
+
4955 std::nullopt,
+
4956 {features});
+
4957
+
4958 // Offer crossing, 0.5% fee
+
4959 testAMM(
+
4960 [&](AMM& ammAlice, Env& env) {
+
4961 // No fee
+
4962 env(offer(carol, EUR(10), USD(10)));
+
4963 env.close();
+
4964 BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
+
4965 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
+
4966 // Change pool composition back
+
4967 env(offer(carol, USD(10), EUR(10)));
+
4968 env.close();
+
4969 // Set fee to 0.5%
+
4970 ammAlice.vote(alice, 500);
+
4971 BEAST_EXPECT(ammAlice.expectTradingFee(500));
+
4972 env(offer(carol, EUR(10), USD(10)));
+
4973 env.close();
+
4974 // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes
+
4975 // to the pool
+
4976 BEAST_EXPECT(expectLine(
+
4977 env,
+
4978 carol,
+
4979 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
+
4980 BEAST_EXPECT(expectLine(
+
4981 env,
+
4982 carol,
+
4983 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
+
4984 BEAST_EXPECT(expectOffers(
+
4985 env,
+
4986 carol,
+
4987 1,
+
4988 {{Amounts{
+
4989 STAmount{EUR, UINT64_C(5'025125628140703), -15},
+
4990 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
+
4991 if (!features[fixAMMv1_1])
+
4992 {
+
4993 BEAST_EXPECT(ammAlice.expectBalances(
+
4994 STAmount{USD, UINT64_C(1'004'974874371859), -12},
+
4995 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
+
4996 ammAlice.tokens()));
+
4997 }
+
4998 else
+
4999 {
+
5000 BEAST_EXPECT(ammAlice.expectBalances(
+
5001 STAmount{USD, UINT64_C(1'004'97487437186), -11},
+
5002 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
+
5003 ammAlice.tokens()));
+
5004 }
+
5005 },
+
5006 {{USD(1'000), EUR(1'010)}},
+
5007 0,
+
5008 std::nullopt,
+
5009 {features});
+
5010
+
5011 // Payment with AMM and CLOB offer, 0 fee
+
5012 // AMM liquidity is consumed first up to CLOB offer quality
+
5013 // CLOB offer is fully consumed next
+
5014 // Remaining amount is consumed via AMM liquidity
+
5015 {
+
5016 Env env(*this, features);
+
5017 Account const ed("ed");
+
5018 fund(
+
5019 env,
+
5020 gw,
+
5021 {alice, bob, carol, ed},
+
5022 XRP(1'000),
+
5023 {USD(2'000), EUR(2'000)});
+
5024 env(offer(carol, EUR(5), USD(5)));
+
5025 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
+
5026 env(pay(bob, ed, USD(10)),
+
5027 path(~USD),
+
5028 sendmax(EUR(15)),
+
5029 txflags(tfNoRippleDirect));
+
5030 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5031 if (!features[fixAMMv1_1])
+
5032 {
+
5033 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
+
5034 BEAST_EXPECT(ammAlice.expectBalances(
+
5035 USD(1'000), EUR(1'005), ammAlice.tokens()));
+
5036 }
+
5037 else
+
5038 {
+
5039 BEAST_EXPECT(expectLine(
+
5040 env, bob, STAmount(EUR, UINT64_C(1989'999999999999), -12)));
+
5041 BEAST_EXPECT(ammAlice.expectBalances(
+
5042 USD(1'000),
+
5043 STAmount(EUR, UINT64_C(1005'000000000001), -12),
+
5044 ammAlice.tokens()));
+
5045 }
+
5046 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5047 }
+
5048
+
5049 // Payment with AMM and CLOB offer. Same as above but with 0.25%
+
5050 // fee.
+
5051 {
+
5052 Env env(*this, features);
+
5053 Account const ed("ed");
+
5054 fund(
+
5055 env,
+
5056 gw,
+
5057 {alice, bob, carol, ed},
+
5058 XRP(1'000),
+
5059 {USD(2'000), EUR(2'000)});
+
5060 env(offer(carol, EUR(5), USD(5)));
+
5061 // Set 0.25% fee
+
5062 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 250);
+
5063 env(pay(bob, ed, USD(10)),
+
5064 path(~USD),
+
5065 sendmax(EUR(15)),
+
5066 txflags(tfNoRippleDirect));
+
5067 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5068 if (!features[fixAMMv1_1])
+
5069 {
+
5070 BEAST_EXPECT(expectLine(
+
5071 env,
+
5072 bob,
+
5073 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
+
5074 BEAST_EXPECT(ammAlice.expectBalances(
+
5075 USD(1'000),
+
5076 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
+
5077 ammAlice.tokens()));
+
5078 }
+
5079 else
+
5080 {
+
5081 BEAST_EXPECT(expectLine(
+
5082 env,
+
5083 bob,
+
5084 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
+
5085 BEAST_EXPECT(ammAlice.expectBalances(
+
5086 USD(1'000),
+
5087 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
+
5088 ammAlice.tokens()));
+
5089 }
+
5090 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5091 }
+
5092
+
5093 // Payment with AMM and CLOB offer. AMM has a better
+
5094 // spot price quality, but 1% fee offsets that. As the result
+
5095 // the entire trade is executed via LOB.
+
5096 {
+
5097 Env env(*this, features);
+
5098 Account const ed("ed");
+
5099 fund(
+
5100 env,
+
5101 gw,
+
5102 {alice, bob, carol, ed},
+
5103 XRP(1'000),
+
5104 {USD(2'000), EUR(2'000)});
+
5105 env(offer(carol, EUR(10), USD(10)));
+
5106 // Set 1% fee
+
5107 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
+
5108 env(pay(bob, ed, USD(10)),
+
5109 path(~USD),
+
5110 sendmax(EUR(15)),
+
5111 txflags(tfNoRippleDirect));
+
5112 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5113 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
+
5114 BEAST_EXPECT(ammAlice.expectBalances(
+
5115 USD(1'005), EUR(1'000), ammAlice.tokens()));
+
5116 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5117 }
+
5118
+
5119 // Payment with AMM and CLOB offer. AMM has a better
+
5120 // spot price quality, but 1% fee offsets that.
+
5121 // The CLOB offer is consumed first and the remaining
+
5122 // amount is consumed via AMM liquidity.
+
5123 {
+
5124 Env env(*this, features);
+
5125 Account const ed("ed");
+
5126 fund(
+
5127 env,
+
5128 gw,
+
5129 {alice, bob, carol, ed},
+
5130 XRP(1'000),
+
5131 {USD(2'000), EUR(2'000)});
+
5132 env(offer(carol, EUR(9), USD(9)));
+
5133 // Set 1% fee
+
5134 AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
+
5135 env(pay(bob, ed, USD(10)),
+
5136 path(~USD),
+
5137 sendmax(EUR(15)),
+
5138 txflags(tfNoRippleDirect));
+
5139 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
+
5140 BEAST_EXPECT(expectLine(
+
5141 env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
+
5142 BEAST_EXPECT(ammAlice.expectBalances(
+
5143 USD(1'004),
+
5144 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
+
5145 ammAlice.tokens()));
+
5146 BEAST_EXPECT(expectOffers(env, carol, 0));
+
5147 }
+
5148 }
+
5149
+
5150 void
+
5151 testAdjustedTokens(FeatureBitset features)
+
5152 {
+
5153 testcase("Adjusted Deposit/Withdraw Tokens");
+
5154
+
5155 using namespace jtx;
+
5156
+
5157 // Deposit/Withdraw in USD
+
5158 testAMM(
+
5159 [&](AMM& ammAlice, Env& env) {
+
5160 Account const bob("bob");
+
5161 Account const ed("ed");
+
5162 Account const paul("paul");
+
5163 Account const dan("dan");
+
5164 Account const chris("chris");
+
5165 Account const simon("simon");
+
5166 Account const ben("ben");
+
5167 Account const nataly("nataly");
+
5168 fund(
+
5169 env,
+
5170 gw,
+
5171 {bob, ed, paul, dan, chris, simon, ben, nataly},
+
5172 {USD(1'500'000)},
+
5173 Fund::Acct);
+
5174 for (int i = 0; i < 10; ++i)
+
5175 {
+
5176 ammAlice.deposit(ben, STAmount{USD, 1, -10});
+
5177 ammAlice.withdrawAll(ben, USD(0));
+
5178 ammAlice.deposit(simon, USD(0.1));
+
5179 ammAlice.withdrawAll(simon, USD(0));
+
5180 ammAlice.deposit(chris, USD(1));
+
5181 ammAlice.withdrawAll(chris, USD(0));
+
5182 ammAlice.deposit(dan, USD(10));
+
5183 ammAlice.withdrawAll(dan, USD(0));
+
5184 ammAlice.deposit(bob, USD(100));
+
5185 ammAlice.withdrawAll(bob, USD(0));
+
5186 ammAlice.deposit(carol, USD(1'000));
+
5187 ammAlice.withdrawAll(carol, USD(0));
+
5188 ammAlice.deposit(ed, USD(10'000));
+
5189 ammAlice.withdrawAll(ed, USD(0));
+
5190 ammAlice.deposit(paul, USD(100'000));
+
5191 ammAlice.withdrawAll(paul, USD(0));
+
5192 ammAlice.deposit(nataly, USD(1'000'000));
+
5193 ammAlice.withdrawAll(nataly, USD(0));
+
5194 }
+
5195 // Due to round off some accounts have a tiny gain, while
+
5196 // other have a tiny loss. The last account to withdraw
+
5197 // gets everything in the pool.
+
5198 if (!features[fixAMMv1_1])
+
5199 BEAST_EXPECT(ammAlice.expectBalances(
+
5200 XRP(10'000),
+
5201 STAmount{USD, UINT64_C(10'000'0000000013), -10},
+
5202 IOUAmount{10'000'000}));
+
5203 else
+
5204 BEAST_EXPECT(ammAlice.expectBalances(
+
5205 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
5206 BEAST_EXPECT(expectLine(env, ben, USD(1'500'000)));
+
5207 BEAST_EXPECT(expectLine(env, simon, USD(1'500'000)));
+
5208 BEAST_EXPECT(expectLine(env, chris, USD(1'500'000)));
+
5209 BEAST_EXPECT(expectLine(env, dan, USD(1'500'000)));
+
5210 if (!features[fixAMMv1_1])
5211 BEAST_EXPECT(expectLine(
5212 env,
-
5213 nataly,
-
5214 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
-
5215 ammAlice.withdrawAll(alice);
-
5216 BEAST_EXPECT(!ammAlice.ammExists());
-
5217 if (!features[fixAMMv1_1])
-
5218 BEAST_EXPECT(expectLine(
-
5219 env,
-
5220 alice,
-
5221 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
-
5222 else
-
5223 BEAST_EXPECT(expectLine(env, alice, USD(30'000)));
-
5224 // alice XRP balance is 30,000initial - 50 ammcreate fee -
-
5225 // 10drops fee
-
5226 BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
-
5227 },
-
5228 std::nullopt,
-
5229 0,
-
5230 std::nullopt,
-
5231 {features});
-
5232
-
5233 // Same as above but deposit/withdraw in XRP
-
5234 testAMM([&](AMM& ammAlice, Env& env) {
-
5235 Account const bob("bob");
-
5236 Account const ed("ed");
-
5237 Account const paul("paul");
-
5238 Account const dan("dan");
-
5239 Account const chris("chris");
-
5240 Account const simon("simon");
-
5241 Account const ben("ben");
-
5242 Account const nataly("nataly");
-
5243 fund(
-
5244 env,
-
5245 gw,
-
5246 {bob, ed, paul, dan, chris, simon, ben, nataly},
-
5247 XRP(2'000'000),
-
5248 {},
-
5249 Fund::Acct);
-
5250 for (int i = 0; i < 10; ++i)
-
5251 {
-
5252 ammAlice.deposit(ben, XRPAmount{1});
-
5253 ammAlice.withdrawAll(ben, XRP(0));
-
5254 ammAlice.deposit(simon, XRPAmount(1'000));
-
5255 ammAlice.withdrawAll(simon, XRP(0));
-
5256 ammAlice.deposit(chris, XRP(1));
-
5257 ammAlice.withdrawAll(chris, XRP(0));
-
5258 ammAlice.deposit(dan, XRP(10));
-
5259 ammAlice.withdrawAll(dan, XRP(0));
-
5260 ammAlice.deposit(bob, XRP(100));
-
5261 ammAlice.withdrawAll(bob, XRP(0));
-
5262 ammAlice.deposit(carol, XRP(1'000));
-
5263 ammAlice.withdrawAll(carol, XRP(0));
-
5264 ammAlice.deposit(ed, XRP(10'000));
-
5265 ammAlice.withdrawAll(ed, XRP(0));
-
5266 ammAlice.deposit(paul, XRP(100'000));
-
5267 ammAlice.withdrawAll(paul, XRP(0));
-
5268 ammAlice.deposit(nataly, XRP(1'000'000));
-
5269 ammAlice.withdrawAll(nataly, XRP(0));
-
5270 }
-
5271 // No round off with XRP in this test
-
5272 BEAST_EXPECT(ammAlice.expectBalances(
-
5273 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
-
5274 ammAlice.withdrawAll(alice);
-
5275 BEAST_EXPECT(!ammAlice.ammExists());
-
5276 // 20,000 initial - (deposit+withdraw) * 10
-
5277 auto const xrpBalance = (XRP(2'000'000) - txfee(env, 20)).getText();
-
5278 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
-
5279 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
-
5280 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
-
5281 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
-
5282 // 30,000 initial - (deposit+withdraw) * 10
-
5283 BEAST_EXPECT(accountBalance(env, carol) == "29999999800");
-
5284 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
-
5285 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
-
5286 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
-
5287 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
-
5288 BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
-
5289 });
-
5290 }
-
5291
-
5292 void
-
5293 testAutoDelete()
-
5294 {
-
5295 testcase("Auto Delete");
-
5296
-
5297 using namespace jtx;
-
5298 FeatureBitset const all{supported_amendments()};
+
5213 carol,
+
5214 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
+
5215 else
+
5216 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
+
5217 BEAST_EXPECT(expectLine(env, ed, USD(1'500'000)));
+
5218 BEAST_EXPECT(expectLine(env, paul, USD(1'500'000)));
+
5219 if (!features[fixAMMv1_1])
+
5220 BEAST_EXPECT(expectLine(
+
5221 env,
+
5222 nataly,
+
5223 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
+
5224 else
+
5225 BEAST_EXPECT(expectLine(
+
5226 env,
+
5227 nataly,
+
5228 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
+
5229 ammAlice.withdrawAll(alice);
+
5230 BEAST_EXPECT(!ammAlice.ammExists());
+
5231 if (!features[fixAMMv1_1])
+
5232 BEAST_EXPECT(expectLine(
+
5233 env,
+
5234 alice,
+
5235 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
+
5236 else
+
5237 BEAST_EXPECT(expectLine(env, alice, USD(30'000)));
+
5238 // alice XRP balance is 30,000initial - 50 ammcreate fee -
+
5239 // 10drops fee
+
5240 BEAST_EXPECT(
+
5241 accountBalance(env, alice) ==
+
5242 std::to_string(
+
5243 29950000000 - env.current()->fees().base.drops()));
+
5244 },
+
5245 std::nullopt,
+
5246 0,
+
5247 std::nullopt,
+
5248 {features});
+
5249
+
5250 // Same as above but deposit/withdraw in XRP
+
5251 testAMM([&](AMM& ammAlice, Env& env) {
+
5252 Account const bob("bob");
+
5253 Account const ed("ed");
+
5254 Account const paul("paul");
+
5255 Account const dan("dan");
+
5256 Account const chris("chris");
+
5257 Account const simon("simon");
+
5258 Account const ben("ben");
+
5259 Account const nataly("nataly");
+
5260 fund(
+
5261 env,
+
5262 gw,
+
5263 {bob, ed, paul, dan, chris, simon, ben, nataly},
+
5264 XRP(2'000'000),
+
5265 {},
+
5266 Fund::Acct);
+
5267 for (int i = 0; i < 10; ++i)
+
5268 {
+
5269 ammAlice.deposit(ben, XRPAmount{1});
+
5270 ammAlice.withdrawAll(ben, XRP(0));
+
5271 ammAlice.deposit(simon, XRPAmount(1'000));
+
5272 ammAlice.withdrawAll(simon, XRP(0));
+
5273 ammAlice.deposit(chris, XRP(1));
+
5274 ammAlice.withdrawAll(chris, XRP(0));
+
5275 ammAlice.deposit(dan, XRP(10));
+
5276 ammAlice.withdrawAll(dan, XRP(0));
+
5277 ammAlice.deposit(bob, XRP(100));
+
5278 ammAlice.withdrawAll(bob, XRP(0));
+
5279 ammAlice.deposit(carol, XRP(1'000));
+
5280 ammAlice.withdrawAll(carol, XRP(0));
+
5281 ammAlice.deposit(ed, XRP(10'000));
+
5282 ammAlice.withdrawAll(ed, XRP(0));
+
5283 ammAlice.deposit(paul, XRP(100'000));
+
5284 ammAlice.withdrawAll(paul, XRP(0));
+
5285 ammAlice.deposit(nataly, XRP(1'000'000));
+
5286 ammAlice.withdrawAll(nataly, XRP(0));
+
5287 }
+
5288 // No round off with XRP in this test
+
5289 BEAST_EXPECT(ammAlice.expectBalances(
+
5290 XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
+
5291 ammAlice.withdrawAll(alice);
+
5292 BEAST_EXPECT(!ammAlice.ammExists());
+
5293 // 20,000 initial - (deposit+withdraw) * 10
+
5294 auto const xrpBalance = (XRP(2'000'000) - txfee(env, 20)).getText();
+
5295 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
+
5296 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
+
5297 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
+
5298 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
5299
-
5300 {
-
5301 Env env(
-
5302 *this,
-
5303 envconfig([](std::unique_ptr<Config> cfg) {
-
5304 cfg->FEES.reference_fee = XRPAmount(1);
-
5305 return cfg;
-
5306 }),
-
5307 all);
-
5308 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
-
5309 AMM amm(env, gw, XRP(10'000), USD(10'000));
-
5310 for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i)
-
5311 {
-
5312 Account const a{std::to_string(i)};
-
5313 env.fund(XRP(1'000), a);
-
5314 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
-
5315 env.close();
-
5316 }
-
5317 // The trustlines are partially deleted,
-
5318 // AMM is set to an empty state.
-
5319 amm.withdrawAll(gw);
-
5320 BEAST_EXPECT(amm.ammExists());
-
5321
-
5322 // Bid,Vote,Deposit,Withdraw,SetTrust failing with
-
5323 // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
-
5324 env(amm.bid({
-
5325 .account = alice,
-
5326 .bidMin = 1000,
-
5327 }),
-
5328 ter(tecAMM_EMPTY));
-
5329 amm.vote(
-
5330 std::nullopt,
-
5331 100,
-
5332 std::nullopt,
-
5333 std::nullopt,
-
5334 std::nullopt,
-
5335 ter(tecAMM_EMPTY));
-
5336 amm.withdraw(
-
5337 alice, 100, std::nullopt, std::nullopt, ter(tecAMM_EMPTY));
-
5338 amm.deposit(
-
5339 alice,
-
5340 USD(100),
-
5341 std::nullopt,
-
5342 std::nullopt,
-
5343 std::nullopt,
-
5344 ter(tecAMM_EMPTY));
-
5345 env(trust(alice, STAmount{amm.lptIssue(), 10'000}),
-
5346 ter(tecAMM_EMPTY));
-
5347
-
5348 // Can deposit with tfTwoAssetIfEmpty option
-
5349 amm.deposit(
-
5350 alice,
-
5351 std::nullopt,
-
5352 XRP(10'000),
-
5353 USD(10'000),
-
5354 std::nullopt,
-
5355 tfTwoAssetIfEmpty,
+
5300 auto const baseFee = env.current()->fees().base.drops();
+
5301 // 30,000 initial - (deposit+withdraw) * 10
+
5302 BEAST_EXPECT(
+
5303 accountBalance(env, carol) ==
+
5304 std::to_string(30000000000 - 20 * baseFee));
+
5305 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
+
5306 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
+
5307 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
+
5308 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
+
5309 BEAST_EXPECT(
+
5310 accountBalance(env, alice) ==
+
5311 std::to_string(29950000000 - baseFee));
+
5312 });
+
5313 }
+
5314
+
5315 void
+
5316 testAutoDelete()
+
5317 {
+
5318 testcase("Auto Delete");
+
5319
+
5320 using namespace jtx;
+
5321 FeatureBitset const all{supported_amendments()};
+
5322
+
5323 {
+
5324 Env env(
+
5325 *this,
+
5326 envconfig([](std::unique_ptr<Config> cfg) {
+
5327 cfg->FEES.reference_fee = XRPAmount(1);
+
5328 return cfg;
+
5329 }),
+
5330 all);
+
5331 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
+
5332 AMM amm(env, gw, XRP(10'000), USD(10'000));
+
5333 for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i)
+
5334 {
+
5335 Account const a{std::to_string(i)};
+
5336 env.fund(XRP(1'000), a);
+
5337 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
+
5338 env.close();
+
5339 }
+
5340 // The trustlines are partially deleted,
+
5341 // AMM is set to an empty state.
+
5342 amm.withdrawAll(gw);
+
5343 BEAST_EXPECT(amm.ammExists());
+
5344
+
5345 // Bid,Vote,Deposit,Withdraw,SetTrust failing with
+
5346 // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
+
5347 env(amm.bid({
+
5348 .account = alice,
+
5349 .bidMin = 1000,
+
5350 }),
+
5351 ter(tecAMM_EMPTY));
+
5352 amm.vote(
+
5353 std::nullopt,
+
5354 100,
+
5355 std::nullopt,
5356 std::nullopt,
5357 std::nullopt,
-
5358 1'000);
-
5359 BEAST_EXPECT(
-
5360 amm.expectBalances(XRP(10'000), USD(10'000), amm.tokens()));
-
5361 BEAST_EXPECT(amm.expectTradingFee(1'000));
-
5362 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
-
5363
-
5364 // Withdrawing all tokens deletes AMM since the number
-
5365 // of remaining trustlines is less than max
-
5366 amm.withdrawAll(alice);
-
5367 BEAST_EXPECT(!amm.ammExists());
-
5368 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
-
5369 }
+
5358 ter(tecAMM_EMPTY));
+
5359 amm.withdraw(
+
5360 alice, 100, std::nullopt, std::nullopt, ter(tecAMM_EMPTY));
+
5361 amm.deposit(
+
5362 alice,
+
5363 USD(100),
+
5364 std::nullopt,
+
5365 std::nullopt,
+
5366 std::nullopt,
+
5367 ter(tecAMM_EMPTY));
+
5368 env(trust(alice, STAmount{amm.lptIssue(), 10'000}),
+
5369 ter(tecAMM_EMPTY));
5370
-
5371 {
-
5372 Env env(
-
5373 *this,
-
5374 envconfig([](std::unique_ptr<Config> cfg) {
-
5375 cfg->FEES.reference_fee = XRPAmount(1);
-
5376 return cfg;
-
5377 }),
-
5378 all);
-
5379 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
-
5380 AMM amm(env, gw, XRP(10'000), USD(10'000));
-
5381 for (auto i = 0; i < maxDeletableAMMTrustLines * 2 + 10; ++i)
-
5382 {
-
5383 Account const a{std::to_string(i)};
-
5384 env.fund(XRP(1'000), a);
-
5385 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
-
5386 env.close();
-
5387 }
-
5388 // The trustlines are partially deleted.
-
5389 amm.withdrawAll(gw);
-
5390 BEAST_EXPECT(amm.ammExists());
-
5391
-
5392 // AMMDelete has to be called twice to delete AMM.
-
5393 amm.ammDelete(alice, ter(tecINCOMPLETE));
-
5394 BEAST_EXPECT(amm.ammExists());
-
5395 // Deletes remaining trustlines and deletes AMM.
-
5396 amm.ammDelete(alice);
-
5397 BEAST_EXPECT(!amm.ammExists());
-
5398 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
-
5399
-
5400 // Try redundant delete
-
5401 amm.ammDelete(alice, ter(terNO_AMM));
-
5402 }
-
5403 }
-
5404
-
5405 void
-
5406 testClawback()
-
5407 {
-
5408 testcase("Clawback");
-
5409 using namespace jtx;
-
5410 Env env(*this);
-
5411 env.fund(XRP(2'000), gw);
-
5412 env.fund(XRP(2'000), alice);
-
5413 AMM amm(env, gw, XRP(1'000), USD(1'000));
-
5414 env(fset(gw, asfAllowTrustLineClawback), ter(tecOWNERS));
-
5415 }
-
5416
-
5417 void
-
5418 testAMMID()
-
5419 {
-
5420 testcase("AMMID");
-
5421 using namespace jtx;
-
5422 testAMM([&](AMM& amm, Env& env) {
-
5423 amm.setClose(false);
-
5424 auto const info = env.rpc(
-
5425 "json",
-
5426 "account_info",
-
5427 std::string(
-
5428 "{\"account\": \"" + to_string(amm.ammAccount()) + "\"}"));
-
5429 try
-
5430 {
-
5431 BEAST_EXPECT(
-
5432 info[jss::result][jss::account_data][jss::AMMID]
-
5433 .asString() == to_string(amm.ammID()));
-
5434 }
-
5435 catch (...)
-
5436 {
-
5437 fail();
-
5438 }
-
5439 amm.deposit(carol, 1'000);
-
5440 auto affected = env.meta()->getJson(
-
5441 JsonOptions::none)[sfAffectedNodes.fieldName];
-
5442 try
-
5443 {
-
5444 bool found = false;
-
5445 for (auto const& node : affected)
-
5446 {
-
5447 if (node.isMember(sfModifiedNode.fieldName) &&
-
5448 node[sfModifiedNode.fieldName]
-
5449 [sfLedgerEntryType.fieldName]
-
5450 .asString() == "AccountRoot" &&
-
5451 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
-
5452 [jss::Account]
-
5453 .asString() == to_string(amm.ammAccount()))
-
5454 {
-
5455 found = node[sfModifiedNode.fieldName]
-
5456 [sfFinalFields.fieldName][jss::AMMID]
-
5457 .asString() == to_string(amm.ammID());
-
5458 break;
-
5459 }
-
5460 }
-
5461 BEAST_EXPECT(found);
-
5462 }
-
5463 catch (...)
-
5464 {
-
5465 fail();
-
5466 }
-
5467 });
-
5468 }
-
5469
-
5470 void
-
5471 testSelection(FeatureBitset features)
-
5472 {
-
5473 testcase("Offer/Strand Selection");
-
5474 using namespace jtx;
-
5475 Account const ed("ed");
-
5476 Account const gw1("gw1");
-
5477 auto const ETH = gw1["ETH"];
-
5478 auto const CAN = gw1["CAN"];
-
5479
-
5480 // These tests are expected to fail if the OwnerPaysFee feature
-
5481 // is ever supported. Updates will need to be made to AMM handling
-
5482 // in the payment engine, and these tests will need to be updated.
-
5483
-
5484 auto prep = [&](Env& env, auto gwRate, auto gw1Rate) {
-
5485 fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {USD(2'000)});
-
5486 env.fund(XRP(2'000), gw1);
-
5487 fund(
-
5488 env,
-
5489 gw1,
-
5490 {alice, carol, bob, ed},
-
5491 {ETH(2'000), CAN(2'000)},
-
5492 Fund::IOUOnly);
-
5493 env(rate(gw, gwRate));
-
5494 env(rate(gw1, gw1Rate));
-
5495 env.close();
-
5496 };
-
5497
-
5498 for (auto const& rates :
-
5499 {std::make_pair(1.5, 1.9), std::make_pair(1.9, 1.5)})
-
5500 {
-
5501 // Offer Selection
+
5371 // Can deposit with tfTwoAssetIfEmpty option
+
5372 amm.deposit(
+
5373 alice,
+
5374 std::nullopt,
+
5375 XRP(10'000),
+
5376 USD(10'000),
+
5377 std::nullopt,
+
5378 tfTwoAssetIfEmpty,
+
5379 std::nullopt,
+
5380 std::nullopt,
+
5381 1'000);
+
5382 BEAST_EXPECT(
+
5383 amm.expectBalances(XRP(10'000), USD(10'000), amm.tokens()));
+
5384 BEAST_EXPECT(amm.expectTradingFee(1'000));
+
5385 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
+
5386
+
5387 // Withdrawing all tokens deletes AMM since the number
+
5388 // of remaining trustlines is less than max
+
5389 amm.withdrawAll(alice);
+
5390 BEAST_EXPECT(!amm.ammExists());
+
5391 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
+
5392 }
+
5393
+
5394 {
+
5395 Env env(
+
5396 *this,
+
5397 envconfig([](std::unique_ptr<Config> cfg) {
+
5398 cfg->FEES.reference_fee = XRPAmount(1);
+
5399 return cfg;
+
5400 }),
+
5401 all);
+
5402 fund(env, gw, {alice}, XRP(20'000), {USD(10'000)});
+
5403 AMM amm(env, gw, XRP(10'000), USD(10'000));
+
5404 for (auto i = 0; i < maxDeletableAMMTrustLines * 2 + 10; ++i)
+
5405 {
+
5406 Account const a{std::to_string(i)};
+
5407 env.fund(XRP(1'000), a);
+
5408 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
+
5409 env.close();
+
5410 }
+
5411 // The trustlines are partially deleted.
+
5412 amm.withdrawAll(gw);
+
5413 BEAST_EXPECT(amm.ammExists());
+
5414
+
5415 // AMMDelete has to be called twice to delete AMM.
+
5416 amm.ammDelete(alice, ter(tecINCOMPLETE));
+
5417 BEAST_EXPECT(amm.ammExists());
+
5418 // Deletes remaining trustlines and deletes AMM.
+
5419 amm.ammDelete(alice);
+
5420 BEAST_EXPECT(!amm.ammExists());
+
5421 BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount())));
+
5422
+
5423 // Try redundant delete
+
5424 amm.ammDelete(alice, ter(terNO_AMM));
+
5425 }
+
5426 }
+
5427
+
5428 void
+
5429 testClawback()
+
5430 {
+
5431 testcase("Clawback");
+
5432 using namespace jtx;
+
5433 Env env(*this);
+
5434 env.fund(XRP(2'000), gw);
+
5435 env.fund(XRP(2'000), alice);
+
5436 AMM amm(env, gw, XRP(1'000), USD(1'000));
+
5437 env(fset(gw, asfAllowTrustLineClawback), ter(tecOWNERS));
+
5438 }
+
5439
+
5440 void
+
5441 testAMMID()
+
5442 {
+
5443 testcase("AMMID");
+
5444 using namespace jtx;
+
5445 testAMM([&](AMM& amm, Env& env) {
+
5446 amm.setClose(false);
+
5447 auto const info = env.rpc(
+
5448 "json",
+
5449 "account_info",
+
5450 std::string(
+
5451 "{\"account\": \"" + to_string(amm.ammAccount()) + "\"}"));
+
5452 try
+
5453 {
+
5454 BEAST_EXPECT(
+
5455 info[jss::result][jss::account_data][jss::AMMID]
+
5456 .asString() == to_string(amm.ammID()));
+
5457 }
+
5458 catch (...)
+
5459 {
+
5460 fail();
+
5461 }
+
5462 amm.deposit(carol, 1'000);
+
5463 auto affected = env.meta()->getJson(
+
5464 JsonOptions::none)[sfAffectedNodes.fieldName];
+
5465 try
+
5466 {
+
5467 bool found = false;
+
5468 for (auto const& node : affected)
+
5469 {
+
5470 if (node.isMember(sfModifiedNode.fieldName) &&
+
5471 node[sfModifiedNode.fieldName]
+
5472 [sfLedgerEntryType.fieldName]
+
5473 .asString() == "AccountRoot" &&
+
5474 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
+
5475 [jss::Account]
+
5476 .asString() == to_string(amm.ammAccount()))
+
5477 {
+
5478 found = node[sfModifiedNode.fieldName]
+
5479 [sfFinalFields.fieldName][jss::AMMID]
+
5480 .asString() == to_string(amm.ammID());
+
5481 break;
+
5482 }
+
5483 }
+
5484 BEAST_EXPECT(found);
+
5485 }
+
5486 catch (...)
+
5487 {
+
5488 fail();
+
5489 }
+
5490 });
+
5491 }
+
5492
+
5493 void
+
5494 testSelection(FeatureBitset features)
+
5495 {
+
5496 testcase("Offer/Strand Selection");
+
5497 using namespace jtx;
+
5498 Account const ed("ed");
+
5499 Account const gw1("gw1");
+
5500 auto const ETH = gw1["ETH"];
+
5501 auto const CAN = gw1["CAN"];
5502
-
5503 // Cross-currency payment: AMM has the same spot price quality
-
5504 // as CLOB's offer and can't generate a better quality offer.
-
5505 // The transfer fee in this case doesn't change the CLOB quality
-
5506 // because trIn is ignored on adjustment and trOut on payment is
-
5507 // also ignored because ownerPaysTransferFee is false in this
-
5508 // case. Run test for 0) offer, 1) AMM, 2) offer and AMM to
-
5509 // verify that the quality is better in the first case, and CLOB
-
5510 // is selected in the second case.
-
5511 {
-
5512 std::array<Quality, 3> q;
-
5513 for (auto i = 0; i < 3; ++i)
-
5514 {
-
5515 Env env(*this, features);
-
5516 prep(env, rates.first, rates.second);
-
5517 std::optional<AMM> amm;
-
5518 if (i == 0 || i == 2)
-
5519 {
-
5520 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
-
5521 env.close();
-
5522 }
-
5523 if (i > 0)
-
5524 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5525 env(pay(carol, bob, USD(100)),
-
5526 path(~USD),
-
5527 sendmax(ETH(500)));
-
5528 env.close();
-
5529 // CLOB and AMM, AMM is not selected
-
5530 if (i == 2)
-
5531 {
-
5532 BEAST_EXPECT(amm->expectBalances(
-
5533 USD(1'000), ETH(1'000), amm->tokens()));
-
5534 }
-
5535 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5536 q[i] = Quality(Amounts{
-
5537 ETH(2'000) - env.balance(carol, ETH),
-
5538 env.balance(bob, USD) - USD(2'000)});
-
5539 }
-
5540 // CLOB is better quality than AMM
-
5541 BEAST_EXPECT(q[0] > q[1]);
-
5542 // AMM is not selected with CLOB
-
5543 BEAST_EXPECT(q[0] == q[2]);
-
5544 }
-
5545 // Offer crossing: AMM has the same spot price quality
-
5546 // as CLOB's offer and can't generate a better quality offer.
-
5547 // The transfer fee in this case doesn't change the CLOB quality
-
5548 // because the quality adjustment is ignored for the offer
-
5549 // crossing.
-
5550 for (auto i = 0; i < 3; ++i)
-
5551 {
-
5552 Env env(*this, features);
-
5553 prep(env, rates.first, rates.second);
-
5554 std::optional<AMM> amm;
-
5555 if (i == 0 || i == 2)
-
5556 {
-
5557 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
-
5558 env.close();
-
5559 }
-
5560 if (i > 0)
-
5561 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5562 env(offer(alice, USD(400), ETH(400)));
-
5563 env.close();
-
5564 // AMM is not selected
-
5565 if (i > 0)
-
5566 {
-
5567 BEAST_EXPECT(amm->expectBalances(
-
5568 USD(1'000), ETH(1'000), amm->tokens()));
-
5569 }
-
5570 if (i == 0 || i == 2)
-
5571 {
-
5572 // Fully crosses
-
5573 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5574 }
-
5575 // Fails to cross because AMM is not selected
-
5576 else
-
5577 {
-
5578 BEAST_EXPECT(expectOffers(
-
5579 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
-
5580 }
-
5581 BEAST_EXPECT(expectOffers(env, ed, 0));
-
5582 }
-
5583
-
5584 // Show that the CLOB quality reduction
-
5585 // results in AMM offer selection.
-
5586
-
5587 // Same as the payment but reduced offer quality
-
5588 {
-
5589 std::array<Quality, 3> q;
-
5590 for (auto i = 0; i < 3; ++i)
-
5591 {
-
5592 Env env(*this, features);
-
5593 prep(env, rates.first, rates.second);
-
5594 std::optional<AMM> amm;
-
5595 if (i == 0 || i == 2)
-
5596 {
-
5597 env(offer(ed, ETH(400), USD(300)), txflags(tfPassive));
-
5598 env.close();
-
5599 }
-
5600 if (i > 0)
-
5601 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5602 env(pay(carol, bob, USD(100)),
-
5603 path(~USD),
-
5604 sendmax(ETH(500)));
-
5605 env.close();
-
5606 // AMM and CLOB are selected
-
5607 if (i > 0)
-
5608 {
-
5609 BEAST_EXPECT(!amm->expectBalances(
-
5610 USD(1'000), ETH(1'000), amm->tokens()));
-
5611 }
-
5612 if (i == 2 && !features[fixAMMv1_1])
-
5613 {
-
5614 if (rates.first == 1.5)
-
5615 {
-
5616 if (!features[fixAMMv1_1])
-
5617 BEAST_EXPECT(expectOffers(
-
5618 env,
-
5619 ed,
-
5620 1,
-
5621 {{Amounts{
-
5622 STAmount{
-
5623 ETH,
-
5624 UINT64_C(378'6327949540823),
-
5625 -13},
-
5626 STAmount{
-
5627 USD,
-
5628 UINT64_C(283'9745962155617),
-
5629 -13}}}}));
-
5630 else
-
5631 BEAST_EXPECT(expectOffers(
-
5632 env,
-
5633 ed,
-
5634 1,
-
5635 {{Amounts{
-
5636 STAmount{
-
5637 ETH,
-
5638 UINT64_C(378'6327949540813),
-
5639 -13},
-
5640 STAmount{
-
5641 USD,
-
5642 UINT64_C(283'974596215561),
-
5643 -12}}}}));
-
5644 }
-
5645 else
-
5646 {
-
5647 if (!features[fixAMMv1_1])
-
5648 BEAST_EXPECT(expectOffers(
-
5649 env,
-
5650 ed,
-
5651 1,
-
5652 {{Amounts{
-
5653 STAmount{
-
5654 ETH,
-
5655 UINT64_C(325'299461620749),
-
5656 -12},
-
5657 STAmount{
-
5658 USD,
-
5659 UINT64_C(243'9745962155617),
-
5660 -13}}}}));
-
5661 else
-
5662 BEAST_EXPECT(expectOffers(
-
5663 env,
-
5664 ed,
-
5665 1,
-
5666 {{Amounts{
-
5667 STAmount{
-
5668 ETH,
-
5669 UINT64_C(325'299461620748),
-
5670 -12},
-
5671 STAmount{
-
5672 USD,
-
5673 UINT64_C(243'974596215561),
-
5674 -12}}}}));
-
5675 }
-
5676 }
-
5677 else if (i == 2)
-
5678 {
-
5679 if (rates.first == 1.5)
-
5680 {
-
5681 BEAST_EXPECT(expectOffers(
-
5682 env,
-
5683 ed,
-
5684 1,
-
5685 {{Amounts{
-
5686 STAmount{
-
5687 ETH, UINT64_C(378'6327949540812), -13},
-
5688 STAmount{
-
5689 USD,
-
5690 UINT64_C(283'9745962155609),
-
5691 -13}}}}));
-
5692 }
-
5693 else
-
5694 {
-
5695 BEAST_EXPECT(expectOffers(
-
5696 env,
-
5697 ed,
-
5698 1,
-
5699 {{Amounts{
-
5700 STAmount{
-
5701 ETH, UINT64_C(325'2994616207479), -13},
-
5702 STAmount{
-
5703 USD,
-
5704 UINT64_C(243'9745962155609),
-
5705 -13}}}}));
-
5706 }
-
5707 }
-
5708 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5709 q[i] = Quality(Amounts{
-
5710 ETH(2'000) - env.balance(carol, ETH),
-
5711 env.balance(bob, USD) - USD(2'000)});
-
5712 }
-
5713 // AMM is better quality
-
5714 BEAST_EXPECT(q[1] > q[0]);
-
5715 // AMM and CLOB produce better quality
-
5716 BEAST_EXPECT(q[2] > q[1]);
-
5717 }
-
5718
-
5719 // Same as the offer-crossing but reduced offer quality
-
5720 for (auto i = 0; i < 3; ++i)
-
5721 {
-
5722 Env env(*this, features);
-
5723 prep(env, rates.first, rates.second);
-
5724 std::optional<AMM> amm;
-
5725 if (i == 0 || i == 2)
-
5726 {
-
5727 env(offer(ed, ETH(400), USD(250)), txflags(tfPassive));
-
5728 env.close();
-
5729 }
-
5730 if (i > 0)
-
5731 amm.emplace(env, ed, USD(1'000), ETH(1'000));
-
5732 env(offer(alice, USD(250), ETH(400)));
-
5733 env.close();
-
5734 // AMM is selected in both cases
-
5735 if (i > 0)
-
5736 {
-
5737 BEAST_EXPECT(!amm->expectBalances(
-
5738 USD(1'000), ETH(1'000), amm->tokens()));
-
5739 }
-
5740 // Partially crosses, AMM is selected, CLOB fails
-
5741 // limitQuality
-
5742 if (i == 2)
-
5743 {
-
5744 if (rates.first == 1.5)
-
5745 {
-
5746 if (!features[fixAMMv1_1])
-
5747 {
-
5748 BEAST_EXPECT(expectOffers(
-
5749 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
-
5750 BEAST_EXPECT(expectOffers(
-
5751 env,
-
5752 alice,
-
5753 1,
-
5754 {{Amounts{
-
5755 STAmount{
-
5756 USD, UINT64_C(40'5694150420947), -13},
-
5757 STAmount{
-
5758 ETH, UINT64_C(64'91106406735152), -14},
-
5759 }}}));
-
5760 }
-
5761 else
-
5762 {
-
5763 // Ed offer is partially crossed.
-
5764 // The updated rounding makes limitQuality
-
5765 // work if both amendments are enabled
-
5766 BEAST_EXPECT(expectOffers(
-
5767 env,
-
5768 ed,
-
5769 1,
-
5770 {{Amounts{
-
5771 STAmount{
-
5772 ETH, UINT64_C(335'0889359326475), -13},
-
5773 STAmount{
-
5774 USD, UINT64_C(209'4305849579047), -13},
-
5775 }}}));
-
5776 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5777 }
-
5778 }
-
5779 else
-
5780 {
-
5781 if (!features[fixAMMv1_1])
-
5782 {
-
5783 // Ed offer is partially crossed.
-
5784 BEAST_EXPECT(expectOffers(
-
5785 env,
-
5786 ed,
-
5787 1,
-
5788 {{Amounts{
-
5789 STAmount{
-
5790 ETH, UINT64_C(335'0889359326485), -13},
-
5791 STAmount{
-
5792 USD, UINT64_C(209'4305849579053), -13},
-
5793 }}}));
-
5794 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5795 }
-
5796 else
-
5797 {
-
5798 // Ed offer is partially crossed.
-
5799 BEAST_EXPECT(expectOffers(
-
5800 env,
-
5801 ed,
-
5802 1,
-
5803 {{Amounts{
-
5804 STAmount{
-
5805 ETH, UINT64_C(335'0889359326475), -13},
-
5806 STAmount{
-
5807 USD, UINT64_C(209'4305849579047), -13},
-
5808 }}}));
-
5809 BEAST_EXPECT(expectOffers(env, alice, 0));
-
5810 }
-
5811 }
-
5812 }
-
5813 }
-
5814
-
5815 // Strand selection
-
5816
-
5817 // Two book steps strand quality is 1.
-
5818 // AMM strand's best quality is equal to AMM's spot price
-
5819 // quality, which is 1. Both strands (steps) are adjusted
-
5820 // for the transfer fee in qualityUpperBound. In case
-
5821 // of two strands, AMM offers have better quality and are
-
5822 // consumed first, remaining liquidity is generated by CLOB
-
5823 // offers. Liquidity from two strands is better in this case
-
5824 // than in case of one strand with two book steps. Liquidity
-
5825 // from one strand with AMM has better quality than either one
-
5826 // strand with two book steps or two strands. It may appear
-
5827 // unintuitive, but one strand with AMM is optimized and
-
5828 // generates one AMM offer, while in case of two strands,
-
5829 // multiple AMM offers are generated, which results in slightly
-
5830 // worse overall quality.
-
5831 {
-
5832 std::array<Quality, 3> q;
-
5833 for (auto i = 0; i < 3; ++i)
-
5834 {
-
5835 Env env(*this, features);
-
5836 prep(env, rates.first, rates.second);
-
5837 std::optional<AMM> amm;
-
5838
-
5839 if (i == 0 || i == 2)
-
5840 {
-
5841 env(offer(ed, ETH(400), CAN(400)), txflags(tfPassive));
-
5842 env(offer(ed, CAN(400), USD(400))), txflags(tfPassive);
-
5843 env.close();
-
5844 }
-
5845
-
5846 if (i > 0)
-
5847 amm.emplace(env, ed, ETH(1'000), USD(1'000));
-
5848
-
5849 env(pay(carol, bob, USD(100)),
-
5850 path(~USD),
-
5851 path(~CAN, ~USD),
-
5852 sendmax(ETH(600)));
-
5853 env.close();
-
5854
-
5855 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
-
5856
-
5857 if (i == 2 && !features[fixAMMv1_1])
-
5858 {
-
5859 if (rates.first == 1.5)
-
5860 {
-
5861 // Liquidity is consumed from AMM strand only
-
5862 BEAST_EXPECT(amm->expectBalances(
-
5863 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
-
5864 USD(850),
-
5865 amm->tokens()));
-
5866 }
-
5867 else
-
5868 {
-
5869 BEAST_EXPECT(amm->expectBalances(
-
5870 STAmount{
-
5871 ETH, UINT64_C(1'179'540094339627), -12},
-
5872 STAmount{USD, UINT64_C(847'7880529867501), -13},
-
5873 amm->tokens()));
-
5874 BEAST_EXPECT(expectOffers(
-
5875 env,
-
5876 ed,
-
5877 2,
-
5878 {{Amounts{
-
5879 STAmount{
-
5880 ETH,
-
5881 UINT64_C(343'3179205198749),
-
5882 -13},
-
5883 STAmount{
-
5884 CAN,
-
5885 UINT64_C(343'3179205198749),
-
5886 -13},
-
5887 },
-
5888 Amounts{
-
5889 STAmount{
-
5890 CAN,
-
5891 UINT64_C(362'2119470132499),
-
5892 -13},
-
5893 STAmount{
-
5894 USD,
-
5895 UINT64_C(362'2119470132499),
-
5896 -13},
-
5897 }}}));
-
5898 }
-
5899 }
-
5900 else if (i == 2)
-
5901 {
-
5902 if (rates.first == 1.5)
-
5903 {
-
5904 // Liquidity is consumed from AMM strand only
-
5905 BEAST_EXPECT(amm->expectBalances(
-
5906 STAmount{
-
5907 ETH, UINT64_C(1'176'660389557593), -12},
-
5908 USD(850),
-
5909 amm->tokens()));
-
5910 }
-
5911 else
-
5912 {
-
5913 BEAST_EXPECT(amm->expectBalances(
-
5914 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
-
5915 STAmount{USD, UINT64_C(847'7880529867501), -13},
-
5916 amm->tokens()));
-
5917 BEAST_EXPECT(expectOffers(
-
5918 env,
-
5919 ed,
-
5920 2,
-
5921 {{Amounts{
-
5922 STAmount{
-
5923 ETH,
-
5924 UINT64_C(343'3179205198749),
-
5925 -13},
-
5926 STAmount{
-
5927 CAN,
-
5928 UINT64_C(343'3179205198749),
-
5929 -13},
-
5930 },
-
5931 Amounts{
-
5932 STAmount{
-
5933 CAN,
-
5934 UINT64_C(362'2119470132499),
-
5935 -13},
-
5936 STAmount{
-
5937 USD,
-
5938 UINT64_C(362'2119470132499),
-
5939 -13},
-
5940 }}}));
-
5941 }
-
5942 }
-
5943 q[i] = Quality(Amounts{
-
5944 ETH(2'000) - env.balance(carol, ETH),
-
5945 env.balance(bob, USD) - USD(2'000)});
-
5946 }
-
5947 BEAST_EXPECT(q[1] > q[0]);
-
5948 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
-
5949 }
-
5950 }
-
5951 }
-
5952
-
5953 void
-
5954 testFixDefaultInnerObj()
-
5955 {
-
5956 testcase("Fix Default Inner Object");
-
5957 using namespace jtx;
-
5958 FeatureBitset const all{supported_amendments()};
-
5959
-
5960 auto test = [&](FeatureBitset features,
-
5961 TER const& err1,
-
5962 TER const& err2,
-
5963 TER const& err3,
-
5964 TER const& err4,
-
5965 std::uint16_t tfee,
-
5966 bool closeLedger,
-
5967 std::optional<std::uint16_t> extra = std::nullopt) {
-
5968 Env env(*this, features);
-
5969 fund(env, gw, {alice}, XRP(1'000), {USD(10)});
-
5970 AMM amm(
-
5971 env,
-
5972 gw,
-
5973 XRP(10),
-
5974 USD(10),
-
5975 {.tfee = tfee, .close = closeLedger});
-
5976 amm.deposit(alice, USD(10), XRP(10));
-
5977 amm.vote(VoteArg{.account = alice, .tfee = tfee, .err = ter(err1)});
-
5978 amm.withdraw(WithdrawArg{
-
5979 .account = gw, .asset1Out = USD(1), .err = ter(err2)});
-
5980 // with the amendment disabled and ledger not closed,
-
5981 // second vote succeeds if the first vote sets the trading fee
-
5982 // to non-zero; if the first vote sets the trading fee to >0 &&
-
5983 // <9 then the second withdraw succeeds if the second vote sets
-
5984 // the trading fee so that the discounted fee is non-zero
-
5985 amm.vote(VoteArg{.account = alice, .tfee = 20, .err = ter(err3)});
-
5986 amm.withdraw(WithdrawArg{
-
5987 .account = gw, .asset1Out = USD(2), .err = ter(err4)});
-
5988 };
-
5989
-
5990 // ledger is closed after each transaction, vote/withdraw don't fail
-
5991 // regardless whether the amendment is enabled or not
-
5992 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
-
5993 test(
-
5994 all - fixInnerObjTemplate,
-
5995 tesSUCCESS,
-
5996 tesSUCCESS,
-
5997 tesSUCCESS,
-
5998 tesSUCCESS,
-
5999 0,
-
6000 true);
-
6001 // ledger is not closed after each transaction
-
6002 // vote/withdraw don't fail if the amendment is enabled
-
6003 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, false);
-
6004 // vote/withdraw fail if the amendment is not enabled
-
6005 // second vote/withdraw still fail: second vote fails because
-
6006 // the initial trading fee is 0, consequently second withdraw fails
-
6007 // because the second vote fails
-
6008 test(
-
6009 all - fixInnerObjTemplate,
-
6010 tefEXCEPTION,
-
6011 tefEXCEPTION,
-
6012 tefEXCEPTION,
-
6013 tefEXCEPTION,
-
6014 0,
-
6015 false);
-
6016 // if non-zero trading/discounted fee then vote/withdraw
-
6017 // don't fail whether the ledger is closed or not and
-
6018 // the amendment is enabled or not
-
6019 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
-
6020 test(
-
6021 all - fixInnerObjTemplate,
-
6022 tesSUCCESS,
-
6023 tesSUCCESS,
-
6024 tesSUCCESS,
-
6025 tesSUCCESS,
-
6026 10,
-
6027 true);
-
6028 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
-
6029 test(
-
6030 all - fixInnerObjTemplate,
-
6031 tesSUCCESS,
-
6032 tesSUCCESS,
-
6033 tesSUCCESS,
-
6034 tesSUCCESS,
-
6035 10,
-
6036 false);
-
6037 // non-zero trading fee but discounted fee is 0, vote doesn't fail
-
6038 // but withdraw fails
-
6039 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 9, false);
-
6040 // second vote sets the trading fee to non-zero, consequently
-
6041 // second withdraw doesn't fail even if the amendment is not
-
6042 // enabled and the ledger is not closed
+
5503 // These tests are expected to fail if the OwnerPaysFee feature
+
5504 // is ever supported. Updates will need to be made to AMM handling
+
5505 // in the payment engine, and these tests will need to be updated.
+
5506
+
5507 auto prep = [&](Env& env, auto gwRate, auto gw1Rate) {
+
5508 fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {USD(2'000)});
+
5509 env.fund(XRP(2'000), gw1);
+
5510 fund(
+
5511 env,
+
5512 gw1,
+
5513 {alice, carol, bob, ed},
+
5514 {ETH(2'000), CAN(2'000)},
+
5515 Fund::IOUOnly);
+
5516 env(rate(gw, gwRate));
+
5517 env(rate(gw1, gw1Rate));
+
5518 env.close();
+
5519 };
+
5520
+
5521 for (auto const& rates :
+
5522 {std::make_pair(1.5, 1.9), std::make_pair(1.9, 1.5)})
+
5523 {
+
5524 // Offer Selection
+
5525
+
5526 // Cross-currency payment: AMM has the same spot price quality
+
5527 // as CLOB's offer and can't generate a better quality offer.
+
5528 // The transfer fee in this case doesn't change the CLOB quality
+
5529 // because trIn is ignored on adjustment and trOut on payment is
+
5530 // also ignored because ownerPaysTransferFee is false in this
+
5531 // case. Run test for 0) offer, 1) AMM, 2) offer and AMM to
+
5532 // verify that the quality is better in the first case, and CLOB
+
5533 // is selected in the second case.
+
5534 {
+
5535 std::array<Quality, 3> q;
+
5536 for (auto i = 0; i < 3; ++i)
+
5537 {
+
5538 Env env(*this, features);
+
5539 prep(env, rates.first, rates.second);
+
5540 std::optional<AMM> amm;
+
5541 if (i == 0 || i == 2)
+
5542 {
+
5543 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
+
5544 env.close();
+
5545 }
+
5546 if (i > 0)
+
5547 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5548 env(pay(carol, bob, USD(100)),
+
5549 path(~USD),
+
5550 sendmax(ETH(500)));
+
5551 env.close();
+
5552 // CLOB and AMM, AMM is not selected
+
5553 if (i == 2)
+
5554 {
+
5555 BEAST_EXPECT(amm->expectBalances(
+
5556 USD(1'000), ETH(1'000), amm->tokens()));
+
5557 }
+
5558 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
5559 q[i] = Quality(Amounts{
+
5560 ETH(2'000) - env.balance(carol, ETH),
+
5561 env.balance(bob, USD) - USD(2'000)});
+
5562 }
+
5563 // CLOB is better quality than AMM
+
5564 BEAST_EXPECT(q[0] > q[1]);
+
5565 // AMM is not selected with CLOB
+
5566 BEAST_EXPECT(q[0] == q[2]);
+
5567 }
+
5568 // Offer crossing: AMM has the same spot price quality
+
5569 // as CLOB's offer and can't generate a better quality offer.
+
5570 // The transfer fee in this case doesn't change the CLOB quality
+
5571 // because the quality adjustment is ignored for the offer
+
5572 // crossing.
+
5573 for (auto i = 0; i < 3; ++i)
+
5574 {
+
5575 Env env(*this, features);
+
5576 prep(env, rates.first, rates.second);
+
5577 std::optional<AMM> amm;
+
5578 if (i == 0 || i == 2)
+
5579 {
+
5580 env(offer(ed, ETH(400), USD(400)), txflags(tfPassive));
+
5581 env.close();
+
5582 }
+
5583 if (i > 0)
+
5584 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5585 env(offer(alice, USD(400), ETH(400)));
+
5586 env.close();
+
5587 // AMM is not selected
+
5588 if (i > 0)
+
5589 {
+
5590 BEAST_EXPECT(amm->expectBalances(
+
5591 USD(1'000), ETH(1'000), amm->tokens()));
+
5592 }
+
5593 if (i == 0 || i == 2)
+
5594 {
+
5595 // Fully crosses
+
5596 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5597 }
+
5598 // Fails to cross because AMM is not selected
+
5599 else
+
5600 {
+
5601 BEAST_EXPECT(expectOffers(
+
5602 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
+
5603 }
+
5604 BEAST_EXPECT(expectOffers(env, ed, 0));
+
5605 }
+
5606
+
5607 // Show that the CLOB quality reduction
+
5608 // results in AMM offer selection.
+
5609
+
5610 // Same as the payment but reduced offer quality
+
5611 {
+
5612 std::array<Quality, 3> q;
+
5613 for (auto i = 0; i < 3; ++i)
+
5614 {
+
5615 Env env(*this, features);
+
5616 prep(env, rates.first, rates.second);
+
5617 std::optional<AMM> amm;
+
5618 if (i == 0 || i == 2)
+
5619 {
+
5620 env(offer(ed, ETH(400), USD(300)), txflags(tfPassive));
+
5621 env.close();
+
5622 }
+
5623 if (i > 0)
+
5624 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5625 env(pay(carol, bob, USD(100)),
+
5626 path(~USD),
+
5627 sendmax(ETH(500)));
+
5628 env.close();
+
5629 // AMM and CLOB are selected
+
5630 if (i > 0)
+
5631 {
+
5632 BEAST_EXPECT(!amm->expectBalances(
+
5633 USD(1'000), ETH(1'000), amm->tokens()));
+
5634 }
+
5635 if (i == 2 && !features[fixAMMv1_1])
+
5636 {
+
5637 if (rates.first == 1.5)
+
5638 {
+
5639 if (!features[fixAMMv1_1])
+
5640 BEAST_EXPECT(expectOffers(
+
5641 env,
+
5642 ed,
+
5643 1,
+
5644 {{Amounts{
+
5645 STAmount{
+
5646 ETH,
+
5647 UINT64_C(378'6327949540823),
+
5648 -13},
+
5649 STAmount{
+
5650 USD,
+
5651 UINT64_C(283'9745962155617),
+
5652 -13}}}}));
+
5653 else
+
5654 BEAST_EXPECT(expectOffers(
+
5655 env,
+
5656 ed,
+
5657 1,
+
5658 {{Amounts{
+
5659 STAmount{
+
5660 ETH,
+
5661 UINT64_C(378'6327949540813),
+
5662 -13},
+
5663 STAmount{
+
5664 USD,
+
5665 UINT64_C(283'974596215561),
+
5666 -12}}}}));
+
5667 }
+
5668 else
+
5669 {
+
5670 if (!features[fixAMMv1_1])
+
5671 BEAST_EXPECT(expectOffers(
+
5672 env,
+
5673 ed,
+
5674 1,
+
5675 {{Amounts{
+
5676 STAmount{
+
5677 ETH,
+
5678 UINT64_C(325'299461620749),
+
5679 -12},
+
5680 STAmount{
+
5681 USD,
+
5682 UINT64_C(243'9745962155617),
+
5683 -13}}}}));
+
5684 else
+
5685 BEAST_EXPECT(expectOffers(
+
5686 env,
+
5687 ed,
+
5688 1,
+
5689 {{Amounts{
+
5690 STAmount{
+
5691 ETH,
+
5692 UINT64_C(325'299461620748),
+
5693 -12},
+
5694 STAmount{
+
5695 USD,
+
5696 UINT64_C(243'974596215561),
+
5697 -12}}}}));
+
5698 }
+
5699 }
+
5700 else if (i == 2)
+
5701 {
+
5702 if (rates.first == 1.5)
+
5703 {
+
5704 BEAST_EXPECT(expectOffers(
+
5705 env,
+
5706 ed,
+
5707 1,
+
5708 {{Amounts{
+
5709 STAmount{
+
5710 ETH, UINT64_C(378'6327949540812), -13},
+
5711 STAmount{
+
5712 USD,
+
5713 UINT64_C(283'9745962155609),
+
5714 -13}}}}));
+
5715 }
+
5716 else
+
5717 {
+
5718 BEAST_EXPECT(expectOffers(
+
5719 env,
+
5720 ed,
+
5721 1,
+
5722 {{Amounts{
+
5723 STAmount{
+
5724 ETH, UINT64_C(325'2994616207479), -13},
+
5725 STAmount{
+
5726 USD,
+
5727 UINT64_C(243'9745962155609),
+
5728 -13}}}}));
+
5729 }
+
5730 }
+
5731 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
5732 q[i] = Quality(Amounts{
+
5733 ETH(2'000) - env.balance(carol, ETH),
+
5734 env.balance(bob, USD) - USD(2'000)});
+
5735 }
+
5736 // AMM is better quality
+
5737 BEAST_EXPECT(q[1] > q[0]);
+
5738 // AMM and CLOB produce better quality
+
5739 BEAST_EXPECT(q[2] > q[1]);
+
5740 }
+
5741
+
5742 // Same as the offer-crossing but reduced offer quality
+
5743 for (auto i = 0; i < 3; ++i)
+
5744 {
+
5745 Env env(*this, features);
+
5746 prep(env, rates.first, rates.second);
+
5747 std::optional<AMM> amm;
+
5748 if (i == 0 || i == 2)
+
5749 {
+
5750 env(offer(ed, ETH(400), USD(250)), txflags(tfPassive));
+
5751 env.close();
+
5752 }
+
5753 if (i > 0)
+
5754 amm.emplace(env, ed, USD(1'000), ETH(1'000));
+
5755 env(offer(alice, USD(250), ETH(400)));
+
5756 env.close();
+
5757 // AMM is selected in both cases
+
5758 if (i > 0)
+
5759 {
+
5760 BEAST_EXPECT(!amm->expectBalances(
+
5761 USD(1'000), ETH(1'000), amm->tokens()));
+
5762 }
+
5763 // Partially crosses, AMM is selected, CLOB fails
+
5764 // limitQuality
+
5765 if (i == 2)
+
5766 {
+
5767 if (rates.first == 1.5)
+
5768 {
+
5769 if (!features[fixAMMv1_1])
+
5770 {
+
5771 BEAST_EXPECT(expectOffers(
+
5772 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
+
5773 BEAST_EXPECT(expectOffers(
+
5774 env,
+
5775 alice,
+
5776 1,
+
5777 {{Amounts{
+
5778 STAmount{
+
5779 USD, UINT64_C(40'5694150420947), -13},
+
5780 STAmount{
+
5781 ETH, UINT64_C(64'91106406735152), -14},
+
5782 }}}));
+
5783 }
+
5784 else
+
5785 {
+
5786 // Ed offer is partially crossed.
+
5787 // The updated rounding makes limitQuality
+
5788 // work if both amendments are enabled
+
5789 BEAST_EXPECT(expectOffers(
+
5790 env,
+
5791 ed,
+
5792 1,
+
5793 {{Amounts{
+
5794 STAmount{
+
5795 ETH, UINT64_C(335'0889359326475), -13},
+
5796 STAmount{
+
5797 USD, UINT64_C(209'4305849579047), -13},
+
5798 }}}));
+
5799 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5800 }
+
5801 }
+
5802 else
+
5803 {
+
5804 if (!features[fixAMMv1_1])
+
5805 {
+
5806 // Ed offer is partially crossed.
+
5807 BEAST_EXPECT(expectOffers(
+
5808 env,
+
5809 ed,
+
5810 1,
+
5811 {{Amounts{
+
5812 STAmount{
+
5813 ETH, UINT64_C(335'0889359326485), -13},
+
5814 STAmount{
+
5815 USD, UINT64_C(209'4305849579053), -13},
+
5816 }}}));
+
5817 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5818 }
+
5819 else
+
5820 {
+
5821 // Ed offer is partially crossed.
+
5822 BEAST_EXPECT(expectOffers(
+
5823 env,
+
5824 ed,
+
5825 1,
+
5826 {{Amounts{
+
5827 STAmount{
+
5828 ETH, UINT64_C(335'0889359326475), -13},
+
5829 STAmount{
+
5830 USD, UINT64_C(209'4305849579047), -13},
+
5831 }}}));
+
5832 BEAST_EXPECT(expectOffers(env, alice, 0));
+
5833 }
+
5834 }
+
5835 }
+
5836 }
+
5837
+
5838 // Strand selection
+
5839
+
5840 // Two book steps strand quality is 1.
+
5841 // AMM strand's best quality is equal to AMM's spot price
+
5842 // quality, which is 1. Both strands (steps) are adjusted
+
5843 // for the transfer fee in qualityUpperBound. In case
+
5844 // of two strands, AMM offers have better quality and are
+
5845 // consumed first, remaining liquidity is generated by CLOB
+
5846 // offers. Liquidity from two strands is better in this case
+
5847 // than in case of one strand with two book steps. Liquidity
+
5848 // from one strand with AMM has better quality than either one
+
5849 // strand with two book steps or two strands. It may appear
+
5850 // unintuitive, but one strand with AMM is optimized and
+
5851 // generates one AMM offer, while in case of two strands,
+
5852 // multiple AMM offers are generated, which results in slightly
+
5853 // worse overall quality.
+
5854 {
+
5855 std::array<Quality, 3> q;
+
5856 for (auto i = 0; i < 3; ++i)
+
5857 {
+
5858 Env env(*this, features);
+
5859 prep(env, rates.first, rates.second);
+
5860 std::optional<AMM> amm;
+
5861
+
5862 if (i == 0 || i == 2)
+
5863 {
+
5864 env(offer(ed, ETH(400), CAN(400)), txflags(tfPassive));
+
5865 env(offer(ed, CAN(400), USD(400))), txflags(tfPassive);
+
5866 env.close();
+
5867 }
+
5868
+
5869 if (i > 0)
+
5870 amm.emplace(env, ed, ETH(1'000), USD(1'000));
+
5871
+
5872 env(pay(carol, bob, USD(100)),
+
5873 path(~USD),
+
5874 path(~CAN, ~USD),
+
5875 sendmax(ETH(600)));
+
5876 env.close();
+
5877
+
5878 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
+
5879
+
5880 if (i == 2 && !features[fixAMMv1_1])
+
5881 {
+
5882 if (rates.first == 1.5)
+
5883 {
+
5884 // Liquidity is consumed from AMM strand only
+
5885 BEAST_EXPECT(amm->expectBalances(
+
5886 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
+
5887 USD(850),
+
5888 amm->tokens()));
+
5889 }
+
5890 else
+
5891 {
+
5892 BEAST_EXPECT(amm->expectBalances(
+
5893 STAmount{
+
5894 ETH, UINT64_C(1'179'540094339627), -12},
+
5895 STAmount{USD, UINT64_C(847'7880529867501), -13},
+
5896 amm->tokens()));
+
5897 BEAST_EXPECT(expectOffers(
+
5898 env,
+
5899 ed,
+
5900 2,
+
5901 {{Amounts{
+
5902 STAmount{
+
5903 ETH,
+
5904 UINT64_C(343'3179205198749),
+
5905 -13},
+
5906 STAmount{
+
5907 CAN,
+
5908 UINT64_C(343'3179205198749),
+
5909 -13},
+
5910 },
+
5911 Amounts{
+
5912 STAmount{
+
5913 CAN,
+
5914 UINT64_C(362'2119470132499),
+
5915 -13},
+
5916 STAmount{
+
5917 USD,
+
5918 UINT64_C(362'2119470132499),
+
5919 -13},
+
5920 }}}));
+
5921 }
+
5922 }
+
5923 else if (i == 2)
+
5924 {
+
5925 if (rates.first == 1.5)
+
5926 {
+
5927 // Liquidity is consumed from AMM strand only
+
5928 BEAST_EXPECT(amm->expectBalances(
+
5929 STAmount{
+
5930 ETH, UINT64_C(1'176'660389557593), -12},
+
5931 USD(850),
+
5932 amm->tokens()));
+
5933 }
+
5934 else
+
5935 {
+
5936 BEAST_EXPECT(amm->expectBalances(
+
5937 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
+
5938 STAmount{USD, UINT64_C(847'7880529867501), -13},
+
5939 amm->tokens()));
+
5940 BEAST_EXPECT(expectOffers(
+
5941 env,
+
5942 ed,
+
5943 2,
+
5944 {{Amounts{
+
5945 STAmount{
+
5946 ETH,
+
5947 UINT64_C(343'3179205198749),
+
5948 -13},
+
5949 STAmount{
+
5950 CAN,
+
5951 UINT64_C(343'3179205198749),
+
5952 -13},
+
5953 },
+
5954 Amounts{
+
5955 STAmount{
+
5956 CAN,
+
5957 UINT64_C(362'2119470132499),
+
5958 -13},
+
5959 STAmount{
+
5960 USD,
+
5961 UINT64_C(362'2119470132499),
+
5962 -13},
+
5963 }}}));
+
5964 }
+
5965 }
+
5966 q[i] = Quality(Amounts{
+
5967 ETH(2'000) - env.balance(carol, ETH),
+
5968 env.balance(bob, USD) - USD(2'000)});
+
5969 }
+
5970 BEAST_EXPECT(q[1] > q[0]);
+
5971 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
+
5972 }
+
5973 }
+
5974 }
+
5975
+
5976 void
+
5977 testFixDefaultInnerObj()
+
5978 {
+
5979 testcase("Fix Default Inner Object");
+
5980 using namespace jtx;
+
5981 FeatureBitset const all{supported_amendments()};
+
5982
+
5983 auto test = [&](FeatureBitset features,
+
5984 TER const& err1,
+
5985 TER const& err2,
+
5986 TER const& err3,
+
5987 TER const& err4,
+
5988 std::uint16_t tfee,
+
5989 bool closeLedger,
+
5990 std::optional<std::uint16_t> extra = std::nullopt) {
+
5991 Env env(*this, features);
+
5992 fund(env, gw, {alice}, XRP(1'000), {USD(10)});
+
5993 AMM amm(
+
5994 env,
+
5995 gw,
+
5996 XRP(10),
+
5997 USD(10),
+
5998 {.tfee = tfee, .close = closeLedger});
+
5999 amm.deposit(alice, USD(10), XRP(10));
+
6000 amm.vote(VoteArg{.account = alice, .tfee = tfee, .err = ter(err1)});
+
6001 amm.withdraw(WithdrawArg{
+
6002 .account = gw, .asset1Out = USD(1), .err = ter(err2)});
+
6003 // with the amendment disabled and ledger not closed,
+
6004 // second vote succeeds if the first vote sets the trading fee
+
6005 // to non-zero; if the first vote sets the trading fee to >0 &&
+
6006 // <9 then the second withdraw succeeds if the second vote sets
+
6007 // the trading fee so that the discounted fee is non-zero
+
6008 amm.vote(VoteArg{.account = alice, .tfee = 20, .err = ter(err3)});
+
6009 amm.withdraw(WithdrawArg{
+
6010 .account = gw, .asset1Out = USD(2), .err = ter(err4)});
+
6011 };
+
6012
+
6013 // ledger is closed after each transaction, vote/withdraw don't fail
+
6014 // regardless whether the amendment is enabled or not
+
6015 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
+
6016 test(
+
6017 all - fixInnerObjTemplate,
+
6018 tesSUCCESS,
+
6019 tesSUCCESS,
+
6020 tesSUCCESS,
+
6021 tesSUCCESS,
+
6022 0,
+
6023 true);
+
6024 // ledger is not closed after each transaction
+
6025 // vote/withdraw don't fail if the amendment is enabled
+
6026 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, false);
+
6027 // vote/withdraw fail if the amendment is not enabled
+
6028 // second vote/withdraw still fail: second vote fails because
+
6029 // the initial trading fee is 0, consequently second withdraw fails
+
6030 // because the second vote fails
+
6031 test(
+
6032 all - fixInnerObjTemplate,
+
6033 tefEXCEPTION,
+
6034 tefEXCEPTION,
+
6035 tefEXCEPTION,
+
6036 tefEXCEPTION,
+
6037 0,
+
6038 false);
+
6039 // if non-zero trading/discounted fee then vote/withdraw
+
6040 // don't fail whether the ledger is closed or not and
+
6041 // the amendment is enabled or not
+
6042 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
6043 test(
6044 all - fixInnerObjTemplate,
6045 tesSUCCESS,
-
6046 tefEXCEPTION,
+
6046 tesSUCCESS,
6047 tesSUCCESS,
6048 tesSUCCESS,
-
6049 9,
-
6050 false);
-
6051 }
-
6052
-
6053 void
-
6054 testFixChangeSpotPriceQuality(FeatureBitset features)
-
6055 {
-
6056 testcase("Fix changeSpotPriceQuality");
-
6057 using namespace jtx;
-
6058
-
6059 enum class Status {
-
6060 SucceedShouldSucceedResize, // Succeed in pre-fix because
-
6061 // error allowance, succeed post-fix
-
6062 // because of offer resizing
-
6063 FailShouldSucceed, // Fail in pre-fix due to rounding,
-
6064 // succeed after fix because of XRP
-
6065 // side is generated first
-
6066 SucceedShouldFail, // Succeed in pre-fix, fail after fix
-
6067 // due to small quality difference
-
6068 Fail, // Both fail because the quality can't be matched
-
6069 Succeed // Both succeed
-
6070 };
-
6071 using enum Status;
-
6072 auto const xrpIouAmounts10_100 =
-
6073 TAmounts{XRPAmount{10}, IOUAmount{100}};
-
6074 auto const iouXrpAmounts10_100 =
-
6075 TAmounts{IOUAmount{10}, XRPAmount{100}};
-
6076 // clang-format off
-
6077 std::vector<std::tuple<std::string, std::string, Quality, std::uint16_t, Status>> tests = {
-
6078 //Pool In , Pool Out, Quality , Fee, Status
-
6079 {"0.001519763260828713", "1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
-
6080 {"0.01099814367603737", "1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
-
6081 {"0.78", "796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
-
6082 {"105439.2955578965", "49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
-
6083 {"12408293.23445213", "4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
-
6084 {"1892611", "0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
-
6085 {"423028.8508101858", "3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
-
6086 {"44565388.41001027", "73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
-
6087 {"66831.68494832662", "16", Quality{6346111134641742975}, 0, FailShouldSucceed},
-
6088 {"675.9287302203422", "1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
-
6089 {"7047.112186735699", "1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
-
6090 {"840236.4402981238", "47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
-
6091 {"992715.618909774", "189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
-
6092 {"504636667521", "185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
-
6093 {"992706.7218636649", "189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
-
6094 {"1.068737911388205", "127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
-
6095 {"17932506.56880419", "189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
-
6096 {"1.066379294658174", "128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
-
6097 {"350131413924", "1576879.110907892", Quality{6487411636539049449}, 650, Fail},
-
6098 {"422093460", "2.731797662057464", Quality{6702911108534394924}, 1000, Fail},
-
6099 {"76128132223", "367172.7148422662", Quality{6487263463413514240}, 548, Fail},
-
6100 {"132701839250", "280703770.7695443", Quality{6273750681188885075}, 562, Fail},
-
6101 {"994165.7604612011", "189551302411", Quality{5697835592690668727}, 815, Fail},
-
6102 {"45053.33303227917", "86612695359", Quality{5625695218943638190}, 500, Fail},
-
6103 {"199649.077043865", "14017933007", Quality{5766034667318524880}, 324, Fail},
-
6104 {"27751824831.70903", "78896950", Quality{6272538159621630432}, 500, Fail},
-
6105 {"225.3731275781907", "156431793648", Quality{5477818047604078924}, 989, Fail},
-
6106 {"199649.077043865", "14017933007", Quality{5766036094462806309}, 324, Fail},
-
6107 {"3.590272027140361", "20677643641", Quality{5406056147042156356}, 808, Fail},
-
6108 {"1.070884664490231", "127604712776", Quality{5268620608623825741}, 293, Fail},
-
6109 {"3272.448829820197", "6275124076", Quality{5625710328924117902}, 81, Fail},
-
6110 {"0.009059512633902926", "7994028", Quality{5477511954775533172}, 1000, Fail},
-
6111 {"1", "1.0", Quality{0}, 100, Fail},
-
6112 {"1.0", "1", Quality{0}, 100, Fail},
-
6113 {"10", "10.0", Quality{xrpIouAmounts10_100}, 100, Fail},
-
6114 {"10.0", "10", Quality{iouXrpAmounts10_100}, 100, Fail},
-
6115 {"69864389131", "287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
-
6116 {"4328342973", "12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
-
6117 {"32347017", "7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
-
6118 {"61697206161", "36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
-
6119 {"1654524979", "7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
-
6120 {"88621.22277293179", "5128418948", Quality{5766347291552869205}, 380, Succeed},
-
6121 {"1892611", "0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
-
6122 {"4542.639373338766", "24554809", Quality{5838994982188783710}, 0, Succeed},
-
6123 {"5132932546", "88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
-
6124 {"78929964.1549083", "1506494795", Quality{5986890029845558688}, 589, Succeed},
-
6125 {"10096561906", "44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
-
6126 {"5092.219565514988", "8768257694", Quality{5626349534958379008}, 503, Succeed},
-
6127 {"1819778294", "8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
-
6128 {"6970462.633911943", "57359281", Quality{6054087899185946624}, 850, Succeed},
-
6129 {"3983448845", "2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
-
6130 // This is a tiny offer 12drops/19321952e-15 it succeeds pre-amendment because of the error allowance.
-
6131 // Post amendment it is resized to 11drops/17711789e-15 but the quality is still less than
-
6132 // the target quality and the offer fails.
-
6133 {"771493171", "1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
-
6134 };
-
6135 // clang-format on
-
6136
-
6137 boost::regex rx("^\\d+$");
-
6138 boost::smatch match;
-
6139 // tests that succeed should have the same amounts pre-fix and post-fix
-
6140 std::vector<std::pair<STAmount, STAmount>> successAmounts;
-
6141 Env env(*this, features);
-
6142 auto rules = env.current()->rules();
-
6143 CurrentTransactionRulesGuard rg(rules);
-
6144 for (auto const& t : tests)
-
6145 {
-
6146 auto getPool = [&](std::string const& v, bool isXRP) {
-
6147 if (isXRP)
-
6148 return amountFromString(xrpIssue(), v);
-
6149 return amountFromString(noIssue(), v);
-
6150 };
-
6151 auto const& quality = std::get<Quality>(t);
-
6152 auto const tfee = std::get<std::uint16_t>(t);
-
6153 auto const status = std::get<Status>(t);
-
6154 auto const poolInIsXRP =
-
6155 boost::regex_search(std::get<0>(t), match, rx);
-
6156 auto const poolOutIsXRP =
-
6157 boost::regex_search(std::get<1>(t), match, rx);
-
6158 assert(!(poolInIsXRP && poolOutIsXRP));
-
6159 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
-
6160 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
-
6161 try
-
6162 {
-
6163 auto const amounts = changeSpotPriceQuality(
-
6164 Amounts{poolIn, poolOut},
-
6165 quality,
-
6166 tfee,
-
6167 env.current()->rules(),
-
6168 env.journal);
-
6169 if (amounts)
-
6170 {
-
6171 if (status == SucceedShouldSucceedResize)
-
6172 {
-
6173 if (!features[fixAMMv1_1])
-
6174 BEAST_EXPECT(Quality{*amounts} < quality);
-
6175 else
-
6176 BEAST_EXPECT(Quality{*amounts} >= quality);
-
6177 }
-
6178 else if (status == Succeed)
-
6179 {
-
6180 if (!features[fixAMMv1_1])
-
6181 BEAST_EXPECT(
-
6182 Quality{*amounts} >= quality ||
-
6183 withinRelativeDistance(
-
6184 Quality{*amounts}, quality, Number{1, -7}));
-
6185 else
-
6186 BEAST_EXPECT(Quality{*amounts} >= quality);
-
6187 }
-
6188 else if (status == FailShouldSucceed)
-
6189 {
-
6190 BEAST_EXPECT(
-
6191 features[fixAMMv1_1] &&
-
6192 Quality{*amounts} >= quality);
-
6193 }
-
6194 else if (status == SucceedShouldFail)
+
6049 10,
+
6050 true);
+
6051 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
+
6052 test(
+
6053 all - fixInnerObjTemplate,
+
6054 tesSUCCESS,
+
6055 tesSUCCESS,
+
6056 tesSUCCESS,
+
6057 tesSUCCESS,
+
6058 10,
+
6059 false);
+
6060 // non-zero trading fee but discounted fee is 0, vote doesn't fail
+
6061 // but withdraw fails
+
6062 test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 9, false);
+
6063 // second vote sets the trading fee to non-zero, consequently
+
6064 // second withdraw doesn't fail even if the amendment is not
+
6065 // enabled and the ledger is not closed
+
6066 test(
+
6067 all - fixInnerObjTemplate,
+
6068 tesSUCCESS,
+
6069 tefEXCEPTION,
+
6070 tesSUCCESS,
+
6071 tesSUCCESS,
+
6072 9,
+
6073 false);
+
6074 }
+
6075
+
6076 void
+
6077 testFixChangeSpotPriceQuality(FeatureBitset features)
+
6078 {
+
6079 testcase("Fix changeSpotPriceQuality");
+
6080 using namespace jtx;
+
6081
+
6082 enum class Status {
+
6083 SucceedShouldSucceedResize, // Succeed in pre-fix because
+
6084 // error allowance, succeed post-fix
+
6085 // because of offer resizing
+
6086 FailShouldSucceed, // Fail in pre-fix due to rounding,
+
6087 // succeed after fix because of XRP
+
6088 // side is generated first
+
6089 SucceedShouldFail, // Succeed in pre-fix, fail after fix
+
6090 // due to small quality difference
+
6091 Fail, // Both fail because the quality can't be matched
+
6092 Succeed // Both succeed
+
6093 };
+
6094 using enum Status;
+
6095 auto const xrpIouAmounts10_100 =
+
6096 TAmounts{XRPAmount{10}, IOUAmount{100}};
+
6097 auto const iouXrpAmounts10_100 =
+
6098 TAmounts{IOUAmount{10}, XRPAmount{100}};
+
6099 // clang-format off
+
6100 std::vector<std::tuple<std::string, std::string, Quality, std::uint16_t, Status>> tests = {
+
6101 //Pool In , Pool Out, Quality , Fee, Status
+
6102 {"0.001519763260828713", "1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
+
6103 {"0.01099814367603737", "1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
+
6104 {"0.78", "796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
+
6105 {"105439.2955578965", "49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
+
6106 {"12408293.23445213", "4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
+
6107 {"1892611", "0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
+
6108 {"423028.8508101858", "3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
+
6109 {"44565388.41001027", "73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
+
6110 {"66831.68494832662", "16", Quality{6346111134641742975}, 0, FailShouldSucceed},
+
6111 {"675.9287302203422", "1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
+
6112 {"7047.112186735699", "1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
+
6113 {"840236.4402981238", "47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
+
6114 {"992715.618909774", "189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
+
6115 {"504636667521", "185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
+
6116 {"992706.7218636649", "189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
+
6117 {"1.068737911388205", "127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
+
6118 {"17932506.56880419", "189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
+
6119 {"1.066379294658174", "128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
+
6120 {"350131413924", "1576879.110907892", Quality{6487411636539049449}, 650, Fail},
+
6121 {"422093460", "2.731797662057464", Quality{6702911108534394924}, 1000, Fail},
+
6122 {"76128132223", "367172.7148422662", Quality{6487263463413514240}, 548, Fail},
+
6123 {"132701839250", "280703770.7695443", Quality{6273750681188885075}, 562, Fail},
+
6124 {"994165.7604612011", "189551302411", Quality{5697835592690668727}, 815, Fail},
+
6125 {"45053.33303227917", "86612695359", Quality{5625695218943638190}, 500, Fail},
+
6126 {"199649.077043865", "14017933007", Quality{5766034667318524880}, 324, Fail},
+
6127 {"27751824831.70903", "78896950", Quality{6272538159621630432}, 500, Fail},
+
6128 {"225.3731275781907", "156431793648", Quality{5477818047604078924}, 989, Fail},
+
6129 {"199649.077043865", "14017933007", Quality{5766036094462806309}, 324, Fail},
+
6130 {"3.590272027140361", "20677643641", Quality{5406056147042156356}, 808, Fail},
+
6131 {"1.070884664490231", "127604712776", Quality{5268620608623825741}, 293, Fail},
+
6132 {"3272.448829820197", "6275124076", Quality{5625710328924117902}, 81, Fail},
+
6133 {"0.009059512633902926", "7994028", Quality{5477511954775533172}, 1000, Fail},
+
6134 {"1", "1.0", Quality{0}, 100, Fail},
+
6135 {"1.0", "1", Quality{0}, 100, Fail},
+
6136 {"10", "10.0", Quality{xrpIouAmounts10_100}, 100, Fail},
+
6137 {"10.0", "10", Quality{iouXrpAmounts10_100}, 100, Fail},
+
6138 {"69864389131", "287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
+
6139 {"4328342973", "12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
+
6140 {"32347017", "7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
+
6141 {"61697206161", "36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
+
6142 {"1654524979", "7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
+
6143 {"88621.22277293179", "5128418948", Quality{5766347291552869205}, 380, Succeed},
+
6144 {"1892611", "0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
+
6145 {"4542.639373338766", "24554809", Quality{5838994982188783710}, 0, Succeed},
+
6146 {"5132932546", "88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
+
6147 {"78929964.1549083", "1506494795", Quality{5986890029845558688}, 589, Succeed},
+
6148 {"10096561906", "44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
+
6149 {"5092.219565514988", "8768257694", Quality{5626349534958379008}, 503, Succeed},
+
6150 {"1819778294", "8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
+
6151 {"6970462.633911943", "57359281", Quality{6054087899185946624}, 850, Succeed},
+
6152 {"3983448845", "2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
+
6153 // This is a tiny offer 12drops/19321952e-15 it succeeds pre-amendment because of the error allowance.
+
6154 // Post amendment it is resized to 11drops/17711789e-15 but the quality is still less than
+
6155 // the target quality and the offer fails.
+
6156 {"771493171", "1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
+
6157 };
+
6158 // clang-format on
+
6159
+
6160 boost::regex rx("^\\d+$");
+
6161 boost::smatch match;
+
6162 // tests that succeed should have the same amounts pre-fix and post-fix
+
6163 std::vector<std::pair<STAmount, STAmount>> successAmounts;
+
6164 Env env(*this, features);
+
6165 auto rules = env.current()->rules();
+
6166 CurrentTransactionRulesGuard rg(rules);
+
6167 for (auto const& t : tests)
+
6168 {
+
6169 auto getPool = [&](std::string const& v, bool isXRP) {
+
6170 if (isXRP)
+
6171 return amountFromString(xrpIssue(), v);
+
6172 return amountFromString(noIssue(), v);
+
6173 };
+
6174 auto const& quality = std::get<Quality>(t);
+
6175 auto const tfee = std::get<std::uint16_t>(t);
+
6176 auto const status = std::get<Status>(t);
+
6177 auto const poolInIsXRP =
+
6178 boost::regex_search(std::get<0>(t), match, rx);
+
6179 auto const poolOutIsXRP =
+
6180 boost::regex_search(std::get<1>(t), match, rx);
+
6181 assert(!(poolInIsXRP && poolOutIsXRP));
+
6182 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
+
6183 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
+
6184 try
+
6185 {
+
6186 auto const amounts = changeSpotPriceQuality(
+
6187 Amounts{poolIn, poolOut},
+
6188 quality,
+
6189 tfee,
+
6190 env.current()->rules(),
+
6191 env.journal);
+
6192 if (amounts)
+
6193 {
+
6194 if (status == SucceedShouldSucceedResize)
6195 {
-
6196 BEAST_EXPECT(
-
6197 !features[fixAMMv1_1] &&
-
6198 Quality{*amounts} < quality &&
-
6199 withinRelativeDistance(
-
6200 Quality{*amounts}, quality, Number{1, -7}));
-
6201 }
-
6202 }
-
6203 else
-
6204 {
-
6205 // Fails pre- and post-amendment because the quality can't
-
6206 // be matched. Verify by generating a tiny offer, which
-
6207 // doesn't match the quality. Exclude zero quality since
-
6208 // no offer is generated in this case.
-
6209 if (status == Fail && quality != Quality{0})
-
6210 {
-
6211 auto tinyOffer = [&]() {
-
6212 if (isXRP(poolIn))
-
6213 {
-
6214 auto const takerPays = STAmount{xrpIssue(), 1};
-
6215 return Amounts{
-
6216 takerPays,
-
6217 swapAssetIn(
-
6218 Amounts{poolIn, poolOut},
-
6219 takerPays,
-
6220 tfee)};
-
6221 }
-
6222 else if (isXRP(poolOut))
-
6223 {
-
6224 auto const takerGets = STAmount{xrpIssue(), 1};
-
6225 return Amounts{
-
6226 swapAssetOut(
-
6227 Amounts{poolIn, poolOut},
-
6228 takerGets,
-
6229 tfee),
-
6230 takerGets};
-
6231 }
-
6232 auto const takerPays = toAmount<STAmount>(
-
6233 getIssue(poolIn), Number{1, -10} * poolIn);
-
6234 return Amounts{
-
6235 takerPays,
-
6236 swapAssetIn(
-
6237 Amounts{poolIn, poolOut}, takerPays, tfee)};
-
6238 }();
-
6239 BEAST_EXPECT(Quality(tinyOffer) < quality);
-
6240 }
-
6241 else if (status == FailShouldSucceed)
-
6242 {
-
6243 BEAST_EXPECT(!features[fixAMMv1_1]);
-
6244 }
-
6245 else if (status == SucceedShouldFail)
-
6246 {
-
6247 BEAST_EXPECT(features[fixAMMv1_1]);
-
6248 }
-
6249 }
-
6250 }
-
6251 catch (std::runtime_error const& e)
-
6252 {
-
6253 BEAST_EXPECT(
-
6254 !strcmp(e.what(), "changeSpotPriceQuality failed"));
-
6255 BEAST_EXPECT(
-
6256 !features[fixAMMv1_1] && status == FailShouldSucceed);
-
6257 }
-
6258 }
-
6259
-
6260 // Test negative discriminant
-
6261 {
-
6262 // b**2 - 4 * a * c -> 1 * 1 - 4 * 1 * 1 = -3
-
6263 auto const res =
-
6264 solveQuadraticEqSmallest(Number{1}, Number{1}, Number{1});
-
6265 BEAST_EXPECT(!res.has_value());
-
6266 }
-
6267 }
-
6268
-
6269 void
-
6270 testMalformed()
-
6271 {
-
6272 using namespace jtx;
-
6273
-
6274 testAMM([&](AMM& ammAlice, Env& env) {
-
6275 WithdrawArg args{
-
6276 .flags = tfSingleAsset,
-
6277 .err = ter(temMALFORMED),
-
6278 };
-
6279 ammAlice.withdraw(args);
-
6280 });
-
6281
-
6282 testAMM([&](AMM& ammAlice, Env& env) {
-
6283 WithdrawArg args{
-
6284 .flags = tfOneAssetLPToken,
-
6285 .err = ter(temMALFORMED),
-
6286 };
-
6287 ammAlice.withdraw(args);
-
6288 });
-
6289
-
6290 testAMM([&](AMM& ammAlice, Env& env) {
-
6291 WithdrawArg args{
-
6292 .flags = tfLimitLPToken,
-
6293 .err = ter(temMALFORMED),
-
6294 };
-
6295 ammAlice.withdraw(args);
-
6296 });
-
6297
-
6298 testAMM([&](AMM& ammAlice, Env& env) {
-
6299 WithdrawArg args{
-
6300 .asset1Out = XRP(100),
-
6301 .asset2Out = XRP(100),
-
6302 .err = ter(temBAD_AMM_TOKENS),
-
6303 };
-
6304 ammAlice.withdraw(args);
-
6305 });
-
6306
-
6307 testAMM([&](AMM& ammAlice, Env& env) {
-
6308 WithdrawArg args{
-
6309 .asset1Out = XRP(100),
-
6310 .asset2Out = BAD(100),
-
6311 .err = ter(temBAD_CURRENCY),
-
6312 };
-
6313 ammAlice.withdraw(args);
-
6314 });
-
6315
-
6316 testAMM([&](AMM& ammAlice, Env& env) {
-
6317 Json::Value jv;
-
6318 jv[jss::TransactionType] = jss::AMMWithdraw;
-
6319 jv[jss::Flags] = tfLimitLPToken;
-
6320 jv[jss::Account] = alice.human();
-
6321 ammAlice.setTokens(jv);
-
6322 XRP(100).value().setJson(jv[jss::Amount]);
-
6323 USD(100).value().setJson(jv[jss::EPrice]);
-
6324 env(jv, ter(temBAD_AMM_TOKENS));
-
6325 });
-
6326 }
-
6327
-
6328 void
-
6329 testFixOverflowOffer(FeatureBitset features)
-
6330 {
-
6331 using namespace jtx;
-
6332 using namespace std::chrono;
-
6333 FeatureBitset const all{features};
-
6334
-
6335 Account const gatehub{"gatehub"};
-
6336 Account const bitstamp{"bitstamp"};
-
6337 Account const trader{"trader"};
-
6338 auto const usdGH = gatehub["USD"];
-
6339 auto const btcGH = gatehub["BTC"];
-
6340 auto const usdBIT = bitstamp["USD"];
-
6341
-
6342 struct InputSet
-
6343 {
-
6344 char const* testCase;
-
6345 double const poolUsdBIT;
-
6346 double const poolUsdGH;
-
6347 sendmax const sendMaxUsdBIT;
-
6348 STAmount const sendUsdGH;
-
6349 STAmount const failUsdGH;
-
6350 STAmount const failUsdGHr;
-
6351 STAmount const failUsdBIT;
-
6352 STAmount const failUsdBITr;
-
6353 STAmount const goodUsdGH;
-
6354 STAmount const goodUsdGHr;
-
6355 STAmount const goodUsdBIT;
-
6356 STAmount const goodUsdBITr;
-
6357 IOUAmount const lpTokenBalance;
-
6358 double const offer1BtcGH = 0.1;
-
6359 double const offer2BtcGH = 0.1;
-
6360 double const offer2UsdGH = 1;
-
6361 double const rateBIT = 0.0;
-
6362 double const rateGH = 0.0;
-
6363 };
+
6196 if (!features[fixAMMv1_1])
+
6197 BEAST_EXPECT(Quality{*amounts} < quality);
+
6198 else
+
6199 BEAST_EXPECT(Quality{*amounts} >= quality);
+
6200 }
+
6201 else if (status == Succeed)
+
6202 {
+
6203 if (!features[fixAMMv1_1])
+
6204 BEAST_EXPECT(
+
6205 Quality{*amounts} >= quality ||
+
6206 withinRelativeDistance(
+
6207 Quality{*amounts}, quality, Number{1, -7}));
+
6208 else
+
6209 BEAST_EXPECT(Quality{*amounts} >= quality);
+
6210 }
+
6211 else if (status == FailShouldSucceed)
+
6212 {
+
6213 BEAST_EXPECT(
+
6214 features[fixAMMv1_1] &&
+
6215 Quality{*amounts} >= quality);
+
6216 }
+
6217 else if (status == SucceedShouldFail)
+
6218 {
+
6219 BEAST_EXPECT(
+
6220 !features[fixAMMv1_1] &&
+
6221 Quality{*amounts} < quality &&
+
6222 withinRelativeDistance(
+
6223 Quality{*amounts}, quality, Number{1, -7}));
+
6224 }
+
6225 }
+
6226 else
+
6227 {
+
6228 // Fails pre- and post-amendment because the quality can't
+
6229 // be matched. Verify by generating a tiny offer, which
+
6230 // doesn't match the quality. Exclude zero quality since
+
6231 // no offer is generated in this case.
+
6232 if (status == Fail && quality != Quality{0})
+
6233 {
+
6234 auto tinyOffer = [&]() {
+
6235 if (isXRP(poolIn))
+
6236 {
+
6237 auto const takerPays = STAmount{xrpIssue(), 1};
+
6238 return Amounts{
+
6239 takerPays,
+
6240 swapAssetIn(
+
6241 Amounts{poolIn, poolOut},
+
6242 takerPays,
+
6243 tfee)};
+
6244 }
+
6245 else if (isXRP(poolOut))
+
6246 {
+
6247 auto const takerGets = STAmount{xrpIssue(), 1};
+
6248 return Amounts{
+
6249 swapAssetOut(
+
6250 Amounts{poolIn, poolOut},
+
6251 takerGets,
+
6252 tfee),
+
6253 takerGets};
+
6254 }
+
6255 auto const takerPays = toAmount<STAmount>(
+
6256 getIssue(poolIn), Number{1, -10} * poolIn);
+
6257 return Amounts{
+
6258 takerPays,
+
6259 swapAssetIn(
+
6260 Amounts{poolIn, poolOut}, takerPays, tfee)};
+
6261 }();
+
6262 BEAST_EXPECT(Quality(tinyOffer) < quality);
+
6263 }
+
6264 else if (status == FailShouldSucceed)
+
6265 {
+
6266 BEAST_EXPECT(!features[fixAMMv1_1]);
+
6267 }
+
6268 else if (status == SucceedShouldFail)
+
6269 {
+
6270 BEAST_EXPECT(features[fixAMMv1_1]);
+
6271 }
+
6272 }
+
6273 }
+
6274 catch (std::runtime_error const& e)
+
6275 {
+
6276 BEAST_EXPECT(
+
6277 !strcmp(e.what(), "changeSpotPriceQuality failed"));
+
6278 BEAST_EXPECT(
+
6279 !features[fixAMMv1_1] && status == FailShouldSucceed);
+
6280 }
+
6281 }
+
6282
+
6283 // Test negative discriminant
+
6284 {
+
6285 // b**2 - 4 * a * c -> 1 * 1 - 4 * 1 * 1 = -3
+
6286 auto const res =
+
6287 solveQuadraticEqSmallest(Number{1}, Number{1}, Number{1});
+
6288 BEAST_EXPECT(!res.has_value());
+
6289 }
+
6290 }
+
6291
+
6292 void
+
6293 testMalformed()
+
6294 {
+
6295 using namespace jtx;
+
6296
+
6297 testAMM([&](AMM& ammAlice, Env& env) {
+
6298 WithdrawArg args{
+
6299 .flags = tfSingleAsset,
+
6300 .err = ter(temMALFORMED),
+
6301 };
+
6302 ammAlice.withdraw(args);
+
6303 });
+
6304
+
6305 testAMM([&](AMM& ammAlice, Env& env) {
+
6306 WithdrawArg args{
+
6307 .flags = tfOneAssetLPToken,
+
6308 .err = ter(temMALFORMED),
+
6309 };
+
6310 ammAlice.withdraw(args);
+
6311 });
+
6312
+
6313 testAMM([&](AMM& ammAlice, Env& env) {
+
6314 WithdrawArg args{
+
6315 .flags = tfLimitLPToken,
+
6316 .err = ter(temMALFORMED),
+
6317 };
+
6318 ammAlice.withdraw(args);
+
6319 });
+
6320
+
6321 testAMM([&](AMM& ammAlice, Env& env) {
+
6322 WithdrawArg args{
+
6323 .asset1Out = XRP(100),
+
6324 .asset2Out = XRP(100),
+
6325 .err = ter(temBAD_AMM_TOKENS),
+
6326 };
+
6327 ammAlice.withdraw(args);
+
6328 });
+
6329
+
6330 testAMM([&](AMM& ammAlice, Env& env) {
+
6331 WithdrawArg args{
+
6332 .asset1Out = XRP(100),
+
6333 .asset2Out = BAD(100),
+
6334 .err = ter(temBAD_CURRENCY),
+
6335 };
+
6336 ammAlice.withdraw(args);
+
6337 });
+
6338
+
6339 testAMM([&](AMM& ammAlice, Env& env) {
+
6340 Json::Value jv;
+
6341 jv[jss::TransactionType] = jss::AMMWithdraw;
+
6342 jv[jss::Flags] = tfLimitLPToken;
+
6343 jv[jss::Account] = alice.human();
+
6344 ammAlice.setTokens(jv);
+
6345 XRP(100).value().setJson(jv[jss::Amount]);
+
6346 USD(100).value().setJson(jv[jss::EPrice]);
+
6347 env(jv, ter(temBAD_AMM_TOKENS));
+
6348 });
+
6349 }
+
6350
+
6351 void
+
6352 testFixOverflowOffer(FeatureBitset features)
+
6353 {
+
6354 using namespace jtx;
+
6355 using namespace std::chrono;
+
6356 FeatureBitset const all{features};
+
6357
+
6358 Account const gatehub{"gatehub"};
+
6359 Account const bitstamp{"bitstamp"};
+
6360 Account const trader{"trader"};
+
6361 auto const usdGH = gatehub["USD"];
+
6362 auto const btcGH = gatehub["BTC"];
+
6363 auto const usdBIT = bitstamp["USD"];
6364
-
6365 using uint64_t = std::uint64_t;
-
6366
-
6367 for (auto const& input : {
-
6368 InputSet{
-
6369 .testCase = "Test Fix Overflow Offer", //
-
6370 .poolUsdBIT = 3, //
-
6371 .poolUsdGH = 273, //
-
6372 .sendMaxUsdBIT{usdBIT(50)}, //
-
6373 .sendUsdGH{usdGH, uint64_t(272'455089820359), -12}, //
-
6374 .failUsdGH = STAmount{0}, //
-
6375 .failUsdGHr = STAmount{0}, //
-
6376 .failUsdBIT{usdBIT, uint64_t(46'47826086956522), -14}, //
-
6377 .failUsdBITr{usdBIT, uint64_t(46'47826086956521), -14}, //
-
6378 .goodUsdGH{usdGH, uint64_t(96'7543114220382), -13}, //
-
6379 .goodUsdGHr{usdGH, uint64_t(96'7543114222965), -13}, //
-
6380 .goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, //
-
6381 .goodUsdBITr{usdBIT, uint64_t(8'464739069098152), -15}, //
-
6382 .lpTokenBalance = {28'61817604250837, -14}, //
-
6383 .offer1BtcGH = 0.1, //
-
6384 .offer2BtcGH = 0.1, //
-
6385 .offer2UsdGH = 1, //
-
6386 .rateBIT = 1.15, //
-
6387 .rateGH = 1.2, //
-
6388 },
-
6389 InputSet{
-
6390 .testCase = "Overflow test {1, 100, 0.111}", //
-
6391 .poolUsdBIT = 1, //
-
6392 .poolUsdGH = 100, //
-
6393 .sendMaxUsdBIT{usdBIT(0.111)}, //
-
6394 .sendUsdGH{usdGH, 100}, //
-
6395 .failUsdGH = STAmount{0}, //
-
6396 .failUsdGHr = STAmount{0}, //
-
6397 .failUsdBIT{usdBIT, uint64_t(1'111), -3}, //
-
6398 .failUsdBITr{usdBIT, uint64_t(1'111), -3}, //
-
6399 .goodUsdGH{usdGH, uint64_t(90'04347888284115), -14}, //
-
6400 .goodUsdGHr{usdGH, uint64_t(90'04347888284201), -14}, //
-
6401 .goodUsdBIT{usdBIT, uint64_t(1'111), -3}, //
-
6402 .goodUsdBITr{usdBIT, uint64_t(1'111), -3}, //
-
6403 .lpTokenBalance{10, 0}, //
-
6404 .offer1BtcGH = 1e-5, //
-
6405 .offer2BtcGH = 1, //
-
6406 .offer2UsdGH = 1e-5, //
-
6407 .rateBIT = 0, //
-
6408 .rateGH = 0, //
-
6409 },
-
6410 InputSet{
-
6411 .testCase = "Overflow test {1, 100, 1.00}", //
-
6412 .poolUsdBIT = 1, //
-
6413 .poolUsdGH = 100, //
-
6414 .sendMaxUsdBIT{usdBIT(1.00)}, //
-
6415 .sendUsdGH{usdGH, 100}, //
-
6416 .failUsdGH = STAmount{0}, //
-
6417 .failUsdGHr = STAmount{0}, //
-
6418 .failUsdBIT{usdBIT, uint64_t(2), 0}, //
-
6419 .failUsdBITr{usdBIT, uint64_t(2), 0}, //
-
6420 .goodUsdGH{usdGH, uint64_t(52'94379354424079), -14}, //
-
6421 .goodUsdGHr{usdGH, uint64_t(52'94379354424135), -14}, //
-
6422 .goodUsdBIT{usdBIT, uint64_t(2), 0}, //
-
6423 .goodUsdBITr{usdBIT, uint64_t(2), 0}, //
-
6424 .lpTokenBalance{10, 0}, //
-
6425 .offer1BtcGH = 1e-5, //
-
6426 .offer2BtcGH = 1, //
-
6427 .offer2UsdGH = 1e-5, //
-
6428 .rateBIT = 0, //
-
6429 .rateGH = 0, //
-
6430 },
-
6431 InputSet{
-
6432 .testCase = "Overflow test {1, 100, 4.6432}", //
-
6433 .poolUsdBIT = 1, //
-
6434 .poolUsdGH = 100, //
-
6435 .sendMaxUsdBIT{usdBIT(4.6432)}, //
-
6436 .sendUsdGH{usdGH, 100}, //
-
6437 .failUsdGH = STAmount{0}, //
-
6438 .failUsdGHr = STAmount{0}, //
-
6439 .failUsdBIT{usdBIT, uint64_t(5'6432), -4}, //
-
6440 .failUsdBITr{usdBIT, uint64_t(5'6432), -4}, //
-
6441 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6442 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6443 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
-
6444 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
-
6445 .lpTokenBalance{10, 0}, //
-
6446 .offer1BtcGH = 1e-5, //
-
6447 .offer2BtcGH = 1, //
-
6448 .offer2UsdGH = 1e-5, //
-
6449 .rateBIT = 0, //
-
6450 .rateGH = 0, //
-
6451 },
-
6452 InputSet{
-
6453 .testCase = "Overflow test {1, 100, 10}", //
-
6454 .poolUsdBIT = 1, //
-
6455 .poolUsdGH = 100, //
-
6456 .sendMaxUsdBIT{usdBIT(10)}, //
-
6457 .sendUsdGH{usdGH, 100}, //
-
6458 .failUsdGH = STAmount{0}, //
-
6459 .failUsdGHr = STAmount{0}, //
-
6460 .failUsdBIT{usdBIT, uint64_t(11), 0}, //
-
6461 .failUsdBITr{usdBIT, uint64_t(11), 0}, //
-
6462 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6463 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6464 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
-
6465 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
-
6466 .lpTokenBalance{10, 0}, //
-
6467 .offer1BtcGH = 1e-5, //
-
6468 .offer2BtcGH = 1, //
-
6469 .offer2UsdGH = 1e-5, //
-
6470 .rateBIT = 0, //
-
6471 .rateGH = 0, //
-
6472 },
-
6473 InputSet{
-
6474 .testCase = "Overflow test {50, 100, 5.55}", //
-
6475 .poolUsdBIT = 50, //
-
6476 .poolUsdGH = 100, //
-
6477 .sendMaxUsdBIT{usdBIT(5.55)}, //
-
6478 .sendUsdGH{usdGH, 100}, //
-
6479 .failUsdGH = STAmount{0}, //
-
6480 .failUsdGHr = STAmount{0}, //
-
6481 .failUsdBIT{usdBIT, uint64_t(55'55), -2}, //
-
6482 .failUsdBITr{usdBIT, uint64_t(55'55), -2}, //
-
6483 .goodUsdGH{usdGH, uint64_t(90'04347888284113), -14}, //
-
6484 .goodUsdGHr{usdGH, uint64_t(90'0434788828413), -13}, //
-
6485 .goodUsdBIT{usdBIT, uint64_t(55'55), -2}, //
-
6486 .goodUsdBITr{usdBIT, uint64_t(55'55), -2}, //
-
6487 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
-
6488 .offer1BtcGH = 1e-5, //
-
6489 .offer2BtcGH = 1, //
-
6490 .offer2UsdGH = 1e-5, //
-
6491 .rateBIT = 0, //
-
6492 .rateGH = 0, //
-
6493 },
-
6494 InputSet{
-
6495 .testCase = "Overflow test {50, 100, 50.00}", //
-
6496 .poolUsdBIT = 50, //
-
6497 .poolUsdGH = 100, //
-
6498 .sendMaxUsdBIT{usdBIT(50.00)}, //
-
6499 .sendUsdGH{usdGH, 100}, //
-
6500 .failUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
-
6501 .failUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
-
6502 .failUsdBIT{usdBIT, uint64_t(100), 0}, //
-
6503 .failUsdBITr{usdBIT, uint64_t(100), 0}, //
-
6504 .goodUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
-
6505 .goodUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
-
6506 .goodUsdBIT{usdBIT, uint64_t(100), 0}, //
-
6507 .goodUsdBITr{usdBIT, uint64_t(100), 0}, //
-
6508 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
-
6509 .offer1BtcGH = 1e-5, //
-
6510 .offer2BtcGH = 1, //
-
6511 .offer2UsdGH = 1e-5, //
-
6512 .rateBIT = 0, //
-
6513 .rateGH = 0, //
-
6514 },
-
6515 InputSet{
-
6516 .testCase = "Overflow test {50, 100, 232.16}", //
-
6517 .poolUsdBIT = 50, //
-
6518 .poolUsdGH = 100, //
-
6519 .sendMaxUsdBIT{usdBIT(232.16)}, //
-
6520 .sendUsdGH{usdGH, 100}, //
-
6521 .failUsdGH = STAmount{0}, //
-
6522 .failUsdGHr = STAmount{0}, //
-
6523 .failUsdBIT{usdBIT, uint64_t(282'16), -2}, //
-
6524 .failUsdBITr{usdBIT, uint64_t(282'16), -2}, //
-
6525 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6526 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6527 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
-
6528 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
-
6529 .lpTokenBalance{70'71067811865475, -14}, //
-
6530 .offer1BtcGH = 1e-5, //
-
6531 .offer2BtcGH = 1, //
-
6532 .offer2UsdGH = 1e-5, //
-
6533 .rateBIT = 0, //
-
6534 .rateGH = 0, //
-
6535 },
-
6536 InputSet{
-
6537 .testCase = "Overflow test {50, 100, 500}", //
-
6538 .poolUsdBIT = 50, //
-
6539 .poolUsdGH = 100, //
-
6540 .sendMaxUsdBIT{usdBIT(500)}, //
-
6541 .sendUsdGH{usdGH, 100}, //
-
6542 .failUsdGH = STAmount{0}, //
-
6543 .failUsdGHr = STAmount{0}, //
-
6544 .failUsdBIT{usdBIT, uint64_t(550), 0}, //
-
6545 .failUsdBITr{usdBIT, uint64_t(550), 0}, //
-
6546 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
-
6547 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
-
6548 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
-
6549 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
-
6550 .lpTokenBalance{70'71067811865475, -14}, //
-
6551 .offer1BtcGH = 1e-5, //
-
6552 .offer2BtcGH = 1, //
-
6553 .offer2UsdGH = 1e-5, //
-
6554 .rateBIT = 0, //
-
6555 .rateGH = 0, //
-
6556 },
-
6557 })
-
6558 {
-
6559 testcase(input.testCase);
-
6560 for (auto const& features :
-
6561 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
-
6562 {
-
6563 Env env(*this, features);
-
6564
-
6565 env.fund(XRP(5'000), gatehub, bitstamp, trader);
-
6566 env.close();
-
6567
-
6568 if (input.rateGH != 0.0)
-
6569 env(rate(gatehub, input.rateGH));
-
6570 if (input.rateBIT != 0.0)
-
6571 env(rate(bitstamp, input.rateBIT));
-
6572
-
6573 env(trust(trader, usdGH(10'000'000)));
-
6574 env(trust(trader, usdBIT(10'000'000)));
-
6575 env(trust(trader, btcGH(10'000'000)));
-
6576 env.close();
-
6577
-
6578 env(pay(gatehub, trader, usdGH(100'000)));
-
6579 env(pay(gatehub, trader, btcGH(100'000)));
-
6580 env(pay(bitstamp, trader, usdBIT(100'000)));
-
6581 env.close();
-
6582
-
6583 AMM amm{
-
6584 env,
-
6585 trader,
-
6586 usdGH(input.poolUsdGH),
-
6587 usdBIT(input.poolUsdBIT)};
-
6588 env.close();
-
6589
-
6590 IOUAmount const preSwapLPTokenBalance =
-
6591 amm.getLPTokensBalance();
-
6592
-
6593 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
-
6594 env(offer(
-
6595 trader,
-
6596 btcGH(input.offer2BtcGH),
-
6597 usdGH(input.offer2UsdGH)));
-
6598 env.close();
-
6599
-
6600 env(pay(trader, trader, input.sendUsdGH),
-
6601 path(~usdGH),
-
6602 path(~btcGH, ~usdGH),
-
6603 sendmax(input.sendMaxUsdBIT),
-
6604 txflags(tfPartialPayment));
-
6605 env.close();
-
6606
-
6607 auto const failUsdGH =
-
6608 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
-
6609 auto const failUsdBIT =
-
6610 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
-
6611 auto const goodUsdGH =
-
6612 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
-
6613 auto const goodUsdBIT =
-
6614 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
-
6615 if (!features[fixAMMOverflowOffer])
-
6616 {
-
6617 BEAST_EXPECT(amm.expectBalances(
-
6618 failUsdGH, failUsdBIT, input.lpTokenBalance));
-
6619 }
-
6620 else
-
6621 {
-
6622 BEAST_EXPECT(amm.expectBalances(
-
6623 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
-
6624
-
6625 // Invariant: LPToken balance must not change in a
-
6626 // payment or a swap transaction
-
6627 BEAST_EXPECT(
-
6628 amm.getLPTokensBalance() == preSwapLPTokenBalance);
+
6365 struct InputSet
+
6366 {
+
6367 char const* testCase;
+
6368 double const poolUsdBIT;
+
6369 double const poolUsdGH;
+
6370 sendmax const sendMaxUsdBIT;
+
6371 STAmount const sendUsdGH;
+
6372 STAmount const failUsdGH;
+
6373 STAmount const failUsdGHr;
+
6374 STAmount const failUsdBIT;
+
6375 STAmount const failUsdBITr;
+
6376 STAmount const goodUsdGH;
+
6377 STAmount const goodUsdGHr;
+
6378 STAmount const goodUsdBIT;
+
6379 STAmount const goodUsdBITr;
+
6380 IOUAmount const lpTokenBalance;
+
6381 double const offer1BtcGH = 0.1;
+
6382 double const offer2BtcGH = 0.1;
+
6383 double const offer2UsdGH = 1;
+
6384 double const rateBIT = 0.0;
+
6385 double const rateGH = 0.0;
+
6386 };
+
6387
+
6388 using uint64_t = std::uint64_t;
+
6389
+
6390 for (auto const& input : {
+
6391 InputSet{
+
6392 .testCase = "Test Fix Overflow Offer", //
+
6393 .poolUsdBIT = 3, //
+
6394 .poolUsdGH = 273, //
+
6395 .sendMaxUsdBIT{usdBIT(50)}, //
+
6396 .sendUsdGH{usdGH, uint64_t(272'455089820359), -12}, //
+
6397 .failUsdGH = STAmount{0}, //
+
6398 .failUsdGHr = STAmount{0}, //
+
6399 .failUsdBIT{usdBIT, uint64_t(46'47826086956522), -14}, //
+
6400 .failUsdBITr{usdBIT, uint64_t(46'47826086956521), -14}, //
+
6401 .goodUsdGH{usdGH, uint64_t(96'7543114220382), -13}, //
+
6402 .goodUsdGHr{usdGH, uint64_t(96'7543114222965), -13}, //
+
6403 .goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, //
+
6404 .goodUsdBITr{usdBIT, uint64_t(8'464739069098152), -15}, //
+
6405 .lpTokenBalance = {28'61817604250837, -14}, //
+
6406 .offer1BtcGH = 0.1, //
+
6407 .offer2BtcGH = 0.1, //
+
6408 .offer2UsdGH = 1, //
+
6409 .rateBIT = 1.15, //
+
6410 .rateGH = 1.2, //
+
6411 },
+
6412 InputSet{
+
6413 .testCase = "Overflow test {1, 100, 0.111}", //
+
6414 .poolUsdBIT = 1, //
+
6415 .poolUsdGH = 100, //
+
6416 .sendMaxUsdBIT{usdBIT(0.111)}, //
+
6417 .sendUsdGH{usdGH, 100}, //
+
6418 .failUsdGH = STAmount{0}, //
+
6419 .failUsdGHr = STAmount{0}, //
+
6420 .failUsdBIT{usdBIT, uint64_t(1'111), -3}, //
+
6421 .failUsdBITr{usdBIT, uint64_t(1'111), -3}, //
+
6422 .goodUsdGH{usdGH, uint64_t(90'04347888284115), -14}, //
+
6423 .goodUsdGHr{usdGH, uint64_t(90'04347888284201), -14}, //
+
6424 .goodUsdBIT{usdBIT, uint64_t(1'111), -3}, //
+
6425 .goodUsdBITr{usdBIT, uint64_t(1'111), -3}, //
+
6426 .lpTokenBalance{10, 0}, //
+
6427 .offer1BtcGH = 1e-5, //
+
6428 .offer2BtcGH = 1, //
+
6429 .offer2UsdGH = 1e-5, //
+
6430 .rateBIT = 0, //
+
6431 .rateGH = 0, //
+
6432 },
+
6433 InputSet{
+
6434 .testCase = "Overflow test {1, 100, 1.00}", //
+
6435 .poolUsdBIT = 1, //
+
6436 .poolUsdGH = 100, //
+
6437 .sendMaxUsdBIT{usdBIT(1.00)}, //
+
6438 .sendUsdGH{usdGH, 100}, //
+
6439 .failUsdGH = STAmount{0}, //
+
6440 .failUsdGHr = STAmount{0}, //
+
6441 .failUsdBIT{usdBIT, uint64_t(2), 0}, //
+
6442 .failUsdBITr{usdBIT, uint64_t(2), 0}, //
+
6443 .goodUsdGH{usdGH, uint64_t(52'94379354424079), -14}, //
+
6444 .goodUsdGHr{usdGH, uint64_t(52'94379354424135), -14}, //
+
6445 .goodUsdBIT{usdBIT, uint64_t(2), 0}, //
+
6446 .goodUsdBITr{usdBIT, uint64_t(2), 0}, //
+
6447 .lpTokenBalance{10, 0}, //
+
6448 .offer1BtcGH = 1e-5, //
+
6449 .offer2BtcGH = 1, //
+
6450 .offer2UsdGH = 1e-5, //
+
6451 .rateBIT = 0, //
+
6452 .rateGH = 0, //
+
6453 },
+
6454 InputSet{
+
6455 .testCase = "Overflow test {1, 100, 4.6432}", //
+
6456 .poolUsdBIT = 1, //
+
6457 .poolUsdGH = 100, //
+
6458 .sendMaxUsdBIT{usdBIT(4.6432)}, //
+
6459 .sendUsdGH{usdGH, 100}, //
+
6460 .failUsdGH = STAmount{0}, //
+
6461 .failUsdGHr = STAmount{0}, //
+
6462 .failUsdBIT{usdBIT, uint64_t(5'6432), -4}, //
+
6463 .failUsdBITr{usdBIT, uint64_t(5'6432), -4}, //
+
6464 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6465 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6466 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
+
6467 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
+
6468 .lpTokenBalance{10, 0}, //
+
6469 .offer1BtcGH = 1e-5, //
+
6470 .offer2BtcGH = 1, //
+
6471 .offer2UsdGH = 1e-5, //
+
6472 .rateBIT = 0, //
+
6473 .rateGH = 0, //
+
6474 },
+
6475 InputSet{
+
6476 .testCase = "Overflow test {1, 100, 10}", //
+
6477 .poolUsdBIT = 1, //
+
6478 .poolUsdGH = 100, //
+
6479 .sendMaxUsdBIT{usdBIT(10)}, //
+
6480 .sendUsdGH{usdGH, 100}, //
+
6481 .failUsdGH = STAmount{0}, //
+
6482 .failUsdGHr = STAmount{0}, //
+
6483 .failUsdBIT{usdBIT, uint64_t(11), 0}, //
+
6484 .failUsdBITr{usdBIT, uint64_t(11), 0}, //
+
6485 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6486 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6487 .goodUsdBIT{usdBIT, uint64_t(2'821579689703915), -15}, //
+
6488 .goodUsdBITr{usdBIT, uint64_t(2'821579689703954), -15}, //
+
6489 .lpTokenBalance{10, 0}, //
+
6490 .offer1BtcGH = 1e-5, //
+
6491 .offer2BtcGH = 1, //
+
6492 .offer2UsdGH = 1e-5, //
+
6493 .rateBIT = 0, //
+
6494 .rateGH = 0, //
+
6495 },
+
6496 InputSet{
+
6497 .testCase = "Overflow test {50, 100, 5.55}", //
+
6498 .poolUsdBIT = 50, //
+
6499 .poolUsdGH = 100, //
+
6500 .sendMaxUsdBIT{usdBIT(5.55)}, //
+
6501 .sendUsdGH{usdGH, 100}, //
+
6502 .failUsdGH = STAmount{0}, //
+
6503 .failUsdGHr = STAmount{0}, //
+
6504 .failUsdBIT{usdBIT, uint64_t(55'55), -2}, //
+
6505 .failUsdBITr{usdBIT, uint64_t(55'55), -2}, //
+
6506 .goodUsdGH{usdGH, uint64_t(90'04347888284113), -14}, //
+
6507 .goodUsdGHr{usdGH, uint64_t(90'0434788828413), -13}, //
+
6508 .goodUsdBIT{usdBIT, uint64_t(55'55), -2}, //
+
6509 .goodUsdBITr{usdBIT, uint64_t(55'55), -2}, //
+
6510 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
+
6511 .offer1BtcGH = 1e-5, //
+
6512 .offer2BtcGH = 1, //
+
6513 .offer2UsdGH = 1e-5, //
+
6514 .rateBIT = 0, //
+
6515 .rateGH = 0, //
+
6516 },
+
6517 InputSet{
+
6518 .testCase = "Overflow test {50, 100, 50.00}", //
+
6519 .poolUsdBIT = 50, //
+
6520 .poolUsdGH = 100, //
+
6521 .sendMaxUsdBIT{usdBIT(50.00)}, //
+
6522 .sendUsdGH{usdGH, 100}, //
+
6523 .failUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
+
6524 .failUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
+
6525 .failUsdBIT{usdBIT, uint64_t(100), 0}, //
+
6526 .failUsdBITr{usdBIT, uint64_t(100), 0}, //
+
6527 .goodUsdGH{usdGH, uint64_t(52'94379354424081), -14}, //
+
6528 .goodUsdGHr{usdGH, uint64_t(52'94379354424092), -14}, //
+
6529 .goodUsdBIT{usdBIT, uint64_t(100), 0}, //
+
6530 .goodUsdBITr{usdBIT, uint64_t(100), 0}, //
+
6531 .lpTokenBalance{uint64_t(70'71067811865475), -14}, //
+
6532 .offer1BtcGH = 1e-5, //
+
6533 .offer2BtcGH = 1, //
+
6534 .offer2UsdGH = 1e-5, //
+
6535 .rateBIT = 0, //
+
6536 .rateGH = 0, //
+
6537 },
+
6538 InputSet{
+
6539 .testCase = "Overflow test {50, 100, 232.16}", //
+
6540 .poolUsdBIT = 50, //
+
6541 .poolUsdGH = 100, //
+
6542 .sendMaxUsdBIT{usdBIT(232.16)}, //
+
6543 .sendUsdGH{usdGH, 100}, //
+
6544 .failUsdGH = STAmount{0}, //
+
6545 .failUsdGHr = STAmount{0}, //
+
6546 .failUsdBIT{usdBIT, uint64_t(282'16), -2}, //
+
6547 .failUsdBITr{usdBIT, uint64_t(282'16), -2}, //
+
6548 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6549 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6550 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
+
6551 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
+
6552 .lpTokenBalance{70'71067811865475, -14}, //
+
6553 .offer1BtcGH = 1e-5, //
+
6554 .offer2BtcGH = 1, //
+
6555 .offer2UsdGH = 1e-5, //
+
6556 .rateBIT = 0, //
+
6557 .rateGH = 0, //
+
6558 },
+
6559 InputSet{
+
6560 .testCase = "Overflow test {50, 100, 500}", //
+
6561 .poolUsdBIT = 50, //
+
6562 .poolUsdGH = 100, //
+
6563 .sendMaxUsdBIT{usdBIT(500)}, //
+
6564 .sendUsdGH{usdGH, 100}, //
+
6565 .failUsdGH = STAmount{0}, //
+
6566 .failUsdGHr = STAmount{0}, //
+
6567 .failUsdBIT{usdBIT, uint64_t(550), 0}, //
+
6568 .failUsdBITr{usdBIT, uint64_t(550), 0}, //
+
6569 .goodUsdGH{usdGH, uint64_t(35'44113971506987), -14}, //
+
6570 .goodUsdGHr{usdGH, uint64_t(35'44113971506987), -14}, //
+
6571 .goodUsdBIT{usdBIT, uint64_t(141'0789844851958), -13}, //
+
6572 .goodUsdBITr{usdBIT, uint64_t(141'0789844851962), -13}, //
+
6573 .lpTokenBalance{70'71067811865475, -14}, //
+
6574 .offer1BtcGH = 1e-5, //
+
6575 .offer2BtcGH = 1, //
+
6576 .offer2UsdGH = 1e-5, //
+
6577 .rateBIT = 0, //
+
6578 .rateGH = 0, //
+
6579 },
+
6580 })
+
6581 {
+
6582 testcase(input.testCase);
+
6583 for (auto const& features :
+
6584 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
+
6585 {
+
6586 Env env(*this, features);
+
6587
+
6588 env.fund(XRP(5'000), gatehub, bitstamp, trader);
+
6589 env.close();
+
6590
+
6591 if (input.rateGH != 0.0)
+
6592 env(rate(gatehub, input.rateGH));
+
6593 if (input.rateBIT != 0.0)
+
6594 env(rate(bitstamp, input.rateBIT));
+
6595
+
6596 env(trust(trader, usdGH(10'000'000)));
+
6597 env(trust(trader, usdBIT(10'000'000)));
+
6598 env(trust(trader, btcGH(10'000'000)));
+
6599 env.close();
+
6600
+
6601 env(pay(gatehub, trader, usdGH(100'000)));
+
6602 env(pay(gatehub, trader, btcGH(100'000)));
+
6603 env(pay(bitstamp, trader, usdBIT(100'000)));
+
6604 env.close();
+
6605
+
6606 AMM amm{
+
6607 env,
+
6608 trader,
+
6609 usdGH(input.poolUsdGH),
+
6610 usdBIT(input.poolUsdBIT)};
+
6611 env.close();
+
6612
+
6613 IOUAmount const preSwapLPTokenBalance =
+
6614 amm.getLPTokensBalance();
+
6615
+
6616 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
+
6617 env(offer(
+
6618 trader,
+
6619 btcGH(input.offer2BtcGH),
+
6620 usdGH(input.offer2UsdGH)));
+
6621 env.close();
+
6622
+
6623 env(pay(trader, trader, input.sendUsdGH),
+
6624 path(~usdGH),
+
6625 path(~btcGH, ~usdGH),
+
6626 sendmax(input.sendMaxUsdBIT),
+
6627 txflags(tfPartialPayment));
+
6628 env.close();
6629
-
6630 // Invariant: The square root of (product of the pool
-
6631 // balances) must be at least the LPTokenBalance
-
6632 Number const sqrtPoolProduct =
-
6633 root2(goodUsdGH * goodUsdBIT);
-
6634
-
6635 // Include a tiny tolerance for the test cases using
-
6636 // .goodUsdGH{usdGH, uint64_t(35'44113971506987),
-
6637 // -14}, .goodUsdBIT{usdBIT,
-
6638 // uint64_t(2'821579689703915), -15},
-
6639 // These two values multiply
-
6640 // to 99.99999999999994227040383754105 which gets
-
6641 // internally rounded to 100, due to representation
-
6642 // error.
-
6643 BEAST_EXPECT(
-
6644 (sqrtPoolProduct + Number{1, -14} >=
-
6645 input.lpTokenBalance));
-
6646 }
-
6647 }
-
6648 }
-
6649 }
-
6650
-
6651 void
-
6652 testSwapRounding()
-
6653 {
-
6654 testcase("swapRounding");
-
6655 using namespace jtx;
-
6656
-
6657 const STAmount xrpPool{XRP, UINT64_C(51600'000981)};
-
6658 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
-
6659
-
6660 const STAmount xrpBob{XRP, UINT64_C(1092'878933)};
-
6661 const STAmount iouBob{
-
6662 USD, UINT64_C(3'988035892323031), -28}; // 3.9...e-13
-
6663
-
6664 testAMM(
-
6665 [&](AMM& amm, Env& env) {
-
6666 // Check our AMM starting conditions.
-
6667 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(XRP, USD);
-
6668
-
6669 // Set Bob's starting conditions.
-
6670 env.fund(xrpBob, bob);
-
6671 env.trust(USD(1'000'000), bob);
-
6672 env(pay(gw, bob, iouBob));
-
6673 env.close();
-
6674
-
6675 env(offer(bob, XRP(6300), USD(100'000)));
-
6676 env.close();
-
6677
-
6678 // Assert that AMM is unchanged.
-
6679 BEAST_EXPECT(
-
6680 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
-
6681 },
-
6682 {{xrpPool, iouPool}},
-
6683 889,
-
6684 std::nullopt,
-
6685 {jtx::supported_amendments() | fixAMMv1_1});
-
6686 }
-
6687
-
6688 void
-
6689 testFixAMMOfferBlockedByLOB(FeatureBitset features)
-
6690 {
-
6691 testcase("AMM Offer Blocked By LOB");
-
6692 using namespace jtx;
-
6693
-
6694 // Low quality LOB offer blocks AMM liquidity
-
6695
-
6696 // USD/XRP crosses AMM
-
6697 {
-
6698 Env env(*this, features);
-
6699
-
6700 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
-
6701 // This offer blocks AMM offer in pre-amendment
-
6702 env(offer(alice, XRP(1), USD(0.01)));
-
6703 env.close();
-
6704
-
6705 AMM amm(env, gw, XRP(200'000), USD(100'000));
-
6706
-
6707 // The offer doesn't cross AMM in pre-amendment code
-
6708 // It crosses AMM in post-amendment code
-
6709 env(offer(carol, USD(0.49), XRP(1)));
-
6710 env.close();
-
6711
-
6712 if (!features[fixAMMv1_1])
-
6713 {
-
6714 BEAST_EXPECT(amm.expectBalances(
-
6715 XRP(200'000), USD(100'000), amm.tokens()));
-
6716 BEAST_EXPECT(expectOffers(
-
6717 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
-
6718 // Carol's offer is blocked by alice's offer
-
6719 BEAST_EXPECT(expectOffers(
-
6720 env, carol, 1, {{Amounts{USD(0.49), XRP(1)}}}));
-
6721 }
-
6722 else
-
6723 {
-
6724 BEAST_EXPECT(amm.expectBalances(
-
6725 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
-
6726 BEAST_EXPECT(expectOffers(
-
6727 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
-
6728 // Carol's offer crosses AMM
-
6729 BEAST_EXPECT(expectOffers(env, carol, 0));
-
6730 }
-
6731 }
-
6732
-
6733 // There is no blocking offer, the same AMM liquidity is consumed
-
6734 // pre- and post-amendment.
-
6735 {
-
6736 Env env(*this, features);
-
6737
-
6738 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
-
6739 // There is no blocking offer
-
6740 // env(offer(alice, XRP(1), USD(0.01)));
-
6741
-
6742 AMM amm(env, gw, XRP(200'000), USD(100'000));
-
6743
-
6744 // The offer crosses AMM
-
6745 env(offer(carol, USD(0.49), XRP(1)));
-
6746 env.close();
-
6747
-
6748 // The same result as with the blocking offer
-
6749 BEAST_EXPECT(amm.expectBalances(
-
6750 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
-
6751 // Carol's offer crosses AMM
-
6752 BEAST_EXPECT(expectOffers(env, carol, 0));
-
6753 }
-
6754
-
6755 // XRP/USD crosses AMM
-
6756 {
-
6757 Env env(*this, features);
-
6758 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
-
6759
-
6760 // This offer blocks AMM offer in pre-amendment
-
6761 // It crosses AMM in post-amendment code
-
6762 env(offer(bob, USD(1), XRPAmount(500)));
-
6763 env.close();
-
6764 AMM amm(env, alice, XRP(1'000), USD(500));
-
6765 env(offer(carol, XRP(100), USD(55)));
-
6766 env.close();
-
6767 if (!features[fixAMMv1_1])
-
6768 {
-
6769 BEAST_EXPECT(
-
6770 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
-
6771 BEAST_EXPECT(expectOffers(
-
6772 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
-
6773 BEAST_EXPECT(expectOffers(
-
6774 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
-
6775 }
-
6776 else
-
6777 {
-
6778 BEAST_EXPECT(amm.expectBalances(
-
6779 XRPAmount(909'090'909),
-
6780 STAmount{USD, UINT64_C(550'000000055), -9},
-
6781 amm.tokens()));
-
6782 BEAST_EXPECT(expectOffers(
-
6783 env,
-
6784 carol,
-
6785 1,
-
6786 {{Amounts{
-
6787 XRPAmount{9'090'909},
-
6788 STAmount{USD, 4'99999995, -8}}}}));
-
6789 BEAST_EXPECT(expectOffers(
-
6790 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
-
6791 }
-
6792 }
-
6793
-
6794 // There is no blocking offer, the same AMM liquidity is consumed
-
6795 // pre- and post-amendment.
-
6796 {
-
6797 Env env(*this, features);
-
6798 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
-
6799
-
6800 AMM amm(env, alice, XRP(1'000), USD(500));
-
6801 env(offer(carol, XRP(100), USD(55)));
-
6802 env.close();
-
6803 BEAST_EXPECT(amm.expectBalances(
-
6804 XRPAmount(909'090'909),
-
6805 STAmount{USD, UINT64_C(550'000000055), -9},
-
6806 amm.tokens()));
-
6807 BEAST_EXPECT(expectOffers(
-
6808 env,
-
6809 carol,
-
6810 1,
-
6811 {{Amounts{
-
6812 XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
-
6813 }
-
6814 }
-
6815
-
6816 void
-
6817 testLPTokenBalance(FeatureBitset features)
-
6818 {
-
6819 using namespace jtx;
-
6820
-
6821 // Last Liquidity Provider is the issuer of one token
-
6822 {
-
6823 Env env(*this, features);
-
6824 fund(
-
6825 env,
-
6826 gw,
-
6827 {alice, carol},
-
6828 XRP(1'000'000'000),
-
6829 {USD(1'000'000'000)});
-
6830 AMM amm(env, gw, XRP(2), USD(1));
-
6831 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
-
6832 amm.deposit(carol, IOUAmount{1'000'000});
-
6833 amm.withdrawAll(alice);
-
6834 amm.withdrawAll(carol);
-
6835 auto const lpToken = getAccountLines(
-
6836 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
-
6837 auto const lpTokenBalance =
-
6838 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
-
6839 BEAST_EXPECT(
-
6840 lpToken == "1414.213562373095" &&
-
6841 lpTokenBalance == "1414.213562373");
-
6842 if (!features[fixAMMv1_1])
-
6843 {
-
6844 amm.withdrawAll(gw, std::nullopt, ter(tecAMM_BALANCE));
-
6845 BEAST_EXPECT(amm.ammExists());
-
6846 }
-
6847 else
-
6848 {
-
6849 amm.withdrawAll(gw);
-
6850 BEAST_EXPECT(!amm.ammExists());
-
6851 }
-
6852 }
-
6853
-
6854 // Last Liquidity Provider is the issuer of two tokens, or not
-
6855 // the issuer
-
6856 for (auto const& lp : {gw, bob})
-
6857 {
-
6858 Env env(*this, features);
-
6859 auto const ABC = gw["ABC"];
-
6860 fund(
-
6861 env,
-
6862 gw,
-
6863 {alice, carol, bob},
-
6864 XRP(1'000),
-
6865 {USD(1'000'000'000), ABC(1'000'000'000'000)});
-
6866 AMM amm(env, lp, ABC(2'000'000), USD(1));
-
6867 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
-
6868 amm.deposit(carol, IOUAmount{1'000'000});
-
6869 amm.withdrawAll(alice);
-
6870 amm.withdrawAll(carol);
-
6871 auto const lpToken = getAccountLines(
-
6872 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
-
6873 auto const lpTokenBalance =
-
6874 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
-
6875 BEAST_EXPECT(
-
6876 lpToken == "1414.213562373095" &&
-
6877 lpTokenBalance == "1414.213562373");
-
6878 if (!features[fixAMMv1_1])
-
6879 {
-
6880 amm.withdrawAll(lp, std::nullopt, ter(tecAMM_BALANCE));
-
6881 BEAST_EXPECT(amm.ammExists());
-
6882 }
-
6883 else
-
6884 {
-
6885 amm.withdrawAll(lp);
-
6886 BEAST_EXPECT(!amm.ammExists());
-
6887 }
-
6888 }
-
6889
-
6890 // More than one Liquidity Provider
-
6891 // XRP/IOU
-
6892 {
-
6893 Env env(*this, features);
-
6894 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)});
-
6895 AMM amm(env, gw, XRP(10), USD(10));
-
6896 amm.deposit(alice, 1'000);
-
6897 auto res =
-
6898 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6899 BEAST_EXPECT(res && !res.value());
-
6900 res =
-
6901 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
-
6902 BEAST_EXPECT(res && !res.value());
-
6903 }
-
6904 // IOU/IOU, issuer of both IOU
-
6905 {
-
6906 Env env(*this, features);
-
6907 fund(env, gw, {alice}, XRP(1'000), {USD(1'000), EUR(1'000)});
-
6908 AMM amm(env, gw, EUR(10), USD(10));
-
6909 amm.deposit(alice, 1'000);
-
6910 auto res =
-
6911 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6912 BEAST_EXPECT(res && !res.value());
-
6913 res =
-
6914 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
-
6915 BEAST_EXPECT(res && !res.value());
-
6916 }
-
6917 // IOU/IOU, issuer of one IOU
-
6918 {
-
6919 Env env(*this, features);
-
6920 Account const gw1("gw1");
-
6921 auto const YAN = gw1["YAN"];
-
6922 fund(env, gw, {gw1}, XRP(1'000), {USD(1'000)});
-
6923 fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
-
6924 AMM amm(env, gw1, YAN(10), USD(10));
-
6925 amm.deposit(gw, 1'000);
-
6926 auto res =
-
6927 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
-
6928 BEAST_EXPECT(res && !res.value());
-
6929 res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw1);
-
6930 BEAST_EXPECT(res && !res.value());
-
6931 }
-
6932 }
-
6933
-
6934 void
-
6935 testAMMClawback(FeatureBitset features)
-
6936 {
-
6937 testcase("test clawback from AMM account");
-
6938 using namespace jtx;
-
6939
-
6940 // Issuer has clawback enabled
-
6941 Env env(*this, features);
-
6942 env.fund(XRP(1'000), gw);
-
6943 env(fset(gw, asfAllowTrustLineClawback));
-
6944 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
-
6945 env.close();
-
6946
-
6947 // If featureAMMClawback is not enabled, AMMCreate is not allowed for
-
6948 // clawback-enabled issuer
-
6949 if (!features[featureAMMClawback])
-
6950 {
-
6951 AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-
6952 AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
-
6953 env(fclear(gw, asfAllowTrustLineClawback));
-
6954 env.close();
-
6955 // Can't be cleared
-
6956 AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-
6957 }
-
6958 // If featureAMMClawback is enabled, AMMCreate is allowed for
-
6959 // clawback-enabled issuer. Clawback from the AMM Account is not
-
6960 // allowed, which will return tecAMM_ACCOUNT. We can only use
-
6961 // AMMClawback transaction to claw back from AMM Account.
-
6962 else
-
6963 {
-
6964 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
-
6965 AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
-
6966
-
6967 // Construct the amount being clawed back using AMM account.
-
6968 // By doing this, we make the clawback transaction's Amount field's
-
6969 // subfield `issuer` to be the AMM account, which means
-
6970 // we are clawing back from an AMM account. This should return an
-
6971 // tecAMM_ACCOUNT error because regular Clawback transaction is not
-
6972 // allowed for clawing back from an AMM account. Please notice the
-
6973 // `issuer` subfield represents the account being clawed back, which
-
6974 // is confusing.
-
6975 Issue usd(USD.issue().currency, amm.ammAccount());
-
6976 auto amount = amountFromString(usd, "10");
-
6977 env(claw(gw, amount), ter(tecAMM_ACCOUNT));
-
6978 }
-
6979 }
-
6980
-
6981 void
-
6982 testAMMDepositWithFrozenAssets(FeatureBitset features)
-
6983 {
-
6984 testcase("test AMMDeposit with frozen assets");
-
6985 using namespace jtx;
-
6986
-
6987 // This lambda function is used to create trustlines
-
6988 // between gw and alice, and create an AMM account.
-
6989 // And also test the callback function.
-
6990 auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
-
6991 env.fund(XRP(1'000), gw);
-
6992 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
-
6993 env.close();
-
6994 AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
-
6995 env(trust(gw, alice["USD"](0), tfSetFreeze));
-
6996 cb(amm);
-
6997 };
-
6998
-
6999 // Deposit two assets, one of which is frozen,
-
7000 // then we should get tecFROZEN error.
-
7001 {
-
7002 Env env(*this, features);
-
7003 testAMMDeposit(env, [&](AMM& amm) {
-
7004 amm.deposit(
-
7005 alice,
-
7006 USD(100),
-
7007 XRP(100),
-
7008 std::nullopt,
-
7009 tfTwoAsset,
-
7010 ter(tecFROZEN));
-
7011 });
-
7012 }
-
7013
-
7014 // Deposit one asset, which is the frozen token,
-
7015 // then we should get tecFROZEN error.
-
7016 {
-
7017 Env env(*this, features);
-
7018 testAMMDeposit(env, [&](AMM& amm) {
-
7019 amm.deposit(
-
7020 alice,
-
7021 USD(100),
-
7022 std::nullopt,
-
7023 std::nullopt,
-
7024 tfSingleAsset,
-
7025 ter(tecFROZEN));
-
7026 });
-
7027 }
-
7028
-
7029 if (features[featureAMMClawback])
-
7030 {
-
7031 // Deposit one asset which is not the frozen token,
-
7032 // but the other asset is frozen. We should get tecFROZEN error
-
7033 // when feature AMMClawback is enabled.
-
7034 Env env(*this, features);
-
7035 testAMMDeposit(env, [&](AMM& amm) {
-
7036 amm.deposit(
-
7037 alice,
-
7038 XRP(100),
-
7039 std::nullopt,
-
7040 std::nullopt,
-
7041 tfSingleAsset,
-
7042 ter(tecFROZEN));
-
7043 });
-
7044 }
-
7045 else
-
7046 {
-
7047 // Deposit one asset which is not the frozen token,
-
7048 // but the other asset is frozen. We will get tecSUCCESS
-
7049 // when feature AMMClawback is not enabled.
-
7050 Env env(*this, features);
-
7051 testAMMDeposit(env, [&](AMM& amm) {
-
7052 amm.deposit(
-
7053 alice,
-
7054 XRP(100),
-
7055 std::nullopt,
-
7056 std::nullopt,
-
7057 tfSingleAsset,
-
7058 ter(tesSUCCESS));
-
7059 });
-
7060 }
-
7061 }
-
7062
-
7063 void
-
7064 testFixReserveCheckOnWithdrawal(FeatureBitset features)
-
7065 {
-
7066 testcase("Fix Reserve Check On Withdrawal");
-
7067 using namespace jtx;
-
7068
-
7069 auto const err = features[fixAMMv1_2] ? ter(tecINSUFFICIENT_RESERVE)
-
7070 : ter(tesSUCCESS);
-
7071
-
7072 auto test = [&](auto&& cb) {
+
6630 auto const failUsdGH =
+
6631 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
+
6632 auto const failUsdBIT =
+
6633 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
+
6634 auto const goodUsdGH =
+
6635 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
+
6636 auto const goodUsdBIT =
+
6637 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
+
6638 if (!features[fixAMMOverflowOffer])
+
6639 {
+
6640 BEAST_EXPECT(amm.expectBalances(
+
6641 failUsdGH, failUsdBIT, input.lpTokenBalance));
+
6642 }
+
6643 else
+
6644 {
+
6645 BEAST_EXPECT(amm.expectBalances(
+
6646 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
+
6647
+
6648 // Invariant: LPToken balance must not change in a
+
6649 // payment or a swap transaction
+
6650 BEAST_EXPECT(
+
6651 amm.getLPTokensBalance() == preSwapLPTokenBalance);
+
6652
+
6653 // Invariant: The square root of (product of the pool
+
6654 // balances) must be at least the LPTokenBalance
+
6655 Number const sqrtPoolProduct =
+
6656 root2(goodUsdGH * goodUsdBIT);
+
6657
+
6658 // Include a tiny tolerance for the test cases using
+
6659 // .goodUsdGH{usdGH, uint64_t(35'44113971506987),
+
6660 // -14}, .goodUsdBIT{usdBIT,
+
6661 // uint64_t(2'821579689703915), -15},
+
6662 // These two values multiply
+
6663 // to 99.99999999999994227040383754105 which gets
+
6664 // internally rounded to 100, due to representation
+
6665 // error.
+
6666 BEAST_EXPECT(
+
6667 (sqrtPoolProduct + Number{1, -14} >=
+
6668 input.lpTokenBalance));
+
6669 }
+
6670 }
+
6671 }
+
6672 }
+
6673
+
6674 void
+
6675 testSwapRounding()
+
6676 {
+
6677 testcase("swapRounding");
+
6678 using namespace jtx;
+
6679
+
6680 const STAmount xrpPool{XRP, UINT64_C(51600'000981)};
+
6681 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
+
6682
+
6683 const STAmount xrpBob{XRP, UINT64_C(1092'878933)};
+
6684 const STAmount iouBob{
+
6685 USD, UINT64_C(3'988035892323031), -28}; // 3.9...e-13
+
6686
+
6687 testAMM(
+
6688 [&](AMM& amm, Env& env) {
+
6689 // Check our AMM starting conditions.
+
6690 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(XRP, USD);
+
6691
+
6692 // Set Bob's starting conditions.
+
6693 env.fund(xrpBob, bob);
+
6694 env.trust(USD(1'000'000), bob);
+
6695 env(pay(gw, bob, iouBob));
+
6696 env.close();
+
6697
+
6698 env(offer(bob, XRP(6300), USD(100'000)));
+
6699 env.close();
+
6700
+
6701 // Assert that AMM is unchanged.
+
6702 BEAST_EXPECT(
+
6703 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
+
6704 },
+
6705 {{xrpPool, iouPool}},
+
6706 889,
+
6707 std::nullopt,
+
6708 {jtx::supported_amendments() | fixAMMv1_1});
+
6709 }
+
6710
+
6711 void
+
6712 testFixAMMOfferBlockedByLOB(FeatureBitset features)
+
6713 {
+
6714 testcase("AMM Offer Blocked By LOB");
+
6715 using namespace jtx;
+
6716
+
6717 // Low quality LOB offer blocks AMM liquidity
+
6718
+
6719 // USD/XRP crosses AMM
+
6720 {
+
6721 Env env(*this, features);
+
6722
+
6723 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
+
6724 // This offer blocks AMM offer in pre-amendment
+
6725 env(offer(alice, XRP(1), USD(0.01)));
+
6726 env.close();
+
6727
+
6728 AMM amm(env, gw, XRP(200'000), USD(100'000));
+
6729
+
6730 // The offer doesn't cross AMM in pre-amendment code
+
6731 // It crosses AMM in post-amendment code
+
6732 env(offer(carol, USD(0.49), XRP(1)));
+
6733 env.close();
+
6734
+
6735 if (!features[fixAMMv1_1])
+
6736 {
+
6737 BEAST_EXPECT(amm.expectBalances(
+
6738 XRP(200'000), USD(100'000), amm.tokens()));
+
6739 BEAST_EXPECT(expectOffers(
+
6740 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
+
6741 // Carol's offer is blocked by alice's offer
+
6742 BEAST_EXPECT(expectOffers(
+
6743 env, carol, 1, {{Amounts{USD(0.49), XRP(1)}}}));
+
6744 }
+
6745 else
+
6746 {
+
6747 BEAST_EXPECT(amm.expectBalances(
+
6748 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
+
6749 BEAST_EXPECT(expectOffers(
+
6750 env, alice, 1, {{Amounts{XRP(1), USD(0.01)}}}));
+
6751 // Carol's offer crosses AMM
+
6752 BEAST_EXPECT(expectOffers(env, carol, 0));
+
6753 }
+
6754 }
+
6755
+
6756 // There is no blocking offer, the same AMM liquidity is consumed
+
6757 // pre- and post-amendment.
+
6758 {
+
6759 Env env(*this, features);
+
6760
+
6761 fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)});
+
6762 // There is no blocking offer
+
6763 // env(offer(alice, XRP(1), USD(0.01)));
+
6764
+
6765 AMM amm(env, gw, XRP(200'000), USD(100'000));
+
6766
+
6767 // The offer crosses AMM
+
6768 env(offer(carol, USD(0.49), XRP(1)));
+
6769 env.close();
+
6770
+
6771 // The same result as with the blocking offer
+
6772 BEAST_EXPECT(amm.expectBalances(
+
6773 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
+
6774 // Carol's offer crosses AMM
+
6775 BEAST_EXPECT(expectOffers(env, carol, 0));
+
6776 }
+
6777
+
6778 // XRP/USD crosses AMM
+
6779 {
+
6780 Env env(*this, features);
+
6781 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
+
6782
+
6783 // This offer blocks AMM offer in pre-amendment
+
6784 // It crosses AMM in post-amendment code
+
6785 env(offer(bob, USD(1), XRPAmount(500)));
+
6786 env.close();
+
6787 AMM amm(env, alice, XRP(1'000), USD(500));
+
6788 env(offer(carol, XRP(100), USD(55)));
+
6789 env.close();
+
6790 if (!features[fixAMMv1_1])
+
6791 {
+
6792 BEAST_EXPECT(
+
6793 amm.expectBalances(XRP(1'000), USD(500), amm.tokens()));
+
6794 BEAST_EXPECT(expectOffers(
+
6795 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
+
6796 BEAST_EXPECT(expectOffers(
+
6797 env, carol, 1, {{Amounts{XRP(100), USD(55)}}}));
+
6798 }
+
6799 else
+
6800 {
+
6801 BEAST_EXPECT(amm.expectBalances(
+
6802 XRPAmount(909'090'909),
+
6803 STAmount{USD, UINT64_C(550'000000055), -9},
+
6804 amm.tokens()));
+
6805 BEAST_EXPECT(expectOffers(
+
6806 env,
+
6807 carol,
+
6808 1,
+
6809 {{Amounts{
+
6810 XRPAmount{9'090'909},
+
6811 STAmount{USD, 4'99999995, -8}}}}));
+
6812 BEAST_EXPECT(expectOffers(
+
6813 env, bob, 1, {{Amounts{USD(1), XRPAmount(500)}}}));
+
6814 }
+
6815 }
+
6816
+
6817 // There is no blocking offer, the same AMM liquidity is consumed
+
6818 // pre- and post-amendment.
+
6819 {
+
6820 Env env(*this, features);
+
6821 fund(env, gw, {alice, carol, bob}, XRP(10'000), {USD(1'000)});
+
6822
+
6823 AMM amm(env, alice, XRP(1'000), USD(500));
+
6824 env(offer(carol, XRP(100), USD(55)));
+
6825 env.close();
+
6826 BEAST_EXPECT(amm.expectBalances(
+
6827 XRPAmount(909'090'909),
+
6828 STAmount{USD, UINT64_C(550'000000055), -9},
+
6829 amm.tokens()));
+
6830 BEAST_EXPECT(expectOffers(
+
6831 env,
+
6832 carol,
+
6833 1,
+
6834 {{Amounts{
+
6835 XRPAmount{9'090'909}, STAmount{USD, 4'99999995, -8}}}}));
+
6836 }
+
6837 }
+
6838
+
6839 void
+
6840 testLPTokenBalance(FeatureBitset features)
+
6841 {
+
6842 using namespace jtx;
+
6843
+
6844 // Last Liquidity Provider is the issuer of one token
+
6845 {
+
6846 Env env(*this, features);
+
6847 fund(
+
6848 env,
+
6849 gw,
+
6850 {alice, carol},
+
6851 XRP(1'000'000'000),
+
6852 {USD(1'000'000'000)});
+
6853 AMM amm(env, gw, XRP(2), USD(1));
+
6854 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
+
6855 amm.deposit(carol, IOUAmount{1'000'000});
+
6856 amm.withdrawAll(alice);
+
6857 amm.withdrawAll(carol);
+
6858 auto const lpToken = getAccountLines(
+
6859 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
+
6860 auto const lpTokenBalance =
+
6861 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
+
6862 BEAST_EXPECT(
+
6863 lpToken == "1414.213562373095" &&
+
6864 lpTokenBalance == "1414.213562373");
+
6865 if (!features[fixAMMv1_1])
+
6866 {
+
6867 amm.withdrawAll(gw, std::nullopt, ter(tecAMM_BALANCE));
+
6868 BEAST_EXPECT(amm.ammExists());
+
6869 }
+
6870 else
+
6871 {
+
6872 amm.withdrawAll(gw);
+
6873 BEAST_EXPECT(!amm.ammExists());
+
6874 }
+
6875 }
+
6876
+
6877 // Last Liquidity Provider is the issuer of two tokens, or not
+
6878 // the issuer
+
6879 for (auto const& lp : {gw, bob})
+
6880 {
+
6881 Env env(*this, features);
+
6882 auto const ABC = gw["ABC"];
+
6883 fund(
+
6884 env,
+
6885 gw,
+
6886 {alice, carol, bob},
+
6887 XRP(1'000),
+
6888 {USD(1'000'000'000), ABC(1'000'000'000'000)});
+
6889 AMM amm(env, lp, ABC(2'000'000), USD(1));
+
6890 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
+
6891 amm.deposit(carol, IOUAmount{1'000'000});
+
6892 amm.withdrawAll(alice);
+
6893 amm.withdrawAll(carol);
+
6894 auto const lpToken = getAccountLines(
+
6895 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
+
6896 auto const lpTokenBalance =
+
6897 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
+
6898 BEAST_EXPECT(
+
6899 lpToken == "1414.213562373095" &&
+
6900 lpTokenBalance == "1414.213562373");
+
6901 if (!features[fixAMMv1_1])
+
6902 {
+
6903 amm.withdrawAll(lp, std::nullopt, ter(tecAMM_BALANCE));
+
6904 BEAST_EXPECT(amm.ammExists());
+
6905 }
+
6906 else
+
6907 {
+
6908 amm.withdrawAll(lp);
+
6909 BEAST_EXPECT(!amm.ammExists());
+
6910 }
+
6911 }
+
6912
+
6913 // More than one Liquidity Provider
+
6914 // XRP/IOU
+
6915 {
+
6916 Env env(*this, features);
+
6917 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)});
+
6918 AMM amm(env, gw, XRP(10), USD(10));
+
6919 amm.deposit(alice, 1'000);
+
6920 auto res =
+
6921 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
6922 BEAST_EXPECT(res && !res.value());
+
6923 res =
+
6924 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
+
6925 BEAST_EXPECT(res && !res.value());
+
6926 }
+
6927 // IOU/IOU, issuer of both IOU
+
6928 {
+
6929 Env env(*this, features);
+
6930 fund(env, gw, {alice}, XRP(1'000), {USD(1'000), EUR(1'000)});
+
6931 AMM amm(env, gw, EUR(10), USD(10));
+
6932 amm.deposit(alice, 1'000);
+
6933 auto res =
+
6934 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
6935 BEAST_EXPECT(res && !res.value());
+
6936 res =
+
6937 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
+
6938 BEAST_EXPECT(res && !res.value());
+
6939 }
+
6940 // IOU/IOU, issuer of one IOU
+
6941 {
+
6942 Env env(*this, features);
+
6943 Account const gw1("gw1");
+
6944 auto const YAN = gw1["YAN"];
+
6945 fund(env, gw, {gw1}, XRP(1'000), {USD(1'000)});
+
6946 fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
+
6947 AMM amm(env, gw1, YAN(10), USD(10));
+
6948 amm.deposit(gw, 1'000);
+
6949 auto res =
+
6950 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw);
+
6951 BEAST_EXPECT(res && !res.value());
+
6952 res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw1);
+
6953 BEAST_EXPECT(res && !res.value());
+
6954 }
+
6955 }
+
6956
+
6957 void
+
6958 testAMMClawback(FeatureBitset features)
+
6959 {
+
6960 testcase("test clawback from AMM account");
+
6961 using namespace jtx;
+
6962
+
6963 // Issuer has clawback enabled
+
6964 Env env(*this, features);
+
6965 env.fund(XRP(1'000), gw);
+
6966 env(fset(gw, asfAllowTrustLineClawback));
+
6967 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+
6968 env.close();
+
6969
+
6970 // If featureAMMClawback is not enabled, AMMCreate is not allowed for
+
6971 // clawback-enabled issuer
+
6972 if (!features[featureAMMClawback])
+
6973 {
+
6974 AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+
6975 AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
+
6976 env(fclear(gw, asfAllowTrustLineClawback));
+
6977 env.close();
+
6978 // Can't be cleared
+
6979 AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+
6980 }
+
6981 // If featureAMMClawback is enabled, AMMCreate is allowed for
+
6982 // clawback-enabled issuer. Clawback from the AMM Account is not
+
6983 // allowed, which will return tecAMM_ACCOUNT. We can only use
+
6984 // AMMClawback transaction to claw back from AMM Account.
+
6985 else
+
6986 {
+
6987 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
6988 AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
+
6989
+
6990 // Construct the amount being clawed back using AMM account.
+
6991 // By doing this, we make the clawback transaction's Amount field's
+
6992 // subfield `issuer` to be the AMM account, which means
+
6993 // we are clawing back from an AMM account. This should return an
+
6994 // tecAMM_ACCOUNT error because regular Clawback transaction is not
+
6995 // allowed for clawing back from an AMM account. Please notice the
+
6996 // `issuer` subfield represents the account being clawed back, which
+
6997 // is confusing.
+
6998 Issue usd(USD.issue().currency, amm.ammAccount());
+
6999 auto amount = amountFromString(usd, "10");
+
7000 env(claw(gw, amount), ter(tecAMM_ACCOUNT));
+
7001 }
+
7002 }
+
7003
+
7004 void
+
7005 testAMMDepositWithFrozenAssets(FeatureBitset features)
+
7006 {
+
7007 testcase("test AMMDeposit with frozen assets");
+
7008 using namespace jtx;
+
7009
+
7010 // This lambda function is used to create trustlines
+
7011 // between gw and alice, and create an AMM account.
+
7012 // And also test the callback function.
+
7013 auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
+
7014 env.fund(XRP(1'000), gw);
+
7015 fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+
7016 env.close();
+
7017 AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
+
7018 env(trust(gw, alice["USD"](0), tfSetFreeze));
+
7019 cb(amm);
+
7020 };
+
7021
+
7022 // Deposit two assets, one of which is frozen,
+
7023 // then we should get tecFROZEN error.
+
7024 {
+
7025 Env env(*this, features);
+
7026 testAMMDeposit(env, [&](AMM& amm) {
+
7027 amm.deposit(
+
7028 alice,
+
7029 USD(100),
+
7030 XRP(100),
+
7031 std::nullopt,
+
7032 tfTwoAsset,
+
7033 ter(tecFROZEN));
+
7034 });
+
7035 }
+
7036
+
7037 // Deposit one asset, which is the frozen token,
+
7038 // then we should get tecFROZEN error.
+
7039 {
+
7040 Env env(*this, features);
+
7041 testAMMDeposit(env, [&](AMM& amm) {
+
7042 amm.deposit(
+
7043 alice,
+
7044 USD(100),
+
7045 std::nullopt,
+
7046 std::nullopt,
+
7047 tfSingleAsset,
+
7048 ter(tecFROZEN));
+
7049 });
+
7050 }
+
7051
+
7052 if (features[featureAMMClawback])
+
7053 {
+
7054 // Deposit one asset which is not the frozen token,
+
7055 // but the other asset is frozen. We should get tecFROZEN error
+
7056 // when feature AMMClawback is enabled.
+
7057 Env env(*this, features);
+
7058 testAMMDeposit(env, [&](AMM& amm) {
+
7059 amm.deposit(
+
7060 alice,
+
7061 XRP(100),
+
7062 std::nullopt,
+
7063 std::nullopt,
+
7064 tfSingleAsset,
+
7065 ter(tecFROZEN));
+
7066 });
+
7067 }
+
7068 else
+
7069 {
+
7070 // Deposit one asset which is not the frozen token,
+
7071 // but the other asset is frozen. We will get tecSUCCESS
+
7072 // when feature AMMClawback is not enabled.
7073 Env env(*this, features);
-
7074 auto const starting_xrp =
-
7075 reserve(env, 2) + env.current()->fees().base * 5;
-
7076 env.fund(starting_xrp, gw);
-
7077 env.fund(starting_xrp, alice);
-
7078 env.trust(USD(2'000), alice);
-
7079 env.close();
-
7080 env(pay(gw, alice, USD(2'000)));
-
7081 env.close();
-
7082 AMM amm(env, gw, EUR(1'000), USD(1'000));
-
7083 amm.deposit(alice, USD(1));
-
7084 cb(amm);
-
7085 };
-
7086
-
7087 // Equal withdraw
-
7088 test([&](AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
-
7089
-
7090 // Equal withdraw with a limit
-
7091 test([&](AMM& amm) {
-
7092 amm.withdraw(WithdrawArg{
-
7093 .account = alice,
-
7094 .asset1Out = EUR(0.1),
-
7095 .asset2Out = USD(0.1),
-
7096 .err = err});
-
7097 amm.withdraw(WithdrawArg{
-
7098 .account = alice,
-
7099 .asset1Out = USD(0.1),
-
7100 .asset2Out = EUR(0.1),
-
7101 .err = err});
-
7102 });
-
7103
-
7104 // Single withdraw
-
7105 test([&](AMM& amm) {
-
7106 amm.withdraw(WithdrawArg{
-
7107 .account = alice, .asset1Out = EUR(0.1), .err = err});
-
7108 amm.withdraw(WithdrawArg{.account = alice, .asset1Out = USD(0.1)});
-
7109 });
-
7110 }
-
7111
-
7112 void
-
7113 run() override
-
7114 {
-
7115 FeatureBitset const all{jtx::supported_amendments()};
-
7116 testInvalidInstance();
-
7117 testInstanceCreate();
-
7118 testInvalidDeposit(all);
-
7119 testInvalidDeposit(all - featureAMMClawback);
-
7120 testDeposit();
-
7121 testInvalidWithdraw();
-
7122 testWithdraw();
-
7123 testInvalidFeeVote();
-
7124 testFeeVote();
-
7125 testInvalidBid();
-
7126 testBid(all);
-
7127 testBid(all - fixAMMv1_1);
-
7128 testInvalidAMMPayment();
-
7129 testBasicPaymentEngine(all);
-
7130 testBasicPaymentEngine(all - fixAMMv1_1);
-
7131 testBasicPaymentEngine(all - fixReducedOffersV2);
-
7132 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
-
7133 testAMMTokens();
-
7134 testAmendment();
-
7135 testFlags();
-
7136 testRippling();
-
7137 testAMMAndCLOB(all);
-
7138 testAMMAndCLOB(all - fixAMMv1_1);
-
7139 testTradingFee(all);
-
7140 testTradingFee(all - fixAMMv1_1);
-
7141 testAdjustedTokens(all);
-
7142 testAdjustedTokens(all - fixAMMv1_1);
-
7143 testAutoDelete();
-
7144 testClawback();
-
7145 testAMMID();
-
7146 testSelection(all);
-
7147 testSelection(all - fixAMMv1_1);
-
7148 testFixDefaultInnerObj();
-
7149 testMalformed();
-
7150 testFixOverflowOffer(all);
-
7151 testFixOverflowOffer(all - fixAMMv1_1);
-
7152 testSwapRounding();
-
7153 testFixChangeSpotPriceQuality(all);
-
7154 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
-
7155 testFixAMMOfferBlockedByLOB(all);
-
7156 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
-
7157 testLPTokenBalance(all);
-
7158 testLPTokenBalance(all - fixAMMv1_1);
-
7159 testAMMClawback(all);
-
7160 testAMMClawback(all - featureAMMClawback);
-
7161 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
-
7162 testAMMDepositWithFrozenAssets(all);
-
7163 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
-
7164 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
-
7165 testFixReserveCheckOnWithdrawal(all);
-
7166 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
-
7167 }
-
7168};
-
7169
-
7170BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
-
7171
-
7172} // namespace test
-
7173} // namespace ripple
+
7074 testAMMDeposit(env, [&](AMM& amm) {
+
7075 amm.deposit(
+
7076 alice,
+
7077 XRP(100),
+
7078 std::nullopt,
+
7079 std::nullopt,
+
7080 tfSingleAsset,
+
7081 ter(tesSUCCESS));
+
7082 });
+
7083 }
+
7084 }
+
7085
+
7086 void
+
7087 testFixReserveCheckOnWithdrawal(FeatureBitset features)
+
7088 {
+
7089 testcase("Fix Reserve Check On Withdrawal");
+
7090 using namespace jtx;
+
7091
+
7092 auto const err = features[fixAMMv1_2] ? ter(tecINSUFFICIENT_RESERVE)
+
7093 : ter(tesSUCCESS);
+
7094
+
7095 auto test = [&](auto&& cb) {
+
7096 Env env(*this, features);
+
7097 auto const starting_xrp =
+
7098 reserve(env, 2) + env.current()->fees().base * 5;
+
7099 env.fund(starting_xrp, gw);
+
7100 env.fund(starting_xrp, alice);
+
7101 env.trust(USD(2'000), alice);
+
7102 env.close();
+
7103 env(pay(gw, alice, USD(2'000)));
+
7104 env.close();
+
7105 AMM amm(env, gw, EUR(1'000), USD(1'000));
+
7106 amm.deposit(alice, USD(1));
+
7107 cb(amm);
+
7108 };
+
7109
+
7110 // Equal withdraw
+
7111 test([&](AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
+
7112
+
7113 // Equal withdraw with a limit
+
7114 test([&](AMM& amm) {
+
7115 amm.withdraw(WithdrawArg{
+
7116 .account = alice,
+
7117 .asset1Out = EUR(0.1),
+
7118 .asset2Out = USD(0.1),
+
7119 .err = err});
+
7120 amm.withdraw(WithdrawArg{
+
7121 .account = alice,
+
7122 .asset1Out = USD(0.1),
+
7123 .asset2Out = EUR(0.1),
+
7124 .err = err});
+
7125 });
+
7126
+
7127 // Single withdraw
+
7128 test([&](AMM& amm) {
+
7129 amm.withdraw(WithdrawArg{
+
7130 .account = alice, .asset1Out = EUR(0.1), .err = err});
+
7131 amm.withdraw(WithdrawArg{.account = alice, .asset1Out = USD(0.1)});
+
7132 });
+
7133 }
+
7134
+
7135 void
+
7136 run() override
+
7137 {
+
7138 FeatureBitset const all{jtx::supported_amendments()};
+
7139 testInvalidInstance();
+
7140 testInstanceCreate();
+
7141 testInvalidDeposit(all);
+
7142 testInvalidDeposit(all - featureAMMClawback);
+
7143 testDeposit();
+
7144 testInvalidWithdraw();
+
7145 testWithdraw();
+
7146 testInvalidFeeVote();
+
7147 testFeeVote();
+
7148 testInvalidBid();
+
7149 testBid(all);
+
7150 testBid(all - fixAMMv1_1);
+
7151 testInvalidAMMPayment();
+
7152 testBasicPaymentEngine(all);
+
7153 testBasicPaymentEngine(all - fixAMMv1_1);
+
7154 testBasicPaymentEngine(all - fixReducedOffersV2);
+
7155 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
+
7156 testAMMTokens();
+
7157 testAmendment();
+
7158 testFlags();
+
7159 testRippling();
+
7160 testAMMAndCLOB(all);
+
7161 testAMMAndCLOB(all - fixAMMv1_1);
+
7162 testTradingFee(all);
+
7163 testTradingFee(all - fixAMMv1_1);
+
7164 testAdjustedTokens(all);
+
7165 testAdjustedTokens(all - fixAMMv1_1);
+
7166 testAutoDelete();
+
7167 testClawback();
+
7168 testAMMID();
+
7169 testSelection(all);
+
7170 testSelection(all - fixAMMv1_1);
+
7171 testFixDefaultInnerObj();
+
7172 testMalformed();
+
7173 testFixOverflowOffer(all);
+
7174 testFixOverflowOffer(all - fixAMMv1_1);
+
7175 testSwapRounding();
+
7176 testFixChangeSpotPriceQuality(all);
+
7177 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
+
7178 testFixAMMOfferBlockedByLOB(all);
+
7179 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
+
7180 testLPTokenBalance(all);
+
7181 testLPTokenBalance(all - fixAMMv1_1);
+
7182 testAMMClawback(all);
+
7183 testAMMClawback(all - featureAMMClawback);
+
7184 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
+
7185 testAMMDepositWithFrozenAssets(all);
+
7186 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
+
7187 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
+
7188 testFixReserveCheckOnWithdrawal(all);
+
7189 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
+
7190 }
+
7191};
+
7192
+
7193BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
+
7194
+
7195} // namespace test
+
7196} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
@@ -7429,40 +7452,40 @@ $(function() {
Zero allows classes to offer efficient comparisons to zero.
Definition: Zero.h:43
@ none
Definition: STBase.h:44
Basic tests of AMM that do not use offers.
Definition: AMM_test.cpp:47
-
void testBid(FeatureBitset features)
Definition: AMM_test.cpp:2835
-
void testRippling()
Definition: AMM_test.cpp:4627
-
void testAMMTokens()
Definition: AMM_test.cpp:4472
+
void testBid(FeatureBitset features)
Definition: AMM_test.cpp:2838
+
void testRippling()
Definition: AMM_test.cpp:4641
+
void testAMMTokens()
Definition: AMM_test.cpp:4481
void testInvalidDeposit(FeatureBitset features)
Definition: AMM_test.cpp:418
-
void testAMMID()
Definition: AMM_test.cpp:5418
-
void testFeeVote()
Definition: AMM_test.cpp:2505
-
void testWithdraw()
Definition: AMM_test.cpp:2119
-
void testInvalidAMMPayment()
Definition: AMM_test.cpp:3344
-
void testSelection(FeatureBitset features)
Definition: AMM_test.cpp:5471
-
void testAMMClawback(FeatureBitset features)
Definition: AMM_test.cpp:6935
-
void testInvalidFeeVote()
Definition: AMM_test.cpp:2436
-
void testSwapRounding()
Definition: AMM_test.cpp:6652
-
void testLPTokenBalance(FeatureBitset features)
Definition: AMM_test.cpp:6817
+
void testAMMID()
Definition: AMM_test.cpp:5441
+
void testFeeVote()
Definition: AMM_test.cpp:2508
+
void testWithdraw()
Definition: AMM_test.cpp:2121
+
void testInvalidAMMPayment()
Definition: AMM_test.cpp:3349
+
void testSelection(FeatureBitset features)
Definition: AMM_test.cpp:5494
+
void testAMMClawback(FeatureBitset features)
Definition: AMM_test.cpp:6958
+
void testInvalidFeeVote()
Definition: AMM_test.cpp:2439
+
void testSwapRounding()
Definition: AMM_test.cpp:6675
+
void testLPTokenBalance(FeatureBitset features)
Definition: AMM_test.cpp:6840
void testDeposit()
Definition: AMM_test.cpp:1303
-
void run() override
Runs the suite.
Definition: AMM_test.cpp:7113
+
void run() override
Runs the suite.
Definition: AMM_test.cpp:7136
void testInstanceCreate()
Definition: AMM_test.cpp:50
-
void testTradingFee(FeatureBitset features)
Definition: AMM_test.cpp:4748
-
void testFixOverflowOffer(FeatureBitset features)
Definition: AMM_test.cpp:6329
-
void testInvalidWithdraw()
Definition: AMM_test.cpp:1602
-
void testAMMAndCLOB(FeatureBitset features)
Definition: AMM_test.cpp:4677
+
void testTradingFee(FeatureBitset features)
Definition: AMM_test.cpp:4762
+
void testFixOverflowOffer(FeatureBitset features)
Definition: AMM_test.cpp:6352
+
void testInvalidWithdraw()
Definition: AMM_test.cpp:1603
+
void testAMMAndCLOB(FeatureBitset features)
Definition: AMM_test.cpp:4691
void testInvalidInstance()
Definition: AMM_test.cpp:135
-
void testMalformed()
Definition: AMM_test.cpp:6270
-
void testAutoDelete()
Definition: AMM_test.cpp:5293
-
void testBasicPaymentEngine(FeatureBitset features)
Definition: AMM_test.cpp:3506
-
void testInvalidBid()
Definition: AMM_test.cpp:2610
-
void testFixChangeSpotPriceQuality(FeatureBitset features)
Definition: AMM_test.cpp:6054
-
void testAmendment()
Definition: AMM_test.cpp:4578
-
void testClawback()
Definition: AMM_test.cpp:5406
-
void testFixDefaultInnerObj()
Definition: AMM_test.cpp:5954
-
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
Definition: AMM_test.cpp:7064
-
void testAdjustedTokens(FeatureBitset features)
Definition: AMM_test.cpp:5137
-
void testFlags()
Definition: AMM_test.cpp:4606
-
void testAMMDepositWithFrozenAssets(FeatureBitset features)
Definition: AMM_test.cpp:6982
-
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
Definition: AMM_test.cpp:6689
+
void testMalformed()
Definition: AMM_test.cpp:6293
+
void testAutoDelete()
Definition: AMM_test.cpp:5316
+
void testBasicPaymentEngine(FeatureBitset features)
Definition: AMM_test.cpp:3512
+
void testInvalidBid()
Definition: AMM_test.cpp:2613
+
void testFixChangeSpotPriceQuality(FeatureBitset features)
Definition: AMM_test.cpp:6077
+
void testAmendment()
Definition: AMM_test.cpp:4592
+
void testClawback()
Definition: AMM_test.cpp:5429
+
void testFixDefaultInnerObj()
Definition: AMM_test.cpp:5977
+
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
Definition: AMM_test.cpp:7087
+
void testAdjustedTokens(FeatureBitset features)
Definition: AMM_test.cpp:5151
+
void testFlags()
Definition: AMM_test.cpp:4620
+
void testAMMDepositWithFrozenAssets(FeatureBitset features)
Definition: AMM_test.cpp:7005
+
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
Definition: AMM_test.cpp:6712
Definition: AMM.h:75
std::optional< Account > account
Definition: AMM.h:76
std::optional< STAmount > asset1In
Definition: AMM.h:78
diff --git a/AccountTx__test_8cpp_source.html b/AccountTx__test_8cpp_source.html index 6fbf8bd9e4..b1f3a64703 100644 --- a/AccountTx__test_8cpp_source.html +++ b/AccountTx__test_8cpp_source.html @@ -191,662 +191,665 @@ $(function() {
113 testcase("Parameters APIv" + std::to_string(apiVersion));
114 using namespace test::jtx;
115
-
116 Env env(*this);
-
117 Account A1{"A1"};
-
118 env.fund(XRP(10000), A1);
-
119 env.close();
-
120
-
121 // Ledger 3 has the two txs associated with funding the account
-
122 // All other ledgers have no txs
+
116 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
+
117 cfg->FEES.reference_fee = 10;
+
118 return cfg;
+
119 }));
+
120 Account A1{"A1"};
+
121 env.fund(XRP(10000), A1);
+
122 env.close();
123
-
124 auto hasTxs = [apiVersion](Json::Value const& j) {
-
125 switch (apiVersion)
-
126 {
-
127 case 1:
-
128 return j.isMember(jss::result) &&
-
129 (j[jss::result][jss::status] == "success") &&
-
130 (j[jss::result][jss::transactions].size() == 2) &&
-
131 (j[jss::result][jss::transactions][0u][jss::tx]
-
132 [jss::TransactionType] == jss::AccountSet) &&
-
133 (j[jss::result][jss::transactions][1u][jss::tx]
-
134 [jss::TransactionType] == jss::Payment) &&
-
135 (j[jss::result][jss::transactions][1u][jss::tx]
-
136 [jss::DeliverMax] == "10000000010") &&
-
137 (j[jss::result][jss::transactions][1u][jss::tx]
-
138 [jss::Amount] ==
-
139 j[jss::result][jss::transactions][1u][jss::tx]
-
140 [jss::DeliverMax]);
-
141 case 2:
-
142 case 3:
-
143 if (j.isMember(jss::result) &&
-
144 (j[jss::result][jss::status] == "success") &&
-
145 (j[jss::result][jss::transactions].size() == 2) &&
-
146 (j[jss::result][jss::transactions][0u][jss::tx_json]
-
147 [jss::TransactionType] == jss::AccountSet))
-
148 {
-
149 auto const& payment =
-
150 j[jss::result][jss::transactions][1u];
-
151
-
152 return (payment.isMember(jss::tx_json)) &&
-
153 (payment[jss::tx_json][jss::TransactionType] ==
-
154 jss::Payment) &&
-
155 (payment[jss::tx_json][jss::DeliverMax] ==
-
156 "10000000010") &&
-
157 (!payment[jss::tx_json].isMember(jss::Amount)) &&
-
158 (!payment[jss::tx_json].isMember(jss::hash)) &&
-
159 (payment[jss::hash] ==
-
160 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
-
161 "ECF0D4CE981D0A8") &&
-
162 (payment[jss::validated] == true) &&
-
163 (payment[jss::ledger_index] == 3) &&
-
164 (payment[jss::ledger_hash] ==
-
165 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
-
166 "580A5AFDD727E33") &&
-
167 (payment[jss::close_time_iso] ==
-
168 "2000-01-01T00:00:10Z");
-
169 }
-
170 else
-
171 return false;
-
172
-
173 default:
-
174 return false;
-
175 }
-
176 };
-
177
-
178 auto noTxs = [](Json::Value const& j) {
-
179 return j.isMember(jss::result) &&
-
180 (j[jss::result][jss::status] == "success") &&
-
181 (j[jss::result][jss::transactions].size() == 0);
-
182 };
-
183
-
184 auto isErr = [](Json::Value const& j, error_code_i code) {
-
185 return j.isMember(jss::result) &&
-
186 j[jss::result].isMember(jss::error) &&
-
187 j[jss::result][jss::error] == RPC::get_error_info(code).token;
-
188 };
-
189
-
190 Json::Value jParms;
-
191 jParms[jss::api_version] = apiVersion;
+
124 // Ledger 3 has the two txs associated with funding the account
+
125 // All other ledgers have no txs
+
126
+
127 auto hasTxs = [apiVersion](Json::Value const& j) {
+
128 switch (apiVersion)
+
129 {
+
130 case 1:
+
131 return j.isMember(jss::result) &&
+
132 (j[jss::result][jss::status] == "success") &&
+
133 (j[jss::result][jss::transactions].size() == 2) &&
+
134 (j[jss::result][jss::transactions][0u][jss::tx]
+
135 [jss::TransactionType] == jss::AccountSet) &&
+
136 (j[jss::result][jss::transactions][1u][jss::tx]
+
137 [jss::TransactionType] == jss::Payment) &&
+
138 (j[jss::result][jss::transactions][1u][jss::tx]
+
139 [jss::DeliverMax] == "10000000010") &&
+
140 (j[jss::result][jss::transactions][1u][jss::tx]
+
141 [jss::Amount] ==
+
142 j[jss::result][jss::transactions][1u][jss::tx]
+
143 [jss::DeliverMax]);
+
144 case 2:
+
145 case 3:
+
146 if (j.isMember(jss::result) &&
+
147 (j[jss::result][jss::status] == "success") &&
+
148 (j[jss::result][jss::transactions].size() == 2) &&
+
149 (j[jss::result][jss::transactions][0u][jss::tx_json]
+
150 [jss::TransactionType] == jss::AccountSet))
+
151 {
+
152 auto const& payment =
+
153 j[jss::result][jss::transactions][1u];
+
154
+
155 return (payment.isMember(jss::tx_json)) &&
+
156 (payment[jss::tx_json][jss::TransactionType] ==
+
157 jss::Payment) &&
+
158 (payment[jss::tx_json][jss::DeliverMax] ==
+
159 "10000000010") &&
+
160 (!payment[jss::tx_json].isMember(jss::Amount)) &&
+
161 (!payment[jss::tx_json].isMember(jss::hash)) &&
+
162 (payment[jss::hash] ==
+
163 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
+
164 "ECF0D4CE981D0A8") &&
+
165 (payment[jss::validated] == true) &&
+
166 (payment[jss::ledger_index] == 3) &&
+
167 (payment[jss::ledger_hash] ==
+
168 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
+
169 "580A5AFDD727E33") &&
+
170 (payment[jss::close_time_iso] ==
+
171 "2000-01-01T00:00:10Z");
+
172 }
+
173 else
+
174 return false;
+
175
+
176 default:
+
177 return false;
+
178 }
+
179 };
+
180
+
181 auto noTxs = [](Json::Value const& j) {
+
182 return j.isMember(jss::result) &&
+
183 (j[jss::result][jss::status] == "success") &&
+
184 (j[jss::result][jss::transactions].size() == 0);
+
185 };
+
186
+
187 auto isErr = [](Json::Value const& j, error_code_i code) {
+
188 return j.isMember(jss::result) &&
+
189 j[jss::result].isMember(jss::error) &&
+
190 j[jss::result][jss::error] == RPC::get_error_info(code).token;
+
191 };
192
-
193 BEAST_EXPECT(isErr(
-
194 env.rpc("json", "account_tx", to_string(jParms)),
-
195 rpcINVALID_PARAMS));
-
196
-
197 jParms[jss::account] = "0xDEADBEEF";
-
198
-
199 BEAST_EXPECT(isErr(
-
200 env.rpc("json", "account_tx", to_string(jParms)),
-
201 rpcACT_MALFORMED));
-
202
-
203 jParms[jss::account] = A1.human();
-
204 BEAST_EXPECT(hasTxs(
-
205 env.rpc(apiVersion, "json", "account_tx", to_string(jParms))));
-
206
-
207 // Ledger min/max index
-
208 {
-
209 Json::Value p{jParms};
-
210 p[jss::ledger_index_min] = -1;
-
211 p[jss::ledger_index_max] = -1;
-
212 BEAST_EXPECT(hasTxs(
-
213 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
214
-
215 p[jss::ledger_index_min] = 0;
-
216 p[jss::ledger_index_max] = 100;
-
217 if (apiVersion < 2u)
-
218 BEAST_EXPECT(hasTxs(
-
219 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
220 else
-
221 BEAST_EXPECT(isErr(
-
222 env.rpc("json", "account_tx", to_string(p)),
-
223 rpcLGR_IDX_MALFORMED));
-
224
-
225 p[jss::ledger_index_min] = 1;
-
226 p[jss::ledger_index_max] = 2;
-
227 if (apiVersion < 2u)
-
228 BEAST_EXPECT(
-
229 noTxs(env.rpc("json", "account_tx", to_string(p))));
-
230 else
-
231 BEAST_EXPECT(isErr(
-
232 env.rpc("json", "account_tx", to_string(p)),
-
233 rpcLGR_IDX_MALFORMED));
-
234
-
235 p[jss::ledger_index_min] = 2;
-
236 p[jss::ledger_index_max] = 1;
-
237 BEAST_EXPECT(isErr(
-
238 env.rpc("json", "account_tx", to_string(p)),
-
239 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
-
240 : rpcINVALID_LGR_RANGE)));
-
241 }
-
242 // Ledger index min only
-
243 {
-
244 Json::Value p{jParms};
-
245 p[jss::ledger_index_min] = -1;
-
246 BEAST_EXPECT(hasTxs(
-
247 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
248
-
249 p[jss::ledger_index_min] = 1;
-
250 if (apiVersion < 2u)
-
251 BEAST_EXPECT(hasTxs(
-
252 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
253 else
-
254 BEAST_EXPECT(isErr(
-
255 env.rpc("json", "account_tx", to_string(p)),
-
256 rpcLGR_IDX_MALFORMED));
-
257
-
258 p[jss::ledger_index_min] = env.current()->info().seq;
-
259 BEAST_EXPECT(isErr(
-
260 env.rpc("json", "account_tx", to_string(p)),
-
261 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
-
262 : rpcINVALID_LGR_RANGE)));
-
263 }
-
264
-
265 // Ledger index max only
-
266 {
-
267 Json::Value p{jParms};
-
268 p[jss::ledger_index_max] = -1;
-
269 BEAST_EXPECT(hasTxs(
-
270 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
271
-
272 p[jss::ledger_index_max] = env.current()->info().seq;
-
273 if (apiVersion < 2u)
-
274 BEAST_EXPECT(hasTxs(
-
275 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
276 else
-
277 BEAST_EXPECT(isErr(
-
278 env.rpc("json", "account_tx", to_string(p)),
-
279 rpcLGR_IDX_MALFORMED));
-
280
-
281 p[jss::ledger_index_max] = 3;
-
282 BEAST_EXPECT(hasTxs(
-
283 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
284
-
285 p[jss::ledger_index_max] = env.closed()->info().seq;
-
286 BEAST_EXPECT(hasTxs(
-
287 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
288
-
289 p[jss::ledger_index_max] = env.closed()->info().seq - 1;
-
290 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
-
291 }
-
292
-
293 // Ledger Sequence
-
294 {
-
295 Json::Value p{jParms};
-
296
-
297 p[jss::ledger_index] = env.closed()->info().seq;
-
298 BEAST_EXPECT(hasTxs(
-
299 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
300
-
301 p[jss::ledger_index] = env.closed()->info().seq - 1;
-
302 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
+
193 Json::Value jParms;
+
194 jParms[jss::api_version] = apiVersion;
+
195
+
196 BEAST_EXPECT(isErr(
+
197 env.rpc("json", "account_tx", to_string(jParms)),
+
198 rpcINVALID_PARAMS));
+
199
+
200 jParms[jss::account] = "0xDEADBEEF";
+
201
+
202 BEAST_EXPECT(isErr(
+
203 env.rpc("json", "account_tx", to_string(jParms)),
+
204 rpcACT_MALFORMED));
+
205
+
206 jParms[jss::account] = A1.human();
+
207 BEAST_EXPECT(hasTxs(
+
208 env.rpc(apiVersion, "json", "account_tx", to_string(jParms))));
+
209
+
210 // Ledger min/max index
+
211 {
+
212 Json::Value p{jParms};
+
213 p[jss::ledger_index_min] = -1;
+
214 p[jss::ledger_index_max] = -1;
+
215 BEAST_EXPECT(hasTxs(
+
216 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
217
+
218 p[jss::ledger_index_min] = 0;
+
219 p[jss::ledger_index_max] = 100;
+
220 if (apiVersion < 2u)
+
221 BEAST_EXPECT(hasTxs(
+
222 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
223 else
+
224 BEAST_EXPECT(isErr(
+
225 env.rpc("json", "account_tx", to_string(p)),
+
226 rpcLGR_IDX_MALFORMED));
+
227
+
228 p[jss::ledger_index_min] = 1;
+
229 p[jss::ledger_index_max] = 2;
+
230 if (apiVersion < 2u)
+
231 BEAST_EXPECT(
+
232 noTxs(env.rpc("json", "account_tx", to_string(p))));
+
233 else
+
234 BEAST_EXPECT(isErr(
+
235 env.rpc("json", "account_tx", to_string(p)),
+
236 rpcLGR_IDX_MALFORMED));
+
237
+
238 p[jss::ledger_index_min] = 2;
+
239 p[jss::ledger_index_max] = 1;
+
240 BEAST_EXPECT(isErr(
+
241 env.rpc("json", "account_tx", to_string(p)),
+
242 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
+
243 : rpcINVALID_LGR_RANGE)));
+
244 }
+
245 // Ledger index min only
+
246 {
+
247 Json::Value p{jParms};
+
248 p[jss::ledger_index_min] = -1;
+
249 BEAST_EXPECT(hasTxs(
+
250 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
251
+
252 p[jss::ledger_index_min] = 1;
+
253 if (apiVersion < 2u)
+
254 BEAST_EXPECT(hasTxs(
+
255 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
256 else
+
257 BEAST_EXPECT(isErr(
+
258 env.rpc("json", "account_tx", to_string(p)),
+
259 rpcLGR_IDX_MALFORMED));
+
260
+
261 p[jss::ledger_index_min] = env.current()->info().seq;
+
262 BEAST_EXPECT(isErr(
+
263 env.rpc("json", "account_tx", to_string(p)),
+
264 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
+
265 : rpcINVALID_LGR_RANGE)));
+
266 }
+
267
+
268 // Ledger index max only
+
269 {
+
270 Json::Value p{jParms};
+
271 p[jss::ledger_index_max] = -1;
+
272 BEAST_EXPECT(hasTxs(
+
273 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
274
+
275 p[jss::ledger_index_max] = env.current()->info().seq;
+
276 if (apiVersion < 2u)
+
277 BEAST_EXPECT(hasTxs(
+
278 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
279 else
+
280 BEAST_EXPECT(isErr(
+
281 env.rpc("json", "account_tx", to_string(p)),
+
282 rpcLGR_IDX_MALFORMED));
+
283
+
284 p[jss::ledger_index_max] = 3;
+
285 BEAST_EXPECT(hasTxs(
+
286 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
287
+
288 p[jss::ledger_index_max] = env.closed()->info().seq;
+
289 BEAST_EXPECT(hasTxs(
+
290 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
291
+
292 p[jss::ledger_index_max] = env.closed()->info().seq - 1;
+
293 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
+
294 }
+
295
+
296 // Ledger Sequence
+
297 {
+
298 Json::Value p{jParms};
+
299
+
300 p[jss::ledger_index] = env.closed()->info().seq;
+
301 BEAST_EXPECT(hasTxs(
+
302 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
303
-
304 p[jss::ledger_index] = env.current()->info().seq;
-
305 BEAST_EXPECT(isErr(
-
306 env.rpc("json", "account_tx", to_string(p)),
-
307 rpcLGR_NOT_VALIDATED));
-
308
-
309 p[jss::ledger_index] = env.current()->info().seq + 1;
-
310 BEAST_EXPECT(isErr(
-
311 env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_FOUND));
-
312 }
-
313
-
314 // Ledger Hash
-
315 {
-
316 Json::Value p{jParms};
-
317
-
318 p[jss::ledger_hash] = to_string(env.closed()->info().hash);
-
319 BEAST_EXPECT(hasTxs(
-
320 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
321
-
322 p[jss::ledger_hash] = to_string(env.closed()->info().parentHash);
-
323 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
-
324 }
-
325
-
326 // Ledger index max/min/index all specified
-
327 // ERRORS out with invalid Parenthesis
-
328 {
-
329 jParms[jss::account] = "0xDEADBEEF";
-
330 jParms[jss::account] = A1.human();
-
331 Json::Value p{jParms};
-
332
-
333 p[jss::ledger_index_max] = -1;
-
334 p[jss::ledger_index_min] = -1;
-
335 p[jss::ledger_index] = -1;
-
336
-
337 if (apiVersion < 2u)
-
338 BEAST_EXPECT(hasTxs(
-
339 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
340 else
-
341 BEAST_EXPECT(isErr(
-
342 env.rpc("json", "account_tx", to_string(p)),
-
343 rpcINVALID_PARAMS));
-
344 }
-
345
-
346 // Ledger index max only
-
347 {
-
348 Json::Value p{jParms};
-
349 p[jss::ledger_index_max] = env.current()->info().seq;
-
350 if (apiVersion < 2u)
-
351 BEAST_EXPECT(hasTxs(
-
352 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
-
353 else
-
354 BEAST_EXPECT(isErr(
-
355 env.rpc("json", "account_tx", to_string(p)),
-
356 rpcLGR_IDX_MALFORMED));
-
357 }
-
358 // test account non-string
-
359 {
-
360 auto testInvalidAccountParam = [&](auto const& param) {
-
361 Json::Value params;
-
362 params[jss::account] = param;
-
363 auto jrr = env.rpc(
-
364 "json", "account_tx", to_string(params))[jss::result];
-
365 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
366 BEAST_EXPECT(
-
367 jrr[jss::error_message] == "Invalid field 'account'.");
-
368 };
-
369
-
370 testInvalidAccountParam(1);
-
371 testInvalidAccountParam(1.1);
-
372 testInvalidAccountParam(true);
-
373 testInvalidAccountParam(Json::Value(Json::nullValue));
-
374 testInvalidAccountParam(Json::Value(Json::objectValue));
-
375 testInvalidAccountParam(Json::Value(Json::arrayValue));
-
376 }
-
377 // test binary and forward for bool/non bool values
-
378 {
-
379 Json::Value p{jParms};
-
380 p[jss::binary] = "asdf";
-
381 if (apiVersion < 2u)
-
382 {
-
383 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
-
384 BEAST_EXPECT(result[jss::result][jss::status] == "success");
-
385 }
-
386 else
-
387 BEAST_EXPECT(isErr(
-
388 env.rpc("json", "account_tx", to_string(p)),
-
389 rpcINVALID_PARAMS));
-
390
-
391 p[jss::binary] = true;
-
392 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
-
393 BEAST_EXPECT(result[jss::result][jss::status] == "success");
-
394
-
395 p[jss::forward] = "true";
-
396 if (apiVersion < 2u)
-
397 BEAST_EXPECT(result[jss::result][jss::status] == "success");
-
398 else
-
399 BEAST_EXPECT(isErr(
-
400 env.rpc("json", "account_tx", to_string(p)),
-
401 rpcINVALID_PARAMS));
-
402
-
403 p[jss::forward] = false;
-
404 result = env.rpc("json", "account_tx", to_string(p));
-
405 BEAST_EXPECT(result[jss::result][jss::status] == "success");
-
406 }
-
407 }
-
408
-
409 void
-
410 testContents()
-
411 {
-
412 testcase("Contents");
-
413
-
414 // Get results for all transaction types that can be associated
-
415 // with an account. Start by generating all transaction types.
-
416 using namespace test::jtx;
-
417 using namespace std::chrono_literals;
-
418
-
419 Env env(*this);
-
420 Account const alice{"alice"};
-
421 Account const alie{"alie"};
-
422 Account const gw{"gw"};
-
423 auto const USD{gw["USD"]};
-
424
-
425 env.fund(XRP(1000000), alice, gw);
-
426 env.close();
+
304 p[jss::ledger_index] = env.closed()->info().seq - 1;
+
305 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
+
306
+
307 p[jss::ledger_index] = env.current()->info().seq;
+
308 BEAST_EXPECT(isErr(
+
309 env.rpc("json", "account_tx", to_string(p)),
+
310 rpcLGR_NOT_VALIDATED));
+
311
+
312 p[jss::ledger_index] = env.current()->info().seq + 1;
+
313 BEAST_EXPECT(isErr(
+
314 env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_FOUND));
+
315 }
+
316
+
317 // Ledger Hash
+
318 {
+
319 Json::Value p{jParms};
+
320
+
321 p[jss::ledger_hash] = to_string(env.closed()->info().hash);
+
322 BEAST_EXPECT(hasTxs(
+
323 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
324
+
325 p[jss::ledger_hash] = to_string(env.closed()->info().parentHash);
+
326 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
+
327 }
+
328
+
329 // Ledger index max/min/index all specified
+
330 // ERRORS out with invalid Parenthesis
+
331 {
+
332 jParms[jss::account] = "0xDEADBEEF";
+
333 jParms[jss::account] = A1.human();
+
334 Json::Value p{jParms};
+
335
+
336 p[jss::ledger_index_max] = -1;
+
337 p[jss::ledger_index_min] = -1;
+
338 p[jss::ledger_index] = -1;
+
339
+
340 if (apiVersion < 2u)
+
341 BEAST_EXPECT(hasTxs(
+
342 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
343 else
+
344 BEAST_EXPECT(isErr(
+
345 env.rpc("json", "account_tx", to_string(p)),
+
346 rpcINVALID_PARAMS));
+
347 }
+
348
+
349 // Ledger index max only
+
350 {
+
351 Json::Value p{jParms};
+
352 p[jss::ledger_index_max] = env.current()->info().seq;
+
353 if (apiVersion < 2u)
+
354 BEAST_EXPECT(hasTxs(
+
355 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
+
356 else
+
357 BEAST_EXPECT(isErr(
+
358 env.rpc("json", "account_tx", to_string(p)),
+
359 rpcLGR_IDX_MALFORMED));
+
360 }
+
361 // test account non-string
+
362 {
+
363 auto testInvalidAccountParam = [&](auto const& param) {
+
364 Json::Value params;
+
365 params[jss::account] = param;
+
366 auto jrr = env.rpc(
+
367 "json", "account_tx", to_string(params))[jss::result];
+
368 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
369 BEAST_EXPECT(
+
370 jrr[jss::error_message] == "Invalid field 'account'.");
+
371 };
+
372
+
373 testInvalidAccountParam(1);
+
374 testInvalidAccountParam(1.1);
+
375 testInvalidAccountParam(true);
+
376 testInvalidAccountParam(Json::Value(Json::nullValue));
+
377 testInvalidAccountParam(Json::Value(Json::objectValue));
+
378 testInvalidAccountParam(Json::Value(Json::arrayValue));
+
379 }
+
380 // test binary and forward for bool/non bool values
+
381 {
+
382 Json::Value p{jParms};
+
383 p[jss::binary] = "asdf";
+
384 if (apiVersion < 2u)
+
385 {
+
386 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
+
387 BEAST_EXPECT(result[jss::result][jss::status] == "success");
+
388 }
+
389 else
+
390 BEAST_EXPECT(isErr(
+
391 env.rpc("json", "account_tx", to_string(p)),
+
392 rpcINVALID_PARAMS));
+
393
+
394 p[jss::binary] = true;
+
395 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
+
396 BEAST_EXPECT(result[jss::result][jss::status] == "success");
+
397
+
398 p[jss::forward] = "true";
+
399 if (apiVersion < 2u)
+
400 BEAST_EXPECT(result[jss::result][jss::status] == "success");
+
401 else
+
402 BEAST_EXPECT(isErr(
+
403 env.rpc("json", "account_tx", to_string(p)),
+
404 rpcINVALID_PARAMS));
+
405
+
406 p[jss::forward] = false;
+
407 result = env.rpc("json", "account_tx", to_string(p));
+
408 BEAST_EXPECT(result[jss::result][jss::status] == "success");
+
409 }
+
410 }
+
411
+
412 void
+
413 testContents()
+
414 {
+
415 testcase("Contents");
+
416
+
417 // Get results for all transaction types that can be associated
+
418 // with an account. Start by generating all transaction types.
+
419 using namespace test::jtx;
+
420 using namespace std::chrono_literals;
+
421
+
422 Env env(*this);
+
423 Account const alice{"alice"};
+
424 Account const alie{"alie"};
+
425 Account const gw{"gw"};
+
426 auto const USD{gw["USD"]};
427
-
428 // AccountSet
-
429 env(noop(alice));
+
428 env.fund(XRP(1000000), alice, gw);
+
429 env.close();
430
-
431 // Payment
-
432 env(pay(alice, gw, XRP(100)));
+
431 // AccountSet
+
432 env(noop(alice));
433
-
434 // Regular key set
-
435 env(regkey(alice, alie));
-
436 env.close();
-
437
-
438 // Trust and Offers
-
439 env(trust(alice, USD(200)), sig(alie));
-
440 std::uint32_t const offerSeq{env.seq(alice)};
-
441 env(offer(alice, USD(50), XRP(150)), sig(alie));
-
442 env.close();
-
443
-
444 env(offer_cancel(alice, offerSeq), sig(alie));
+
434 // Payment
+
435 env(pay(alice, gw, XRP(100)));
+
436
+
437 // Regular key set
+
438 env(regkey(alice, alie));
+
439 env.close();
+
440
+
441 // Trust and Offers
+
442 env(trust(alice, USD(200)), sig(alie));
+
443 std::uint32_t const offerSeq{env.seq(alice)};
+
444 env(offer(alice, USD(50), XRP(150)), sig(alie));
445 env.close();
446
-
447 // SignerListSet
-
448 env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), sig(alie));
+
447 env(offer_cancel(alice, offerSeq), sig(alie));
+
448 env.close();
449
-
450 // Escrow
-
451 {
-
452 // Create an escrow. Requires either a CancelAfter or FinishAfter.
-
453 auto escrow = [](Account const& account,
-
454 Account const& to,
-
455 STAmount const& amount) {
-
456 Json::Value escro;
-
457 escro[jss::TransactionType] = jss::EscrowCreate;
-
458 escro[jss::Flags] = tfUniversal;
-
459 escro[jss::Account] = account.human();
-
460 escro[jss::Destination] = to.human();
-
461 escro[jss::Amount] = amount.getJson(JsonOptions::none);
-
462 return escro;
-
463 };
-
464
-
465 NetClock::time_point const nextTime{env.now() + 2s};
-
466
-
467 Json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
-
468 escrowWithFinish[sfFinishAfter.jsonName] =
-
469 nextTime.time_since_epoch().count();
-
470
-
471 std::uint32_t const escrowFinishSeq{env.seq(alice)};
-
472 env(escrowWithFinish, sig(alie));
+
450 // SignerListSet
+
451 env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), sig(alie));
+
452
+
453 // Escrow
+
454 {
+
455 // Create an escrow. Requires either a CancelAfter or FinishAfter.
+
456 auto escrow = [](Account const& account,
+
457 Account const& to,
+
458 STAmount const& amount) {
+
459 Json::Value escro;
+
460 escro[jss::TransactionType] = jss::EscrowCreate;
+
461 escro[jss::Flags] = tfUniversal;
+
462 escro[jss::Account] = account.human();
+
463 escro[jss::Destination] = to.human();
+
464 escro[jss::Amount] = amount.getJson(JsonOptions::none);
+
465 return escro;
+
466 };
+
467
+
468 NetClock::time_point const nextTime{env.now() + 2s};
+
469
+
470 Json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
+
471 escrowWithFinish[sfFinishAfter.jsonName] =
+
472 nextTime.time_since_epoch().count();
473
-
474 Json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
-
475 escrowWithCancel[sfFinishAfter.jsonName] =
-
476 nextTime.time_since_epoch().count();
-
477 escrowWithCancel[sfCancelAfter.jsonName] =
-
478 nextTime.time_since_epoch().count() + 1;
-
479
-
480 std::uint32_t const escrowCancelSeq{env.seq(alice)};
-
481 env(escrowWithCancel, sig(alie));
-
482 env.close();
-
483
-
484 {
-
485 Json::Value escrowFinish;
-
486 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
-
487 escrowFinish[jss::Flags] = tfUniversal;
-
488 escrowFinish[jss::Account] = alice.human();
-
489 escrowFinish[sfOwner.jsonName] = alice.human();
-
490 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
-
491 env(escrowFinish, sig(alie));
-
492 }
-
493 {
-
494 Json::Value escrowCancel;
-
495 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
-
496 escrowCancel[jss::Flags] = tfUniversal;
-
497 escrowCancel[jss::Account] = alice.human();
-
498 escrowCancel[sfOwner.jsonName] = alice.human();
-
499 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
-
500 env(escrowCancel, sig(alie));
-
501 }
-
502 env.close();
-
503 }
-
504
-
505 // PayChan
-
506 {
-
507 std::uint32_t payChanSeq{env.seq(alice)};
-
508 Json::Value payChanCreate;
-
509 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
-
510 payChanCreate[jss::Flags] = tfUniversal;
-
511 payChanCreate[jss::Account] = alice.human();
-
512 payChanCreate[jss::Destination] = gw.human();
-
513 payChanCreate[jss::Amount] =
-
514 XRP(500).value().getJson(JsonOptions::none);
-
515 payChanCreate[sfSettleDelay.jsonName] =
-
516 NetClock::duration{100s}.count();
-
517 payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
-
518 env(payChanCreate, sig(alie));
-
519 env.close();
-
520
-
521 std::string const payChanIndex{
-
522 strHex(keylet::payChan(alice, gw, payChanSeq).key)};
+
474 std::uint32_t const escrowFinishSeq{env.seq(alice)};
+
475 env(escrowWithFinish, sig(alie));
+
476
+
477 Json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
+
478 escrowWithCancel[sfFinishAfter.jsonName] =
+
479 nextTime.time_since_epoch().count();
+
480 escrowWithCancel[sfCancelAfter.jsonName] =
+
481 nextTime.time_since_epoch().count() + 1;
+
482
+
483 std::uint32_t const escrowCancelSeq{env.seq(alice)};
+
484 env(escrowWithCancel, sig(alie));
+
485 env.close();
+
486
+
487 {
+
488 Json::Value escrowFinish;
+
489 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
+
490 escrowFinish[jss::Flags] = tfUniversal;
+
491 escrowFinish[jss::Account] = alice.human();
+
492 escrowFinish[sfOwner.jsonName] = alice.human();
+
493 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
+
494 env(escrowFinish, sig(alie));
+
495 }
+
496 {
+
497 Json::Value escrowCancel;
+
498 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
+
499 escrowCancel[jss::Flags] = tfUniversal;
+
500 escrowCancel[jss::Account] = alice.human();
+
501 escrowCancel[sfOwner.jsonName] = alice.human();
+
502 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
+
503 env(escrowCancel, sig(alie));
+
504 }
+
505 env.close();
+
506 }
+
507
+
508 // PayChan
+
509 {
+
510 std::uint32_t payChanSeq{env.seq(alice)};
+
511 Json::Value payChanCreate;
+
512 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
+
513 payChanCreate[jss::Flags] = tfUniversal;
+
514 payChanCreate[jss::Account] = alice.human();
+
515 payChanCreate[jss::Destination] = gw.human();
+
516 payChanCreate[jss::Amount] =
+
517 XRP(500).value().getJson(JsonOptions::none);
+
518 payChanCreate[sfSettleDelay.jsonName] =
+
519 NetClock::duration{100s}.count();
+
520 payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
+
521 env(payChanCreate, sig(alie));
+
522 env.close();
523
-
524 {
-
525 Json::Value payChanFund;
-
526 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
-
527 payChanFund[jss::Flags] = tfUniversal;
-
528 payChanFund[jss::Account] = alice.human();
-
529 payChanFund[sfChannel.jsonName] = payChanIndex;
-
530 payChanFund[jss::Amount] =
-
531 XRP(200).value().getJson(JsonOptions::none);
-
532 env(payChanFund, sig(alie));
-
533 env.close();
-
534 }
-
535 {
-
536 Json::Value payChanClaim;
-
537 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
-
538 payChanClaim[jss::Flags] = tfClose;
-
539 payChanClaim[jss::Account] = gw.human();
-
540 payChanClaim[sfChannel.jsonName] = payChanIndex;
-
541 payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice());
-
542 env(payChanClaim);
-
543 env.close();
-
544 }
-
545 }
-
546
-
547 // Check
-
548 {
-
549 auto const aliceCheckId = keylet::check(alice, env.seq(alice)).key;
-
550 env(check::create(alice, gw, XRP(300)), sig(alie));
-
551
-
552 auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
-
553 env(check::create(gw, alice, XRP(200)));
-
554 env.close();
-
555
-
556 env(check::cash(alice, gwCheckId, XRP(200)), sig(alie));
-
557 env(check::cancel(alice, aliceCheckId), sig(alie));
-
558 env.close();
-
559 }
-
560 {
-
561 // Deposit preauthorization with a Ticket.
-
562 std::uint32_t const tktSeq{env.seq(alice) + 1};
-
563 env(ticket::create(alice, 1), sig(alie));
-
564 env.close();
-
565
-
566 env(deposit::auth(alice, gw), ticket::use(tktSeq), sig(alie));
+
524 std::string const payChanIndex{
+
525 strHex(keylet::payChan(alice, gw, payChanSeq).key)};
+
526
+
527 {
+
528 Json::Value payChanFund;
+
529 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
+
530 payChanFund[jss::Flags] = tfUniversal;
+
531 payChanFund[jss::Account] = alice.human();
+
532 payChanFund[sfChannel.jsonName] = payChanIndex;
+
533 payChanFund[jss::Amount] =
+
534 XRP(200).value().getJson(JsonOptions::none);
+
535 env(payChanFund, sig(alie));
+
536 env.close();
+
537 }
+
538 {
+
539 Json::Value payChanClaim;
+
540 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
+
541 payChanClaim[jss::Flags] = tfClose;
+
542 payChanClaim[jss::Account] = gw.human();
+
543 payChanClaim[sfChannel.jsonName] = payChanIndex;
+
544 payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice());
+
545 env(payChanClaim);
+
546 env.close();
+
547 }
+
548 }
+
549
+
550 // Check
+
551 {
+
552 auto const aliceCheckId = keylet::check(alice, env.seq(alice)).key;
+
553 env(check::create(alice, gw, XRP(300)), sig(alie));
+
554
+
555 auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
+
556 env(check::create(gw, alice, XRP(200)));
+
557 env.close();
+
558
+
559 env(check::cash(alice, gwCheckId, XRP(200)), sig(alie));
+
560 env(check::cancel(alice, aliceCheckId), sig(alie));
+
561 env.close();
+
562 }
+
563 {
+
564 // Deposit preauthorization with a Ticket.
+
565 std::uint32_t const tktSeq{env.seq(alice) + 1};
+
566 env(ticket::create(alice, 1), sig(alie));
567 env.close();
-
568 }
-
569
-
570 // Setup is done. Look at the transactions returned by account_tx.
-
571 Json::Value params;
-
572 params[jss::account] = alice.human();
-
573 params[jss::ledger_index_min] = -1;
-
574 params[jss::ledger_index_max] = -1;
-
575
-
576 Json::Value const result{
-
577 env.rpc("json", "account_tx", to_string(params))};
+
568
+
569 env(deposit::auth(alice, gw), ticket::use(tktSeq), sig(alie));
+
570 env.close();
+
571 }
+
572
+
573 // Setup is done. Look at the transactions returned by account_tx.
+
574 Json::Value params;
+
575 params[jss::account] = alice.human();
+
576 params[jss::ledger_index_min] = -1;
+
577 params[jss::ledger_index_max] = -1;
578
-
579 BEAST_EXPECT(result[jss::result][jss::status] == "success");
-
580 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
+
579 Json::Value const result{
+
580 env.rpc("json", "account_tx", to_string(params))};
581
-
582 Json::Value const& txs{result[jss::result][jss::transactions]};
-
583
-
584 // clang-format off
-
585 // Do a sanity check on each returned transaction. They should
-
586 // be returned in the reverse order of application to the ledger.
-
587 static const NodeSanity sanity[]{
-
588 // txType, created, deleted, modified
-
589 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
-
590 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
-
591 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
-
592 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
-
593 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
-
594 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
-
595 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
-
596 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
-
597 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
-
598 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
-
599 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
-
600 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
-
601 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
-
602 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
-
603 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
-
604 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
-
605 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
-
606 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
-
607 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
-
608 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
-
609 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
-
610 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
-
611 };
-
612 // clang-format on
-
613
-
614 BEAST_EXPECT(
-
615 std::size(sanity) == result[jss::result][jss::transactions].size());
+
582 BEAST_EXPECT(result[jss::result][jss::status] == "success");
+
583 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
+
584
+
585 Json::Value const& txs{result[jss::result][jss::transactions]};
+
586
+
587 // clang-format off
+
588 // Do a sanity check on each returned transaction. They should
+
589 // be returned in the reverse order of application to the ledger.
+
590 static const NodeSanity sanity[]{
+
591 // txType, created, deleted, modified
+
592 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
+
593 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
+
594 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
+
595 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
+
596 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
+
597 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
+
598 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
+
599 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
+
600 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
+
601 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
+
602 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
+
603 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
+
604 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
+
605 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
+
606 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
+
607 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
+
608 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
+
609 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
+
610 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
+
611 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
+
612 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
+
613 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
+
614 };
+
615 // clang-format on
616
-
617 for (unsigned int index{0}; index < std::size(sanity); ++index)
-
618 {
-
619 checkSanity(txs[index], sanity[index]);
-
620 }
-
621 }
-
622
-
623 void
-
624 testAccountDelete()
-
625 {
-
626 testcase("AccountDelete");
-
627
-
628 // Verify that if an account is resurrected then the account_tx RPC
-
629 // command still recovers all transactions on that account before
-
630 // and after resurrection.
-
631 using namespace test::jtx;
-
632 using namespace std::chrono_literals;
-
633
-
634 Env env(*this);
-
635 Account const alice{"alice"};
-
636 Account const becky{"becky"};
-
637
-
638 env.fund(XRP(10000), alice, becky);
-
639 env.close();
+
617 BEAST_EXPECT(
+
618 std::size(sanity) == result[jss::result][jss::transactions].size());
+
619
+
620 for (unsigned int index{0}; index < std::size(sanity); ++index)
+
621 {
+
622 checkSanity(txs[index], sanity[index]);
+
623 }
+
624 }
+
625
+
626 void
+
627 testAccountDelete()
+
628 {
+
629 testcase("AccountDelete");
+
630
+
631 // Verify that if an account is resurrected then the account_tx RPC
+
632 // command still recovers all transactions on that account before
+
633 // and after resurrection.
+
634 using namespace test::jtx;
+
635 using namespace std::chrono_literals;
+
636
+
637 Env env(*this);
+
638 Account const alice{"alice"};
+
639 Account const becky{"becky"};
640
-
641 // Verify that becky's account root is present.
-
642 Keylet const beckyAcctKey{keylet::account(becky.id())};
-
643 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
-
644
-
645 // becky does an AccountSet .
-
646 env(noop(becky));
+
641 env.fund(XRP(10000), alice, becky);
+
642 env.close();
+
643
+
644 // Verify that becky's account root is present.
+
645 Keylet const beckyAcctKey{keylet::account(becky.id())};
+
646 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
647
-
648 // Close enough ledgers to be able to delete becky's account.
-
649 std::uint32_t const ledgerCount{
-
650 env.current()->seq() + 257 - env.seq(becky)};
-
651
-
652 for (std::uint32_t i = 0; i < ledgerCount; ++i)
-
653 env.close();
+
648 // becky does an AccountSet .
+
649 env(noop(becky));
+
650
+
651 // Close enough ledgers to be able to delete becky's account.
+
652 std::uint32_t const ledgerCount{
+
653 env.current()->seq() + 257 - env.seq(becky)};
654
-
655 auto const beckyPreDelBalance{env.balance(becky)};
-
656
-
657 auto const acctDelFee{drops(env.current()->fees().increment)};
-
658 env(acctdelete(becky, alice), fee(acctDelFee));
-
659 env.close();
-
660
-
661 // Verify that becky's account root is gone.
-
662 BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
-
663 env.close();
-
664
-
665 // clang-format off
-
666 // Do a sanity check on each returned transaction. They should
-
667 // be returned in the reverse order of application to the ledger.
-
668 //
-
669 // Note that the first two transactions in sanity have not occurred
-
670 // yet. We'll see those after becky's account is resurrected.
-
671 static const NodeSanity sanity[]
-
672 {
-
673 // txType, created, deleted, modified
-
674/* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
-
675/* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
-
676/* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
-
677/* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
-
678/* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
-
679/* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
-
680 };
-
681 // clang-format on
-
682
-
683 // Verify that we can recover becky's account_tx information even
-
684 // after the account is deleted.
-
685 {
-
686 Json::Value params;
-
687 params[jss::account] = becky.human();
-
688 params[jss::ledger_index_min] = -1;
-
689 params[jss::ledger_index_max] = -1;
-
690
-
691 Json::Value const result{
-
692 env.rpc("json", "account_tx", to_string(params))};
+
655 for (std::uint32_t i = 0; i < ledgerCount; ++i)
+
656 env.close();
+
657
+
658 auto const beckyPreDelBalance{env.balance(becky)};
+
659
+
660 auto const acctDelFee{drops(env.current()->fees().increment)};
+
661 env(acctdelete(becky, alice), fee(acctDelFee));
+
662 env.close();
+
663
+
664 // Verify that becky's account root is gone.
+
665 BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
+
666 env.close();
+
667
+
668 // clang-format off
+
669 // Do a sanity check on each returned transaction. They should
+
670 // be returned in the reverse order of application to the ledger.
+
671 //
+
672 // Note that the first two transactions in sanity have not occurred
+
673 // yet. We'll see those after becky's account is resurrected.
+
674 static const NodeSanity sanity[]
+
675 {
+
676 // txType, created, deleted, modified
+
677/* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
+
678/* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
+
679/* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
+
680/* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
+
681/* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
+
682/* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
+
683 };
+
684 // clang-format on
+
685
+
686 // Verify that we can recover becky's account_tx information even
+
687 // after the account is deleted.
+
688 {
+
689 Json::Value params;
+
690 params[jss::account] = becky.human();
+
691 params[jss::ledger_index_min] = -1;
+
692 params[jss::ledger_index_max] = -1;
693
-
694 BEAST_EXPECT(result[jss::result][jss::status] == "success");
-
695 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
+
694 Json::Value const result{
+
695 env.rpc("json", "account_tx", to_string(params))};
696
-
697 // The first two transactions listed in sanity haven't happened yet.
-
698 constexpr unsigned int beckyDeletedOffest = 2;
-
699 BEAST_EXPECT(
-
700 std::size(sanity) ==
-
701 result[jss::result][jss::transactions].size() +
-
702 beckyDeletedOffest);
-
703
-
704 Json::Value const& txs{result[jss::result][jss::transactions]};
-
705
-
706 for (unsigned int index = beckyDeletedOffest;
-
707 index < std::size(sanity);
-
708 ++index)
-
709 {
-
710 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
-
711 }
-
712 }
-
713
-
714 // All it takes is a large enough XRP payment to resurrect
-
715 // becky's account. Try too small a payment.
-
716 env(pay(alice,
-
717 becky,
-
718 drops(env.current()->fees().accountReserve(0)) - XRP(1)),
-
719 ter(tecNO_DST_INSUF_XRP));
-
720 env.close();
-
721
-
722 // Actually resurrect becky's account.
-
723 env(pay(alice, becky, XRP(45)));
-
724 env.close();
-
725
-
726 // becky's account root should be back.
-
727 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
-
728 BEAST_EXPECT(env.balance(becky) == XRP(45));
-
729
-
730 // becky pays alice.
-
731 env(pay(becky, alice, XRP(20)));
-
732 env.close();
-
733
-
734 // Setup is done. Look at the transactions returned by account_tx.
-
735 // Verify that account_tx locates all of becky's transactions.
-
736 Json::Value params;
-
737 params[jss::account] = becky.human();
-
738 params[jss::ledger_index_min] = -1;
-
739 params[jss::ledger_index_max] = -1;
-
740
-
741 Json::Value const result{
-
742 env.rpc("json", "account_tx", to_string(params))};
+
697 BEAST_EXPECT(result[jss::result][jss::status] == "success");
+
698 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
+
699
+
700 // The first two transactions listed in sanity haven't happened yet.
+
701 constexpr unsigned int beckyDeletedOffest = 2;
+
702 BEAST_EXPECT(
+
703 std::size(sanity) ==
+
704 result[jss::result][jss::transactions].size() +
+
705 beckyDeletedOffest);
+
706
+
707 Json::Value const& txs{result[jss::result][jss::transactions]};
+
708
+
709 for (unsigned int index = beckyDeletedOffest;
+
710 index < std::size(sanity);
+
711 ++index)
+
712 {
+
713 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
+
714 }
+
715 }
+
716
+
717 // All it takes is a large enough XRP payment to resurrect
+
718 // becky's account. Try too small a payment.
+
719 env(pay(alice,
+
720 becky,
+
721 drops(env.current()->fees().accountReserve(0)) - XRP(1)),
+
722 ter(tecNO_DST_INSUF_XRP));
+
723 env.close();
+
724
+
725 // Actually resurrect becky's account.
+
726 env(pay(alice, becky, XRP(45)));
+
727 env.close();
+
728
+
729 // becky's account root should be back.
+
730 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
+
731 BEAST_EXPECT(env.balance(becky) == XRP(45));
+
732
+
733 // becky pays alice.
+
734 env(pay(becky, alice, XRP(20)));
+
735 env.close();
+
736
+
737 // Setup is done. Look at the transactions returned by account_tx.
+
738 // Verify that account_tx locates all of becky's transactions.
+
739 Json::Value params;
+
740 params[jss::account] = becky.human();
+
741 params[jss::ledger_index_min] = -1;
+
742 params[jss::ledger_index_max] = -1;
743
-
744 BEAST_EXPECT(result[jss::result][jss::status] == "success");
-
745 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
+
744 Json::Value const result{
+
745 env.rpc("json", "account_tx", to_string(params))};
746
-
747 BEAST_EXPECT(
-
748 std::size(sanity) == result[jss::result][jss::transactions].size());
+
747 BEAST_EXPECT(result[jss::result][jss::status] == "success");
+
748 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
749
-
750 Json::Value const& txs{result[jss::result][jss::transactions]};
-
751
-
752 for (unsigned int index = 0; index < std::size(sanity); ++index)
-
753 {
-
754 checkSanity(txs[index], sanity[index]);
-
755 }
-
756 }
-
757
-
758public:
-
759 void
-
760 run() override
-
761 {
-
762 forAllApiVersions(
-
763 std::bind_front(&AccountTx_test::testParameters, this));
-
764 testContents();
-
765 testAccountDelete();
-
766 }
-
767};
-
768BEAST_DEFINE_TESTSUITE(AccountTx, rpc, ripple);
-
769
-
770} // namespace test
-
771} // namespace ripple
+
750 BEAST_EXPECT(
+
751 std::size(sanity) == result[jss::result][jss::transactions].size());
+
752
+
753 Json::Value const& txs{result[jss::result][jss::transactions]};
+
754
+
755 for (unsigned int index = 0; index < std::size(sanity); ++index)
+
756 {
+
757 checkSanity(txs[index], sanity[index]);
+
758 }
+
759 }
+
760
+
761public:
+
762 void
+
763 run() override
+
764 {
+
765 forAllApiVersions(
+
766 std::bind_front(&AccountTx_test::testParameters, this));
+
767 testContents();
+
768 testAccountDelete();
+
769 }
+
770};
+
771BEAST_DEFINE_TESTSUITE(AccountTx, rpc, ripple);
+
772
+
773} // namespace test
+
774} // namespace ripple
T bind_front(T... args)
Lightweight wrapper to tag static string.
Definition: json_value.h:62
@@ -857,10 +860,10 @@ $(function() {
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:533
Definition: STAmount.h:50
-
void run() override
Runs the suite.
+
void run() override
Runs the suite.
void checkSanity(Json::Value const &txNode, NodeSanity const &sane)
-
void testAccountDelete()
-
void testContents()
+
void testAccountDelete()
+
void testContents()
void testParameters(unsigned int apiVersion)
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:120
@@ -899,6 +902,7 @@ $(function() {
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:32
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
+
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
Json::Value noop(Account const &account)
The null transaction.
Definition: noop.h:31
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:29
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
Definition: acctdelete.cpp:30
@@ -934,6 +938,7 @@ $(function() {
boost::container::flat_set< std::string > created
T to_string(T... args)
+