Skip to content

Commit 6e40508

Browse files
authored
Revise liquidation flow to punitive REP seizure (#500)
- Replace repair-cap liquidation math with punitive REP seizure logic - Update docs, UI copy, and tests to match the new liquidation behavior - Refresh deployment address references and shared liquidation helpers
1 parent b42ec6c commit 6e40508

26 files changed

Lines changed: 921 additions & 382 deletions

docs/liquidation.html

Lines changed: 281 additions & 135 deletions
Large diffs are not rendered by default.

docs/mainnet-deployment-addresses.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
{
1414
"id": "deploymentStatusOracle",
1515
"label": "Deployment Status Oracle",
16-
"address": "0x22D512D74a15b69D43e23B2fC7c899F0495d4525"
16+
"address": "0x4e87d565E3ceF63cb61EeF32FBfe26494521cEFE"
1717
},
1818
{
1919
"id": "multicall3",
@@ -33,7 +33,7 @@
3333
{
3434
"id": "securityPoolUtils",
3535
"label": "SecurityPoolUtils",
36-
"address": "0x925Eaf4512D192498fF2638c36E84200469615a9"
36+
"address": "0x793731992E60970423599a1189c8a894f89c9A54"
3737
},
3838
{
3939
"id": "openOracle",
@@ -53,34 +53,34 @@
5353
{
5454
"id": "shareTokenFactory",
5555
"label": "ShareTokenFactory",
56-
"address": "0x2255ae657cA45A75F9aa7dF453A901B1F1E7f9Ca"
56+
"address": "0x4F29b2459140e40ed4C582Cb13D1ab54e9412D4A"
5757
},
5858
{
5959
"id": "priceOracleManagerAndOperatorQueuerFactory",
6060
"label": "OpenOracle Price Coordinator Factory",
61-
"address": "0x20CeeD3987AEeaFB24B83C7281C1C52135BbA9BA"
61+
"address": "0x73DEa9f9394838d05e3dE0743d154a024fbb9B99"
6262
},
6363
{
6464
"id": "securityPoolForker",
6565
"label": "Security Pool Forker",
66-
"address": "0x55AD363204C991d0bb37494372F3821f2B5681De"
66+
"address": "0xAab444A64E2866a5Cd2642B6666cB523D3FEA807"
6767
},
6868
{
6969
"id": "escalationGameFactory",
7070
"label": "Escalation Game Factory",
71-
"address": "0x04AbAd5b45864673586C0b19c611D8e96B60266d"
71+
"address": "0x7C372b65ef83BAca130CF336d8007C7875eFC75e"
7272
},
7373
{
7474
"id": "securityPoolFactory",
7575
"label": "Security Pool Factory",
76-
"address": "0x1c157c8CF501aFeBa567514661a191dee26CEd82"
76+
"address": "0xE5e583B470FD93F603ed279fd7d5C75eeaC446fA"
7777
}
7878
],
7979
"derivedContracts": [
8080
{
8181
"id": "escalationGameProofVerifier",
8282
"label": "Escalation Game Proof Verifier",
83-
"address": "0x1A899c1D329ad210CC6DD88ED068410a007D51De"
83+
"address": "0xBbf8C7c730Ea881C39eEF426eFacaC3289a39C1E"
8484
}
8585
]
8686
}

docs/openOracleIntegration.html

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,15 @@ <h2>Placeholder Integration</h2>
333333
<p>
334334
<span class="term" tabindex="0">Staged operation</span>
335335
inputs are constrained before any oracle report is requested.
336-
Liquidation and withdrawal amounts must be non-zero, allowance updates
337-
may set zero, <code>validForSeconds</code> must be positive and no
338-
more than five minutes, non-liquidation operations must target the
339-
caller's own vault, and staging is disabled after the security pool's
340-
local escalation has already resolved.
341-
</p>
336+
Liquidation and withdrawal amounts must be non-zero, allowance updates
337+
may set zero, <code>validForSeconds</code> must be positive and no
338+
more than five minutes, liquidation must target a different vault,
339+
non-liquidation operations must target the caller's own vault, and
340+
staging is disabled after the security pool's local escalation has
341+
already resolved. Same-vault liquidation is rejected with
342+
<code>Caller bad</code> before the coordinator requests or uses an
343+
oracle report.
344+
</p>
342345
<p>
343346
Report funding is caller supplied. The coordinator computes
344347
<code>requestPriceEthCost</code>, the caller-funded
@@ -1355,14 +1358,15 @@ <h2>Attack Model and Defenses</h2>
13551358
</mtr>
13561359
</mtable>
13571360
</math>
1358-
<p class="equation-caption">
1359-
<span class="equation-label">Contract Liquidation Guard</span
1360-
><code>SecurityPool</code> first requires the scaled solvency
1361-
inequality to be strictly liquidatable. The coordinator then floors
1362-
the threshold and distance calculations. Equality passes only at the
1363-
distance check; <code>currentPrice</code> must still be strictly
1364-
above <code>thresholdPrice</code>.
1365-
</p>
1361+
<p class="equation-caption">
1362+
<span class="equation-label">Contract Liquidation Guard</span
1363+
>The coordinator computes the floored threshold and distance checks
1364+
before calling the pool. <code>SecurityPool</code> then rechecks the
1365+
scaled liquidation-threshold inequality and rejects chunks that fail
1366+
target, caller, or floor checks. Equality passes only at the distance
1367+
check; <code>currentPrice</code> must still be strictly above
1368+
<code>thresholdPrice</code>.
1369+
</p>
13661370
</div>
13671371
<p>
13681372
The attack model separates attacker payoff from delay cost. The payoff

docs/operator-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ callback behavior, stale-operation handling, and liquidation boundaries.
139139
| Price cache | One accepted REP/ETH price inside the coordinator, reusable only while it remains inside the freshness window. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
140140
| Initial report size | The configured `exactToken1Report` is sized from dispute economics: target error, OpenOracle fees, dispute gas, assumed gas price, REP/ETH price, dispute multiplier, and expected adverse REP/ETH movement during settlement. It does not scale with the outside funds protected by the price. The shared `@zoltar/shared/oracleInitialReport` helper computes `259.332023575638507216 REP` from those inputs; UI deployment helpers and simulator deployment helpers consume that shared value. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol), [oracleInitialReport.ts](../shared/ts/oracleInitialReport.ts), [deploymentHelpers.ts](../ui/ts/contracts/deploymentHelpers.ts), [deployPeripherals.ts](../solidity/ts/testsuite/simulator/utils/contracts/deployPeripherals.ts) |
141141
| Immediate execution | If a price is still valid, the operation executes immediately and the caller's ETH is refunded. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
142-
| Staging guardrails | Withdraw and liquidation amounts must be non-zero; allowance updates may set zero. Validity must be positive and no more than five minutes. Non-liquidation operations must target the caller's own vault. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
142+
| Staging guardrails | Withdraw and liquidation amounts must be non-zero; allowance updates may set zero. Validity must be positive and no more than five minutes. Non-liquidation operations must target the caller's own vault. Liquidation must target a different vault; same-vault liquidation is rejected with `Caller bad` before oracle request or execution. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
143143
| Pending report bound | At most four operations are attached to one settlement callback. Operations can still be tracked as active even when they do not fit into the pending callback batch. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
144144
| High settlement basefee | The callback clears the pending report but no-ops if settlement basefee is above the stored maximum multiplier from request time. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
145145
| Zero report values | A callback with zero amount, zero denominator, or a computed zero price does not update `lastPrice`. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |

docs/start-here.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ <h2>Documentation Map</h2>
502502
<td><a href="./openOracleIntegration.html">OpenOracle integration</a></td>
503503
</tr>
504504
<tr>
505-
<td>Understand repair-cap liquidation math, chunking limits, and incentives.</td>
505+
<td>Understand punitive REP-seizure liquidation math, chunking limits, and incentives.</td>
506506
<td><a href="./liquidation.html">Liquidation design</a></td>
507507
</tr>
508508
<tr>

docs/whitepaper_placeholder.html

Lines changed: 71 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,37 +2004,50 @@ <h3>Solvency Operations and Liquidation</h3>
20042004
valid REP/ETH price. Withdrawal and allowance changes check backing
20052005
against allowance, while liquidation applies the stronger
20062006
<code>securityMultiplier</code>-adjusted liquidability condition.
2007-
</p>
2008-
<p>
2009-
Liquidation is a transfer of risk, not a direct sale of collateral.
2010-
When a vault becomes undercollateralized, another vault can call
2011-
<code>performLiquidation</code>
2012-
through the queued-oracle path. The contract snapshots the target
2007+
</p>
2008+
<p>
2009+
Liquidation is a punitive vault-to-vault transfer: debt moves to
2010+
the caller vault, and unlocked target REP moves to that caller at
2011+
the liquidation bonus. When a vault becomes undercollateralized,
2012+
another vault can call
2013+
<code>performLiquidation</code>
2014+
through the queued-oracle path. The contract snapshots the target
20132015
vault’s state at queue time and uses that snapshot when the
20142016
operation executes, so later target-vault manipulation cannot
20152017
trivially invalidate the liquidation attempt. The queued liquidation
20162018
executes only if the fresh current REP/ETH price is strictly above
20172019
the computed threshold and far enough beyond it to satisfy the
20182020
configured <code>minLiquidationPriceDistanceBps</code> liquidation
20192021
distance check.
2022+
</p>
2023+
<p>
2024+
Economically, liquidation is punitive: the liquidator vault absorbs
2025+
target debt and receives unlocked target REP at a fixed
2026+
five-percent bonus over the debt chunk’s REP market value. That
2027+
punishes the target and rewards the liquidator, while the
2028+
<code>securityMultiplier</code> still makes the target healthier as
2029+
long as required backing falls faster than seized REP.
20202030
</p>
20212031
<p>
2022-
Economically, liquidation now moves debt first and only as far as is
2023-
needed to repair the target vault at the current price. The target’s
2024-
snapshot-derived unlocked vault REP claim stays with the target on
2025-
that repair path, so the caller vault is absorbing the under-backed
2026-
debt rather than extracting target REP. If the exact repair point
2027-
would leave a non-zero allowance below the pool’s minimum debt floor,
2028-
the cap rounds up to a full allowance transfer instead. REP
2029-
committed to escalation is never part of the liquidation transfer.
2030-
The receiving vault must remain solvent after taking on the moved
2031-
debt, and both sides must still satisfy the minimum deposit
2032-
requirements enforced by the pool.
2032+
The purpose is therefore threefold: punish the liquidated vault,
2033+
reward the liquidator, and move the system into a healthier state.
2034+
The first two effects are direct REP transfers; the third comes from
2035+
the strict health-improvement guard that rejects chunks whose
2036+
rounding would fail to reduce the target shortfall.
2037+
Healthier means the target shortfall shrinks and the moved debt
2038+
lands only on a caller vault that remains non-liquidatable under the
2039+
<code>securityMultiplier</code>-adjusted collateral threshold;
2040+
liquidation redistributes REP and allowance rather than increasing
2041+
aggregate backing.
20332042
</p>
20342043
<p>
2035-
For a focused walkthrough of the repair-cap math, chunking limits,
2036-
and incentive tradeoffs, see
2037-
<a href="./liquidation.html">Liquidation Design</a>.
2044+
Snapshot sizing, REP-seizure math, the strict health-improvement
2045+
guard
2046+
<code>debtToMove * securityMultiplier * currentRepPerEthPrice &gt; repToMove * PRICE_PRECISION</code>,
2047+
target/caller dust floors, and repeated-liquidation edge cases
2048+
define the liquidation execution envelope;
2049+
<a href="./liquidation.html">Liquidation Design</a> derives that
2050+
flow in full.
20382051
</p>
20392052
<figure class="diagram" id="fig-placeholder-liquidation-transfer">
20402053
<svg
@@ -2043,13 +2056,12 @@ <h3>Solvency Operations and Liquidation</h3>
20432056
aria-labelledby="liquidation-transfer-title liquidation-transfer-desc"
20442057
>
20452058
<title id="liquidation-transfer-title">
2046-
Liquidation repair transfer
2059+
Punitive liquidation transfer
20472060
</title>
20482061
<desc id="liquidation-transfer-desc">
2049-
Liquidation moves only the debt shortfall needed to repair the
2050-
target vault at the current price, leaves unlocked target REP in
2051-
place, and rounds up to a full debt transfer only when the repair
2052-
point would otherwise leave forbidden debt dust.
2062+
Liquidation moves debt to the caller vault and moves unlocked REP
2063+
from the target vault to the caller at a fixed liquidation bonus,
2064+
subject to target and caller minimum-balance constraints.
20532065
</desc>
20542066
<defs>
20552067
<marker
@@ -2079,18 +2091,18 @@ <h3>Solvency Operations and Liquidation</h3>
20792091
unsafe allowance
20802092
</text>
20812093
<text class="svg-small" x="179" y="164" text-anchor="middle">
2082-
REP backing snapshot
2094+
loses unlocked REP
20832095
</text>
20842096
<path
20852097
class="svg-line"
20862098
marker-end="url(#arrow-liquidation-transfer)"
20872099
d="M 304 118 H 610"
20882100
></path>
20892101
<text class="svg-small" x="457" y="99" text-anchor="middle">
2090-
debtToMove
2102+
debtToMove + repToMove
20912103
</text>
20922104
<text class="svg-small" x="457" y="184" text-anchor="middle">
2093-
no REP moves on the repair path
2105+
snapshot sizing + floor checks
20942106
</text>
20952107
<rect
20962108
class="svg-green"
@@ -2107,7 +2119,7 @@ <h3>Solvency Operations and Liquidation</h3>
21072119
takes debt
21082120
</text>
21092121
<text class="svg-small" x="735" y="164" text-anchor="middle">
2110-
must remain solvent
2122+
earns seized REP
21112123
</text>
21122124
<rect
21132125
class="svg-box"
@@ -2118,99 +2130,40 @@ <h3>Solvency Operations and Liquidation</h3>
21182130
rx="10"
21192131
></rect>
21202132
<text class="svg-label" x="460" y="256" text-anchor="middle">
2121-
Repair cap = target debt shortfall at the execution price
2133+
REP moves with a fixed 5% liquidation bonus
21222134
</text>
21232135
<text class="svg-small" x="460" y="276" text-anchor="middle">
2124-
full transfer only when the repair point would leave debt dust
2136+
full rules and worked examples live in Liquidation Design
21252137
</text>
21262138
</svg>
21272139
<p class="diagram-caption">
21282140
<span class="figure-label">Liquidation Transfer</span>Liquidation
2129-
moves a capped slice of snapshot target allowance to the caller
2130-
vault and leaves the target vault’s unlocked REP in place on the
2131-
repair path.
2141+
moves snapshot-sized debt to the caller vault and seizes unlocked
2142+
target REP at the configured liquidation bonus.
21322143
</p>
21332144
</figure>
21342145
<div class="equation" id="eq-placeholder-liquidation-transfer">
21352146
<math
21362147
display="block"
2137-
aria-label="unsafe value numerator equals snapshot target allowance times security multiplier times current price minus snapshot target REP times price precision, repair debt cap equals the ceiling of unsafe value numerator divided by security multiplier times current price, debt to move equals the minimum of requested debt and the repair debt cap after dust rounding, and REP to move equals zero on the repair path"
2138-
data-source="unsafeValueNumerator = snapshotTargetAllowance \cdot securityMultiplier \cdot currentPrice - snapshotTargetRep \cdot PRICE_PRECISION; repairDebtCap = ceil(unsafeValueNumerator / (securityMultiplier \cdot currentPrice)); debtToMove = min(requestedDebt, maxDebtToMoveAfterDustRounding); repToMove = 0"
2148+
aria-label="rep to move equals the ceiling of debt moved times current REP per ETH price times one plus the liquidation bonus divided by price precision"
2149+
data-source="repToMove = ceil(debtToMove \cdot currentPrice \cdot (BPS_DENOMINATOR + liquidationRepBonusBps) / (PRICE_PRECISION \cdot BPS_DENOMINATOR))"
21392150
>
2140-
<mtable>
2141-
<mtr>
2142-
<mtd>
2143-
<mi>unsafeValueNumerator</mi>
2144-
</mtd>
2145-
<mtd>
2146-
<mo>=</mo>
2147-
</mtd>
2148-
<mtd>
2149-
<mi>snapshotTargetAllowance</mi>
2150-
<mo>&#x22C5;</mo>
2151-
<mi>securityMultiplier</mi>
2152-
<mo>&#x22C5;</mo>
2153-
<mi>currentPrice</mi>
2154-
<mo>-</mo>
2155-
<mi>snapshotTargetRep</mi>
2156-
<mo>&#x22C5;</mo>
2157-
<mi>PRICE_PRECISION</mi>
2158-
</mtd>
2159-
</mtr>
2160-
<mtr>
2161-
<mtd>
2162-
<mi>repairDebtCap</mi>
2163-
</mtd>
2164-
<mtd>
2165-
<mo>=</mo>
2166-
</mtd>
2167-
<mtd>
2168-
<mi>ceil</mi>
2169-
<mo>(</mo>
2170-
<mfrac>
2171-
<mi>unsafeValueNumerator</mi>
2172-
<mrow>
2173-
<mi>securityMultiplier</mi>
2174-
<mo>&#x22C5;</mo>
2175-
<mi>currentPrice</mi>
2176-
</mrow>
2177-
</mfrac>
2178-
<mo>)</mo>
2179-
</mtd>
2180-
</mtr>
2181-
<mtr>
2182-
<mtd>
2183-
<mi>debtToMove</mi>
2184-
</mtd>
2185-
<mtd>
2186-
<mo>=</mo>
2187-
</mtd>
2188-
<mtd>
2189-
<mi>min</mi>
2190-
<mo>(</mo>
2191-
<mi>requestedDebt</mi>
2192-
<mo>,</mo>
2193-
<mi>maxDebtToMoveAfterDustRounding</mi>
2194-
<mo>)</mo>
2195-
</mtd>
2196-
</mtr>
2197-
<mtr>
2198-
<mtd>
2199-
<mi>repToMove</mi>
2200-
</mtd>
2201-
<mtd>
2202-
<mo>=</mo>
2203-
</mtd>
2204-
<mtd><mn>0</mn></mtd>
2205-
</mtr>
2206-
</mtable>
2151+
<mrow>
2152+
<mi>repToMove</mi>
2153+
<mo>=</mo>
2154+
<mi>ceil</mi>
2155+
<mo>(</mo>
2156+
<mfrac>
2157+
<mrow><mi>debtToMove</mi><mo>&#x22C5;</mo><mi>currentPrice</mi><mo>&#x22C5;</mo><mo>(</mo><mi>BPS_DENOMINATOR</mi><mo>+</mo><mi>liquidationRepBonusBps</mi><mo>)</mo></mrow>
2158+
<mrow><mi>PRICE_PRECISION</mi><mo>&#x22C5;</mo><mi>BPS_DENOMINATOR</mi></mrow>
2159+
</mfrac>
2160+
<mo>)</mo>
2161+
</mrow>
22072162
</math>
22082163
<p class="equation-caption">
2209-
<span class="equation-label">Liquidation Transfer</span>The debt
2210-
transfer is capped at the minimum amount needed to repair the
2211-
target vault at the execution price, except when that repair point
2212-
would leave non-zero debt dust below the minimum allowance. The
2213-
repair path moves no REP.
2164+
<span class="equation-label">Liquidation Transfer</span>The caller
2165+
receives REP priced from the executed debt chunk plus the fixed
2166+
liquidation bonus.
22142167
</p>
22152168
</div>
22162169
<p>
@@ -8725,6 +8678,16 @@ <h3>Migration, Auction, Solvency, and Retention Parameters</h3>
87258678
minimum. Solidity constant in <code>SecurityPoolUtils</code>.
87268679
</td>
87278680
</tr>
8681+
<tr>
8682+
<td><code>LIQUIDATION_REP_BONUS_BPS</code></td>
8683+
<td><code>500</code></td>
8684+
<td>
8685+
Fixed liquidator reward bonus in basis points. Higher values
8686+
increase liquidator reward but reduce the margin by which a
8687+
liquidation improves target health. Solidity constant in
8688+
<code>SecurityPoolUtils</code>.
8689+
</td>
8690+
</tr>
87288691
<tr>
87298692
<td><code>MIN_SECURITY_BOND_DEBT</code></td>
87308693
<td><code>1 ether</code></td>

scripts/sharedBrowserArtifacts.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
1-
export const sharedBrowserArtifactRelativePaths = ['shared/js/bigInt.js', 'shared/js/constants.js', 'shared/js/deploymentAddresses.js', 'shared/js/escalationMath.js', 'shared/js/ethereum.js', 'shared/js/oracleInitialReport.js', 'shared/js/protocolConfig.js', 'shared/js/truthAuctionTickMath.js'] as const
1+
export const sharedBrowserArtifactRelativePaths = [
2+
'shared/js/bigInt.js',
3+
'shared/js/constants.js',
4+
'shared/js/deploymentAddresses.js',
5+
'shared/js/escalationMath.js',
6+
'shared/js/ethereum.js',
7+
'shared/js/liquidation.js',
8+
'shared/js/oracleInitialReport.js',
9+
'shared/js/protocolConfig.js',
10+
'shared/js/truthAuctionTickMath.js',
11+
] as const

0 commit comments

Comments
 (0)