feat: Add invariant checks and invariant check tests for LoanAccept#7822
Conversation
| // fee to the broker owner). | ||
| auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId); | ||
| if (!maybeVaultDeltaAssets) | ||
| if (!maybeVaultDeltaAssets && !isPendingLoan) |
There was a problem hiding this comment.
🟠 Severity: HIGH
When isPendingLoan is true and the vault balance is unchanged, maybeVaultDeltaAssets is nullopt but the guard no longer returns early. The subsequent dereference on line 1140 (*maybeVaultDeltaAssets) and line 1149 (maybeVaultDeltaAssets->delta) is undefined behavior—crashing the node or potentially corrupting memory. Compare with ttLOAN_DELETE at line 1560 which correctly uses value_or().
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Follow the safe-access pattern already used at lines 1227-1229 and 1559-1561 in this file: after the existing early-return guard, add a value_or() call to safely unwrap maybeVaultDeltaAssets, and then use the unwrapped value instead of direct dereferences.
Specifically:
- After the closing brace of the guard block (after line 1137), insert:
auto const vaultDelta = maybeVaultDeltaAssets.value_or(
DeltaInfo{.delta = kZero, .scale = scale(afterVault.assetsTotal, vaultAsset)});- On line 1140, change
computeVaultMinScale(*maybeVaultDeltaAssets, view.rules())tocomputeVaultMinScale(vaultDelta, view.rules()). - On line 1149, change
maybeVaultDeltaAssets->deltatovaultDelta.delta.
This mirrors exactly how ttLOAN_DELETE (line 1560) and ttLOAN_IMPAIR/ttLOAN_UNIMPAIR (line 1228) handle the same optional safely.
| // finalise a loan whose StartDate is still in the future. | ||
| if (lendingV11Enabled && txType == ttLOAN_ACCEPT) | ||
| { | ||
| if (after->getAccountID(sfBorrower) != tx.getAccountID(sfAccount)) |
There was a problem hiding this comment.
⚪ Severity: LOW
The Borrower authorization check reads from after (post-modification state) instead of before (pre-modification state). A buggy or malicious transactor that overwrites sfBorrower to match the submitter during execution would bypass this invariant, defeating its purpose as a last-line-of-defense authorization check.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change after->getAccountID(sfBorrower) to before->getAccountID(sfBorrower) on line 132. The invariant check should read the sfBorrower field from the pre-modification state (before) rather than the post-modification state (after). This ensures that even if a buggy transactor overwrites sfBorrower during execution to match the submitter's account, the invariant will still catch the unauthorized access by comparing against the original, unmodified value. The before variable is already available in scope (the code is inside the if (before) block starting at line 77).
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| if (after->getAccountID(sfBorrower) != tx.getAccountID(sfAccount)) | |
| if (before->getAccountID(sfBorrower) != tx.getAccountID(sfAccount)) |
High Level Overview of Change
This PR adds invariant checks and invariant check tests for LoanAccept with all related fields, flags, and a
LoanAccepttransactor that does nothing.Context of Change
This PR defines what invariant checks should be performed before the actual development starts to ensure everything will work properly.
API Impact
libxrplchange (any change that may affectlibxrplor dependents oflibxrpl)