Introduce the Sovryn Perimeter Fee on Zero - #10
Open
tjcloa wants to merge 8 commits into
Open
Conversation
Phase 1 of the Sovryn security perimeter (SIP-0094): a minimal exit fee on user-initiated withdrawal surfaces, funding continuous exit monitoring. This change carries the Zero half of the system: - exit-fee hooks in BorrowerOperations on collateral withdrawal and trove closure, quoting through the shared ExitFeeController (Sovryn-perimeter repo) and paying the fee leg to the ExitFeeVault; every hook fails open — a fee fault forgoes the fee, never blocks a withdrawal; - the surplus-claim surface: CollSurplusPool.claimCollWithFee, a BO-only two-leg split that keeps claimColl byte-untouched, with the pool implementation upgrade ordered strictly before the BO upgrade inside SIP-0094 executable part 1; - impl-only deploy scripts for both contracts (the proxy swaps are governance actions), storage-layout zero-diff guards, and the ColFee test suite incl. reentrancy/fail-open matrices and Echidna invariants. The fee system deploys disabled and enables only by governance after post-deployment verification.
Deployed 2026-08-13 by 0x163463b7ddbce853832037a059f5c5e6606bf9c4 (nonces 25-26), both inert until SIP-0094 Part 1 calls setImplementation: BorrowerOperations_Implementation 0xcD22ba4b3ED7D7297b40Dcd26d982634A0207885 CollSurplusPool_Implementation 0x71A605F81a66eB93Ce9b8091014da858bFD4b6dA Verified after deploy: both hold code; both live proxies still point at their current implementations (BO 0xD603B4c5…, CSP 0xE9005C36…); the permit2 immutable baked into the new BO implementation equals the live proxy's. Runtime codehashes match the fork rehearsal's pins exactly, so mainnet carries bit-for-bit the code the dress run exercised. The stale BorrowerOperations_Implementation record was moved aside before the run: hardhat-deploy's bytecode comparison needs the historical deploy transaction, which no public RSK endpoint still serves. The prior record remains in git history.
Zero's half of the Phase 1 re-cut. The controller pointer slot and the two Zero surface ids are keccak256 of a name, so renaming the name moves the value -- which is the point: Phase 2 then inherits these ids instead of migrating off live ones. sovryn.exitFeeController -> sovryn.perimeterExitFeeController SURFACE_ZERO_* -> PERIMETER_SURFACE_ZERO_* Code identifiers and the tests directory follow, so the Echidna suite and the fixtures match what Phase 2 expects. Identifiers.pinned cross-checks the literal 32 bytes against the lending repo's copies. Zero declares these independently, so a one-character drift between the two repos would not fail loudly -- Zero would resolve no policy and stop charging silently. Zero perimeter suite 47 passing plus 4 pinned identifiers.
The Phase-1 id was keccak256("COLFEE:" + name). The rename swept the prefix to
"PERIMETER:" and left it in place, which hashes to neither the Phase-1 id nor
the one Phase 2 expects -- a constant that looks right, resolves no policy, and
stops the fee without failing.
The namespace now lives inside the name, so the preimage is the name alone,
matching the lending repo and the controller bootstrap.
The rename walked deployment/deployments and rewrote three records of what is already on RSK mainnet: the Phase-B BorrowerOperations and CollSurplusPool implementations and their shared solc input. Those files are evidence, not source -- they are the rollback anchor and the input the explorer verification was performed against. Rewriting their embedded source left records whose metadata described code that was never deployed, while the bytecode field still held the original build. Restored verbatim from the Phase-1 branch. The re-cut deploys new implementations under new records; the old ones stay exactly as they were.
The prettier pre-commit hook reformatted them on the way back in. These files are evidence of what is deployed -- they must match the Phase-1 branch exactly, formatting included, so committed with the hook bypassed.
The rename mapped ColFeeApplied/ColFeeSkipped onto Perimeter* here, but the events this enum documents are ExitFeeApplied and ExitFeeSkipped -- the Perimeter* names belong to the Echidna harness, not the product ABI. A NatSpec line naming an event that does not exist sends a reader looking for the wrong topic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 of the Sovryn security perimeter (SIP-0094): a minimal exit fee on user-initiated withdrawal surfaces, funding continuous exit monitoring. This is the Zero half of the system; the shared fee rail lives in Sovryn-perimeter and the lending half in Sovryn-smart-contracts#580.
What this carries
BorrowerOperationson collateral withdrawal and trove closure, quoting through the sharedExitFeeControllerand paying the fee leg to theExitFeeVault. Every hook fails open: a fee fault forgoes the fee, never blocks a withdrawal.CollSurplusPool.claimCollWithFee, a BorrowerOperations-only two-leg split that keepsclaimCollbyte-untouched, with the pool implementation upgrade ordered strictly before the BorrowerOperations upgrade inside SIP-0094 executable part 1.The fee system deploys disabled and is enabled only by governance after post-deployment verification.
Merge timing
This PR is intended to stay open until SIP-0094 has been approved and executed on-chain — mainline follows the chain rather than leading it. The deployment runs from this branch's commit, so no contract file here may change between deploy and execution; the proposal builders compare the live runtime code hash at creation, so a stale implementation produces a refused proposal rather than a silent mismatch.