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