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 ripple {
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 =
30 Oracle::aggregatePrice(env, std::nullopt, "USD", oracles);
31 BEAST_EXPECT(
32 ret[jss::error_message].asString() ==
33 "Missing field 'base_asset'.");
34
35 // missing quote_asset
36 ret = Oracle::aggregatePrice(env, "XRP", std::nullopt, oracles);
37 BEAST_EXPECT(
38 ret[jss::error_message].asString() ==
39 "Missing field 'quote_asset'.");
40
41 // invalid base_asset, quote_asset
42 std::vector<AnyValue> invalidAsset = {
43 NoneTag,
44 1,
45 -1,
46 1.2,
47 "",
48 "invalid",
49 "a",
50 "ab",
51 "A",
52 "AB",
53 "ABCD",
54 "010101",
55 "012345678901234567890123456789012345678",
56 "012345678901234567890123456789012345678G"};
57 for (auto const& v : invalidAsset)
58 {
59 ret = Oracle::aggregatePrice(env, "USD", v, oracles);
60 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
61 ret = Oracle::aggregatePrice(env, v, "USD", oracles);
62 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
63 ret = Oracle::aggregatePrice(env, v, v, oracles);
64 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
65 }
66
67 // missing oracles array
68 ret = Oracle::aggregatePrice(env, "XRP", "USD");
69 BEAST_EXPECT(
70 ret[jss::error_message].asString() ==
71 "Missing field 'oracles'.");
72
73 // empty oracles array
74 ret = Oracle::aggregatePrice(env, "XRP", "USD", OraclesData{});
75 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
76
77 // no token pairs found
78 ret = Oracle::aggregatePrice(env, "YAN", "USD", oracles);
79 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
80
81 // invalid oracle document id
82 // id doesn't exist
83 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, 2}}});
84 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
85 // invalid values
86 std::vector<AnyValue> invalidDocument = {
87 NoneTag, 1.2, -1, "", "none", "1.2"};
88 for (auto const& v : invalidDocument)
89 {
90 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, v}}});
91 Json::Value jv;
92 toJson(jv, v);
93 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
94 }
95 // missing document id
97 env, "XRP", "USD", {{{owner, std::nullopt}}});
98 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
99
100 // invalid owner
101 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{some, 1}}});
102 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
103 // missing account
105 env, "XRP", "USD", {{{std::nullopt, 1}}});
106 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
107
108 // oracles have wrong asset pair
109 env.fund(XRP(1'000), owner);
110 Oracle oracle(
111 env,
112 {.owner = owner,
113 .series = {{"XRP", "EUR", 740, 1}},
114 .fee = static_cast<int>(baseFee.drops())});
116 env, "XRP", "USD", {{{owner, oracle.documentID()}}});
117 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
118
119 // invalid trim value
120 std::vector<AnyValue> invalidTrim = {
121 NoneTag, 0, 26, -1, 1.2, "", "none", "1.2"};
122 for (auto const& v : invalidTrim)
123 {
125 env, "XRP", "USD", {{{owner, oracle.documentID()}}}, v);
126 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
127 }
128
129 // invalid time threshold value
130 std::vector<AnyValue> invalidTime = {
131 NoneTag, -1, 1.2, "", "none", "1.2"};
132 for (auto const& v : invalidTime)
133 {
135 env,
136 "XRP",
137 "USD",
138 {{{owner, oracle.documentID()}}},
140 v);
141 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
142 }
143 }
144
145 // too many oracles
146 {
147 Env env(*this);
148 auto const baseFee =
149 static_cast<int>(env.current()->fees().base.drops());
150
151 OraclesData oracles;
152 for (int i = 0; i < 201; ++i)
153 {
154 Account const owner(std::to_string(i));
155 env.fund(XRP(1'000), owner);
156 Oracle oracle(
157 env, {.owner = owner, .documentID = i, .fee = baseFee});
158 oracles.emplace_back(owner, oracle.documentID());
159 }
160 auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
161 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
162 }
163 }
164
165 void
167 {
168 testcase("RPC");
169 using namespace jtx;
170
171 auto prep = [&](Env& env, auto& oracles) {
172 oracles.reserve(10);
173 for (int i = 0; i < 10; ++i)
174 {
175 auto const baseFee =
176 static_cast<int>(env.current()->fees().base.drops());
177
178 Account const owner{std::to_string(i)};
179 env.fund(XRP(1'000), owner);
180 Oracle oracle(
181 env,
182 {.owner = owner,
183 .documentID = rand(),
184 .series =
185 {{"XRP", "USD", 740 + i, 1}, {"XRP", "EUR", 740, 1}},
186 .fee = baseFee});
187 oracles.emplace_back(owner, oracle.documentID());
188 }
189 };
190
191 // Aggregate data set includes all price oracle instances, no trimming
192 // or time threshold
193 {
194 Env env(*this);
195 OraclesData oracles;
196 prep(env, oracles);
197 // entire and trimmed stats
198 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
199 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45");
200 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10);
201 BEAST_EXPECT(
202 ret[jss::entire_set][jss::standard_deviation] ==
203 "0.3027650354097492");
204 BEAST_EXPECT(ret[jss::median] == "74.45");
205 BEAST_EXPECT(ret[jss::time] == 946694900);
206 }
207
208 // Aggregate data set includes all price oracle instances
209 {
210 Env env(*this);
211 OraclesData oracles;
212 prep(env, oracles);
213 // entire and trimmed stats
214 auto ret =
215 Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, 100);
216 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45");
217 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10);
218 BEAST_EXPECT(
219 ret[jss::entire_set][jss::standard_deviation] ==
220 "0.3027650354097492");
221 BEAST_EXPECT(ret[jss::median] == "74.45");
222 BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.45");
223 BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 6);
224 BEAST_EXPECT(
225 ret[jss::trimmed_set][jss::standard_deviation] ==
226 "0.187082869338697");
227 BEAST_EXPECT(ret[jss::time] == 946694900);
228 }
229
230 // A reduced dataset, as some price oracles have data beyond three
231 // updated ledgers
232 {
233 Env env(*this);
234 auto const baseFee =
235 static_cast<int>(env.current()->fees().base.drops());
236
237 OraclesData oracles;
238 prep(env, oracles);
239 for (int i = 0; i < 3; ++i)
240 {
241 Oracle oracle(
242 env,
243 {.owner = oracles[i].first,
244 .documentID = asUInt(*oracles[i].second),
245 .fee = baseFee},
246 false);
247 // push XRP/USD by more than three ledgers, so this price
248 // oracle is not included in the dataset
249 oracle.set(UpdateArg{
250 .series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
251 oracle.set(UpdateArg{
252 .series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
253 oracle.set(UpdateArg{
254 .series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
255 }
256 for (int i = 3; i < 6; ++i)
257 {
258 Oracle oracle(
259 env,
260 {.owner = oracles[i].first,
261 .documentID = asUInt(*oracles[i].second),
262 .fee = baseFee},
263 false);
264 // push XRP/USD by two ledgers, so this price
265 // is included in the dataset
266 oracle.set(UpdateArg{
267 .series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
268 oracle.set(UpdateArg{
269 .series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
270 }
271
272 // entire and trimmed stats
273 auto ret =
274 Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, "200");
275 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.6");
276 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 7);
277 BEAST_EXPECT(
278 ret[jss::entire_set][jss::standard_deviation] ==
279 "0.2160246899469287");
280 BEAST_EXPECT(ret[jss::median] == "74.6");
281 BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.6");
282 BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 5);
283 BEAST_EXPECT(
284 ret[jss::trimmed_set][jss::standard_deviation] ==
285 "0.158113883008419");
286 BEAST_EXPECT(ret[jss::time] == 946694900);
287 }
288
289 // Reduced data set because of the time threshold
290 {
291 Env env(*this);
292 auto const baseFee =
293 static_cast<int>(env.current()->fees().base.drops());
294
295 OraclesData oracles;
296 prep(env, oracles);
297 for (int i = 0; i < oracles.size(); ++i)
298 {
299 Oracle oracle(
300 env,
301 {.owner = oracles[i].first,
302 .documentID = asUInt(*oracles[i].second),
303 .fee = baseFee},
304 false);
305 // push XRP/USD by two ledgers, so this price
306 // is included in the dataset
307 oracle.set(UpdateArg{
308 .series = {{"XRP", "USD", 740, 1}}, .fee = baseFee});
309 }
310
311 // entire stats only, limit lastUpdateTime to {200, 125}
312 auto ret = Oracle::aggregatePrice(
313 env, "XRP", "USD", oracles, std::nullopt, 75);
314 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74");
315 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 8);
316 BEAST_EXPECT(ret[jss::entire_set][jss::standard_deviation] == "0");
317 BEAST_EXPECT(ret[jss::median] == "74");
318 BEAST_EXPECT(ret[jss::time] == 946695000);
319 }
320 }
321
322 void
323 run() override
324 {
325 testErrors();
326 testRpc();
327 }
328};
329
330BEAST_DEFINE_TESTSUITE(GetAggregatePrice, rpc, ripple);
331
332} // namespace oracle
333} // namespace jtx
334} // namespace test
335} // namespace ripple
Represents a JSON value.
Definition json_value.h:130
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
Immutable cryptographic account descriptor.
Definition Account.h:20
A transaction testing environment.
Definition Env.h:102
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:312
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition Oracle.h:101
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 &timeTreshold=std::nullopt)
Definition Oracle.cpp:127
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:16
T emplace_back(T... args)
T is_same_v
Keylet oracle(AccountID const &account, std::uint32_t const &documentID) noexcept
Definition Indexes.cpp:501
std::uint32_t asUInt(AnyValue const &v)
Definition Oracle.cpp:334
void toJson(Json::Value &jv, AnyValue const &v)
Definition Oracle.cpp:310
constexpr char const * NoneTag
Definition Oracle.h:19
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
T size(T... args)
T to_string(T... args)