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> const 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> const 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 const oracle(
101 env,
102 {.owner = owner,
103 .series = {{"XRP", "EUR", 740, 1}},
104 .fee = static_cast<int>(baseFee.drops())});
105 ret = Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, oracle.documentID()}}});
106 BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
107
108 // invalid trim value
109 std::vector<AnyValue> const invalidTrim = {NoneTag, 0, 26, -1, 1.2, "", "none", "1.2"};
110 for (auto const& v : invalidTrim)
111 {
112 ret =
113 Oracle::aggregatePrice(env, "XRP", "USD", {{{owner, oracle.documentID()}}}, v);
114 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
115 }
116
117 // invalid time threshold value
118 std::vector<AnyValue> const invalidTime = {NoneTag, -1, 1.2, "", "none", "1.2"};
119 for (auto const& v : invalidTime)
120 {
122 env, "XRP", "USD", {{{owner, oracle.documentID()}}}, std::nullopt, v);
123 BEAST_EXPECT(ret[jss::error].asString() == "invalidParams");
124 }
125 }
126
127 // too many oracles
128 {
129 Env env(*this);
130 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
131
132 OraclesData oracles;
133 for (int i = 0; i < 201; ++i)
134 {
135 Account const owner(std::to_string(i));
136 env.fund(XRP(1'000), owner);
137 Oracle const oracle(env, {.owner = owner, .documentID = i, .fee = baseFee});
138 oracles.emplace_back(owner, oracle.documentID());
139 }
140 auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
141 BEAST_EXPECT(ret[jss::error].asString() == "oracleMalformed");
142 }
143 }
144
145 void
147 {
148 testcase("RPC");
149 using namespace jtx;
150
151 auto prep = [&](Env& env, auto& oracles) {
152 oracles.reserve(10);
153 for (int i = 0; i < 10; ++i)
154 {
155 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
156
157 Account const owner{std::to_string(i)};
158 env.fund(XRP(1'000), owner);
159 Oracle const oracle(
160 env,
161 {.owner = owner,
162 .documentID = rand(),
163 .series = {{"XRP", "USD", 740 + i, 1}, {"XRP", "EUR", 740, 1}},
164 .fee = baseFee});
165 oracles.emplace_back(owner, oracle.documentID());
166 }
167 };
168
169 // Aggregate data set includes all price oracle instances, no trimming
170 // or time threshold
171 {
172 auto const all = testable_amendments();
173 for (auto const& feats : {all - featureSingleAssetVault - featureLendingProtocol, all})
174 {
175 for (auto const mantissaSize : {MantissaRange::small, MantissaRange::large})
176 {
177 // Regardless of the features enabled, RPC is controlled by
178 // the global mantissa size. And since it's a thread-local,
179 // overriding it locally won't make a difference either.
180 // This will mean all RPC will use the default of "large".
181 NumberMantissaScaleGuard const mg(mantissaSize);
182
183 Env env(*this, feats);
184 OraclesData oracles;
185 prep(env, oracles);
186 // entire and trimmed stats
187 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
188 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45");
189 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10);
190 // Short: 0.3027650354097492
191 BEAST_EXPECTS(
192 ret[jss::entire_set][jss::standard_deviation] == "0.3027650354097491666",
193 ret[jss::entire_set][jss::standard_deviation].asString());
194 BEAST_EXPECT(ret[jss::median] == "74.45");
195 BEAST_EXPECT(ret[jss::time] == 946694900);
196 }
197 }
198 }
199
200 // Aggregate data set includes all price oracle instances
201 {
202 Env env(*this);
203 OraclesData oracles;
204 prep(env, oracles);
205 // entire and trimmed stats
206 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, 100);
207 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45");
208 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10);
209 // Short: "0.3027650354097492",
210 BEAST_EXPECTS(
211 ret[jss::entire_set][jss::standard_deviation] == "0.3027650354097491666",
212 ret[jss::entire_set][jss::standard_deviation].asString());
213 BEAST_EXPECT(ret[jss::median] == "74.45");
214 BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.45");
215 BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 6);
216 // Short: "0.187082869338697",
217 BEAST_EXPECTS(
218 ret[jss::trimmed_set][jss::standard_deviation] == "0.1870828693386970693",
219 ret[jss::trimmed_set][jss::standard_deviation].asString());
220 BEAST_EXPECT(ret[jss::time] == 946694900);
221 }
222
223 // A reduced dataset, as some price oracles have data beyond three
224 // updated ledgers
225 {
226 Env env(*this);
227 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
228
229 OraclesData oracles;
230 prep(env, oracles);
231 for (int i = 0; i < 3; ++i)
232 {
233 Oracle oracle(
234 env,
235 {.owner = oracles[i].first,
236 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
237 .documentID = asUInt(*oracles[i].second),
238 .fee = baseFee},
239 false);
240 // push XRP/USD by more than three ledgers, so this price
241 // oracle is not 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 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
245 }
246 for (int i = 3; i < 6; ++i)
247 {
248 Oracle oracle(
249 env,
250 {.owner = oracles[i].first,
251 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
252 .documentID = asUInt(*oracles[i].second),
253 .fee = baseFee},
254 false);
255 // push XRP/USD by two ledgers, so this price
256 // is included in the dataset
257 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
258 oracle.set(UpdateArg{.series = {{"XRP", "EUR", 740, 1}}, .fee = baseFee});
259 }
260
261 // entire and trimmed stats
262 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, "200");
263 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.6");
264 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 7);
265 // Short: 0.2160246899469287
266 BEAST_EXPECTS(
267 ret[jss::entire_set][jss::standard_deviation] == "0.2160246899469286744",
268 ret[jss::entire_set][jss::standard_deviation].asString());
269 BEAST_EXPECT(ret[jss::median] == "74.6");
270 BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.6");
271 BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 5);
272 // Short: 0.158113883008419
273 BEAST_EXPECTS(
274 ret[jss::trimmed_set][jss::standard_deviation] == "0.1581138830084189666",
275 ret[jss::trimmed_set][jss::standard_deviation].asString());
276 BEAST_EXPECT(ret[jss::time] == 946694900);
277 }
278
279 // Reduced data set because of the time threshold
280 {
281 Env env(*this);
282 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
283
284 OraclesData oracles;
285 prep(env, oracles);
286 for (int i = 0; i < oracles.size(); ++i)
287 {
288 Oracle oracle(
289 env,
290 {.owner = oracles[i].first,
291 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
292 .documentID = asUInt(*oracles[i].second),
293 .fee = baseFee},
294 false);
295 // push XRP/USD by two ledgers, so this price
296 // is included in the dataset
297 oracle.set(UpdateArg{.series = {{"XRP", "USD", 740, 1}}, .fee = baseFee});
298 }
299
300 // entire stats only, limit lastUpdateTime to {200, 125}
301 auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles, std::nullopt, 75);
302 BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74");
303 BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 8);
304 BEAST_EXPECT(ret[jss::entire_set][jss::standard_deviation] == "0");
305 BEAST_EXPECT(ret[jss::median] == "74");
306 BEAST_EXPECT(ret[jss::time] == 946695000);
307 }
308 }
309
310 void
311 run() override
312 {
313 testErrors();
314 testRpc();
315 }
316};
317
318BEAST_DEFINE_TESTSUITE(GetAggregatePrice, rpc, xrpl);
319
320} // namespace oracle
321} // namespace jtx
322} // namespace test
323} // 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:150
Sets the new scale and restores the old scale when it leaves scope.
Definition Number.h:814
Immutable cryptographic account descriptor.
Definition Account.h:19
A transaction testing environment.
Definition Env.h:122
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:329
Oracle class facilitates unit-testing of the Price Oracle feature.
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:143
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:468
constexpr char const * NoneTag
std::uint32_t asUInt(AnyValue const &v)
Definition Oracle.cpp:380
void toJson(Json::Value &jv, AnyValue const &v)
Definition Oracle.cpp:350
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:95
FeatureBitset testable_amendments()
Definition Env.h:78
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)