mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-24 07:30:30 +00:00
Fix invariant tests
This commit is contained in:
@@ -479,12 +479,9 @@ ValidVault::finalize(
|
||||
// pending loan (Borrower present, CounterpartySignature absent).
|
||||
if (afterVault.assetsReserved != beforeVault.assetsReserved)
|
||||
{
|
||||
bool const pendingLoanSet = txnType == ttLOAN_SET &&
|
||||
tx.isFieldPresent(sfBorrower) &&
|
||||
tx.isFieldPresent(sfStartDate) &&
|
||||
!tx.isFieldPresent(sfCounterpartySignature) &&
|
||||
bool const pendingLoanSet = txnType == ttLOAN_SET && tx.isFieldPresent(sfBorrower) &&
|
||||
tx.isFieldPresent(sfStartDate) && !tx.isFieldPresent(sfCounterpartySignature) &&
|
||||
!tx.isFieldPresent(sfCounterparty);
|
||||
!tx.isFieldPresent(sfCounterpartySignature);
|
||||
if (txnType != ttLOAN_DELETE && txnType != ttLOAN_ACCEPT && !pendingLoanSet)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: vault AssetsReserved changed "
|
||||
@@ -1438,6 +1435,107 @@ ValidVault::finalize(
|
||||
return result;
|
||||
}
|
||||
|
||||
case ttLOAN_ACCEPT: {
|
||||
bool result = true;
|
||||
|
||||
XRPL_ASSERT(
|
||||
!beforeVault_.empty(),
|
||||
"xrpl::ValidVault::finalize : loan accept updated a vault");
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
|
||||
// Accepting a pending loan disburses the reserved principal
|
||||
// from the vault pseudo-account to the borrower (and the
|
||||
// origination fee, if any, to the broker owner) and releases it
|
||||
// from the reserved bucket. The assets available and assets
|
||||
// outstanding were already settled when the pending loan was
|
||||
// created, so they must not move now.
|
||||
auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
||||
if (!maybeVaultDeltaAssets)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: loan accept must change vault balance";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
// A loan accept modifies exactly the loan being accepted; its
|
||||
// principal outstanding is the amount disbursed and released.
|
||||
if (afterLoan_.size() != 1 || beforeLoan_.size() != 1 ||
|
||||
afterLoan_[0].key != beforeLoan_[0].key)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: loan accept must modify exactly one "
|
||||
"loan";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
auto const& loan = beforeLoan_[0];
|
||||
|
||||
// Get the posterior scale to round calculations to
|
||||
auto const minScale = computeVaultMinScale(*maybeVaultDeltaAssets, view.rules());
|
||||
|
||||
auto const principalDelta =
|
||||
roundToAsset(vaultAsset, -loan.principalOutstanding, minScale);
|
||||
|
||||
// The vault (pseudo-account) balance must fall by exactly the
|
||||
// disbursed principal.
|
||||
auto const vaultDeltaAssets =
|
||||
roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale);
|
||||
if (vaultDeltaAssets != principalDelta)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: loan accept must decrease vault "
|
||||
"balance by the principal outstanding";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// The reserved principal (held back when the pending loan was
|
||||
// created) must be released by exactly the disbursed principal.
|
||||
auto const assetsReservedDelta = roundToAsset(
|
||||
vaultAsset, afterVault.assetsReserved - beforeVault.assetsReserved, minScale);
|
||||
if (assetsReservedDelta != principalDelta)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: loan accept must decrease assets "
|
||||
"reserved by the principal outstanding";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Accepting a loan neither adds to nor removes from the pool
|
||||
// tracked by assets available.
|
||||
auto const assetAvailableDelta = roundToAsset(
|
||||
vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale);
|
||||
if (assetAvailableDelta != kZero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: loan accept must not change assets "
|
||||
"available";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Likewise the interest booked at loan creation stands: assets
|
||||
// outstanding must not move on accept.
|
||||
auto const assetsTotalDelta = roundToAsset(
|
||||
vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale);
|
||||
if (assetsTotalDelta != kZero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: loan accept must not change assets "
|
||||
"outstanding";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// A loan accept neither mints nor burns vault shares.
|
||||
if (beforeShares && updatedShares &&
|
||||
beforeShares->sharesTotal != updatedShares->sharesTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: loan accept must not change shares "
|
||||
"outstanding";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type");
|
||||
|
||||
@@ -4004,15 +4004,37 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
Account const a1{"A1"};
|
||||
Account const a2{"A2"};
|
||||
env.fund(XRP(1000), a1, a2);
|
||||
BEAST_EXPECT(precloseXrp(a1, a2, env));
|
||||
// Create a real vault so a LoanAccept - which must modify the
|
||||
// vault it lends from - can apply a self-consistent vault delta
|
||||
// alongside the loan mutation under test.
|
||||
Vault const vault{env};
|
||||
auto [createTx, vaultKeylet] = vault.create({.owner = a1, .asset = xrpIssue()});
|
||||
env(createTx);
|
||||
env(vault.deposit({.depositor = a1, .id = vaultKeylet.key, .amount = XRP(10)}));
|
||||
env.close();
|
||||
|
||||
// The disbursed principal shared by the loan and the vault
|
||||
// delta for every LoanAccept case below.
|
||||
Number const principal{100};
|
||||
|
||||
OpenView ov{*env.current()};
|
||||
auto const vaultKeylet = keylet::vault(a1.id(), ov.seq());
|
||||
auto const loanKeylet = keylet::loan(vaultKeylet.key, 1);
|
||||
|
||||
// Seed the vault with reserved principal (as if a pending loan
|
||||
// had already set it aside) so LoanAccept can release it.
|
||||
AccountID vaultPseudo;
|
||||
{
|
||||
auto const sleVaultBase = ov.read(vaultKeylet);
|
||||
if (!BEAST_EXPECT(sleVaultBase))
|
||||
return;
|
||||
vaultPseudo = sleVaultBase->getAccountID(sfAccount);
|
||||
auto sleVault = std::make_shared<SLE>(*sleVaultBase);
|
||||
sleVault->at(sfAssetsReserved) = principal;
|
||||
ov.rawReplace(sleVault);
|
||||
}
|
||||
{
|
||||
auto sleLoan = std::make_shared<SLE>(loanKeylet);
|
||||
sleLoan->at(sfPrincipalOutstanding) = Number(100);
|
||||
sleLoan->at(sfPrincipalOutstanding) = principal;
|
||||
sleLoan->at(sfTotalValueOutstanding) = Number(150);
|
||||
sleLoan->at(sfManagementFeeOutstanding) = Number(0);
|
||||
sleLoan->at(sfPeriodicPayment) = Number(1);
|
||||
@@ -4038,6 +4060,31 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
mutate(sleLoan);
|
||||
ac.view().update(sleLoan);
|
||||
|
||||
// A LoanAccept must modify the vault it lends from: release the
|
||||
// reserved principal and disburse it from the vault
|
||||
// pseudo-account. The disbursed XRP is credited to a2 so the
|
||||
// XRP-conservation invariant is satisfied.
|
||||
if (tx.getTxnType() == ttLOAN_ACCEPT)
|
||||
{
|
||||
auto sleVault = ac.view().peek(vaultKeylet);
|
||||
auto slePseudo = ac.view().peek(keylet::account(vaultPseudo));
|
||||
auto sleDest = ac.view().peek(keylet::account(a2.id()));
|
||||
if (!BEAST_EXPECT(sleVault && slePseudo && sleDest))
|
||||
return;
|
||||
|
||||
auto reservedProxy = sleVault->at(sfAssetsReserved);
|
||||
reservedProxy -= principal;
|
||||
ac.view().update(sleVault);
|
||||
|
||||
STAmount const disbursed{100};
|
||||
slePseudo->setFieldAmount(
|
||||
sfBalance, slePseudo->getFieldAmount(sfBalance) - disbursed);
|
||||
ac.view().update(slePseudo);
|
||||
sleDest->setFieldAmount(
|
||||
sfBalance, sleDest->getFieldAmount(sfBalance) + disbursed);
|
||||
ac.view().update(sleDest);
|
||||
}
|
||||
|
||||
auto transactor = makeTransactor(ac);
|
||||
if (!BEAST_EXPECT(transactor))
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user