From 28a57e7ac3d62367643c788b086e5c8290736236 Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Mon, 17 Aug 2026 15:52:57 +0300 Subject: [PATCH 1/7] Introduce the Sovryn Perimeter Delay on Zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 of the Sovryn security perimeter: a voluntary borrower collateral exit that already pays the Perimeter Fee can additionally be held in the ExitDelayQueue for a governance-configured delay, so a detected theft can be frozen or blacklisted and routed to recovery before the funds leave. This change carries the Zero half. - delay hooks on the voluntary collateral-out paths (withdrawColl, the collateral-decreasing adjustTrove, and closeTrove), sharing the surface the fee already uses. The fee leg is paid immediately and only the NET is escrowed, and a single delay quote taken once per exit governs the whole exit — including the full-gross path taken when the fee leg fails, so a fee-vault fault cannot route around the delay; - ActivePool pushes the native RBTC to the queue and the record follows in the same transaction, so a record failure rolls the push back and the exit reverts as a whole rather than leaving value stranded; - fail-open POINTER, fail-closed QUOTE: an unset queue or controller pointer leaves exits paying direct, while a controller that answers incorrectly reverts the exit rather than silently disabling the perimeter. The queue's custom-error selectors propagate unchanged so the off-chain halt watcher can key on them; - BorrowerOperations gains the owner-gated setter for the queue pointer; the pointer lives in an unstructured slot, so neither hook adds state to any upgradeable proxy (asserted by the storage-layout zero-diff guard, whose baseline now covers the delay hooks as well). Redemptions, liquidations and Stability Pool operations stay untouched, as does the surplus claim, which remains exempt from the delay and keeps a pinning test to prove it. The delay ships disabled and is enabled only by governance after post-deployment verification. --- contracts/BorrowerOperations.sol | 171 +++++- .../Interfaces/colfee/IExitDelayQueue.sol | 370 +++++++++++++ .../Interfaces/colfee/IExitDelayQueueHook.sol | 85 +++ .../Interfaces/colfee/IExitFeeController.sol | 142 ++++- .../TestContracts/ExitFeeControllerMock.sol | 46 ++ .../TestContracts/MockExitDelayQueue.sol | 203 +++++++ .../SelectorRevertingExitDelayQueue.sol | 92 ++++ hardhat.config.ts | 7 +- tests-colfee/ClaimSurplus.notouch.test.js | 188 +++++++ tests-colfee/StorageLayout.zerodiff.test.js | 33 +- .../ZeroBorrowerExit.delay.notouch.test.js | 175 ++++++ .../ZeroBorrowerExit.delay.selector.test.js | 171 ++++++ tests-colfee/ZeroBorrowerExit.delay.test.js | 496 ++++++++++++++++++ .../storage-layout.sovryn-perimeter-fee.json | 4 +- 14 files changed, 2156 insertions(+), 27 deletions(-) create mode 100644 contracts/Interfaces/colfee/IExitDelayQueue.sol create mode 100644 contracts/Interfaces/colfee/IExitDelayQueueHook.sol create mode 100644 contracts/TestContracts/MockExitDelayQueue.sol create mode 100644 contracts/TestContracts/SelectorRevertingExitDelayQueue.sol create mode 100644 tests-colfee/ClaimSurplus.notouch.test.js create mode 100644 tests-colfee/ZeroBorrowerExit.delay.notouch.test.js create mode 100644 tests-colfee/ZeroBorrowerExit.delay.selector.test.js create mode 100644 tests-colfee/ZeroBorrowerExit.delay.test.js diff --git a/contracts/BorrowerOperations.sol b/contracts/BorrowerOperations.sol index 3d78ac9..02ba404 100644 --- a/contracts/BorrowerOperations.sol +++ b/contracts/BorrowerOperations.sol @@ -17,6 +17,7 @@ import "./BorrowerOperationsStorage.sol"; import "./Dependencies/Mynt/MyntLib.sol"; import "./Interfaces/IPermit2.sol"; import "./Interfaces/colfee/IExitFeeController.sol"; +import "./Interfaces/colfee/IExitDelayQueueHook.sol"; contract BorrowerOperations is LiquityBase, @@ -37,7 +38,17 @@ contract BorrowerOperations is bytes32 private constant SURFACE_ZERO_CLAIM_SURPLUS = keccak256("COLFEE:SURFACE_ZERO_CLAIM_SURPLUS"); + // --- Security-perimeter exit-delay hook --- + // The delay queue pointer also lives in an EIP-1967-style unstructured + // slot, so `BorrowerOperations` storage-layout is unchanged (zero-diff). It is + // rotated with `setExitDelayQueue` under the SAME owner as the controller + // pointer. Because the pointer redirects ESCROW it is more sensitive than the + // controller pointer — rotation is an Owner/SIP action. + bytes32 private constant EXIT_DELAY_QUEUE_SLOT = + bytes32(uint256(keccak256("sovryn.exitDelayQueue")) - 1); + event ExitFeeControllerSet(address indexed previous, address indexed current); + event ExitDelayQueueSet(address indexed previous, address indexed current); event ExitFeeApplied( bytes32 indexed surfaceId, address indexed actor, @@ -1004,6 +1015,86 @@ contract BorrowerOperations is emit ExitFeeControllerSet(prev, ctrl); } + /// @notice Address of the ExitDelayQueue this instance escrows delayed + /// collateral exits into. Held in an EIP-1967-style unstructured slot + /// (no regular-storage footprint). address(0) until governance pins + /// one ⇒ the security-perimeter reroute is unwired ⇒ exits pay direct + /// at `d == 0` and fail CLOSED at `d > 0` (a delay is never silently + /// bypassed by a missing pointer). + function exitDelayQueue() public view returns (address queue) { + bytes32 slot = EXIT_DELAY_QUEUE_SLOT; + assembly { + queue := sload(slot) + } + } + + /// @notice Pin/rotate the ExitDelayQueue pointer. `onlyOwner` — the same + /// BorrowerOperations proxy owner (= TimelockOwner on mainnet) that + /// gates `setExitFeeController`. Because the pointer redirects ESCROW + /// it is MORE sensitive than the controller pointer; rotation is an + /// Owner/SIP action. Reverts on a non-contract so a typo cannot point + /// the reroute at a no-code address (address(0) is likewise rejected — + /// unwiring, if ever needed, is a deliberate distinct governance path, + /// and the perimeter is instead disabled via the controller kill + /// switch, which quotes `d == 0` and pays direct). + function setExitDelayQueue(address queue) external onlyOwner { + require(queue != address(0), "EDQ:zero"); + checkContract(queue); + address prev = exitDelayQueue(); + bytes32 slot = EXIT_DELAY_QUEUE_SLOT; + assembly { + sstore(slot, queue) + } + emit ExitDelayQueueSet(prev, queue); + } + + /// @dev Fail-CLOSED delay quote wrapper. Resolves the single hook + /// entry `quoteExitDelayFor` on the shared ColFee controller and returns + /// `(d, effOrig, effOwner)`. Two levels, deliberately distinct: + /// 1. controller-POINTER lookup is FAIL-OPEN — a missing OR code-less + /// controller ⇒ perimeter unwired ⇒ `(0, raw, raw)` ⇒ pay direct + /// (mirrors the fee path; also, 0.6.11 try/catch does NOT catch a + /// call to a no-code address, so the extcodesize guard is required); + /// 2. once a controller is resolved, the `quoteExitDelayFor` CALL is + /// FAIL-CLOSED — a revert reverts the whole exit and MUST NOT be + /// interpreted as `d = 0`-direct (that would silently disable the + /// perimeter — the hazard this guards against). Uses a DISTINCT revert selector for + /// halt monitoring. + /// The `!securityPerimeterEnabled` short-circuit is the FIRST statement + /// inside `quoteExitDelayFor`, so a healthy-but-disabled perimeter returns + /// `(0, raw, owner)` normally (liveness escape). The hook ignores + /// `effOrig`/`effOwner` whenever `d == 0`. + function _safeQuoteExitDelay( + address rawOriginator, + address owner, + address receiver + ) private view returns (uint32 d, address effOrig, address effOwner) { + address ctrl = exitFeeController(); + uint256 ctrlSize; + assembly { + ctrlSize := extcodesize(ctrl) + } + // Level 1 — FAIL-OPEN pointer lookup: unwired/unreachable ⇒ direct pay. + // Raw identities are returned but the caller ignores them when d == 0. + if (ctrl == address(0) || ctrlSize == 0) { + return (0, rawOriginator, owner); + } + // Level 2 — FAIL-CLOSED quote: a controller revert reverts the exit. + try + IExitFeeController(ctrl).quoteExitDelayFor( + rawOriginator, + owner, + receiver, + SURFACE_ZERO_WITHDRAW_COLL, + address(0) + ) + returns (uint32 d_, address effOrig_, address effOwner_) { + return (d_, effOrig_, effOwner_); + } catch { + revert("COLFEE:delay-quote-failed"); + } + } + /// @dev Fail-open quote wrapper. On a missing/reverting controller or a /// semantically invalid quote, returns a non-charging quote with /// `netAmount == gross`. The validity gate uses subtraction only @@ -1079,6 +1170,17 @@ contract BorrowerOperations is return; } + // Security-perimeter delay quote — computed ONCE up-front so a single `d` + // governs the WHOLE exit: a fee-vault failure still escrows GROSS + // behind the delay and cannot bypass it. FAIL-CLOSED (except the + // kill-switch / unwired short-circuit): a controller revert reverts the + // exit. Zero has no passthrough, so originator == owner == receiver + // == borrower (== msg.sender on every collateral-out path). The queue is + // NEVER touched here — only inside the `d > 0` branch of `_payUserColl` + //. + DelayLeg memory dl; + (dl.d, dl.effOrig, dl.effOwner) = _safeQuoteExitDelay(borrower, borrower, borrower); + // Single Zero deployment: subProduct = address(0). Asset is native RBTC. IExitFeeController.ExitFeeQuote memory q = _safeQuote( SURFACE_ZERO_WITHDRAW_COLL, @@ -1089,7 +1191,8 @@ contract BorrowerOperations is if (q.active && q.feeAmount > 0) { try _activePool.sendETH(q.feeReceiver, q.feeAmount) { - _activePool.sendETH(borrower, q.netAmount); // user leg: existing fail-closed behavior + // user leg (net): direct-pay OR reroute to the delay queue when d>0 + _payUserColl(_activePool, borrower, q.netAmount, dl); // Emit only after BOTH legs settle, so an ExitFeeApplied event always // implies a completed borrower payout (truthful by construction). emit ExitFeeApplied( @@ -1126,7 +1229,71 @@ contract BorrowerOperations is q.reason ); } - _activePool.sendETH(borrower, gross); // full-gross fallback (any non-charging path) + // full-gross fallback (any non-charging path): direct-pay OR reroute to the + // delay queue when d>0 — the same up-front `d` governs both legs. + _payUserColl(_activePool, borrower, gross, dl); + } + + /// @dev Bundles the resolved delay-leg fields so `_payUserColl` stays a + /// single-slot call and `_sendCollWithExitFee` does not run into the + /// 0.6.11 stack-depth limit. `d == 0` ⇒ perimeter off / bypassed / + /// unwired ⇒ pay direct. Zero surface has no passthrough, so + /// `effOrig`/`effOwner` are the raw identities. + struct DelayLeg { + uint32 d; + address effOrig; + address effOwner; + } + + /// @dev Settle the (post-fee) borrower USER leg of a voluntary collateral-out. + /// When the perimeter quotes no delay (`d == 0`) this is the EXISTING + /// native payout, byte-for-byte unchanged (`sendETH(receiver, amount)`). + /// When `d > 0` the leg is rerouted into the ExitDelayQueue: ActivePool + /// PUSHES the native RBTC to the queue, immediately followed by + /// `recordReceivedNativeExit` in the SAME outer tx — both INSIDE this + /// `d > 0` branch so the queue is never touched until a delay is + /// established off-queue, and a record revert rolls back the push + /// (fail-CLOSED: after the trove state already mutated, the whole + /// close/adjust reverts atomically — a bricked queue blocks Zero closes + /// until the kill switch is flipped). The queue's `receive()` is + /// unconditional and, via measured-receipt, credits EXACTLY `amount` when + /// its surplus `>= amount` — a donation cannot brick the record. + function _payUserColl( + IActivePool _activePool, + address receiver, + uint256 amount, + DelayLeg memory dl + ) private { + // A net leg can be 0 on a full-fee edge; nothing to pay or escrow. + if (amount == 0) { + return; + } + + if (dl.d > 0) { + address queue = exitDelayQueue(); + // FAIL-CLOSED: once the perimeter quotes d>0 the user leg MUST escrow. + // An unwired queue reverts the exit with a DISTINCT selector (halt + // monitoring) — a delay can never be silently bypassed by a missing + // pointer. + require(queue != address(0), "COLFEE:queue-unset"); + require(amount <= uint256(uint128(-1)), "COLFEE:amount-too-large"); + + // PUSH native to the queue (reuses the existing fail-closed sendETH + // primitive — ActivePool.ETH decrements by exactly `amount`, identical + // to the direct payout), then measured-record in the SAME outer tx. + _activePool.sendETH(queue, amount); + IExitDelayQueueHook(queue).recordReceivedNativeExit( + uint128(amount), + dl.d, + SURFACE_ZERO_WITHDRAW_COLL, + address(0), + dl.effOrig, + dl.effOwner, + receiver + ); + } else { + _activePool.sendETH(receiver, amount); // EXISTING native payout, unchanged + } } /// @notice Read-only preview of the ColFee exit fee on a Zero borrower collateral diff --git a/contracts/Interfaces/colfee/IExitDelayQueue.sol b/contracts/Interfaces/colfee/IExitDelayQueue.sol new file mode 100644 index 0000000..6ca8e8f --- /dev/null +++ b/contracts/Interfaces/colfee/IExitDelayQueue.sol @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: MIT +// ───────────────────────────────────────────────────────────────────────────── +// PROVENANCE — copied verbatim from DistributedCollective/colfee +// @ 51457b21bc9a87958e99ea51325ded150422e791 +// src/interfaces/IExitDelayQueue.sol +// Do NOT modify the ABI here — the queue's IExitDelayQueue is final. +// To update: change upstream, re-copy, bump the SHA in this header. +// +// This file is the authoritative type/event/error + ABI catalog, kept for ABI +// parity and off-chain tooling. Upstream pins `0.8.20`; here the pragma is +// WIDENED to `>=0.8.4 <0.9.0` (mirroring the range-pragma convention already used +// for the copied `IExitFeeController.sol`) so it builds against this repo's +// configured 0.8.x compilers. The **ABI is ABI-identical** to upstream (all four +// `record*` signatures, event param lists/indexing, and the full error catalog +// incl. `UnregisteredSource(address)` match byte-for-byte); the only NON-ABI +// divergences are the widened pragma and **whitespace/line-wrap normalized by +// this repo's prettier** (some event/function declarations are re-wrapped). So a +// re-copy MUST compare ABI/selectors, not raw bytes. It is NOT importable from the +// 0.5.17 product hosts (custom `error`s + struct returns need 0.8.x); the 0.5.x +// hooks call the queue through the minimal cross-pragma stub +// `IExitDelayQueueHook.sol` (the four `record*` fns), whose signatures are kept +// byte-for-byte identical to the ones declared below. +// aderyn-ignore-next-line(unspecific-solidity-pragma) +pragma solidity >=0.8.4 <0.9.0; + +/// @title IExitDelayQueue +/// @notice External ABI + type/event/error catalog for `ExitDelayQueue`, the +/// per-request escrow that holds the *user* leg of an exit for a +/// configurable delay so a detected theft can be blocked (frozen or +/// blacklisted) and routed to recovery before the funds leave. +/// +/// This interface mirrors the queue's complete function catalog +/// and its event/error catalog, and declares the shared types. +/// +/// Types (enums/structs) are declared here so cross-pragma callers +/// and off-chain tooling share one source of truth. The queue itself +/// is 0.8.20 UUPS; the four `record*` ingress fns are consumed from +/// 0.5.x / 0.6.x product hosts via a minimal interface stub. +interface IExitDelayQueue { + // ─── Types ─────────────────────────────────────────────────── + + /// @notice Per-request lifecycle. `None` is the zero value (never stored + /// for a live id); the three terminal states are mutually + /// exclusive and a request leaves `Queued` at most once. + enum ExitStatus { + None, // 0 — never recorded + Queued, // 1 — escrowed, awaiting execute / recovery + Executed, // 2 — paid to receiver (terminal) + ResolvedToProtocol, // 3 — Leg-2 recovery-away (terminal) + ResolvedBySIP // 4 — Leg-3 DAO catch-all (terminal) + } + + /// @notice Per-address block state. `Frozen` = temporary (investigating); + /// `Blacklisted` = confirmed hack. Execution treats both as + /// "blocked"; recovery-away distinguishes them. + enum BlockState { + None, // 0 + Frozen, // 1 — temporary, cleared by unfreeze + Blacklisted // 2 — confirmed, cleared only by unblacklist + } + + /// @notice An immutable exit request. Every field except `status` is + /// frozen at record time. Packed into 7 words. + struct ExitRequest { + // word 1 (128 + 64 + 64 = 256 bits): + uint128 amount; // narrowed from the uint256 ColFee amount at record + uint64 createdAt; // audit/analytics; emitted in ExitQueued + uint64 unlockAt; // COMPUTED by the queue = createdAt + delaySeconds + // words 2-5: + address originator; // withdrawal caller (effective, post-normalization) — block key + executor + address owner; // position owner — MANDATORY block key + executor + address receiver; // immutable payout destination — block key iff freezeReceiver; NOT an executor + address token; // address(0) = native RBTC + // word 6: + bytes32 surfaceId; // provenance: recovery-route key + // word 7 (160 + 8 + 8 = 176 bits): + address subProduct; // provenance: iToken / converter / address(0) + ExitStatus status; // uint8 + bool unwrapOnDelivery; // queue holds WRBTC, executeExit unwraps → native RBTC + } + + /// @notice A pre-approved Leg-2 recovery route. `routeId` is + /// `keccak256(abi.encode(surfaceId, subProduct, token, destination))`. + struct RecoveryRoute { + bool active; + bytes32 surfaceId; + address subProduct; + address token; + address destination; + bool topUpPool; // 2a: plain top-up of the originating pool (destination == subProduct) + } + + // ─── Events ────────────────────────────────────────────────── + + event ExitQueued( + uint256 indexed id, + address indexed originator, + address indexed owner, + address receiver, + address token, + uint128 amount, + uint64 unlockAt, + bytes32 surfaceId, + address subProduct + ); + event ExitExecuted( + uint256 indexed id, + address indexed receiver, + address token, + uint128 amount + ); + event ExitResolvedToProtocol( + uint256 indexed id, + bytes32 indexed routeId, + address destination, + uint128 amount + ); + event ExitResolvedBySIP(uint256 indexed id, address indexed destination, uint128 amount); + event AccountBlocked( + address indexed account, + BlockState state, + uint256 indexed triggerRequestId, + bytes32 reasonHash + ); + event AccountUnblocked(address indexed account, BlockState fromState); + event RecoveryRouteSet( + bytes32 indexed routeId, + bytes32 surfaceId, + address subProduct, + address token, + address destination, + bool topUpPool + ); + event RecoveryRouteRemoved(bytes32 indexed routeId); + event AllowedSourceSet(address indexed source, bool allowed); + event TopUpFeasibleSet(bytes32 indexed surfaceId, bool feasible); + event MinimumDelaySet(uint32 seconds_); + event SecurityPerimeterPausedSet(bool paused); + event NativePusherSet(address indexed pusher); + event SurplusSwept(address indexed token, address indexed to, uint256 amount); + + // ─── Custom errors ─────────────────────────────────────────── + + error UnregisteredSource(address caller); // onlyAllowedSource — DISTINCT record-path halt selector + error ActorBlocked(address actor, BlockState state); // execution-gate revert (event: AccountBlocked) + error NotExecutor(address caller); // msg.sender ∉ {originator, owner} + error NotUnlocked(uint256 id, uint64 unlockAt); + error QueuePaused(); + error AlreadyTerminal(uint256 id); // status != Queued at a transition (also duplicate-batch-id) + error UnknownRequest(uint256 id); + error DelayBelowFloor(uint32 delay, uint32 floor); + error AmountTooLarge(uint256 amount); // uint256→uint128 narrowing guard + error AmountMismatch(uint256 msgValue, uint256 amount); // native value-carrying + error ReceivedAmountMismatch(address token, uint256 have, uint256 want); // pull / measured-delta proof + error ZeroAmount(); + error RouteInactive(bytes32 routeId); + error RouteProvenanceMismatch(uint256 id, bytes32 routeId); + error TopUpInfeasibleSurface(bytes32 surfaceId); // setRecoveryRoute topUpPool guard + error SourceNotBlacklisted(address src); // Leg-2 OR-predicate not satisfied + error NotBlacklisted(address a); // unblacklist on a non-Blacklisted address + error NotFrozen(address a); // unfreeze on a non-Frozen address + error NotResolvableBySIP(uint256 id); // Leg-3 bounded predicate not satisfied + error UnwrapNonWrbtc(); // unwrapOnDelivery set on a non-WRBTC token + error InvalidAltReceiver(address altReceiver); // recoverStuckExit altReceiver ∈ {0,this,token,wrbtc} + error SelfOnly(); // payoutExternal trampoline is self-call-only + error ZeroAddress(); + error EmptyIds(); + error SweepToZero(); + error SolvencyViolated(); // post-sweep balance < totalEscrowed + + // ─── Ingress ───────────────────────────────────────────────── + + /// @dev CALLER-SIDE NARROWING PRECONDITION. Every + /// `record*` takes `amount` as a **`uint128`**, deliberately NOT widened + /// to `uint256`. The ColFee hook computes the user leg as a `uint256` and + /// MUST narrow it (`uint128(userAmount)`) at the call site; that narrowing + /// is the caller's responsibility and MUST be preceded by the caller's own + /// `require(userAmount <= type(uint128).max)` (`AmountTooLarge`) so a value + /// that would silently truncate is rejected UPSTREAM, before any escrow + /// accounting. The queue keeps `AmountTooLarge` as a defensive + /// queue-boundary guard on the narrowing path — it is NOT dead code: it is + /// the last line of defense if a caller ever omits its own check. Keeping + /// the ABI at `uint128` also packs `amount` into `ExitRequest` word 1 + /// — widening would cost a whole extra storage word per request. + + function recordERC20Exit( + address token, + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver, + bool unwrapOnDelivery + ) external returns (uint256 id); + + function recordReceivedERC20Exit( + address token, + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver + ) external returns (uint256 id); + + function recordNativeExit( + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver + ) external payable returns (uint256 id); + + function recordReceivedNativeExit( + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver + ) external returns (uint256 id); + + // ─── Execution ─────────────────────────────────────────────── + + function executeExit(uint256 requestId) external; + + function executeExits(uint256[] calldata ids) external; + + /// @notice Verify-by-attempting stuck-exit recovery. Callable ONLY by the + /// frozen-metadata `{originator, owner}` set (same as `executeExit`; the + /// receiver is NEVER an executor). Requires the request Queued, unlocked, + /// and the queue not paused. + /// + /// Attempts the STORED-receiver payout FIRST; pays `altReceiver` ONLY if + /// the stored-receiver payout genuinely bounces — so a HEALTHY exit is + /// never redirected (no arbitrary redirect) + /// and there is NO stored failure flag. If `altReceiver` also fails, the + /// whole call reverts (funds stay Queued). + /// + /// Block gate covers ALL FOUR actors — `{originator, owner, STORED + /// receiver, altReceiver}`: a blocked/hacked original receiver refuses + /// recovery entirely (→ Leg-3), so the blacklist trap is preserved + /// rather than turned into an escape hatch, and + /// a permanently undeliverable payout. `altReceiver` is guarded: reverts if it is + /// `0`, this contract, the request token, or WRBTC. The stored request is + /// NEVER re-targeted (`altReceiver` is a payout-time destination only), so + /// request immutability and the block gate still hold. + function recoverStuckExit(uint256 id, address altReceiver) external; + + // ─── Block model ───────────────────────────────────────────── + + function freezeFromRequest( + uint256 requestId, + bool freezeReceiver, + bytes32 reasonHash + ) external; + + function blacklistFromRequest( + uint256 requestId, + bool freezeReceiver, + bytes32 reasonHash + ) external; + + // Batch by-request-id — whole-batch atomic (one bad + // id reverts all, like executeExits); last-write-wins trigger/reason per account. + function freezeFromRequest( + uint256[] calldata requestIds, + bool freezeReceiver, + bytes32 reasonHash + ) external; + + function blacklistFromRequest( + uint256[] calldata requestIds, + bool freezeReceiver, + bytes32 reasonHash + ) external; + + function freeze(address a) external; + + function blacklist(address a) external; + + function unfreeze(address a) external; + + function unblacklist(address a) external; + + // Batch by-address: each reverts `EmptyIds()` on + // an empty array, for API consistency with the by-id batch variants + // (`executeExits` / batch `freezeFromRequest` / `resolveToProtocol` / + // `resolveBySIP`) — an empty batch is a caller mistake, never a silent no-op. + function freeze(address[] calldata a) external; + + function blacklist(address[] calldata a) external; + + function unfreeze(address[] calldata a) external; + + function unblacklist(address[] calldata a) external; + + // ─── Pause ─────────────────────────────────────────────────── + + function setSecurityPerimeterPaused(bool p) external; + + // ─── Recovery ──────────────────────────────────────────────── + + function resolveToProtocol(uint256[] calldata ids, bytes32 routeId) external; + + function resolveBySIP(uint256[] calldata ids, address destination) external; + + function setRecoveryRoute(RecoveryRoute calldata route) external returns (bytes32 routeId); + + function removeRecoveryRoute(bytes32 routeId) external; + + function setTopUpFeasible(bytes32 surfaceId, bool feasible) external; + + // ─── Config ────────────────────────────────────────────────── + + function addAllowedSource(address src) external; + + function removeAllowedSource(address src) external; + + function setNativePusher(address pusher) external; + + function setMinimumDelaySeconds(uint32 s) external; + + function sweepSurplus(address token, address to) external; + + // ─── Views ─────────────────────────────────────────────────── + + function getRequest(uint256 id) external view returns (ExitRequest memory); + + function getActive( + address party, + uint256 cursor, + uint256 n + ) external view returns (uint256[] memory ids, uint256 nextCursor); + + function blockStateOf(address a) external view returns (BlockState); + + /// @notice Paginate the blocked set (Frozen ∪ Blacklisted). + /// @param offset First index into the blocked set to return. + /// @param limit Requested page size; clamped to `MAX_GET_ACTIVE_PAGE` (500). + /// @return page The clamped slice `[offset, offset + page.length)` of the + /// blocked set (empty when `offset >= total` or `limit == 0`). + /// @return total The FULL blocked-set size (EnumerableSet length), independent + /// of `offset`/`limit` — so a caller/monitor knows the whole + /// range ("showing offset..offset+page.length of total") and + /// never silently undercounts past the 500-entry page cap + function blockedAccounts( + uint256 offset, + uint256 limit + ) external view returns (address[] memory page, uint256 total); + + function blockTrigger(address a) external view returns (uint256); + + function totalEscrowed(address token) external view returns (uint256); + + function getRecoveryRoute(bytes32 routeId) external view returns (RecoveryRoute memory); + + function allowedSources() external view returns (address[] memory); + + /// @notice Max page size for the paginated `getActive` / `blockedAccounts` + /// views. Public constant, so paging is + /// self-describing on-chain (500). + function MAX_GET_ACTIVE_PAGE() external view returns (uint256); +} diff --git a/contracts/Interfaces/colfee/IExitDelayQueueHook.sol b/contracts/Interfaces/colfee/IExitDelayQueueHook.sol new file mode 100644 index 0000000..2af066e --- /dev/null +++ b/contracts/Interfaces/colfee/IExitDelayQueueHook.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +// ───────────────────────────────────────────────────────────────────────────── +// Cross-pragma ingress stub for `ExitDelayQueue` (the security-perimeter delay +// queue). The FULL interface + type/event/error catalog lives in +// `IExitDelayQueue.sol` (0.8.20, provenance-locked to +// DistributedCollective/colfee @ 51457b21). This stub declares ONLY the members +// the 0.5.17 lending + borrower/margin product hooks actually call, so it can be +// imported under the range pragma the product repos compile with. +// +// The four `record*` signatures here are byte-for-byte identical to the FINAL +// ones in `IExitDelayQueue.sol`. Do NOT diverge — the queue's +// IExitDelayQueue is final. +// ───────────────────────────────────────────────────────────────────────────── +// aderyn-ignore-next-line(unspecific-solidity-pragma) +pragma solidity >=0.5.17 <0.9.0; + +/// @title IExitDelayQueueHook +/// @notice The minimal ingress surface the product hooks reach on the queue when +/// the controller quote returns `d > 0`. All ingress is +/// `onlyAllowedSource` on the queue (the record-CALLER must be a +/// registered source): the iToken proxy for lending, the `sovrynProtocol` +/// singleton for borrower/margin. Never called until `d > 0` is +/// established off-queue. +interface IExitDelayQueueHook { + /// @notice ERC20 pull ingress (preferred; also the WRBTC path). The queue does + /// `safeTransferFrom(msg.sender, address(this), amount)` and requires + /// the measured received amount == `amount`. `unwrapOnDelivery` is + /// guarded to `token == WRBTC` at the queue boundary; set it true only + /// for lending `burnToBTC` (escrow WRBTC, unwrap → native at delivery). + function recordERC20Exit( + address token, + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver, + bool unwrapOnDelivery + ) external returns (uint256 id); + + /// @notice ERC20 measured-delta ingress: the source PUSHES `amount` to the + /// queue first, then records in the SAME outer transaction. The queue + /// measures `delta = balanceOf(token) - totalEscrowed[token]`, requires + /// `delta >= amount`, and credits EXACTLY `amount`. Used by the + /// borrower/margin surface (push via `vaultWithdraw` then record). + function recordReceivedERC20Exit( + address token, + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver + ) external returns (uint256 id); + + /// @notice Native value-carrying ingress: `require(msg.value == amount)`; + /// token is implicitly `address(0)`. + function recordNativeExit( + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver + ) external payable returns (uint256 id); + + /// @notice Native measured-receipt ingress: the native RBTC is pushed to the + /// queue's `receive()` first (e.g. `vaultEtherWithdraw(queue, amount)`), + /// then recorded in the SAME outer transaction. The queue measures + /// `delta = address(this).balance - totalEscrowed[address(0)]`, requires + /// `delta >= amount`, and credits EXACTLY `amount`. Used by the + /// borrower/margin native-collateral path. + function recordReceivedNativeExit( + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver + ) external returns (uint256 id); +} diff --git a/contracts/Interfaces/colfee/IExitFeeController.sol b/contracts/Interfaces/colfee/IExitFeeController.sol index 24927d9..0490a08 100644 --- a/contracts/Interfaces/colfee/IExitFeeController.sol +++ b/contracts/Interfaces/colfee/IExitFeeController.sol @@ -1,12 +1,28 @@ // SPDX-License-Identifier: MIT // ───────────────────────────────────────────────────────────────────────────── -// Vendored copy of the ColFee exit-fee controller interface, taken from -// DistributedCollective/colfee @ c85f60aef91bc644517cf1b3ea7c5e8c565f4ca5 +// Curated cross-pragma subset of the exit-fee controller interface, derived from +// DistributedCollective/colfee @ 51457b21bc9a87958e99ea51325ded150422e791 // src/interfaces/IExitFeeController.sol -// Do not change the declarations here: the binding property is ABI equality with -// the deployed controller. To pick up an interface change, change it upstream, -// re-copy, and bump the SHA above. Local formatting follows this repo's -// formatter, so the file is not byte-identical to the upstream source. +// +// This is a deliberate SUBSET, not a verbatim copy: it declares only the members +// the product hooks call — `quoteExitFee`, the delay members `quoteExitDelayFor` +// / `quoteExitDelay` / `effectiveActor`, and the bypass/passthrough read views. +// It omits the upstream governance, enumeration and admin surface, which is used +// only by the 0.8.20 controller implementation and its tooling, never by a +// 0.6.11 hook. +// +// The binding property is ABI equality with the deployed controller for the +// members declared here. In particular +// quoteExitDelayFor(address,address,address,bytes32,address) +// view returns (uint32, address, address) +// is byte-identical to upstream and MUST stay so — `BorrowerOperations. +// _safeQuoteExitDelay` calls it. +// +// To update: re-derive from upstream at a known SHA rather than blind-copying +// (a copy that trims back to the upstream member list would delete +// `quoteExitDelayFor`), keep the subset to what the hosts need, and bump the SHA +// above. Local formatting follows this repo's formatter, so the file is not +// byte-identical to the upstream source. // ───────────────────────────────────────────────────────────────────────────── // Range pragma is intentional: the same declarations are compiled under Solidity // 0.5.17, 0.6.11 (this repo), and 0.8.20. @@ -54,6 +70,18 @@ interface IExitFeeController { uint16 rateBps; } + /// @notice A delay bypass/exemption entry, mirroring the fee tiers + /// (actor → sub-product → surface). `active == false` ⇒ the tier is + /// not configured; resolution falls through. `active == true` ⇒ this + /// tier decides: `bypass == true` exempts (`d = 0`), `bypass == false` + /// FORCES `globalDelaySeconds` (overriding a broader bypass). It is an + /// exemption toggle only — there is no per-instance delay + /// duration. Copied final from colfee. + struct DelayBypassPolicy { + bool active; + bool bypass; + } + /// @notice Quote returned by `quoteExitFee`. `reason` carries the precise /// off-state code; `active` is the resolved policy state (true iff /// a RatePolicy.active entry was used and reason ∈ {NONE}). @@ -86,6 +114,28 @@ interface IExitFeeController { event SubProductPolicyRemoved(bytes32 indexed surfaceId, address indexed subProduct); event ActorPolicyRemoved(bytes32 indexed surfaceId, address indexed actor); + // Delay extension. + event SecurityPerimeterEnabledSet(bool enabled); + event GlobalDelaySet(uint32 seconds_); + event SurfaceBypassSet(bytes32 indexed surfaceId, bool active, bool bypass); + event SubProductBypassSet( + bytes32 indexed surfaceId, + address indexed subProduct, + bool active, + bool bypass + ); + event ActorBypassSet( + bytes32 indexed surfaceId, + address indexed actor, + bool active, + bool bypass + ); + event PassthroughActorSet( + bytes32 indexed surfaceId, + address indexed actor, + bool isPassthrough + ); + // ─── Quote ──────────────────────────────────────────────────────────── /// @notice Resolve the fee policy for `(surfaceId, subProduct, actor)` and @@ -100,6 +150,42 @@ interface IExitFeeController { uint256 grossAmount ) external view returns (ExitFeeQuote memory); + // ─── Delay quote (security perimeter) ───────────────────────────────── + + /// @notice The hook's SINGLE delay entry. Short-circuits the kill + /// switch FIRST: `if (!securityPerimeterEnabled) return (0, + /// rawOriginator, owner)` (pays direct without touching the queue). + /// Otherwise resolves the surface-scoped effective actors, quotes on + /// `effOrig`, and returns all three — so the quote and the record use + /// the SAME identity (Finding 2). The hook MUST ignore `effOrig` / + /// `effOwner` and pay direct whenever `d == 0`. + /// @return d Delay seconds to escrow for (0 ⇒ off / inactive / bypassed). + /// @return effOrig Effective originator (raw, or passthrough→receiver). + /// @return effOwner Effective owner (raw, or passthrough→receiver). + function quoteExitDelayFor( + address rawOriginator, + address owner, + address receiver, + bytes32 surfaceId, + address subProduct + ) external view returns (uint32 d, address effOrig, address effOwner); + + /// @notice Inner per-actor delay view (off / inactive / bypass ⇒ 0, else + /// `globalDelaySeconds`) on an already-effective actor; off-chain use. + function quoteExitDelay( + bytes32 surfaceId, + address subProduct, + address effectiveActor + ) external view returns (uint32); + + /// @notice Resolve a surface-scoped passthrough: a passthrough registered for + /// `surfaceId` resolves `raw` to `receiver`, else identity. + function effectiveActor( + bytes32 surfaceId, + address raw, + address receiver + ) external view returns (address); + // ─── State views ────────────────────────────────────────────────────── function exitFeeEnabled() external view returns (bool); @@ -122,6 +208,26 @@ interface IExitFeeController { function actorKeys(bytes32 surfaceId) external view returns (address[] memory); + // ─── Delay state views ──────────────────────────────────────────────── + + function securityPerimeterEnabled() external view returns (bool); + + function globalDelaySeconds() external view returns (uint32); + + function surfaceBypass(bytes32 surfaceId) external view returns (DelayBypassPolicy memory); + + function subProductBypass( + bytes32 surfaceId, + address subProduct + ) external view returns (DelayBypassPolicy memory); + + function actorBypass( + bytes32 surfaceId, + address actor + ) external view returns (DelayBypassPolicy memory); + + function passthroughActor(bytes32 surfaceId, address a) external view returns (bool); + // ─── Admin ──────────────────────────────────────────────────────────── function setExitFeeEnabled(bool enabled) external; @@ -157,4 +263,28 @@ interface IExitFeeController { function removeActorPolicy(bytes32 surfaceId, address actor) external; function removeActorPolicies(bytes32 surfaceId, address[] calldata actors) external; + + // ─── Delay admin (security perimeter) ───────────────────────────────── + // The kill switch is `onlyAdminOrOwner`; every other delay setter is + // `onlyOwner`. View quotes are ungated. + + function setSecurityPerimeterEnabled(bool enabled) external; + + function setGlobalDelaySeconds(uint32 seconds_) external; + + function setSurfaceBypass(bytes32 surfaceId, DelayBypassPolicy calldata policy) external; + + function setSubProductBypass( + bytes32 surfaceId, + address subProduct, + DelayBypassPolicy calldata policy + ) external; + + function setActorBypass( + bytes32 surfaceId, + address actor, + DelayBypassPolicy calldata policy + ) external; + + function setPassthroughActor(bytes32 surfaceId, address a, bool isPassthrough) external; } diff --git a/contracts/TestContracts/ExitFeeControllerMock.sol b/contracts/TestContracts/ExitFeeControllerMock.sol index a825a19..7a481fc 100644 --- a/contracts/TestContracts/ExitFeeControllerMock.sol +++ b/contracts/TestContracts/ExitFeeControllerMock.sol @@ -28,6 +28,16 @@ contract ExitFeeControllerMock { uint256 public forcedFeeAmount; uint256 public forcedNetAmount; + // --- Delay (security-perimeter) knobs --- + bool public perimeterEnabled; // maps to securityPerimeterEnabled + uint32 public delaySeconds; // returned as `d` when the perimeter charges a delay + bool public delayRevert; // when true, quoteExitDelayFor reverts → exercises the hook's FAIL-CLOSED leg + // Optional passthrough override so a test can force effOrig/effOwner != raw + // (Zero has no passthrough in production, but the fail-closed identity + // threading is still asserted). + bool public overridePassthrough; + address public forcedEffActor; + function configure( bool _active, uint16 _rateBps, @@ -57,6 +67,42 @@ contract ExitFeeControllerMock { forcedNetAmount = _net; } + // --- Delay configuration --- + + function configureDelay(bool _enabled, uint32 _delaySeconds) external { + perimeterEnabled = _enabled; + delaySeconds = _delaySeconds; + } + + function setDelayRevert(bool _v) external { + delayRevert = _v; + } + + function setForcedPassthrough(bool _on, address _effActor) external { + overridePassthrough = _on; + forcedEffActor = _effActor; + } + + /// @dev Single hook entry. Short-circuits the kill switch FIRST: + /// a disabled perimeter returns (0, raw, owner) — pay direct. Otherwise + /// returns the configured delay and (optionally forced) effective actors. + function quoteExitDelayFor( + address rawOriginator, + address owner, + address /* receiver */, + bytes32 /* surfaceId */, + address /* subProduct */ + ) external view returns (uint32 d, address effOrig, address effOwner) { + require(!delayRevert, "EFCMock: forced delay revert"); + if (!perimeterEnabled) { + return (0, rawOriginator, owner); + } + if (overridePassthrough) { + return (delaySeconds, forcedEffActor, forcedEffActor); + } + return (delaySeconds, rawOriginator, owner); + } + function quoteExitFee( bytes32, address, diff --git a/contracts/TestContracts/MockExitDelayQueue.sol b/contracts/TestContracts/MockExitDelayQueue.sol new file mode 100644 index 0000000..3e873b9 --- /dev/null +++ b/contracts/TestContracts/MockExitDelayQueue.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.11; +pragma experimental ABIEncoderV2; + +import "../Interfaces/colfee/IExitDelayQueueHook.sol"; + +/// @title MockExitDelayQueue +/// @notice Minimal test double for the real 0.8.20 `ExitDelayQueue`, implemented +/// at 0.6.11 so the zero-contracts hardhat suites can deploy it and wire +/// it behind `BorrowerOperations` (the registered allowed source). It +/// mirrors ONLY the behaviour the Zero borrower hook depends on: +/// - an unconditional native `receive()` that accepts RBTC from +/// anyone with no sender gate; +/// - `recordReceivedNativeExit` (`onlyAllowedSource`), which narrows to +/// uint128, enforces the per-request delay floor, proves receipt by +/// measured-delta (surplus `>= amount`, credit EXACTLY `amount` — +/// ), stamps `unlockAt = now + delaySeconds`, and emits +/// `ExitQueued`; +/// - `executeExit`, which pays the immutable receiver after `unlockAt` +/// iff `msg.sender ∈ {originator, owner}` (receiver is NOT an executor) +/// and none of {originator, owner, receiver} is blocked; +/// - a minimal `freeze`/`unfreeze` block model so the fail-closed / +/// block-trap regressions can be exercised. +/// The three ERC20 / value-carrying ingress fns are present for interface +/// completeness but revert (the Zero surface is native-only). This is +/// deliberately NOT the full security model — the real queue's recovery +/// legs and per-request index are covered by the colfee Foundry suite. +contract MockExitDelayQueue is IExitDelayQueueHook { + struct Req { + uint128 amount; + uint64 createdAt; + uint64 unlockAt; + address originator; + address owner; + address receiver; + address token; // address(0) = native + bytes32 surfaceId; + address subProduct; + bool executed; + } + + uint32 public minimumDelaySeconds; + uint256 public lastRequestId; + mapping(uint256 => Req) internal _requests; + mapping(address => bool) public allowedSource; + mapping(address => bool) public blocked; + // token => sum of Queued amounts (backing). address(0) = native. + mapping(address => uint256) public totalEscrowed; + + event ExitQueued( + uint256 indexed id, + address indexed originator, + address indexed owner, + address receiver, + address token, + uint128 amount, + uint64 unlockAt, + bytes32 surfaceId, + address subProduct + ); + event ExitExecuted( + uint256 indexed id, + address indexed receiver, + address token, + uint128 amount + ); + + constructor(uint32 _minDelay) public { + minimumDelaySeconds = _minDelay; + } + + /// @dev Unconditional native receive() — accepts RBTC from anyone. + receive() external payable {} + + function setAllowedSource(address src, bool ok) external { + allowedSource[src] = ok; + } + + function freeze(address a) external { + blocked[a] = true; + } + + function unfreeze(address a) external { + blocked[a] = false; + } + + modifier onlyAllowedSource() { + require(allowedSource[msg.sender], "MockQueue: unregistered source"); + _; + } + + function getRequest(uint256 id) external view returns (Req memory) { + return _requests[id]; + } + + // ── Native measured-receipt ingress (the ONLY path Zero uses) ───────────── + + function recordReceivedNativeExit( + uint128 amount, + uint32 delaySeconds, + bytes32 surfaceId, + address subProduct, + address effOrig, + address effOwner, + address receiver + ) external override onlyAllowedSource returns (uint256 id) { + require(amount > 0, "MockQueue: zero amount"); + require(delaySeconds >= minimumDelaySeconds, "MockQueue: delay below floor"); + // measured-receipt: the native RBTC was pushed to receive() first; the + // non-backing surplus must cover `amount`, and we credit EXACTLY `amount` + // A stray donation only raises the surplus and cannot brick the record. + uint256 surplus = address(this).balance - totalEscrowed[address(0)]; + require(surplus >= amount, "MockQueue: received amount mismatch"); + totalEscrowed[address(0)] += amount; + + id = ++lastRequestId; + Req storage r = _requests[id]; + r.amount = amount; + r.createdAt = uint64(block.timestamp); + r.unlockAt = uint64(block.timestamp + delaySeconds); + r.originator = effOrig; + r.owner = effOwner; + r.receiver = receiver; + r.token = address(0); + r.surfaceId = surfaceId; + r.subProduct = subProduct; + + emit ExitQueued( + id, + effOrig, + effOwner, + receiver, + address(0), + amount, + r.unlockAt, + surfaceId, + subProduct + ); + } + + // ── Execution (subset) ──────────────────────────────────────────────────── + + function executeExit(uint256 id) external { + Req storage r = _requests[id]; + require(r.amount > 0 && !r.executed, "MockQueue: not queued"); + require(block.timestamp >= r.unlockAt, "MockQueue: not unlocked"); + require(msg.sender == r.originator || msg.sender == r.owner, "MockQueue: not executor"); + require( + !blocked[r.originator] && !blocked[r.owner] && !blocked[r.receiver], + "MockQueue: actor blocked" + ); + + r.executed = true; + totalEscrowed[r.token] -= r.amount; + uint128 amount = r.amount; + address payable receiver = address(uint160(r.receiver)); + + (bool ok, ) = receiver.call{ value: amount }(""); + require(ok, "MockQueue: payout failed"); + emit ExitExecuted(id, receiver, r.token, amount); + } + + // ── Interface completeness (Zero is native-only; these are unused) ──────── + + function recordERC20Exit( + address, + uint128, + uint32, + bytes32, + address, + address, + address, + address, + bool + ) external override returns (uint256) { + revert("MockQueue: erc20 unsupported"); + } + + function recordReceivedERC20Exit( + address, + uint128, + uint32, + bytes32, + address, + address, + address, + address + ) external override returns (uint256) { + revert("MockQueue: erc20 unsupported"); + } + + function recordNativeExit( + uint128, + uint32, + bytes32, + address, + address, + address, + address + ) external payable override returns (uint256) { + revert("MockQueue: value-carrying unsupported"); + } +} diff --git a/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol b/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol new file mode 100644 index 0000000..e540dd5 --- /dev/null +++ b/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.11; +pragma experimental ABIEncoderV2; + +import "../Interfaces/colfee/IExitDelayQueueHook.sol"; + +/// @title SelectorRevertingExitDelayQueue +/// @notice Test double for the SR1 selector-propagation regression. The +/// REAL `ExitDelayQueue` is Solidity 0.8.20 and its `onlyAllowedSource` +/// guard reverts with the CUSTOM ERROR `UnregisteredSource(address)` — a +/// distinct 4-byte selector the off-chain halt watcher keys on. This +/// mock reproduces that exact revert payload at 0.6.11 +/// (which cannot declare `error` types) by reverting with the raw +/// ABI-encoding `abi.encodeWithSelector(UnregisteredSource.selector, +/// msg.sender)` via inline assembly. +/// +/// The point of the regression: the 0.6.11 `BorrowerOperations` delay +/// hook calls `recordReceivedNativeExit` as a PLAIN external call (it does +/// NOT wrap it in a try/catch or re-`require` with a string reason), so +/// the queue's custom-error selector must BUBBLE UP UNCHANGED out of the +/// reverting trove exit. A test asserts the returndata's leading 4 bytes +/// equal `bytes4(keccak256("UnregisteredSource(address)"))` — proving the +/// distinct halt selector survives the cross-pragma boundary and is not +/// masked by a host-side wrapper. +contract SelectorRevertingExitDelayQueue is IExitDelayQueueHook { + /// bytes4(keccak256("UnregisteredSource(address)")) — evaluated at compile time. + bytes4 public constant UNREGISTERED_SOURCE_SELECTOR = + bytes4(keccak256("UnregisteredSource(address)")); + + /// @dev Unconditional native receive() — the ActivePool push lands here + /// BEFORE the record call, exactly as against the real queue, so the revert + /// under test is the record leg (not a failed push). + receive() external payable {} + + /// @dev Reverts with the raw `UnregisteredSource(msg.sender)` custom-error bytes, + /// byte-identical to what the 0.8.20 queue emits. No string wrapping. + function recordReceivedNativeExit( + uint128, + uint32, + bytes32, + address, + address, + address, + address + ) external override returns (uint256) { + bytes memory err = abi.encodeWithSelector(UNREGISTERED_SOURCE_SELECTOR, msg.sender); + assembly { + revert(add(err, 0x20), mload(err)) + } + } + + // ── Interface completeness (unused by the Zero native path) ─────────────── + + function recordERC20Exit( + address, + uint128, + uint32, + bytes32, + address, + address, + address, + address, + bool + ) external override returns (uint256) { + revert("unsupported"); + } + + function recordReceivedERC20Exit( + address, + uint128, + uint32, + bytes32, + address, + address, + address, + address + ) external override returns (uint256) { + revert("unsupported"); + } + + function recordNativeExit( + uint128, + uint32, + bytes32, + address, + address, + address, + address + ) external payable override returns (uint256) { + revert("unsupported"); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index d7d8fec..50a8739 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -163,9 +163,12 @@ const config: HardhatUserConfig = { }, // Emit per-contract storageLayout so the ColFee storage-layout // zero-diff regression (tests-colfee/StorageLayout.zerodiff.test.js) - // can assert the surplus-claim fee hook adds NO state to the + // can assert that neither the surplus-claim fee hook nor the + // security-perimeter delay reroute adds state to the // upgradeable BorrowerOperations / CollSurplusPool proxies or - // ActivePool. Additive solc output; does not affect bytecode. + // ActivePool (controller/queue pointers live in EIP-1967 + // unstructured slots). Additive solc output; does not affect + // bytecode. outputSelection: { "*": { "*": ["storageLayout"], diff --git a/tests-colfee/ClaimSurplus.notouch.test.js b/tests-colfee/ClaimSurplus.notouch.test.js new file mode 100644 index 0000000..13fab50 --- /dev/null +++ b/tests-colfee/ClaimSurplus.notouch.test.js @@ -0,0 +1,188 @@ +// ColFee security perimeter — surplus-claim DELAY exemption pinning test. +// +// SURFACE_ZERO_CLAIM_SURPLUS is exempt by design from the exit-delay +// perimeter: surplus is involuntary in origin (full redemption or +// recovery-mode liquidation), is not attacker-creatable without capital, and +// rerouting it would widen the custody pool for thin marginal protection. So +// with the perimeter ACTIVE (controller enabled, d>0) and the queue WIRED, +// claimCollateral() still pays the claimant INSTANTLY — fee-ON (pool-side +// two-leg split) and fee-OFF (untouched claimColl path) alike — and the queue +// is never touched. The control test proves the SAME arming reroutes a +// voluntary withdrawColl, so the no-touch assertions are non-vacuous. +// +// This exemption is a deliberate, reviewable choice, not an oversight. If a +// delay leg is ever added to the surplus claim, retire this suite together +// with that change rather than deleting it on its own. + +const deploymentHelper = require("../utils/js/deploymentHelpers.js"); +const testHelpers = require("../utils/js/testHelpers.js"); +const timeMachine = require("ganache-time-traveler"); + +const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); +const TroveManagerTester = artifacts.require("TroveManagerTester"); +const MassetManagerTester = artifacts.require("MassetManagerTester"); +const ExitFeeControllerMock = artifacts.require("ExitFeeControllerMock"); +const MockExitDelayQueue = artifacts.require("MockExitDelayQueue"); + +const th = testHelpers.TestHelper; +const dec = th.dec; +const toBN = th.toBN; +const timeValues = testHelpers.TimeValues; +const ZERO_ADDRESS = th.ZERO_ADDRESS; + +const NONE = 0; +const DELAY = 3600; +const MIN_DELAY = 100; +const GAS_PRICE = toBN(dec(1, 9)); + +contract("ColFee delay — surplus claim EXEMPT (no-touch pinning)", async (accounts) => { + const [owner, alice, whale, dennis] = accounts; + const feeReceiver = accounts[995]; + const multisig = accounts[999]; + + let priceFeed; + let collSurplusPool; + let borrowerOperations; + let controller; + let queue; + let contracts; + + const openTrove = async (params) => th.openTrove(contracts, params); + const getEvent = (tx, name) => tx.logs.find((l) => l.event === name); + + before(async () => { + contracts = await deploymentHelper.deployLiquityCore(); + const permit2 = contracts.permit2; + + contracts.borrowerOperations = await BorrowerOperationsTester.new(permit2.address); + contracts.massetManager = await MassetManagerTester.new(); + contracts.troveManager = await TroveManagerTester.new(permit2.address); + contracts = await deploymentHelper.deployZUSDTokenTester(contracts); + const ZEROContracts = await deploymentHelper.deployZEROTesterContractsHardhat(multisig); + + await ZEROContracts.zeroToken.unprotectedMint(multisig, toBN(dec(20, 24))); + + await deploymentHelper.connectZEROContracts(ZEROContracts); + await deploymentHelper.connectCoreContracts(contracts, ZEROContracts); + await deploymentHelper.connectZEROContractsToCore(ZEROContracts, contracts); + + priceFeed = contracts.priceFeedTestnet; + collSurplusPool = contracts.collSurplusPool; + borrowerOperations = contracts.borrowerOperations; + + await borrowerOperations.setMassetManagerAddress(contracts.massetManager.address); + }); + + let snapshotId; + beforeEach(async () => { + const snap = await timeMachine.takeSnapshot(); + snapshotId = snap["result"]; + controller = await ExitFeeControllerMock.new(); + queue = await MockExitDelayQueue.new(MIN_DELAY); + await queue.setAllowedSource(borrowerOperations.address, true); + // Perimeter ACTIVE + queue WIRED — identical arming to the reroute suites, + // so a delay leg on the surplus claim WOULD fire here if one existed. + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + await controller.configureDelay(true, DELAY); + }); + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + + // Same surplus fixture as ZeroClaimSurplus.test.js: fully redeem a ~200%-ICR + // trove at ETH:USD = 100; surplus == coll - netDebt/price stays for claimant. + const setupSurplus = async (claimant) => { + const price = toBN(dec(100, 18)); + await priceFeed.setPrice(price); + const { netDebt } = await openTrove({ + ICR: toBN(dec(200, 16)), + extraParams: { from: claimant }, + }); + await openTrove({ + extraZUSDAmount: netDebt, + extraParams: { from: whale, value: dec(3000, "ether") }, + }); + await th.fastForwardTime(timeValues.SECONDS_IN_ONE_WEEK * 2, web3.currentProvider); + await th.redeemCollateralAndGetTxObject(whale, contracts, netDebt); + const gross = await collSurplusPool.getCollateral(claimant); + assert.isTrue(gross.gt(toBN(0)), "setup failed: no surplus created"); + return gross; + }; + + const assertQueueUntouched = async () => { + assert.equal((await queue.lastRequestId()).toString(), "0", "queue recorded a request"); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(toBN(0)), + "queue escrowed RBTC" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(toBN(0))); + }; + + it("CONTROL (non-vacuous): the SAME arming reroutes a voluntary withdrawColl into the queue", async () => { + await priceFeed.setPrice(dec(200, 18)); + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: dennis, value: toBN(dec(100, "ether")) }, + }); + const amount = toBN(dec(1, "ether")); + await borrowerOperations.withdrawColl(amount, dennis, dennis, { from: dennis }); + + assert.equal( + (await queue.lastRequestId()).toString(), + "1", + "arming is vacuous: withdrawColl did not reroute" + ); + assert.isTrue( + (await queue.totalEscrowed(ZERO_ADDRESS)).eq(amount), + "escrowed != withdrawn gross" + ); + }); + + it("fee-OFF claim: claimant paid FULL gross instantly, queue untouched", async () => { + const gross = await setupSurplus(alice); + + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(gross).sub(gasCost)), + "claimant != +FULL gross instantly" + ); + assert.isTrue( + (await collSurplusPool.getCollateral(alice)).eq(toBN(0)), + "claimable not zeroed" + ); + assert.isDefined(getEvent(tx, "ExitFeeSkipped"), "fee-off path must emit ExitFeeSkipped"); + await assertQueueUntouched(); + }); + + it("fee-ON claim (50 bps): fee→feeReceiver + net→claimant instantly, queue untouched", async () => { + const gross = await setupSurplus(alice); + await controller.configure(true, 50, feeReceiver, NONE); + + const fee = gross.mul(toBN(50)).div(toBN(10000)); + const net = gross.sub(fee); + + const frBefore = toBN(await web3.eth.getBalance(feeReceiver)); + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue( + toBN(await web3.eth.getBalance(feeReceiver)).eq(frBefore.add(fee)), + "feeReceiver != +fee" + ); + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(net).sub(gasCost)), + "claimant != +net instantly" + ); + assert.isTrue( + (await collSurplusPool.getCollateral(alice)).eq(toBN(0)), + "claimable not zeroed" + ); + assert.isDefined(getEvent(tx, "ExitFeeApplied"), "charging path must emit ExitFeeApplied"); + await assertQueueUntouched(); + }); +}); diff --git a/tests-colfee/StorageLayout.zerodiff.test.js b/tests-colfee/StorageLayout.zerodiff.test.js index 6159b71..3cd07e3 100644 --- a/tests-colfee/StorageLayout.zerodiff.test.js +++ b/tests-colfee/StorageLayout.zerodiff.test.js @@ -1,28 +1,31 @@ // ColFee security perimeter — storage-layout ZERO-DIFF regression. // -// The Zero surplus-claim exit-fee hook adds NO storage to any deployed -// upgradeable contract: the surface id is a constant, the exit-fee controller -// pointer lives in an EIP-1967-style unstructured slot, and the hook declares -// no new state variables on the BorrowerOperations or CollSurplusPool proxies. -// That is true BY CONSTRUCTION today — but nothing GUARDS a future edit from -// appending a `uint256` to the proxy and silently corrupting every live -// trove's storage on the next upgrade. +// Neither the Zero surplus-claim exit-fee hook NOR the borrower exit-DELAY +// reroute adds storage to any deployed upgradeable contract: the surface ids +// are constants, the exit-fee controller pointer and the ExitDelayQueue +// pointer (keccak256("sovryn.exitDelayQueue") - 1) live in EIP-1967-style +// unstructured slots, and the hooks declare no new state variables on the +// BorrowerOperations or CollSurplusPool proxies. That is true BY CONSTRUCTION +// today — but nothing GUARDS a future edit from appending a `uint256` to the +// proxy and silently corrupting every live trove's storage on the next +// upgrade. // // This test is that guard. It compares the current, normalized solc // `storageLayout` of BorrowerOperations, CollSurplusPool, and ActivePool // against a committed baseline and FAILS on any label/slot/offset/type -// difference. The ColFee lending side carries an equivalent Hardhat guard over -// its own upgradeable contracts. +// difference. The lending side carries an equivalent guard over its own +// upgradeable contracts. // // SCOPE OF THE BASELINE (be precise about what this proves): the committed // baseline was captured at `sovryn-perimeter-fee @ b6584a6`, a tree that ALREADY // contains the borrower-exit hook (`_sendCollWithExitFee`, the unstructured // controller slot, the surface-id constants). So this guard proves the -// SURPLUS-CLAIM hook appended no state, and forbids any future append to all -// three contracts. It does NOT independently re-prove the borrower-exit hook's -// zero-diff — that holds by construction (constants + an EIP-1967-style slot, -// neither of which occupies a regular-storage slot) and is reviewable in the -// contract source, but it is not what this baseline compares against. +// SURPLUS-CLAIM hook and the EXIT-DELAY hooks appended no state, and forbids +// any future append to all three contracts. It does NOT independently re-prove +// the borrower-exit hook's zero-diff — that holds by construction (constants +// plus EIP-1967-style slots for the controller and queue pointers, none of +// which occupies a regular-storage slot) and is reviewable in the contract +// source, but it is not what this baseline compares against. // // Requires `storageLayout` in the 0.6.11 compiler outputSelection // (hardhat.config.ts) — the shared helper throws (never silently passes) if the @@ -49,7 +52,7 @@ const TARGETS = [ "contracts/CollSurplusPool.sol:CollSurplusPool", // gains claimCollWithFee — functions only, no state ]; -describe("ColFee — storage-layout zero-diff (Zero surplus-claim exit fee)", () => { +describe("ColFee — storage-layout zero-diff (surplus-claim fee hook + exit-delay reroute)", () => { let baseline; before(() => { diff --git a/tests-colfee/ZeroBorrowerExit.delay.notouch.test.js b/tests-colfee/ZeroBorrowerExit.delay.notouch.test.js new file mode 100644 index 0000000..05bb50e --- /dev/null +++ b/tests-colfee/ZeroBorrowerExit.delay.notouch.test.js @@ -0,0 +1,175 @@ +// ColFee security perimeter — Zero DELAY no-touch regression. +// +// With the perimeter ACTIVE (controller enabled, d>0) and the queue WIRED, +// proves the delay reroute fires ONLY on the voluntary collateral-out chokepoint +// and is EXEMPT on the involuntary/keeper paths — liquidation, redemption, and +// stability-pool ETH-gain withdrawal route their collateral through +// TroveManager / StabilityPool, NOT through BorrowerOperations._sendCollWithExitFee, +// so the queue is never touched (lastRequestId stays 0, no RBTC escrowed). A +// keeper/liquidator/redeemer payout must never be escrowed behind a delay. + +const deploymentHelper = require("../utils/js/deploymentHelpers.js"); +const testHelpers = require("../utils/js/testHelpers.js"); +const timeMachine = require("ganache-time-traveler"); + +const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); +const TroveManagerTester = artifacts.require("TroveManagerTester"); +const MassetManagerTester = artifacts.require("MassetManagerTester"); +const ExitFeeControllerMock = artifacts.require("ExitFeeControllerMock"); +const MockExitDelayQueue = artifacts.require("MockExitDelayQueue"); + +const th = testHelpers.TestHelper; +const dec = th.dec; +const toBN = th.toBN; +const timeValues = testHelpers.TimeValues; +const ZERO_ADDRESS = th.ZERO_ADDRESS; + +const DELAY = 3600; +const MIN_DELAY = 100; +const GAS_PRICE = toBN(dec(1, 9)); + +contract( + "ColFee delay — Zero no-touch (liquidation/redemption/SP-gain exempt)", + async (accounts) => { + const [owner, alice, whale, defaulter_1] = accounts; + const feeReceiver = accounts[995]; + const multisig = accounts[999]; + + let priceFeed; + let zusdToken; + let troveManager; + let activePool; + let stabilityPool; + let borrowerOperations; + let controller; + let queue; + let contracts; + + const openTrove = async (params) => th.openTrove(contracts, params); + + before(async () => { + contracts = await deploymentHelper.deployLiquityCore(); + const permit2 = contracts.permit2; + + contracts.borrowerOperations = await BorrowerOperationsTester.new(permit2.address); + contracts.massetManager = await MassetManagerTester.new(); + contracts.troveManager = await TroveManagerTester.new(permit2.address); + contracts = await deploymentHelper.deployZUSDTokenTester(contracts); + const ZEROContracts = await deploymentHelper.deployZEROTesterContractsHardhat( + multisig + ); + + await ZEROContracts.zeroToken.unprotectedMint(multisig, toBN(dec(20, 24))); + + await deploymentHelper.connectZEROContracts(ZEROContracts); + await deploymentHelper.connectCoreContracts(contracts, ZEROContracts); + await deploymentHelper.connectZEROContractsToCore(ZEROContracts, contracts); + + priceFeed = contracts.priceFeedTestnet; + zusdToken = contracts.zusdToken; + troveManager = contracts.troveManager; + activePool = contracts.activePool; + stabilityPool = contracts.stabilityPool; + borrowerOperations = contracts.borrowerOperations; + + await borrowerOperations.setMassetManagerAddress(contracts.massetManager.address); + }); + + let snapshotId; + beforeEach(async () => { + const snap = await timeMachine.takeSnapshot(); + snapshotId = snap["result"]; + controller = await ExitFeeControllerMock.new(); + queue = await MockExitDelayQueue.new(MIN_DELAY); + await queue.setAllowedSource(borrowerOperations.address, true); + // Perimeter ACTIVE + queue WIRED — so a touched queue would be visible. + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + await controller.configureDelay(true, DELAY); + }); + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + + const assertQueueUntouched = async () => { + assert.equal( + (await queue.lastRequestId()).toString(), + "0", + "queue recorded a request" + ); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(toBN(0)), + "queue escrowed RBTC" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(toBN(0))); + }; + + it("liquidation: collateral routes via TroveManager — queue untouched", async () => { + await priceFeed.setPrice(dec(200, 18)); + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: whale, value: toBN(dec(1000, "ether")) }, + }); + await openTrove({ ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1 } }); + + await priceFeed.setPrice(dec(100, 18)); + assert.isFalse(await th.checkRecoveryMode(contracts)); + await troveManager.liquidate(defaulter_1, { from: owner }); + + assert.equal((await troveManager.Troves(defaulter_1))[3].toString(), "3"); // closedByLiquidation + await assertQueueUntouched(); + }); + + it("redemption: collateral routes via TroveManager — queue untouched", async () => { + await priceFeed.setPrice(dec(200, 18)); + await openTrove({ + ICR: toBN(dec(20, 18)), + extraZUSDAmount: toBN(dec(50000, 18)), + extraParams: { from: whale, value: toBN(dec(1000, "ether")) }, + }); + await openTrove({ ICR: toBN(dec(2, 18)), extraParams: { from: alice } }); + + await th.fastForwardTime( + timeValues.SECONDS_IN_ONE_WEEK * 2 + timeValues.SECONDS_IN_ONE_DAY, + web3.currentProvider + ); + + const apEthBefore = await activePool.getETH(); + await th.redeemCollateral(whale, contracts, toBN(dec(1000, 18))); + + assert.isTrue( + (await activePool.getETH()).lt(apEthBefore), + "redemption moved no collateral (vacuous)" + ); + await assertQueueUntouched(); + }); + + it("stability-pool ETH-gain withdrawal: routes via StabilityPool — queue untouched", async () => { + await priceFeed.setPrice(dec(200, 18)); + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: whale, value: toBN(dec(1000, "ether")) }, + }); + await openTrove({ + ICR: toBN(dec(10, 18)), + extraZUSDAmount: toBN(dec(20000, 18)), + extraParams: { from: alice, value: toBN(dec(200, "ether")) }, + }); + await stabilityPool.provideToSP(toBN(dec(10000, 18)), ZERO_ADDRESS, { from: alice }); + + await openTrove({ ICR: toBN(dec(2, 18)), extraParams: { from: defaulter_1 } }); + await priceFeed.setPrice(dec(100, 18)); + await troveManager.liquidate(defaulter_1, { from: owner }); + await priceFeed.setPrice(dec(200, 18)); + + const gain = await stabilityPool.getDepositorETHGain(alice); + assert.isTrue(gain.gt(toBN(0)), "no ETH gain accrued — setup invalid"); + + await stabilityPool.withdrawFromSP(toBN(dec(10000, 18)), { + from: alice, + gasPrice: GAS_PRICE, + }); + await assertQueueUntouched(); + }); + } +); diff --git a/tests-colfee/ZeroBorrowerExit.delay.selector.test.js b/tests-colfee/ZeroBorrowerExit.delay.selector.test.js new file mode 100644 index 0000000..bc2774b --- /dev/null +++ b/tests-colfee/ZeroBorrowerExit.delay.selector.test.js @@ -0,0 +1,171 @@ +// ColFee security perimeter — SR1 queue custom-error SELECTOR propagation. +// +// The real ExitDelayQueue is Solidity 0.8.20 and its onlyAllowedSource guard +// reverts with the DISTINCT custom error `UnregisteredSource(address)` — the +// primary fail-closed halt signal the off-chain watcher keys on. The 0.6.11 +// BorrowerOperations delay hook calls +// `recordReceivedNativeExit` as a PLAIN external call (NOT wrapped in a +// try/catch or re-`require` with a COLFEE: string), so that selector must +// BUBBLE UP UNCHANGED out of the reverting trove exit — it is neither swallowed +// nor re-wrapped by the host. +// +// This regression drives a real withdrawColl/closeTrove into a queue that +// reverts with the exact `UnregisteredSource(msg.sender)` payload and asserts +// the returndata's leading 4 bytes equal the queue's selector (and are NOT a +// COLFEE:-prefixed host string). The COMPANION host-side pre-check strings +// (COLFEE:queue-unset / COLFEE:delay-quote-failed) are asserted in +// ZeroBorrowerExit.delay.test.js — those are the reverts that CANNOT bubble a +// queue selector because they fire before/around the queue call. + +const deploymentHelper = require("../utils/js/deploymentHelpers.js"); +const testHelpers = require("../utils/js/testHelpers.js"); +const timeMachine = require("ganache-time-traveler"); + +const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); +const TroveManagerTester = artifacts.require("TroveManagerTester"); +const MassetManagerTester = artifacts.require("MassetManagerTester"); +const ExitFeeControllerMock = artifacts.require("ExitFeeControllerMock"); +const SelectorRevertingExitDelayQueue = artifacts.require("SelectorRevertingExitDelayQueue"); + +const th = testHelpers.TestHelper; +const dec = th.dec; +const toBN = th.toBN; + +const DELAY = 3600; + +// bytes4(keccak256("UnregisteredSource(address)")) — the queue's distinct halt selector. +const UNREGISTERED_SOURCE_SELECTOR = web3.utils + .keccak256("UnregisteredSource(address)") + .slice(0, 10); + +// Pull the raw revert returndata out of a reverting call. eth_call is used so the +// FULL custom-error payload (selector + args) is returned verbatim by the node, +// independent of receipt/tx error formatting. Handles the hardhat error shapes +// (top-level `data` hex, nested `data.data`, or a 0x-hex substring in `message`). +const rawRevertData = async (from, to, data) => + new Promise((resolve) => { + web3.currentProvider.send( + { + jsonrpc: "2.0", + id: Date.now(), + method: "eth_call", + params: [{ from, to, data }, "latest"], + }, + (err, res) => { + const e = err || (res && res.error); + assert.isOk(e, "expected the call to revert but it succeeded"); + let d = e.data; + if (d && typeof d === "object") d = d.data || d.result || d.value; + if (typeof d !== "string" || !d.startsWith("0x")) { + const m = (e.message || "") + " " + JSON.stringify(e); + const found = m.match(/0x[0-9a-fA-F]{8,}/); + d = found ? found[0] : ""; + } + resolve(d.toLowerCase()); + } + ); + }); + +contract("ColFee delay — SR1 queue selector propagation", async (accounts) => { + const [owner, alice] = accounts; + const multisig = accounts[999]; + + let borrowerOperations; + let controller; + let queue; + let contracts; + + const openTrove = async (params) => th.openTrove(contracts, params); + + before(async () => { + contracts = await deploymentHelper.deployLiquityCore(); + const permit2 = contracts.permit2; + + contracts.borrowerOperations = await BorrowerOperationsTester.new(permit2.address); + contracts.massetManager = await MassetManagerTester.new(); + contracts.troveManager = await TroveManagerTester.new(permit2.address); + contracts = await deploymentHelper.deployZUSDTokenTester(contracts); + const ZEROContracts = await deploymentHelper.deployZEROTesterContractsHardhat(multisig); + + await ZEROContracts.zeroToken.unprotectedMint(multisig, toBN(dec(20, 24))); + await deploymentHelper.connectZEROContracts(ZEROContracts); + await deploymentHelper.connectCoreContracts(contracts, ZEROContracts); + await deploymentHelper.connectZEROContractsToCore(ZEROContracts, contracts); + + borrowerOperations = contracts.borrowerOperations; + await borrowerOperations.setMassetManagerAddress(contracts.massetManager.address); + }); + + let snapshotId; + beforeEach(async () => { + const snap = await timeMachine.takeSnapshot(); + snapshotId = snap["result"]; + controller = await ExitFeeControllerMock.new(); + queue = await SelectorRevertingExitDelayQueue.new(); + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + await controller.configureDelay(true, DELAY); // d>0 ⇒ the reroute engages the queue + }); + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + + it("sanity: the mock reverts with the exact UnregisteredSource(address) selector", async () => { + const sel = await queue.UNREGISTERED_SOURCE_SELECTOR(); + assert.equal( + sel.toLowerCase(), + UNREGISTERED_SOURCE_SELECTOR, + "mock selector != keccak selector" + ); + }); + + it("withdrawColl (d>0): queue UnregisteredSource selector BUBBLES UP unwrapped (not a COLFEE: string)", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + + const data = borrowerOperations.contract.methods + .withdrawColl(toBN(dec(1, "ether")).toString(), alice, alice) + .encodeABI(); + const revertData = await rawRevertData(alice, borrowerOperations.address, data); + + // Distinct 4-byte selector preserved cross-pragma (0.8.20 queue → 0.6.11 host). + assert.equal( + revertData.slice(0, 10), + UNREGISTERED_SOURCE_SELECTOR, + `expected queue selector to bubble; got ${revertData}` + ); + // The bubbled payload ABI-encodes the offending caller (the BO proxy), proving + // the FULL custom-error data survived — not a truncated / re-wrapped revert. + const encodedCaller = borrowerOperations.address.slice(2).toLowerCase().padStart(64, "0"); + assert.include(revertData, encodedCaller, "custom-error arg (caller) not preserved"); + }); + + it("closeTrove (d>0): queue selector bubbles out of a failing trove CLOSE (fail-closed)", async () => { + // A second trove so alice can close hers (system keeps >1 trove / TCR ok). + await openTrove({ + extraZUSDAmount: toBN(dec(20000, 18)), + ICR: toBN(dec(3, 18)), + extraParams: { from: owner }, + }); + await openTrove({ + extraZUSDAmount: toBN(dec(10000, 18)), + ICR: toBN(dec(2, 18)), + extraParams: { from: alice }, + }); + // alice already holds enough ZUSD from her own draw to repay; top up from owner. + await contracts.zusdToken.transfer(alice, await contracts.zusdToken.balanceOf(owner), { + from: owner, + }); + + const data = borrowerOperations.contract.methods.closeTrove().encodeABI(); + const revertData = await rawRevertData(alice, borrowerOperations.address, data); + + assert.equal( + revertData.slice(0, 10), + UNREGISTERED_SOURCE_SELECTOR, + `expected queue selector to bubble out of closeTrove; got ${revertData}` + ); + }); +}); diff --git a/tests-colfee/ZeroBorrowerExit.delay.test.js b/tests-colfee/ZeroBorrowerExit.delay.test.js new file mode 100644 index 0000000..bbe67cc --- /dev/null +++ b/tests-colfee/ZeroBorrowerExit.delay.test.js @@ -0,0 +1,496 @@ +// ColFee security perimeter — Zero borrower exit DELAY hook +// (surface SURFACE_ZERO_WITHDRAW_COLL). +// +// Proves the delay reroute at the single voluntary collateral-out chokepoint +// `_sendCollWithExitFee` (reached by withdrawColl, collateral-decreasing +// adjustTrove, and closeTrove): +// - d>0 ⇒ the borrower USER leg (net on fee-ok, GROSS on fee-fail) is +// PUSHED to the queue via ActivePool.sendETH and recorded via +// recordReceivedNativeExit in the SAME tx; the borrower is NOT paid directly; +// - d==0 (perimeter disabled / unwired controller) ⇒ direct pay, byte-for-byte +// baseline, and the queue is NEVER touched; +// - fee + delay compose: fee leg to feeReceiver, delayed leg to the queue; +// - FAIL-CLOSED: a controller-quote revert, an unwired queue at d>0, or a +// record revert reverts the WHOLE exit atomically (trove state rolls back); +// - executeExit pays the immutable receiver after unlock; a blocked actor +// cannot execute (block-trap); +// - conservation: escrowed(net) + fee == gross, ActivePool drained by gross. + +const deploymentHelper = require("../utils/js/deploymentHelpers.js"); +const testHelpers = require("../utils/js/testHelpers.js"); +const timeMachine = require("ganache-time-traveler"); + +const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); +const TroveManagerTester = artifacts.require("TroveManagerTester"); +const MassetManagerTester = artifacts.require("MassetManagerTester"); +const ExitFeeControllerMock = artifacts.require("ExitFeeControllerMock"); +const MockExitDelayQueue = artifacts.require("MockExitDelayQueue"); +const NonPayable = artifacts.require("NonPayable"); + +const th = testHelpers.TestHelper; +const dec = th.dec; +const toBN = th.toBN; +const ZERO_ADDRESS = th.ZERO_ADDRESS; + +const GAS_PRICE = toBN(dec(1, 9)); +const DELAY = 3600; // 1h +const MIN_DELAY = 100; +const SURFACE = web3.utils.keccak256("COLFEE:SURFACE_ZERO_WITHDRAW_COLL"); + +contract("ColFee delay — Zero borrower exit reroute", async (accounts) => { + const [owner, alice, dennis] = accounts; + const feeReceiver = accounts[995]; + const multisig = accounts[999]; + + let zusdToken; + let troveManager; + let activePool; + let sortedTroves; + let borrowerOperations; + let controller; + let queue; + let contracts; + + const openTrove = async (params) => th.openTrove(contracts, params); + const getTroveEntireColl = async (trove) => th.getTroveEntireColl(contracts, trove); + const getEvent = (tx, name) => tx.logs.find((l) => l.event === name); + const bal = async (a) => toBN(await web3.eth.getBalance(a)); + + before(async () => { + contracts = await deploymentHelper.deployLiquityCore(); + const permit2 = contracts.permit2; + + contracts.borrowerOperations = await BorrowerOperationsTester.new(permit2.address); + contracts.massetManager = await MassetManagerTester.new(); + contracts.troveManager = await TroveManagerTester.new(permit2.address); + contracts = await deploymentHelper.deployZUSDTokenTester(contracts); + const ZEROContracts = await deploymentHelper.deployZEROTesterContractsHardhat(multisig); + + await ZEROContracts.zeroToken.unprotectedMint(multisig, toBN(dec(20, 24))); + + await deploymentHelper.connectZEROContracts(ZEROContracts); + await deploymentHelper.connectCoreContracts(contracts, ZEROContracts); + await deploymentHelper.connectZEROContractsToCore(ZEROContracts, contracts); + + zusdToken = contracts.zusdToken; + troveManager = contracts.troveManager; + activePool = contracts.activePool; + sortedTroves = contracts.sortedTroves; + borrowerOperations = contracts.borrowerOperations; + + await borrowerOperations.setMassetManagerAddress(contracts.massetManager.address); + }); + + let snapshotId; + beforeEach(async () => { + const snap = await timeMachine.takeSnapshot(); + snapshotId = snap["result"]; + controller = await ExitFeeControllerMock.new(); + queue = await MockExitDelayQueue.new(MIN_DELAY); + await queue.setAllowedSource(borrowerOperations.address, true); + }); + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + + // Wire the controller (+ optional queue pointer) and enable the perimeter with `d`. + const wire = async ({ + delaySecs = DELAY, + wireQueue = true, + feeActive = false, + rateBps = 0, + } = {}) => { + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + if (wireQueue) await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + await controller.configureDelay(true, delaySecs); + if (feeActive) await controller.configure(true, rateBps, feeReceiver, 0); + }; + + const setupCloseable = async () => { + await openTrove({ + extraZUSDAmount: toBN(dec(10000, 18)), + ICR: toBN(dec(2, 18)), + extraParams: { from: dennis }, + }); + await openTrove({ + extraZUSDAmount: toBN(dec(10000, 18)), + ICR: toBN(dec(2, 18)), + extraParams: { from: alice }, + }); + await zusdToken.transfer(alice, await zusdToken.balanceOf(dennis), { from: dennis }); + }; + + // ── Pointer wiring ────────────────────────────────────────────────────── + + it("setExitDelayQueue: only owner, rejects zero + non-contract, rotatable, emits event", async () => { + await th.assertRevert( + borrowerOperations.setExitDelayQueue(queue.address, { from: alice }) + ); + await th.assertRevert(borrowerOperations.setExitDelayQueue(ZERO_ADDRESS, { from: owner })); + await th.assertRevert(borrowerOperations.setExitDelayQueue(alice, { from: owner })); // EOA / no code + + const tx = await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + assert.equal(await borrowerOperations.exitDelayQueue(), queue.address); + const ev = getEvent(tx, "ExitDelayQueueSet"); + assert.isDefined(ev); + assert.equal(ev.args.current, queue.address); + + const queue2 = await MockExitDelayQueue.new(MIN_DELAY); + await borrowerOperations.setExitDelayQueue(queue2.address, { from: owner }); + assert.equal(await borrowerOperations.exitDelayQueue(), queue2.address); + }); + + // ── withdrawColl reroute (d>0, no fee) ────────────────────────────────── + + it("withdrawColl (d>0, fee inactive): GROSS escrowed to queue, borrower NOT paid, ActivePool -= gross", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + await wire(); + + const gross = toBN(dec(1, "ether")); + const apBefore = await activePool.getETH(); + const aliceBefore = await bal(alice); + const collBefore = await getTroveEntireColl(alice); + + const tx = await borrowerOperations.withdrawColl(gross, alice, alice, { + from: alice, + gasPrice: GAS_PRICE, + }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + // ActivePool drained by exactly gross; the queue now custodies it. + assert.isTrue((await activePool.getETH()).eq(apBefore.sub(gross)), "ActivePool != -gross"); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(gross), + "queue balance != gross" + ); + assert.isTrue( + (await queue.totalEscrowed(ZERO_ADDRESS)).eq(gross), + "totalEscrowed != gross" + ); + // borrower received NOTHING directly (only lost gas) — the leg is escrowed. + assert.isTrue( + (await bal(alice)).eq(aliceBefore.sub(gasCost)), + "borrower paid directly on a delayed exit" + ); + assert.isTrue( + (await getTroveEntireColl(alice)).eq(collBefore.sub(gross)), + "trove coll not reduced" + ); + + // one request, immutable metadata: originator=owner=receiver=alice, token=native. + assert.equal((await queue.lastRequestId()).toString(), "1"); + const r = await queue.getRequest(1); + assert.equal(r.originator, alice); + assert.equal(r.owner, alice); + assert.equal(r.receiver, alice); + assert.equal(r.token, ZERO_ADDRESS); + assert.equal(r.surfaceId, SURFACE); + assert.isTrue(toBN(r.amount).eq(gross)); + assert.equal(toBN(r.unlockAt).sub(toBN(r.createdAt)).toString(), String(DELAY)); + }); + + // ── fee + delay compose (d>0, fee active) ─────────────────────────────── + + it("withdrawColl (d>0, fee 50bps): fee→feeReceiver, NET→queue, ExitFeeApplied; sums to gross", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + await wire({ feeActive: true, rateBps: 50 }); + + const gross = toBN(dec(1, "ether")); + const fee = gross.mul(toBN(50)).div(toBN(10000)); + const net = gross.sub(fee); + + const apBefore = await activePool.getETH(); + const frBefore = await bal(feeReceiver); + + const tx = await borrowerOperations.withdrawColl(gross, alice, alice, { from: alice }); + + assert.isTrue((await activePool.getETH()).eq(apBefore.sub(gross)), "ActivePool != -gross"); + assert.isTrue((await bal(feeReceiver)).eq(frBefore.add(fee)), "feeReceiver != +fee"); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(net), + "queue != +net (delayed leg)" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(net)); + // conservation: fee + escrowed net == gross + assert.isTrue(fee.add(net).eq(gross)); + + const applied = getEvent(tx, "ExitFeeApplied"); + assert.isDefined(applied, "ExitFeeApplied not emitted"); + assert.isTrue(toBN(applied.args.netAmount).eq(net)); + const r = await queue.getRequest(1); + assert.isTrue(toBN(r.amount).eq(net), "escrowed amount != net"); + }); + + // ── fee-vault failure still escrows GROSS behind the delay ────────────── + + it("withdrawColl (d>0, fee-vault reverts): GROSS escrowed behind the delay (cannot bypass), ExitFeeSkipped(VAULT_REVERT)", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + const badReceiver = await NonPayable.new(); + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + await controller.configureDelay(true, DELAY); + await controller.configure(true, 50, badReceiver.address, 0); // fee active but receiver bounces + + const gross = toBN(dec(1, "ether")); + const apBefore = await activePool.getETH(); + + const tx = await borrowerOperations.withdrawColl(gross, alice, alice, { from: alice }); + + // fee leg bounced ⇒ full GROSS routed to the queue (NOT paid direct, NOT skimmed). + assert.isTrue((await activePool.getETH()).eq(apBefore.sub(gross)), "ActivePool != -gross"); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(gross), + "gross not escrowed on fee-fail" + ); + assert.isTrue( + toBN(await web3.eth.getBalance(badReceiver.address)).eq(toBN(0)), + "bad receiver got ETH" + ); + assert.equal(toBN(getEvent(tx, "ExitFeeSkipped").args.reason).toNumber(), 5); // VAULT_REVERT + const r = await queue.getRequest(1); + assert.isTrue(toBN(r.amount).eq(gross)); + }); + + // ── closeTrove + adjustTrove reroute ──────────────────────────────────── + + it("closeTrove (d>0): entire collateral escrowed, trove removed, ActivePool -= coll", async () => { + await setupCloseable(); + await wire(); + const gross = await getTroveEntireColl(alice); + const apBefore = await activePool.getETH(); + + await borrowerOperations.closeTrove({ from: alice }); + + assert.isTrue((await activePool.getETH()).eq(apBefore.sub(gross)), "ActivePool != -coll"); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(gross), + "coll not escrowed" + ); + assert.isFalse(await sortedTroves.contains(alice), "trove not removed"); + const r = await queue.getRequest(1); + assert.isTrue(toBN(r.amount).eq(gross)); + assert.equal(r.receiver, alice); + }); + + it("adjustTrove (coll-decreasing, d>0): withdrawal escrowed; debt-only adjust never touches the queue", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + await wire(); + + // coll-decreasing adjust (withdraw 2 ETH, no debt change) + const w = toBN(dec(2, "ether")); + await borrowerOperations.adjustTrove(0, w, 0, false, alice, alice, { from: alice }); + assert.equal((await queue.lastRequestId()).toString(), "1"); + assert.isTrue(toBN(await web3.eth.getBalance(queue.address)).eq(w)); + + // debt-increase-only adjust (no collateral out) ⇒ gross==0 ⇒ hook early-returns, queue untouched + await borrowerOperations.adjustTrove( + toBN(dec(1, 18)), + 0, + toBN(dec(100, 18)), + true, + alice, + alice, + { from: alice } + ); + assert.equal( + (await queue.lastRequestId()).toString(), + "1", + "debt-only adjust touched the queue" + ); + }); + + // ── d==0 direct pay, queue never touched ──────────────────────────────── + + it("perimeter disabled (d==0): direct pay to borrower, queue NEVER touched even when wired", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + // controller + queue wired, but perimeter OFF ⇒ quoteExitDelayFor returns d=0. + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + await controller.configureDelay(false, DELAY); + + const gross = toBN(dec(1, "ether")); + const aliceBefore = await bal(alice); + const tx = await borrowerOperations.withdrawColl(gross, alice, alice, { + from: alice, + gasPrice: GAS_PRICE, + }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + // borrower paid directly; queue untouched. + assert.isTrue( + (await bal(alice)).eq(aliceBefore.add(gross).sub(gasCost)), + "borrower not paid direct at d==0" + ); + assert.equal((await queue.lastRequestId()).toString(), "0", "queue touched at d==0"); + assert.isTrue(toBN(await web3.eth.getBalance(queue.address)).eq(toBN(0))); + }); + + it("controller unwired (no pointer): fail-open direct pay at d==0 (perimeter unreachable)", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + // No controller set at all ⇒ _safeQuoteExitDelay short-circuits to (0, raw, raw). + const gross = toBN(dec(1, "ether")); + const aliceBefore = await bal(alice); + const tx = await borrowerOperations.withdrawColl(gross, alice, alice, { + from: alice, + gasPrice: GAS_PRICE, + }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + assert.isTrue((await bal(alice)).eq(aliceBefore.add(gross).sub(gasCost))); + assert.equal((await queue.lastRequestId()).toString(), "0"); + }); + + // ── FAIL-CLOSED legs ──────────────────────────────────────────────────── + + it("FAIL-CLOSED: controller quote reverts ⇒ whole withdrawColl reverts, trove unchanged", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + await wire(); + await controller.setDelayRevert(true); + + const collBefore = await getTroveEntireColl(alice); + await th.assertRevert( + borrowerOperations.withdrawColl(toBN(dec(1, "ether")), alice, alice, { from: alice }), + "COLFEE:delay-quote-failed" + ); + assert.isTrue( + (await getTroveEntireColl(alice)).eq(collBefore), + "trove mutated on a fail-closed revert" + ); + }); + + it("FAIL-CLOSED: d>0 but queue unwired ⇒ whole withdrawColl reverts (delay never silently bypassed)", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + // controller enabled with d>0, but the queue pointer is NEVER set. + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await controller.configureDelay(true, DELAY); + + const collBefore = await getTroveEntireColl(alice); + await th.assertRevert( + borrowerOperations.withdrawColl(toBN(dec(1, "ether")), alice, alice, { from: alice }), + "COLFEE:queue-unset" + ); + assert.isTrue((await getTroveEntireColl(alice)).eq(collBefore)); + }); + + it("FAIL-CLOSED: a bricked queue (record reverts) reverts the whole close atomically", async () => { + await setupCloseable(); + await wire(); + // De-register BO as an allowed source ⇒ recordReceivedNativeExit reverts. + await queue.setAllowedSource(borrowerOperations.address, false); + + const collBefore = await getTroveEntireColl(alice); + const apBefore = await activePool.getETH(); + await th.assertRevert(borrowerOperations.closeTrove({ from: alice })); + // trove + pool fully rolled back — no partial close, no orphaned push. + assert.isTrue( + (await getTroveEntireColl(alice)).eq(collBefore), + "trove partially closed on fail-closed" + ); + assert.isTrue( + (await activePool.getETH()).eq(apBefore), + "ActivePool drained on fail-closed" + ); + assert.isTrue(await sortedTroves.contains(alice), "trove removed on fail-closed"); + }); + + // ── executeExit after unlock + block-trap ─────────────────────────────── + + it("executeExit: reverts before unlock, pays the receiver after unlock", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + await wire(); + const gross = toBN(dec(1, "ether")); + await borrowerOperations.withdrawColl(gross, alice, alice, { from: alice }); + + await th.assertRevert(queue.executeExit(1, { from: alice }), "not unlocked"); + + await th.fastForwardTime(DELAY + 1, web3.currentProvider); + const aliceBefore = await bal(alice); + const tx = await queue.executeExit(1, { from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + assert.isTrue( + (await bal(alice)).eq(aliceBefore.add(gross).sub(gasCost)), + "receiver not paid on execute" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(toBN(0))); + }); + + it("block-trap: a frozen actor cannot execute the escrowed exit", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + await wire(); + await borrowerOperations.withdrawColl(toBN(dec(1, "ether")), alice, alice, { + from: alice, + }); + await th.fastForwardTime(DELAY + 1, web3.currentProvider); + + await queue.freeze(alice); + await th.assertRevert(queue.executeExit(1, { from: alice }), "actor blocked"); + await queue.unfreeze(alice); + await queue.executeExit(1, { from: alice }); // succeeds once unblocked + }); + + // ── property/fuzz: escrow == net and conservation over random amounts ──── + + it("property (fuzz): over random withdrawals, escrowed == net and fee+net == gross", async () => { + await openTrove({ + ICR: toBN(dec(50, 18)), + extraParams: { from: alice, value: toBN(dec(500, "ether")) }, + }); + await wire({ feeActive: true, rateBps: 137 }); + + for (let i = 0; i < 12; i++) { + const inner = (await timeMachine.takeSnapshot())["result"]; + // random gross in [1, 10] ether, at 1e12-wei granularity + const units = toBN(1 + Math.floor(Math.random() * 9)); // 1..9 ether + const extra = toBN(String(Math.floor(Math.random() * 1e6))).mul(toBN(dec(1, 12))); + const gross = units.mul(toBN(dec(1, 18))).add(extra); + const fee = gross.mul(toBN(137)).div(toBN(10000)); + const net = gross.sub(fee); + + const apBefore = await activePool.getETH(); + await borrowerOperations.withdrawColl(gross, alice, alice, { from: alice }); + + const escrowed = await queue.totalEscrowed(ZERO_ADDRESS); + assert.isTrue(escrowed.eq(net), `escrowed(${escrowed}) != net(${net})`); + assert.isTrue(fee.add(net).eq(gross), "fee+net != gross"); + assert.isTrue( + (await activePool.getETH()).eq(apBefore.sub(gross)), + "ActivePool != -gross" + ); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(net), + "queue balance != net" + ); + + await timeMachine.revertToSnapshot(inner); + } + }); +}); diff --git a/tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json b/tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json index fd1f2b9..0f37cd5 100644 --- a/tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json +++ b/tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json @@ -1,8 +1,8 @@ { "_meta": { - "purpose": "Storage-layout zero-diff baseline for the Zero surplus-claim exit fee.", + "purpose": "Storage-layout zero-diff baseline for the Zero surplus-claim exit fee AND the security-perimeter delay hooks.", "baseRef": "sovryn-perimeter-fee @ b6584a6", - "note": "Normalized solc storageLayout (AST id suffixes after ')' stripped) for the upgradeable BorrowerOperations and CollSurplusPool proxies plus ActivePool, captured from the UNMODIFIED b6584a6 tree, before the surplus-claim fee hook was applied. tests-colfee/StorageLayout.zerodiff.test.js asserts the current tree's layout is identical — proving the hook appends NO state. Regenerate only on an intentional, reviewed layout change (see the test header).", + "note": "Normalized solc storageLayout (AST id suffixes after ')' stripped) for the upgradeable BorrowerOperations and CollSurplusPool proxies plus ActivePool, captured from the UNMODIFIED b6584a6 tree, before the surplus-claim fee hook and before the exit-delay hooks were applied. tests-colfee/StorageLayout.zerodiff.test.js asserts the current tree's layout is identical \u2014 proving neither hook appends state (the controller and queue pointers live in EIP-1967 unstructured slots). Regenerate only on an intentional, reviewed layout change (see the test header).", "consumers": [ "tests-colfee/StorageLayout.zerodiff.test.js", "tests-colfee/utils/storageLayout.js" From 310cc6084028bffc3d928243072adab89af1d3a9 Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Wed, 19 Aug 2026 15:39:22 +0300 Subject: [PATCH 2/7] Rename ColFee to Perimeter across the Zero integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches the lending repo: contracts, interfaces, directories, the Echidna tester and the test suite now use Perimeter naming, and the surface constants become PERIMETER_SURFACE_ZERO_*, each hashing a literal identical to its own name rather than a prefixed one. The surface ids therefore change value, exactly as on the lending side, and need configuring against the new hashes before these hooks can charge. Whether that is a migration or simply part of a first activation depends on the release shape recorded in the runbook. Two strings deliberately keep their spelling: the controller and delay-queue pointer slots, "sovryn.exitFeeController" and "sovryn.exitDelayQueue". Neither contains the old name, and both are shared with the lending integration — renaming them here alone would put the two products on different slots for the same pointer. Also fixes the surface-id helper the tests use. It built ids by prefixing the constant name, so the rename left it hashing a doubly-prefixed string and every surface assertion failed; under the new scheme the string is the constant name and no prefix is applied. 70 tests passing, including the storage-layout zero-diff. --- contracts/BorrowerOperations.sol | 68 +- contracts/CollSurplusPool.sol | 2 +- contracts/DefaultPool.sol | 8 +- contracts/Dependencies/BaseMath.sol | 3 +- contracts/Dependencies/CheckContract.sol | 5 +- contracts/Dependencies/Counters.sol | 2 +- contracts/Dependencies/IERC20.sol | 14 +- contracts/Dependencies/IERC2612.sol | 23 +- contracts/Dependencies/Initializable.sol | 2 +- contracts/Dependencies/LiquityMath.sol | 78 +- contracts/Dependencies/LiquitySafeMath128.sol | 4 +- contracts/Dependencies/Mynt/MyntLib.sol | 13 +- contracts/Dependencies/Ownable.sol | 2 +- .../PriceFeed/IExternalPriceFeed.sol | 2 +- .../Dependencies/PriceFeed/RskOracle.sol | 1 - contracts/Dependencies/SafeMath.sol | 18 +- .../Dependencies/TroveManagerRedeemOps.sol | 5 +- contracts/Dependencies/console.sol | 4478 ++++++++++------- .../permit2/AllowanceTransfer.sol | 47 +- contracts/Dependencies/permit2/EIP712.sol | 14 +- contracts/Dependencies/permit2/ERC20.sol | 22 +- contracts/Dependencies/permit2/Permit2.sol | 6 +- .../permit2/SignatureTransfer.sol | 27 +- .../permit2/interfaces/IAllowanceTransfer.sol | 35 +- .../permit2/interfaces/IERC1271.sol | 5 +- .../permit2/interfaces/IPermit2.sol | 6 +- .../permit2/interfaces/ISignatureTransfer.sol | 2 +- .../permit2/libraries/Allowance.sol | 14 +- .../permit2/libraries/Permit2Lib.sol | 61 +- .../permit2/libraries/PermitHash.sol | 166 +- .../permit2/libraries/SafeTransferLib.sol | 36 +- .../libraries/SignatureVerification.sol | 9 +- contracts/HintHelpers.sol | 10 +- contracts/Interfaces/IAllowanceTransfer.sol | 35 +- .../Interfaces/IBalanceRedirectPresale.sol | 5 +- contracts/Interfaces/IBorrowerOperations.sol | 5 +- contracts/Interfaces/ICollSurplusPool.sol | 4 +- contracts/Interfaces/ICommunityIssuance.sol | 7 +- contracts/Interfaces/IFeeSharingCollector.sol | 12 +- contracts/Interfaces/ILiquityBaseParams.sol | 4 +- contracts/Interfaces/IPermit2.sol | 6 +- contracts/Interfaces/IPriceFeedSovryn.sol | 16 +- contracts/Interfaces/ISignatureTransfer.sol | 2 +- contracts/Interfaces/ISortedTroves.sol | 14 +- contracts/Interfaces/ITroveManager.sol | 1 - contracts/Interfaces/IWrbtc.sol | 6 +- contracts/Interfaces/IZEROToken.sol | 4 +- contracts/Interfaces/IZUSDToken.sol | 7 +- .../{colfee => perimeter}/IExitDelayQueue.sol | 6 +- .../IExitDelayQueueHook.sol | 2 +- .../IExitFeeController.sol | 10 +- contracts/MultiTroveGetter.sol | 35 +- contracts/PriceFeed.sol | 4 +- contracts/Proxy/BorrowerOperationsScript.sol | 40 +- contracts/Proxy/BorrowerWrappersScript.sol | 61 +- contracts/Proxy/ETHTransferScript.sol | 3 +- contracts/Proxy/Proxy.sol | 5 +- contracts/Proxy/StabilityPoolScript.sol | 3 +- contracts/Proxy/TokenScript.sol | 9 +- contracts/Proxy/TroveManagerScript.sol | 3 +- contracts/Proxy/UpgradableProxy.sol | 3 +- contracts/Proxy/ZEROStakingScript.sol | 1 - contracts/StabilityPool.sol | 35 +- contracts/TestContracts/ActivePoolTester.sol | 3 +- .../BorrowerOperationsTester.sol | 88 +- .../TestContracts/CommunityIssuanceTester.sol | 8 +- contracts/TestContracts/DappSys/proxy.sol | 93 +- contracts/TestContracts/DefaultPoolTester.sol | 3 +- contracts/TestContracts/Destructible.sol | 3 +- ...eTester.sol => EchidnaPerimeterTester.sol} | 38 +- .../TestContracts/ExitFeeControllerMock.sol | 4 +- contracts/TestContracts/FunctionCaller.sol | 8 +- .../TestContracts/GasSinkFeeReceiver.sol | 2 +- .../LegacyCollSurplusPoolMock.sol | 2 +- contracts/TestContracts/LiquityMathTester.sol | 1 - .../MockBalanceRedirectPresale.sol | 6 +- .../TestContracts/MockExitDelayQueue.sol | 4 +- .../TestContracts/MockFeeSharingCollector.sol | 12 +- contracts/TestContracts/NonPayable.sol | 1 - .../TestContracts/PriceFeedSovrynTester.sol | 11 +- contracts/TestContracts/PriceFeedTestnet.sol | 7 +- .../SelectorRevertingExitDelayQueue.sol | 2 +- .../TestContracts/SortedTrovesTester.sol | 1 - .../TestContracts/StabilityPoolTester.sol | 3 +- .../TestContracts/UpgradableProxyTester.sol | 4 +- contracts/TestContracts/WRBTCTokenTester.sol | 160 +- contracts/TestContracts/ZEROStakingTester.sol | 1 - contracts/TestContracts/ZEROTokenTester.sol | 31 +- contracts/TestContracts/ZUSDTokenCaller.sol | 8 +- contracts/TestContracts/ZUSDTokenTester.sol | 58 +- contracts/TroveManager.sol | 5 +- contracts/ZERO/CommunityIssuance.sol | 4 +- contracts/ZERO/CommunityIssuanceStorage.sol | 2 +- contracts/ZERO/ZEROStaking.sol | 2 +- contracts/ZERO/ZEROStakingStorage.sol | 11 +- contracts/ZERO/ZEROToken.sol | 44 +- contracts/ZERO/ZEROTokenStorage.sol | 27 +- contracts/ZUSDToken.sol | 36 +- hardhat.config.ts | 4 +- package.json | 4 +- .../ClaimSurplus.notouch.test.js | 6 +- .../StorageLayout.zerodiff.test.js | 6 +- .../ZeroBorrowerExit.adjust.test.js | 18 +- .../ZeroBorrowerExit.close.test.js | 10 +- .../ZeroBorrowerExit.delay.notouch.test.js | 4 +- .../ZeroBorrowerExit.delay.selector.test.js | 12 +- .../ZeroBorrowerExit.delay.test.js | 12 +- .../ZeroBorrowerExit.notouch.test.js | 12 +- .../ZeroClaimSurplus.test.js | 16 +- .../ZeroPreview.test.js | 6 +- .../storage-layout.sovryn-perimeter-fee.json | 6 +- .../utils/assertions.js | 22 +- .../utils/storageLayout.js | 0 113 files changed, 3617 insertions(+), 2750 deletions(-) rename contracts/Interfaces/{colfee => perimeter}/IExitDelayQueue.sol (98%) rename contracts/Interfaces/{colfee => perimeter}/IExitDelayQueueHook.sol (98%) rename contracts/Interfaces/{colfee => perimeter}/IExitFeeController.sol (97%) rename contracts/TestContracts/{EchidnaColFeeTester.sol => EchidnaPerimeterTester.sol} (83%) rename {tests-colfee => tests-perimeter}/ClaimSurplus.notouch.test.js (96%) rename {tests-colfee => tests-perimeter}/StorageLayout.zerodiff.test.js (93%) rename {tests-colfee => tests-perimeter}/ZeroBorrowerExit.adjust.test.js (97%) rename {tests-colfee => tests-perimeter}/ZeroBorrowerExit.close.test.js (95%) rename {tests-colfee => tests-perimeter}/ZeroBorrowerExit.delay.notouch.test.js (98%) rename {tests-colfee => tests-perimeter}/ZeroBorrowerExit.delay.selector.test.js (94%) rename {tests-colfee => tests-perimeter}/ZeroBorrowerExit.delay.test.js (98%) rename {tests-colfee => tests-perimeter}/ZeroBorrowerExit.notouch.test.js (95%) rename {tests-colfee => tests-perimeter}/ZeroClaimSurplus.test.js (97%) rename {tests-colfee => tests-perimeter}/ZeroPreview.test.js (97%) rename {tests-colfee => tests-perimeter}/baselines/storage-layout.sovryn-perimeter-fee.json (90%) rename {tests-colfee => tests-perimeter}/utils/assertions.js (72%) rename {tests-colfee => tests-perimeter}/utils/storageLayout.js (100%) diff --git a/contracts/BorrowerOperations.sol b/contracts/BorrowerOperations.sol index 02ba404..61074d3 100644 --- a/contracts/BorrowerOperations.sol +++ b/contracts/BorrowerOperations.sol @@ -16,8 +16,8 @@ import "./Dependencies/console.sol"; import "./BorrowerOperationsStorage.sol"; import "./Dependencies/Mynt/MyntLib.sol"; import "./Interfaces/IPermit2.sol"; -import "./Interfaces/colfee/IExitFeeController.sol"; -import "./Interfaces/colfee/IExitDelayQueueHook.sol"; +import "./Interfaces/perimeter/IExitFeeController.sol"; +import "./Interfaces/perimeter/IExitDelayQueueHook.sol"; contract BorrowerOperations is LiquityBase, @@ -28,15 +28,15 @@ contract BorrowerOperations is /** CONSTANT / IMMUTABLE VARIABLE ONLY */ IPermit2 public immutable permit2; - // --- ColFee (exit-fee) hook --- + // --- Perimeter (exit-fee) hook --- // No new regular storage: the controller pointer lives in an EIP-1967-style // unstructured slot so `BorrowerOperations` storage-layout is unchanged. bytes32 private constant EXIT_FEE_CONTROLLER_SLOT = bytes32(uint256(keccak256("sovryn.exitFeeController")) - 1); - bytes32 private constant SURFACE_ZERO_WITHDRAW_COLL = - keccak256("COLFEE:SURFACE_ZERO_WITHDRAW_COLL"); - bytes32 private constant SURFACE_ZERO_CLAIM_SURPLUS = - keccak256("COLFEE:SURFACE_ZERO_CLAIM_SURPLUS"); + bytes32 private constant PERIMETER_SURFACE_ZERO_WITHDRAW_COLL = + keccak256("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); + bytes32 private constant PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS = + keccak256("PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS"); // --- Security-perimeter exit-delay hook --- // The delay queue pointer also lives in an EIP-1967-style unstructured @@ -823,16 +823,16 @@ contract BorrowerOperations is ZUSD_GAS_COMPENSATION ); - // Send the collateral back to the user (charging the ColFee exit fee) + // Send the collateral back to the user (charging the Perimeter exit fee) _sendCollWithExitFee(activePoolCached, msg.sender, coll); } /** * Claim remaining collateral from a redemption or from a liquidation with ICR > MCR in Recovery Mode, - * charging the ColFee exit fee when the SURFACE_ZERO_CLAIM_SURPLUS policy is active. - * Fail-open like every ColFee hook: on any ColFee failure (controller missing/ + * charging the Perimeter exit fee when the PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS policy is active. + * Fail-open like every Perimeter hook: on any Perimeter failure (controller missing/ * reverting, invalid quote, fee-leg transfer failure inside the pool) the claimant - * receives the full surplus — a ColFee failure can never brick a claim. The + * receives the full surplus — a Perimeter failure can never brick a claim. The * non-charging path is the untouched claimColl flow (plus the ExitFeeSkipped * event, same convention as _sendCollWithExitFee). */ @@ -840,7 +840,7 @@ contract BorrowerOperations is uint256 gross = collSurplusPool.getCollateral(msg.sender); // Single Zero deployment: subProduct = address(0). Asset is native RBTC. IExitFeeController.ExitFeeQuote memory q = _safeQuote( - SURFACE_ZERO_CLAIM_SURPLUS, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, address(0), msg.sender, gross @@ -865,7 +865,7 @@ contract BorrowerOperations is ); if (feePaid) { emit ExitFeeApplied( - SURFACE_ZERO_CLAIM_SURPLUS, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, msg.sender, address(0), address(0), @@ -877,7 +877,7 @@ contract BorrowerOperations is ); } else { emit ExitFeeSkipped( - SURFACE_ZERO_CLAIM_SURPLUS, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, msg.sender, address(0), gross, @@ -889,7 +889,7 @@ contract BorrowerOperations is // !active (INACTIVE / DISABLED / INVALID_QUOTE / CONTROLLER_REVERT) // OR active-but-zero-fee (dust / zero-rate / gross == 0 → reason NONE). emit ExitFeeSkipped( - SURFACE_ZERO_CLAIM_SURPLUS, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, msg.sender, address(0), gross, @@ -987,9 +987,9 @@ contract BorrowerOperations is } } - // --- ColFee (exit-fee) helpers --- + // --- Perimeter (exit-fee) helpers --- - /// @notice Address of the ColFee controller this instance consults. Held in + /// @notice Address of the Perimeter controller this instance consults. Held in /// an EIP-1967-style unstructured slot (no regular-storage footprint). function exitFeeController() public view returns (address ctrl) { bytes32 slot = EXIT_FEE_CONTROLLER_SLOT; @@ -998,7 +998,7 @@ contract BorrowerOperations is } } - /// @notice Set (or rotate) the ColFee controller this instance consults. + /// @notice Set (or rotate) the Perimeter controller this instance consults. /// Owner-only, one call, effective for every subsequent exit. function setExitFeeController(address ctrl) external onlyOwner { require(ctrl != address(0), "EFC:zero"); @@ -1049,7 +1049,7 @@ contract BorrowerOperations is } /// @dev Fail-CLOSED delay quote wrapper. Resolves the single hook - /// entry `quoteExitDelayFor` on the shared ColFee controller and returns + /// entry `quoteExitDelayFor` on the shared Perimeter controller and returns /// `(d, effOrig, effOwner)`. Two levels, deliberately distinct: /// 1. controller-POINTER lookup is FAIL-OPEN — a missing OR code-less /// controller ⇒ perimeter unwired ⇒ `(0, raw, raw)` ⇒ pay direct @@ -1085,13 +1085,13 @@ contract BorrowerOperations is rawOriginator, owner, receiver, - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, address(0) ) returns (uint32 d_, address effOrig_, address effOwner_) { return (d_, effOrig_, effOwner_); } catch { - revert("COLFEE:delay-quote-failed"); + revert("PERIMETER:delay-quote-failed"); } } @@ -1148,7 +1148,7 @@ contract BorrowerOperations is } } - /// @dev Settle a borrower collateral payout, charging the ColFee exit fee + /// @dev Settle a borrower collateral payout, charging the Perimeter exit fee /// when the resolved policy is active. The fee leg uses `try/catch` /// (0.6.11 native) so a fee-receiver failure never bricks the exit; on /// any non-charging path the full `gross` is sent to the borrower via @@ -1162,10 +1162,10 @@ contract BorrowerOperations is ) private { // Debt-only adjustments (repay / debt-decrease) reach here with gross == 0: // no collateral leaves the pool, so there is nothing to settle. Skip the - // controller round-trip and the ColFee event. (Baseline called + // controller round-trip and the Perimeter event. (Baseline called // sendETH(borrower, 0) here — a value-less no-op that only emitted // EtherSent(_, 0) / ActivePoolETHBalanceUpdated; we drop that redundant - // transfer, so debt-only ops emit fewer events than pre-ColFee.) + // transfer, so debt-only ops emit fewer events than pre-Perimeter.) if (gross == 0) { return; } @@ -1183,7 +1183,7 @@ contract BorrowerOperations is // Single Zero deployment: subProduct = address(0). Asset is native RBTC. IExitFeeController.ExitFeeQuote memory q = _safeQuote( - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, address(0), borrower, gross @@ -1196,7 +1196,7 @@ contract BorrowerOperations is // Emit only after BOTH legs settle, so an ExitFeeApplied event always // implies a completed borrower payout (truthful by construction). emit ExitFeeApplied( - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, borrower, address(0), address(0), @@ -1209,7 +1209,7 @@ contract BorrowerOperations is return; } catch { emit ExitFeeSkipped( - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, borrower, address(0), gross, @@ -1221,7 +1221,7 @@ contract BorrowerOperations is // !active (INACTIVE / DISABLED / INVALID_QUOTE / CONTROLLER_REVERT) // OR active-but-zero-fee (dust / zero-rate policy → q.reason == NONE). emit ExitFeeSkipped( - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, borrower, address(0), gross, @@ -1275,8 +1275,8 @@ contract BorrowerOperations is // An unwired queue reverts the exit with a DISTINCT selector (halt // monitoring) — a delay can never be silently bypassed by a missing // pointer. - require(queue != address(0), "COLFEE:queue-unset"); - require(amount <= uint256(uint128(-1)), "COLFEE:amount-too-large"); + require(queue != address(0), "PERIMETER:queue-unset"); + require(amount <= uint256(uint128(-1)), "PERIMETER:amount-too-large"); // PUSH native to the queue (reuses the existing fail-closed sendETH // primitive — ActivePool.ETH decrements by exactly `amount`, identical @@ -1285,7 +1285,7 @@ contract BorrowerOperations is IExitDelayQueueHook(queue).recordReceivedNativeExit( uint128(amount), dl.d, - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, address(0), dl.effOrig, dl.effOwner, @@ -1296,9 +1296,9 @@ contract BorrowerOperations is } } - /// @notice Read-only preview of the ColFee exit fee on a Zero borrower collateral + /// @notice Read-only preview of the Perimeter exit fee on a Zero borrower collateral /// payout of `grossColl` for `borrower`. Hard-wired to - /// SURFACE_ZERO_WITHDRAW_COLL / subProduct=address(0) / actor=borrower, and + /// PERIMETER_SURFACE_ZERO_WITHDRAW_COLL / subProduct=address(0) / actor=borrower, and /// routes through the same `_safeQuote` the live hook uses — so the synthesized /// fail-open quote on controller failure matches execution wei-for-wise. The /// caller passes `grossColl` (computed from trove state); this is a thin policy @@ -1325,7 +1325,7 @@ contract BorrowerOperations is ) { IExitFeeController.ExitFeeQuote memory q = _safeQuote( - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, address(0), borrower, grossColl diff --git a/contracts/CollSurplusPool.sol b/contracts/CollSurplusPool.sol index 954d2d1..d9e08d6 100644 --- a/contracts/CollSurplusPool.sol +++ b/contracts/CollSurplusPool.sol @@ -83,7 +83,7 @@ contract CollSurplusPool is CollSurplusPoolStorage, CheckContract, ICollSurplusP uint256 private constant FEE_LEG_GAS_CAP = 100_000; /// @notice Two-leg claim: `_feeAmount` to `_feeReceiver`, remainder to `_account`. - /// Only callable by BorrowerOperations (the ColFee surplus-claim hook); + /// Only callable by BorrowerOperations (the Perimeter surplus-claim hook); /// `claimColl` remains the untouched non-charging path. /// CEI: all effects (balance zeroing, ETH accounting) precede both external /// calls, so a reentrant claim sees balances == 0 and reverts. The single diff --git a/contracts/DefaultPool.sol b/contracts/DefaultPool.sol index 8d8076f..b671b33 100644 --- a/contracts/DefaultPool.sol +++ b/contracts/DefaultPool.sol @@ -24,10 +24,10 @@ contract DefaultPool is DefaultPoolStorage, CheckContract, IDefaultPool { // --- Dependency setters --- - function setAddresses(address _troveManagerAddress, address _activePoolAddress) - external - onlyOwner - { + function setAddresses( + address _troveManagerAddress, + address _activePoolAddress + ) external onlyOwner { checkContract(_troveManagerAddress); checkContract(_activePoolAddress); diff --git a/contracts/Dependencies/BaseMath.sol b/contracts/Dependencies/BaseMath.sol index 94de51b..6624fe3 100644 --- a/contracts/Dependencies/BaseMath.sol +++ b/contracts/Dependencies/BaseMath.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.6.11; - contract BaseMath { - uint constant public DECIMAL_PRECISION = 1e18; + uint public constant DECIMAL_PRECISION = 1e18; } diff --git a/contracts/Dependencies/CheckContract.sol b/contracts/Dependencies/CheckContract.sol index 5d6cd02..e8946ef 100644 --- a/contracts/Dependencies/CheckContract.sol +++ b/contracts/Dependencies/CheckContract.sol @@ -2,7 +2,6 @@ pragma solidity 0.6.11; - contract CheckContract { /** * @dev Check that the account is an already deployed non-destroyed contract. @@ -13,7 +12,9 @@ contract CheckContract { uint256 size; // solhint-disable-next-line no-inline-assembly - assembly { size := extcodesize(_account) } + assembly { + size := extcodesize(_account) + } require(size > 0, "Account code size cannot be zero"); } } diff --git a/contracts/Dependencies/Counters.sol b/contracts/Dependencies/Counters.sol index 672a4e0..2a0089c 100644 --- a/contracts/Dependencies/Counters.sol +++ b/contracts/Dependencies/Counters.sol @@ -25,4 +25,4 @@ library Counters { function decrement(Counter storage counter) internal { counter._value = counter._value.sub(1); } -} \ No newline at end of file +} diff --git a/contracts/Dependencies/IERC20.sol b/contracts/Dependencies/IERC20.sol index 3c25c85..0093119 100644 --- a/contracts/Dependencies/IERC20.sol +++ b/contracts/Dependencies/IERC20.sol @@ -36,7 +36,9 @@ interface IERC20 { * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); /** @@ -64,12 +66,18 @@ interface IERC20 { * * Emits a {Transfer} event. */ - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); - + /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). @@ -83,4 +91,4 @@ interface IERC20 { * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); -} \ No newline at end of file +} diff --git a/contracts/Dependencies/IERC2612.sol b/contracts/Dependencies/IERC2612.sol index df31f9f..e31020d 100644 --- a/contracts/Dependencies/IERC2612.sol +++ b/contracts/Dependencies/IERC2612.sol @@ -10,7 +10,7 @@ pragma solidity 0.6.11; * message. This allows users to spend tokens without having to hold Ether. * * See https://eips.ethereum.org/EIPS/eip-2612. - * + * * Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/ */ interface IERC2612 { @@ -36,9 +36,16 @@ interface IERC2612 { * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ - function permit(address owner, address spender, uint256 amount, - uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; - + function permit( + address owner, + address spender, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + /** * @dev Returns the current ERC2612 nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. @@ -46,13 +53,15 @@ interface IERC2612 { * Every successful call to {permit} increases `owner`'s nonce by one. This * prevents a signature from being used multiple times. * - * `owner` can limit the time a Permit is valid for by setting `deadline` to - * a value in the near future. The deadline argument can be set to uint(-1) to + * `owner` can limit the time a Permit is valid for by setting `deadline` to + * a value in the near future. The deadline argument can be set to uint(-1) to * create Permits that effectively never expire. */ function nonces(address owner) external view returns (uint256); - + function version() external view returns (string memory); + function permitTypeHash() external view returns (bytes32); + function domainSeparator() external view returns (bytes32); } diff --git a/contracts/Dependencies/Initializable.sol b/contracts/Dependencies/Initializable.sol index 0cdf9cc..f57da2e 100644 --- a/contracts/Dependencies/Initializable.sol +++ b/contracts/Dependencies/Initializable.sol @@ -7,7 +7,7 @@ pragma solidity 0.6.11; * * Based on OpenZeppelin's Initializable contract: * https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/packages/core/contracts/Initializable.sol - * + * * @dev Helper contract to support initializer functions. To use it, replace * the constructor with a function that has the `initializer` modifier. * WARNING: Unlike constructors, initializer functions must be manually diff --git a/contracts/Dependencies/LiquityMath.sol b/contracts/Dependencies/LiquityMath.sol index 9f6c44d..329dc58 100644 --- a/contracts/Dependencies/LiquityMath.sol +++ b/contracts/Dependencies/LiquityMath.sol @@ -13,7 +13,7 @@ library LiquityMath { /* Precision for Nominal ICR (independent of price). Rationale for the value: * * - Making it “too high” could lead to overflows. - * - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division. + * - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division. * * This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH, * and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator. @@ -29,42 +29,45 @@ library LiquityMath { return (_a >= _b) ? _a : _b; } - /* - * Multiply two decimal numbers and use normal rounding rules: - * -round product up if 19'th mantissa digit >= 5 - * -round product down if 19'th mantissa digit < 5 - * - * Used only inside the exponentiation, _decPow(). - */ + /* + * Multiply two decimal numbers and use normal rounding rules: + * -round product up if 19'th mantissa digit >= 5 + * -round product down if 19'th mantissa digit < 5 + * + * Used only inside the exponentiation, _decPow(). + */ function decMul(uint x, uint y) internal pure returns (uint decProd) { uint prod_xy = x.mul(y); decProd = prod_xy.add(DECIMAL_PRECISION / 2).div(DECIMAL_PRECISION); } - /* - * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n. - * - * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity. - * - * Called by two functions that represent time in units of minutes: - * 1) TroveManager._calcDecayedBaseRate - * 2) CommunityIssuance._getCumulativeIssuanceFraction - * - * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals - * "minutes in 1000 years": 60 * 24 * 365 * 1000 - * - * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be - * negligibly different from just passing the cap, since: - * - * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years - * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible - */ + /* + * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n. + * + * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity. + * + * Called by two functions that represent time in units of minutes: + * 1) TroveManager._calcDecayedBaseRate + * 2) CommunityIssuance._getCumulativeIssuanceFraction + * + * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals + * "minutes in 1000 years": 60 * 24 * 365 * 1000 + * + * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be + * negligibly different from just passing the cap, since: + * + * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years + * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible + */ function _decPow(uint _base, uint _minutes) internal pure returns (uint) { - - if (_minutes > 525600000) {_minutes = 525600000;} // cap to avoid overflow - - if (_minutes == 0) {return DECIMAL_PRECISION;} + if (_minutes > 525600000) { + _minutes = 525600000; + } // cap to avoid overflow + + if (_minutes == 0) { + return DECIMAL_PRECISION; + } uint y = DECIMAL_PRECISION; uint x = _base; @@ -75,7 +78,8 @@ library LiquityMath { if (n % 2 == 0) { x = decMul(x, x); n = n.div(2); - } else { // if (n % 2 != 0) + } else { + // if (n % 2 != 0) y = decMul(x, y); x = decMul(x, x); n = (n.sub(1)).div(2); @@ -83,7 +87,7 @@ library LiquityMath { } return decMul(x, y); - } + } function _getAbsoluteDifference(uint _a, uint _b) internal pure returns (uint) { return (_a >= _b) ? _a.sub(_b) : _b.sub(_a); @@ -94,8 +98,9 @@ library LiquityMath { return _coll.mul(NICR_PRECISION).div(_debt); } // Return the maximal value for uint256 if the Trove has a debt of 0. Represents "infinite" CR. - else { // if (_debt == 0) - return 2**256 - 1; + else { + // if (_debt == 0) + return 2 ** 256 - 1; } } @@ -106,8 +111,9 @@ library LiquityMath { return newCollRatio; } // Return the maximal value for uint256 if the Trove has a debt of 0. Represents "infinite" CR. - else { // if (_debt == 0) - return 2**256 - 1; + else { + // if (_debt == 0) + return 2 ** 256 - 1; } } } diff --git a/contracts/Dependencies/LiquitySafeMath128.sol b/contracts/Dependencies/LiquitySafeMath128.sol index 2736ecd..bdb9483 100644 --- a/contracts/Dependencies/LiquitySafeMath128.sol +++ b/contracts/Dependencies/LiquitySafeMath128.sol @@ -11,11 +11,11 @@ library LiquitySafeMath128 { return c; } - + function sub(uint128 a, uint128 b) internal pure returns (uint128) { require(b <= a, "LiquitySafeMath128: subtraction overflow"); uint128 c = a - b; return c; } -} \ No newline at end of file +} diff --git a/contracts/Dependencies/Mynt/MyntLib.sol b/contracts/Dependencies/Mynt/MyntLib.sol index c5d89d0..8632727 100644 --- a/contracts/Dependencies/Mynt/MyntLib.sol +++ b/contracts/Dependencies/Mynt/MyntLib.sol @@ -94,13 +94,14 @@ library MyntLib { * @param _to ultimate recipient * @param _amount amount of transfer * - * @return SignatureTransferDetails struct object + * @return SignatureTransferDetails struct object */ - function _generateTransferDetails(address _to, uint256 _amount) private view returns (ISignatureTransfer.SignatureTransferDetails memory) { - ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({ - to: _to, - requestedAmount: _amount - }); + function _generateTransferDetails( + address _to, + uint256 _amount + ) private view returns (ISignatureTransfer.SignatureTransferDetails memory) { + ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer + .SignatureTransferDetails({ to: _to, requestedAmount: _amount }); return transferDetails; } diff --git a/contracts/Dependencies/Ownable.sol b/contracts/Dependencies/Ownable.sol index 922f8f9..bd6ff25 100644 --- a/contracts/Dependencies/Ownable.sol +++ b/contracts/Dependencies/Ownable.sol @@ -22,7 +22,7 @@ contract Ownable { /** * @dev Initializes the contract setting the deployer as the initial owner. */ - constructor () internal { + constructor() internal { _setOwner(msg.sender); } diff --git a/contracts/Dependencies/PriceFeed/IExternalPriceFeed.sol b/contracts/Dependencies/PriceFeed/IExternalPriceFeed.sol index aac2ea2..bbec078 100644 --- a/contracts/Dependencies/PriceFeed/IExternalPriceFeed.sol +++ b/contracts/Dependencies/PriceFeed/IExternalPriceFeed.sol @@ -2,7 +2,7 @@ pragma solidity 0.6.11; -/// @title A generic interface for external price providers +/// @title A generic interface for external price providers interface IExternalPriceFeed { /// @dev The returned price should be 18-decimal value /// @return the prive value and a boolean stating if the query was successful diff --git a/contracts/Dependencies/PriceFeed/RskOracle.sol b/contracts/Dependencies/PriceFeed/RskOracle.sol index 7a5d98c..637723b 100644 --- a/contracts/Dependencies/PriceFeed/RskOracle.sol +++ b/contracts/Dependencies/PriceFeed/RskOracle.sol @@ -8,7 +8,6 @@ interface IRSKOracle { } contract RskOracle is IExternalPriceFeed { - IRSKOracle rskOracle; constructor(address _address) public { diff --git a/contracts/Dependencies/SafeMath.sol b/contracts/Dependencies/SafeMath.sol index 04e66a6..8c02803 100644 --- a/contracts/Dependencies/SafeMath.sol +++ b/contracts/Dependencies/SafeMath.sol @@ -59,7 +59,11 @@ library SafeMath { * * _Available since v2.4.0._ */ - function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; @@ -117,7 +121,11 @@ library SafeMath { * * _Available since v2.4.0._ */ - function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; @@ -154,7 +162,11 @@ library SafeMath { * * _Available since v2.4.0._ */ - function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } diff --git a/contracts/Dependencies/TroveManagerRedeemOps.sol b/contracts/Dependencies/TroveManagerRedeemOps.sol index 2686be6..a2df35d 100644 --- a/contracts/Dependencies/TroveManagerRedeemOps.sol +++ b/contracts/Dependencies/TroveManagerRedeemOps.sol @@ -37,7 +37,10 @@ contract TroveManagerRedeemOps is TroveManagerBase { */ /** Constructor */ - constructor(uint256 _bootstrapPeriod, address _permit2) public TroveManagerBase(_bootstrapPeriod) { + constructor( + uint256 _bootstrapPeriod, + address _permit2 + ) public TroveManagerBase(_bootstrapPeriod) { permit2 = IPermit2(_permit2); } diff --git a/contracts/Dependencies/console.sol b/contracts/Dependencies/console.sol index ef26dc2..343808d 100644 --- a/contracts/Dependencies/console.sol +++ b/contracts/Dependencies/console.sol @@ -4,1904 +4,2582 @@ pragma solidity 0.6.11; // Buidler's helper contract for console logging library console { - address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); - - function log() internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log()")); - ignored; - } function logInt(int p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(int)", p0)); - ignored; - } - - function logUint(uint p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint)", p0)); - ignored; - } - - function logString(string memory p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string)", p0)); - ignored; - } - - function logBool(bool p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool)", p0)); - ignored; - } - - function logAddress(address p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address)", p0)); - ignored; - } - - function logBytes(bytes memory p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes)", p0)); - ignored; - } - - function logByte(byte p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(byte)", p0)); - ignored; - } - - function logBytes1(bytes1 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes1)", p0)); - ignored; - } - - function logBytes2(bytes2 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes2)", p0)); - ignored; - } - - function logBytes3(bytes3 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes3)", p0)); - ignored; - } - - function logBytes4(bytes4 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes4)", p0)); - ignored; - } - - function logBytes5(bytes5 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes5)", p0)); - ignored; - } - - function logBytes6(bytes6 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes6)", p0)); - ignored; - } - - function logBytes7(bytes7 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes7)", p0)); - ignored; - } - - function logBytes8(bytes8 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes8)", p0)); - ignored; - } - - function logBytes9(bytes9 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes9)", p0)); - ignored; - } - - function logBytes10(bytes10 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes10)", p0)); - ignored; - } - - function logBytes11(bytes11 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes11)", p0)); - ignored; - } - - function logBytes12(bytes12 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes12)", p0)); - ignored; - } - - function logBytes13(bytes13 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes13)", p0)); - ignored; - } - - function logBytes14(bytes14 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes14)", p0)); - ignored; - } - - function logBytes15(bytes15 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes15)", p0)); - ignored; - } - - function logBytes16(bytes16 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes16)", p0)); - ignored; - } - - function logBytes17(bytes17 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes17)", p0)); - ignored; - } - - function logBytes18(bytes18 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes18)", p0)); - ignored; - } - - function logBytes19(bytes19 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes19)", p0)); - ignored; - } - - function logBytes20(bytes20 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes20)", p0)); - ignored; - } - - function logBytes21(bytes21 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes21)", p0)); - ignored; - } - - function logBytes22(bytes22 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes22)", p0)); - ignored; - } - - function logBytes23(bytes23 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes23)", p0)); - ignored; - } - - function logBytes24(bytes24 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes24)", p0)); - ignored; - } - - function logBytes25(bytes25 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes25)", p0)); - ignored; - } - - function logBytes26(bytes26 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes26)", p0)); - ignored; - } - - function logBytes27(bytes27 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes27)", p0)); - ignored; - } - - function logBytes28(bytes28 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes28)", p0)); - ignored; - } - - function logBytes29(bytes29 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes29)", p0)); - ignored; - } - - function logBytes30(bytes30 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes30)", p0)); - ignored; - } - - function logBytes31(bytes31 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes31)", p0)); - ignored; - } - - function logBytes32(bytes32 p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes32)", p0)); - ignored; - } - - function log(uint p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint)", p0)); - ignored; - } - - function log(string memory p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string)", p0)); - ignored; - } - - function log(bool p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool)", p0)); - ignored; - } - - function log(address p0) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address)", p0)); - ignored; - } - - function log(uint p0, uint p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint)", p0, p1)); - ignored; - } - - function log(uint p0, string memory p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string)", p0, p1)); - ignored; - } - - function log(uint p0, bool p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool)", p0, p1)); - ignored; - } - - function log(uint p0, address p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address)", p0, p1)); - ignored; - } - - function log(string memory p0, uint p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint)", p0, p1)); - ignored; - } - - function log(string memory p0, string memory p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string)", p0, p1)); - ignored; - } - - function log(string memory p0, bool p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool)", p0, p1)); - ignored; - } - - function log(string memory p0, address p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address)", p0, p1)); - ignored; - } - - function log(bool p0, uint p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint)", p0, p1)); - ignored; - } - - function log(bool p0, string memory p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string)", p0, p1)); - ignored; - } - - function log(bool p0, bool p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool)", p0, p1)); - ignored; - } - - function log(bool p0, address p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address)", p0, p1)); - ignored; - } - - function log(address p0, uint p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint)", p0, p1)); - ignored; - } - - function log(address p0, string memory p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string)", p0, p1)); - ignored; - } - - function log(address p0, bool p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool)", p0, p1)); - ignored; - } - - function log(address p0, address p1) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address)", p0, p1)); - ignored; - } - - function log(uint p0, uint p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,uint)", p0, p1, p2)); - ignored; - } - - function log(uint p0, uint p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,string)", p0, p1, p2)); - ignored; - } - - function log(uint p0, uint p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,bool)", p0, p1, p2)); - ignored; - } - - function log(uint p0, uint p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,address)", p0, p1, p2)); - ignored; - } - - function log(uint p0, string memory p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,uint)", p0, p1, p2)); - ignored; - } - - function log(uint p0, string memory p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,string)", p0, p1, p2)); - ignored; - } - - function log(uint p0, string memory p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,bool)", p0, p1, p2)); - ignored; - } - - function log(uint p0, string memory p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,address)", p0, p1, p2)); - ignored; - } - - function log(uint p0, bool p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,uint)", p0, p1, p2)); - ignored; - } - - function log(uint p0, bool p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,string)", p0, p1, p2)); - ignored; - } - - function log(uint p0, bool p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,bool)", p0, p1, p2)); - ignored; - } - - function log(uint p0, bool p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,address)", p0, p1, p2)); - ignored; - } - - function log(uint p0, address p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,uint)", p0, p1, p2)); - ignored; - } - - function log(uint p0, address p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,string)", p0, p1, p2)); - ignored; - } - - function log(uint p0, address p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,bool)", p0, p1, p2)); - ignored; - } - - function log(uint p0, address p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,address)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, uint p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,uint)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, uint p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,string)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, uint p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,bool)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, uint p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,address)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, string memory p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,uint)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, string memory p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, string memory p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, string memory p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, bool p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,uint)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, bool p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, bool p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, bool p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, address p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,uint)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, address p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, address p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2)); - ignored; - } - - function log(string memory p0, address p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2)); - ignored; - } - - function log(bool p0, uint p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,uint)", p0, p1, p2)); - ignored; - } - - function log(bool p0, uint p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,string)", p0, p1, p2)); - ignored; - } - - function log(bool p0, uint p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,bool)", p0, p1, p2)); - ignored; - } - - function log(bool p0, uint p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,address)", p0, p1, p2)); - ignored; - } - - function log(bool p0, string memory p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,uint)", p0, p1, p2)); - ignored; - } - - function log(bool p0, string memory p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2)); - ignored; - } - - function log(bool p0, string memory p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2)); - ignored; - } - - function log(bool p0, string memory p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2)); - ignored; - } - - function log(bool p0, bool p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,uint)", p0, p1, p2)); - ignored; - } - - function log(bool p0, bool p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2)); - ignored; - } - - function log(bool p0, bool p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2)); - ignored; - } - - function log(bool p0, bool p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2)); - ignored; - } - - function log(bool p0, address p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,uint)", p0, p1, p2)); - ignored; - } - - function log(bool p0, address p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2)); - ignored; - } - - function log(bool p0, address p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2)); - ignored; - } - - function log(bool p0, address p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2)); - ignored; - } - - function log(address p0, uint p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,uint)", p0, p1, p2)); - ignored; - } - - function log(address p0, uint p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,string)", p0, p1, p2)); - ignored; - } - - function log(address p0, uint p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,bool)", p0, p1, p2)); - ignored; - } - - function log(address p0, uint p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,address)", p0, p1, p2)); - ignored; - } - - function log(address p0, string memory p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,uint)", p0, p1, p2)); - ignored; - } - - function log(address p0, string memory p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2)); - ignored; - } - - function log(address p0, string memory p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2)); - ignored; - } - - function log(address p0, string memory p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2)); - ignored; - } - - function log(address p0, bool p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,uint)", p0, p1, p2)); - ignored; - } - - function log(address p0, bool p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2)); - ignored; - } - - function log(address p0, bool p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2)); - ignored; - } - - function log(address p0, bool p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2)); - ignored; - } - - function log(address p0, address p1, uint p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,uint)", p0, p1, p2)); - ignored; - } - - function log(address p0, address p1, string memory p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2)); - ignored; - } - - function log(address p0, address p1, bool p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2)); - ignored; - } - - function log(address p0, address p1, address p2) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2)); - ignored; - } - - function log(uint p0, uint p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, uint p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,uint,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, string memory p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,string,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, bool p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,bool,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(uint p0, address p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint,address,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, uint p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,uint,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, string memory p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, bool p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(string memory p0, address p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, uint p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,uint,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, string memory p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, bool p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(bool p0, address p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, uint p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,uint,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, string memory p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, bool p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, uint p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,uint,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, uint p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,uint,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, uint p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,uint,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, uint p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,uint,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, string memory p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,string,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, string memory p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, string memory p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, string memory p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, bool p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,bool,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, bool p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, bool p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, bool p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, address p2, uint p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,address,uint)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, address p2, string memory p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, address p2, bool p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3)); - ignored; - } - - function log(address p0, address p1, address p2, address p3) internal view { - (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); - ignored; - } - + address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); + + function log() internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log()")); + ignored; + } + + function logInt(int p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(int)", p0)); + ignored; + } + + function logUint(uint p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint)", p0)); + ignored; + } + + function logString(string memory p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string)", p0)); + ignored; + } + + function logBool(bool p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool)", p0)); + ignored; + } + + function logAddress(address p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address)", p0)); + ignored; + } + + function logBytes(bytes memory p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes)", p0)); + ignored; + } + + function logByte(bytes1 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(byte)", p0)); + ignored; + } + + function logBytes1(bytes1 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes1)", p0)); + ignored; + } + + function logBytes2(bytes2 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes2)", p0)); + ignored; + } + + function logBytes3(bytes3 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes3)", p0)); + ignored; + } + + function logBytes4(bytes4 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes4)", p0)); + ignored; + } + + function logBytes5(bytes5 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes5)", p0)); + ignored; + } + + function logBytes6(bytes6 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes6)", p0)); + ignored; + } + + function logBytes7(bytes7 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes7)", p0)); + ignored; + } + + function logBytes8(bytes8 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes8)", p0)); + ignored; + } + + function logBytes9(bytes9 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes9)", p0)); + ignored; + } + + function logBytes10(bytes10 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes10)", p0)); + ignored; + } + + function logBytes11(bytes11 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes11)", p0)); + ignored; + } + + function logBytes12(bytes12 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes12)", p0)); + ignored; + } + + function logBytes13(bytes13 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes13)", p0)); + ignored; + } + + function logBytes14(bytes14 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes14)", p0)); + ignored; + } + + function logBytes15(bytes15 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes15)", p0)); + ignored; + } + + function logBytes16(bytes16 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes16)", p0)); + ignored; + } + + function logBytes17(bytes17 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes17)", p0)); + ignored; + } + + function logBytes18(bytes18 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes18)", p0)); + ignored; + } + + function logBytes19(bytes19 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes19)", p0)); + ignored; + } + + function logBytes20(bytes20 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes20)", p0)); + ignored; + } + + function logBytes21(bytes21 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes21)", p0)); + ignored; + } + + function logBytes22(bytes22 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes22)", p0)); + ignored; + } + + function logBytes23(bytes23 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes23)", p0)); + ignored; + } + + function logBytes24(bytes24 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes24)", p0)); + ignored; + } + + function logBytes25(bytes25 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes25)", p0)); + ignored; + } + + function logBytes26(bytes26 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes26)", p0)); + ignored; + } + + function logBytes27(bytes27 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes27)", p0)); + ignored; + } + + function logBytes28(bytes28 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes28)", p0)); + ignored; + } + + function logBytes29(bytes29 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes29)", p0)); + ignored; + } + + function logBytes30(bytes30 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes30)", p0)); + ignored; + } + + function logBytes31(bytes31 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes31)", p0)); + ignored; + } + + function logBytes32(bytes32 p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bytes32)", p0)); + ignored; + } + + function log(uint p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(uint)", p0)); + ignored; + } + + function log(string memory p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(string)", p0)); + ignored; + } + + function log(bool p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(bool)", p0)); + ignored; + } + + function log(address p0) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature("log(address)", p0)); + ignored; + } + + function log(uint p0, uint p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint)", p0, p1) + ); + ignored; + } + + function log(uint p0, string memory p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string)", p0, p1) + ); + ignored; + } + + function log(uint p0, bool p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool)", p0, p1) + ); + ignored; + } + + function log(uint p0, address p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address)", p0, p1) + ); + ignored; + } + + function log(string memory p0, uint p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint)", p0, p1) + ); + ignored; + } + + function log(string memory p0, string memory p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string)", p0, p1) + ); + ignored; + } + + function log(string memory p0, bool p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool)", p0, p1) + ); + ignored; + } + + function log(string memory p0, address p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address)", p0, p1) + ); + ignored; + } + + function log(bool p0, uint p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint)", p0, p1) + ); + ignored; + } + + function log(bool p0, string memory p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string)", p0, p1) + ); + ignored; + } + + function log(bool p0, bool p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool)", p0, p1) + ); + ignored; + } + + function log(bool p0, address p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address)", p0, p1) + ); + ignored; + } + + function log(address p0, uint p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint)", p0, p1) + ); + ignored; + } + + function log(address p0, string memory p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string)", p0, p1) + ); + ignored; + } + + function log(address p0, bool p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool)", p0, p1) + ); + ignored; + } + + function log(address p0, address p1) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address)", p0, p1) + ); + ignored; + } + + function log(uint p0, uint p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,uint)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, uint p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,string)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, uint p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,bool)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, uint p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,address)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, string memory p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,uint)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, string memory p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,string)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, string memory p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,bool)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, string memory p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,address)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, bool p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,uint)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, bool p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,string)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, bool p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,bool)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, bool p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,address)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, address p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,uint)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, address p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,string)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, address p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,bool)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, address p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,address)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, uint p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,uint)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, uint p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,string)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, uint p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,bool)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, uint p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,address)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, string memory p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,uint)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, string memory p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,string)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, string memory p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, string memory p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,address)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, bool p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,uint)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, bool p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, bool p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, bool p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, address p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,uint)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, address p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,string)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, address p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2) + ); + ignored; + } + + function log(string memory p0, address p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,address)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, uint p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,uint)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, uint p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,string)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, uint p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,bool)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, uint p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,address)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, string memory p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,uint)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, string memory p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, string memory p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, string memory p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, bool p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,uint)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, bool p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, bool p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, bool p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, address p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,uint)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, address p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, address p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2) + ); + ignored; + } + + function log(bool p0, address p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, uint p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,uint)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, uint p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,string)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, uint p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,bool)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, uint p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,address)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, string memory p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,uint)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, string memory p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,string)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, string memory p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, string memory p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,address)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, bool p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,uint)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, bool p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, bool p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, bool p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, address p1, uint p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,uint)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, address p1, string memory p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,string)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, address p1, bool p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2) + ); + ignored; + } + + function log(address p0, address p1, address p2) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,address)", p0, p1, p2) + ); + ignored; + } + + function log(uint p0, uint p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, uint p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,uint,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, string memory p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,string,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, bool p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,bool,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(uint p0, address p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(uint,address,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, uint p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,uint,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log( + string memory p0, + string memory p1, + string memory p2, + string memory p3 + ) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, string memory p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, bool p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(string memory p0, address p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, uint p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,uint,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, string memory p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, bool p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(bool p0, address p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, uint p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,uint,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, string memory p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, bool p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, uint p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,uint,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, uint p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,uint,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, uint p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,uint,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, uint p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,uint,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, string memory p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,string,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, string memory p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, string memory p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, string memory p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, bool p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,bool,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, bool p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, bool p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, bool p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, address p2, uint p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,address,uint)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, address p2, string memory p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, address p2, bool p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3) + ); + ignored; + } + + function log(address p0, address p1, address p2, address p3) internal view { + (bool ignored, ) = CONSOLE_ADDRESS.staticcall( + abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3) + ); + ignored; + } } diff --git a/contracts/Dependencies/permit2/AllowanceTransfer.sol b/contracts/Dependencies/permit2/AllowanceTransfer.sol index 876bc69..7f46fd8 100644 --- a/contracts/Dependencies/permit2/AllowanceTransfer.sol +++ b/contracts/Dependencies/permit2/AllowanceTransfer.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {ERC20} from "./ERC20.sol"; -import {SafeTransferLib} from "./libraries/SafeTransferLib.sol"; -import {PermitHash} from "./libraries/PermitHash.sol"; -import {SignatureVerification} from "./libraries/SignatureVerification.sol"; -import {EIP712} from "./EIP712.sol"; -import {IAllowanceTransfer} from "./interfaces/IAllowanceTransfer.sol"; -import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; -import {Allowance} from "./libraries/Allowance.sol"; +import { ERC20 } from "./ERC20.sol"; +import { SafeTransferLib } from "./libraries/SafeTransferLib.sol"; +import { PermitHash } from "./libraries/PermitHash.sol"; +import { SignatureVerification } from "./libraries/SignatureVerification.sol"; +import { EIP712 } from "./EIP712.sol"; +import { IAllowanceTransfer } from "./interfaces/IAllowanceTransfer.sol"; +import { SignatureExpired, InvalidNonce } from "./PermitErrors.sol"; +import { Allowance } from "./libraries/Allowance.sol"; contract AllowanceTransfer is IAllowanceTransfer, EIP712 { using SignatureVerification for bytes; @@ -30,8 +30,13 @@ contract AllowanceTransfer is IAllowanceTransfer, EIP712 { } /// @inheritdoc IAllowanceTransfer - function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external { - if (block.timestamp > permitSingle.sigDeadline) revert SignatureExpired(permitSingle.sigDeadline); + function permit( + address owner, + PermitSingle memory permitSingle, + bytes calldata signature + ) external { + if (block.timestamp > permitSingle.sigDeadline) + revert SignatureExpired(permitSingle.sigDeadline); // Verify the signer address from the signature. signature.verify(_hashTypedData(permitSingle.hash()), owner); @@ -40,8 +45,13 @@ contract AllowanceTransfer is IAllowanceTransfer, EIP712 { } /// @inheritdoc IAllowanceTransfer - function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external { - if (block.timestamp > permitBatch.sigDeadline) revert SignatureExpired(permitBatch.sigDeadline); + function permit( + address owner, + PermitBatch memory permitBatch, + bytes calldata signature + ) external { + if (block.timestamp > permitBatch.sigDeadline) + revert SignatureExpired(permitBatch.sigDeadline); // Verify the signer address from the signature. signature.verify(_hashTypedData(permitBatch.hash()), owner); @@ -66,7 +76,12 @@ contract AllowanceTransfer is IAllowanceTransfer, EIP712 { uint256 length = transferDetails.length; for (uint256 i = 0; i < length; ++i) { AllowanceTransferDetails memory transferDetail = transferDetails[i]; - _transfer(transferDetail.from, transferDetail.to, transferDetail.amount, transferDetail.token); + _transfer( + transferDetail.from, + transferDetail.to, + transferDetail.amount, + transferDetail.token + ); } } } @@ -128,7 +143,11 @@ contract AllowanceTransfer is IAllowanceTransfer, EIP712 { /// @notice Sets the new values for amount, expiration, and nonce. /// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1. /// @dev Emits a Permit event. - function _updateApproval(PermitDetails memory details, address owner, address spender) private { + function _updateApproval( + PermitDetails memory details, + address owner, + address spender + ) private { uint48 nonce = details.nonce; address token = details.token; uint160 amount = details.amount; diff --git a/contracts/Dependencies/permit2/EIP712.sol b/contracts/Dependencies/permit2/EIP712.sol index 971a03d..de86e42 100644 --- a/contracts/Dependencies/permit2/EIP712.sol +++ b/contracts/Dependencies/permit2/EIP712.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {IEIP712} from "./interfaces/IEIP712.sol"; +import { IEIP712 } from "./interfaces/IEIP712.sol"; /// @notice EIP712 helpers for permit2 /// @dev Maintains cross-chain replay protection in the event of a fork @@ -24,13 +24,17 @@ contract EIP712 is IEIP712 { /// @notice Returns the domain separator for the current chain. /// @dev Uses cached version if chainid and address are unchanged from construction. function DOMAIN_SEPARATOR() public view override returns (bytes32) { - return block.chainid == _CACHED_CHAIN_ID - ? _CACHED_DOMAIN_SEPARATOR - : _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); + return + block.chainid == _CACHED_CHAIN_ID + ? _CACHED_DOMAIN_SEPARATOR + : _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); } /// @notice Builds a domain separator using the current chainId and contract address. - function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash) private view returns (bytes32) { + function _buildDomainSeparator( + bytes32 typeHash, + bytes32 nameHash + ) private view returns (bytes32) { return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this))); } diff --git a/contracts/Dependencies/permit2/ERC20.sol b/contracts/Dependencies/permit2/ERC20.sol index 879f99e..f407204 100644 --- a/contracts/Dependencies/permit2/ERC20.sol +++ b/contracts/Dependencies/permit2/ERC20.sol @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; @@ -49,11 +48,7 @@ abstract contract ERC20 { CONSTRUCTOR //////////////////////////////////////////////////////////////*/ - constructor( - string memory _name, - string memory _symbol, - uint8 _decimals - ) { + constructor(string memory _name, string memory _symbol, uint8 _decimals) { name = _name; symbol = _symbol; decimals = _decimals; @@ -88,11 +83,7 @@ abstract contract ERC20 { return true; } - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual returns (bool) { + function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; @@ -161,14 +152,19 @@ abstract contract ERC20 { } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { - return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); + return + block.chainid == INITIAL_CHAIN_ID + ? INITIAL_DOMAIN_SEPARATOR + : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), keccak256(bytes(name)), keccak256("1"), block.chainid, diff --git a/contracts/Dependencies/permit2/Permit2.sol b/contracts/Dependencies/permit2/Permit2.sol index 7249e40..cc7a623 100644 --- a/contracts/Dependencies/permit2/Permit2.sol +++ b/contracts/Dependencies/permit2/Permit2.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {SignatureTransfer} from "./SignatureTransfer.sol"; -import {AllowanceTransfer} from "./AllowanceTransfer.sol"; +import { SignatureTransfer } from "./SignatureTransfer.sol"; +import { AllowanceTransfer } from "./AllowanceTransfer.sol"; /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. /// @dev Users must approve Permit2 before calling any of the transfer functions. contract Permit2 is SignatureTransfer, AllowanceTransfer { -// Permit2 unifies the two contracts so users have maximal flexibility with their approval. + // Permit2 unifies the two contracts so users have maximal flexibility with their approval. } diff --git a/contracts/Dependencies/permit2/SignatureTransfer.sol b/contracts/Dependencies/permit2/SignatureTransfer.sol index 6494a9f..1c789f6 100644 --- a/contracts/Dependencies/permit2/SignatureTransfer.sol +++ b/contracts/Dependencies/permit2/SignatureTransfer.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {ISignatureTransfer} from "./interfaces/ISignatureTransfer.sol"; -import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; -import {ERC20} from "./ERC20.sol"; -import {SafeTransferLib} from "./libraries/SafeTransferLib.sol"; -import {SignatureVerification} from "./libraries/SignatureVerification.sol"; -import {PermitHash} from "./libraries/PermitHash.sol"; -import {EIP712} from "./EIP712.sol"; +import { ISignatureTransfer } from "./interfaces/ISignatureTransfer.sol"; +import { SignatureExpired, InvalidNonce } from "./PermitErrors.sol"; +import { ERC20 } from "./ERC20.sol"; +import { SafeTransferLib } from "./libraries/SafeTransferLib.sol"; +import { SignatureVerification } from "./libraries/SignatureVerification.sol"; +import { PermitHash } from "./libraries/PermitHash.sol"; +import { EIP712 } from "./EIP712.sol"; contract SignatureTransfer is ISignatureTransfer, EIP712 { using SignatureVerification for bytes; @@ -44,7 +44,8 @@ contract SignatureTransfer is ISignatureTransfer, EIP712 { uint256 requestedAmount = transferDetails.requestedAmount; if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline); - if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount); + if (requestedAmount > permit.permitted.amount) + revert InvalidAmount(permit.permitted.amount); _useUnorderedNonce(owner, permit.nonce); @@ -92,7 +93,11 @@ contract SignatureTransfer is ISignatureTransfer, EIP712 { if (requestedAmount != 0) { // allow spender to specify which of the permitted tokens should be transferred - ERC20(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedAmount); + ERC20(permitted.token).safeTransferFrom( + owner, + transferDetails[i].to, + requestedAmount + ); } } } @@ -111,7 +116,9 @@ contract SignatureTransfer is ISignatureTransfer, EIP712 { /// @return bitPos The bit position /// @dev The first 248 bits of the nonce value is the index of the desired bitmap /// @dev The last 8 bits of the nonce value is the position of the bit in the bitmap - function bitmapPositions(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) { + function bitmapPositions( + uint256 nonce + ) private pure returns (uint256 wordPos, uint256 bitPos) { wordPos = uint248(nonce >> 8); bitPos = uint8(nonce); } diff --git a/contracts/Dependencies/permit2/interfaces/IAllowanceTransfer.sol b/contracts/Dependencies/permit2/interfaces/IAllowanceTransfer.sol index 712aa9d..c684b84 100644 --- a/contracts/Dependencies/permit2/interfaces/IAllowanceTransfer.sol +++ b/contracts/Dependencies/permit2/interfaces/IAllowanceTransfer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IEIP712} from "./IEIP712.sol"; +import { IEIP712 } from "./IEIP712.sol"; /// @title AllowanceTransfer /// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts @@ -20,12 +20,20 @@ interface IAllowanceTransfer is IEIP712 { /// @notice Emits an event when the owner successfully invalidates an ordered nonce. event NonceInvalidation( - address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce + address indexed owner, + address indexed token, + address indexed spender, + uint48 newNonce, + uint48 oldNonce ); /// @notice Emits an event when the owner successfully sets permissions on a token for the spender. event Approval( - address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration + address indexed owner, + address indexed token, + address indexed spender, + uint160 amount, + uint48 expiration ); /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender. @@ -108,10 +116,11 @@ interface IAllowanceTransfer is IEIP712 { /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval. /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress] /// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals. - function allowance(address user, address token, address spender) - external - view - returns (uint160 amount, uint48 expiration, uint48 nonce); + function allowance( + address user, + address token, + address spender + ) external view returns (uint160 amount, uint48 expiration, uint48 nonce); /// @notice Approves the spender to use up to amount of the specified token up until the expiration /// @param token The token to approve @@ -127,14 +136,22 @@ interface IAllowanceTransfer is IEIP712 { /// @param owner The owner of the tokens being approved /// @param permitSingle Data signed over by the owner specifying the terms of approval /// @param signature The owner's signature over the permit data - function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external; + function permit( + address owner, + PermitSingle memory permitSingle, + bytes calldata signature + ) external; /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce /// @param owner The owner of the tokens being approved /// @param permitBatch Data signed over by the owner specifying the terms of approval /// @param signature The owner's signature over the permit data - function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external; + function permit( + address owner, + PermitBatch memory permitBatch, + bytes calldata signature + ) external; /// @notice Transfer approved tokens from one address to another /// @param from The address to transfer from diff --git a/contracts/Dependencies/permit2/interfaces/IERC1271.sol b/contracts/Dependencies/permit2/interfaces/IERC1271.sol index a3c1cba..488dde2 100644 --- a/contracts/Dependencies/permit2/interfaces/IERC1271.sol +++ b/contracts/Dependencies/permit2/interfaces/IERC1271.sol @@ -6,5 +6,8 @@ interface IERC1271 { /// @param hash Hash of the data to be signed /// @param signature Signature byte array associated with _data /// @return magicValue The bytes4 magic value 0x1626ba7e - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); + function isValidSignature( + bytes32 hash, + bytes memory signature + ) external view returns (bytes4 magicValue); } diff --git a/contracts/Dependencies/permit2/interfaces/IPermit2.sol b/contracts/Dependencies/permit2/interfaces/IPermit2.sol index a800a18..a668b91 100644 --- a/contracts/Dependencies/permit2/interfaces/IPermit2.sol +++ b/contracts/Dependencies/permit2/interfaces/IPermit2.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {ISignatureTransfer} from "./ISignatureTransfer.sol"; -import {IAllowanceTransfer} from "./IAllowanceTransfer.sol"; +import { ISignatureTransfer } from "./ISignatureTransfer.sol"; +import { IAllowanceTransfer } from "./IAllowanceTransfer.sol"; /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. /// @dev Users must approve Permit2 before calling any of the transfer functions. interface IPermit2 is ISignatureTransfer, IAllowanceTransfer { -// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval. + // IPermit2 unifies the two interfaces so users have maximal flexibility with their approval. } diff --git a/contracts/Dependencies/permit2/interfaces/ISignatureTransfer.sol b/contracts/Dependencies/permit2/interfaces/ISignatureTransfer.sol index 57459da..a69bd84 100644 --- a/contracts/Dependencies/permit2/interfaces/ISignatureTransfer.sol +++ b/contracts/Dependencies/permit2/interfaces/ISignatureTransfer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IEIP712} from "./IEIP712.sol"; +import { IEIP712 } from "./IEIP712.sol"; /// @title SignatureTransfer /// @notice Handles ERC20 token transfers through signature based actions diff --git a/contracts/Dependencies/permit2/libraries/Allowance.sol b/contracts/Dependencies/permit2/libraries/Allowance.sol index 671c972..6c9fa45 100644 --- a/contracts/Dependencies/permit2/libraries/Allowance.sol +++ b/contracts/Dependencies/permit2/libraries/Allowance.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; +import { IAllowanceTransfer } from "../interfaces/IAllowanceTransfer.sol"; library Allowance { // note if the expiration passed is 0, then it the approval set to the block.timestamp @@ -21,7 +21,9 @@ library Allowance { storedNonce = nonce + 1; } - uint48 storedExpiration = expiration == BLOCK_TIMESTAMP_EXPIRATION ? uint48(block.timestamp) : expiration; + uint48 storedExpiration = expiration == BLOCK_TIMESTAMP_EXPIRATION + ? uint48(block.timestamp) + : expiration; uint256 word = pack(amount, storedExpiration, storedNonce); assembly { @@ -42,7 +44,11 @@ library Allowance { } /// @notice Computes the packed slot of the amount, expiration, and nonce that make up PackedAllowance - function pack(uint160 amount, uint48 expiration, uint48 nonce) internal pure returns (uint256 word) { - word = (uint256(nonce) << 208) | uint256(expiration) << 160 | amount; + function pack( + uint160 amount, + uint48 expiration, + uint48 nonce + ) internal pure returns (uint256 word) { + word = (uint256(nonce) << 208) | (uint256(expiration) << 160) | amount; } } diff --git a/contracts/Dependencies/permit2/libraries/Permit2Lib.sol b/contracts/Dependencies/permit2/libraries/Permit2Lib.sol index 3901342..843b57a 100644 --- a/contracts/Dependencies/permit2/libraries/Permit2Lib.sol +++ b/contracts/Dependencies/permit2/libraries/Permit2Lib.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {ERC20} from "../ERC20.sol"; +import { ERC20 } from "../ERC20.sol"; -import {IDAIPermit} from "../interfaces/IDAIPermit.sol"; -import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; -import {SafeCast160} from "./SafeCast160.sol"; +import { IDAIPermit } from "../interfaces/IDAIPermit.sol"; +import { IAllowanceTransfer } from "../interfaces/IAllowanceTransfer.sol"; +import { SafeCast160 } from "./SafeCast160.sol"; /// @title Permit2Lib /// @notice Enables efficient transfers and EIP-2612/DAI @@ -17,10 +17,12 @@ library Permit2Lib { //////////////////////////////////////////////////////////////*/ /// @dev The unique EIP-712 domain domain separator for the DAI token contract. - bytes32 internal constant DAI_DOMAIN_SEPARATOR = 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; + bytes32 internal constant DAI_DOMAIN_SEPARATOR = + 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; /// @dev The address for the WETH9 contract on Ethereum mainnet, encoded as a bytes32. - bytes32 internal constant WETH9_ADDRESS = 0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2; + bytes32 internal constant WETH9_ADDRESS = + 0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2; /// @dev The address of the Permit2 contract the library will use. IAllowanceTransfer internal constant PERMIT2 = @@ -37,16 +39,15 @@ library Permit2Lib { bool success; // Call the token contract as normal, capturing whether it succeeded. assembly { - success := - and( - // Set success to whether the call reverted, if not we check it either - // returned exactly 1 (can't just be non-zero data), or had no return data. - or(eq(mload(0), 1), iszero(returndatasize())), - // Counterintuitively, this call() must be positioned after the or() in the - // surrounding and() because and() evaluates its arguments from right to left. - // We use 0 and 32 to copy up to 32 bytes of return data into the first slot of scratch space. - call(gas(), token, 0, add(inputData, 32), mload(inputData), 0, 32) - ) + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(eq(mload(0), 1), iszero(returndatasize())), + // Counterintuitively, this call() must be positioned after the or() in the + // surrounding and() because and() evaluates its arguments from right to left. + // We use 0 and 32 to copy up to 32 bytes of return data into the first slot of scratch space. + call(gas(), token, 0, add(inputData, 32), mload(inputData), 0, 32) + ) } // We'll fall back to using Permit2 if calling transferFrom on the token directly reverted. @@ -88,17 +89,16 @@ library Permit2Lib { // If the token is WETH9, we know it doesn't have a DOMAIN_SEPARATOR, and we'll skip this step. // We make sure to mask the token address as its higher order bits aren't guaranteed to be clean. if iszero(eq(and(token, 0xffffffffffffffffffffffffffffffffffffffff), WETH9_ADDRESS)) { - success := - and( - // Should resolve false if its not 32 bytes or its first word is 0. - and(iszero(iszero(mload(0))), eq(returndatasize(), 32)), - // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. - // Counterintuitively, this call must be positioned second to the and() call in the - // surrounding and() call or else returndatasize() will be zero during the computation. - // We send a maximum of 5000 gas to prevent tokens with fallbacks from using a ton of gas. - // which should be plenty to allow tokens to fetch their DOMAIN_SEPARATOR from storage, etc. - staticcall(5000, token, add(inputData, 32), mload(inputData), 0, 32) - ) + success := and( + // Should resolve false if its not 32 bytes or its first word is 0. + and(iszero(iszero(mload(0))), eq(returndatasize(), 32)), + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the and() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + // We send a maximum of 5000 gas to prevent tokens with fallbacks from using a ton of gas. + // which should be plenty to allow tokens to fetch their DOMAIN_SEPARATOR from storage, etc. + staticcall(5000, token, add(inputData, 32), mload(inputData), 0, 32) + ) domainSeparator := mload(0) // Copy the return value into the domainSeparator variable. } @@ -109,7 +109,10 @@ library Permit2Lib { // We'll use DAI's special permit if it's DOMAIN_SEPARATOR matches, // otherwise we'll just encode a call to the standard permit function. inputData = domainSeparator == DAI_DOMAIN_SEPARATOR - ? abi.encodeCall(IDAIPermit.permit, (owner, spender, token.nonces(owner), deadline, true, v, r, s)) + ? abi.encodeCall( + IDAIPermit.permit, + (owner, spender, token.nonces(owner), deadline, true, v, r, s) + ) : abi.encodeCall(ERC20.permit, (owner, spender, amount, deadline, v, r, s)); assembly { @@ -143,7 +146,7 @@ library Permit2Lib { bytes32 r, bytes32 s ) internal { - (,, uint48 nonce) = PERMIT2.allowance(owner, address(token), spender); + (, , uint48 nonce) = PERMIT2.allowance(owner, address(token), spender); PERMIT2.permit( owner, diff --git a/contracts/Dependencies/permit2/libraries/PermitHash.sol b/contracts/Dependencies/permit2/libraries/PermitHash.sol index 32d4a83..4f1f0f8 100644 --- a/contracts/Dependencies/permit2/libraries/PermitHash.sol +++ b/contracts/Dependencies/permit2/libraries/PermitHash.sol @@ -1,32 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {IAllowanceTransfer} from "../interfaces/IAllowanceTransfer.sol"; -import {ISignatureTransfer} from "../interfaces/ISignatureTransfer.sol"; +import { IAllowanceTransfer } from "../interfaces/IAllowanceTransfer.sol"; +import { ISignatureTransfer } from "../interfaces/ISignatureTransfer.sol"; library PermitHash { bytes32 public constant _PERMIT_DETAILS_TYPEHASH = keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"); - bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256( - "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); + bytes32 public constant _PERMIT_SINGLE_TYPEHASH = + keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); - bytes32 public constant _PERMIT_BATCH_TYPEHASH = keccak256( - "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); + bytes32 public constant _PERMIT_BATCH_TYPEHASH = + keccak256( + "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); - bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)"); + bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = + keccak256("TokenPermissions(address token,uint256 amount)"); - bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = keccak256( - "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" - ); + bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = + keccak256( + "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" + ); - bytes32 public constant _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH = keccak256( - "PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" - ); + bytes32 public constant _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH = + keccak256( + "PermitBatchTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)" + ); - string public constant _TOKEN_PERMISSIONS_TYPESTRING = "TokenPermissions(address token,uint256 amount)"; + string public constant _TOKEN_PERMISSIONS_TYPESTRING = + "TokenPermissions(address token,uint256 amount)"; string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB = "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,"; @@ -34,36 +40,59 @@ library PermitHash { string public constant _PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB = "PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,"; - function hash(IAllowanceTransfer.PermitSingle memory permitSingle) internal pure returns (bytes32) { + function hash( + IAllowanceTransfer.PermitSingle memory permitSingle + ) internal pure returns (bytes32) { bytes32 permitHash = _hashPermitDetails(permitSingle.details); return - keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)); + keccak256( + abi.encode( + _PERMIT_SINGLE_TYPEHASH, + permitHash, + permitSingle.spender, + permitSingle.sigDeadline + ) + ); } - function hash(IAllowanceTransfer.PermitBatch memory permitBatch) internal pure returns (bytes32) { + function hash( + IAllowanceTransfer.PermitBatch memory permitBatch + ) internal pure returns (bytes32) { uint256 numPermits = permitBatch.details.length; bytes32[] memory permitHashes = new bytes32[](numPermits); for (uint256 i = 0; i < numPermits; ++i) { permitHashes[i] = _hashPermitDetails(permitBatch.details[i]); } - return keccak256( - abi.encode( - _PERMIT_BATCH_TYPEHASH, - keccak256(abi.encodePacked(permitHashes)), - permitBatch.spender, - permitBatch.sigDeadline - ) - ); + return + keccak256( + abi.encode( + _PERMIT_BATCH_TYPEHASH, + keccak256(abi.encodePacked(permitHashes)), + permitBatch.spender, + permitBatch.sigDeadline + ) + ); } - function hash(ISignatureTransfer.PermitTransferFrom memory permit) internal view returns (bytes32) { + function hash( + ISignatureTransfer.PermitTransferFrom memory permit + ) internal view returns (bytes32) { bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted); - return keccak256( - abi.encode(_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline) - ); + return + keccak256( + abi.encode( + _PERMIT_TRANSFER_FROM_TYPEHASH, + tokenPermissionsHash, + msg.sender, + permit.nonce, + permit.deadline + ) + ); } - function hash(ISignatureTransfer.PermitBatchTransferFrom memory permit) internal view returns (bytes32) { + function hash( + ISignatureTransfer.PermitBatchTransferFrom memory permit + ) internal view returns (bytes32) { uint256 numPermitted = permit.permitted.length; bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted); @@ -71,15 +100,16 @@ library PermitHash { tokenPermissionHashes[i] = _hashTokenPermissions(permit.permitted[i]); } - return keccak256( - abi.encode( - _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH, - keccak256(abi.encodePacked(tokenPermissionHashes)), - msg.sender, - permit.nonce, - permit.deadline - ) - ); + return + keccak256( + abi.encode( + _PERMIT_BATCH_TRANSFER_FROM_TYPEHASH, + keccak256(abi.encodePacked(tokenPermissionHashes)), + msg.sender, + permit.nonce, + permit.deadline + ) + ); } function hashWithWitness( @@ -87,10 +117,22 @@ library PermitHash { bytes32 witness, string calldata witnessTypeString ) internal view returns (bytes32) { - bytes32 typeHash = keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString)); + bytes32 typeHash = keccak256( + abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString) + ); bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted); - return keccak256(abi.encode(typeHash, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline, witness)); + return + keccak256( + abi.encode( + typeHash, + tokenPermissionsHash, + msg.sender, + permit.nonce, + permit.deadline, + witness + ) + ); } function hashWithWitness( @@ -98,8 +140,9 @@ library PermitHash { bytes32 witness, string calldata witnessTypeString ) internal view returns (bytes32) { - bytes32 typeHash = - keccak256(abi.encodePacked(_PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB, witnessTypeString)); + bytes32 typeHash = keccak256( + abi.encodePacked(_PERMIT_BATCH_WITNESS_TRANSFER_FROM_TYPEHASH_STUB, witnessTypeString) + ); uint256 numPermitted = permit.permitted.length; bytes32[] memory tokenPermissionHashes = new bytes32[](numPermitted); @@ -108,27 +151,28 @@ library PermitHash { tokenPermissionHashes[i] = _hashTokenPermissions(permit.permitted[i]); } - return keccak256( - abi.encode( - typeHash, - keccak256(abi.encodePacked(tokenPermissionHashes)), - msg.sender, - permit.nonce, - permit.deadline, - witness - ) - ); + return + keccak256( + abi.encode( + typeHash, + keccak256(abi.encodePacked(tokenPermissionHashes)), + msg.sender, + permit.nonce, + permit.deadline, + witness + ) + ); } - function _hashPermitDetails(IAllowanceTransfer.PermitDetails memory details) private pure returns (bytes32) { + function _hashPermitDetails( + IAllowanceTransfer.PermitDetails memory details + ) private pure returns (bytes32) { return keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, details)); } - function _hashTokenPermissions(ISignatureTransfer.TokenPermissions memory permitted) - private - pure - returns (bytes32) - { + function _hashTokenPermissions( + ISignatureTransfer.TokenPermissions memory permitted + ) private pure returns (bytes32) { return keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permitted)); } } diff --git a/contracts/Dependencies/permit2/libraries/SafeTransferLib.sol b/contracts/Dependencies/permit2/libraries/SafeTransferLib.sol index 19b2760..f62576f 100644 --- a/contracts/Dependencies/permit2/libraries/SafeTransferLib.sol +++ b/contracts/Dependencies/permit2/libraries/SafeTransferLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; -import {ERC20} from "../ERC20.sol"; +import { ERC20 } from "../ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) @@ -28,12 +28,7 @@ library SafeTransferLib { ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ - function safeTransferFrom( - ERC20 token, - address from, - address to, - uint256 amount - ) internal { + function safeTransferFrom(ERC20 token, address from, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly @@ -42,7 +37,10 @@ library SafeTransferLib { let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + mstore( + freeMemoryPointer, + 0x23b872dd00000000000000000000000000000000000000000000000000000000 + ) mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. @@ -62,11 +60,7 @@ library SafeTransferLib { require(success, "TRANSFER_FROM_FAILED"); } - function safeTransfer( - ERC20 token, - address to, - uint256 amount - ) internal { + function safeTransfer(ERC20 token, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly @@ -75,7 +69,10 @@ library SafeTransferLib { let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) + mstore( + freeMemoryPointer, + 0xa9059cbb00000000000000000000000000000000000000000000000000000000 + ) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. @@ -94,11 +91,7 @@ library SafeTransferLib { require(success, "TRANSFER_FAILED"); } - function safeApprove( - ERC20 token, - address to, - uint256 amount - ) internal { + function safeApprove(ERC20 token, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly @@ -107,7 +100,10 @@ library SafeTransferLib { let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) + mstore( + freeMemoryPointer, + 0x095ea7b300000000000000000000000000000000000000000000000000000000 + ) mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. diff --git a/contracts/Dependencies/permit2/libraries/SignatureVerification.sol b/contracts/Dependencies/permit2/libraries/SignatureVerification.sol index 904dfcd..2fabfbb 100644 --- a/contracts/Dependencies/permit2/libraries/SignatureVerification.sol +++ b/contracts/Dependencies/permit2/libraries/SignatureVerification.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {IERC1271} from "../interfaces/IERC1271.sol"; +import { IERC1271 } from "../interfaces/IERC1271.sol"; library SignatureVerification { /// @notice Thrown when the passed in signature is not a valid length @@ -16,7 +16,9 @@ library SignatureVerification { /// @notice Thrown when the recovered contract signature is incorrect error InvalidContractSignature(); - bytes32 constant UPPER_BIT_MASK = (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + bytes32 constant UPPER_BIT_MASK = ( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); function verify(bytes calldata signature, bytes32 hash, address claimedSigner) internal view { bytes32 r; @@ -41,7 +43,8 @@ library SignatureVerification { if (signer != claimedSigner) revert InvalidSigner(); } else { bytes4 magicValue = IERC1271(claimedSigner).isValidSignature(hash, signature); - if (magicValue != IERC1271.isValidSignature.selector) revert InvalidContractSignature(); + if (magicValue != IERC1271.isValidSignature.selector) + revert InvalidContractSignature(); } } } diff --git a/contracts/HintHelpers.sol b/contracts/HintHelpers.sol index 3f77c3a..37ada45 100644 --- a/contracts/HintHelpers.sol +++ b/contracts/HintHelpers.sol @@ -136,15 +136,7 @@ contract HintHelpers is LiquityBase, HintHelpersStorage, CheckContract { uint256 _CR, uint256 _numTrials, uint256 _inputRandomSeed - ) - external - view - returns ( - address hintAddress, - uint256 diff, - uint256 latestRandomSeed - ) - { + ) external view returns (address hintAddress, uint256 diff, uint256 latestRandomSeed) { uint256 arrayLength = troveManager.getTroveOwnersCount(); if (arrayLength == 0) { diff --git a/contracts/Interfaces/IAllowanceTransfer.sol b/contracts/Interfaces/IAllowanceTransfer.sol index 8f4b339..6959348 100644 --- a/contracts/Interfaces/IAllowanceTransfer.sol +++ b/contracts/Interfaces/IAllowanceTransfer.sol @@ -2,7 +2,7 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; -import {IEIP712} from "./IEIP712.sol"; +import { IEIP712 } from "./IEIP712.sol"; /// @title AllowanceTransfer /// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts @@ -10,12 +10,20 @@ import {IEIP712} from "./IEIP712.sol"; interface IAllowanceTransfer is IEIP712 { /// @notice Emits an event when the owner successfully invalidates an ordered nonce. event NonceInvalidation( - address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce + address indexed owner, + address indexed token, + address indexed spender, + uint48 newNonce, + uint48 oldNonce ); /// @notice Emits an event when the owner successfully sets permissions on a token for the spender. event Approval( - address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration + address indexed owner, + address indexed token, + address indexed spender, + uint160 amount, + uint48 expiration ); /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender. @@ -98,10 +106,11 @@ interface IAllowanceTransfer is IEIP712 { /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval. /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress] /// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals. - function allowance(address user, address token, address spender) - external - view - returns (uint160 amount, uint48 expiration, uint48 nonce); + function allowance( + address user, + address token, + address spender + ) external view returns (uint160 amount, uint48 expiration, uint48 nonce); /// @notice Approves the spender to use up to amount of the specified token up until the expiration /// @param token The token to approve @@ -117,14 +126,22 @@ interface IAllowanceTransfer is IEIP712 { /// @param owner The owner of the tokens being approved /// @param permitSingle Data signed over by the owner specifying the terms of approval /// @param signature The owner's signature over the permit data - function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external; + function permit( + address owner, + PermitSingle memory permitSingle, + bytes calldata signature + ) external; /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce /// @param owner The owner of the tokens being approved /// @param permitBatch Data signed over by the owner specifying the terms of approval /// @param signature The owner's signature over the permit data - function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external; + function permit( + address owner, + PermitBatch memory permitBatch, + bytes calldata signature + ) external; /// @notice Transfer approved tokens from one address to another /// @param from The address to transfer from diff --git a/contracts/Interfaces/IBalanceRedirectPresale.sol b/contracts/Interfaces/IBalanceRedirectPresale.sol index e651fd4..4a3e192 100644 --- a/contracts/Interfaces/IBalanceRedirectPresale.sol +++ b/contracts/Interfaces/IBalanceRedirectPresale.sol @@ -3,6 +3,5 @@ pragma solidity 0.6.11; interface IBalanceRedirectPresale { - - function isClosed() external view returns (bool); -} \ No newline at end of file + function isClosed() external view returns (bool); +} diff --git a/contracts/Interfaces/IBorrowerOperations.sol b/contracts/Interfaces/IBorrowerOperations.sol index d6ed054..8b0a78f 100644 --- a/contracts/Interfaces/IBorrowerOperations.sol +++ b/contracts/Interfaces/IBorrowerOperations.sol @@ -190,7 +190,10 @@ interface IBorrowerOperations { * Requires the borrower have a NUE balance sufficient to repay their trove's debt, excluding gas compensation - i.e. `(debt - 50)` NUE. * This method is identical to `closeTrove()`, but operates on NUE tokens instead of ZUSD. */ - function closeNueTroveWithPermit2(ISignatureTransfer.PermitTransferFrom memory _permit, bytes calldata _signature) external; + function closeNueTroveWithPermit2( + ISignatureTransfer.PermitTransferFrom memory _permit, + bytes calldata _signature + ) external; /** * @notice enables a borrower to simultaneously change both their collateral and debt, subject to all the restrictions that apply to individual increases/decreases of each quantity with the following particularity: diff --git a/contracts/Interfaces/ICollSurplusPool.sol b/contracts/Interfaces/ICollSurplusPool.sol index d5fd30d..03b9901 100644 --- a/contracts/Interfaces/ICollSurplusPool.sol +++ b/contracts/Interfaces/ICollSurplusPool.sol @@ -45,11 +45,11 @@ interface ICollSurplusPool { function claimColl(address _account) external; /// @notice Two-leg claim: `_feeAmount` to `_feeReceiver`, remainder to `_account`. - /// Only callable by BorrowerOperations (the ColFee surplus-claim hook). + /// Only callable by BorrowerOperations (the Perimeter surplus-claim hook). /// The fee leg is fail-open: if the fee transfer fails, `_account` /// receives the full claimable balance. /// @param _account account whose claimable collateral is paid out - /// @param _feeReceiver ColFee fee destination for the fee leg + /// @param _feeReceiver Perimeter fee destination for the fee leg /// @param _feeAmount fee in wei; must not exceed the account's claimable balance /// @return feePaid true iff the fee transfer succeeded (caller emits the matching event) function claimCollWithFee( diff --git a/contracts/Interfaces/ICommunityIssuance.sol b/contracts/Interfaces/ICommunityIssuance.sol index 87d5197..fd3ac7e 100644 --- a/contracts/Interfaces/ICommunityIssuance.sol +++ b/contracts/Interfaces/ICommunityIssuance.sol @@ -2,10 +2,9 @@ pragma solidity 0.6.11; -interface ICommunityIssuance { - +interface ICommunityIssuance { // --- Events --- - + event SOVTokenAddressSet(address _zeroTokenAddress); event ZUSDTokenAddressSet(address _zusdTokenAddress); event StabilityPoolAddressSet(address _stabilityPoolAddress); @@ -54,7 +53,7 @@ interface ICommunityIssuance { function setRewardManager(address _rewardManagerAddress) external; /// @notice issues SOV tokens based on total zusd is deposited. - /// @return SOV tokens issuance + /// @return SOV tokens issuance function issueSOV(uint256 _totalZUSDDeposits) external returns (uint256); /// @notice sends ZERO tokens to given account diff --git a/contracts/Interfaces/IFeeSharingCollector.sol b/contracts/Interfaces/IFeeSharingCollector.sol index 1842650..4fd06c8 100644 --- a/contracts/Interfaces/IFeeSharingCollector.sol +++ b/contracts/Interfaces/IFeeSharingCollector.sol @@ -6,15 +6,11 @@ pragma solidity 0.6.11; * @dev Interfaces are used to cast a contract address into a callable instance. * */ interface IFeeSharingCollector { - function withdrawFees(address _token) external; + function withdrawFees(address _token) external; - function transferTokens(address _token, uint96 _amount) external; + function transferTokens(address _token, uint96 _amount) external; - function withdraw( - address _loanPoolToken, - uint32 _maxCheckpoints, - address _receiver - ) external; + function withdraw(address _loanPoolToken, uint32 _maxCheckpoints, address _receiver) external; - function transferRBTC() external payable; + function transferRBTC() external payable; } diff --git a/contracts/Interfaces/ILiquityBaseParams.sol b/contracts/Interfaces/ILiquityBaseParams.sol index 791711e..c8fca17 100644 --- a/contracts/Interfaces/ILiquityBaseParams.sol +++ b/contracts/Interfaces/ILiquityBaseParams.sol @@ -3,7 +3,6 @@ pragma solidity 0.6.11; interface ILiquityBaseParams { - /// Minimum collateral ratio for individual troves function MCR() external view returns (uint); @@ -21,5 +20,4 @@ interface ILiquityBaseParams { function REDEMPTION_FEE_FLOOR() external view returns (uint); function MAX_BORROWING_FEE() external view returns (uint); - -} \ No newline at end of file +} diff --git a/contracts/Interfaces/IPermit2.sol b/contracts/Interfaces/IPermit2.sol index f21ce7e..f1814bd 100644 --- a/contracts/Interfaces/IPermit2.sol +++ b/contracts/Interfaces/IPermit2.sol @@ -2,11 +2,11 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; -import {ISignatureTransfer} from "./ISignatureTransfer.sol"; -import {IAllowanceTransfer} from "./IAllowanceTransfer.sol"; +import { ISignatureTransfer } from "./ISignatureTransfer.sol"; +import { IAllowanceTransfer } from "./IAllowanceTransfer.sol"; /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. /// @dev Users must approve Permit2 before calling any of the transfer functions. interface IPermit2 is ISignatureTransfer, IAllowanceTransfer { -// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval. + // IPermit2 unifies the two interfaces so users have maximal flexibility with their approval. } diff --git a/contracts/Interfaces/IPriceFeedSovryn.sol b/contracts/Interfaces/IPriceFeedSovryn.sol index 0a0e07b..e3287a8 100644 --- a/contracts/Interfaces/IPriceFeedSovryn.sol +++ b/contracts/Interfaces/IPriceFeedSovryn.sol @@ -13,15 +13,15 @@ pragma solidity 0.6.11; * drawdown, margin and collateral. * */ interface IPriceFeedSovryn { - function queryRate(address sourceToken, address destToken) - external - view - returns (uint256 rate, uint256 precision); + function queryRate( + address sourceToken, + address destToken + ) external view returns (uint256 rate, uint256 precision); - function queryPrecision(address sourceToken, address destToken) - external - view - returns (uint256 precision); + function queryPrecision( + address sourceToken, + address destToken + ) external view returns (uint256 precision); function queryReturn( address sourceToken, diff --git a/contracts/Interfaces/ISignatureTransfer.sol b/contracts/Interfaces/ISignatureTransfer.sol index 6038f55..467582d 100644 --- a/contracts/Interfaces/ISignatureTransfer.sol +++ b/contracts/Interfaces/ISignatureTransfer.sol @@ -2,7 +2,7 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; -import {IEIP712} from "./IEIP712.sol"; +import { IEIP712 } from "./IEIP712.sol"; /// @title SignatureTransfer /// @notice Handles ERC20 token transfers through signature based actions diff --git a/contracts/Interfaces/ISortedTroves.sol b/contracts/Interfaces/ISortedTroves.sol index b0e2f7d..6023624 100644 --- a/contracts/Interfaces/ISortedTroves.sol +++ b/contracts/Interfaces/ISortedTroves.sol @@ -33,12 +33,7 @@ interface ISortedTroves { * @param _prevId Id of previous node for the insert position * @param _nextId Id of next node for the insert position */ - function insert( - address _id, - uint256 _ICR, - address _prevId, - address _nextId - ) external; + function insert(address _id, uint256 _ICR, address _prevId, address _nextId) external; /** * @dev Remove a node from the list @@ -53,12 +48,7 @@ interface ISortedTroves { * @param _prevId Id of previous node for the new insert position * @param _nextId Id of next node for the new insert position */ - function reInsert( - address _id, - uint256 _newICR, - address _prevId, - address _nextId - ) external; + function reInsert(address _id, uint256 _newICR, address _prevId, address _nextId) external; /** * @dev Checks if the list contains a node diff --git a/contracts/Interfaces/ITroveManager.sol b/contracts/Interfaces/ITroveManager.sol index bb0c442..e6c3d09 100644 --- a/contracts/Interfaces/ITroveManager.sol +++ b/contracts/Interfaces/ITroveManager.sol @@ -199,7 +199,6 @@ interface ITroveManager is ILiquityBase { ISignatureTransfer.PermitTransferFrom memory _permit, bytes calldata _signature ) external; - /// @notice Update borrower's stake based on their latest collateral value /// @param _borrower borrower address diff --git a/contracts/Interfaces/IWrbtc.sol b/contracts/Interfaces/IWrbtc.sol index efd7b9c..4afb31c 100644 --- a/contracts/Interfaces/IWrbtc.sol +++ b/contracts/Interfaces/IWrbtc.sol @@ -4,9 +4,7 @@ pragma solidity 0.6.11; import "../Dependencies/IERC20.sol"; interface IWrbtc is IERC20 { - - function deposit() external payable; - - function withdraw(uint256 wad) external; + function deposit() external payable; + function withdraw(uint256 wad) external; } diff --git a/contracts/Interfaces/IZEROToken.sol b/contracts/Interfaces/IZEROToken.sol index e6c0df9..9e308eb 100644 --- a/contracts/Interfaces/IZEROToken.sol +++ b/contracts/Interfaces/IZEROToken.sol @@ -5,8 +5,7 @@ pragma solidity 0.6.11; import "../Dependencies/IERC20.sol"; import "../Dependencies/IERC2612.sol"; -interface IZEROToken is IERC20, IERC2612 { - +interface IZEROToken is IERC20, IERC2612 { // --- Functions --- /// @notice send zero tokens to ZEROStaking contract @@ -16,5 +15,4 @@ interface IZEROToken is IERC20, IERC2612 { /// @return deployment start time function getDeploymentStartTime() external view returns (uint256); - } diff --git a/contracts/Interfaces/IZUSDToken.sol b/contracts/Interfaces/IZUSDToken.sol index 35b4f03..6b74d4e 100644 --- a/contracts/Interfaces/IZUSDToken.sol +++ b/contracts/Interfaces/IZUSDToken.sol @@ -5,8 +5,7 @@ pragma solidity 0.6.11; import "../Dependencies/IERC20.sol"; import "../Dependencies/IERC2612.sol"; -interface IZUSDToken is IERC20, IERC2612 { - +interface IZUSDToken is IERC20, IERC2612 { // --- Events --- event TroveManagerAddressChanged(address _troveManagerAddress); @@ -21,7 +20,7 @@ interface IZUSDToken is IERC20, IERC2612 { function burn(address _account, uint256 _amount) external; - function sendToPool(address _sender, address poolAddress, uint256 _amount) external; + function sendToPool(address _sender, address poolAddress, uint256 _amount) external; - function returnFromPool(address poolAddress, address user, uint256 _amount ) external; + function returnFromPool(address poolAddress, address user, uint256 _amount) external; } diff --git a/contracts/Interfaces/colfee/IExitDelayQueue.sol b/contracts/Interfaces/perimeter/IExitDelayQueue.sol similarity index 98% rename from contracts/Interfaces/colfee/IExitDelayQueue.sol rename to contracts/Interfaces/perimeter/IExitDelayQueue.sol index 6ca8e8f..1a464db 100644 --- a/contracts/Interfaces/colfee/IExitDelayQueue.sol +++ b/contracts/Interfaces/perimeter/IExitDelayQueue.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // ───────────────────────────────────────────────────────────────────────────── -// PROVENANCE — copied verbatim from DistributedCollective/colfee +// PROVENANCE — copied verbatim from DistributedCollective/perimeter // @ 51457b21bc9a87958e99ea51325ded150422e791 // src/interfaces/IExitDelayQueue.sol // Do NOT modify the ABI here — the queue's IExitDelayQueue is final. @@ -63,7 +63,7 @@ interface IExitDelayQueue { /// frozen at record time. Packed into 7 words. struct ExitRequest { // word 1 (128 + 64 + 64 = 256 bits): - uint128 amount; // narrowed from the uint256 ColFee amount at record + uint128 amount; // narrowed from the uint256 Perimeter amount at record uint64 createdAt; // audit/analytics; emitted in ExitQueued uint64 unlockAt; // COMPUTED by the queue = createdAt + delaySeconds // words 2-5: @@ -172,7 +172,7 @@ interface IExitDelayQueue { /// @dev CALLER-SIDE NARROWING PRECONDITION. Every /// `record*` takes `amount` as a **`uint128`**, deliberately NOT widened - /// to `uint256`. The ColFee hook computes the user leg as a `uint256` and + /// to `uint256`. The Perimeter hook computes the user leg as a `uint256` and /// MUST narrow it (`uint128(userAmount)`) at the call site; that narrowing /// is the caller's responsibility and MUST be preceded by the caller's own /// `require(userAmount <= type(uint128).max)` (`AmountTooLarge`) so a value diff --git a/contracts/Interfaces/colfee/IExitDelayQueueHook.sol b/contracts/Interfaces/perimeter/IExitDelayQueueHook.sol similarity index 98% rename from contracts/Interfaces/colfee/IExitDelayQueueHook.sol rename to contracts/Interfaces/perimeter/IExitDelayQueueHook.sol index 2af066e..c4f185f 100644 --- a/contracts/Interfaces/colfee/IExitDelayQueueHook.sol +++ b/contracts/Interfaces/perimeter/IExitDelayQueueHook.sol @@ -3,7 +3,7 @@ // Cross-pragma ingress stub for `ExitDelayQueue` (the security-perimeter delay // queue). The FULL interface + type/event/error catalog lives in // `IExitDelayQueue.sol` (0.8.20, provenance-locked to -// DistributedCollective/colfee @ 51457b21). This stub declares ONLY the members +// DistributedCollective/perimeter @ 51457b21). This stub declares ONLY the members // the 0.5.17 lending + borrower/margin product hooks actually call, so it can be // imported under the range pragma the product repos compile with. // diff --git a/contracts/Interfaces/colfee/IExitFeeController.sol b/contracts/Interfaces/perimeter/IExitFeeController.sol similarity index 97% rename from contracts/Interfaces/colfee/IExitFeeController.sol rename to contracts/Interfaces/perimeter/IExitFeeController.sol index 0490a08..85011c6 100644 --- a/contracts/Interfaces/colfee/IExitFeeController.sol +++ b/contracts/Interfaces/perimeter/IExitFeeController.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // ───────────────────────────────────────────────────────────────────────────── // Curated cross-pragma subset of the exit-fee controller interface, derived from -// DistributedCollective/colfee @ 51457b21bc9a87958e99ea51325ded150422e791 +// DistributedCollective/perimeter @ 51457b21bc9a87958e99ea51325ded150422e791 // src/interfaces/IExitFeeController.sol // // This is a deliberate SUBSET, not a verbatim copy: it declares only the members @@ -43,7 +43,7 @@ pragma solidity >=0.5.17 <0.9.0; pragma experimental ABIEncoderV2; /// @title IExitFeeController -/// @notice Cross-pragma interface for the Sovryn ExitFee (ColFee) controller. +/// @notice Cross-pragma interface for the Sovryn ExitFee (Perimeter) controller. /// One declaration shared by every consumer so they all resolve the /// same ABI. Products compiled under a pragma this file cannot span /// declare their own ABI-equivalent variant instead. @@ -51,8 +51,8 @@ pragma experimental ABIEncoderV2; interface IExitFeeController { // ─── Types ──────────────────────────────────────────────────────────── - /// @notice Reason a `ColFeeSkipped` event was emitted instead of an - /// `ColFeeApplied`. NONE covers honest paths (positive charge, + /// @notice Reason a `PerimeterSkipped` event was emitted instead of an + /// `PerimeterApplied`. NONE covers honest paths (positive charge, /// dust, or actor-exemption); the rest cover off-state outcomes. enum SkipReason { NONE, // Controller computed an honest quote (charge / dust / zero-rate). @@ -76,7 +76,7 @@ interface IExitFeeController { /// tier decides: `bypass == true` exempts (`d = 0`), `bypass == false` /// FORCES `globalDelaySeconds` (overriding a broader bypass). It is an /// exemption toggle only — there is no per-instance delay - /// duration. Copied final from colfee. + /// duration. Copied final from perimeter. struct DelayBypassPolicy { bool active; bool bypass; diff --git a/contracts/MultiTroveGetter.sol b/contracts/MultiTroveGetter.sol index 006cf15..3f17fc7 100644 --- a/contracts/MultiTroveGetter.sol +++ b/contracts/MultiTroveGetter.sol @@ -18,19 +18,18 @@ contract MultiTroveGetter is MultiTroveGetterStorage { uint256 snapshotZUSDDebt; } - function setAddresses(TroveManager _troveManager, ISortedTroves _sortedTroves) - public - onlyOwner - { + function setAddresses( + TroveManager _troveManager, + ISortedTroves _sortedTroves + ) public onlyOwner { troveManager = _troveManager; sortedTroves = _sortedTroves; } - function getMultipleSortedTroves(int256 _startIdx, uint256 _count) - external - view - returns (CombinedTroveData[] memory _troves) - { + function getMultipleSortedTroves( + int256 _startIdx, + uint256 _count + ) external view returns (CombinedTroveData[] memory _troves) { uint256 startIdx; bool descend; @@ -61,11 +60,10 @@ contract MultiTroveGetter is MultiTroveGetterStorage { } } - function _getMultipleSortedTrovesFromHead(uint256 _startIdx, uint256 _count) - internal - view - returns (CombinedTroveData[] memory _troves) - { + function _getMultipleSortedTrovesFromHead( + uint256 _startIdx, + uint256 _count + ) internal view returns (CombinedTroveData[] memory _troves) { address currentTroveowner = sortedTroves.getFirst(); for (uint256 idx = 0; idx < _startIdx; ++idx) { @@ -92,11 +90,10 @@ contract MultiTroveGetter is MultiTroveGetterStorage { } } - function _getMultipleSortedTrovesFromTail(uint256 _startIdx, uint256 _count) - internal - view - returns (CombinedTroveData[] memory _troves) - { + function _getMultipleSortedTrovesFromTail( + uint256 _startIdx, + uint256 _count + ) internal view returns (CombinedTroveData[] memory _troves) { address currentTroveowner = sortedTroves.getLast(); for (uint256 idx = 0; idx < _startIdx; ++idx) { diff --git a/contracts/PriceFeed.sol b/contracts/PriceFeed.sol index 4bf046a..9cbcc39 100644 --- a/contracts/PriceFeed.sol +++ b/contracts/PriceFeed.sol @@ -39,7 +39,7 @@ contract PriceFeed is PriceFeedStorage, IPriceFeed { emit PriceFeedBroken(index, address(priceFeeds[index])); } } - + revert("PriceFeed: Price feed price is stale"); } @@ -62,7 +62,7 @@ contract PriceFeed is PriceFeedStorage, IPriceFeed { emit LastGoodPriceUpdated(_currentPrice); } - function getPriceFeedAtIndex(uint8 _index) external view returns(address) { + function getPriceFeedAtIndex(uint8 _index) external view returns (address) { return address(priceFeeds[_index]); } } diff --git a/contracts/Proxy/BorrowerOperationsScript.sol b/contracts/Proxy/BorrowerOperationsScript.sol index e8a1d3e..f7c9c61 100644 --- a/contracts/Proxy/BorrowerOperationsScript.sol +++ b/contracts/Proxy/BorrowerOperationsScript.sol @@ -5,7 +5,6 @@ pragma solidity 0.6.11; import "../Dependencies/CheckContract.sol"; import "../Interfaces/IBorrowerOperations.sol"; - contract BorrowerOperationsScript is CheckContract { IBorrowerOperations immutable borrowerOperations; @@ -14,8 +13,18 @@ contract BorrowerOperationsScript is CheckContract { borrowerOperations = _borrowerOperations; } - function openTrove(uint _maxFee, uint _ZUSDAmount, address _upperHint, address _lowerHint) external payable { - borrowerOperations.openTrove{ value: msg.value }(_maxFee, _ZUSDAmount, _upperHint, _lowerHint); + function openTrove( + uint _maxFee, + uint _ZUSDAmount, + address _upperHint, + address _lowerHint + ) external payable { + borrowerOperations.openTrove{ value: msg.value }( + _maxFee, + _ZUSDAmount, + _upperHint, + _lowerHint + ); } function addColl(address _upperHint, address _lowerHint) external payable { @@ -26,7 +35,12 @@ contract BorrowerOperationsScript is CheckContract { borrowerOperations.withdrawColl(_amount, _upperHint, _lowerHint); } - function withdrawZUSD(uint _maxFee, uint _amount, address _upperHint, address _lowerHint) external { + function withdrawZUSD( + uint _maxFee, + uint _amount, + address _upperHint, + address _lowerHint + ) external { borrowerOperations.withdrawZUSD(_maxFee, _amount, _upperHint, _lowerHint); } @@ -38,8 +52,22 @@ contract BorrowerOperationsScript is CheckContract { borrowerOperations.closeTrove(); } - function adjustTrove(uint _maxFee, uint _collWithdrawal, uint _debtChange, bool isDebtIncrease, address _upperHint, address _lowerHint) external payable { - borrowerOperations.adjustTrove{ value: msg.value }(_maxFee, _collWithdrawal, _debtChange, isDebtIncrease, _upperHint, _lowerHint); + function adjustTrove( + uint _maxFee, + uint _collWithdrawal, + uint _debtChange, + bool isDebtIncrease, + address _upperHint, + address _lowerHint + ) external payable { + borrowerOperations.adjustTrove{ value: msg.value }( + _maxFee, + _collWithdrawal, + _debtChange, + isDebtIncrease, + _upperHint, + _lowerHint + ); } function claimCollateral() external { diff --git a/contracts/Proxy/BorrowerWrappersScript.sol b/contracts/Proxy/BorrowerWrappersScript.sol index a06b677..4b08fd9 100644 --- a/contracts/Proxy/BorrowerWrappersScript.sol +++ b/contracts/Proxy/BorrowerWrappersScript.sol @@ -15,11 +15,10 @@ import "./ETHTransferScript.sol"; import "./ZEROStakingScript.sol"; import "../Dependencies/console.sol"; - contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, ZEROStakingScript { using SafeMath for uint; - string constant public NAME = "BorrowerWrappersScript"; + string public constant NAME = "BorrowerWrappersScript"; ITroveManager immutable troveManager; IStabilityPool immutable stabilityPool; @@ -37,9 +36,9 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, address _zusdTokenAddress, address _zeroTokenAddress ) + public BorrowerOperationsScript(IBorrowerOperations(_borrowerOperationsAddress)) ZEROStakingScript(_zeroStakingAddress) - public { checkContract(_troveManagerAddress); ITroveManager troveManagerCached = ITroveManager(_troveManagerAddress); @@ -49,7 +48,7 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, checkContract(_stabilityPoolAddress); stabilityPool = stabilityPoolCached; - IPriceFeed priceFeedCached = IPriceFeed(_priceFeedAddress); + IPriceFeed priceFeedCached = IPriceFeed(_priceFeedAddress); checkContract(_priceFeedAddress); priceFeed = priceFeedCached; @@ -64,7 +63,12 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, zeroStaking = zeroStakingCached; } - function claimCollateralAndOpenTrove(uint _maxFee, uint _ZUSDAmount, address _upperHint, address _lowerHint) external payable { + function claimCollateralAndOpenTrove( + uint _maxFee, + uint _ZUSDAmount, + address _upperHint, + address _lowerHint + ) external payable { uint balanceBefore = address(this).balance; // Claim collateral @@ -78,10 +82,19 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, uint totalCollateral = balanceAfter.sub(balanceBefore).add(msg.value); // Open trove with obtained collateral, plus collateral sent by user - borrowerOperations.openTrove{ value: totalCollateral }(_maxFee, _ZUSDAmount, _upperHint, _lowerHint); + borrowerOperations.openTrove{ value: totalCollateral }( + _maxFee, + _ZUSDAmount, + _upperHint, + _lowerHint + ); } - function claimSPRewardsAndRecycle(uint _maxFee, address _upperHint, address _lowerHint) external { + function claimSPRewardsAndRecycle( + uint _maxFee, + address _upperHint, + address _lowerHint + ) external { uint collBalanceBefore = address(this).balance; uint zeroBalanceBefore = zeroToken.balanceOf(address(this)); @@ -96,7 +109,14 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, if (claimedCollateral > 0) { _requireUserHasTrove(address(this)); uint ZUSDAmount = _getNetZUSDAmount(claimedCollateral); - borrowerOperations.adjustTrove{ value: claimedCollateral }(_maxFee, 0, ZUSDAmount, true, _upperHint, _lowerHint); + borrowerOperations.adjustTrove{ value: claimedCollateral }( + _maxFee, + 0, + ZUSDAmount, + true, + _upperHint, + _lowerHint + ); // Provide withdrawn ZUSD to Stability Pool if (ZUSDAmount > 0) { stabilityPool.provideToSP(ZUSDAmount, address(0)); @@ -110,7 +130,11 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, } } - function claimStakingGainsAndRecycle(uint _maxFee, address _upperHint, address _lowerHint) external { + function claimStakingGainsAndRecycle( + uint _maxFee, + address _upperHint, + address _lowerHint + ) external { uint collBalanceBefore = address(this).balance; uint zusdBalanceBefore = zusdToken.balanceOf(address(this)); uint zeroBalanceBefore = zeroToken.balanceOf(address(this)); @@ -126,7 +150,14 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, if (gainedCollateral > 0) { _requireUserHasTrove(address(this)); netZUSDAmount = _getNetZUSDAmount(gainedCollateral); - borrowerOperations.adjustTrove{ value: gainedCollateral }(_maxFee, 0, netZUSDAmount, true, _upperHint, _lowerHint); + borrowerOperations.adjustTrove{ value: gainedCollateral }( + _maxFee, + 0, + netZUSDAmount, + true, + _upperHint, + _lowerHint + ); } uint totalZUSD = gainedZUSD.add(netZUSDAmount); @@ -140,7 +171,6 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, zeroStaking.stake(claimedZERO); } } - } function _getNetZUSDAmount(uint _collateral) internal returns (uint) { @@ -149,12 +179,17 @@ contract BorrowerWrappersScript is BorrowerOperationsScript, ETHTransferScript, uint ZUSDAmount = _collateral.mul(price).div(ICR); uint borrowingRate = troveManager.getBorrowingRateWithDecay(); - uint netDebt = ZUSDAmount.mul(LiquityMath.DECIMAL_PRECISION).div(LiquityMath.DECIMAL_PRECISION.add(borrowingRate)); + uint netDebt = ZUSDAmount.mul(LiquityMath.DECIMAL_PRECISION).div( + LiquityMath.DECIMAL_PRECISION.add(borrowingRate) + ); return netDebt; } function _requireUserHasTrove(address _depositor) internal view { - require(troveManager.getTroveStatus(_depositor) == 1, "BorrowerWrappersScript: caller must have an active trove"); + require( + troveManager.getTroveStatus(_depositor) == 1, + "BorrowerWrappersScript: caller must have an active trove" + ); } } diff --git a/contracts/Proxy/ETHTransferScript.sol b/contracts/Proxy/ETHTransferScript.sol index ce70c91..0bd109b 100644 --- a/contracts/Proxy/ETHTransferScript.sol +++ b/contracts/Proxy/ETHTransferScript.sol @@ -2,10 +2,9 @@ pragma solidity 0.6.11; - contract ETHTransferScript { function transferETH(address _recipient, uint256 _amount) external returns (bool) { - (bool success, ) = _recipient.call{value: _amount}(""); + (bool success, ) = _recipient.call{ value: _amount }(""); return success; } } diff --git a/contracts/Proxy/Proxy.sol b/contracts/Proxy/Proxy.sol index 81ba359..4cff6d1 100644 --- a/contracts/Proxy/Proxy.sol +++ b/contracts/Proxy/Proxy.sol @@ -2,10 +2,11 @@ pragma solidity 0.6.11; import "../Dependencies/Ownable.sol"; + /** * @title Base Proxy contract. - * - * Adapted version of https://github.com/DistributedCollective/Sovryn-smart-contracts/blob/development/contracts/proxy/Proxy.sol + * + * Adapted version of https://github.com/DistributedCollective/Sovryn-smart-contracts/blob/development/contracts/proxy/Proxy.sol * * @notice The proxy performs delegated calls to the contract implementation * it is pointing to. This way upgradable contracts are possible on blockchain. diff --git a/contracts/Proxy/StabilityPoolScript.sol b/contracts/Proxy/StabilityPoolScript.sol index d6aca5e..6a68f8f 100644 --- a/contracts/Proxy/StabilityPoolScript.sol +++ b/contracts/Proxy/StabilityPoolScript.sol @@ -5,9 +5,8 @@ pragma solidity 0.6.11; import "../Dependencies/CheckContract.sol"; import "../Interfaces/IStabilityPool.sol"; - contract StabilityPoolScript is CheckContract { - string constant public NAME = "StabilityPoolScript"; + string public constant NAME = "StabilityPoolScript"; IStabilityPool immutable stabilityPool; diff --git a/contracts/Proxy/TokenScript.sol b/contracts/Proxy/TokenScript.sol index ddfc13d..8a1b789 100644 --- a/contracts/Proxy/TokenScript.sol +++ b/contracts/Proxy/TokenScript.sol @@ -5,9 +5,8 @@ pragma solidity 0.6.11; import "../Dependencies/CheckContract.sol"; import "../Dependencies/IERC20.sol"; - contract TokenScript is CheckContract { - string constant public NAME = "TokenScript"; + string public constant NAME = "TokenScript"; IERC20 immutable token; @@ -28,7 +27,11 @@ contract TokenScript is CheckContract { token.approve(spender, amount); } - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) { + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool) { token.transferFrom(sender, recipient, amount); } diff --git a/contracts/Proxy/TroveManagerScript.sol b/contracts/Proxy/TroveManagerScript.sol index 99e6bb7..cd1c961 100644 --- a/contracts/Proxy/TroveManagerScript.sol +++ b/contracts/Proxy/TroveManagerScript.sol @@ -5,9 +5,8 @@ pragma solidity 0.6.11; import "../Dependencies/CheckContract.sol"; import "../Interfaces/ITroveManager.sol"; - contract TroveManagerScript is CheckContract { - string constant public NAME = "TroveManagerScript"; + string public constant NAME = "TroveManagerScript"; ITroveManager immutable troveManager; diff --git a/contracts/Proxy/UpgradableProxy.sol b/contracts/Proxy/UpgradableProxy.sol index 8754fb7..f76ff30 100644 --- a/contracts/Proxy/UpgradableProxy.sol +++ b/contracts/Proxy/UpgradableProxy.sol @@ -7,7 +7,7 @@ import "./Proxy.sol"; * @title Upgradable Proxy contract. * * Adapted version of https://github.com/DistributedCollective/Sovryn-smart-contracts/blob/development/contracts/proxy/UpgradableProxy.sol - * + * * @notice A disadvantage of the immutable ledger is that nobody can change the * source code of a smart contract after it’s been deployed. In order to fix * bugs or introduce new features, smart contracts need to be upgradable somehow. @@ -32,5 +32,4 @@ contract UpgradableProxy is Proxy { function setImplementation(address _implementation) public onlyOwner { _setImplementation(_implementation); } - } diff --git a/contracts/Proxy/ZEROStakingScript.sol b/contracts/Proxy/ZEROStakingScript.sol index f4100a9..6a32a3f 100644 --- a/contracts/Proxy/ZEROStakingScript.sol +++ b/contracts/Proxy/ZEROStakingScript.sol @@ -5,7 +5,6 @@ pragma solidity 0.6.11; import "../Dependencies/CheckContract.sol"; import "../Interfaces/IZEROStaking.sol"; - contract ZEROStakingScript is CheckContract { IZEROStaking immutable ZEROStaking; diff --git a/contracts/StabilityPool.sol b/contracts/StabilityPool.sol index d0f6f46..0a590ff 100644 --- a/contracts/StabilityPool.sol +++ b/contracts/StabilityPool.sol @@ -486,10 +486,10 @@ contract StabilityPool is LiquityBase, StabilityPoolStorage, CheckContract, ISta emit G_Updated(epochToScaleToG[currentEpoch][currentScale], currentEpoch, currentScale); } - function _computeSOVPerUnitStaked(uint256 _SOVIssuance, uint256 _totalZUSDDeposits) - internal - returns (uint256) - { + function _computeSOVPerUnitStaked( + uint256 _SOVIssuance, + uint256 _totalZUSDDeposits + ) internal returns (uint256) { /* * Calculate the SOV-per-unit staked. Division uses a "feedback" error correction, to keep the * cumulative error low in the running total G: @@ -672,11 +672,10 @@ contract StabilityPool is LiquityBase, StabilityPoolStorage, CheckContract, ISta return ETHGain; } - function _getETHGainFromSnapshots(uint256 initialDeposit, Snapshots memory snapshots) - internal - view - returns (uint256) - { + function _getETHGainFromSnapshots( + uint256 initialDeposit, + Snapshots memory snapshots + ) internal view returns (uint256) { /* * Grab the sum 'S' from the epoch at which the stake was made. The ETH gain may span up to one scale change. * If it does, the second portion of the ETH gain is scaled by 1e9. @@ -754,11 +753,10 @@ contract StabilityPool is LiquityBase, StabilityPoolStorage, CheckContract, ISta return SOVGain; } - function _getSOVGainFromSnapshots(uint256 initialStake, Snapshots memory snapshots) - internal - view - returns (uint256) - { + function _getSOVGainFromSnapshots( + uint256 initialStake, + Snapshots memory snapshots + ) internal view returns (uint256) { /* * Grab the sum 'G' from the epoch at which the stake was made. The SOV gain may span up to one scale change. * If it does, the second portion of the SOV gain is scaled by 1e9. @@ -822,11 +820,10 @@ contract StabilityPool is LiquityBase, StabilityPoolStorage, CheckContract, ISta } // Internal function, used to calculcate compounded deposits and compounded front end stakes. - function _getCompoundedStakeFromSnapshots(uint256 initialStake, Snapshots memory snapshots) - internal - view - returns (uint256) - { + function _getCompoundedStakeFromSnapshots( + uint256 initialStake, + Snapshots memory snapshots + ) internal view returns (uint256) { uint256 snapshot_P = snapshots.P; uint128 scaleSnapshot = snapshots.scale; uint128 epochSnapshot = snapshots.epoch; diff --git a/contracts/TestContracts/ActivePoolTester.sol b/contracts/TestContracts/ActivePoolTester.sol index 20f8940..e816da6 100644 --- a/contracts/TestContracts/ActivePoolTester.sol +++ b/contracts/TestContracts/ActivePoolTester.sol @@ -5,9 +5,8 @@ pragma solidity 0.6.11; import "../ActivePool.sol"; contract ActivePoolTester is ActivePool { - function unprotectedIncreaseZUSDDebt(uint _amount) external { - ZUSDDebt = ZUSDDebt.add(_amount); + ZUSDDebt = ZUSDDebt.add(_amount); } function unprotectedPayable() external payable { diff --git a/contracts/TestContracts/BorrowerOperationsTester.sol b/contracts/TestContracts/BorrowerOperationsTester.sol index f13b9ce..1c26df2 100644 --- a/contracts/TestContracts/BorrowerOperationsTester.sol +++ b/contracts/TestContracts/BorrowerOperationsTester.sol @@ -8,59 +8,69 @@ import "../BorrowerOperations.sol"; /* Tester contract inherits from BorrowerOperations, and provides external functions for testing the parent's internal functions. */ contract BorrowerOperationsTester is BorrowerOperations { - constructor(address _permit2) public BorrowerOperations(_permit2) {} - function getNewICRFromTroveChange - ( - uint _coll, - uint _debt, - uint _collChange, - bool isCollIncrease, - uint _debtChange, - bool isDebtIncrease, + function getNewICRFromTroveChange( + uint _coll, + uint _debt, + uint _collChange, + bool isCollIncrease, + uint _debtChange, + bool isDebtIncrease, uint _price - ) - external - pure - returns (uint) - { - return _getNewICRFromTroveChange(_coll, _debt, _collChange, isCollIncrease, _debtChange, isDebtIncrease, _price); + ) external pure returns (uint) { + return + _getNewICRFromTroveChange( + _coll, + _debt, + _collChange, + isCollIncrease, + _debtChange, + isDebtIncrease, + _price + ); } - function getNewTCRFromTroveChange - ( - uint _collChange, - bool isCollIncrease, - uint _debtChange, - bool isDebtIncrease, + function getNewTCRFromTroveChange( + uint _collChange, + bool isCollIncrease, + uint _debtChange, + bool isDebtIncrease, uint _price - ) - external - view - returns (uint) - { - return _getNewTCRFromTroveChange(_collChange, isCollIncrease, _debtChange, isDebtIncrease, _price); + ) external view returns (uint) { + return + _getNewTCRFromTroveChange( + _collChange, + isCollIncrease, + _debtChange, + isDebtIncrease, + _price + ); } function getUSDValue(uint _coll, uint _price) external pure returns (uint) { return _getUSDValue(_coll, _price); } - function callInternalAdjustLoan - ( - address _borrower, - uint _collWithdrawal, - uint _debtChange, - bool _isDebtIncrease, + function callInternalAdjustLoan( + address _borrower, + uint _collWithdrawal, + uint _debtChange, + bool _isDebtIncrease, address _upperHint, - address _lowerHint) - external - { - _adjustTrove(_borrower, _collWithdrawal, _debtChange, _isDebtIncrease, _upperHint, _lowerHint, 0); + address _lowerHint + ) external { + _adjustTrove( + _borrower, + _collWithdrawal, + _debtChange, + _isDebtIncrease, + _upperHint, + _lowerHint, + 0 + ); } - // Payable fallback function - receive() external payable { } + receive() external payable {} } diff --git a/contracts/TestContracts/CommunityIssuanceTester.sol b/contracts/TestContracts/CommunityIssuanceTester.sol index 8ab3c31..875e4d8 100644 --- a/contracts/TestContracts/CommunityIssuanceTester.sol +++ b/contracts/TestContracts/CommunityIssuanceTester.sol @@ -6,12 +6,12 @@ import "../ZERO/CommunityIssuance.sol"; contract CommunityIssuanceTester is CommunityIssuance { function obtainSOV(uint _amount) external { - sovToken.transfer(msg.sender, _amount); + sovToken.transfer(msg.sender, _amount); } function unprotectedIssueSOV(uint256 _totalZUSDDeposits) external returns (uint) { - // No checks on caller address - - return _issueSOV(_totalZUSDDeposits); + // No checks on caller address + + return _issueSOV(_totalZUSDDeposits); } } diff --git a/contracts/TestContracts/DappSys/proxy.sol b/contracts/TestContracts/DappSys/proxy.sol index 062df94..5f9b86e 100644 --- a/contracts/TestContracts/DappSys/proxy.sol +++ b/contracts/TestContracts/DappSys/proxy.sol @@ -1,7 +1,7 @@ // From: https://etherscan.io/address/0xa26e15c895efc0616177b7c1e7270a4c7d51c997#code /** *Submitted for verification at Etherscan.io on 2018-06-22 -*/ + */ // proxy.sol - execute actions atomically through the proxy's identity @@ -25,42 +25,34 @@ pragma solidity 0.6.11; abstract contract DSAuthority { - function canCall( - address src, address dst, bytes4 sig - ) virtual public view returns (bool); + function canCall(address src, address dst, bytes4 sig) public view virtual returns (bool); } contract DSAuthEvents { - event LogSetAuthority (address indexed authority); - event LogSetOwner (address indexed owner); + event LogSetAuthority(address indexed authority); + event LogSetOwner(address indexed owner); } contract DSAuth is DSAuthEvents { - DSAuthority public authority; - address public owner; + DSAuthority public authority; + address public owner; constructor() public { owner = msg.sender; emit LogSetOwner(msg.sender); } - function setOwner(address owner_) - public - auth - { + function setOwner(address owner_) public auth { owner = owner_; emit LogSetOwner(owner); } - function setAuthority(DSAuthority authority_) - public - auth - { + function setAuthority(DSAuthority authority_) public auth { authority = authority_; emit LogSetAuthority(address(authority)); } - modifier auth { + modifier auth() { require(isAuthorized(msg.sender, msg.sig)); _; } @@ -80,15 +72,15 @@ contract DSAuth is DSAuthEvents { contract DSNote { event LogNote( - bytes4 indexed sig, - address indexed guy, - bytes32 indexed foo, - bytes32 indexed bar, - uint wad, - bytes fax + bytes4 indexed sig, + address indexed guy, + bytes32 indexed foo, + bytes32 indexed bar, + uint wad, + bytes fax ) anonymous; - modifier note { + modifier note() { bytes32 foo; bytes32 bar; @@ -109,23 +101,21 @@ contract DSNote { // the proxy can be changed, this allows for dynamic ownership models // i.e. a multisig contract DSProxy is DSAuth, DSNote { - DSProxyCache public cache; // global cache for contracts + DSProxyCache public cache; // global cache for contracts constructor(address _cacheAddr) public { require(setCache(_cacheAddr)); } - fallback() external payable { - } - receive() external payable { - } + fallback() external payable {} + + receive() external payable {} // use the proxy to execute calldata _data on contract _code - function execute(bytes calldata _code, bytes calldata _data) - public - payable - returns (address target, bytes32 response) - { + function execute( + bytes calldata _code, + bytes calldata _data + ) public payable returns (address target, bytes32 response) { target = cache.read(_code); if (target == address(0x0)) { // deploy contract & store its address in cache @@ -135,19 +125,23 @@ contract DSProxy is DSAuth, DSNote { response = execute(target, _data); } - function execute(address _target, bytes memory _data) - public - auth - note - payable - returns (bytes32 response) - { + function execute( + address _target, + bytes memory _data + ) public payable auth note returns (bytes32 response) { require(_target != address(0x0)); // call contract in current context assembly { - let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 32) - response := mload(0) // load delegatecall output + let succeeded := delegatecall( + sub(gas(), 5000), + _target, + add(_data, 0x20), + mload(_data), + 0, + 32 + ) + response := mload(0) // load delegatecall output switch iszero(succeeded) case 1 { // throw if delegatecall failed @@ -157,14 +151,9 @@ contract DSProxy is DSAuth, DSNote { } //set new cache - function setCache(address _cacheAddr) - internal - auth - note - returns (bool) - { - require(_cacheAddr != address(0x0)); // invalid cache address - cache = DSProxyCache(_cacheAddr); // overwrite cache + function setCache(address _cacheAddr) internal auth note returns (bool) { + require(_cacheAddr != address(0x0)); // invalid cache address + cache = DSProxyCache(_cacheAddr); // overwrite cache return true; } } @@ -174,7 +163,7 @@ contract DSProxy is DSAuth, DSNote { // Deployed proxy addresses are logged contract DSProxyFactory { event Created(address indexed sender, address indexed owner, address proxy, address cache); - mapping(address=>bool) public isProxy; + mapping(address => bool) public isProxy; DSProxyCache public cache = new DSProxyCache(); // deploys a new proxy instance diff --git a/contracts/TestContracts/DefaultPoolTester.sol b/contracts/TestContracts/DefaultPoolTester.sol index be72bca..7ea0f31 100644 --- a/contracts/TestContracts/DefaultPoolTester.sol +++ b/contracts/TestContracts/DefaultPoolTester.sol @@ -5,9 +5,8 @@ pragma solidity 0.6.11; import "../DefaultPool.sol"; contract DefaultPoolTester is DefaultPool { - function unprotectedIncreaseZUSDDebt(uint _amount) external { - ZUSDDebt = ZUSDDebt.add(_amount); + ZUSDDebt = ZUSDDebt.add(_amount); } function unprotectedPayable() external payable { diff --git a/contracts/TestContracts/Destructible.sol b/contracts/TestContracts/Destructible.sol index 2950986..3a50274 100644 --- a/contracts/TestContracts/Destructible.sol +++ b/contracts/TestContracts/Destructible.sol @@ -3,9 +3,8 @@ pragma solidity 0.6.11; contract Destructible { - receive() external payable {} - + function destruct(address payable _receiver) external { selfdestruct(_receiver); } diff --git a/contracts/TestContracts/EchidnaColFeeTester.sol b/contracts/TestContracts/EchidnaPerimeterTester.sol similarity index 83% rename from contracts/TestContracts/EchidnaColFeeTester.sol rename to contracts/TestContracts/EchidnaPerimeterTester.sol index bd4ce01..63b4035 100644 --- a/contracts/TestContracts/EchidnaColFeeTester.sol +++ b/contracts/TestContracts/EchidnaPerimeterTester.sol @@ -6,12 +6,12 @@ pragma experimental ABIEncoderV2; import "./EchidnaTester.sol"; import "./ExitFeeControllerMock.sol"; -/// @title EchidnaColFeeTester -/// @notice Re-runs the full Zero Echidna campaign with the ColFee exit fee +/// @title EchidnaPerimeterTester +/// @notice Re-runs the full Zero Echidna campaign with the Perimeter exit fee /// ACTIVE, so every collateral exit driven by the actor proxies routes /// through the fee hook (`_sendCollWithExitFee`) with a real fee leg. /// -/// The ColFee load-bearing invariant is the inherited +/// The Perimeter load-bearing invariant is the inherited /// `echidna_ETH_balances`: /// - `borrowerOperations` holds 0 ETH — the fee leg never strands ETH /// in BorrowerOperations; @@ -20,33 +20,33 @@ import "./ExitFeeControllerMock.sol"; /// in sync, so no ETH is created, destroyed, or double-counted by /// the fee. /// A fee-leg accounting bug breaks it. The two added invariants below -/// guard that the run is genuinely exercising ColFee (not vacuous) and +/// guard that the run is genuinely exercising Perimeter (not vacuous) and /// that ETH reaches the fee receiver only through the accounted leg. /// /// Note: the inherited `echidna_canary_*` properties are Liquity's /// coverage markers and are EXPECTED to be falsified once the actors -/// open troves / fund the pool — that is their purpose, not a ColFee +/// open troves / fund the pool — that is their purpose, not a Perimeter /// failure. The meaningful result is that `echidna_ETH_balances`, /// `echidna_trove_properties`, `echidna_troves_order`, -/// `echidna_ZUSD_global_balances`, and the two `echidna_colfee_*` +/// `echidna_ZUSD_global_balances`, and the two `echidna_perimeter_*` /// invariants below HOLD with the fee active. /// /// Run (from repo root, project/hardhat mode so the CryptoEnv-wrapped /// compile resolves — the single-file form needs a bare `solc` on PATH): /// __decryptionAlreadyDone__=TRUE echidna . \ -/// --contract EchidnaColFeeTester \ +/// --contract EchidnaPerimeterTester \ /// --config fuzzTests/js/echidna_config.yaml -contract EchidnaColFeeTester is EchidnaTester { +contract EchidnaPerimeterTester is EchidnaTester { // Canonical deterministic Permit2 deployment address. Permit2 is not on any - // ColFee path, so a fixed (codeless-in-VM) address is inert here; pinning it + // Perimeter path, so a fixed (codeless-in-VM) address is inert here; pinning it // lets Echidna deploy this tester with NO constructor arguments. address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; ExitFeeControllerMock public exitFeeCtrl; - ColFeeEchidnaSink public feeSink; + PerimeterEchidnaSink public feeSink; constructor() public payable EchidnaTester(PERMIT2) { - feeSink = new ColFeeEchidnaSink(); + feeSink = new PerimeterEchidnaSink(); exitFeeCtrl = new ExitFeeControllerMock(); // Active policy: 1% (100 bps) of the borrower's gross collateral, paid // to a sink that accepts ETH (so the fee leg settles, exercising the @@ -58,7 +58,7 @@ contract EchidnaColFeeTester is EchidnaTester { /// Guards against a vacuous run: the controller stays pinned and active, so /// the inherited invariants are genuinely exercised WITH the fee in the loop. - function echidna_colfee_controller_pinned() public view returns (bool) { + function echidna_perimeter_controller_pinned() public view returns (bool) { return borrowerOperations.exitFeeController() == address(exitFeeCtrl); } @@ -66,11 +66,11 @@ contract EchidnaColFeeTester is EchidnaTester { /// real balance equals the total it recorded receiving. (Combined with the /// inherited pool `balance == getETH()` invariant, this closes the loop on /// fee-leg value conservation.) - function echidna_colfee_sink_synced() public view returns (bool) { + function echidna_perimeter_sink_synced() public view returns (bool) { return address(feeSink).balance == feeSink.totalReceived(); } - function exerciseColFeeExt() external { + function exercisePerimeterExt() external { EchidnaProxy echidnaProxy = echidnaProxies[0]; if (troveManager.getTroveDebt(address(echidnaProxy)) == 0) { openTroveExt(0, 1e23, 1e21); @@ -83,8 +83,8 @@ contract EchidnaColFeeTester is EchidnaTester { } /// Canary: EXPECTED to be falsified once any exit charges a fee. If this - /// stays passing, the campaign never exercised ColFee. - function echidna_canary_colfee_charged() public view returns (bool) { + /// stays passing, the campaign never exercised Perimeter. + function echidna_canary_perimeter_charged() public view returns (bool) { return feeSink.totalReceived() == 0; } @@ -117,14 +117,14 @@ contract EchidnaColFeeTester is EchidnaTester { /// CollSurplusPool conservation under the two-leg split: raw balance always /// equals the recorded ETH accounting (mirrors the inherited per-pool checks - /// in echidna_ETH_balances, which predates ColFee and does not cover this pool). - function echidna_colfee_surplus_pool_synced() public view returns (bool) { + /// in echidna_ETH_balances, which predates Perimeter and does not cover this pool). + function echidna_perimeter_surplus_pool_synced() public view returns (bool) { return address(collSurplusPool).balance == collSurplusPool.getETH(); } } /// Minimal payable fee receiver that records what it is paid. -contract ColFeeEchidnaSink { +contract PerimeterEchidnaSink { uint256 public totalReceived; receive() external payable { diff --git a/contracts/TestContracts/ExitFeeControllerMock.sol b/contracts/TestContracts/ExitFeeControllerMock.sol index 7a481fc..bbef20c 100644 --- a/contracts/TestContracts/ExitFeeControllerMock.sol +++ b/contracts/TestContracts/ExitFeeControllerMock.sol @@ -2,10 +2,10 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; -import "../Interfaces/colfee/IExitFeeController.sol"; +import "../Interfaces/perimeter/IExitFeeController.sol"; /// @title ExitFeeControllerMock -/// @notice Test double for the ColFee controller. NOT production code — lives in +/// @notice Test double for the Perimeter controller. NOT production code — lives in /// TestContracts/ only. The production controller is Solidity 0.8.20 and /// cannot be compiled into the 0.6.11 zero-contracts workspace, so the /// hooks are exercised against this configurable stand-in. diff --git a/contracts/TestContracts/FunctionCaller.sol b/contracts/TestContracts/FunctionCaller.sol index 91d8188..988ae3b 100644 --- a/contracts/TestContracts/FunctionCaller.sol +++ b/contracts/TestContracts/FunctionCaller.sol @@ -38,10 +38,10 @@ contract FunctionCaller { // --- Non-view wrapper functions used for calculating gas --- - function troveManager_getCurrentICR(address _address, uint256 _price) - external - returns (uint256) - { + function troveManager_getCurrentICR( + address _address, + uint256 _price + ) external returns (uint256) { return troveManager.getCurrentICR(_address, _price); } diff --git a/contracts/TestContracts/GasSinkFeeReceiver.sol b/contracts/TestContracts/GasSinkFeeReceiver.sol index 6066a78..9f682d4 100644 --- a/contracts/TestContracts/GasSinkFeeReceiver.sol +++ b/contracts/TestContracts/GasSinkFeeReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.6.11; -/// @notice ColFee test double: a fee receiver that burns essentially all the +/// @notice Perimeter test double: a fee receiver that burns essentially all the /// gas forwarded to it. consumeAll=false → burns down to a small floor /// then RETURNS SUCCESS (the starvation shape: without the pool's /// FEE_LEG_GAS_CAP this would leave the claimant leg out of gas); diff --git a/contracts/TestContracts/LegacyCollSurplusPoolMock.sol b/contracts/TestContracts/LegacyCollSurplusPoolMock.sol index ce64ff2..5c5d535 100644 --- a/contracts/TestContracts/LegacyCollSurplusPoolMock.sol +++ b/contracts/TestContracts/LegacyCollSurplusPoolMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.6.11; -/// @notice ColFee test double simulating the LIVE (pre-upgrade) CollSurplusPool +/// @notice Perimeter test double simulating the LIVE (pre-upgrade) CollSurplusPool /// implementation: `claimColl` exists but `claimCollWithFee` does NOT, /// and there is no fallback — so the hook's pool call reverts on the /// missing selector, reproducing the surface-activated-before-pool- diff --git a/contracts/TestContracts/LiquityMathTester.sol b/contracts/TestContracts/LiquityMathTester.sol index 56983dd..c401ba7 100644 --- a/contracts/TestContracts/LiquityMathTester.sol +++ b/contracts/TestContracts/LiquityMathTester.sol @@ -7,7 +7,6 @@ import "../Dependencies/LiquityMath.sol"; /* Tester contract for math functions in Math.sol library. */ contract LiquityMathTester { - function callMax(uint _a, uint _b) external pure returns (uint) { return LiquityMath._max(_a, _b); } diff --git a/contracts/TestContracts/MockBalanceRedirectPresale.sol b/contracts/TestContracts/MockBalanceRedirectPresale.sol index 4a9e597..1aca0ae 100644 --- a/contracts/TestContracts/MockBalanceRedirectPresale.sol +++ b/contracts/TestContracts/MockBalanceRedirectPresale.sol @@ -2,9 +2,7 @@ pragma solidity 0.6.11; - -contract MockBalanceRedirectPresale { - +contract MockBalanceRedirectPresale { bool public isClosed; function closePresale() public { @@ -14,4 +12,4 @@ contract MockBalanceRedirectPresale { function openPresale() public { isClosed = false; } -} \ No newline at end of file +} diff --git a/contracts/TestContracts/MockExitDelayQueue.sol b/contracts/TestContracts/MockExitDelayQueue.sol index 3e873b9..d51b12f 100644 --- a/contracts/TestContracts/MockExitDelayQueue.sol +++ b/contracts/TestContracts/MockExitDelayQueue.sol @@ -2,7 +2,7 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; -import "../Interfaces/colfee/IExitDelayQueueHook.sol"; +import "../Interfaces/perimeter/IExitDelayQueueHook.sol"; /// @title MockExitDelayQueue /// @notice Minimal test double for the real 0.8.20 `ExitDelayQueue`, implemented @@ -24,7 +24,7 @@ import "../Interfaces/colfee/IExitDelayQueueHook.sol"; /// The three ERC20 / value-carrying ingress fns are present for interface /// completeness but revert (the Zero surface is native-only). This is /// deliberately NOT the full security model — the real queue's recovery -/// legs and per-request index are covered by the colfee Foundry suite. +/// legs and per-request index are covered by the perimeter Foundry suite. contract MockExitDelayQueue is IExitDelayQueueHook { struct Req { uint128 amount; diff --git a/contracts/TestContracts/MockFeeSharingCollector.sol b/contracts/TestContracts/MockFeeSharingCollector.sol index f026a72..748b0f9 100644 --- a/contracts/TestContracts/MockFeeSharingCollector.sol +++ b/contracts/TestContracts/MockFeeSharingCollector.sol @@ -5,15 +5,15 @@ pragma solidity 0.6.11; import "../ZERO/ZEROToken.sol"; interface MockIFeeSharingCollector { - function transferTokens(address _token, uint96 _amount) external; + function transferTokens(address _token, uint96 _amount) external; } /// @dev Simple contract that will receive ZERO tokens issued to the SOV stakers. contract MockFeeSharingCollector is MockIFeeSharingCollector { - function transferTokens(address _token, uint96 _amount) override external { - /// Just a fake function to receive the tokens - ZEROToken(_token).transferFrom(msg.sender, address(this), _amount); - } + function transferTokens(address _token, uint96 _amount) external override { + /// Just a fake function to receive the tokens + ZEROToken(_token).transferFrom(msg.sender, address(this), _amount); + } - function transferRBTC() external payable {} + function transferRBTC() external payable {} } diff --git a/contracts/TestContracts/NonPayable.sol b/contracts/TestContracts/NonPayable.sol index cc22d77..c749325 100644 --- a/contracts/TestContracts/NonPayable.sol +++ b/contracts/TestContracts/NonPayable.sol @@ -4,7 +4,6 @@ pragma solidity 0.6.11; //import "../Dependencies/console.sol"; - contract NonPayable { bool isPayable; diff --git a/contracts/TestContracts/PriceFeedSovrynTester.sol b/contracts/TestContracts/PriceFeedSovrynTester.sol index 88d1092..4fce727 100644 --- a/contracts/TestContracts/PriceFeedSovrynTester.sol +++ b/contracts/TestContracts/PriceFeedSovrynTester.sol @@ -6,9 +6,9 @@ import "../Interfaces/IPriceFeedSovryn.sol"; import "../Dependencies/SafeMath.sol"; /* -* PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state -* variable. The contract does not connect to a live Chainlink price feed. -*/ + * PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state + * variable. The contract does not connect to a live Chainlink price feed. + */ contract PriceFeedSovrynTester { using SafeMath for uint256; @@ -20,7 +20,10 @@ contract PriceFeedSovrynTester { prices[sourceToken][destToken] = price; } - function queryRate(address sourceToken, address destToken) public view returns(uint256 rate, uint256 precision) { + function queryRate( + address sourceToken, + address destToken + ) public view returns (uint256 rate, uint256 precision) { return (prices[sourceToken][destToken], 1e18); } diff --git a/contracts/TestContracts/PriceFeedTestnet.sol b/contracts/TestContracts/PriceFeedTestnet.sol index ffdb76d..d5a71c1 100644 --- a/contracts/TestContracts/PriceFeedTestnet.sol +++ b/contracts/TestContracts/PriceFeedTestnet.sol @@ -5,11 +5,10 @@ pragma solidity 0.6.11; import "../Interfaces/IPriceFeed.sol"; /* -* PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state -* variable. The contract does not connect to a live Chainlink price feed. -*/ + * PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state + * variable. The contract does not connect to a live Chainlink price feed. + */ contract PriceFeedTestnet is IPriceFeed { - uint256 private _price = 200 * 1e18; // --- Functions --- diff --git a/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol b/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol index e540dd5..194398a 100644 --- a/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol +++ b/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol @@ -2,7 +2,7 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; -import "../Interfaces/colfee/IExitDelayQueueHook.sol"; +import "../Interfaces/perimeter/IExitDelayQueueHook.sol"; /// @title SelectorRevertingExitDelayQueue /// @notice Test double for the SR1 selector-propagation regression. The diff --git a/contracts/TestContracts/SortedTrovesTester.sol b/contracts/TestContracts/SortedTrovesTester.sol index 4d75041..142a8cd 100644 --- a/contracts/TestContracts/SortedTrovesTester.sol +++ b/contracts/TestContracts/SortedTrovesTester.sol @@ -4,7 +4,6 @@ pragma solidity 0.6.11; import "../Interfaces/ISortedTroves.sol"; - contract SortedTrovesTester { ISortedTroves sortedTroves; diff --git a/contracts/TestContracts/StabilityPoolTester.sol b/contracts/TestContracts/StabilityPoolTester.sol index 98bd49a..3bc0781 100644 --- a/contracts/TestContracts/StabilityPoolTester.sol +++ b/contracts/TestContracts/StabilityPoolTester.sol @@ -6,10 +6,9 @@ pragma experimental ABIEncoderV2; import "../StabilityPool.sol"; contract StabilityPoolTester is StabilityPool { - /** Constructor */ constructor(address _permit2) public StabilityPool(_permit2) {} - + function unprotectedPayable() external payable { ETH = ETH.add(msg.value); } diff --git a/contracts/TestContracts/UpgradableProxyTester.sol b/contracts/TestContracts/UpgradableProxyTester.sol index 9c1c287..146142a 100644 --- a/contracts/TestContracts/UpgradableProxyTester.sol +++ b/contracts/TestContracts/UpgradableProxyTester.sol @@ -1,14 +1,13 @@ - // SPDX-License-Identifier: MIT pragma solidity 0.6.11; import "../Proxy/UpgradableProxy.sol"; + contract Storage { uint someVar; } contract ProxiableContract is Storage { - function getSomeVar() public view returns (uint) { return someVar; } @@ -23,7 +22,6 @@ contract Storage2 { } contract ProxiableContract2 is ProxiableContract, Storage2 { - function getAnotherVar() public view returns (uint) { return anotherVar; } diff --git a/contracts/TestContracts/WRBTCTokenTester.sol b/contracts/TestContracts/WRBTCTokenTester.sol index a878dc1..3e8d684 100644 --- a/contracts/TestContracts/WRBTCTokenTester.sol +++ b/contracts/TestContracts/WRBTCTokenTester.sol @@ -16,88 +16,84 @@ pragma solidity 0.6.11; contract WRBTCTokenTester { - string public name = "Wrapped BTC"; - string public symbol = "WRBTC"; - uint8 public decimals = 18; - - event Approval(address indexed src, address indexed guy, uint256 wad); - event Transfer(address indexed src, address indexed dst, uint256 wad); - event Deposit(address indexed dst, uint256 wad); - event Withdrawal(address indexed src, uint256 wad); - - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; - - fallback() external payable { - deposit(); - } - - function deposit() public payable { - balanceOf[msg.sender] += msg.value; - emit Deposit(msg.sender, msg.value); - } - - function withdraw(uint256 wad) public { - require(balanceOf[msg.sender] >= wad); - balanceOf[msg.sender] -= wad; - msg.sender.transfer(wad); - emit Withdrawal(msg.sender, wad); - } - - function totalSupply() public view returns (uint256) { - return address(this).balance; - } - - function approve(address guy, uint256 wad) public returns (bool) { - allowance[msg.sender][guy] = wad; - emit Approval(msg.sender, guy, wad); - return true; - } - - function transfer(address dst, uint256 wad) public returns (bool) { - return transferFrom(msg.sender, dst, wad); - } - - function transferFrom( - address src, - address dst, - uint256 wad - ) public returns (bool) { - require(balanceOf[src] >= wad); - - if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) { - require(allowance[src][msg.sender] >= wad); - allowance[src][msg.sender] -= wad; - } - - balanceOf[src] -= wad; - balanceOf[dst] += wad; - - emit Transfer(src, dst, wad); - - return true; - } - - /** - * added for local swap implementation - * */ - function mint(address _to, uint256 _value) public { - require(_to != address(0), "no burn allowed"); - balanceOf[_to] = balanceOf[_to] + _value; - emit Transfer(address(0), _to, _value); - } - - /** - * added for local swap implementation - * */ - function burn(address _who, uint256 _value) public { - require(_value <= balanceOf[_who], "balance too low"); - // no need to require _value <= totalSupply, since that would imply the - // sender's balance is greater than the totalSupply, which *should* be an assertion failure - - balanceOf[_who] = balanceOf[_who] - _value; - emit Transfer(_who, address(0), _value); - } + string public name = "Wrapped BTC"; + string public symbol = "WRBTC"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + fallback() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + msg.sender.transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } + + /** + * added for local swap implementation + * */ + function mint(address _to, uint256 _value) public { + require(_to != address(0), "no burn allowed"); + balanceOf[_to] = balanceOf[_to] + _value; + emit Transfer(address(0), _to, _value); + } + + /** + * added for local swap implementation + * */ + function burn(address _who, uint256 _value) public { + require(_value <= balanceOf[_who], "balance too low"); + // no need to require _value <= totalSupply, since that would imply the + // sender's balance is greater than the totalSupply, which *should* be an assertion failure + + balanceOf[_who] = balanceOf[_who] - _value; + emit Transfer(_who, address(0), _value); + } } /* diff --git a/contracts/TestContracts/ZEROStakingTester.sol b/contracts/TestContracts/ZEROStakingTester.sol index db71335..10da67a 100644 --- a/contracts/TestContracts/ZEROStakingTester.sol +++ b/contracts/TestContracts/ZEROStakingTester.sol @@ -4,7 +4,6 @@ pragma solidity 0.6.11; import "../ZERO/ZEROStaking.sol"; - contract ZEROStakingTester is ZEROStaking { function requireCallerIsFeeDistributor() external view { _requireCallerIsFeeDistributor(); diff --git a/contracts/TestContracts/ZEROTokenTester.sol b/contracts/TestContracts/ZEROTokenTester.sol index 009c12a..2511178 100644 --- a/contracts/TestContracts/ZEROTokenTester.sol +++ b/contracts/TestContracts/ZEROTokenTester.sol @@ -5,20 +5,13 @@ pragma solidity 0.6.11; import "../ZERO/ZEROToken.sol"; contract ZEROTokenTester is ZEROToken { - constructor - ( + constructor( address _zeroStakingAddress, address _marketMakerAddress, address _presaleAddress - ) - public - { - initialize( - _zeroStakingAddress, - _marketMakerAddress, - _presaleAddress - ); - } + ) public { + initialize(_zeroStakingAddress, _marketMakerAddress, _presaleAddress); + } function unprotectedMint(address account, uint256 amount) external { // No check for the caller here @@ -28,15 +21,23 @@ contract ZEROTokenTester is ZEROToken { function unprotectedSendToZEROStaking(address _sender, uint256 _amount) external { // No check for the caller here - + _transfer(_sender, zeroStakingAddress, _amount); } - function callInternalApprove(address owner, address spender, uint256 amount) external returns (bool) { + function callInternalApprove( + address owner, + address spender, + uint256 amount + ) external returns (bool) { _approve(owner, spender, amount); } - function callInternalTransfer(address sender, address recipient, uint256 amount) external returns (bool) { + function callInternalTransfer( + address sender, + address recipient, + uint256 amount + ) external returns (bool) { _transfer(sender, recipient, amount); } @@ -46,4 +47,4 @@ contract ZEROTokenTester is ZEROToken { chainID := chainid() } } -} \ No newline at end of file +} diff --git a/contracts/TestContracts/ZUSDTokenCaller.sol b/contracts/TestContracts/ZUSDTokenCaller.sol index a6340e1..8993d10 100644 --- a/contracts/TestContracts/ZUSDTokenCaller.sol +++ b/contracts/TestContracts/ZUSDTokenCaller.sol @@ -19,11 +19,15 @@ contract ZUSDTokenCaller { ZUSD.burn(_account, _amount); } - function zusdSendToPool(address _sender, address _poolAddress, uint256 _amount) external { + function zusdSendToPool(address _sender, address _poolAddress, uint256 _amount) external { ZUSD.sendToPool(_sender, _poolAddress, _amount); } - function zusdReturnFromPool(address _poolAddress, address _receiver, uint256 _amount ) external { + function zusdReturnFromPool( + address _poolAddress, + address _receiver, + uint256 _amount + ) external { ZUSD.returnFromPool(_poolAddress, _receiver, _amount); } } diff --git a/contracts/TestContracts/ZUSDTokenTester.sol b/contracts/TestContracts/ZUSDTokenTester.sol index d9a7acb..537f211 100644 --- a/contracts/TestContracts/ZUSDTokenTester.sol +++ b/contracts/TestContracts/ZUSDTokenTester.sol @@ -5,15 +5,14 @@ pragma solidity 0.6.11; import "../ZUSDToken.sol"; contract ZUSDTokenTester is ZUSDToken { - - constructor( + constructor( address _troveManagerAddress, address _stabilityPoolAddress, address _borrowerOperationsAddress - ) public { + ) public { initialize(_troveManagerAddress, _stabilityPoolAddress, _borrowerOperationsAddress); } - + function unprotectedMint(address _account, uint256 _amount) external { // No check on caller here @@ -22,23 +21,35 @@ contract ZUSDTokenTester is ZUSDToken { function unprotectedBurn(address _account, uint _amount) external { // No check on caller here - + _burn(_account, _amount); } - function unprotectedSendToPool(address _sender, address _poolAddress, uint256 _amount) external { + function unprotectedSendToPool( + address _sender, + address _poolAddress, + uint256 _amount + ) external { // No check on caller here _transfer(_sender, _poolAddress, _amount); } - function unprotectedReturnFromPool(address _poolAddress, address _receiver, uint256 _amount ) external { + function unprotectedReturnFromPool( + address _poolAddress, + address _receiver, + uint256 _amount + ) external { // No check on caller here _transfer(_poolAddress, _receiver, _amount); } - function callInternalApprove(address owner, address spender, uint256 amount) external returns (bool) { + function callInternalApprove( + address owner, + address spender, + uint256 amount + ) external returns (bool) { _approve(owner, spender, amount); } @@ -49,16 +60,31 @@ contract ZUSDTokenTester is ZUSDToken { } } - function getDigest(address owner, address spender, uint amount, uint nonce, uint deadline) external view returns (bytes32) { - return keccak256(abi.encodePacked( - uint16(0x1901), - domainSeparator(), - keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, nonce, deadline)) - ) - ); + function getDigest( + address owner, + address spender, + uint amount, + uint nonce, + uint deadline + ) external view returns (bytes32) { + return + keccak256( + abi.encodePacked( + uint16(0x1901), + domainSeparator(), + keccak256( + abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, nonce, deadline) + ) + ) + ); } - function recoverAddress(bytes32 digest, uint8 v, bytes32 r, bytes32 s) external pure returns (address) { + function recoverAddress( + bytes32 digest, + uint8 v, + bytes32 r, + bytes32 s + ) external pure returns (address) { return ecrecover(digest, v, r, s); } } diff --git a/contracts/TroveManager.sol b/contracts/TroveManager.sol index cfcc2f0..63bc49c 100644 --- a/contracts/TroveManager.sol +++ b/contracts/TroveManager.sol @@ -38,7 +38,10 @@ contract TroveManager is TroveManagerBase, CheckContract, ITroveManager { event ZEROStakingAddressChanged(address _zeroStakingAddress); ///@param _bootstrapPeriod During bootsrap period redemptions are not allowed - constructor(uint256 _bootstrapPeriod, address _permit2) public TroveManagerBase(_bootstrapPeriod) { + constructor( + uint256 _bootstrapPeriod, + address _permit2 + ) public TroveManagerBase(_bootstrapPeriod) { permit2 = IPermit2(_permit2); } diff --git a/contracts/ZERO/CommunityIssuance.sol b/contracts/ZERO/CommunityIssuance.sol index 75e1328..fd0b313 100644 --- a/contracts/ZERO/CommunityIssuance.sol +++ b/contracts/ZERO/CommunityIssuance.sol @@ -148,7 +148,9 @@ contract CommunityIssuance is */ function _issueSOV(uint256 _totalZUSDDeposits) internal returns (uint256) { uint256 timePassedSinceLastIssuance = (block.timestamp.sub(lastIssuanceTime)); - uint256 issuance = _getZUSDToSOV(_totalZUSDDeposits.mul(APR).mul(timePassedSinceLastIssuance).div(365 days).div(MAX_BPS)); + uint256 issuance = _getZUSDToSOV( + _totalZUSDDeposits.mul(APR).mul(timePassedSinceLastIssuance).div(365 days).div(MAX_BPS) + ); totalSOVIssued = totalSOVIssued + issuance; lastIssuanceTime = block.timestamp; diff --git a/contracts/ZERO/CommunityIssuanceStorage.sol b/contracts/ZERO/CommunityIssuanceStorage.sol index 7d2c3a5..87e1fcd 100644 --- a/contracts/ZERO/CommunityIssuanceStorage.sol +++ b/contracts/ZERO/CommunityIssuanceStorage.sol @@ -10,7 +10,7 @@ import "../Dependencies/Initializable.sol"; contract CommunityIssuanceStorage is Ownable, Initializable { // --- Data --- - string constant public NAME = "CommunityIssuance"; + string public constant NAME = "CommunityIssuance"; uint256 constant MAX_BPS = 10000; diff --git a/contracts/ZERO/ZEROStaking.sol b/contracts/ZERO/ZEROStaking.sol index 9a8b4dc..885ff5c 100644 --- a/contracts/ZERO/ZEROStaking.sol +++ b/contracts/ZERO/ZEROStaking.sol @@ -187,7 +187,7 @@ contract ZEROStaking is ZEROStakingStorage, IZEROStaking, CheckContract, BaseMat function _sendETHGainToUser(uint256 ETHGain) internal { emit EtherSent(msg.sender, ETHGain); - (bool success, ) = msg.sender.call{value: ETHGain}(""); + (bool success, ) = msg.sender.call{ value: ETHGain }(""); require(success, "ZEROStaking: Failed to send accumulated ETHGain"); } diff --git a/contracts/ZERO/ZEROStakingStorage.sol b/contracts/ZERO/ZEROStakingStorage.sol index 9a79294..c5491d4 100644 --- a/contracts/ZERO/ZEROStakingStorage.sol +++ b/contracts/ZERO/ZEROStakingStorage.sol @@ -8,26 +8,25 @@ import "../Interfaces/IZUSDToken.sol"; contract ZEROStakingStorage is Ownable { // --- Data --- - string constant public NAME = "ZEROStaking"; + string public constant NAME = "ZEROStaking"; - mapping( address => uint) public stakes; + mapping(address => uint) public stakes; uint public totalZEROStaked; - uint public F_ETH; // Running sum of ETH fees per-ZERO-staked + uint public F_ETH; // Running sum of ETH fees per-ZERO-staked uint public F_ZUSD; // Running sum of ZERO fees per-ZERO-staked // User snapshots of F_ETH and F_ZUSD, taken at the point at which their latest deposit was made - mapping (address => Snapshot) public snapshots; + mapping(address => Snapshot) public snapshots; struct Snapshot { uint F_ETH_Snapshot; uint F_ZUSD_Snapshot; } - + IZEROToken public zeroToken; IZUSDToken public zusdToken; address public feeDistributorAddress; address public activePoolAddress; - } diff --git a/contracts/ZERO/ZEROToken.sol b/contracts/ZERO/ZEROToken.sol index 02c46cc..6f8ec58 100644 --- a/contracts/ZERO/ZEROToken.sol +++ b/contracts/ZERO/ZEROToken.sol @@ -119,20 +119,18 @@ contract ZEROToken is ZEROTokenStorage, CheckContract, IZEROToken { return true; } - function increaseAllowance(address spender, uint256 addedValue) - external - override - returns (bool) - { + function increaseAllowance( + address spender, + uint256 addedValue + ) external override returns (bool) { _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); return true; } - function decreaseAllowance(address spender, uint256 subtractedValue) - external - override - returns (bool) - { + function decreaseAllowance( + address spender, + uint256 subtractedValue + ) external override returns (bool) { _approve( msg.sender, spender, @@ -174,7 +172,14 @@ contract ZEROToken is ZEROTokenStorage, CheckContract, IZEROToken { "\x19\x01", domainSeparator(), keccak256( - abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner]++, deadline) + abi.encode( + _PERMIT_TYPEHASH, + owner, + spender, + amount, + _nonces[owner]++, + deadline + ) ) ) ); @@ -204,17 +209,16 @@ contract ZEROToken is ZEROTokenStorage, CheckContract, IZEROToken { return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this))); } - function _transfer( - address sender, - address recipient, - uint256 amount - ) internal { + function _transfer(address sender, address recipient, uint256 amount) internal { return; // disable the func call - ZEROToken is not used in beta require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); require(presale.isClosed(), "Presale is not over yet"); - _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[sender] = _balances[sender].sub( + amount, + "ERC20: transfer amount exceeds balance" + ); _balances[recipient] = _balances[recipient].add(amount); emit Transfer(sender, recipient, amount); @@ -239,11 +243,7 @@ contract ZEROToken is ZEROTokenStorage, CheckContract, IZEROToken { emit Transfer(account, address(0), amount); } - function _approve( - address owner, - address spender, - uint256 amount - ) internal { + function _approve(address owner, address spender, uint256 amount) internal { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); diff --git a/contracts/ZERO/ZEROTokenStorage.sol b/contracts/ZERO/ZEROTokenStorage.sol index 7f707c9..9a15995 100644 --- a/contracts/ZERO/ZEROTokenStorage.sol +++ b/contracts/ZERO/ZEROTokenStorage.sol @@ -8,21 +8,23 @@ import "../Dependencies/Initializable.sol"; contract ZEROTokenStorage is Initializable { // --- ERC20 Data --- - string constant internal _NAME = "ZERO"; - string constant internal _SYMBOL = "ZERO"; - string constant internal _VERSION = "1"; - uint8 constant internal _DECIMALS = 18; + string internal constant _NAME = "ZERO"; + string internal constant _SYMBOL = "ZERO"; + string internal constant _VERSION = "1"; + uint8 internal constant _DECIMALS = 18; - mapping (address => uint256) internal _balances; - mapping (address => mapping (address => uint256)) internal _allowances; + mapping(address => uint256) internal _balances; + mapping(address => mapping(address => uint256)) internal _allowances; uint internal _totalSupply; // --- EIP 2612 Data --- // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - bytes32 internal constant _PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + bytes32 internal constant _PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - bytes32 internal constant _TYPE_HASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + bytes32 internal constant _TYPE_HASH = + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to // invalidate the cached domain separator if the chain id changes. @@ -31,20 +33,19 @@ contract ZEROTokenStorage is Initializable { bytes32 internal _HASHED_NAME; bytes32 internal _HASHED_VERSION; - - mapping (address => uint256) internal _nonces; + + mapping(address => uint256) internal _nonces; // --- ZEROToken specific data --- - uint public constant ONE_YEAR_IN_SECONDS = 31536000; // 60 * 60 * 24 * 365 + uint public constant ONE_YEAR_IN_SECONDS = 31536000; // 60 * 60 * 24 * 365 // uint for use with SafeMath - uint internal constant _1_MILLION = 1e24; // 1e6 * 1e18 = 1e24 + uint internal constant _1_MILLION = 1e24; // 1e6 * 1e18 = 1e24 uint internal deploymentStartTime; address public zeroStakingAddress; address public marketMakerAddress; IBalanceRedirectPresale public presale; - } diff --git a/contracts/ZUSDToken.sol b/contracts/ZUSDToken.sol index b56e1b6..7ebcfd8 100644 --- a/contracts/ZUSDToken.sol +++ b/contracts/ZUSDToken.sol @@ -81,11 +81,7 @@ contract ZUSDToken is ZUSDTokenStorage, CheckContract, IZUSDToken, Ownable { _burn(_account, _amount); } - function sendToPool( - address _sender, - address _poolAddress, - uint256 _amount - ) external override { + function sendToPool(address _sender, address _poolAddress, uint256 _amount) external override { _requireCallerIsStabilityPool(); _transfer(_sender, _poolAddress, _amount); } @@ -139,20 +135,18 @@ contract ZUSDToken is ZUSDTokenStorage, CheckContract, IZUSDToken, Ownable { return true; } - function increaseAllowance(address spender, uint256 addedValue) - external - override - returns (bool) - { + function increaseAllowance( + address spender, + uint256 addedValue + ) external override returns (bool) { _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); return true; } - function decreaseAllowance(address spender, uint256 subtractedValue) - external - override - returns (bool) - { + function decreaseAllowance( + address spender, + uint256 subtractedValue + ) external override returns (bool) { _approve( msg.sender, spender, @@ -229,11 +223,7 @@ contract ZUSDToken is ZUSDTokenStorage, CheckContract, IZUSDToken, Ownable { // --- Internal operations --- // Warning: sanity checks (for sender and recipient) should have been done before calling these internal functions - function _transfer( - address sender, - address recipient, - uint256 amount - ) internal { + function _transfer(address sender, address recipient, uint256 amount) internal { assert(sender != address(0)); assert(recipient != address(0)); @@ -261,11 +251,7 @@ contract ZUSDToken is ZUSDTokenStorage, CheckContract, IZUSDToken, Ownable { emit Transfer(account, address(0), amount); } - function _approve( - address owner, - address spender, - uint256 amount - ) internal { + function _approve(address owner, address spender, uint256 amount) internal { assert(owner != address(0)); assert(spender != address(0)); diff --git a/hardhat.config.ts b/hardhat.config.ts index 50a8739..43bd114 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -161,8 +161,8 @@ const config: HardhatUserConfig = { enabled: true, runs: 100, }, - // Emit per-contract storageLayout so the ColFee storage-layout - // zero-diff regression (tests-colfee/StorageLayout.zerodiff.test.js) + // Emit per-contract storageLayout so the Perimeter storage-layout + // zero-diff regression (tests-perimeter/StorageLayout.zerodiff.test.js) // can assert that neither the surplus-claim fee hook nor the // security-perimeter delay reroute adds state to the // upgradeable BorrowerOperations / CollSurplusPool proxies or diff --git a/package.json b/package.json index 95c49b6..3ad1ff5 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "prepare-artifacts": "node scripts/prepare-artifacts.js", "prepack:prepare-dist": "echo 'commented out: yarn prepare-dist'", "test": "hardhat test", - "test:colfee": "hardhat test tests-colfee/*.test.js", - "test:all": "hardhat test && hardhat test tests-colfee/*.test.js", + "test:perimeter": "hardhat test tests-perimeter/*.test.js", + "test:all": "hardhat test && hardhat test tests-perimeter/*.test.js", "coverage": "hardhat coverage", "coveralls": "cat coverage/lcov.info | coveralls", "hh:fork-testnet": "yarn hardhat node --fork https://testnet.sovryn.app/rpc --no-deploy", diff --git a/tests-colfee/ClaimSurplus.notouch.test.js b/tests-perimeter/ClaimSurplus.notouch.test.js similarity index 96% rename from tests-colfee/ClaimSurplus.notouch.test.js rename to tests-perimeter/ClaimSurplus.notouch.test.js index 13fab50..cf93d6b 100644 --- a/tests-colfee/ClaimSurplus.notouch.test.js +++ b/tests-perimeter/ClaimSurplus.notouch.test.js @@ -1,6 +1,6 @@ -// ColFee security perimeter — surplus-claim DELAY exemption pinning test. +// Perimeter security perimeter — surplus-claim DELAY exemption pinning test. // -// SURFACE_ZERO_CLAIM_SURPLUS is exempt by design from the exit-delay +// PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS is exempt by design from the exit-delay // perimeter: surplus is involuntary in origin (full redemption or // recovery-mode liquidation), is not attacker-creatable without capital, and // rerouting it would widen the custody pool for thin marginal protection. So @@ -35,7 +35,7 @@ const DELAY = 3600; const MIN_DELAY = 100; const GAS_PRICE = toBN(dec(1, 9)); -contract("ColFee delay — surplus claim EXEMPT (no-touch pinning)", async (accounts) => { +contract("Perimeter delay — surplus claim EXEMPT (no-touch pinning)", async (accounts) => { const [owner, alice, whale, dennis] = accounts; const feeReceiver = accounts[995]; const multisig = accounts[999]; diff --git a/tests-colfee/StorageLayout.zerodiff.test.js b/tests-perimeter/StorageLayout.zerodiff.test.js similarity index 93% rename from tests-colfee/StorageLayout.zerodiff.test.js rename to tests-perimeter/StorageLayout.zerodiff.test.js index 3cd07e3..c71b4d9 100644 --- a/tests-colfee/StorageLayout.zerodiff.test.js +++ b/tests-perimeter/StorageLayout.zerodiff.test.js @@ -1,4 +1,4 @@ -// ColFee security perimeter — storage-layout ZERO-DIFF regression. +// Perimeter security perimeter — storage-layout ZERO-DIFF regression. // // Neither the Zero surplus-claim exit-fee hook NOR the borrower exit-DELAY // reroute adds storage to any deployed upgradeable contract: the surface ids @@ -37,7 +37,7 @@ // 2. overlay this repo's hardhat.config.ts (storageLayout output) into // 3. (cd && npx hardhat compile --force) // 4. extract the normalized layout for the three targets and overwrite -// tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json (keep _meta). +// tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json (keep _meta). const assert = require("assert"); const fs = require("fs"); @@ -52,7 +52,7 @@ const TARGETS = [ "contracts/CollSurplusPool.sol:CollSurplusPool", // gains claimCollWithFee — functions only, no state ]; -describe("ColFee — storage-layout zero-diff (surplus-claim fee hook + exit-delay reroute)", () => { +describe("Perimeter — storage-layout zero-diff (surplus-claim fee hook + exit-delay reroute)", () => { let baseline; before(() => { diff --git a/tests-colfee/ZeroBorrowerExit.adjust.test.js b/tests-perimeter/ZeroBorrowerExit.adjust.test.js similarity index 97% rename from tests-colfee/ZeroBorrowerExit.adjust.test.js rename to tests-perimeter/ZeroBorrowerExit.adjust.test.js index 7a65d86..e566e6d 100644 --- a/tests-colfee/ZeroBorrowerExit.adjust.test.js +++ b/tests-perimeter/ZeroBorrowerExit.adjust.test.js @@ -1,5 +1,5 @@ -// ColFee — Zero borrower collateral-exit hook: withdrawColl / adjustTrove legs. -// Surface: SURFACE_ZERO_WITHDRAW_COLL +// Perimeter — Zero borrower collateral-exit hook: withdrawColl / adjustTrove legs. +// Surface: PERIMETER_SURFACE_ZERO_WITHDRAW_COLL // Covers the `_moveTokensAndETHfromAdjustment` hook reached by: // - withdrawColl(amount, ...) // - adjustTrove(_collWithdrawal>0, _isDebtIncrease=false, msg.value=0) @@ -14,7 +14,7 @@ const testHelpers = require("../utils/js/testHelpers.js"); const { assertRevertWithReason, assertSurface, - SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, } = require("./utils/assertions.js"); const timeMachine = require("ganache-time-traveler"); @@ -36,7 +36,7 @@ const CONTROLLER_REVERT = 4; const GAS_PRICE = toBN(dec(1, 9)); // 1 gwei — used to back out gas cost from the borrower's RBTC delta -contract("ColFee — Zero borrower collateral exit (adjust/withdraw)", async (accounts) => { +contract("Perimeter — Zero borrower collateral exit (adjust/withdraw)", async (accounts) => { const [owner, alice, bob] = accounts; const feeReceiver = accounts[995]; const multisig = accounts[999]; @@ -163,7 +163,7 @@ contract("ColFee — Zero borrower collateral exit (adjust/withdraw)", async (ac const ev = getEvent(tx, "ExitFeeApplied"); assert.isDefined(ev, "ExitFeeApplied not emitted"); - assertSurface(ev, SURFACE_ZERO_WITHDRAW_COLL, "withdrawColl ExitFeeApplied"); + assertSurface(ev, PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, "withdrawColl ExitFeeApplied"); assert.equal(ev.args.actor, alice); assert.equal(ev.args.asset, ZERO_ADDRESS); assert.equal(ev.args.subProduct, ZERO_ADDRESS); @@ -351,7 +351,7 @@ contract("ColFee — Zero borrower collateral exit (adjust/withdraw)", async (ac ); const ev = getEvent(tx, "ExitFeeSkipped"); assert.isDefined(ev, "ExitFeeSkipped not emitted"); - assertSurface(ev, SURFACE_ZERO_WITHDRAW_COLL, "withdrawColl ExitFeeSkipped"); + assertSurface(ev, PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, "withdrawColl ExitFeeSkipped"); assert.equal(toBN(ev.args.reason).toNumber(), CONTROLLER_REVERT); assert.isUndefined(getEvent(tx, "ExitFeeApplied")); }); @@ -525,7 +525,7 @@ contract("ColFee — Zero borrower collateral exit (adjust/withdraw)", async (ac assert.equal(toBN(ev.args.reason).toNumber(), INACTIVE); }); - it("debt-only adjustTrove (gross==0) emits no ColFee event and skips the controller", async () => { + it("debt-only adjustTrove (gross==0) emits no Perimeter event and skips the controller", async () => { // Repay / debt-only adjustments move no collateral → the hook must short-circuit // (no wasted quoteExitFee round-trip, no spurious ExitFeeSkipped). await openRoomyTrove(alice); @@ -550,7 +550,7 @@ contract("ColFee — Zero borrower collateral exit (adjust/withdraw)", async (ac (await troveManager.Troves(alice))[0].gt(debtBefore), "debt did not increase — setup invalid" ); - assert.isUndefined(getEvent(tx, "ExitFeeApplied"), "no ColFee event on a debt-only op"); - assert.isUndefined(getEvent(tx, "ExitFeeSkipped"), "no ColFee event on a debt-only op"); + assert.isUndefined(getEvent(tx, "ExitFeeApplied"), "no Perimeter event on a debt-only op"); + assert.isUndefined(getEvent(tx, "ExitFeeSkipped"), "no Perimeter event on a debt-only op"); }); }); diff --git a/tests-colfee/ZeroBorrowerExit.close.test.js b/tests-perimeter/ZeroBorrowerExit.close.test.js similarity index 95% rename from tests-colfee/ZeroBorrowerExit.close.test.js rename to tests-perimeter/ZeroBorrowerExit.close.test.js index 55ff6d2..91a4604 100644 --- a/tests-colfee/ZeroBorrowerExit.close.test.js +++ b/tests-perimeter/ZeroBorrowerExit.close.test.js @@ -1,5 +1,5 @@ -// ColFee — Zero borrower collateral-exit hook: closeTrove leg. -// Surface: SURFACE_ZERO_WITHDRAW_COLL +// Perimeter — Zero borrower collateral-exit hook: closeTrove leg. +// Surface: PERIMETER_SURFACE_ZERO_WITHDRAW_COLL // // closeTrove() returns the trove's entire collateral to the borrower; this // suite proves the exit fee is charged on that payout and that close invariants @@ -8,7 +8,7 @@ const deploymentHelper = require("../utils/js/deploymentHelpers.js"); const testHelpers = require("../utils/js/testHelpers.js"); -const { assertSurface, SURFACE_ZERO_WITHDRAW_COLL } = require("./utils/assertions.js"); +const { assertSurface, PERIMETER_SURFACE_ZERO_WITHDRAW_COLL } = require("./utils/assertions.js"); const timeMachine = require("ganache-time-traveler"); const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); @@ -25,7 +25,7 @@ const NONE = 0; const CONTROLLER_REVERT = 4; const GAS_PRICE = toBN(dec(1, 9)); -contract("ColFee — Zero borrower collateral exit (closeTrove)", async (accounts) => { +contract("Perimeter — Zero borrower collateral exit (closeTrove)", async (accounts) => { const [owner, alice, dennis] = accounts; const feeReceiver = accounts[995]; const multisig = accounts[999]; @@ -127,7 +127,7 @@ contract("ColFee — Zero borrower collateral exit (closeTrove)", async (account const ev = getEvent(tx, "ExitFeeApplied"); assert.isDefined(ev, "ExitFeeApplied not emitted"); // closeTrove settles through the same borrower-exit surface as withdrawColl - assertSurface(ev, SURFACE_ZERO_WITHDRAW_COLL, "closeTrove ExitFeeApplied"); + assertSurface(ev, PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, "closeTrove ExitFeeApplied"); assert.equal(ev.args.actor, alice); assert.equal(ev.args.recipient, alice); assert.equal(ev.args.asset, ZERO_ADDRESS); diff --git a/tests-colfee/ZeroBorrowerExit.delay.notouch.test.js b/tests-perimeter/ZeroBorrowerExit.delay.notouch.test.js similarity index 98% rename from tests-colfee/ZeroBorrowerExit.delay.notouch.test.js rename to tests-perimeter/ZeroBorrowerExit.delay.notouch.test.js index 05bb50e..f4cc8da 100644 --- a/tests-colfee/ZeroBorrowerExit.delay.notouch.test.js +++ b/tests-perimeter/ZeroBorrowerExit.delay.notouch.test.js @@ -1,4 +1,4 @@ -// ColFee security perimeter — Zero DELAY no-touch regression. +// Perimeter security perimeter — Zero DELAY no-touch regression. // // With the perimeter ACTIVE (controller enabled, d>0) and the queue WIRED, // proves the delay reroute fires ONLY on the voluntary collateral-out chokepoint @@ -29,7 +29,7 @@ const MIN_DELAY = 100; const GAS_PRICE = toBN(dec(1, 9)); contract( - "ColFee delay — Zero no-touch (liquidation/redemption/SP-gain exempt)", + "Perimeter delay — Zero no-touch (liquidation/redemption/SP-gain exempt)", async (accounts) => { const [owner, alice, whale, defaulter_1] = accounts; const feeReceiver = accounts[995]; diff --git a/tests-colfee/ZeroBorrowerExit.delay.selector.test.js b/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js similarity index 94% rename from tests-colfee/ZeroBorrowerExit.delay.selector.test.js rename to tests-perimeter/ZeroBorrowerExit.delay.selector.test.js index bc2774b..4322a5e 100644 --- a/tests-colfee/ZeroBorrowerExit.delay.selector.test.js +++ b/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js @@ -1,19 +1,19 @@ -// ColFee security perimeter — SR1 queue custom-error SELECTOR propagation. +// Perimeter security perimeter — SR1 queue custom-error SELECTOR propagation. // // The real ExitDelayQueue is Solidity 0.8.20 and its onlyAllowedSource guard // reverts with the DISTINCT custom error `UnregisteredSource(address)` — the // primary fail-closed halt signal the off-chain watcher keys on. The 0.6.11 // BorrowerOperations delay hook calls // `recordReceivedNativeExit` as a PLAIN external call (NOT wrapped in a -// try/catch or re-`require` with a COLFEE: string), so that selector must +// try/catch or re-`require` with a PERIMETER: string), so that selector must // BUBBLE UP UNCHANGED out of the reverting trove exit — it is neither swallowed // nor re-wrapped by the host. // // This regression drives a real withdrawColl/closeTrove into a queue that // reverts with the exact `UnregisteredSource(msg.sender)` payload and asserts // the returndata's leading 4 bytes equal the queue's selector (and are NOT a -// COLFEE:-prefixed host string). The COMPANION host-side pre-check strings -// (COLFEE:queue-unset / COLFEE:delay-quote-failed) are asserted in +// PERIMETER:-prefixed host string). The COMPANION host-side pre-check strings +// (PERIMETER:queue-unset / PERIMETER:delay-quote-failed) are asserted in // ZeroBorrowerExit.delay.test.js — those are the reverts that CANNOT bubble a // queue selector because they fire before/around the queue call. @@ -66,7 +66,7 @@ const rawRevertData = async (from, to, data) => ); }); -contract("ColFee delay — SR1 queue selector propagation", async (accounts) => { +contract("Perimeter delay — SR1 queue selector propagation", async (accounts) => { const [owner, alice] = accounts; const multisig = accounts[999]; @@ -119,7 +119,7 @@ contract("ColFee delay — SR1 queue selector propagation", async (accounts) => ); }); - it("withdrawColl (d>0): queue UnregisteredSource selector BUBBLES UP unwrapped (not a COLFEE: string)", async () => { + it("withdrawColl (d>0): queue UnregisteredSource selector BUBBLES UP unwrapped (not a PERIMETER: string)", async () => { await openTrove({ ICR: toBN(dec(10, 18)), extraParams: { from: alice, value: toBN(dec(100, "ether")) }, diff --git a/tests-colfee/ZeroBorrowerExit.delay.test.js b/tests-perimeter/ZeroBorrowerExit.delay.test.js similarity index 98% rename from tests-colfee/ZeroBorrowerExit.delay.test.js rename to tests-perimeter/ZeroBorrowerExit.delay.test.js index bbe67cc..4be0f74 100644 --- a/tests-colfee/ZeroBorrowerExit.delay.test.js +++ b/tests-perimeter/ZeroBorrowerExit.delay.test.js @@ -1,5 +1,5 @@ -// ColFee security perimeter — Zero borrower exit DELAY hook -// (surface SURFACE_ZERO_WITHDRAW_COLL). +// Perimeter security perimeter — Zero borrower exit DELAY hook +// (surface PERIMETER_SURFACE_ZERO_WITHDRAW_COLL). // // Proves the delay reroute at the single voluntary collateral-out chokepoint // `_sendCollWithExitFee` (reached by withdrawColl, collateral-decreasing @@ -35,9 +35,9 @@ const ZERO_ADDRESS = th.ZERO_ADDRESS; const GAS_PRICE = toBN(dec(1, 9)); const DELAY = 3600; // 1h const MIN_DELAY = 100; -const SURFACE = web3.utils.keccak256("COLFEE:SURFACE_ZERO_WITHDRAW_COLL"); +const SURFACE = web3.utils.keccak256("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); -contract("ColFee delay — Zero borrower exit reroute", async (accounts) => { +contract("Perimeter delay — Zero borrower exit reroute", async (accounts) => { const [owner, alice, dennis] = accounts; const feeReceiver = accounts[995]; const multisig = accounts[999]; @@ -370,7 +370,7 @@ contract("ColFee delay — Zero borrower exit reroute", async (accounts) => { const collBefore = await getTroveEntireColl(alice); await th.assertRevert( borrowerOperations.withdrawColl(toBN(dec(1, "ether")), alice, alice, { from: alice }), - "COLFEE:delay-quote-failed" + "PERIMETER:delay-quote-failed" ); assert.isTrue( (await getTroveEntireColl(alice)).eq(collBefore), @@ -390,7 +390,7 @@ contract("ColFee delay — Zero borrower exit reroute", async (accounts) => { const collBefore = await getTroveEntireColl(alice); await th.assertRevert( borrowerOperations.withdrawColl(toBN(dec(1, "ether")), alice, alice, { from: alice }), - "COLFEE:queue-unset" + "PERIMETER:queue-unset" ); assert.isTrue((await getTroveEntireColl(alice)).eq(collBefore)); }); diff --git a/tests-colfee/ZeroBorrowerExit.notouch.test.js b/tests-perimeter/ZeroBorrowerExit.notouch.test.js similarity index 95% rename from tests-colfee/ZeroBorrowerExit.notouch.test.js rename to tests-perimeter/ZeroBorrowerExit.notouch.test.js index 1daea43..bbdb791 100644 --- a/tests-colfee/ZeroBorrowerExit.notouch.test.js +++ b/tests-perimeter/ZeroBorrowerExit.notouch.test.js @@ -1,9 +1,9 @@ -// ColFee — Zero borrower-exit hook: no-touch + invariant suite. +// Perimeter — Zero borrower-exit hook: no-touch + invariant suite. // // Proves: // - Fee-receiver failure passthrough: a reverting feeReceiver is caught by the // try/catch fee leg; the borrower still receives the full gross and the exit -// completes (ExitFeeSkipped(VAULT_REVERT)). ColFee infra failure cannot brick +// completes (ExitFeeSkipped(VAULT_REVERT)). Perimeter infra failure cannot brick // a borrower exit. // - No-touch: redemption, liquidation, and stability-pool ETH-gain withdrawals // route their collateral through TroveManager / StabilityPool — NOT through @@ -12,7 +12,7 @@ const deploymentHelper = require("../utils/js/deploymentHelpers.js"); const testHelpers = require("../utils/js/testHelpers.js"); -const { assertSurface, SURFACE_ZERO_WITHDRAW_COLL } = require("./utils/assertions.js"); +const { assertSurface, PERIMETER_SURFACE_ZERO_WITHDRAW_COLL } = require("./utils/assertions.js"); const timeMachine = require("ganache-time-traveler"); const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); @@ -31,7 +31,7 @@ const NONE = 0; const VAULT_REVERT = 5; const GAS_PRICE = toBN(dec(1, 9)); -contract("ColFee — Zero borrower exit: no-touch + invariants", async (accounts) => { +contract("Perimeter — Zero borrower exit: no-touch + invariants", async (accounts) => { const [owner, alice, bob, whale, defaulter_1] = accounts; const feeReceiver = accounts[995]; const multisig = accounts[999]; @@ -76,7 +76,7 @@ contract("ColFee — Zero borrower exit: no-touch + invariants", async (accounts ); const ev = getEvent(tx, "ExitFeeApplied"); assert.isDefined(ev, "positive control: ExitFeeApplied not emitted"); - assertSurface(ev, SURFACE_ZERO_WITHDRAW_COLL, "positive control ExitFeeApplied"); + assertSurface(ev, PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, "positive control ExitFeeApplied"); assert.isTrue(toBN(ev.args.feeAmount).eq(expectedFee)); }; @@ -159,7 +159,7 @@ contract("ColFee — Zero borrower exit: no-touch + invariants", async (accounts const ev = getEvent(tx, "ExitFeeSkipped"); assert.isDefined(ev, "ExitFeeSkipped not emitted"); - assertSurface(ev, SURFACE_ZERO_WITHDRAW_COLL, "VAULT_REVERT ExitFeeSkipped"); + assertSurface(ev, PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, "VAULT_REVERT ExitFeeSkipped"); assert.equal(toBN(ev.args.reason).toNumber(), VAULT_REVERT); assert.equal( toBN(ev.args.rateBps).toNumber(), diff --git a/tests-colfee/ZeroClaimSurplus.test.js b/tests-perimeter/ZeroClaimSurplus.test.js similarity index 97% rename from tests-colfee/ZeroClaimSurplus.test.js rename to tests-perimeter/ZeroClaimSurplus.test.js index 34868e5..876e960 100644 --- a/tests-colfee/ZeroClaimSurplus.test.js +++ b/tests-perimeter/ZeroClaimSurplus.test.js @@ -1,10 +1,10 @@ -// ColFee — Zero surplus-claim exit fee (SURFACE_ZERO_CLAIM_SURPLUS) +// Perimeter — Zero surplus-claim exit fee (PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS) // // Surplus enters CollSurplusPool on full redemption (TroveManagerRedeemOps) or // recovery-mode liquidation with ICR > MCR; the ONLY outlet is // BorrowerOperations.claimCollateral(). This suite proves the pool-side two-leg // split (claimCollWithFee) charges the fee when the policy is active, fails -// open on every ColFee failure, and leaves the non-charging path +// open on every Perimeter failure, and leaves the non-charging path // state-equivalent to the untouched claimColl flow. const deploymentHelper = require("../utils/js/deploymentHelpers.js"); @@ -12,7 +12,7 @@ const testHelpers = require("../utils/js/testHelpers.js"); const { assertRevertWithReason, assertSurface, - SURFACE_ZERO_CLAIM_SURPLUS, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, } = require("./utils/assertions.js"); const timeMachine = require("ganache-time-traveler"); @@ -40,7 +40,7 @@ const CONTROLLER_REVERT = 4; const VAULT_REVERT = 5; const GAS_PRICE = toBN(dec(1, 9)); -contract("ColFee — Zero surplus-claim exit fee", async (accounts) => { +contract("Perimeter — Zero surplus-claim exit fee", async (accounts) => { const [owner, alice, whale] = accounts; const feeReceiver = accounts[995]; const multisig = accounts[999]; @@ -186,10 +186,10 @@ contract("ColFee — Zero surplus-claim exit fee", async (accounts) => { const ev = getEvent(tx, "ExitFeeApplied"); assert.isDefined(ev, "ExitFeeApplied not emitted"); // Pins the surplus claim to its OWN surface: the controller mock ignores - // surfaceId, so a hook quoting SURFACE_ZERO_WITHDRAW_COLL here would charge + // surfaceId, so a hook quoting PERIMETER_SURFACE_ZERO_WITHDRAW_COLL here would charge // the borrower-exit policy on surplus claims and every other assertion in // this file would still pass. - assertSurface(ev, SURFACE_ZERO_CLAIM_SURPLUS, "claimCollateral ExitFeeApplied"); + assertSurface(ev, PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, "claimCollateral ExitFeeApplied"); assert.equal(ev.args.actor, alice); assert.equal(ev.args.recipient, alice); assert.equal(ev.args.asset, ZERO_ADDRESS); @@ -224,7 +224,7 @@ contract("ColFee — Zero surplus-claim exit fee", async (accounts) => { const ev = getEvent(tx, "ExitFeeSkipped"); assert.isDefined(ev, "ExitFeeSkipped not emitted"); - assertSurface(ev, SURFACE_ZERO_CLAIM_SURPLUS, "claimCollateral ExitFeeSkipped"); + assertSurface(ev, PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, "claimCollateral ExitFeeSkipped"); assert.equal(toBN(ev.args.reason).toNumber(), CONTROLLER_REVERT); assert.isTrue(toBN(ev.args.grossAmount).eq(gross)); assert.isUndefined(getEvent(tx, "ExitFeeApplied")); @@ -597,7 +597,7 @@ contract("ColFee — Zero surplus-claim exit fee", async (accounts) => { // try/catch in the hook — a pool-side revert must surface loudly rather than // silently degrade a fee-active claim into an unfee'd one. This test PINS the // deployment ordering: the CollSurplusPool implementation upgrade must land - // before SURFACE_ZERO_CLAIM_SURPLUS is activated (handled atomically in one SIP). + // before PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS is activated (handled atomically in one SIP). const mock = await LegacyCollSurplusPoolMock.new(); await mock.setBO(borrowerOperations.address); await mock.setSurplus(alice, { value: dec(1, "ether") }); diff --git a/tests-colfee/ZeroPreview.test.js b/tests-perimeter/ZeroPreview.test.js similarity index 97% rename from tests-colfee/ZeroPreview.test.js rename to tests-perimeter/ZeroPreview.test.js index 80ddad5..199a723 100644 --- a/tests-colfee/ZeroPreview.test.js +++ b/tests-perimeter/ZeroPreview.test.js @@ -1,6 +1,6 @@ -// ColFee — Zero exit-fee preview helper. +// Perimeter — Zero exit-fee preview helper. // previewZeroCollWithdrawExitFee(borrower, grossColl): read-only policy lookup -// hard-wired to SURFACE_ZERO_WITHDRAW_COLL / subProduct=address(0) / actor=borrower. +// hard-wired to PERIMETER_SURFACE_ZERO_WITHDRAW_COLL / subProduct=address(0) / actor=borrower. // Must agree wei-for-wei with the live _sendCollWithExitFee charge. const deploymentHelper = require("../utils/js/deploymentHelpers.js"); @@ -21,7 +21,7 @@ const NONE = 0; const CONTROLLER_REVERT = 4; const GAS_PRICE = toBN(dec(1, 9)); // 1 gwei — used to back gas out of the borrower's RBTC delta -contract("ColFee — Zero exit-fee preview", async (accounts) => { +contract("Perimeter — Zero exit-fee preview", async (accounts) => { const [owner, alice, bob] = accounts; const feeReceiver = accounts[995]; const multisig = accounts[999]; diff --git a/tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json b/tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json similarity index 90% rename from tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json rename to tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json index 0f37cd5..49d6d89 100644 --- a/tests-colfee/baselines/storage-layout.sovryn-perimeter-fee.json +++ b/tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json @@ -2,10 +2,10 @@ "_meta": { "purpose": "Storage-layout zero-diff baseline for the Zero surplus-claim exit fee AND the security-perimeter delay hooks.", "baseRef": "sovryn-perimeter-fee @ b6584a6", - "note": "Normalized solc storageLayout (AST id suffixes after ')' stripped) for the upgradeable BorrowerOperations and CollSurplusPool proxies plus ActivePool, captured from the UNMODIFIED b6584a6 tree, before the surplus-claim fee hook and before the exit-delay hooks were applied. tests-colfee/StorageLayout.zerodiff.test.js asserts the current tree's layout is identical \u2014 proving neither hook appends state (the controller and queue pointers live in EIP-1967 unstructured slots). Regenerate only on an intentional, reviewed layout change (see the test header).", + "note": "Normalized solc storageLayout (AST id suffixes after ')' stripped) for the upgradeable BorrowerOperations and CollSurplusPool proxies plus ActivePool, captured from the UNMODIFIED b6584a6 tree, before the surplus-claim fee hook and before the exit-delay hooks were applied. tests-perimeter/StorageLayout.zerodiff.test.js asserts the current tree's layout is identical \u2014 proving neither hook appends state (the controller and queue pointers live in EIP-1967 unstructured slots). Regenerate only on an intentional, reviewed layout change (see the test header).", "consumers": [ - "tests-colfee/StorageLayout.zerodiff.test.js", - "tests-colfee/utils/storageLayout.js" + "tests-perimeter/StorageLayout.zerodiff.test.js", + "tests-perimeter/utils/storageLayout.js" ] }, "contracts/BorrowerOperations.sol:BorrowerOperations": [ diff --git a/tests-colfee/utils/assertions.js b/tests-perimeter/utils/assertions.js similarity index 72% rename from tests-colfee/utils/assertions.js rename to tests-perimeter/utils/assertions.js index c012434..d9c7325 100644 --- a/tests-colfee/utils/assertions.js +++ b/tests-perimeter/utils/assertions.js @@ -1,4 +1,4 @@ -// Shared assertion helpers for the ColFee Zero test suites. +// Shared assertion helpers for the Perimeter Zero test suites. // // `TestHelper.assertRevert(txPromise, message)` accepts an expected revert // string but NEVER checks it (the comparison is commented out upstream), so a @@ -29,29 +29,31 @@ async function assertRevertWithReason(txPromise, expected) { throw new Error(`${NOT_REVERTED}: expected revert containing "${expected}", but tx succeeded`); } -/// ColFee surface ids as the hooks compute them on-chain -/// (`keccak256("COLFEE:SURFACE_...")`). Asserting these on the emitted events +/// Perimeter surface ids as the hooks compute them on-chain +/// (`keccak256("PERIMETER_SURFACE_...")`). Asserting these on the emitted events /// pins each hook to its OWN surface: the controller mock ignores `surfaceId`, /// so without this a hook quoting the wrong surface would charge the wrong /// policy in production and every test would still pass. /// Computed lazily — `web3` is a test-runtime global, not available at require time. -const surfaceId = (name) => web3.utils.keccak256(`COLFEE:${name}`); -const SURFACE_ZERO_WITHDRAW_COLL = () => surfaceId("SURFACE_ZERO_WITHDRAW_COLL"); -const SURFACE_ZERO_CLAIM_SURPLUS = () => surfaceId("SURFACE_ZERO_CLAIM_SURPLUS"); +const surfaceId = (name) => web3.utils.keccak256(name); +const PERIMETER_SURFACE_ZERO_WITHDRAW_COLL = () => + surfaceId("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); +const PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS = () => + surfaceId("PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS"); -/// Assert a ColFee event carries the expected surface id. +/// Assert a Perimeter event carries the expected surface id. /// `expected` is one of the SURFACE_* thunks above. function assertSurface(ev, expected, label) { assert.equal( ev.args.surfaceId, expected(), - `${label || "ColFee event"} carries the wrong surfaceId` + `${label || "Perimeter event"} carries the wrong surfaceId` ); } module.exports = { assertRevertWithReason, assertSurface, - SURFACE_ZERO_WITHDRAW_COLL, - SURFACE_ZERO_CLAIM_SURPLUS, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, }; diff --git a/tests-colfee/utils/storageLayout.js b/tests-perimeter/utils/storageLayout.js similarity index 100% rename from tests-colfee/utils/storageLayout.js rename to tests-perimeter/utils/storageLayout.js From 53d3aa3b4d11e17c89ee71ce204be714582cce50 Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Wed, 19 Aug 2026 17:12:22 +0300 Subject: [PATCH 3/7] Prefix the perimeter pointer slots with their own namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pointer slots read "sovryn.exitFeeController", "sovryn.exitDelayQueue" and "sovryn.borrowerExitPerimeterOps" — names that say what is stored but not which system owns it, in a protocol that has several kinds of fee. They are now sovryn.perimeterExitFeeController, sovryn.perimeterExitDelayQueue and sovryn.perimeterBorrowerExitOps, so every slot the perimeter owns carries the same prefix. These strings are shared across the lending and Zero integrations and the perimeter contracts: the slot address is the hash of the string, so the three repos must carry identical spellings or the same pointer would live at different addresses in each. The rename is applied to all three together for that reason. Free to do right now, and checked rather than assumed: every one of these slots reads zero on mainnet today, on both the protocol and the Zero BorrowerOperations proxy. Nothing has been pinned, because pinning needs the activation proposal and that has not executed. Once it has, moving a slot would mean re-pinning it. --- contracts/BorrowerOperations.sol | 4 ++-- tests-perimeter/StorageLayout.zerodiff.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/BorrowerOperations.sol b/contracts/BorrowerOperations.sol index 61074d3..c670b34 100644 --- a/contracts/BorrowerOperations.sol +++ b/contracts/BorrowerOperations.sol @@ -32,7 +32,7 @@ contract BorrowerOperations is // No new regular storage: the controller pointer lives in an EIP-1967-style // unstructured slot so `BorrowerOperations` storage-layout is unchanged. bytes32 private constant EXIT_FEE_CONTROLLER_SLOT = - bytes32(uint256(keccak256("sovryn.exitFeeController")) - 1); + bytes32(uint256(keccak256("sovryn.perimeterExitFeeController")) - 1); bytes32 private constant PERIMETER_SURFACE_ZERO_WITHDRAW_COLL = keccak256("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); bytes32 private constant PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS = @@ -45,7 +45,7 @@ contract BorrowerOperations is // pointer. Because the pointer redirects ESCROW it is more sensitive than the // controller pointer — rotation is an Owner/SIP action. bytes32 private constant EXIT_DELAY_QUEUE_SLOT = - bytes32(uint256(keccak256("sovryn.exitDelayQueue")) - 1); + bytes32(uint256(keccak256("sovryn.perimeterExitDelayQueue")) - 1); event ExitFeeControllerSet(address indexed previous, address indexed current); event ExitDelayQueueSet(address indexed previous, address indexed current); diff --git a/tests-perimeter/StorageLayout.zerodiff.test.js b/tests-perimeter/StorageLayout.zerodiff.test.js index c71b4d9..bed1d56 100644 --- a/tests-perimeter/StorageLayout.zerodiff.test.js +++ b/tests-perimeter/StorageLayout.zerodiff.test.js @@ -3,7 +3,7 @@ // Neither the Zero surplus-claim exit-fee hook NOR the borrower exit-DELAY // reroute adds storage to any deployed upgradeable contract: the surface ids // are constants, the exit-fee controller pointer and the ExitDelayQueue -// pointer (keccak256("sovryn.exitDelayQueue") - 1) live in EIP-1967-style +// pointer (keccak256("sovryn.perimeterExitDelayQueue") - 1) live in EIP-1967-style // unstructured slots, and the hooks declare no new state variables on the // BorrowerOperations or CollSurplusPool proxies. That is true BY CONSTRUCTION // today — but nothing GUARDS a future edit from appending a `uint256` to the From f13d55249f6a27a60170b461cf898a903990871e Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Wed, 19 Aug 2026 19:29:48 +0300 Subject: [PATCH 4/7] Settle the Zero borrower exit through a delegatecall companion BorrowerOperations was 685 bytes over the EIP-170 limit once the exit delay joined the fee hook. Move the settlement body into BorrowerOperationsPerimeterOps and invoke it with delegatecall -- the pattern TroveManager already uses for TroveManagerRedeemOps. The companion shares BorrowerOperationsStorage, so it settles in the proxy's own context, reads the proxy's pointer slots and emits the proxy's events. The hook address is a plain storage variable appended after feeDistributor, mirroring troveManagerRedeemOps, and is rotatable by the owner. BorrowerOperations 25,261 -> 24,125 (451 under the limit); the companion is 3,840. --- contracts/BorrowerOperations.sol | 167 ++-------- contracts/BorrowerOperationsStorage.sol | 5 + .../BorrowerOperationsPerimeterOps.sol | 312 ++++++++++++++++++ .../BorrowerOperationsTester.sol | 5 +- contracts/TestContracts/EchidnaTester.sol | 2 + utils/js/deploymentHelpers.js | 9 + 6 files changed, 362 insertions(+), 138 deletions(-) create mode 100644 contracts/Dependencies/BorrowerOperationsPerimeterOps.sol diff --git a/contracts/BorrowerOperations.sol b/contracts/BorrowerOperations.sol index c670b34..b103d5b 100644 --- a/contracts/BorrowerOperations.sol +++ b/contracts/BorrowerOperations.sol @@ -18,6 +18,7 @@ import "./Dependencies/Mynt/MyntLib.sol"; import "./Interfaces/IPermit2.sol"; import "./Interfaces/perimeter/IExitFeeController.sol"; import "./Interfaces/perimeter/IExitDelayQueueHook.sol"; +import "./Dependencies/BorrowerOperationsPerimeterOps.sol"; contract BorrowerOperations is LiquityBase, @@ -1148,152 +1149,44 @@ contract BorrowerOperations is } } - /// @dev Settle a borrower collateral payout, charging the Perimeter exit fee - /// when the resolved policy is active. The fee leg uses `try/catch` - /// (0.6.11 native) so a fee-receiver failure never bricks the exit; on - /// any non-charging path the full `gross` is sent to the borrower via - /// the existing fail-closed `sendETH`. ActivePool's recorded ETH - /// decrements by exactly `gross` either way (the reverted fee-leg - /// subcall rolls back its `ETH.sub`). + /// @dev Settle a borrower collateral payout through the security perimeter: + /// the fee leg, then the delay leg. The body runs in + /// `BorrowerOperationsPerimeterOps` under `delegatecall`, so it settles + /// in this proxy's context and the same transaction, and its events + /// carry this address. + /// + /// Any failure propagates unchanged. Once the perimeter resolves a + /// delay the leg must escrow, so a reverting hook reverts the whole + /// close or adjust rather than paying direct. function _sendCollWithExitFee( IActivePool _activePool, address borrower, uint256 gross ) private { - // Debt-only adjustments (repay / debt-decrease) reach here with gross == 0: - // no collateral leaves the pool, so there is nothing to settle. Skip the - // controller round-trip and the Perimeter event. (Baseline called - // sendETH(borrower, 0) here — a value-less no-op that only emitted - // EtherSent(_, 0) / ActivePoolETHBalanceUpdated; we drop that redundant - // transfer, so debt-only ops emit fewer events than pre-Perimeter.) - if (gross == 0) { - return; - } - - // Security-perimeter delay quote — computed ONCE up-front so a single `d` - // governs the WHOLE exit: a fee-vault failure still escrows GROSS - // behind the delay and cannot bypass it. FAIL-CLOSED (except the - // kill-switch / unwired short-circuit): a controller revert reverts the - // exit. Zero has no passthrough, so originator == owner == receiver - // == borrower (== msg.sender on every collateral-out path). The queue is - // NEVER touched here — only inside the `d > 0` branch of `_payUserColl` - //. - DelayLeg memory dl; - (dl.d, dl.effOrig, dl.effOwner) = _safeQuoteExitDelay(borrower, borrower, borrower); - - // Single Zero deployment: subProduct = address(0). Asset is native RBTC. - IExitFeeController.ExitFeeQuote memory q = _safeQuote( - PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, - address(0), - borrower, - gross + // delegatecall to a code-less address SUCCEEDS with empty returndata, + // which would silently skip the fee and the delay. Require code. + address ops = perimeterOps; + checkContract(ops); + (bool ok, bytes memory ret) = ops.delegatecall( + abi.encodeWithSelector( + BorrowerOperationsPerimeterOps(address(0)).sendCollWithExitFee.selector, + _activePool, + borrower, + gross + ) ); - - if (q.active && q.feeAmount > 0) { - try _activePool.sendETH(q.feeReceiver, q.feeAmount) { - // user leg (net): direct-pay OR reroute to the delay queue when d>0 - _payUserColl(_activePool, borrower, q.netAmount, dl); - // Emit only after BOTH legs settle, so an ExitFeeApplied event always - // implies a completed borrower payout (truthful by construction). - emit ExitFeeApplied( - PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, - borrower, - address(0), - address(0), - borrower, - gross, - q.feeAmount, - q.netAmount, - q.feeReceiver - ); - return; - } catch { - emit ExitFeeSkipped( - PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, - borrower, - address(0), - gross, - q.rateBps, - uint8(IExitFeeController.SkipReason.VAULT_REVERT) - ); + if (!ok) { + assembly { + revert(add(ret, 0x20), mload(ret)) } - } else { - // !active (INACTIVE / DISABLED / INVALID_QUOTE / CONTROLLER_REVERT) - // OR active-but-zero-fee (dust / zero-rate policy → q.reason == NONE). - emit ExitFeeSkipped( - PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, - borrower, - address(0), - gross, - q.rateBps, - q.reason - ); - } - // full-gross fallback (any non-charging path): direct-pay OR reroute to the - // delay queue when d>0 — the same up-front `d` governs both legs. - _payUserColl(_activePool, borrower, gross, dl); - } - - /// @dev Bundles the resolved delay-leg fields so `_payUserColl` stays a - /// single-slot call and `_sendCollWithExitFee` does not run into the - /// 0.6.11 stack-depth limit. `d == 0` ⇒ perimeter off / bypassed / - /// unwired ⇒ pay direct. Zero surface has no passthrough, so - /// `effOrig`/`effOwner` are the raw identities. - struct DelayLeg { - uint32 d; - address effOrig; - address effOwner; - } - - /// @dev Settle the (post-fee) borrower USER leg of a voluntary collateral-out. - /// When the perimeter quotes no delay (`d == 0`) this is the EXISTING - /// native payout, byte-for-byte unchanged (`sendETH(receiver, amount)`). - /// When `d > 0` the leg is rerouted into the ExitDelayQueue: ActivePool - /// PUSHES the native RBTC to the queue, immediately followed by - /// `recordReceivedNativeExit` in the SAME outer tx — both INSIDE this - /// `d > 0` branch so the queue is never touched until a delay is - /// established off-queue, and a record revert rolls back the push - /// (fail-CLOSED: after the trove state already mutated, the whole - /// close/adjust reverts atomically — a bricked queue blocks Zero closes - /// until the kill switch is flipped). The queue's `receive()` is - /// unconditional and, via measured-receipt, credits EXACTLY `amount` when - /// its surplus `>= amount` — a donation cannot brick the record. - function _payUserColl( - IActivePool _activePool, - address receiver, - uint256 amount, - DelayLeg memory dl - ) private { - // A net leg can be 0 on a full-fee edge; nothing to pay or escrow. - if (amount == 0) { - return; } + } - if (dl.d > 0) { - address queue = exitDelayQueue(); - // FAIL-CLOSED: once the perimeter quotes d>0 the user leg MUST escrow. - // An unwired queue reverts the exit with a DISTINCT selector (halt - // monitoring) — a delay can never be silently bypassed by a missing - // pointer. - require(queue != address(0), "PERIMETER:queue-unset"); - require(amount <= uint256(uint128(-1)), "PERIMETER:amount-too-large"); - - // PUSH native to the queue (reuses the existing fail-closed sendETH - // primitive — ActivePool.ETH decrements by exactly `amount`, identical - // to the direct payout), then measured-record in the SAME outer tx. - _activePool.sendETH(queue, amount); - IExitDelayQueueHook(queue).recordReceivedNativeExit( - uint128(amount), - dl.d, - PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, - address(0), - dl.effOrig, - dl.effOwner, - receiver - ); - } else { - _activePool.sendETH(receiver, amount); // EXISTING native payout, unchanged - } + /// @notice Rotate the perimeter settlement hook. `onlyOwner`, mirroring + /// `setTroveManagerRedeemOps`. + function setPerimeterOps(address _perimeterOps) external onlyOwner { + checkContract(_perimeterOps); + perimeterOps = _perimeterOps; } /// @notice Read-only preview of the Perimeter exit fee on a Zero borrower collateral diff --git a/contracts/BorrowerOperationsStorage.sol b/contracts/BorrowerOperationsStorage.sol index 9e7d769..0f9f88d 100644 --- a/contracts/BorrowerOperationsStorage.sol +++ b/contracts/BorrowerOperationsStorage.sol @@ -36,4 +36,9 @@ contract BorrowerOperationsStorage is Ownable { IMassetManager public massetManager; IFeeDistributor public feeDistributor; + + /// @notice The perimeter settlement hook `_sendCollWithExitFee` delegates to. + /// Appended last so no existing slot moves; set at init and + /// rotatable by the owner, mirroring `troveManagerRedeemOps`. + address public perimeterOps; } diff --git a/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol b/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol new file mode 100644 index 0000000..841e3d8 --- /dev/null +++ b/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.11; +pragma experimental ABIEncoderV2; + +import "../BorrowerOperationsStorage.sol"; +import "../Interfaces/IActivePool.sol"; +import "../Interfaces/perimeter/IExitFeeController.sol"; +import "../Interfaces/perimeter/IExitDelayQueueHook.sol"; + +/// @title BorrowerOperationsPerimeterOps +/// @notice Settles a Zero borrower collateral payout through the security +/// perimeter: the exit fee leg, then the delay leg. +/// +/// @dev Used via `delegatecall` from BorrowerOperations, the same way +/// TroveManagerRedeemOps is used from TroveManager. It shares +/// BorrowerOperationsStorage, so storage, `address(this)` and +/// `msg.sender` are the caller\'s, the pointer slots it reads are the +/// caller\'s, and the events it emits carry the caller\'s address. It +/// declares no storage of its own. +contract BorrowerOperationsPerimeterOps is BorrowerOperationsStorage { + bytes32 private constant EXIT_FEE_CONTROLLER_SLOT = + bytes32(uint256(keccak256("sovryn.perimeterExitFeeController")) - 1); + bytes32 private constant EXIT_DELAY_QUEUE_SLOT = + bytes32(uint256(keccak256("sovryn.perimeterExitDelayQueue")) - 1); + bytes32 private constant PERIMETER_SURFACE_ZERO_WITHDRAW_COLL = + keccak256("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); + + event ExitFeeApplied( + bytes32 indexed surfaceId, + address indexed actor, + address indexed asset, + address subProduct, + address recipient, + uint256 grossAmount, + uint256 feeAmount, + uint256 netAmount, + address feeReceiver + ); + event ExitFeeSkipped( + bytes32 indexed surfaceId, + address indexed actor, + address indexed asset, + uint256 grossAmount, + uint16 rateBps, + uint8 reason + ); + + /// @dev Reads the caller\'s pinned controller from the shared slot. + function exitFeeController() internal view returns (address ctrl) { + bytes32 slot = EXIT_FEE_CONTROLLER_SLOT; + assembly { + ctrl := sload(slot) + } + } + + /// @dev Reads the caller\'s pinned queue from the shared slot. + function exitDelayQueue() internal view returns (address queue) { + bytes32 slot = EXIT_DELAY_QUEUE_SLOT; + assembly { + queue := sload(slot) + } + } + + /// @dev Settle a borrower collateral payout, charging the Perimeter exit fee + /// when the resolved policy is active. The fee leg uses `try/catch` + /// (0.6.11 native) so a fee-receiver failure never bricks the exit; on + /// any non-charging path the full `gross` is sent to the borrower via + /// the existing fail-closed `sendETH`. ActivePool's recorded ETH + /// decrements by exactly `gross` either way (the reverted fee-leg + /// subcall rolls back its `ETH.sub`). + function sendCollWithExitFee( + IActivePool _activePool, + address borrower, + uint256 gross + ) external { + // Debt-only adjustments (repay / debt-decrease) reach here with gross == 0: + // no collateral leaves the pool, so there is nothing to settle. Skip the + // controller round-trip and the Perimeter event. (Baseline called + // sendETH(borrower, 0) here — a value-less no-op that only emitted + // EtherSent(_, 0) / ActivePoolETHBalanceUpdated; we drop that redundant + // transfer, so debt-only ops emit fewer events than pre-Perimeter.) + if (gross == 0) { + return; + } + + // Security-perimeter delay quote — computed ONCE up-front so a single `d` + // governs the WHOLE exit: a fee-vault failure still escrows GROSS + // behind the delay and cannot bypass it. FAIL-CLOSED (except the + // kill-switch / unwired short-circuit): a controller revert reverts the + // exit. Zero has no passthrough, so originator == owner == receiver + // == borrower (== msg.sender on every collateral-out path). The queue is + // NEVER touched here — only inside the `d > 0` branch of `_payUserColl` + //. + DelayLeg memory dl; + (dl.d, dl.effOrig, dl.effOwner) = _safeQuoteExitDelay(borrower, borrower, borrower); + + // Single Zero deployment: subProduct = address(0). Asset is native RBTC. + IExitFeeController.ExitFeeQuote memory q = _safeQuote( + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + address(0), + borrower, + gross + ); + + if (q.active && q.feeAmount > 0) { + try _activePool.sendETH(q.feeReceiver, q.feeAmount) { + // user leg (net): direct-pay OR reroute to the delay queue when d>0 + _payUserColl(_activePool, borrower, q.netAmount, dl); + // Emit only after BOTH legs settle, so an ExitFeeApplied event always + // implies a completed borrower payout (truthful by construction). + emit ExitFeeApplied( + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + borrower, + address(0), + address(0), + borrower, + gross, + q.feeAmount, + q.netAmount, + q.feeReceiver + ); + return; + } catch { + emit ExitFeeSkipped( + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + borrower, + address(0), + gross, + q.rateBps, + uint8(IExitFeeController.SkipReason.VAULT_REVERT) + ); + } + } else { + // !active (INACTIVE / DISABLED / INVALID_QUOTE / CONTROLLER_REVERT) + // OR active-but-zero-fee (dust / zero-rate policy → q.reason == NONE). + emit ExitFeeSkipped( + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + borrower, + address(0), + gross, + q.rateBps, + q.reason + ); + } + // full-gross fallback (any non-charging path): direct-pay OR reroute to the + // delay queue when d>0 — the same up-front `d` governs both legs. + _payUserColl(_activePool, borrower, gross, dl); + } + + /// @dev Settle the (post-fee) borrower USER leg of a voluntary collateral-out. + /// When the perimeter quotes no delay (`d == 0`) this is the EXISTING + /// native payout, byte-for-byte unchanged (`sendETH(receiver, amount)`). + /// When `d > 0` the leg is rerouted into the ExitDelayQueue: ActivePool + /// PUSHES the native RBTC to the queue, immediately followed by + /// `recordReceivedNativeExit` in the SAME outer tx — both INSIDE this + /// `d > 0` branch so the queue is never touched until a delay is + /// established off-queue, and a record revert rolls back the push + /// (fail-CLOSED: after the trove state already mutated, the whole + /// close/adjust reverts atomically — a bricked queue blocks Zero closes + /// until the kill switch is flipped). The queue's `receive()` is + /// unconditional and, via measured-receipt, credits EXACTLY `amount` when + /// its surplus `>= amount` — a donation cannot brick the record. + function _payUserColl( + IActivePool _activePool, + address receiver, + uint256 amount, + DelayLeg memory dl + ) private { + // A net leg can be 0 on a full-fee edge; nothing to pay or escrow. + if (amount == 0) { + return; + } + + if (dl.d > 0) { + address queue = exitDelayQueue(); + // FAIL-CLOSED: once the perimeter quotes d>0 the user leg MUST escrow. + // An unwired queue reverts the exit with a DISTINCT selector (halt + // monitoring) — a delay can never be silently bypassed by a missing + // pointer. + require(queue != address(0), "PERIMETER:queue-unset"); + require(amount <= uint256(uint128(-1)), "PERIMETER:amount-too-large"); + + // PUSH native to the queue (reuses the existing fail-closed sendETH + // primitive — ActivePool.ETH decrements by exactly `amount`, identical + // to the direct payout), then measured-record in the SAME outer tx. + _activePool.sendETH(queue, amount); + IExitDelayQueueHook(queue).recordReceivedNativeExit( + uint128(amount), + dl.d, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + address(0), + dl.effOrig, + dl.effOwner, + receiver + ); + } else { + _activePool.sendETH(receiver, amount); // EXISTING native payout, unchanged + } + } + + /// @dev Fail-open quote wrapper. On a missing/reverting controller or a + /// semantically invalid quote, returns a non-charging quote with + /// `netAmount == gross`. The validity gate uses subtraction only + /// (`feeAmount > gross`), never an unchecked addition, and recomputes + /// `netAmount = gross - feeAmount` so the fee + user legs always sum to + /// exactly `gross` — protecting ActivePool liquidity from a bad or + /// upgraded controller. + function _safeQuote( + bytes32 surfaceId, + address subProduct, + address actor, + uint256 gross + ) private view returns (IExitFeeController.ExitFeeQuote memory q) { + address ctrl = exitFeeController(); + // Fail open on a missing OR code-less controller. The address(0) check + // alone is not enough: a high-level call to any no-code address (EOA, + // or a controller that self-destructed after being set) reverts with + // "function call to a non-contract account", which 0.6.11 try/catch + // does NOT catch — so guard on extcodesize before the call. + uint256 ctrlSize; + assembly { + ctrlSize := extcodesize(ctrl) + } + if (ctrl == address(0) || ctrlSize == 0) { + q.netAmount = gross; + q.reason = uint8(IExitFeeController.SkipReason.CONTROLLER_REVERT); + return q; + } + try IExitFeeController(ctrl).quoteExitFee(surfaceId, subProduct, actor, gross) returns ( + IExitFeeController.ExitFeeQuote memory got + ) { + // Pool conservation is the consumer's own concern: it holds exactly `gross` + // wei to distribute, so it must never be asked to pay out more. A + // feeAmount > gross would underflow the net recompute below (bricking the + // exit) and a fee leg > gross could draw OTHER troves' collateral out of + // ActivePool. Rate, receiver, and fee policy are the configured + // controller's responsibility — not re-validated here. + if (got.feeAmount > gross) { + // Override only the verdict; leave the controller's raw feeAmount / + // rateBps / feeReceiver intact (active=false gates charging downstream). + got.active = false; + got.netAmount = gross; // non-charging shape: net == gross + got.reason = uint8(IExitFeeController.SkipReason.INVALID_QUOTE); + return got; + } + got.netAmount = gross - got.feeAmount; // fee + net == gross (no residue) + return got; + } catch { + q.netAmount = gross; + q.reason = uint8(IExitFeeController.SkipReason.CONTROLLER_REVERT); + } + } + + /// @dev Fail-CLOSED delay quote wrapper. Resolves the single hook + /// entry `quoteExitDelayFor` on the shared Perimeter controller and returns + /// `(d, effOrig, effOwner)`. Two levels, deliberately distinct: + /// 1. controller-POINTER lookup is FAIL-OPEN — a missing OR code-less + /// controller ⇒ perimeter unwired ⇒ `(0, raw, raw)` ⇒ pay direct + /// (mirrors the fee path; also, 0.6.11 try/catch does NOT catch a + /// call to a no-code address, so the extcodesize guard is required); + /// 2. once a controller is resolved, the `quoteExitDelayFor` CALL is + /// FAIL-CLOSED — a revert reverts the whole exit and MUST NOT be + /// interpreted as `d = 0`-direct (that would silently disable the + /// perimeter — the hazard this guards against). Uses a DISTINCT revert selector for + /// halt monitoring. + /// The `!securityPerimeterEnabled` short-circuit is the FIRST statement + /// inside `quoteExitDelayFor`, so a healthy-but-disabled perimeter returns + /// `(0, raw, owner)` normally (liveness escape). The hook ignores + /// `effOrig`/`effOwner` whenever `d == 0`. + function _safeQuoteExitDelay( + address rawOriginator, + address owner, + address receiver + ) private view returns (uint32 d, address effOrig, address effOwner) { + address ctrl = exitFeeController(); + uint256 ctrlSize; + assembly { + ctrlSize := extcodesize(ctrl) + } + // Level 1 — FAIL-OPEN pointer lookup: unwired/unreachable ⇒ direct pay. + // Raw identities are returned but the caller ignores them when d == 0. + if (ctrl == address(0) || ctrlSize == 0) { + return (0, rawOriginator, owner); + } + // Level 2 — FAIL-CLOSED quote: a controller revert reverts the exit. + try + IExitFeeController(ctrl).quoteExitDelayFor( + rawOriginator, + owner, + receiver, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + address(0) + ) + returns (uint32 d_, address effOrig_, address effOwner_) { + return (d_, effOrig_, effOwner_); + } catch { + revert("PERIMETER:delay-quote-failed"); + } + } + + /// @dev Bundles the resolved delay-leg fields so `_payUserColl` stays a + /// single-slot call and `_sendCollWithExitFee` does not run into the + /// 0.6.11 stack-depth limit. `d == 0` ⇒ perimeter off / bypassed / + /// unwired ⇒ pay direct. Zero surface has no passthrough, so + /// `effOrig`/`effOwner` are the raw identities. + struct DelayLeg { + uint32 d; + address effOrig; + address effOwner; + } +} diff --git a/contracts/TestContracts/BorrowerOperationsTester.sol b/contracts/TestContracts/BorrowerOperationsTester.sol index 1c26df2..3bdfef1 100644 --- a/contracts/TestContracts/BorrowerOperationsTester.sol +++ b/contracts/TestContracts/BorrowerOperationsTester.sol @@ -4,11 +4,14 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; import "../BorrowerOperations.sol"; +import "../Dependencies/BorrowerOperationsPerimeterOps.sol"; /* Tester contract inherits from BorrowerOperations, and provides external functions for testing the parent's internal functions. */ contract BorrowerOperationsTester is BorrowerOperations { - constructor(address _permit2) public BorrowerOperations(_permit2) {} + constructor(address _permit2) public BorrowerOperations(_permit2) { + perimeterOps = address(new BorrowerOperationsPerimeterOps()); + } function getNewICRFromTroveChange( uint _coll, diff --git a/contracts/TestContracts/EchidnaTester.sol b/contracts/TestContracts/EchidnaTester.sol index ed94914..bcb0bfe 100644 --- a/contracts/TestContracts/EchidnaTester.sol +++ b/contracts/TestContracts/EchidnaTester.sol @@ -9,6 +9,7 @@ import "../TroveManager.sol"; import "../TroveManagerStorage.sol"; import "../Dependencies/TroveManagerRedeemOps.sol"; import "../BorrowerOperations.sol"; +import "../Dependencies/BorrowerOperationsPerimeterOps.sol"; import "../ActivePool.sol"; import "../DefaultPool.sol"; import "../StabilityPool.sol"; @@ -58,6 +59,7 @@ contract EchidnaTester { troveManagerRedeemOps = new TroveManagerRedeemOps(14 * 86400, _permit2); troveManager = new TroveManager(14 days, _permit2); borrowerOperations = new BorrowerOperations(_permit2); + borrowerOperations.setPerimeterOps(address(new BorrowerOperationsPerimeterOps())); activePool = new ActivePool(); defaultPool = new DefaultPool(); stabilityPool = new StabilityPool(_permit2); diff --git a/utils/js/deploymentHelpers.js b/utils/js/deploymentHelpers.js index bc2b1b3..e0c6e7f 100644 --- a/utils/js/deploymentHelpers.js +++ b/utils/js/deploymentHelpers.js @@ -13,6 +13,9 @@ const GasPool = artifacts.require("./GasPool.sol"); const CollSurplusPool = artifacts.require("./CollSurplusPool.sol"); const FunctionCaller = artifacts.require("./TestContracts/FunctionCaller.sol"); const BorrowerOperations = artifacts.require("./BorrowerOperations.sol"); +const BorrowerOperationsPerimeterOps = artifacts.require( + "./Dependencies/BorrowerOperationsPerimeterOps.sol" +); const HintHelpers = artifacts.require("./HintHelpers.sol"); const FeeDistributor = artifacts.require("./FeeDistributor.sol"); @@ -114,6 +117,9 @@ class DeploymentHelper { const collSurplusPool = await CollSurplusPool.new(); const functionCaller = await FunctionCaller.new(); const borrowerOperations = await BorrowerOperations.new(permit2.address); + await borrowerOperations.setPerimeterOps( + (await BorrowerOperationsPerimeterOps.new()).address + ); const hintHelpers = await HintHelpers.new(); const zusdToken = await ZUSDToken.new(); const feeDistributor = await FeeDistributor.new(); @@ -279,6 +285,9 @@ class DeploymentHelper { const collSurplusPool = await CollSurplusPool.new(); const functionCaller = await FunctionCaller.new(); const borrowerOperations = await BorrowerOperations.new(permit2.address); + await borrowerOperations.setPerimeterOps( + (await BorrowerOperationsPerimeterOps.new()).address + ); const hintHelpers = await HintHelpers.new(); const zusdToken = await ZUSDToken.new(); const feeDistributor = await FeeDistributor.new(); From 4f20fac89770b192b550f5b7202b71b793cd1696 Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Wed, 19 Aug 2026 21:41:49 +0300 Subject: [PATCH 5/7] Read the bubbled queue selector on-chain, and guard the appended slot The selector-propagation regression pulled the revert payload out of the node's eth_call error object. Hardhat cannot attach a custom-error payload when it fails to build a stack trace through the settlement delegatecall, so the assertion saw an empty string even though the selector bubbles intact. Read the returndata through PerimeterRawCatcher instead: that is what the EVM hands the caller, independent of how the node formats errors. The storage-layout guard forbade any appended state on every target. BorrowerOperations now holds the settlement hook in one appended slot, so its policy becomes append-only -- the deployed prefix must stay byte-identical and additions must land beyond every baseline slot. ActivePool and CollSurplusPool stay zero-diff. --- .../TestContracts/PerimeterRawCatcher.sol | 18 +++ .../StorageLayout.zerodiff.test.js | 125 ++++++++++++------ .../ZeroBorrowerExit.delay.selector.test.js | 121 +++++++++-------- 3 files changed, 166 insertions(+), 98 deletions(-) create mode 100644 contracts/TestContracts/PerimeterRawCatcher.sol diff --git a/contracts/TestContracts/PerimeterRawCatcher.sol b/contracts/TestContracts/PerimeterRawCatcher.sol new file mode 100644 index 0000000..1b513ec --- /dev/null +++ b/contracts/TestContracts/PerimeterRawCatcher.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.11; + +/// @notice Test helper that performs a low-level call and RETURNS the raw +/// returndata instead of bubbling it, so a test can read the exact +/// revert payload a reverting exit produces — independent of how the +/// node formats its error. +contract PerimeterRawCatcher { + function probe( + address target, + bytes calldata data + ) external payable returns (bool ok, bytes memory ret) { + (ok, ret) = target.call.value(msg.value)(data); + } + + receive() external payable {} +} diff --git a/tests-perimeter/StorageLayout.zerodiff.test.js b/tests-perimeter/StorageLayout.zerodiff.test.js index bed1d56..8bccdfa 100644 --- a/tests-perimeter/StorageLayout.zerodiff.test.js +++ b/tests-perimeter/StorageLayout.zerodiff.test.js @@ -1,31 +1,30 @@ -// Perimeter security perimeter — storage-layout ZERO-DIFF regression. +// Perimeter security perimeter — storage-layout upgrade-safety regression. // -// Neither the Zero surplus-claim exit-fee hook NOR the borrower exit-DELAY -// reroute adds storage to any deployed upgradeable contract: the surface ids -// are constants, the exit-fee controller pointer and the ExitDelayQueue -// pointer (keccak256("sovryn.perimeterExitDelayQueue") - 1) live in EIP-1967-style -// unstructured slots, and the hooks declare no new state variables on the -// BorrowerOperations or CollSurplusPool proxies. That is true BY CONSTRUCTION -// today — but nothing GUARDS a future edit from appending a `uint256` to the -// proxy and silently corrupting every live trove's storage on the next -// upgrade. +// The perimeter surfaces are storage-frugal by construction: the surface ids are +// constants and the exit-fee controller / ExitDelayQueue pointers live in +// EIP-1967-style unstructured slots (keccak256("sovryn.perimeter*") - 1), so they +// occupy no regular-storage slot. The one exception is BorrowerOperations, which +// holds the perimeter settlement hook address in a plain, APPENDED slot — +// mirroring TroveManager's `troveManagerRedeemOps`. // -// This test is that guard. It compares the current, normalized solc -// `storageLayout` of BorrowerOperations, CollSurplusPool, and ActivePool -// against a committed baseline and FAILS on any label/slot/offset/type -// difference. The lending side carries an equivalent guard over its own -// upgradeable contracts. +// This test guards both properties against a future edit that would silently +// corrupt every live trove's storage on the next upgrade. It compares the +// current, normalized solc `storageLayout` against a committed baseline under a +// per-target policy: // -// SCOPE OF THE BASELINE (be precise about what this proves): the committed -// baseline was captured at `sovryn-perimeter-fee @ b6584a6`, a tree that ALREADY -// contains the borrower-exit hook (`_sendCollWithExitFee`, the unstructured -// controller slot, the surface-id constants). So this guard proves the -// SURPLUS-CLAIM hook and the EXIT-DELAY hooks appended no state, and forbids -// any future append to all three contracts. It does NOT independently re-prove -// the borrower-exit hook's zero-diff — that holds by construction (constants -// plus EIP-1967-style slots for the controller and queue pointers, none of -// which occupies a regular-storage slot) and is reviewable in the contract -// source, but it is not what this baseline compares against. +// ZERO_DIFF — any label/slot/offset/type difference fails (ActivePool, +// CollSurplusPool: hooked but stateless). +// APPEND_ONLY — the baseline prefix must be byte-identical AND every added +// entry must occupy a slot strictly beyond the baseline's last +// slot; reordering, retyping, resizing or inserting fails +// (BorrowerOperations). +// +// SCOPE OF THE BASELINE: captured at `sovryn-perimeter-fee @ b6584a6`, a tree that +// ALREADY contains the borrower-exit fee hook. So this guard proves the +// surplus-claim hook and the exit-delay hooks appended nothing beyond the single +// declared settlement-hook slot. It does not independently re-prove the +// borrower-exit fee hook's zero-diff — that holds by construction and is +// reviewable in the contract source. // // Requires `storageLayout` in the 0.6.11 compiler outputSelection // (hardhat.config.ts) — the shared helper throws (never silently passes) if the @@ -46,13 +45,19 @@ const { normalizedLayout } = require("./utils/storageLayout.js"); const BASELINE = path.join(__dirname, "baselines", "storage-layout.sovryn-perimeter-fee.json"); +const ZERO_DIFF = "zero-diff"; +const APPEND_ONLY = "append-only"; + const TARGETS = [ - "contracts/BorrowerOperations.sol:BorrowerOperations", // hooked upgradeable proxy - "contracts/ActivePool.sol:ActivePool", // native pusher — must stay untouched - "contracts/CollSurplusPool.sol:CollSurplusPool", // gains claimCollWithFee — functions only, no state + // Holds the perimeter settlement hook in one appended slot (`perimeterOps`). + { fq: "contracts/BorrowerOperations.sol:BorrowerOperations", policy: APPEND_ONLY }, + // Native pusher — must stay untouched. + { fq: "contracts/ActivePool.sol:ActivePool", policy: ZERO_DIFF }, + // Gains claimCollWithFee — functions only, no state. + { fq: "contracts/CollSurplusPool.sol:CollSurplusPool", policy: ZERO_DIFF }, ]; -describe("Perimeter — storage-layout zero-diff (surplus-claim fee hook + exit-delay reroute)", () => { +describe("Perimeter — storage-layout upgrade safety (surplus-claim fee hook + exit-delay reroute)", () => { let baseline; before(() => { @@ -60,7 +65,7 @@ describe("Perimeter — storage-layout zero-diff (surplus-claim fee hook + exit- }); it("baseline snapshot is present and non-empty for every target", () => { - for (const fq of TARGETS) { + for (const { fq } of TARGETS) { assert.ok(Array.isArray(baseline[fq]), `baseline missing ${fq}`); assert.ok( baseline[fq].length > 0, @@ -69,18 +74,52 @@ describe("Perimeter — storage-layout zero-diff (surplus-claim fee hook + exit- } }); - for (const fq of TARGETS) { - it(`${fq}: current layout == sovryn-perimeter-fee baseline (no appended state)`, async () => { - const current = await normalizedLayout(fq); - const base = baseline[fq]; - // Exact structural equality: label/slot/offset/type per entry, in order. - assert.deepStrictEqual( - current, - base, - `STORAGE LAYOUT DIFF for ${fq} vs sovryn-perimeter-fee baseline:\n` + - ` baseline entries: ${base.length}\n current entries : ${current.length}\n` + - ` current: ${JSON.stringify(current)}` - ); - }); + for (const { fq, policy } of TARGETS) { + if (policy === ZERO_DIFF) { + it(`${fq}: current layout == sovryn-perimeter-fee baseline (no appended state)`, async () => { + const current = await normalizedLayout(fq); + const base = baseline[fq]; + // Exact structural equality: label/slot/offset/type per entry, in order. + assert.deepStrictEqual( + current, + base, + `STORAGE LAYOUT DIFF for ${fq} vs sovryn-perimeter-fee baseline:\n` + + ` baseline entries: ${base.length}\n current entries : ${current.length}\n` + + ` current: ${JSON.stringify(current)}` + ); + }); + } else { + it(`${fq}: baseline prefix unchanged, additions strictly appended`, async () => { + const current = await normalizedLayout(fq); + const base = baseline[fq]; + + assert.ok( + current.length >= base.length, + `${fq}: ${base.length - current.length} baseline entr(ies) REMOVED — ` + + `every deployed slot must survive an upgrade.\n` + + ` current: ${JSON.stringify(current)}` + ); + + // The deployed prefix must be byte-identical: no reorder, retype, + // resize or insertion anywhere inside the live layout. + assert.deepStrictEqual( + current.slice(0, base.length), + base, + `STORAGE LAYOUT DIFF inside the deployed prefix of ${fq}:\n` + + ` current: ${JSON.stringify(current.slice(0, base.length))}` + ); + + // Additions must land beyond every baseline slot, so nothing can + // pack into a slot the live contract already accounts for. + const lastBaselineSlot = base.reduce((m, e) => Math.max(m, Number(e.slot)), -1); + for (const added of current.slice(base.length)) { + assert.ok( + Number(added.slot) > lastBaselineSlot, + `${fq}: appended '${added.label}' sits at slot ${added.slot}, ` + + `inside the deployed range (last baseline slot ${lastBaselineSlot})` + ); + } + }); + } } }); diff --git a/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js b/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js index 4322a5e..d44d973 100644 --- a/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js +++ b/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js @@ -3,11 +3,11 @@ // The real ExitDelayQueue is Solidity 0.8.20 and its onlyAllowedSource guard // reverts with the DISTINCT custom error `UnregisteredSource(address)` — the // primary fail-closed halt signal the off-chain watcher keys on. The 0.6.11 -// BorrowerOperations delay hook calls -// `recordReceivedNativeExit` as a PLAIN external call (NOT wrapped in a -// try/catch or re-`require` with a PERIMETER: string), so that selector must -// BUBBLE UP UNCHANGED out of the reverting trove exit — it is neither swallowed -// nor re-wrapped by the host. +// BorrowerOperations delay hook calls `recordReceivedNativeExit` as a PLAIN +// external call (NOT wrapped in a try/catch or re-`require` with a PERIMETER: +// string), and BorrowerOperations re-emits the settlement hook's revert +// unchanged, so that selector must BUBBLE UP UNWRAPPED out of the reverting +// trove exit — across BOTH the pragma and the delegatecall boundary. // // This regression drives a real withdrawColl/closeTrove into a queue that // reverts with the exact `UnregisteredSource(msg.sender)` payload and asserts @@ -16,6 +16,12 @@ // (PERIMETER:queue-unset / PERIMETER:delay-quote-failed) are asserted in // ZeroBorrowerExit.delay.test.js — those are the reverts that CANNOT bubble a // queue selector because they fire before/around the queue call. +// +// The revert payload is read ON-CHAIN through `PerimeterRawCatcher`, which +// low-level-calls BorrowerOperations and RETURNS the raw returndata. That is +// the EVM's own answer, and it holds whether or not the node can decode a +// custom error or build a stack trace through the settlement delegatecall — +// which parsing the node's error object cannot. const deploymentHelper = require("../utils/js/deploymentHelpers.js"); const testHelpers = require("../utils/js/testHelpers.js"); @@ -26,6 +32,7 @@ const TroveManagerTester = artifacts.require("TroveManagerTester"); const MassetManagerTester = artifacts.require("MassetManagerTester"); const ExitFeeControllerMock = artifacts.require("ExitFeeControllerMock"); const SelectorRevertingExitDelayQueue = artifacts.require("SelectorRevertingExitDelayQueue"); +const PerimeterRawCatcher = artifacts.require("PerimeterRawCatcher"); const th = testHelpers.TestHelper; const dec = th.dec; @@ -38,45 +45,24 @@ const UNREGISTERED_SOURCE_SELECTOR = web3.utils .keccak256("UnregisteredSource(address)") .slice(0, 10); -// Pull the raw revert returndata out of a reverting call. eth_call is used so the -// FULL custom-error payload (selector + args) is returned verbatim by the node, -// independent of receipt/tx error formatting. Handles the hardhat error shapes -// (top-level `data` hex, nested `data.data`, or a 0x-hex substring in `message`). -const rawRevertData = async (from, to, data) => - new Promise((resolve) => { - web3.currentProvider.send( - { - jsonrpc: "2.0", - id: Date.now(), - method: "eth_call", - params: [{ from, to, data }, "latest"], - }, - (err, res) => { - const e = err || (res && res.error); - assert.isOk(e, "expected the call to revert but it succeeded"); - let d = e.data; - if (d && typeof d === "object") d = d.data || d.result || d.value; - if (typeof d !== "string" || !d.startsWith("0x")) { - const m = (e.message || "") + " " + JSON.stringify(e); - const found = m.match(/0x[0-9a-fA-F]{8,}/); - d = found ? found[0] : ""; - } - resolve(d.toLowerCase()); - } - ); - }); - contract("Perimeter delay — SR1 queue selector propagation", async (accounts) => { - const [owner, alice] = accounts; + const [owner] = accounts; const multisig = accounts[999]; let borrowerOperations; let controller; let queue; + let catcher; let contracts; const openTrove = async (params) => th.openTrove(contracts, params); + // withdrawColl/closeTrove act on msg.sender's trove, so the catcher opens its + // own and drives the exit itself — the returndata it receives IS the payload + // the reverting exit hands its caller. + const catcherCall = (data) => + catcher.probe.call(borrowerOperations.address, data, { from: owner }); + before(async () => { contracts = await deploymentHelper.deployLiquityCore(); const permit2 = contracts.permit2; @@ -94,6 +80,7 @@ contract("Perimeter delay — SR1 queue selector propagation", async (accounts) borrowerOperations = contracts.borrowerOperations; await borrowerOperations.setMassetManagerAddress(contracts.massetManager.address); + catcher = await PerimeterRawCatcher.new(); }); let snapshotId; @@ -110,6 +97,28 @@ contract("Perimeter delay — SR1 queue selector propagation", async (accounts) await timeMachine.revertToSnapshot(snapshotId); }); + // A trove owned by the catcher: 100 RBTC of collateral against a small draw, + // so it stays far above MCR and can also be closed within a test. + const openCatcherTrove = async () => { + const data = borrowerOperations.contract.methods + .openTrove( + toBN(dec(5, 16)).toString(), // maxFeePercentage 5% + toBN(dec(2000, 18)).toString(), + catcher.address, + catcher.address + ) + .encodeABI(); + await catcher.probe(borrowerOperations.address, data, { + from: owner, + value: toBN(dec(100, "ether")), + }); + assert.equal( + (await contracts.troveManager.getTroveStatus(catcher.address)).toString(), + "1", + "catcher trove was not opened" + ); + }; + it("sanity: the mock reverts with the exact UnregisteredSource(address) selector", async () => { const sel = await queue.UNREGISTERED_SOURCE_SELECTOR(); assert.equal( @@ -120,50 +129,52 @@ contract("Perimeter delay — SR1 queue selector propagation", async (accounts) }); it("withdrawColl (d>0): queue UnregisteredSource selector BUBBLES UP unwrapped (not a PERIMETER: string)", async () => { - await openTrove({ - ICR: toBN(dec(10, 18)), - extraParams: { from: alice, value: toBN(dec(100, "ether")) }, - }); + await openCatcherTrove(); const data = borrowerOperations.contract.methods - .withdrawColl(toBN(dec(1, "ether")).toString(), alice, alice) + .withdrawColl(toBN(dec(1, "ether")).toString(), catcher.address, catcher.address) .encodeABI(); - const revertData = await rawRevertData(alice, borrowerOperations.address, data); + const { ok, ret: revertData } = await catcherCall(data); - // Distinct 4-byte selector preserved cross-pragma (0.8.20 queue → 0.6.11 host). + assert.isFalse(ok, "expected the exit to revert"); + // Distinct 4-byte selector preserved cross-pragma (0.8.20 queue → 0.6.11 + // host) and across the settlement delegatecall. assert.equal( - revertData.slice(0, 10), + revertData.toLowerCase().slice(0, 10), UNREGISTERED_SOURCE_SELECTOR, `expected queue selector to bubble; got ${revertData}` ); // The bubbled payload ABI-encodes the offending caller (the BO proxy), proving // the FULL custom-error data survived — not a truncated / re-wrapped revert. const encodedCaller = borrowerOperations.address.slice(2).toLowerCase().padStart(64, "0"); - assert.include(revertData, encodedCaller, "custom-error arg (caller) not preserved"); + assert.include( + revertData.toLowerCase(), + encodedCaller, + "custom-error arg (caller) not preserved" + ); }); it("closeTrove (d>0): queue selector bubbles out of a failing trove CLOSE (fail-closed)", async () => { - // A second trove so alice can close hers (system keeps >1 trove / TCR ok). + // A second trove so the catcher can close its own (system keeps >1 trove). await openTrove({ extraZUSDAmount: toBN(dec(20000, 18)), ICR: toBN(dec(3, 18)), extraParams: { from: owner }, }); - await openTrove({ - extraZUSDAmount: toBN(dec(10000, 18)), - ICR: toBN(dec(2, 18)), - extraParams: { from: alice }, - }); - // alice already holds enough ZUSD from her own draw to repay; top up from owner. - await contracts.zusdToken.transfer(alice, await contracts.zusdToken.balanceOf(owner), { - from: owner, - }); + await openCatcherTrove(); + // closeTrove burns ZUSD from the caller — fund the catcher to cover its debt. + await contracts.zusdToken.transfer( + catcher.address, + await contracts.zusdToken.balanceOf(owner), + { from: owner } + ); const data = borrowerOperations.contract.methods.closeTrove().encodeABI(); - const revertData = await rawRevertData(alice, borrowerOperations.address, data); + const { ok, ret: revertData } = await catcherCall(data); + assert.isFalse(ok, "expected the close to revert"); assert.equal( - revertData.slice(0, 10), + revertData.toLowerCase().slice(0, 10), UNREGISTERED_SOURCE_SELECTOR, `expected queue selector to bubble out of closeTrove; got ${revertData}` ); From 47dffb6d225cc616f19bdfa0e71334c47ca04d11 Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Wed, 19 Aug 2026 21:41:58 +0300 Subject: [PATCH 6/7] Deploy the Zero settlement companion and wire it in Mirror 4-TroveManagerRedeemOps: deploy the companion, then set it on BorrowerOperations -- through the multisig on testnet, as a proposal line on mainnet, directly otherwise. --- .../9-BorrowerOperationsPerimeterOps.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 deployment/deploy/9-BorrowerOperationsPerimeterOps.ts diff --git a/deployment/deploy/9-BorrowerOperationsPerimeterOps.ts b/deployment/deploy/9-BorrowerOperationsPerimeterOps.ts new file mode 100644 index 0000000..2b9ea93 --- /dev/null +++ b/deployment/deploy/9-BorrowerOperationsPerimeterOps.ts @@ -0,0 +1,68 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { getContractNameFromScriptFileName } from "../../scripts/helpers/utils"; +const path = require("path"); +import Logs from "node-logs"; +const logger = new Logs().showInConsole(true); +import * as helpers from "../../scripts/helpers/helpers"; + +const deploymentName = getContractNameFromScriptFileName(path.basename(__filename)); + +const func: DeployFunction = async (hre) => { + const { + getNamedAccounts, + ethers, + deployments: { get, deploy, log, execute }, + network, + } = hre; + + const { deployer } = await getNamedAccounts(); + const borrowerOperations = await ethers.getContract("BorrowerOperations"); + + const tx = await deploy(deploymentName, { + from: deployer, + args: [], + log: true, + }); + + const prevImpl = await borrowerOperations.perimeterOps(); + log(`Current ${deploymentName}: ${prevImpl}`); + + if (tx.newlyDeployed || tx.address != prevImpl) { + if (tx.address != prevImpl) { + logger.information( + `${deploymentName} is reused. However it was not set in the BorrowerOperations contract as perimeterOps yet.` + ); + } + if (network.tags.testnet) { + console.log("testnet"); + logger.information( + `Initiating multisig tx to set BorrowerOperationsPerimeterOps in BorrowerOperations....` + ); + const deployment = await get(deploymentName); + const multisigAddress = (await get("MultiSigWallet")).address; + const data = borrowerOperations.interface.encodeFunctionData("setPerimeterOps", [ + deployment.address, + ]); + + await helpers.sendWithMultisig( + hre, + multisigAddress, + borrowerOperations.target.toString(), + data, + deployer + ); + } else if (network.tags.mainnet) { + // create SIP message + console.log("mainnet"); + logger.info(`>>> Add ${deploymentName} address ${tx.address} update to a SIP`); + } else { + // just set the hook directly + console.log("else!"); + await execute("BorrowerOperations", { from: deployer }, "setPerimeterOps", tx.address); + } + } +}; + +func.tags = [deploymentName]; +func.dependencies = ["BorrowerOperations"]; +export default func; From cbcdac430bfa4db3b61bebc658a7654369c1765e Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Thu, 20 Aug 2026 02:38:49 +0300 Subject: [PATCH 7/7] Bring the surplus claim inside the perimeter delay The surplus claim charged a fee but was always paid straight out, because the deployed claimCollWithFee hard-sends the net to the claimant with no way to redirect it. Add claimCollWithFeeTo, which pays the remainder to a named recipient under the same accounting and the same CEI ordering, so the perimeter can escrow it. A zero fee now takes no leg at all rather than making a zero-value call that would report a fee never charged. claimCollWithFee is left exactly as deployed: it is the selector the shipped BorrowerOperations calls, and the rollback target. The whole settlement moved into the companion as claimSurplusWithPerimeter and claimCollateral became a thin wrapper, which is why BorrowerOperations came out smaller: 24,125 -> 23,233, 1,343 under the limit. The delay is quoted once, up front, so a fee-vault failure still escrows the gross behind the hold. The fee leg stays fail-open; the delay leg is fail-closed, so an unwired queue or a reverting record reverts the claim and the claimant keeps their balance. The companion no longer inherits BorrowerOperationsStorage. It never shared those slots: BorrowerOperations also inherits LiquityBase, so every plain variable sat four words earlier here, and reading collSurplusPool would have returned liquityBaseParams. It was harmless only because nothing read plain storage, and this change needed to. The companion now declares none, takes what it needs as arguments, and a regression pins the declared layout as empty. ClaimSurplus.notouch retired for ZeroClaimSurplus.delay (11 tests): 79 perimeter tests, 831 in the base suite, none failing. --- contracts/BorrowerOperations.sol | 105 ++---- contracts/CollSurplusPool.sol | 44 +++ .../BorrowerOperationsPerimeterOps.sol | 180 +++++++++- contracts/Interfaces/ICollSurplusPool.sol | 22 ++ tests-perimeter/ClaimSurplus.notouch.test.js | 188 ---------- .../StorageLayout.zerodiff.test.js | 24 +- .../ZeroClaimSurplus.delay.test.js | 326 ++++++++++++++++++ tests-perimeter/utils/storageLayout.js | 28 +- 8 files changed, 632 insertions(+), 285 deletions(-) delete mode 100644 tests-perimeter/ClaimSurplus.notouch.test.js create mode 100644 tests-perimeter/ZeroClaimSurplus.delay.test.js diff --git a/contracts/BorrowerOperations.sol b/contracts/BorrowerOperations.sol index b103d5b..9f297a2 100644 --- a/contracts/BorrowerOperations.sol +++ b/contracts/BorrowerOperations.sol @@ -829,78 +829,26 @@ contract BorrowerOperations is } /** - * Claim remaining collateral from a redemption or from a liquidation with ICR > MCR in Recovery Mode, - * charging the Perimeter exit fee when the PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS policy is active. - * Fail-open like every Perimeter hook: on any Perimeter failure (controller missing/ - * reverting, invalid quote, fee-leg transfer failure inside the pool) the claimant - * receives the full surplus — a Perimeter failure can never brick a claim. The - * non-charging path is the untouched claimColl flow (plus the ExitFeeSkipped - * event, same convention as _sendCollWithExitFee). + * Claim remaining collateral from a redemption or from a liquidation with ICR > MCR in + * Recovery Mode, settled through the security perimeter: the exit fee leg when the + * PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS policy is active, then the delay leg. + * + * The FEE leg is fail-open, as every Perimeter fee hook is: a missing or reverting + * controller, an invalid quote or a failed fee transfer leaves the claimant with the + * full surplus. The DELAY leg is fail-CLOSED: once the perimeter resolves a hold the + * net must escrow, so an unwired or reverting queue reverts the claim and the claimant + * keeps their surplus balance to claim again later. + * + * With no fee and no delay this is the untouched claimColl flow, plus the + * ExitFeeSkipped event. */ function claimCollateral() external override { - uint256 gross = collSurplusPool.getCollateral(msg.sender); - // Single Zero deployment: subProduct = address(0). Asset is native RBTC. - IExitFeeController.ExitFeeQuote memory q = _safeQuote( - PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, - address(0), - msg.sender, - gross + _delegateToPerimeterOps( + abi.encodeWithSelector( + BorrowerOperationsPerimeterOps(address(0)).claimSurplusWithPerimeter.selector, + collSurplusPool + ) ); - - // Defensive: a quote that charges into address(0) would burn the fee (a - // value call to a no-code address succeeds). Demote to the non-charging - // path — DISABLED is the enum's "feeReceiver == address(0)" reason. - if (q.active && q.feeAmount > 0 && q.feeReceiver == address(0)) { - q.active = false; - q.netAmount = gross; - q.reason = uint8(IExitFeeController.SkipReason.DISABLED); - } - - if (q.active && q.feeAmount > 0) { - // Two-leg split inside the pool (fee → feeReceiver, net → claimant); - // the pool's fee leg is fail-open and reports which event is truthful. - bool feePaid = collSurplusPool.claimCollWithFee( - msg.sender, - q.feeReceiver, - q.feeAmount - ); - if (feePaid) { - emit ExitFeeApplied( - PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, - msg.sender, - address(0), - address(0), - msg.sender, - gross, - q.feeAmount, - q.netAmount, - q.feeReceiver - ); - } else { - emit ExitFeeSkipped( - PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, - msg.sender, - address(0), - gross, - q.rateBps, - uint8(IExitFeeController.SkipReason.VAULT_REVERT) - ); - } - } else { - // !active (INACTIVE / DISABLED / INVALID_QUOTE / CONTROLLER_REVERT) - // OR active-but-zero-fee (dust / zero-rate / gross == 0 → reason NONE). - emit ExitFeeSkipped( - PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, - msg.sender, - address(0), - gross, - q.rateBps, - q.reason - ); - // send ETH from CollSurplus Pool to owner — untouched original path - // (gross == 0 falls through to claimColl's own revert, identical to today) - collSurplusPool.claimColl(msg.sender); - } } // --- Helper functions --- @@ -1163,11 +1111,7 @@ contract BorrowerOperations is address borrower, uint256 gross ) private { - // delegatecall to a code-less address SUCCEEDS with empty returndata, - // which would silently skip the fee and the delay. Require code. - address ops = perimeterOps; - checkContract(ops); - (bool ok, bytes memory ret) = ops.delegatecall( + _delegateToPerimeterOps( abi.encodeWithSelector( BorrowerOperationsPerimeterOps(address(0)).sendCollWithExitFee.selector, _activePool, @@ -1175,6 +1119,19 @@ contract BorrowerOperations is gross ) ); + } + + /// @dev Run one perimeter settlement in this proxy's context and transaction. + /// Any failure propagates unchanged, so a reverting settlement reverts + /// the whole close, adjust or claim rather than paying direct. + /// + /// `checkContract` is what makes that true: a `delegatecall` to a + /// code-less address SUCCEEDS with empty returndata, so an unset hook + /// would otherwise skip the fee and the delay in silence. + function _delegateToPerimeterOps(bytes memory payload) private { + address ops = perimeterOps; + checkContract(ops); + (bool ok, bytes memory ret) = ops.delegatecall(payload); if (!ok) { assembly { revert(add(ret, 0x20), mload(ret)) diff --git a/contracts/CollSurplusPool.sol b/contracts/CollSurplusPool.sol index d9e08d6..c25e64f 100644 --- a/contracts/CollSurplusPool.sol +++ b/contracts/CollSurplusPool.sol @@ -117,6 +117,50 @@ contract CollSurplusPool is CollSurplusPoolStorage, CheckContract, ICollSurplusP require(success, "CollSurplusPool: sending ETH failed"); } + /// @notice Two-leg claim that sends the remainder to `_netRecipient`. + /// Same accounting and same CEI ordering as `claimCollWithFee`: the + /// claim is resolved against `_account`'s balance, which is zeroed + /// before either external call, and the single `ETH` decrement equals + /// fee + net exactly. Only the destination of the net leg differs, so + /// the perimeter can escrow it in the exit delay queue instead of + /// paying the claimant directly. The fee leg stays fail-open and the + /// net leg fail-closed. + /// + /// `claimCollWithFee` is left exactly as deployed rather than + /// delegating here: it is the selector the shipped BorrowerOperations + /// calls, and it is the rollback target. + function claimCollWithFeeTo( + address _account, + address _feeReceiver, + uint256 _feeAmount, + address _netRecipient + ) external override returns (bool feePaid, uint256 netAmount) { + _requireCallerIsBorrowerOperations(); + require(_netRecipient != address(0), "CollSurplusPool: zero net recipient"); + uint256 claimableColl = balances[_account]; + require(claimableColl > 0, "CollSurplusPool: No collateral available to claim"); + require(_feeAmount <= claimableColl, "CollSurplusPool: fee exceeds claimable"); + + balances[_account] = 0; + emit CollBalanceUpdated(_account, 0); + + ETH = ETH.sub(claimableColl); + + // A zero fee takes no leg at all: a zero-value call would report a fee + // that was never charged, and an uncharged claim can still be delayed. + if (_feeAmount > 0) { + (feePaid, ) = _feeReceiver.call{ value: _feeAmount, gas: FEE_LEG_GAS_CAP }(""); + if (feePaid) { + emit EtherSent(_feeReceiver, _feeAmount); + } + } + netAmount = feePaid ? claimableColl.sub(_feeAmount) : claimableColl; + + emit EtherSent(_netRecipient, netAmount); + (bool success, ) = _netRecipient.call{ value: netAmount }(""); + require(success, "CollSurplusPool: sending ETH failed"); + } + // --- 'require' functions --- function _requireCallerIsBorrowerOperations() internal view { diff --git a/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol b/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol index 841e3d8..2ac2234 100644 --- a/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol +++ b/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol @@ -3,28 +3,39 @@ pragma solidity 0.6.11; pragma experimental ABIEncoderV2; -import "../BorrowerOperationsStorage.sol"; import "../Interfaces/IActivePool.sol"; +import "../Interfaces/ICollSurplusPool.sol"; import "../Interfaces/perimeter/IExitFeeController.sol"; import "../Interfaces/perimeter/IExitDelayQueueHook.sol"; /// @title BorrowerOperationsPerimeterOps -/// @notice Settles a Zero borrower collateral payout through the security -/// perimeter: the exit fee leg, then the delay leg. +/// @notice Settles a Zero borrower collateral payout, and a surplus claim, +/// through the security perimeter: the exit fee leg, then the delay leg. /// /// @dev Used via `delegatecall` from BorrowerOperations, the same way -/// TroveManagerRedeemOps is used from TroveManager. It shares -/// BorrowerOperationsStorage, so storage, `address(this)` and -/// `msg.sender` are the caller\'s, the pointer slots it reads are the -/// caller\'s, and the events it emits carry the caller\'s address. It -/// declares no storage of its own. -contract BorrowerOperationsPerimeterOps is BorrowerOperationsStorage { +/// TroveManagerRedeemOps is used from TroveManager, so `address(this)` +/// and `msg.sender` are the caller\'s, the unstructured pointer slots it +/// reads are the caller\'s, and the events it emits carry the caller\'s +/// address. +/// +/// It declares NO STORAGE and inherits none, deliberately. A companion +/// that inherited BorrowerOperationsStorage would appear to share the +/// caller\'s variables while its slots sat four words earlier, because +/// BorrowerOperations also inherits LiquityBase and this contract does +/// not: reading `collSurplusPool` here would return +/// BorrowerOperations\' `liquityBaseParams`. Everything this contract +/// needs from the caller\'s state therefore arrives as an argument, and +/// only the two EIP-1967-style slots — whose addresses are constants, +/// not declaration order — are read directly. +contract BorrowerOperationsPerimeterOps { bytes32 private constant EXIT_FEE_CONTROLLER_SLOT = bytes32(uint256(keccak256("sovryn.perimeterExitFeeController")) - 1); bytes32 private constant EXIT_DELAY_QUEUE_SLOT = bytes32(uint256(keccak256("sovryn.perimeterExitDelayQueue")) - 1); bytes32 private constant PERIMETER_SURFACE_ZERO_WITHDRAW_COLL = keccak256("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); + bytes32 private constant PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS = + keccak256("PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS"); event ExitFeeApplied( bytes32 indexed surfaceId, @@ -93,7 +104,12 @@ contract BorrowerOperationsPerimeterOps is BorrowerOperationsStorage { // NEVER touched here — only inside the `d > 0` branch of `_payUserColl` //. DelayLeg memory dl; - (dl.d, dl.effOrig, dl.effOwner) = _safeQuoteExitDelay(borrower, borrower, borrower); + (dl.d, dl.effOrig, dl.effOwner) = _safeQuoteExitDelay( + borrower, + borrower, + borrower, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL + ); // Single Zero deployment: subProduct = address(0). Asset is native RBTC. IExitFeeController.ExitFeeQuote memory q = _safeQuote( @@ -199,6 +215,145 @@ contract BorrowerOperationsPerimeterOps is BorrowerOperationsStorage { } } + /// @notice Settle a surplus claim through the perimeter: the fee leg, then + /// the delay leg. + /// + /// @dev The claimant is `msg.sender`, preserved across the delegatecall + /// from BorrowerOperations. The pool arrives as an argument because + /// this contract declares no storage and cannot read the caller's. + /// + /// The delay is quoted ONCE, up front, so a fee-vault failure still + /// escrows the gross behind the hold and cannot bypass it — the same + /// rule the collateral exit follows. + /// + /// When the perimeter imposes no delay this is the existing claim, + /// unchanged: `claimCollWithFee` on the charging path and the + /// untouched `claimColl` otherwise, both paying the claimant + /// directly. When it does, the pool sends the net leg to the queue + /// instead and the record follows in the same transaction. A record + /// failure reverts the whole claim, so a hold can never be silently + /// skipped; the claimant keeps their surplus balance and can claim + /// again once the queue is healthy. + function claimSurplusWithPerimeter(ICollSurplusPool pool) external { + address claimant = msg.sender; + uint256 gross = pool.getCollateral(claimant); + + // FAIL-CLOSED: a controller revert reverts the claim. Zero has no + // passthrough, so originator == owner == receiver == the claimant. + DelayLeg memory dl; + (dl.d, dl.effOrig, dl.effOwner) = _safeQuoteExitDelay( + claimant, + claimant, + claimant, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS + ); + + // Single Zero deployment: subProduct = address(0). Asset is native RBTC. + IExitFeeController.ExitFeeQuote memory q = _safeQuote( + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, + address(0), + claimant, + gross + ); + + // A quote that charges into address(0) would burn the fee, because a + // value call to a no-code address succeeds. Demote to the non-charging + // path; DISABLED is the enum's "feeReceiver == address(0)" reason. + if (q.active && q.feeAmount > 0 && q.feeReceiver == address(0)) { + q.active = false; + q.netAmount = gross; + q.reason = uint8(IExitFeeController.SkipReason.DISABLED); + } + + address netRecipient = claimant; + if (dl.d > 0) { + netRecipient = exitDelayQueue(); + // FAIL-CLOSED: once the perimeter quotes d>0 the net leg MUST escrow. + // An unwired queue reverts the claim with a DISTINCT selector — a + // hold can never be bypassed by a missing pointer. + require(netRecipient != address(0), "PERIMETER:queue-unset"); + } + + if (q.active && q.feeAmount > 0) { + // Two-leg split inside the pool (fee -> feeReceiver, net -> recipient); + // the pool's fee leg is fail-open and reports which event is truthful. + (bool feePaid, uint256 netAmount) = pool.claimCollWithFeeTo( + claimant, + q.feeReceiver, + q.feeAmount, + netRecipient + ); + _recordSurplusExit(netRecipient, netAmount, claimant, dl); + if (feePaid) { + emit ExitFeeApplied( + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, + claimant, + address(0), + address(0), + claimant, + gross, + q.feeAmount, + q.netAmount, + q.feeReceiver + ); + } else { + emit ExitFeeSkipped( + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, + claimant, + address(0), + gross, + q.rateBps, + uint8(IExitFeeController.SkipReason.VAULT_REVERT) + ); + } + return; + } + + // !active (INACTIVE / DISABLED / INVALID_QUOTE / CONTROLLER_REVERT) OR + // active-but-zero-fee (dust / zero-rate / gross == 0 -> reason NONE). + emit ExitFeeSkipped( + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, + claimant, + address(0), + gross, + q.rateBps, + q.reason + ); + if (dl.d > 0) { + (, uint256 netAmount) = pool.claimCollWithFeeTo(claimant, address(0), 0, netRecipient); + _recordSurplusExit(netRecipient, netAmount, claimant, dl); + } else { + // Untouched original path. A zero surplus falls through to + // claimColl's own revert, identical to a claim without the perimeter. + pool.claimColl(claimant); + } + } + + /// @dev Record a surplus net leg the pool has just pushed into the queue. + /// A no-delay claim never reaches the queue, and a fully-charged claim + /// (net == 0) has nothing to escrow — the queue rejects a zero amount, + /// and there is no exit for the claimant to execute later. + function _recordSurplusExit( + address queue, + uint256 netAmount, + address claimant, + DelayLeg memory dl + ) private { + if (dl.d == 0 || netAmount == 0) { + return; + } + require(netAmount <= uint256(uint128(-1)), "PERIMETER:amount-too-large"); + IExitDelayQueueHook(queue).recordReceivedNativeExit( + uint128(netAmount), + dl.d, + PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS, + address(0), + dl.effOrig, + dl.effOwner, + claimant + ); + } + /// @dev Fail-open quote wrapper. On a missing/reverting controller or a /// semantically invalid quote, returns a non-charging quote with /// `netAmount == gross`. The validity gate uses subtraction only @@ -271,7 +426,8 @@ contract BorrowerOperationsPerimeterOps is BorrowerOperationsStorage { function _safeQuoteExitDelay( address rawOriginator, address owner, - address receiver + address receiver, + bytes32 surfaceId ) private view returns (uint32 d, address effOrig, address effOwner) { address ctrl = exitFeeController(); uint256 ctrlSize; @@ -289,7 +445,7 @@ contract BorrowerOperationsPerimeterOps is BorrowerOperationsStorage { rawOriginator, owner, receiver, - PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + surfaceId, address(0) ) returns (uint32 d_, address effOrig_, address effOwner_) { diff --git a/contracts/Interfaces/ICollSurplusPool.sol b/contracts/Interfaces/ICollSurplusPool.sol index 03b9901..a1b8013 100644 --- a/contracts/Interfaces/ICollSurplusPool.sol +++ b/contracts/Interfaces/ICollSurplusPool.sol @@ -57,4 +57,26 @@ interface ICollSurplusPool { address _feeReceiver, uint256 _feeAmount ) external returns (bool feePaid); + + /// @notice Two-leg claim that pays the remainder to `_netRecipient` instead of + /// to `_account`. Only callable by BorrowerOperations. The claim is + /// still resolved against `_account`'s balance and the account keeps + /// its claim: the recipient only names where the net leg is sent, so + /// the perimeter can escrow it in the exit delay queue. Passing + /// `_account` as the recipient is exactly `claimCollWithFee`. + /// A zero `_feeAmount` skips the fee leg entirely rather than making + /// a zero-value call, so an uncharged-but-delayed claim does not + /// report a fee that was never taken. + /// @param _account account whose claimable collateral is paid out + /// @param _feeReceiver Perimeter fee destination for the fee leg + /// @param _feeAmount fee in wei; must not exceed the account's claimable balance + /// @param _netRecipient destination of the remainder after the fee leg + /// @return feePaid true iff a fee leg ran and succeeded + /// @return netAmount wei actually sent to `_netRecipient` + function claimCollWithFeeTo( + address _account, + address _feeReceiver, + uint256 _feeAmount, + address _netRecipient + ) external returns (bool feePaid, uint256 netAmount); } diff --git a/tests-perimeter/ClaimSurplus.notouch.test.js b/tests-perimeter/ClaimSurplus.notouch.test.js deleted file mode 100644 index cf93d6b..0000000 --- a/tests-perimeter/ClaimSurplus.notouch.test.js +++ /dev/null @@ -1,188 +0,0 @@ -// Perimeter security perimeter — surplus-claim DELAY exemption pinning test. -// -// PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS is exempt by design from the exit-delay -// perimeter: surplus is involuntary in origin (full redemption or -// recovery-mode liquidation), is not attacker-creatable without capital, and -// rerouting it would widen the custody pool for thin marginal protection. So -// with the perimeter ACTIVE (controller enabled, d>0) and the queue WIRED, -// claimCollateral() still pays the claimant INSTANTLY — fee-ON (pool-side -// two-leg split) and fee-OFF (untouched claimColl path) alike — and the queue -// is never touched. The control test proves the SAME arming reroutes a -// voluntary withdrawColl, so the no-touch assertions are non-vacuous. -// -// This exemption is a deliberate, reviewable choice, not an oversight. If a -// delay leg is ever added to the surplus claim, retire this suite together -// with that change rather than deleting it on its own. - -const deploymentHelper = require("../utils/js/deploymentHelpers.js"); -const testHelpers = require("../utils/js/testHelpers.js"); -const timeMachine = require("ganache-time-traveler"); - -const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); -const TroveManagerTester = artifacts.require("TroveManagerTester"); -const MassetManagerTester = artifacts.require("MassetManagerTester"); -const ExitFeeControllerMock = artifacts.require("ExitFeeControllerMock"); -const MockExitDelayQueue = artifacts.require("MockExitDelayQueue"); - -const th = testHelpers.TestHelper; -const dec = th.dec; -const toBN = th.toBN; -const timeValues = testHelpers.TimeValues; -const ZERO_ADDRESS = th.ZERO_ADDRESS; - -const NONE = 0; -const DELAY = 3600; -const MIN_DELAY = 100; -const GAS_PRICE = toBN(dec(1, 9)); - -contract("Perimeter delay — surplus claim EXEMPT (no-touch pinning)", async (accounts) => { - const [owner, alice, whale, dennis] = accounts; - const feeReceiver = accounts[995]; - const multisig = accounts[999]; - - let priceFeed; - let collSurplusPool; - let borrowerOperations; - let controller; - let queue; - let contracts; - - const openTrove = async (params) => th.openTrove(contracts, params); - const getEvent = (tx, name) => tx.logs.find((l) => l.event === name); - - before(async () => { - contracts = await deploymentHelper.deployLiquityCore(); - const permit2 = contracts.permit2; - - contracts.borrowerOperations = await BorrowerOperationsTester.new(permit2.address); - contracts.massetManager = await MassetManagerTester.new(); - contracts.troveManager = await TroveManagerTester.new(permit2.address); - contracts = await deploymentHelper.deployZUSDTokenTester(contracts); - const ZEROContracts = await deploymentHelper.deployZEROTesterContractsHardhat(multisig); - - await ZEROContracts.zeroToken.unprotectedMint(multisig, toBN(dec(20, 24))); - - await deploymentHelper.connectZEROContracts(ZEROContracts); - await deploymentHelper.connectCoreContracts(contracts, ZEROContracts); - await deploymentHelper.connectZEROContractsToCore(ZEROContracts, contracts); - - priceFeed = contracts.priceFeedTestnet; - collSurplusPool = contracts.collSurplusPool; - borrowerOperations = contracts.borrowerOperations; - - await borrowerOperations.setMassetManagerAddress(contracts.massetManager.address); - }); - - let snapshotId; - beforeEach(async () => { - const snap = await timeMachine.takeSnapshot(); - snapshotId = snap["result"]; - controller = await ExitFeeControllerMock.new(); - queue = await MockExitDelayQueue.new(MIN_DELAY); - await queue.setAllowedSource(borrowerOperations.address, true); - // Perimeter ACTIVE + queue WIRED — identical arming to the reroute suites, - // so a delay leg on the surplus claim WOULD fire here if one existed. - await borrowerOperations.setExitFeeController(controller.address, { from: owner }); - await borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); - await controller.configureDelay(true, DELAY); - }); - afterEach(async () => { - await timeMachine.revertToSnapshot(snapshotId); - }); - - // Same surplus fixture as ZeroClaimSurplus.test.js: fully redeem a ~200%-ICR - // trove at ETH:USD = 100; surplus == coll - netDebt/price stays for claimant. - const setupSurplus = async (claimant) => { - const price = toBN(dec(100, 18)); - await priceFeed.setPrice(price); - const { netDebt } = await openTrove({ - ICR: toBN(dec(200, 16)), - extraParams: { from: claimant }, - }); - await openTrove({ - extraZUSDAmount: netDebt, - extraParams: { from: whale, value: dec(3000, "ether") }, - }); - await th.fastForwardTime(timeValues.SECONDS_IN_ONE_WEEK * 2, web3.currentProvider); - await th.redeemCollateralAndGetTxObject(whale, contracts, netDebt); - const gross = await collSurplusPool.getCollateral(claimant); - assert.isTrue(gross.gt(toBN(0)), "setup failed: no surplus created"); - return gross; - }; - - const assertQueueUntouched = async () => { - assert.equal((await queue.lastRequestId()).toString(), "0", "queue recorded a request"); - assert.isTrue( - toBN(await web3.eth.getBalance(queue.address)).eq(toBN(0)), - "queue escrowed RBTC" - ); - assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(toBN(0))); - }; - - it("CONTROL (non-vacuous): the SAME arming reroutes a voluntary withdrawColl into the queue", async () => { - await priceFeed.setPrice(dec(200, 18)); - await openTrove({ - ICR: toBN(dec(10, 18)), - extraParams: { from: dennis, value: toBN(dec(100, "ether")) }, - }); - const amount = toBN(dec(1, "ether")); - await borrowerOperations.withdrawColl(amount, dennis, dennis, { from: dennis }); - - assert.equal( - (await queue.lastRequestId()).toString(), - "1", - "arming is vacuous: withdrawColl did not reroute" - ); - assert.isTrue( - (await queue.totalEscrowed(ZERO_ADDRESS)).eq(amount), - "escrowed != withdrawn gross" - ); - }); - - it("fee-OFF claim: claimant paid FULL gross instantly, queue untouched", async () => { - const gross = await setupSurplus(alice); - - const aliceBefore = toBN(await web3.eth.getBalance(alice)); - const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); - const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); - - assert.isTrue( - toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(gross).sub(gasCost)), - "claimant != +FULL gross instantly" - ); - assert.isTrue( - (await collSurplusPool.getCollateral(alice)).eq(toBN(0)), - "claimable not zeroed" - ); - assert.isDefined(getEvent(tx, "ExitFeeSkipped"), "fee-off path must emit ExitFeeSkipped"); - await assertQueueUntouched(); - }); - - it("fee-ON claim (50 bps): fee→feeReceiver + net→claimant instantly, queue untouched", async () => { - const gross = await setupSurplus(alice); - await controller.configure(true, 50, feeReceiver, NONE); - - const fee = gross.mul(toBN(50)).div(toBN(10000)); - const net = gross.sub(fee); - - const frBefore = toBN(await web3.eth.getBalance(feeReceiver)); - const aliceBefore = toBN(await web3.eth.getBalance(alice)); - const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); - const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); - - assert.isTrue( - toBN(await web3.eth.getBalance(feeReceiver)).eq(frBefore.add(fee)), - "feeReceiver != +fee" - ); - assert.isTrue( - toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(net).sub(gasCost)), - "claimant != +net instantly" - ); - assert.isTrue( - (await collSurplusPool.getCollateral(alice)).eq(toBN(0)), - "claimable not zeroed" - ); - assert.isDefined(getEvent(tx, "ExitFeeApplied"), "charging path must emit ExitFeeApplied"); - await assertQueueUntouched(); - }); -}); diff --git a/tests-perimeter/StorageLayout.zerodiff.test.js b/tests-perimeter/StorageLayout.zerodiff.test.js index 8bccdfa..817f149 100644 --- a/tests-perimeter/StorageLayout.zerodiff.test.js +++ b/tests-perimeter/StorageLayout.zerodiff.test.js @@ -41,7 +41,7 @@ const assert = require("assert"); const fs = require("fs"); const path = require("path"); -const { normalizedLayout } = require("./utils/storageLayout.js"); +const { normalizedLayout, rawLayout } = require("./utils/storageLayout.js"); const BASELINE = path.join(__dirname, "baselines", "storage-layout.sovryn-perimeter-fee.json"); @@ -74,6 +74,28 @@ describe("Perimeter — storage-layout upgrade safety (surplus-claim fee hook + } }); + // The settlement companion runs under `delegatecall` in BorrowerOperations' + // context, but it does NOT share BorrowerOperations' plain-storage layout: + // BorrowerOperations also inherits LiquityBase, so any variable declared here + // would sit four words earlier than the same-named variable there. Declaring + // no storage at all is what makes that impossible to get wrong; everything + // the companion needs from the caller's state arrives as an argument, and the + // only slots it reads directly are the EIP-1967-style ones, addressed by + // constant rather than by declaration order. + it("the settlement companion declares no storage at all", async () => { + const layout = await rawLayout( + "contracts/Dependencies/BorrowerOperationsPerimeterOps.sol:BorrowerOperationsPerimeterOps" + ); + assert.deepStrictEqual( + layout, + [], + "BorrowerOperationsPerimeterOps declared storage: under delegatecall its slots do " + + "NOT line up with BorrowerOperations', so a read or write here corrupts the " + + "caller. Pass the value in as an argument instead.\n" + + ` declared: ${JSON.stringify(layout)}` + ); + }); + for (const { fq, policy } of TARGETS) { if (policy === ZERO_DIFF) { it(`${fq}: current layout == sovryn-perimeter-fee baseline (no appended state)`, async () => { diff --git a/tests-perimeter/ZeroClaimSurplus.delay.test.js b/tests-perimeter/ZeroClaimSurplus.delay.test.js new file mode 100644 index 0000000..af0eee7 --- /dev/null +++ b/tests-perimeter/ZeroClaimSurplus.delay.test.js @@ -0,0 +1,326 @@ +// Perimeter security perimeter — surplus-claim DELAY hook +// (surface PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS). +// +// The surplus claim composes the fee leg and the delay leg the same way the +// voluntary collateral exit does, with one structural difference: the pool, not +// ActivePool, holds the funds, so the net leg is sent by +// `CollSurplusPool.claimCollWithFeeTo` and recorded by BorrowerOperations in the +// same transaction. +// +// - d>0 ⇒ the claimant's leg (net on fee-ok, GROSS on the uncharged path) is +// PUSHED to the queue by the pool and recorded via recordReceivedNativeExit +// in the SAME tx; the claimant is NOT paid directly; +// - d==0 (perimeter disabled / unwired controller) ⇒ the untouched claim, and +// the queue is NEVER touched; +// - the FEE leg stays fail-OPEN and the DELAY leg fail-CLOSED: an unwired +// queue or a reverting record reverts the whole claim and the claimant keeps +// their surplus balance to claim again; +// - conservation: escrowed(net) + fee == gross, the pool drained by gross; +// - executeExit pays the claimant after unlock. +// +// This supersedes the earlier exemption pinning: the surplus surface was +// initially delay-exempt by design, and that decision was reversed. + +const deploymentHelper = require("../utils/js/deploymentHelpers.js"); +const testHelpers = require("../utils/js/testHelpers.js"); +const timeMachine = require("ganache-time-traveler"); + +const BorrowerOperationsTester = artifacts.require("./BorrowerOperationsTester.sol"); +const TroveManagerTester = artifacts.require("TroveManagerTester"); +const MassetManagerTester = artifacts.require("MassetManagerTester"); +const ExitFeeControllerMock = artifacts.require("ExitFeeControllerMock"); +const MockExitDelayQueue = artifacts.require("MockExitDelayQueue"); + +const th = testHelpers.TestHelper; +const dec = th.dec; +const toBN = th.toBN; +const timeValues = testHelpers.TimeValues; +const ZERO_ADDRESS = th.ZERO_ADDRESS; + +const NONE = 0; +const DELAY = 3600; // 1h +const MIN_DELAY = 100; +const GAS_PRICE = toBN(dec(1, 9)); +const SURFACE = web3.utils.keccak256("PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS"); + +contract("Perimeter delay — Zero surplus claim reroute", async (accounts) => { + const [owner, alice, whale, dennis] = accounts; + const feeReceiver = accounts[995]; + const multisig = accounts[999]; + + let priceFeed; + let collSurplusPool; + let borrowerOperations; + let controller; + let queue; + let contracts; + + const openTrove = async (params) => th.openTrove(contracts, params); + const getEvent = (tx, name) => tx.logs.find((l) => l.event === name); + + before(async () => { + contracts = await deploymentHelper.deployLiquityCore(); + const permit2 = contracts.permit2; + + contracts.borrowerOperations = await BorrowerOperationsTester.new(permit2.address); + contracts.massetManager = await MassetManagerTester.new(); + contracts.troveManager = await TroveManagerTester.new(permit2.address); + contracts = await deploymentHelper.deployZUSDTokenTester(contracts); + const ZEROContracts = await deploymentHelper.deployZEROTesterContractsHardhat(multisig); + + await ZEROContracts.zeroToken.unprotectedMint(multisig, toBN(dec(20, 24))); + + await deploymentHelper.connectZEROContracts(ZEROContracts); + await deploymentHelper.connectCoreContracts(contracts, ZEROContracts); + await deploymentHelper.connectZEROContractsToCore(ZEROContracts, contracts); + + priceFeed = contracts.priceFeedTestnet; + collSurplusPool = contracts.collSurplusPool; + borrowerOperations = contracts.borrowerOperations; + + await borrowerOperations.setMassetManagerAddress(contracts.massetManager.address); + }); + + let snapshotId; + beforeEach(async () => { + const snap = await timeMachine.takeSnapshot(); + snapshotId = snap["result"]; + controller = await ExitFeeControllerMock.new(); + queue = await MockExitDelayQueue.new(MIN_DELAY); + await queue.setAllowedSource(borrowerOperations.address, true); + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await controller.configureDelay(true, DELAY); + }); + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + + // Fully redeem a ~200%-ICR trove at ETH:USD = 100; the surplus + // (coll - netDebt/price) stays claimable by its owner. + const setupSurplus = async (claimant) => { + const price = toBN(dec(100, 18)); + await priceFeed.setPrice(price); + const { netDebt } = await openTrove({ + ICR: toBN(dec(200, 16)), + extraParams: { from: claimant }, + }); + await openTrove({ + extraZUSDAmount: netDebt, + extraParams: { from: whale, value: dec(3000, "ether") }, + }); + await th.fastForwardTime(timeValues.SECONDS_IN_ONE_WEEK * 2, web3.currentProvider); + await th.redeemCollateralAndGetTxObject(whale, contracts, netDebt); + const gross = await collSurplusPool.getCollateral(claimant); + assert.isTrue(gross.gt(toBN(0)), "setup failed: no surplus created"); + return gross; + }; + + // The queue pointer rejects address(0) by design, so "unwired" can only be + // expressed by never setting it — hence wiring is per-test, not shared setup. + const wireQueue = () => borrowerOperations.setExitDelayQueue(queue.address, { from: owner }); + + const assertQueueUntouched = async () => { + assert.equal((await queue.lastRequestId()).toString(), "0", "queue recorded a request"); + assert.isTrue( + toBN(await web3.eth.getBalance(queue.address)).eq(toBN(0)), + "queue escrowed RBTC" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(toBN(0))); + }; + + it("CONTROL (non-vacuous): the SAME arming reroutes a voluntary withdrawColl", async () => { + await wireQueue(); + await priceFeed.setPrice(dec(200, 18)); + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: dennis, value: toBN(dec(100, "ether")) }, + }); + const amount = toBN(dec(1, "ether")); + await borrowerOperations.withdrawColl(amount, dennis, dennis, { from: dennis }); + + assert.equal((await queue.lastRequestId()).toString(), "1"); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(amount)); + }); + + it("fee-OFF claim (d>0): FULL gross escrowed, claimant NOT paid, pool drained", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.sub(gasCost)), + "claimant was paid directly despite the hold" + ); + assert.isTrue( + (await collSurplusPool.getCollateral(alice)).eq(toBN(0)), + "claimable not zeroed" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(gross), "escrowed != gross"); + assert.isDefined( + getEvent(tx, "ExitFeeSkipped"), + "uncharged path must emit ExitFeeSkipped" + ); + + const request = await queue.getRequest(1); + assert.equal(request.receiver, alice, "receiver must be the claimant"); + assert.equal(request.originator, alice); + assert.equal(request.owner, alice); + assert.equal(request.surfaceId, SURFACE, "wrong surface recorded"); + assert.equal(request.token, ZERO_ADDRESS, "surplus is native RBTC"); + }); + + it("fee-ON claim (50 bps, d>0): fee to the receiver, NET escrowed, sums to gross", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await controller.configure(true, 50, feeReceiver, NONE); + + const fee = gross.mul(toBN(50)).div(toBN(10000)); + const net = gross.sub(fee); + + const frBefore = toBN(await web3.eth.getBalance(feeReceiver)); + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue( + toBN(await web3.eth.getBalance(feeReceiver)).eq(frBefore.add(fee)), + "feeReceiver != +fee" + ); + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.sub(gasCost)), + "claimant was paid directly despite the hold" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(net), "escrowed != net"); + assert.isTrue( + (await queue.totalEscrowed(ZERO_ADDRESS)).add(fee).eq(gross), + "escrowed + fee != gross" + ); + assert.isDefined(getEvent(tx, "ExitFeeApplied"), "charging path must emit ExitFeeApplied"); + }); + + it("perimeter disabled (d==0): the untouched claim, queue NEVER touched", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await controller.configureDelay(false, DELAY); + + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(gross).sub(gasCost)), + "claimant != +FULL gross instantly" + ); + await assertQueueUntouched(); + }); + + it("perimeter disabled (d==0), fee ON: fee and net both paid instantly", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await controller.configureDelay(false, DELAY); + await controller.configure(true, 50, feeReceiver, NONE); + + const fee = gross.mul(toBN(50)).div(toBN(10000)); + const net = gross.sub(fee); + + const frBefore = toBN(await web3.eth.getBalance(feeReceiver)); + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + const tx = await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue(toBN(await web3.eth.getBalance(feeReceiver)).eq(frBefore.add(fee))); + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(net).sub(gasCost)), + "claimant != +net instantly" + ); + await assertQueueUntouched(); + }); + + it("FAIL-CLOSED: d>0 but the queue is unwired ⇒ the whole claim reverts", async () => { + // wireQueue() deliberately NOT called: the controller quotes a hold and + // there is nowhere to escrow it. + const gross = await setupSurplus(alice); + + await th.assertRevert( + borrowerOperations.claimCollateral({ from: alice }), + "PERIMETER:queue-unset" + ); + assert.isTrue( + (await collSurplusPool.getCollateral(alice)).eq(gross), + "surplus must survive a refused claim" + ); + }); + + it("FAIL-CLOSED: a reverting delay quote reverts the whole claim", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await controller.setDelayRevert(true); + + await th.assertRevert( + borrowerOperations.claimCollateral({ from: alice }), + "PERIMETER:delay-quote-failed" + ); + assert.isTrue((await collSurplusPool.getCollateral(alice)).eq(gross)); + }); + + it("FAIL-CLOSED: a bricked queue (record reverts) reverts the whole claim", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await queue.setAllowedSource(borrowerOperations.address, false); + + await th.assertRevert(borrowerOperations.claimCollateral({ from: alice })); + assert.isTrue( + (await collSurplusPool.getCollateral(alice)).eq(gross), + "surplus must survive a refused claim" + ); + await assertQueueUntouched(); + }); + + it("100% fee policy (d>0): nothing to escrow, the claim still settles", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await controller.configure(true, 10000, feeReceiver, NONE); + + const frBefore = toBN(await web3.eth.getBalance(feeReceiver)); + const tx = await borrowerOperations.claimCollateral({ from: alice }); + + assert.isTrue( + toBN(await web3.eth.getBalance(feeReceiver)).eq(frBefore.add(gross)), + "feeReceiver != +gross" + ); + assert.isDefined(getEvent(tx, "ExitFeeApplied")); + await assertQueueUntouched(); + }); + + it("executeExit: reverts before unlock, pays the claimant after", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await borrowerOperations.claimCollateral({ from: alice, gasPrice: GAS_PRICE }); + + await th.assertRevert(queue.executeExit(1, { from: alice }), "MockQueue: not unlocked"); + + await th.fastForwardTime(DELAY + 1, web3.currentProvider); + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + const tx = await queue.executeExit(1, { from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(gross).sub(gasCost)), + "claimant != +gross after unlock" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(toBN(0))); + }); + + it("block-trap: a frozen claimant cannot execute the escrowed claim", async () => { + await wireQueue(); + await setupSurplus(alice); + await borrowerOperations.claimCollateral({ from: alice }); + await th.fastForwardTime(DELAY + 1, web3.currentProvider); + await queue.freeze(alice); + + await th.assertRevert(queue.executeExit(1, { from: alice }), "MockQueue: actor blocked"); + }); +}); diff --git a/tests-perimeter/utils/storageLayout.js b/tests-perimeter/utils/storageLayout.js index 124f12e..0533fda 100644 --- a/tests-perimeter/utils/storageLayout.js +++ b/tests-perimeter/utils/storageLayout.js @@ -13,10 +13,10 @@ const hre = require("hardhat"); const normType = (t) => (typeof t === "string" ? t.replace(/\)[0-9]+/g, ")") : t); // Return a stable, comparable array of {label, slot, offset, type} for the -// contract's declared state variables. Throws (never silently empties) if the -// layout is missing/empty — an empty layout compared to an empty layout is a -// silent false PASS, which would make this guard useless. -async function normalizedLayout(fqName) { +// contract's declared state variables. Throws if the layout is missing, but +// ALLOWS an empty one: a contract that declares no storage is a real and +// checkable property (the delegatecall companion depends on it). +async function rawLayout(fqName) { const bi = await hre.artifacts.getBuildInfo(fqName); if (!bi) throw new Error(`no build-info for ${fqName} (compile with storageLayout enabled)`); const [source, name] = fqName.split(":"); @@ -26,11 +26,6 @@ async function normalizedLayout(fqName) { if (!layout || !Array.isArray(layout.storage)) { throw new Error(`no storageLayout for ${fqName} — is "storageLayout" in outputSelection?`); } - if (layout.storage.length === 0) { - throw new Error( - `${fqName} storageLayout has ZERO entries — refusing to treat as zero-diff (silent false pass)` - ); - } return layout.storage .map((s) => ({ label: s.label, @@ -46,4 +41,17 @@ async function normalizedLayout(fqName) { ); } -module.exports = { normalizedLayout, normType }; +// Same as `rawLayout`, but refuses an empty layout: comparing an empty layout +// against an empty baseline is a silent false PASS, which would make the +// zero-diff and append-only guards useless. +async function normalizedLayout(fqName) { + const layout = await rawLayout(fqName); + if (layout.length === 0) { + throw new Error( + `${fqName} storageLayout has ZERO entries — refusing to treat as zero-diff (silent false pass)` + ); + } + return layout; +} + +module.exports = { normalizedLayout, rawLayout, normType };