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 =
596 env.rpc("json", "ledger_data", to_string(jvParams));
597 BEAST_EXPECT(jrr[jss::result][jss::state].size() == 2);
598 };
599 verifyLedgerData(jss::ledger_index, index);
600 verifyLedgerData(jss::ledger_hash, to_string(hash));
601 }
602 }
603
604 void
606 {
607 testcase("Update");
608 using namespace jtx;
609 Account const owner("owner");
610
611 {
612 Env env(*this);
613 auto const baseFee =
614 static_cast<int>(env.current()->fees().base.drops());
615 env.fund(XRP(1'000), owner);
616 auto count = ownerCount(env, owner);
617 Oracle oracle(env, {.owner = owner, .fee = baseFee});
618 BEAST_EXPECT(oracle.exists());
619
620 // update existing pair
621 oracle.set(
622 UpdateArg{.series = {{"XRP", "USD", 740, 2}}, .fee = baseFee});
623 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 740, 2}}));
624 // owner count is increased by 1 since the oracle object is added
625 // with one token pair
626 count += 1;
627 BEAST_EXPECT(ownerCount(env, owner) == count);
628
629 // add new pairs, not-included pair is reset
630 oracle.set(
631 UpdateArg{.series = {{"XRP", "EUR", 700, 2}}, .fee = baseFee});
632 BEAST_EXPECT(oracle.expectPrice(
633 {{"XRP", "USD", 0, 0}, {"XRP", "EUR", 700, 2}}));
634 // owner count is not changed since the number of pairs is 2
635 BEAST_EXPECT(ownerCount(env, owner) == count);
636
637 // update both pairs
638 oracle.set(UpdateArg{
639 .series = {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}},
640 .fee = baseFee});
641 BEAST_EXPECT(oracle.expectPrice(
642 {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}}));
643 // owner count is not changed since the number of pairs is 2
644 BEAST_EXPECT(ownerCount(env, owner) == count);
645
646 // owner count is increased by 1 since the number of pairs is 6
647 oracle.set(UpdateArg{
648 .series =
649 {
650 {"BTC", "USD", 741, 2},
651 {"ETH", "EUR", 710, 2},
652 {"YAN", "EUR", 710, 2},
653 {"CAN", "EUR", 710, 2},
654 },
655 .fee = baseFee});
656 count += 1;
657 BEAST_EXPECT(ownerCount(env, owner) == count);
658
659 // update two pairs and delete four
660 oracle.set(UpdateArg{
661 .series = {{"BTC", "USD", std::nullopt, std::nullopt}},
662 .fee = baseFee});
663 oracle.set(UpdateArg{
664 .series =
665 {{"XRP", "USD", 742, 2},
666 {"XRP", "EUR", 711, 2},
667 {"ETH", "EUR", std::nullopt, std::nullopt},
668 {"YAN", "EUR", std::nullopt, std::nullopt},
669 {"CAN", "EUR", std::nullopt, std::nullopt}},
670 .fee = baseFee});
671 BEAST_EXPECT(oracle.expectPrice(
672 {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}}));
673 // owner count is decreased by 1 since the number of pairs is 2
674 count -= 1;
675 BEAST_EXPECT(ownerCount(env, owner) == count);
676 }
677
678 // Min reserve to create and update
679 {
680 Env env(*this);
681 auto const baseFee =
682 static_cast<int>(env.current()->fees().base.drops());
683 env.fund(
684 env.current()->fees().accountReserve(1) +
685 env.current()->fees().base * 2,
686 owner);
687 Oracle oracle(env, {.owner = owner, .fee = baseFee});
688 oracle.set(
689 UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee});
690 }
691
692 for (bool const withFixOrder : {false, true})
693 {
694 // Should be same order as creation
695 Env env(
696 *this,
697 withFixOrder ? testable_amendments()
698 : testable_amendments() - fixPriceOracleOrder);
699 auto const baseFee =
700 static_cast<int>(env.current()->fees().base.drops());
701
702 auto test = [&](Env& env, DataSeries const& series) {
703 env.fund(XRP(1'000), owner);
704 Oracle oracle(
705 env, {.owner = owner, .series = series, .fee = baseFee});
706 BEAST_EXPECT(oracle.exists());
707 auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
708 BEAST_EXPECT(
709 sle->getFieldArray(sfPriceDataSeries).size() ==
710 series.size());
711
712 auto const beforeQuoteAssetName1 =
713 sle->getFieldArray(sfPriceDataSeries)[0]
714 .getFieldCurrency(sfQuoteAsset)
715 .getText();
716 auto const beforeQuoteAssetName2 =
717 sle->getFieldArray(sfPriceDataSeries)[1]
718 .getFieldCurrency(sfQuoteAsset)
719 .getText();
720
721 oracle.set(UpdateArg{.series = series, .fee = baseFee});
722 sle = env.le(keylet::oracle(owner, oracle.documentID()));
723
724 auto const afterQuoteAssetName1 =
725 sle->getFieldArray(sfPriceDataSeries)[0]
726 .getFieldCurrency(sfQuoteAsset)
727 .getText();
728 auto const afterQuoteAssetName2 =
729 sle->getFieldArray(sfPriceDataSeries)[1]
730 .getFieldCurrency(sfQuoteAsset)
731 .getText();
732
733 if (env.current()->rules().enabled(fixPriceOracleOrder))
734 {
735 BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
736 BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
737 }
738 else
739 {
740 BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1);
741 BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2);
742 }
743 };
744 test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
745 }
746 }
747
748 void
750 {
751 testcase("Multisig");
752 using namespace jtx;
753 Oracle::setFee(100'000);
754
755 Env env(*this, features);
756 auto const baseFee =
757 static_cast<int>(env.current()->fees().base.drops());
758
759 Account const alice{"alice", KeyType::secp256k1};
760 Account const bogie{"bogie", KeyType::secp256k1};
761 Account const ed{"ed", KeyType::secp256k1};
762 Account const becky{"becky", KeyType::ed25519};
763 Account const zelda{"zelda", KeyType::secp256k1};
764 Account const bob{"bob", KeyType::secp256k1};
765 env.fund(XRP(10'000), alice, becky, zelda, ed, bob);
766
767 // alice uses a regular key with the master disabled.
768 Account const alie{"alie", KeyType::secp256k1};
769 env(regkey(alice, alie));
770 env(fset(alice, asfDisableMaster), sig(alice));
771
772 // Attach signers to alice.
773 env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}), sig(alie));
774 env.close();
775 // if multiSignReserve disabled then its 2 + 1 per signer
776 int const signerListOwners{features[featureMultiSignReserve] ? 1 : 5};
777 env.require(owners(alice, signerListOwners));
778
779 // Create
780 // Force close (true) and time advancement because the close time
781 // is no longer 0.
782 Oracle oracle(
783 env,
784 CreateArg{.owner = alice, .fee = baseFee, .close = true},
785 false);
786 oracle.set(CreateArg{
787 .msig = msig(becky), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
788 oracle.set(CreateArg{
789 .msig = msig(zelda), .fee = baseFee, .err = ter(tefBAD_SIGNATURE)});
790 oracle.set(CreateArg{.msig = msig(becky, bogie), .fee = baseFee});
791 BEAST_EXPECT(oracle.exists());
792
793 // Update
794 oracle.set(UpdateArg{
795 .series = {{"XRP", "USD", 740, 1}},
796 .msig = msig(becky),
797 .fee = baseFee,
798 .err = ter(tefBAD_QUORUM)});
799 oracle.set(UpdateArg{
800 .series = {{"XRP", "USD", 740, 1}},
801 .msig = msig(zelda),
802 .fee = baseFee,
803 .err = ter(tefBAD_SIGNATURE)});
804 oracle.set(UpdateArg{
805 .series = {{"XRP", "USD", 741, 1}},
806 .msig = msig(becky, bogie),
807 .fee = baseFee});
808 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 741, 1}}));
809 // remove the signer list
810 env(signers(alice, jtx::none), sig(alie));
811 env.close();
812 env.require(owners(alice, 1));
813 // create new signer list
814 env(signers(alice, 2, {{zelda, 1}, {bob, 1}, {ed, 2}}), sig(alie));
815 env.close();
816 // old list fails
817 oracle.set(UpdateArg{
818 .series = {{"XRP", "USD", 740, 1}},
819 .msig = msig(becky, bogie),
820 .fee = baseFee,
821 .err = ter(tefBAD_SIGNATURE)});
822 // updated list succeeds
823 oracle.set(UpdateArg{
824 .series = {{"XRP", "USD", 7412, 2}},
825 .msig = msig(zelda, bob),
826 .fee = baseFee});
827 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 7412, 2}}));
828 oracle.set(UpdateArg{
829 .series = {{"XRP", "USD", 74245, 3}},
830 .msig = msig(ed),
831 .fee = baseFee});
832 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 74245, 3}}));
833
834 // Remove
835 oracle.remove(
836 {.msig = msig(bob), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
837 oracle.remove(
838 {.msig = msig(becky),
839 .fee = baseFee,
840 .err = ter(tefBAD_SIGNATURE)});
841 oracle.remove({.msig = msig(ed), .fee = baseFee});
842 BEAST_EXPECT(!oracle.exists());
843 }
844
845 void
847 {
848 testcase("Amendment");
849 using namespace jtx;
850
851 auto const features = testable_amendments() - featurePriceOracle;
852 Account const owner("owner");
853 Env env(*this, features);
854 auto const baseFee =
855 static_cast<int>(env.current()->fees().base.drops());
856
857 env.fund(XRP(1'000), owner);
858 {
859 Oracle oracle(
860 env, {.owner = owner, .fee = baseFee, .err = ter(temDISABLED)});
861 }
862
863 {
864 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
865 oracle.remove({.fee = baseFee, .err = ter(temDISABLED)});
866 }
867 }
868
869public:
870 void
871 run() override
872 {
873 using namespace jtx;
874 auto const all = testable_amendments();
875 testInvalidSet();
876 testInvalidDelete();
877 testCreate(all);
878 testCreate(all - fixIncludeKeyletFields);
879 testDelete();
880 testUpdate();
881 testAmendment();
882 for (auto const& features :
883 {all,
884 all - featureMultiSignReserve - featureExpandedSignerList,
885 all - featureExpandedSignerList})
886 testMultisig(features);
887 }
888};
889
890BEAST_DEFINE_TESTSUITE(Oracle, app, ripple);
891
892} // namespace oracle
893
894} // namespace jtx
895
896} // namespace test
897
898} // 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