Files
rippled/src/test/jtx/impl/Oracle.cpp
Alex Kremer afc660a1b5 refactor: Fix clang-tidy bugprone-empty-catch check (#6419)
This change fixes or suppresses instances detected by the `bugprone-empty-catch` clang-tidy check.
2026-03-02 17:08:56 +00:00

350 lines
10 KiB
C++

#include <test/jtx/Oracle.h>
#include <xrpl/protocol/jss.h>
#include <boost/lexical_cast/try_lexical_convert.hpp>
#include <boost/regex.hpp>
#include <vector>
namespace xrpl {
namespace test {
namespace jtx {
namespace oracle {
Oracle::Oracle(Env& env, CreateArg const& arg, bool submit) : env_(env), owner_{}, documentID_{}
{
// LastUpdateTime is checked to be in range
// {close-maxLastUpdateTimeDelta, close+maxLastUpdateTimeDelta}.
// To make the validation work and to make the clock consistent
// for tests running at different time, simulate Unix time starting
// on testStartTime since Ripple epoch.
auto const now = env_.timeKeeper().now();
if (now.time_since_epoch().count() == 0 || arg.close)
env_.close(now + testStartTime - epoch_offset);
if (arg.owner)
owner_ = *arg.owner;
if (arg.documentID && validDocumentID(*arg.documentID))
documentID_ = asUInt(*arg.documentID);
if (submit)
set(arg);
}
void
Oracle::remove(RemoveArg const& arg)
{
Json::Value jv;
jv[jss::TransactionType] = jss::OracleDelete;
jv[jss::Account] = to_string(arg.owner.value_or(owner_));
toJson(jv[jss::OracleDocumentID], arg.documentID.value_or(documentID_));
if (Oracle::fee != 0)
jv[jss::Fee] = std::to_string(Oracle::fee);
else if (arg.fee != 0)
jv[jss::Fee] = std::to_string(arg.fee);
else
jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
if (arg.flags != 0)
jv[jss::Flags] = arg.flags;
submit(jv, arg.msig, arg.seq, arg.err);
}
void
Oracle::submit(
Json::Value const& jv,
std::optional<jtx::msig> const& msig,
std::optional<jtx::seq> const& seq,
std::optional<ter> const& err)
{
if (msig)
{
if (seq && err)
env_(jv, *msig, *seq, *err);
else if (seq)
env_(jv, *msig, *seq);
else if (err)
env_(jv, *msig, *err);
else
env_(jv, *msig);
}
else if (seq && err)
env_(jv, *seq, *err);
else if (seq)
env_(jv, *seq);
else if (err)
env_(jv, *err);
else
env_(jv);
env_.close();
}
bool
Oracle::exists(Env& env, AccountID const& account, std::uint32_t documentID)
{
assert(account.isNonZero());
return env.le(keylet::oracle(account, documentID)) != nullptr;
}
bool
Oracle::expectPrice(DataSeries const& series) const
{
if (auto const sle = env_.le(keylet::oracle(owner_, documentID_)))
{
auto const& leSeries = sle->getFieldArray(sfPriceDataSeries);
if (leSeries.size() == 0 || leSeries.size() != series.size())
return false;
for (auto const& data : series)
{
if (std::find_if(leSeries.begin(), leSeries.end(), [&](STObject const& o) -> bool {
auto const& baseAsset = o.getFieldCurrency(sfBaseAsset);
auto const& quoteAsset = o.getFieldCurrency(sfQuoteAsset);
auto const& price = o.getFieldU64(sfAssetPrice);
auto const& scale = o.getFieldU8(sfScale);
return baseAsset.getText() == std::get<0>(data) &&
quoteAsset.getText() == std::get<1>(data) && price == std::get<2>(data) &&
scale == std::get<3>(data);
}) == leSeries.end())
return false;
}
return true;
}
return false;
}
bool
Oracle::expectLastUpdateTime(std::uint32_t lastUpdateTime) const
{
auto const sle = env_.le(keylet::oracle(owner_, documentID_));
return sle && (*sle)[sfLastUpdateTime] == lastUpdateTime;
}
Json::Value
Oracle::aggregatePrice(
Env& env,
std::optional<AnyValue> const& baseAsset,
std::optional<AnyValue> const& quoteAsset,
std::optional<OraclesData> const& oracles,
std::optional<AnyValue> const& trim,
std::optional<AnyValue> const& timeThreshold)
{
Json::Value jv;
Json::Value jvOracles(Json::arrayValue);
if (oracles)
{
for (auto const& id : *oracles)
{
Json::Value oracle;
if (id.first)
oracle[jss::account] = to_string((*id.first).id());
if (id.second)
toJson(oracle[jss::oracle_document_id], *id.second);
jvOracles.append(oracle);
}
jv[jss::oracles] = jvOracles;
}
if (trim)
toJson(jv[jss::trim], *trim);
if (baseAsset)
toJson(jv[jss::base_asset], *baseAsset);
if (quoteAsset)
toJson(jv[jss::quote_asset], *quoteAsset);
if (timeThreshold)
toJson(jv[jss::time_threshold], *timeThreshold);
// Convert "%None%" to None
auto str = to_string(jv);
str = boost::regex_replace(str, boost::regex(NonePattern), UnquotedNone);
auto jr = env.rpc("json", "get_aggregate_price", str);
if (jr.isObject())
{
if (jr.isMember(jss::result) && jr[jss::result].isMember(jss::status))
return jr[jss::result];
else if (jr.isMember(jss::error))
return jr;
}
return Json::nullValue;
}
void
Oracle::set(UpdateArg const& arg)
{
using namespace std::chrono;
Json::Value jv;
if (arg.owner)
owner_ = *arg.owner;
if (arg.documentID && std::holds_alternative<std::uint32_t>(*arg.documentID))
{
documentID_ = std::get<std::uint32_t>(*arg.documentID);
jv[jss::OracleDocumentID] = documentID_;
}
else if (arg.documentID)
toJson(jv[jss::OracleDocumentID], *arg.documentID);
else
jv[jss::OracleDocumentID] = documentID_;
jv[jss::TransactionType] = jss::OracleSet;
jv[jss::Account] = to_string(owner_);
if (arg.assetClass)
toJsonHex(jv[jss::AssetClass], *arg.assetClass);
if (arg.provider)
toJsonHex(jv[jss::Provider], *arg.provider);
if (arg.uri)
toJsonHex(jv[jss::URI], *arg.uri);
if (arg.flags != 0)
jv[jss::Flags] = arg.flags;
if (Oracle::fee != 0)
jv[jss::Fee] = std::to_string(Oracle::fee);
else if (arg.fee != 0)
jv[jss::Fee] = std::to_string(arg.fee);
else
jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
// lastUpdateTime if provided is offset from testStartTime
if (arg.lastUpdateTime)
{
if (std::holds_alternative<std::uint32_t>(*arg.lastUpdateTime))
jv[jss::LastUpdateTime] =
to_string(testStartTime.count() + std::get<std::uint32_t>(*arg.lastUpdateTime));
else
toJson(jv[jss::LastUpdateTime], *arg.lastUpdateTime);
}
else
jv[jss::LastUpdateTime] = to_string(
duration_cast<seconds>(env_.current()->header().closeTime.time_since_epoch()).count() +
epoch_offset.count());
Json::Value dataSeries(Json::arrayValue);
auto assetToStr = [](std::string const& s) {
// assume standard currency
if (s.size() == 3)
return s;
assert(s.size() <= 20);
// anything else must be 160-bit hex string
std::string h = strHex(s);
return strHex(s).append(40 - s.size() * 2, '0');
};
for (auto const& data : arg.series)
{
Json::Value priceData;
Json::Value price;
price[jss::BaseAsset] = assetToStr(std::get<0>(data));
price[jss::QuoteAsset] = assetToStr(std::get<1>(data));
if (std::get<2>(data))
price[jss::AssetPrice] = *std::get<2>(data);
if (std::get<3>(data))
price[jss::Scale] = *std::get<3>(data);
priceData[jss::PriceData] = price;
dataSeries.append(priceData);
}
jv[jss::PriceDataSeries] = dataSeries;
submit(jv, arg.msig, arg.seq, arg.err);
}
void
Oracle::set(CreateArg const& arg)
{
set(UpdateArg{
.owner = arg.owner,
.documentID = arg.documentID,
.series = arg.series,
.assetClass = arg.assetClass,
.provider = arg.provider,
.uri = arg.uri,
.lastUpdateTime = arg.lastUpdateTime,
.flags = arg.flags,
.msig = arg.msig,
.seq = arg.seq,
.fee = arg.fee,
.err = arg.err});
}
Json::Value
Oracle::ledgerEntry(
Env& env,
std::optional<std::variant<AccountID, std::string>> const& account,
std::optional<AnyValue> const& documentID,
std::optional<std::string> const& index)
{
Json::Value jvParams;
if (account)
{
if (std::holds_alternative<AccountID>(*account))
jvParams[jss::oracle][jss::account] = to_string(std::get<AccountID>(*account));
else
jvParams[jss::oracle][jss::account] = std::get<std::string>(*account);
}
if (documentID)
toJson(jvParams[jss::oracle][jss::oracle_document_id], *documentID);
if (index)
{
std::uint32_t i;
if (boost::conversion::try_lexical_convert(*index, i))
jvParams[jss::oracle][jss::ledger_index] = i;
else
jvParams[jss::oracle][jss::ledger_index] = *index;
}
// Convert "%None%" to None
auto str = to_string(jvParams);
str = boost::regex_replace(str, boost::regex(NonePattern), UnquotedNone);
auto jr = env.rpc("json", "ledger_entry", str);
if (jr.isObject())
{
if (jr.isMember(jss::error))
return jr;
if (jr.isMember(jss::result) && jr[jss::result].isMember(jss::status))
return jr[jss::result];
}
return Json::nullValue;
}
void
toJson(Json::Value& jv, AnyValue const& v)
{
std::visit([&](auto&& arg) { jv = arg; }, v);
}
void
toJsonHex(Json::Value& jv, AnyValue const& v)
{
std::visit(
[&]<typename T>(T&& arg) {
if constexpr (std::is_same_v<T, std::string const&>)
{
if (arg.starts_with("##"))
jv = arg.substr(2);
else
jv = strHex(arg);
}
else
jv = arg;
},
v);
}
std::uint32_t
asUInt(AnyValue const& v)
{
Json::Value jv;
toJson(jv, v);
return jv.asUInt();
}
bool
validDocumentID(AnyValue const& v)
{
try
{
Json::Value jv;
toJson(jv, v);
jv.asUInt();
jv.isNumeric();
return true;
}
catch (...)
{
return false;
}
}
} // namespace oracle
} // namespace jtx
} // namespace test
} // namespace xrpl