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 BEAST_EXPECT(oracle.expectLastUpdateTime(946694810));
417 };
418
419 {
420 // owner count is adjusted by 1
421 Env env(*this);
422 test(env, {{"XRP", "USD", 740, 1}}, 1);
423 }
424
425 {
426 // owner count is adjusted by 2
427 Env env(*this);
428 test(
429 env,
430 {{"XRP", "USD", 740, 1},
431 {"BTC", "USD", 740, 1},
432 {"ETH", "USD", 740, 1},
433 {"CAN", "USD", 740, 1},
434 {"YAN", "USD", 740, 1},
435 {"GBP", "USD", 740, 1}},
436 2);
437 }
438
439 {
440 // Different owner creates a new object
441 Env env(*this);
442 auto const baseFee =
443 static_cast<int>(env.current()->fees().base.drops());
444 Account const some("some");
445 env.fund(XRP(1'000), owner);
446 env.fund(XRP(1'000), some);
447 Oracle oracle(env, {.owner = owner, .fee = baseFee});
448 BEAST_EXPECT(oracle.exists());
449 oracle.set(CreateArg{
450 .owner = some,
451 .series = {{"912810RR9", "USD", 740, 1}},
452 .fee = baseFee});
453 BEAST_EXPECT(Oracle::exists(env, some, oracle.documentID()));
454 }
455 }
456
457 void
459 {
460 testcase("Invalid Delete");
461
462 using namespace jtx;
463 Env env(*this);
464 auto const baseFee =
465 static_cast<int>(env.current()->fees().base.drops());
466 Account const owner("owner");
467 env.fund(XRP(1'000), owner);
468 Oracle oracle(env, {.owner = owner, .fee = baseFee});
469 BEAST_EXPECT(oracle.exists());
470
471 {
472 // Invalid account
473 Account const bad("bad");
474 env.memoize(bad);
475 oracle.remove(
476 {.owner = bad,
477 .seq = seq(1),
478 .fee = baseFee,
479 .err = ter(terNO_ACCOUNT)});
480 }
481
482 // Invalid DocumentID
483 oracle.remove(
484 {.documentID = 2, .fee = baseFee, .err = ter(tecNO_ENTRY)});
485
486 // Invalid owner
487 Account const invalid("invalid");
488 env.fund(XRP(1'000), invalid);
489 oracle.remove(
490 {.owner = invalid, .fee = baseFee, .err = ter(tecNO_ENTRY)});
491
492 // Invalid flags
493 oracle.remove(
494 {.flags = tfSellNFToken,
495 .fee = baseFee,
496 .err = ter(temINVALID_FLAG)});
497
498 // Bad fee
499 oracle.remove({.fee = -1, .err = ter(temBAD_FEE)});
500 }
501
502 void
504 {
505 testcase("Delete");
506 using namespace jtx;
507 Account const owner("owner");
508
509 auto test = [&](Env& env, DataSeries const& series, std::uint16_t adj) {
510 auto const baseFee =
511 static_cast<int>(env.current()->fees().base.drops());
512 env.fund(XRP(1'000), owner);
513 Oracle oracle(
514 env, {.owner = owner, .series = series, .fee = baseFee});
515 auto const count = ownerCount(env, owner);
516 BEAST_EXPECT(oracle.exists());
517 oracle.remove({.fee = baseFee});
518 BEAST_EXPECT(!oracle.exists());
519 BEAST_EXPECT(ownerCount(env, owner) == (count - adj));
520 };
521
522 {
523 // owner count is adjusted by 1
524 Env env(*this);
525 test(env, {{"XRP", "USD", 740, 1}}, 1);
526 }
527
528 {
529 // owner count is adjusted by 2
530 Env env(*this);
531 test(
532 env,
533 {
534 {"XRP", "USD", 740, 1},
535 {"BTC", "USD", 740, 1},
536 {"ETH", "USD", 740, 1},
537 {"CAN", "USD", 740, 1},
538 {"YAN", "USD", 740, 1},
539 {"GBP", "USD", 740, 1},
540 },
541 2);
542 }
543
544 {
545 // deleting the account deletes the oracles
546 Env env(*this);
547 auto const baseFee =
548 static_cast<int>(env.current()->fees().base.drops());
549
550 auto const alice = Account("alice");
551 auto const acctDelFee{drops(env.current()->fees().increment)};
552 env.fund(XRP(1'000), owner);
553 env.fund(XRP(1'000), alice);
554 Oracle oracle(
555 env,
556 {.owner = owner,
557 .series = {{"XRP", "USD", 740, 1}},
558 .fee = baseFee});
559 Oracle oracle1(
560 env,
561 {.owner = owner,
562 .documentID = 2,
563 .series = {{"XRP", "EUR", 740, 1}},
564 .fee = baseFee});
565 BEAST_EXPECT(ownerCount(env, owner) == 2);
566 BEAST_EXPECT(oracle.exists());
567 BEAST_EXPECT(oracle1.exists());
568 auto const index = env.closed()->seq();
569 auto const hash = env.closed()->info().hash;
570 for (int i = 0; i < 256; ++i)
571 env.close();
572 env(acctdelete(owner, alice), fee(acctDelFee));
573 env.close();
574 BEAST_EXPECT(!oracle.exists());
575 BEAST_EXPECT(!oracle1.exists());
576
577 // can still get the oracles via the ledger index or hash
578 auto verifyLedgerData = [&](auto const& field, auto const& value) {
579 Json::Value jvParams;
580 jvParams[field] = value;
581 jvParams[jss::binary] = false;
582 jvParams[jss::type] = jss::oracle;
583 Json::Value jrr = env.rpc(
584 "json",
585 "ledger_data",
586 boost::lexical_cast<std::string>(jvParams));
587 BEAST_EXPECT(jrr[jss::result][jss::state].size() == 2);
588 };
589 verifyLedgerData(jss::ledger_index, index);
590 verifyLedgerData(jss::ledger_hash, to_string(hash));
591 }
592 }
593
594 void
596 {
597 testcase("Update");
598 using namespace jtx;
599 Account const owner("owner");
600
601 {
602 Env env(*this);
603 auto const baseFee =
604 static_cast<int>(env.current()->fees().base.drops());
605 env.fund(XRP(1'000), owner);
606 auto count = ownerCount(env, owner);
607 Oracle oracle(env, {.owner = owner, .fee = baseFee});
608 BEAST_EXPECT(oracle.exists());
609
610 // update existing pair
611 oracle.set(
612 UpdateArg{.series = {{"XRP", "USD", 740, 2}}, .fee = baseFee});
613 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 740, 2}}));
614 // owner count is increased by 1 since the oracle object is added
615 // with one token pair
616 count += 1;
617 BEAST_EXPECT(ownerCount(env, owner) == count);
618
619 // add new pairs, not-included pair is reset
620 oracle.set(
621 UpdateArg{.series = {{"XRP", "EUR", 700, 2}}, .fee = baseFee});
622 BEAST_EXPECT(oracle.expectPrice(
623 {{"XRP", "USD", 0, 0}, {"XRP", "EUR", 700, 2}}));
624 // owner count is not changed since the number of pairs is 2
625 BEAST_EXPECT(ownerCount(env, owner) == count);
626
627 // update both pairs
628 oracle.set(UpdateArg{
629 .series = {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}},
630 .fee = baseFee});
631 BEAST_EXPECT(oracle.expectPrice(
632 {{"XRP", "USD", 741, 2}, {"XRP", "EUR", 710, 2}}));
633 // owner count is not changed since the number of pairs is 2
634 BEAST_EXPECT(ownerCount(env, owner) == count);
635
636 // owner count is increased by 1 since the number of pairs is 6
637 oracle.set(UpdateArg{
638 .series =
639 {
640 {"BTC", "USD", 741, 2},
641 {"ETH", "EUR", 710, 2},
642 {"YAN", "EUR", 710, 2},
643 {"CAN", "EUR", 710, 2},
644 },
645 .fee = baseFee});
646 count += 1;
647 BEAST_EXPECT(ownerCount(env, owner) == count);
648
649 // update two pairs and delete four
650 oracle.set(UpdateArg{
651 .series = {{"BTC", "USD", std::nullopt, std::nullopt}},
652 .fee = baseFee});
653 oracle.set(UpdateArg{
654 .series =
655 {{"XRP", "USD", 742, 2},
656 {"XRP", "EUR", 711, 2},
657 {"ETH", "EUR", std::nullopt, std::nullopt},
658 {"YAN", "EUR", std::nullopt, std::nullopt},
659 {"CAN", "EUR", std::nullopt, std::nullopt}},
660 .fee = baseFee});
661 BEAST_EXPECT(oracle.expectPrice(
662 {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}}));
663 // owner count is decreased by 1 since the number of pairs is 2
664 count -= 1;
665 BEAST_EXPECT(ownerCount(env, owner) == count);
666 }
667
668 // Min reserve to create and update
669 {
670 Env env(*this);
671 auto const baseFee =
672 static_cast<int>(env.current()->fees().base.drops());
673 env.fund(
674 env.current()->fees().accountReserve(1) +
675 env.current()->fees().base * 2,
676 owner);
677 Oracle oracle(env, {.owner = owner, .fee = baseFee});
678 oracle.set(
679 UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee});
680 }
681
682 for (bool const withFixOrder : {false, true})
683 {
684 // Should be same order as creation
685 Env env(
686 *this,
687 withFixOrder ? testable_amendments()
688 : testable_amendments() - fixPriceOracleOrder);
689 auto const baseFee =
690 static_cast<int>(env.current()->fees().base.drops());
691
692 auto test = [&](Env& env, DataSeries const& series) {
693 env.fund(XRP(1'000), owner);
694 Oracle oracle(
695 env, {.owner = owner, .series = series, .fee = baseFee});
696 BEAST_EXPECT(oracle.exists());
697 auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
698 BEAST_EXPECT(
699 sle->getFieldArray(sfPriceDataSeries).size() ==
700 series.size());
701
702 auto const beforeQuoteAssetName1 =
703 sle->getFieldArray(sfPriceDataSeries)[0]
704 .getFieldCurrency(sfQuoteAsset)
705 .getText();
706 auto const beforeQuoteAssetName2 =
707 sle->getFieldArray(sfPriceDataSeries)[1]
708 .getFieldCurrency(sfQuoteAsset)
709 .getText();
710
711 oracle.set(UpdateArg{.series = series, .fee = baseFee});
712 sle = env.le(keylet::oracle(owner, oracle.documentID()));
713
714 auto const afterQuoteAssetName1 =
715 sle->getFieldArray(sfPriceDataSeries)[0]
716 .getFieldCurrency(sfQuoteAsset)
717 .getText();
718 auto const afterQuoteAssetName2 =
719 sle->getFieldArray(sfPriceDataSeries)[1]
720 .getFieldCurrency(sfQuoteAsset)
721 .getText();
722
723 if (env.current()->rules().enabled(fixPriceOracleOrder))
724 {
725 BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
726 BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
727 }
728 else
729 {
730 BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1);
731 BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2);
732 }
733 };
734 test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
735 }
736 }
737
738 void
740 {
741 testcase("Multisig");
742 using namespace jtx;
743 Oracle::setFee(100'000);
744
745 Env env(*this, features);
746 auto const baseFee =
747 static_cast<int>(env.current()->fees().base.drops());
748
749 Account const alice{"alice", KeyType::secp256k1};
750 Account const bogie{"bogie", KeyType::secp256k1};
751 Account const ed{"ed", KeyType::secp256k1};
752 Account const becky{"becky", KeyType::ed25519};
753 Account const zelda{"zelda", KeyType::secp256k1};
754 Account const bob{"bob", KeyType::secp256k1};
755 env.fund(XRP(10'000), alice, becky, zelda, ed, bob);
756
757 // alice uses a regular key with the master disabled.
758 Account const alie{"alie", KeyType::secp256k1};
759 env(regkey(alice, alie));
760 env(fset(alice, asfDisableMaster), sig(alice));
761
762 // Attach signers to alice.
763 env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}), sig(alie));
764 env.close();
765 // if multiSignReserve disabled then its 2 + 1 per signer
766 int const signerListOwners{features[featureMultiSignReserve] ? 1 : 5};
767 env.require(owners(alice, signerListOwners));
768
769 // Create
770 // Force close (true) and time advancement because the close time
771 // is no longer 0.
772 Oracle oracle(
773 env,
774 CreateArg{.owner = alice, .fee = baseFee, .close = true},
775 false);
776 oracle.set(CreateArg{
777 .msig = msig(becky), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
778 oracle.set(CreateArg{
779 .msig = msig(zelda), .fee = baseFee, .err = ter(tefBAD_SIGNATURE)});
780 oracle.set(CreateArg{.msig = msig(becky, bogie), .fee = baseFee});
781 BEAST_EXPECT(oracle.exists());
782
783 // Update
784 oracle.set(UpdateArg{
785 .series = {{"XRP", "USD", 740, 1}},
786 .msig = msig(becky),
787 .fee = baseFee,
788 .err = ter(tefBAD_QUORUM)});
789 oracle.set(UpdateArg{
790 .series = {{"XRP", "USD", 740, 1}},
791 .msig = msig(zelda),
792 .fee = baseFee,
793 .err = ter(tefBAD_SIGNATURE)});
794 oracle.set(UpdateArg{
795 .series = {{"XRP", "USD", 741, 1}},
796 .msig = msig(becky, bogie),
797 .fee = baseFee});
798 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 741, 1}}));
799 // remove the signer list
800 env(signers(alice, jtx::none), sig(alie));
801 env.close();
802 env.require(owners(alice, 1));
803 // create new signer list
804 env(signers(alice, 2, {{zelda, 1}, {bob, 1}, {ed, 2}}), sig(alie));
805 env.close();
806 // old list fails
807 oracle.set(UpdateArg{
808 .series = {{"XRP", "USD", 740, 1}},
809 .msig = msig(becky, bogie),
810 .fee = baseFee,
811 .err = ter(tefBAD_SIGNATURE)});
812 // updated list succeeds
813 oracle.set(UpdateArg{
814 .series = {{"XRP", "USD", 7412, 2}},
815 .msig = msig(zelda, bob),
816 .fee = baseFee});
817 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 7412, 2}}));
818 oracle.set(UpdateArg{
819 .series = {{"XRP", "USD", 74245, 3}},
820 .msig = msig(ed),
821 .fee = baseFee});
822 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 74245, 3}}));
823
824 // Remove
825 oracle.remove(
826 {.msig = msig(bob), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
827 oracle.remove(
828 {.msig = msig(becky),
829 .fee = baseFee,
830 .err = ter(tefBAD_SIGNATURE)});
831 oracle.remove({.msig = msig(ed), .fee = baseFee});
832 BEAST_EXPECT(!oracle.exists());
833 }
834
835 void
837 {
838 testcase("Amendment");
839 using namespace jtx;
840
841 auto const features = testable_amendments() - featurePriceOracle;
842 Account const owner("owner");
843 Env env(*this, features);
844 auto const baseFee =
845 static_cast<int>(env.current()->fees().base.drops());
846
847 env.fund(XRP(1'000), owner);
848 {
849 Oracle oracle(
850 env, {.owner = owner, .fee = baseFee, .err = ter(temDISABLED)});
851 }
852
853 {
854 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
855 oracle.remove({.fee = baseFee, .err = ter(temDISABLED)});
856 }
857 }
858
859public:
860 void
861 run() override
862 {
863 using namespace jtx;
864 auto const all = testable_amendments();
865 testInvalidSet();
866 testInvalidDelete();
867 testCreate();
868 testDelete();
869 testUpdate();
870 testAmendment();
871 for (auto const& features :
872 {all,
873 all - featureMultiSignReserve - featureExpandedSignerList,
874 all - featureExpandedSignerList})
875 testMultisig(features);
876 }
877};
878
879BEAST_DEFINE_TESTSUITE(Oracle, app, ripple);
880
881} // namespace oracle
882
883} // namespace jtx
884
885} // namespace test
886
887} // 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
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:544
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:788
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:279
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:105
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:162
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:194
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 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