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