20#include <test/jtx/Oracle.h>
22#include <xrpl/protocol/jss.h>
49 .fee =
static_cast<int>(env.
current()->fees().base.drops()),
56 env.
fund(env.
current()->fees().accountReserve(0), owner);
60 .fee =
static_cast<int>(env.
current()->fees().base.drops()),
67 env.
current()->fees().accountReserve(1) +
73 .fee =
static_cast<int>(env.
current()->fees().base.drops())});
74 BEAST_EXPECT(oracle.exists());
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},
84 .fee =
static_cast<int>(env.
current()->fees().base.drops()),
91 static_cast<int>(env.
current()->fees().base.drops());
93 Oracle oracle(env, {.owner = owner, .fee = baseFee},
false);
103 .
series = {{
"XRP",
"USD", 740, 1}, {
"XRP",
"USD", 750, 1}},
110 {{
"XRP",
"USD", 740, 1}, {
"XRP",
"EUR",
std::nullopt, 1}},
117 {{
"XRP",
"USD", 740, 1}, {
"XRP",
"USD",
std::nullopt, 1}},
123 {{
"XRP",
"EUR", 740, 1}, {
"XRP",
"EUR",
std::nullopt, 1}},
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}},
151 static_cast<int>(env.current()->fees().base.drops());
158 .series = {{{
"XRP",
"USD", 740, 1}}},
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},
181 static_cast<int>(env.
current()->fees().base.drops());
183 Oracle oracle(env, {.owner = owner, .fee = baseFee},
false);
188 .provider =
"provider",
201 .
fee =
static_cast<int>(env.
current()->fees().base.drops())});
202 BEAST_EXPECT(oracle.exists());
204 .
series = {{
"XRP",
"USD", 740, 1}},
205 .provider =
"provider1",
209 .
series = {{
"XRP",
"USD", 740, 1}},
210 .assetClass =
"currency1",
218 static_cast<int>(env.
current()->fees().base.drops());
220 Oracle oracle(env, {.owner = owner, .fee = baseFee},
false);
253 static_cast<int>(env.
current()->fees().base.drops());
257 Oracle oracle(env, {.owner = owner, .fee = baseFee});
258 BEAST_EXPECT(oracle.exists());
261 .series = {{
"XRP",
"USD", 740, 1}},
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() -
279 Oracle oracle(env, {.owner = owner, .fee = baseFee});
280 BEAST_EXPECT(oracle.exists());
284 .
series = {{
"XRP",
"USD", 740, 1}},
285 .lastUpdateTime =
static_cast<std::uint32_t>(closeTime() - 301),
290 .
series = {{
"XRP",
"USD", 740, 1}},
291 .lastUpdateTime =
static_cast<std::uint32_t>(closeTime() + 311),
296 BEAST_EXPECT(oracle.expectLastUpdateTime(
300 .
series = {{
"XRP",
"USD", 740, 1}},
306 .
series = {{
"XRP",
"USD", 740, 1}},
316 static_cast<int>(env.
current()->fees().base.drops());
318 Oracle oracle(env, {.owner = owner, .fee = baseFee});
319 BEAST_EXPECT(oracle.exists());
335 static_cast<int>(env.
current()->fees().base.drops());
340 .series = {{
"USD",
"USD", 740, 1}},
349 static_cast<int>(env.
current()->fees().base.drops());
363 static_cast<int>(env.
current()->fees().base.drops());
365 Oracle oracle(env, {.owner = owner, .fee = baseFee});
369 {
"XRP",
"EUR", 740, 1}},
394 .fee =
static_cast<int>(env.
current()->fees().base.drops())});
409 static_cast<int>(env.
current()->fees().base.drops());
413 env, {.owner = owner, .series = series, .fee = baseFee});
414 BEAST_EXPECT(oracle.exists());
415 BEAST_EXPECT(
ownerCount(env, owner) == (count + adj));
416 auto const entry = oracle.ledgerEntry();
417 BEAST_EXPECT(entry[jss::node][jss::Owner] == owner.
human());
418 if (features[fixIncludeKeyletFields])
421 entry[jss::node][jss::OracleDocumentID] ==
422 oracle.documentID());
426 BEAST_EXPECT(!entry[jss::node].isMember(jss::OracleDocumentID));
428 BEAST_EXPECT(oracle.expectLastUpdateTime(946694810));
433 Env env(*
this, features);
434 test(env, {{
"XRP",
"USD", 740, 1}}, 1);
439 Env env(*
this, features);
442 {{
"XRP",
"USD", 740, 1},
443 {
"BTC",
"USD", 740, 1},
444 {
"ETH",
"USD", 740, 1},
445 {
"CAN",
"USD", 740, 1},
446 {
"YAN",
"USD", 740, 1},
447 {
"GBP",
"USD", 740, 1}},
453 Env env(*
this, features);
455 static_cast<int>(env.
current()->fees().base.drops());
459 Oracle oracle(env, {.owner = owner, .fee = baseFee});
460 BEAST_EXPECT(oracle.exists());
463 .series = {{
"912810RR9",
"USD", 740, 1}},
477 static_cast<int>(env.
current()->fees().base.drops());
480 Oracle oracle(env, {.owner = owner, .fee = baseFee});
481 BEAST_EXPECT(oracle.exists());
523 static_cast<int>(env.
current()->fees().base.drops());
526 env, {.owner = owner, .series = series, .fee = baseFee});
528 BEAST_EXPECT(oracle.exists());
529 oracle.remove({.fee = baseFee});
530 BEAST_EXPECT(!oracle.exists());
531 BEAST_EXPECT(
ownerCount(env, owner) == (count - adj));
537 test(env, {{
"XRP",
"USD", 740, 1}}, 1);
546 {
"XRP",
"USD", 740, 1},
547 {
"BTC",
"USD", 740, 1},
548 {
"ETH",
"USD", 740, 1},
549 {
"CAN",
"USD", 740, 1},
550 {
"YAN",
"USD", 740, 1},
551 {
"GBP",
"USD", 740, 1},
560 static_cast<int>(env.
current()->fees().base.drops());
562 auto const alice =
Account(
"alice");
563 auto const acctDelFee{
drops(env.
current()->fees().increment)};
569 .series = {{
"XRP",
"USD", 740, 1}},
575 .series = {{
"XRP",
"EUR", 740, 1}},
578 BEAST_EXPECT(oracle.exists());
579 BEAST_EXPECT(oracle1.exists());
580 auto const index = env.
closed()->seq();
582 for (
int i = 0; i < 256; ++i)
586 BEAST_EXPECT(!oracle.exists());
587 BEAST_EXPECT(!oracle1.exists());
590 auto verifyLedgerData = [&](
auto const& field,
auto const& value) {
592 jvParams[field] = value;
593 jvParams[jss::binary] =
false;
594 jvParams[jss::type] = jss::oracle;
598 boost::lexical_cast<std::string>(jvParams));
599 BEAST_EXPECT(jrr[jss::result][jss::state].size() == 2);
601 verifyLedgerData(jss::ledger_index, index);
616 static_cast<int>(env.
current()->fees().base.drops());
619 Oracle oracle(env, {.owner = owner, .fee = baseFee});
620 BEAST_EXPECT(oracle.exists());
625 BEAST_EXPECT(oracle.expectPrice({{
"XRP",
"USD", 740, 2}}));
629 BEAST_EXPECT(
ownerCount(env, owner) == count);
634 BEAST_EXPECT(oracle.expectPrice(
635 {{
"XRP",
"USD", 0, 0}, {
"XRP",
"EUR", 700, 2}}));
637 BEAST_EXPECT(
ownerCount(env, owner) == count);
641 .
series = {{
"XRP",
"USD", 741, 2}, {
"XRP",
"EUR", 710, 2}},
643 BEAST_EXPECT(oracle.expectPrice(
644 {{
"XRP",
"USD", 741, 2}, {
"XRP",
"EUR", 710, 2}}));
646 BEAST_EXPECT(
ownerCount(env, owner) == count);
652 {
"BTC",
"USD", 741, 2},
653 {
"ETH",
"EUR", 710, 2},
654 {
"YAN",
"EUR", 710, 2},
655 {
"CAN",
"EUR", 710, 2},
659 BEAST_EXPECT(
ownerCount(env, owner) == count);
667 {{
"XRP",
"USD", 742, 2},
668 {
"XRP",
"EUR", 711, 2},
673 BEAST_EXPECT(
oracle.expectPrice(
674 {{
"XRP",
"USD", 742, 2}, {
"XRP",
"EUR", 711, 2}}));
677 BEAST_EXPECT(
ownerCount(env, owner) == count);
684 static_cast<int>(env.current()->fees().base.drops());
686 env.current()->fees().accountReserve(1) +
687 env.current()->fees().base * 2,
689 Oracle
oracle(env, {.owner = owner, .fee = baseFee});
691 UpdateArg{.series = {{
"XRP",
"USD", 742, 2}}, .fee = baseFee});
694 for (
bool const withFixOrder : {
false,
true})
702 static_cast<int>(env.current()->fees().base.drops());
704 auto test = [&](Env& env,
DataSeries const& series) {
705 env.fund(
XRP(1'000), owner);
707 env, {.owner = owner, .series = series, .fee = baseFee});
708 BEAST_EXPECT(
oracle.exists());
711 sle->getFieldArray(sfPriceDataSeries).size() ==
714 auto const beforeQuoteAssetName1 =
715 sle->getFieldArray(sfPriceDataSeries)[0]
716 .getFieldCurrency(sfQuoteAsset)
718 auto const beforeQuoteAssetName2 =
719 sle->getFieldArray(sfPriceDataSeries)[1]
720 .getFieldCurrency(sfQuoteAsset)
723 oracle.set(UpdateArg{.series = series, .fee = baseFee});
726 auto const afterQuoteAssetName1 =
727 sle->getFieldArray(sfPriceDataSeries)[0]
728 .getFieldCurrency(sfQuoteAsset)
730 auto const afterQuoteAssetName2 =
731 sle->getFieldArray(sfPriceDataSeries)[1]
732 .getFieldCurrency(sfQuoteAsset)
735 if (env.current()->rules().enabled(fixPriceOracleOrder))
737 BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
738 BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
742 BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1);
743 BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2);
746 test(env, {{
"XRP",
"USD", 742, 2}, {
"XRP",
"EUR", 711, 2}});
753 testcase(
"Multisig");
755 Oracle::setFee(100'000);
757 Env env(*
this, features);
759 static_cast<int>(env.
current()->fees().base.drops());
767 env.
fund(
XRP(10'000), alice, becky, zelda, ed, bob);
775 env(signers(alice, 2, {{becky, 1}, {bogie, 1}, {ed, 2}}),
sig(alie));
778 int const signerListOwners{features[featureMultiSignReserve] ? 1 : 5};
793 BEAST_EXPECT(oracle.exists());
797 .
series = {{
"XRP",
"USD", 740, 1}},
802 .
series = {{
"XRP",
"USD", 740, 1}},
807 .
series = {{
"XRP",
"USD", 741, 1}},
808 .msig =
msig(becky, bogie),
810 BEAST_EXPECT(oracle.expectPrice({{
"XRP",
"USD", 741, 1}}));
812 env(signers(alice, jtx::none),
sig(alie));
814 env.require(
owners(alice, 1));
816 env(signers(alice, 2, {{zelda, 1}, {bob, 1}, {ed, 2}}), sig(alie));
819 oracle.set(UpdateArg{
820 .series = {{
"XRP",
"USD", 740, 1}},
821 .msig = msig(becky, bogie),
826 .series = {{
"XRP",
"USD", 7412, 2}},
827 .msig = msig(zelda, bob),
829 BEAST_EXPECT(
oracle.expectPrice({{
"XRP",
"USD", 7412, 2}}));
831 .series = {{
"XRP",
"USD", 74245, 3}},
834 BEAST_EXPECT(
oracle.expectPrice({{
"XRP",
"USD", 74245, 3}}));
838 {.msig = msig(bob), .fee = baseFee, .err = ter(tefBAD_QUORUM)});
840 {.msig = msig(becky),
842 .err = ter(tefBAD_SIGNATURE)});
843 oracle.remove({.msig = msig(ed), .fee = baseFee});
844 BEAST_EXPECT(!
oracle.exists());
850 testcase(
"Amendment");
855 Env env(*
this, features);
857 static_cast<int>(env.
current()->fees().base.drops());
866 Oracle oracle(env, {.owner = owner, .fee = baseFee},
false);
880 testCreate(all - fixIncludeKeyletFields);
884 for (
auto const& features :
886 all - featureMultiSignReserve - featureExpandedSignerList,
887 all - featureExpandedSignerList})
888 testMultisig(features);
testcase_t testcase
Memberspace for declaring test cases.
Immutable cryptographic account descriptor.
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
void require(Args const &... args)
Check a set of requirements.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
void memoize(Account const &account)
Associate AccountID with account.
Set a multisignature on a JTx.
Oracle class facilitates unit-testing of the Price Oracle feature.
Match the number of items in the account's owner directory.
Set the regular signature on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Keylet oracle(AccountID const &account, std::uint32_t const &documentID) noexcept
std::vector< std::tuple< std::string, std::string, std::optional< std::uint32_t >, std::optional< std::uint8_t > > > DataSeries
static constexpr std::chrono::seconds testStartTime
std::uint32_t ownerCount(Env const &env, Account const &account)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
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.
FeatureBitset testable_amendments()
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::size_t constexpr maxPriceScale
The maximum price scaling factor.
constexpr std::uint32_t const tfSellNFToken
constexpr std::uint32_t asfDisableMaster
@ invalid
Timely, but invalid signature.
@ tecINSUFFICIENT_RESERVE
@ tecTOKEN_PAIR_NOT_FOUND
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
std::string to_string(base_uint< Bits, Tag > const &a)
std::optional< AnyValue > uri
std::optional< AnyValue > assetClass
std::optional< AccountID > owner
std::optional< jtx::msig > msig
std::optional< AnyValue > provider
void run() override
Runs the suite.
void testCreate(FeatureBitset features)
void testMultisig(FeatureBitset features)
std::optional< AnyValue > documentID
std::optional< AccountID > owner
Set the sequence number on a JTx.