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