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