rippled
Loading...
Searching...
No Matches
Oracle_test.cpp
1#include <test/jtx/Oracle.h>
2
3#include <xrpl/protocol/jss.h>
4
5namespace ripple {
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()->info().closeTime.time_since_epoch() -
256 10'000s)
257 .count();
258 };
259 env.fund(XRP(1'000), owner);
260 Oracle oracle(env, {.owner = owner, .fee = baseFee});
261 BEAST_EXPECT(oracle.exists());
262 env.close(seconds(400));
263 // Less than the last close time - 300s
264 oracle.set(UpdateArg{
265 .series = {{"XRP", "USD", 740, 1}},
266 .lastUpdateTime = static_cast<std::uint32_t>(closeTime() - 301),
267 .fee = baseFee,
268 .err = ter(tecINVALID_UPDATE_TIME)});
269 // Greater than last close time + 300s
270 oracle.set(UpdateArg{
271 .series = {{"XRP", "USD", 740, 1}},
272 .lastUpdateTime = static_cast<std::uint32_t>(closeTime() + 311),
273 .fee = baseFee,
274 .err = ter(tecINVALID_UPDATE_TIME)});
275 oracle.set(
276 UpdateArg{.series = {{"XRP", "USD", 740, 1}}, .fee = baseFee});
277 BEAST_EXPECT(oracle.expectLastUpdateTime(
278 static_cast<std::uint32_t>(testStartTime.count() + 450)));
279 // Less than the previous lastUpdateTime
280 oracle.set(UpdateArg{
281 .series = {{"XRP", "USD", 740, 1}},
282 .lastUpdateTime = static_cast<std::uint32_t>(449),
283 .fee = baseFee,
284 .err = ter(tecINVALID_UPDATE_TIME)});
285 // Less than the epoch time
286 oracle.set(UpdateArg{
287 .series = {{"XRP", "USD", 740, 1}},
288 .lastUpdateTime = static_cast<int>(epoch_offset.count() - 1),
289 .fee = baseFee,
290 .err = ter(tecINVALID_UPDATE_TIME)});
291 }
292
293 {
294 // delete token pair that doesn't exist
295 Env env(*this);
296 auto const baseFee =
297 static_cast<int>(env.current()->fees().base.drops());
298 env.fund(XRP(1'000), owner);
299 Oracle oracle(env, {.owner = owner, .fee = baseFee});
300 BEAST_EXPECT(oracle.exists());
301 oracle.set(UpdateArg{
302 .series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
303 .fee = baseFee,
305 // delete all token pairs
306 oracle.set(UpdateArg{
307 .series = {{"XRP", "USD", std::nullopt, std::nullopt}},
308 .fee = baseFee,
309 .err = ter(tecARRAY_EMPTY)});
310 }
311
312 {
313 // same BaseAsset and QuoteAsset
314 Env env(*this);
315 auto const baseFee =
316 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", "USD", 740, 1}},
322 .fee = baseFee,
323 .err = ter(temMALFORMED)});
324 }
325
326 {
327 // Scale is greater than maxPriceScale
328 Env env(*this);
329 auto const baseFee =
330 static_cast<int>(env.current()->fees().base.drops());
331 env.fund(XRP(1'000), owner);
332 Oracle oracle(
333 env,
334 {.owner = owner,
335 .series = {{"USD", "BTC", 740, maxPriceScale + 1}},
336 .fee = baseFee,
337 .err = ter(temMALFORMED)});
338 }
339
340 {
341 // Updating token pair to add and delete
342 Env env(*this);
343 auto const baseFee =
344 static_cast<int>(env.current()->fees().base.drops());
345 env.fund(XRP(1'000), owner);
346 Oracle oracle(env, {.owner = owner, .fee = baseFee});
347 oracle.set(UpdateArg{
348 .series =
349 {{"XRP", "EUR", std::nullopt, std::nullopt},
350 {"XRP", "EUR", 740, 1}},
351 .fee = baseFee,
352 .err = ter(temMALFORMED)});
353 // Delete token pair that doesn't exist in this oracle
354 oracle.set(UpdateArg{
355 .series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
356 .fee = baseFee,
358 // Delete token pair in oracle, which is not in the ledger
359 oracle.set(UpdateArg{
360 .documentID = 10,
361 .series = {{"XRP", "EUR", std::nullopt, std::nullopt}},
362 .fee = baseFee,
363 .err = ter(temMALFORMED)});
364 }
365
366 {
367 // Bad fee
368 Env env(*this);
369 env.fund(XRP(1'000), owner);
370 Oracle oracle(
371 env, {.owner = owner, .fee = -1, .err = ter(temBAD_FEE)});
372 Oracle oracle1(
373 env,
374 {.owner = owner,
375 .fee = static_cast<int>(env.current()->fees().base.drops())});
376 oracle.set(
377 UpdateArg{.owner = owner, .fee = -1, .err = ter(temBAD_FEE)});
378 }
379 }
380
381 void
383 {
384 testcase("Create");
385 using namespace jtx;
386 Account const owner("owner");
387
388 auto test = [&](Env& env, DataSeries const& series, std::uint16_t adj) {
389 auto const baseFee =
390 static_cast<int>(env.current()->fees().base.drops());
391 env.fund(XRP(1'000), owner);
392 auto const count = ownerCount(env, owner);
393 Oracle oracle(
394 env, {.owner = owner, .series = series, .fee = baseFee});
395 BEAST_EXPECT(oracle.exists());
396 BEAST_EXPECT(ownerCount(env, owner) == (count + adj));
397 auto const entry = oracle.ledgerEntry();
398 BEAST_EXPECT(entry[jss::node][jss::Owner] == owner.human());
399 if (features[fixIncludeKeyletFields])
400 {
401 BEAST_EXPECT(
402 entry[jss::node][jss::OracleDocumentID] ==
403 oracle.documentID());
404 }
405 else
406 {
407 BEAST_EXPECT(!entry[jss::node].isMember(jss::OracleDocumentID));
408 }
409 BEAST_EXPECT(oracle.expectLastUpdateTime(946694810));
410 };
411
412 {
413 // owner count is adjusted by 1
414 Env env(*this, features);
415 test(env, {{"XRP", "USD", 740, 1}}, 1);
416 }
417
418 {
419 // owner count is adjusted by 2
420 Env env(*this, features);
421 test(
422 env,
423 {{"XRP", "USD", 740, 1},
424 {"BTC", "USD", 740, 1},
425 {"ETH", "USD", 740, 1},
426 {"CAN", "USD", 740, 1},
427 {"YAN", "USD", 740, 1},
428 {"GBP", "USD", 740, 1}},
429 2);
430 }
431
432 {
433 // Different owner creates a new object
434 Env env(*this, features);
435 auto const baseFee =
436 static_cast<int>(env.current()->fees().base.drops());
437 Account const some("some");
438 env.fund(XRP(1'000), owner);
439 env.fund(XRP(1'000), some);
440 Oracle oracle(env, {.owner = owner, .fee = baseFee});
441 BEAST_EXPECT(oracle.exists());
442 oracle.set(CreateArg{
443 .owner = some,
444 .series = {{"912810RR9", "USD", 740, 1}},
445 .fee = baseFee});
446 BEAST_EXPECT(Oracle::exists(env, some, oracle.documentID()));
447 }
448 }
449
450 void
452 {
453 testcase("Invalid Delete");
454
455 using namespace jtx;
456 Env env(*this);
457 auto const baseFee =
458 static_cast<int>(env.current()->fees().base.drops());
459 Account const owner("owner");
460 env.fund(XRP(1'000), owner);
461 Oracle oracle(env, {.owner = owner, .fee = baseFee});
462 BEAST_EXPECT(oracle.exists());
463
464 {
465 // Invalid account
466 Account const bad("bad");
467 env.memoize(bad);
468 oracle.remove(
469 {.owner = bad,
470 .seq = seq(1),
471 .fee = baseFee,
472 .err = ter(terNO_ACCOUNT)});
473 }
474
475 // Invalid DocumentID
476 oracle.remove(
477 {.documentID = 2, .fee = baseFee, .err = ter(tecNO_ENTRY)});
478
479 // Invalid owner
480 Account const invalid("invalid");
481 env.fund(XRP(1'000), invalid);
482 oracle.remove(
483 {.owner = invalid, .fee = baseFee, .err = ter(tecNO_ENTRY)});
484
485 // Invalid flags
486 oracle.remove(
487 {.flags = tfSellNFToken,
488 .fee = baseFee,
489 .err = ter(temINVALID_FLAG)});
490
491 // Bad fee
492 oracle.remove({.fee = -1, .err = ter(temBAD_FEE)});
493 }
494
495 void
497 {
498 testcase("Delete");
499 using namespace jtx;
500 Account const owner("owner");
501
502 auto test = [&](Env& env, DataSeries const& series, std::uint16_t adj) {
503 auto const baseFee =
504 static_cast<int>(env.current()->fees().base.drops());
505 env.fund(XRP(1'000), owner);
506 Oracle oracle(
507 env, {.owner = owner, .series = series, .fee = baseFee});
508 auto const count = ownerCount(env, owner);
509 BEAST_EXPECT(oracle.exists());
510 oracle.remove({.fee = baseFee});
511 BEAST_EXPECT(!oracle.exists());
512 BEAST_EXPECT(ownerCount(env, owner) == (count - adj));
513 };
514
515 {
516 // owner count is adjusted by 1
517 Env env(*this);
518 test(env, {{"XRP", "USD", 740, 1}}, 1);
519 }
520
521 {
522 // owner count is adjusted by 2
523 Env env(*this);
524 test(
525 env,
526 {
527 {"XRP", "USD", 740, 1},
528 {"BTC", "USD", 740, 1},
529 {"ETH", "USD", 740, 1},
530 {"CAN", "USD", 740, 1},
531 {"YAN", "USD", 740, 1},
532 {"GBP", "USD", 740, 1},
533 },
534 2);
535 }
536
537 {
538 // deleting the account deletes the oracles
539 Env env(*this);
540 auto const baseFee =
541 static_cast<int>(env.current()->fees().base.drops());
542
543 auto const alice = Account("alice");
544 auto const acctDelFee{drops(env.current()->fees().increment)};
545 env.fund(XRP(1'000), owner);
546 env.fund(XRP(1'000), alice);
547 Oracle oracle(
548 env,
549 {.owner = owner,
550 .series = {{"XRP", "USD", 740, 1}},
551 .fee = baseFee});
552 Oracle oracle1(
553 env,
554 {.owner = owner,
555 .documentID = 2,
556 .series = {{"XRP", "EUR", 740, 1}},
557 .fee = baseFee});
558 BEAST_EXPECT(ownerCount(env, owner) == 2);
559 BEAST_EXPECT(oracle.exists());
560 BEAST_EXPECT(oracle1.exists());
561 auto const index = env.closed()->seq();
562 auto const hash = env.closed()->info().hash;
563 for (int i = 0; i < 256; ++i)
564 env.close();
565 env(acctdelete(owner, alice), fee(acctDelFee));
566 env.close();
567 BEAST_EXPECT(!oracle.exists());
568 BEAST_EXPECT(!oracle1.exists());
569
570 // can still get the oracles via the ledger index or hash
571 auto verifyLedgerData = [&](auto const& field, auto const& value) {
572 Json::Value jvParams;
573 jvParams[field] = value;
574 jvParams[jss::binary] = false;
575 jvParams[jss::type] = jss::oracle;
576 Json::Value jrr =
577 env.rpc("json", "ledger_data", to_string(jvParams));
578 BEAST_EXPECT(jrr[jss::result][jss::state].size() == 2);
579 };
580 verifyLedgerData(jss::ledger_index, index);
581 verifyLedgerData(jss::ledger_hash, to_string(hash));
582 }
583 }
584
585 void
587 {
588 testcase("Update");
589 using namespace jtx;
590 Account const owner("owner");
591
592 {
593 Env env(*this);
594 auto const baseFee =
595 static_cast<int>(env.current()->fees().base.drops());
596 env.fund(XRP(1'000), owner);
597 auto count = ownerCount(env, owner);
598 Oracle oracle(env, {.owner = owner, .fee = baseFee});
599 BEAST_EXPECT(oracle.exists());
600
601 // update existing pair
602 oracle.set(
603 UpdateArg{.series = {{"XRP", "USD", 740, 2}}, .fee = baseFee});
604 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 740, 2}}));
605 // owner count is increased by 1 since the oracle object is added
606 // with one token pair
607 count += 1;
608 BEAST_EXPECT(ownerCount(env, owner) == count);
609
610 // add new pairs, not-included pair is reset
611 oracle.set(
612 UpdateArg{.series = {{"XRP", "EUR", 700, 2}}, .fee = baseFee});
613 BEAST_EXPECT(oracle.expectPrice(
614 {{"XRP", "USD", 0, 0}, {"XRP", "EUR", 700, 2}}));
615 // owner count is not changed since the number of pairs is 2
616 BEAST_EXPECT(ownerCount(env, owner) == count);
617
618 // update both pairs
619 oracle.set(UpdateArg{
620 .series = {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}},
621 .fee = baseFee});
622 BEAST_EXPECT(oracle.expectPrice(
623 {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}}));
624 // owner count is not changed since the number of pairs is 2
625 BEAST_EXPECT(ownerCount(env, owner) == count);
626
627 // owner count is increased by 1 since the number of pairs is 6
628 oracle.set(UpdateArg{
629 .series =
630 {
631 {"BTC", "USD", 741, 2},
632 {"ETH", "EUR", 710, 2},
633 {"YAN", "EUR", 710, 2},
634 {"CAN", "EUR", 710, 2},
635 },
636 .fee = baseFee});
637 count += 1;
638 BEAST_EXPECT(ownerCount(env, owner) == count);
639
640 // update two pairs and delete four
641 oracle.set(UpdateArg{
642 .series = {{"BTC", "USD", std::nullopt, std::nullopt}},
643 .fee = baseFee});
644 oracle.set(UpdateArg{
645 .series =
646 {{"XRP", "USD", 742, 2},
647 {"XRP", "EUR", 711, 2},
648 {"ETH", "EUR", std::nullopt, std::nullopt},
649 {"YAN", "EUR", std::nullopt, std::nullopt},
650 {"CAN", "EUR", std::nullopt, std::nullopt}},
651 .fee = baseFee});
652 BEAST_EXPECT(oracle.expectPrice(
653 {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}}));
654 // owner count is decreased by 1 since the number of pairs is 2
655 count -= 1;
656 BEAST_EXPECT(ownerCount(env, owner) == count);
657 }
658
659 // Min reserve to create and update
660 {
661 Env env(*this);
662 auto const baseFee =
663 static_cast<int>(env.current()->fees().base.drops());
664 env.fund(
665 env.current()->fees().accountReserve(1) +
666 env.current()->fees().base * 2,
667 owner);
668 Oracle oracle(env, {.owner = owner, .fee = baseFee});
669 oracle.set(
670 UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee});
671 }
672
673 for (bool const withFixOrder : {false, true})
674 {
675 // Should be same order as creation
676 Env env(
677 *this,
678 withFixOrder ? testable_amendments()
679 : testable_amendments() - fixPriceOracleOrder);
680 auto const baseFee =
681 static_cast<int>(env.current()->fees().base.drops());
682
683 auto test = [&](Env& env, DataSeries const& series) {
684 env.fund(XRP(1'000), owner);
685 Oracle oracle(
686 env, {.owner = owner, .series = series, .fee = baseFee});
687 BEAST_EXPECT(oracle.exists());
688 auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
689 BEAST_EXPECT(
690 sle->getFieldArray(sfPriceDataSeries).size() ==
691 series.size());
692
693 auto const beforeQuoteAssetName1 =
694 sle->getFieldArray(sfPriceDataSeries)[0]
695 .getFieldCurrency(sfQuoteAsset)
696 .getText();
697 auto const beforeQuoteAssetName2 =
698 sle->getFieldArray(sfPriceDataSeries)[1]
699 .getFieldCurrency(sfQuoteAsset)
700 .getText();
701
702 oracle.set(UpdateArg{.series = series, .fee = baseFee});
703 sle = env.le(keylet::oracle(owner, oracle.documentID()));
704
705 auto const afterQuoteAssetName1 =
706 sle->getFieldArray(sfPriceDataSeries)[0]
707 .getFieldCurrency(sfQuoteAsset)
708 .getText();
709 auto const afterQuoteAssetName2 =
710 sle->getFieldArray(sfPriceDataSeries)[1]
711 .getFieldCurrency(sfQuoteAsset)
712 .getText();
713
714 if (env.current()->rules().enabled(fixPriceOracleOrder))
715 {
716 BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
717 BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
718 }
719 else
720 {
721 BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1);
722 BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2);
723 }
724 };
725 test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
726 }
727 }
728
729 void
731 {
732 testcase("Multisig");
733 using namespace jtx;
734 Oracle::setFee(100'000);
735
736 Env env(*this, features);
737 auto const baseFee =
738 static_cast<int>(env.current()->fees().base.drops());
739
740 Account const alice{"alice", KeyType::secp256k1};
741 Account const bogie{"bogie", KeyType::secp256k1};
742 Account const ed{"ed", KeyType::secp256k1};
743 Account const becky{"becky", KeyType::ed25519};
744 Account const zelda{"zelda", KeyType::secp256k1};
745 Account const bob{"bob", KeyType::secp256k1};
746 env.fund(XRP(10'000), alice, becky, zelda, ed, bob);
747
748 // alice uses a regular key with the master disabled.
749 Account const alie{"alie", KeyType::secp256k1};
750 env(regkey(alice, alie));
751 env(fset(alice, asfDisableMaster), sig(alice));
752
753 // Attach signers to alice.
754 env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}), sig(alie));
755 env.close();
756 // if multiSignReserve disabled then its 2 + 1 per signer
757 int const signerListOwners{features[featureMultiSignReserve] ? 1 : 5};
758 env.require(owners(alice, signerListOwners));
759
760 // Create
761 // Force close (true) and time advancement because the close time
762 // is no longer 0.
763 Oracle oracle(
764 env,
765 CreateArg{.owner = alice, .fee = baseFee, .close = true},
766 false);
767 oracle.set(CreateArg{
768 .msig = msig(becky), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
769 oracle.set(CreateArg{
770 .msig = msig(zelda), .fee = baseFee, .err = ter(tefBAD_SIGNATURE)});
771 oracle.set(CreateArg{.msig = msig(becky, bogie), .fee = baseFee});
772 BEAST_EXPECT(oracle.exists());
773
774 // Update
775 oracle.set(UpdateArg{
776 .series = {{"XRP", "USD", 740, 1}},
777 .msig = msig(becky),
778 .fee = baseFee,
779 .err = ter(tefBAD_QUORUM)});
780 oracle.set(UpdateArg{
781 .series = {{"XRP", "USD", 740, 1}},
782 .msig = msig(zelda),
783 .fee = baseFee,
784 .err = ter(tefBAD_SIGNATURE)});
785 oracle.set(UpdateArg{
786 .series = {{"XRP", "USD", 741, 1}},
787 .msig = msig(becky, bogie),
788 .fee = baseFee});
789 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 741, 1}}));
790 // remove the signer list
791 env(signers(alice, jtx::none), sig(alie));
792 env.close();
793 env.require(owners(alice, 1));
794 // create new signer list
795 env(signers(alice, 2, {{zelda, 1}, {bob, 1}, {ed, 2}}), sig(alie));
796 env.close();
797 // old list fails
798 oracle.set(UpdateArg{
799 .series = {{"XRP", "USD", 740, 1}},
800 .msig = msig(becky, bogie),
801 .fee = baseFee,
802 .err = ter(tefBAD_SIGNATURE)});
803 // updated list succeeds
804 oracle.set(UpdateArg{
805 .series = {{"XRP", "USD", 7412, 2}},
806 .msig = msig(zelda, bob),
807 .fee = baseFee});
808 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 7412, 2}}));
809 oracle.set(UpdateArg{
810 .series = {{"XRP", "USD", 74245, 3}},
811 .msig = msig(ed),
812 .fee = baseFee});
813 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 74245, 3}}));
814
815 // Remove
816 oracle.remove(
817 {.msig = msig(bob), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
818 oracle.remove(
819 {.msig = msig(becky),
820 .fee = baseFee,
821 .err = ter(tefBAD_SIGNATURE)});
822 oracle.remove({.msig = msig(ed), .fee = baseFee});
823 BEAST_EXPECT(!oracle.exists());
824 }
825
826 void
828 {
829 testcase("Amendment");
830 using namespace jtx;
831
832 auto const features = testable_amendments() - featurePriceOracle;
833 Account const owner("owner");
834 Env env(*this, features);
835 auto const baseFee =
836 static_cast<int>(env.current()->fees().base.drops());
837
838 env.fund(XRP(1'000), owner);
839 {
840 Oracle oracle(
841 env, {.owner = owner, .fee = baseFee, .err = ter(temDISABLED)});
842 }
843
844 {
845 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
846 oracle.remove({.fee = baseFee, .err = ter(temDISABLED)});
847 }
848 }
849
850public:
851 void
852 run() override
853 {
854 using namespace jtx;
855 auto const all = testable_amendments();
856 testInvalidSet();
857 testInvalidDelete();
858 testCreate(all);
859 testCreate(all - fixIncludeKeyletFields);
860 testDelete();
861 testUpdate();
862 testAmendment();
863 for (auto const& features :
864 {all,
865 all - featureMultiSignReserve - featureExpandedSignerList,
866 all - featureExpandedSignerList})
867 testMultisig(features);
868 }
869};
870
871BEAST_DEFINE_TESTSUITE(Oracle, app, ripple);
872
873} // namespace oracle
874
875} // namespace jtx
876
877} // namespace test
878
879} // namespace ripple
Represents a JSON value.
Definition json_value.h:130
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
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:97
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:528
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:312
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:103
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:772
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
void memoize(Account const &account)
Associate AccountID with account.
Definition Env.cpp:138
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:501
std::vector< std::tuple< std::string, std::string, std::optional< std::uint32_t >, std::optional< std::uint8_t > > > DataSeries
Definition Oracle.h:40
static constexpr std::chrono::seconds testStartTime
Definition Oracle.h:93
std::uint32_t ownerCount(Env const &env, Account const &account)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition regkey.cpp:10
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
FeatureBitset testable_amendments()
Definition Env.h:55
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::size_t constexpr maxPriceScale
The maximum price scaling factor.
Definition Protocol.h:152
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:211
constexpr std::uint32_t asfDisableMaster
Definition TxFlags.h:61
@ tefBAD_QUORUM
Definition TER.h:161
@ tefBAD_SIGNATURE
Definition TER.h:160
@ invalid
Timely, but invalid signature.
@ tecNO_ENTRY
Definition TER.h:288
@ tecARRAY_TOO_LARGE
Definition TER.h:339
@ tecINVALID_UPDATE_TIME
Definition TER.h:336
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecARRAY_EMPTY
Definition TER.h:338
@ tecTOKEN_PAIR_NOT_FOUND
Definition TER.h:337
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
Definition chrono.h:36
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@ terNO_ACCOUNT
Definition TER.h:198
@ temBAD_FEE
Definition TER.h:73
@ temMALFORMED
Definition TER.h:68
@ temINVALID_FLAG
Definition TER.h:92
@ temARRAY_EMPTY
Definition TER.h:121
@ temARRAY_TOO_LARGE
Definition TER.h:122
@ temDISABLED
Definition TER.h:95
std::optional< AnyValue > uri
Definition Oracle.h:50
std::optional< AnyValue > assetClass
Definition Oracle.h:48
std::optional< AccountID > owner
Definition Oracle.h:45
std::optional< jtx::msig > msig
Definition Oracle.h:53
std::optional< AnyValue > provider
Definition Oracle.h:49
void run() override
Runs the suite.
void testCreate(FeatureBitset features)
void testMultisig(FeatureBitset features)
std::optional< AnyValue > documentID
Definition Oracle.h:64
std::optional< AccountID > owner
Definition Oracle.h:63
Set the sequence number on a JTx.
Definition seq.h:15