mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Compare commits
1 Commits
dangell/lo
...
tapanito/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28d891c125 |
@@ -44,6 +44,8 @@
|
|||||||
#include <xrpl/protocol/TxFlags.h>
|
#include <xrpl/protocol/TxFlags.h>
|
||||||
#include <xrpl/protocol/jss.h>
|
#include <xrpl/protocol/jss.h>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
@@ -59,7 +61,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
static constexpr auto const coverRateMinParameter =
|
static constexpr auto const coverRateMinParameter =
|
||||||
percentageToTenthBips(10);
|
percentageToTenthBips(10);
|
||||||
static constexpr auto const maxCoveredLoanValue = 1000 * 100 / 10;
|
static constexpr auto const maxCoveredLoanValue = 1000 * 100 / 10;
|
||||||
static constexpr auto const vaultDeposit = 50'000;
|
static constexpr auto const vaultDeposit = 5'000;
|
||||||
static constexpr auto const debtMaximumParameter = 25'000;
|
static constexpr auto const debtMaximumParameter = 25'000;
|
||||||
std::string const iouCurrency{"IOU"};
|
std::string const iouCurrency{"IOU"};
|
||||||
|
|
||||||
@@ -119,7 +121,10 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
jtx::PrettyAsset asset;
|
jtx::PrettyAsset asset;
|
||||||
uint256 brokerID;
|
uint256 brokerID;
|
||||||
uint256 vaultID;
|
uint256 vaultID;
|
||||||
BrokerInfo(jtx::PrettyAsset const& asset_, uint256 const& brokerID_, uint256 const& vaultID_)
|
BrokerInfo(
|
||||||
|
jtx::PrettyAsset const& asset_,
|
||||||
|
uint256 const& brokerID_,
|
||||||
|
uint256 const& vaultID_)
|
||||||
: asset(asset_), brokerID(brokerID_), vaultID(vaultID_)
|
: asset(asset_), brokerID(brokerID_), vaultID(vaultID_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -368,7 +373,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
using namespace loanBroker;
|
using namespace loanBroker;
|
||||||
env(set(lender, vaultKeylet.key),
|
env(set(lender, vaultKeylet.key),
|
||||||
data(testData),
|
data(testData),
|
||||||
managementFeeRate(TenthBips16(100)),
|
managementFeeRate(TenthBips16(0)),
|
||||||
debtMaximum(debtMaximumValue),
|
debtMaximum(debtMaximumValue),
|
||||||
coverRateMinimum(TenthBips32(coverRateMinParameter)),
|
coverRateMinimum(TenthBips32(coverRateMinParameter)),
|
||||||
coverRateLiquidation(TenthBips32(percentageToTenthBips(25))));
|
coverRateLiquidation(TenthBips32(percentageToTenthBips(25))));
|
||||||
@@ -1907,7 +1912,10 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
Account const exploiter{"exploiter"};
|
Account const exploiter{"exploiter"};
|
||||||
Account const borrower{"borrower"};
|
Account const borrower{"borrower"};
|
||||||
|
|
||||||
env.fund(XRP(100'000), issuer, noripple(lender, depositor, exploiter, borrower));
|
env.fund(
|
||||||
|
XRP(100'000),
|
||||||
|
issuer,
|
||||||
|
noripple(lender, depositor, exploiter, borrower));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
@@ -1920,121 +1928,45 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
Vault vault{env};
|
Vault vault{env};
|
||||||
auto const deposit = broker.asset(vaultDeposit);
|
auto const deposit = broker.asset(vaultDeposit);
|
||||||
env(vault.deposit({.depositor = depositor, .id = broker.vaultID, .amount = deposit}));
|
env(vault.deposit(
|
||||||
|
{.depositor = depositor, .id = broker.vaultID, .amount = deposit}));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// {
|
auto logVaultStatus = [&]() {
|
||||||
// Json::Value params;
|
auto const vaultKeylet = keylet::vault(broker.vaultID);
|
||||||
// params[jss::ledger_index] = env.current()->seq() - 1;
|
auto vaultSle = env.le(vaultKeylet);
|
||||||
// params[jss::transactions] = true;
|
std::cout << "Assets Available: " << vaultSle->at(sfAssetsAvailable)
|
||||||
// params[jss::expand] = true;
|
<< std::endl;
|
||||||
// auto const jrr = env.rpc("json", "ledger", to_string(params));
|
std::cout << "Assets Total: " << vaultSle->at(sfAssetsTotal)
|
||||||
// std::cout << jrr << std::endl;
|
<< std::endl;
|
||||||
// }
|
};
|
||||||
|
|
||||||
// {
|
std::cout << "Vault Created" << std::endl;
|
||||||
// auto const withdraw = broker.asset(vaultDeposit);
|
|
||||||
// env(vault.withdraw({.depositor = depositor, .id = broker.vaultID, .amount = withdraw}));
|
|
||||||
// env.close();
|
|
||||||
|
|
||||||
// Json::Value params;
|
logVaultStatus();
|
||||||
// params[jss::ledger_index] = env.current()->seq() - 1;
|
std::cout << std::endl;
|
||||||
// params[jss::transactions] = true;
|
|
||||||
// params[jss::expand] = true;
|
|
||||||
// auto const jrr = env.rpc("json", "ledger", to_string(params));
|
|
||||||
// std::cout << jrr << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// {
|
|
||||||
// auto const withdraw = broker.asset(vaultDeposit);
|
|
||||||
// env(vault.withdraw({.depositor = lender, .id = broker.vaultID, .amount = withdraw}));
|
|
||||||
// env.close();
|
|
||||||
|
|
||||||
// Json::Value params;
|
|
||||||
// params[jss::ledger_index] = env.current()->seq() - 1;
|
|
||||||
// params[jss::transactions] = true;
|
|
||||||
// params[jss::expand] = true;
|
|
||||||
// auto const jrr = env.rpc("json", "ledger", to_string(params));
|
|
||||||
// std::cout << jrr << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Number const loanAmount{1, 3}; // 1000
|
Number const loanAmount{1, 3}; // 1000
|
||||||
int interestExponent = 0; // 1.2%
|
|
||||||
auto const loanSetFee = fee(env.current()->fees().base * 2);
|
auto const loanSetFee = fee(env.current()->fees().base * 2);
|
||||||
Number const principalRequest = broker.asset(loanAmount).value();
|
Number const principalRequest = broker.asset(loanAmount).value();
|
||||||
auto const startDate = env.now() + 3600s;
|
auto const startDate = env.now() + 3600s;
|
||||||
auto const originationFee = broker.asset(1).value();
|
|
||||||
auto const serviceFee = broker.asset(2).value();
|
|
||||||
auto const lateFee = broker.asset(3).value();
|
|
||||||
auto const closeFee = broker.asset(4).value();
|
|
||||||
|
|
||||||
auto applyExponent = [interestExponent,
|
// Loan with no fees
|
||||||
this](TenthBips32 value) mutable {
|
auto const originationFee = broker.asset(0).value();
|
||||||
BEAST_EXPECT(value > TenthBips32(0));
|
auto const serviceFee = broker.asset(0).value();
|
||||||
while (interestExponent > 0)
|
auto const lateFee = broker.asset(0).value();
|
||||||
{
|
auto const closeFee = broker.asset(0).value();
|
||||||
auto const oldValue = value;
|
|
||||||
value *= 10;
|
|
||||||
--interestExponent;
|
|
||||||
BEAST_EXPECT(value / 10 == oldValue);
|
|
||||||
}
|
|
||||||
while (interestExponent < 0)
|
|
||||||
{
|
|
||||||
auto const oldValue = value;
|
|
||||||
value /= 10;
|
|
||||||
++interestExponent;
|
|
||||||
BEAST_EXPECT(value * 10 == oldValue);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto const overFee = applyExponent(percentageToTenthBips(5) / 10);
|
auto const interest = percentageToTenthBips(10);
|
||||||
auto const interest = applyExponent(percentageToTenthBips(12));
|
auto const payments = 12;
|
||||||
// 2.4%
|
|
||||||
auto const lateInterest = applyExponent(percentageToTenthBips(24) / 10);
|
|
||||||
auto const closeInterest = applyExponent(percentageToTenthBips(36) / 10);
|
|
||||||
auto const overpaymentInterest = applyExponent(percentageToTenthBips(48) / 10);
|
|
||||||
auto const total = 12;
|
|
||||||
auto const _interval = 600;
|
auto const _interval = 600;
|
||||||
auto const grace = 60;
|
auto const grace = 60;
|
||||||
|
|
||||||
// Use the defined values
|
// Use the defined values
|
||||||
auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
|
auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
|
||||||
auto loanSequence = brokerSle->at(sfLoanSequence);
|
auto loanSequence = brokerSle->at(sfLoanSequence);
|
||||||
auto createJtx = env.jt(
|
|
||||||
set(borrower, broker.brokerID, principalRequest, startDate, tfLoanOverpayment),
|
|
||||||
sig(sfCounterpartySignature, lender),
|
|
||||||
loanOriginationFee(originationFee),
|
|
||||||
loanServiceFee(serviceFee),
|
|
||||||
latePaymentFee(lateFee),
|
|
||||||
closePaymentFee(closeFee),
|
|
||||||
overpaymentFee(overFee),
|
|
||||||
interestRate(interest),
|
|
||||||
lateInterestRate(lateInterest),
|
|
||||||
closeInterestRate(closeInterest),
|
|
||||||
overpaymentInterestRate(overpaymentInterest),
|
|
||||||
paymentTotal(total),
|
|
||||||
paymentInterval(_interval),
|
|
||||||
gracePeriod(grace),
|
|
||||||
fee(loanSetFee));
|
|
||||||
// Successfully create a Loan
|
|
||||||
env(createJtx);
|
|
||||||
env.close();
|
|
||||||
|
|
||||||
{
|
|
||||||
Json::Value params;
|
|
||||||
params[jss::ledger_index] = env.current()->seq() - 1;
|
|
||||||
params[jss::transactions] = true;
|
|
||||||
params[jss::expand] = true;
|
|
||||||
auto const jrr = env.rpc("json", "ledger", to_string(params));
|
|
||||||
std::cout << jrr << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
|
auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
|
||||||
auto logLoanStatus = [&](auto const& keylet) {
|
|
||||||
auto loanSle = env.le(keylet);
|
|
||||||
std::cout << "Principal Outstanding: " << loanSle->at(sfPrincipalOutstanding) << std::endl;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto getLoanState = [&](auto const& keylet) {
|
auto getLoanState = [&](auto const& keylet) {
|
||||||
auto loanSle = env.le(keylet);
|
auto loanSle = env.le(keylet);
|
||||||
@@ -2052,24 +1984,76 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto logLoanStatus = [&](auto const& keylet) {
|
||||||
|
auto state = getLoanState(keylet);
|
||||||
|
std::cout << "Loan Outstanding Value: "
|
||||||
|
<< detail::loanTotalValueOutstanding(
|
||||||
|
broker.asset,
|
||||||
|
state.principalRequested,
|
||||||
|
state.principalOutstanding,
|
||||||
|
percentageToTenthBips(10),
|
||||||
|
state.paymentInterval,
|
||||||
|
state.paymentRemaining)
|
||||||
|
<< std::endl;
|
||||||
|
std::cout << "Loan Outstanding Interest: "
|
||||||
|
<< detail::loanTotalInterestOutstanding(
|
||||||
|
state.principalOutstanding,
|
||||||
|
detail::loanTotalValueOutstanding(
|
||||||
|
broker.asset,
|
||||||
|
state.principalRequested,
|
||||||
|
state.principalOutstanding,
|
||||||
|
percentageToTenthBips(10),
|
||||||
|
state.paymentInterval,
|
||||||
|
state.paymentRemaining))
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
std::cout << "Principal Outstanding: " << state.principalOutstanding
|
||||||
|
<< std::endl;
|
||||||
|
};
|
||||||
|
std::cout << "Creating Loan.." << std::endl;
|
||||||
|
|
||||||
|
auto createJtx = env.jt(
|
||||||
|
set(borrower,
|
||||||
|
broker.brokerID,
|
||||||
|
principalRequest,
|
||||||
|
startDate,
|
||||||
|
tfLoanOverpayment),
|
||||||
|
sig(sfCounterpartySignature, lender),
|
||||||
|
loanOriginationFee(0),
|
||||||
|
loanServiceFee(0),
|
||||||
|
latePaymentFee(0),
|
||||||
|
closePaymentFee(0),
|
||||||
|
overpaymentFee(percentageToTenthBips(0)),
|
||||||
|
interestRate(interest),
|
||||||
|
lateInterestRate(percentageToTenthBips(0)),
|
||||||
|
closeInterestRate(percentageToTenthBips(0)),
|
||||||
|
overpaymentInterestRate(percentageToTenthBips(0)),
|
||||||
|
paymentTotal(payments),
|
||||||
|
paymentInterval(_interval),
|
||||||
|
gracePeriod(grace),
|
||||||
|
fee(loanSetFee));
|
||||||
|
// Successfully create a Loan
|
||||||
|
env(createJtx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
std::cout << "Loan State" << std::endl;
|
||||||
|
logLoanStatus(loanKeylet);
|
||||||
|
|
||||||
|
std::cout << "Vault State after Loan" << std::endl;
|
||||||
|
logVaultStatus();
|
||||||
|
|
||||||
|
auto const vaultKeylet = keylet::vault(broker.vaultID);
|
||||||
|
auto vaultSle = env.le(vaultKeylet);
|
||||||
|
auto const expectedLoanTotalValue = vaultSle->at(sfAssetsTotal);
|
||||||
|
|
||||||
auto state = getLoanState(loanKeylet);
|
auto state = getLoanState(loanKeylet);
|
||||||
|
|
||||||
env.close(state.startDate + 5s);
|
env.close(state.startDate + 5s);
|
||||||
|
|
||||||
auto logVaultStatus = [&]() {
|
|
||||||
auto const vaultKeylet = keylet::vault(broker.vaultID);
|
|
||||||
auto vaultSle = env.le(vaultKeylet);
|
|
||||||
std::cout << "Assets Available: " << vaultSle->at(sfAssetsAvailable) << std::endl;
|
|
||||||
std::cout << "Assets Total: " << vaultSle->at(sfAssetsTotal) << std::endl;
|
|
||||||
};
|
|
||||||
|
|
||||||
STAmount const drawAmount{broker.asset, state.assetsAvailable};
|
STAmount const drawAmount{broker.asset, state.assetsAvailable};
|
||||||
env(draw(borrower, loanKeylet.key, drawAmount));
|
env(draw(borrower, loanKeylet.key, drawAmount));
|
||||||
env.close(state.startDate + 20s);
|
env.close(state.startDate + 20s);
|
||||||
|
|
||||||
logVaultStatus();
|
|
||||||
logLoanStatus(loanKeylet);
|
|
||||||
|
|
||||||
PrettyAmount adjustment = broker.asset(0);
|
PrettyAmount adjustment = broker.asset(0);
|
||||||
if (broker.asset.raw().native())
|
if (broker.asset.raw().native())
|
||||||
{
|
{
|
||||||
@@ -2077,75 +2061,64 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
|
|
||||||
Number const interval = state.paymentInterval;
|
Number const interval = state.paymentInterval;
|
||||||
auto const periodicRate = interval * Number(12, -2) / (365 * 24 * 60 * 60);
|
auto const periodicRate =
|
||||||
|
interval * Number(10, -2) / (365 * 24 * 60 * 60);
|
||||||
|
std::cout << "Starting Loan Payments" << std::endl;
|
||||||
|
|
||||||
while (state.paymentRemaining > 0)
|
while (true)
|
||||||
{
|
{
|
||||||
STAmount const principalRequestedAmount{broker.asset, state.principalRequested};
|
auto state = getLoanState(loanKeylet);
|
||||||
auto const rateFactor = power(1 + periodicRate, state.paymentRemaining);
|
if (state.paymentRemaining == 0)
|
||||||
Number const rawPeriodicPayment = state.principalOutstanding * periodicRate * rateFactor / (rateFactor - 1);
|
break;
|
||||||
STAmount const periodicPayment = roundToReference(STAmount{broker.asset, rawPeriodicPayment}, principalRequestedAmount);
|
|
||||||
STAmount const totalDue = roundToReference(periodicPayment + broker.asset(2), principalRequestedAmount);
|
STAmount const principalRequestedAmount{
|
||||||
STAmount const transactionAmount = STAmount{broker.asset, totalDue} + broker.asset(10);
|
broker.asset, state.principalRequested};
|
||||||
|
auto const rateFactor =
|
||||||
|
power(1 + periodicRate, state.paymentRemaining);
|
||||||
|
Number const rawPeriodicPayment = state.principalOutstanding *
|
||||||
|
periodicRate * rateFactor / (rateFactor - 1);
|
||||||
|
STAmount const periodicPayment = roundToReference(
|
||||||
|
STAmount{broker.asset, rawPeriodicPayment},
|
||||||
|
principalRequestedAmount);
|
||||||
|
STAmount const totalDue = roundToReference(
|
||||||
|
periodicPayment + broker.asset(2), principalRequestedAmount);
|
||||||
|
STAmount const transactionAmount = STAmount{broker.asset, totalDue};
|
||||||
|
|
||||||
auto const totalDueAmount = STAmount{broker.asset, totalDue};
|
auto const totalDueAmount = STAmount{broker.asset, totalDue};
|
||||||
Number const rawInterest = state.paymentRemaining == 1 ? rawPeriodicPayment - state.principalOutstanding : state.principalOutstanding * periodicRate;
|
Number const rawInterest = state.paymentRemaining == 1
|
||||||
STAmount const interest = roundToReference(STAmount{broker.asset, rawInterest}, principalRequestedAmount);
|
? rawPeriodicPayment - state.principalOutstanding
|
||||||
|
: state.principalOutstanding * periodicRate;
|
||||||
|
STAmount const interest = roundToReference(
|
||||||
|
STAmount{broker.asset, rawInterest}, principalRequestedAmount);
|
||||||
// auto const rawPrincipal = rawPeriodicPayment - rawInterest;
|
// auto const rawPrincipal = rawPeriodicPayment - rawInterest;
|
||||||
auto const principal = roundToReference(STAmount{broker.asset, periodicPayment - interest}, principalRequestedAmount);
|
auto const principal = roundToReference(
|
||||||
auto const borrowerBalanceBeforePayment = env.balance(borrower, broker.asset);
|
STAmount{broker.asset, periodicPayment - interest},
|
||||||
|
principalRequestedAmount);
|
||||||
|
auto const borrowerBalanceBeforePayment =
|
||||||
|
env.balance(borrower, broker.asset);
|
||||||
|
|
||||||
// Make the payment
|
// Make the payment
|
||||||
env(pay(borrower, loanKeylet.key, transactionAmount));
|
env(pay(borrower, loanKeylet.key, transactionAmount));
|
||||||
env.close();
|
env.close();
|
||||||
// Need to account for fees if the loan is in XRP
|
|
||||||
adjustment = broker.asset(0);
|
|
||||||
if (broker.asset.raw().native())
|
|
||||||
{
|
|
||||||
adjustment = env.current()->fees().base;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the result
|
std::cout << "Payment Remaining: " << state.paymentRemaining
|
||||||
auto const borrowerBalance =
|
<< std::endl;
|
||||||
env.balance(borrower, broker.asset);
|
|
||||||
auto const expectedBalance = borrowerBalanceBeforePayment -
|
|
||||||
totalDueAmount - adjustment;
|
|
||||||
|
|
||||||
--state.paymentRemaining;
|
std::cout << "Loan Payment: " << totalDue << std::endl;
|
||||||
state.previousPaymentDate = state.nextPaymentDate;
|
|
||||||
state.nextPaymentDate += state.paymentInterval;
|
|
||||||
state.principalOutstanding -= principal;
|
|
||||||
std::cout << "Payment Remaining: " << state.paymentRemaining << std::endl;
|
|
||||||
std::cout << "Principal Remaining: " << state.principalOutstanding << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
logVaultStatus();
|
|
||||||
logLoanStatus(loanKeylet);
|
logLoanStatus(loanKeylet);
|
||||||
|
logVaultStatus();
|
||||||
|
|
||||||
// // Loan is paid off
|
std::cout << std::endl;
|
||||||
BEAST_EXPECT(state.paymentRemaining == 0);
|
|
||||||
BEAST_EXPECT(state.principalOutstanding == 0);
|
|
||||||
|
|
||||||
auto const MPT = MPTIssue{MPTID{"0000000107AC772C89DFE65E093629FEA501C68AD3119454"}};
|
|
||||||
{
|
|
||||||
STAmount mpt1{MPT, UINT64_C(50000000000)};
|
|
||||||
STAmount mpt2{MPT, UINT64_C(50000000000)};
|
|
||||||
env(vault.withdraw({.depositor = depositor, .id = broker.vaultID, .amount = mpt1}));
|
|
||||||
env(vault.withdraw({.depositor = lender, .id = broker.vaultID, .amount = mpt2}));
|
|
||||||
env.close();
|
|
||||||
|
|
||||||
Json::Value params;
|
|
||||||
params[jss::ledger_index] = env.current()->seq() - 1;
|
|
||||||
params[jss::transactions] = true;
|
|
||||||
params[jss::expand] = true;
|
|
||||||
auto const jrr = env.rpc("json", "ledger", to_string(params));
|
|
||||||
std::cout << jrr << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Lender XRP Balance: " << env.balance(lender, broker.asset) << std::endl;
|
BEAST_EXPECT(getLoanState(loanKeylet).paymentRemaining == 0);
|
||||||
BEAST_EXPECT(env.balance(lender, broker.asset) == XRP(98978.905268));
|
BEAST_EXPECT(getLoanState(loanKeylet).principalOutstanding == 0);
|
||||||
env.balance(lender, MPT);
|
|
||||||
std::cout << "Depositor XRP Balance: " << env.balance(depositor, broker.asset) << std::endl;
|
auto const actualLoanTotalValue = vaultSle->at(sfAssetsTotal);
|
||||||
BEAST_EXPECT(env.balance(depositor, broker.asset) == XRP(100002.969921));
|
|
||||||
env.balance(depositor, MPT);
|
BEAST_EXPECT(expectedLoanTotalValue == actualLoanTotalValue);
|
||||||
|
BEAST_EXPECT(
|
||||||
|
vaultSle->at(sfAssetsAvailable) == vaultSle->at(sfAssetsTotal));
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user