mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-23 15:20:54 +00:00
Implement Lending Protocol (unsupported) (#5270)
- Spec: XLS-66 - Introduces amendment "LendingProtocol", but leaves it UNSUPPORTED to allow for standalone testing, future development work, and potential bug fixes. - AccountInfo RPC will indicate the type of pseudo-account when appropriate. - Refactors and improves several existing classes and functional areas, including Number, STAmount, STObject, json_value, Asset, directory handling, View helper functions, and unit test helpers.
This commit is contained in:
@@ -427,14 +427,16 @@ Env::postconditions(
|
||||
ParsedResult const& parsed,
|
||||
Json::Value const& jr)
|
||||
{
|
||||
bool bad = !test.expect(parsed.ter, "apply: No ter result!");
|
||||
auto const line = jt.testLine ? " (" + to_string(*jt.testLine) + ")" : "";
|
||||
bool bad = !test.expect(parsed.ter, "apply: No ter result!" + line);
|
||||
bad =
|
||||
(jt.ter && parsed.ter &&
|
||||
!test.expect(
|
||||
*parsed.ter == *jt.ter,
|
||||
"apply: Got " + transToken(*parsed.ter) + " (" +
|
||||
transHuman(*parsed.ter) + "); Expected " +
|
||||
transToken(*jt.ter) + " (" + transHuman(*jt.ter) + ")"));
|
||||
transToken(*jt.ter) + " (" + transHuman(*jt.ter) + ")" +
|
||||
line));
|
||||
using namespace std::string_literals;
|
||||
bad = (jt.rpcCode &&
|
||||
!test.expect(
|
||||
@@ -446,21 +448,21 @@ Env::postconditions(
|
||||
: "NO RESULT") +
|
||||
" (" + parsed.rpcMessage + "); Expected " +
|
||||
RPC::get_error_info(jt.rpcCode->first).token.c_str() + " (" +
|
||||
jt.rpcCode->second + ")")) ||
|
||||
jt.rpcCode->second + ")" + line)) ||
|
||||
bad;
|
||||
// If we have an rpcCode (just checked), then the rpcException check is
|
||||
// optional - the 'error' field may not be defined, but if it is, it must
|
||||
// match rpcError.
|
||||
bad =
|
||||
(jt.rpcException &&
|
||||
!test.expect(
|
||||
(jt.rpcCode && parsed.rpcError.empty()) ||
|
||||
(parsed.rpcError == jt.rpcException->first &&
|
||||
(!jt.rpcException->second ||
|
||||
parsed.rpcException == *jt.rpcException->second)),
|
||||
"apply: Got RPC result "s + parsed.rpcError + " (" +
|
||||
parsed.rpcException + "); Expected " + jt.rpcException->first +
|
||||
" (" + jt.rpcException->second.value_or("n/a") + ")")) ||
|
||||
bad = (jt.rpcException &&
|
||||
!test.expect(
|
||||
(jt.rpcCode && parsed.rpcError.empty()) ||
|
||||
(parsed.rpcError == jt.rpcException->first &&
|
||||
(!jt.rpcException->second ||
|
||||
parsed.rpcException == *jt.rpcException->second)),
|
||||
"apply: Got RPC result "s + parsed.rpcError + " (" +
|
||||
parsed.rpcException + "); Expected " +
|
||||
jt.rpcException->first + " (" +
|
||||
jt.rpcException->second.value_or("n/a") + ")" + line)) ||
|
||||
bad;
|
||||
if (bad)
|
||||
{
|
||||
|
||||
@@ -356,6 +356,134 @@ allpe(AccountID const& a, Issue const& iss)
|
||||
iss.account);
|
||||
};
|
||||
|
||||
/* LoanBroker */
|
||||
/******************************************************************************/
|
||||
|
||||
namespace loanBroker {
|
||||
|
||||
Json::Value
|
||||
set(AccountID const& account, uint256 const& vaultId, uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanBrokerSet;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfVaultID] = to_string(vaultId);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
del(AccountID const& account, uint256 const& brokerID, uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanBrokerDelete;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanBrokerID] = to_string(brokerID);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
coverDeposit(
|
||||
AccountID const& account,
|
||||
uint256 const& brokerID,
|
||||
STAmount const& amount,
|
||||
uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanBrokerCoverDeposit;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanBrokerID] = to_string(brokerID);
|
||||
jv[sfAmount] = amount.getJson(JsonOptions::none);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
coverWithdraw(
|
||||
AccountID const& account,
|
||||
uint256 const& brokerID,
|
||||
STAmount const& amount,
|
||||
uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanBrokerCoverWithdraw;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanBrokerID] = to_string(brokerID);
|
||||
jv[sfAmount] = amount.getJson(JsonOptions::none);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
coverClawback(AccountID const& account, std::uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanBrokerCoverClawback;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
} // namespace loanBroker
|
||||
|
||||
/* Loan */
|
||||
/******************************************************************************/
|
||||
namespace loan {
|
||||
|
||||
Json::Value
|
||||
set(AccountID const& account,
|
||||
uint256 const& loanBrokerID,
|
||||
Number principalRequested,
|
||||
std::uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanSet;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanBrokerID] = to_string(loanBrokerID);
|
||||
jv[sfPrincipalRequested] = to_string(principalRequested);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
manage(AccountID const& account, uint256 const& loanID, std::uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanManage;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanID] = to_string(loanID);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
del(AccountID const& account, uint256 const& loanID, std::uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanDelete;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanID] = to_string(loanID);
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
pay(AccountID const& account,
|
||||
uint256 const& loanID,
|
||||
STAmount const& amount,
|
||||
std::uint32_t flags)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfTransactionType] = jss::LoanPay;
|
||||
jv[sfAccount] = to_string(account);
|
||||
jv[sfLoanID] = to_string(loanID);
|
||||
jv[sfAmount] = amount.getJson();
|
||||
jv[sfFlags] = flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
} // namespace loan
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -62,17 +62,67 @@ MPTTester::MPTTester(Env& env, Account const& issuer, MPTInit const& arg)
|
||||
env_.require(owners(it.second, 0));
|
||||
}
|
||||
}
|
||||
if (arg.create)
|
||||
create(*arg.create);
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::create(MPTCreate const& arg)
|
||||
MPTTester::MPTTester(
|
||||
Env& env,
|
||||
Account const& issuer,
|
||||
MPTID const& id,
|
||||
std::vector<Account> const& holders,
|
||||
bool close)
|
||||
: env_(env)
|
||||
, issuer_(issuer)
|
||||
, holders_(makeHolders(holders))
|
||||
, id_(id)
|
||||
, close_(close)
|
||||
{
|
||||
if (id_)
|
||||
Throw<std::runtime_error>("MPT can't be reused");
|
||||
id_ = makeMptID(env_.seq(issuer_), issuer_);
|
||||
}
|
||||
|
||||
static MPTCreate
|
||||
makeMPTCreate(MPTInitDef const& arg)
|
||||
{
|
||||
if (arg.pay)
|
||||
return {
|
||||
.maxAmt = arg.maxAmt,
|
||||
.transferFee = arg.transferFee,
|
||||
.pay = {{arg.holders, *arg.pay}},
|
||||
.flags = arg.flags,
|
||||
.authHolder = arg.authHolder};
|
||||
return {
|
||||
.maxAmt = arg.maxAmt,
|
||||
.transferFee = arg.transferFee,
|
||||
.authorize = arg.holders,
|
||||
.flags = arg.flags,
|
||||
.authHolder = arg.authHolder};
|
||||
}
|
||||
|
||||
MPTTester::MPTTester(MPTInitDef const& arg)
|
||||
: MPTTester{
|
||||
arg.env,
|
||||
arg.issuer,
|
||||
MPTInit{
|
||||
.fund = arg.fund,
|
||||
.close = arg.close,
|
||||
.create = makeMPTCreate(arg)}}
|
||||
{
|
||||
}
|
||||
|
||||
MPTTester::operator MPT() const
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
return MPT("", *id_);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
MPTTester::createjv(MPTCreate const& arg)
|
||||
{
|
||||
if (!arg.issuer)
|
||||
Throw<std::runtime_error>("MPTTester::createjv: issuer is not set");
|
||||
Json::Value jv;
|
||||
jv[sfAccount] = issuer_.human();
|
||||
jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
|
||||
jv[sfAccount] = arg.issuer->human();
|
||||
if (arg.assetScale)
|
||||
jv[sfAssetScale] = *arg.assetScale;
|
||||
if (arg.transferFee)
|
||||
@@ -85,6 +135,25 @@ MPTTester::create(MPTCreate const& arg)
|
||||
jv[sfDomainID] = to_string(*arg.domainID);
|
||||
if (arg.mutableFlags)
|
||||
jv[sfMutableFlags] = *arg.mutableFlags;
|
||||
jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
|
||||
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::create(MPTCreate const& arg)
|
||||
{
|
||||
if (id_)
|
||||
Throw<std::runtime_error>("MPT can't be reused");
|
||||
id_ = makeMptID(env_.seq(issuer_), issuer_);
|
||||
Json::Value jv = createjv(
|
||||
{.issuer = issuer_,
|
||||
.maxAmt = arg.maxAmt,
|
||||
.assetScale = arg.assetScale,
|
||||
.transferFee = arg.transferFee,
|
||||
.metadata = arg.metadata,
|
||||
.mutableFlags = arg.mutableFlags,
|
||||
.domainID = arg.domainID});
|
||||
if (submit(arg, jv) != tesSUCCESS)
|
||||
{
|
||||
// Verify issuance doesn't exist
|
||||
@@ -95,26 +164,62 @@ MPTTester::create(MPTCreate const& arg)
|
||||
id_.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
env_.require(mptflags(*this, arg.flags.value_or(0)));
|
||||
auto authAndPay = [&](auto const& accts, auto const&& getAcct) {
|
||||
for (auto const& it : accts)
|
||||
{
|
||||
authorize({.account = getAcct(it)});
|
||||
if ((arg.flags.value_or(0) & tfMPTRequireAuth) &&
|
||||
arg.authHolder)
|
||||
authorize({.account = issuer_, .holder = getAcct(it)});
|
||||
if (arg.pay && arg.pay->first.empty())
|
||||
pay(issuer_, getAcct(it), arg.pay->second);
|
||||
}
|
||||
if (arg.pay)
|
||||
{
|
||||
for (auto const& p : arg.pay->first)
|
||||
pay(issuer_, p, arg.pay->second);
|
||||
}
|
||||
};
|
||||
if (arg.authorize)
|
||||
{
|
||||
if (arg.authorize->empty())
|
||||
authAndPay(holders_, [](auto const& it) { return it.second; });
|
||||
else
|
||||
authAndPay(*arg.authorize, [](auto const& it) { return it; });
|
||||
}
|
||||
else if (arg.pay)
|
||||
{
|
||||
if (arg.pay->first.empty())
|
||||
authAndPay(holders_, [](auto const& it) { return it.second; });
|
||||
else
|
||||
authAndPay(arg.pay->first, [](auto const& it) { return it; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value
|
||||
MPTTester::destroyjv(MPTDestroy const& arg)
|
||||
{
|
||||
Json::Value jv;
|
||||
if (!arg.issuer || !arg.id)
|
||||
Throw<std::runtime_error>("MPTTester::destroyjv: issuer/id is not set");
|
||||
jv[sfAccount] = arg.issuer->human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||
jv[sfTransactionType] = jss::MPTokenIssuanceDestroy;
|
||||
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::destroy(MPTDestroy const& arg)
|
||||
{
|
||||
Json::Value jv;
|
||||
if (arg.issuer)
|
||||
jv[sfAccount] = arg.issuer->human();
|
||||
else
|
||||
jv[sfAccount] = issuer_.human();
|
||||
if (arg.id)
|
||||
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||
else
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
jv[sfMPTokenIssuanceID] = to_string(*id_);
|
||||
}
|
||||
jv[sfTransactionType] = jss::MPTokenIssuanceDestroy;
|
||||
if (!arg.id && !id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
Json::Value jv = destroyjv(
|
||||
{.issuer = arg.issuer ? arg.issuer : issuer_,
|
||||
.id = arg.id ? arg.id : id_});
|
||||
submit(arg, jv);
|
||||
}
|
||||
|
||||
@@ -127,25 +232,32 @@ MPTTester::holder(std::string const& holder_) const
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
MPTTester::authorizejv(MPTAuthorize const& arg)
|
||||
{
|
||||
Json::Value jv;
|
||||
if (!arg.account || !arg.id)
|
||||
Throw<std::runtime_error>(
|
||||
"MPTTester::authorizejv: issuer/id is not set");
|
||||
jv[sfAccount] = arg.account->human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||
if (arg.holder)
|
||||
jv[sfHolder] = arg.holder->human();
|
||||
jv[sfTransactionType] = jss::MPTokenAuthorize;
|
||||
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::authorize(MPTAuthorize const& arg)
|
||||
{
|
||||
Json::Value jv;
|
||||
if (arg.account)
|
||||
jv[sfAccount] = arg.account->human();
|
||||
else
|
||||
jv[sfAccount] = issuer_.human();
|
||||
jv[sfTransactionType] = jss::MPTokenAuthorize;
|
||||
if (arg.id)
|
||||
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||
else
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
jv[sfMPTokenIssuanceID] = to_string(*id_);
|
||||
}
|
||||
if (arg.holder)
|
||||
jv[sfHolder] = arg.holder->human();
|
||||
if (!arg.id && !id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
Json::Value jv = authorizejv({
|
||||
.account = arg.account ? arg.account : issuer_,
|
||||
.holder = arg.holder,
|
||||
.id = arg.id ? arg.id : id_,
|
||||
});
|
||||
if (auto const result = submit(arg, jv); result == tesSUCCESS)
|
||||
{
|
||||
// Issuer authorizes
|
||||
@@ -201,24 +313,34 @@ MPTTester::authorize(MPTAuthorize const& arg)
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::set(MPTSet const& arg)
|
||||
MPTTester::authorizeHolders(Holders const& holders)
|
||||
{
|
||||
for (auto const& holder : holders)
|
||||
{
|
||||
authorize({.account = holder});
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value
|
||||
MPTTester::setjv(MPTSet const& arg)
|
||||
{
|
||||
Json::Value jv;
|
||||
if (arg.account)
|
||||
jv[sfAccount] = arg.account->human();
|
||||
else
|
||||
jv[sfAccount] = issuer_.human();
|
||||
jv[sfTransactionType] = jss::MPTokenIssuanceSet;
|
||||
if (arg.id)
|
||||
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||
else
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
jv[sfMPTokenIssuanceID] = to_string(*id_);
|
||||
}
|
||||
if (!arg.account || !arg.id)
|
||||
Throw<std::runtime_error>("MPTTester::setjv: issuer/id is not set");
|
||||
jv[sfAccount] = arg.account->human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||
if (arg.holder)
|
||||
jv[sfHolder] = arg.holder->human();
|
||||
{
|
||||
std::visit(
|
||||
[&jv]<typename T>(T const& holder) {
|
||||
if constexpr (std::is_same_v<T, Account>)
|
||||
jv[sfHolder] = holder.human();
|
||||
else if constexpr (std::is_same_v<T, AccountID>)
|
||||
jv[sfHolder] = toBase58(holder);
|
||||
},
|
||||
*arg.holder);
|
||||
}
|
||||
|
||||
if (arg.delegate)
|
||||
jv[sfDelegate] = arg.delegate->human();
|
||||
if (arg.domainID)
|
||||
@@ -229,7 +351,27 @@ MPTTester::set(MPTSet const& arg)
|
||||
jv[sfTransferFee] = *arg.transferFee;
|
||||
if (arg.metadata)
|
||||
jv[sfMPTokenMetadata] = strHex(*arg.metadata);
|
||||
if (submit(arg, jv) == tesSUCCESS && (arg.flags || arg.mutableFlags))
|
||||
jv[sfTransactionType] = jss::MPTokenIssuanceSet;
|
||||
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::set(MPTSet const& arg)
|
||||
{
|
||||
if (!arg.id && !id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
Json::Value jv = setjv(
|
||||
{.account = arg.account ? arg.account : issuer_,
|
||||
.holder = arg.holder,
|
||||
.id = arg.id ? arg.id : id_,
|
||||
.mutableFlags = arg.mutableFlags,
|
||||
.transferFee = arg.transferFee,
|
||||
.metadata = arg.metadata,
|
||||
.delegate = arg.delegate,
|
||||
.domainID = arg.domainID});
|
||||
if (submit(arg, jv) == tesSUCCESS &&
|
||||
(arg.flags.value_or(0) || arg.mutableFlags))
|
||||
{
|
||||
auto require = [&](std::optional<Account> const& holder,
|
||||
bool unchanged) {
|
||||
@@ -281,8 +423,9 @@ MPTTester::set(MPTSet const& arg)
|
||||
};
|
||||
if (arg.account)
|
||||
require(std::nullopt, arg.holder.has_value());
|
||||
if (arg.holder)
|
||||
require(*arg.holder, false);
|
||||
if (auto const account =
|
||||
(arg.holder ? std::get_if<Account>(&(*arg.holder)) : nullptr))
|
||||
require(*account, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,6 +598,13 @@ MPTTester::mpt(std::int64_t amount) const
|
||||
return ripple::test::jtx::MPT(issuer_.name(), *id_)(amount);
|
||||
}
|
||||
|
||||
MPTTester::operator Asset() const
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
return Asset(*id_);
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
MPTTester::getBalance(Account const& account) const
|
||||
{
|
||||
@@ -493,6 +643,12 @@ MPTTester::operator[](std::string const& name) const
|
||||
return MPT(name, issuanceID());
|
||||
}
|
||||
|
||||
PrettyAmount
|
||||
MPTTester::operator()(std::uint64_t amount) const
|
||||
{
|
||||
return MPT("", issuanceID())(amount);
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
15
src/test/jtx/impl/testline.cpp
Normal file
15
src/test/jtx/impl/testline.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <test/jtx/testline.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
void
|
||||
testline::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.testLine = line_;
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
Reference in New Issue
Block a user