mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 19:56:00 +00:00
@@ -23,6 +23,7 @@ class Clio(ConanFile):
|
|||||||
'boost/1.82.0',
|
'boost/1.82.0',
|
||||||
'cassandra-cpp-driver/2.17.0',
|
'cassandra-cpp-driver/2.17.0',
|
||||||
'fmt/10.1.1',
|
'fmt/10.1.1',
|
||||||
|
'protobuf/3.21.12',
|
||||||
'grpc/1.50.1',
|
'grpc/1.50.1',
|
||||||
'openssl/1.1.1u',
|
'openssl/1.1.1u',
|
||||||
'xrpl/2.0.0-b2',
|
'xrpl/2.0.0-b2',
|
||||||
|
|||||||
@@ -65,6 +65,21 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
|
|||||||
auto const id = ripple::parseBase58<ripple::AccountID>(input.ticket->at(JS(account)).as_string().c_str());
|
auto const id = ripple::parseBase58<ripple::AccountID>(input.ticket->at(JS(account)).as_string().c_str());
|
||||||
|
|
||||||
key = ripple::getTicketIndex(*id, input.ticket->at(JS(ticket_seq)).as_int64());
|
key = ripple::getTicketIndex(*id, input.ticket->at(JS(ticket_seq)).as_int64());
|
||||||
|
} else if (input.amm) {
|
||||||
|
auto const getIssuerFromJson = [](auto const& assetJson) {
|
||||||
|
// the field check has been done in validator
|
||||||
|
auto const currency = ripple::to_currency(assetJson.at(JS(currency)).as_string().c_str());
|
||||||
|
if (ripple::isXRP(currency)) {
|
||||||
|
return ripple::xrpIssue();
|
||||||
|
}
|
||||||
|
auto const issuer = ripple::parseBase58<ripple::AccountID>(assetJson.at(JS(issuer)).as_string().c_str());
|
||||||
|
return ripple::Issue{currency, *issuer};
|
||||||
|
};
|
||||||
|
|
||||||
|
key = ripple::keylet::amm(
|
||||||
|
getIssuerFromJson(input.amm->at(JS(asset))), getIssuerFromJson(input.amm->at(JS(asset2)))
|
||||||
|
)
|
||||||
|
.key;
|
||||||
} else {
|
} else {
|
||||||
// Must specify 1 of the following fields to indicate what type
|
// Must specify 1 of the following fields to indicate what type
|
||||||
if (ctx.apiVersion == 1)
|
if (ctx.apiVersion == 1)
|
||||||
@@ -179,7 +194,7 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
|
|||||||
{JS(deposit_preauth), ripple::ltDEPOSIT_PREAUTH},
|
{JS(deposit_preauth), ripple::ltDEPOSIT_PREAUTH},
|
||||||
{JS(ticket), ripple::ltTICKET},
|
{JS(ticket), ripple::ltTICKET},
|
||||||
{JS(nft_page), ripple::ltNFTOKEN_PAGE},
|
{JS(nft_page), ripple::ltNFTOKEN_PAGE},
|
||||||
};
|
{JS(amm), ripple::ltAMM}};
|
||||||
|
|
||||||
auto const indexFieldType =
|
auto const indexFieldType =
|
||||||
std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) {
|
std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) {
|
||||||
@@ -208,6 +223,8 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
|
|||||||
input.depositPreauth = jv.at(JS(deposit_preauth)).as_object();
|
input.depositPreauth = jv.at(JS(deposit_preauth)).as_object();
|
||||||
} else if (jsonObject.contains(JS(ticket))) {
|
} else if (jsonObject.contains(JS(ticket))) {
|
||||||
input.ticket = jv.at(JS(ticket)).as_object();
|
input.ticket = jv.at(JS(ticket)).as_object();
|
||||||
|
} else if (jsonObject.contains(JS(amm))) {
|
||||||
|
input.amm = jv.at(JS(amm)).as_object();
|
||||||
}
|
}
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public:
|
|||||||
std::optional<boost::json::object> escrow;
|
std::optional<boost::json::object> escrow;
|
||||||
std::optional<boost::json::object> depositPreauth;
|
std::optional<boost::json::object> depositPreauth;
|
||||||
std::optional<boost::json::object> ticket;
|
std::optional<boost::json::object> ticket;
|
||||||
|
std::optional<boost::json::object> amm;
|
||||||
};
|
};
|
||||||
|
|
||||||
using Result = HandlerReturnType<Output>;
|
using Result = HandlerReturnType<Output>;
|
||||||
@@ -100,6 +101,27 @@ public:
|
|||||||
static auto const malformedRequestIntValidator =
|
static auto const malformedRequestIntValidator =
|
||||||
meta::WithCustomError{validation::Type<uint32_t>{}, Status(ClioError::rpcMALFORMED_REQUEST)};
|
meta::WithCustomError{validation::Type<uint32_t>{}, Status(ClioError::rpcMALFORMED_REQUEST)};
|
||||||
|
|
||||||
|
static auto const ammAssetValidator =
|
||||||
|
validation::CustomValidator{[](boost::json::value const& value, std::string_view /* key */) -> MaybeError {
|
||||||
|
if (!value.is_object()) {
|
||||||
|
return Error{Status{ClioError::rpcMALFORMED_REQUEST}};
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value jvAsset;
|
||||||
|
if (value.as_object().contains(JS(issuer)))
|
||||||
|
jvAsset["issuer"] = value.at(JS(issuer)).as_string().c_str();
|
||||||
|
if (value.as_object().contains(JS(currency)))
|
||||||
|
jvAsset["currency"] = value.at(JS(currency)).as_string().c_str();
|
||||||
|
// same as rippled
|
||||||
|
try {
|
||||||
|
ripple::issueFromJson(jvAsset);
|
||||||
|
} catch (std::runtime_error const&) {
|
||||||
|
return Error{Status{ClioError::rpcMALFORMED_REQUEST}};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MaybeError{};
|
||||||
|
}};
|
||||||
|
|
||||||
static auto const rpcSpec = RpcSpec{
|
static auto const rpcSpec = RpcSpec{
|
||||||
{JS(binary), validation::Type<bool>{}},
|
{JS(binary), validation::Type<bool>{}},
|
||||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||||
@@ -162,7 +184,23 @@ public:
|
|||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
{JS(nft_page), malformedRequestHexStringValidator},
|
{JS(nft_page), malformedRequestHexStringValidator},
|
||||||
};
|
{JS(amm),
|
||||||
|
validation::Type<std::string, boost::json::object>{},
|
||||||
|
meta::IfType<std::string>{malformedRequestHexStringValidator},
|
||||||
|
meta::IfType<boost::json::object>{
|
||||||
|
meta::Section{
|
||||||
|
{JS(asset),
|
||||||
|
meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
|
||||||
|
meta::WithCustomError{
|
||||||
|
validation::Type<boost::json::object>{}, Status(ClioError::rpcMALFORMED_REQUEST)},
|
||||||
|
ammAssetValidator},
|
||||||
|
{JS(asset2),
|
||||||
|
meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
|
||||||
|
meta::WithCustomError{
|
||||||
|
validation::Type<boost::json::object>{}, Status(ClioError::rpcMALFORMED_REQUEST)},
|
||||||
|
ammAssetValidator},
|
||||||
|
},
|
||||||
|
}}};
|
||||||
|
|
||||||
return rpcSpec;
|
return rpcSpec;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -544,6 +544,245 @@ generateTestValuesForParametersTest()
|
|||||||
),
|
),
|
||||||
"malformedRequest",
|
"malformedRequest",
|
||||||
"Malformed request."},
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"InvalidAMMStringIndex",
|
||||||
|
R"({
|
||||||
|
"amm": "invalid"
|
||||||
|
})",
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"EmptyAMMJson",
|
||||||
|
R"({
|
||||||
|
"amm": {}
|
||||||
|
})",
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"EmptyAMMAssetJson",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset":{{}},
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"EmptyAMMAsset2Json",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2":{{}},
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"MissingAMMAsset2Json",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"MissingAMMAssetJson",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"AMMAssetNotJson",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset": "invalid",
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"AMMAsset2NotJson",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2": "invalid",
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"WrongAMMAssetCurrency",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency":"XRP"
|
||||||
|
}},
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"currency" : "USD2",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"WrongAMMAssetIssuer",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency":"XRP"
|
||||||
|
}},
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "aa{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"MissingAMMAssetIssuerForNonXRP",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency":"JPY"
|
||||||
|
}},
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"AMMAssetHasIssuerForXRP",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency":"XRP",
|
||||||
|
"issuer":"{}"
|
||||||
|
}},
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"currency" : "USD",
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT,
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
|
|
||||||
|
ParamTestCaseBundle{
|
||||||
|
"MissingAMMAssetCurrency",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"amm":
|
||||||
|
{{
|
||||||
|
"asset2":
|
||||||
|
{{
|
||||||
|
"currency":"XRP"
|
||||||
|
}},
|
||||||
|
"asset":
|
||||||
|
{{
|
||||||
|
"issuer" : "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
ACCOUNT
|
||||||
|
),
|
||||||
|
"malformedRequest",
|
||||||
|
"Malformed request."},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,7 +1156,38 @@ generateTestValuesForNormalPathTest()
|
|||||||
ripple::keylet::offer(account1, 2).key,
|
ripple::keylet::offer(account1, 2).key,
|
||||||
CreateOfferLedgerObject(
|
CreateOfferLedgerObject(
|
||||||
ACCOUNT, 100, 200, "USD", "XRP", ACCOUNT2, ripple::toBase58(ripple::xrpAccount()), INDEX1
|
ACCOUNT, 100, 200, "USD", "XRP", ACCOUNT2, ripple::toBase58(ripple::xrpAccount()), INDEX1
|
||||||
)}};
|
)},
|
||||||
|
NormalPathTestBundle{
|
||||||
|
"AMMViaIndex",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"binary": true,
|
||||||
|
"amm": "{}"
|
||||||
|
}})",
|
||||||
|
INDEX1
|
||||||
|
),
|
||||||
|
ripple::uint256{INDEX1},
|
||||||
|
CreateAMMObject(ACCOUNT, "XRP", ripple::toBase58(ripple::xrpAccount()), "JPY", ACCOUNT2)},
|
||||||
|
NormalPathTestBundle{
|
||||||
|
"AMMViaJson",
|
||||||
|
fmt::format(
|
||||||
|
R"({{
|
||||||
|
"binary": true,
|
||||||
|
"amm": {{
|
||||||
|
"asset": {{
|
||||||
|
"currency": "XRP"
|
||||||
|
}},
|
||||||
|
"asset2": {{
|
||||||
|
"currency": "{}",
|
||||||
|
"issuer": "{}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}})",
|
||||||
|
"JPY",
|
||||||
|
ACCOUNT2
|
||||||
|
),
|
||||||
|
ripple::keylet::amm(GetIssue("XRP", ripple::toBase58(ripple::xrpAccount())), GetIssue("JPY", ACCOUNT2)).key,
|
||||||
|
CreateAMMObject(ACCOUNT, "XRP", ripple::toBase58(ripple::xrpAccount()), "JPY", ACCOUNT2)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
INSTANTIATE_TEST_CASE_P(
|
INSTANTIATE_TEST_CASE_P(
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
||||||
|
constexpr static auto CURRENCY = "03930D02208264E2E40EC1B0C09E4DB96EE197B1";
|
||||||
|
|
||||||
ripple::AccountID
|
ripple::AccountID
|
||||||
GetAccountIDWithString(std::string_view id)
|
GetAccountIDWithString(std::string_view id)
|
||||||
@@ -749,3 +750,27 @@ CreateAmendmentsObject(std::vector<ripple::uint256> const& enabledAmendments)
|
|||||||
amendments.setFieldV256(ripple::sfAmendments, list);
|
amendments.setFieldV256(ripple::sfAmendments, list);
|
||||||
return amendments;
|
return amendments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ripple::STObject
|
||||||
|
CreateAMMObject(
|
||||||
|
std::string_view accountId,
|
||||||
|
std::string_view assetCurrency,
|
||||||
|
std::string_view assetIssuer,
|
||||||
|
std::string_view asset2Currency,
|
||||||
|
std::string_view asset2Issuer
|
||||||
|
)
|
||||||
|
{
|
||||||
|
auto amm = ripple::STObject(ripple::sfLedgerEntry);
|
||||||
|
amm.setFieldU16(ripple::sfLedgerEntryType, ripple::ltAMM);
|
||||||
|
amm.setAccountID(ripple::sfAccount, GetAccountIDWithString(accountId));
|
||||||
|
amm.setFieldU16(ripple::sfTradingFee, 5);
|
||||||
|
amm.setFieldU64(ripple::sfOwnerNode, 0);
|
||||||
|
amm.setFieldIssue(ripple::sfAsset, ripple::STIssue{ripple::sfAsset, GetIssue(assetCurrency, assetIssuer)});
|
||||||
|
amm.setFieldIssue(ripple::sfAsset2, ripple::STIssue{ripple::sfAsset2, GetIssue(asset2Currency, asset2Issuer)});
|
||||||
|
ripple::Issue const issue1(
|
||||||
|
ripple::Currency{CURRENCY}, ripple::parseBase58<ripple::AccountID>(std::string(accountId)).value()
|
||||||
|
);
|
||||||
|
amm.setFieldAmount(ripple::sfLPTokenBalance, ripple::STAmount(issue1, 100));
|
||||||
|
amm.setFieldU32(ripple::sfFlags, 0);
|
||||||
|
return amm;
|
||||||
|
}
|
||||||
|
|||||||
@@ -277,3 +277,12 @@ CreateCreateNFTOfferTxWithMetadata(
|
|||||||
|
|
||||||
[[nodiscard]] ripple::STObject
|
[[nodiscard]] ripple::STObject
|
||||||
CreateAmendmentsObject(std::vector<ripple::uint256> const& enabledAmendments);
|
CreateAmendmentsObject(std::vector<ripple::uint256> const& enabledAmendments);
|
||||||
|
|
||||||
|
[[nodiscard]] ripple::STObject
|
||||||
|
CreateAMMObject(
|
||||||
|
std::string_view accountId,
|
||||||
|
std::string_view assetCurrency,
|
||||||
|
std::string_view assetIssuer,
|
||||||
|
std::string_view asset2Currency,
|
||||||
|
std::string_view asset2Issuer
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user