rippled
Loading...
Searching...
No Matches
Oracle_test.cpp
1#include <test/jtx/Oracle.h>
2
3#include <xrpl/protocol/jss.h>
4
5namespace xrpl {
6namespace test {
7namespace jtx {
8namespace oracle {
9
11{
12private:
13 void
15 {
16 testcase("Invalid Set");
17
18 using namespace jtx;
19 Account const owner("owner");
20
21 {
22 // Invalid account
23 Env env(*this);
24 Account const bad("bad");
25 env.memoize(bad);
26 Oracle oracle(
27 env,
28 {.owner = bad,
29 .seq = seq(1),
30 .fee = static_cast<int>(env.current()->fees().base.drops()),
31 .err = ter(terNO_ACCOUNT)});
32 }
33
34 // Insufficient reserve
35 {
36 Env env(*this);
37 env.fund(env.current()->fees().accountReserve(0), owner);
38 Oracle oracle(
39 env,
40 {.owner = owner,
41 .fee = static_cast<int>(env.current()->fees().base.drops()),
43 }
44 // Insufficient reserve if the data series extends to greater than 5
45 {
46 Env env(*this);
47 env.fund(
48 env.current()->fees().accountReserve(1) + env.current()->fees().base * 2, owner);
49 Oracle oracle(
50 env, {.owner = owner, .fee = static_cast<int>(env.current()->fees().base.drops())});
51 BEAST_EXPECT(oracle.exists());
52 oracle.set(
54 .series =
55 {
56 {"XRP", "EUR", 740, 1},
57 {"XRP", "GBP", 740, 1},
58 {"XRP", "CNY", 740, 1},
59 {"XRP", "CAD", 740, 1},
60 {"XRP", "AUD", 740, 1},
61 },
62 .fee = static_cast<int>(env.current()->fees().base.drops()),
64 }
65
66 {
67 Env env(*this);
68 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
69 env.fund(XRP(1'000), owner);
70 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
71
72 // Invalid flag
73 oracle.set(
74 CreateArg{.flags = tfSellNFToken, .fee = baseFee, .err = ter(temINVALID_FLAG)});
75
76 // Duplicate token pair
77 oracle.set(
79 .series = {{"XRP", "USD", 740, 1}, {"XRP", "USD", 750, 1}},
80 .fee = baseFee,
81 .err = ter(temMALFORMED)});
82
83 // Price is not included
84 oracle.set(
86 .series = {{"XRP", "USD", 740, 1}, {"XRP", "EUR", std::nullopt, 1}},
87 .fee = baseFee,
88 .err = ter(temMALFORMED)});
89
90 // Token pair is in update and delete
91 oracle.set(
93 .series = {{"XRP", "USD", 740, 1}, {"XRP", "USD", std::nullopt, 1}},
94 .fee = baseFee,
95 .err = ter(temMALFORMED)});
96 // Token pair is in add and delete
97 oracle.set(
99 .series = {{"XRP", "EUR", 740, 1}, {"XRP", "EUR", std::nullopt, 1}},
100 .fee = baseFee,
101 .err = ter(temMALFORMED)});
102
103 // Array of token pair is 0 or exceeds 10
104 oracle.set(
105 CreateArg{
106 .series =
107 {{"XRP", "US1", 740, 1},
108 {"XRP", "US2", 750, 1},
109 {"XRP", "US3", 740, 1},
110 {"XRP", "US4", 750, 1},
111 {"XRP", "US5", 740, 1},
112 {"XRP", "US6", 750, 1},
113 {"XRP", "US7", 740, 1},
114 {"XRP", "US8", 750, 1},
115 {"XRP", "US9", 740, 1},
116 {"XRP", "U10", 750, 1},
117 {"XRP", "U11", 740, 1}},
118 .fee = baseFee,
119 .err = ter(temARRAY_TOO_LARGE)});
120 oracle.set(CreateArg{.series = {}, .fee = baseFee, .err = ter(temARRAY_EMPTY)});
121 }
122
123 // Array of token pair exceeds 10 after update
124 {
125 Env env{*this};
126 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
127 env.fund(XRP(1'000), owner);
128
129 Oracle oracle(
130 env,
131 CreateArg{.owner = owner, .series = {{{"XRP", "USD", 740, 1}}}, .fee = baseFee});
132 oracle.set(
133 UpdateArg{
134 .series =
135 {
136 {"XRP", "US1", 740, 1},
137 {"XRP", "US2", 750, 1},
138 {"XRP", "US3", 740, 1},
139 {"XRP", "US4", 750, 1},
140 {"XRP", "US5", 740, 1},
141 {"XRP", "US6", 750, 1},
142 {"XRP", "US7", 740, 1},
143 {"XRP", "US8", 750, 1},
144 {"XRP", "US9", 740, 1},
145 {"XRP", "U10", 750, 1},
146 },
147 .fee = baseFee,
148 .err = ter(tecARRAY_TOO_LARGE)});
149 }
150
151 {
152 Env env(*this);
153 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
154 env.fund(XRP(1'000), owner);
155 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
156
157 // Asset class or provider not included on create
158 oracle.set(
159 CreateArg{
161 .provider = "provider",
162 .fee = baseFee,
163 .err = ter(temMALFORMED)});
164 oracle.set(
165 CreateArg{
166 .assetClass = "currency",
167 .provider = std::nullopt,
168 .uri = "URI",
169 .fee = baseFee,
170 .err = ter(temMALFORMED)});
171
172 // Asset class or provider are included on update
173 // and don't match the current values
174 oracle.set(CreateArg{.fee = static_cast<int>(env.current()->fees().base.drops())});
175 BEAST_EXPECT(oracle.exists());
176 oracle.set(
177 UpdateArg{
178 .series = {{"XRP", "USD", 740, 1}},
179 .provider = "provider1",
180 .fee = baseFee,
181 .err = ter(temMALFORMED)});
182 oracle.set(
183 UpdateArg{
184 .series = {{"XRP", "USD", 740, 1}},
185 .assetClass = "currency1",
186 .fee = baseFee,
187 .err = ter(temMALFORMED)});
188 }
189
190 {
191 Env env(*this);
192 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
193 env.fund(XRP(1'000), owner);
194 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
195
196 // Fields too long
197 // Asset class
198 std::string assetClass(17, '0');
199 oracle.set(
200 CreateArg{.assetClass = assetClass, .fee = baseFee, .err = ter(temMALFORMED)});
201 // provider
202 std::string const large(257, '0');
203 oracle.set(CreateArg{.provider = large, .fee = baseFee, .err = ter(temMALFORMED)});
204 // URI
205 oracle.set(CreateArg{.uri = large, .fee = baseFee, .err = ter(temMALFORMED)});
206 // Empty field
207 // Asset class
208 oracle.set(CreateArg{.assetClass = "", .fee = baseFee, .err = ter(temMALFORMED)});
209 // provider
210 oracle.set(CreateArg{.provider = "", .fee = baseFee, .err = ter(temMALFORMED)});
211 // URI
212 oracle.set(CreateArg{.uri = "", .fee = baseFee, .err = ter(temMALFORMED)});
213 }
214
215 {
216 // Different owner creates a new object and fails because
217 // of missing fields currency/provider
218 Env env(*this);
219 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
220 Account const some("some");
221 env.fund(XRP(1'000), owner);
222 env.fund(XRP(1'000), some);
223 Oracle oracle(env, {.owner = owner, .fee = baseFee});
224 BEAST_EXPECT(oracle.exists());
225 oracle.set(
226 UpdateArg{
227 .owner = some,
228 .series = {{"XRP", "USD", 740, 1}},
229 .fee = baseFee,
230 .err = ter(temMALFORMED)});
231 }
232
233 {
234 // Invalid update time
235 using namespace std::chrono;
236 Env env(*this);
237 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
238 auto closeTime = [&]() {
239 return duration_cast<seconds>(
240 env.current()->header().closeTime.time_since_epoch() - 10'000s)
241 .count();
242 };
243 env.fund(XRP(1'000), owner);
244 Oracle oracle(env, {.owner = owner, .fee = baseFee});
245 BEAST_EXPECT(oracle.exists());
246 env.close(seconds(400));
247 // Less than the last close time - 300s
248 oracle.set(
249 UpdateArg{
250 .series = {{"XRP", "USD", 740, 1}},
251 .lastUpdateTime = static_cast<std::uint32_t>(closeTime() - 301),
252 .fee = baseFee,
253 .err = ter(tecINVALID_UPDATE_TIME)});
254 // Greater than last close time + 300s
255 oracle.set(
256 UpdateArg{
257 .series = {{"XRP", "USD", 740, 1}},
258 .lastUpdateTime = static_cast<std::uint32_t>(closeTime() + 311),
259 .fee = baseFee,
260 .err = ter(tecINVALID_UPDATE_TIME)});
261 oracle.set(UpdateArg{.series = {{"XRP", "USD", 740, 1}}, .fee = baseFee});
262 BEAST_EXPECT(oracle.expectLastUpdateTime(
263 static_cast<std::uint32_t>(testStartTime.count() + 450)));
264 // Less than the previous lastUpdateTime
265 oracle.set(
266 UpdateArg{
267 .series = {{"XRP", "USD", 740, 1}},
268 .lastUpdateTime = static_cast<std::uint32_t>(449),
269 .fee = baseFee,
270 .err = ter(tecINVALID_UPDATE_TIME)});
271 // Less than the epoch time
272 oracle.set(
273 UpdateArg{
274 .series = {{"XRP", "USD", 740, 1}},
275 .lastUpdateTime = static_cast<int>(epoch_offset.count() - 1),
276 .fee = baseFee,
277 .err = ter(tecINVALID_UPDATE_TIME)});
278 }
279
280 {
281 // delete token pair that doesn't exist
282 Env env(*this);
283 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
284 env.fund(XRP(1'000), owner);
285 Oracle oracle(env, {.owner = owner, .fee = baseFee});
286 BEAST_EXPECT(oracle.exists());
287 oracle.set(
288 UpdateArg{
289 .series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
290 .fee = baseFee,
292 // delete all token pairs
293 oracle.set(
294 UpdateArg{
295 .series = {{"XRP", "USD", std::nullopt, std::nullopt}},
296 .fee = baseFee,
297 .err = ter(tecARRAY_EMPTY)});
298 }
299
300 {
301 // same BaseAsset and QuoteAsset
302 Env env(*this);
303 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
304 env.fund(XRP(1'000), owner);
305 Oracle oracle(
306 env,
307 {.owner = owner,
308 .series = {{"USD", "USD", 740, 1}},
309 .fee = baseFee,
310 .err = ter(temMALFORMED)});
311 }
312
313 {
314 // Scale is greater than maxPriceScale
315 Env env(*this);
316 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
317 env.fund(XRP(1'000), owner);
318 Oracle oracle(
319 env,
320 {.owner = owner,
321 .series = {{"USD", "BTC", 740, maxPriceScale + 1}},
322 .fee = baseFee,
323 .err = ter(temMALFORMED)});
324 }
325
326 {
327 // Updating token pair to add and delete
328 Env env(*this);
329 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
330 env.fund(XRP(1'000), owner);
331 Oracle oracle(env, {.owner = owner, .fee = baseFee});
332 oracle.set(
333 UpdateArg{
334 .series = {{"XRP", "EUR", std::nullopt, std::nullopt}, {"XRP", "EUR", 740, 1}},
335 .fee = baseFee,
336 .err = ter(temMALFORMED)});
337 // Delete token pair that doesn't exist in this oracle
338 oracle.set(
339 UpdateArg{
340 .series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
341 .fee = baseFee,
343 // Delete token pair in oracle, which is not in the ledger
344 oracle.set(
345 UpdateArg{
346 .documentID = 10,
347 .series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
348 .fee = baseFee,
349 .err = ter(temMALFORMED)});
350 }
351
352 {
353 // Bad fee
354 Env env(*this);
355 env.fund(XRP(1'000), owner);
356 Oracle oracle(env, {.owner = owner, .fee = -1, .err = ter(temBAD_FEE)});
357 Oracle oracle1(
358 env, {.owner = owner, .fee = static_cast<int>(env.current()->fees().base.drops())});
359 oracle.set(UpdateArg{.owner = owner, .fee = -1, .err = ter(temBAD_FEE)});
360 }
361 }
362
363 void
365 {
366 testcase("Create");
367 using namespace jtx;
368 Account const owner("owner");
369
370 auto test = [&](Env& env, DataSeries const& series, std::uint16_t adj) {
371 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
372 env.fund(XRP(1'000), owner);
373 auto const count = ownerCount(env, owner);
374 Oracle oracle(env, {.owner = owner, .series = series, .fee = baseFee});
375 BEAST_EXPECT(oracle.exists());
376 BEAST_EXPECT(ownerCount(env, owner) == (count + adj));
377 auto const entry = oracle.ledgerEntry();
378 BEAST_EXPECT(entry[jss::node][jss::Owner] == owner.human());
379 if (features[fixIncludeKeyletFields])
380 {
381 BEAST_EXPECT(entry[jss::node][jss::OracleDocumentID] == oracle.documentID());
382 }
383 else
384 {
385 BEAST_EXPECT(!entry[jss::node].isMember(jss::OracleDocumentID));
386 }
387 BEAST_EXPECT(oracle.expectLastUpdateTime(946694810));
388 };
389
390 {
391 // owner count is adjusted by 1
392 Env env(*this, features);
393 test(env, {{"XRP", "USD", 740, 1}}, 1);
394 }
395
396 {
397 // owner count is adjusted by 2
398 Env env(*this, features);
399 test(
400 env,
401 {{"XRP", "USD", 740, 1},
402 {"BTC", "USD", 740, 1},
403 {"ETH", "USD", 740, 1},
404 {"CAN", "USD", 740, 1},
405 {"YAN", "USD", 740, 1},
406 {"GBP", "USD", 740, 1}},
407 2);
408 }
409
410 {
411 // Different owner creates a new object
412 Env env(*this, features);
413 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
414 Account const some("some");
415 env.fund(XRP(1'000), owner);
416 env.fund(XRP(1'000), some);
417 Oracle oracle(env, {.owner = owner, .fee = baseFee});
418 BEAST_EXPECT(oracle.exists());
419 oracle.set(
420 CreateArg{.owner = some, .series = {{"912810RR9", "USD", 740, 1}}, .fee = baseFee});
421 BEAST_EXPECT(Oracle::exists(env, some, oracle.documentID()));
422 }
423 }
424
425 void
427 {
428 testcase("Invalid Delete");
429
430 using namespace jtx;
431 Env env(*this);
432 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
433 Account const owner("owner");
434 env.fund(XRP(1'000), owner);
435 Oracle oracle(env, {.owner = owner, .fee = baseFee});
436 BEAST_EXPECT(oracle.exists());
437
438 {
439 // Invalid account
440 Account const bad("bad");
441 env.memoize(bad);
442 oracle.remove({.owner = bad, .seq = seq(1), .fee = baseFee, .err = ter(terNO_ACCOUNT)});
443 }
444
445 // Invalid DocumentID
446 oracle.remove({.documentID = 2, .fee = baseFee, .err = ter(tecNO_ENTRY)});
447
448 // Invalid owner
449 Account const invalid("invalid");
450 env.fund(XRP(1'000), invalid);
451 oracle.remove({.owner = invalid, .fee = baseFee, .err = ter(tecNO_ENTRY)});
452
453 // Invalid flags
454 oracle.remove({.flags = tfSellNFToken, .fee = baseFee, .err = ter(temINVALID_FLAG)});
455
456 // Bad fee
457 oracle.remove({.fee = -1, .err = ter(temBAD_FEE)});
458 }
459
460 void
462 {
463 testcase("Delete");
464 using namespace jtx;
465 Account const owner("owner");
466
467 auto test = [&](Env& env, DataSeries const& series, std::uint16_t adj) {
468 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
469 env.fund(XRP(1'000), owner);
470 Oracle oracle(env, {.owner = owner, .series = series, .fee = baseFee});
471 auto const count = ownerCount(env, owner);
472 BEAST_EXPECT(oracle.exists());
473 oracle.remove({.fee = baseFee});
474 BEAST_EXPECT(!oracle.exists());
475 BEAST_EXPECT(ownerCount(env, owner) == (count - adj));
476 };
477
478 {
479 // owner count is adjusted by 1
480 Env env(*this);
481 test(env, {{"XRP", "USD", 740, 1}}, 1);
482 }
483
484 {
485 // owner count is adjusted by 2
486 Env env(*this);
487 test(
488 env,
489 {
490 {"XRP", "USD", 740, 1},
491 {"BTC", "USD", 740, 1},
492 {"ETH", "USD", 740, 1},
493 {"CAN", "USD", 740, 1},
494 {"YAN", "USD", 740, 1},
495 {"GBP", "USD", 740, 1},
496 },
497 2);
498 }
499
500 {
501 // deleting the account deletes the oracles
502 Env env(*this);
503 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
504
505 auto const alice = Account("alice");
506 auto const acctDelFee{drops(env.current()->fees().increment)};
507 env.fund(XRP(1'000), owner);
508 env.fund(XRP(1'000), alice);
509 Oracle oracle(
510 env, {.owner = owner, .series = {{"XRP", "USD", 740, 1}}, .fee = baseFee});
511 Oracle oracle1(
512 env,
513 {.owner = owner,
514 .documentID = 2,
515 .series = {{"XRP", "EUR", 740, 1}},
516 .fee = baseFee});
517 BEAST_EXPECT(ownerCount(env, owner) == 2);
518 BEAST_EXPECT(oracle.exists());
519 BEAST_EXPECT(oracle1.exists());
520 auto const index = env.closed()->seq();
521 auto const hash = env.closed()->header().hash;
522 for (int i = 0; i < 256; ++i)
523 env.close();
524 env(acctdelete(owner, alice), fee(acctDelFee));
525 env.close();
526 BEAST_EXPECT(!oracle.exists());
527 BEAST_EXPECT(!oracle1.exists());
528
529 // can still get the oracles via the ledger index or hash
530 auto verifyLedgerData = [&](auto const& field, auto const& value) {
531 Json::Value jvParams;
532 jvParams[field] = value;
533 jvParams[jss::binary] = false;
534 jvParams[jss::type] = jss::oracle;
535 Json::Value jrr = env.rpc("json", "ledger_data", to_string(jvParams));
536 BEAST_EXPECT(jrr[jss::result][jss::state].size() == 2);
537 };
538 verifyLedgerData(jss::ledger_index, index);
539 verifyLedgerData(jss::ledger_hash, to_string(hash));
540 }
541 }
542
543 void
545 {
546 testcase("Update");
547 using namespace jtx;
548 Account const owner("owner");
549
550 {
551 Env env(*this);
552 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
553 env.fund(XRP(1'000), owner);
554 auto count = ownerCount(env, owner);
555 Oracle oracle(env, {.owner = owner, .fee = baseFee});
556 BEAST_EXPECT(oracle.exists());
557
558 // update existing pair
559 oracle.set(UpdateArg{.series = {{"XRP", "USD", 740, 2}}, .fee = baseFee});
560 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 740, 2}}));
561 // owner count is increased by 1 since the oracle object is added
562 // with one token pair
563 count += 1;
564 BEAST_EXPECT(ownerCount(env, owner) == count);
565
566 // add new pairs, not-included pair is reset
567 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 700, 2}}, .fee = baseFee});
568 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 0, 0}, {"XRP", "EUR", 700, 2}}));
569 // owner count is not changed since the number of pairs is 2
570 BEAST_EXPECT(ownerCount(env, owner) == count);
571
572 // update both pairs
573 oracle.set(
574 UpdateArg{
575 .series = {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}}, .fee = baseFee});
576 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}}));
577 // owner count is not changed since the number of pairs is 2
578 BEAST_EXPECT(ownerCount(env, owner) == count);
579
580 // owner count is increased by 1 since the number of pairs is 6
581 oracle.set(
582 UpdateArg{
583 .series =
584 {
585 {"BTC", "USD", 741, 2},
586 {"ETH", "EUR", 710, 2},
587 {"YAN", "EUR", 710, 2},
588 {"CAN", "EUR", 710, 2},
589 },
590 .fee = baseFee});
591 count += 1;
592 BEAST_EXPECT(ownerCount(env, owner) == count);
593
594 // update two pairs and delete four
595 oracle.set(
596 UpdateArg{.series = {{"BTC", "USD", std::nullopt, std::nullopt}}, .fee = baseFee});
597 oracle.set(
598 UpdateArg{
599 .series =
600 {{"XRP", "USD", 742, 2},
601 {"XRP", "EUR", 711, 2},
602 {"ETH", "EUR", std::nullopt, std::nullopt},
603 {"YAN", "EUR", std::nullopt, std::nullopt},
604 {"CAN", "EUR", std::nullopt, std::nullopt}},
605 .fee = baseFee});
606 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}}));
607 // owner count is decreased by 1 since the number of pairs is 2
608 count -= 1;
609 BEAST_EXPECT(ownerCount(env, owner) == count);
610 }
611
612 // Min reserve to create and update
613 {
614 Env env(*this);
615 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
616 env.fund(
617 env.current()->fees().accountReserve(1) + env.current()->fees().base * 2, owner);
618 Oracle oracle(env, {.owner = owner, .fee = baseFee});
619 oracle.set(UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee});
620 }
621
622 for (bool const withFixOrder : {false, true})
623 {
624 // Should be same order as creation
625 Env env(
626 *this,
627 withFixOrder ? testable_amendments() : testable_amendments() - fixPriceOracleOrder);
628 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
629
630 auto test = [&](Env& env, DataSeries const& series) {
631 env.fund(XRP(1'000), owner);
632 Oracle oracle(env, {.owner = owner, .series = series, .fee = baseFee});
633 BEAST_EXPECT(oracle.exists());
634 auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
635 BEAST_EXPECT(sle->getFieldArray(sfPriceDataSeries).size() == series.size());
636
637 auto const beforeQuoteAssetName1 = sle->getFieldArray(sfPriceDataSeries)[0]
638 .getFieldCurrency(sfQuoteAsset)
639 .getText();
640 auto const beforeQuoteAssetName2 = sle->getFieldArray(sfPriceDataSeries)[1]
641 .getFieldCurrency(sfQuoteAsset)
642 .getText();
643
644 oracle.set(UpdateArg{.series = series, .fee = baseFee});
645 sle = env.le(keylet::oracle(owner, oracle.documentID()));
646
647 auto const afterQuoteAssetName1 = sle->getFieldArray(sfPriceDataSeries)[0]
648 .getFieldCurrency(sfQuoteAsset)
649 .getText();
650 auto const afterQuoteAssetName2 = sle->getFieldArray(sfPriceDataSeries)[1]
651 .getFieldCurrency(sfQuoteAsset)
652 .getText();
653
654 if (env.current()->rules().enabled(fixPriceOracleOrder))
655 {
656 BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
657 BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
658 }
659 else
660 {
661 BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1);
662 BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2);
663 }
664 };
665 test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
666 }
667 }
668
669 void
671 {
672 testcase("Multisig");
673 using namespace jtx;
674 Oracle::setFee(100'000);
675
676 Env env(*this);
677 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
678
679 Account const alice{"alice", KeyType::secp256k1};
680 Account const bogie{"bogie", KeyType::secp256k1};
681 Account const ed{"ed", KeyType::secp256k1};
682 Account const becky{"becky", KeyType::ed25519};
683 Account const zelda{"zelda", KeyType::secp256k1};
684 Account const bob{"bob", KeyType::secp256k1};
685 env.fund(XRP(10'000), alice, becky, zelda, ed, bob);
686
687 // alice uses a regular key with the master disabled.
688 Account const alie{"alie", KeyType::secp256k1};
689 env(regkey(alice, alie));
690 env(fset(alice, asfDisableMaster), sig(alice));
691
692 // Attach signers to alice.
693 env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}), sig(alie));
694 env.close();
695
696 env.require(owners(alice, 1));
697
698 // Create
699 // Force close (true) and time advancement because the close time
700 // is no longer 0.
701 Oracle oracle(env, CreateArg{.owner = alice, .fee = baseFee, .close = true}, false);
702 oracle.set(CreateArg{.msig = msig(becky), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
703 oracle.set(CreateArg{.msig = msig(zelda), .fee = baseFee, .err = ter(tefBAD_SIGNATURE)});
704 oracle.set(CreateArg{.msig = msig(becky, bogie), .fee = baseFee});
705 BEAST_EXPECT(oracle.exists());
706
707 // Update
708 oracle.set(
709 UpdateArg{
710 .series = {{"XRP", "USD", 740, 1}},
711 .msig = msig(becky),
712 .fee = baseFee,
713 .err = ter(tefBAD_QUORUM)});
714 oracle.set(
715 UpdateArg{
716 .series = {{"XRP", "USD", 740, 1}},
717 .msig = msig(zelda),
718 .fee = baseFee,
719 .err = ter(tefBAD_SIGNATURE)});
720 oracle.set(
721 UpdateArg{
722 .series = {{"XRP", "USD", 741, 1}}, .msig = msig(becky, bogie), .fee = baseFee});
723 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 741, 1}}));
724 // remove the signer list
725 env(signers(alice, jtx::none), sig(alie));
726 env.close();
727 env.require(owners(alice, 1));
728 // create new signer list
729 env(signers(alice, 2, {{zelda, 1}, {bob, 1}, {ed, 2}}), sig(alie));
730 env.close();
731 // old list fails
732 oracle.set(
733 UpdateArg{
734 .series = {{"XRP", "USD", 740, 1}},
735 .msig = msig(becky, bogie),
736 .fee = baseFee,
737 .err = ter(tefBAD_SIGNATURE)});
738 // updated list succeeds
739 oracle.set(
740 UpdateArg{
741 .series = {{"XRP", "USD", 7412, 2}}, .msig = msig(zelda, bob), .fee = baseFee});
742 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 7412, 2}}));
743 oracle.set(
744 UpdateArg{.series = {{"XRP", "USD", 74245, 3}}, .msig = msig(ed), .fee = baseFee});
745 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 74245, 3}}));
746
747 // Remove
748 oracle.remove({.msig = msig(bob), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
749 oracle.remove({.msig = msig(becky), .fee = baseFee, .err = ter(tefBAD_SIGNATURE)});
750 oracle.remove({.msig = msig(ed), .fee = baseFee});
751 BEAST_EXPECT(!oracle.exists());
752 }
753
754 void
756 {
757 testcase("Amendment");
758 using namespace jtx;
759
760 auto const features = testable_amendments() - featurePriceOracle;
761 Account const owner("owner");
762 Env env(*this, features);
763 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
764
765 env.fund(XRP(1'000), owner);
766 {
767 Oracle oracle(env, {.owner = owner, .fee = baseFee, .err = ter(temDISABLED)});
768 }
769
770 {
771 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
772 oracle.remove({.fee = baseFee, .err = ter(temDISABLED)});
773 }
774 }
775
776public:
777 void
778 run() override
779 {
780 using namespace jtx;
781 auto const all = testable_amendments();
782 testInvalidSet();
783 testInvalidDelete();
784 testCreate(all);
785 testCreate(all - fixIncludeKeyletFields);
786 testDelete();
787 testUpdate();
788 testAmendment();
789 testMultisig();
790 }
791};
792
793BEAST_DEFINE_TESTSUITE(Oracle, app, xrpl);
794
795} // namespace oracle
796
797} // namespace jtx
798
799} // namespace test
800
801} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
Immutable cryptographic account descriptor.
Definition Account.h:19
std::string const & human() const
Returns the human readable public key.
Definition Account.h:94
A transaction testing environment.
Definition Env.h:121
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:94
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:263
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:803
void memoize(Account const &account)
Associate AccountID with account.
Definition Env.cpp:133
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:544
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:328
Set the fee on a JTx.
Definition fee.h:17
Set a multisignature on a JTx.
Definition multisign.h:41
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition Oracle.h:96
Match the number of items in the account's owner directory.
Definition owners.h:52
Set the regular signature on a JTx.
Definition sig.h:15
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
T count(T... args)
T is_same_v
Keylet oracle(AccountID const &account, std::uint32_t const &documentID) noexcept
Definition Indexes.cpp:468
std::vector< std::tuple< std::string, std::string, std::optional< std::uint32_t >, std::optional< std::uint8_t > > > DataSeries
Definition Oracle.h:36
static constexpr std::chrono::seconds testStartTime
Definition Oracle.h:89
std::uint32_t ownerCount(Env const &env, Account const &account)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:91
FeatureBitset testable_amendments()
Definition Env.h:77
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition regkey.cpp:10
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_ACCOUNT
Definition TER.h:197
constexpr std::uint32_t asfDisableMaster
Definition TxFlags.h:60
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:600
@ tefBAD_QUORUM
Definition TER.h:160
@ tefBAD_SIGNATURE
Definition TER.h:159
std::size_t constexpr maxPriceScale
The maximum price scaling factor.
Definition Protocol.h:297
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
Definition chrono.h:33
@ temARRAY_TOO_LARGE
Definition TER.h:121
@ temBAD_FEE
Definition TER.h:72
@ temINVALID_FLAG
Definition TER.h:91
@ temMALFORMED
Definition TER.h:67
@ temARRAY_EMPTY
Definition TER.h:120
@ temDISABLED
Definition TER.h:94
@ tecINVALID_UPDATE_TIME
Definition TER.h:335
@ tecNO_ENTRY
Definition TER.h:287
@ tecARRAY_TOO_LARGE
Definition TER.h:338
@ tecARRAY_EMPTY
Definition TER.h:337
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecTOKEN_PAIR_NOT_FOUND
Definition TER.h:336
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:210
@ invalid
Timely, but invalid signature.
std::optional< AccountID > owner
Definition Oracle.h:41
std::optional< AnyValue > provider
Definition Oracle.h:45
std::optional< AnyValue > assetClass
Definition Oracle.h:44
std::optional< jtx::msig > msig
Definition Oracle.h:49
std::optional< AnyValue > uri
Definition Oracle.h:46
void run() override
Runs the suite.
void testCreate(FeatureBitset features)
std::optional< AccountID > owner
Definition Oracle.h:59
std::optional< AnyValue > documentID
Definition Oracle.h:60
Set the sequence number on a JTx.
Definition seq.h:14