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