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