rippled
Loading...
Searching...
No Matches
GetAggregatePrice_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/Oracle.h>
3
4#include <xrpld/app/ledger/LedgerMaster.h>
5
6#include <xrpl/protocol/jss.h>
7
8namespace xrpl {
9namespace test {
10namespace jtx {
11namespace oracle {
12
14{
15public:
16 void
18 {
19 testcase("Errors");
20 using namespace jtx;
21 Account const owner{"owner"};
22 Account const some{"some"};
23 static OraclesData oracles = {{owner, 1}};
24
25 {
26 Env env(*this);
27 auto const baseFee = env.current()->fees().base;
28 // missing base_asset
29 auto ret = Oracle::aggregatePrice(env, std::nullopt, "USD", oracles);
30 BEAST_EXPECT(ret[jss::error_message].asString() == "Missing field 'base_asset'.");
31
32 // missing quote_asset
33 ret = Oracle::aggregatePrice(env, "XRP", std::nullopt, oracles);
34 BEAST_EXPECT(ret[jss::error_message].asString() == "Missing field 'quote_asset'.");
35
36 // invalid base_asset, quote_asset
37 std::vector<AnyValue> invalidAsset = {
38 NoneTag,
39 1,
40 -1,
41 1.2,
42 "",
43 "invalid",
44 "a",
45 "ab",
46 "A",
47 "AB",
48 "ABCD",
49 "010101",
50 "012345678901234567890123456789012345678",
51 "012345678901234567890123456789012345678G"};
52 for (auto const& v : invalidAsset)
53 {
54 ret = Oracle::aggregatePrice(env, "USD", v, oracles);
55 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
56 ret = Oracle::aggregatePrice(env, v, "USD", oracles);
57 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
58 ret = Oracle::aggregatePrice(env, v, v, oracles);
59 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
60 }
61
62 // missing oracles array
63 ret = Oracle::aggregatePrice(env, "XRP", "USD");
64 BEAST_EXPECT(ret[jss::error_message].asString() == "Missing field 'oracles'.");
65
66 // empty oracles array
67 ret = Oracle::aggregatePrice(env, "XRP", "USD", OraclesData{});
68 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
69
70 // no token pairs found
71 ret = Oracle::aggregatePrice(env, "YAN", "USD", oracles);
72 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
73
74 // invalid oracle document id
75 // id doesn't exist
76 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, 2}}});
77 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
78 // invalid values
79 std::vector<AnyValue> invalidDocument = {NoneTag, 1.2, -1, "", "none", "1.2"};
80 for (auto const& v : invalidDocument)
81 {
82 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, v}}});
83 Json::Value jv;
84 toJson(jv, v);
85 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
86 }
87 // missing document id
88 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, std::nullopt}}});
89 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
90
91 // invalid owner
92 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{some, 1}}});
93 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
94 // missing account
95 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{std::nullopt, 1}}});
96 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
97
98 // oracles have wrong asset pair
99 env.fund(XRP(1'000), owner);
100 Oracle oracle(
101 env, {.owner = owner, .series = {{"XRP", "EUR", 740, 1}}, .fee = static_cast<int>(baseFee.drops())});
102 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, oracle.documentID()}}});
103 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
104
105 // invalid trim value
106 std::vector<AnyValue> invalidTrim = {NoneTag, 0, 26, -1, 1.2, "", "none", "1.2"};
107 for (auto const& v : invalidTrim)
108 {
109 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, oracle.documentID()}}}, v);
110 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
111 }
112
113 // invalid time threshold value
114 std::vector<AnyValue> invalidTime = {NoneTag, -1, 1.2, "", "none", "1.2"};
115 for (auto const& v : invalidTime)
116 {
117 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, oracle.documentID()}}}, std::nullopt, v);
118 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
119 }
120 }
121
122 // too many oracles
123 {
124 Env env(*this);
125 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
126
127 OraclesData oracles;
128 for (int i = 0; i < 201; ++i)
129 {
130 Account const owner(std::to_string(i));
131 env.fund(XRP(1'000), owner);
132 Oracle oracle(env, {.owner = owner, .documentID = i, .fee = baseFee});
133 oracles.emplace_back(owner, oracle.documentID());
134 }
135 auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
136 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
137 }
138 }
139
140 void
142 {
143 testcase("RPC");
144 using namespace jtx;
145
146 auto prep = [&](Env& env, auto& oracles) {
147 oracles.reserve(10);
148 for (int i = 0; i < 10; ++i)
149 {
150 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
151
152 Account const owner{std::to_string(i)};
153 env.fund(XRP(1'000), owner);
154 Oracle oracle(
155 env,
156 {.owner = owner,
157 .documentID = rand(),
158 .series = {{"XRP", "USD", 740 + i, 1}, {"XRP", "EUR", 740, 1}},
159 .fee = baseFee});
160 oracles.emplace_back(owner, oracle.documentID());
161 }
162 };
163
164 // Aggregate data set includes all price oracle instances, no trimming
165 // or time threshold
166 {
167 auto const all = testable_amendments();
168 for (auto const& feats : {all - featureSingleAssetVault - featureLendingProtocol, all})
169 {
170 for (auto const mantissaSize : {MantissaRange::small, MantissaRange::large})
171 {
172 // Regardless of the features enabled, RPC is controlled by
173 // the global mantissa size. And since it's a thread-local,
174 // overriding it locally won't make a difference either.
175 // This will mean all RPC will use the default of "large".
176 NumberMantissaScaleGuard mg(mantissaSize);
177
178 Env env(*this, feats);
179 OraclesData oracles;
180 prep(env, oracles);
181 // entire and trimmed stats
182 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
183 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45");
184 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10);
185 // Short: 0.3027650354097492
186 BEAST_EXPECTS(
187 ret[jss::entire_set][jss::standard_deviation] == "0.3027650354097491666",
188 ret[jss::entire_set][jss::standard_deviation].asString());
189 BEAST_EXPECT(ret[jss::median] == "74.45");
190 BEAST_EXPECT(ret[jss::time] == 946694900);
191 }
192 }
193 }
194
195 // Aggregate data set includes all price oracle instances
196 {
197 Env env(*this);
198 OraclesData oracles;
199 prep(env, oracles);
200 // entire and trimmed stats
201 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, 100);
202 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45");
203 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10);
204 // Short: "0.3027650354097492",
205 BEAST_EXPECTS(
206 ret[jss::entire_set][jss::standard_deviation] == "0.3027650354097491666",
207 ret[jss::entire_set][jss::standard_deviation].asString());
208 BEAST_EXPECT(ret[jss::median] == "74.45");
209 BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.45");
210 BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 6);
211 // Short: "0.187082869338697",
212 BEAST_EXPECTS(
213 ret[jss::trimmed_set][jss::standard_deviation] == "0.1870828693386970693",
214 ret[jss::trimmed_set][jss::standard_deviation].asString());
215 BEAST_EXPECT(ret[jss::time] == 946694900);
216 }
217
218 // A reduced dataset, as some price oracles have data beyond three
219 // updated ledgers
220 {
221 Env env(*this);
222 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
223
224 OraclesData oracles;
225 prep(env, oracles);
226 for (int i = 0; i < 3; ++i)
227 {
228 Oracle oracle(
229 env, {.owner = oracles[i].first, .documentID = asUInt(*oracles[i].second), .fee = baseFee}, false);
230 // push XRP/USD by more than three ledgers, so this price
231 // oracle is not included in the dataset
232 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
233 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
234 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
235 }
236 for (int i = 3; i < 6; ++i)
237 {
238 Oracle oracle(
239 env, {.owner = oracles[i].first, .documentID = asUInt(*oracles[i].second), .fee = baseFee}, false);
240 // push XRP/USD by two ledgers, so this price
241 // is included in the dataset
242 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
243 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
244 }
245
246 // entire and trimmed stats
247 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, "200");
248 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.6");
249 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 7);
250 // Short: 0.2160246899469287
251 BEAST_EXPECTS(
252 ret[jss::entire_set][jss::standard_deviation] == "0.2160246899469286744",
253 ret[jss::entire_set][jss::standard_deviation].asString());
254 BEAST_EXPECT(ret[jss::median] == "74.6");
255 BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.6");
256 BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 5);
257 // Short: 0.158113883008419
258 BEAST_EXPECTS(
259 ret[jss::trimmed_set][jss::standard_deviation] == "0.1581138830084189666",
260 ret[jss::trimmed_set][jss::standard_deviation].asString());
261 BEAST_EXPECT(ret[jss::time] == 946694900);
262 }
263
264 // Reduced data set because of the time threshold
265 {
266 Env env(*this);
267 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
268
269 OraclesData oracles;
270 prep(env, oracles);
271 for (int i = 0; i < oracles.size(); ++i)
272 {
273 Oracle oracle(
274 env, {.owner = oracles[i].first, .documentID = asUInt(*oracles[i].second), .fee = baseFee}, false);
275 // push XRP/USD by two ledgers, so this price
276 // is included in the dataset
277 oracle.set(UpdateArg{.series = {{"XRP", "USD", 740, 1}}, .fee = baseFee});
278 }
279
280 // entire stats only, limit lastUpdateTime to {200, 125}
281 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles, std::nullopt, 75);
282 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74");
283 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 8);
284 BEAST_EXPECT(ret[jss::entire_set][jss::standard_deviation] == "0");
285 BEAST_EXPECT(ret[jss::median] == "74");
286 BEAST_EXPECT(ret[jss::time] == 946695000);
287 }
288 }
289
290 void
291 run() override
292 {
293 testErrors();
294 testRpc();
295 }
296};
297
298BEAST_DEFINE_TESTSUITE(GetAggregatePrice, rpc, xrpl);
299
300} // namespace oracle
301} // namespace jtx
302} // namespace test
303} // 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
Sets the new scale and restores the old scale when it leaves scope.
Definition Number.h:800
Immutable cryptographic account descriptor.
Definition Account.h:19
A transaction testing environment.
Definition Env.h:119
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:261
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:319
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition Oracle.h:95
static Json::Value aggregatePrice(Env &env, std::optional< AnyValue > const &baseAsset, std::optional< AnyValue > const &quoteAsset, std::optional< OraclesData > const &oracles=std::nullopt, std::optional< AnyValue > const &trim=std::nullopt, std::optional< AnyValue > const &timeThreshold=std::nullopt)
Definition Oracle.cpp:120
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:15
T emplace_back(T... args)
T is_same_v
Keylet oracle(AccountID const &account, std::uint32_t const &documentID) noexcept
Definition Indexes.cpp:456
constexpr char const * NoneTag
Definition Oracle.h:17
std::uint32_t asUInt(AnyValue const &v)
Definition Oracle.cpp:320
void toJson(Json::Value &jv, AnyValue const &v)
Definition Oracle.cpp:296
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
FeatureBitset testable_amendments()
Definition Env.h:76
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
T size(T... args)
T to_string(T... args)