xrpld
Loading...
Searching...
No Matches
Oracle.cpp
1#include <test/jtx/Oracle.h>
2
3#include <test/jtx/Env.h>
4#include <test/jtx/multisign.h>
5#include <test/jtx/seq.h>
6#include <test/jtx/ter.h>
7
8#include <xrpl/basics/Number.h>
9#include <xrpl/basics/chrono.h>
10#include <xrpl/basics/strHex.h>
11#include <xrpl/json/json_value.h>
12#include <xrpl/json/to_string.h>
13#include <xrpl/protocol/AccountID.h>
14#include <xrpl/protocol/Indexes.h>
15#include <xrpl/protocol/SField.h>
16#include <xrpl/protocol/STArray.h>
17#include <xrpl/protocol/STObject.h>
18#include <xrpl/protocol/jss.h>
19
20#include <boost/lexical_cast/try_lexical_convert.hpp>
21#include <boost/regex.hpp> // IWYU pragma: keep
22#include <boost/regex/v5/regex_replace.hpp>
23
24#include <algorithm>
25#include <cassert>
26#include <chrono>
27#include <cstdint>
28#include <optional>
29#include <string>
30#include <variant>
31
33
34Oracle::Oracle(Env& env, CreateArg const& arg, bool submit) : env_(env)
35{
36 // LastUpdateTime is checked to be in range
37 // {close-maxLastUpdateTimeDelta, close+maxLastUpdateTimeDelta}.
38 // To make the validation work and to make the clock consistent
39 // for tests running at different time, simulate Unix time starting
40 // on testStartTime since XRPL epoch.
41 auto const now = env_.timeKeeper().now();
42 if (now.time_since_epoch().count() == 0 || arg.close)
43 env_.close(now + kTestStartTime - kEpochOffset);
44 if (arg.owner)
45 owner_ = *arg.owner;
46 if (arg.documentID && validDocumentID(*arg.documentID))
48 if (submit)
49 set(arg);
50}
51
52void
54{
55 json::Value jv;
56 jv[jss::TransactionType] = jss::OracleDelete;
57 jv[jss::Account] = to_string(arg.owner.value_or(owner_));
58 toJson(jv[jss::OracleDocumentID], arg.documentID.value_or(documentID_));
59 if (Oracle::fee != 0)
60 {
61 jv[jss::Fee] = std::to_string(Oracle::fee);
62 }
63 else if (arg.fee != 0)
64 {
65 jv[jss::Fee] = std::to_string(arg.fee);
66 }
67 else
68 {
69 jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
70 }
71 if (arg.flags != 0)
72 jv[jss::Flags] = arg.flags;
73 submit(jv, arg.msig, arg.seq, arg.err);
74}
75
76void
78 json::Value const& jv,
79 std::optional<jtx::Msig> const& msig,
80 std::optional<jtx::Seq> const& seq,
81 std::optional<Ter> const& err)
82{
83 if (msig)
84 {
85 if (seq && err)
86 {
87 env_(jv, *msig, *seq, *err);
88 }
89 else if (seq)
90 {
91 env_(jv, *msig, *seq);
92 }
93 else if (err)
94 {
95 env_(jv, *msig, *err);
96 }
97 else
98 {
99 env_(jv, *msig);
100 }
101 }
102 else if (seq && err)
103 {
104 env_(jv, *seq, *err);
105 }
106 else if (seq)
107 {
108 env_(jv, *seq);
109 }
110 else if (err)
111 {
112 env_(jv, *err);
113 }
114 else
115 {
116 env_(jv);
117 }
118 env_.close();
119}
120
121bool
123{
124 assert(account.isNonZero());
125 return env.le(keylet::oracle(account, documentID)) != nullptr;
126}
127
128bool
129Oracle::expectPrice(DataSeries const& series) const
130{
131 if (auto const sle = env_.le(keylet::oracle(owner_, documentID_)))
132 {
133 auto const& leSeries = sle->getFieldArray(sfPriceDataSeries);
134 if (leSeries.empty() || leSeries.size() != series.size())
135 return false;
136 for (auto const& data : series)
137 {
138 if (std::ranges::find_if(leSeries, [&](STObject const& o) -> bool {
139 auto const& baseAsset = o.getFieldCurrency(sfBaseAsset);
140 auto const& quoteAsset = o.getFieldCurrency(sfQuoteAsset);
141 auto const& price = o.getFieldU64(sfAssetPrice);
142 auto const& scale = o.getFieldU8(sfScale);
143 return baseAsset.getText() == std::get<0>(data) &&
144 quoteAsset.getText() == std::get<1>(data) && price == std::get<2>(data) &&
145 scale == std::get<3>(data);
146 }) == leSeries.end())
147 return false;
148 }
149 return true;
150 }
151 return false;
152}
153
154bool
156{
157 auto const sle = env_.le(keylet::oracle(owner_, documentID_));
158 return sle && (*sle)[sfLastUpdateTime] == lastUpdateTime;
159}
160
163 Env& env,
164 std::optional<AnyValue> const& baseAsset,
165 std::optional<AnyValue> const& quoteAsset,
166 std::optional<OraclesData> const& oracles,
167 std::optional<AnyValue> const& trim,
168 std::optional<AnyValue> const& timeThreshold)
169{
170 json::Value jv;
172 if (oracles)
173 {
174 for (auto const& id : *oracles)
175 {
177 if (id.first)
178 oracle[jss::account] = to_string((*id.first).id());
179 if (id.second)
180 toJson(oracle[jss::oracle_document_id], *id.second);
181 jvOracles.append(oracle);
182 }
183 jv[jss::oracles] = jvOracles;
184 }
185 if (trim)
186 toJson(jv[jss::trim], *trim);
187 if (baseAsset)
188 toJson(jv[jss::base_asset], *baseAsset);
189 if (quoteAsset)
190 toJson(jv[jss::quote_asset], *quoteAsset);
191 if (timeThreshold)
192 toJson(jv[jss::time_threshold], *timeThreshold);
193 // Convert "%None%" to None
194 auto str = to_string(jv);
195 str = boost::regex_replace(str, boost::regex(kNonePattern), kUnquotedNone);
196 auto jr = env.rpc("json", "get_aggregate_price", str);
197
198 if (jr.isObject())
199 {
200 if (jr.isMember(jss::result) && jr[jss::result].isMember(jss::status))
201 {
202 return jr[jss::result];
203 }
204 if (jr.isMember(jss::error))
205 {
206 return jr;
207 }
208 }
210}
211
212void
214{
215 using namespace std::chrono;
216 json::Value jv;
217 if (arg.owner)
218 owner_ = *arg.owner;
220 {
221 documentID_ = std::get<std::uint32_t>(*arg.documentID);
222 jv[jss::OracleDocumentID] = documentID_;
223 }
224 else if (arg.documentID)
225 {
226 toJson(jv[jss::OracleDocumentID], *arg.documentID);
227 }
228 else
229 {
230 jv[jss::OracleDocumentID] = documentID_;
231 }
232 jv[jss::TransactionType] = jss::OracleSet;
233 jv[jss::Account] = to_string(owner_);
234 if (arg.assetClass)
235 toJsonHex(jv[jss::AssetClass], *arg.assetClass);
236 if (arg.provider)
237 toJsonHex(jv[jss::Provider], *arg.provider);
238 if (arg.uri)
239 toJsonHex(jv[jss::URI], *arg.uri);
240 if (arg.flags != 0)
241 jv[jss::Flags] = arg.flags;
242 if (Oracle::fee != 0)
243 {
244 jv[jss::Fee] = std::to_string(Oracle::fee);
245 }
246 else if (arg.fee != 0)
247 {
248 jv[jss::Fee] = std::to_string(arg.fee);
249 }
250 else
251 {
252 jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
253 }
254 // lastUpdateTime if provided is offset from testStartTime
255 if (arg.lastUpdateTime)
256 {
258 {
259 jv[jss::LastUpdateTime] =
260 to_string(kTestStartTime.count() + std::get<std::uint32_t>(*arg.lastUpdateTime));
261 }
262 else
263 {
264 toJson(jv[jss::LastUpdateTime], *arg.lastUpdateTime);
265 }
266 }
267 else
268 {
269 jv[jss::LastUpdateTime] = to_string(
270 duration_cast<seconds>(env_.current()->header().closeTime.time_since_epoch()).count() +
271 kEpochOffset.count());
272 }
274 auto assetToStr = [](std::string const& s) {
275 // assume standard currency
276 if (s.size() == 3)
277 return s;
278 assert(s.size() <= 20);
279 // anything else must be 160-bit hex string
280 return strHex(s).append(40 - (s.size() * 2), '0');
281 };
282 for (auto const& data : arg.series)
283 {
284 json::Value priceData;
285 json::Value price;
286 price[jss::BaseAsset] = assetToStr(std::get<0>(data));
287 price[jss::QuoteAsset] = assetToStr(std::get<1>(data));
288 if (std::get<2>(data))
289 {
290 price[jss::AssetPrice] =
291 *std::get<2>(data); // NOLINT(bugprone-unchecked-optional-access)
292 }
293 if (std::get<3>(data))
294 price[jss::Scale] = *std::get<3>(data); // NOLINT(bugprone-unchecked-optional-access)
295 priceData[jss::PriceData] = price;
296 dataSeries.append(priceData);
297 }
298 jv[jss::PriceDataSeries] = dataSeries;
299 submit(jv, arg.msig, arg.seq, arg.err);
300}
301
302void
304{
306 .owner = arg.owner,
307 .documentID = arg.documentID,
308 .series = arg.series,
309 .assetClass = arg.assetClass,
310 .provider = arg.provider,
311 .uri = arg.uri,
312 .lastUpdateTime = arg.lastUpdateTime,
313 .flags = arg.flags,
314 .msig = arg.msig,
315 .seq = arg.seq,
316 .fee = arg.fee,
317 .err = arg.err});
318}
319
322 Env& env,
325 std::optional<std::string> const& index)
326{
327 json::Value jvParams;
328 if (account)
329 {
331 {
332 jvParams[jss::oracle][jss::account] = to_string(std::get<AccountID>(*account));
333 }
334 else
335 {
336 jvParams[jss::oracle][jss::account] = std::get<std::string>(*account);
337 }
338 }
339 if (documentID)
340 toJson(jvParams[jss::oracle][jss::oracle_document_id], *documentID);
341 if (index)
342 {
343 std::uint32_t i = 0;
344 if (boost::conversion::try_lexical_convert(*index, i))
345 {
346 jvParams[jss::oracle][jss::ledger_index] = i;
347 }
348 else
349 {
350 jvParams[jss::oracle][jss::ledger_index] = *index;
351 }
352 }
353 // Convert "%None%" to None
354 auto str = to_string(jvParams);
355 str = boost::regex_replace(str, boost::regex(kNonePattern), kUnquotedNone);
356 auto jr = env.rpc("json", "ledger_entry", str);
357
358 if (jr.isObject())
359 {
360 if (jr.isMember(jss::error))
361 return jr;
362 if (jr.isMember(jss::result) && jr[jss::result].isMember(jss::status))
363 return jr[jss::result];
364 }
366}
367
368void
370{
371 std::visit([&](auto&& arg) { jv = arg; }, v);
372}
373
374void
376{
378 [&]<typename T>(T&& arg) {
380 {
381 if (arg.starts_with("##"))
382 {
383 jv = arg.substr(2);
384 }
385 else
386 {
387 jv = strHex(arg);
388 }
389 }
390 else
391 {
392 jv = arg;
393 }
394 },
395 v);
396}
397
400{
401 json::Value jv;
402 toJson(jv, v);
403 return jv.asUInt();
404}
405
406bool
408{
409 try
410 {
411 json::Value jv;
412 toJson(jv, v);
413 [[maybe_unused]] auto unused1 = jv.asUInt();
414 [[maybe_unused]] auto unused2 = jv.isNumeric();
415 return true;
416 }
417 catch (...)
418 {
419 return false;
420 }
421}
422
423} // namespace xrpl::test::jtx::oracle
T append(T... args)
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
bool isNumeric() const
UInt asUInt() const
STCurrency const & getFieldCurrency(SField const &field) const
Definition STObject.cpp:695
unsigned char getFieldU8(SField const &field) const
Definition STObject.cpp:589
std::uint64_t getFieldU64(SField const &field) const
Definition STObject.cpp:607
A transaction testing environment.
Definition Env.h:161
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:311
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:1056
void set(CreateArg const &arg)
Definition Oracle.cpp:303
bool expectLastUpdateTime(std::uint32_t lastUpdateTime) const
Definition Oracle.cpp:155
Oracle(Env &env, CreateArg const &arg, bool submit=true)
Definition Oracle.cpp:34
bool expectPrice(DataSeries const &prices) const
Definition Oracle.cpp:129
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:162
static json::Value ledgerEntry(Env &env, std::optional< std::variant< AccountID, std::string > > const &account, std::optional< AnyValue > const &documentID, std::optional< std::string > const &index=std::nullopt)
Definition Oracle.cpp:321
void remove(RemoveArg const &arg)
Definition Oracle.cpp:53
void submit(json::Value const &jv, std::optional< jtx::Msig > const &msig, std::optional< jtx::Seq > const &seq, std::optional< Ter > const &err)
Definition Oracle.cpp:77
T data(T... args)
T duration_cast(T... args)
T find_if(T... args)
T holds_alternative(T... args)
T is_same_v
@ Array
array value (ordered list)
Definition json_value.h:28
@ Null
'null' value
Definition json_value.h:22
Keylet oracle(AccountID const &account, std::uint32_t const documentID) noexcept
Definition Indexes.cpp:531
std::uint32_t asUInt(AnyValue const &v)
Definition Oracle.cpp:399
constexpr char const * kUnquotedNone
std::variant< std::string, double, json::Int, json::UInt > AnyValue
void toJsonHex(json::Value &jv, AnyValue const &v)
Definition Oracle.cpp:375
bool validDocumentID(AnyValue const &v)
Definition Oracle.cpp:407
void toJson(json::Value &jv, AnyValue const &v)
Definition Oracle.cpp:369
constexpr char const * kNonePattern
std::vector< std:: tuple< std::string, std::string, std::optional< std::uint32_t >, std::optional< std::uint8_t > > > DataSeries
static constexpr std::chrono::seconds kTestStartTime
static constexpr std::chrono::seconds kEpochOffset
Clock for measuring the network time.
Definition chrono.h:35
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
Definition STAmount.h:794
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
T size(T... args)
std::optional< AnyValue > documentID
std::optional< AnyValue > lastUpdateTime
std::optional< AnyValue > assetClass
std::optional< Ter > const & err
std::optional< AnyValue > const & documentID
std::optional< jtx::Msig > const & msig
std::optional< AccountID > const & owner
std::optional< AnyValue > documentID
std::optional< AnyValue > lastUpdateTime
std::optional< AnyValue > assetClass
T to_string(T... args)
T visit(T... args)