Compare commits

...

1 Commits

Author SHA1 Message Date
Ayaz Salikhov
584d2bb5f2 feat: Support Lending Protocol (#2945) 2026-02-17 22:54:24 +00:00
10 changed files with 479 additions and 25 deletions

View File

@@ -3,15 +3,15 @@
"requires": [
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
"xrpl/3.0.0#534d3f65a336109eee929b88962bae4e%1765375071.547",
"xrpl/3.1.0#3d408ab8c8020014fa7dd52bc7cc7ea8%1769706825.165",
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1767636069.964",
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1768312128.781",
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103",
"rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1754325007.656",
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
"openssl/1.1.1w#a8f0792d7c5121b954578a7149d23e03%1756223730.729",
"nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1765850143.957",
"nudb/2.0.9#0432758a24204da08fee953ec9ea03cb%1769436073.32",
"minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1754325004.374",
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
"libuv/1.46.0#dc28c1f653fa197f00db5b577a6f6011%1754325003.592",
@@ -19,7 +19,7 @@
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736",
"http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1754325001.385",
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1755784855.585",
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152",
"grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1756234248.958",
"fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1763984116.926",
"doctest/2.4.11#a4211dfc329a16ba9f280f9574025659%1756234220.819",
@@ -40,17 +40,20 @@
],
"python_requires": [],
"overrides": {
"boost/1.83.0": [
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88": [
null,
"protobuf/3.21.12"
],
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1": [
null,
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1"
],
"protobuf/3.21.12": [
null,
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88"
],
"lz4/1.9.4": [
"lz4/1.10.0"
],
"boost/1.90.0": [
"boost/1.83.0"
],
"sqlite3/3.44.2": [
"sqlite3/3.49.1"
]

View File

@@ -12,7 +12,6 @@ class ClioConan(ConanFile):
options = {}
requires = [
"boost/1.83.0",
"cassandra-cpp-driver/2.17.0",
"fmt/12.1.0",
"grpc/1.50.1",
@@ -20,7 +19,7 @@ class ClioConan(ConanFile):
"openssl/1.1.1w",
"protobuf/3.21.12",
"spdlog/1.17.0",
"xrpl/3.0.0",
"xrpl/3.1.0",
"zlib/1.3.1",
]
@@ -43,6 +42,7 @@ class ClioConan(ConanFile):
exports_sources = ("CMakeLists.txt", "cmake/*", "src/*")
def requirements(self):
self.requires("boost/1.83.0", force=True)
self.requires("gtest/1.17.0")
self.requires("benchmark/1.9.4")

View File

@@ -177,6 +177,7 @@ struct Amendments {
REGISTER(fix1512);
REGISTER(fix1523);
REGISTER(fix1528);
REGISTER(fixBatchInnerSigs);
// NOLINTEND(readability-identifier-naming)
/** @endcond */
};

View File

@@ -34,6 +34,7 @@
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -173,6 +174,21 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountInfoHandl
jv.as_object()[JS(account_flags)] = std::move(acctFlags);
auto const pseudoFields = ripple::getPseudoAccountFields();
for (auto const& pseudoField : pseudoFields) {
if (output.accountData.isFieldPresent(*pseudoField)) {
std::string name = pseudoField->fieldName;
if (name.ends_with("ID")) {
// Remove the ID suffix from the field name.
name = name.substr(0, name.size() - 2);
ASSERT(!name.empty(), "Field name is empty after stripping 'ID'");
}
// ValidPseudoAccounts invariant guarantees that only one field can be set
jv.as_object()[JS(pseudo_account)].as_object()[JS(type)] = name;
break;
}
}
if (output.signerLists) {
auto signers = boost::json::array();
std::transform(

View File

@@ -192,6 +192,15 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input const& input, Context cons
ripple::parseBase58<ripple::AccountID>(boost::json::value_to<std::string>(input.vault->at(JS(owner))));
auto const seq = util::integralValueAs<uint32_t>(input.vault->at(JS(seq)));
key = ripple::keylet::vault(*account, seq).key;
} else if (input.loanBroker) {
auto const account =
ripple::parseBase58<ripple::AccountID>(boost::json::value_to<std::string>(input.loanBroker->at(JS(owner))));
auto const seq = util::integralValueAs<uint32_t>(input.loanBroker->at(JS(seq)));
key = ripple::keylet::loanbroker(*account, seq).key;
} else if (input.loan) {
auto const id = ripple::uint256{boost::json::value_to<std::string>(input.loan->at(JS(loan_broker_id))).data()};
auto const seq = util::integralValueAs<uint32_t>(input.loan->at(JS(loan_seq)));
key = ripple::keylet::loan(id, seq).key;
} else if (input.delegate) {
auto const account =
ripple::parseBase58<ripple::AccountID>(boost::json::value_to<std::string>(input.delegate->at(JS(account))));
@@ -333,13 +342,15 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
{JS(mptoken), ripple::ltMPTOKEN},
{JS(permissioned_domain), ripple::ltPERMISSIONED_DOMAIN},
{JS(vault), ripple::ltVAULT},
{JS(loan_broker), ripple::ltLOAN_BROKER},
{JS(loan), ripple::ltLOAN},
{JS(delegate), ripple::ltDELEGATE},
{JS(amendments), ripple::ltAMENDMENTS},
{JS(fee), ripple::ltFEE_SETTINGS},
{JS(hashes), ripple::ltLEDGER_HASHES},
{JS(nft_offer), ripple::ltNFTOKEN_OFFER},
{JS(nunl), ripple::ltNEGATIVE_UNL},
{JS(signer_list), ripple::ltSIGNER_LIST}
{JS(signer_list), ripple::ltSIGNER_LIST},
};
auto const parseBridgeFromJson = [](boost::json::value const& bridgeJson) {
@@ -430,6 +441,10 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
input.permissionedDomain = jv.at(JS(permissioned_domain)).as_object();
} else if (jsonObject.contains(JS(vault))) {
input.vault = jv.at(JS(vault)).as_object();
} else if (jsonObject.contains(JS(loan_broker))) {
input.loanBroker = jv.at(JS(loan_broker)).as_object();
} else if (jsonObject.contains(JS(loan))) {
input.loan = jv.at(JS(loan)).as_object();
} else if (jsonObject.contains(JS(delegate))) {
input.delegate = jv.at(JS(delegate)).as_object();
}

View File

@@ -105,6 +105,8 @@ public:
std::optional<boost::json::object> mptoken;
std::optional<boost::json::object> permissionedDomain;
std::optional<boost::json::object> vault;
std::optional<boost::json::object> loanBroker;
std::optional<boost::json::object> loan;
std::optional<ripple::STXChainBridge> bridge;
std::optional<std::string> bridgeAccount;
std::optional<uint32_t> chainClaimId;
@@ -411,6 +413,40 @@ public:
},
},
}}},
{JS(loan_broker),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{}, Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMALFORMED_REQUEST_HEX_STRING_VALIDATOR},
meta::IfType<boost::json::object>{meta::Section{
{JS(seq),
meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)},
meta::WithCustomError{validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)}},
{
JS(owner),
meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator, Status(ClioError::RpcMalformedOwner)
},
},
}}},
{JS(loan),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{}, Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMALFORMED_REQUEST_HEX_STRING_VALIDATOR},
meta::IfType<boost::json::object>{meta::Section{
{JS(loan_seq),
meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)},
meta::WithCustomError{validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)}},
{
JS(loan_broker_id),
meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)},
meta::WithCustomError{
validation::CustomValidators::uint256HexStringValidator, Status(ClioError::RpcMalformedRequest)
},
},
}}},
{JS(delegate),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{}, Status(ClioError::RpcMalformedRequest)

View File

@@ -1800,3 +1800,67 @@ createVault(
return vault;
}
ripple::STObject
createLoanBroker(
std::string_view owner,
std::string_view account,
ripple::LedgerIndex seq,
ripple::uint256 vaultID,
uint32_t loanSequence,
ripple::uint256 previousTxId,
uint32_t previousTxSeq
)
{
auto loanBroker = ripple::STObject(ripple::sfLedgerEntry);
loanBroker.setAccountID(ripple::sfOwner, getAccountIdWithString(owner));
loanBroker.setAccountID(ripple::sfAccount, getAccountIdWithString(account));
loanBroker.setFieldU32(ripple::sfSequence, seq);
loanBroker.setFieldU64(ripple::sfOwnerNode, 0);
loanBroker.setFieldU64(ripple::sfVaultNode, 0);
loanBroker.setFieldH256(ripple::sfVaultID, vaultID);
loanBroker.setFieldH256(ripple::sfPreviousTxnID, previousTxId);
loanBroker.setFieldU32(ripple::sfPreviousTxnLgrSeq, previousTxSeq);
loanBroker.setFieldU32(ripple::sfLoanSequence, loanSequence);
// Optional/default fields - not setting them as they will use default values
loanBroker.setFieldU32(ripple::sfFlags, 0);
loanBroker.setFieldU16(ripple::sfLedgerEntryType, ripple::ltLOAN_BROKER);
return loanBroker;
}
ripple::STObject
createLoan(
std::string_view borrower,
ripple::uint256 loanBrokerID,
uint32_t loanSequence,
uint32_t startDate,
uint32_t paymentInterval,
int64_t periodicPaymentValue,
ripple::uint256 previousTxId,
uint32_t previousTxSeq
)
{
auto loan = ripple::STObject(ripple::sfLedgerEntry);
loan.setAccountID(ripple::sfBorrower, getAccountIdWithString(borrower));
loan.setFieldH256(ripple::sfLoanBrokerID, loanBrokerID);
loan.setFieldU32(ripple::sfLoanSequence, loanSequence);
loan.setFieldU64(ripple::sfOwnerNode, 0);
loan.setFieldU64(ripple::sfLoanBrokerNode, 0);
loan.setFieldH256(ripple::sfPreviousTxnID, previousTxId);
loan.setFieldU32(ripple::sfPreviousTxnLgrSeq, previousTxSeq);
loan.setFieldU32(ripple::sfStartDate, startDate);
loan.setFieldU32(ripple::sfPaymentInterval, paymentInterval);
loan.setFieldNumber(ripple::sfPeriodicPayment, ripple::STNumber{ripple::sfPeriodicPayment, periodicPaymentValue});
// Optional/default fields - not setting them as they will use default values
loan.setFieldU32(ripple::sfFlags, 0);
loan.setFieldU16(ripple::sfLedgerEntryType, ripple::ltLOAN);
return loan;
}

View File

@@ -581,3 +581,26 @@ createVault(
ripple::uint256 previousTxId,
uint32_t previousTxSeq
);
[[nodiscard]] ripple::STObject
createLoanBroker(
std::string_view owner,
std::string_view account,
ripple::LedgerIndex seq,
ripple::uint256 vaultID,
uint32_t loanSequence,
ripple::uint256 previousTxId,
uint32_t previousTxSeq
);
[[nodiscard]] ripple::STObject
createLoan(
std::string_view borrower,
ripple::uint256 loanBrokerID,
uint32_t loanSequence,
uint32_t startDate,
uint32_t paymentInterval,
int64_t periodicPaymentValue,
ripple::uint256 previousTxId,
uint32_t previousTxSeq
);

View File

@@ -741,7 +741,7 @@ TEST_F(RPCGetAggregatePriceHandlerTest, OracleLedgerEntryMultipleOraclesOdd)
"entire_set": {{
"mean": "110",
"size": 3,
"standard_deviation": "164.6207763315433"
"standard_deviation": "164.6207763315432795"
}},
"median": "20",
"time": 4321,
@@ -817,7 +817,7 @@ TEST_F(RPCGetAggregatePriceHandlerTest, OracleLedgerEntryMultipleOraclesEven)
"entire_set": {{
"mean": "92.5",
"size": 4,
"standard_deviation": "138.8944443333378"
"standard_deviation": "138.8944443333377776"
}},
"median": "30",
"time": 4321,
@@ -895,12 +895,12 @@ TEST_F(RPCGetAggregatePriceHandlerTest, OracleLedgerEntryTrim)
"entire_set": {{
"mean": "92.5",
"size": 4,
"standard_deviation": "138.8944443333378"
"standard_deviation": "138.8944443333377776"
}},
"trimmed_set": {{
"mean": "30",
"size": 2,
"standard_deviation": "14.14213562373095"
"standard_deviation": "14.14213562373095049"
}},
"median": "30",
"time": 4321,
@@ -1134,7 +1134,7 @@ TEST_F(RPCGetAggregatePriceHandlerTest, ValidTimeThreshold)
"entire_set": {{
"mean": "15",
"size": 2,
"standard_deviation": "7.071067811865475"
"standard_deviation": "7.071067811865475245"
}},
"median": "15",
"time": {},
@@ -1216,7 +1216,7 @@ TEST_F(RPCGetAggregatePriceHandlerTest, TimeThresholdTooLong)
"entire_set": {{
"mean": "92.5",
"size": 4,
"standard_deviation": "138.8944443333378"
"standard_deviation": "138.8944443333377776"
}},
"median": "30",
"time": 1711461384,
@@ -1297,7 +1297,7 @@ TEST_F(RPCGetAggregatePriceHandlerTest, TimeThresholdIncludeOldest)
"entire_set": {{
"mean": "92.5",
"size": 4,
"standard_deviation": "138.8944443333378"
"standard_deviation": "138.8944443333377776"
}},
"median": "30",
"time": 1711461384,

View File

@@ -2174,6 +2174,154 @@ generateTestValuesForParametersTest()
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
// LoanBroker tests
ParamTestCaseBundle{
.testName = "LoanBroker_InvalidType",
.testJson = R"JSON({"loan_broker": 0})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "LoanBroker_NotHex",
.testJson =
R"JSON({
"loan_broker": "invalid_hex"
})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "LoanBroker_MissingOwner",
.testJson =
R"JSON({
"loan_broker": { "seq": 1 }
})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "LoanBroker_MissingSeq",
.testJson =
R"JSON({
"loan_broker": { "owner": "abcd" }
})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "LoanBroker_SeqNotInteger",
.testJson = fmt::format(
R"JSON({{
"loan_broker": {{
"owner": "{}",
"seq": "notAnInteger"
}}
}})JSON",
kACCOUNT
),
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "LoanBroker_InvalidOwnerFormat",
.testJson =
R"JSON({
"loan_broker": {
"owner": "abcd",
"seq": 10
}
})JSON",
.expectedError = "malformedOwner",
.expectedErrorMessage = "Malformed owner.",
},
ParamTestCaseBundle{
.testName = "LoanBroker_NegativeSeq",
.testJson =
R"JSON({
"loan_broker": {
"owner": "abcd",
"seq": -200
}
})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
// Loan tests
ParamTestCaseBundle{
.testName = "Loan_InvalidType",
.testJson = R"JSON({"loan": 0})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "Loan_NotHex",
.testJson =
R"JSON({
"loan": "invalid_hex"
})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "Loan_MissingLoanBrokerId",
.testJson =
R"JSON({
"loan": { "loan_seq": 1 }
})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "Loan_MissingLoanSeq",
.testJson = fmt::format(
R"JSON({{
"loan": {{ "loan_broker_id": "{}" }}
}})JSON",
kINDEX1
),
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "Loan_LoanSeqNotInteger",
.testJson = fmt::format(
R"JSON({{
"loan": {{
"loan_broker_id": "{}",
"loan_seq": "notAnInteger"
}}
}})JSON",
kINDEX1
),
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "Loan_InvalidLoanBrokerIdFormat",
.testJson =
R"JSON({
"loan": {
"loan_broker_id": "invalid_hex",
"loan_seq": 10
}
})JSON",
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "Loan_NegativeLoanSeq",
.testJson = fmt::format(
R"JSON({{
"loan": {{
"loan_broker_id": "{}",
"loan_seq": -200
}}
}})JSON",
kINDEX1
),
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request.",
},
ParamTestCaseBundle{
.testName = "Delegate_InvalidType",
.testJson = R"JSON({"delegate": 123})JSON",
@@ -2377,7 +2525,6 @@ TEST_P(IndexTest, InvalidIndexNotString)
TEST_F(RPCLedgerEntryTest, LedgerEntryNotFound)
{
// return valid ledgerHeader
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));
@@ -3106,6 +3253,64 @@ generateTestValuesForNormalPathTest()
0
)
},
NormalPathTestBundle{
.testName = "CreateLoanBrokerObjectByHexString",
.testJson = fmt::format(
R"JSON({{
"binary": true,
"loan_broker": "{}"
}})JSON",
kINDEX1
),
.expectedIndex = ripple::uint256(kINDEX1),
.mockedEntity =
createLoanBroker(kACCOUNT, kACCOUNT, kRANGE_MAX, ripple::uint256{kINDEX1}, 1, ripple::uint256{0}, 0)
},
NormalPathTestBundle{
.testName = "CreateLoanBrokerObjectByOwnerAndSeq",
.testJson = fmt::format(
R"JSON({{
"binary": true,
"loan_broker": {{
"owner": "{}",
"seq": {}
}}
}})JSON",
kACCOUNT,
kRANGE_MAX
),
.expectedIndex =
ripple::keylet::loanbroker(ripple::parseBase58<ripple::AccountID>(kACCOUNT).value(), kRANGE_MAX).key,
.mockedEntity =
createLoanBroker(kACCOUNT, kACCOUNT, kRANGE_MAX, ripple::uint256{kINDEX1}, 1, ripple::uint256{0}, 0)
},
NormalPathTestBundle{
.testName = "CreateLoanObjectByHexString",
.testJson = fmt::format(
R"JSON({{
"binary": true,
"loan": "{}"
}})JSON",
kINDEX1
),
.expectedIndex = ripple::uint256(kINDEX1),
.mockedEntity = createLoan(kACCOUNT, ripple::uint256{kINDEX1}, 1, 1000, 86400, 100, ripple::uint256{0}, 0)
},
NormalPathTestBundle{
.testName = "CreateLoanObjectByLoanBrokerIdAndSeq",
.testJson = fmt::format(
R"JSON({{
"binary": true,
"loan": {{
"loan_broker_id": "{}",
"loan_seq": 1
}}
}})JSON",
kINDEX1
),
.expectedIndex = ripple::keylet::loan(ripple::uint256{kINDEX1}, 1).key,
.mockedEntity = createLoan(kACCOUNT, ripple::uint256{kINDEX1}, 1, 1000, 86400, 100, ripple::uint256{0}, 0)
},
NormalPathTestBundle{
.testName = "DelegateViaStringIndex",
.testJson = fmt::format(
@@ -3151,7 +3356,6 @@ TEST_P(RPCLedgerEntryNormalPathTest, NormalPath)
{
auto const testBundle = GetParam();
// return valid ledgerHeader
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));
@@ -3199,7 +3403,6 @@ TEST_F(RPCLedgerEntryTest, BinaryFalse)
}
})JSON";
// return valid ledgerHeader
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));
@@ -3226,7 +3429,6 @@ TEST_F(RPCLedgerEntryTest, BinaryFalse)
TEST_F(RPCLedgerEntryTest, Vault_BinaryFalse)
{
// return valid ledgerHeader
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));
@@ -3277,9 +3479,104 @@ TEST_F(RPCLedgerEntryTest, Vault_BinaryFalse)
});
}
TEST_F(RPCLedgerEntryTest, LoanBroker_BinaryFalse)
{
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));
boost::json::object const entry;
auto const loanBroker =
createLoanBroker(kACCOUNT, kACCOUNT, kRANGE_MAX, ripple::uint256{kINDEX1}, 1, ripple::uint256{1}, 0);
auto const loanBrokerKey =
ripple::keylet::loanbroker(ripple::parseBase58<ripple::AccountID>(kACCOUNT).value(), kRANGE_MAX).key;
ripple::STLedgerEntry const sle{
ripple::SerialIter{loanBroker.getSerializer().peekData().data(), loanBroker.getSerializer().peekData().size()},
loanBrokerKey
};
EXPECT_CALL(*backend_, doFetchLedgerObject(loanBrokerKey, testing::_, testing::_))
.WillOnce(Return(loanBroker.getSerializer().peekData()));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{LedgerEntryHandler{backend_}};
auto const req = json::parse(
fmt::format(
R"JSON({{
"binary": false,
"loan_broker": {{
"owner": "{}",
"seq": {}
}}
}})JSON",
kACCOUNT,
kRANGE_MAX
)
);
auto const output = handler.process(req, Context{yield});
ASSERT_TRUE(output);
EXPECT_EQ(output.result->at("node").at("Owner").as_string(), kACCOUNT);
EXPECT_EQ(output.result->at("node").at("Sequence").as_int64(), kRANGE_MAX);
EXPECT_EQ(output.result->at("node").at("LoanSequence").as_int64(), 1);
});
}
TEST_F(RPCLedgerEntryTest, Loan_BinaryFalse)
{
static constexpr auto kLOAN_SEQ = 1;
static constexpr auto kSTART_DATE = 1000;
static constexpr auto kPAYMENT_INTERVAL = 86400;
static constexpr auto kINTEREST_RATE = 100;
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));
boost::json::object const entry;
auto const loan = createLoan(
kACCOUNT,
ripple::uint256{kINDEX1},
kLOAN_SEQ,
kSTART_DATE,
kPAYMENT_INTERVAL,
kINTEREST_RATE,
ripple::uint256{1},
0
);
EXPECT_CALL(*backend_, doFetchLedgerObject(testing::_, testing::_, testing::_))
.WillOnce(Return(loan.getSerializer().peekData()));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{LedgerEntryHandler{backend_}};
auto const req = json::parse(
fmt::format(
R"JSON({{
"binary": false,
"loan": {{
"loan_broker_id": "{}",
"loan_seq": {}
}}
}})JSON",
kINDEX1,
kLOAN_SEQ
)
);
auto const output = handler.process(req, Context{yield});
ASSERT_TRUE(output);
EXPECT_EQ(output.result->at("node").at("Borrower").as_string(), kACCOUNT);
EXPECT_EQ(output.result->at("node").at("LoanSequence").as_int64(), kLOAN_SEQ);
EXPECT_EQ(output.result->at("node").at("StartDate").as_int64(), kSTART_DATE);
EXPECT_EQ(output.result->at("node").at("PaymentInterval").as_int64(), kPAYMENT_INTERVAL);
});
}
TEST_F(RPCLedgerEntryTest, UnexpectedLedgerType)
{
// return valid ledgerHeader
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));
@@ -3768,7 +4065,6 @@ TEST_F(RPCLedgerEntryTest, SyntheticMPTIssuanceID)
auto const mptId = ripple::makeMptID(2, getAccountIdWithString(kACCOUNT));
// return valid ledgerHeader
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX);
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader));