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{
187 .assetClass = std::nullopt,
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
683 void
685 {
686 testcase("Multisig");
687 using namespace jtx;
688 Oracle::setFee(100'000);
689
690 Env env(*this, features);
691 auto const baseFee =
692 static_cast<int>(env.current()->fees().base.drops());
693
694 Account const alice{"alice", KeyType::secp256k1};
695 Account const bogie{"bogie", KeyType::secp256k1};
696 Account const ed{"ed", KeyType::secp256k1};
697 Account const becky{"becky", KeyType::ed25519};
698 Account const zelda{"zelda", KeyType::secp256k1};
699 Account const bob{"bob", KeyType::secp256k1};
700 env.fund(XRP(10'000), alice, becky, zelda, ed, bob);
701
702 // alice uses a regular key with the master disabled.
703 Account const alie{"alie", KeyType::secp256k1};
704 env(regkey(alice, alie));
705 env(fset(alice, asfDisableMaster), sig(alice));
706
707 // Attach signers to alice.
708 env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}), sig(alie));
709 env.close();
710 // if multiSignReserve disabled then its 2 + 1 per signer
711 int const signerListOwners{features[featureMultiSignReserve] ? 1 : 5};
712 env.require(owners(alice, signerListOwners));
713
714 // Create
715 // Force close (true) and time advancement because the close time
716 // is no longer 0.
717 Oracle oracle(
718 env,
719 CreateArg{.owner = alice, .fee = baseFee, .close = true},
720 false);
721 oracle.set(CreateArg{
722 .msig = msig(becky), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
723 oracle.set(CreateArg{
724 .msig = msig(zelda), .fee = baseFee, .err = ter(tefBAD_SIGNATURE)});
725 oracle.set(CreateArg{.msig = msig(becky, bogie), .fee = baseFee});
726 BEAST_EXPECT(oracle.exists());
727
728 // Update
729 oracle.set(UpdateArg{
730 .series = {{"XRP", "USD", 740, 1}},
731 .msig = msig(becky),
732 .fee = baseFee,
733 .err = ter(tefBAD_QUORUM)});
734 oracle.set(UpdateArg{
735 .series = {{"XRP", "USD", 740, 1}},
736 .msig = msig(zelda),
737 .fee = baseFee,
738 .err = ter(tefBAD_SIGNATURE)});
739 oracle.set(UpdateArg{
740 .series = {{"XRP", "USD", 741, 1}},
741 .msig = msig(becky, bogie),
742 .fee = baseFee});
743 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 741, 1}}));
744 // remove the signer list
745 env(signers(alice, jtx::none), sig(alie));
746 env.close();
747 env.require(owners(alice, 1));
748 // create new signer list
749 env(signers(alice, 2, {{zelda, 1}, {bob, 1}, {ed, 2}}), sig(alie));
750 env.close();
751 // old list fails
752 oracle.set(UpdateArg{
753 .series = {{"XRP", "USD", 740, 1}},
754 .msig = msig(becky, bogie),
755 .fee = baseFee,
756 .err = ter(tefBAD_SIGNATURE)});
757 // updated list succeeds
758 oracle.set(UpdateArg{
759 .series = {{"XRP", "USD", 7412, 2}},
760 .msig = msig(zelda, bob),
761 .fee = baseFee});
762 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 7412, 2}}));
763 oracle.set(UpdateArg{
764 .series = {{"XRP", "USD", 74245, 3}},
765 .msig = msig(ed),
766 .fee = baseFee});
767 BEAST_EXPECT(oracle.expectPrice({{"XRP", "USD", 74245, 3}}));
768
769 // Remove
770 oracle.remove(
771 {.msig = msig(bob), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
772 oracle.remove(
773 {.msig = msig(becky),
774 .fee = baseFee,
775 .err = ter(tefBAD_SIGNATURE)});
776 oracle.remove({.msig = msig(ed), .fee = baseFee});
777 BEAST_EXPECT(!oracle.exists());
778 }
779
780 void
782 {
783 testcase("Amendment");
784 using namespace jtx;
785
786 auto const features = supported_amendments() - featurePriceOracle;
787 Account const owner("owner");
788 Env env(*this, features);
789 auto const baseFee =
790 static_cast<int>(env.current()->fees().base.drops());
791
792 env.fund(XRP(1'000), owner);
793 {
794 Oracle oracle(
795 env, {.owner = owner, .fee = baseFee, .err = ter(temDISABLED)});
796 }
797
798 {
799 Oracle oracle(env, {.owner = owner, .fee = baseFee}, false);
800 oracle.remove({.fee = baseFee, .err = ter(temDISABLED)});
801 }
802 }
803
804public:
805 void
806 run() override
807 {
808 using namespace jtx;
809 auto const all = supported_amendments();
810 testInvalidSet();
811 testInvalidDelete();
812 testCreate();
813 testDelete();
814 testUpdate();
815 testAmendment();
816 for (auto const& features :
817 {all,
818 all - featureMultiSignReserve - featureExpandedSignerList,
819 all - featureExpandedSignerList})
820 testMultisig(features);
821 }
822};
823
824BEAST_DEFINE_TESTSUITE(Oracle, app, ripple);
825
826} // namespace oracle
827
828} // namespace jtx
829
830} // namespace test
831
832} // 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:111
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:117
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:275
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:152
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)
Keylet oracle(AccountID const &account, std::uint32_t const &documentID) noexcept
Definition: Indexes.cpp:520
static constexpr std::chrono::seconds testStartTime
Definition: Oracle.h:112
std::uint32_t ownerCount(Env const &env, Account const &account)
Definition: TestHelpers.cpp:54
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
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
Definition: acctdelete.cpp:31
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
FeatureBitset supported_amendments()
Definition: Env.h:74
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