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