Files
rippled/src/test/jtx/impl/Oracle.cpp
Bart 1d42c4f6de refactor: Remove unnecessary copyright notices already covered by LICENSE.md (#5929)
Per XLS-0095, we are taking steps to rename ripple(d) to xrpl(d).

This change specifically removes all copyright notices referencing Ripple, XRPLF, and certain affiliated contributors upon mutual agreement, so the notice in the LICENSE.md file applies throughout. Copyright notices referencing external contributions remain as-is. Duplicate verbiage is also removed.
2025-11-04 08:33:42 +00:00

362 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 ripple {
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()->info().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 ripple