The coefficient that wouldn't stay small: socializing bad debt across many tokens
A companion to the Marginly math. Spreading one borrower's bad debt across a whole pool collapses, in the single-token case, into a tidy 2×2 coefficient you apply globally — the same scaled-balance trick. With many tokens at different prices it looks like it balloons into an ever-growing tensor. It doesn't have to: with the right state it stays a finite matrix — just no longer a tidy one.
I keep promising this one. In the Marginly post a pool-wide operation turned out to be a 2×2 matrix you could apply with a couple of global numbers; in both zero-knowledge posts I waved at the same idea for Equilibrium's bailsmen — the backstop pool whose members absorb liquidated borrowers' leftover bad debt pro-rata — and each time flagged a catch: a bailsman's balance lives in many tokens at different prices, which is "a story for another day." I finally dug the derivation out of an old Overleaf file. Here's the story.
A fair warning before we start: this is the most math-heavy post in the series — matrices, tensor indices, the lot — and I tried and failed to slim it down. The algebra is the story here; there's no honest shortcut around it. If that's not your idea of fun, the prose around each formula carries the gist on its own, and the closing section has the one-sentence moral.
The single-token case: a 2×2, just like Marginly
A liquidation hands the pool a borrower's leftover collateral and debt, and the pool spreads them across bailsmen in proportion to each one's stake. For a bailsman holding collateral and debt (in dollars), with the pool's total collateral and the liquidated amounts , the share is collateral-weighted:
The thing to notice — the same move that makes the Marginly trick work — is that this is linear in , so it's a single matrix:
And the entries depend only on global quantities — the liquidated amounts and the pool total — never on which bailsman. So one matrix updates everyone by left-multiplication, and a second liquidation is just another global matrix: is still 2×2. You never touch individual balances at all — you keep each bailsman's in scaled form, bump a couple of shared coefficients per liquidation, and recover any actual balance on read. A pool-wide payout in . This is exactly the scaled-balance / coefficient idea, and in one dimension it's beautiful.
Adding tokens
Now let bailsmen hold many tokens — call it of them. From here I'll index everything by a kind and a token, and keep the two index families visually distinct:
- — the kind: collateral or debt (2 values);
- — the token ( values);
with a repeated index summed (Einstein convention). So is the quantity of token a bailsman holds as kind , and is the price of token . A bailsman's dollar collateral is then (summed over tokens), and their dollar debt.
The honest state is the whole per-token block . Writing for the coefficient tensor, the natural way to write one distribution is
Read it left to right — collapse the balance against prices ( is the dollar amount of each kind ), then let the coefficients spread the liquidated basket back out. And it's where I got stuck.
Where it blows up
Apply a second distribution — recursively, with the new prices that now prevail (and its own coefficient ). It reduces the new balance to dollars, then spreads again:
Tensor multiplication is associative, so regroup it into "new coefficients" × "balance" × "new prices":
and read off what each grouped factor became:
Both ranks went up — tensor rank, the number of indices, not the matrix-rank sense I'll use later. The coefficient grew an extra token index (); the price vector became a price matrix — an outer product of this distribution's prices with the last one's. Another liquidation lifts them again. Each step has to remember another generation of prices, so the state you'd need to carry grows without bound. My conclusion at the time, written in the margin: Equilibrium-like bailsmen distribution most likely can't be expressed with a constant-size set of coefficients. I shelved it.
The mistake was the shape, not the problem
Years later I came back to it — while drafting this very post, and I should be honest about how. I handed the derivation to an AI and asked it to try to knock my conclusion down. It did, and the reframing that follows is mostly its doing, not some shower-thought of mine: it kept pushing on the one step I'd waved through. The tell is right there in that natural-looking tensor equation. It tries to rebuild the new per-token balance (a -dimensional thing) out of — that is, out of the two-dimensional dollar reduction of the old balance (two-dimensional because it's one number per kind). But the new per-token balance is
and that first term — the old per-token balance, carried through unchanged — is not recoverable from its dollar reduction once you've summed over tokens. By collapsing to dollars I threw away exactly the part the next step needs, and then paid for it by smuggling per-token structure (and every generation of prices) back in through ever-fatter coefficients. The rank inflation was the receipt for information I'd discarded a step earlier.
Keep the information instead. Don't collapse the balance to dollars at all — let the coefficient act on the whole balance (quantities, which don't depend on price). One distribution becomes
Two terms, and the whole argument lives in them. The first, — a product of Kronecker deltas — is the identity (the index-notation form of the identity matrix ): it passes both the kind () and the token () straight through, the passthrough I'd thrown away by reducing to dollars. The second is the distribution: contracts the balance down to the bailsman's dollar collateral ( picks the collateral kind, sums over tokens), the turns it into the weight , and sprays the liquidated basket back across tokens. (It's a rank-one update: the correction is an outer product of the output basket with the input covector .)
The difference from where I got stuck is exactly one index — the token index on the input. My old took a single input index , a kind; it could only eat the dollar reduction. takes an input kind and token , so the token identity can carry the per-token balance through. That one missing token index is the entire repair.
Now compose. A second distribution — new prices , basket , total — is another operator of the same shape, and they chain by plain contraction:
The composed coefficient still has one (kind, token) pair down and one up — the same shape as a single . Nothing grew. That's the whole contrast with the shelved version, where composing grew the coefficient a token index () and turned the price vector into a price matrix . The inflation was the cost of the missing index; restore it and the prices never escape — each distribution's prices are spent forming its own , and only today's prices touch a read, applied once at the very end when you turn quantities back into dollars. The total each step needs is a running aggregate, not a loop over bailsmen.
So the constant-size coefficient does exist: a single operator with inputs and outputs, where is the number of tokens (a handful) — emphatically not the number of bailsmen (the thousands you were trying not to touch). The same settles every bailsman at once. The dead end I'd talked myself into was just the wrong coordinates.
A worked example: two bailsmen, two tokens, two liquidations
Concrete numbers make the "one operator" claim easy to see. Two tokens, ETH and USDT; two bailsmen, each holding collateral and debt per token:
- A — collateral , debt
- B — collateral , debt
Liquidation 1, at prices ETH = 2000 USD, USDT = 1 USD. Both bailsmen hold 4000 USD of collateral, so the pool total is 8000 USD and each one's weight is . The liquidated basket is of collateral and of debt; each absorbs half:
- A → collateral , debt
- B → collateral , debt
Liquidation 2 — and now ETH drops to 1000 USD. Re-pricing the same token quantities, A's collateral is now worth 2500 USD and B's 3500 USD, a total of 6000 USD, so the weights have shifted: A falls to , B rises to . (That's the price change doing its job — ETH-heavy A now carries less of the pool, so it absorbs less.) The liquidated basket is of collateral, no debt:
- A → collateral
- B → collateral
Now the operator view. Order the state as . Each liquidation is one matrix , where is the identity matrix, is the liquidated basket and is the prices (zero on the debt slots). Liquidation 1, with , works out to:
Multiply each bailsman's starting balance by and you land on the post-liquidation-1 balances:
— A's ETH collateral and USDT debt, B's ETH / USDT collateral and USDT debt, exactly as before. Liquidation 2, with , is:
Fold both liquidations into a single matrix:
and apply it directly to the starting balances:
— exactly A's and B's finishing balances. Two liquidations collapsed into one that settles every bailsman in a single multiply, and it would still be with a thousand liquidations behind it or a million bailsmen in front of it. (The wild spread in the entries — sitting next to — is just the ETH/USDT price ratio showing through; the load-bearing facts are only that the box stayed and the arithmetic lands.)
The catch, and why it stayed on paper
This is where I keep myself honest, because "it works" is doing some lifting. In one dimension the coefficient was three numbers. Here it's a matrix — quadratic in the number of supported tokens just to store, and cubic to push around: multiplying two matrices is , and a deposit or withdrawal needs the coefficient's inverse, also . (A single liquidation is only a rank-one update, so you can fold one in at if you're careful — but the general bookkeeping is cubic.) After a few compositions the tidy rank-one structure is gone, and you're storing a dense matrix on-chain, updating it per liquidation, and inverting it whenever someone deposits or withdraws (to push their change back into scaled form). None of that is exotic, but none of it is free, and fixed-point precision over many composed multiplications is its own small nightmare.
Which lands me back where the SNARK post did. The single-token trick is O(1) and nearly weightless. The multi-token version is still O(1) in the number of bailsmen — the thing that actually hurt — but it trades a handful of scalars for a matrix and a pile of bookkeeping, and whether that beats the plain chunked queue Equilibrium already ran is exactly the comparison I never made. I technically still could — the Equilibrium source is public to this day — but the question stopped mattering a long time ago, and outside of scientific curiosity I'm too lazy to benchmark a dead one. My honest guess is the same as it was for the SNARK: probably not worth it. It stayed a derivation for a reason.
What it comes down to
The thing I like about this one is what it did to the earlier post. "It's just a 2×2 matrix" felt like the answer in the single-token world. It was really a special case — the place where a general truth happens to be small enough to look tidy. Add a second dimension and the 2×2 stretches into a ; the elegance was a property of , not of the idea. The idea — a pool-wide rewrite is one global linear map you apply lazily — survives intact. It just stops being small enough to fit in your head — and a good part of engineering is deciding whether something you can no longer hold whole is still worth building.