Compare commits

...

2 Commits

Author SHA1 Message Date
Vito
28d891c125 failing test 2025-07-30 13:19:25 +02:00
Denis Angell
9c3effacb6 add test 2025-07-29 16:59:30 +02:00
2 changed files with 245 additions and 10 deletions

View File

@@ -44,6 +44,8 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <ostream>
namespace ripple {
namespace test {
@@ -59,7 +61,7 @@ class Loan_test : public beast::unit_test::suite
static constexpr auto const coverRateMinParameter =
percentageToTenthBips(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;
std::string const iouCurrency{"IOU"};
@@ -118,8 +120,12 @@ class Loan_test : public beast::unit_test::suite
{
jtx::PrettyAsset asset;
uint256 brokerID;
BrokerInfo(jtx::PrettyAsset const& asset_, uint256 const& brokerID_)
: asset(asset_), brokerID(brokerID_)
uint256 vaultID;
BrokerInfo(
jtx::PrettyAsset const& asset_,
uint256 const& brokerID_,
uint256 const& vaultID_)
: asset(asset_), brokerID(brokerID_), vaultID(vaultID_)
{
}
};
@@ -367,7 +373,7 @@ class Loan_test : public beast::unit_test::suite
using namespace loanBroker;
env(set(lender, vaultKeylet.key),
data(testData),
managementFeeRate(TenthBips16(100)),
managementFeeRate(TenthBips16(0)),
debtMaximum(debtMaximumValue),
coverRateMinimum(TenthBips32(coverRateMinParameter)),
coverRateLiquidation(TenthBips32(percentageToTenthBips(25))));
@@ -376,7 +382,7 @@ class Loan_test : public beast::unit_test::suite
env.close();
return {asset, keylet.key};
return {asset, keylet.key, vaultKeylet.key};
}
void
@@ -1889,17 +1895,244 @@ class Loan_test : public beast::unit_test::suite
}
}
void
testLifecycleSlow()
{
testcase("Lifecycle Slow");
using namespace jtx;
using namespace std::chrono_literals;
using d = NetClock::duration;
using tp = NetClock::time_point;
Env env(*this, all);
Account const issuer{"issuer"};
Account const lender{"lender"};
Account const depositor{"depositor"};
Account const exploiter{"exploiter"};
Account const borrower{"borrower"};
env.fund(
XRP(100'000),
issuer,
noripple(lender, depositor, exploiter, borrower));
env.close();
using namespace jtx;
using namespace loan;
using namespace std::chrono_literals;
PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
BrokerInfo broker = createVaultAndBroker(env, xrpAsset, lender);
std::cout << "Broker ID: " << broker.brokerID << std::endl;
std::cout << "Vault ID: " << broker.vaultID << std::endl;
Vault vault{env};
auto const deposit = broker.asset(vaultDeposit);
env(vault.deposit(
{.depositor = depositor, .id = broker.vaultID, .amount = deposit}));
env.close();
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;
};
std::cout << "Vault Created" << std::endl;
logVaultStatus();
std::cout << std::endl;
Number const loanAmount{1, 3}; // 1000
auto const loanSetFee = fee(env.current()->fees().base * 2);
Number const principalRequest = broker.asset(loanAmount).value();
auto const startDate = env.now() + 3600s;
// Loan with no fees
auto const originationFee = broker.asset(0).value();
auto const serviceFee = broker.asset(0).value();
auto const lateFee = broker.asset(0).value();
auto const closeFee = broker.asset(0).value();
auto const interest = percentageToTenthBips(10);
auto const payments = 12;
auto const _interval = 600;
auto const grace = 60;
// Use the defined values
auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
auto loanSequence = brokerSle->at(sfLoanSequence);
auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
auto getLoanState = [&](auto const& keylet) {
auto loanSle = env.le(keylet);
LoanState state{
.previousPaymentDate = loanSle->at(sfPreviousPaymentDate),
.startDate = tp{d{loanSle->at(sfStartDate)}},
.nextPaymentDate = loanSle->at(sfNextPaymentDueDate),
.paymentRemaining = loanSle->at(sfPaymentRemaining),
.assetsAvailable = loanSle->at(sfAssetsAvailable),
.principalRequested = loanSle->at(sfPrincipalRequested),
.principalOutstanding = loanSle->at(sfPrincipalOutstanding),
.flags = loanSle->at(sfFlags),
.paymentInterval = loanSle->at(sfPaymentInterval),
};
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);
env.close(state.startDate + 5s);
STAmount const drawAmount{broker.asset, state.assetsAvailable};
env(draw(borrower, loanKeylet.key, drawAmount));
env.close(state.startDate + 20s);
PrettyAmount adjustment = broker.asset(0);
if (broker.asset.raw().native())
{
adjustment = env.current()->fees().base;
}
Number const interval = state.paymentInterval;
auto const periodicRate =
interval * Number(10, -2) / (365 * 24 * 60 * 60);
std::cout << "Starting Loan Payments" << std::endl;
while (true)
{
auto state = getLoanState(loanKeylet);
if (state.paymentRemaining == 0)
break;
STAmount const principalRequestedAmount{
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};
Number const rawInterest = state.paymentRemaining == 1
? rawPeriodicPayment - state.principalOutstanding
: state.principalOutstanding * periodicRate;
STAmount const interest = roundToReference(
STAmount{broker.asset, rawInterest}, principalRequestedAmount);
// auto const rawPrincipal = rawPeriodicPayment - rawInterest;
auto const principal = roundToReference(
STAmount{broker.asset, periodicPayment - interest},
principalRequestedAmount);
auto const borrowerBalanceBeforePayment =
env.balance(borrower, broker.asset);
// Make the payment
env(pay(borrower, loanKeylet.key, transactionAmount));
env.close();
std::cout << "Payment Remaining: " << state.paymentRemaining
<< std::endl;
std::cout << "Loan Payment: " << totalDue << std::endl;
logLoanStatus(loanKeylet);
logVaultStatus();
std::cout << std::endl;
}
BEAST_EXPECT(getLoanState(loanKeylet).paymentRemaining == 0);
BEAST_EXPECT(getLoanState(loanKeylet).principalOutstanding == 0);
auto const actualLoanTotalValue = vaultSle->at(sfAssetsTotal);
BEAST_EXPECT(expectedLoanTotalValue == actualLoanTotalValue);
BEAST_EXPECT(
vaultSle->at(sfAssetsAvailable) == vaultSle->at(sfAssetsTotal));
}
public:
void
run() override
{
testDisabled();
testSelfLoan();
testLifecycle();
// testDisabled();
// testSelfLoan();
// testLifecycle();
testLifecycleSlow();
}
};
BEAST_DEFINE_TESTSUITE(Loan, tx, ripple);
BEAST_DEFINE_TESTSUITE(Loan, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -228,7 +228,9 @@ Env::balance(Account const& account, MPTIssue const& mptIssue) const
return {STAmount(mptIssue, 0), account.name()};
STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)};
return {amount, lookup(issuer).name()};
std::cout << "MPT Amount: " << amount.getText() << " Account: " << account.name() << std::endl;
// return {amount, lookup(account).name()};
return {STAmount(mptIssue, 0), ""};
}
}