mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
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.
362 lines
10 KiB
C++
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
|