mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-28 17:40:25 +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:
@@ -2535,6 +2535,207 @@ class Batch_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLoan(FeatureBitset features)
|
||||
{
|
||||
testcase("loan");
|
||||
|
||||
bool const lendingBatchEnabled = !std::any_of(
|
||||
Batch::disabledTxTypes.begin(),
|
||||
Batch::disabledTxTypes.end(),
|
||||
[](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; });
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
envconfig(),
|
||||
features | featureSingleAssetVault | featureLendingProtocol |
|
||||
featureMPTokensV1};
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
// For simplicity, lender will be the sole actor for the vault &
|
||||
// brokers.
|
||||
Account const lender{"lender"};
|
||||
// Borrower only wants to borrow
|
||||
Account const borrower{"borrower"};
|
||||
|
||||
// Fund the accounts and trust lines with the same amount so that tests
|
||||
// can use the same values regardless of the asset.
|
||||
env.fund(XRP(100'000), issuer, noripple(lender, borrower));
|
||||
env.close();
|
||||
|
||||
// Just use an XRP asset
|
||||
PrettyAsset const asset{xrpIssue(), 1'000'000};
|
||||
|
||||
Vault vault{env};
|
||||
|
||||
auto const deposit = asset(50'000);
|
||||
auto const debtMaximumValue = asset(25'000).value();
|
||||
auto const coverDepositValue = asset(1000).value();
|
||||
|
||||
auto [tx, vaultKeylet] =
|
||||
vault.create({.owner = lender, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(vaultKeylet));
|
||||
|
||||
env(vault.deposit(
|
||||
{.depositor = lender, .id = vaultKeylet.key, .amount = deposit}));
|
||||
env.close();
|
||||
|
||||
auto const brokerKeylet =
|
||||
keylet::loanbroker(lender.id(), env.seq(lender));
|
||||
|
||||
{
|
||||
using namespace loanBroker;
|
||||
env(set(lender, vaultKeylet.key),
|
||||
managementFeeRate(TenthBips16(100)),
|
||||
debtMaximum(debtMaximumValue),
|
||||
coverRateMinimum(TenthBips32(percentageToTenthBips(10))),
|
||||
coverRateLiquidation(TenthBips32(percentageToTenthBips(25))));
|
||||
|
||||
env(coverDeposit(lender, brokerKeylet.key, coverDepositValue));
|
||||
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
using namespace loan;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto const lenderSeq = env.seq(lender);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
|
||||
auto const loanKeylet = keylet::loan(brokerKeylet.key, 1);
|
||||
{
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
lendingBatchEnabled ? temBAD_SIGNATURE
|
||||
: temINVALID_INNER_BATCH,
|
||||
batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::inner(
|
||||
env.json(
|
||||
set(lender, brokerKeylet.key, asset(1000).value()),
|
||||
// Not allowed to include the counterparty signature
|
||||
sig(sfCounterpartySignature, borrower),
|
||||
sig(none),
|
||||
fee(none),
|
||||
seq(none)),
|
||||
lenderSeq + 1),
|
||||
batch::inner(
|
||||
pay(lender,
|
||||
loanKeylet.key,
|
||||
STAmount{asset, asset(500).value()}),
|
||||
lenderSeq + 2));
|
||||
}
|
||||
{
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
temINVALID_INNER_BATCH,
|
||||
batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::inner(
|
||||
env.json(
|
||||
set(lender, brokerKeylet.key, asset(1000).value()),
|
||||
// Counterparty must be set
|
||||
sig(none),
|
||||
fee(none),
|
||||
seq(none)),
|
||||
lenderSeq + 1),
|
||||
batch::inner(
|
||||
pay(lender,
|
||||
loanKeylet.key,
|
||||
STAmount{asset, asset(500).value()}),
|
||||
lenderSeq + 2));
|
||||
}
|
||||
{
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
lendingBatchEnabled ? temBAD_SIGNER
|
||||
: temINVALID_INNER_BATCH,
|
||||
batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::inner(
|
||||
env.json(
|
||||
set(lender, brokerKeylet.key, asset(1000).value()),
|
||||
// Counterparty must sign the outer transaction
|
||||
counterparty(borrower.id()),
|
||||
sig(none),
|
||||
fee(none),
|
||||
seq(none)),
|
||||
lenderSeq + 1),
|
||||
batch::inner(
|
||||
pay(lender,
|
||||
loanKeylet.key,
|
||||
STAmount{asset, asset(500).value()}),
|
||||
lenderSeq + 2));
|
||||
}
|
||||
{
|
||||
// LoanSet normally charges at least 2x base fee, but since the
|
||||
// signature check is done by the batch, it only charges the
|
||||
// base fee.
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 2);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
lendingBatchEnabled ? TER(tesSUCCESS)
|
||||
: TER(temINVALID_INNER_BATCH),
|
||||
batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::inner(
|
||||
env.json(
|
||||
set(lender, brokerKeylet.key, asset(1000).value()),
|
||||
counterparty(borrower.id()),
|
||||
sig(none),
|
||||
fee(none),
|
||||
seq(none)),
|
||||
lenderSeq + 1),
|
||||
batch::inner(
|
||||
pay(
|
||||
// However, this inner transaction will fail,
|
||||
// because the lender is not allowed to draw the
|
||||
// transaction
|
||||
lender,
|
||||
loanKeylet.key,
|
||||
STAmount{asset, asset(500).value()}),
|
||||
lenderSeq + 2),
|
||||
batch::sig(borrower));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(brokerKeylet));
|
||||
BEAST_EXPECT(!env.le(loanKeylet));
|
||||
{
|
||||
// LoanSet normally charges at least 2x base fee, but since the
|
||||
// signature check is done by the batch, it only charges the
|
||||
// base fee.
|
||||
auto const lenderSeq = env.seq(lender);
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 2);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
lendingBatchEnabled ? TER(tesSUCCESS)
|
||||
: TER(temINVALID_INNER_BATCH),
|
||||
batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::inner(
|
||||
env.json(
|
||||
set(lender, brokerKeylet.key, asset(1000).value()),
|
||||
counterparty(borrower.id()),
|
||||
sig(none),
|
||||
fee(none),
|
||||
seq(none)),
|
||||
lenderSeq + 1),
|
||||
batch::inner(
|
||||
manage(lender, loanKeylet.key, tfLoanImpair),
|
||||
lenderSeq + 2),
|
||||
batch::sig(borrower));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(brokerKeylet));
|
||||
if (auto const sleLoan = env.le(loanKeylet); lendingBatchEnabled
|
||||
? BEAST_EXPECT(sleLoan)
|
||||
: !BEAST_EXPECT(!sleLoan))
|
||||
{
|
||||
BEAST_EXPECT(sleLoan->isFlag(lsfLoanImpaired));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testObjectCreateSequence(FeatureBitset features)
|
||||
{
|
||||
@@ -4128,6 +4329,7 @@ class Batch_test : public beast::unit_test::suite
|
||||
testAccountActivation(features);
|
||||
testAccountSet(features);
|
||||
testAccountDelete(features);
|
||||
testLoan(features);
|
||||
testObjectCreateSequence(features);
|
||||
testObjectCreateTicket(features);
|
||||
testObjectCreate3rdParty(features);
|
||||
|
||||
Reference in New Issue
Block a user