Our synthetic account has a debt ceiling of 1000 units and already owes 900. A request to receive another 100 units does not fit when the loan also adds an origination fee to the debt. Comparing the requested cash alone against the remaining capacity accepts a transition that breaks the ceiling.

We construct an independent accounting fixture using a fee of 350 basis points. The fee rounds upward to a whole debt unit and is financed into the position. No collateral price or real loan terms appear in this example and the calculation does not recommend a borrowing decision.

Separate cash flow from the obligation

The borrower receives principal. The account owes principal plus any financed fee. If the fee is deducted from the outgoing cash instead then the transition follows a different rule and needs different interface labels. A field called amount is insufficient when two quantities change by different values.

We calculate the fee first and compare the complete debt increase against remaining capacity. The accepted transition then records the new debt and the cash disbursed separately. The conservation question asks where each obligation and payment goes. Comparing two differently named integers does not answer it.

function borrow(account, principal) {
  if (principal <= 0n) throw new Error("principal");
  if (account.delinquent) throw new Error("payment status");
  const fee = ceilDiv(principal * 350n, BPS);
  const increase = principal + fee;
  if (increase > account.ceiling - account.debt) throw new Error("capacity");
  account.debt += increase;
  account.cash += principal;
  return { principal, fee };
}

The ceiling is an input to this small model. In a lending contract it may depend on collateral quantity and a valuation rule. That calculation is a separate trust boundary because a stale or manipulable price can supply a mathematically consistent but economically incorrect ceiling.

Rounding changes the maximum request

At the fixture's fee rate a request for 96 units adds a four unit fee and exactly consumes the remaining 100 units of debt capacity. A request for 97 also adds four units and exceeds the available capacity by one. We test both values so the integer boundary is visible.

The upward rounding is deliberate. A floor policy gives different thresholds for small requests. Neither choice should be hidden inside a generic percentage helper because splitting a request into many small calls can change the total fees when each call rounds independently.

We compare the resulting debt against the ceiling after calculating the rounded fee because the borrower experiences the outgoing principal as spendable cash while the lending system must reserve capacity for the larger obligation and a boundary test using only the principal accepts a position that immediately violates the recorded lending rule.

If an interface displays a maximum principal amount it must use the same rounding rule as the contract. A decimal approximation that looks exact in the UI can still produce a transaction that reverts after the fee rounds upward.

Existing obligations affect the transition

An account may have accrued interest or a missed payment state in addition to principal. The product needs to decide which obligations count against capacity and whether delinquency prevents another draw. Checking only a stored principal field while ignoring accrued charges changes the meaning of the ceiling.

Our fixture rejects another draw when the account is marked delinquent and leaves debt unchanged. It does not calculate interest because introducing time here hides the specific fee boundary. A contract test can compose the fee transition with a separately tested interest calculation using an explicit timestamp.

96 cash units + 4 fee units consume 100 debt units
8 assertions passed

The exact fill test records 96 units of cash against 100 units of additional debt. A rejected larger request changes neither value. That distinction gives the interface an explanation for why the displayed amount received is smaller than the capacity consumed.

Authorization and asset transfer are separate checks

A Solidity borrowing function must also establish who may increase the account's debt and choose its recipient. An accurate ceiling check does not authorize an arbitrary caller to draw against somebody else's collateral. Ownership or delegated permission belongs before the debt transition.

A minted debt asset and a transferred reserve asset have different external call paths. In either implementation the test suite should verify that a failed payout cannot leave a successful recorded debt increase. The pure fixture makes no claim about EVM rollback or token behavior.

Repayment brings another ordering rule into the model. Allocate a partial repayment between fees and interest and principal under an explicit policy and then check that a second draw uses the capacity actually restored by that allocation.

Sources and examples