diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3b1f031 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,61 @@ +name: Tests + +on: + push: + workflow_dispatch: + +concurrency: + group: tests-${{ github.ref }} + cancel-in-progress: true + +env: + # The hardhat config decrypts an optional secrets env on load and prompts + # for a password when it is absent; a runner has no terminal to answer. + __decryptionAlreadyDone__: "TRUE" + NODE_OPTIONS: --max-old-space-size=4096 + +jobs: + test: + name: ${{ matrix.suite }} + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - suite: liquity + command: yarn hardhat test + - suite: perimeter + command: yarn hardhat test tests-perimeter/*.test.js + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + + - name: Cache Solidity compilers + uses: actions/cache@v4 + with: + path: ~/.cache/hardhat-nodejs + key: hardhat-compilers-${{ runner.os }}-${{ hashFiles('hardhat.config.ts') }} + restore-keys: hardhat-compilers-${{ runner.os }}- + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Restore compiler execute bits + # A cache restore can drop the execute bit on the downloaded solc + # binaries; hardhat then fails to spawn them. + run: | + if [ -d "$HOME/.cache/hardhat-nodejs" ]; then + find "$HOME/.cache/hardhat-nodejs" -type f -name 'solc-*' -exec chmod +x {} + + fi + + - name: Compile + run: yarn hardhat compile + + - name: Test + run: ${{ matrix.command }} diff --git a/contracts/BorrowerOperations.sol b/contracts/BorrowerOperations.sol index 7a7e525..13b997a 100644 --- a/contracts/BorrowerOperations.sol +++ b/contracts/BorrowerOperations.sol @@ -17,6 +17,8 @@ import "./BorrowerOperationsStorage.sol"; 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, @@ -37,7 +39,18 @@ contract BorrowerOperations is 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 + // slot, so this pointer consumes no regular storage slot. 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.perimeterExitDelayQueue")) - 1); + event ExitFeeControllerSet(address indexed previous, address indexed current); + event ExitDelayQueueSet(address indexed previous, address indexed current); + event PerimeterOpsSet(address indexed previous, address indexed current); event ExitFeeApplied( bytes32 indexed surfaceId, address indexed actor, @@ -817,78 +830,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 --- @@ -1004,6 +965,39 @@ 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-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 @@ -1057,76 +1051,55 @@ 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; - } - - // Single Zero deployment: subProduct = address(0). Asset is native RBTC. - IExitFeeController.ExitFeeQuote memory q = _safeQuote( - PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, - address(0), - borrower, - gross + _delegateToPerimeterOps( + abi.encodeWithSelector( + BorrowerOperationsPerimeterOps(address(0)).sendCollWithExitFee.selector, + _activePool, + borrower, + gross + ) ); + } - if (q.active && q.feeAmount > 0) { - try _activePool.sendETH(q.feeReceiver, q.feeAmount) { - _activePool.sendETH(borrower, q.netAmount); // user leg: existing fail-closed behavior - // 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) - ); + /// @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)) } - } 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 - ); } - _activePool.sendETH(borrower, gross); // full-gross fallback (any non-charging path) + } + + /// @notice Rotate the perimeter settlement hook. `onlyOwner`, mirroring + /// `setTroveManagerRedeemOps`. + function setPerimeterOps(address _perimeterOps) external onlyOwner { + checkContract(_perimeterOps); + address previous = perimeterOps; + perimeterOps = _perimeterOps; + emit PerimeterOpsSet(previous, _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..c319173 100644 --- a/contracts/BorrowerOperationsStorage.sol +++ b/contracts/BorrowerOperationsStorage.sol @@ -36,4 +36,13 @@ 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 by the owner via + /// setPerimeterOps and rotatable the same way, mirroring + /// `troveManagerRedeemOps`. Unset until that call lands, and the + /// settlement delegatecall reverts on a code-less hook, so an + /// implementation upgrade must be followed by setPerimeterOps in the + /// same governance execution or exits revert until it does. + address public perimeterOps; } 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 new file mode 100644 index 0000000..bc7fceb --- /dev/null +++ b/contracts/Dependencies/BorrowerOperationsPerimeterOps.sol @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.11; +pragma experimental ABIEncoderV2; + +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, 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, 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, + 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, + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL + ); + + // Single Zero deployment: subProduct = address(0). Asset is native RBTC. + IExitFeeController.ExitFeeQuote memory q = _safeQuote( + PERIMETER_SURFACE_ZERO_WITHDRAW_COLL, + address(0), + borrower, + 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, the same guard the surplus-claim leg applies. + 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) { + 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 + } + } + + /// @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. + /// + /// With no delay, charging claims use `claimCollWithFeeTo` with the + /// claimant as recipient, preserving the legacy payout semantics. + /// Non-charging claims use the untouched `claimColl` path directly. + /// With a positive hold, 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 + /// (`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, + bytes32 surfaceId + ) 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, + surfaceId, + 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/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/contracts/Interfaces/perimeter/IExitDelayQueue.sol b/contracts/Interfaces/perimeter/IExitDelayQueue.sol new file mode 100644 index 0000000..0bfd6c2 --- /dev/null +++ b/contracts/Interfaces/perimeter/IExitDelayQueue.sol @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: MIT +// 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. +/// @dev Declares the shared types so cross-pragma callers and off-chain +/// tooling share one source of truth; the pragma range is widened so +/// this repo's 0.8.x compilers can import it. The 0.5.x / 0.6.x +/// product hosts consume the four `record*` fns via the minimal +/// cross-pragma stub `IExitDelayQueueHook.sol` instead. +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) + ResolvedByOwner // 4 — Leg-3 Owner resolution (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 Perimeter amount at record + uint64 createdAt; // audit/analytics; stored in the request + 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; executor + stuck-payout recovery + 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 ExitResolvedByOwner(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); // delivery: msg.sender ∉ {originator, owner, receiver} and the owner has no code; recovery: ∉ {originator, owner, receiver} + 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 TopUpDestinationMismatch(address destination, address subProduct); // top-up must pay its own pool + error SourceNotBlacklisted(address src); // Leg-2 OR-predicate not satisfied + error NotBlacklisted(address a); // unblacklist / downgradeToFrozen on a non-Blacklisted address + error NotFrozen(address a); // unfreeze on a non-Frozen address + error AlreadyBlacklisted(address a); // evidence-free freeze over a Blacklisted address + error NotResolvableByOwner(uint256 id); // Leg-3: no blacklisted party on the request + error UnwrapNonWrbtc(); // unwrapOnDelivery set on a non-WRBTC token + error InvalidAltReceiver(address altReceiver); // recoverStuckExit altReceiver ∈ {0,this,token,wrbtc} + error InvalidReceiver(address receiver); // ingress: the queue itself, or WRBTC when delivery sends native RBTC + 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 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 + /// that would silently truncate is rejected UPSTREAM, before any escrow + /// accounting. The queue itself CANNOT re-assert this on an + /// already-`uint128` argument — the `AmountTooLarge` error is declared for + /// that CALLER-side hook boundary, not re-checked here. 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 by the + /// request's originator, its owner, or its recorded receiver, whether + /// or not the owner has code — narrower than delivery on purpose: + /// delivery pays only the recorded receiver, while this call names a + /// destination, so opening it the same way would let anyone take a + /// contract-owned request whose receiver refuses payment. 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; + + /// @notice Move a Blacklisted address down to Frozen in one call — the + /// remedy for an address blacklisted in haste that should only be held + /// while it is investigated. There is no window in which the address is + /// unblocked, unlike unblacklist-then-freeze. Reverts `NotBlacklisted` + /// on any other state; the recorded trigger is kept, because a + /// downgrade means "still under investigation", not "exonerated". + function downgradeToFrozen(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` / + // `resolveByOwner`) — 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; + + function downgradeToFrozen(address[] calldata a) external; + + // ─── Pause ─────────────────────────────────────────────────── + + function setSecurityPerimeterPaused(bool p) external; + + // ─── Recovery ──────────────────────────────────────────────── + + function resolveToProtocol(uint256[] calldata ids, bytes32 routeId) external; + + function resolveByOwner(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/perimeter/IExitDelayQueueHook.sol b/contracts/Interfaces/perimeter/IExitDelayQueueHook.sol new file mode 100644 index 0000000..20b1ce9 --- /dev/null +++ b/contracts/Interfaces/perimeter/IExitDelayQueueHook.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +// 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. +/// @dev Cross-pragma ingress stub: declares only the members the 0.5.17 +/// product hosts call, so it imports under their pragma. The four +/// `record*` signatures here are byte-for-byte identical to the ones +/// in the queue's own `IExitDelayQueue.sol` and must stay that way. +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/perimeter/IExitFeeController.sol b/contracts/Interfaces/perimeter/IExitFeeController.sol index de2d6c8..aaa2cd7 100644 --- a/contracts/Interfaces/perimeter/IExitFeeController.sol +++ b/contracts/Interfaces/perimeter/IExitFeeController.sol @@ -1,13 +1,5 @@ // SPDX-License-Identifier: MIT // ───────────────────────────────────────────────────────────────────────────── -// Vendored copy of the Perimeter exit-fee controller interface, taken from -// DistributedCollective/perimeter @ c85f60aef91bc644517cf1b3ea7c5e8c565f4ca5 -// 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. -// ───────────────────────────────────────────────────────────────────────────── // Range pragma is intentional: the same declarations are compiled under Solidity // 0.5.17, 0.6.11 (this repo), and 0.8.20. // aderyn-ignore-next-line(unspecific-solidity-pragma) @@ -31,7 +23,11 @@ pragma experimental ABIEncoderV2; /// 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. -/// Zero calls only `quoteExitFee`; the rest is declared for completeness. +/// Zero's hooks call `quoteExitFee` and `quoteExitDelayFor`; the rest is +/// a selected operational catalog, not the controller's complete ABI. +/// @dev `quoteExitDelayFor(address,address,address,bytes32,address) view +/// returns (uint32,address,address)` must stay ABI-identical, as +/// other perimeter components call it by that exact selector. interface IExitFeeController { // ─── Types ──────────────────────────────────────────────────────────── @@ -54,6 +50,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. + 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 +94,23 @@ 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 + ); + // ─── Quote ──────────────────────────────────────────────────────────── /// @notice Resolve the fee policy for `(surfaceId, subProduct, actor)` and @@ -100,6 +125,34 @@ 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 quotes the withdrawal delay on the originator and + /// returns the originator and owner unchanged, so the quote and + /// the record share the same identity. The hook MUST ignore + /// `effOrig` / `effOwner` and pay direct whenever `d == 0`. + /// @return d Delay seconds to escrow for (0 ⇒ off / bypassed). + /// @return effOrig The originator, unchanged. + /// @return effOwner The owner, unchanged. + 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 / bypass ⇒ 0, else + /// `globalDelaySeconds`) on the originator; off-chain use. + function quoteExitDelay( + bytes32 surfaceId, + address subProduct, + address effectiveActor + ) external view returns (uint32); + // ─── State views ────────────────────────────────────────────────────── function exitFeeEnabled() external view returns (bool); @@ -122,6 +175,24 @@ 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); + // ─── Admin ──────────────────────────────────────────────────────────── function setExitFeeEnabled(bool enabled) external; @@ -157,4 +228,26 @@ 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; } diff --git a/contracts/TestContracts/BorrowerOperationsTester.sol b/contracts/TestContracts/BorrowerOperationsTester.sol index f13b9ce..3bdfef1 100644 --- a/contracts/TestContracts/BorrowerOperationsTester.sol +++ b/contracts/TestContracts/BorrowerOperationsTester.sol @@ -4,63 +4,76 @@ 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) { + perimeterOps = address(new BorrowerOperationsPerimeterOps()); + } - 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/ClaimLiquidationObserver.sol b/contracts/TestContracts/ClaimLiquidationObserver.sol new file mode 100644 index 0000000..2723bf3 --- /dev/null +++ b/contracts/TestContracts/ClaimLiquidationObserver.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.11; + +interface IClaimBO { + function openTrove(uint256, uint256, address, address) external payable; + + function claimCollateral() external; +} + +interface IClaimTM { + function liquidate(address) external; + + function liquidateTroves(uint256) external; + + function batchLiquidateTroves(address[] calldata) external; +} + +interface IClaimPool { + function getCollateral(address) external view returns (uint256); + + function getETH() external view returns (uint256); +} + +/// @dev Ordinary claimant fixture observing settlement at its own payout callback. +contract ClaimLiquidationObserver { + IClaimBO public bo; + IClaimTM public tm; + IClaimPool public pool; + address public target; + uint8 public mode; + bool public enabled; + bool public observed; + bool public succeeded; + uint256 public claimAtCallback; + uint256 public trackedAtCallback; + uint256 public nativeAtCallback; + uint256 public payoutAtCallback; + bytes32 public refusal; + + constructor(IClaimBO b, IClaimTM t, IClaimPool p) public { + bo = b; + tm = t; + pool = p; + } + + function open(uint256 debt) external payable { + bo.openTrove{ value: msg.value }(1e18, debt, address(0), address(0)); + } + + function claim() external { + bo.claimCollateral(); + } + + function configure(address a, uint8 m, bool e) external { + target = a; + mode = m; + enabled = e; + } + + function settleThenLiquidate() external { + _liquidate(); + } + + receive() external payable { + if (enabled && !observed) { + observed = true; + claimAtCallback = pool.getCollateral(address(this)); + trackedAtCallback = pool.getETH(); + nativeAtCallback = address(pool).balance; + payoutAtCallback = msg.value; + _liquidate(); + } + } + + function _liquidate() private { + if (mode == 0) { + try tm.liquidate(target) { + succeeded = true; + } catch (bytes memory e) { + refusal = keccak256(e); + } + } else if (mode == 1) { + try tm.liquidateTroves(1) { + succeeded = true; + } catch (bytes memory e) { + refusal = keccak256(e); + } + } else { + address[] memory a = new address[](1); + a[0] = target; + try tm.batchLiquidateTroves(a) { + succeeded = true; + } catch (bytes memory e) { + refusal = keccak256(e); + } + } + } +} 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/contracts/TestContracts/ExitFeeControllerMock.sol b/contracts/TestContracts/ExitFeeControllerMock.sol index 2d54de0..b28a6bc 100644 --- a/contracts/TestContracts/ExitFeeControllerMock.sol +++ b/contracts/TestContracts/ExitFeeControllerMock.sol @@ -10,11 +10,12 @@ import "../Interfaces/perimeter/IExitFeeController.sol"; /// cannot be compiled into the 0.6.11 zero-contracts workspace, so the /// hooks are exercised against this configurable stand-in. /// -/// Only `quoteExitFee` is implemented (the single selector the product -/// hook calls). It deliberately does NOT inherit `IExitFeeController` so -/// we avoid stubbing the full admin/view surface; the selector + ABI of -/// `quoteExitFee` match, which is all `IExitFeeController(ctrl).quoteExitFee` -/// needs at the call site. +/// Only `quoteExitFee` and `quoteExitDelayFor` are implemented (the two +/// selectors the product hooks call). It deliberately does NOT inherit +/// `IExitFeeController` so we avoid stubbing the full admin/view +/// surface; the selector + ABI of each match what +/// `IExitFeeController(ctrl).quoteExitFee` / +/// `.quoteExitDelayFor` need at the call site. contract ExitFeeControllerMock { bool public doRevert; // when true, quoteExitFee reverts → exercises CONTROLLER_REVERT fail-open bool public activeFlag; @@ -28,6 +29,11 @@ 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 + function configure( bool _active, uint16 _rateBps, @@ -57,6 +63,34 @@ contract ExitFeeControllerMock { forcedNetAmount = _net; } + // --- Delay configuration --- + + function configureDelay(bool _enabled, uint32 _delaySeconds) external { + perimeterEnabled = _enabled; + delaySeconds = _delaySeconds; + } + + function setDelayRevert(bool _v) external { + delayRevert = _v; + } + + /// @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 the unchanged originator and owner. + 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); + } + 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..d51b12f --- /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/perimeter/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 perimeter 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/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/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol b/contracts/TestContracts/SelectorRevertingExitDelayQueue.sol new file mode 100644 index 0000000..194398a --- /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/perimeter/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/contracts/TroveManager.sol b/contracts/TroveManager.sol index cfcc2f0..78c1c49 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); } @@ -526,6 +529,7 @@ contract TroveManager is TroveManagerBase, CheckContract, ITroveManager { vars.entireSystemColl = vars .entireSystemColl .sub(singleLiquidation.collToSendToSP) + .sub(singleLiquidation.collGasCompensation) .sub(singleLiquidation.collSurplus); // Add liquidation values to their respective running totals @@ -722,9 +726,11 @@ contract TroveManager is TroveManagerBase, CheckContract, ITroveManager { singleLiquidation.debtToOffset ); vars.entireSystemDebt = vars.entireSystemDebt.sub(singleLiquidation.debtToOffset); - vars.entireSystemColl = vars.entireSystemColl.sub( - singleLiquidation.collToSendToSP - ); + vars.entireSystemColl = vars + .entireSystemColl + .sub(singleLiquidation.collToSendToSP) + .sub(singleLiquidation.collGasCompensation) + .sub(singleLiquidation.collSurplus); // Add liquidation values to their respective running totals totals = _addLiquidationValuesToTotals(totals, singleLiquidation); diff --git a/deployment/deploy/1-BorrowerOperations.ts b/deployment/deploy/1-BorrowerOperations.ts index dd2728b..31fa867 100644 --- a/deployment/deploy/1-BorrowerOperations.ts +++ b/deployment/deploy/1-BorrowerOperations.ts @@ -1,18 +1,33 @@ import { DeployFunction } from "hardhat-deploy/types"; import { deployWithCustomProxy } from "../../scripts/helpers/helpers"; -import { getContractNameFromScriptFileName } from "../../scripts/helpers/utils"; +import { + getContractNameFromScriptFileName, + assertPerimeterDeploymentNetwork, + preflightZeroDelayDeployment, +} from "../../scripts/helpers/utils"; const path = require("path"); const deploymentName = getContractNameFromScriptFileName(path.basename(__filename)); const func: DeployFunction = async (hre) => { - const { + assertPerimeterDeploymentNetwork(hre.network); + await preflightZeroDelayDeployment(hre); + const { deployments: { get }, - getNamedAccounts + getNamedAccounts, } = hre; const { deployer } = await getNamedAccounts(); const permit2Deployment = await get("Permit2"); - await deployWithCustomProxy(hre, deployer, deploymentName, "UpgradableProxy", false, "MultiSigWallet", "", [permit2Deployment.address]); + await deployWithCustomProxy( + hre, + deployer, + deploymentName, + "UpgradableProxy", + false, + "MultiSigWallet", + "", + [permit2Deployment.address] + ); }; func.tags = [deploymentName]; diff --git a/deployment/deploy/3-TroveManager.ts b/deployment/deploy/3-TroveManager.ts index 57783a2..77a4a61 100644 --- a/deployment/deploy/3-TroveManager.ts +++ b/deployment/deploy/3-TroveManager.ts @@ -1,21 +1,31 @@ import { DeployFunction } from "hardhat-deploy/types"; import { deployWithCustomProxy } from "../../scripts/helpers/helpers"; -import { getContractNameFromScriptFileName } from "../../scripts/helpers/utils"; +import { + getContractNameFromScriptFileName, + preflightZeroDelayDeployment, +} from "../../scripts/helpers/utils"; const path = require("path"); const deploymentName = getContractNameFromScriptFileName(path.basename(__filename)); const func: DeployFunction = async (hre) => { - const { - deployments: { get }, - getNamedAccounts + await preflightZeroDelayDeployment(hre); + const { + deployments: { get }, + getNamedAccounts, } = hre; const { deployer } = await getNamedAccounts(); const permit2Deployment = await get("Permit2"); - await deployWithCustomProxy(hre, deployer, deploymentName, "UpgradableProxy", false, "MultiSigWallet", "", [ - "1209600", - permit2Deployment.address - ]); + await deployWithCustomProxy( + hre, + deployer, + deploymentName, + "UpgradableProxy", + false, + "MultiSigWallet", + "", + ["1209600", permit2Deployment.address] + ); }; func.tags = [deploymentName]; diff --git a/deployment/deploy/4-TroveManagerRedeemOps.ts b/deployment/deploy/4-TroveManagerRedeemOps.ts index 794a9b2..3f007d9 100644 --- a/deployment/deploy/4-TroveManagerRedeemOps.ts +++ b/deployment/deploy/4-TroveManagerRedeemOps.ts @@ -1,5 +1,9 @@ import { DeployFunction } from "hardhat-deploy/types"; -import { getContractNameFromScriptFileName } from "../../scripts/helpers/utils"; +import { + getContractNameFromScriptFileName, + assertZeroDelayDeploymentCandidate, + resolveZeroDelayDeploymentNetwork, +} from "../../scripts/helpers/utils"; const path = require("path"); import Logs from "node-logs"; import { TroveManager } from "types/generated"; @@ -9,11 +13,15 @@ import * as helpers from "../../scripts/helpers/helpers"; const deploymentName = getContractNameFromScriptFileName(path.basename(__filename)); const func: DeployFunction = async (hre) => { + assertZeroDelayDeploymentCandidate( + await resolveZeroDelayDeploymentNetwork(hre), + deploymentName + ); const { getNamedAccounts, ethers, deployments: { get, deploy, log, execute }, - network + network, } = hre; const permit2Deployment = await get("Permit2"); @@ -38,14 +46,24 @@ const func: DeployFunction = async (hre) => { } if (network.tags.testnet) { console.log("testnet"); - logger.information(`Initiating multisig tx to set TroveManagerRedeemOps in TroveManager....`) + logger.information( + `Initiating multisig tx to set TroveManagerRedeemOps in TroveManager....` + ); // multisig tx const deployment = await get(deploymentName); const { deployer } = await getNamedAccounts(); const multisigAddress = (await get("MultiSigWallet")).address; - const data = troveManager.interface.encodeFunctionData("setTroveManagerRedeemOps", [deployment.address]); + const data = troveManager.interface.encodeFunctionData("setTroveManagerRedeemOps", [ + deployment.address, + ]); - await helpers.sendWithMultisig(hre, multisigAddress, troveManager.target.toString(), data, deployer); + await helpers.sendWithMultisig( + hre, + multisigAddress, + troveManager.target.toString(), + data, + deployer + ); } else if (network.tags.mainnet) { // create SIP message console.log("mainnet"); diff --git a/deployment/deploy/8-CollSurplusPool.ts b/deployment/deploy/8-CollSurplusPool.ts index 093b25e..28712ae 100644 --- a/deployment/deploy/8-CollSurplusPool.ts +++ b/deployment/deploy/8-CollSurplusPool.ts @@ -1,10 +1,14 @@ import { DeployFunction } from "hardhat-deploy/types"; import { deployWithCustomProxy } from "../../scripts/helpers/helpers"; -import { getContractNameFromScriptFileName } from "../../scripts/helpers/utils"; +import { + getContractNameFromScriptFileName, + preflightZeroDelayDeployment, +} from "../../scripts/helpers/utils"; const path = require("path"); const deploymentName = getContractNameFromScriptFileName(path.basename(__filename)); const func: DeployFunction = async (hre) => { + await preflightZeroDelayDeployment(hre); const { getNamedAccounts } = hre; const { deployer } = await getNamedAccounts(); diff --git a/deployment/deploy/9-BorrowerOperationsPerimeterOps.ts b/deployment/deploy/9-BorrowerOperationsPerimeterOps.ts new file mode 100644 index 0000000..dde5eae --- /dev/null +++ b/deployment/deploy/9-BorrowerOperationsPerimeterOps.ts @@ -0,0 +1,99 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { + getContractNameFromScriptFileName, + assertPerimeterDeploymentNetwork, + assertZeroDelayDeploymentCandidate, + resolveZeroDelayDeploymentNetwork, + preflightZeroDelayDeployment, +} 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) => { + assertPerimeterDeploymentNetwork(hre.network); + assertZeroDelayDeploymentCandidate( + await resolveZeroDelayDeploymentNetwork(hre), + deploymentName + ); + await preflightZeroDelayDeployment(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, + }); + + if (network.tags.mainnet) { + log( + `>>> Add BorrowerOperations.setPerimeterOps(${tx.address}) at ${borrowerOperations.target} to a SIP AFTER its implementation upgrade` + ); + return; + } + + let prevImpl: string; + try { + prevImpl = await borrowerOperations.getFunction("perimeterOps")(); + } catch (error) { + const failure = error as { code?: string; data?: string; value?: string }; + if ( + network.tags.testnet && + ((failure?.code === "CALL_EXCEPTION" && failure.data === "0x") || + (failure?.code === "BAD_DATA" && failure.value === "0x")) + ) { + log( + `>>> ${deploymentName} deployed at ${tx.address}; implementation upgrade is pending or the hook getter is unavailable. Execute the BorrowerOperations upgrade, then rerun this deployment to submit setPerimeterOps` + ); + return; + } + throw error; + } + log(`Current ${deploymentName}: ${prevImpl}`); + + if (tx.address.toLowerCase() !== prevImpl.toLowerCase()) { + if (!tx.newlyDeployed) { + 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 { + // 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; diff --git a/deployment/deployments/rskSovrynMainnet/DefaultPool.json b/deployment/deployments/rskSovrynMainnet/DefaultPool.json index 36075fe..da89f7e 100644 --- a/deployment/deployments/rskSovrynMainnet/DefaultPool.json +++ b/deployment/deployments/rskSovrynMainnet/DefaultPool.json @@ -2,8 +2,8 @@ "_format": "hh-sol-artifact-1", "contractName": "DefaultPool", "sourceName": "contracts/DefaultPool.sol", - "address": "0x9b38044A276fED8bC1703bd4a2DA1b17F2c61d16", - "implementation": "0x5059A057df377436Fc9b68369b696A23cc61Ecc3", + "address": "0xcdbA14ca707B99afb8CA93E178aD614Db422a030", + "implementation": "0x12BaF6f8151bd26f22E4AE1Df3cef6746840da93", "abi": [ { "anonymous": false, diff --git a/deployment/deployments/rskSovrynMainnet/DefaultPool_Proxy.json b/deployment/deployments/rskSovrynMainnet/DefaultPool_Proxy.json index a9c9770..c1d8144 100644 --- a/deployment/deployments/rskSovrynMainnet/DefaultPool_Proxy.json +++ b/deployment/deployments/rskSovrynMainnet/DefaultPool_Proxy.json @@ -2,7 +2,7 @@ "_format": "hh-sol-artifact-1", "contractName": "UpgradableProxy", "sourceName": "contracts/Proxy/UpgradableProxy.sol", - "address": "0x9b38044A276fED8bC1703bd4a2DA1b17F2c61d16", + "address": "0xcdbA14ca707B99afb8CA93E178aD614Db422a030", "abi": [ { "anonymous": false, diff --git a/deployment/deployments/rskSovrynMainnet/TroveManager_Implementation.json b/deployment/deployments/rskSovrynMainnet/TroveManager_Implementation.json index 485d129..dea1d04 100644 --- a/deployment/deployments/rskSovrynMainnet/TroveManager_Implementation.json +++ b/deployment/deployments/rskSovrynMainnet/TroveManager_Implementation.json @@ -1,2755 +1,2094 @@ { - "address": "0x3d841398AcA5794b9da2f2573F473075417a0117", - "abi": [ - { - "inputs": [ + "address": "0x3d841398AcA5794b9da2f2573F473075417a0117", + "abi": [ { - "internalType": "uint256", - "name": "_bootstrapPeriod", - "type": "uint256" + "inputs": [ + { + "internalType": "uint256", + "name": "_bootstrapPeriod", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_permit2", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" }, { - "internalType": "address", - "name": "_permit2", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_activePoolAddress", - "type": "address" - } - ], - "name": "ActivePoolAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "_baseRate", - "type": "uint256" - } - ], - "name": "BaseRateUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_newBorrowerOperationsAddress", - "type": "address" - } - ], - "name": "BorrowerOperationsAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_collSurplusPoolAddress", - "type": "address" - } - ], - "name": "CollSurplusPoolAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_defaultPoolAddress", - "type": "address" - } - ], - "name": "DefaultPoolAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_feeDistributorAddress", - "type": "address" - } - ], - "name": "FeeDistributorAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_gasPoolAddress", - "type": "address" - } - ], - "name": "GasPoolAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_activePoolAddress", + "type": "address" + } + ], + "name": "ActivePoolAddressChanged", + "type": "event" + }, { - "indexed": false, - "internalType": "uint256", - "name": "_L_ETH", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_baseRate", + "type": "uint256" + } + ], + "name": "BaseRateUpdated", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_L_ZUSDDebt", - "type": "uint256" - } - ], - "name": "LTermsUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "_lastFeeOpTime", - "type": "uint256" - } - ], - "name": "LastFeeOpTimeUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_newBorrowerOperationsAddress", + "type": "address" + } + ], + "name": "BorrowerOperationsAddressChanged", + "type": "event" + }, { - "indexed": false, - "internalType": "uint256", - "name": "_liquidatedDebt", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_collSurplusPoolAddress", + "type": "address" + } + ], + "name": "CollSurplusPoolAddressChanged", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_liquidatedColl", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_defaultPoolAddress", + "type": "address" + } + ], + "name": "DefaultPoolAddressChanged", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_collGasCompensation", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_feeDistributorAddress", + "type": "address" + } + ], + "name": "FeeDistributorAddressChanged", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_ZUSDGasCompensation", - "type": "uint256" - } - ], - "name": "Liquidation", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_borrowerOperationsAddress", - "type": "address" - } - ], - "name": "LiquityBaseParamsAddressChanges", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_gasPoolAddress", + "type": "address" + } + ], + "name": "GasPoolAddressChanged", + "type": "event" + }, { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_L_ETH", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_L_ZUSDDebt", + "type": "uint256" + } + ], + "name": "LTermsUpdated", + "type": "event" }, { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_newPriceFeedAddress", - "type": "address" - } - ], - "name": "PriceFeedAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_lastFeeOpTime", + "type": "uint256" + } + ], + "name": "LastFeeOpTimeUpdated", + "type": "event" + }, { - "indexed": false, - "internalType": "uint256", - "name": "_attemptedZUSDAmount", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_liquidatedDebt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_liquidatedColl", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_collGasCompensation", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_ZUSDGasCompensation", + "type": "uint256" + } + ], + "name": "Liquidation", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_actualZUSDAmount", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_borrowerOperationsAddress", + "type": "address" + } + ], + "name": "LiquityBaseParamsAddressChanges", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_ETHSent", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_ETHFee", - "type": "uint256" - } - ], - "name": "Redemption", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_sortedTrovesAddress", - "type": "address" - } - ], - "name": "SortedTrovesAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_stabilityPoolAddress", - "type": "address" - } - ], - "name": "StabilityPoolAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_newPriceFeedAddress", + "type": "address" + } + ], + "name": "PriceFeedAddressChanged", + "type": "event" + }, { - "indexed": false, - "internalType": "uint256", - "name": "_totalStakesSnapshot", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_attemptedZUSDAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_actualZUSDAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_ETHSent", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_ETHFee", + "type": "uint256" + } + ], + "name": "Redemption", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_totalCollateralSnapshot", - "type": "uint256" - } - ], - "name": "SystemSnapshotsUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "_newTotalStakes", - "type": "uint256" - } - ], - "name": "TotalStakesUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_sortedTrovesAddress", + "type": "address" + } + ], + "name": "SortedTrovesAddressChanged", + "type": "event" + }, { - "indexed": false, - "internalType": "address", - "name": "_borrower", - "type": "address" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_stabilityPoolAddress", + "type": "address" + } + ], + "name": "StabilityPoolAddressChanged", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_newIndex", - "type": "uint256" - } - ], - "name": "TroveIndexUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_totalStakesSnapshot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_totalCollateralSnapshot", + "type": "uint256" + } + ], + "name": "SystemSnapshotsUpdated", + "type": "event" + }, { - "indexed": true, - "internalType": "address", - "name": "_borrower", - "type": "address" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_newTotalStakes", + "type": "uint256" + } + ], + "name": "TotalStakesUpdated", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_debt", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_newIndex", + "type": "uint256" + } + ], + "name": "TroveIndexUpdated", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_coll", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_debt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_coll", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "operation", + "type": "uint8" + } + ], + "name": "TroveLiquidated", + "type": "event" }, { - "indexed": false, - "internalType": "uint8", - "name": "operation", - "type": "uint8" - } - ], - "name": "TroveLiquidated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_troveManagerRedeemOps", - "type": "address" - } - ], - "name": "TroveManagerRedeemOpsAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_troveManagerRedeemOps", + "type": "address" + } + ], + "name": "TroveManagerRedeemOpsAddressChanged", + "type": "event" + }, { - "indexed": false, - "internalType": "uint256", - "name": "_L_ETH", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_L_ETH", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_L_ZUSDDebt", + "type": "uint256" + } + ], + "name": "TroveSnapshotsUpdated", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_L_ZUSDDebt", - "type": "uint256" - } - ], - "name": "TroveSnapshotsUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_debt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_coll", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "stake", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "operation", + "type": "uint8" + } + ], + "name": "TroveUpdated", + "type": "event" + }, { - "indexed": true, - "internalType": "address", - "name": "_borrower", - "type": "address" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_zeroStakingAddress", + "type": "address" + } + ], + "name": "ZEROStakingAddressChanged", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_debt", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_zeroTokenAddress", + "type": "address" + } + ], + "name": "ZEROTokenAddressChanged", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "_coll", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_newZUSDTokenAddress", + "type": "address" + } + ], + "name": "ZUSDTokenAddressChanged", + "type": "event" }, { - "indexed": false, - "internalType": "uint256", - "name": "stake", - "type": "uint256" + "inputs": [], + "name": "BETA", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "indexed": false, - "internalType": "uint8", - "name": "operation", - "type": "uint8" - } - ], - "name": "TroveUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_zeroStakingAddress", - "type": "address" - } - ], - "name": "ZEROStakingAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_zeroTokenAddress", - "type": "address" - } - ], - "name": "ZEROTokenAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_newZUSDTokenAddress", - "type": "address" - } - ], - "name": "ZUSDTokenAddressChanged", - "type": "event" - }, - { - "inputs": [], - "name": "BETA", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "BOOTSTRAP_PERIOD", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "CCR", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "DECIMAL_PRECISION", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "L_ETH", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "L_ZUSDDebt", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MCR", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MINUTE_DECAY_FACTOR", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MIN_NET_DEBT", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "NAME", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "SECONDS_IN_ONE_MINUTE", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "BOOTSTRAP_PERIOD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "TroveOwners", - "outputs": [ + "inputs": [], + "name": "CCR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "DECIMAL_PRECISION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "Troves", - "outputs": [ + "inputs": [], + "name": "L_ETH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "debt", - "type": "uint256" + "inputs": [], + "name": "L_ZUSDDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "coll", - "type": "uint256" + "inputs": [], + "name": "MCR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "stake", - "type": "uint256" + "inputs": [], + "name": "MINUTE_DECAY_FACTOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "enum TroveManagerStorage.Status", - "name": "status", - "type": "uint8" + "inputs": [], + "name": "MIN_NET_DEBT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint128", - "name": "arrayIndex", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ZUSD_GAS_COMPENSATION", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "_100pct", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "NAME", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" + "inputs": [], + "name": "SECONDS_IN_ONE_MINUTE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_price", - "type": "uint256" - } - ], - "name": "_getCurrentICR", - "outputs": [ + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "TroveOwners", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "Troves", + "outputs": [ + { + "internalType": "uint256", + "name": "debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "coll", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stake", + "type": "uint256" + }, + { + "internalType": "enum TroveManagerStorage.Status", + "name": "status", + "type": "uint8" + }, + { + "internalType": "uint128", + "name": "arrayIndex", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "_getPendingETHReward", - "outputs": [ + "inputs": [], + "name": "ZUSD_GAS_COMPENSATION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "_100pct", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "_getPendingZUSDDebtReward", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_price", + "type": "uint256" + } + ], + "name": "_getCurrentICR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "_getRedemptionRate", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "_hasPendingRewards", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "_stabilityPool", - "outputs": [ - { - "internalType": "contract IStabilityPool", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "_zeroStaking", - "outputs": [ - { - "internalType": "contract IZEROStaking", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "_zeroToken", - "outputs": [ - { - "internalType": "contract IZEROToken", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "_zusdToken", - "outputs": [ - { - "internalType": "contract IZUSDToken", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "activePool", - "outputs": [ - { - "internalType": "contract IActivePool", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "addTroveOwnerToArray", - "outputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "applyPendingRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "baseRate", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "_troveArray", - "type": "address[]" - } - ], - "name": "batchLiquidateTroves", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "borrowerOperationsAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_price", - "type": "uint256" - } - ], - "name": "checkRecoveryMode", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "closeTrove", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decayBaseRateFromBorrowing", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "_getPendingETHReward", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_collDecrease", - "type": "uint256" - } - ], - "name": "decreaseTroveColl", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "_getPendingZUSDDebtReward", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_debtDecrease", - "type": "uint256" - } - ], - "name": "decreaseTroveDebt", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "defaultPool", - "outputs": [ - { - "internalType": "contract IDefaultPool", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "feeDistributor", - "outputs": [ - { - "internalType": "contract IFeeDistributor", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_ZUSDDebt", - "type": "uint256" - } - ], - "name": "getBorrowingFee", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_ZUSDDebt", - "type": "uint256" - } - ], - "name": "getBorrowingFeeWithDecay", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getBorrowingRate", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getBorrowingRateWithDecay", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" + "inputs": [], + "name": "_getRedemptionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_price", - "type": "uint256" - } - ], - "name": "getCurrentICR", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getEntireDebtAndColl", - "outputs": [ - { - "internalType": "uint256", - "name": "debt", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "_hasPendingRewards", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "coll", - "type": "uint256" + "inputs": [], + "name": "_stabilityPool", + "outputs": [ + { + "internalType": "contract IStabilityPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "pendingZUSDDebtReward", - "type": "uint256" + "inputs": [], + "name": "_zeroStaking", + "outputs": [ + { + "internalType": "contract IZEROStaking", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "pendingETHReward", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getEntireSystemColl", - "outputs": [ - { - "internalType": "uint256", - "name": "entireSystemColl", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getEntireSystemDebt", - "outputs": [ - { - "internalType": "uint256", - "name": "entireSystemDebt", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getNominalICR", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getOwner", - "outputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getPendingETHReward", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "_zeroToken", + "outputs": [ + { + "internalType": "contract IZEROToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getPendingZUSDDebtReward", - "outputs": [ + "inputs": [], + "name": "_zusdToken", + "outputs": [ + { + "internalType": "contract IZUSDToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "activePool", + "outputs": [ + { + "internalType": "contract IActivePool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "_ETHDrawn", - "type": "uint256" - } - ], - "name": "getRedemptionFeeWithDecay", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "addTroveOwnerToArray", + "outputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getRedemptionRate", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getRedemptionRateWithDecay", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "applyPendingRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "uint256", - "name": "_price", - "type": "uint256" - } - ], - "name": "getTCR", - "outputs": [ + "inputs": [], + "name": "baseRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "address[]", + "name": "_troveArray", + "type": "address[]" + } + ], + "name": "batchLiquidateTroves", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getTroveColl", - "outputs": [ + "inputs": [], + "name": "borrowerOperationsAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "uint256", + "name": "_price", + "type": "uint256" + } + ], + "name": "checkRecoveryMode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getTroveDebt", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "closeTrove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "decayBaseRateFromBorrowing", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "getTroveFromTroveOwnersArray", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_collDecrease", + "type": "uint256" + } + ], + "name": "decreaseTroveColl", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTroveOwnersCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_debtDecrease", + "type": "uint256" + } + ], + "name": "decreaseTroveDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getTroveStake", - "outputs": [ + "inputs": [], + "name": "defaultPool", + "outputs": [ + { + "internalType": "contract IDefaultPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "feeDistributor", + "outputs": [ + { + "internalType": "contract IFeeDistributor", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "getTroveStatus", - "outputs": [ + "inputs": [ + { + "internalType": "uint256", + "name": "_ZUSDDebt", + "type": "uint256" + } + ], + "name": "getBorrowingFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "uint256", + "name": "_ZUSDDebt", + "type": "uint256" + } + ], + "name": "getBorrowingFeeWithDecay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "hasPendingRewards", - "outputs": [ + "inputs": [], + "name": "getBorrowingRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "getBorrowingRateWithDecay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_price", + "type": "uint256" + } + ], + "name": "getCurrentICR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_collIncrease", - "type": "uint256" - } - ], - "name": "increaseTroveColl", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getEntireDebtAndColl", + "outputs": [ + { + "internalType": "uint256", + "name": "debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "coll", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pendingZUSDDebtReward", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pendingETHReward", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "getEntireSystemColl", + "outputs": [ + { + "internalType": "uint256", + "name": "entireSystemColl", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" + "inputs": [], + "name": "getEntireSystemDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "entireSystemDebt", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_debtIncrease", - "type": "uint256" - } - ], - "name": "increaseTroveDebt", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getNominalICR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "lastETHError_Redistribution", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lastFeeOperationTime", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lastZUSDDebtError_Redistribution", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "getOwner", + "outputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "liquidate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getPendingETHReward", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "_n", - "type": "uint256" - } - ], - "name": "liquidateTroves", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "liquityBaseParams", - "outputs": [ - { - "internalType": "contract ILiquityBaseParams", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "permit2", - "outputs": [ - { - "internalType": "contract IPermit2", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "priceFeed", - "outputs": [ - { - "internalType": "contract IPriceFeed", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getPendingZUSDDebtReward", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "_ZUSDamount", - "type": "uint256" + "inputs": [ + { + "internalType": "uint256", + "name": "_ETHDrawn", + "type": "uint256" + } + ], + "name": "getRedemptionFeeWithDecay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_firstRedemptionHint", - "type": "address" + "inputs": [], + "name": "getRedemptionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_upperPartialRedemptionHint", - "type": "address" + "inputs": [], + "name": "getRedemptionRateWithDecay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_lowerPartialRedemptionHint", - "type": "address" + "inputs": [ + { + "internalType": "uint256", + "name": "_price", + "type": "uint256" + } + ], + "name": "getTCR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_partialRedemptionHintNICR", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getTroveColl", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_maxIterations", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getTroveDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_maxFeePercentage", - "type": "uint256" - } - ], - "name": "redeemCollateral", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_dllrAmount", - "type": "uint256" + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getTroveFromTroveOwnersArray", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_firstRedemptionHint", - "type": "address" + "inputs": [], + "name": "getTroveOwnersCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_upperPartialRedemptionHint", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getTroveStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_lowerPartialRedemptionHint", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "getTroveStatus", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_partialRedemptionHintNICR", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "hasPendingRewards", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_maxIterations", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_collIncrease", + "type": "uint256" + } + ], + "name": "increaseTroveColl", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "_maxFeePercentage", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_debtIncrease", + "type": "uint256" + } + ], + "name": "increaseTroveDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" }, { - "components": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "internalType": "struct IMassetManager.PermitParams", - "name": "_permitParams", - "type": "tuple" - } - ], - "name": "redeemCollateralViaDLLR", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + "inputs": [], + "name": "lastETHError_Redistribution", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "_dllrAmount", - "type": "uint256" + "inputs": [], + "name": "lastFeeOperationTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_firstRedemptionHint", - "type": "address" + "inputs": [], + "name": "lastZUSDDebtError_Redistribution", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "_upperPartialRedemptionHint", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "liquidate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "address", - "name": "_lowerPartialRedemptionHint", - "type": "address" + "inputs": [ + { + "internalType": "uint256", + "name": "_n", + "type": "uint256" + } + ], + "name": "liquidateTroves", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "_partialRedemptionHintNICR", - "type": "uint256" + "inputs": [], + "name": "liquityBaseParams", + "outputs": [ + { + "internalType": "contract ILiquityBaseParams", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_maxIterations", - "type": "uint256" + "inputs": [], + "name": "permit2", + "outputs": [ + { + "internalType": "contract IPermit2", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "_maxFeePercentage", - "type": "uint256" + "inputs": [], + "name": "priceFeed", + "outputs": [ + { + "internalType": "contract IPriceFeed", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "components": [ - { - "components": [ + "inputs": [ + { + "internalType": "uint256", + "name": "_ZUSDamount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_firstRedemptionHint", + "type": "address" + }, + { + "internalType": "address", + "name": "_upperPartialRedemptionHint", + "type": "address" + }, + { + "internalType": "address", + "name": "_lowerPartialRedemptionHint", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_partialRedemptionHintNICR", + "type": "uint256" + }, { - "internalType": "address", - "name": "token", - "type": "address" + "internalType": "uint256", + "name": "_maxIterations", + "type": "uint256" }, { - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "uint256", + "name": "_maxFeePercentage", + "type": "uint256" } - ], - "internalType": "struct ISignatureTransfer.TokenPermissions", - "name": "permitted", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - } - ], - "internalType": "struct ISignatureTransfer.PermitTransferFrom", - "name": "_permit", - "type": "tuple" + ], + "name": "redeemCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "bytes", - "name": "_signature", - "type": "bytes" - } - ], - "name": "redeemCollateralViaDllrWithPermit2", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "removeStake", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "rewardSnapshots", - "outputs": [ - { - "internalType": "uint256", - "name": "ETH", - "type": "uint256" + "inputs": [ + { + "internalType": "uint256", + "name": "_dllrAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_firstRedemptionHint", + "type": "address" + }, + { + "internalType": "address", + "name": "_upperPartialRedemptionHint", + "type": "address" + }, + { + "internalType": "address", + "name": "_lowerPartialRedemptionHint", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_partialRedemptionHintNICR", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxIterations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxFeePercentage", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "internalType": "struct IMassetManager.PermitParams", + "name": "_permitParams", + "type": "tuple" + } + ], + "name": "redeemCollateralViaDLLR", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "ZUSDDebt", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "_feeDistributorAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_troveManagerRedeemOps", - "type": "address" - }, - { - "internalType": "address", - "name": "_liquityBaseParamsAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_borrowerOperationsAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_activePoolAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_defaultPoolAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_stabilityPoolAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_gasPoolAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_collSurplusPoolAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_priceFeedAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_zusdTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_sortedTrovesAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_zeroTokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_zeroStakingAddress", - "type": "address" - } - ], - "internalType": "struct ITroveManager.TroveManagerInitAddressesParams", - "name": "_troveManagerInitAddressesParams", - "type": "tuple" - } - ], - "name": "setAddresses", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - } - ], - "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_troveManagerRedeemOps", - "type": "address" - } - ], - "name": "setTroveManagerRedeemOps", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_borrower", - "type": "address" + "inputs": [ + { + "internalType": "uint256", + "name": "_dllrAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_firstRedemptionHint", + "type": "address" + }, + { + "internalType": "address", + "name": "_upperPartialRedemptionHint", + "type": "address" + }, + { + "internalType": "address", + "name": "_lowerPartialRedemptionHint", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_partialRedemptionHintNICR", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxIterations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxFeePercentage", + "type": "uint256" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions", + "name": "permitted", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.PermitTransferFrom", + "name": "_permit", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "_signature", + "type": "bytes" + } + ], + "name": "redeemCollateralViaDllrWithPermit2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "_num", - "type": "uint256" - } - ], - "name": "setTroveStatus", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "sortedTroves", - "outputs": [ - { - "internalType": "contract ISortedTroves", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalCollateralSnapshot", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalStakes", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalStakesSnapshot", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "troveManagerRedeemOps", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "removeStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "updateStakeAndTotalStakes", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "rewardSnapshots", + "outputs": [ + { + "internalType": "uint256", + "name": "ETH", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ZUSDDebt", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "_feeDistributorAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_troveManagerRedeemOps", + "type": "address" + }, + { + "internalType": "address", + "name": "_liquityBaseParamsAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_borrowerOperationsAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_activePoolAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_defaultPoolAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_stabilityPoolAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_gasPoolAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_collSurplusPoolAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_priceFeedAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_zusdTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_sortedTrovesAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_zeroTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_zeroStakingAddress", + "type": "address" + } + ], + "internalType": "struct ITroveManager.TroveManagerInitAddressesParams", + "name": "_troveManagerInitAddressesParams", + "type": "tuple" + } + ], + "name": "setAddresses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "address", - "name": "_borrower", - "type": "address" - } - ], - "name": "updateTroveRewardSnapshots", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "transactionHash": "0x98964a83f4dcaec9042a905d998f3351c02d6dae3c1608d5d9db1d696445f729", - "receipt": { - "to": null, - "from": "0x13Be55487D37FE3C66EE7305e1e9C1ac85de75Ae", - "contractAddress": "0xdC03c72c1730b149E3178624FD4d9Bf51501A6E0", - "transactionIndex": 0, - "gasUsed": "6045491", - "logsBloom": "0x00000000000020000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000080000000000000000001000000000000000000000000000000000000020000000000000000001800000000000000000000000000000000400000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x63ae44ab567d090410beb39dd6c2dad30f27c33679e013ac466adf6afc60e169", - "transactionHash": "0x98964a83f4dcaec9042a905d998f3351c02d6dae3c1608d5d9db1d696445f729", - "logs": [ - { - "transactionIndex": 0, - "blockNumber": 4748962, - "transactionHash": "0x98964a83f4dcaec9042a905d998f3351c02d6dae3c1608d5d9db1d696445f729", - "address": "0xdC03c72c1730b149E3178624FD4d9Bf51501A6E0", - "topics": [ - "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x00000000000000000000000013be55487d37fe3c66ee7305e1e9c1ac85de75ae" - ], - "data": "0x", - "logIndex": 0, - "blockHash": "0x63ae44ab567d090410beb39dd6c2dad30f27c33679e013ac466adf6afc60e169" - } - ], - "blockNumber": 4748962, - "cumulativeGasUsed": "6045491", - "status": 1, - "byzantium": true - }, - "args": [ - "1209600", - "0x000000000022d473030f116ddee9f6b43ac78ba3" - ], - "numDeployments": 2, - "solcInputHash": "849fadfa265e27de6fba2f2d99bdf763", - "metadata": "{\"compiler\":{\"version\":\"0.6.11+commit.5ef660b1\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_bootstrapPeriod\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_permit2\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_activePoolAddress\",\"type\":\"address\"}],\"name\":\"ActivePoolAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_baseRate\",\"type\":\"uint256\"}],\"name\":\"BaseRateUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_newBorrowerOperationsAddress\",\"type\":\"address\"}],\"name\":\"BorrowerOperationsAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_collSurplusPoolAddress\",\"type\":\"address\"}],\"name\":\"CollSurplusPoolAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_defaultPoolAddress\",\"type\":\"address\"}],\"name\":\"DefaultPoolAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_feeDistributorAddress\",\"type\":\"address\"}],\"name\":\"FeeDistributorAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_gasPoolAddress\",\"type\":\"address\"}],\"name\":\"GasPoolAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_L_ETH\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_L_ZUSDDebt\",\"type\":\"uint256\"}],\"name\":\"LTermsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_lastFeeOpTime\",\"type\":\"uint256\"}],\"name\":\"LastFeeOpTimeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_liquidatedDebt\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_liquidatedColl\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_collGasCompensation\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_ZUSDGasCompensation\",\"type\":\"uint256\"}],\"name\":\"Liquidation\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_borrowerOperationsAddress\",\"type\":\"address\"}],\"name\":\"LiquityBaseParamsAddressChanges\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_newPriceFeedAddress\",\"type\":\"address\"}],\"name\":\"PriceFeedAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_attemptedZUSDAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_actualZUSDAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_ETHSent\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_ETHFee\",\"type\":\"uint256\"}],\"name\":\"Redemption\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_sortedTrovesAddress\",\"type\":\"address\"}],\"name\":\"SortedTrovesAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_stabilityPoolAddress\",\"type\":\"address\"}],\"name\":\"StabilityPoolAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_totalStakesSnapshot\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_totalCollateralSnapshot\",\"type\":\"uint256\"}],\"name\":\"SystemSnapshotsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_newTotalStakes\",\"type\":\"uint256\"}],\"name\":\"TotalStakesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_newIndex\",\"type\":\"uint256\"}],\"name\":\"TroveIndexUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_debt\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_coll\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"operation\",\"type\":\"uint8\"}],\"name\":\"TroveLiquidated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_troveManagerRedeemOps\",\"type\":\"address\"}],\"name\":\"TroveManagerRedeemOpsAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_L_ETH\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_L_ZUSDDebt\",\"type\":\"uint256\"}],\"name\":\"TroveSnapshotsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_debt\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_coll\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"operation\",\"type\":\"uint8\"}],\"name\":\"TroveUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_zeroStakingAddress\",\"type\":\"address\"}],\"name\":\"ZEROStakingAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_zeroTokenAddress\",\"type\":\"address\"}],\"name\":\"ZEROTokenAddressChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_newZUSDTokenAddress\",\"type\":\"address\"}],\"name\":\"ZUSDTokenAddressChanged\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BETA\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"BOOTSTRAP_PERIOD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"CCR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DECIMAL_PRECISION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L_ETH\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L_ZUSDDebt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MCR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MINUTE_DECAY_FACTOR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MIN_NET_DEBT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"NAME\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SECONDS_IN_ONE_MINUTE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"TroveOwners\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"Troves\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debt\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"coll\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"enum TroveManagerStorage.Status\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"uint128\",\"name\":\"arrayIndex\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ZUSD_GAS_COMPENSATION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_100pct\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_price\",\"type\":\"uint256\"}],\"name\":\"_getCurrentICR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"_getPendingETHReward\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"_getPendingZUSDDebtReward\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_getRedemptionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"_hasPendingRewards\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_stabilityPool\",\"outputs\":[{\"internalType\":\"contract IStabilityPool\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_zeroStaking\",\"outputs\":[{\"internalType\":\"contract IZEROStaking\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_zeroToken\",\"outputs\":[{\"internalType\":\"contract IZEROToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_zusdToken\",\"outputs\":[{\"internalType\":\"contract IZUSDToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"activePool\",\"outputs\":[{\"internalType\":\"contract IActivePool\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"addTroveOwnerToArray\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"applyPendingRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"baseRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_troveArray\",\"type\":\"address[]\"}],\"name\":\"batchLiquidateTroves\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"borrowerOperationsAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_price\",\"type\":\"uint256\"}],\"name\":\"checkRecoveryMode\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"closeTrove\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decayBaseRateFromBorrowing\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_collDecrease\",\"type\":\"uint256\"}],\"name\":\"decreaseTroveColl\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_debtDecrease\",\"type\":\"uint256\"}],\"name\":\"decreaseTroveDebt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"defaultPool\",\"outputs\":[{\"internalType\":\"contract IDefaultPool\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeDistributor\",\"outputs\":[{\"internalType\":\"contract IFeeDistributor\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_ZUSDDebt\",\"type\":\"uint256\"}],\"name\":\"getBorrowingFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_ZUSDDebt\",\"type\":\"uint256\"}],\"name\":\"getBorrowingFeeWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBorrowingRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBorrowingRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_price\",\"type\":\"uint256\"}],\"name\":\"getCurrentICR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getEntireDebtAndColl\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debt\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"coll\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"pendingZUSDDebtReward\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"pendingETHReward\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getEntireSystemColl\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"entireSystemColl\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getEntireSystemDebt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"entireSystemDebt\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getNominalICR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getPendingETHReward\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getPendingZUSDDebtReward\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_ETHDrawn\",\"type\":\"uint256\"}],\"name\":\"getRedemptionFeeWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRedemptionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRedemptionRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_price\",\"type\":\"uint256\"}],\"name\":\"getTCR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getTroveColl\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getTroveDebt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_index\",\"type\":\"uint256\"}],\"name\":\"getTroveFromTroveOwnersArray\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTroveOwnersCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getTroveStake\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"getTroveStatus\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"hasPendingRewards\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_collIncrease\",\"type\":\"uint256\"}],\"name\":\"increaseTroveColl\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_debtIncrease\",\"type\":\"uint256\"}],\"name\":\"increaseTroveDebt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastETHError_Redistribution\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastFeeOperationTime\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastZUSDDebtError_Redistribution\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"liquidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_n\",\"type\":\"uint256\"}],\"name\":\"liquidateTroves\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquityBaseParams\",\"outputs\":[{\"internalType\":\"contract ILiquityBaseParams\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"permit2\",\"outputs\":[{\"internalType\":\"contract IPermit2\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"priceFeed\",\"outputs\":[{\"internalType\":\"contract IPriceFeed\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_ZUSDamount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_firstRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_upperPartialRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_lowerPartialRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_partialRedemptionHintNICR\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxIterations\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxFeePercentage\",\"type\":\"uint256\"}],\"name\":\"redeemCollateral\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_dllrAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_firstRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_upperPartialRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_lowerPartialRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_partialRedemptionHintNICR\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxIterations\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxFeePercentage\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"struct IMassetManager.PermitParams\",\"name\":\"_permitParams\",\"type\":\"tuple\"}],\"name\":\"redeemCollateralViaDLLR\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_dllrAmount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_firstRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_upperPartialRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_lowerPartialRedemptionHint\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_partialRedemptionHintNICR\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxIterations\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_maxFeePercentage\",\"type\":\"uint256\"},{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"struct ISignatureTransfer.TokenPermissions\",\"name\":\"permitted\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"internalType\":\"struct ISignatureTransfer.PermitTransferFrom\",\"name\":\"_permit\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"_signature\",\"type\":\"bytes\"}],\"name\":\"redeemCollateralViaDllrWithPermit2\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"removeStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"rewardSnapshots\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"ETH\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"ZUSDDebt\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"_feeDistributorAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_troveManagerRedeemOps\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_liquityBaseParamsAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_borrowerOperationsAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_activePoolAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_defaultPoolAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_stabilityPoolAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_gasPoolAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_collSurplusPoolAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_priceFeedAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_zusdTokenAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_sortedTrovesAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_zeroTokenAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_zeroStakingAddress\",\"type\":\"address\"}],\"internalType\":\"struct ITroveManager.TroveManagerInitAddressesParams\",\"name\":\"_troveManagerInitAddressesParams\",\"type\":\"tuple\"}],\"name\":\"setAddresses\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_troveManagerRedeemOps\",\"type\":\"address\"}],\"name\":\"setTroveManagerRedeemOps\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_num\",\"type\":\"uint256\"}],\"name\":\"setTroveStatus\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sortedTroves\",\"outputs\":[{\"internalType\":\"contract ISortedTroves\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalCollateralSnapshot\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalStakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalStakesSnapshot\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"troveManagerRedeemOps\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"updateStakeAndTotalStakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_borrower\",\"type\":\"address\"}],\"name\":\"updateTroveRewardSnapshots\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"applyPendingRewards(address)\":{\"params\":{\"_borrower\":\"borrower address\"}},\"closeTrove(address)\":{\"params\":{\"_borrower\":\"borrower address\"}},\"constructor\":{\"params\":{\"_bootstrapPeriod\":\"During bootsrap period redemptions are not allowed\"}},\"decreaseTroveColl(address,uint256)\":{\"params\":{\"_borrower\":\"borrower address\",\"_collDecrease\":\"amount of collateral to decrease\"},\"returns\":{\"_0\":\"new trove collateral\"}},\"decreaseTroveDebt(address,uint256)\":{\"params\":{\"_borrower\":\"borrower address\",\"_debtDecrease\":\"amount of debt to decrease\"},\"returns\":{\"_0\":\"new trove debt\"}},\"getBorrowingFeeWithDecay(uint256)\":{\"params\":{\"_ZUSDDebt\":\"ZUSD debt amount to calculate fee\"},\"returns\":{\"_0\":\"borrowing fee using borrowing rate with decay\"}},\"getBorrowingRate()\":{\"returns\":{\"_0\":\"borrowing rate\"}},\"getBorrowingRateWithDecay()\":{\"returns\":{\"_0\":\"borrowing rate calculated using decayed as base rate\"}},\"getCurrentICR(address,uint256)\":{\"params\":{\"_borrower\":\"borrower address\",\"_price\":\"ETH price\"},\"returns\":{\"_0\":\"the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account.\"}},\"getNominalICR(address)\":{\"returns\":{\"_0\":\"the nominal collateral ratio (ICR) of a given Trove, without the price. Takes a trove's pending coll and debt rewards from redistributions into account.\"}},\"getOwner()\":{\"returns\":{\"_owner\":\"Address of the owner. \"}},\"getPendingETHReward(address)\":{\"params\":{\"_borrower\":\"borrower address\"},\"returns\":{\"_0\":\"the borrower's pending accumulated ETH reward, earned by their stake\"}},\"getPendingZUSDDebtReward(address)\":{\"params\":{\"_borrower\":\"borrower address\"},\"returns\":{\"_0\":\"the borrower's pending accumulated ZUSD reward, earned by their stake\"}},\"getRedemptionFeeWithDecay(uint256)\":{\"params\":{\"_ETHDrawn\":\"ETH drawn\"}},\"getRedemptionRate()\":{\"returns\":{\"_0\":\"calculated redemption rate using baseRate\"}},\"getRedemptionRateWithDecay()\":{\"returns\":{\"_0\":\"calculated redemption rate using calculated decayed as base rate\"}},\"getTCR(uint256)\":{\"params\":{\"_price\":\"ETH price\"},\"returns\":{\"_0\":\"the total collateralization ratio (TCR) of the system. The TCR is based on the the entire system debt and collateral (including pending rewards).\"}},\"getTroveColl(address)\":{\"params\":{\"_borrower\":\"borrower address\"},\"returns\":{\"_0\":\"Trove collateral from given trove\"}},\"getTroveDebt(address)\":{\"params\":{\"_borrower\":\"borrower address\"},\"returns\":{\"_0\":\"Trove debt from given trove\"}},\"getTroveFromTroveOwnersArray(uint256)\":{\"params\":{\"_index\":\"Trove owner index\"},\"returns\":{\"_0\":\"Trove from TroveOwners array in given index\"}},\"getTroveOwnersCount()\":{\"returns\":{\"_0\":\"Trove owners count\"}},\"getTroveStake(address)\":{\"params\":{\"_borrower\":\"borrower address\"},\"returns\":{\"_0\":\"Trove stake from given trove\"}},\"getTroveStatus(address)\":{\"params\":{\"_borrower\":\"borrower address\"},\"returns\":{\"_0\":\"Trove status from given trove\"}},\"increaseTroveColl(address,uint256)\":{\"params\":{\"_borrower\":\"borrower address\",\"_collIncrease\":\"amount of collateral to increase\"},\"returns\":{\"_0\":\"new trove collateral\"}},\"increaseTroveDebt(address,uint256)\":{\"params\":{\"_borrower\":\"borrower address\",\"_debtIncrease\":\"amount of debt to increase\"},\"returns\":{\"_0\":\"new trove debt\"}},\"redeemCollateral(uint256,address,address,address,uint256,uint256,uint256)\":{\"details\":\"this function forwards the call to the troveManagerRedeemOps in a delegate call fashion so the parameters are not needed\"},\"redeemCollateralViaDLLR(uint256,address,address,address,uint256,uint256,uint256,(uint256,uint8,bytes32,bytes32))\":{\"details\":\"this function forwards the call to the troveManagerRedeemOps in a delegate call fashion so the parameters are not needed DLLR _owner or _spender can use Sovryn Mynt to convert DLLR to ZUSD, then use the Zero redemption mechanism to redeem ZUSD for RBTC, all in a single transaction\"},\"redeemCollateralViaDllrWithPermit2(uint256,address,address,address,uint256,uint256,uint256,((address,uint256),uint256,uint256),bytes)\":{\"details\":\"this function forwards the call to the troveManagerRedeemOps in a delegate call fashion so the parameters are not needed DLLR _owner or _spender can use Sovryn Mynt to convert DLLR to ZUSD, then use the Zero redemption mechanism to redeem ZUSD for RBTC, all in a single transaction\"},\"removeStake(address)\":{\"params\":{\"_borrower\":\"borrower address\"}},\"setOwner(address)\":{\"params\":{\"_owner\":\"Address of the owner. \"}},\"updateStakeAndTotalStakes(address)\":{\"params\":{\"_borrower\":\"borrower address\"}}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"BETA()\":{\"notice\":\"BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption. Corresponds to (1 / ALPHA) in the white paper.\"},\"BOOTSTRAP_PERIOD()\":{\"notice\":\"During bootsrap period redemptions are not allowed\"},\"MIN_NET_DEBT()\":{\"notice\":\"Minimum amount of net ZUSD debt a trove must have\"},\"ZUSD_GAS_COMPENSATION()\":{\"notice\":\"Amount of ZUSD to be locked in gas pool on opening troves\"},\"_getCurrentICR(address,uint256)\":{\"notice\":\"Return the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account.\"},\"_getPendingETHReward(address)\":{\"notice\":\"Get the borrower's pending accumulated ETH reward, earned by their stake\"},\"_getPendingZUSDDebtReward(address)\":{\"notice\":\"Get the borrower's pending accumulated ZUSD reward, earned by their stake\"},\"addTroveOwnerToArray(address)\":{\"notice\":\"Push the owner's address to the Trove owners list, and record the corresponding array index on the Trove struct\"},\"applyPendingRewards(address)\":{\"notice\":\"Add the borrowers's coll and debt rewards earned from redistributions, to their Trove\"},\"batchLiquidateTroves(address[])\":{\"notice\":\"Attempt to liquidate a custom list of troves provided by the caller.\"},\"checkRecoveryMode(uint256)\":{\"notice\":\"reveals whether or not the system is in Recovery Mode (i.e. whether the Total Collateralization Ratio (TCR) is below the Critical Collateralization Ratio (CCR)).\"},\"closeTrove(address)\":{\"notice\":\"Close given trove. Called by BorrowerOperations.\"},\"decayBaseRateFromBorrowing()\":{\"notice\":\"Updates the baseRate state variable based on time elapsed since the last redemption or ZUSD borrowing operation.\"},\"getCurrentICR(address,uint256)\":{\"notice\":\"computes the user\\u2019s individual collateralization ratio (ICR) based on their total collateral and total ZUSD debt. Returns 2^256 -1 if they have 0 debt.\"},\"getEntireDebtAndColl(address)\":{\"notice\":\"Return the Troves entire debt and coll, including pending rewards from redistributions.\"},\"getOwner()\":{\"notice\":\"Return address of the owner.\"},\"getRedemptionFeeWithDecay(uint256)\":{\"notice\":\"The redemption fee is taken as a cut of the total ETH drawn from the system in a redemption. It is based on the current redemption rate.\"},\"liquidate(address)\":{\"notice\":\"Single liquidation function. Closes the trove if its ICR is lower than the minimum collateral ratio.\"},\"liquidateTroves(uint256)\":{\"notice\":\"Liquidate a sequence of troves. Closes a maximum number of n under-collateralized Troves, starting from the one with the lowest collateral ratio in the system, and moving upwards\"},\"permit2()\":{\"notice\":\"CONSTANT / IMMUTABLE VARIABLE ONLY \"},\"removeStake(address)\":{\"notice\":\"Remove borrower's stake from the totalStakes sum, and set their stake to 0\"},\"setOwner(address)\":{\"notice\":\"Set address of the owner (only owner can call this function)\"},\"updateStakeAndTotalStakes(address)\":{\"notice\":\"Update borrower's stake based on their latest collateral value\"},\"updateTroveRewardSnapshots(address)\":{\"notice\":\"Update borrower's snapshots of L_ETH and L_ZUSDDebt to reflect the current values\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/TroveManager.sol\":\"TroveManager\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":100},\"remappings\":[]},\"sources\":{\"contracts/Dependencies/BaseMath.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.6.11;\\n\\n\\ncontract BaseMath {\\n uint constant public DECIMAL_PRECISION = 1e18;\\n}\\n\",\"keccak256\":\"0x7e1369ca5cb09e818e345a2def19a261401f79c985a6030b55b7311dd6f53be4\",\"license\":\"MIT\"},\"contracts/Dependencies/CheckContract.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n\\ncontract CheckContract {\\n /**\\n * @dev Check that the account is an already deployed non-destroyed contract.\\n * See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L12\\n */\\n function checkContract(address _account) internal view {\\n require(_account != address(0), \\\"Account cannot be zero address\\\");\\n\\n uint256 size;\\n // solhint-disable-next-line no-inline-assembly\\n assembly { size := extcodesize(_account) }\\n require(size > 0, \\\"Account code size cannot be zero\\\");\\n }\\n}\\n\",\"keccak256\":\"0x4c7dc4d0197c27ebc7de671b00458a9ff45f57223aeb520e6ddd2eb6d2d89e5c\",\"license\":\"MIT\"},\"contracts/Dependencies/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n/**\\n * Based on the OpenZeppelin IER20 interface:\\n * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol\\n *\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n function increaseAllowance(address spender, uint256 addedValue) external returns (bool);\\n function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n\\n function name() external view returns (string memory);\\n function symbol() external view returns (string memory);\\n function decimals() external view returns (uint8);\\n \\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\",\"keccak256\":\"0xe0b2473eba89df8d27d7cea2a99fce788c212f3fd393c9508e449e51a3f220fa\",\"license\":\"MIT\"},\"contracts/Dependencies/IERC2612.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n/**\\n * @dev Interface of the ERC2612 standard as defined in the EIP.\\n *\\n * Adds the {permit} method, which can be used to change one's\\n * {IERC20-allowance} without having to send a transaction, by signing a\\n * message. This allows users to spend tokens without having to hold Ether.\\n *\\n * See https://eips.ethereum.org/EIPS/eip-2612.\\n * \\n * Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/\\n */\\ninterface IERC2612 {\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens,\\n * given `owner`'s signed approval.\\n *\\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\\n * ordering also apply here.\\n *\\n * Emits an {Approval} event.\\n *\\n * Requirements:\\n *\\n * - `owner` cannot be the zero address.\\n * - `spender` cannot be the zero address.\\n * - `deadline` must be a timestamp in the future.\\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\\n * over the EIP712-formatted function arguments.\\n * - the signature must use ``owner``'s current nonce (see {nonces}).\\n *\\n * For more information on the signature format, see the\\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\\n * section].\\n */\\n function permit(address owner, address spender, uint256 amount, \\n uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;\\n \\n /**\\n * @dev Returns the current ERC2612 nonce for `owner`. This value must be\\n * included whenever a signature is generated for {permit}.\\n *\\n * Every successful call to {permit} increases `owner`'s nonce by one. This\\n * prevents a signature from being used multiple times.\\n *\\n * `owner` can limit the time a Permit is valid for by setting `deadline` to \\n * a value in the near future. The deadline argument can be set to uint(-1) to \\n * create Permits that effectively never expire.\\n */\\n function nonces(address owner) external view returns (uint256);\\n \\n function version() external view returns (string memory);\\n function permitTypeHash() external view returns (bytes32);\\n function domainSeparator() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0xd376458452f8b480bfea549637bd71d3f9eb1f12e9d59d1beff373417462d67f\",\"license\":\"MIT\"},\"contracts/Dependencies/LiquityBase.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"./BaseMath.sol\\\";\\nimport \\\"./LiquityMath.sol\\\";\\nimport \\\"../Interfaces/IActivePool.sol\\\";\\nimport \\\"../Interfaces/IDefaultPool.sol\\\";\\nimport \\\"../Interfaces/IPriceFeed.sol\\\";\\nimport \\\"../Interfaces/ILiquityBase.sol\\\";\\nimport \\\"../Interfaces/ILiquityBaseParams.sol\\\";\\n\\n/**\\n * Base contract for TroveManager, BorrowerOperations and StabilityPool. Contains global system constants and\\n * common functions.\\n */\\ncontract LiquityBase is BaseMath, ILiquityBase {\\n using SafeMath for uint256;\\n\\n uint256 public constant _100pct = 1000000000000000000; // 1e18 == 100%\\n\\n /// Amount of ZUSD to be locked in gas pool on opening troves\\n uint256 public constant ZUSD_GAS_COMPENSATION = 20e18;\\n\\n /// Minimum amount of net ZUSD debt a trove must have\\n uint256 public constant MIN_NET_DEBT = 180e18;\\n\\n IActivePool public activePool;\\n\\n IDefaultPool public defaultPool;\\n\\n IPriceFeed public override priceFeed;\\n\\n ILiquityBaseParams public override liquityBaseParams;\\n\\n // --- Gas compensation functions ---\\n\\n // Returns the composite debt (drawn debt + gas compensation) of a trove, for the purpose of ICR calculation\\n function _getCompositeDebt(uint256 _debt) internal pure returns (uint256) {\\n return _debt.add(ZUSD_GAS_COMPENSATION);\\n }\\n\\n function _getNetDebt(uint256 _debt) internal pure returns (uint256) {\\n return _debt.sub(ZUSD_GAS_COMPENSATION);\\n }\\n\\n /// Return the amount of ETH to be drawn from a trove's collateral and sent as gas compensation.\\n function _getCollGasCompensation(uint256 _entireColl) internal view returns (uint256) {\\n return _entireColl / liquityBaseParams.PERCENT_DIVISOR();\\n }\\n\\n function getEntireSystemColl() public view returns (uint256 entireSystemColl) {\\n uint256 activeColl = activePool.getETH();\\n uint256 liquidatedColl = defaultPool.getETH();\\n\\n return activeColl.add(liquidatedColl);\\n }\\n\\n function getEntireSystemDebt() public view returns (uint256 entireSystemDebt) {\\n uint256 activeDebt = activePool.getZUSDDebt();\\n uint256 closedDebt = defaultPool.getZUSDDebt();\\n\\n return activeDebt.add(closedDebt);\\n }\\n\\n function _getTCR(uint256 _price) internal view returns (uint256 TCR) {\\n uint256 entireSystemColl = getEntireSystemColl();\\n uint256 entireSystemDebt = getEntireSystemDebt();\\n\\n TCR = LiquityMath._computeCR(entireSystemColl, entireSystemDebt, _price);\\n\\n return TCR;\\n }\\n\\n function _checkRecoveryMode(uint256 _price) internal view returns (bool) {\\n uint256 TCR = _getTCR(_price);\\n\\n return TCR < liquityBaseParams.CCR();\\n }\\n\\n function _requireUserAcceptsFee(\\n uint256 _fee,\\n uint256 _amount,\\n uint256 _maxFeePercentage\\n ) internal pure {\\n uint256 feePercentage = _fee.mul(DECIMAL_PRECISION).div(_amount);\\n require(feePercentage <= _maxFeePercentage, \\\"Fee exceeded provided maximum\\\");\\n }\\n}\\n\",\"keccak256\":\"0x100b8a1c17caa95f5c9977e88f9263847a1977a365ca0a795753dd74aa1d6d7c\",\"license\":\"MIT\"},\"contracts/Dependencies/LiquityMath.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"./SafeMath.sol\\\";\\nimport \\\"./console.sol\\\";\\n\\nlibrary LiquityMath {\\n using SafeMath for uint;\\n\\n uint internal constant DECIMAL_PRECISION = 1e18;\\n\\n /* Precision for Nominal ICR (independent of price). Rationale for the value:\\n *\\n * - Making it \\u201ctoo high\\u201d could lead to overflows.\\n * - Making it \\u201ctoo low\\u201d could lead to an ICR equal to zero, due to truncation from Solidity floor division. \\n *\\n * This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH,\\n * and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.\\n *\\n */\\n uint internal constant NICR_PRECISION = 1e20;\\n\\n function _min(uint _a, uint _b) internal pure returns (uint) {\\n return (_a < _b) ? _a : _b;\\n }\\n\\n function _max(uint _a, uint _b) internal pure returns (uint) {\\n return (_a >= _b) ? _a : _b;\\n }\\n\\n /* \\n * Multiply two decimal numbers and use normal rounding rules:\\n * -round product up if 19'th mantissa digit >= 5\\n * -round product down if 19'th mantissa digit < 5\\n *\\n * Used only inside the exponentiation, _decPow().\\n */\\n function decMul(uint x, uint y) internal pure returns (uint decProd) {\\n uint prod_xy = x.mul(y);\\n\\n decProd = prod_xy.add(DECIMAL_PRECISION / 2).div(DECIMAL_PRECISION);\\n }\\n\\n /* \\n * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n.\\n * \\n * Uses the efficient \\\"exponentiation by squaring\\\" algorithm. O(log(n)) complexity. \\n * \\n * Called by two functions that represent time in units of minutes:\\n * 1) TroveManager._calcDecayedBaseRate\\n * 2) CommunityIssuance._getCumulativeIssuanceFraction \\n * \\n * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals\\n * \\\"minutes in 1000 years\\\": 60 * 24 * 365 * 1000\\n * \\n * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be\\n * negligibly different from just passing the cap, since: \\n *\\n * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years\\n * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible\\n */\\n function _decPow(uint _base, uint _minutes) internal pure returns (uint) {\\n \\n if (_minutes > 525600000) {_minutes = 525600000;} // cap to avoid overflow\\n \\n if (_minutes == 0) {return DECIMAL_PRECISION;}\\n\\n uint y = DECIMAL_PRECISION;\\n uint x = _base;\\n uint n = _minutes;\\n\\n // Exponentiation-by-squaring\\n while (n > 1) {\\n if (n % 2 == 0) {\\n x = decMul(x, x);\\n n = n.div(2);\\n } else { // if (n % 2 != 0)\\n y = decMul(x, y);\\n x = decMul(x, x);\\n n = (n.sub(1)).div(2);\\n }\\n }\\n\\n return decMul(x, y);\\n }\\n\\n function _getAbsoluteDifference(uint _a, uint _b) internal pure returns (uint) {\\n return (_a >= _b) ? _a.sub(_b) : _b.sub(_a);\\n }\\n\\n function _computeNominalCR(uint _coll, uint _debt) internal pure returns (uint) {\\n if (_debt > 0) {\\n return _coll.mul(NICR_PRECISION).div(_debt);\\n }\\n // Return the maximal value for uint256 if the Trove has a debt of 0. Represents \\\"infinite\\\" CR.\\n else { // if (_debt == 0)\\n return 2**256 - 1;\\n }\\n }\\n\\n function _computeCR(uint _coll, uint _debt, uint _price) internal pure returns (uint) {\\n if (_debt > 0) {\\n uint newCollRatio = _coll.mul(_price).div(_debt);\\n\\n return newCollRatio;\\n }\\n // Return the maximal value for uint256 if the Trove has a debt of 0. Represents \\\"infinite\\\" CR.\\n else { // if (_debt == 0)\\n return 2**256 - 1; \\n }\\n }\\n}\\n\",\"keccak256\":\"0x7a95ed70d8937e0896c054b433ad0dfc87a9cfd028cae1694098e9d5d68127cd\",\"license\":\"MIT\"},\"contracts/Dependencies/Mynt/IMassetManager.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\ninterface IMassetManager {\\n struct PermitParams {\\n uint256 deadline;\\n uint8 v;\\n bytes32 r;\\n bytes32 s;\\n }\\n\\n function mintTo(\\n address _bAsset,\\n uint256 _bAssetQuantity,\\n address _recipient\\n ) external returns (uint256);\\n\\n function getToken() external view returns (address);\\n\\n /**\\n * @dev Credits a recipient with a certain quantity of selected bAsset, in exchange for burning the\\n * relative mAsset quantity from the sender. Sender also incurs a small fee, if any.\\n * @param _bAsset Address of the bAsset to redeem.\\n * @param _massetQuantity Units of the masset to redeem.\\n * @param _recipient Address to credit with withdrawn bAssets.\\n * @return massetRedeemed Relative number of mAsset units burned to pay for the bAssets.\\n */\\n function redeemTo(\\n address _bAsset,\\n uint256 _massetQuantity,\\n address _recipient\\n ) external returns (uint256 massetRedeemed);\\n}\\n\",\"keccak256\":\"0x3e8de462d45e8f07ef83b6b6e7eb90a5d09f21d3bcbb1225e8f781488ab4a771\",\"license\":\"MIT\"},\"contracts/Dependencies/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n/**\\n * Based on OpenZeppelin's Ownable contract:\\n * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol\\n *\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\ncontract Ownable {\\n bytes32 private constant KEY_OWNER = keccak256(\\\"key.ownable.owner\\\");\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor () internal {\\n _setOwner(msg.sender);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(msg.sender == getOwner(), \\\"Ownable:: access denied\\\");\\n _;\\n }\\n\\n /**\\n * @notice Set address of the owner.\\n * @param _owner Address of the owner.\\n * */\\n function _setOwner(address _owner) internal {\\n require(_owner != address(0), \\\"Ownable::setOwner: invalid address\\\");\\n emit OwnershipTransferred(getOwner(), _owner);\\n\\n bytes32 key = KEY_OWNER;\\n assembly {\\n sstore(key, _owner)\\n }\\n }\\n\\n /**\\n * @notice Set address of the owner (only owner can call this function)\\n * @param _owner Address of the owner.\\n * */\\n function setOwner(address _owner) public onlyOwner {\\n _setOwner(_owner);\\n }\\n\\n /**\\n * @notice Return address of the owner.\\n * @return _owner Address of the owner.\\n * */\\n function getOwner() public view returns (address _owner) {\\n bytes32 key = KEY_OWNER;\\n assembly {\\n _owner := sload(key)\\n }\\n }\\n}\\n\",\"keccak256\":\"0xb5fc626e0b227fc0feb1d84440585015a0a5f586547d298534a604dd113efec6\",\"license\":\"MIT\"},\"contracts/Dependencies/SafeMath.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n/**\\n * Based on OpenZeppelin's SafeMath:\\n * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol\\n *\\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\\n * checks.\\n *\\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\\n * in bugs, because programmers usually assume that an overflow raises an\\n * error, which is the standard behavior in high level programming languages.\\n * `SafeMath` restores this intuition by reverting the transaction when an\\n * operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n */\\nlibrary SafeMath {\\n /**\\n * @dev Returns the addition of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `+` operator.\\n *\\n * Requirements:\\n * - Addition cannot overflow.\\n */\\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\\n uint256 c = a + b;\\n require(c >= a, \\\"SafeMath: addition overflow\\\");\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n * - Subtraction cannot overflow.\\n */\\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\\n return sub(a, b, \\\"SafeMath: subtraction overflow\\\");\\n }\\n\\n /**\\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\\n * overflow (when the result is negative).\\n *\\n * Counterpart to Solidity's `-` operator.\\n *\\n * Requirements:\\n * - Subtraction cannot overflow.\\n *\\n * _Available since v2.4.0._\\n */\\n function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b <= a, errorMessage);\\n uint256 c = a - b;\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the multiplication of two unsigned integers, reverting on\\n * overflow.\\n *\\n * Counterpart to Solidity's `*` operator.\\n *\\n * Requirements:\\n * - Multiplication cannot overflow.\\n */\\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\\n // benefit is lost if 'b' is also tested.\\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\\n if (a == 0) {\\n return 0;\\n }\\n\\n uint256 c = a * b;\\n require(c / a == b, \\\"SafeMath: multiplication overflow\\\");\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers. Reverts on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator. Note: this function uses a\\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\\n * uses an invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n * - The divisor cannot be zero.\\n */\\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\\n return div(a, b, \\\"SafeMath: division by zero\\\");\\n }\\n\\n /**\\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\\n * division by zero. The result is rounded towards zero.\\n *\\n * Counterpart to Solidity's `/` operator. Note: this function uses a\\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\\n * uses an invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n * - The divisor cannot be zero.\\n *\\n * _Available since v2.4.0._\\n */\\n function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n // Solidity only automatically asserts when dividing by 0\\n require(b > 0, errorMessage);\\n uint256 c = a / b;\\n // assert(a == b * c + a % b); // There is no case in which this doesn't hold\\n\\n return c;\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * Reverts when dividing by zero.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n * - The divisor cannot be zero.\\n */\\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\\n return mod(a, b, \\\"SafeMath: modulo by zero\\\");\\n }\\n\\n /**\\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\\n * Reverts with custom message when dividing by zero.\\n *\\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\\n * opcode (which leaves remaining gas untouched) while Solidity uses an\\n * invalid opcode to revert (consuming all remaining gas).\\n *\\n * Requirements:\\n * - The divisor cannot be zero.\\n *\\n * _Available since v2.4.0._\\n */\\n function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\\n require(b != 0, errorMessage);\\n return a % b;\\n }\\n}\\n\",\"keccak256\":\"0x666b890992a066cc791f36c2975cd595d9761a014c654c385ed36ffaf658f3fd\",\"license\":\"MIT\"},\"contracts/Dependencies/TroveManagerBase.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"../Interfaces/IActivePool.sol\\\";\\nimport \\\"../Interfaces/IDefaultPool.sol\\\";\\nimport \\\"../Interfaces/IZUSDToken.sol\\\";\\nimport \\\"../Interfaces/IZEROStaking.sol\\\";\\nimport \\\"../Interfaces/ISortedTroves.sol\\\";\\nimport \\\"../Interfaces/ICollSurplusPool.sol\\\";\\nimport \\\"../TroveManagerStorage.sol\\\";\\nimport \\\"./LiquityBase.sol\\\";\\n\\ncontract TroveManagerBase is LiquityBase, TroveManagerStorage {\\n uint256 public constant SECONDS_IN_ONE_MINUTE = 60;\\n\\n uint256 public constant MINUTE_DECAY_FACTOR = 999037758833783000;\\n\\n /// During bootsrap period redemptions are not allowed\\n uint256 public immutable BOOTSTRAP_PERIOD;\\n\\n /**\\n BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption.\\n Corresponds to (1 / ALPHA) in the white paper.\\n */\\n uint256 public constant BETA = 2;\\n\\n /**\\n --- Variable container structs for liquidations ---\\n \\n These structs are used to hold, return and assign variables inside the liquidation functions,\\n in order to avoid the error: \\\"CompilerError: Stack too deep\\\".\\n */\\n\\n struct LocalVariables_OuterLiquidationFunction {\\n uint256 price;\\n uint256 ZUSDInStabPool;\\n bool recoveryModeAtStart;\\n uint256 liquidatedDebt;\\n uint256 liquidatedColl;\\n }\\n\\n struct LocalVariables_InnerSingleLiquidateFunction {\\n uint256 collToLiquidate;\\n uint256 pendingDebtReward;\\n uint256 pendingCollReward;\\n }\\n\\n struct LocalVariables_LiquidationSequence {\\n uint256 remainingZUSDInStabPool;\\n uint256 i;\\n uint256 ICR;\\n address user;\\n bool backToNormalMode;\\n uint256 entireSystemDebt;\\n uint256 entireSystemColl;\\n }\\n\\n struct LiquidationValues {\\n uint256 entireTroveDebt;\\n uint256 entireTroveColl;\\n uint256 collGasCompensation;\\n uint256 ZUSDGasCompensation;\\n uint256 debtToOffset;\\n uint256 collToSendToSP;\\n uint256 debtToRedistribute;\\n uint256 collToRedistribute;\\n uint256 collSurplus;\\n }\\n\\n struct LiquidationTotals {\\n uint256 totalCollInSequence;\\n uint256 totalDebtInSequence;\\n uint256 totalCollGasCompensation;\\n uint256 totalZUSDGasCompensation;\\n uint256 totalDebtToOffset;\\n uint256 totalCollToSendToSP;\\n uint256 totalDebtToRedistribute;\\n uint256 totalCollToRedistribute;\\n uint256 totalCollSurplus;\\n }\\n\\n struct ContractsCache {\\n IActivePool activePool;\\n IDefaultPool defaultPool;\\n IZUSDToken zusdToken;\\n IZEROStaking zeroStaking;\\n ISortedTroves sortedTroves;\\n ICollSurplusPool collSurplusPool;\\n address gasPoolAddress;\\n }\\n // --- Variable container structs for redemptions ---\\n\\n struct RedemptionTotals {\\n uint256 remainingZUSD;\\n uint256 totalZUSDToRedeem;\\n uint256 totalETHDrawn;\\n uint256 ETHFee;\\n uint256 ETHToSendToRedeemer;\\n uint256 decayedBaseRate;\\n uint256 price;\\n uint256 totalZUSDSupplyAtStart;\\n }\\n\\n struct SingleRedemptionValues {\\n uint256 ZUSDLot;\\n uint256 ETHLot;\\n bool cancelledPartial;\\n }\\n\\n // --- Events ---\\n\\n event Liquidation(\\n uint256 _liquidatedDebt,\\n uint256 _liquidatedColl,\\n uint256 _collGasCompensation,\\n uint256 _ZUSDGasCompensation\\n );\\n event Redemption(\\n uint256 _attemptedZUSDAmount,\\n uint256 _actualZUSDAmount,\\n uint256 _ETHSent,\\n uint256 _ETHFee\\n );\\n event TroveUpdated(\\n address indexed _borrower,\\n uint256 _debt,\\n uint256 _coll,\\n uint256 _stake,\\n TroveManagerOperation _operation\\n );\\n event TroveLiquidated(\\n address indexed _borrower,\\n uint256 _debt,\\n uint256 _coll,\\n TroveManagerOperation _operation\\n );\\n event BaseRateUpdated(uint256 _baseRate);\\n event LastFeeOpTimeUpdated(uint256 _lastFeeOpTime);\\n event TotalStakesUpdated(uint256 _newTotalStakes);\\n event SystemSnapshotsUpdated(uint256 _totalStakesSnapshot, uint256 _totalCollateralSnapshot);\\n event LTermsUpdated(uint256 _L_ETH, uint256 _L_ZUSDDebt);\\n event TroveSnapshotsUpdated(uint256 _L_ETH, uint256 _L_ZUSDDebt);\\n event TroveIndexUpdated(address _borrower, uint256 _newIndex);\\n\\n enum TroveManagerOperation {\\n applyPendingRewards,\\n liquidateInNormalMode,\\n liquidateInRecoveryMode,\\n redeemCollateral\\n }\\n\\n constructor(uint256 _bootstrapPeriod) public {\\n BOOTSTRAP_PERIOD = _bootstrapPeriod;\\n }\\n\\n /// Return the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account.\\n function _getCurrentICR(address _borrower, uint256 _price) public view returns (uint256) {\\n (uint256 currentETH, uint256 currentZUSDDebt) = _getCurrentTroveAmounts(_borrower);\\n\\n uint256 ICR = LiquityMath._computeCR(currentETH, currentZUSDDebt, _price);\\n return ICR;\\n }\\n\\n function _getCurrentTroveAmounts(address _borrower) internal view returns (uint256, uint256) {\\n uint256 pendingETHReward = _getPendingETHReward(_borrower);\\n uint256 pendingZUSDDebtReward = _getPendingZUSDDebtReward(_borrower);\\n\\n uint256 currentETH = Troves[_borrower].coll.add(pendingETHReward);\\n uint256 currentZUSDDebt = Troves[_borrower].debt.add(pendingZUSDDebtReward);\\n\\n return (currentETH, currentZUSDDebt);\\n }\\n\\n /// Get the borrower's pending accumulated ETH reward, earned by their stake\\n function _getPendingETHReward(address _borrower) public view returns (uint256) {\\n uint256 snapshotETH = rewardSnapshots[_borrower].ETH;\\n uint256 rewardPerUnitStaked = L_ETH.sub(snapshotETH);\\n\\n if (rewardPerUnitStaked == 0 || Troves[_borrower].status != Status.active) {\\n return 0;\\n }\\n\\n uint256 stake = Troves[_borrower].stake;\\n\\n uint256 pendingETHReward = stake.mul(rewardPerUnitStaked).div(DECIMAL_PRECISION);\\n\\n return pendingETHReward;\\n }\\n\\n /// Get the borrower's pending accumulated ZUSD reward, earned by their stake\\n function _getPendingZUSDDebtReward(address _borrower) public view returns (uint256) {\\n uint256 snapshotZUSDDebt = rewardSnapshots[_borrower].ZUSDDebt;\\n uint256 rewardPerUnitStaked = L_ZUSDDebt.sub(snapshotZUSDDebt);\\n\\n if (rewardPerUnitStaked == 0 || Troves[_borrower].status != Status.active) {\\n return 0;\\n }\\n\\n uint256 stake = Troves[_borrower].stake;\\n\\n uint256 pendingZUSDDebtReward = stake.mul(rewardPerUnitStaked).div(DECIMAL_PRECISION);\\n\\n return pendingZUSDDebtReward;\\n }\\n\\n /// Add the borrowers's coll and debt rewards earned from redistributions, to their Trove\\n function _applyPendingRewards(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n address _borrower\\n ) internal {\\n if (_hasPendingRewards(_borrower)) {\\n _requireTroveIsActive(_borrower);\\n\\n // Compute pending rewards\\n uint256 pendingETHReward = _getPendingETHReward(_borrower);\\n uint256 pendingZUSDDebtReward = _getPendingZUSDDebtReward(_borrower);\\n\\n // Apply pending rewards to trove's state\\n Troves[_borrower].coll = Troves[_borrower].coll.add(pendingETHReward);\\n Troves[_borrower].debt = Troves[_borrower].debt.add(pendingZUSDDebtReward);\\n\\n _updateTroveRewardSnapshots(_borrower);\\n\\n // Transfer from DefaultPool to ActivePool\\n _movePendingTroveRewardsToActivePool(\\n _activePool,\\n _defaultPool,\\n pendingZUSDDebtReward,\\n pendingETHReward\\n );\\n\\n emit TroveUpdated(\\n _borrower,\\n Troves[_borrower].debt,\\n Troves[_borrower].coll,\\n Troves[_borrower].stake,\\n TroveManagerOperation.applyPendingRewards\\n );\\n }\\n }\\n\\n function _hasPendingRewards(address _borrower) public view returns (bool) {\\n /*\\n * A Trove has pending rewards if its snapshot is less than the current rewards per-unit-staked sum:\\n * this indicates that rewards have occured since the snapshot was made, and the user therefore has\\n * pending rewards\\n */\\n if (Troves[_borrower].status != Status.active) {\\n return false;\\n }\\n\\n return (rewardSnapshots[_borrower].ETH < L_ETH);\\n }\\n\\n function _updateTroveRewardSnapshots(address _borrower) internal {\\n rewardSnapshots[_borrower].ETH = L_ETH;\\n rewardSnapshots[_borrower].ZUSDDebt = L_ZUSDDebt;\\n emit TroveSnapshotsUpdated(L_ETH, L_ZUSDDebt);\\n }\\n\\n /// Move a Trove's pending debt and collateral rewards from distributions, from the Default Pool to the Active Pool\\n function _movePendingTroveRewardsToActivePool(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n uint256 _ZUSD,\\n uint256 _ETH\\n ) internal {\\n _defaultPool.decreaseZUSDDebt(_ZUSD);\\n _activePool.increaseZUSDDebt(_ZUSD);\\n _defaultPool.sendETHToActivePool(_ETH);\\n }\\n\\n /// Remove borrower's stake from the totalStakes sum, and set their stake to 0\\n function _removeStake(address _borrower) internal {\\n uint256 stake = Troves[_borrower].stake;\\n totalStakes = totalStakes.sub(stake);\\n Troves[_borrower].stake = 0;\\n }\\n\\n function _closeTrove(address _borrower, Status closedStatus) internal {\\n assert(closedStatus != Status.nonExistent && closedStatus != Status.active);\\n\\n uint256 TroveOwnersArrayLength = TroveOwners.length;\\n _requireMoreThanOneTroveInSystem(TroveOwnersArrayLength);\\n\\n Troves[_borrower].status = closedStatus;\\n Troves[_borrower].coll = 0;\\n Troves[_borrower].debt = 0;\\n\\n rewardSnapshots[_borrower].ETH = 0;\\n rewardSnapshots[_borrower].ZUSDDebt = 0;\\n\\n _removeTroveOwner(_borrower, TroveOwnersArrayLength);\\n sortedTroves.remove(_borrower);\\n }\\n\\n /// Update borrower's stake based on their latest collateral value\\n function _updateStakeAndTotalStakes(address _borrower) internal returns (uint256) {\\n uint256 newStake = _computeNewStake(Troves[_borrower].coll);\\n uint256 oldStake = Troves[_borrower].stake;\\n Troves[_borrower].stake = newStake;\\n\\n totalStakes = totalStakes.sub(oldStake).add(newStake);\\n emit TotalStakesUpdated(totalStakes);\\n\\n return newStake;\\n }\\n\\n // Calculate a new stake based on the snapshots of the totalStakes and totalCollateral taken at the last liquidation\\n function _computeNewStake(uint256 _coll) internal view returns (uint256) {\\n uint256 stake;\\n if (totalCollateralSnapshot == 0) {\\n stake = _coll;\\n } else {\\n /*\\n * The following assert() holds true because:\\n * - The system always contains >= 1 trove\\n * - When we close or liquidate a trove, we redistribute the pending rewards, so if all troves were closed/liquidated,\\n * rewards would\\u2019ve been emptied and totalCollateralSnapshot would be zero too.\\n */\\n assert(totalStakesSnapshot > 0);\\n stake = _coll.mul(totalStakesSnapshot).div(totalCollateralSnapshot);\\n }\\n return stake;\\n }\\n\\n function _calcDecayedBaseRate() internal view returns (uint256) {\\n uint256 minutesPassed = _minutesPassedSinceLastFeeOp();\\n uint256 decayFactor = LiquityMath._decPow(MINUTE_DECAY_FACTOR, minutesPassed);\\n\\n return baseRate.mul(decayFactor).div(DECIMAL_PRECISION);\\n }\\n\\n function _minutesPassedSinceLastFeeOp() internal view returns (uint256) {\\n return (block.timestamp.sub(lastFeeOperationTime)).div(SECONDS_IN_ONE_MINUTE);\\n }\\n\\n // Update the last fee operation time only if time passed >= decay interval. This prevents base rate griefing.\\n function _updateLastFeeOpTime() internal {\\n uint256 timePassed = block.timestamp.sub(lastFeeOperationTime);\\n\\n if (timePassed >= SECONDS_IN_ONE_MINUTE) {\\n lastFeeOperationTime = block.timestamp;\\n emit LastFeeOpTimeUpdated(block.timestamp);\\n }\\n }\\n\\n function _calcRedemptionFee(\\n uint256 _redemptionRate,\\n uint256 _ETHDrawn\\n ) internal pure returns (uint256) {\\n uint256 redemptionFee = _redemptionRate.mul(_ETHDrawn).div(DECIMAL_PRECISION);\\n require(\\n redemptionFee < _ETHDrawn,\\n \\\"TroveManager: Fee would eat up all returned collateral\\\"\\n );\\n return redemptionFee;\\n }\\n\\n function _getRedemptionRate() public view returns (uint256) {\\n return _calcRedemptionRate(baseRate);\\n }\\n\\n function _getRedemptionFee(uint256 _ETHDrawn) internal view returns (uint256) {\\n return _calcRedemptionFee(_getRedemptionRate(), _ETHDrawn);\\n }\\n\\n function _calcRedemptionRate(uint256 _baseRate) internal view returns (uint256) {\\n return\\n LiquityMath._min(\\n liquityBaseParams.REDEMPTION_FEE_FLOOR().add(_baseRate),\\n DECIMAL_PRECISION // cap at a maximum of 100%\\n );\\n }\\n\\n /**\\n Remove a Trove owner from the TroveOwners array, not preserving array order. Removing owner 'B' does the following:\\n [A B C D E] => [A E C D], and updates E's Trove struct to point to its new array index.\\n */\\n function _removeTroveOwner(address _borrower, uint256 TroveOwnersArrayLength) internal {\\n Status troveStatus = Troves[_borrower].status;\\n // It\\u2019s set in caller function `_closeTrove`\\n assert(troveStatus != Status.nonExistent && troveStatus != Status.active);\\n\\n uint128 index = Troves[_borrower].arrayIndex;\\n uint256 length = TroveOwnersArrayLength;\\n uint256 idxLast = length.sub(1);\\n\\n assert(index <= idxLast);\\n\\n address addressToMove = TroveOwners[idxLast];\\n\\n TroveOwners[index] = addressToMove;\\n Troves[addressToMove].arrayIndex = index;\\n emit TroveIndexUpdated(addressToMove, index);\\n\\n TroveOwners.pop();\\n }\\n\\n // --- 'require' wrapper functions ---\\n\\n function _requireCallerIsBorrowerOperations() internal view {\\n require(\\n msg.sender == borrowerOperationsAddress,\\n \\\"TroveManager: Caller is not the BorrowerOperations contract\\\"\\n );\\n }\\n\\n function _requireTroveIsActive(address _borrower) internal view {\\n require(\\n Troves[_borrower].status == Status.active,\\n \\\"TroveManager: Trove does not exist or is closed\\\"\\n );\\n }\\n\\n function _requireZUSDBalanceCoversRedemption(\\n IZUSDToken _zusdToken,\\n address _redeemer,\\n uint256 _amount\\n ) internal view {\\n require(\\n _zusdToken.balanceOf(_redeemer) >= _amount,\\n \\\"TroveManager: Requested redemption amount must be <= user's ZUSD token balance\\\"\\n );\\n }\\n\\n function _requireMoreThanOneTroveInSystem(uint256 TroveOwnersArrayLength) internal view {\\n require(\\n TroveOwnersArrayLength > 1 && sortedTroves.getSize() > 1,\\n \\\"TroveManager: Only one trove in the system\\\"\\n );\\n }\\n\\n function _requireAmountGreaterThanZero(uint256 _amount) internal pure {\\n require(_amount > 0, \\\"TroveManager: Amount must be greater than zero\\\");\\n }\\n\\n function _requireTCRoverMCR(uint256 _price) internal view {\\n require(\\n _getTCR(_price) >= liquityBaseParams.MCR(),\\n \\\"TroveManager: Cannot redeem when TCR < MCR\\\"\\n );\\n }\\n\\n function _requireAfterBootstrapPeriod() internal view {\\n uint256 systemDeploymentTime = _zeroToken.getDeploymentStartTime();\\n require(\\n block.timestamp >= systemDeploymentTime.add(BOOTSTRAP_PERIOD),\\n \\\"TroveManager: Redemptions are not allowed during bootstrap phase\\\"\\n );\\n }\\n\\n function _requireValidMaxFeePercentage(uint256 _maxFeePercentage) internal view {\\n require(\\n _maxFeePercentage >= liquityBaseParams.REDEMPTION_FEE_FLOOR() &&\\n _maxFeePercentage <= DECIMAL_PRECISION,\\n \\\"Max fee percentage must be between 0.5% and 100%\\\"\\n );\\n }\\n}\\n\",\"keccak256\":\"0x83e1bfaa93cf973052c41df1cc0741a93f737659cd664f207c0d42256f78617b\",\"license\":\"MIT\"},\"contracts/Dependencies/console.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n// Buidler's helper contract for console logging\\nlibrary console {\\n\\taddress constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);\\n\\n\\tfunction log() internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log()\\\"));\\n\\t\\tignored;\\n\\t}\\tfunction logInt(int p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(int)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logUint(uint p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logString(string memory p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBool(bool p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logAddress(address p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes(bytes memory p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logByte(byte p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(byte)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes1(bytes1 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes1)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes2(bytes2 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes2)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes3(bytes3 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes3)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes4(bytes4 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes4)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes5(bytes5 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes5)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes6(bytes6 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes6)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes7(bytes7 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes7)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes8(bytes8 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes8)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes9(bytes9 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes9)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes10(bytes10 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes10)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes11(bytes11 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes11)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes12(bytes12 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes12)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes13(bytes13 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes13)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes14(bytes14 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes14)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes15(bytes15 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes15)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes16(bytes16 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes16)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes17(bytes17 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes17)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes18(bytes18 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes18)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes19(bytes19 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes19)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes20(bytes20 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes20)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes21(bytes21 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes21)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes22(bytes22 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes22)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes23(bytes23 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes23)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes24(bytes24 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes24)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes25(bytes25 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes25)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes26(bytes26 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes26)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes27(bytes27 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes27)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes28(bytes28 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes28)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes29(bytes29 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes29)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes30(bytes30 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes30)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes31(bytes31 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes31)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction logBytes32(bytes32 p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bytes32)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address)\\\", p0));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address)\\\", p0, p1));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, uint p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,uint)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, string memory p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,string)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, bool p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,bool)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, address p2) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,address)\\\", p0, p1, p2));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, uint p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,uint,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, string memory p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,string,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, bool p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,bool,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(uint p0, address p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(uint,address,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, uint p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,uint,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, string memory p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,string,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, bool p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,bool,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(string memory p0, address p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(string,address,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, uint p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,uint,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, string memory p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,string,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, bool p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,bool,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(bool p0, address p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(bool,address,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, uint p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,uint,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, string memory p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,string,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, bool p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,bool,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, uint p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,uint,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, uint p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,uint,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, uint p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,uint,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, uint p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,uint,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, string memory p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,string,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, string memory p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,string,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, string memory p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,string,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, string memory p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,string,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, bool p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,bool,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, bool p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,bool,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, bool p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,bool,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, bool p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,bool,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, address p2, uint p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,address,uint)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, address p2, string memory p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,address,string)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, address p2, bool p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,address,bool)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n\\tfunction log(address p0, address p1, address p2, address p3) internal view {\\n\\t\\t(bool ignored, ) = CONSOLE_ADDRESS.staticcall(abi.encodeWithSignature(\\\"log(address,address,address,address)\\\", p0, p1, p2, p3));\\n\\t\\tignored;\\n\\t}\\n\\n}\\n\",\"keccak256\":\"0x6fa1de4ffe22b8f58b0b64d65db11dd5037be9b9db47b365a72adb489e217000\",\"license\":\"MIT\"},\"contracts/Interfaces/IActivePool.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"./IPool.sol\\\";\\n\\n/**\\n * The Active Pool holds the ETH collateral and ZUSD debt (but not ZUSD tokens) for all active troves.\\n *\\n * When a trove is liquidated, it's ETH and ZUSD debt are transferred from the Active Pool, to either the\\n * Stability Pool, the Default Pool, or both, depending on the liquidation conditions.\\n *\\n */\\ninterface IActivePool is IPool {\\n // --- Events ---\\n event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);\\n event TroveManagerAddressChanged(address _newTroveManagerAddress);\\n event ActivePoolZUSDDebtUpdated(uint _ZUSDDebt);\\n event ActivePoolETHBalanceUpdated(uint _ETH);\\n\\n // --- Functions ---\\n\\n /// @notice Send ETH amount to given account. Updates ActivePool balance. Only callable by BorrowerOperations, TroveManager or StabilityPool.\\n /// @param _account account to receive the ETH amount\\n /// @param _amount ETH amount to send\\n function sendETH(address _account, uint _amount) external;\\n}\\n\",\"keccak256\":\"0xdd5f1b6fae4050b4c885a85a10c2d0e73b82187a51736d009065aaeea33bf0d0\",\"license\":\"MIT\"},\"contracts/Interfaces/IAllowanceTransfer.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.6.11;\\npragma experimental ABIEncoderV2;\\n\\nimport {IEIP712} from \\\"./IEIP712.sol\\\";\\n\\n/// @title AllowanceTransfer\\n/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts\\n/// @dev Requires user's token approval on the Permit2 contract\\ninterface IAllowanceTransfer is IEIP712 {\\n /// @notice Emits an event when the owner successfully invalidates an ordered nonce.\\n event NonceInvalidation(\\n address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce\\n );\\n\\n /// @notice Emits an event when the owner successfully sets permissions on a token for the spender.\\n event Approval(\\n address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration\\n );\\n\\n /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.\\n event Permit(\\n address indexed owner,\\n address indexed token,\\n address indexed spender,\\n uint160 amount,\\n uint48 expiration,\\n uint48 nonce\\n );\\n\\n /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.\\n event Lockdown(address indexed owner, address token, address spender);\\n\\n /// @notice The permit data for a token\\n struct PermitDetails {\\n // ERC20 token address\\n address token;\\n // the maximum amount allowed to spend\\n uint160 amount;\\n // timestamp at which a spender's token allowances become invalid\\n uint48 expiration;\\n // an incrementing value indexed per owner,token,and spender for each signature\\n uint48 nonce;\\n }\\n\\n /// @notice The permit message signed for a single token allowance\\n struct PermitSingle {\\n // the permit data for a single token alownce\\n PermitDetails details;\\n // address permissioned on the allowed tokens\\n address spender;\\n // deadline on the permit signature\\n uint256 sigDeadline;\\n }\\n\\n /// @notice The permit message signed for multiple token allowances\\n struct PermitBatch {\\n // the permit data for multiple token allowances\\n PermitDetails[] details;\\n // address permissioned on the allowed tokens\\n address spender;\\n // deadline on the permit signature\\n uint256 sigDeadline;\\n }\\n\\n /// @notice The saved permissions\\n /// @dev This info is saved per owner, per token, per spender and all signed over in the permit message\\n /// @dev Setting amount to type(uint160).max sets an unlimited approval\\n struct PackedAllowance {\\n // amount allowed\\n uint160 amount;\\n // permission expiry\\n uint48 expiration;\\n // an incrementing value indexed per owner,token,and spender for each signature\\n uint48 nonce;\\n }\\n\\n /// @notice A token spender pair.\\n struct TokenSpenderPair {\\n // the token the spender is approved\\n address token;\\n // the spender address\\n address spender;\\n }\\n\\n /// @notice Details for a token transfer.\\n struct AllowanceTransferDetails {\\n // the owner of the token\\n address from;\\n // the recipient of the token\\n address to;\\n // the amount of the token\\n uint160 amount;\\n // the token to be transferred\\n address token;\\n }\\n\\n /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.\\n /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]\\n /// @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.\\n function allowance(address user, address token, address spender)\\n external\\n view\\n returns (uint160 amount, uint48 expiration, uint48 nonce);\\n\\n /// @notice Approves the spender to use up to amount of the specified token up until the expiration\\n /// @param token The token to approve\\n /// @param spender The spender address to approve\\n /// @param amount The approved amount of the token\\n /// @param expiration The timestamp at which the approval is no longer valid\\n /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve\\n /// @dev Setting amount to type(uint160).max sets an unlimited approval\\n function approve(address token, address spender, uint160 amount, uint48 expiration) external;\\n\\n /// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature\\n /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce\\n /// @param owner The owner of the tokens being approved\\n /// @param permitSingle Data signed over by the owner specifying the terms of approval\\n /// @param signature The owner's signature over the permit data\\n function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;\\n\\n /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature\\n /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce\\n /// @param owner The owner of the tokens being approved\\n /// @param permitBatch Data signed over by the owner specifying the terms of approval\\n /// @param signature The owner's signature over the permit data\\n function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;\\n\\n /// @notice Transfer approved tokens from one address to another\\n /// @param from The address to transfer from\\n /// @param to The address of the recipient\\n /// @param amount The amount of the token to transfer\\n /// @param token The token address to transfer\\n /// @dev Requires the from address to have approved at least the desired amount\\n /// of tokens to msg.sender.\\n function transferFrom(address from, address to, uint160 amount, address token) external;\\n\\n /// @notice Transfer approved tokens in a batch\\n /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers\\n /// @dev Requires the from addresses to have approved at least the desired amount\\n /// of tokens to msg.sender.\\n function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;\\n\\n /// @notice Enables performing a \\\"lockdown\\\" of the sender's Permit2 identity\\n /// by batch revoking approvals\\n /// @param approvals Array of approvals to revoke.\\n function lockdown(TokenSpenderPair[] calldata approvals) external;\\n\\n /// @notice Invalidate nonces for a given (token, spender) pair\\n /// @param token The token to invalidate nonces for\\n /// @param spender The spender to invalidate nonces for\\n /// @param newNonce The new nonce to set. Invalidates all nonces less than it.\\n /// @dev Can't invalidate more than 2**16 nonces per transaction.\\n function invalidateNonces(address token, address spender, uint48 newNonce) external;\\n}\\n\",\"keccak256\":\"0xf15059fb68f89542908f963f22e18c0b0ae9997a6f9aaf6a9fb46aa2424acac9\",\"license\":\"MIT\"},\"contracts/Interfaces/ICollSurplusPool.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\ninterface ICollSurplusPool {\\n // --- Events ---\\n\\n event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);\\n event TroveManagerAddressChanged(address _newTroveManagerAddress);\\n event ActivePoolAddressChanged(address _newActivePoolAddress);\\n\\n event CollBalanceUpdated(address indexed _account, uint256 _newBalance);\\n event EtherSent(address _to, uint256 _amount);\\n\\n // --- Contract setters ---\\n\\n /**\\n * @notice Called only once on init, to set addresses of other Zero contracts. Callable only by owner\\n * @dev initializer function, checks addresses are contracts\\n * @param _borrowerOperationsAddress BorrowerOperations contract address\\n * @param _troveManagerAddress TroveManager contract address\\n * @param _activePoolAddress ActivePool contract address\\n */\\n function setAddresses(\\n address _borrowerOperationsAddress,\\n address _troveManagerAddress,\\n address _activePoolAddress\\n ) external;\\n\\n /// @notice Not necessarily equal to the raw ether balance - ether can be forcibly sent to contracts.\\n /// @return ETH state variable\\n function getETH() external view returns (uint256);\\n\\n /// @param _account account to retrieve collateral\\n /// @return collateral\\n function getCollateral(address _account) external view returns (uint256);\\n\\n /// @notice adds amount to current account balance. Only callable by TroveManager.\\n /// @param _account account to add amount\\n /// @param _amount amount to add\\n function accountSurplus(address _account, uint256 _amount) external;\\n\\n /// @notice claims collateral for given account. Only callable by BorrowerOperations.\\n /// @param _account account to send claimable collateral\\n function claimColl(address _account) external;\\n}\\n\",\"keccak256\":\"0xac983936efe70d19205bff65a18b4e6000d489d4e4d1e2e92f951873cee91048\",\"license\":\"MIT\"},\"contracts/Interfaces/IDefaultPool.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"./IPool.sol\\\";\\n\\ninterface IDefaultPool is IPool {\\n // --- Events ---\\n event TroveManagerAddressChanged(address _newTroveManagerAddress);\\n event DefaultPoolZUSDDebtUpdated(uint256 _ZUSDDebt);\\n event DefaultPoolETHBalanceUpdated(uint256 _ETH);\\n\\n // --- Functions ---\\n\\n /// @notice Send ETH to Active Pool\\n /// @param _amount ETH to send\\n function sendETHToActivePool(uint256 _amount) external;\\n}\\n\",\"keccak256\":\"0xfb2607676b2eb0f2defd248b4dd32895820048317f29aa6bdb572403a3e3d44e\",\"license\":\"MIT\"},\"contracts/Interfaces/IEIP712.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.6.11;\\n\\ninterface IEIP712 {\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0xff52e9168eaa532ebacdad2ab6197f60171e3aa2fa2c1d6397d9da4d7782a543\",\"license\":\"MIT\"},\"contracts/Interfaces/IFeeDistributor.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n/// Common interface for Fee Distributor.\\ninterface IFeeDistributor {\\n // --- Events ---\\n\\n event FeeSharingCollectorAddressChanged(address _feeSharingCollectorAddress);\\n event ZeroStakingAddressChanged(address _zeroStakingAddress);\\n event BorrowerOperationsAddressChanged(address _borrowerOperationsAddress);\\n event TroveManagerAddressChanged(address _troveManagerAddress);\\n event WrbtcAddressChanged(address _wrbtcAddress);\\n event ZUSDTokenAddressChanged(address _zusdTokenAddress);\\n event ActivePoolAddressSet(address _activePoolAddress);\\n\\n event ZUSDDistributed(uint256 _zusdDistributedAmount);\\n event RBTCistributed(uint256 _rbtcDistributedAmount);\\n\\n // --- Functions ---\\n\\n /**\\n * @notice Called only once on init, to set addresses of other Zero contracts. Callable only by owner\\n * @dev initializer function, checks addresses are contracts\\n * @param _feeSharingCollectorAddress FeeSharingCollector address\\n * @param _zeroStakingAddress ZEROStaking contract address\\n * @param _borrowerOperationsAddress borrowerOperations contract address\\n * @param _troveManagerAddress TroveManager contract address\\n * @param _wrbtcAddress wrbtc ERC20 contract address\\n * @param _zusdTokenAddress ZUSDToken contract address\\n * @param _activePoolAddress ActivePool contract address\\n */\\n function setAddresses(\\n address _feeSharingCollectorAddress,\\n address _zeroStakingAddress,\\n address _borrowerOperationsAddress,\\n address _troveManagerAddress,\\n address _wrbtcAddress,\\n address _zusdTokenAddress,\\n address _activePoolAddress\\n ) external;\\n\\n function distributeFees() external;\\n}\\n\",\"keccak256\":\"0x4b9bc6eaa8a9ea5e0570ffd84c0af2a92e74b001ae1ee1c8518d76382691a07f\",\"license\":\"MIT\"},\"contracts/Interfaces/ILiquityBase.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"./IPriceFeed.sol\\\";\\nimport \\\"./ILiquityBaseParams.sol\\\";\\n\\ninterface ILiquityBase {\\n /// @return PriceFeed contract\\n function priceFeed() external view returns (IPriceFeed);\\n\\n /// @return LiquityBaseParams contract\\n function liquityBaseParams() external view returns (ILiquityBaseParams);\\n}\\n\",\"keccak256\":\"0xa4a57bd79e64d56a687c28d2a35c55b733fde8dda2a7ba861606eed3211724e1\",\"license\":\"MIT\"},\"contracts/Interfaces/ILiquityBaseParams.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\ninterface ILiquityBaseParams {\\n\\n /// Minimum collateral ratio for individual troves\\n function MCR() external view returns (uint);\\n\\n /// Critical system collateral ratio. If the system's total collateral ratio (TCR) falls below the CCR, Recovery Mode is triggered.\\n function CCR() external view returns (uint);\\n\\n function PERCENT_DIVISOR() external view returns (uint);\\n\\n function BORROWING_FEE_FLOOR() external view returns (uint);\\n\\n /**\\n * Half-life of 12h. 12h = 720 min\\n * (1/2) = d^720 => d = (1/2)^(1/720)\\n */\\n function REDEMPTION_FEE_FLOOR() external view returns (uint);\\n\\n function MAX_BORROWING_FEE() external view returns (uint);\\n\\n}\",\"keccak256\":\"0xef8c0e8ad5d13d604c11b04983ff5bdd41768b646f2b33f45ddd988adec204e0\",\"license\":\"MIT\"},\"contracts/Interfaces/IPermit2.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.6.11;\\npragma experimental ABIEncoderV2;\\n\\nimport {ISignatureTransfer} from \\\"./ISignatureTransfer.sol\\\";\\nimport {IAllowanceTransfer} from \\\"./IAllowanceTransfer.sol\\\";\\n\\n/// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer.\\n/// @dev Users must approve Permit2 before calling any of the transfer functions.\\ninterface IPermit2 is ISignatureTransfer, IAllowanceTransfer {\\n// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval.\\n}\\n\",\"keccak256\":\"0x3df819f5ca8de7324a676839d72e9f44c0f789c41c13bf0a892f3bb98d72ee86\",\"license\":\"MIT\"},\"contracts/Interfaces/IPool.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n// Common interface for the Pools.\\ninterface IPool {\\n // --- Events ---\\n\\n event ETHBalanceUpdated(uint _newBalance);\\n event ZUSDBalanceUpdated(uint _newBalance);\\n event ActivePoolAddressChanged(address _newActivePoolAddress);\\n event DefaultPoolAddressChanged(address _newDefaultPoolAddress);\\n event StabilityPoolAddressChanged(address _newStabilityPoolAddress);\\n event EtherSent(address _to, uint _amount);\\n\\n // --- Functions ---\\n\\n /// @notice Not necessarily equal to the raw ether balance - ether can be forcibly sent to contracts.\\n /// @return ETH pool balance\\n function getETH() external view returns (uint);\\n\\n /// @return ZUSD debt pool balance\\n function getZUSDDebt() external view returns (uint);\\n\\n /// @notice Increases ZUSD debt of the pool.\\n /// @param _amount ZUSD amount to add to the pool debt\\n function increaseZUSDDebt(uint _amount) external;\\n\\n /// @notice Decreases ZUSD debt of the pool.\\n /// @param _amount ZUSD amount to subtract to the pool debt\\n function decreaseZUSDDebt(uint _amount) external;\\n}\\n\",\"keccak256\":\"0x148e87ab38c6176d74f36c9e8989b99e768a7b18d8a045f1f01d6583b986806d\",\"license\":\"MIT\"},\"contracts/Interfaces/IPriceFeed.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\ninterface IPriceFeed {\\n // --- Events ---\\n event LastGoodPriceUpdated(uint256 _lastGoodPrice);\\n\\n // --- Function ---\\n\\n /// @notice Returns the latest price obtained from the Oracle. Called by Zero functions that require a current price.\\n /// It uses the main price feed and fallback to the backup one in case of an error. If both fail return the last\\n /// good price seen.\\n /// @dev It's also callable by anyone externally\\n /// @return The price\\n function fetchPrice() external returns (uint256);\\n}\\n\",\"keccak256\":\"0x85fd97219a8156209d2cb5c6ae7c5ead01d893db000bf575023fcef0e62f9591\",\"license\":\"MIT\"},\"contracts/Interfaces/ISignatureTransfer.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.6.11;\\npragma experimental ABIEncoderV2;\\n\\nimport {IEIP712} from \\\"./IEIP712.sol\\\";\\n\\n/// @title SignatureTransfer\\n/// @notice Handles ERC20 token transfers through signature based actions\\n/// @dev Requires user's token approval on the Permit2 contract\\ninterface ISignatureTransfer is IEIP712 {\\n /// @notice Emits an event when the owner successfully invalidates an unordered nonce.\\n event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);\\n\\n /// @notice The token and amount details for a transfer signed in the permit transfer signature\\n struct TokenPermissions {\\n // ERC20 token address\\n address token;\\n // the maximum amount that can be spent\\n uint256 amount;\\n }\\n\\n /// @notice The signed permit message for a single token transfer\\n struct PermitTransferFrom {\\n TokenPermissions permitted;\\n // a unique value for every token owner's signature to prevent signature replays\\n uint256 nonce;\\n // deadline on the permit signature\\n uint256 deadline;\\n }\\n\\n /// @notice Specifies the recipient address and amount for batched transfers.\\n /// @dev Recipients and amounts correspond to the index of the signed token permissions array.\\n /// @dev Reverts if the requested amount is greater than the permitted signed amount.\\n struct SignatureTransferDetails {\\n // recipient address\\n address to;\\n // spender requested amount\\n uint256 requestedAmount;\\n }\\n\\n /// @notice Used to reconstruct the signed permit message for multiple token transfers\\n /// @dev Do not need to pass in spender address as it is required that it is msg.sender\\n /// @dev Note that a user still signs over a spender address\\n struct PermitBatchTransferFrom {\\n // the tokens and corresponding amounts permitted for a transfer\\n TokenPermissions[] permitted;\\n // a unique value for every token owner's signature to prevent signature replays\\n uint256 nonce;\\n // deadline on the permit signature\\n uint256 deadline;\\n }\\n\\n /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection\\n /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order\\n /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce\\n /// @dev It returns a uint256 bitmap\\n /// @dev The index, or wordPosition is capped at type(uint248).max\\n function nonceBitmap(address, uint256) external view returns (uint256);\\n\\n /// @notice Transfers a token using a signed permit message\\n /// @dev Reverts if the requested amount is greater than the permitted signed amount\\n /// @param permit The permit data signed over by the owner\\n /// @param owner The owner of the tokens to transfer\\n /// @param transferDetails The spender's requested transfer details for the permitted token\\n /// @param signature The signature to verify\\n function permitTransferFrom(\\n PermitTransferFrom memory permit,\\n SignatureTransferDetails calldata transferDetails,\\n address owner,\\n bytes calldata signature\\n ) external;\\n\\n /// @notice Transfers multiple tokens using a signed permit message\\n /// @param permit The permit data signed over by the owner\\n /// @param owner The owner of the tokens to transfer\\n /// @param transferDetails Specifies the recipient and requested amount for the token transfer\\n /// @param signature The signature to verify\\n function permitTransferFrom(\\n PermitBatchTransferFrom memory permit,\\n SignatureTransferDetails[] calldata transferDetails,\\n address owner,\\n bytes calldata signature\\n ) external;\\n\\n /// @notice Invalidates the bits specified in mask for the bitmap at the word position\\n /// @dev The wordPos is maxed at type(uint248).max\\n /// @param wordPos A number to index the nonceBitmap at\\n /// @param mask A bitmap masked against msg.sender's current bitmap at the word position\\n function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;\\n}\\n\",\"keccak256\":\"0x7efc63c119694e23dd76e44a5b125999829026bbc23409de7646a6a45e1ac341\",\"license\":\"MIT\"},\"contracts/Interfaces/ISortedTroves.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\n// Common interface for the SortedTroves Doubly Linked List.\\ninterface ISortedTroves {\\n // --- Events ---\\n\\n event SortedTrovesAddressChanged(address _sortedDoublyLLAddress);\\n event BorrowerOperationsAddressChanged(address _borrowerOperationsAddress);\\n event NodeAdded(address _id, uint256 _NICR);\\n event NodeRemoved(address _id);\\n\\n // --- Functions ---\\n\\n /**\\n * @notice Called only once on init, to set addresses of other Zero contracts and size. Callable only by owner\\n * @dev initializer function, checks addresses are contracts\\n * @param _size max size of troves list\\n * @param _TroveManagerAddress TroveManager contract address\\n * @param _borrowerOperationsAddress BorrowerOperations contract address\\n */\\n function setParams(\\n uint256 _size,\\n address _TroveManagerAddress,\\n address _borrowerOperationsAddress\\n ) external;\\n\\n /**\\n * @dev Add a node to the list\\n * @param _id Node's id\\n * @param _ICR Node's NICR\\n * @param _prevId Id of previous node for the insert position\\n * @param _nextId Id of next node for the insert position\\n */\\n function insert(\\n address _id,\\n uint256 _ICR,\\n address _prevId,\\n address _nextId\\n ) external;\\n\\n /**\\n * @dev Remove a node from the list\\n * @param _id Node's id\\n */\\n function remove(address _id) external;\\n\\n /**\\n * @dev Re-insert the node at a new position, based on its new NICR\\n * @param _id Node's id\\n * @param _newICR Node's new NICR\\n * @param _prevId Id of previous node for the new insert position\\n * @param _nextId Id of next node for the new insert position\\n */\\n function reInsert(\\n address _id,\\n uint256 _newICR,\\n address _prevId,\\n address _nextId\\n ) external;\\n\\n /**\\n * @dev Checks if the list contains a node\\n * @param _id Node's id\\n * @return true if list contains a node with given id\\n */\\n function contains(address _id) external view returns (bool);\\n\\n /**\\n * @dev Checks if the list is full\\n * @return true if list is full\\n */\\n function isFull() external view returns (bool);\\n\\n /**\\n * @dev Checks if the list is empty\\n * @return true if list is empty\\n */\\n function isEmpty() external view returns (bool);\\n\\n /**\\n * @return list current size\\n */\\n function getSize() external view returns (uint256);\\n\\n /**\\n * @return list max size\\n */\\n function getMaxSize() external view returns (uint256);\\n\\n /**\\n * @return the first node in the list (node with the largest NICR)\\n */\\n function getFirst() external view returns (address);\\n\\n /**\\n * @return the last node in the list (node with the smallest NICR)\\n */\\n function getLast() external view returns (address);\\n\\n /**\\n * @param _id Node's id\\n * @return the next node (with a smaller NICR) in the list for a given node\\n */\\n function getNext(address _id) external view returns (address);\\n\\n /**\\n * @param _id Node's id\\n * @return the previous node (with a larger NICR) in the list for a given node\\n */\\n function getPrev(address _id) external view returns (address);\\n\\n /**\\n * @notice Check if a pair of nodes is a valid insertion point for a new node with the given NICR\\n * @param _ICR Node's NICR\\n * @param _prevId Id of previous node for the insert position\\n * @param _nextId Id of next node for the insert position\\n */\\n function validInsertPosition(\\n uint256 _ICR,\\n address _prevId,\\n address _nextId\\n ) external view returns (bool);\\n\\n /**\\n * @notice Find the insert position for a new node with the given NICR\\n * @param _ICR Node's NICR\\n * @param _prevId Id of previous node for the insert position\\n * @param _nextId Id of next node for the insert position\\n */\\n function findInsertPosition(\\n uint256 _ICR,\\n address _prevId,\\n address _nextId\\n ) external view returns (address, address);\\n}\\n\",\"keccak256\":\"0x7328ad009da6230ddea1559564428464a5c3ace2258fb534dfbba5b5a8c7c60d\",\"license\":\"MIT\"},\"contracts/Interfaces/IStabilityPool.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"../Dependencies/Mynt/IMassetManager.sol\\\";\\nimport { IPermit2, ISignatureTransfer } from \\\"./IPermit2.sol\\\";\\n\\n/*\\n * The Stability Pool holds ZUSD tokens deposited by Stability Pool depositors.\\n *\\n * When a trove is liquidated, then depending on system conditions, some of its ZUSD debt gets offset with\\n * ZUSD in the Stability Pool: that is, the offset debt evaporates, and an equal amount of ZUSD tokens in the Stability Pool is burned.\\n *\\n * Thus, a liquidation causes each depositor to receive a ZUSD loss, in proportion to their deposit as a share of total deposits.\\n * They also receive an ETH gain, as the ETH collateral of the liquidated trove is distributed among Stability depositors,\\n * in the same proportion.\\n *\\n * When a liquidation occurs, it depletes every deposit by the same fraction: for example, a liquidation that depletes 40%\\n * of the total ZUSD in the Stability Pool, depletes 40% of each deposit.\\n *\\n * A deposit that has experienced a series of liquidations is termed a \\\"compounded deposit\\\": each liquidation depletes the deposit,\\n * multiplying it by some factor in range ]0,1[\\n *\\n * Please see the implementation spec in the proof document, which closely follows on from the compounded deposit / ETH gain derivations:\\n * https://github.com/liquity/liquity/blob/master/papers/Scalable_Reward_Distribution_with_Compounding_Stakes.pdf\\n *\\n * --- SOV ISSUANCE TO STABILITY POOL DEPOSITORS ---\\n *\\n * An SOV issuance event occurs at every deposit operation, and every liquidation.\\n *\\n * Each deposit is tagged with the address of the front end through which it was made.\\n *\\n * All deposits earn a share of the issued SOV in proportion to the deposit as a share of total deposits. The SOV earned\\n * by a given deposit, is split between the depositor and the front end through which the deposit was made, based on the front end's kickbackRate.\\n *\\n * Please see the system Readme for an overview:\\n * https://github.com/liquity/dev/blob/main/README.md#zero-issuance-to-stability-providers\\n */\\ninterface IStabilityPool {\\n // --- Events ---\\n\\n event StabilityPoolETHBalanceUpdated(uint _newBalance);\\n event StabilityPoolZUSDBalanceUpdated(uint _newBalance);\\n\\n event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);\\n event TroveManagerAddressChanged(address _newTroveManagerAddress);\\n event ActivePoolAddressChanged(address _newActivePoolAddress);\\n event DefaultPoolAddressChanged(address _newDefaultPoolAddress);\\n event ZUSDTokenAddressChanged(address _newZUSDTokenAddress);\\n event SortedTrovesAddressChanged(address _newSortedTrovesAddress);\\n event PriceFeedAddressChanged(address _newPriceFeedAddress);\\n event CommunityIssuanceAddressChanged(address _newCommunityIssuanceAddress);\\n\\n event P_Updated(uint _P);\\n event S_Updated(uint _S, uint128 _epoch, uint128 _scale);\\n event G_Updated(uint _G, uint128 _epoch, uint128 _scale);\\n event EpochUpdated(uint128 _currentEpoch);\\n event ScaleUpdated(uint128 _currentScale);\\n\\n event FrontEndRegistered(address indexed _frontEnd, uint _kickbackRate);\\n event FrontEndTagSet(address indexed _depositor, address indexed _frontEnd);\\n\\n event DepositSnapshotUpdated(address indexed _depositor, uint _P, uint _S, uint _G);\\n event FrontEndSnapshotUpdated(address indexed _frontEnd, uint _P, uint _G);\\n event UserDepositChanged(address indexed _depositor, uint _newDeposit);\\n event FrontEndStakeChanged(\\n address indexed _frontEnd,\\n uint _newFrontEndStake,\\n address _depositor\\n );\\n\\n event ETHGainWithdrawn(address indexed _depositor, uint _ETH, uint _ZUSDLoss);\\n event SOVPaidToDepositor(address indexed _depositor, uint _SOV);\\n event SOVPaidToFrontEnd(address indexed _frontEnd, uint _SOV);\\n event EtherSent(address _to, uint _amount);\\n\\n event WithdrawFromSpAndConvertToDLLR(\\n address _depositor,\\n uint256 _zusdAmountRequested,\\n uint256 _dllrAmountReceived\\n );\\n\\n // --- Functions ---\\n\\n /**\\n * @notice Called only once on init, to set addresses of other Liquity contracts. Callable only by owner\\n * @dev initializer function, checks addresses are contracts\\n * @param _liquityBaseParamsAddress LiquidityBaseParams contract address\\n * @param _borrowerOperationsAddress BorrowerOperations contract address\\n * @param _troveManagerAddress TroveManager contract address\\n * @param _activePoolAddress ActivePool contract address\\n * @param _zusdTokenAddress ZUSDToken contract address\\n * @param _sortedTrovesAddress SortedTroves contract address\\n * @param _priceFeedAddress PriceFeed contract address\\n * @param _communityIssuanceAddress CommunityIssuanceAddress\\n */\\n function setAddresses(\\n address _liquityBaseParamsAddress,\\n address _borrowerOperationsAddress,\\n address _troveManagerAddress,\\n address _activePoolAddress,\\n address _zusdTokenAddress,\\n address _sortedTrovesAddress,\\n address _priceFeedAddress,\\n address _communityIssuanceAddress\\n ) external;\\n\\n /**\\n * @notice Initial checks:\\n * - Frontend is registered or zero address\\n * - Sender is not a registered frontend\\n * - _amount is not zero\\n * ---\\n * - Triggers a SOV issuance, based on time passed since the last issuance. The SOV issuance is shared between *all* depositors and front ends\\n * - Tags the deposit with the provided front end tag param, if it's a new deposit\\n * - Sends depositor's accumulated gains (SOV, ETH) to depositor\\n * - Sends the tagged front end's accumulated SOV gains to the tagged front end\\n * - Increases deposit and tagged front end's stake, and takes new snapshots for each.\\n * @param _amount amount to provide\\n * @param _frontEndTag frontend address to receive accumulated SOV gains\\n */\\n function provideToSP(uint _amount, address _frontEndTag) external;\\n\\n /**\\n * @notice Initial checks:\\n * - _amount is zero or there are no under collateralized troves left in the system\\n * - User has a non zero deposit\\n * ---\\n * - Triggers a SOV issuance, based on time passed since the last issuance. The SOV issuance is shared between *all* depositors and front ends\\n * - Removes the deposit's front end tag if it is a full withdrawal\\n * - Sends all depositor's accumulated gains (SOV, ETH) to depositor\\n * - Sends the tagged front end's accumulated SOV gains to the tagged front end\\n * - Decreases deposit and tagged front end's stake, and takes new snapshots for each.\\n *\\n * If _amount > userDeposit, the user withdraws all of their compounded deposit.\\n * @param _amount amount to withdraw\\n */\\n function withdrawFromSP(uint _amount) external;\\n\\n /**\\n * @notice Initial checks:\\n * - User has a non zero deposit\\n * - User has an open trove\\n * - User has some ETH gain\\n * ---\\n * - Triggers a SOV issuance, based on time passed since the last issuance. The SOV issuance is shared between *all* depositors and front ends\\n * - Sends all depositor's SOV gain to depositor\\n * - Sends all tagged front end's SOV gain to the tagged front end\\n * - Transfers the depositor's entire ETH gain from the Stability Pool to the caller's trove\\n * - Leaves their compounded deposit in the Stability Pool\\n * - Updates snapshots for deposit and tagged front end stake\\n * @param _upperHint upper trove id hint\\n * @param _lowerHint lower trove id hint\\n */\\n function withdrawETHGainToTrove(address _upperHint, address _lowerHint) external;\\n\\n /**\\n * @notice Initial checks:\\n * - Frontend (sender) not already registered\\n * - User (sender) has no deposit\\n * - _kickbackRate is in the range [0, 100%]\\n * ---\\n * Front end makes a one-time selection of kickback rate upon registering\\n * @param _kickbackRate kickback rate selected by frontend\\n */\\n function registerFrontEnd(uint _kickbackRate) external;\\n\\n /**\\n * @notice Initial checks:\\n * - Caller is TroveManager\\n * ---\\n * Cancels out the specified debt against the ZUSD contained in the Stability Pool (as far as possible)\\n * and transfers the Trove's ETH collateral from ActivePool to StabilityPool.\\n * Only called by liquidation functions in the TroveManager.\\n * @param _debt debt to cancel\\n * @param _coll collateral to transfer\\n */\\n function offset(uint _debt, uint _coll) external;\\n\\n /**\\n * @return the total amount of ETH held by the pool, accounted in an internal variable instead of `balance`,\\n * to exclude edge cases like ETH received from a self-destruct.\\n */\\n function getETH() external view returns (uint);\\n\\n /**\\n * @return ZUSD held in the pool. Changes when users deposit/withdraw, and when Trove debt is offset.\\n */\\n function getTotalZUSDDeposits() external view returns (uint);\\n\\n /**\\n * @notice Calculates the ETH gain earned by the deposit since its last snapshots were taken.\\n * @param _depositor address to calculate ETH gain\\n * @return ETH gain from given depositor\\n */\\n function getDepositorETHGain(address _depositor) external view returns (uint);\\n\\n /**\\n * @notice Calculate the SOV gain earned by a deposit since its last snapshots were taken.\\n * If not tagged with a front end, the depositor gets a 100% cut of what their deposit earned.\\n * Otherwise, their cut of the deposit's earnings is equal to the kickbackRate, set by the front end through\\n * which they made their deposit.\\n * @param _depositor address to calculate ETH gain\\n * @return SOV gain from given depositor\\n */\\n function getDepositorSOVGain(address _depositor) external view returns (uint);\\n\\n /**\\n * @param _frontEnd front end address\\n * @return the SOV gain earned by the front end.\\n */\\n function getFrontEndSOVGain(address _frontEnd) external view returns (uint);\\n\\n /**\\n * @param _depositor depositor address\\n * @return the user's compounded deposit.\\n */\\n function getCompoundedZUSDDeposit(address _depositor) external view returns (uint);\\n\\n /**\\n * @notice The front end's compounded stake is equal to the sum of its depositors' compounded deposits.\\n * @param _frontEnd front end address\\n * @return the front end's compounded stake.\\n */\\n function getCompoundedFrontEndStake(address _frontEnd) external view returns (uint);\\n\\n //DLLR _owner or _spender can convert a specified amount of DLLR into ZUSD via Sovryn Mynt and deposit the ZUSD into the Zero Stability Pool, all in a single transaction\\n function provideToSpFromDLLR(\\n uint _dllrAmount,\\n IMassetManager.PermitParams calldata _permitParams\\n ) external;\\n\\n function provideToSpFromDllrWithPermit2(\\n uint256 _dllrAmount,\\n ISignatureTransfer.PermitTransferFrom memory _permit,\\n bytes calldata _signature\\n ) external;\\n\\n /// Stability Pool depositor can withdraw a specified amount of ZUSD from the Zero Stability Pool and optionally convert the ZUSD to DLLR via Sovryn Mynt, all in a single transaction\\n function withdrawFromSpAndConvertToDLLR(uint256 _zusdAmount) external;\\n\\n /**\\n * Fallback function\\n * Only callable by Active Pool, it just accounts for ETH received\\n * receive() external payable;\\n */\\n}\\n\",\"keccak256\":\"0xb35c5ec991dd2b4f8ecb6b28ae29e97313fca6054aa0df14ebdb7336fcea84a6\",\"license\":\"MIT\"},\"contracts/Interfaces/ITroveManager.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./ILiquityBase.sol\\\";\\nimport \\\"./IStabilityPool.sol\\\";\\nimport \\\"./IZUSDToken.sol\\\";\\nimport \\\"./IZEROToken.sol\\\";\\nimport \\\"./IZEROStaking.sol\\\";\\nimport \\\"../Dependencies/Mynt/IMassetManager.sol\\\";\\nimport { IPermit2, ISignatureTransfer } from \\\"./IPermit2.sol\\\";\\n\\n/// Common interface for the Trove Manager.\\ninterface ITroveManager is ILiquityBase {\\n // --- Events ---\\n\\n event FeeDistributorAddressChanged(address _feeDistributorAddress);\\n event TroveManagerRedeemOpsAddressChanged(address _troveManagerRedeemOps);\\n event LiquityBaseParamsAddressChanges(address _borrowerOperationsAddress);\\n event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);\\n event PriceFeedAddressChanged(address _newPriceFeedAddress);\\n event ZUSDTokenAddressChanged(address _newZUSDTokenAddress);\\n event ActivePoolAddressChanged(address _activePoolAddress);\\n event DefaultPoolAddressChanged(address _defaultPoolAddress);\\n event StabilityPoolAddressChanged(address _stabilityPoolAddress);\\n event GasPoolAddressChanged(address _gasPoolAddress);\\n event CollSurplusPoolAddressChanged(address _collSurplusPoolAddress);\\n event SortedTrovesAddressChanged(address _sortedTrovesAddress);\\n event ZEROTokenAddressChanged(address _zeroTokenAddress);\\n event ZEROStakingAddressChanged(address _zeroStakingAddress);\\n\\n event Liquidation(\\n uint256 _liquidatedDebt,\\n uint256 _liquidatedColl,\\n uint256 _collGasCompensation,\\n uint256 _ZUSDGasCompensation\\n );\\n event Redemption(\\n uint256 _attemptedZUSDAmount,\\n uint256 _actualZUSDAmount,\\n uint256 _ETHSent,\\n uint256 _ETHFee\\n );\\n event TroveUpdated(\\n address indexed _borrower,\\n uint256 _debt,\\n uint256 _coll,\\n uint256 stake,\\n uint8 operation\\n );\\n event TroveLiquidated(\\n address indexed _borrower,\\n uint256 _debt,\\n uint256 _coll,\\n uint8 operation\\n );\\n event BaseRateUpdated(uint256 _baseRate);\\n event LastFeeOpTimeUpdated(uint256 _lastFeeOpTime);\\n event TotalStakesUpdated(uint256 _newTotalStakes);\\n event SystemSnapshotsUpdated(uint256 _totalStakesSnapshot, uint256 _totalCollateralSnapshot);\\n event LTermsUpdated(uint256 _L_ETH, uint256 _L_ZUSDDebt);\\n event TroveSnapshotsUpdated(uint256 _L_ETH, uint256 _L_ZUSDDebt);\\n event TroveIndexUpdated(address _borrower, uint256 _newIndex);\\n\\n struct TroveManagerInitAddressesParams {\\n address _feeDistributorAddress;\\n address _troveManagerRedeemOps;\\n address _liquityBaseParamsAddress;\\n address _borrowerOperationsAddress;\\n address _activePoolAddress;\\n address _defaultPoolAddress;\\n address _stabilityPoolAddress;\\n address _gasPoolAddress;\\n address _collSurplusPoolAddress;\\n address _priceFeedAddress;\\n address _zusdTokenAddress;\\n address _sortedTrovesAddress;\\n address _zeroTokenAddress;\\n address _zeroStakingAddress;\\n }\\n\\n // --- Functions ---\\n /**\\n * @notice Called only once on init, to set addresses of other Zero contracts. Callable only by owner\\n * @dev initializer function, checks addresses are contracts\\n * @param _troveManagerInitAddresses addresses list to intialize TroveManager with _\\n * _feeDistributorAddress feeDistributor contract address\\n * _troveManagerRedeemOps TroveManagerRedeemOps contract address\\n * _liquityBaseParamsAddress LiquityBaseParams contract address\\n * _borrowerOperationsAddress BorrowerOperations contract address\\n * _activePoolAddress ActivePool contract address\\n * _defaultPoolAddress DefaultPool contract address\\n * _stabilityPoolAddress StabilityPool contract address\\n * _gasPoolAddress GasPool contract address\\n * _collSurplusPoolAddress CollSurplusPool contract address\\n * _priceFeedAddress PriceFeed contract address\\n * _zusdTokenAddress ZUSDToken contract address\\n * _sortedTrovesAddress SortedTroves contract address\\n * _zeroTokenAddress ZEROToken contract address\\n * _zeroStakingAddress ZEROStaking contract address\\n */\\n function setAddresses(\\n TroveManagerInitAddressesParams memory _troveManagerInitAddresses\\n ) external;\\n\\n function setTroveManagerRedeemOps(address _troveManagerRedeemOps) external;\\n\\n /// @return Trove owners count\\n function getTroveOwnersCount() external view returns (uint256);\\n\\n /// @param _index Trove owner index\\n /// @return Trove from TroveOwners array in given index\\n function getTroveFromTroveOwnersArray(uint256 _index) external view returns (address);\\n\\n /// @param _borrower borrower address\\n /// @return the nominal collateral ratio (ICR) of a given Trove, without the price. Takes a trove's pending coll and debt rewards from redistributions into account.\\n function getNominalICR(address _borrower) external view returns (uint256);\\n\\n /// @notice computes the user\\u2019s individual collateralization ratio (ICR) based on their total collateral and total ZUSD debt. Returns 2^256 -1 if they have 0 debt.\\n /// @param _borrower borrower address\\n /// @param _price ETH price\\n /// @return the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account.\\n function getCurrentICR(address _borrower, uint256 _price) external view returns (uint256);\\n\\n /// @notice Closes the trove if its ICR is lower than the minimum collateral ratio.\\n /// @param _borrower borrower address\\n function liquidate(address _borrower) external;\\n\\n /**\\n * @notice Liquidate a sequence of troves. Closes a maximum number of n under-collateralized Troves,\\n * starting from the one with the lowest collateral ratio in the system, and moving upwards\\n * @param _n max number of under-collateralized Troves to liquidate\\n */\\n function liquidateTroves(uint256 _n) external;\\n\\n /**\\n * @notice Attempt to liquidate a custom list of troves provided by the caller.\\n * @param _troveArray list of trove addresses\\n */\\n function batchLiquidateTroves(address[] calldata _troveArray) external;\\n\\n /**\\n * @notice Send _ZUSDamount ZUSD to the system and redeem the corresponding amount of collateral from as many Troves as are needed to fill the redemption\\n * request. Applies pending rewards to a Trove before reducing its debt and coll.\\n *\\n * Note that if _amount is very large, this function can run out of gas, specially if traversed troves are small. This can be easily avoided by\\n * splitting the total _amount in appropriate chunks and calling the function multiple times.\\n *\\n * Param `_maxIterations` can also be provided, so the loop through Troves is capped (if it\\u2019s zero, it will be ignored).This makes it easier to\\n * avoid OOG for the frontend, as only knowing approximately the average cost of an iteration is enough, without needing to know the \\u201ctopology\\u201d\\n * of the trove list. It also avoids the need to set the cap in stone in the contract, nor doing gas calculations, as both gas price and opcode\\n * costs can vary.\\n *\\n * All Troves that are redeemed from -- with the likely exception of the last one -- will end up with no debt left, therefore they will be closed.\\n * If the last Trove does have some remaining debt, it has a finite ICR, and the reinsertion could be anywhere in the list, therefore it requires a hint.\\n * A frontend should use getRedemptionHints() to calculate what the ICR of this Trove will be after redemption, and pass a hint for its position\\n * in the sortedTroves list along with the ICR value that the hint was found for.\\n *\\n * If another transaction modifies the list between calling getRedemptionHints() and passing the hints to redeemCollateral(), it\\n * is very likely that the last (partially) redeemed Trove would end up with a different ICR than what the hint is for. In this case the\\n * redemption will stop after the last completely redeemed Trove and the sender will keep the remaining ZUSD amount, which they can attempt\\n * to redeem later.\\n *\\n * @param _ZUSDAmount ZUSD amount to send to the system\\n * @param _firstRedemptionHint calculated ICR hint of first trove after redemption\\n * @param _maxIterations max Troves iterations (can be 0)\\n * @param _maxFee max fee percentage to accept\\n */\\n function redeemCollateral(\\n uint256 _ZUSDAmount,\\n address _firstRedemptionHint,\\n address _upperPartialRedemptionHint,\\n address _lowerPartialRedemptionHint,\\n uint256 _partialRedemptionHintNICR,\\n uint256 _maxIterations,\\n uint256 _maxFee\\n ) external;\\n\\n function redeemCollateralViaDLLR(\\n uint256 _dllrAmount,\\n address _firstRedemptionHint,\\n address _upperPartialRedemptionHint,\\n address _lowerPartialRedemptionHint,\\n uint256 _partialRedemptionHintNICR,\\n uint256 _maxIterations,\\n uint256 _maxFeePercentage,\\n IMassetManager.PermitParams calldata _permitParams\\n ) external;\\n\\n function redeemCollateralViaDllrWithPermit2(\\n uint256 _dllrAmount,\\n address _firstRedemptionHint,\\n address _upperPartialRedemptionHint,\\n address _lowerPartialRedemptionHint,\\n uint256 _partialRedemptionHintNICR,\\n uint256 _maxIterations,\\n uint256 _maxFeePercentage,\\n ISignatureTransfer.PermitTransferFrom memory _permit,\\n bytes calldata _signature\\n ) external;\\n \\n\\n /// @notice Update borrower's stake based on their latest collateral value\\n /// @param _borrower borrower address\\n function updateStakeAndTotalStakes(address _borrower) external returns (uint256);\\n\\n /// @notice Update borrower's snapshots of L_ETH and L_ZUSDDebt to reflect the current values\\n /// @param _borrower borrower address\\n function updateTroveRewardSnapshots(address _borrower) external;\\n\\n /// @notice Push the owner's address to the Trove owners list, and record the corresponding array index on the Trove struct\\n /// @param _borrower borrower address\\n /// @return index where Trove was inserted\\n function addTroveOwnerToArray(address _borrower) external returns (uint256 index);\\n\\n /// @notice Add the borrowers's coll and debt rewards earned from redistributions, to their Trove\\n /// @param _borrower borrower address\\n function applyPendingRewards(address _borrower) external;\\n\\n /// @param _borrower borrower address\\n /// @return the borrower's pending accumulated ETH reward, earned by their stake\\n function getPendingETHReward(address _borrower) external view returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @return the borrower's pending accumulated ZUSD reward, earned by their stake\\n function getPendingZUSDDebtReward(address _borrower) external view returns (uint256);\\n\\n /*\\n * @notice A Trove has pending rewards if its snapshot is less than the current rewards per-unit-staked sum:\\n * this indicates that rewards have occured since the snapshot was made, and the user therefore has\\n * pending rewards\\n *\\n * @param _borrower borrower address\\n * @return true if has pending rewards\\n */\\n function hasPendingRewards(address _borrower) external view returns (bool);\\n\\n /// @notice returns the Troves entire debt and coll, including pending rewards from redistributions.\\n /// @param _borrower borrower address\\n function getEntireDebtAndColl(\\n address _borrower\\n )\\n external\\n view\\n returns (\\n uint256 debt,\\n uint256 coll,\\n uint256 pendingZUSDDebtReward,\\n uint256 pendingETHReward\\n );\\n\\n /// @notice Close given trove. Called by BorrowerOperations.\\n /// @param _borrower borrower address\\n function closeTrove(address _borrower) external;\\n\\n /// @notice Remove borrower's stake from the totalStakes sum, and set their stake to 0\\n /// @param _borrower borrower address\\n function removeStake(address _borrower) external;\\n\\n /// @return calculated redemption rate using baseRate\\n function getRedemptionRate() external view returns (uint256);\\n\\n /// @return calculated redemption rate using calculated decayed as base rate\\n function getRedemptionRateWithDecay() external view returns (uint256);\\n\\n /// @notice The redemption fee is taken as a cut of the total ETH drawn from the system in a redemption. It is based on the current redemption rate.\\n /// @param _ETHDrawn ETH drawn\\n function getRedemptionFeeWithDecay(uint256 _ETHDrawn) external view returns (uint256);\\n\\n /// @return borrowing rate\\n function getBorrowingRate() external view returns (uint256);\\n\\n /// @return borrowing rate calculated using decayed as base rate\\n function getBorrowingRateWithDecay() external view returns (uint256);\\n\\n /// @param ZUSDDebt ZUSD debt amount to calculate fee\\n /// @return borrowing fee using borrowing rate\\n function getBorrowingFee(uint256 ZUSDDebt) external view returns (uint256);\\n\\n /// @param _ZUSDDebt ZUSD debt amount to calculate fee\\n /// @return borrowing fee using borrowing rate with decay\\n function getBorrowingFeeWithDecay(uint256 _ZUSDDebt) external view returns (uint256);\\n\\n /// @notice Updates the baseRate state variable based on time elapsed since the last redemption or ZUSD borrowing operation.\\n function decayBaseRateFromBorrowing() external;\\n\\n /// @param _borrower borrower address\\n /// @return Trove status from given trove\\n function getTroveStatus(address _borrower) external view returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @return Trove stake from given trove\\n function getTroveStake(address _borrower) external view returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @return Trove debt from given trove\\n function getTroveDebt(address _borrower) external view returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @return Trove collateral from given trove\\n function getTroveColl(address _borrower) external view returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @param num status to set\\n function setTroveStatus(address _borrower, uint256 num) external;\\n\\n /// @param _borrower borrower address\\n /// @param _collIncrease amount of collateral to increase\\n /// @return new trove collateral\\n function increaseTroveColl(\\n address _borrower,\\n uint256 _collIncrease\\n ) external returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @param _collDecrease amount of collateral to decrease\\n /// @return new trove collateral\\n function decreaseTroveColl(\\n address _borrower,\\n uint256 _collDecrease\\n ) external returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @param _debtIncrease amount of debt to increase\\n /// @return new trove debt\\n function increaseTroveDebt(\\n address _borrower,\\n uint256 _debtIncrease\\n ) external returns (uint256);\\n\\n /// @param _borrower borrower address\\n /// @param _debtDecrease amount of debt to decrease\\n /// @return new trove debt\\n function decreaseTroveDebt(\\n address _borrower,\\n uint256 _debtDecrease\\n ) external returns (uint256);\\n\\n /**\\n * @param _price ETH price\\n * @return the total collateralization ratio (TCR) of the system.\\n * The TCR is based on the the entire system debt and collateral (including pending rewards).\\n */\\n function getTCR(uint256 _price) external view returns (uint256);\\n\\n function MCR() external view returns (uint256);\\n\\n function CCR() external view returns (uint256);\\n\\n /// @notice reveals whether or not the system is in Recovery Mode (i.e. whether the Total Collateralization Ratio (TCR) is below the Critical Collateralization Ratio (CCR)).\\n function checkRecoveryMode(uint256 _price) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x396367eb7763c289e419a025532150e2a1d9d99eead359ceb6a081787501a00b\",\"license\":\"MIT\"},\"contracts/Interfaces/IZEROStaking.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\ninterface IZEROStaking {\\n // --- Events --\\n\\n event ZEROTokenAddressSet(address _zeroTokenAddress);\\n event ZUSDTokenAddressSet(address _zusdTokenAddress);\\n event FeeDistributorAddressAddressSet(address _feeDistributorAddress);\\n event ActivePoolAddressSet(address _activePoolAddress);\\n\\n event StakeChanged(address indexed staker, uint256 newStake);\\n event StakingGainsWithdrawn(address indexed staker, uint256 ZUSDGain, uint256 ETHGain);\\n event F_ETHUpdated(uint256 _F_ETH);\\n event F_ZUSDUpdated(uint256 _F_ZUSD);\\n event TotalZEROStakedUpdated(uint256 _totalZEROStaked);\\n event EtherSent(address _account, uint256 _amount);\\n event StakerSnapshotsUpdated(address _staker, uint256 _F_ETH, uint256 _F_ZUSD);\\n\\n // --- Functions ---\\n\\n /**\\n * @notice Called only once on init, to set addresses of other Zero contracts. Callable only by owner\\n * @dev initializer function, checks addresses are contracts\\n * @param _zeroTokenAddress ZEROToken contract address\\n * @param _zusdTokenAddress ZUSDToken contract address\\n * @param _feeDistributorAddress FeeDistributorAddress contract address\\n * @param _activePoolAddress ActivePool contract address\\n */\\n function setAddresses(\\n address _zeroTokenAddress,\\n address _zusdTokenAddress,\\n address _feeDistributorAddress,\\n address _activePoolAddress\\n ) external;\\n\\n /// @notice If caller has a pre-existing stake, send any accumulated ETH and ZUSD gains to them.\\n /// @param _ZEROamount ZERO tokens to stake\\n function stake(uint256 _ZEROamount) external;\\n\\n /**\\n * @notice Unstake the ZERO and send the it back to the caller, along with their accumulated ZUSD & ETH gains.\\n * If requested amount > stake, send their entire stake.\\n * @param _ZEROamount ZERO tokens to unstake\\n */\\n function unstake(uint256 _ZEROamount) external;\\n\\n /// @param _ETHFee ETH fee\\n /// @notice increase ETH fee\\n function increaseF_ETH(uint256 _ETHFee) external;\\n\\n /// @param _ZEROFee ZUSD fee\\n /// @notice increase ZUSD fee\\n function increaseF_ZUSD(uint256 _ZEROFee) external;\\n\\n /// @param _user user address\\n /// @return pending ETH gain of given user\\n function getPendingETHGain(address _user) external view returns (uint256);\\n\\n /// @param _user user address\\n /// @return pending ZUSD gain of given user\\n function getPendingZUSDGain(address _user) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x4c7948ce7dff9ea9b8495054e511eabcf44a91c7db8520ec58ff2a002327e0c5\",\"license\":\"MIT\"},\"contracts/Interfaces/IZEROToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"../Dependencies/IERC20.sol\\\";\\nimport \\\"../Dependencies/IERC2612.sol\\\";\\n\\ninterface IZEROToken is IERC20, IERC2612 { \\n\\n // --- Functions ---\\n\\n /// @notice send zero tokens to ZEROStaking contract\\n /// @param _sender sender address\\n /// @param _amount amount to send\\n function sendToZEROStaking(address _sender, uint256 _amount) external;\\n\\n /// @return deployment start time\\n function getDeploymentStartTime() external view returns (uint256);\\n\\n}\\n\",\"keccak256\":\"0xbcc0baabe4c4686563a09cf1486f2d152b70404996676a89d525691f69637f66\",\"license\":\"MIT\"},\"contracts/Interfaces/IZUSDToken.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"../Dependencies/IERC20.sol\\\";\\nimport \\\"../Dependencies/IERC2612.sol\\\";\\n\\ninterface IZUSDToken is IERC20, IERC2612 { \\n \\n // --- Events ---\\n\\n event TroveManagerAddressChanged(address _troveManagerAddress);\\n event StabilityPoolAddressChanged(address _newStabilityPoolAddress);\\n event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);\\n\\n event ZUSDTokenBalanceUpdated(address _user, uint _amount);\\n\\n // --- Functions ---\\n\\n function mint(address _account, uint256 _amount) external;\\n\\n function burn(address _account, uint256 _amount) external;\\n\\n function sendToPool(address _sender, address poolAddress, uint256 _amount) external;\\n\\n function returnFromPool(address poolAddress, address user, uint256 _amount ) external;\\n}\\n\",\"keccak256\":\"0xe52df063aa08f709640c28888edd27310c820f6d08564855538ae245eb2f5a8c\",\"license\":\"MIT\"},\"contracts/TroveManager.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"./Interfaces/ITroveManager.sol\\\";\\nimport \\\"./Interfaces/IStabilityPool.sol\\\";\\nimport \\\"./Interfaces/ICollSurplusPool.sol\\\";\\nimport \\\"./Interfaces/IZUSDToken.sol\\\";\\nimport \\\"./Interfaces/ISortedTroves.sol\\\";\\nimport \\\"./Interfaces/IZEROToken.sol\\\";\\nimport \\\"./Interfaces/IZEROStaking.sol\\\";\\nimport \\\"./Interfaces/IFeeDistributor.sol\\\";\\nimport \\\"./Dependencies/LiquityBase.sol\\\";\\nimport \\\"./Dependencies/CheckContract.sol\\\";\\nimport \\\"./Dependencies/console.sol\\\";\\nimport \\\"./Dependencies/TroveManagerBase.sol\\\";\\nimport \\\"./TroveManagerStorage.sol\\\";\\nimport \\\"./Interfaces/IPermit2.sol\\\";\\n\\ncontract TroveManager is TroveManagerBase, CheckContract, ITroveManager {\\n /** CONSTANT / IMMUTABLE VARIABLE ONLY */\\n IPermit2 public immutable permit2;\\n\\n event FeeDistributorAddressChanged(address _feeDistributorAddress);\\n event TroveManagerRedeemOpsAddressChanged(address _troveManagerRedeemOps);\\n event LiquityBaseParamsAddressChanges(address _borrowerOperationsAddress);\\n event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);\\n event PriceFeedAddressChanged(address _newPriceFeedAddress);\\n event ZUSDTokenAddressChanged(address _newZUSDTokenAddress);\\n event ActivePoolAddressChanged(address _activePoolAddress);\\n event DefaultPoolAddressChanged(address _defaultPoolAddress);\\n event StabilityPoolAddressChanged(address _stabilityPoolAddress);\\n event GasPoolAddressChanged(address _gasPoolAddress);\\n event CollSurplusPoolAddressChanged(address _collSurplusPoolAddress);\\n event SortedTrovesAddressChanged(address _sortedTrovesAddress);\\n event ZEROTokenAddressChanged(address _zeroTokenAddress);\\n event ZEROStakingAddressChanged(address _zeroStakingAddress);\\n\\n ///@param _bootstrapPeriod During bootsrap period redemptions are not allowed\\n constructor(uint256 _bootstrapPeriod, address _permit2) public TroveManagerBase(_bootstrapPeriod) {\\n permit2 = IPermit2(_permit2);\\n }\\n\\n // --- Dependency setter ---\\n function setAddresses(\\n TroveManagerInitAddressesParams memory _troveManagerInitAddressesParams\\n ) external override onlyOwner {\\n {\\n checkContract(_troveManagerInitAddressesParams._feeDistributorAddress);\\n checkContract(_troveManagerInitAddressesParams._troveManagerRedeemOps);\\n checkContract(_troveManagerInitAddressesParams._liquityBaseParamsAddress);\\n checkContract(_troveManagerInitAddressesParams._borrowerOperationsAddress);\\n checkContract(_troveManagerInitAddressesParams._activePoolAddress);\\n checkContract(_troveManagerInitAddressesParams._defaultPoolAddress);\\n checkContract(_troveManagerInitAddressesParams._stabilityPoolAddress);\\n checkContract(_troveManagerInitAddressesParams._gasPoolAddress);\\n checkContract(_troveManagerInitAddressesParams._collSurplusPoolAddress);\\n checkContract(_troveManagerInitAddressesParams._priceFeedAddress);\\n checkContract(_troveManagerInitAddressesParams._zusdTokenAddress);\\n checkContract(_troveManagerInitAddressesParams._sortedTrovesAddress);\\n checkContract(_troveManagerInitAddressesParams._zeroTokenAddress);\\n checkContract(_troveManagerInitAddressesParams._zeroStakingAddress);\\n }\\n\\n feeDistributor = IFeeDistributor(_troveManagerInitAddressesParams._feeDistributorAddress);\\n troveManagerRedeemOps = _troveManagerInitAddressesParams._troveManagerRedeemOps;\\n liquityBaseParams = ILiquityBaseParams(\\n _troveManagerInitAddressesParams._liquityBaseParamsAddress\\n );\\n {\\n borrowerOperationsAddress = _troveManagerInitAddressesParams\\n ._borrowerOperationsAddress;\\n activePool = IActivePool(_troveManagerInitAddressesParams._activePoolAddress);\\n defaultPool = IDefaultPool(_troveManagerInitAddressesParams._defaultPoolAddress);\\n _stabilityPool = IStabilityPool(\\n _troveManagerInitAddressesParams._stabilityPoolAddress\\n );\\n gasPoolAddress = _troveManagerInitAddressesParams._gasPoolAddress;\\n collSurplusPool = ICollSurplusPool(\\n _troveManagerInitAddressesParams._collSurplusPoolAddress\\n );\\n priceFeed = IPriceFeed(_troveManagerInitAddressesParams._priceFeedAddress);\\n _zusdToken = IZUSDToken(_troveManagerInitAddressesParams._zusdTokenAddress);\\n sortedTroves = ISortedTroves(_troveManagerInitAddressesParams._sortedTrovesAddress);\\n _zeroToken = IZEROToken(_troveManagerInitAddressesParams._zeroTokenAddress);\\n _zeroStaking = IZEROStaking(_troveManagerInitAddressesParams._zeroStakingAddress);\\n }\\n\\n emit FeeDistributorAddressChanged(_troveManagerInitAddressesParams._feeDistributorAddress);\\n emit TroveManagerRedeemOpsAddressChanged(\\n _troveManagerInitAddressesParams._troveManagerRedeemOps\\n );\\n emit LiquityBaseParamsAddressChanges(\\n _troveManagerInitAddressesParams._borrowerOperationsAddress\\n );\\n emit BorrowerOperationsAddressChanged(\\n _troveManagerInitAddressesParams._borrowerOperationsAddress\\n );\\n emit ActivePoolAddressChanged(_troveManagerInitAddressesParams._activePoolAddress);\\n emit DefaultPoolAddressChanged(_troveManagerInitAddressesParams._defaultPoolAddress);\\n emit StabilityPoolAddressChanged(_troveManagerInitAddressesParams._stabilityPoolAddress);\\n emit GasPoolAddressChanged(_troveManagerInitAddressesParams._gasPoolAddress);\\n emit CollSurplusPoolAddressChanged(\\n _troveManagerInitAddressesParams._collSurplusPoolAddress\\n );\\n emit PriceFeedAddressChanged(_troveManagerInitAddressesParams._priceFeedAddress);\\n emit ZUSDTokenAddressChanged(_troveManagerInitAddressesParams._zusdTokenAddress);\\n emit SortedTrovesAddressChanged(_troveManagerInitAddressesParams._sortedTrovesAddress);\\n emit ZEROTokenAddressChanged(_troveManagerInitAddressesParams._zeroTokenAddress);\\n emit ZEROStakingAddressChanged(_troveManagerInitAddressesParams._zeroStakingAddress);\\n }\\n\\n function setTroveManagerRedeemOps(address _troveManagerRedeemOps) external override onlyOwner {\\n checkContract(_troveManagerRedeemOps);\\n troveManagerRedeemOps = _troveManagerRedeemOps;\\n emit TroveManagerRedeemOpsAddressChanged(_troveManagerRedeemOps);\\n }\\n\\n // --- Getters ---\\n\\n function getTroveOwnersCount() external view override returns (uint256) {\\n return TroveOwners.length;\\n }\\n\\n function getTroveFromTroveOwnersArray(\\n uint256 _index\\n ) external view override returns (address) {\\n return TroveOwners[_index];\\n }\\n\\n // --- Trove Liquidation functions ---\\n\\n /// Single liquidation function. Closes the trove if its ICR is lower than the minimum collateral ratio.\\n function liquidate(address _borrower) external override {\\n _requireTroveIsActive(_borrower);\\n\\n address[] memory borrowers = new address[](1);\\n borrowers[0] = _borrower;\\n batchLiquidateTroves(borrowers);\\n }\\n\\n // --- Inner single liquidation functions ---\\n\\n /// Liquidate one trove, in Normal Mode.\\n function _liquidateNormalMode(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n address _borrower,\\n uint256 _ZUSDInStabPool\\n ) internal returns (LiquidationValues memory singleLiquidation) {\\n LocalVariables_InnerSingleLiquidateFunction memory vars;\\n\\n (\\n singleLiquidation.entireTroveDebt,\\n singleLiquidation.entireTroveColl,\\n vars.pendingDebtReward,\\n vars.pendingCollReward\\n ) = getEntireDebtAndColl(_borrower);\\n\\n _movePendingTroveRewardsToActivePool(\\n _activePool,\\n _defaultPool,\\n vars.pendingDebtReward,\\n vars.pendingCollReward\\n );\\n _removeStake(_borrower);\\n\\n singleLiquidation.collGasCompensation = _getCollGasCompensation(\\n singleLiquidation.entireTroveColl\\n );\\n singleLiquidation.ZUSDGasCompensation = ZUSD_GAS_COMPENSATION;\\n uint256 collToLiquidate = singleLiquidation.entireTroveColl.sub(\\n singleLiquidation.collGasCompensation\\n );\\n\\n (\\n singleLiquidation.debtToOffset,\\n singleLiquidation.collToSendToSP,\\n singleLiquidation.debtToRedistribute,\\n singleLiquidation.collToRedistribute\\n ) = _getOffsetAndRedistributionVals(\\n singleLiquidation.entireTroveDebt,\\n collToLiquidate,\\n _ZUSDInStabPool\\n );\\n\\n _closeTrove(_borrower, Status.closedByLiquidation);\\n emit TroveLiquidated(\\n _borrower,\\n singleLiquidation.entireTroveDebt,\\n singleLiquidation.entireTroveColl,\\n TroveManagerOperation.liquidateInNormalMode\\n );\\n emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInNormalMode);\\n return singleLiquidation;\\n }\\n\\n /// Liquidate one trove, in Recovery Mode.\\n function _liquidateRecoveryMode(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n address _borrower,\\n uint256 _ICR,\\n uint256 _ZUSDInStabPool,\\n uint256 _TCR,\\n uint256 _price\\n ) internal returns (LiquidationValues memory singleLiquidation) {\\n LocalVariables_InnerSingleLiquidateFunction memory vars;\\n if (TroveOwners.length <= 1) {\\n return singleLiquidation;\\n } // don't liquidate if last trove\\n (\\n singleLiquidation.entireTroveDebt,\\n singleLiquidation.entireTroveColl,\\n vars.pendingDebtReward,\\n vars.pendingCollReward\\n ) = getEntireDebtAndColl(_borrower);\\n\\n singleLiquidation.collGasCompensation = _getCollGasCompensation(\\n singleLiquidation.entireTroveColl\\n );\\n singleLiquidation.ZUSDGasCompensation = ZUSD_GAS_COMPENSATION;\\n vars.collToLiquidate = singleLiquidation.entireTroveColl.sub(\\n singleLiquidation.collGasCompensation\\n );\\n\\n // If ICR <= 100%, purely redistribute the Trove across all active Troves\\n if (_ICR <= _100pct) {\\n _movePendingTroveRewardsToActivePool(\\n _activePool,\\n _defaultPool,\\n vars.pendingDebtReward,\\n vars.pendingCollReward\\n );\\n _removeStake(_borrower);\\n\\n singleLiquidation.debtToOffset = 0;\\n singleLiquidation.collToSendToSP = 0;\\n singleLiquidation.debtToRedistribute = singleLiquidation.entireTroveDebt;\\n singleLiquidation.collToRedistribute = vars.collToLiquidate;\\n\\n _closeTrove(_borrower, Status.closedByLiquidation);\\n emit TroveLiquidated(\\n _borrower,\\n singleLiquidation.entireTroveDebt,\\n singleLiquidation.entireTroveColl,\\n TroveManagerOperation.liquidateInRecoveryMode\\n );\\n emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInRecoveryMode);\\n\\n // If 100% < ICR < MCR, offset as much as possible, and redistribute the remainder\\n } else if ((_ICR > _100pct) && (_ICR < liquityBaseParams.MCR())) {\\n _movePendingTroveRewardsToActivePool(\\n _activePool,\\n _defaultPool,\\n vars.pendingDebtReward,\\n vars.pendingCollReward\\n );\\n _removeStake(_borrower);\\n\\n (\\n singleLiquidation.debtToOffset,\\n singleLiquidation.collToSendToSP,\\n singleLiquidation.debtToRedistribute,\\n singleLiquidation.collToRedistribute\\n ) = _getOffsetAndRedistributionVals(\\n singleLiquidation.entireTroveDebt,\\n vars.collToLiquidate,\\n _ZUSDInStabPool\\n );\\n\\n _closeTrove(_borrower, Status.closedByLiquidation);\\n emit TroveLiquidated(\\n _borrower,\\n singleLiquidation.entireTroveDebt,\\n singleLiquidation.entireTroveColl,\\n TroveManagerOperation.liquidateInRecoveryMode\\n );\\n emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInRecoveryMode);\\n /*\\n * If 110% <= ICR < current TCR (accounting for the preceding liquidations in the current sequence)\\n * and there is ZUSD in the Stability Pool, only offset, with no redistribution,\\n * but at a capped rate of 1.1 and only if the whole debt can be liquidated.\\n * The remainder due to the capped rate will be claimable as collateral surplus.\\n */\\n } else if (\\n (_ICR >= liquityBaseParams.MCR()) &&\\n (_ICR < _TCR) &&\\n (singleLiquidation.entireTroveDebt <= _ZUSDInStabPool)\\n ) {\\n _movePendingTroveRewardsToActivePool(\\n _activePool,\\n _defaultPool,\\n vars.pendingDebtReward,\\n vars.pendingCollReward\\n );\\n assert(_ZUSDInStabPool != 0);\\n\\n _removeStake(_borrower);\\n singleLiquidation = _getCappedOffsetVals(\\n singleLiquidation.entireTroveDebt,\\n singleLiquidation.entireTroveColl,\\n _price\\n );\\n\\n _closeTrove(_borrower, Status.closedByLiquidation);\\n if (singleLiquidation.collSurplus > 0) {\\n collSurplusPool.accountSurplus(_borrower, singleLiquidation.collSurplus);\\n }\\n\\n emit TroveLiquidated(\\n _borrower,\\n singleLiquidation.entireTroveDebt,\\n singleLiquidation.collToSendToSP,\\n TroveManagerOperation.liquidateInRecoveryMode\\n );\\n emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInRecoveryMode);\\n } else {\\n // if (_ICR >= liquityBaseParams.MCR() && ( _ICR >= _TCR || singleLiquidation.entireTroveDebt > _ZUSDInStabPool))\\n LiquidationValues memory zeroVals;\\n return zeroVals;\\n }\\n\\n return singleLiquidation;\\n }\\n\\n /** In a full liquidation, returns the values for a trove's coll and debt to be offset, and coll and debt to be\\n * redistributed to active troves.\\n */\\n function _getOffsetAndRedistributionVals(\\n uint256 _debt,\\n uint256 _coll,\\n uint256 _ZUSDInStabPool\\n )\\n internal\\n pure\\n returns (\\n uint256 debtToOffset,\\n uint256 collToSendToSP,\\n uint256 debtToRedistribute,\\n uint256 collToRedistribute\\n )\\n {\\n if (_ZUSDInStabPool > 0) {\\n /*\\n * Offset as much debt & collateral as possible against the Stability Pool, and redistribute the remainder\\n * between all active troves.\\n *\\n * If the trove's debt is larger than the deposited ZUSD in the Stability Pool:\\n *\\n * - Offset an amount of the trove's debt equal to the ZUSD in the Stability Pool\\n * - Send a fraction of the trove's collateral to the Stability Pool, equal to the fraction of its offset debt\\n *\\n */\\n debtToOffset = LiquityMath._min(_debt, _ZUSDInStabPool);\\n collToSendToSP = _coll.mul(debtToOffset).div(_debt);\\n debtToRedistribute = _debt.sub(debtToOffset);\\n collToRedistribute = _coll.sub(collToSendToSP);\\n } else {\\n debtToOffset = 0;\\n collToSendToSP = 0;\\n debtToRedistribute = _debt;\\n collToRedistribute = _coll;\\n }\\n }\\n\\n /**\\n * Get its offset coll/debt and ETH gas comp, and close the trove.\\n */\\n function _getCappedOffsetVals(\\n uint256 _entireTroveDebt,\\n uint256 _entireTroveColl,\\n uint256 _price\\n ) internal view returns (LiquidationValues memory singleLiquidation) {\\n singleLiquidation.entireTroveDebt = _entireTroveDebt;\\n singleLiquidation.entireTroveColl = _entireTroveColl;\\n uint256 collToOffset = _entireTroveDebt.mul(liquityBaseParams.MCR()).div(_price);\\n\\n singleLiquidation.collGasCompensation = _getCollGasCompensation(collToOffset);\\n singleLiquidation.ZUSDGasCompensation = ZUSD_GAS_COMPENSATION;\\n\\n singleLiquidation.debtToOffset = _entireTroveDebt;\\n singleLiquidation.collToSendToSP = collToOffset.sub(singleLiquidation.collGasCompensation);\\n singleLiquidation.collSurplus = _entireTroveColl.sub(collToOffset);\\n singleLiquidation.debtToRedistribute = 0;\\n singleLiquidation.collToRedistribute = 0;\\n }\\n\\n /**\\n * Liquidate a sequence of troves. Closes a maximum number of n under-collateralized Troves,\\n * starting from the one with the lowest collateral ratio in the system, and moving upwards\\n */\\n function liquidateTroves(uint256 _n) external override {\\n ContractsCache memory contractsCache = ContractsCache(\\n activePool,\\n defaultPool,\\n IZUSDToken(address(0)),\\n IZEROStaking(address(0)),\\n sortedTroves,\\n ICollSurplusPool(address(0)),\\n address(0)\\n );\\n IStabilityPool stabilityPoolCached = _stabilityPool;\\n\\n LocalVariables_OuterLiquidationFunction memory vars;\\n\\n LiquidationTotals memory totals;\\n\\n vars.price = priceFeed.fetchPrice();\\n vars.ZUSDInStabPool = stabilityPoolCached.getTotalZUSDDeposits();\\n vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);\\n\\n // Perform the appropriate liquidation sequence - tally the values, and obtain their totals\\n if (vars.recoveryModeAtStart) {\\n totals = _getTotalsFromLiquidateTrovesSequence_RecoveryMode(\\n contractsCache,\\n vars.price,\\n vars.ZUSDInStabPool,\\n _n\\n );\\n } else {\\n // if !vars.recoveryModeAtStart\\n totals = _getTotalsFromLiquidateTrovesSequence_NormalMode(\\n contractsCache.activePool,\\n contractsCache.defaultPool,\\n vars.price,\\n vars.ZUSDInStabPool,\\n _n\\n );\\n }\\n\\n require(totals.totalDebtInSequence > 0, \\\"TroveManager: nothing to liquidate\\\");\\n\\n // Move liquidated ETH and ZUSD to the appropriate pools\\n stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP);\\n _redistributeDebtAndColl(\\n contractsCache.activePool,\\n contractsCache.defaultPool,\\n totals.totalDebtToRedistribute,\\n totals.totalCollToRedistribute\\n );\\n if (totals.totalCollSurplus > 0) {\\n contractsCache.activePool.sendETH(address(collSurplusPool), totals.totalCollSurplus);\\n }\\n\\n // Update system snapshots\\n _updateSystemSnapshots_excludeCollRemainder(\\n contractsCache.activePool,\\n totals.totalCollGasCompensation\\n );\\n\\n vars.liquidatedDebt = totals.totalDebtInSequence;\\n vars.liquidatedColl = totals.totalCollInSequence.sub(totals.totalCollGasCompensation).sub(\\n totals.totalCollSurplus\\n );\\n emit Liquidation(\\n vars.liquidatedDebt,\\n vars.liquidatedColl,\\n totals.totalCollGasCompensation,\\n totals.totalZUSDGasCompensation\\n );\\n\\n // Send gas compensation to caller\\n _sendGasCompensation(\\n contractsCache.activePool,\\n msg.sender,\\n totals.totalZUSDGasCompensation,\\n totals.totalCollGasCompensation\\n );\\n }\\n\\n /**\\n * This function is used when the liquidateTroves sequence starts during Recovery Mode. However, it\\n * handle the case where the system *leaves* Recovery Mode, part way through the liquidation sequence\\n */\\n function _getTotalsFromLiquidateTrovesSequence_RecoveryMode(\\n ContractsCache memory _contractsCache,\\n uint256 _price,\\n uint256 _ZUSDInStabPool,\\n uint256 _n\\n ) internal returns (LiquidationTotals memory totals) {\\n LocalVariables_LiquidationSequence memory vars;\\n LiquidationValues memory singleLiquidation;\\n\\n vars.remainingZUSDInStabPool = _ZUSDInStabPool;\\n vars.backToNormalMode = false;\\n vars.entireSystemDebt = getEntireSystemDebt();\\n vars.entireSystemColl = getEntireSystemColl();\\n\\n vars.user = _contractsCache.sortedTroves.getLast();\\n address firstUser = _contractsCache.sortedTroves.getFirst();\\n for (vars.i = 0; vars.i < _n && vars.user != firstUser; vars.i++) {\\n // we need to cache it, because current user is likely going to be deleted\\n address nextUser = _contractsCache.sortedTroves.getPrev(vars.user);\\n\\n vars.ICR = _getCurrentICR(vars.user, _price);\\n\\n if (!vars.backToNormalMode) {\\n // Break the loop if ICR is greater than liquityBaseParams.MCR() and Stability Pool is empty\\n if (vars.ICR >= liquityBaseParams.MCR() && vars.remainingZUSDInStabPool == 0) {\\n break;\\n }\\n\\n uint256 TCR = LiquityMath._computeCR(\\n vars.entireSystemColl,\\n vars.entireSystemDebt,\\n _price\\n );\\n\\n singleLiquidation = _liquidateRecoveryMode(\\n _contractsCache.activePool,\\n _contractsCache.defaultPool,\\n vars.user,\\n vars.ICR,\\n vars.remainingZUSDInStabPool,\\n TCR,\\n _price\\n );\\n\\n // Update aggregate trackers\\n vars.remainingZUSDInStabPool = vars.remainingZUSDInStabPool.sub(\\n singleLiquidation.debtToOffset\\n );\\n vars.entireSystemDebt = vars.entireSystemDebt.sub(singleLiquidation.debtToOffset);\\n vars.entireSystemColl = vars\\n .entireSystemColl\\n .sub(singleLiquidation.collToSendToSP)\\n .sub(singleLiquidation.collSurplus);\\n\\n // Add liquidation values to their respective running totals\\n totals = _addLiquidationValuesToTotals(totals, singleLiquidation);\\n\\n vars.backToNormalMode = !_checkPotentialRecoveryMode(\\n vars.entireSystemColl,\\n vars.entireSystemDebt,\\n _price\\n );\\n } else if (vars.backToNormalMode && vars.ICR < liquityBaseParams.MCR()) {\\n singleLiquidation = _liquidateNormalMode(\\n _contractsCache.activePool,\\n _contractsCache.defaultPool,\\n vars.user,\\n vars.remainingZUSDInStabPool\\n );\\n\\n vars.remainingZUSDInStabPool = vars.remainingZUSDInStabPool.sub(\\n singleLiquidation.debtToOffset\\n );\\n\\n // Add liquidation values to their respective running totals\\n totals = _addLiquidationValuesToTotals(totals, singleLiquidation);\\n } else break; // break if the loop reaches a Trove with ICR >= MCR\\n\\n vars.user = nextUser;\\n }\\n }\\n\\n function _getTotalsFromLiquidateTrovesSequence_NormalMode(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n uint256 _price,\\n uint256 _ZUSDInStabPool,\\n uint256 _n\\n ) internal returns (LiquidationTotals memory totals) {\\n LocalVariables_LiquidationSequence memory vars;\\n LiquidationValues memory singleLiquidation;\\n ISortedTroves sortedTrovesCached = sortedTroves;\\n\\n vars.remainingZUSDInStabPool = _ZUSDInStabPool;\\n\\n for (vars.i = 0; vars.i < _n; vars.i++) {\\n vars.user = sortedTrovesCached.getLast();\\n vars.ICR = _getCurrentICR(vars.user, _price);\\n\\n if (vars.ICR < liquityBaseParams.MCR()) {\\n singleLiquidation = _liquidateNormalMode(\\n _activePool,\\n _defaultPool,\\n vars.user,\\n vars.remainingZUSDInStabPool\\n );\\n\\n vars.remainingZUSDInStabPool = vars.remainingZUSDInStabPool.sub(\\n singleLiquidation.debtToOffset\\n );\\n\\n // Add liquidation values to their respective running totals\\n totals = _addLiquidationValuesToTotals(totals, singleLiquidation);\\n } else break; // break if the loop reaches a Trove with ICR >= MCR\\n }\\n }\\n\\n /**\\n * Attempt to liquidate a custom list of troves provided by the caller.\\n */\\n function batchLiquidateTroves(address[] memory _troveArray) public override {\\n require(_troveArray.length != 0, \\\"TroveManager: Calldata address array must not be empty\\\");\\n\\n IActivePool activePoolCached = activePool;\\n IDefaultPool defaultPoolCached = defaultPool;\\n IStabilityPool stabilityPoolCached = _stabilityPool;\\n\\n LocalVariables_OuterLiquidationFunction memory vars;\\n LiquidationTotals memory totals;\\n\\n vars.price = priceFeed.fetchPrice();\\n vars.ZUSDInStabPool = stabilityPoolCached.getTotalZUSDDeposits();\\n vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);\\n\\n // Perform the appropriate liquidation sequence - tally values and obtain their totals.\\n if (vars.recoveryModeAtStart) {\\n totals = _getTotalFromBatchLiquidate_RecoveryMode(\\n activePoolCached,\\n defaultPoolCached,\\n vars.price,\\n vars.ZUSDInStabPool,\\n _troveArray\\n );\\n } else {\\n // if !vars.recoveryModeAtStart\\n totals = _getTotalsFromBatchLiquidate_NormalMode(\\n activePoolCached,\\n defaultPoolCached,\\n vars.price,\\n vars.ZUSDInStabPool,\\n _troveArray\\n );\\n }\\n\\n require(totals.totalDebtInSequence > 0, \\\"TroveManager: nothing to liquidate\\\");\\n\\n // Move liquidated ETH and ZUSD to the appropriate pools\\n stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP);\\n _redistributeDebtAndColl(\\n activePoolCached,\\n defaultPoolCached,\\n totals.totalDebtToRedistribute,\\n totals.totalCollToRedistribute\\n );\\n if (totals.totalCollSurplus > 0) {\\n activePoolCached.sendETH(address(collSurplusPool), totals.totalCollSurplus);\\n }\\n\\n // Update system snapshots\\n _updateSystemSnapshots_excludeCollRemainder(\\n activePoolCached,\\n totals.totalCollGasCompensation\\n );\\n\\n vars.liquidatedDebt = totals.totalDebtInSequence;\\n vars.liquidatedColl = totals.totalCollInSequence.sub(totals.totalCollGasCompensation).sub(\\n totals.totalCollSurplus\\n );\\n emit Liquidation(\\n vars.liquidatedDebt,\\n vars.liquidatedColl,\\n totals.totalCollGasCompensation,\\n totals.totalZUSDGasCompensation\\n );\\n\\n // Send gas compensation to caller\\n _sendGasCompensation(\\n activePoolCached,\\n msg.sender,\\n totals.totalZUSDGasCompensation,\\n totals.totalCollGasCompensation\\n );\\n }\\n\\n /**\\n * This function is used when the batch liquidation sequence starts during Recovery Mode. However, it\\n * handle the case where the system *leaves* Recovery Mode, part way through the liquidation sequence\\n */\\n function _getTotalFromBatchLiquidate_RecoveryMode(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n uint256 _price,\\n uint256 _ZUSDInStabPool,\\n address[] memory _troveArray\\n ) internal returns (LiquidationTotals memory totals) {\\n LocalVariables_LiquidationSequence memory vars;\\n LiquidationValues memory singleLiquidation;\\n\\n vars.remainingZUSDInStabPool = _ZUSDInStabPool;\\n vars.backToNormalMode = false;\\n vars.entireSystemDebt = getEntireSystemDebt();\\n vars.entireSystemColl = getEntireSystemColl();\\n\\n for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {\\n vars.user = _troveArray[vars.i];\\n // Skip non-active troves\\n if (Troves[vars.user].status != Status.active) {\\n continue;\\n }\\n vars.ICR = _getCurrentICR(vars.user, _price);\\n\\n if (!vars.backToNormalMode) {\\n // Skip this trove if ICR is greater than liquityBaseParams.MCR() and Stability Pool is empty\\n if (vars.ICR >= liquityBaseParams.MCR() && vars.remainingZUSDInStabPool == 0) {\\n continue;\\n }\\n\\n uint256 TCR = LiquityMath._computeCR(\\n vars.entireSystemColl,\\n vars.entireSystemDebt,\\n _price\\n );\\n\\n singleLiquidation = _liquidateRecoveryMode(\\n _activePool,\\n _defaultPool,\\n vars.user,\\n vars.ICR,\\n vars.remainingZUSDInStabPool,\\n TCR,\\n _price\\n );\\n\\n // Update aggregate trackers\\n vars.remainingZUSDInStabPool = vars.remainingZUSDInStabPool.sub(\\n singleLiquidation.debtToOffset\\n );\\n vars.entireSystemDebt = vars.entireSystemDebt.sub(singleLiquidation.debtToOffset);\\n vars.entireSystemColl = vars.entireSystemColl.sub(\\n singleLiquidation.collToSendToSP\\n );\\n\\n // Add liquidation values to their respective running totals\\n totals = _addLiquidationValuesToTotals(totals, singleLiquidation);\\n\\n vars.backToNormalMode = !_checkPotentialRecoveryMode(\\n vars.entireSystemColl,\\n vars.entireSystemDebt,\\n _price\\n );\\n } else if (vars.backToNormalMode && vars.ICR < liquityBaseParams.MCR()) {\\n singleLiquidation = _liquidateNormalMode(\\n _activePool,\\n _defaultPool,\\n vars.user,\\n vars.remainingZUSDInStabPool\\n );\\n vars.remainingZUSDInStabPool = vars.remainingZUSDInStabPool.sub(\\n singleLiquidation.debtToOffset\\n );\\n\\n // Add liquidation values to their respective running totals\\n totals = _addLiquidationValuesToTotals(totals, singleLiquidation);\\n } else continue; // In Normal Mode skip troves with ICR >= MCR\\n }\\n }\\n\\n function _getTotalsFromBatchLiquidate_NormalMode(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n uint256 _price,\\n uint256 _ZUSDInStabPool,\\n address[] memory _troveArray\\n ) internal returns (LiquidationTotals memory totals) {\\n LocalVariables_LiquidationSequence memory vars;\\n LiquidationValues memory singleLiquidation;\\n\\n vars.remainingZUSDInStabPool = _ZUSDInStabPool;\\n\\n for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {\\n vars.user = _troveArray[vars.i];\\n vars.ICR = _getCurrentICR(vars.user, _price);\\n\\n if (vars.ICR < liquityBaseParams.MCR()) {\\n singleLiquidation = _liquidateNormalMode(\\n _activePool,\\n _defaultPool,\\n vars.user,\\n vars.remainingZUSDInStabPool\\n );\\n vars.remainingZUSDInStabPool = vars.remainingZUSDInStabPool.sub(\\n singleLiquidation.debtToOffset\\n );\\n\\n // Add liquidation values to their respective running totals\\n totals = _addLiquidationValuesToTotals(totals, singleLiquidation);\\n }\\n }\\n }\\n\\n // --- Liquidation helper functions ---\\n\\n function _addLiquidationValuesToTotals(\\n LiquidationTotals memory oldTotals,\\n LiquidationValues memory singleLiquidation\\n ) internal pure returns (LiquidationTotals memory newTotals) {\\n // Tally all the values with their respective running totals\\n newTotals.totalCollGasCompensation = oldTotals.totalCollGasCompensation.add(\\n singleLiquidation.collGasCompensation\\n );\\n newTotals.totalZUSDGasCompensation = oldTotals.totalZUSDGasCompensation.add(\\n singleLiquidation.ZUSDGasCompensation\\n );\\n newTotals.totalDebtInSequence = oldTotals.totalDebtInSequence.add(\\n singleLiquidation.entireTroveDebt\\n );\\n newTotals.totalCollInSequence = oldTotals.totalCollInSequence.add(\\n singleLiquidation.entireTroveColl\\n );\\n newTotals.totalDebtToOffset = oldTotals.totalDebtToOffset.add(\\n singleLiquidation.debtToOffset\\n );\\n newTotals.totalCollToSendToSP = oldTotals.totalCollToSendToSP.add(\\n singleLiquidation.collToSendToSP\\n );\\n newTotals.totalDebtToRedistribute = oldTotals.totalDebtToRedistribute.add(\\n singleLiquidation.debtToRedistribute\\n );\\n newTotals.totalCollToRedistribute = oldTotals.totalCollToRedistribute.add(\\n singleLiquidation.collToRedistribute\\n );\\n newTotals.totalCollSurplus = oldTotals.totalCollSurplus.add(singleLiquidation.collSurplus);\\n\\n return newTotals;\\n }\\n\\n function _sendGasCompensation(\\n IActivePool _activePool,\\n address _liquidator,\\n uint256 _ZUSD,\\n uint256 _ETH\\n ) internal {\\n if (_ZUSD > 0) {\\n _zusdToken.returnFromPool(gasPoolAddress, _liquidator, _ZUSD);\\n }\\n\\n if (_ETH > 0) {\\n _activePool.sendETH(_liquidator, _ETH);\\n }\\n }\\n\\n // --- Helper functions ---\\n\\n /// @return the nominal collateral ratio (ICR) of a given Trove, without the price. Takes a trove's pending coll and debt rewards from redistributions into account.\\n function getNominalICR(address _borrower) public view override returns (uint256) {\\n (uint256 currentETH, uint256 currentZUSDDebt) = _getCurrentTroveAmounts(_borrower);\\n\\n uint256 NICR = LiquityMath._computeNominalCR(currentETH, currentZUSDDebt);\\n return NICR;\\n }\\n\\n function applyPendingRewards(address _borrower) external override {\\n _requireCallerIsBorrowerOperations();\\n return _applyPendingRewards(activePool, defaultPool, _borrower);\\n }\\n\\n /// Update borrower's snapshots of L_ETH and L_ZUSDDebt to reflect the current values\\n function updateTroveRewardSnapshots(address _borrower) external override {\\n _requireCallerIsBorrowerOperations();\\n return _updateTroveRewardSnapshots(_borrower);\\n }\\n\\n /// Return the Troves entire debt and coll, including pending rewards from redistributions.\\n function getEntireDebtAndColl(\\n address _borrower\\n )\\n public\\n view\\n override\\n returns (\\n uint256 debt,\\n uint256 coll,\\n uint256 pendingZUSDDebtReward,\\n uint256 pendingETHReward\\n )\\n {\\n debt = Troves[_borrower].debt;\\n coll = Troves[_borrower].coll;\\n\\n pendingZUSDDebtReward = getPendingZUSDDebtReward(_borrower);\\n pendingETHReward = getPendingETHReward(_borrower);\\n\\n debt = debt.add(pendingZUSDDebtReward);\\n coll = coll.add(pendingETHReward);\\n }\\n\\n function removeStake(address _borrower) external override {\\n _requireCallerIsBorrowerOperations();\\n return _removeStake(_borrower);\\n }\\n\\n function updateStakeAndTotalStakes(address _borrower) external override returns (uint256) {\\n _requireCallerIsBorrowerOperations();\\n return _updateStakeAndTotalStakes(_borrower);\\n }\\n\\n function _redistributeDebtAndColl(\\n IActivePool _activePool,\\n IDefaultPool _defaultPool,\\n uint256 _debt,\\n uint256 _coll\\n ) internal {\\n if (_debt == 0) {\\n return;\\n }\\n\\n /*\\n * Add distributed coll and debt rewards-per-unit-staked to the running totals. Division uses a \\\"feedback\\\"\\n * error correction, to keep the cumulative error low in the running totals L_ETH and L_ZUSDDebt:\\n *\\n * 1) Form numerators which compensate for the floor division errors that occurred the last time this\\n * function was called.\\n * 2) Calculate \\\"per-unit-staked\\\" ratios.\\n * 3) Multiply each ratio back by its denominator, to reveal the current floor division error.\\n * 4) Store these errors for use in the next correction when this function is called.\\n * 5) Note: static analysis tools complain about this \\\"division before multiplication\\\", however, it is intended.\\n */\\n uint256 ETHNumerator = _coll.mul(DECIMAL_PRECISION).add(lastETHError_Redistribution);\\n uint256 ZUSDDebtNumerator = _debt.mul(DECIMAL_PRECISION).add(\\n lastZUSDDebtError_Redistribution\\n );\\n\\n // Get the per-unit-staked terms\\n uint256 ETHRewardPerUnitStaked = ETHNumerator.div(totalStakes);\\n uint256 ZUSDDebtRewardPerUnitStaked = ZUSDDebtNumerator.div(totalStakes);\\n\\n lastETHError_Redistribution = ETHNumerator.sub(ETHRewardPerUnitStaked.mul(totalStakes));\\n lastZUSDDebtError_Redistribution = ZUSDDebtNumerator.sub(\\n ZUSDDebtRewardPerUnitStaked.mul(totalStakes)\\n );\\n\\n // Add per-unit-staked terms to the running totals\\n L_ETH = L_ETH.add(ETHRewardPerUnitStaked);\\n L_ZUSDDebt = L_ZUSDDebt.add(ZUSDDebtRewardPerUnitStaked);\\n\\n emit LTermsUpdated(L_ETH, L_ZUSDDebt);\\n\\n // Transfer coll and debt from ActivePool to DefaultPool\\n _activePool.decreaseZUSDDebt(_debt);\\n _defaultPool.increaseZUSDDebt(_debt);\\n _activePool.sendETH(address(_defaultPool), _coll);\\n }\\n\\n function closeTrove(address _borrower) external override {\\n _requireCallerIsBorrowerOperations();\\n return _closeTrove(_borrower, Status.closedByOwner);\\n }\\n\\n /**\\n * Updates snapshots of system total stakes and total collateral, excluding a given collateral remainder from the calculation.\\n * Used in a liquidation sequence.\\n *\\n * The calculation excludes a portion of collateral that is in the ActivePool:\\n *\\n * the total ETH gas compensation from the liquidation sequence\\n *\\n * The ETH as compensation must be excluded as it is always sent out at the very end of the liquidation sequence.\\n */\\n function _updateSystemSnapshots_excludeCollRemainder(\\n IActivePool _activePool,\\n uint256 _collRemainder\\n ) internal {\\n totalStakesSnapshot = totalStakes;\\n\\n uint256 activeColl = _activePool.getETH();\\n uint256 liquidatedColl = defaultPool.getETH();\\n totalCollateralSnapshot = activeColl.sub(_collRemainder).add(liquidatedColl);\\n\\n emit SystemSnapshotsUpdated(totalStakesSnapshot, totalCollateralSnapshot);\\n }\\n\\n /// Push the owner's address to the Trove owners list, and record the corresponding array index on the Trove struct\\n function addTroveOwnerToArray(address _borrower) external override returns (uint256 index) {\\n _requireCallerIsBorrowerOperations();\\n return _addTroveOwnerToArray(_borrower);\\n }\\n\\n function _addTroveOwnerToArray(address _borrower) internal returns (uint128 index) {\\n /* Max array size is 2**128 - 1, i.e. ~3e30 troves. No risk of overflow, since troves have minimum ZUSD\\n debt of liquidation reserve plus MIN_NET_DEBT. 3e30 ZUSD dwarfs the value of all wealth in the world ( which is < 1e15 USD). */\\n\\n // Push the Troveowner to the array\\n TroveOwners.push(_borrower);\\n\\n // Record the index of the new Troveowner on their Trove struct\\n index = uint128(TroveOwners.length.sub(1));\\n Troves[_borrower].arrayIndex = index;\\n\\n return index;\\n }\\n\\n // --- Recovery Mode and TCR functions ---\\n\\n function getTCR(uint256 _price) external view override returns (uint256) {\\n return _getTCR(_price);\\n }\\n\\n function MCR() external view override returns (uint256) {\\n return liquityBaseParams.MCR();\\n }\\n\\n function CCR() external view override returns (uint256) {\\n return liquityBaseParams.CCR();\\n }\\n\\n function checkRecoveryMode(uint256 _price) external view override returns (bool) {\\n return _checkRecoveryMode(_price);\\n }\\n\\n // Check whether or not the system *would be* in Recovery Mode, given an ETH:USD price, and the entire system coll and debt.\\n function _checkPotentialRecoveryMode(\\n uint256 _entireSystemColl,\\n uint256 _entireSystemDebt,\\n uint256 _price\\n ) internal view returns (bool) {\\n uint256 TCR = LiquityMath._computeCR(_entireSystemColl, _entireSystemDebt, _price);\\n\\n return TCR < liquityBaseParams.CCR();\\n }\\n\\n function getRedemptionRateWithDecay() public view override returns (uint256) {\\n return _calcRedemptionRate(_calcDecayedBaseRate());\\n }\\n\\n function getRedemptionFeeWithDecay(\\n uint256 _ETHDrawn\\n ) external view override returns (uint256) {\\n return _calcRedemptionFee(getRedemptionRateWithDecay(), _ETHDrawn);\\n }\\n\\n // --- Borrowing fee functions ---\\n\\n function getBorrowingRate() public view override returns (uint256) {\\n return _calcBorrowingRate(baseRate);\\n }\\n\\n function getBorrowingRateWithDecay() public view override returns (uint256) {\\n return _calcBorrowingRate(_calcDecayedBaseRate());\\n }\\n\\n function _calcBorrowingRate(uint256 _baseRate) internal view returns (uint256) {\\n return\\n LiquityMath._min(\\n liquityBaseParams.BORROWING_FEE_FLOOR().add(_baseRate),\\n liquityBaseParams.MAX_BORROWING_FEE()\\n );\\n }\\n\\n function getBorrowingFee(uint256 _ZUSDDebt) external view override returns (uint256) {\\n return _calcBorrowingFee(getBorrowingRate(), _ZUSDDebt);\\n }\\n\\n function getBorrowingFeeWithDecay(uint256 _ZUSDDebt) external view override returns (uint256) {\\n return _calcBorrowingFee(getBorrowingRateWithDecay(), _ZUSDDebt);\\n }\\n\\n function _calcBorrowingFee(\\n uint256 _borrowingRate,\\n uint256 _ZUSDDebt\\n ) internal pure returns (uint256) {\\n return _borrowingRate.mul(_ZUSDDebt).div(DECIMAL_PRECISION);\\n }\\n\\n /// Updates the baseRate state variable based on time elapsed since the last redemption or ZUSD borrowing operation.\\n function decayBaseRateFromBorrowing() external override {\\n _requireCallerIsBorrowerOperations();\\n\\n uint256 decayedBaseRate = _calcDecayedBaseRate();\\n assert(decayedBaseRate <= DECIMAL_PRECISION); // The baseRate can decay to 0\\n\\n baseRate = decayedBaseRate;\\n emit BaseRateUpdated(decayedBaseRate);\\n\\n _updateLastFeeOpTime();\\n }\\n\\n // --- Internal fee functions ---\\n\\n // --- Trove property getters ---\\n\\n function getTroveStatus(address _borrower) external view override returns (uint256) {\\n return uint256(Troves[_borrower].status);\\n }\\n\\n function getTroveStake(address _borrower) external view override returns (uint256) {\\n return Troves[_borrower].stake;\\n }\\n\\n function getTroveDebt(address _borrower) external view override returns (uint256) {\\n return Troves[_borrower].debt;\\n }\\n\\n function getTroveColl(address _borrower) external view override returns (uint256) {\\n return Troves[_borrower].coll;\\n }\\n\\n // --- Trove property setters, called by BorrowerOperations ---\\n\\n function setTroveStatus(address _borrower, uint256 _num) external override {\\n _requireCallerIsBorrowerOperations();\\n Troves[_borrower].status = Status(_num);\\n }\\n\\n function increaseTroveColl(\\n address _borrower,\\n uint256 _collIncrease\\n ) external override returns (uint256) {\\n _requireCallerIsBorrowerOperations();\\n uint256 newColl = Troves[_borrower].coll.add(_collIncrease);\\n Troves[_borrower].coll = newColl;\\n return newColl;\\n }\\n\\n function decreaseTroveColl(\\n address _borrower,\\n uint256 _collDecrease\\n ) external override returns (uint256) {\\n _requireCallerIsBorrowerOperations();\\n uint256 newColl = Troves[_borrower].coll.sub(_collDecrease);\\n Troves[_borrower].coll = newColl;\\n return newColl;\\n }\\n\\n function increaseTroveDebt(\\n address _borrower,\\n uint256 _debtIncrease\\n ) external override returns (uint256) {\\n _requireCallerIsBorrowerOperations();\\n uint256 newDebt = Troves[_borrower].debt.add(_debtIncrease);\\n Troves[_borrower].debt = newDebt;\\n return newDebt;\\n }\\n\\n function decreaseTroveDebt(\\n address _borrower,\\n uint256 _debtDecrease\\n ) external override returns (uint256) {\\n _requireCallerIsBorrowerOperations();\\n uint256 newDebt = Troves[_borrower].debt.sub(_debtDecrease);\\n Troves[_borrower].debt = newDebt;\\n return newDebt;\\n }\\n\\n function getCurrentICR(\\n address _borrower,\\n uint256 _price\\n ) external view override returns (uint256) {\\n return _getCurrentICR(_borrower, _price);\\n }\\n\\n function getPendingETHReward(address _borrower) public view override returns (uint256) {\\n return _getPendingETHReward(_borrower);\\n }\\n\\n function getPendingZUSDDebtReward(address _borrower) public view override returns (uint256) {\\n return _getPendingZUSDDebtReward(_borrower);\\n }\\n\\n function hasPendingRewards(address _borrower) public view override returns (bool) {\\n return _hasPendingRewards(_borrower);\\n }\\n\\n function getRedemptionRate() public view override returns (uint256) {\\n return _getRedemptionRate();\\n }\\n\\n /// @dev this function forwards the call to the troveManagerRedeemOps in a delegate call fashion\\n /// so the parameters are not needed\\n function redeemCollateral(\\n uint256 _ZUSDamount,\\n address _firstRedemptionHint,\\n address _upperPartialRedemptionHint,\\n address _lowerPartialRedemptionHint,\\n uint256 _partialRedemptionHintNICR,\\n uint256 _maxIterations,\\n uint256 _maxFeePercentage\\n ) external override {\\n (bool success, bytes memory returndata) = troveManagerRedeemOps.delegatecall(msg.data);\\n require(success, string(returndata));\\n }\\n\\n /// @dev this function forwards the call to the troveManagerRedeemOps in a delegate call fashion\\n /// so the parameters are not needed\\n ///DLLR _owner or _spender can use Sovryn Mynt to convert DLLR to ZUSD, then use the Zero redemption mechanism to redeem ZUSD for RBTC, all in a single transaction\\n function redeemCollateralViaDLLR(\\n uint256 _dllrAmount,\\n address _firstRedemptionHint,\\n address _upperPartialRedemptionHint,\\n address _lowerPartialRedemptionHint,\\n uint256 _partialRedemptionHintNICR,\\n uint256 _maxIterations,\\n uint256 _maxFeePercentage,\\n IMassetManager.PermitParams calldata _permitParams\\n ) external override {\\n (bool success, bytes memory returndata) = troveManagerRedeemOps.delegatecall(msg.data);\\n require(success, string(returndata));\\n }\\n\\n /// @dev this function forwards the call to the troveManagerRedeemOps in a delegate call fashion\\n /// so the parameters are not needed\\n ///DLLR _owner or _spender can use Sovryn Mynt to convert DLLR to ZUSD, then use the Zero redemption mechanism to redeem ZUSD for RBTC, all in a single transaction\\n function redeemCollateralViaDllrWithPermit2(\\n uint256 _dllrAmount,\\n address _firstRedemptionHint,\\n address _upperPartialRedemptionHint,\\n address _lowerPartialRedemptionHint,\\n uint256 _partialRedemptionHintNICR,\\n uint256 _maxIterations,\\n uint256 _maxFeePercentage,\\n ISignatureTransfer.PermitTransferFrom memory _permit,\\n bytes calldata _signature\\n ) external override {\\n (bool success, bytes memory returndata) = troveManagerRedeemOps.delegatecall(msg.data);\\n require(success, string(returndata));\\n }\\n}\\n\",\"keccak256\":\"0xbe8cf91ac61c6634335c692e9d285827666fbee79db0032cd1bd0bc49762cfc0\",\"license\":\"MIT\"},\"contracts/TroveManagerStorage.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.6.11;\\n\\nimport \\\"./Interfaces/IStabilityPool.sol\\\";\\nimport \\\"./Interfaces/ICollSurplusPool.sol\\\";\\nimport \\\"./Interfaces/IZUSDToken.sol\\\";\\nimport \\\"./Interfaces/ISortedTroves.sol\\\";\\nimport \\\"./Interfaces/IZEROToken.sol\\\";\\nimport \\\"./Interfaces/IZEROStaking.sol\\\";\\nimport \\\"./Interfaces/IFeeDistributor.sol\\\";\\nimport \\\"./Dependencies/Ownable.sol\\\";\\nimport \\\"./Dependencies/BaseMath.sol\\\";\\nimport \\\"./Dependencies/console.sol\\\";\\n\\ncontract TroveManagerStorage is Ownable, BaseMath {\\n string public constant NAME = \\\"TroveManager\\\";\\n\\n // --- Connected contract declarations ---\\n\\n address public troveManagerRedeemOps;\\n\\n address public borrowerOperationsAddress;\\n\\n IStabilityPool public _stabilityPool;\\n\\n address gasPoolAddress;\\n\\n ICollSurplusPool collSurplusPool;\\n\\n IZUSDToken public _zusdToken;\\n\\n IZEROToken public _zeroToken;\\n\\n IZEROStaking public _zeroStaking;\\n\\n IFeeDistributor public feeDistributor;\\n\\n // A doubly linked list of Troves, sorted by their sorted by their collateral ratios\\n ISortedTroves public sortedTroves;\\n\\n // --- Data structures ---\\n\\n uint256 public baseRate;\\n\\n // The timestamp of the latest fee operation (redemption or new ZUSD issuance)\\n uint256 public lastFeeOperationTime;\\n\\n enum Status {\\n nonExistent,\\n active,\\n closedByOwner,\\n closedByLiquidation,\\n closedByRedemption\\n }\\n\\n // Store the necessary data for a trove\\n struct Trove {\\n uint256 debt;\\n uint256 coll;\\n uint256 stake;\\n Status status;\\n uint128 arrayIndex;\\n }\\n\\n mapping(address => Trove) public Troves;\\n\\n uint256 public totalStakes;\\n\\n // Snapshot of the value of totalStakes, taken immediately after the latest liquidation\\n uint256 public totalStakesSnapshot;\\n\\n // Snapshot of the total collateral across the ActivePool and DefaultPool, immediately after the latest liquidation.\\n uint256 public totalCollateralSnapshot;\\n\\n /*\\n * L_ETH and L_ZUSDDebt track the sums of accumulated liquidation rewards per unit staked. During its lifetime, each stake earns:\\n *\\n * An ETH gain of ( stake * [L_ETH - L_ETH(0)] )\\n * A ZUSDDebt increase of ( stake * [L_ZUSDDebt - L_ZUSDDebt(0)] )\\n *\\n * Where L_ETH(0) and L_ZUSDDebt(0) are snapshots of L_ETH and L_ZUSDDebt for the active Trove taken at the instant the stake was made\\n */\\n uint256 public L_ETH;\\n uint256 public L_ZUSDDebt;\\n\\n // Map addresses with active troves to their RewardSnapshot\\n mapping(address => RewardSnapshot) public rewardSnapshots;\\n\\n // Object containing the ETH and ZUSD snapshots for a given active trove\\n struct RewardSnapshot {\\n uint256 ETH;\\n uint256 ZUSDDebt;\\n }\\n\\n // Array of all active trove addresses - used to to compute an approximate hint off-chain, for the sorted list insertion\\n address[] public TroveOwners;\\n\\n // Error trackers for the trove redistribution calculation\\n uint256 public lastETHError_Redistribution;\\n uint256 public lastZUSDDebtError_Redistribution;\\n}\\n\",\"keccak256\":\"0x979836e7db9988074cd7cbbaaa94d67a297a078a8f93ceb14430ffe548545145\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x60c06040523480156200001157600080fd5b50604051620058f2380380620058f2833981016040819052620000349162000128565b8162000049336001600160e01b036200006316565b60805260601b6001600160601b03191660a05250620001c4565b6001600160a01b038116620000955760405162461bcd60e51b81526004016200008c9062000182565b60405180910390fd5b6001600160a01b038116620000b26001600160e01b036200010716565b6001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a36000604051620000f79062000165565b6040519081900390209190915550565b600080604051620001189062000165565b6040519081900390205492915050565b600080604083850312156200013b578182fd5b825160208401519092506001600160a01b03811681146200015a578182fd5b809150509250929050565b7035b2bc9737bbb730b136329737bbb732b960791b815260110190565b60208082526022908201527f4f776e61626c653a3a7365744f776e65723a20696e76616c6964206164647265604082015261737360f01b606082015260800190565b60805160a05160601c615707620001eb60003980610ff152508061223252506157076000f3fe608060405234801561001057600080fd5b50600436106104c25760003560e01c8063756b253e11610278578063b0d8e1811161015c578063d293c710116100ce578063d815e8e911610092578063d815e8e91461095a578063d9a7244414610962578063e056e91814610975578063e2ac77b014610988578063f36b24251461099b578063fe2ba848146109a3576104c2565b8063d293c71014610906578063d380a37c14610919578063d3d6f84314610921578063d5b3563514610934578063d66a255314610947576104c2565b8063be4b033411610120578063be4b0334146108cb578063bf9befb1146108d3578063c35bc550146108db578063c52861f2146108e3578063c7b55481146108eb578063cbd138ae146108f3576104c2565b8063b0d8e18114610867578063b7f8cf9b1461087a578063b82f263d14610882578063b91af97c14610895578063bcd37526146108b8576104c2565b8063887105d3116101f55780639dd233d2116101b95780639dd233d2146108325780639f0706701461083a578063a20baee614610773578063a3f4df7e14610842578063ae7bec1914610857578063ae9187541461085f576104c2565b8063887105d3146107f4578063893d20e8146107fc57806396d711ff146108045780639708daf41461080c5780639976cf451461081f576104c2565b80637cf54e401161023c5780637cf54e40146107b65780637f7dde4a146107be578063807d138d146107c657806382fe3eb9146107ce57806387436936146107e1576104c2565b8063756b253e14610783578063759b303414610796578063794e57241461079e578063795d26c3146107a6578063797250e3146107ae576104c2565b80633cc74225116103aa57806361ec893d1161031c5780636b444952116102e05780636b4449521461072c5780636ef6433814610734578063716c47e61461075857806372423c171461076057806372fe25aa14610773578063741bef1a1461077b576104c2565b806361ec893d146106e3578063631203b0146106eb57806364cee260146106fe578063653d46e71461071157806366ca4a2114610724576104c2565b80634a767d681161036e5780634a767d681461067a5780634e443d9e1461068d5780635733d58f146106ad5780635d6b480f146106b55780635d8c9609146106c85780635dba4c4a146106db576104c2565b80633cc742251461063157806342ccf1e414610639578063477d66cf1461064c578063480cd5781461065f57806349eefeee14610672576104c2565b806317c62b17116104435780631f68f20a116104075780631f68f20a146105eb57806321e37801146105f35780632b11551a146106065780632f8655681461060e57806331c903b0146106215780633a12859514610629576104c2565b806317c62b171461059757806318f2817a146105aa5780631a59a50e146105bd5780631bf43555146105d05780631e8b1c2b146105d8576104c2565b806312261ee71161048a57806312261ee71461053557806312610e921461053d57806313af40351461055057806315d549f1146105635780631673c79a14610576576104c2565b806301f16e18146104c757806305b6f5ca146104dc578063071a7541146104ef5780630b0765571461050d5780630d43e8ad14610520575b600080fd5b6104da6104d5366004614d96565b6109b6565b005b6104da6104ea366004614f61565b610f1c565b6104f7610fb3565b6040516105049190615568565b60405180910390f35b6104da61051b366004614c9f565b610fb8565b610528610fe0565b604051610504919061512e565b610528610fef565b6104f761054b366004614cd7565b611013565b6104da61055e366004614c9f565b61106b565b6104f7610571366004614c9f565b6110ac565b610589610584366004614c9f565b6110d0565b604051610504929190615571565b6104f76105a5366004614c9f565b6110e9565b6104f76105b8366004614c9f565b6110f4565b6104f76105cb366004614c9f565b611107565b6104f76111cd565b6104da6105e6366004614d02565b6111da565b6104f761153b565b6104f7610601366004614c9f565b611541565b6104f761156b565b6104da61061c366004614c9f565b61157a565b6104f76115e0565b6105286115ed565b6105286115fc565b6104f7610647366004614c9f565b61160b565b6104f761065a366004614ec4565b61163c565b6104f761066d366004614c9f565b61164f565b6104f761166d565b6104f7610688366004614cd7565b611673565b6106a061069b366004614ec4565b611692565b60405161050491906151a1565b6104f761169d565b6104da6106c3366004614cd7565b61171a565b6104f76106d6366004614c9f565b611766565b6104da611771565b6104f76117dc565b6104f76106f9366004614ec4565b6117e1565b6104f761070c366004614c9f565b6117ee565b6104da61071f366004614ec4565b61180c565b6104f7611b96565b6104f7611ba8565b610747610742366004614c9f565b611bae565b6040516105049594939291906155a3565b610528611be8565b6104f761076e366004614cd7565b611bf7565b6104f7611c54565b610528611c60565b610528610791366004614ec4565b611c6f565b6104f7611c96565b6104f7611ca3565b6104f7611ce8565b6104f7611e07565b610528611e0d565b610528611e1c565b6104f7611e2b565b6104da6107dc366004614c9f565b611e31565b6104da6107ef366004614c9f565b611e42565b6104f7611ece565b610528611f9d565b6104f7611fbc565b6104da61081a366004614feb565b611fc2565b6104f761082d366004614cd7565b61205b565b6104f761208e565b610528612094565b61084a6120a3565b60405161050491906151da565b6105286120cb565b6105286120da565b6104f7610875366004614c9f565b6120e9565b610528612110565b6104f7610890366004614ec4565b61211f565b6108a86108a3366004614c9f565b61212a565b604051610504949392919061560f565b6104da6108c6366004614ef4565b61218e565b6104f7612224565b6104f761222a565b6104f7612230565b6104f7612254565b6104f7612266565b6104da610901366004614c9f565b612272565b6104f7610914366004614cd7565b612285565b6104f7612298565b6104f761092f366004614cd7565b61229e565b6104f7610942366004614ec4565b6122d4565b6104f7610955366004614c9f565b6122e7565b610528612302565b610528610970366004614ec4565b612311565b6106a0610983366004614c9f565b61233b565b6106a0610996366004614c9f565b612399565b6104f76123a4565b6104da6109b1366004614c9f565b6123b1565b6109be611f9d565b6001600160a01b0316336001600160a01b0316146109f75760405162461bcd60e51b81526004016109ee906153c5565b60405180910390fd5b8051610a02906123c2565b610a0f81602001516123c2565b610a1c81604001516123c2565b610a2981606001516123c2565b610a3681608001516123c2565b610a438160a001516123c2565b610a508160c001516123c2565b610a5d8160e001516123c2565b610a6b8161010001516123c2565b610a798161012001516123c2565b610a878161014001516123c2565b610a958161016001516123c2565b610aa38161018001516123c2565b610ab1816101a001516123c2565b8051600c80546001600160a01b03199081166001600160a01b038085169190911790925560208401516004805483169184169190911790556040808501516003805484169185169190911790556060850151600580548416918516919091179055608085015160008054841691851691909117905560a085015160018054841691851691909117905560c085015160068054841691851691909117905560e0850151600780548416918516919091179055610100850151600880548416918516919091179055610120850151600280548416918516919091179055610140850151600980548416918516919091179055610160850151600d80548416918516919091179055610180850151600a805484169185169190911790556101a0850151600b80549093169316929092179055517f84861842e6a98dd759145eb5e7b48279d0f02ebeabc8a69e5196b2f4c5023db991610c0c9161512e565b60405180910390a17f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b18160200151604051610c47919061512e565b60405180910390a17fbf65195e6d5213f6fcbce65b1454c925197a45e616dabd2e243542b039b050928160600151604051610c82919061512e565b60405180910390a17f3ca631ffcd2a9b5d9ae18543fc82f58eb4ca33af9e6ab01b7a8e95331e6ed9858160600151604051610cbd919061512e565b60405180910390a17f78f058b189175430c48dc02699e3a0031ea4ff781536dc2fab847de4babdd8828160800151604051610cf8919061512e565b60405180910390a17f5ee0cae2f063ed938bb55046f6a932fb6ae792bf43624806bb90abe68a50be9b8160a00151604051610d33919061512e565b60405180910390a17f82966d27eea39b038ee0fa30cd16532bb24f6e65d31cb58fb227aa5766cdcc7f8160c00151604051610d6e919061512e565b60405180910390a17fcfb07d791fcafc032b35837b50eb84b74df518cf4cc287e8084f47630fa70fa08160e00151604051610da9919061512e565b60405180910390a17fe67f36a6e961157d6eff83b91f3af5a62131ceb6f04954ef74f51c1c05e7f88d816101000151604051610de5919061512e565b60405180910390a17f8c537274438aa850a330284665d81a85dd38267d09e4050d416bfc94142db264816101200151604051610e21919061512e565b60405180910390a17fcdf33850c44a1a874b5fefb51dae615ac6afebd581ef90d571f81b06541d8e9d816101400151604051610e5d919061512e565b60405180910390a17f65f4cf077bc01e4742eb5ad98326f6e95b63548ea24b17f8d5e823111fe78800816101600151604051610e99919061512e565b60405180910390a17f61e0c29d5028a9e4facaa476a46e78912e99f1ba945c9560b86b82ebe36ee52d816101800151604051610ed5919061512e565b60405180910390a17f7624075ed781e3aa34b0f492d70d1bb5a6fcde04534165e20f990d2e546be4dd816101a00151604051610f11919061512e565b60405180910390a150565b6004546040516000916060916001600160a01b0390911690610f419084903690615101565b600060405180830381855af49150503d8060008114610f7c576040519150601f19603f3d011682016040523d82523d6000602084013e610f81565b606091505b5091509150818190610fa65760405162461bcd60e51b81526004016109ee91906151da565b5050505050505050505050565b600281565b610fc0612407565b600054600154610fdd916001600160a01b03908116911683612433565b50565b600c546001600160a01b031681565b7f000000000000000000000000000000000000000000000000000000000000000081565b600061101d612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff61254516565b6001600160a01b03851660009081526010602052604090208190559150505b92915050565b611073611f9d565b6001600160a01b0316336001600160a01b0316146110a35760405162461bcd60e51b81526004016109ee906153c5565b610fdd81612587565b60006110b6612407565b6110bf82612612565b6001600160801b031690505b919050565b6016602052600090815260409020805460019091015482565b60006110658261160b565b60006110fe612407565b611065826126ac565b6001600160a01b0381166000908152601660205260408120546014548290611135908363ffffffff61254516565b905080158061116e575060016001600160a01b03851660009081526010602052604090206003015460ff16600481111561116b57fe5b14155b1561117e576000925050506110cb565b6001600160a01b038416600090815260106020526040812060020154906111c3670de0b6b3a76400006111b7848663ffffffff61275f16565b9063ffffffff61279916565b9695505050505050565b6809c2007651b250000081565b80516111f85760405162461bcd60e51b81526004016109ee906153f6565b6000546001546006546001600160a01b0392831692918216911661121a614b2b565b611222614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561127257600080fd5b505af1158015611286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112aa9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b1580156112ec57600080fd5b505afa158015611300573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113249190614edc565b60208301528151611334906127db565b158015604084015261135c576113558585846000015185602001518a612876565b9050611374565b6113718585846000015185602001518a612b8b565b90505b60008160200151116113985760405162461bcd60e51b81526004016109ee906154d7565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad926113cc92600401615571565b600060405180830381600087803b1580156113e657600080fd5b505af11580156113fa573d6000803e3d6000fd5b5050505061141285858360c001518460e00151612ccb565b61010081015115611489576008546101008201516040516364a197f360e01b81526001600160a01b03888116936364a197f393611456939290911691600401615188565b600060405180830381600087803b15801561147057600080fd5b505af1158015611484573d6000803e3d6000fd5b505050505b611497858260400151612f2f565b60208101516060830152610100810151604082015182516114cf92916114c3919063ffffffff61254516565b9063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda940394611517949092909161560f565b60405180910390a1611533853383606001518460400151613090565b505050505050565b600e5481565b6001600160a01b03811660009081526010602052604081206003015460ff16600481111561106557fe5b60006115756115e0565b905090565b6115838161316d565b6040805160018082528183019092526060916020808301908036833701905050905081816000815181106115b357fe5b60200260200101906001600160a01b031690816001600160a01b0316815250506115dc816111da565b5050565b6000611575600e546131b6565b600b546001600160a01b031681565b6001546001600160a01b031681565b6001600160a01b0381166000908152601660205260408120600101546015548290611135908363ffffffff61254516565b6000611065611649611b96565b83613253565b6001600160a01b031660009081526010602052604090206001015490565b60175490565b600080600061168185613271565b9150915060006111c38383876132f7565b6000611065826127db565b60035460408051635733d58f60e01b815290516000926001600160a01b031691635733d58f916004808301926020929190829003018186803b1580156116e257600080fd5b505afa1580156116f6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115759190614edc565b611722612407565b80600481111561172e57fe5b6001600160a01b0383166000908152601060205260409020600301805460ff1916600183600481111561175d57fe5b02179055505050565b600061106582611107565b611779612407565b6000611783613329565b9050670de0b6b3a764000081111561179757fe5b600e8190556040517fc454ee9b76c52f782a256af821b857ca6e125d1e3333bcede402fec2bed9600c906117cc908390615568565b60405180910390a1610fdd61336d565b603c81565b60006110656116496123a4565b6001600160a01b031660009081526010602052604090206002015490565b611814614ba8565b506040805160e081018252600080546001600160a01b0390811683526001548116602084015292820181905260608201819052600d548316608083015260a0820181905260c082015260065490911661186b614b2b565b611873614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b1580156118c357600080fd5b505af11580156118d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118fb9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b15801561193d57600080fd5b505afa158015611951573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119759190614edc565b60208301528151611985906127db565b15801560408401526119ac576119a58483600001518460200151886133c2565b90506119cc565b6119c9846000015185602001518460000151856020015189613869565b90505b60008160200151116119f05760405162461bcd60e51b81526004016109ee906154d7565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad92611a2492600401615571565b600060405180830381600087803b158015611a3e57600080fd5b505af1158015611a52573d6000803e3d6000fd5b50505050611a72846000015185602001518360c001518460e00151612ccb565b61010081015115611ae95783516008546101008301516040516364a197f360e01b81526001600160a01b03938416936364a197f393611ab693911691600401615188565b600060405180830381600087803b158015611ad057600080fd5b505af1158015611ae4573d6000803e3d6000fd5b505050505b611afb84600001518260400151612f2f565b6020810151606083015261010081015160408201518251611b2792916114c3919063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda940394611b6f949092909161560f565b60405180910390a1611b8f84600001513383606001518460400151613090565b5050505050565b6000611575611ba3613329565b613a1f565b60195481565b60106020526000908152604090208054600182015460028301546003909301549192909160ff81169061010090046001600160801b031685565b6004546001600160a01b031681565b6000611c01612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff613b0116565b6001600160a01b038516600090815260106020526040902060010181905591505092915050565b670de0b6b3a764000081565b6002546001600160a01b031681565b60178181548110611c7c57fe5b6000918252602090912001546001600160a01b0316905081565b6801158e460913d0000081565b60035460408051631e5395c960e21b815290516000926001600160a01b03169163794e5724916004808301926020929190829003018186803b1580156116e257600080fd5b60008054604080516272c7d360e71b8152905183926001600160a01b031691633963e980916004808301926020929190829003018186803b158015611d2c57600080fd5b505afa158015611d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d649190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b0316633963e9806040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b505afa158015611dca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dee9190614edc565b9050611e00828263ffffffff613b0116565b9250505090565b60185481565b600a546001600160a01b031681565b6000546001600160a01b031681565b60125481565b611e39612407565b610fdd81613b26565b611e4a611f9d565b6001600160a01b0316336001600160a01b031614611e7a5760405162461bcd60e51b81526004016109ee906153c5565b611e83816123c2565b600480546001600160a01b0319166001600160a01b0383161790556040517f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b190610f1190839061512e565b6000805460408051630a7b61df60e11b8152905183926001600160a01b0316916314f6c3be916004808301926020929190829003018186803b158015611f1357600080fd5b505afa158015611f27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f4b9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b600080604051611fac90615111565b6040519081900390205492915050565b60135481565b6004546040516000916060916001600160a01b0390911690611fe79084903690615101565b600060405180830381855af49150503d8060008114612022576040519150601f19603f3d011682016040523d82523d6000602084013e612027565b606091505b509150915081819061204c5760405162461bcd60e51b81526004016109ee91906151da565b50505050505050505050505050565b6000612065612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff613b0116565b60145481565b6003546001600160a01b031681565b6040518060400160405280600c81526020016b2a3937bb32a6b0b730b3b2b960a11b81525081565b6006546001600160a01b031681565b600d546001600160a01b031681565b60008060006120f784613271565b9150915060006121078383613b83565b95945050505050565b6005546001600160a01b031681565b600061106582613bb8565b6001600160a01b03811660009081526010602052604081208054600190910154909180612156856110e9565b915061216185611766565b9050612173848363ffffffff613b0116565b9350612185838263ffffffff613b0116565b92509193509193565b6004546040516000916060916001600160a01b03909116906121b39084903690615101565b600060405180830381855af49150503d80600081146121ee576040519150601f19603f3d011682016040523d82523d6000602084013e6121f3565b606091505b50915091508181906122185760405162461bcd60e51b81526004016109ee91906151da565b50505050505050505050565b60155481565b60115481565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000611575612261613329565b6131b6565b670ddd4b8c6c7d70d881565b61227a612407565b610fdd816002613be4565b60006122918383611673565b9392505050565b600f5481565b60006122a8612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff61254516565b60006110656122e1612254565b83613cf9565b6001600160a01b031660009081526010602052604090205490565b6009546001600160a01b031681565b60006017828154811061232057fe5b6000918252602090912001546001600160a01b031692915050565b600060016001600160a01b03831660009081526010602052604090206003015460ff16600481111561236957fe5b14612376575060006110cb565b506014546001600160a01b03821660009081526016602052604090205410919050565b60006110658261233b565b6000611575600e54613a1f565b6123b9612407565b610fdd81613d39565b6001600160a01b0381166123e85760405162461bcd60e51b81526004016109ee906152a6565b803b806115dc5760405162461bcd60e51b81526004016109ee9061544c565b6005546001600160a01b031633146124315760405162461bcd60e51b81526004016109ee90615368565b565b61243c8161233b565b156125405761244a8161316d565b600061245582611107565b905060006124628361160b565b6001600160a01b038416600090815260106020526040902060010154909150612491908363ffffffff613b0116565b6001600160a01b03841660009081526010602052604090206001810191909155546124c2908263ffffffff613b0116565b6001600160a01b0384166000908152601060205260409020556124e483613b26565b6124f085858385613d89565b6001600160a01b0383166000818152601060205260408082208054600182015460029092015492516000805160206156b28339815191529461253594929392916155e3565b60405180910390a250505b505050565b600061229183836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613e71565b6001600160a01b0381166125ad5760405162461bcd60e51b81526004016109ee90615264565b806001600160a01b03166125bf611f9d565b6001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3600060405161260290615111565b6040519081900390209190915550565b601780546001808201835560008381527fc624b66cc0138b8fabc209247f72d758e1cf3343756d543badbf24212bed8c1590920180546001600160a01b0319166001600160a01b0386161790559154909161266d9190612545565b6001600160a01b039290921660009081526010602052604090206003018054610100600160881b0319166101006001600160801b038516021790555090565b6001600160a01b03811660009081526010602052604081206001015481906126d390613e9d565b6001600160a01b03841660009081526010602052604090206002018054908290556011549192509061271d908390612711908463ffffffff61254516565b9063ffffffff613b0116565b60118190556040517f6bac5e0eb3c44eb03a60ab11ec3a2c051771616aecadbcfff2630aabae5203829161275091615568565b60405180910390a15092915050565b60008261276e57506000611065565b8282028284828161277b57fe5b04146122915760405162461bcd60e51b81526004016109ee90615327565b600061229183836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613ed9565b6000806127e783613bb8565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561283757600080fd5b505afa15801561284b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061286f9190614edc565b1192915050565b61287e614b5c565b612886614be4565b61288e614b5c565b848252600060808301526128a0611ce8565b60a08301526128ad611ece565b60c0830152600060208301525b835182602001511015612b8057838260200151815181106128d757fe5b6020908102919091018101516001600160a01b03166060840181905260009081526010909152604090206003015460019060ff16600481111561291657fe5b1461292057612b70565b61292e826060015187611673565b60408301526080820151612a9557600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561298a57600080fd5b505afa15801561299e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129c29190614edc565b8260400151101580156129d457508151155b156129de57612b70565b60006129f38360c001518460a00151896132f7565b9050612a108989856060015186604001518760000151868d613f10565b60808101518451919350612a2a919063ffffffff61254516565b8352608082015160a0840151612a459163ffffffff61254516565b60a08085019190915282015160c0840151612a659163ffffffff61254516565b60c0840152612a7484836142e6565b9350612a898360c001518460a00151896143fd565b15608084015250612b70565b81608001518015612b2e5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b158015612aef57600080fd5b505afa158015612b03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b279190614edc565b8260400151105b15612b7057612b4788888460600151856000015161449c565b60808101518351919250612b61919063ffffffff61254516565b8252612b6d83826142e6565b92505b60208201805160010190526128ba565b505095945050505050565b612b93614b5c565b612b9b614be4565b612ba3614b5c565b848252600060208301525b835182602001511015612b805783826020015181518110612bcb57fe5b60209081029190910101516001600160a01b031660608301819052612bf09087611673565b6040808401919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b158015612c3b57600080fd5b505afa158015612c4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c739190614edc565b82604001511015612cbb57612c9288888460600151856000015161449c565b60808101518351919250612cac919063ffffffff61254516565b8252612cb883826142e6565b92505b6020820180516001019052612bae565b81612cd557612f29565b601854600090612cf79061271184670de0b6b3a764000063ffffffff61275f16565b90506000612d1c601954612711670de0b6b3a76400008761275f90919063ffffffff16565b90506000612d356011548461279990919063ffffffff16565b90506000612d4e6011548461279990919063ffffffff16565b9050612d75612d686011548461275f90919063ffffffff16565b859063ffffffff61254516565b601855601154612d9d90612d9090839063ffffffff61275f16565b849063ffffffff61254516565b601955601454612db3908363ffffffff613b0116565b601455601554612dc9908263ffffffff613b0116565b60158190556014546040517f9f8bc8ab0daf5bceef75ecfd2085d1fcc6548c657ea970d9a23a60610d0737e392612e009291615571565b60405180910390a160405163121cbc4d60e11b81526001600160a01b03891690632439789a90612e34908990600401615568565b600060405180830381600087803b158015612e4e57600080fd5b505af1158015612e62573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038a16925063f2e91d719150612e92908990600401615568565b600060405180830381600087803b158015612eac57600080fd5b505af1158015612ec0573d6000803e3d6000fd5b50506040516364a197f360e01b81526001600160a01b038b1692506364a197f39150612ef2908a908990600401615188565b600060405180830381600087803b158015612f0c57600080fd5b505af1158015612f20573d6000803e3d6000fd5b50505050505050505b50505050565b6011546012819055506000826001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612f7357600080fd5b505afa158015612f87573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fab9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612ffd57600080fd5b505afa158015613011573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130359190614edc565b905061304b81612711848663ffffffff61254516565b60138190556012546040517f51bf4c63ec3cba9d03d43238abbdd979dd91bd16d9895c74ceea9118c7baaf60926130829291615571565b60405180910390a150505050565b811561310157600954600754604051631062c15f60e11b81526001600160a01b03928316926320c582be926130ce9291169087908790600401615142565b600060405180830381600087803b1580156130e857600080fd5b505af11580156130fc573d6000803e3d6000fd5b505050505b8015612f29576040516364a197f360e01b81526001600160a01b038516906364a197f3906131359086908590600401615188565b600060405180830381600087803b15801561314f57600080fd5b505af1158015613163573d6000803e3d6000fd5b5050505050505050565b60016001600160a01b03821660009081526010602052604090206003015460ff16600481111561319957fe5b14610fdd5760405162461bcd60e51b81526004016109ee90615519565b600061106561324583600360009054906101000a90046001600160a01b03166001600160a01b03166328d28b5b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b505afa158015613221573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127119190614edc565b670de0b6b3a76400006145ce565b6000612291670de0b6b3a76400006111b7858563ffffffff61275f16565b600080600061327f84611107565b9050600061328c8561160b565b6001600160a01b038616600090815260106020526040812060010154919250906132bc908463ffffffff613b0116565b6001600160a01b038716600090815260106020526040812054919250906132e9908463ffffffff613b0116565b919550909350505050915091565b6000821561331e576000613315846111b7878663ffffffff61275f16565b91506122919050565b506000199392505050565b6000806133346145e4565b9050600061334a670ddd4b8c6c7d70d883614600565b9050611e00670de0b6b3a76400006111b783600e5461275f90919063ffffffff16565b6000613384600f544261254590919063ffffffff16565b9050603c8110610fdd5742600f8190556040517f860f8d2f0c74dd487e89e2883e3b25b8159ce1e1b3433a291cba7b82c508f3bc91610f1191615568565b6133ca614b5c565b6133d2614be4565b6133da614b5c565b848252600060808301526133ec611ce8565b60a08301526133f9611ece565b8260c001818152505086608001516001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b15801561343f57600080fd5b505afa158015613453573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134779190614cbb565b82606001906001600160a01b031690816001600160a01b031681525050600087608001516001600160a01b0316631e2231436040518163ffffffff1660e01b815260040160206040518083038186803b1580156134d357600080fd5b505afa1580156134e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061350b9190614cbb565b6000602085015290505b84836020015110801561353e5750806001600160a01b031683606001516001600160a01b031614155b1561385e5760808801516060840151604051632dc9c0eb60e21b81526000926001600160a01b03169163b72703ac9161357a919060040161512e565b60206040518083038186803b15801561359257600080fd5b505afa1580156135a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135ca9190614cbb565b90506135da846060015189611673565b6040850152608084015161375357600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561363657600080fd5b505afa15801561364a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061366e9190614edc565b84604001511015801561368057508351155b1561368b575061385e565b60006136a08560c001518660a001518b6132f7565b8a5160208c01516060880151604089015189519495506136c194868f613f10565b608081015186519195506136db919063ffffffff61254516565b8552608084015160a08601516136f69163ffffffff61254516565b8560a00181815250506137238461010001516114c38660a001518860c0015161254590919063ffffffff16565b60c086015261373286856142e6565b95506137478560c001518660a001518b6143fd565b15608086015250613840565b836080015180156137ec5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156137ad57600080fd5b505afa1580156137c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137e59190614edc565b8460400151105b1561383a5761380d89600001518a602001518660600151876000015161449c565b60808101518551919450613827919063ffffffff61254516565b845261383385846142e6565b9450613840565b5061385e565b6001600160a01b031660608401526020830180516001019052613515565b505050949350505050565b613871614b5c565b613879614be4565b613881614b5c565b600d54858352600060208401526001600160a01b03165b8483602001511015613a1357806001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b1580156138dd57600080fd5b505afa1580156138f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139159190614cbb565b6001600160a01b03166060840181905261392f9088611673565b6040808501919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b15801561397a57600080fd5b505afa15801561398e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139b29190614edc565b836040015110156139fe576139d189898560600151866000015161449c565b608081015184519193506139eb919063ffffffff61254516565b83526139f784836142e6565b9350613a03565b613a13565b6020830180516001019052613898565b50505095945050505050565b6000611065613a7683600360009054906101000a90046001600160a01b03166001600160a01b031663f92d34336040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b600360009054906101000a90046001600160a01b03166001600160a01b031663240926696040518163ffffffff1660e01b815260040160206040518083038186803b158015613ac457600080fd5b505afa158015613ad8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613afc9190614edc565b6145ce565b6000828201838110156122915760405162461bcd60e51b81526004016109ee9061522d565b601480546001600160a01b038316600090815260166020526040908190209182556015546001909201829055915491517fc437f324d85e369394148dd9d62f98f534b382e01ed3dd2eb98138fb6d3ab49a92610f11929091615571565b60008115613baf57613ba8826111b78568056bc75e2d6310000063ffffffff61275f16565b9050611065565b50600019611065565b600080613bc3611ece565b90506000613bcf611ce8565b9050613bdc8282866132f7565b949350505050565b6000816004811115613bf257fe5b14158015613c0c57506001816004811115613c0957fe5b14155b613c1257fe5b601754613c1e816146ab565b6001600160a01b0383166000908152601060205260409020600301805483919060ff19166001836004811115613c5057fe5b02179055506001600160a01b0383166000908152601060209081526040808320600180820185905590849055601690925282208281550155613c928382614751565b600d54604051631484968760e11b81526001600160a01b03909116906329092d0e90613cc290869060040161512e565b600060405180830381600087803b158015613cdc57600080fd5b505af1158015613cf0573d6000803e3d6000fd5b50505050505050565b600080613d18670de0b6b3a76400006111b7868663ffffffff61275f16565b90508281106122915760405162461bcd60e51b81526004016109ee90615481565b6001600160a01b038116600090815260106020526040902060020154601154613d68908263ffffffff61254516565b601155506001600160a01b0316600090815260106020526040812060020155565b60405163121cbc4d60e11b81526001600160a01b03841690632439789a90613db5908590600401615568565b600060405180830381600087803b158015613dcf57600080fd5b505af1158015613de3573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038716925063f2e91d719150613e13908590600401615568565b600060405180830381600087803b158015613e2d57600080fd5b505af1158015613e41573d6000803e3d6000fd5b505060405163be41205f60e01b81526001600160a01b038616925063be41205f9150613135908490600401615568565b60008184841115613e955760405162461bcd60e51b81526004016109ee91906151da565b505050900390565b60008060135460001415613eb2575081611065565b600060125411613ebe57fe5b6122916013546111b76012548661275f90919063ffffffff16565b60008183613efa5760405162461bcd60e51b81526004016109ee91906151da565b506000838581613f0657fe5b0495945050505050565b613f18614b5c565b613f20614c2c565b601754600110613f3057506142db565b613f398761212a565b60408501526020848101919091528401819052908352613f58906148f2565b604083018190526801158e460913d0000060608401526020830151613f829163ffffffff61254516565b8152670de0b6b3a7640000861161404d57613fa7898983602001518460400151613d89565b613fb087613d39565b60006080830181905260a0830152815160c0830152805160e0830152613fd7876003613be4565b815160208301516040516001600160a01b038a1692600080516020615692833981519152926140089260029061557f565b60405180910390a2866001600160a01b03166000805160206156b28339815191526000806000600260405161404094939291906151ac565b60405180910390a26142d9565b670de0b6b3a7640000861180156140e85750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156140ad57600080fd5b505afa1580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614edc565b86105b1561413857614101898983602001518460400151613d89565b61410a87613d39565b8151815161411991908761497e565b60e086015260c085015260a08401526080830152613fd7876003613be4565b600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561418657600080fd5b505afa15801561419a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141be9190614edc565b86101580156141cc57508386105b80156141d9575081518510155b156142c8576141f2898983602001518460400151613d89565b846141f957fe5b61420287613d39565b61421582600001518360200151856149ea565b9150614222876003613be4565b6101008201511561429757600854610100830151604051633f10abab60e01b81526001600160a01b0390921691633f10abab91614264918b9190600401615188565b600060405180830381600087803b15801561427e57600080fd5b505af1158015614292573d6000803e3d6000fd5b505050505b815160a08301516040516001600160a01b038a1692600080516020615692833981519152926140089260029061557f565b6142d0614b5c565b91506142db9050565b505b979650505050505050565b6142ee614b5c565b604080830151908401516143079163ffffffff613b0116565b6040820152606080830151908401516143259163ffffffff613b0116565b6060820152815160208401516143409163ffffffff613b0116565b602080830191909152820151835161435d9163ffffffff613b0116565b8152608080830151908401516143789163ffffffff613b0116565b608082015260a080830151908401516143969163ffffffff613b0116565b60a082015260c080830151908401516143b49163ffffffff613b0116565b60c082015260e080830151908401516143d29163ffffffff613b0116565b60e082015261010080830151908401516143f19163ffffffff613b0116565b61010082015292915050565b60008061440b8585856132f7565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561445b57600080fd5b505afa15801561446f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144939190614edc565b11949350505050565b6144a4614b5c565b6144ac614c2c565b6144b58461212a565b604085019081526020858101928352860192909252918452905190516144df918891889190613d89565b6144e884613d39565b6144f582602001516148f2565b604083018190526801158e460913d0000060608401526020830151600091614523919063ffffffff61254516565b90506145348360000151828661497e565b60e087015260c086015260a08501526080840152614553856003613be4565b825160208401516040516001600160a01b03881692600080516020615692833981519152926145849260019061557f565b60405180910390a2846001600160a01b03166000805160206156b2833981519152600080600060016040516145bc94939291906151ac565b60405180910390a25050949350505050565b60008183106145dd5781612291565b5090919050565b6000611575603c6111b7600f544261254590919063ffffffff16565b6000631f54050082111561461657631f54050091505b8161462a5750670de0b6b3a7640000611065565b670de0b6b3a764000083835b60018111156146a1576002810661466b576146518283614af8565b915061466481600263ffffffff61279916565b905061469c565b6146758284614af8565b92506146818283614af8565b915061469960026111b783600163ffffffff61254516565b90505b614636565b6111c38284614af8565b6001811180156147355750600d546040805163de8fa43160e01b815290516001926001600160a01b03169163de8fa431916004808301926020929190829003018186803b1580156146fb57600080fd5b505afa15801561470f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147339190614edc565b115b610fdd5760405162461bcd60e51b81526004016109ee906152dd565b6001600160a01b03821660009081526010602052604081206003015460ff169081600481111561477d57fe5b141580156147975750600181600481111561479457fe5b14155b61479d57fe5b6001600160a01b03831660009081526010602052604081206003015461010090046001600160801b03169083906147d5826001612545565b905080836001600160801b031611156147ea57fe5b6000601782815481106147f957fe5b600091825260209091200154601780546001600160a01b03909216925082916001600160801b03871690811061482b57fe5b600091825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918316815260109091526040908190206003018054610100600160881b0319166101006001600160801b03881602179055517f02b04ae5f7be9ca7c103293a2aa15f3c339d15d6eda53b721fef7b0e609c831a906148b49083908790615166565b60405180910390a160178054806148c757fe5b600082815260209020810160001990810180546001600160a01b031916905501905550505050505050565b600354604080516324386ecd60e11b815290516000926001600160a01b031691634870dd9a916004808301926020929190829003018186803b15801561493757600080fd5b505afa15801561494b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061496f9190614edc565b828161497757fe5b0492915050565b600080808084156149d45761499387866145ce565b93506149a9876111b7888763ffffffff61275f16565b92506149bb878563ffffffff61254516565b91506149cd868463ffffffff61254516565b90506149e1565b5060009250829150859050845b93509350935093565b6149f2614b5c565b838152602080820184905260035460408051631e5395c960e21b81529051600093614a8e9387936111b7936001600160a01b039092169263794e572492600480840193829003018186803b158015614a4957600080fd5b505afa158015614a5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614a819190614edc565b889063ffffffff61275f16565b9050614a99816148f2565b604083018190526801158e460913d00000606084015260808301869052614ac790829063ffffffff61254516565b60a0830152614adc848263ffffffff61254516565b61010083015250600060c0820181905260e08201529392505050565b600080614b0b848463ffffffff61275f16565b9050613bdc670de0b6b3a76400006111b7836706f05b59d3b20000613b01565b6040518060a00160405280600081526020016000815260200160001515815260200160008152602001600081525090565b6040518061012001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040518060e0016040528060008152602001600081526020016000815260200160006001600160a01b0316815260200160001515815260200160008152602001600081525090565b60405180606001604052806000815260200160008152602001600081525090565b80356110658161567c565b60008083601f840112614c69578081fd5b50813567ffffffffffffffff811115614c80578182fd5b602083019150836020828501011115614c9857600080fd5b9250929050565b600060208284031215614cb0578081fd5b81356122918161567c565b600060208284031215614ccc578081fd5b81516122918161567c565b60008060408385031215614ce9578081fd5b8235614cf48161567c565b946020939093013593505050565b60006020808385031215614d14578182fd5b823567ffffffffffffffff811115614d2a578283fd5b80840185601f820112614d3b578384fd5b80359150614d50614d4b83615651565b61562a565b8281528381019082850185850284018601891015614d6c578687fd5b8693505b848410156142d957614d828982614c4d565b835260019390930192918501918501614d70565b60006101c0808385031215614da9578182fd5b614db28161562a565b614dbc8585614c4d565b8152614dcb8560208601614c4d565b6020820152614ddd8560408601614c4d565b6040820152614def8560608601614c4d565b6060820152614e018560808601614c4d565b6080820152614e138560a08601614c4d565b60a0820152614e258560c08601614c4d565b60c0820152614e378560e08601614c4d565b60e08201526101009150614e4d85838601614c4d565b828201526101209150614e6285838601614c4d565b828201526101409150614e7785838601614c4d565b828201526101609150614e8c85838601614c4d565b828201526101809150614ea185838601614c4d565b828201526101a09150614eb685838601614c4d565b918101919091529392505050565b600060208284031215614ed5578081fd5b5035919050565b600060208284031215614eed578081fd5b5051919050565b600080600080600080600060e0888a031215614f0e578283fd5b873596506020880135614f208161567c565b95506040880135614f308161567c565b94506060880135614f408161567c565b9699959850939660808101359560a0820135955060c0909101359350915050565b600080600080600080600080888a03610160811215614f7e578182fd5b8935985060208a0135614f908161567c565b975060408a0135614fa08161567c565b965060608a0135614fb08161567c565b955060808a810135955060a08b0135945060c08b0135935060df1982011215614fd7578182fd5b5060e0890190509295985092959890939650565b6000806000806000806000806000808a8c0361018081121561500b578485fd5b8b359a5060208c013561501d8161567c565b995060408c013561502d8161567c565b985060608c013561503d8161567c565b975060808c810135975060a08d0135965060c08d0135955060df19820190811215615066578384fd5b615070606061562a565b9150604081121561507f578384fd5b5061508a604061562a565b60e08d01356150988161567c565b81526101008d01356020808301919091529082526101208d0135908201526101408c0135604082015292506101608b013567ffffffffffffffff8111156150dd578283fd5b6150e98d828e01614c58565b8194508093505050509295989b9194979a5092959850565b6000828483379101908152919050565b7035b2bc9737bbb730b136329737bbb732b960791b815260110190565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b039290921682526001600160801b0316602082015260400190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b848152602081018490526040810183905260808101600483106151cb57fe5b82606083015295945050505050565b6000602080835283518082850152825b81811015615206578581018301518582016040015282016151ea565b818111156152175783604083870101525b50601f01601f1916929092016040019392505050565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526022908201527f4f776e61626c653a3a7365744f776e65723a20696e76616c6964206164647265604082015261737360f01b606082015260800190565b6020808252601e908201527f4163636f756e742063616e6e6f74206265207a65726f20616464726573730000604082015260600190565b6020808252602a908201527f54726f76654d616e616765723a204f6e6c79206f6e652074726f766520696e206040820152697468652073797374656d60b01b606082015260800190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252603b908201527f54726f76654d616e616765723a2043616c6c6572206973206e6f74207468652060408201527f426f72726f7765724f7065726174696f6e7320636f6e74726163740000000000606082015260800190565b60208082526017908201527613dddb98589b194e8e881858d8d95cdcc819195b9a5959604a1b604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2043616c6c646174612061646472657373206160408201527572726179206d757374206e6f7420626520656d70747960501b606082015260800190565b6020808252818101527f4163636f756e7420636f64652073697a652063616e6e6f74206265207a65726f604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2046656520776f756c642065617420757020616040820152751b1b081c995d1d5c9b99590818dbdb1b185d195c985b60521b606082015260800190565b60208082526022908201527f54726f76654d616e616765723a206e6f7468696e6720746f206c697175696461604082015261746560f01b606082015260800190565b6020808252602f908201527f54726f76654d616e616765723a2054726f766520646f6573206e6f742065786960408201526e1cdd081bdc881a5cc818db1bdcd959608a1b606082015260800190565b90815260200190565b918252602082015260400190565b838152602081018390526060810161559683615671565b6040830152949350505050565b858152602081018590526040810184905260a08101600584106155c257fe5b60608201939093526001600160801b03919091166080909101529392505050565b84815260208101849052604081018390526080810161560183615671565b606083015295945050505050565b93845260208401929092526040830152606082015260800190565b60405181810167ffffffffffffffff8111828210171561564957600080fd5b604052919050565b600067ffffffffffffffff821115615667578081fd5b5060209081020190565b80600481106110cb57fe5b6001600160a01b0381168114610fdd57600080fdfeea67486ed7ebe3eea8ab3390efd4a3c8aae48be5bea27df104a8af786c408434c3770d654ed33aeea6bf11ac8ef05d02a6a04ed4686dd2f624d853bbec43cc8ba2646970667358221220a4c2a9a9ec603c1ba14143d02dd4e890d8f91d610315ce69bf8078c0892e8c2464736f6c634300060b0033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106104c25760003560e01c8063756b253e11610278578063b0d8e1811161015c578063d293c710116100ce578063d815e8e911610092578063d815e8e91461095a578063d9a7244414610962578063e056e91814610975578063e2ac77b014610988578063f36b24251461099b578063fe2ba848146109a3576104c2565b8063d293c71014610906578063d380a37c14610919578063d3d6f84314610921578063d5b3563514610934578063d66a255314610947576104c2565b8063be4b033411610120578063be4b0334146108cb578063bf9befb1146108d3578063c35bc550146108db578063c52861f2146108e3578063c7b55481146108eb578063cbd138ae146108f3576104c2565b8063b0d8e18114610867578063b7f8cf9b1461087a578063b82f263d14610882578063b91af97c14610895578063bcd37526146108b8576104c2565b8063887105d3116101f55780639dd233d2116101b95780639dd233d2146108325780639f0706701461083a578063a20baee614610773578063a3f4df7e14610842578063ae7bec1914610857578063ae9187541461085f576104c2565b8063887105d3146107f4578063893d20e8146107fc57806396d711ff146108045780639708daf41461080c5780639976cf451461081f576104c2565b80637cf54e401161023c5780637cf54e40146107b65780637f7dde4a146107be578063807d138d146107c657806382fe3eb9146107ce57806387436936146107e1576104c2565b8063756b253e14610783578063759b303414610796578063794e57241461079e578063795d26c3146107a6578063797250e3146107ae576104c2565b80633cc74225116103aa57806361ec893d1161031c5780636b444952116102e05780636b4449521461072c5780636ef6433814610734578063716c47e61461075857806372423c171461076057806372fe25aa14610773578063741bef1a1461077b576104c2565b806361ec893d146106e3578063631203b0146106eb57806364cee260146106fe578063653d46e71461071157806366ca4a2114610724576104c2565b80634a767d681161036e5780634a767d681461067a5780634e443d9e1461068d5780635733d58f146106ad5780635d6b480f146106b55780635d8c9609146106c85780635dba4c4a146106db576104c2565b80633cc742251461063157806342ccf1e414610639578063477d66cf1461064c578063480cd5781461065f57806349eefeee14610672576104c2565b806317c62b17116104435780631f68f20a116104075780631f68f20a146105eb57806321e37801146105f35780632b11551a146106065780632f8655681461060e57806331c903b0146106215780633a12859514610629576104c2565b806317c62b171461059757806318f2817a146105aa5780631a59a50e146105bd5780631bf43555146105d05780631e8b1c2b146105d8576104c2565b806312261ee71161048a57806312261ee71461053557806312610e921461053d57806313af40351461055057806315d549f1146105635780631673c79a14610576576104c2565b806301f16e18146104c757806305b6f5ca146104dc578063071a7541146104ef5780630b0765571461050d5780630d43e8ad14610520575b600080fd5b6104da6104d5366004614d96565b6109b6565b005b6104da6104ea366004614f61565b610f1c565b6104f7610fb3565b6040516105049190615568565b60405180910390f35b6104da61051b366004614c9f565b610fb8565b610528610fe0565b604051610504919061512e565b610528610fef565b6104f761054b366004614cd7565b611013565b6104da61055e366004614c9f565b61106b565b6104f7610571366004614c9f565b6110ac565b610589610584366004614c9f565b6110d0565b604051610504929190615571565b6104f76105a5366004614c9f565b6110e9565b6104f76105b8366004614c9f565b6110f4565b6104f76105cb366004614c9f565b611107565b6104f76111cd565b6104da6105e6366004614d02565b6111da565b6104f761153b565b6104f7610601366004614c9f565b611541565b6104f761156b565b6104da61061c366004614c9f565b61157a565b6104f76115e0565b6105286115ed565b6105286115fc565b6104f7610647366004614c9f565b61160b565b6104f761065a366004614ec4565b61163c565b6104f761066d366004614c9f565b61164f565b6104f761166d565b6104f7610688366004614cd7565b611673565b6106a061069b366004614ec4565b611692565b60405161050491906151a1565b6104f761169d565b6104da6106c3366004614cd7565b61171a565b6104f76106d6366004614c9f565b611766565b6104da611771565b6104f76117dc565b6104f76106f9366004614ec4565b6117e1565b6104f761070c366004614c9f565b6117ee565b6104da61071f366004614ec4565b61180c565b6104f7611b96565b6104f7611ba8565b610747610742366004614c9f565b611bae565b6040516105049594939291906155a3565b610528611be8565b6104f761076e366004614cd7565b611bf7565b6104f7611c54565b610528611c60565b610528610791366004614ec4565b611c6f565b6104f7611c96565b6104f7611ca3565b6104f7611ce8565b6104f7611e07565b610528611e0d565b610528611e1c565b6104f7611e2b565b6104da6107dc366004614c9f565b611e31565b6104da6107ef366004614c9f565b611e42565b6104f7611ece565b610528611f9d565b6104f7611fbc565b6104da61081a366004614feb565b611fc2565b6104f761082d366004614cd7565b61205b565b6104f761208e565b610528612094565b61084a6120a3565b60405161050491906151da565b6105286120cb565b6105286120da565b6104f7610875366004614c9f565b6120e9565b610528612110565b6104f7610890366004614ec4565b61211f565b6108a86108a3366004614c9f565b61212a565b604051610504949392919061560f565b6104da6108c6366004614ef4565b61218e565b6104f7612224565b6104f761222a565b6104f7612230565b6104f7612254565b6104f7612266565b6104da610901366004614c9f565b612272565b6104f7610914366004614cd7565b612285565b6104f7612298565b6104f761092f366004614cd7565b61229e565b6104f7610942366004614ec4565b6122d4565b6104f7610955366004614c9f565b6122e7565b610528612302565b610528610970366004614ec4565b612311565b6106a0610983366004614c9f565b61233b565b6106a0610996366004614c9f565b612399565b6104f76123a4565b6104da6109b1366004614c9f565b6123b1565b6109be611f9d565b6001600160a01b0316336001600160a01b0316146109f75760405162461bcd60e51b81526004016109ee906153c5565b60405180910390fd5b8051610a02906123c2565b610a0f81602001516123c2565b610a1c81604001516123c2565b610a2981606001516123c2565b610a3681608001516123c2565b610a438160a001516123c2565b610a508160c001516123c2565b610a5d8160e001516123c2565b610a6b8161010001516123c2565b610a798161012001516123c2565b610a878161014001516123c2565b610a958161016001516123c2565b610aa38161018001516123c2565b610ab1816101a001516123c2565b8051600c80546001600160a01b03199081166001600160a01b038085169190911790925560208401516004805483169184169190911790556040808501516003805484169185169190911790556060850151600580548416918516919091179055608085015160008054841691851691909117905560a085015160018054841691851691909117905560c085015160068054841691851691909117905560e0850151600780548416918516919091179055610100850151600880548416918516919091179055610120850151600280548416918516919091179055610140850151600980548416918516919091179055610160850151600d80548416918516919091179055610180850151600a805484169185169190911790556101a0850151600b80549093169316929092179055517f84861842e6a98dd759145eb5e7b48279d0f02ebeabc8a69e5196b2f4c5023db991610c0c9161512e565b60405180910390a17f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b18160200151604051610c47919061512e565b60405180910390a17fbf65195e6d5213f6fcbce65b1454c925197a45e616dabd2e243542b039b050928160600151604051610c82919061512e565b60405180910390a17f3ca631ffcd2a9b5d9ae18543fc82f58eb4ca33af9e6ab01b7a8e95331e6ed9858160600151604051610cbd919061512e565b60405180910390a17f78f058b189175430c48dc02699e3a0031ea4ff781536dc2fab847de4babdd8828160800151604051610cf8919061512e565b60405180910390a17f5ee0cae2f063ed938bb55046f6a932fb6ae792bf43624806bb90abe68a50be9b8160a00151604051610d33919061512e565b60405180910390a17f82966d27eea39b038ee0fa30cd16532bb24f6e65d31cb58fb227aa5766cdcc7f8160c00151604051610d6e919061512e565b60405180910390a17fcfb07d791fcafc032b35837b50eb84b74df518cf4cc287e8084f47630fa70fa08160e00151604051610da9919061512e565b60405180910390a17fe67f36a6e961157d6eff83b91f3af5a62131ceb6f04954ef74f51c1c05e7f88d816101000151604051610de5919061512e565b60405180910390a17f8c537274438aa850a330284665d81a85dd38267d09e4050d416bfc94142db264816101200151604051610e21919061512e565b60405180910390a17fcdf33850c44a1a874b5fefb51dae615ac6afebd581ef90d571f81b06541d8e9d816101400151604051610e5d919061512e565b60405180910390a17f65f4cf077bc01e4742eb5ad98326f6e95b63548ea24b17f8d5e823111fe78800816101600151604051610e99919061512e565b60405180910390a17f61e0c29d5028a9e4facaa476a46e78912e99f1ba945c9560b86b82ebe36ee52d816101800151604051610ed5919061512e565b60405180910390a17f7624075ed781e3aa34b0f492d70d1bb5a6fcde04534165e20f990d2e546be4dd816101a00151604051610f11919061512e565b60405180910390a150565b6004546040516000916060916001600160a01b0390911690610f419084903690615101565b600060405180830381855af49150503d8060008114610f7c576040519150601f19603f3d011682016040523d82523d6000602084013e610f81565b606091505b5091509150818190610fa65760405162461bcd60e51b81526004016109ee91906151da565b5050505050505050505050565b600281565b610fc0612407565b600054600154610fdd916001600160a01b03908116911683612433565b50565b600c546001600160a01b031681565b7f000000000000000000000000000000000000000000000000000000000000000081565b600061101d612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff61254516565b6001600160a01b03851660009081526010602052604090208190559150505b92915050565b611073611f9d565b6001600160a01b0316336001600160a01b0316146110a35760405162461bcd60e51b81526004016109ee906153c5565b610fdd81612587565b60006110b6612407565b6110bf82612612565b6001600160801b031690505b919050565b6016602052600090815260409020805460019091015482565b60006110658261160b565b60006110fe612407565b611065826126ac565b6001600160a01b0381166000908152601660205260408120546014548290611135908363ffffffff61254516565b905080158061116e575060016001600160a01b03851660009081526010602052604090206003015460ff16600481111561116b57fe5b14155b1561117e576000925050506110cb565b6001600160a01b038416600090815260106020526040812060020154906111c3670de0b6b3a76400006111b7848663ffffffff61275f16565b9063ffffffff61279916565b9695505050505050565b6809c2007651b250000081565b80516111f85760405162461bcd60e51b81526004016109ee906153f6565b6000546001546006546001600160a01b0392831692918216911661121a614b2b565b611222614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561127257600080fd5b505af1158015611286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112aa9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b1580156112ec57600080fd5b505afa158015611300573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113249190614edc565b60208301528151611334906127db565b158015604084015261135c576113558585846000015185602001518a612876565b9050611374565b6113718585846000015185602001518a612b8b565b90505b60008160200151116113985760405162461bcd60e51b81526004016109ee906154d7565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad926113cc92600401615571565b600060405180830381600087803b1580156113e657600080fd5b505af11580156113fa573d6000803e3d6000fd5b5050505061141285858360c001518460e00151612ccb565b61010081015115611489576008546101008201516040516364a197f360e01b81526001600160a01b03888116936364a197f393611456939290911691600401615188565b600060405180830381600087803b15801561147057600080fd5b505af1158015611484573d6000803e3d6000fd5b505050505b611497858260400151612f2f565b60208101516060830152610100810151604082015182516114cf92916114c3919063ffffffff61254516565b9063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda940394611517949092909161560f565b60405180910390a1611533853383606001518460400151613090565b505050505050565b600e5481565b6001600160a01b03811660009081526010602052604081206003015460ff16600481111561106557fe5b60006115756115e0565b905090565b6115838161316d565b6040805160018082528183019092526060916020808301908036833701905050905081816000815181106115b357fe5b60200260200101906001600160a01b031690816001600160a01b0316815250506115dc816111da565b5050565b6000611575600e546131b6565b600b546001600160a01b031681565b6001546001600160a01b031681565b6001600160a01b0381166000908152601660205260408120600101546015548290611135908363ffffffff61254516565b6000611065611649611b96565b83613253565b6001600160a01b031660009081526010602052604090206001015490565b60175490565b600080600061168185613271565b9150915060006111c38383876132f7565b6000611065826127db565b60035460408051635733d58f60e01b815290516000926001600160a01b031691635733d58f916004808301926020929190829003018186803b1580156116e257600080fd5b505afa1580156116f6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115759190614edc565b611722612407565b80600481111561172e57fe5b6001600160a01b0383166000908152601060205260409020600301805460ff1916600183600481111561175d57fe5b02179055505050565b600061106582611107565b611779612407565b6000611783613329565b9050670de0b6b3a764000081111561179757fe5b600e8190556040517fc454ee9b76c52f782a256af821b857ca6e125d1e3333bcede402fec2bed9600c906117cc908390615568565b60405180910390a1610fdd61336d565b603c81565b60006110656116496123a4565b6001600160a01b031660009081526010602052604090206002015490565b611814614ba8565b506040805160e081018252600080546001600160a01b0390811683526001548116602084015292820181905260608201819052600d548316608083015260a0820181905260c082015260065490911661186b614b2b565b611873614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b1580156118c357600080fd5b505af11580156118d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118fb9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b15801561193d57600080fd5b505afa158015611951573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119759190614edc565b60208301528151611985906127db565b15801560408401526119ac576119a58483600001518460200151886133c2565b90506119cc565b6119c9846000015185602001518460000151856020015189613869565b90505b60008160200151116119f05760405162461bcd60e51b81526004016109ee906154d7565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad92611a2492600401615571565b600060405180830381600087803b158015611a3e57600080fd5b505af1158015611a52573d6000803e3d6000fd5b50505050611a72846000015185602001518360c001518460e00151612ccb565b61010081015115611ae95783516008546101008301516040516364a197f360e01b81526001600160a01b03938416936364a197f393611ab693911691600401615188565b600060405180830381600087803b158015611ad057600080fd5b505af1158015611ae4573d6000803e3d6000fd5b505050505b611afb84600001518260400151612f2f565b6020810151606083015261010081015160408201518251611b2792916114c3919063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda940394611b6f949092909161560f565b60405180910390a1611b8f84600001513383606001518460400151613090565b5050505050565b6000611575611ba3613329565b613a1f565b60195481565b60106020526000908152604090208054600182015460028301546003909301549192909160ff81169061010090046001600160801b031685565b6004546001600160a01b031681565b6000611c01612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff613b0116565b6001600160a01b038516600090815260106020526040902060010181905591505092915050565b670de0b6b3a764000081565b6002546001600160a01b031681565b60178181548110611c7c57fe5b6000918252602090912001546001600160a01b0316905081565b6801158e460913d0000081565b60035460408051631e5395c960e21b815290516000926001600160a01b03169163794e5724916004808301926020929190829003018186803b1580156116e257600080fd5b60008054604080516272c7d360e71b8152905183926001600160a01b031691633963e980916004808301926020929190829003018186803b158015611d2c57600080fd5b505afa158015611d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d649190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b0316633963e9806040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b505afa158015611dca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dee9190614edc565b9050611e00828263ffffffff613b0116565b9250505090565b60185481565b600a546001600160a01b031681565b6000546001600160a01b031681565b60125481565b611e39612407565b610fdd81613b26565b611e4a611f9d565b6001600160a01b0316336001600160a01b031614611e7a5760405162461bcd60e51b81526004016109ee906153c5565b611e83816123c2565b600480546001600160a01b0319166001600160a01b0383161790556040517f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b190610f1190839061512e565b6000805460408051630a7b61df60e11b8152905183926001600160a01b0316916314f6c3be916004808301926020929190829003018186803b158015611f1357600080fd5b505afa158015611f27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f4b9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b600080604051611fac90615111565b6040519081900390205492915050565b60135481565b6004546040516000916060916001600160a01b0390911690611fe79084903690615101565b600060405180830381855af49150503d8060008114612022576040519150601f19603f3d011682016040523d82523d6000602084013e612027565b606091505b509150915081819061204c5760405162461bcd60e51b81526004016109ee91906151da565b50505050505050505050505050565b6000612065612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff613b0116565b60145481565b6003546001600160a01b031681565b6040518060400160405280600c81526020016b2a3937bb32a6b0b730b3b2b960a11b81525081565b6006546001600160a01b031681565b600d546001600160a01b031681565b60008060006120f784613271565b9150915060006121078383613b83565b95945050505050565b6005546001600160a01b031681565b600061106582613bb8565b6001600160a01b03811660009081526010602052604081208054600190910154909180612156856110e9565b915061216185611766565b9050612173848363ffffffff613b0116565b9350612185838263ffffffff613b0116565b92509193509193565b6004546040516000916060916001600160a01b03909116906121b39084903690615101565b600060405180830381855af49150503d80600081146121ee576040519150601f19603f3d011682016040523d82523d6000602084013e6121f3565b606091505b50915091508181906122185760405162461bcd60e51b81526004016109ee91906151da565b50505050505050505050565b60155481565b60115481565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000611575612261613329565b6131b6565b670ddd4b8c6c7d70d881565b61227a612407565b610fdd816002613be4565b60006122918383611673565b9392505050565b600f5481565b60006122a8612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff61254516565b60006110656122e1612254565b83613cf9565b6001600160a01b031660009081526010602052604090205490565b6009546001600160a01b031681565b60006017828154811061232057fe5b6000918252602090912001546001600160a01b031692915050565b600060016001600160a01b03831660009081526010602052604090206003015460ff16600481111561236957fe5b14612376575060006110cb565b506014546001600160a01b03821660009081526016602052604090205410919050565b60006110658261233b565b6000611575600e54613a1f565b6123b9612407565b610fdd81613d39565b6001600160a01b0381166123e85760405162461bcd60e51b81526004016109ee906152a6565b803b806115dc5760405162461bcd60e51b81526004016109ee9061544c565b6005546001600160a01b031633146124315760405162461bcd60e51b81526004016109ee90615368565b565b61243c8161233b565b156125405761244a8161316d565b600061245582611107565b905060006124628361160b565b6001600160a01b038416600090815260106020526040902060010154909150612491908363ffffffff613b0116565b6001600160a01b03841660009081526010602052604090206001810191909155546124c2908263ffffffff613b0116565b6001600160a01b0384166000908152601060205260409020556124e483613b26565b6124f085858385613d89565b6001600160a01b0383166000818152601060205260408082208054600182015460029092015492516000805160206156b28339815191529461253594929392916155e3565b60405180910390a250505b505050565b600061229183836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613e71565b6001600160a01b0381166125ad5760405162461bcd60e51b81526004016109ee90615264565b806001600160a01b03166125bf611f9d565b6001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3600060405161260290615111565b6040519081900390209190915550565b601780546001808201835560008381527fc624b66cc0138b8fabc209247f72d758e1cf3343756d543badbf24212bed8c1590920180546001600160a01b0319166001600160a01b0386161790559154909161266d9190612545565b6001600160a01b039290921660009081526010602052604090206003018054610100600160881b0319166101006001600160801b038516021790555090565b6001600160a01b03811660009081526010602052604081206001015481906126d390613e9d565b6001600160a01b03841660009081526010602052604090206002018054908290556011549192509061271d908390612711908463ffffffff61254516565b9063ffffffff613b0116565b60118190556040517f6bac5e0eb3c44eb03a60ab11ec3a2c051771616aecadbcfff2630aabae5203829161275091615568565b60405180910390a15092915050565b60008261276e57506000611065565b8282028284828161277b57fe5b04146122915760405162461bcd60e51b81526004016109ee90615327565b600061229183836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613ed9565b6000806127e783613bb8565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561283757600080fd5b505afa15801561284b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061286f9190614edc565b1192915050565b61287e614b5c565b612886614be4565b61288e614b5c565b848252600060808301526128a0611ce8565b60a08301526128ad611ece565b60c0830152600060208301525b835182602001511015612b8057838260200151815181106128d757fe5b6020908102919091018101516001600160a01b03166060840181905260009081526010909152604090206003015460019060ff16600481111561291657fe5b1461292057612b70565b61292e826060015187611673565b60408301526080820151612a9557600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561298a57600080fd5b505afa15801561299e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129c29190614edc565b8260400151101580156129d457508151155b156129de57612b70565b60006129f38360c001518460a00151896132f7565b9050612a108989856060015186604001518760000151868d613f10565b60808101518451919350612a2a919063ffffffff61254516565b8352608082015160a0840151612a459163ffffffff61254516565b60a08085019190915282015160c0840151612a659163ffffffff61254516565b60c0840152612a7484836142e6565b9350612a898360c001518460a00151896143fd565b15608084015250612b70565b81608001518015612b2e5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b158015612aef57600080fd5b505afa158015612b03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b279190614edc565b8260400151105b15612b7057612b4788888460600151856000015161449c565b60808101518351919250612b61919063ffffffff61254516565b8252612b6d83826142e6565b92505b60208201805160010190526128ba565b505095945050505050565b612b93614b5c565b612b9b614be4565b612ba3614b5c565b848252600060208301525b835182602001511015612b805783826020015181518110612bcb57fe5b60209081029190910101516001600160a01b031660608301819052612bf09087611673565b6040808401919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b158015612c3b57600080fd5b505afa158015612c4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c739190614edc565b82604001511015612cbb57612c9288888460600151856000015161449c565b60808101518351919250612cac919063ffffffff61254516565b8252612cb883826142e6565b92505b6020820180516001019052612bae565b81612cd557612f29565b601854600090612cf79061271184670de0b6b3a764000063ffffffff61275f16565b90506000612d1c601954612711670de0b6b3a76400008761275f90919063ffffffff16565b90506000612d356011548461279990919063ffffffff16565b90506000612d4e6011548461279990919063ffffffff16565b9050612d75612d686011548461275f90919063ffffffff16565b859063ffffffff61254516565b601855601154612d9d90612d9090839063ffffffff61275f16565b849063ffffffff61254516565b601955601454612db3908363ffffffff613b0116565b601455601554612dc9908263ffffffff613b0116565b60158190556014546040517f9f8bc8ab0daf5bceef75ecfd2085d1fcc6548c657ea970d9a23a60610d0737e392612e009291615571565b60405180910390a160405163121cbc4d60e11b81526001600160a01b03891690632439789a90612e34908990600401615568565b600060405180830381600087803b158015612e4e57600080fd5b505af1158015612e62573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038a16925063f2e91d719150612e92908990600401615568565b600060405180830381600087803b158015612eac57600080fd5b505af1158015612ec0573d6000803e3d6000fd5b50506040516364a197f360e01b81526001600160a01b038b1692506364a197f39150612ef2908a908990600401615188565b600060405180830381600087803b158015612f0c57600080fd5b505af1158015612f20573d6000803e3d6000fd5b50505050505050505b50505050565b6011546012819055506000826001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612f7357600080fd5b505afa158015612f87573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fab9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612ffd57600080fd5b505afa158015613011573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130359190614edc565b905061304b81612711848663ffffffff61254516565b60138190556012546040517f51bf4c63ec3cba9d03d43238abbdd979dd91bd16d9895c74ceea9118c7baaf60926130829291615571565b60405180910390a150505050565b811561310157600954600754604051631062c15f60e11b81526001600160a01b03928316926320c582be926130ce9291169087908790600401615142565b600060405180830381600087803b1580156130e857600080fd5b505af11580156130fc573d6000803e3d6000fd5b505050505b8015612f29576040516364a197f360e01b81526001600160a01b038516906364a197f3906131359086908590600401615188565b600060405180830381600087803b15801561314f57600080fd5b505af1158015613163573d6000803e3d6000fd5b5050505050505050565b60016001600160a01b03821660009081526010602052604090206003015460ff16600481111561319957fe5b14610fdd5760405162461bcd60e51b81526004016109ee90615519565b600061106561324583600360009054906101000a90046001600160a01b03166001600160a01b03166328d28b5b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b505afa158015613221573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127119190614edc565b670de0b6b3a76400006145ce565b6000612291670de0b6b3a76400006111b7858563ffffffff61275f16565b600080600061327f84611107565b9050600061328c8561160b565b6001600160a01b038616600090815260106020526040812060010154919250906132bc908463ffffffff613b0116565b6001600160a01b038716600090815260106020526040812054919250906132e9908463ffffffff613b0116565b919550909350505050915091565b6000821561331e576000613315846111b7878663ffffffff61275f16565b91506122919050565b506000199392505050565b6000806133346145e4565b9050600061334a670ddd4b8c6c7d70d883614600565b9050611e00670de0b6b3a76400006111b783600e5461275f90919063ffffffff16565b6000613384600f544261254590919063ffffffff16565b9050603c8110610fdd5742600f8190556040517f860f8d2f0c74dd487e89e2883e3b25b8159ce1e1b3433a291cba7b82c508f3bc91610f1191615568565b6133ca614b5c565b6133d2614be4565b6133da614b5c565b848252600060808301526133ec611ce8565b60a08301526133f9611ece565b8260c001818152505086608001516001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b15801561343f57600080fd5b505afa158015613453573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134779190614cbb565b82606001906001600160a01b031690816001600160a01b031681525050600087608001516001600160a01b0316631e2231436040518163ffffffff1660e01b815260040160206040518083038186803b1580156134d357600080fd5b505afa1580156134e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061350b9190614cbb565b6000602085015290505b84836020015110801561353e5750806001600160a01b031683606001516001600160a01b031614155b1561385e5760808801516060840151604051632dc9c0eb60e21b81526000926001600160a01b03169163b72703ac9161357a919060040161512e565b60206040518083038186803b15801561359257600080fd5b505afa1580156135a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135ca9190614cbb565b90506135da846060015189611673565b6040850152608084015161375357600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561363657600080fd5b505afa15801561364a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061366e9190614edc565b84604001511015801561368057508351155b1561368b575061385e565b60006136a08560c001518660a001518b6132f7565b8a5160208c01516060880151604089015189519495506136c194868f613f10565b608081015186519195506136db919063ffffffff61254516565b8552608084015160a08601516136f69163ffffffff61254516565b8560a00181815250506137238461010001516114c38660a001518860c0015161254590919063ffffffff16565b60c086015261373286856142e6565b95506137478560c001518660a001518b6143fd565b15608086015250613840565b836080015180156137ec5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156137ad57600080fd5b505afa1580156137c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137e59190614edc565b8460400151105b1561383a5761380d89600001518a602001518660600151876000015161449c565b60808101518551919450613827919063ffffffff61254516565b845261383385846142e6565b9450613840565b5061385e565b6001600160a01b031660608401526020830180516001019052613515565b505050949350505050565b613871614b5c565b613879614be4565b613881614b5c565b600d54858352600060208401526001600160a01b03165b8483602001511015613a1357806001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b1580156138dd57600080fd5b505afa1580156138f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139159190614cbb565b6001600160a01b03166060840181905261392f9088611673565b6040808501919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b15801561397a57600080fd5b505afa15801561398e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139b29190614edc565b836040015110156139fe576139d189898560600151866000015161449c565b608081015184519193506139eb919063ffffffff61254516565b83526139f784836142e6565b9350613a03565b613a13565b6020830180516001019052613898565b50505095945050505050565b6000611065613a7683600360009054906101000a90046001600160a01b03166001600160a01b031663f92d34336040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b600360009054906101000a90046001600160a01b03166001600160a01b031663240926696040518163ffffffff1660e01b815260040160206040518083038186803b158015613ac457600080fd5b505afa158015613ad8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613afc9190614edc565b6145ce565b6000828201838110156122915760405162461bcd60e51b81526004016109ee9061522d565b601480546001600160a01b038316600090815260166020526040908190209182556015546001909201829055915491517fc437f324d85e369394148dd9d62f98f534b382e01ed3dd2eb98138fb6d3ab49a92610f11929091615571565b60008115613baf57613ba8826111b78568056bc75e2d6310000063ffffffff61275f16565b9050611065565b50600019611065565b600080613bc3611ece565b90506000613bcf611ce8565b9050613bdc8282866132f7565b949350505050565b6000816004811115613bf257fe5b14158015613c0c57506001816004811115613c0957fe5b14155b613c1257fe5b601754613c1e816146ab565b6001600160a01b0383166000908152601060205260409020600301805483919060ff19166001836004811115613c5057fe5b02179055506001600160a01b0383166000908152601060209081526040808320600180820185905590849055601690925282208281550155613c928382614751565b600d54604051631484968760e11b81526001600160a01b03909116906329092d0e90613cc290869060040161512e565b600060405180830381600087803b158015613cdc57600080fd5b505af1158015613cf0573d6000803e3d6000fd5b50505050505050565b600080613d18670de0b6b3a76400006111b7868663ffffffff61275f16565b90508281106122915760405162461bcd60e51b81526004016109ee90615481565b6001600160a01b038116600090815260106020526040902060020154601154613d68908263ffffffff61254516565b601155506001600160a01b0316600090815260106020526040812060020155565b60405163121cbc4d60e11b81526001600160a01b03841690632439789a90613db5908590600401615568565b600060405180830381600087803b158015613dcf57600080fd5b505af1158015613de3573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038716925063f2e91d719150613e13908590600401615568565b600060405180830381600087803b158015613e2d57600080fd5b505af1158015613e41573d6000803e3d6000fd5b505060405163be41205f60e01b81526001600160a01b038616925063be41205f9150613135908490600401615568565b60008184841115613e955760405162461bcd60e51b81526004016109ee91906151da565b505050900390565b60008060135460001415613eb2575081611065565b600060125411613ebe57fe5b6122916013546111b76012548661275f90919063ffffffff16565b60008183613efa5760405162461bcd60e51b81526004016109ee91906151da565b506000838581613f0657fe5b0495945050505050565b613f18614b5c565b613f20614c2c565b601754600110613f3057506142db565b613f398761212a565b60408501526020848101919091528401819052908352613f58906148f2565b604083018190526801158e460913d0000060608401526020830151613f829163ffffffff61254516565b8152670de0b6b3a7640000861161404d57613fa7898983602001518460400151613d89565b613fb087613d39565b60006080830181905260a0830152815160c0830152805160e0830152613fd7876003613be4565b815160208301516040516001600160a01b038a1692600080516020615692833981519152926140089260029061557f565b60405180910390a2866001600160a01b03166000805160206156b28339815191526000806000600260405161404094939291906151ac565b60405180910390a26142d9565b670de0b6b3a7640000861180156140e85750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156140ad57600080fd5b505afa1580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614edc565b86105b1561413857614101898983602001518460400151613d89565b61410a87613d39565b8151815161411991908761497e565b60e086015260c085015260a08401526080830152613fd7876003613be4565b600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561418657600080fd5b505afa15801561419a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141be9190614edc565b86101580156141cc57508386105b80156141d9575081518510155b156142c8576141f2898983602001518460400151613d89565b846141f957fe5b61420287613d39565b61421582600001518360200151856149ea565b9150614222876003613be4565b6101008201511561429757600854610100830151604051633f10abab60e01b81526001600160a01b0390921691633f10abab91614264918b9190600401615188565b600060405180830381600087803b15801561427e57600080fd5b505af1158015614292573d6000803e3d6000fd5b505050505b815160a08301516040516001600160a01b038a1692600080516020615692833981519152926140089260029061557f565b6142d0614b5c565b91506142db9050565b505b979650505050505050565b6142ee614b5c565b604080830151908401516143079163ffffffff613b0116565b6040820152606080830151908401516143259163ffffffff613b0116565b6060820152815160208401516143409163ffffffff613b0116565b602080830191909152820151835161435d9163ffffffff613b0116565b8152608080830151908401516143789163ffffffff613b0116565b608082015260a080830151908401516143969163ffffffff613b0116565b60a082015260c080830151908401516143b49163ffffffff613b0116565b60c082015260e080830151908401516143d29163ffffffff613b0116565b60e082015261010080830151908401516143f19163ffffffff613b0116565b61010082015292915050565b60008061440b8585856132f7565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561445b57600080fd5b505afa15801561446f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144939190614edc565b11949350505050565b6144a4614b5c565b6144ac614c2c565b6144b58461212a565b604085019081526020858101928352860192909252918452905190516144df918891889190613d89565b6144e884613d39565b6144f582602001516148f2565b604083018190526801158e460913d0000060608401526020830151600091614523919063ffffffff61254516565b90506145348360000151828661497e565b60e087015260c086015260a08501526080840152614553856003613be4565b825160208401516040516001600160a01b03881692600080516020615692833981519152926145849260019061557f565b60405180910390a2846001600160a01b03166000805160206156b2833981519152600080600060016040516145bc94939291906151ac565b60405180910390a25050949350505050565b60008183106145dd5781612291565b5090919050565b6000611575603c6111b7600f544261254590919063ffffffff16565b6000631f54050082111561461657631f54050091505b8161462a5750670de0b6b3a7640000611065565b670de0b6b3a764000083835b60018111156146a1576002810661466b576146518283614af8565b915061466481600263ffffffff61279916565b905061469c565b6146758284614af8565b92506146818283614af8565b915061469960026111b783600163ffffffff61254516565b90505b614636565b6111c38284614af8565b6001811180156147355750600d546040805163de8fa43160e01b815290516001926001600160a01b03169163de8fa431916004808301926020929190829003018186803b1580156146fb57600080fd5b505afa15801561470f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147339190614edc565b115b610fdd5760405162461bcd60e51b81526004016109ee906152dd565b6001600160a01b03821660009081526010602052604081206003015460ff169081600481111561477d57fe5b141580156147975750600181600481111561479457fe5b14155b61479d57fe5b6001600160a01b03831660009081526010602052604081206003015461010090046001600160801b03169083906147d5826001612545565b905080836001600160801b031611156147ea57fe5b6000601782815481106147f957fe5b600091825260209091200154601780546001600160a01b03909216925082916001600160801b03871690811061482b57fe5b600091825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918316815260109091526040908190206003018054610100600160881b0319166101006001600160801b03881602179055517f02b04ae5f7be9ca7c103293a2aa15f3c339d15d6eda53b721fef7b0e609c831a906148b49083908790615166565b60405180910390a160178054806148c757fe5b600082815260209020810160001990810180546001600160a01b031916905501905550505050505050565b600354604080516324386ecd60e11b815290516000926001600160a01b031691634870dd9a916004808301926020929190829003018186803b15801561493757600080fd5b505afa15801561494b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061496f9190614edc565b828161497757fe5b0492915050565b600080808084156149d45761499387866145ce565b93506149a9876111b7888763ffffffff61275f16565b92506149bb878563ffffffff61254516565b91506149cd868463ffffffff61254516565b90506149e1565b5060009250829150859050845b93509350935093565b6149f2614b5c565b838152602080820184905260035460408051631e5395c960e21b81529051600093614a8e9387936111b7936001600160a01b039092169263794e572492600480840193829003018186803b158015614a4957600080fd5b505afa158015614a5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614a819190614edc565b889063ffffffff61275f16565b9050614a99816148f2565b604083018190526801158e460913d00000606084015260808301869052614ac790829063ffffffff61254516565b60a0830152614adc848263ffffffff61254516565b61010083015250600060c0820181905260e08201529392505050565b600080614b0b848463ffffffff61275f16565b9050613bdc670de0b6b3a76400006111b7836706f05b59d3b20000613b01565b6040518060a00160405280600081526020016000815260200160001515815260200160008152602001600081525090565b6040518061012001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040518060e0016040528060008152602001600081526020016000815260200160006001600160a01b0316815260200160001515815260200160008152602001600081525090565b60405180606001604052806000815260200160008152602001600081525090565b80356110658161567c565b60008083601f840112614c69578081fd5b50813567ffffffffffffffff811115614c80578182fd5b602083019150836020828501011115614c9857600080fd5b9250929050565b600060208284031215614cb0578081fd5b81356122918161567c565b600060208284031215614ccc578081fd5b81516122918161567c565b60008060408385031215614ce9578081fd5b8235614cf48161567c565b946020939093013593505050565b60006020808385031215614d14578182fd5b823567ffffffffffffffff811115614d2a578283fd5b80840185601f820112614d3b578384fd5b80359150614d50614d4b83615651565b61562a565b8281528381019082850185850284018601891015614d6c578687fd5b8693505b848410156142d957614d828982614c4d565b835260019390930192918501918501614d70565b60006101c0808385031215614da9578182fd5b614db28161562a565b614dbc8585614c4d565b8152614dcb8560208601614c4d565b6020820152614ddd8560408601614c4d565b6040820152614def8560608601614c4d565b6060820152614e018560808601614c4d565b6080820152614e138560a08601614c4d565b60a0820152614e258560c08601614c4d565b60c0820152614e378560e08601614c4d565b60e08201526101009150614e4d85838601614c4d565b828201526101209150614e6285838601614c4d565b828201526101409150614e7785838601614c4d565b828201526101609150614e8c85838601614c4d565b828201526101809150614ea185838601614c4d565b828201526101a09150614eb685838601614c4d565b918101919091529392505050565b600060208284031215614ed5578081fd5b5035919050565b600060208284031215614eed578081fd5b5051919050565b600080600080600080600060e0888a031215614f0e578283fd5b873596506020880135614f208161567c565b95506040880135614f308161567c565b94506060880135614f408161567c565b9699959850939660808101359560a0820135955060c0909101359350915050565b600080600080600080600080888a03610160811215614f7e578182fd5b8935985060208a0135614f908161567c565b975060408a0135614fa08161567c565b965060608a0135614fb08161567c565b955060808a810135955060a08b0135945060c08b0135935060df1982011215614fd7578182fd5b5060e0890190509295985092959890939650565b6000806000806000806000806000808a8c0361018081121561500b578485fd5b8b359a5060208c013561501d8161567c565b995060408c013561502d8161567c565b985060608c013561503d8161567c565b975060808c810135975060a08d0135965060c08d0135955060df19820190811215615066578384fd5b615070606061562a565b9150604081121561507f578384fd5b5061508a604061562a565b60e08d01356150988161567c565b81526101008d01356020808301919091529082526101208d0135908201526101408c0135604082015292506101608b013567ffffffffffffffff8111156150dd578283fd5b6150e98d828e01614c58565b8194508093505050509295989b9194979a5092959850565b6000828483379101908152919050565b7035b2bc9737bbb730b136329737bbb732b960791b815260110190565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b039290921682526001600160801b0316602082015260400190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b848152602081018490526040810183905260808101600483106151cb57fe5b82606083015295945050505050565b6000602080835283518082850152825b81811015615206578581018301518582016040015282016151ea565b818111156152175783604083870101525b50601f01601f1916929092016040019392505050565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526022908201527f4f776e61626c653a3a7365744f776e65723a20696e76616c6964206164647265604082015261737360f01b606082015260800190565b6020808252601e908201527f4163636f756e742063616e6e6f74206265207a65726f20616464726573730000604082015260600190565b6020808252602a908201527f54726f76654d616e616765723a204f6e6c79206f6e652074726f766520696e206040820152697468652073797374656d60b01b606082015260800190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252603b908201527f54726f76654d616e616765723a2043616c6c6572206973206e6f74207468652060408201527f426f72726f7765724f7065726174696f6e7320636f6e74726163740000000000606082015260800190565b60208082526017908201527613dddb98589b194e8e881858d8d95cdcc819195b9a5959604a1b604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2043616c6c646174612061646472657373206160408201527572726179206d757374206e6f7420626520656d70747960501b606082015260800190565b6020808252818101527f4163636f756e7420636f64652073697a652063616e6e6f74206265207a65726f604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2046656520776f756c642065617420757020616040820152751b1b081c995d1d5c9b99590818dbdb1b185d195c985b60521b606082015260800190565b60208082526022908201527f54726f76654d616e616765723a206e6f7468696e6720746f206c697175696461604082015261746560f01b606082015260800190565b6020808252602f908201527f54726f76654d616e616765723a2054726f766520646f6573206e6f742065786960408201526e1cdd081bdc881a5cc818db1bdcd959608a1b606082015260800190565b90815260200190565b918252602082015260400190565b838152602081018390526060810161559683615671565b6040830152949350505050565b858152602081018590526040810184905260a08101600584106155c257fe5b60608201939093526001600160801b03919091166080909101529392505050565b84815260208101849052604081018390526080810161560183615671565b606083015295945050505050565b93845260208401929092526040830152606082015260800190565b60405181810167ffffffffffffffff8111828210171561564957600080fd5b604052919050565b600067ffffffffffffffff821115615667578081fd5b5060209081020190565b80600481106110cb57fe5b6001600160a01b0381168114610fdd57600080fdfeea67486ed7ebe3eea8ab3390efd4a3c8aae48be5bea27df104a8af786c408434c3770d654ed33aeea6bf11ac8ef05d02a6a04ed4686dd2f624d853bbec43cc8ba2646970667358221220a4c2a9a9ec603c1ba14143d02dd4e890d8f91d610315ce69bf8078c0892e8c2464736f6c634300060b0033", - "devdoc": { - "kind": "dev", - "methods": { - "applyPendingRewards(address)": { - "params": { - "_borrower": "borrower address" - } - }, - "closeTrove(address)": { - "params": { - "_borrower": "borrower address" - } - }, - "constructor": { - "params": { - "_bootstrapPeriod": "During bootsrap period redemptions are not allowed" - } - }, - "decreaseTroveColl(address,uint256)": { - "params": { - "_borrower": "borrower address", - "_collDecrease": "amount of collateral to decrease" - }, - "returns": { - "_0": "new trove collateral" - } - }, - "decreaseTroveDebt(address,uint256)": { - "params": { - "_borrower": "borrower address", - "_debtDecrease": "amount of debt to decrease" - }, - "returns": { - "_0": "new trove debt" - } - }, - "getBorrowingFeeWithDecay(uint256)": { - "params": { - "_ZUSDDebt": "ZUSD debt amount to calculate fee" + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, - "returns": { - "_0": "borrowing fee using borrowing rate with decay" - } - }, - "getBorrowingRate()": { - "returns": { - "_0": "borrowing rate" - } - }, - "getBorrowingRateWithDecay()": { - "returns": { - "_0": "borrowing rate calculated using decayed as base rate" - } - }, - "getCurrentICR(address,uint256)": { - "params": { - "_borrower": "borrower address", - "_price": "ETH price" - }, - "returns": { - "_0": "the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account." - } - }, - "getNominalICR(address)": { - "returns": { - "_0": "the nominal collateral ratio (ICR) of a given Trove, without the price. Takes a trove's pending coll and debt rewards from redistributions into account." - } - }, - "getOwner()": { - "returns": { - "_owner": "Address of the owner. " - } - }, - "getPendingETHReward(address)": { - "params": { - "_borrower": "borrower address" + { + "inputs": [ + { + "internalType": "address", + "name": "_troveManagerRedeemOps", + "type": "address" + } + ], + "name": "setTroveManagerRedeemOps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, - "returns": { - "_0": "the borrower's pending accumulated ETH reward, earned by their stake" - } - }, - "getPendingZUSDDebtReward(address)": { - "params": { - "_borrower": "borrower address" + { + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_num", + "type": "uint256" + } + ], + "name": "setTroveStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, - "returns": { - "_0": "the borrower's pending accumulated ZUSD reward, earned by their stake" - } - }, - "getRedemptionFeeWithDecay(uint256)": { - "params": { - "_ETHDrawn": "ETH drawn" - } - }, - "getRedemptionRate()": { - "returns": { - "_0": "calculated redemption rate using baseRate" - } - }, - "getRedemptionRateWithDecay()": { - "returns": { - "_0": "calculated redemption rate using calculated decayed as base rate" - } - }, - "getTCR(uint256)": { - "params": { - "_price": "ETH price" + { + "inputs": [], + "name": "sortedTroves", + "outputs": [ + { + "internalType": "contract ISortedTroves", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, - "returns": { - "_0": "the total collateralization ratio (TCR) of the system. The TCR is based on the the entire system debt and collateral (including pending rewards)." - } - }, - "getTroveColl(address)": { - "params": { - "_borrower": "borrower address" + { + "inputs": [], + "name": "totalCollateralSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, - "returns": { - "_0": "Trove collateral from given trove" - } - }, - "getTroveDebt(address)": { - "params": { - "_borrower": "borrower address" + { + "inputs": [], + "name": "totalStakes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, - "returns": { - "_0": "Trove debt from given trove" - } - }, - "getTroveFromTroveOwnersArray(uint256)": { - "params": { - "_index": "Trove owner index" + { + "inputs": [], + "name": "totalStakesSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, - "returns": { - "_0": "Trove from TroveOwners array in given index" - } - }, - "getTroveOwnersCount()": { - "returns": { - "_0": "Trove owners count" - } - }, - "getTroveStake(address)": { - "params": { - "_borrower": "borrower address" + { + "inputs": [], + "name": "troveManagerRedeemOps", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, - "returns": { - "_0": "Trove stake from given trove" - } - }, - "getTroveStatus(address)": { - "params": { - "_borrower": "borrower address" + { + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "updateStakeAndTotalStakes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" }, - "returns": { - "_0": "Trove status from given trove" - } - }, - "increaseTroveColl(address,uint256)": { - "params": { - "_borrower": "borrower address", - "_collIncrease": "amount of collateral to increase" - }, - "returns": { - "_0": "new trove collateral" - } - }, - "increaseTroveDebt(address,uint256)": { - "params": { - "_borrower": "borrower address", - "_debtIncrease": "amount of debt to increase" - }, - "returns": { - "_0": "new trove debt" - } - }, - "redeemCollateral(uint256,address,address,address,uint256,uint256,uint256)": { - "details": "this function forwards the call to the troveManagerRedeemOps in a delegate call fashion so the parameters are not needed" - }, - "redeemCollateralViaDLLR(uint256,address,address,address,uint256,uint256,uint256,(uint256,uint8,bytes32,bytes32))": { - "details": "this function forwards the call to the troveManagerRedeemOps in a delegate call fashion so the parameters are not needed DLLR _owner or _spender can use Sovryn Mynt to convert DLLR to ZUSD, then use the Zero redemption mechanism to redeem ZUSD for RBTC, all in a single transaction" - }, - "redeemCollateralViaDllrWithPermit2(uint256,address,address,address,uint256,uint256,uint256,((address,uint256),uint256,uint256),bytes)": { - "details": "this function forwards the call to the troveManagerRedeemOps in a delegate call fashion so the parameters are not needed DLLR _owner or _spender can use Sovryn Mynt to convert DLLR to ZUSD, then use the Zero redemption mechanism to redeem ZUSD for RBTC, all in a single transaction" - }, - "removeStake(address)": { - "params": { - "_borrower": "borrower address" - } - }, - "setOwner(address)": { - "params": { - "_owner": "Address of the owner. " - } - }, - "updateStakeAndTotalStakes(address)": { - "params": { - "_borrower": "borrower address" + { + "inputs": [ + { + "internalType": "address", + "name": "_borrower", + "type": "address" + } + ], + "name": "updateTroveRewardSnapshots", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } - } - }, - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": { - "BETA()": { - "notice": "BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption. Corresponds to (1 / ALPHA) in the white paper." - }, - "BOOTSTRAP_PERIOD()": { - "notice": "During bootsrap period redemptions are not allowed" - }, - "MIN_NET_DEBT()": { - "notice": "Minimum amount of net ZUSD debt a trove must have" - }, - "ZUSD_GAS_COMPENSATION()": { - "notice": "Amount of ZUSD to be locked in gas pool on opening troves" - }, - "_getCurrentICR(address,uint256)": { - "notice": "Return the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account." - }, - "_getPendingETHReward(address)": { - "notice": "Get the borrower's pending accumulated ETH reward, earned by their stake" - }, - "_getPendingZUSDDebtReward(address)": { - "notice": "Get the borrower's pending accumulated ZUSD reward, earned by their stake" - }, - "addTroveOwnerToArray(address)": { - "notice": "Push the owner's address to the Trove owners list, and record the corresponding array index on the Trove struct" - }, - "applyPendingRewards(address)": { - "notice": "Add the borrowers's coll and debt rewards earned from redistributions, to their Trove" - }, - "batchLiquidateTroves(address[])": { - "notice": "Attempt to liquidate a custom list of troves provided by the caller." - }, - "checkRecoveryMode(uint256)": { - "notice": "reveals whether or not the system is in Recovery Mode (i.e. whether the Total Collateralization Ratio (TCR) is below the Critical Collateralization Ratio (CCR))." - }, - "closeTrove(address)": { - "notice": "Close given trove. Called by BorrowerOperations." - }, - "decayBaseRateFromBorrowing()": { - "notice": "Updates the baseRate state variable based on time elapsed since the last redemption or ZUSD borrowing operation." - }, - "getCurrentICR(address,uint256)": { - "notice": "computes the user’s individual collateralization ratio (ICR) based on their total collateral and total ZUSD debt. Returns 2^256 -1 if they have 0 debt." - }, - "getEntireDebtAndColl(address)": { - "notice": "Return the Troves entire debt and coll, including pending rewards from redistributions." - }, - "getOwner()": { - "notice": "Return address of the owner." - }, - "getRedemptionFeeWithDecay(uint256)": { - "notice": "The redemption fee is taken as a cut of the total ETH drawn from the system in a redemption. It is based on the current redemption rate." - }, - "liquidate(address)": { - "notice": "Single liquidation function. Closes the trove if its ICR is lower than the minimum collateral ratio." - }, - "liquidateTroves(uint256)": { - "notice": "Liquidate a sequence of troves. Closes a maximum number of n under-collateralized Troves, starting from the one with the lowest collateral ratio in the system, and moving upwards" - }, - "permit2()": { - "notice": "CONSTANT / IMMUTABLE VARIABLE ONLY " - }, - "removeStake(address)": { - "notice": "Remove borrower's stake from the totalStakes sum, and set their stake to 0" - }, - "setOwner(address)": { - "notice": "Set address of the owner (only owner can call this function)" - }, - "updateStakeAndTotalStakes(address)": { - "notice": "Update borrower's stake based on their latest collateral value" - }, - "updateTroveRewardSnapshots(address)": { - "notice": "Update borrower's snapshots of L_ETH and L_ZUSDDebt to reflect the current values" - } - }, - "version": 1 - }, - "storageLayout": { - "storage": [ - { - "astId": 5495, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "activePool", - "offset": 0, - "slot": "0", - "type": "t_contract(IActivePool)19557" - }, - { - "astId": 5497, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "defaultPool", - "offset": 0, - "slot": "1", - "type": "t_contract(IDefaultPool)20229" - }, - { - "astId": 5500, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "priceFeed", - "offset": 0, - "slot": "2", - "type": "t_contract(IPriceFeed)20458" - }, - { - "astId": 5503, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "liquityBaseParams", - "offset": 0, - "slot": "3", - "type": "t_contract(ILiquityBaseParams)20379" - }, - { - "astId": 35227, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "troveManagerRedeemOps", - "offset": 0, - "slot": "4", - "type": "t_address" - }, - { - "astId": 35229, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "borrowerOperationsAddress", - "offset": 0, - "slot": "5", - "type": "t_address" - }, - { - "astId": 35231, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "_stabilityPool", - "offset": 0, - "slot": "6", - "type": "t_contract(IStabilityPool)21101" - }, - { - "astId": 35233, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "gasPoolAddress", - "offset": 0, - "slot": "7", - "type": "t_address" - }, - { - "astId": 35235, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "collSurplusPool", - "offset": 0, - "slot": "8", - "type": "t_contract(ICollSurplusPool)20130" - }, - { - "astId": 35237, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "_zusdToken", - "offset": 0, - "slot": "9", - "type": "t_contract(IZUSDToken)21842" - }, - { - "astId": 35239, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "_zeroToken", - "offset": 0, - "slot": "10", - "type": "t_contract(IZEROToken)21783" - }, - { - "astId": 35241, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "_zeroStaking", - "offset": 0, - "slot": "11", - "type": "t_contract(IZEROStaking)21760" - }, - { - "astId": 35243, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "feeDistributor", - "offset": 0, - "slot": "12", - "type": "t_contract(IFeeDistributor)20298" - }, - { - "astId": 35245, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "sortedTroves", - "offset": 0, - "slot": "13", - "type": "t_contract(ISortedTroves)20817" - }, - { - "astId": 35247, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "baseRate", - "offset": 0, - "slot": "14", - "type": "t_uint256" - }, - { - "astId": 35249, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "lastFeeOperationTime", - "offset": 0, - "slot": "15", - "type": "t_uint256" - }, - { - "astId": 35270, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "Troves", - "offset": 0, - "slot": "16", - "type": "t_mapping(t_address,t_struct(Trove)35266_storage)" - }, - { - "astId": 35272, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "totalStakes", - "offset": 0, - "slot": "17", - "type": "t_uint256" - }, - { - "astId": 35274, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "totalStakesSnapshot", - "offset": 0, - "slot": "18", - "type": "t_uint256" - }, - { - "astId": 35276, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "totalCollateralSnapshot", - "offset": 0, - "slot": "19", - "type": "t_uint256" - }, - { - "astId": 35278, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "L_ETH", - "offset": 0, - "slot": "20", - "type": "t_uint256" - }, - { - "astId": 35280, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "L_ZUSDDebt", - "offset": 0, - "slot": "21", - "type": "t_uint256" - }, - { - "astId": 35284, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "rewardSnapshots", - "offset": 0, - "slot": "22", - "type": "t_mapping(t_address,t_struct(RewardSnapshot)35289_storage)" - }, - { - "astId": 35292, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "TroveOwners", - "offset": 0, - "slot": "23", - "type": "t_array(t_address)dyn_storage" - }, - { - "astId": 35294, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "lastETHError_Redistribution", - "offset": 0, - "slot": "24", - "type": "t_uint256" - }, - { - "astId": 35296, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "lastZUSDDebtError_Redistribution", - "offset": 0, - "slot": "25", - "type": "t_uint256" - } ], - "types": { - "t_address": { - "encoding": "inplace", - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_address)dyn_storage": { - "base": "t_address", - "encoding": "dynamic_array", - "label": "address[]", - "numberOfBytes": "32" - }, - "t_contract(IActivePool)19557": { - "encoding": "inplace", - "label": "contract IActivePool", - "numberOfBytes": "20" - }, - "t_contract(ICollSurplusPool)20130": { - "encoding": "inplace", - "label": "contract ICollSurplusPool", - "numberOfBytes": "20" - }, - "t_contract(IDefaultPool)20229": { - "encoding": "inplace", - "label": "contract IDefaultPool", - "numberOfBytes": "20" - }, - "t_contract(IFeeDistributor)20298": { - "encoding": "inplace", - "label": "contract IFeeDistributor", - "numberOfBytes": "20" - }, - "t_contract(ILiquityBaseParams)20379": { - "encoding": "inplace", - "label": "contract ILiquityBaseParams", - "numberOfBytes": "20" - }, - "t_contract(IPriceFeed)20458": { - "encoding": "inplace", - "label": "contract IPriceFeed", - "numberOfBytes": "20" - }, - "t_contract(ISortedTroves)20817": { - "encoding": "inplace", - "label": "contract ISortedTroves", - "numberOfBytes": "20" - }, - "t_contract(IStabilityPool)21101": { - "encoding": "inplace", - "label": "contract IStabilityPool", - "numberOfBytes": "20" - }, - "t_contract(IZEROStaking)21760": { - "encoding": "inplace", - "label": "contract IZEROStaking", - "numberOfBytes": "20" - }, - "t_contract(IZEROToken)21783": { - "encoding": "inplace", - "label": "contract IZEROToken", - "numberOfBytes": "20" - }, - "t_contract(IZUSDToken)21842": { - "encoding": "inplace", - "label": "contract IZUSDToken", - "numberOfBytes": "20" - }, - "t_enum(Status)35255": { - "encoding": "inplace", - "label": "enum TroveManagerStorage.Status", - "numberOfBytes": "1" - }, - "t_mapping(t_address,t_struct(RewardSnapshot)35289_storage)": { - "encoding": "mapping", - "key": "t_address", - "label": "mapping(address => struct TroveManagerStorage.RewardSnapshot)", - "numberOfBytes": "32", - "value": "t_struct(RewardSnapshot)35289_storage" - }, - "t_mapping(t_address,t_struct(Trove)35266_storage)": { - "encoding": "mapping", - "key": "t_address", - "label": "mapping(address => struct TroveManagerStorage.Trove)", - "numberOfBytes": "32", - "value": "t_struct(Trove)35266_storage" - }, - "t_struct(RewardSnapshot)35289_storage": { - "encoding": "inplace", - "label": "struct TroveManagerStorage.RewardSnapshot", - "members": [ - { - "astId": 35286, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "ETH", - "offset": 0, - "slot": "0", - "type": "t_uint256" - }, - { - "astId": 35288, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "ZUSDDebt", - "offset": 0, - "slot": "1", - "type": "t_uint256" - } - ], - "numberOfBytes": "64" - }, - "t_struct(Trove)35266_storage": { - "encoding": "inplace", - "label": "struct TroveManagerStorage.Trove", - "members": [ - { - "astId": 35257, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "debt", - "offset": 0, - "slot": "0", - "type": "t_uint256" - }, - { - "astId": 35259, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "coll", - "offset": 0, - "slot": "1", - "type": "t_uint256" - }, - { - "astId": 35261, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "stake", - "offset": 0, - "slot": "2", - "type": "t_uint256" - }, - { - "astId": 35263, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "status", - "offset": 0, - "slot": "3", - "type": "t_enum(Status)35255" - }, - { - "astId": 35265, - "contract": "contracts/TroveManager.sol:TroveManager", - "label": "arrayIndex", - "offset": 1, - "slot": "3", - "type": "t_uint128" - } + "transactionHash": "0xb70eae1fa822d4bceaa61efe247ad6f5814f2b63dfa78eabf34efcf284fe584e", + "receipt": { + "transactionHash": "0xb70eae1fa822d4bceaa61efe247ad6f5814f2b63dfa78eabf34efcf284fe584e", + "transactionIndex": "0x0", + "blockHash": "0x4750d0e3f78cdd8c0a662cec3fc3f929a6c902d630ab77e81b2f9363dfa63a8c", + "blockNumber": "0x5c179e", + "cumulativeGasUsed": "0x5bfe3a", + "gasUsed": "0x5bfe3a", + "contractAddress": "0x3d841398aca5794b9da2f2573f473075417a0117", + "logs": [ + { + "logIndex": "0x0", + "blockNumber": "0x5c179e", + "blockHash": "0x4750d0e3f78cdd8c0a662cec3fc3f929a6c902d630ab77e81b2f9363dfa63a8c", + "transactionHash": "0xb70eae1fa822d4bceaa61efe247ad6f5814f2b63dfa78eabf34efcf284fe584e", + "transactionIndex": "0x0", + "address": "0x3d841398aca5794b9da2f2573f473075417a0117", + "data": "0x", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000013be55487d37fe3c66ee7305e1e9c1ac85de75ae" + ] + } ], - "numberOfBytes": "128" - }, - "t_uint128": { - "encoding": "inplace", - "label": "uint128", - "numberOfBytes": "16" - }, - "t_uint256": { - "encoding": "inplace", - "label": "uint256", - "numberOfBytes": "32" - } - } - } -} \ No newline at end of file + "from": "0x13be55487d37fe3c66ee7305e1e9c1ac85de75ae", + "to": null, + "status": "0x1", + "logsBloom": "0x00000000000020000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000020000000000000000001800000000000000000000000000000000400000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000001020000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x0", + "effectiveGasPrice": "0x3e252e0" + }, + "args": [ + "1209600", + "0x000000000022D473030F116dDEE9F6B43aC78BA3" + ], + "bytecode": "0x60c06040523480156200001157600080fd5b50604051620058b4380380620058b4833981016040819052620000349162000128565b8162000049336001600160e01b036200006316565b60805260601b6001600160601b03191660a05250620001c4565b6001600160a01b038116620000955760405162461bcd60e51b81526004016200008c9062000182565b60405180910390fd5b6001600160a01b038116620000b26001600160e01b036200010716565b6001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a36000604051620000f79062000165565b6040519081900390209190915550565b600080604051620001189062000165565b6040519081900390205492915050565b600080604083850312156200013b578182fd5b825160208401519092506001600160a01b03811681146200015a578182fd5b809150509250929050565b7035b2bc9737bbb730b136329737bbb732b960791b815260110190565b60208082526022908201527f4f776e61626c653a3a7365744f776e65723a20696e76616c6964206164647265604082015261737360f01b606082015260800190565b60805160a05160601c6156c9620001eb60003980610ff152508061223252506156c96000f3fe608060405234801561001057600080fd5b50600436106104c25760003560e01c8063756b253e11610278578063b0d8e1811161015c578063d293c710116100ce578063d815e8e911610092578063d815e8e91461095a578063d9a7244414610962578063e056e91814610975578063e2ac77b014610988578063f36b24251461099b578063fe2ba848146109a3576104c2565b8063d293c71014610906578063d380a37c14610919578063d3d6f84314610921578063d5b3563514610934578063d66a255314610947576104c2565b8063be4b033411610120578063be4b0334146108cb578063bf9befb1146108d3578063c35bc550146108db578063c52861f2146108e3578063c7b55481146108eb578063cbd138ae146108f3576104c2565b8063b0d8e18114610867578063b7f8cf9b1461087a578063b82f263d14610882578063b91af97c14610895578063bcd37526146108b8576104c2565b8063887105d3116101f55780639dd233d2116101b95780639dd233d2146108325780639f0706701461083a578063a20baee614610773578063a3f4df7e14610842578063ae7bec1914610857578063ae9187541461085f576104c2565b8063887105d3146107f4578063893d20e8146107fc57806396d711ff146108045780639708daf41461080c5780639976cf451461081f576104c2565b80637cf54e401161023c5780637cf54e40146107b65780637f7dde4a146107be578063807d138d146107c657806382fe3eb9146107ce57806387436936146107e1576104c2565b8063756b253e14610783578063759b303414610796578063794e57241461079e578063795d26c3146107a6578063797250e3146107ae576104c2565b80633cc74225116103aa57806361ec893d1161031c5780636b444952116102e05780636b4449521461072c5780636ef6433814610734578063716c47e61461075857806372423c171461076057806372fe25aa14610773578063741bef1a1461077b576104c2565b806361ec893d146106e3578063631203b0146106eb57806364cee260146106fe578063653d46e71461071157806366ca4a2114610724576104c2565b80634a767d681161036e5780634a767d681461067a5780634e443d9e1461068d5780635733d58f146106ad5780635d6b480f146106b55780635d8c9609146106c85780635dba4c4a146106db576104c2565b80633cc742251461063157806342ccf1e414610639578063477d66cf1461064c578063480cd5781461065f57806349eefeee14610672576104c2565b806317c62b17116104435780631f68f20a116104075780631f68f20a146105eb57806321e37801146105f35780632b11551a146106065780632f8655681461060e57806331c903b0146106215780633a12859514610629576104c2565b806317c62b171461059757806318f2817a146105aa5780631a59a50e146105bd5780631bf43555146105d05780631e8b1c2b146105d8576104c2565b806312261ee71161048a57806312261ee71461053557806312610e921461053d57806313af40351461055057806315d549f1146105635780631673c79a14610576576104c2565b806301f16e18146104c757806305b6f5ca146104dc578063071a7541146104ef5780630b0765571461050d5780630d43e8ad14610520575b600080fd5b6104da6104d5366004614d96565b6109b6565b005b6104da6104ea366004614f61565b610f1c565b6104f7610fb3565b604051610504919061556a565b60405180910390f35b6104da61051b366004614c9f565b610fb8565b610528610fe0565b604051610504919061513c565b610528610fef565b6104f761054b366004614cd7565b611013565b6104da61055e366004614c9f565b61106b565b6104f7610571366004614c9f565b6110ac565b610589610584366004614c9f565b6110d0565b604051610504929190615573565b6104f76105a5366004614c9f565b6110e9565b6104f76105b8366004614c9f565b6110f4565b6104f76105cb366004614c9f565b611107565b6104f76111cd565b6104da6105e6366004614d02565b6111da565b6104f761153b565b6104f7610601366004614c9f565b611541565b6104f761156b565b6104da61061c366004614c9f565b61157a565b6104f76115e0565b6105286115ed565b6105286115fc565b6104f7610647366004614c9f565b61160b565b6104f761065a366004614ec4565b61163c565b6104f761066d366004614c9f565b61164f565b6104f761166d565b6104f7610688366004614cd7565b611673565b6106a061069b366004614ec4565b611692565b60405161050491906151af565b6104f761169d565b6104da6106c3366004614cd7565b61171a565b6104f76106d6366004614c9f565b611766565b6104da611771565b6104f76117dc565b6104f76106f9366004614ec4565b6117e1565b6104f761070c366004614c9f565b6117ee565b6104da61071f366004614ec4565b61180c565b6104f7611b96565b6104f7611ba8565b610747610742366004614c9f565b611bae565b60405161050495949392919061559c565b610528611be8565b6104f761076e366004614cd7565b611bf7565b6104f7611c54565b610528611c60565b610528610791366004614ec4565b611c6f565b6104f7611c96565b6104f7611ca3565b6104f7611ce8565b6104f7611e07565b610528611e0d565b610528611e1c565b6104f7611e2b565b6104da6107dc366004614c9f565b611e31565b6104da6107ef366004614c9f565b611e42565b6104f7611ece565b610528611f9d565b6104f7611fbc565b6104da61081a366004614feb565b611fc2565b6104f761082d366004614cd7565b61205b565b6104f761208e565b610528612094565b61084a6120a3565b60405161050491906151dc565b6105286120cb565b6105286120da565b6104f7610875366004614c9f565b6120e9565b610528612110565b6104f7610890366004614ec4565b61211f565b6108a86108a3366004614c9f565b61212a565b60405161050494939291906155dc565b6104da6108c6366004614ef4565b61218e565b6104f7612224565b6104f761222a565b6104f7612230565b6104f7612254565b6104f7612266565b6104da610901366004614c9f565b612272565b6104f7610914366004614cd7565b612285565b6104f7612298565b6104f761092f366004614cd7565b61229e565b6104f7610942366004614ec4565b6122d4565b6104f7610955366004614c9f565b6122e7565b610528612302565b610528610970366004614ec4565b612311565b6106a0610983366004614c9f565b61233b565b6106a0610996366004614c9f565b612399565b6104f76123a4565b6104da6109b1366004614c9f565b6123b1565b6109be611f9d565b6001600160a01b0316336001600160a01b0316146109f75760405162461bcd60e51b81526004016109ee906153c7565b60405180910390fd5b8051610a02906123c2565b610a0f81602001516123c2565b610a1c81604001516123c2565b610a2981606001516123c2565b610a3681608001516123c2565b610a438160a001516123c2565b610a508160c001516123c2565b610a5d8160e001516123c2565b610a6b8161010001516123c2565b610a798161012001516123c2565b610a878161014001516123c2565b610a958161016001516123c2565b610aa38161018001516123c2565b610ab1816101a001516123c2565b8051600c80546001600160a01b03199081166001600160a01b038085169190911790925560208401516004805483169184169190911790556040808501516003805484169185169190911790556060850151600580548416918516919091179055608085015160008054841691851691909117905560a085015160018054841691851691909117905560c085015160068054841691851691909117905560e0850151600780548416918516919091179055610100850151600880548416918516919091179055610120850151600280548416918516919091179055610140850151600980548416918516919091179055610160850151600d80548416918516919091179055610180850151600a805484169185169190911790556101a0850151600b80549093169316929092179055517f84861842e6a98dd759145eb5e7b48279d0f02ebeabc8a69e5196b2f4c5023db991610c0c9161513c565b60405180910390a17f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b18160200151604051610c47919061513c565b60405180910390a17fbf65195e6d5213f6fcbce65b1454c925197a45e616dabd2e243542b039b050928160600151604051610c82919061513c565b60405180910390a17f3ca631ffcd2a9b5d9ae18543fc82f58eb4ca33af9e6ab01b7a8e95331e6ed9858160600151604051610cbd919061513c565b60405180910390a17f78f058b189175430c48dc02699e3a0031ea4ff781536dc2fab847de4babdd8828160800151604051610cf8919061513c565b60405180910390a17f5ee0cae2f063ed938bb55046f6a932fb6ae792bf43624806bb90abe68a50be9b8160a00151604051610d33919061513c565b60405180910390a17f82966d27eea39b038ee0fa30cd16532bb24f6e65d31cb58fb227aa5766cdcc7f8160c00151604051610d6e919061513c565b60405180910390a17fcfb07d791fcafc032b35837b50eb84b74df518cf4cc287e8084f47630fa70fa08160e00151604051610da9919061513c565b60405180910390a17fe67f36a6e961157d6eff83b91f3af5a62131ceb6f04954ef74f51c1c05e7f88d816101000151604051610de5919061513c565b60405180910390a17f8c537274438aa850a330284665d81a85dd38267d09e4050d416bfc94142db264816101200151604051610e21919061513c565b60405180910390a17fcdf33850c44a1a874b5fefb51dae615ac6afebd581ef90d571f81b06541d8e9d816101400151604051610e5d919061513c565b60405180910390a17f65f4cf077bc01e4742eb5ad98326f6e95b63548ea24b17f8d5e823111fe78800816101600151604051610e99919061513c565b60405180910390a17f61e0c29d5028a9e4facaa476a46e78912e99f1ba945c9560b86b82ebe36ee52d816101800151604051610ed5919061513c565b60405180910390a17f7624075ed781e3aa34b0f492d70d1bb5a6fcde04534165e20f990d2e546be4dd816101a00151604051610f11919061513c565b60405180910390a150565b6004546040516000916060916001600160a01b0390911690610f41908490369061510f565b600060405180830381855af49150503d8060008114610f7c576040519150601f19603f3d011682016040523d82523d6000602084013e610f81565b606091505b5091509150818190610fa65760405162461bcd60e51b81526004016109ee91906151dc565b5050505050505050505050565b600281565b610fc0612407565b600054600154610fdd916001600160a01b03908116911683612433565b50565b600c546001600160a01b031681565b7f000000000000000000000000000000000000000000000000000000000000000081565b600061101d612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff61254516565b6001600160a01b03851660009081526010602052604090208190559150505b92915050565b611073611f9d565b6001600160a01b0316336001600160a01b0316146110a35760405162461bcd60e51b81526004016109ee906153c7565b610fdd81612587565b60006110b6612407565b6110bf82612612565b6001600160801b031690505b919050565b6016602052600090815260409020805460019091015482565b60006110658261160b565b60006110fe612407565b611065826126ac565b6001600160a01b0381166000908152601660205260408120546014548290611135908363ffffffff61254516565b905080158061116e575060016001600160a01b03851660009081526010602052604090206003015460ff16600481111561116b57fe5b14155b1561117e576000925050506110cb565b6001600160a01b038416600090815260106020526040812060020154906111c3670de0b6b3a76400006111b7848663ffffffff61275f16565b9063ffffffff61279916565b9695505050505050565b6809c2007651b250000081565b80516111f85760405162461bcd60e51b81526004016109ee906153f8565b6000546001546006546001600160a01b0392831692918216911661121a614b2b565b611222614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561127257600080fd5b505af1158015611286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112aa9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b1580156112ec57600080fd5b505afa158015611300573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113249190614edc565b60208301528151611334906127db565b158015604084015261135c576113558585846000015185602001518a612876565b9050611374565b6113718585846000015185602001518a612b8b565b90505b60008160200151116113985760405162461bcd60e51b81526004016109ee906154d9565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad926113cc92600401615573565b600060405180830381600087803b1580156113e657600080fd5b505af11580156113fa573d6000803e3d6000fd5b5050505061141285858360c001518460e00151612ccb565b61010081015115611489576008546101008201516040516364a197f360e01b81526001600160a01b03888116936364a197f393611456939290911691600401615196565b600060405180830381600087803b15801561147057600080fd5b505af1158015611484573d6000803e3d6000fd5b505050505b611497858260400151612f2f565b60208101516060830152610100810151604082015182516114cf92916114c3919063ffffffff61254516565b9063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda94039461151794909290916155dc565b60405180910390a1611533853383606001518460400151613090565b505050505050565b600e5481565b6001600160a01b03811660009081526010602052604081206003015460ff16600481111561106557fe5b60006115756115e0565b905090565b6115838161316d565b6040805160018082528183019092526060916020808301908036833701905050905081816000815181106115b357fe5b60200260200101906001600160a01b031690816001600160a01b0316815250506115dc816111da565b5050565b6000611575600e546131b6565b600b546001600160a01b031681565b6001546001600160a01b031681565b6001600160a01b0381166000908152601660205260408120600101546015548290611135908363ffffffff61254516565b6000611065611649611b96565b83613253565b6001600160a01b031660009081526010602052604090206001015490565b60175490565b600080600061168185613271565b9150915060006111c38383876132f7565b6000611065826127db565b60035460408051635733d58f60e01b815290516000926001600160a01b031691635733d58f916004808301926020929190829003018186803b1580156116e257600080fd5b505afa1580156116f6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115759190614edc565b611722612407565b80600481111561172e57fe5b6001600160a01b0383166000908152601060205260409020600301805460ff1916600183600481111561175d57fe5b02179055505050565b600061106582611107565b611779612407565b6000611783613329565b9050670de0b6b3a764000081111561179757fe5b600e8190556040517fc454ee9b76c52f782a256af821b857ca6e125d1e3333bcede402fec2bed9600c906117cc90839061556a565b60405180910390a1610fdd61336d565b603c81565b60006110656116496123a4565b6001600160a01b031660009081526010602052604090206002015490565b611814614ba8565b506040805160e081018252600080546001600160a01b0390811683526001548116602084015292820181905260608201819052600d548316608083015260a0820181905260c082015260065490911661186b614b2b565b611873614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b1580156118c357600080fd5b505af11580156118d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118fb9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b15801561193d57600080fd5b505afa158015611951573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119759190614edc565b60208301528151611985906127db565b15801560408401526119ac576119a58483600001518460200151886133c2565b90506119cc565b6119c9846000015185602001518460000151856020015189613869565b90505b60008160200151116119f05760405162461bcd60e51b81526004016109ee906154d9565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad92611a2492600401615573565b600060405180830381600087803b158015611a3e57600080fd5b505af1158015611a52573d6000803e3d6000fd5b50505050611a72846000015185602001518360c001518460e00151612ccb565b61010081015115611ae95783516008546101008301516040516364a197f360e01b81526001600160a01b03938416936364a197f393611ab693911691600401615196565b600060405180830381600087803b158015611ad057600080fd5b505af1158015611ae4573d6000803e3d6000fd5b505050505b611afb84600001518260400151612f2f565b6020810151606083015261010081015160408201518251611b2792916114c3919063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda940394611b6f94909290916155dc565b60405180910390a1611b8f84600001513383606001518460400151613090565b5050505050565b6000611575611ba3613329565b613a1f565b60195481565b60106020526000908152604090208054600182015460028301546003909301549192909160ff81169061010090046001600160801b031685565b6004546001600160a01b031681565b6000611c01612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff613b0116565b6001600160a01b038516600090815260106020526040902060010181905591505092915050565b670de0b6b3a764000081565b6002546001600160a01b031681565b60178181548110611c7c57fe5b6000918252602090912001546001600160a01b0316905081565b6801158e460913d0000081565b60035460408051631e5395c960e21b815290516000926001600160a01b03169163794e5724916004808301926020929190829003018186803b1580156116e257600080fd5b60008054604080516272c7d360e71b8152905183926001600160a01b031691633963e980916004808301926020929190829003018186803b158015611d2c57600080fd5b505afa158015611d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d649190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b0316633963e9806040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b505afa158015611dca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dee9190614edc565b9050611e00828263ffffffff613b0116565b9250505090565b60185481565b600a546001600160a01b031681565b6000546001600160a01b031681565b60125481565b611e39612407565b610fdd81613b26565b611e4a611f9d565b6001600160a01b0316336001600160a01b031614611e7a5760405162461bcd60e51b81526004016109ee906153c7565b611e83816123c2565b600480546001600160a01b0319166001600160a01b0383161790556040517f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b190610f1190839061513c565b6000805460408051630a7b61df60e11b8152905183926001600160a01b0316916314f6c3be916004808301926020929190829003018186803b158015611f1357600080fd5b505afa158015611f27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f4b9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b600080604051611fac9061511f565b6040519081900390205492915050565b60135481565b6004546040516000916060916001600160a01b0390911690611fe7908490369061510f565b600060405180830381855af49150503d8060008114612022576040519150601f19603f3d011682016040523d82523d6000602084013e612027565b606091505b509150915081819061204c5760405162461bcd60e51b81526004016109ee91906151dc565b50505050505050505050505050565b6000612065612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff613b0116565b60145481565b6003546001600160a01b031681565b6040518060400160405280600c81526020016b2a3937bb32a6b0b730b3b2b960a11b81525081565b6006546001600160a01b031681565b600d546001600160a01b031681565b60008060006120f784613271565b9150915060006121078383613b83565b95945050505050565b6005546001600160a01b031681565b600061106582613bb8565b6001600160a01b03811660009081526010602052604081208054600190910154909180612156856110e9565b915061216185611766565b9050612173848363ffffffff613b0116565b9350612185838263ffffffff613b0116565b92509193509193565b6004546040516000916060916001600160a01b03909116906121b3908490369061510f565b600060405180830381855af49150503d80600081146121ee576040519150601f19603f3d011682016040523d82523d6000602084013e6121f3565b606091505b50915091508181906122185760405162461bcd60e51b81526004016109ee91906151dc565b50505050505050505050565b60155481565b60115481565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000611575612261613329565b6131b6565b670ddd4b8c6c7d70d881565b61227a612407565b610fdd816002613be4565b60006122918383611673565b9392505050565b600f5481565b60006122a8612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff61254516565b60006110656122e1612254565b83613cf9565b6001600160a01b031660009081526010602052604090205490565b6009546001600160a01b031681565b60006017828154811061232057fe5b6000918252602090912001546001600160a01b031692915050565b600060016001600160a01b03831660009081526010602052604090206003015460ff16600481111561236957fe5b14612376575060006110cb565b506014546001600160a01b03821660009081526016602052604090205410919050565b60006110658261233b565b6000611575600e54613a1f565b6123b9612407565b610fdd81613d39565b6001600160a01b0381166123e85760405162461bcd60e51b81526004016109ee906152a8565b803b806115dc5760405162461bcd60e51b81526004016109ee9061544e565b6005546001600160a01b031633146124315760405162461bcd60e51b81526004016109ee9061536a565b565b61243c8161233b565b156125405761244a8161316d565b600061245582611107565b905060006124628361160b565b6001600160a01b038416600090815260106020526040902060010154909150612491908363ffffffff613b0116565b6001600160a01b03841660009081526010602052604090206001810191909155546124c2908263ffffffff613b0116565b6001600160a01b0384166000908152601060205260409020556124e483613b26565b6124f085858385613d89565b6001600160a01b0383166000818152601060205260408082208054600182015460029092015492516000805160206156748339815191529461253594929392916151ba565b60405180910390a250505b505050565b600061229183836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613e71565b6001600160a01b0381166125ad5760405162461bcd60e51b81526004016109ee90615266565b806001600160a01b03166125bf611f9d565b6001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a360006040516126029061511f565b6040519081900390209190915550565b601780546001808201835560008381527fc624b66cc0138b8fabc209247f72d758e1cf3343756d543badbf24212bed8c1590920180546001600160a01b0319166001600160a01b0386161790559154909161266d9190612545565b6001600160a01b039290921660009081526010602052604090206003018054610100600160881b0319166101006001600160801b038516021790555090565b6001600160a01b03811660009081526010602052604081206001015481906126d390613e9d565b6001600160a01b03841660009081526010602052604090206002018054908290556011549192509061271d908390612711908463ffffffff61254516565b9063ffffffff613b0116565b60118190556040517f6bac5e0eb3c44eb03a60ab11ec3a2c051771616aecadbcfff2630aabae520382916127509161556a565b60405180910390a15092915050565b60008261276e57506000611065565b8282028284828161277b57fe5b04146122915760405162461bcd60e51b81526004016109ee90615329565b600061229183836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613ed9565b6000806127e783613bb8565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561283757600080fd5b505afa15801561284b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061286f9190614edc565b1192915050565b61287e614b5c565b612886614be4565b61288e614b5c565b848252600060808301526128a0611ce8565b60a08301526128ad611ece565b60c0830152600060208301525b835182602001511015612b8057838260200151815181106128d757fe5b6020908102919091018101516001600160a01b03166060840181905260009081526010909152604090206003015460019060ff16600481111561291657fe5b1461292057612b70565b61292e826060015187611673565b60408301526080820151612a9557600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561298a57600080fd5b505afa15801561299e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129c29190614edc565b8260400151101580156129d457508151155b156129de57612b70565b60006129f38360c001518460a00151896132f7565b9050612a108989856060015186604001518760000151868d613f10565b60808101518451919350612a2a919063ffffffff61254516565b8352608082015160a0840151612a459163ffffffff61254516565b60a08085019190915282015160c0840151612a659163ffffffff61254516565b60c0840152612a7484836142e6565b9350612a898360c001518460a00151896143fd565b15608084015250612b70565b81608001518015612b2e5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b158015612aef57600080fd5b505afa158015612b03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b279190614edc565b8260400151105b15612b7057612b4788888460600151856000015161449c565b60808101518351919250612b61919063ffffffff61254516565b8252612b6d83826142e6565b92505b60208201805160010190526128ba565b505095945050505050565b612b93614b5c565b612b9b614be4565b612ba3614b5c565b848252600060208301525b835182602001511015612b805783826020015181518110612bcb57fe5b60209081029190910101516001600160a01b031660608301819052612bf09087611673565b6040808401919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b158015612c3b57600080fd5b505afa158015612c4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c739190614edc565b82604001511015612cbb57612c9288888460600151856000015161449c565b60808101518351919250612cac919063ffffffff61254516565b8252612cb883826142e6565b92505b6020820180516001019052612bae565b81612cd557612f29565b601854600090612cf79061271184670de0b6b3a764000063ffffffff61275f16565b90506000612d1c601954612711670de0b6b3a76400008761275f90919063ffffffff16565b90506000612d356011548461279990919063ffffffff16565b90506000612d4e6011548461279990919063ffffffff16565b9050612d75612d686011548461275f90919063ffffffff16565b859063ffffffff61254516565b601855601154612d9d90612d9090839063ffffffff61275f16565b849063ffffffff61254516565b601955601454612db3908363ffffffff613b0116565b601455601554612dc9908263ffffffff613b0116565b60158190556014546040517f9f8bc8ab0daf5bceef75ecfd2085d1fcc6548c657ea970d9a23a60610d0737e392612e009291615573565b60405180910390a160405163121cbc4d60e11b81526001600160a01b03891690632439789a90612e3490899060040161556a565b600060405180830381600087803b158015612e4e57600080fd5b505af1158015612e62573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038a16925063f2e91d719150612e9290899060040161556a565b600060405180830381600087803b158015612eac57600080fd5b505af1158015612ec0573d6000803e3d6000fd5b50506040516364a197f360e01b81526001600160a01b038b1692506364a197f39150612ef2908a908990600401615196565b600060405180830381600087803b158015612f0c57600080fd5b505af1158015612f20573d6000803e3d6000fd5b50505050505050505b50505050565b6011546012819055506000826001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612f7357600080fd5b505afa158015612f87573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fab9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612ffd57600080fd5b505afa158015613011573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130359190614edc565b905061304b81612711848663ffffffff61254516565b60138190556012546040517f51bf4c63ec3cba9d03d43238abbdd979dd91bd16d9895c74ceea9118c7baaf60926130829291615573565b60405180910390a150505050565b811561310157600954600754604051631062c15f60e11b81526001600160a01b03928316926320c582be926130ce9291169087908790600401615150565b600060405180830381600087803b1580156130e857600080fd5b505af11580156130fc573d6000803e3d6000fd5b505050505b8015612f29576040516364a197f360e01b81526001600160a01b038516906364a197f3906131359086908590600401615196565b600060405180830381600087803b15801561314f57600080fd5b505af1158015613163573d6000803e3d6000fd5b5050505050505050565b60016001600160a01b03821660009081526010602052604090206003015460ff16600481111561319957fe5b14610fdd5760405162461bcd60e51b81526004016109ee9061551b565b600061106561324583600360009054906101000a90046001600160a01b03166001600160a01b03166328d28b5b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b505afa158015613221573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127119190614edc565b670de0b6b3a76400006145ce565b6000612291670de0b6b3a76400006111b7858563ffffffff61275f16565b600080600061327f84611107565b9050600061328c8561160b565b6001600160a01b038616600090815260106020526040812060010154919250906132bc908463ffffffff613b0116565b6001600160a01b038716600090815260106020526040812054919250906132e9908463ffffffff613b0116565b919550909350505050915091565b6000821561331e576000613315846111b7878663ffffffff61275f16565b91506122919050565b506000199392505050565b6000806133346145e4565b9050600061334a670ddd4b8c6c7d70d883614600565b9050611e00670de0b6b3a76400006111b783600e5461275f90919063ffffffff16565b6000613384600f544261254590919063ffffffff16565b9050603c8110610fdd5742600f8190556040517f860f8d2f0c74dd487e89e2883e3b25b8159ce1e1b3433a291cba7b82c508f3bc91610f119161556a565b6133ca614b5c565b6133d2614be4565b6133da614b5c565b848252600060808301526133ec611ce8565b60a08301526133f9611ece565b8260c001818152505086608001516001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b15801561343f57600080fd5b505afa158015613453573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134779190614cbb565b82606001906001600160a01b031690816001600160a01b031681525050600087608001516001600160a01b0316631e2231436040518163ffffffff1660e01b815260040160206040518083038186803b1580156134d357600080fd5b505afa1580156134e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061350b9190614cbb565b6000602085015290505b84836020015110801561353e5750806001600160a01b031683606001516001600160a01b031614155b1561385e5760808801516060840151604051632dc9c0eb60e21b81526000926001600160a01b03169163b72703ac9161357a919060040161513c565b60206040518083038186803b15801561359257600080fd5b505afa1580156135a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135ca9190614cbb565b90506135da846060015189611673565b6040850152608084015161375357600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561363657600080fd5b505afa15801561364a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061366e9190614edc565b84604001511015801561368057508351155b1561368b575061385e565b60006136a08560c001518660a001518b6132f7565b8a5160208c01516060880151604089015189519495506136c194868f613f10565b608081015186519195506136db919063ffffffff61254516565b8552608084015160a08601516136f69163ffffffff61254516565b8560a00181815250506137238461010001516114c38660a001518860c0015161254590919063ffffffff16565b60c086015261373286856142e6565b95506137478560c001518660a001518b6143fd565b15608086015250613840565b836080015180156137ec5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156137ad57600080fd5b505afa1580156137c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137e59190614edc565b8460400151105b1561383a5761380d89600001518a602001518660600151876000015161449c565b60808101518551919450613827919063ffffffff61254516565b845261383385846142e6565b9450613840565b5061385e565b6001600160a01b031660608401526020830180516001019052613515565b505050949350505050565b613871614b5c565b613879614be4565b613881614b5c565b600d54858352600060208401526001600160a01b03165b8483602001511015613a1357806001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b1580156138dd57600080fd5b505afa1580156138f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139159190614cbb565b6001600160a01b03166060840181905261392f9088611673565b6040808501919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b15801561397a57600080fd5b505afa15801561398e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139b29190614edc565b836040015110156139fe576139d189898560600151866000015161449c565b608081015184519193506139eb919063ffffffff61254516565b83526139f784836142e6565b9350613a03565b613a13565b6020830180516001019052613898565b50505095945050505050565b6000611065613a7683600360009054906101000a90046001600160a01b03166001600160a01b031663f92d34336040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b600360009054906101000a90046001600160a01b03166001600160a01b031663240926696040518163ffffffff1660e01b815260040160206040518083038186803b158015613ac457600080fd5b505afa158015613ad8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613afc9190614edc565b6145ce565b6000828201838110156122915760405162461bcd60e51b81526004016109ee9061522f565b601480546001600160a01b038316600090815260166020526040908190209182556015546001909201829055915491517fc437f324d85e369394148dd9d62f98f534b382e01ed3dd2eb98138fb6d3ab49a92610f11929091615573565b60008115613baf57613ba8826111b78568056bc75e2d6310000063ffffffff61275f16565b9050611065565b50600019611065565b600080613bc3611ece565b90506000613bcf611ce8565b9050613bdc8282866132f7565b949350505050565b6000816004811115613bf257fe5b14158015613c0c57506001816004811115613c0957fe5b14155b613c1257fe5b601754613c1e816146ab565b6001600160a01b0383166000908152601060205260409020600301805483919060ff19166001836004811115613c5057fe5b02179055506001600160a01b0383166000908152601060209081526040808320600180820185905590849055601690925282208281550155613c928382614751565b600d54604051631484968760e11b81526001600160a01b03909116906329092d0e90613cc290869060040161513c565b600060405180830381600087803b158015613cdc57600080fd5b505af1158015613cf0573d6000803e3d6000fd5b50505050505050565b600080613d18670de0b6b3a76400006111b7868663ffffffff61275f16565b90508281106122915760405162461bcd60e51b81526004016109ee90615483565b6001600160a01b038116600090815260106020526040902060020154601154613d68908263ffffffff61254516565b601155506001600160a01b0316600090815260106020526040812060020155565b60405163121cbc4d60e11b81526001600160a01b03841690632439789a90613db590859060040161556a565b600060405180830381600087803b158015613dcf57600080fd5b505af1158015613de3573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038716925063f2e91d719150613e1390859060040161556a565b600060405180830381600087803b158015613e2d57600080fd5b505af1158015613e41573d6000803e3d6000fd5b505060405163be41205f60e01b81526001600160a01b038616925063be41205f915061313590849060040161556a565b60008184841115613e955760405162461bcd60e51b81526004016109ee91906151dc565b505050900390565b60008060135460001415613eb2575081611065565b600060125411613ebe57fe5b6122916013546111b76012548661275f90919063ffffffff16565b60008183613efa5760405162461bcd60e51b81526004016109ee91906151dc565b506000838581613f0657fe5b0495945050505050565b613f18614b5c565b613f20614c2c565b601754600110613f3057506142db565b613f398761212a565b60408501526020848101919091528401819052908352613f58906148f2565b604083018190526801158e460913d0000060608401526020830151613f829163ffffffff61254516565b8152670de0b6b3a7640000861161404d57613fa7898983602001518460400151613d89565b613fb087613d39565b60006080830181905260a0830152815160c0830152805160e0830152613fd7876003613be4565b815160208301516040516001600160a01b038a16926000805160206156548339815191529261400892600290615581565b60405180910390a2866001600160a01b03166000805160206156748339815191526000806000600260405161404094939291906151ba565b60405180910390a26142d9565b670de0b6b3a7640000861180156140e85750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156140ad57600080fd5b505afa1580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614edc565b86105b1561413857614101898983602001518460400151613d89565b61410a87613d39565b8151815161411991908761497e565b60e086015260c085015260a08401526080830152613fd7876003613be4565b600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561418657600080fd5b505afa15801561419a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141be9190614edc565b86101580156141cc57508386105b80156141d9575081518510155b156142c8576141f2898983602001518460400151613d89565b846141f957fe5b61420287613d39565b61421582600001518360200151856149ea565b9150614222876003613be4565b6101008201511561429757600854610100830151604051633f10abab60e01b81526001600160a01b0390921691633f10abab91614264918b9190600401615196565b600060405180830381600087803b15801561427e57600080fd5b505af1158015614292573d6000803e3d6000fd5b505050505b815160a08301516040516001600160a01b038a16926000805160206156548339815191529261400892600290615581565b6142d0614b5c565b91506142db9050565b505b979650505050505050565b6142ee614b5c565b604080830151908401516143079163ffffffff613b0116565b6040820152606080830151908401516143259163ffffffff613b0116565b6060820152815160208401516143409163ffffffff613b0116565b602080830191909152820151835161435d9163ffffffff613b0116565b8152608080830151908401516143789163ffffffff613b0116565b608082015260a080830151908401516143969163ffffffff613b0116565b60a082015260c080830151908401516143b49163ffffffff613b0116565b60c082015260e080830151908401516143d29163ffffffff613b0116565b60e082015261010080830151908401516143f19163ffffffff613b0116565b61010082015292915050565b60008061440b8585856132f7565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561445b57600080fd5b505afa15801561446f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144939190614edc565b11949350505050565b6144a4614b5c565b6144ac614c2c565b6144b58461212a565b604085019081526020858101928352860192909252918452905190516144df918891889190613d89565b6144e884613d39565b6144f582602001516148f2565b604083018190526801158e460913d0000060608401526020830151600091614523919063ffffffff61254516565b90506145348360000151828661497e565b60e087015260c086015260a08501526080840152614553856003613be4565b825160208401516040516001600160a01b038816926000805160206156548339815191529261458492600190615581565b60405180910390a2846001600160a01b0316600080516020615674833981519152600080600060016040516145bc94939291906151ba565b60405180910390a25050949350505050565b60008183106145dd5781612291565b5090919050565b6000611575603c6111b7600f544261254590919063ffffffff16565b6000631f54050082111561461657631f54050091505b8161462a5750670de0b6b3a7640000611065565b670de0b6b3a764000083835b60018111156146a1576002810661466b576146518283614af8565b915061466481600263ffffffff61279916565b905061469c565b6146758284614af8565b92506146818283614af8565b915061469960026111b783600163ffffffff61254516565b90505b614636565b6111c38284614af8565b6001811180156147355750600d546040805163de8fa43160e01b815290516001926001600160a01b03169163de8fa431916004808301926020929190829003018186803b1580156146fb57600080fd5b505afa15801561470f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147339190614edc565b115b610fdd5760405162461bcd60e51b81526004016109ee906152df565b6001600160a01b03821660009081526010602052604081206003015460ff169081600481111561477d57fe5b141580156147975750600181600481111561479457fe5b14155b61479d57fe5b6001600160a01b03831660009081526010602052604081206003015461010090046001600160801b03169083906147d5826001612545565b905080836001600160801b031611156147ea57fe5b6000601782815481106147f957fe5b600091825260209091200154601780546001600160a01b03909216925082916001600160801b03871690811061482b57fe5b600091825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918316815260109091526040908190206003018054610100600160881b0319166101006001600160801b03881602179055517f02b04ae5f7be9ca7c103293a2aa15f3c339d15d6eda53b721fef7b0e609c831a906148b49083908790615174565b60405180910390a160178054806148c757fe5b600082815260209020810160001990810180546001600160a01b031916905501905550505050505050565b600354604080516324386ecd60e11b815290516000926001600160a01b031691634870dd9a916004808301926020929190829003018186803b15801561493757600080fd5b505afa15801561494b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061496f9190614edc565b828161497757fe5b0492915050565b600080808084156149d45761499387866145ce565b93506149a9876111b7888763ffffffff61275f16565b92506149bb878563ffffffff61254516565b91506149cd868463ffffffff61254516565b90506149e1565b5060009250829150859050845b93509350935093565b6149f2614b5c565b838152602080820184905260035460408051631e5395c960e21b81529051600093614a8e9387936111b7936001600160a01b039092169263794e572492600480840193829003018186803b158015614a4957600080fd5b505afa158015614a5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614a819190614edc565b889063ffffffff61275f16565b9050614a99816148f2565b604083018190526801158e460913d00000606084015260808301869052614ac790829063ffffffff61254516565b60a0830152614adc848263ffffffff61254516565b61010083015250600060c0820181905260e08201529392505050565b600080614b0b848463ffffffff61275f16565b9050613bdc670de0b6b3a76400006111b7836706f05b59d3b20000613b01565b6040518060a00160405280600081526020016000815260200160001515815260200160008152602001600081525090565b6040518061012001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040518060e0016040528060008152602001600081526020016000815260200160006001600160a01b0316815260200160001515815260200160008152602001600081525090565b60405180606001604052806000815260200160008152602001600081525090565b80356110658161563e565b60008083601f840112614c69578081fd5b50813567ffffffffffffffff811115614c80578182fd5b602083019150836020828501011115614c9857600080fd5b9250929050565b600060208284031215614cb0578081fd5b81356122918161563e565b600060208284031215614ccc578081fd5b81516122918161563e565b60008060408385031215614ce9578081fd5b8235614cf48161563e565b946020939093013593505050565b60006020808385031215614d14578182fd5b823567ffffffffffffffff811115614d2a578283fd5b80840185601f820112614d3b578384fd5b80359150614d50614d4b8361561e565b6155f7565b8281528381019082850185850284018601891015614d6c578687fd5b8693505b848410156142d957614d828982614c4d565b835260019390930192918501918501614d70565b60006101c0808385031215614da9578182fd5b614db2816155f7565b614dbc8585614c4d565b8152614dcb8560208601614c4d565b6020820152614ddd8560408601614c4d565b6040820152614def8560608601614c4d565b6060820152614e018560808601614c4d565b6080820152614e138560a08601614c4d565b60a0820152614e258560c08601614c4d565b60c0820152614e378560e08601614c4d565b60e08201526101009150614e4d85838601614c4d565b828201526101209150614e6285838601614c4d565b828201526101409150614e7785838601614c4d565b828201526101609150614e8c85838601614c4d565b828201526101809150614ea185838601614c4d565b828201526101a09150614eb685838601614c4d565b918101919091529392505050565b600060208284031215614ed5578081fd5b5035919050565b600060208284031215614eed578081fd5b5051919050565b600080600080600080600060e0888a031215614f0e578283fd5b873596506020880135614f208161563e565b95506040880135614f308161563e565b94506060880135614f408161563e565b9699959850939660808101359560a0820135955060c0909101359350915050565b600080600080600080600080888a03610160811215614f7e578182fd5b8935985060208a0135614f908161563e565b975060408a0135614fa08161563e565b965060608a0135614fb08161563e565b955060808a810135955060a08b0135945060c08b0135935060df1982011215614fd7578182fd5b5060e0890190509295985092959890939650565b6000806000806000806000806000808a8c0361018081121561500b578485fd5b8b359a5060208c013561501d8161563e565b995060408c013561502d8161563e565b985060608c013561503d8161563e565b975060808c810135975060a08d0135965060c08d0135955060df19820190811215615066578384fd5b61507060606155f7565b9150604081121561507f578384fd5b5061508a60406155f7565b60e08d01356150988161563e565b81526101008d01356020808301919091529082526101208d0135908201526101408c0135604082015292506101608b013567ffffffffffffffff8111156150dd578283fd5b6150e98d828e01614c58565b8194508093505050509295989b9194979a5092959850565b6004811061510b57fe5b9052565b6000828483379101908152919050565b7035b2bc9737bbb730b136329737bbb732b960791b815260110190565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b039290921682526001600160801b0316602082015260400190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b8481526020810184905260408101839052608081016121076060830184615101565b6000602080835283518082850152825b81811015615208578581018301518582016040015282016151ec565b818111156152195783604083870101525b50601f01601f1916929092016040019392505050565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526022908201527f4f776e61626c653a3a7365744f776e65723a20696e76616c6964206164647265604082015261737360f01b606082015260800190565b6020808252601e908201527f4163636f756e742063616e6e6f74206265207a65726f20616464726573730000604082015260600190565b6020808252602a908201527f54726f76654d616e616765723a204f6e6c79206f6e652074726f766520696e206040820152697468652073797374656d60b01b606082015260800190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252603b908201527f54726f76654d616e616765723a2043616c6c6572206973206e6f74207468652060408201527f426f72726f7765724f7065726174696f6e7320636f6e74726163740000000000606082015260800190565b60208082526017908201527613dddb98589b194e8e881858d8d95cdcc819195b9a5959604a1b604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2043616c6c646174612061646472657373206160408201527572726179206d757374206e6f7420626520656d70747960501b606082015260800190565b6020808252818101527f4163636f756e7420636f64652073697a652063616e6e6f74206265207a65726f604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2046656520776f756c642065617420757020616040820152751b1b081c995d1d5c9b99590818dbdb1b185d195c985b60521b606082015260800190565b60208082526022908201527f54726f76654d616e616765723a206e6f7468696e6720746f206c697175696461604082015261746560f01b606082015260800190565b6020808252602f908201527f54726f76654d616e616765723a2054726f766520646f6573206e6f742065786960408201526e1cdd081bdc881a5cc818db1bdcd959608a1b606082015260800190565b90815260200190565b918252602082015260400190565b8381526020810183905260608101613bdc6040830184615101565b858152602081018590526040810184905260a08101600584106155bb57fe5b60608201939093526001600160801b03919091166080909101529392505050565b93845260208401929092526040830152606082015260800190565b60405181810167ffffffffffffffff8111828210171561561657600080fd5b604052919050565b600067ffffffffffffffff821115615634578081fd5b5060209081020190565b6001600160a01b0381168114610fdd57600080fdfeea67486ed7ebe3eea8ab3390efd4a3c8aae48be5bea27df104a8af786c408434c3770d654ed33aeea6bf11ac8ef05d02a6a04ed4686dd2f624d853bbec43cc8ba2646970667358221220160726666ba9181d0cbf2304c477c7f0275025dff3b8c1ef566a2dd6bfd6b69d64736f6c634300060b0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106104c25760003560e01c8063756b253e11610278578063b0d8e1811161015c578063d293c710116100ce578063d815e8e911610092578063d815e8e91461095a578063d9a7244414610962578063e056e91814610975578063e2ac77b014610988578063f36b24251461099b578063fe2ba848146109a3576104c2565b8063d293c71014610906578063d380a37c14610919578063d3d6f84314610921578063d5b3563514610934578063d66a255314610947576104c2565b8063be4b033411610120578063be4b0334146108cb578063bf9befb1146108d3578063c35bc550146108db578063c52861f2146108e3578063c7b55481146108eb578063cbd138ae146108f3576104c2565b8063b0d8e18114610867578063b7f8cf9b1461087a578063b82f263d14610882578063b91af97c14610895578063bcd37526146108b8576104c2565b8063887105d3116101f55780639dd233d2116101b95780639dd233d2146108325780639f0706701461083a578063a20baee614610773578063a3f4df7e14610842578063ae7bec1914610857578063ae9187541461085f576104c2565b8063887105d3146107f4578063893d20e8146107fc57806396d711ff146108045780639708daf41461080c5780639976cf451461081f576104c2565b80637cf54e401161023c5780637cf54e40146107b65780637f7dde4a146107be578063807d138d146107c657806382fe3eb9146107ce57806387436936146107e1576104c2565b8063756b253e14610783578063759b303414610796578063794e57241461079e578063795d26c3146107a6578063797250e3146107ae576104c2565b80633cc74225116103aa57806361ec893d1161031c5780636b444952116102e05780636b4449521461072c5780636ef6433814610734578063716c47e61461075857806372423c171461076057806372fe25aa14610773578063741bef1a1461077b576104c2565b806361ec893d146106e3578063631203b0146106eb57806364cee260146106fe578063653d46e71461071157806366ca4a2114610724576104c2565b80634a767d681161036e5780634a767d681461067a5780634e443d9e1461068d5780635733d58f146106ad5780635d6b480f146106b55780635d8c9609146106c85780635dba4c4a146106db576104c2565b80633cc742251461063157806342ccf1e414610639578063477d66cf1461064c578063480cd5781461065f57806349eefeee14610672576104c2565b806317c62b17116104435780631f68f20a116104075780631f68f20a146105eb57806321e37801146105f35780632b11551a146106065780632f8655681461060e57806331c903b0146106215780633a12859514610629576104c2565b806317c62b171461059757806318f2817a146105aa5780631a59a50e146105bd5780631bf43555146105d05780631e8b1c2b146105d8576104c2565b806312261ee71161048a57806312261ee71461053557806312610e921461053d57806313af40351461055057806315d549f1146105635780631673c79a14610576576104c2565b806301f16e18146104c757806305b6f5ca146104dc578063071a7541146104ef5780630b0765571461050d5780630d43e8ad14610520575b600080fd5b6104da6104d5366004614d96565b6109b6565b005b6104da6104ea366004614f61565b610f1c565b6104f7610fb3565b604051610504919061556a565b60405180910390f35b6104da61051b366004614c9f565b610fb8565b610528610fe0565b604051610504919061513c565b610528610fef565b6104f761054b366004614cd7565b611013565b6104da61055e366004614c9f565b61106b565b6104f7610571366004614c9f565b6110ac565b610589610584366004614c9f565b6110d0565b604051610504929190615573565b6104f76105a5366004614c9f565b6110e9565b6104f76105b8366004614c9f565b6110f4565b6104f76105cb366004614c9f565b611107565b6104f76111cd565b6104da6105e6366004614d02565b6111da565b6104f761153b565b6104f7610601366004614c9f565b611541565b6104f761156b565b6104da61061c366004614c9f565b61157a565b6104f76115e0565b6105286115ed565b6105286115fc565b6104f7610647366004614c9f565b61160b565b6104f761065a366004614ec4565b61163c565b6104f761066d366004614c9f565b61164f565b6104f761166d565b6104f7610688366004614cd7565b611673565b6106a061069b366004614ec4565b611692565b60405161050491906151af565b6104f761169d565b6104da6106c3366004614cd7565b61171a565b6104f76106d6366004614c9f565b611766565b6104da611771565b6104f76117dc565b6104f76106f9366004614ec4565b6117e1565b6104f761070c366004614c9f565b6117ee565b6104da61071f366004614ec4565b61180c565b6104f7611b96565b6104f7611ba8565b610747610742366004614c9f565b611bae565b60405161050495949392919061559c565b610528611be8565b6104f761076e366004614cd7565b611bf7565b6104f7611c54565b610528611c60565b610528610791366004614ec4565b611c6f565b6104f7611c96565b6104f7611ca3565b6104f7611ce8565b6104f7611e07565b610528611e0d565b610528611e1c565b6104f7611e2b565b6104da6107dc366004614c9f565b611e31565b6104da6107ef366004614c9f565b611e42565b6104f7611ece565b610528611f9d565b6104f7611fbc565b6104da61081a366004614feb565b611fc2565b6104f761082d366004614cd7565b61205b565b6104f761208e565b610528612094565b61084a6120a3565b60405161050491906151dc565b6105286120cb565b6105286120da565b6104f7610875366004614c9f565b6120e9565b610528612110565b6104f7610890366004614ec4565b61211f565b6108a86108a3366004614c9f565b61212a565b60405161050494939291906155dc565b6104da6108c6366004614ef4565b61218e565b6104f7612224565b6104f761222a565b6104f7612230565b6104f7612254565b6104f7612266565b6104da610901366004614c9f565b612272565b6104f7610914366004614cd7565b612285565b6104f7612298565b6104f761092f366004614cd7565b61229e565b6104f7610942366004614ec4565b6122d4565b6104f7610955366004614c9f565b6122e7565b610528612302565b610528610970366004614ec4565b612311565b6106a0610983366004614c9f565b61233b565b6106a0610996366004614c9f565b612399565b6104f76123a4565b6104da6109b1366004614c9f565b6123b1565b6109be611f9d565b6001600160a01b0316336001600160a01b0316146109f75760405162461bcd60e51b81526004016109ee906153c7565b60405180910390fd5b8051610a02906123c2565b610a0f81602001516123c2565b610a1c81604001516123c2565b610a2981606001516123c2565b610a3681608001516123c2565b610a438160a001516123c2565b610a508160c001516123c2565b610a5d8160e001516123c2565b610a6b8161010001516123c2565b610a798161012001516123c2565b610a878161014001516123c2565b610a958161016001516123c2565b610aa38161018001516123c2565b610ab1816101a001516123c2565b8051600c80546001600160a01b03199081166001600160a01b038085169190911790925560208401516004805483169184169190911790556040808501516003805484169185169190911790556060850151600580548416918516919091179055608085015160008054841691851691909117905560a085015160018054841691851691909117905560c085015160068054841691851691909117905560e0850151600780548416918516919091179055610100850151600880548416918516919091179055610120850151600280548416918516919091179055610140850151600980548416918516919091179055610160850151600d80548416918516919091179055610180850151600a805484169185169190911790556101a0850151600b80549093169316929092179055517f84861842e6a98dd759145eb5e7b48279d0f02ebeabc8a69e5196b2f4c5023db991610c0c9161513c565b60405180910390a17f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b18160200151604051610c47919061513c565b60405180910390a17fbf65195e6d5213f6fcbce65b1454c925197a45e616dabd2e243542b039b050928160600151604051610c82919061513c565b60405180910390a17f3ca631ffcd2a9b5d9ae18543fc82f58eb4ca33af9e6ab01b7a8e95331e6ed9858160600151604051610cbd919061513c565b60405180910390a17f78f058b189175430c48dc02699e3a0031ea4ff781536dc2fab847de4babdd8828160800151604051610cf8919061513c565b60405180910390a17f5ee0cae2f063ed938bb55046f6a932fb6ae792bf43624806bb90abe68a50be9b8160a00151604051610d33919061513c565b60405180910390a17f82966d27eea39b038ee0fa30cd16532bb24f6e65d31cb58fb227aa5766cdcc7f8160c00151604051610d6e919061513c565b60405180910390a17fcfb07d791fcafc032b35837b50eb84b74df518cf4cc287e8084f47630fa70fa08160e00151604051610da9919061513c565b60405180910390a17fe67f36a6e961157d6eff83b91f3af5a62131ceb6f04954ef74f51c1c05e7f88d816101000151604051610de5919061513c565b60405180910390a17f8c537274438aa850a330284665d81a85dd38267d09e4050d416bfc94142db264816101200151604051610e21919061513c565b60405180910390a17fcdf33850c44a1a874b5fefb51dae615ac6afebd581ef90d571f81b06541d8e9d816101400151604051610e5d919061513c565b60405180910390a17f65f4cf077bc01e4742eb5ad98326f6e95b63548ea24b17f8d5e823111fe78800816101600151604051610e99919061513c565b60405180910390a17f61e0c29d5028a9e4facaa476a46e78912e99f1ba945c9560b86b82ebe36ee52d816101800151604051610ed5919061513c565b60405180910390a17f7624075ed781e3aa34b0f492d70d1bb5a6fcde04534165e20f990d2e546be4dd816101a00151604051610f11919061513c565b60405180910390a150565b6004546040516000916060916001600160a01b0390911690610f41908490369061510f565b600060405180830381855af49150503d8060008114610f7c576040519150601f19603f3d011682016040523d82523d6000602084013e610f81565b606091505b5091509150818190610fa65760405162461bcd60e51b81526004016109ee91906151dc565b5050505050505050505050565b600281565b610fc0612407565b600054600154610fdd916001600160a01b03908116911683612433565b50565b600c546001600160a01b031681565b7f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba381565b600061101d612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff61254516565b6001600160a01b03851660009081526010602052604090208190559150505b92915050565b611073611f9d565b6001600160a01b0316336001600160a01b0316146110a35760405162461bcd60e51b81526004016109ee906153c7565b610fdd81612587565b60006110b6612407565b6110bf82612612565b6001600160801b031690505b919050565b6016602052600090815260409020805460019091015482565b60006110658261160b565b60006110fe612407565b611065826126ac565b6001600160a01b0381166000908152601660205260408120546014548290611135908363ffffffff61254516565b905080158061116e575060016001600160a01b03851660009081526010602052604090206003015460ff16600481111561116b57fe5b14155b1561117e576000925050506110cb565b6001600160a01b038416600090815260106020526040812060020154906111c3670de0b6b3a76400006111b7848663ffffffff61275f16565b9063ffffffff61279916565b9695505050505050565b6809c2007651b250000081565b80516111f85760405162461bcd60e51b81526004016109ee906153f8565b6000546001546006546001600160a01b0392831692918216911661121a614b2b565b611222614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561127257600080fd5b505af1158015611286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112aa9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b1580156112ec57600080fd5b505afa158015611300573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113249190614edc565b60208301528151611334906127db565b158015604084015261135c576113558585846000015185602001518a612876565b9050611374565b6113718585846000015185602001518a612b8b565b90505b60008160200151116113985760405162461bcd60e51b81526004016109ee906154d9565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad926113cc92600401615573565b600060405180830381600087803b1580156113e657600080fd5b505af11580156113fa573d6000803e3d6000fd5b5050505061141285858360c001518460e00151612ccb565b61010081015115611489576008546101008201516040516364a197f360e01b81526001600160a01b03888116936364a197f393611456939290911691600401615196565b600060405180830381600087803b15801561147057600080fd5b505af1158015611484573d6000803e3d6000fd5b505050505b611497858260400151612f2f565b60208101516060830152610100810151604082015182516114cf92916114c3919063ffffffff61254516565b9063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda94039461151794909290916155dc565b60405180910390a1611533853383606001518460400151613090565b505050505050565b600e5481565b6001600160a01b03811660009081526010602052604081206003015460ff16600481111561106557fe5b60006115756115e0565b905090565b6115838161316d565b6040805160018082528183019092526060916020808301908036833701905050905081816000815181106115b357fe5b60200260200101906001600160a01b031690816001600160a01b0316815250506115dc816111da565b5050565b6000611575600e546131b6565b600b546001600160a01b031681565b6001546001600160a01b031681565b6001600160a01b0381166000908152601660205260408120600101546015548290611135908363ffffffff61254516565b6000611065611649611b96565b83613253565b6001600160a01b031660009081526010602052604090206001015490565b60175490565b600080600061168185613271565b9150915060006111c38383876132f7565b6000611065826127db565b60035460408051635733d58f60e01b815290516000926001600160a01b031691635733d58f916004808301926020929190829003018186803b1580156116e257600080fd5b505afa1580156116f6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115759190614edc565b611722612407565b80600481111561172e57fe5b6001600160a01b0383166000908152601060205260409020600301805460ff1916600183600481111561175d57fe5b02179055505050565b600061106582611107565b611779612407565b6000611783613329565b9050670de0b6b3a764000081111561179757fe5b600e8190556040517fc454ee9b76c52f782a256af821b857ca6e125d1e3333bcede402fec2bed9600c906117cc90839061556a565b60405180910390a1610fdd61336d565b603c81565b60006110656116496123a4565b6001600160a01b031660009081526010602052604090206002015490565b611814614ba8565b506040805160e081018252600080546001600160a01b0390811683526001548116602084015292820181905260608201819052600d548316608083015260a0820181905260c082015260065490911661186b614b2b565b611873614b5c565b600260009054906101000a90046001600160a01b03166001600160a01b0316630fdb11cf6040518163ffffffff1660e01b8152600401602060405180830381600087803b1580156118c357600080fd5b505af11580156118d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118fb9190614edc565b826000018181525050826001600160a01b031663bdaf37ea6040518163ffffffff1660e01b815260040160206040518083038186803b15801561193d57600080fd5b505afa158015611951573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119759190614edc565b60208301528151611985906127db565b15801560408401526119ac576119a58483600001518460200151886133c2565b90506119cc565b6119c9846000015185602001518460000151856020015189613869565b90505b60008160200151116119f05760405162461bcd60e51b81526004016109ee906154d9565b608081015160a082015160405163335525ad60e01b81526001600160a01b0386169263335525ad92611a2492600401615573565b600060405180830381600087803b158015611a3e57600080fd5b505af1158015611a52573d6000803e3d6000fd5b50505050611a72846000015185602001518360c001518460e00151612ccb565b61010081015115611ae95783516008546101008301516040516364a197f360e01b81526001600160a01b03938416936364a197f393611ab693911691600401615196565b600060405180830381600087803b158015611ad057600080fd5b505af1158015611ae4573d6000803e3d6000fd5b505050505b611afb84600001518260400151612f2f565b6020810151606083015261010081015160408201518251611b2792916114c3919063ffffffff61254516565b608083018190526060808401516040808501519285015190517f4152c73dd2614c4f9fc35e8c9cf16013cd588c75b49a4c1673ecffdcbcda940394611b6f94909290916155dc565b60405180910390a1611b8f84600001513383606001518460400151613090565b5050505050565b6000611575611ba3613329565b613a1f565b60195481565b60106020526000908152604090208054600182015460028301546003909301549192909160ff81169061010090046001600160801b031685565b6004546001600160a01b031681565b6000611c01612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff613b0116565b6001600160a01b038516600090815260106020526040902060010181905591505092915050565b670de0b6b3a764000081565b6002546001600160a01b031681565b60178181548110611c7c57fe5b6000918252602090912001546001600160a01b0316905081565b6801158e460913d0000081565b60035460408051631e5395c960e21b815290516000926001600160a01b03169163794e5724916004808301926020929190829003018186803b1580156116e257600080fd5b60008054604080516272c7d360e71b8152905183926001600160a01b031691633963e980916004808301926020929190829003018186803b158015611d2c57600080fd5b505afa158015611d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d649190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b0316633963e9806040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b505afa158015611dca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dee9190614edc565b9050611e00828263ffffffff613b0116565b9250505090565b60185481565b600a546001600160a01b031681565b6000546001600160a01b031681565b60125481565b611e39612407565b610fdd81613b26565b611e4a611f9d565b6001600160a01b0316336001600160a01b031614611e7a5760405162461bcd60e51b81526004016109ee906153c7565b611e83816123c2565b600480546001600160a01b0319166001600160a01b0383161790556040517f6493041f605f233753d9b16dd158c9f297b8eaca22b2d0dc402a454f5683a5b190610f1190839061513c565b6000805460408051630a7b61df60e11b8152905183926001600160a01b0316916314f6c3be916004808301926020929190829003018186803b158015611f1357600080fd5b505afa158015611f27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f4b9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015611db657600080fd5b600080604051611fac9061511f565b6040519081900390205492915050565b60135481565b6004546040516000916060916001600160a01b0390911690611fe7908490369061510f565b600060405180830381855af49150503d8060008114612022576040519150601f19603f3d011682016040523d82523d6000602084013e612027565b606091505b509150915081819061204c5760405162461bcd60e51b81526004016109ee91906151dc565b50505050505050505050505050565b6000612065612407565b6001600160a01b038316600090815260106020526040812054611046908463ffffffff613b0116565b60145481565b6003546001600160a01b031681565b6040518060400160405280600c81526020016b2a3937bb32a6b0b730b3b2b960a11b81525081565b6006546001600160a01b031681565b600d546001600160a01b031681565b60008060006120f784613271565b9150915060006121078383613b83565b95945050505050565b6005546001600160a01b031681565b600061106582613bb8565b6001600160a01b03811660009081526010602052604081208054600190910154909180612156856110e9565b915061216185611766565b9050612173848363ffffffff613b0116565b9350612185838263ffffffff613b0116565b92509193509193565b6004546040516000916060916001600160a01b03909116906121b3908490369061510f565b600060405180830381855af49150503d80600081146121ee576040519150601f19603f3d011682016040523d82523d6000602084013e6121f3565b606091505b50915091508181906122185760405162461bcd60e51b81526004016109ee91906151dc565b50505050505050505050565b60155481565b60115481565b7f000000000000000000000000000000000000000000000000000000000012750081565b6000611575612261613329565b6131b6565b670ddd4b8c6c7d70d881565b61227a612407565b610fdd816002613be4565b60006122918383611673565b9392505050565b600f5481565b60006122a8612407565b6001600160a01b038316600090815260106020526040812060010154611c2d908463ffffffff61254516565b60006110656122e1612254565b83613cf9565b6001600160a01b031660009081526010602052604090205490565b6009546001600160a01b031681565b60006017828154811061232057fe5b6000918252602090912001546001600160a01b031692915050565b600060016001600160a01b03831660009081526010602052604090206003015460ff16600481111561236957fe5b14612376575060006110cb565b506014546001600160a01b03821660009081526016602052604090205410919050565b60006110658261233b565b6000611575600e54613a1f565b6123b9612407565b610fdd81613d39565b6001600160a01b0381166123e85760405162461bcd60e51b81526004016109ee906152a8565b803b806115dc5760405162461bcd60e51b81526004016109ee9061544e565b6005546001600160a01b031633146124315760405162461bcd60e51b81526004016109ee9061536a565b565b61243c8161233b565b156125405761244a8161316d565b600061245582611107565b905060006124628361160b565b6001600160a01b038416600090815260106020526040902060010154909150612491908363ffffffff613b0116565b6001600160a01b03841660009081526010602052604090206001810191909155546124c2908263ffffffff613b0116565b6001600160a01b0384166000908152601060205260409020556124e483613b26565b6124f085858385613d89565b6001600160a01b0383166000818152601060205260408082208054600182015460029092015492516000805160206156748339815191529461253594929392916151ba565b60405180910390a250505b505050565b600061229183836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613e71565b6001600160a01b0381166125ad5760405162461bcd60e51b81526004016109ee90615266565b806001600160a01b03166125bf611f9d565b6001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a360006040516126029061511f565b6040519081900390209190915550565b601780546001808201835560008381527fc624b66cc0138b8fabc209247f72d758e1cf3343756d543badbf24212bed8c1590920180546001600160a01b0319166001600160a01b0386161790559154909161266d9190612545565b6001600160a01b039290921660009081526010602052604090206003018054610100600160881b0319166101006001600160801b038516021790555090565b6001600160a01b03811660009081526010602052604081206001015481906126d390613e9d565b6001600160a01b03841660009081526010602052604090206002018054908290556011549192509061271d908390612711908463ffffffff61254516565b9063ffffffff613b0116565b60118190556040517f6bac5e0eb3c44eb03a60ab11ec3a2c051771616aecadbcfff2630aabae520382916127509161556a565b60405180910390a15092915050565b60008261276e57506000611065565b8282028284828161277b57fe5b04146122915760405162461bcd60e51b81526004016109ee90615329565b600061229183836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613ed9565b6000806127e783613bb8565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561283757600080fd5b505afa15801561284b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061286f9190614edc565b1192915050565b61287e614b5c565b612886614be4565b61288e614b5c565b848252600060808301526128a0611ce8565b60a08301526128ad611ece565b60c0830152600060208301525b835182602001511015612b8057838260200151815181106128d757fe5b6020908102919091018101516001600160a01b03166060840181905260009081526010909152604090206003015460019060ff16600481111561291657fe5b1461292057612b70565b61292e826060015187611673565b60408301526080820151612a9557600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561298a57600080fd5b505afa15801561299e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129c29190614edc565b8260400151101580156129d457508151155b156129de57612b70565b60006129f38360c001518460a00151896132f7565b9050612a108989856060015186604001518760000151868d613f10565b60808101518451919350612a2a919063ffffffff61254516565b8352608082015160a0840151612a459163ffffffff61254516565b60a08085019190915282015160c0840151612a659163ffffffff61254516565b60c0840152612a7484836142e6565b9350612a898360c001518460a00151896143fd565b15608084015250612b70565b81608001518015612b2e5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b158015612aef57600080fd5b505afa158015612b03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b279190614edc565b8260400151105b15612b7057612b4788888460600151856000015161449c565b60808101518351919250612b61919063ffffffff61254516565b8252612b6d83826142e6565b92505b60208201805160010190526128ba565b505095945050505050565b612b93614b5c565b612b9b614be4565b612ba3614b5c565b848252600060208301525b835182602001511015612b805783826020015181518110612bcb57fe5b60209081029190910101516001600160a01b031660608301819052612bf09087611673565b6040808401919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b158015612c3b57600080fd5b505afa158015612c4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c739190614edc565b82604001511015612cbb57612c9288888460600151856000015161449c565b60808101518351919250612cac919063ffffffff61254516565b8252612cb883826142e6565b92505b6020820180516001019052612bae565b81612cd557612f29565b601854600090612cf79061271184670de0b6b3a764000063ffffffff61275f16565b90506000612d1c601954612711670de0b6b3a76400008761275f90919063ffffffff16565b90506000612d356011548461279990919063ffffffff16565b90506000612d4e6011548461279990919063ffffffff16565b9050612d75612d686011548461275f90919063ffffffff16565b859063ffffffff61254516565b601855601154612d9d90612d9090839063ffffffff61275f16565b849063ffffffff61254516565b601955601454612db3908363ffffffff613b0116565b601455601554612dc9908263ffffffff613b0116565b60158190556014546040517f9f8bc8ab0daf5bceef75ecfd2085d1fcc6548c657ea970d9a23a60610d0737e392612e009291615573565b60405180910390a160405163121cbc4d60e11b81526001600160a01b03891690632439789a90612e3490899060040161556a565b600060405180830381600087803b158015612e4e57600080fd5b505af1158015612e62573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038a16925063f2e91d719150612e9290899060040161556a565b600060405180830381600087803b158015612eac57600080fd5b505af1158015612ec0573d6000803e3d6000fd5b50506040516364a197f360e01b81526001600160a01b038b1692506364a197f39150612ef2908a908990600401615196565b600060405180830381600087803b158015612f0c57600080fd5b505af1158015612f20573d6000803e3d6000fd5b50505050505050505b50505050565b6011546012819055506000826001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612f7357600080fd5b505afa158015612f87573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fab9190614edc565b90506000600160009054906101000a90046001600160a01b03166001600160a01b03166314f6c3be6040518163ffffffff1660e01b815260040160206040518083038186803b158015612ffd57600080fd5b505afa158015613011573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130359190614edc565b905061304b81612711848663ffffffff61254516565b60138190556012546040517f51bf4c63ec3cba9d03d43238abbdd979dd91bd16d9895c74ceea9118c7baaf60926130829291615573565b60405180910390a150505050565b811561310157600954600754604051631062c15f60e11b81526001600160a01b03928316926320c582be926130ce9291169087908790600401615150565b600060405180830381600087803b1580156130e857600080fd5b505af11580156130fc573d6000803e3d6000fd5b505050505b8015612f29576040516364a197f360e01b81526001600160a01b038516906364a197f3906131359086908590600401615196565b600060405180830381600087803b15801561314f57600080fd5b505af1158015613163573d6000803e3d6000fd5b5050505050505050565b60016001600160a01b03821660009081526010602052604090206003015460ff16600481111561319957fe5b14610fdd5760405162461bcd60e51b81526004016109ee9061551b565b600061106561324583600360009054906101000a90046001600160a01b03166001600160a01b03166328d28b5b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b505afa158015613221573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127119190614edc565b670de0b6b3a76400006145ce565b6000612291670de0b6b3a76400006111b7858563ffffffff61275f16565b600080600061327f84611107565b9050600061328c8561160b565b6001600160a01b038616600090815260106020526040812060010154919250906132bc908463ffffffff613b0116565b6001600160a01b038716600090815260106020526040812054919250906132e9908463ffffffff613b0116565b919550909350505050915091565b6000821561331e576000613315846111b7878663ffffffff61275f16565b91506122919050565b506000199392505050565b6000806133346145e4565b9050600061334a670ddd4b8c6c7d70d883614600565b9050611e00670de0b6b3a76400006111b783600e5461275f90919063ffffffff16565b6000613384600f544261254590919063ffffffff16565b9050603c8110610fdd5742600f8190556040517f860f8d2f0c74dd487e89e2883e3b25b8159ce1e1b3433a291cba7b82c508f3bc91610f119161556a565b6133ca614b5c565b6133d2614be4565b6133da614b5c565b848252600060808301526133ec611ce8565b60a08301526133f9611ece565b8260c001818152505086608001516001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b15801561343f57600080fd5b505afa158015613453573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134779190614cbb565b82606001906001600160a01b031690816001600160a01b031681525050600087608001516001600160a01b0316631e2231436040518163ffffffff1660e01b815260040160206040518083038186803b1580156134d357600080fd5b505afa1580156134e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061350b9190614cbb565b6000602085015290505b84836020015110801561353e5750806001600160a01b031683606001516001600160a01b031614155b1561385e5760808801516060840151604051632dc9c0eb60e21b81526000926001600160a01b03169163b72703ac9161357a919060040161513c565b60206040518083038186803b15801561359257600080fd5b505afa1580156135a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135ca9190614cbb565b90506135da846060015189611673565b6040850152608084015161375357600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561363657600080fd5b505afa15801561364a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061366e9190614edc565b84604001511015801561368057508351155b1561368b575061385e565b60006136a08560c001518660a001518b6132f7565b8a5160208c01516060880151604089015189519495506136c194868f613f10565b608081015186519195506136db919063ffffffff61254516565b8552608084015160a08601516136f69163ffffffff61254516565b8560a00181815250506137238461010001516114c38660a001518860c0015161254590919063ffffffff16565b60c086015261373286856142e6565b95506137478560c001518660a001518b6143fd565b15608086015250613840565b836080015180156137ec5750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156137ad57600080fd5b505afa1580156137c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137e59190614edc565b8460400151105b1561383a5761380d89600001518a602001518660600151876000015161449c565b60808101518551919450613827919063ffffffff61254516565b845261383385846142e6565b9450613840565b5061385e565b6001600160a01b031660608401526020830180516001019052613515565b505050949350505050565b613871614b5c565b613879614be4565b613881614b5c565b600d54858352600060208401526001600160a01b03165b8483602001511015613a1357806001600160a01b0316634d6228316040518163ffffffff1660e01b815260040160206040518083038186803b1580156138dd57600080fd5b505afa1580156138f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139159190614cbb565b6001600160a01b03166060840181905261392f9088611673565b6040808501919091526003548151631e5395c960e21b815291516001600160a01b039091169163794e5724916004808301926020929190829003018186803b15801561397a57600080fd5b505afa15801561398e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139b29190614edc565b836040015110156139fe576139d189898560600151866000015161449c565b608081015184519193506139eb919063ffffffff61254516565b83526139f784836142e6565b9350613a03565b613a13565b6020830180516001019052613898565b50505095945050505050565b6000611065613a7683600360009054906101000a90046001600160a01b03166001600160a01b031663f92d34336040518163ffffffff1660e01b815260040160206040518083038186803b15801561320d57600080fd5b600360009054906101000a90046001600160a01b03166001600160a01b031663240926696040518163ffffffff1660e01b815260040160206040518083038186803b158015613ac457600080fd5b505afa158015613ad8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613afc9190614edc565b6145ce565b6000828201838110156122915760405162461bcd60e51b81526004016109ee9061522f565b601480546001600160a01b038316600090815260166020526040908190209182556015546001909201829055915491517fc437f324d85e369394148dd9d62f98f534b382e01ed3dd2eb98138fb6d3ab49a92610f11929091615573565b60008115613baf57613ba8826111b78568056bc75e2d6310000063ffffffff61275f16565b9050611065565b50600019611065565b600080613bc3611ece565b90506000613bcf611ce8565b9050613bdc8282866132f7565b949350505050565b6000816004811115613bf257fe5b14158015613c0c57506001816004811115613c0957fe5b14155b613c1257fe5b601754613c1e816146ab565b6001600160a01b0383166000908152601060205260409020600301805483919060ff19166001836004811115613c5057fe5b02179055506001600160a01b0383166000908152601060209081526040808320600180820185905590849055601690925282208281550155613c928382614751565b600d54604051631484968760e11b81526001600160a01b03909116906329092d0e90613cc290869060040161513c565b600060405180830381600087803b158015613cdc57600080fd5b505af1158015613cf0573d6000803e3d6000fd5b50505050505050565b600080613d18670de0b6b3a76400006111b7868663ffffffff61275f16565b90508281106122915760405162461bcd60e51b81526004016109ee90615483565b6001600160a01b038116600090815260106020526040902060020154601154613d68908263ffffffff61254516565b601155506001600160a01b0316600090815260106020526040812060020155565b60405163121cbc4d60e11b81526001600160a01b03841690632439789a90613db590859060040161556a565b600060405180830381600087803b158015613dcf57600080fd5b505af1158015613de3573d6000803e3d6000fd5b505060405163f2e91d7160e01b81526001600160a01b038716925063f2e91d719150613e1390859060040161556a565b600060405180830381600087803b158015613e2d57600080fd5b505af1158015613e41573d6000803e3d6000fd5b505060405163be41205f60e01b81526001600160a01b038616925063be41205f915061313590849060040161556a565b60008184841115613e955760405162461bcd60e51b81526004016109ee91906151dc565b505050900390565b60008060135460001415613eb2575081611065565b600060125411613ebe57fe5b6122916013546111b76012548661275f90919063ffffffff16565b60008183613efa5760405162461bcd60e51b81526004016109ee91906151dc565b506000838581613f0657fe5b0495945050505050565b613f18614b5c565b613f20614c2c565b601754600110613f3057506142db565b613f398761212a565b60408501526020848101919091528401819052908352613f58906148f2565b604083018190526801158e460913d0000060608401526020830151613f829163ffffffff61254516565b8152670de0b6b3a7640000861161404d57613fa7898983602001518460400151613d89565b613fb087613d39565b60006080830181905260a0830152815160c0830152805160e0830152613fd7876003613be4565b815160208301516040516001600160a01b038a16926000805160206156548339815191529261400892600290615581565b60405180910390a2866001600160a01b03166000805160206156748339815191526000806000600260405161404094939291906151ba565b60405180910390a26142d9565b670de0b6b3a7640000861180156140e85750600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b1580156140ad57600080fd5b505afa1580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614edc565b86105b1561413857614101898983602001518460400151613d89565b61410a87613d39565b8151815161411991908761497e565b60e086015260c085015260a08401526080830152613fd7876003613be4565b600360009054906101000a90046001600160a01b03166001600160a01b031663794e57246040518163ffffffff1660e01b815260040160206040518083038186803b15801561418657600080fd5b505afa15801561419a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141be9190614edc565b86101580156141cc57508386105b80156141d9575081518510155b156142c8576141f2898983602001518460400151613d89565b846141f957fe5b61420287613d39565b61421582600001518360200151856149ea565b9150614222876003613be4565b6101008201511561429757600854610100830151604051633f10abab60e01b81526001600160a01b0390921691633f10abab91614264918b9190600401615196565b600060405180830381600087803b15801561427e57600080fd5b505af1158015614292573d6000803e3d6000fd5b505050505b815160a08301516040516001600160a01b038a16926000805160206156548339815191529261400892600290615581565b6142d0614b5c565b91506142db9050565b505b979650505050505050565b6142ee614b5c565b604080830151908401516143079163ffffffff613b0116565b6040820152606080830151908401516143259163ffffffff613b0116565b6060820152815160208401516143409163ffffffff613b0116565b602080830191909152820151835161435d9163ffffffff613b0116565b8152608080830151908401516143789163ffffffff613b0116565b608082015260a080830151908401516143969163ffffffff613b0116565b60a082015260c080830151908401516143b49163ffffffff613b0116565b60c082015260e080830151908401516143d29163ffffffff613b0116565b60e082015261010080830151908401516143f19163ffffffff613b0116565b61010082015292915050565b60008061440b8585856132f7565b9050600360009054906101000a90046001600160a01b03166001600160a01b0316635733d58f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561445b57600080fd5b505afa15801561446f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144939190614edc565b11949350505050565b6144a4614b5c565b6144ac614c2c565b6144b58461212a565b604085019081526020858101928352860192909252918452905190516144df918891889190613d89565b6144e884613d39565b6144f582602001516148f2565b604083018190526801158e460913d0000060608401526020830151600091614523919063ffffffff61254516565b90506145348360000151828661497e565b60e087015260c086015260a08501526080840152614553856003613be4565b825160208401516040516001600160a01b038816926000805160206156548339815191529261458492600190615581565b60405180910390a2846001600160a01b0316600080516020615674833981519152600080600060016040516145bc94939291906151ba565b60405180910390a25050949350505050565b60008183106145dd5781612291565b5090919050565b6000611575603c6111b7600f544261254590919063ffffffff16565b6000631f54050082111561461657631f54050091505b8161462a5750670de0b6b3a7640000611065565b670de0b6b3a764000083835b60018111156146a1576002810661466b576146518283614af8565b915061466481600263ffffffff61279916565b905061469c565b6146758284614af8565b92506146818283614af8565b915061469960026111b783600163ffffffff61254516565b90505b614636565b6111c38284614af8565b6001811180156147355750600d546040805163de8fa43160e01b815290516001926001600160a01b03169163de8fa431916004808301926020929190829003018186803b1580156146fb57600080fd5b505afa15801561470f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147339190614edc565b115b610fdd5760405162461bcd60e51b81526004016109ee906152df565b6001600160a01b03821660009081526010602052604081206003015460ff169081600481111561477d57fe5b141580156147975750600181600481111561479457fe5b14155b61479d57fe5b6001600160a01b03831660009081526010602052604081206003015461010090046001600160801b03169083906147d5826001612545565b905080836001600160801b031611156147ea57fe5b6000601782815481106147f957fe5b600091825260209091200154601780546001600160a01b03909216925082916001600160801b03871690811061482b57fe5b600091825260208083209190910180546001600160a01b0319166001600160a01b03948516179055918316815260109091526040908190206003018054610100600160881b0319166101006001600160801b03881602179055517f02b04ae5f7be9ca7c103293a2aa15f3c339d15d6eda53b721fef7b0e609c831a906148b49083908790615174565b60405180910390a160178054806148c757fe5b600082815260209020810160001990810180546001600160a01b031916905501905550505050505050565b600354604080516324386ecd60e11b815290516000926001600160a01b031691634870dd9a916004808301926020929190829003018186803b15801561493757600080fd5b505afa15801561494b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061496f9190614edc565b828161497757fe5b0492915050565b600080808084156149d45761499387866145ce565b93506149a9876111b7888763ffffffff61275f16565b92506149bb878563ffffffff61254516565b91506149cd868463ffffffff61254516565b90506149e1565b5060009250829150859050845b93509350935093565b6149f2614b5c565b838152602080820184905260035460408051631e5395c960e21b81529051600093614a8e9387936111b7936001600160a01b039092169263794e572492600480840193829003018186803b158015614a4957600080fd5b505afa158015614a5d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614a819190614edc565b889063ffffffff61275f16565b9050614a99816148f2565b604083018190526801158e460913d00000606084015260808301869052614ac790829063ffffffff61254516565b60a0830152614adc848263ffffffff61254516565b61010083015250600060c0820181905260e08201529392505050565b600080614b0b848463ffffffff61275f16565b9050613bdc670de0b6b3a76400006111b7836706f05b59d3b20000613b01565b6040518060a00160405280600081526020016000815260200160001515815260200160008152602001600081525090565b6040518061012001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040518060e0016040528060008152602001600081526020016000815260200160006001600160a01b0316815260200160001515815260200160008152602001600081525090565b60405180606001604052806000815260200160008152602001600081525090565b80356110658161563e565b60008083601f840112614c69578081fd5b50813567ffffffffffffffff811115614c80578182fd5b602083019150836020828501011115614c9857600080fd5b9250929050565b600060208284031215614cb0578081fd5b81356122918161563e565b600060208284031215614ccc578081fd5b81516122918161563e565b60008060408385031215614ce9578081fd5b8235614cf48161563e565b946020939093013593505050565b60006020808385031215614d14578182fd5b823567ffffffffffffffff811115614d2a578283fd5b80840185601f820112614d3b578384fd5b80359150614d50614d4b8361561e565b6155f7565b8281528381019082850185850284018601891015614d6c578687fd5b8693505b848410156142d957614d828982614c4d565b835260019390930192918501918501614d70565b60006101c0808385031215614da9578182fd5b614db2816155f7565b614dbc8585614c4d565b8152614dcb8560208601614c4d565b6020820152614ddd8560408601614c4d565b6040820152614def8560608601614c4d565b6060820152614e018560808601614c4d565b6080820152614e138560a08601614c4d565b60a0820152614e258560c08601614c4d565b60c0820152614e378560e08601614c4d565b60e08201526101009150614e4d85838601614c4d565b828201526101209150614e6285838601614c4d565b828201526101409150614e7785838601614c4d565b828201526101609150614e8c85838601614c4d565b828201526101809150614ea185838601614c4d565b828201526101a09150614eb685838601614c4d565b918101919091529392505050565b600060208284031215614ed5578081fd5b5035919050565b600060208284031215614eed578081fd5b5051919050565b600080600080600080600060e0888a031215614f0e578283fd5b873596506020880135614f208161563e565b95506040880135614f308161563e565b94506060880135614f408161563e565b9699959850939660808101359560a0820135955060c0909101359350915050565b600080600080600080600080888a03610160811215614f7e578182fd5b8935985060208a0135614f908161563e565b975060408a0135614fa08161563e565b965060608a0135614fb08161563e565b955060808a810135955060a08b0135945060c08b0135935060df1982011215614fd7578182fd5b5060e0890190509295985092959890939650565b6000806000806000806000806000808a8c0361018081121561500b578485fd5b8b359a5060208c013561501d8161563e565b995060408c013561502d8161563e565b985060608c013561503d8161563e565b975060808c810135975060a08d0135965060c08d0135955060df19820190811215615066578384fd5b61507060606155f7565b9150604081121561507f578384fd5b5061508a60406155f7565b60e08d01356150988161563e565b81526101008d01356020808301919091529082526101208d0135908201526101408c0135604082015292506101608b013567ffffffffffffffff8111156150dd578283fd5b6150e98d828e01614c58565b8194508093505050509295989b9194979a5092959850565b6004811061510b57fe5b9052565b6000828483379101908152919050565b7035b2bc9737bbb730b136329737bbb732b960791b815260110190565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b039290921682526001600160801b0316602082015260400190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b8481526020810184905260408101839052608081016121076060830184615101565b6000602080835283518082850152825b81811015615208578581018301518582016040015282016151ec565b818111156152195783604083870101525b50601f01601f1916929092016040019392505050565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526022908201527f4f776e61626c653a3a7365744f776e65723a20696e76616c6964206164647265604082015261737360f01b606082015260800190565b6020808252601e908201527f4163636f756e742063616e6e6f74206265207a65726f20616464726573730000604082015260600190565b6020808252602a908201527f54726f76654d616e616765723a204f6e6c79206f6e652074726f766520696e206040820152697468652073797374656d60b01b606082015260800190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252603b908201527f54726f76654d616e616765723a2043616c6c6572206973206e6f74207468652060408201527f426f72726f7765724f7065726174696f6e7320636f6e74726163740000000000606082015260800190565b60208082526017908201527613dddb98589b194e8e881858d8d95cdcc819195b9a5959604a1b604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2043616c6c646174612061646472657373206160408201527572726179206d757374206e6f7420626520656d70747960501b606082015260800190565b6020808252818101527f4163636f756e7420636f64652073697a652063616e6e6f74206265207a65726f604082015260600190565b60208082526036908201527f54726f76654d616e616765723a2046656520776f756c642065617420757020616040820152751b1b081c995d1d5c9b99590818dbdb1b185d195c985b60521b606082015260800190565b60208082526022908201527f54726f76654d616e616765723a206e6f7468696e6720746f206c697175696461604082015261746560f01b606082015260800190565b6020808252602f908201527f54726f76654d616e616765723a2054726f766520646f6573206e6f742065786960408201526e1cdd081bdc881a5cc818db1bdcd959608a1b606082015260800190565b90815260200190565b918252602082015260400190565b8381526020810183905260608101613bdc6040830184615101565b858152602081018590526040810184905260a08101600584106155bb57fe5b60608201939093526001600160801b03919091166080909101529392505050565b93845260208401929092526040830152606082015260800190565b60405181810167ffffffffffffffff8111828210171561561657600080fd5b604052919050565b600067ffffffffffffffff821115615634578081fd5b5060209081020190565b6001600160a01b0381168114610fdd57600080fdfeea67486ed7ebe3eea8ab3390efd4a3c8aae48be5bea27df104a8af786c408434c3770d654ed33aeea6bf11ac8ef05d02a6a04ed4686dd2f624d853bbec43cc8ba2646970667358221220160726666ba9181d0cbf2304c477c7f0275025dff3b8c1ef566a2dd6bfd6b69d64736f6c634300060b0033" +} diff --git a/hardhat.config.release.cjs b/hardhat.config.release.cjs new file mode 100644 index 0000000..a6ad778 --- /dev/null +++ b/hardhat.config.release.cjs @@ -0,0 +1,36 @@ +require("@nomicfoundation/hardhat-ethers"); +require("@nomicfoundation/hardhat-chai-matchers"); +require("@nomiclabs/hardhat-web3"); +require("@nomiclabs/hardhat-truffle5"); + +const forkUrl = process.env.ZERO_RELEASE_FORK_RPC; +const forkBlock = Number(process.env.ZERO_RELEASE_FORK_BLOCK); +if (forkUrl && (!Number.isSafeInteger(forkBlock) || forkBlock <= 0)) { + throw new Error("ZERO_RELEASE_FORK_BLOCK must explicitly pin the fork"); +} +if (!forkUrl && process.env.ZERO_RELEASE_FORK_BLOCK) { + throw new Error("ZERO_RELEASE_FORK_RPC is required with a fork block"); +} + +module.exports = { + solidity: { + compilers: ["0.4.23", "0.5.17", "0.6.11", "0.8.17"].map((version) => ({ + version, + settings: { + optimizer: { enabled: true, runs: 100 }, + outputSelection: { "*": { "*": ["storageLayout"] } }, + }, + })), + }, + networks: { + hardhat: { + accounts: require(forkUrl + ? "./hardhatAccountsList20.js" + : "./hardhatAccountsList2k.js").accountsList, + initialBaseFeePerGas: 0, + ...(forkUrl ? { forking: { url: forkUrl, blockNumber: forkBlock } } : {}), + }, + }, + paths: { tests: "./tests-release" }, + mocha: { timeout: 240000 }, +}; diff --git a/hardhat.config.ts b/hardhat.config.ts index 90d976d..652b664 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -162,10 +162,11 @@ const config: HardhatUserConfig = { runs: 100, }, // Emit per-contract storageLayout so the Perimeter storage-layout - // zero-diff regression (tests-perimeter/StorageLayout.zerodiff.test.js) - // can assert the surplus-claim fee hook adds NO state to the - // upgradeable BorrowerOperations / CollSurplusPool proxies or - // ActivePool. Additive solc output; does not affect bytecode. + // regression (tests-perimeter/StorageLayout.zerodiff.test.js) + // validates unchanged existing proxy slots and the appended + // BorrowerOperations companion pointer. Controller/queue + // pointers live in unstructured slots. Additive solc output; + // does not affect bytecode. outputSelection: { "*": { "*": ["storageLayout"], diff --git a/package.json b/package.json index 5675005..a5ba3e2 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "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", - "hh:fork-mainnet": "yarn hardhat node --fork https://mainnet-dev.sovryn.app/rpc --no-deploy", + "hh:fork-testnet": "yarn hardhat node --fork https://testnet.sovryn.app/rpc --no-deploy --no-reset", + "hh:fork-mainnet": "yarn hardhat node --fork https://mainnet-dev.sovryn.app/rpc --no-deploy --no-reset", "hh:console-mainnet": "hardhat console --network rskSovrynMainnet", "hh:console-testnet": "hardhat console --network rskSovrynTestnet" }, diff --git a/scripts/helpers/helpers.ts b/scripts/helpers/helpers.ts index 62d8cf2..b7569e8 100644 --- a/scripts/helpers/helpers.ts +++ b/scripts/helpers/helpers.ts @@ -16,6 +16,11 @@ import { import { TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; import { Address } from "hardhat-deploy/types"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { + assertZeroDelayProxyCandidate, + resolveZeroDelayDeploymentNetwork, + preflightZeroDelayDeployment, +} from "./utils"; import { GovernorAlpha, MultiSigWallet } from "types/generated"; import Logs from "node-logs"; const logger = new Logs().showInConsole(true); @@ -344,6 +349,8 @@ const deployWithCustomProxy = async ( args: any[] = [], proxyArgs: any[] = [] ) => { + assertZeroDelayProxyCandidate(await resolveZeroDelayDeploymentNetwork(hre), logicName); + await preflightZeroDelayDeployment(hre); const { deployments: { deploy, get, getOrNull, log, save: deploymentsSave }, ethers, diff --git a/scripts/helpers/utils.ts b/scripts/helpers/utils.ts index ede6a7e..0c9c5ec 100644 --- a/scripts/helpers/utils.ts +++ b/scripts/helpers/utils.ts @@ -6,6 +6,122 @@ const getContractNameFromScriptFileName = (filename) => { return filename.substring(filename.lastIndexOf("-") + 1, filename.lastIndexOf(".")); }; +const assertPerimeterDeploymentNetwork = (network: HardhatRuntimeEnvironment["network"]) => { + if (network.tags.testnet && network.tags.mainnet) { + throw new Error("PerimeterOps: conflicting mainnet/testnet network tags"); + } + if (!network.tags.testnet && !network.tags.mainnet) { + const url = "url" in network.config ? network.config.url : undefined; + const localEndpoint = + (network.name === "localhost" || network.name === "rskdev") && + typeof url === "string" && + ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname); + if (network.name !== "hardhat" && !localEndpoint) { + throw new Error("PerimeterOps: explicit mainnet/testnet network tags are required"); + } + } +}; + +const ZERO_DELAY_RELEASE_CANDIDATES = [ + "BorrowerOperations", + "CollSurplusPool", + "TroveManager", + "BorrowerOperationsPerimeterOps", +]; + +const isZeroDelayReleaseNetwork = (network: HardhatRuntimeEnvironment["network"]) => + Boolean( + network.tags.mainnet || + network.tags.testnet || + network.tags.forked || + ("forking" in network.config && + network.config.forking?.url && + network.config.forking.enabled !== false) + ); + +const resolveZeroDelayDeploymentNetwork = async (hre: HardhatRuntimeEnvironment) => { + assertPerimeterDeploymentNetwork(hre.network); + if (!["hardhat", "localhost", "rskdev"].includes(hre.network.name) || !hre.network.provider) + return hre.network; + let metadata; + try { + metadata = await hre.network.provider.send("hardhat_metadata"); + } catch (error) { + if (hre.network.name !== "hardhat" && (error as { code?: number }).code === -32601) + return hre.network; + throw error; + } + return metadata.forkedNetwork + ? { ...hre.network, tags: { ...hre.network.tags, forked: true } } + : hre.network; +}; + +const assertZeroDelayDeploymentEnvironment = (network: HardhatRuntimeEnvironment["network"]) => { + if (isZeroDelayReleaseNetwork(network) && process.env.HARDHAT_DEPLOY_FIXTURE) { + throw new Error("Zero delay release: implicit deployment fixtures are not permitted"); + } +}; + +const assertZeroDelayProxyCandidate = ( + network: HardhatRuntimeEnvironment["network"], + name: string +) => { + assertZeroDelayDeploymentCandidate(network, name); + if ( + isZeroDelayReleaseNetwork(network) && + !ZERO_DELAY_RELEASE_CANDIDATES.slice(0, 3).includes(name) + ) { + throw new Error(`Zero delay release: ${name} is not an approved proxy candidate`); + } +}; + +const assertZeroDelayDeploymentCandidate = ( + network: HardhatRuntimeEnvironment["network"], + name: string +) => { + assertPerimeterDeploymentNetwork(network); + assertZeroDelayDeploymentEnvironment(network); + if (isZeroDelayReleaseNetwork(network) && !ZERO_DELAY_RELEASE_CANDIDATES.includes(name)) { + throw new Error(`Zero delay release: ${name} is not an approved candidate`); + } +}; + +const assertZeroDelayDeploymentSelection = ( + network: HardhatRuntimeEnvironment["network"], + options: { tags?: string; reset?: boolean; tagsRequireAll?: boolean } +) => { + assertPerimeterDeploymentNetwork(network); + assertZeroDelayDeploymentEnvironment(network); + if (!isZeroDelayReleaseNetwork(network)) return; + const selected = options.tags?.split(",").filter(Boolean) || []; + if (options.reset || options.tagsRequireAll || selected.length === 0) { + throw new Error( + "Zero delay release: explicit candidate tags without reset or require-all are required" + ); + } + for (const name of selected) assertZeroDelayDeploymentCandidate(network, name); +}; + +const preflightZeroDelayDeployment = async (hre: HardhatRuntimeEnvironment) => { + const network = await resolveZeroDelayDeploymentNetwork(hre); + assertPerimeterDeploymentNetwork(network); + assertZeroDelayDeploymentEnvironment(network); + if (!isZeroDelayReleaseNetwork(network)) return; + for (const name of ZERO_DELAY_RELEASE_CANDIDATES.slice(0, 3)) { + const proxy = await hre.deployments.getOrNull(`${name}_Proxy`); + if (!proxy) + throw new Error(`Zero delay release: existing proxy record required for ${name}`); + if ((await hre.ethers.provider.getCode(proxy.address)) === "0x") { + throw new Error(`Zero delay release: existing proxy code missing for ${name}`); + } + const contract = await hre.ethers.getContractAt("UpgradableProxy", proxy.address); + const implementation = await contract.getImplementation(); + if ((await hre.ethers.provider.getCode(implementation)) === "0x") { + throw new Error(`Zero delay release: implementation code missing for ${name}`); + } + } +}; + const arrayToUnique = (value, index, self) => { return self.indexOf(value) === index; }; @@ -27,4 +143,17 @@ const logTimer = (time, passedTime) => { process.stdout.write(hoursStr + ":" + minutesStr + ":" + secondsStr); }; -export { getContractNameFromScriptFileName, arrayToUnique, logTimer, delay }; +export { + getContractNameFromScriptFileName, + assertPerimeterDeploymentNetwork, + ZERO_DELAY_RELEASE_CANDIDATES, + isZeroDelayReleaseNetwork, + resolveZeroDelayDeploymentNetwork, + assertZeroDelayProxyCandidate, + assertZeroDelayDeploymentCandidate, + assertZeroDelayDeploymentSelection, + preflightZeroDelayDeployment, + arrayToUnique, + logTimer, + delay, +}; diff --git a/tasks/index.ts b/tasks/index.ts index 6feb50e..b24cc44 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -1,3 +1,4 @@ import "./sips.ts"; import "./multisig.ts"; import "./params.ts"; +import "./zeroDelayRelease"; diff --git a/tasks/zeroDelayRelease.ts b/tasks/zeroDelayRelease.ts new file mode 100644 index 0000000..7ec1cc3 --- /dev/null +++ b/tasks/zeroDelayRelease.ts @@ -0,0 +1,64 @@ +import { extendEnvironment, subtask, task } from "hardhat/config"; +import { + assertZeroDelayDeploymentSelection, + preflightZeroDelayDeployment, + ZERO_DELAY_RELEASE_CANDIDATES, + resolveZeroDelayDeploymentNetwork, + isZeroDelayReleaseNetwork, +} from "../scripts/helpers/utils"; + +const guardedDeploy = async (args, hre, runSuper) => { + assertZeroDelayDeploymentSelection(await resolveZeroDelayDeploymentNetwork(hre), args); + await preflightZeroDelayDeployment(hre); + return runSuper(args); +}; + +task("deploy").setAction(guardedDeploy); +subtask("deploy:main").setAction(guardedDeploy); +subtask("deploy:runDeploy").setAction(guardedDeploy); + +task("node").setAction(async (args, hre, runSuper) => { + const network = await resolveZeroDelayDeploymentNetwork(hre); + const forkNetwork = args.fork + ? { ...network, tags: { ...network.tags, forked: true } } + : network; + if (isZeroDelayReleaseNetwork(forkNetwork)) { + if (!args.noReset) throw new Error("Zero delay release: fork node requires no-reset"); + if (!args.noDeploy) assertZeroDelayDeploymentSelection(forkNetwork, args); + } + return runSuper(args); +}); + +extendEnvironment((hre) => { + const runDeployments = hre.deployments.run.bind(hre.deployments); + const runFixture = hre.deployments.fixture.bind(hre.deployments); + hre.deployments.run = async (tags, options) => { + const selected = Array.isArray(tags) ? tags.join(",") : tags; + const network = await resolveZeroDelayDeploymentNetwork(hre); + assertZeroDelayDeploymentSelection(network, { + tags: selected, + reset: options?.deletePreviousDeployments, + }); + await preflightZeroDelayDeployment(hre); + if (isZeroDelayReleaseNetwork(network)) { + if (options?.resetMemory === true) { + throw new Error("Zero delay release: deployment memory reset is not permitted"); + } + return runDeployments(selected.split(","), { ...options, resetMemory: false }); + } + return runDeployments(tags, options); + }; + hre.deployments.fixture = async (tags, options) => { + if (isZeroDelayReleaseNetwork(await resolveZeroDelayDeploymentNetwork(hre))) { + throw new Error("Zero delay release: deployment fixtures are not permitted"); + } + return runFixture(tags, options); + }; +}); + +task( + "zero:stage-delay", + "Stage only the four Perimeter Delay candidates; keep existing proxies" +).setAction(async (_, hre) => + hre.run("deploy", { tags: ZERO_DELAY_RELEASE_CANDIDATES.join(",") }) +); diff --git a/tests-perimeter/Deployment.perimeterOps.test.js b/tests-perimeter/Deployment.perimeterOps.test.js new file mode 100644 index 0000000..290dbdb --- /dev/null +++ b/tests-perimeter/Deployment.perimeterOps.test.js @@ -0,0 +1,281 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); +const ts = require("typescript"); +const { Interface } = require("ethers"); + +const scriptPath = path.resolve( + __dirname, + "../deployment/deploy/9-BorrowerOperationsPerimeterOps.ts" +); + +function loadModule(filename, imports, messages) { + const compiled = ts.transpileModule(fs.readFileSync(filename, "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, esModuleInterop: true }, + }).outputText; + const module = { exports: {} }; + vm.runInNewContext(compiled, { + module, + exports: module.exports, + __filename: filename, + URL, + process: { env: {} }, + console: { log: (message) => messages.push(message) }, + require: (specifier) => { + assert.ok( + Object.hasOwn(imports, specifier), + `Unexpected deployment import: ${specifier}` + ); + return imports[specifier]; + }, + }); + return module.exports; +} + +function deploymentFixture({ + name = "hardhat", + tags = {}, + url, + pointer = "0x0000000000000000000000000000000000000000", + getterError, + newlyDeployed = true, +} = {}) { + const calls = []; + const messages = []; + const candidate = "0x0000000000000000000000000000000000000011"; + const proxy = "0x0000000000000000000000000000000000000022"; + const multisig = "0x0000000000000000000000000000000000000033"; + const deployer = "0x0000000000000000000000000000000000000044"; + const borrowerOperations = { + target: proxy, + interface: new Interface(["function setPerimeterOps(address)"]), + getFunction: (name) => { + assert.equal(name, "perimeterOps"); + return borrowerOperations.perimeterOps; + }, + perimeterOps: async () => { + calls.push("getter"); + if (getterError) throw getterError; + return pointer; + }, + }; + class TestLogs { + showInConsole() { + return this; + } + information(message) { + messages.push(message); + } + info(message) { + messages.push(message); + } + } + const imports = { + path, + "node-logs": TestLogs, + "../../scripts/helpers/utils": loadModule( + path.resolve(__dirname, "../scripts/helpers/utils.ts"), + {}, + messages + ), + "../../scripts/helpers/helpers": { + sendWithMultisig: async (...args) => calls.push(["multisig", ...args.slice(1)]), + deployWithCustomProxy: async () => calls.push(["borrowerUpgrade"]), + }, + }; + const companionModule = loadModule(scriptPath, imports, messages); + const borrowerModule = loadModule( + path.resolve(__dirname, "../deployment/deploy/1-BorrowerOperations.ts"), + imports, + messages + ); + const hre = { + network: { name, tags, config: { url } }, + getNamedAccounts: async () => ({ deployer }), + ethers: { + getContract: async () => borrowerOperations, + getContractAt: async () => ({ getImplementation: async () => candidate }), + provider: { getCode: async () => "0x6000" }, + }, + deployments: { + getOrNull: async () => ({ address: proxy }), + get: async (deploymentName) => ({ + address: deploymentName === "MultiSigWallet" ? multisig : candidate, + }), + deploy: async (deploymentName, options) => { + calls.push(["deploy", deploymentName, options.from]); + return { address: candidate, newlyDeployed }; + }, + log: (message) => messages.push(message), + execute: async (...args) => calls.push(["execute", ...args]), + }, + }; + return { + run: () => companionModule.default(hre), + runWithDependency: async () => { + await borrowerModule.default(hre); + await companionModule.default(hre); + }, + calls, + messages, + candidate, + proxy, + multisig, + }; +} + +const missingGetter = () => + Object.assign(new Error("execution reverted"), { + code: "CALL_EXCEPTION", + data: "0x", + }); + +describe("Perimeter companion deployment safety", () => { + it("mainnet can deploy against the legacy proxy without calling a new getter or wiring it", async () => { + const fixture = deploymentFixture({ + name: "rskSovrynMainnet", + tags: { mainnet: true }, + getterError: missingGetter(), + }); + await fixture.run(); + assert.equal(fixture.calls.length, 1); + assert.equal(fixture.calls[0][0], "deploy"); + assert.ok(fixture.messages.some((message) => message.includes("setPerimeterOps"))); + assert.ok(fixture.messages.some((message) => message.includes(fixture.candidate))); + }); + + it("mainnet can reuse a candidate while its implementation upgrade remains pending", async () => { + const fixture = deploymentFixture({ + name: "rskSovrynMainnet", + tags: { mainnet: true, forked: true }, + getterError: missingGetter(), + newlyDeployed: false, + }); + await fixture.run(); + assert.equal(fixture.calls.length, 1); + }); + + it("testnet defers wiring until the implementation exposes the hook getter", async () => { + const fixture = deploymentFixture({ + name: "rskSovrynTestnet", + tags: { testnet: true }, + getterError: missingGetter(), + }); + await fixture.run(); + assert.equal(fixture.calls.length, 2); + assert.equal(fixture.calls[1], "getter"); + assert.ok(fixture.messages.some((message) => message.includes("upgrade"))); + }); + + it("testnet treats empty getter data as an unwired legacy implementation", async () => { + const fixture = deploymentFixture({ + name: "rskSovrynTestnet", + tags: { testnet: true }, + getterError: Object.assign(new Error("could not decode result data"), { + code: "BAD_DATA", + value: "0x", + }), + }); + await fixture.run(); + assert.equal(fixture.calls.length, 2); + }); + + it("upgraded testnet submits the setter to multisig with the proxy and candidate addresses", async () => { + const fixture = deploymentFixture({ name: "rskSovrynTestnet", tags: { testnet: true } }); + await fixture.run(); + const submission = fixture.calls.find((call) => call[0] === "multisig"); + assert.ok(submission); + assert.equal(submission[1], fixture.multisig); + assert.equal(submission[2], fixture.proxy); + const data = new Interface(["function setPerimeterOps(address)"]); + assert.equal( + data.decodeFunctionData("setPerimeterOps", submission[3])[0], + fixture.candidate + ); + assert.ok(!fixture.calls.some((call) => call[0] === "execute")); + }); + + it("an existing matching hook needs no setter transaction", async () => { + const fixture = deploymentFixture({ + pointer: "0x0000000000000000000000000000000000000011", + newlyDeployed: false, + }); + await fixture.run(); + assert.equal(fixture.calls.length, 2); + }); + + it("the local Hardhat network still wires the new companion", async () => { + const fixture = deploymentFixture(); + await fixture.run(); + const execution = fixture.calls.find((call) => call[0] === "execute"); + assert.ok(execution); + assert.equal(execution[1], "BorrowerOperations"); + assert.equal(execution[3], "setPerimeterOps"); + assert.equal(execution[4], fixture.candidate); + }); + + it("rejects conflicting production tags before deploying anything", async () => { + const fixture = deploymentFixture({ tags: { mainnet: true, testnet: true } }); + await assert.rejects(fixture.run, /conflicting/); + assert.equal(fixture.calls.length, 0); + }); + + it("rejects an untagged remote network before deploying or setting a hook", async () => { + const fixture = deploymentFixture({ name: "mainnet", url: "https://example.invalid" }); + await assert.rejects(fixture.run, /network tags/); + assert.equal(fixture.calls.length, 0); + }); + + it("unexpected getter errors abort rather than submitting a setter", async () => { + const error = Object.assign(new Error("RPC unavailable"), { code: "NETWORK_ERROR" }); + const fixture = deploymentFixture({ + name: "rskSovrynTestnet", + tags: { testnet: true }, + getterError: error, + }); + await assert.rejects(fixture.run, (observed) => observed === error); + assert.equal(fixture.calls.length, 2); + }); + + it("a loopback Rootstock development node can wire directly", async () => { + const fixture = deploymentFixture({ name: "rskdev", url: "http://localhost:4444" }); + await fixture.run(); + assert.ok(fixture.calls.some((call) => call[0] === "execute")); + }); + + it("a network named localhost cannot wire directly through a remote endpoint", async () => { + const fixture = deploymentFixture({ name: "localhost", url: "https://example.invalid" }); + await assert.rejects(fixture.run, /network tags/); + assert.equal(fixture.calls.length, 0); + }); + + it("a legacy local getter failure never falls back to a direct setter", async () => { + const error = missingGetter(); + const fixture = deploymentFixture({ getterError: error }); + await assert.rejects(fixture.run, (observed) => observed === error); + assert.equal(fixture.calls.length, 2); + }); + + it("a tagged testnet fork retains the multisig path", async () => { + const fixture = deploymentFixture({ + name: "testnetFork", + tags: { testnet: true, forked: true }, + }); + await fixture.run(); + assert.ok(fixture.calls.some((call) => call[0] === "multisig")); + assert.ok(!fixture.calls.some((call) => call[0] === "execute")); + }); + + it("rejects an untagged remote network before the BorrowerOperations dependency can upgrade", async () => { + const fixture = deploymentFixture({ name: "mainnet", url: "https://example.invalid" }); + await assert.rejects(fixture.runWithDependency, /network tags/); + assert.equal(fixture.calls.length, 0); + }); + + it("rejects conflicting tags before the BorrowerOperations dependency can deploy", async () => { + const fixture = deploymentFixture({ tags: { mainnet: true, testnet: true } }); + await assert.rejects(fixture.runWithDependency, /conflicting/); + assert.equal(fixture.calls.length, 0); + }); +}); diff --git a/tests-perimeter/Deployment.releaseScope.test.js b/tests-perimeter/Deployment.releaseScope.test.js new file mode 100644 index 0000000..14278f6 --- /dev/null +++ b/tests-perimeter/Deployment.releaseScope.test.js @@ -0,0 +1,476 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); +const ts = require("typescript"); + +const filename = path.resolve(__dirname, "../scripts/helpers/utils.ts"); +const compiled = ts.transpileModule(fs.readFileSync(filename, "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS }, +}).outputText; +const loaded = { exports: {} }; +const environment = { env: {} }; +vm.runInNewContext(compiled, { + module: loaded, + exports: loaded.exports, + URL, + process: environment, +}); +const utils = loaded.exports; +const tags = [ + "BorrowerOperations", + "CollSurplusPool", + "TroveManager", + "BorrowerOperationsPerimeterOps", +]; +const release = { name: "rskSovrynMainnet", tags: { mainnet: true }, config: {} }; + +function loadScript(script, imports) { + const filename = path.resolve(__dirname, "..", script); + const compiled = ts.transpileModule(fs.readFileSync(filename, "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, esModuleInterop: true }, + }).outputText; + const loaded = { exports: {} }; + vm.runInNewContext(compiled, { + module: loaded, + exports: loaded.exports, + __filename: filename, + URL, + require: (specifier) => { + assert.ok(Object.hasOwn(imports, specifier), `Unexpected import: ${specifier}`); + return imports[specifier]; + }, + console: { log: () => {} }, + process: environment, + }); + return loaded.exports; +} + +class Logs { + showInConsole() { + return this; + } + information() {} + info() {} +} + +const helper = () => + loadScript("scripts/helpers/helpers.ts", { + "node-logs": Logs, + "./utils": utils, + }); + +function fixture(missing, emptyCode) { + const calls = []; + const address = "0x0000000000000000000000000000000000000011"; + return { + calls, + network: release, + deployments: { + fixture: async () => { + throw new Error("FIXTURE_ACTION_REACHED"); + }, + getOrNull: async (name) => { + calls.push(name); + return name === missing ? null : { address }; + }, + }, + ethers: { + provider: { getCode: async () => (emptyCode ? "0x" : "0x6000") }, + getContractAt: async () => ({ getImplementation: async () => address }), + }, + }; +} + +describe("Zero delay deployment frozen scope", function () { + for (const networkTags of [ + { mainnet: true }, + { testnet: true }, + { mainnet: true, forked: true }, + ]) { + it(`rejects broad, forbidden, reset and require-all selection for ${JSON.stringify( + networkTags + )}`, function () { + const network = { ...release, tags: networkTags }; + for (const options of [ + {}, + { tags: "ZUSDToken" }, + { tags: tags.join(","), reset: true }, + { tags: tags.join(","), tagsRequireAll: true }, + ]) { + assert.throws( + () => utils.assertZeroDelayDeploymentSelection(network, options), + /Zero delay release/ + ); + } + utils.assertZeroDelayDeploymentSelection(network, { tags: tags.join(",") }); + }); + } + + for (const name of [ + "StabilityPool", + "TroveManagerRedeemOps", + "ZUSDToken", + "CommunityIssuance", + "PriceFeed", + ]) { + it(`rejects programmatic deployment of excluded ${name} before artifact comparison`, function () { + assert.throws( + () => utils.assertZeroDelayDeploymentCandidate(release, name), + /not an approved candidate/ + ); + }); + } + + for (const name of tags.slice(0, 3)) { + it(`rejects missing ${name} proxy during the all-candidate preflight`, async function () { + await assert.rejects( + utils.preflightZeroDelayDeployment(fixture(`${name}_Proxy`)), + /existing proxy/ + ); + }); + } + + it("rejects missing live proxy code", async function () { + await assert.rejects(utils.preflightZeroDelayDeployment(fixture(undefined, true)), /code/); + }); + + it("preflights all three existing proxies without creating or upgrading them", async function () { + const hre = fixture(); + await utils.preflightZeroDelayDeployment(hre); + assert.deepEqual( + hre.calls, + tags.slice(0, 3).map((name) => `${name}_Proxy`) + ); + }); + + it("keeps genuinely fresh internal local deployment available", async function () { + const local = { name: "hardhat", tags: {}, config: {} }; + utils.assertZeroDelayDeploymentSelection(local, { reset: true }); + utils.assertZeroDelayDeploymentCandidate(local, "ZUSDToken"); + const hre = fixture("BorrowerOperations_Proxy"); + hre.network = local; + await utils.preflightZeroDelayDeployment(hre); + assert.deepEqual(hre.calls, []); + }); + + it("rejects excluded deployment on an untagged configured Hardhat fork", async function () { + const hre = fixture(); + hre.network = { + name: "hardhat", + tags: {}, + config: { forking: { url: "https://example.invalid" } }, + }; + hre.deployments.deploy = async () => { + throw new Error("DEPLOY_ACTION_REACHED"); + }; + await assert.rejects( + helper().deployWithCustomProxy(hre, "deployer", "ZUSDToken", "UpgradableProxy"), + /not an approved candidate/ + ); + assert.deepEqual(hre.calls, []); + }); + + it("rejects excluded deployment on an untagged runtime-reset Hardhat fork", async function () { + const hre = fixture(); + hre.deployments.deploy = async () => { + throw new Error("DEPLOY_ACTION_REACHED"); + }; + hre.network = { + name: "hardhat", + tags: {}, + config: {}, + provider: { + send: async () => ({ forkedNetwork: { chainId: 30, forkBlockNumber: 9293098 } }), + }, + }; + await assert.rejects( + helper().deployWithCustomProxy(hre, "deployer", "ZUSDToken", "UpgradableProxy"), + /not an approved candidate/ + ); + assert.deepEqual(hre.calls, []); + }); + + it("the proxy helper cannot create a proxy for the non-proxy companion", async function () { + const hre = fixture(); + hre.deployments.deploy = async () => { + throw new Error("DEPLOY_ACTION_REACHED"); + }; + await assert.rejects( + helper().deployWithCustomProxy( + hre, + "deployer", + "BorrowerOperationsPerimeterOps", + "UpgradableProxy" + ), + /not an approved proxy candidate/ + ); + }); + + it("recognizes a Hardhat fork reached through an allowed localhost RPC", async function () { + const hre = fixture(); + hre.network = { + name: "localhost", + tags: {}, + config: { url: "http://127.0.0.1:8545" }, + provider: { send: async () => ({ forkedNetwork: { chainId: 30 } }) }, + }; + hre.deployments.deploy = async () => { + throw new Error("DEPLOY_ACTION_REACHED"); + }; + await assert.rejects( + helper().deployWithCustomProxy(hre, "deployer", "ZUSDToken", "UpgradableProxy"), + /not an approved candidate/ + ); + assert.deepEqual(hre.calls, []); + }); + + it("rejects implicit global fixture mode before any deployment lookup", async function () { + environment.env.HARDHAT_DEPLOY_FIXTURE = "1"; + const hre = fixture(); + try { + await assert.rejects( + utils.preflightZeroDelayDeployment(hre), + /implicit deployment fixtures/ + ); + assert.deepEqual(hre.calls, []); + } finally { + delete environment.env.HARDHAT_DEPLOY_FIXTURE; + } + }); + + it("preserves a genuine local non-Hardhat node that has no metadata method", async function () { + const hre = fixture(); + hre.network = { + name: "rskdev", + tags: {}, + config: { url: "http://127.0.0.1:4444" }, + provider: { + send: async () => { + throw Object.assign(new Error("Method not found"), { code: -32601 }); + }, + }, + }; + await utils.preflightZeroDelayDeployment(hre); + assert.deepEqual(hre.calls, []); + }); + + it("fails closed on a loopback metadata read failure, not mistaken for a fresh node", async function () { + const hre = fixture(); + hre.network = { + name: "localhost", + tags: {}, + config: { url: "http://127.0.0.1:8545" }, + provider: { + send: async () => { + throw new Error("RPC_READ_FAILED"); + }, + }, + }; + await assert.rejects(utils.preflightZeroDelayDeployment(hre), /RPC_READ_FAILED/); + assert.deepEqual(hre.calls, []); + }); + + it("direct BO invocation rejects implicit fixtures before Permit2 lookup", async function () { + const script = loadScript("deployment/deploy/1-BorrowerOperations.ts", { + "../../scripts/helpers/utils": utils, + "../../scripts/helpers/helpers": helper(), + path, + }); + const hre = fixture(); + hre.getNamedAccounts = async () => ({ deployer: "deployer" }); + hre.deployments.get = async () => { + throw new Error("IMPLICIT_FIXTURE_LOOKUP_REACHED"); + }; + environment.env.HARDHAT_DEPLOY_FIXTURE = "1"; + try { + await assert.rejects(script.default(hre), /implicit deployment fixtures/); + assert.deepEqual(hre.calls, []); + } finally { + delete environment.env.HARDHAT_DEPLOY_FIXTURE; + } + }); + + it("the companion preflights the entire release before deploying", async function () { + const script = loadScript("deployment/deploy/9-BorrowerOperationsPerimeterOps.ts", { + "../../scripts/helpers/utils": utils, + "../../scripts/helpers/helpers": {}, + path, + "node-logs": Logs, + }); + const hre = fixture("CollSurplusPool_Proxy"); + hre.getNamedAccounts = async () => { + throw new Error("DEPLOY_ACTION_REACHED"); + }; + await assert.rejects(script.default(hre), /existing proxy/); + }); + + it("the real proxy helper refuses excluded candidates before any deploy or record save", async function () { + const hre = fixture(); + hre.deployments.deploy = async () => { + throw new Error("DEPLOY_ACTION_REACHED"); + }; + for (const name of ["StabilityPool", "ZUSDToken", "CommunityIssuance", "PriceFeed"]) { + await assert.rejects( + helper().deployWithCustomProxy(hre, "deployer", name, "UpgradableProxy"), + /not an approved candidate/ + ); + } + assert.deepEqual(hre.calls, []); + }); + + it("the real proxy helper rejects an incomplete release packet before staging its first candidate", async function () { + const hre = fixture("CollSurplusPool_Proxy"); + hre.deployments.deploy = async () => { + throw new Error("DEPLOY_ACTION_REACHED"); + }; + await assert.rejects( + helper().deployWithCustomProxy( + hre, + "deployer", + "BorrowerOperations", + "UpgradableProxy" + ), + /existing proxy/ + ); + }); + + it("the direct redemption-companion script rejects production before deploying or rewiring", async function () { + const script = loadScript("deployment/deploy/4-TroveManagerRedeemOps.ts", { + "../../scripts/helpers/utils": utils, + "../../scripts/helpers/helpers": {}, + path, + "node-logs": Logs, + }); + const hre = fixture(); + await assert.rejects(script.default(hre), /not an approved candidate/); + assert.deepEqual(hre.calls, []); + }); + + it("the deploy task rejects broad selection before running the deployment scheduler", async function () { + const actions = new Map(); + loadScript("tasks/zeroDelayRelease.ts", { + "../scripts/helpers/utils": utils, + "hardhat/config": { + task: (name) => ({ setAction: (action) => actions.set(name, action) }), + subtask: (name) => ({ setAction: (action) => actions.set(name, action) }), + extendEnvironment: () => {}, + }, + }); + const hre = fixture(); + let schedulerCalls = 0; + const scheduler = async () => { + schedulerCalls += 1; + }; + await assert.rejects(actions.get("deploy")({}, hre, scheduler), /explicit candidate tags/); + assert.equal(schedulerCalls, 0); + await actions.get("deploy")({ tags: tags.join(",") }, hre, scheduler); + assert.equal(schedulerCalls, 1); + for (const name of ["deploy:main", "deploy:runDeploy"]) { + await assert.rejects(actions.get(name)({}, hre, scheduler), /explicit candidate tags/); + } + assert.equal(schedulerCalls, 1); + }); + + it("the manual staging task always supplies exactly the frozen four-candidate selection", async function () { + const actions = new Map(); + loadScript("tasks/zeroDelayRelease.ts", { + "../scripts/helpers/utils": utils, + "hardhat/config": { + task: (name) => ({ setAction: (action) => actions.set(name, action) }), + subtask: (name) => ({ setAction: (action) => actions.set(name, action) }), + extendEnvironment: () => {}, + }, + }); + let selection; + await actions.get("zero:stage-delay")( + {}, + { + run: async (name, args) => { + selection = { name, ...args }; + }, + } + ); + assert.equal(selection.name, "deploy"); + assert.equal(selection.tags, tags.join(",")); + }); + + it("programmatic deployments.run rejects broad and destructive selection before scheduling", async function () { + let extend; + let scheduled = 0; + loadScript("tasks/zeroDelayRelease.ts", { + "../scripts/helpers/utils": utils, + "hardhat/config": { + task: () => ({ setAction: () => {} }), + subtask: () => ({ setAction: () => {} }), + extendEnvironment: (callback) => { + extend = callback; + }, + }, + }); + const hre = fixture(); + let received; + hre.deployments.run = async (selected, options) => { + received = { selected, options }; + scheduled += 1; + }; + extend(hre); + await assert.rejects(hre.deployments.run(), /explicit candidate tags/); + await assert.rejects( + hre.deployments.run(tags, { deletePreviousDeployments: true }), + /explicit candidate tags/ + ); + assert.equal(scheduled, 0); + await hre.deployments.run(tags); + assert.equal(scheduled, 1); + assert.equal(received.options.resetMemory, false); + assert.deepEqual(Array.from(received.selected), tags); + await hre.deployments.run(tags.join(",")); + assert.equal(scheduled, 2); + assert.deepEqual(Array.from(received.selected), tags); + await assert.rejects(hre.deployments.run(tags, { resetMemory: true }), /memory reset/); + await assert.rejects(hre.deployments.fixture(tags), /fixtures are not permitted/); + assert.equal(scheduled, 2); + }); + + it("fork node rejects deletion and broad scheduling before its provider task runs", async function () { + const actions = new Map(); + loadScript("tasks/zeroDelayRelease.ts", { + "../scripts/helpers/utils": utils, + "hardhat/config": { + task: (name) => ({ setAction: (action) => actions.set(name, action) }), + subtask: (name) => ({ setAction: (action) => actions.set(name, action) }), + extendEnvironment: () => {}, + }, + }); + let reached = 0; + const underlying = async () => { + reached += 1; + }; + const hre = fixture(); + hre.network = { name: "hardhat", tags: {}, config: {} }; + await assert.rejects( + actions.get("node")( + { fork: "https://example.invalid", noDeploy: true }, + hre, + underlying + ), + /requires no-reset/ + ); + await assert.rejects( + actions.get("node")( + { fork: "https://example.invalid", noReset: true }, + hre, + underlying + ), + /explicit candidate tags/ + ); + assert.equal(reached, 0); + await actions.get("node")( + { fork: "https://example.invalid", noDeploy: true, noReset: true }, + hre, + underlying + ); + assert.equal(reached, 1); + }); +}); diff --git a/tests-perimeter/StorageLayout.zerodiff.test.js b/tests-perimeter/StorageLayout.zerodiff.test.js index ea91a5b..0b5e76a 100644 --- a/tests-perimeter/StorageLayout.zerodiff.test.js +++ b/tests-perimeter/StorageLayout.zerodiff.test.js @@ -1,28 +1,31 @@ -// Perimeter security perimeter — storage-layout ZERO-DIFF regression. +// Perimeter security perimeter — storage-layout upgrade-safety 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. +// 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 Perimeter lending side carries an equivalent Hardhat 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 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. +// ZERO_DIFF — any label/slot/offset/type difference fails (ActivePool, +// CollSurplusPool: hooked but stateless; TroveManager: upgraded +// for the Recovery-Mode liquidation fix, logic only). +// 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 @@ -33,23 +36,31 @@ // 1. git worktree add sovryn-perimeter-fee // 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 +// 4. extract the normalized layout for the four targets and overwrite // tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json (keep _meta). 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"); +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 }, + // Upgraded for the Recovery-Mode liquidation fix (W9a): logic only, no state. + { fq: "contracts/TroveManager.sol:TroveManager", policy: ZERO_DIFF }, ]; -describe("Perimeter — storage-layout zero-diff (Zero surplus-claim exit fee)", () => { +describe("Perimeter — storage-layout upgrade safety (surplus-claim fee hook + exit-delay reroute)", () => { let baseline; before(() => { @@ -57,7 +68,7 @@ describe("Perimeter — storage-layout zero-diff (Zero surplus-claim exit fee)", }); 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, @@ -66,18 +77,74 @@ describe("Perimeter — storage-layout zero-diff (Zero surplus-claim exit fee)", } }); - 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)}` - ); - }); + // 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 () => { + 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.close.test.js b/tests-perimeter/ZeroBorrowerExit.close.test.js index 91a4604..7cd5959 100644 --- a/tests-perimeter/ZeroBorrowerExit.close.test.js +++ b/tests-perimeter/ZeroBorrowerExit.close.test.js @@ -163,6 +163,40 @@ contract("Perimeter — Zero borrower collateral exit (closeTrove)", async (acco assert.equal((await troveManager.Troves(alice))[3].toString(), baseStatus); }); + it("closeTrove (active but feeReceiver == 0): fee demoted, full coll to borrower, nothing burned", async () => { + // A value call to a no-code address succeeds, so charging into + // address(0) would burn the fee. The coll path must demote the same way + // the surplus-claim path does, rather than send RBTC to 0x0. + await setupCloseable(); + await borrowerOperations.setExitFeeController(controller.address, { from: owner }); + await controller.configure(true, 50, ZERO_ADDRESS, NONE); // active, 50bps, receiver 0 + + const gross = await getTroveEntireColl(alice); + const apEthBefore = await activePool.getETH(); + const zeroBalBefore = toBN(await web3.eth.getBalance(ZERO_ADDRESS)); + const aliceBefore = toBN(await web3.eth.getBalance(alice)); + + const tx = await borrowerOperations.closeTrove({ from: alice, gasPrice: GAS_PRICE }); + const gasCost = GAS_PRICE.mul(toBN(tx.receipt.gasUsed)); + + assert.isTrue( + (await activePool.getETH()).eq(apEthBefore.sub(gross)), + "ActivePool != -coll" + ); + assert.isTrue( + toBN(await web3.eth.getBalance(ZERO_ADDRESS)).eq(zeroBalBefore), + "fee was burned to 0x0" + ); + assert.isTrue( + toBN(await web3.eth.getBalance(alice)).eq(aliceBefore.add(gross).sub(gasCost)), + "borrower != +gross (fee demoted, minus gas)" + ); + const ev = getEvent(tx, "ExitFeeSkipped"); + assert.isDefined(ev, "ExitFeeSkipped not emitted"); + assert.equal(toBN(ev.args.reason).toNumber(), 2, "reason != DISABLED"); + assert.isUndefined(getEvent(tx, "ExitFeeApplied")); + }); + it("closeTrove: controller unset → full coll to borrower, ExitFeeSkipped(CONTROLLER_REVERT)", async () => { await setupCloseable(); const gross = await getTroveEntireColl(alice); diff --git a/tests-perimeter/ZeroBorrowerExit.delay.notouch.test.js b/tests-perimeter/ZeroBorrowerExit.delay.notouch.test.js new file mode 100644 index 0000000..ea2c0bc --- /dev/null +++ b/tests-perimeter/ZeroBorrowerExit.delay.notouch.test.js @@ -0,0 +1,180 @@ +// Perimeter security perimeter — Zero DELAY no-touch regression. +// +// With the perimeter ACTIVE (controller enabled, d>0) and the queue WIRED, +// proves the withdrawal delay reroute fires ONLY on the voluntary +// collateral-out chokepoint and is EXEMPT elsewhere, for two separate +// reasons. Liquidation and the stability-pool ETH-gain withdrawal are +// keeper-driven paths whose collateral routes through TroveManager / +// StabilityPool, NOT through BorrowerOperations._sendCollWithExitFee, so +// they never reach the chokepoint. Redemption is a swap — collateral +// exchanged for ZUSD at face value, not a withdrawal — so it sits outside +// the withdrawal delay's scope regardless of which contract routes it. +// None of the three touches the queue (lastRequestId stays 0, no RBTC +// escrowed). A keeper/liquidator/redeemer payout must never be escrowed +// behind the withdrawal 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( + "Perimeter 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-perimeter/ZeroBorrowerExit.delay.selector.test.js b/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js new file mode 100644 index 0000000..d44d973 --- /dev/null +++ b/tests-perimeter/ZeroBorrowerExit.delay.selector.test.js @@ -0,0 +1,182 @@ +// 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 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 +// the returndata's leading 4 bytes equal the queue's selector (and are NOT a +// 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. +// +// 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"); +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 PerimeterRawCatcher = artifacts.require("PerimeterRawCatcher"); + +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); + +contract("Perimeter delay — SR1 queue selector propagation", async (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; + + 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); + catcher = await PerimeterRawCatcher.new(); + }); + + 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); + }); + + // 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( + sel.toLowerCase(), + UNREGISTERED_SOURCE_SELECTOR, + "mock selector != keccak selector" + ); + }); + + it("withdrawColl (d>0): queue UnregisteredSource selector BUBBLES UP unwrapped (not a PERIMETER: string)", async () => { + await openCatcherTrove(); + + const data = borrowerOperations.contract.methods + .withdrawColl(toBN(dec(1, "ether")).toString(), catcher.address, catcher.address) + .encodeABI(); + const { ok, ret: revertData } = await catcherCall(data); + + 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.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.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 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 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 { ok, ret: revertData } = await catcherCall(data); + + assert.isFalse(ok, "expected the close to revert"); + assert.equal( + revertData.toLowerCase().slice(0, 10), + UNREGISTERED_SOURCE_SELECTOR, + `expected queue selector to bubble out of closeTrove; got ${revertData}` + ); + }); +}); diff --git a/tests-perimeter/ZeroBorrowerExit.delay.test.js b/tests-perimeter/ZeroBorrowerExit.delay.test.js new file mode 100644 index 0000000..d86038a --- /dev/null +++ b/tests-perimeter/ZeroBorrowerExit.delay.test.js @@ -0,0 +1,563 @@ +// 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 +// 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 { assertRevertWithReason } = require("./utils/assertions.js"); + +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 BorrowerOperationsPerimeterOps = artifacts.require("BorrowerOperationsPerimeterOps"); +const Destructible = artifacts.require("Destructible"); + +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("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); + +contract("Perimeter 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 assertRevertWithReason( + borrowerOperations.setExitDelayQueue(queue.address, { from: alice }), + "Ownable:: access denied" + ); + await assertRevertWithReason( + borrowerOperations.setExitDelayQueue(ZERO_ADDRESS, { from: owner }), + "EDQ:zero" + ); + await assertRevertWithReason( + borrowerOperations.setExitDelayQueue(alice, { from: owner }), + "Account code size cannot be zero" + ); + + 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); + }); + + it("setPerimeterOps: only owner, rejects code-less targets and preserves the previous hook on failure", async () => { + const previous = await borrowerOperations.perimeterOps(); + const replacement = await BorrowerOperationsPerimeterOps.new(); + await assertRevertWithReason( + borrowerOperations.setPerimeterOps(replacement.address, { from: alice }), + "Ownable:: access denied" + ); + await assertRevertWithReason( + borrowerOperations.setPerimeterOps(ZERO_ADDRESS, { from: owner }), + "Account cannot be zero address" + ); + await assertRevertWithReason( + borrowerOperations.setPerimeterOps(alice, { from: owner }), + "Account code size cannot be zero" + ); + assert.equal(await borrowerOperations.perimeterOps(), previous); + + const tx = await borrowerOperations.setPerimeterOps(replacement.address, { from: owner }); + const event = getEvent(tx, "PerimeterOpsSet"); + assert.equal(event.args.previous, previous); + assert.equal(event.args.current, replacement.address); + assert.equal(await borrowerOperations.perimeterOps(), replacement.address); + + 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 }); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(gross)); + }); + + it("a settlement hook that loses its code cannot silently consume a collateral withdrawal", async () => { + await openTrove({ + ICR: toBN(dec(10, 18)), + extraParams: { from: alice, value: toBN(dec(100, "ether")) }, + }); + await wire(); + const collateralBefore = await getTroveEntireColl(alice); + const poolBefore = await activePool.getETH(); + const hook = await Destructible.new(); + await borrowerOperations.setPerimeterOps(hook.address, { from: owner }); + assert.notEqual(await web3.eth.getCode(hook.address), "0x"); + await hook.destruct(owner); + assert.equal(await web3.eth.getCode(hook.address), "0x", "hook code was not cleared"); + + await assertRevertWithReason( + borrowerOperations.withdrawColl(toBN(dec(1, "ether")), alice, alice, { from: alice }), + "Account code size cannot be zero" + ); + assert.isTrue((await getTroveEntireColl(alice)).eq(collateralBefore)); + assert.isTrue((await activePool.getETH()).eq(poolBefore)); + assert.equal((await queue.lastRequestId()).toString(), "0"); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).isZero()); + }); + + // ── 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 }), + "PERIMETER: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 }), + "PERIMETER: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-perimeter/ZeroClaimSurplus.delay.test.js b/tests-perimeter/ZeroClaimSurplus.delay.test.js new file mode 100644 index 0000000..3534c27 --- /dev/null +++ b/tests-perimeter/ZeroClaimSurplus.delay.test.js @@ -0,0 +1,420 @@ +// 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 { assertRevertWithReason } = require("./utils/assertions.js"); + +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 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("claimCollWithFeeTo: only BorrowerOperations can consume a funded claim or redirect its payout", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + const poolBefore = await collSurplusPool.getETH(); + const receiverBefore = toBN(await web3.eth.getBalance(feeReceiver)); + await assertRevertWithReason( + collSurplusPool.claimCollWithFeeTo(alice, ZERO_ADDRESS, 0, feeReceiver, { + from: dennis, + }), + "CollSurplusPool: Caller is not Borrower Operations" + ); + assert.isTrue((await collSurplusPool.getCollateral(alice)).eq(gross)); + assert.isTrue((await collSurplusPool.getETH()).eq(poolBefore)); + assert.isTrue(toBN(await web3.eth.getBalance(feeReceiver)).eq(receiverBefore)); + await assertQueueUntouched(); + + await borrowerOperations.claimCollateral({ from: alice }); + assert.isTrue((await collSurplusPool.getCollateral(alice)).isZero()); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(gross)); + }); + + it("a failed delayed record rolls back an already-paid fee and every surplus accounting change", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + await controller.configure(true, 50, feeReceiver, NONE); + const fee = gross.mul(toBN(50)).div(toBN(10000)); + assert.isTrue(fee.gt(toBN(0)), "rollback control must charge a positive fee"); + await queue.setAllowedSource(borrowerOperations.address, false); + const poolBefore = await collSurplusPool.getETH(); + const poolBalanceBefore = toBN(await web3.eth.getBalance(collSurplusPool.address)); + const receiverBefore = toBN(await web3.eth.getBalance(feeReceiver)); + + await assertRevertWithReason( + borrowerOperations.claimCollateral({ from: alice }), + "MockQueue: unregistered source" + ); + assert.isTrue((await collSurplusPool.getCollateral(alice)).eq(gross)); + assert.isTrue((await collSurplusPool.getETH()).eq(poolBefore)); + assert.isTrue( + toBN(await web3.eth.getBalance(collSurplusPool.address)).eq(poolBalanceBefore) + ); + assert.isTrue(toBN(await web3.eth.getBalance(feeReceiver)).eq(receiverBefore)); + await assertQueueUntouched(); + + await queue.setAllowedSource(borrowerOperations.address, true); + await borrowerOperations.claimCollateral({ from: alice }); + assert.isTrue(toBN(await web3.eth.getBalance(feeReceiver)).eq(receiverBefore.add(fee))); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(gross.sub(fee))); + assert.isTrue((await collSurplusPool.getCollateral(alice)).isZero()); + }); + + 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("fee-vault reverts (d>0): GROSS escrowed behind the delay (cannot bypass), ExitFeeSkipped(VAULT_REVERT)", async () => { + await wireQueue(); + const gross = await setupSurplus(alice); + const badReceiver = await NonPayable.new(); // receive() reverts while isPayable=false + await controller.configure(true, 50, badReceiver.address, NONE); + + 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)); + + // fee leg bounced ⇒ full GROSS routed to the queue (NOT paid direct, NOT skimmed). + 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( + toBN(await web3.eth.getBalance(badReceiver.address)).eq(toBN(0)), + "bad receiver got RBTC" + ); + assert.isTrue((await queue.totalEscrowed(ZERO_ADDRESS)).eq(gross), "escrowed != gross"); + + const applied = getEvent(tx, "ExitFeeApplied"); + assert.isUndefined(applied, "ExitFeeApplied emitted on a bounced fee leg"); + const skipped = getEvent(tx, "ExitFeeSkipped"); + assert.isDefined(skipped, "ExitFeeSkipped not emitted"); + assert.equal(toBN(skipped.args.reason).toNumber(), 5); // VAULT_REVERT + + // the amount the queue recorded must equal what the pool actually sent it + // (the gross), never the controller's quoted net. + const request = await queue.getRequest(1); + assert.isTrue( + toBN(request.amount).eq(gross), + "escrowed request amount != gross sent by the pool" + ); + assert.equal(request.receiver, alice, "receiver must be the claimant"); + }); + + 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/ZeroRecoveryMode.accounting.test.js b/tests-perimeter/ZeroRecoveryMode.accounting.test.js new file mode 100644 index 0000000..9a094cb --- /dev/null +++ b/tests-perimeter/ZeroRecoveryMode.accounting.test.js @@ -0,0 +1,84 @@ +const deploymentHelper = require("../utils/js/deploymentHelpers.js"); +const testHelpers = require("../utils/js/testHelpers.js"); + +const TroveManagerTester = artifacts.require("TroveManagerTester"); +const ZUSDToken = artifacts.require("ZUSDToken"); + +const th = testHelpers.TestHelper; +const { toBN, dec } = th; + +contract("Zero Recovery Mode — batch collateral accounting", async (accounts) => { + const [owner, alice, bob, whale] = accounts; + const multisig = accounts[999]; + let contracts; + + beforeEach(async () => { + contracts = await deploymentHelper.deployLiquityCore(); + contracts.troveManager = await TroveManagerTester.new(contracts.permit2.address); + contracts.zusdToken = await ZUSDToken.new(); + await contracts.zusdToken.initialize( + contracts.troveManager.address, + contracts.stabilityPool.address, + contracts.borrowerOperations.address + ); + const zeroContracts = await deploymentHelper.deployZEROContracts(multisig); + await deploymentHelper.connectZEROContracts(zeroContracts); + await deploymentHelper.connectCoreContracts(contracts, zeroContracts); + await deploymentHelper.connectZEROContractsToCore(zeroContracts, contracts); + }); + + it("batch gas compensation cannot inflate TCR enough to liquidate a protected trove", async () => { + const first = await th.openTrove(contracts, { + ICR: toBN(dec(210, 16)), + extraParams: { from: alice }, + }); + const candidate = await th.openTrove(contracts, { + ICR: toBN(dec(2802, 15)), + extraParams: { from: bob }, + }); + const deposit = first.totalDebt.add(candidate.totalDebt).add(toBN(dec(1, 18))); + await th.openTrove(contracts, { + ICR: toBN(dec(280, 16)), + extraZUSDAmount: deposit, + extraParams: { from: whale }, + }); + await contracts.stabilityPool.provideToSP(deposit, th.ZERO_ADDRESS, { from: whale }); + await contracts.priceFeedTestnet.setPrice(dec(100, 18)); + + const price = await contracts.priceFeedTestnet.getPrice(); + assert.isTrue(await th.checkRecoveryMode(contracts)); + const firstICR = await contracts.troveManager.getCurrentICR(alice, price); + assert.isTrue(firstICR.gt(toBN(dec(1, 18)))); + assert.isTrue(firstICR.lt(testHelpers.MoneyValues._MCR)); + + const systemCollateral = (await contracts.activePool.getETH()).add( + await contracts.defaultPool.getETH() + ); + const systemDebt = (await contracts.activePool.getZUSDDebt()).add( + await contracts.defaultPool.getZUSDDebt() + ); + const remainingDebt = systemDebt.sub(first.totalDebt); + const remainingCollateral = systemCollateral.sub(first.collateral); + const gasCompensation = first.collateral.div(toBN(200)); + const correctTCR = remainingCollateral.mul(price).div(remainingDebt); + const inflatedTCR = remainingCollateral.add(gasCompensation).mul(price).div(remainingDebt); + const candidateICR = await contracts.troveManager.getCurrentICR(bob, price); + assert.isTrue(candidateICR.gte(testHelpers.MoneyValues._MCR)); + assert.isTrue(inflatedTCR.lt(testHelpers.MoneyValues._CCR)); + assert.isTrue(candidateICR.gte(correctTCR), "candidate must be protected by actual TCR"); + assert.isTrue(candidateICR.lt(inflatedTCR), "candidate must expose the omitted deduction"); + + const tx = await contracts.troveManager.batchLiquidateTroves([alice, bob], { + from: owner, + }); + assert.equal(th.getAllEventsByName(tx, "TroveLiquidated").length, 1); + assert.isFalse(await contracts.sortedTroves.contains(alice)); + assert.isTrue(await contracts.sortedTroves.contains(bob)); + assert.equal((await contracts.troveManager.Troves(bob))[3].toString(), "1"); + assert.equal( + (await contracts.activePool.getETH()).toString(), + remainingCollateral.toString() + ); + assert.equal((await th.getTCR(contracts)).toString(), correctTCR.toString()); + }); +}); diff --git a/tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json b/tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json index 9a2c2a6..6002a47 100644 --- a/tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json +++ b/tests-perimeter/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-perimeter/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-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). 2026-09-07 (W9a): contracts/TroveManager.sol:TroveManager added as a ZERO_DIFF target, captured from the same b6584a6 tree, because the Recovery-Mode liquidation fix makes TroveManager an upgraded implementation in the Phase 2 release; the fix changes no state.", "consumers": [ "tests-perimeter/StorageLayout.zerodiff.test.js", "tests-perimeter/utils/storageLayout.js" @@ -163,5 +163,163 @@ "offset": 0, "type": "t_mapping(t_address,t_uint256)" } + ], + "contracts/TroveManager.sol:TroveManager": [ + { + "label": "activePool", + "slot": "0", + "offset": 0, + "type": "t_contract(IActivePool)" + }, + { + "label": "defaultPool", + "slot": "1", + "offset": 0, + "type": "t_contract(IDefaultPool)" + }, + { + "label": "priceFeed", + "slot": "2", + "offset": 0, + "type": "t_contract(IPriceFeed)" + }, + { + "label": "liquityBaseParams", + "slot": "3", + "offset": 0, + "type": "t_contract(ILiquityBaseParams)" + }, + { + "label": "troveManagerRedeemOps", + "slot": "4", + "offset": 0, + "type": "t_address" + }, + { + "label": "borrowerOperationsAddress", + "slot": "5", + "offset": 0, + "type": "t_address" + }, + { + "label": "_stabilityPool", + "slot": "6", + "offset": 0, + "type": "t_contract(IStabilityPool)" + }, + { + "label": "gasPoolAddress", + "slot": "7", + "offset": 0, + "type": "t_address" + }, + { + "label": "collSurplusPool", + "slot": "8", + "offset": 0, + "type": "t_contract(ICollSurplusPool)" + }, + { + "label": "_zusdToken", + "slot": "9", + "offset": 0, + "type": "t_contract(IZUSDToken)" + }, + { + "label": "_zeroToken", + "slot": "10", + "offset": 0, + "type": "t_contract(IZEROToken)" + }, + { + "label": "_zeroStaking", + "slot": "11", + "offset": 0, + "type": "t_contract(IZEROStaking)" + }, + { + "label": "feeDistributor", + "slot": "12", + "offset": 0, + "type": "t_contract(IFeeDistributor)" + }, + { + "label": "sortedTroves", + "slot": "13", + "offset": 0, + "type": "t_contract(ISortedTroves)" + }, + { + "label": "baseRate", + "slot": "14", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "lastFeeOperationTime", + "slot": "15", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "Troves", + "slot": "16", + "offset": 0, + "type": "t_mapping(t_address,t_struct(Trove)_storage)" + }, + { + "label": "totalStakes", + "slot": "17", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "totalStakesSnapshot", + "slot": "18", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "totalCollateralSnapshot", + "slot": "19", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "L_ETH", + "slot": "20", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "L_ZUSDDebt", + "slot": "21", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "rewardSnapshots", + "slot": "22", + "offset": 0, + "type": "t_mapping(t_address,t_struct(RewardSnapshot)_storage)" + }, + { + "label": "TroveOwners", + "slot": "23", + "offset": 0, + "type": "t_array(t_address)dyn_storage" + }, + { + "label": "lastETHError_Redistribution", + "slot": "24", + "offset": 0, + "type": "t_uint256" + }, + { + "label": "lastZUSDDebtError_Redistribution", + "slot": "25", + "offset": 0, + "type": "t_uint256" + } ] } 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 }; diff --git a/tests-release/ArtifactProvenance.test.js b/tests-release/ArtifactProvenance.test.js new file mode 100644 index 0000000..0994b73 --- /dev/null +++ b/tests-release/ArtifactProvenance.test.js @@ -0,0 +1,54 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); +const crypto = require("crypto"); +const { artifacts } = require("hardhat"); + +function verifier({ filesystem = fs, artifactReader = artifacts } = {}) { + const filename = path.resolve(__dirname, "helpers/zero.js"); + const loaded = { exports: {} }; + vm.runInNewContext(fs.readFileSync(filename, "utf8"), { + module: loaded, + exports: loaded.exports, + __dirname: path.dirname(filename), + require: (name) => + ({ fs: filesystem, path, crypto, hardhat: { artifacts: artifactReader } }[name]), + }); + return loaded.exports.verifyZeroReleaseArtifacts; +} + +describe("Zero release artifact provenance controls", function () { + it("rejects a changed source input without modifying repository files", async function () { + const filesystem = { + ...fs, + readFileSync: (filename, options) => fs.readFileSync(filename, options) + "\n", + }; + await assert.rejects(verifier({ filesystem })(), /Stale Zero release artifact source/); + }); + + it("rejects an artifact built with the wrong compiler", async function () { + const artifactReader = { + readArtifact: (...args) => artifacts.readArtifact(...args), + getBuildInfo: async (...args) => ({ + ...(await artifacts.getBuildInfo(...args)), + solcVersion: "0.6.12", + }), + }; + await assert.rejects(verifier({ artifactReader })(), /Unexpected Zero release compiler/); + }); + + it("rejects a bytecode/build-output mismatch", async function () { + const artifactReader = { + readArtifact: async (...args) => ({ + ...(await artifacts.readArtifact(...args)), + bytecode: "0x6000", + }), + getBuildInfo: (...args) => artifacts.getBuildInfo(...args), + }; + await assert.rejects( + verifier({ artifactReader })(), + /Zero release artifact\/build mismatch/ + ); + }); +}); diff --git a/tests-release/DeploymentMetadata.hazard.test.js b/tests-release/DeploymentMetadata.hazard.test.js new file mode 100644 index 0000000..0ed8864 --- /dev/null +++ b/tests-release/DeploymentMetadata.hazard.test.js @@ -0,0 +1,63 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const { artifacts } = require("hardhat"); +const { getCompilersDir } = require("hardhat/internal/util/global-dir"); +const { CompilerDownloader } = require("hardhat/internal/solidity/compiler/downloader"); +const { DeploymentFactory } = require("hardhat-deploy/dist/src/DeploymentFactory"); + +function executable(bytecode) { + const metadataBytes = parseInt(bytecode.slice(-4), 16); + return bytecode.slice(0, -(metadataBytes + 2) * 2); +} + +describe("Installed deployment comparator formatting hazard", function () { + it("a comment-only ZUSDToken change preserves ABI/runtime but triggers the real deployment comparator", async function () { + const name = "contracts/ZUSDToken.sol:ZUSDToken"; + const build = await artifacts.getBuildInfo(name); + assert.equal(build.solcVersion, "0.6.11"); + assert.equal( + build.input.sources["contracts/ZUSDToken.sol"].content, + fs.readFileSync(path.resolve(__dirname, "../contracts/ZUSDToken.sol"), "utf8") + ); + const input = JSON.parse(JSON.stringify(build.input)); + input.sources["contracts/ZUSDToken.sol"].content = + "// release metadata-only control\n" + + input.sources["contracts/ZUSDToken.sol"].content; + const downloader = CompilerDownloader.getConcurrencySafeDownloader( + CompilerDownloader.getCompilerPlatform(), + await getCompilersDir() + ); + const compiler = await downloader.getCompiler(build.solcVersion); + assert.ok( + compiler && !compiler.isSolcJs, + "Exact cached native Solidity compiler is required" + ); + const processResult = spawnSync(compiler.compilerPath, ["--standard-json"], { + input: JSON.stringify(input), + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + assert.equal(processResult.status, 0, processResult.stderr); + const output = JSON.parse(processResult.stdout); + assert.deepEqual( + (output.errors || []).filter((error) => error.severity === "error"), + [] + ); + const before = build.output.contracts["contracts/ZUSDToken.sol"].ZUSDToken; + const after = output.contracts["contracts/ZUSDToken.sol"].ZUSDToken; + assert.deepEqual(after.abi, before.abi); + assert.equal( + executable(after.evm.deployedBytecode.object), + executable(before.evm.deployedBytecode.object) + ); + const artifact = await artifacts.readArtifact(name); + const changed = { ...artifact, bytecode: `0x${after.evm.bytecode.object}` }; + const factory = new DeploymentFactory(async () => changed, changed, [], {}); + assert.equal( + await factory.compareDeploymentTransaction({ data: artifact.bytecode }), + true + ); + }); +}); diff --git a/tests-release/README.md b/tests-release/README.md new file mode 100644 index 0000000..8b8a924 --- /dev/null +++ b/tests-release/README.md @@ -0,0 +1,85 @@ +# Manual Zero release proof + +These suites run locally, not in CI. They do not sign, stage, upgrade, or activate +anything on a production network. `hardhat.config.release.cjs` deliberately does not +load operator secrets and only defines an in-process Hardhat network. Contract-size +limits remain enforced. + +## Exact shared-core artifacts + +Supply the independently built core controller, queue, and ERC1967 proxy artifacts +and their manifest. The helper requires absolute paths, the full core source revision, +matching artifact SHA256 values, Solidity 0.8.20/Paris, and nonempty deployed runtime +within the 24,576-byte limit. Do not substitute historical lending fixtures. + +```sh +export CORE_RELEASE_ARTIFACTS_DIR="/absolute/path/to/core-out" +export CORE_RELEASE_MANIFEST="/absolute/path/to/HANDOFF.json" +export CORE_RELEASE_REVISION="full-40-character-core-source-commit" +npx hardhat --config hardhat.config.release.cjs compile +npx hardhat --config hardhat.config.release.cjs test --no-compile \ + tests-perimeter/Deployment.perimeterOps.test.js \ + tests-perimeter/Deployment.releaseScope.test.js \ + tests-release/ArtifactProvenance.test.js \ + tests-release/ZeroCore.integration.test.js \ + tests-release/DeploymentMetadata.hazard.test.js +``` + +The metadata test invokes the exact cached native Solidity 0.6.11 compiler with an +in-memory comment mutation. It verifies the ABI and executable runtime stay equal +while the actual installed deployment comparator detects different creation data. +The compiler must already be available; this suite does not install a replacement. +Production safeguards exclude unrelated candidates rather than ignoring metadata. +Both integration suites also compare all four Zero candidate artifacts against their +Solidity 0.6.11 optimizer100 build outputs and every cached source/dependency input +against the current files. Stale local artifacts are rejected instead of being used +as evidence for newer source. + +## Disposable mainnet fork + +Choose and record a read-only Rootstock block/hash first. Do not use a production +wallet, a shared QA node, or `--network` to point these tests at a public broadcaster. + +```sh +export ZERO_RELEASE_FORK_RPC="https://your-read-only-rootstock-rpc" +export ZERO_RELEASE_FORK_BLOCK="explicit-mainnet-block-number" +export ZERO_RELEASE_FORK_HASH="0xfull-block-hash" +export ZERO_RELEASE_FORK_REPORT="/absolute/path/to/fork-proof.json" +npx hardhat --config hardhat.config.release.cjs test --no-compile \ + tests-release/ZeroFork.rehearsal.test.js +``` + +The fork verifies upstream chain30 and the block hash, impersonates authorities +**locally**, stages exactly four Zero candidates, preserves live constructor inputs, +and installs them in the five-action release order. It checks old storage prefixes, +owners, balances, representative live troves, and excluded contract identities. It +then exercises fee-first withdrawals, adjustments, closes, funded surplus, failed +record rollback, 24-hour release, emergency disable, and host implementation rollback +without losing an already queued claim. + +### Fixture boundaries + +- The production oracle is read and retained during installation. Afterwards only + its primary external adapter is replaced on the disposable fork with the existing + `ExternalPriceFeedTester`, seeded from the pinned live price. The report identifies + the original feed pointers and unchanged Zero PriceFeed implementation. Fork + setup/time travel can expire the external oracle's publication; these exit tests + therefore do **not** certify ongoing live-oracle updates. +- The fork surplus fixture is funded through locally impersonated, authorised + ActivePool/TroveManager identities. It proves real pool/BO/controller/queue + settlement and rollback, not the generation of surplus by a mainnet liquidation. + The unforked integration suite generates actual redemption surplus using the + production Zero hosts and surrounding local test assets. +- Queue floor60 is a test parameter, not a production decision. Global delay86400 + is tested. Existing queued claims remain independent of emergency ingress disable + and host implementation rollback. +- These are Zero technical proofs. Full exact-artifact governance voting/queueing/ + execution, multisig threshold, lending integration, frontend approval, source + freeze, production floor selection, and owner release approval remain separate + gates in the private Perimeter Delay Runbook. + +For production Zero deployment, use `zero:stage-delay` only after the runbook's +authorisation/preflight. Do not use this test configuration as a production +deployment configuration or run broad/reset deployments on a release network. +Existing `hh:fork-mainnet`/`hh:fork-testnet` commands retain no-deploy and now add +no-reset, preserving deployment records under the new fork-node guard. diff --git a/tests-release/ZeroComposition.stateful.test.js b/tests-release/ZeroComposition.stateful.test.js new file mode 100644 index 0000000..d66ebce --- /dev/null +++ b/tests-release/ZeroComposition.stateful.test.js @@ -0,0 +1,311 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); +const { ethers, network } = require("hardhat"); +const { fixture, random, Model, WITHDRAW, SURPLUS, DELAY } = require("./helpers/composition"); + +const seeds = (process.env.ZERO_COMPOSITION_SEEDS || "20261003,20261004").split(",").map(Number); +const runs = Number(process.env.ZERO_COMPOSITION_RUNS || 2); +const steps = Number(process.env.ZERO_COMPOSITION_STEPS || 40); +for (const value of [...seeds, runs, steps]) + assert( + Number.isSafeInteger(value) && value > 0, + "Campaign settings must be positive integers" + ); +const reports = []; + +describe("Zero stateful composition with the real controller and queue", function () { + let ctx; + let baseline; + before(async function () { + ctx = await fixture(); + baseline = await network.provider.send("evm_snapshot"); + }); + after(async function () { + if (process.env.ZERO_COMPOSITION_REPORT) { + const filename = process.env.ZERO_COMPOSITION_REPORT; + assert(path.isAbsolute(filename), "Report must have an absolute path"); + fs.writeFileSync( + filename, + JSON.stringify( + { + configured: { + seeds, + runs, + generatedStepsPerRun: steps, + globalDelay: DELAY, + testFloor: 60, + }, + coreRevision: process.env.CORE_RELEASE_REVISION, + mutation: process.env.ZERO_COMPOSITION_MUTANT_FILE || null, + provenance: ctx?.provenance, + campaigns: reports, + }, + (_, value) => (typeof value === "bigint" ? value.toString() : value), + 2 + ) + "\n" + ); + } + }); + + for (const seed of seeds) { + it(`conserves custody and state through repeated transitions with seed ${seed}`, async function () { + for (let run = 0; run < runs; run++) { + await network.provider.send("evm_revert", [baseline]); + baseline = await network.provider.send("evm_snapshot"); + const model = new Model(ctx); + const rng = random((seed + run * 104729) >>> 0); + const report = { seed, run, configuredGeneratedSteps: steps, status: "running" }; + reports.push(report); + try { + const surplus = await ctx.createSurplus(ctx.bob); + for (const actor of [ctx.alice, ctx.carol, ctx.contractActor]) + await ctx.open(actor); + await model.control("remove-source", () => + ctx.queue.removeAllowedSource(ctx.borrower.target) + ); + await model.rejected(ctx.bob, "surplus", "UnregisteredSource"); + await model.control("restore-source", () => + ctx.queue.addAllowedSource(ctx.borrower.target) + ); + await model.control("withdraw-surface-bypass", () => + ctx.controller.setActorBypass(WITHDRAW, ctx.bob.address, [true, true]) + ); + await model.control("fee-receiver-failure", () => + ctx.controller.setFeeReceiver(ctx.nonPayable.target) + ); + await model.settle(ctx.bob, "surplus", surplus, true, true); + await model.control("fee-receiver-restore", () => + ctx.controller.setFeeReceiver(ctx.feeReceiver.address) + ); + const first = await model.settle( + ctx.alice, + "withdraw", + ethers.parseEther("0.01"), + true + ); + await model.settle(ctx.alice, "adjust", ethers.parseEther("0.02"), true); + const contractId = await model.settle( + ctx.contractActor, + "withdraw", + ethers.parseEther("0.01"), + true + ); + await model.release(contractId, ctx.outsider, "NotUnlocked"); + await model.control("remove-source", () => + ctx.queue.removeAllowedSource(ctx.borrower.target) + ); + await model.rejected(ctx.alice, "withdraw", "UnregisteredSource"); + await model.control("delay-disable", () => + ctx.controller.setSecurityPerimeterEnabled(false) + ); + await model.settle(ctx.alice, "withdraw", ethers.parseEther("0.01"), false); + await model.control("restore-source", () => + ctx.queue.addAllowedSource(ctx.borrower.target) + ); + await model.control("delay-enable", () => + ctx.controller.setSecurityPerimeterEnabled(true) + ); + await model.control("actor-bypass", () => + ctx.controller.setActorBypass(WITHDRAW, ctx.alice.address, [true, true]) + ); + await model.settle(ctx.alice, "withdraw", ethers.parseEther("0.01"), false); + await model.control("actor-bypass-clear", () => + ctx.controller.setActorBypass(WITHDRAW, ctx.alice.address, [false, false]) + ); + await model.settle(ctx.alice, "withdraw", ethers.parseEther("0.01"), true); + await model.control("below-floor", () => + ctx.controller.setGlobalDelaySeconds(1) + ); + await model.rejected(ctx.alice, "withdraw", "DelayBelowFloor"); + await model.control("restore-delay", () => + ctx.controller.setGlobalDelaySeconds(DELAY) + ); + await model.mature(first); + await model.release(first, ctx.outsider, "NotExecutor"); + await model.control("freeze-party", () => + ctx.queue["freeze(address)"](ctx.alice.address) + ); + await model.release(first, ctx.alice, "ActorBlocked"); + await model.control("clear-party", () => + ctx.queue["unfreeze(address)"](ctx.alice.address) + ); + await model.control("pause", () => ctx.queue.setSecurityPerimeterPaused(true)); + await model.release(first, ctx.alice, "QueuePaused"); + await model.control("resume", () => + ctx.queue.setSecurityPerimeterPaused(false) + ); + await model.release(first, ctx.alice); + await model.release(first, ctx.alice, "AlreadyTerminal"); + await model.mature(contractId); + await model.control("freeze-executor", () => + ctx.queue["freeze(address)"](ctx.outsider.address) + ); + await model.release(contractId, ctx.outsider, "ActorBlocked"); + await model.control("clear-executor", () => + ctx.queue["unfreeze(address)"](ctx.outsider.address) + ); + await model.release(contractId, ctx.outsider); + model.mark("contract-owner-delivery"); + await model.control("fee-disable", () => + ctx.controller.setExitFeeEnabled(false) + ); + await model.settle( + ctx.carol, + "withdraw", + ethers.parseEther("0.01"), + true, + true + ); + await model.control("fee-enable", () => + ctx.controller.setExitFeeEnabled(true) + ); + + const actors = [ctx.alice, ctx.carol, ctx.contractActor]; + let enabled = true; + let feeFails = false; + let paused = false; + const bypassed = new Set(); + const frozen = new Set(); + for (let step = 0; step < steps; step++) { + const actor = actors[rng(actors.length)]; + const choice = rng(9); + model.mark("generated-step", { step, choice }); + if (choice < 2) { + const gross = BigInt(1 + rng(30)) * 10n ** 15n; + await model.settle( + actor, + choice === 0 ? "withdraw" : "adjust", + gross, + enabled && !bypassed.has(actor.address), + feeFails + ); + } else if (choice === 2) { + feeFails = !feeFails; + await model.control("generated-fee", () => + ctx.controller.setFeeReceiver( + feeFails ? ctx.nonPayable.target : ctx.feeReceiver.address + ) + ); + } else if (choice === 3) { + if (bypassed.has(actor.address)) bypassed.delete(actor.address); + else bypassed.add(actor.address); + await model.control("generated-bypass", () => + ctx.controller.setActorBypass(WITHDRAW, actor.address, [ + true, + bypassed.has(actor.address), + ]) + ); + } else if (choice === 4) { + enabled = !enabled; + await model.control("generated-enable", () => + ctx.controller.setSecurityPerimeterEnabled(enabled) + ); + } else if (choice === 5) { + await model.control("generated-reject-enable", () => + ctx.controller.setSecurityPerimeterEnabled(true) + ); + await model.control("generated-reject-bypass-clear", () => + ctx.controller.setActorBypass(WITHDRAW, ctx.alice.address, [ + false, + false, + ]) + ); + await model.control("remove-source", () => + ctx.queue.removeAllowedSource(ctx.borrower.target) + ); + await model.rejected(ctx.alice, "withdraw", "UnregisteredSource"); + await model.control("restore-source", () => + ctx.queue.addAllowedSource(ctx.borrower.target) + ); + await model.control("generated-reject-enable-restore", () => + ctx.controller.setSecurityPerimeterEnabled(enabled) + ); + await model.control("generated-reject-bypass-restore", () => + ctx.controller.setActorBypass(WITHDRAW, ctx.alice.address, [ + true, + bypassed.has(ctx.alice.address), + ]) + ); + } else if (choice === 6) { + paused = !paused; + await model.control("generated-pause", () => + ctx.queue.setSecurityPerimeterPaused(paused) + ); + } else if (choice === 7) { + const wasFrozen = frozen.has(actor.address); + if (wasFrozen) frozen.delete(actor.address); + else frozen.add(actor.address); + await model.control("generated-freeze", () => + ctx.queue[wasFrozen ? "unfreeze(address)" : "freeze(address)"]( + actor.address + ) + ); + } else { + const entries = [...model.requests.entries()]; + const [id, stored] = entries[rng(entries.length)]; + await model.mature(id); + const owner = + actors.find((entry) => entry.address === stored.receiver) || + ctx.bob; + const executor = owner.proxy ? ctx.outsider : owner; + const error = paused + ? "QueuePaused" + : stored.terminal + ? "AlreadyTerminal" + : frozen.has(stored.receiver) + ? "ActorBlocked" + : null; + await model.release(id, executor, error); + } + } + for (const required of [ + "surplus", + "withdraw", + "adjust", + "record-reject", + "fee-receiver-failure", + "actor-bypass", + "delay-disable", + "NotUnlocked", + "NotExecutor", + "ActorBlocked", + "QueuePaused", + "AlreadyTerminal", + "release", + "contract-owner-delivery", + "fee-disable", + "below-floor", + ]) + assert(model.counts[required] > 0, `Transition not reached: ${required}`); + assert.equal( + model.counts["generated-step"], + steps, + "Generated sequence must complete its configured budget" + ); + report.status = "passed"; + report.counts = model.counts; + report.contractCalls = model.contractCalls; + report.receipts = model.receipts; + report.evidence = model.evidence; + report.trace = model.trace; + report.requests = [...model.requests.entries()]; + report.finalOutstandingRequests = [...model.requests.values()].filter( + (request) => !request.terminal + ).length; + } catch (error) { + report.status = "failed"; + report.error = error.message; + report.trace = model.trace; + report.counts = model.counts; + report.contractCalls = model.contractCalls; + report.evidence = model.evidence; + error.message += `\nComposition seed=${seed}, run=${run}, trace=${JSON.stringify( + model.trace + )}`; + throw error; + } + } + }); + } +}); diff --git a/tests-release/ZeroContractOwner.integration.test.js b/tests-release/ZeroContractOwner.integration.test.js new file mode 100644 index 0000000..6a5d5d7 --- /dev/null +++ b/tests-release/ZeroContractOwner.integration.test.js @@ -0,0 +1,132 @@ +const assert = require("assert").strict; +const { expect } = require("chai"); +const { ethers, network } = require("hardhat"); +const { fixture, balances, Model, SURPLUS } = require("./helpers/composition"); + +describe("Zero genuine contract-owner and composite wrapper settlement", function () { + let ctx; + let baseline; + before(async function () { + ctx = await fixture(); + baseline = await network.provider.send("evm_snapshot"); + }); + beforeEach(async function () { + await network.provider.send("evm_revert", [baseline]); + baseline = await network.provider.send("evm_snapshot"); + }); + + it("records the DSProxy withdrawal identity and lets a clean outsider pay only that contract", async function () { + const actor = ctx.contractActor; + await ctx.open(actor); + const model = new Model(ctx); + const id = await model.settle(actor, "withdraw", ethers.parseEther("0.01"), true); + assert.notEqual(await ethers.provider.getCode(actor.address), "0x"); + await model.release(id, ctx.outsider, "NotUnlocked"); + await model.mature(id); + await model.control("freeze-executor", () => + ctx.queue["freeze(address)"](ctx.outsider.address) + ); + await model.release(id, ctx.outsider, "ActorBlocked"); + await model.control("clear-executor", () => + ctx.queue["unfreeze(address)"](ctx.outsider.address) + ); + const outsiderBefore = await ethers.provider.getBalance(ctx.outsider.address); + await model.release(id, ctx.outsider); + assert( + (await ethers.provider.getBalance(ctx.outsider.address)) < outsiderBefore, + "Outsider pays gas and receives no collateral" + ); + await model.release(id, ctx.outsider, "AlreadyTerminal"); + }); + + it("rolls back a held surplus-to-new-trove composite, then permits plain claim, outsider delivery and separate opening", async function () { + const actor = ctx.contractActor; + const gross = await ctx.createSurplus(actor); + const before = await balances(ctx, actor, SURPLUS); + assert.equal(before.status, "4", "Redemption must close the actual contract-owned trove"); + const payload = ctx.wrapperScript.interface.encodeFunctionData( + "claimCollateralAndOpenTrove", + [ + ethers.parseEther("1"), + ethers.parseEther("200"), + ethers.ZeroAddress, + ethers.ZeroAddress, + ] + ); + await expect( + actor.proxy["execute(address,bytes)"](ctx.wrapperScript.target, payload, { + gasLimit: 8000000, + }) + ).to.be.revertedWithoutReason(); + assert.deepEqual( + await balances(ctx, actor, SURPLUS), + before, + "Composite revert must restore funded claim, fee, queue, pool and proxy state" + ); + const model = new Model(ctx); + const id = await model.settle(actor, "surplus", gross, true); + await model.mature(id); + await model.release(id, ctx.outsider); + const delivered = await ethers.provider.getBalance(actor.address); + assert(delivered > 0n, "Delivered surplus must be actual native collateral at DSProxy"); + const transfer = ctx.wrapperScript.interface.encodeFunctionData("transferETH", [ + ctx.alice.address, + delivered, + ]); + const ownerBefore = await ethers.provider.getBalance(ctx.alice.address); + const transferReceipt = await ( + await actor.proxy["execute(address,bytes)"](ctx.wrapperScript.target, transfer) + ).wait(); + assert.equal( + await ethers.provider.getBalance(ctx.alice.address), + ownerBefore + delivered - transferReceipt.gasUsed * transferReceipt.gasPrice, + "The owner must actually receive the delivered collateral before forwarding it" + ); + assert.equal(await ethers.provider.getBalance(actor.address), 0n); + const script = ctx.boScript.interface; + const open = script.encodeFunctionData("openTrove", [ + ethers.parseEther("1"), + ethers.parseEther("200"), + ethers.ZeroAddress, + ethers.ZeroAddress, + ]); + await ( + await actor.proxy["execute(address,bytes)"](ctx.boScript.target, open, { + value: delivered, + }) + ).wait(); + assert.equal(String(await ctx.contracts.troveManager.getTroveStatus(actor.address)), "1"); + await model.invariants(); + }); + + it("completes the same genuine composite with positive fee when delay is disabled", async function () { + const actor = ctx.contractActor; + const gross = await ctx.createSurplus(actor); + await (await ctx.controller.setSecurityPerimeterEnabled(false)).wait(); + const feeBefore = await ethers.provider.getBalance(ctx.feeReceiver.address); + const payload = ctx.wrapperScript.interface.encodeFunctionData( + "claimCollateralAndOpenTrove", + [ + ethers.parseEther("1"), + ethers.parseEther("200"), + ethers.ZeroAddress, + ethers.ZeroAddress, + ] + ); + await ( + await actor.proxy["execute(address,bytes)"](ctx.wrapperScript.target, payload) + ).wait(); + assert.equal( + await ethers.provider.getBalance(ctx.feeReceiver.address), + feeBefore + (gross * 50n) / 10000n + ); + assert.equal(String(await ctx.contracts.troveManager.getTroveStatus(actor.address)), "1"); + assert.equal( + String(await ctx.contracts.collSurplusPool.getCollateral(actor.address)), + "0" + ); + assert.equal(await ctx.queue.lastRequestId(), 0n); + assert.equal(await ctx.queue.totalEscrowed(ethers.ZeroAddress), 0n); + assert.equal(await ethers.provider.getBalance(ctx.queue.target), 0n); + }); +}); diff --git a/tests-release/ZeroCore.integration.test.js b/tests-release/ZeroCore.integration.test.js new file mode 100644 index 0000000..d2a65e3 --- /dev/null +++ b/tests-release/ZeroCore.integration.test.js @@ -0,0 +1,304 @@ +const { expect } = require("chai"); +const { ethers, network } = require("hardhat"); +const deploymentHelper = require("../utils/js/deploymentHelpers.js"); +const { TestHelper: th, TimeValues: timeValues } = require("../utils/js/testHelpers.js"); +const { deployCore, requestFromReceipt, WITHDRAW, SURPLUS, DELAY } = require("./helpers/core"); +const { verifyZeroReleaseArtifacts } = require("./helpers/zero"); + +describe("Zero exact-release integration with real Perimeter core", function () { + let contracts; + let owner; + let alice; + let whale; + let outsider; + let feeReceiver; + let borrower; + let controller; + let queue; + let snapshot; + + before(async function () { + if (process.env.ZERO_RELEASE_FORK_RPC) throw new Error("Run this suite without a fork"); + await verifyZeroReleaseArtifacts(); + [owner, alice, whale, outsider, feeReceiver] = await ethers.getSigners(); + contracts = await deploymentHelper.deployLiquityCoreHardhat(); + const zero = await deploymentHelper.deployZEROTesterContractsHardhat(owner.address); + await zero.zeroToken.unprotectedMint(owner.address, th.dec(20, 24)); + await deploymentHelper.connectZEROContracts(zero); + await deploymentHelper.connectCoreContracts(contracts, zero); + await deploymentHelper.connectZEROContractsToCore(zero, contracts); + borrower = await ethers.getContractAt( + "BorrowerOperations", + contracts.borrowerOperations.address + ); + ({ controller, queue } = await deployCore({ + owner, + source: borrower.target, + wrbtc: contracts.wrbtcTokenTester.address, + feeReceiver: feeReceiver.address, + })); + await (await borrower.setExitFeeController(controller.target)).wait(); + await (await borrower.setExitDelayQueue(queue.target)).wait(); + }); + + beforeEach(async function () { + snapshot = await network.provider.send("evm_snapshot"); + }); + + afterEach(async function () { + await network.provider.send("evm_revert", [snapshot]); + }); + + async function open(actor, extra = {}) { + const amount = + ethers.parseEther("2000") + BigInt(extra.extraZUSDAmount?.toString() || "0"); + const fee = BigInt( + (await contracts.troveManager.getBorrowingFeeWithDecay(amount.toString())).toString() + ); + const reserve = await borrower.ZUSD_GAS_COMPENSATION(); + const price = BigInt((await contracts.priceFeedTestnet.getPrice()).toString()); + const ratio = BigInt(extra.ICR?.toString() || ethers.parseEther("3").toString()); + const value = extra.extraParams?.value || (ratio * (amount + fee + reserve)) / price; + await ( + await borrower + .connect(actor) + .openTrove( + ethers.parseEther("1"), + amount, + ethers.ZeroAddress, + ethers.ZeroAddress, + { value } + ) + ).wait(); + const debt = BigInt((await contracts.troveManager.getTroveDebt(actor.address)).toString()); + return { netDebt: th.toBN((debt - reserve).toString()) }; + } + + async function assertQueued(transaction, gross, surface) { + const pool = + surface === SURPLUS ? contracts.collSurplusPool.address : contracts.activePool.address; + const poolBefore = await ethers.provider.getBalance(pool); + const feeBefore = await ethers.provider.getBalance(feeReceiver.address); + const queueBefore = await queue.totalEscrowed(ethers.ZeroAddress); + const receipt = await (await transaction()).wait(); + const request = await requestFromReceipt(queue, receipt); + const fee = (gross * 50n) / 10000n; + expect(request.amount).to.equal(gross - fee); + expect(request.originator).to.equal(alice.address); + expect(request.owner).to.equal(alice.address); + expect(request.receiver).to.equal(alice.address); + expect(request.token).to.equal(ethers.ZeroAddress); + expect(request.surfaceId).to.equal(surface); + expect(request.subProduct).to.equal(ethers.ZeroAddress); + expect(request.unlockAt - request.createdAt).to.equal(BigInt(DELAY)); + expect(await queue.totalEscrowed(ethers.ZeroAddress)).to.equal(queueBefore + gross - fee); + expect(await ethers.provider.getBalance(feeReceiver.address)).to.equal(feeBefore + fee); + expect(await ethers.provider.getBalance(pool)).to.equal(poolBefore - gross); + return request; + } + + it("withdrawal charges the fee first and records the exact net with immutable actors", async function () { + await open(alice); + await assertQueued( + () => + borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), alice.address, alice.address), + ethers.parseEther("1"), + WITHDRAW + ); + }); + + it("adjustTrove uses the same real-core measured native receipt", async function () { + await open(alice); + await assertQueued( + () => + borrower + .connect(alice) + .adjustTrove( + ethers.parseEther("1"), + ethers.parseEther("1"), + 0, + false, + alice.address, + alice.address + ), + ethers.parseEther("1"), + WITHDRAW + ); + }); + + it("closeTrove escrows all collateral and closes only after real queue recording succeeds", async function () { + await open(alice); + await open(whale); + await contracts.zusdToken.transfer(alice.address, th.dec(100, 18), { + from: whale.address, + }); + const gross = BigInt( + (await contracts.troveManager.getTroveColl(alice.address)).toString() + ); + await assertQueued(() => borrower.connect(alice).closeTrove(), gross, WITHDRAW); + expect((await contracts.troveManager.getTroveStatus(alice.address)).toString()).to.equal( + "2" + ); + }); + + it("record rejection rolls back a positive fee, pool debit and trove collateral, then permits retry", async function () { + await open(alice); + await (await queue.removeAllowedSource(borrower.target)).wait(); + const collateral = await contracts.troveManager.getTroveColl(alice.address); + const poolBefore = await ethers.provider.getBalance(contracts.activePool.address); + const feeBefore = await ethers.provider.getBalance(feeReceiver.address); + await expect( + borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), alice.address, alice.address) + ).to.be.revertedWithCustomError(queue, "UnregisteredSource"); + expect((await contracts.troveManager.getTroveColl(alice.address)).toString()).to.equal( + collateral.toString() + ); + expect(await ethers.provider.getBalance(contracts.activePool.address)).to.equal( + poolBefore + ); + expect(await ethers.provider.getBalance(feeReceiver.address)).to.equal(feeBefore); + expect(await queue.lastRequestId()).to.equal(0n); + await (await queue.addAllowedSource(borrower.target)).wait(); + await assertQueued( + () => + borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), alice.address, alice.address), + ethers.parseEther("1"), + WITHDRAW + ); + }); + + it("fee-receiver failure escrows gross rather than bypassing the real delay", async function () { + await open(alice); + const nonPayable = await (await ethers.getContractFactory("NonPayable")).deploy(); + await (await controller.setFeeReceiver(nonPayable.target)).wait(); + const receipt = await ( + await borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), alice.address, alice.address) + ).wait(); + expect((await requestFromReceipt(queue, receipt)).amount).to.equal(ethers.parseEther("1")); + expect(await ethers.provider.getBalance(nonPayable.target)).to.equal(0n); + }); + + it("disabled perimeter pays directly even when the queue source is unregistered", async function () { + await open(alice); + await (await controller.setSecurityPerimeterEnabled(false)).wait(); + await (await queue.removeAllowedSource(borrower.target)).wait(); + await ( + await borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), alice.address, alice.address) + ).wait(); + expect(await queue.lastRequestId()).to.equal(0n); + expect(await queue.totalEscrowed(ethers.ZeroAddress)).to.equal(0n); + expect(await ethers.provider.getBalance(queue.target)).to.equal(0n); + }); + + it("queue floor rejects an undersized delay atomically", async function () { + await open(alice); + await (await controller.setGlobalDelaySeconds(1)).wait(); + await expect( + borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), alice.address, alice.address) + ).to.be.revertedWithCustomError(queue, "DelayBelowFloor"); + expect(await queue.lastRequestId()).to.equal(0n); + expect(await ethers.provider.getBalance(queue.target)).to.equal(0n); + }); + + it("early, unauthorized, frozen and duplicate release fail; eligible release pays exactly once", async function () { + await open(alice); + const request = await assertQueued( + () => + borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), alice.address, alice.address), + ethers.parseEther("1"), + WITHDRAW + ); + await expect(queue.connect(alice).executeExit(1)).to.be.revertedWithCustomError( + queue, + "NotUnlocked" + ); + await network.provider.send("evm_setNextBlockTimestamp", [Number(request.unlockAt)]); + await network.provider.send("evm_mine"); + await expect(queue.connect(outsider).executeExit(1)).to.be.revertedWithCustomError( + queue, + "NotExecutor" + ); + await ( + await queue["freezeFromRequest(uint256,bool,bytes32)"]( + 1, + true, + ethers.id("release-proof") + ) + ).wait(); + await expect(queue.connect(alice).executeExit(1)).to.be.revertedWithCustomError( + queue, + "ActorBlocked" + ); + await (await queue["unfreeze(address)"](alice.address)).wait(); + const balanceBefore = await ethers.provider.getBalance(alice.address); + const receipt = await (await queue.connect(alice).executeExit(1)).wait(); + const gas = receipt.gasUsed * receipt.gasPrice; + expect(await ethers.provider.getBalance(alice.address)).to.equal( + balanceBefore + request.amount - gas + ); + expect((await queue.getRequest(1)).status).to.equal(2n); + expect(await queue.totalEscrowed(ethers.ZeroAddress)).to.equal(0n); + await expect(queue.connect(alice).executeExit(1)).to.be.revertedWithCustomError( + queue, + "AlreadyTerminal" + ); + }); + + async function createSurplus() { + await contracts.priceFeedTestnet.setPrice(th.dec(100, 18)); + const { netDebt } = await open(alice, { ICR: th.toBN(th.dec(200, 16)) }); + await open(whale, { + extraZUSDAmount: netDebt, + extraParams: { from: whale.address, value: th.dec(3000, "ether") }, + }); + await th.fastForwardTime(timeValues.SECONDS_IN_ONE_WEEK * 2, web3.currentProvider); + await th.redeemCollateralAndGetTxObject(whale.address, contracts, netDebt); + const gross = BigInt( + (await contracts.collSurplusPool.getCollateral(alice.address)).toString() + ); + expect(gross).to.be.greaterThan(0n); + return gross; + } + + it("funded surplus charges a fee and escrows the exact net in the real queue", async function () { + const gross = await createSurplus(); + await assertQueued(() => borrower.connect(alice).claimCollateral(), gross, SURPLUS); + expect((await contracts.collSurplusPool.getCollateral(alice.address)).toString()).to.equal( + "0" + ); + }); + + it("surplus record rejection restores the funded claim and positive fee before retry", async function () { + const gross = await createSurplus(); + await (await queue.removeAllowedSource(borrower.target)).wait(); + const feeBefore = await ethers.provider.getBalance(feeReceiver.address); + const poolBefore = await ethers.provider.getBalance(contracts.collSurplusPool.address); + await expect(borrower.connect(alice).claimCollateral()).to.be.revertedWithCustomError( + queue, + "UnregisteredSource" + ); + expect((await contracts.collSurplusPool.getCollateral(alice.address)).toString()).to.equal( + gross.toString() + ); + expect(await ethers.provider.getBalance(feeReceiver.address)).to.equal(feeBefore); + expect(await ethers.provider.getBalance(contracts.collSurplusPool.address)).to.equal( + poolBefore + ); + await (await queue.addAllowedSource(borrower.target)).wait(); + await assertQueued(() => borrower.connect(alice).claimCollateral(), gross, SURPLUS); + }); +}); diff --git a/tests-release/ZeroFork.rehearsal.test.js b/tests-release/ZeroFork.rehearsal.test.js new file mode 100644 index 0000000..78886f3 --- /dev/null +++ b/tests-release/ZeroFork.rehearsal.test.js @@ -0,0 +1,561 @@ +const fs = require("fs"); +const path = require("path"); +const { expect } = require("chai"); +const { ethers, network, artifacts } = require("hardhat"); +const { deployCore, requestFromReceipt, WITHDRAW, DELAY } = require("./helpers/core"); +const { verifyZeroReleaseArtifacts } = require("./helpers/zero"); + +const CONTROLLER = "0x99994b4522483de17f31a5bc010c5901add3440e"; +const WRBTC = "0x542fda317318ebf1d3deaf76e0b632741a7e677d"; +const records = path.resolve(__dirname, "../deployment/deployments/rskSovrynMainnet"); +const record = (name) => JSON.parse(fs.readFileSync(path.join(records, `${name}.json`), "utf8")); + +async function attachProxy(name) { + const implementation = await artifacts.readArtifact(name); + const proxy = await artifacts.readArtifact("UpgradableProxy"); + return new ethers.Contract( + record(`${name}_Proxy`).address, + [...implementation.abi, ...proxy.abi], + ethers.provider + ); +} + +async function impersonate(address) { + if (network.name !== "hardhat") throw new Error("Impersonation is local-only"); + await network.provider.send("hardhat_impersonateAccount", [address]); + await network.provider.send("hardhat_setBalance", [address, "0x3635c9adc5dea00000"]); + return ethers.getSigner(address); +} + +async function slots(address, indexes) { + const values = []; + for (let offset = 0; offset < indexes.length; offset += 8) { + values.push( + ...(await Promise.all( + indexes + .slice(offset, offset + 8) + .map((index) => ethers.provider.getStorage(address, index)) + )) + ); + } + return values; +} + +describe("Zero mainnet-fork exact-release technical rehearsal", function () { + let borrower; + let manager; + let surplusPool; + let alice; + let whale; + let owner; + let controller; + let queue; + let snapshot; + let originalImplementations; + const proof = { + productionTransactions: 0, + authorityMode: "local impersonation, not governance voting", + steps: [], + }; + + before(async function () { + proof.zeroBuild = await verifyZeroReleaseArtifacts(); + if ( + network.name !== "hardhat" || + !process.env.ZERO_RELEASE_FORK_RPC || + !process.env.ZERO_RELEASE_FORK_HASH + ) { + throw new Error( + "Use the secret-free Hardhat config with explicit fork RPC, block and hash" + ); + } + const upstream = new ethers.JsonRpcProvider(process.env.ZERO_RELEASE_FORK_RPC); + try { + expect((await upstream.getNetwork()).chainId).to.equal(30n); + const block = await upstream.getBlock(Number(process.env.ZERO_RELEASE_FORK_BLOCK)); + expect(block.hash.toLowerCase()).to.equal( + process.env.ZERO_RELEASE_FORK_HASH.toLowerCase() + ); + proof.fork = { number: block.number, hash: block.hash, chainId: 30 }; + } finally { + upstream.destroy(); + } + [alice, whale] = await ethers.getSigners(); + const priceFeed = await attachProxy("PriceFeed"); + proof.oracle = { + pinnedTimestamp: ( + await ethers.provider.getBlock(Number(process.env.ZERO_RELEASE_FORK_BLOCK)) + ).timestamp, + beforeUpgradeTimestamp: (await ethers.provider.getBlock("latest")).timestamp, + price: (await priceFeed.fetchPrice.staticCall()).toString(), + }; + borrower = await attachProxy("BorrowerOperations"); + manager = await attachProxy("TroveManager"); + surplusPool = await attachProxy("CollSurplusPool"); + owner = await impersonate(await borrower.getOwner()); + expect(await manager.getOwner()).to.equal(owner.address); + expect(await surplusPool.getOwner()).to.equal(owner.address); + + const existingController = new ethers.Contract( + CONTROLLER, + ["function owner() view returns(address)"], + ethers.provider + ); + const coreOwner = await impersonate(await existingController.owner()); + const controllerSlots = Array.from({ length: 258 }, (_, index) => index); + const controllerBefore = await slots(CONTROLLER, controllerSlots); + ({ controller, queue } = await deployCore({ + owner: coreOwner, + source: borrower.target, + wrbtc: WRBTC, + controllerAddress: CONTROLLER, + configure: false, + })); + expect(await slots(CONTROLLER, controllerSlots)).to.deep.equal(controllerBefore); + expect(await controller.owner()).to.equal(coreOwner.address); + proof.controllerStoragePrefix = { wordsCompared: 258, unchanged: true }; + + const layout = JSON.parse( + fs.readFileSync( + path.resolve( + __dirname, + "../tests-perimeter/baselines/storage-layout.sovryn-perimeter-fee.json" + ), + "utf8" + ) + ); + const hosts = [ + ["BorrowerOperations", borrower], + ["CollSurplusPool", surplusPool], + ["TroveManager", manager], + ]; + const ownerCount = await manager.getTroveOwnersCount(); + expect(ownerCount).to.be.greaterThan(0n); + const liveTroves = []; + for (let index = 0; index < Math.min(Number(ownerCount), 3); index += 1) { + const actor = await manager.getTroveFromTroveOwnersArray(index); + liveTroves.push({ + actor, + trove: (await manager.Troves(actor)).map((value) => value.toString()), + entireDebtAndColl: (await manager.getEntireDebtAndColl(actor)).map((value) => + value.toString() + ), + surplus: (await surplusPool.getCollateral(actor)).toString(), + }); + } + const excluded = []; + for (const name of [ + "StabilityPool", + "TroveManagerRedeemOps", + "ZUSDToken", + "CommunityIssuance", + "PriceFeed", + "ActivePool", + "DefaultPool", + ]) { + const deployed = record(name); + const runtime = await ethers.provider.getCode(deployed.address); + const entry = { name, address: deployed.address, codehash: ethers.keccak256(runtime) }; + if (fs.existsSync(path.join(records, `${name}_Proxy.json`))) { + const proxy = await ethers.getContractAt("UpgradableProxy", deployed.address); + entry.implementation = await proxy.getImplementation(); + } + excluded.push(entry); + } + const preserved = []; + for (const [name, host] of hosts) { + const indexes = [ + ...new Set( + layout[`contracts/${name}.sol:${name}`].map((entry) => Number(entry.slot)) + ), + ]; + preserved.push({ + name, + host, + indexes, + storage: await slots(host.target, indexes), + balance: await ethers.provider.getBalance(host.target), + owner: await host.getOwner(), + implementation: await host.getImplementation(), + }); + } + const immutablePermit2 = await manager.permit2(); + const bootstrap = await manager.BOOTSTRAP_PERIOD(); + expect(await borrower.permit2()).to.equal(immutablePermit2); + const candidates = { + BorrowerOperations: await ( + await ethers.getContractFactory("BorrowerOperations") + ).deploy(immutablePermit2), + CollSurplusPool: await (await ethers.getContractFactory("CollSurplusPool")).deploy(), + TroveManager: await ( + await ethers.getContractFactory("TroveManager") + ).deploy(bootstrap, immutablePermit2), + BorrowerOperationsPerimeterOps: await ( + await ethers.getContractFactory("BorrowerOperationsPerimeterOps") + ).deploy(), + }; + for (const [name, candidate] of Object.entries(candidates)) { + await candidate.waitForDeployment(); + const runtime = await ethers.provider.getCode(candidate.target); + expect((runtime.length - 2) / 2).to.be.at.most(24576); + proof.steps.push({ + candidate: name, + address: candidate.target, + codehash: ethers.keccak256(runtime), + }); + } + expect(await candidates.TroveManager.permit2()).to.equal(immutablePermit2); + expect(await candidates.TroveManager.BOOTSTRAP_PERIOD()).to.equal(bootstrap); + expect(await candidates.BorrowerOperations.permit2()).to.equal(immutablePermit2); + const order = [ + [surplusPool.connect(owner), "setImplementation", candidates.CollSurplusPool.target], + [borrower.connect(owner), "setImplementation", candidates.BorrowerOperations.target], + [ + borrower.connect(owner), + "setPerimeterOps", + candidates.BorrowerOperationsPerimeterOps.target, + ], + [borrower.connect(owner), "setExitDelayQueue", queue.target], + [manager.connect(owner), "setImplementation", candidates.TroveManager.target], + ]; + for (const [host, method, argument] of order) { + const receipt = await (await host[method](argument)).wait(); + proof.steps.push({ + target: host.target, + method, + argument, + transactionHash: receipt.hash, + }); + } + for (const entry of preserved) { + expect(await slots(entry.host.target, entry.indexes)).to.deep.equal(entry.storage); + expect(await entry.host.getOwner()).to.equal(entry.owner); + expect(await ethers.provider.getBalance(entry.host.target)).to.equal(entry.balance); + expect(await entry.host.getImplementation()).to.equal(candidates[entry.name].target); + } + proof.zeroStoragePrefixes = preserved.map(({ name, indexes }) => ({ + name, + wordsCompared: indexes.length, + unchanged: true, + })); + expect(await manager.getTroveOwnersCount()).to.equal(ownerCount); + for (const entry of liveTroves) { + expect( + (await manager.Troves(entry.actor)).map((value) => value.toString()) + ).to.deep.equal(entry.trove); + expect( + (await manager.getEntireDebtAndColl(entry.actor)).map((value) => value.toString()) + ).to.deep.equal(entry.entireDebtAndColl); + expect((await surplusPool.getCollateral(entry.actor)).toString()).to.equal( + entry.surplus + ); + } + proof.liveTroves = liveTroves.map((entry) => ({ ...entry, unchanged: true })); + proof.liveTroveOwnerCount = ownerCount.toString(); + expect(await borrower.exitFeeController()).to.equal(ethers.getAddress(CONTROLLER)); + expect(await borrower.exitDelayQueue()).to.equal(queue.target); + expect(await borrower.perimeterOps()).to.equal( + candidates.BorrowerOperationsPerimeterOps.target + ); + await (await controller.setGlobalDelaySeconds(DELAY)).wait(); + await (await controller.setSecurityPerimeterEnabled(true)).wait(); + proof.zeroOldImplementations = preserved.map(({ name, implementation }) => ({ + name, + implementation, + })); + originalImplementations = Object.fromEntries( + proof.zeroOldImplementations.map(({ name, implementation }) => [name, implementation]) + ); + proof.constructorInputs = { + permit2: immutablePermit2, + bootstrapPeriod: bootstrap.toString(), + }; + proof.oracle.afterUpgradeTimestamp = (await ethers.provider.getBlock("latest")).timestamp; + for (let index = 0; index < 2; index += 1) { + const oracle = new ethers.Contract( + await priceFeed.getPriceFeedAtIndex(index), + ["function latestAnswer() view returns(uint256,bool)"], + ethers.provider + ); + proof.oracle[`feed${index}`] = (await oracle.latestAnswer()).map((value) => + typeof value === "bigint" ? value.toString() : value + ); + } + for (const entry of excluded) { + expect(ethers.keccak256(await ethers.provider.getCode(entry.address))).to.equal( + entry.codehash + ); + if (entry.implementation) { + const proxy = await ethers.getContractAt("UpgradableProxy", entry.address); + expect(await proxy.getImplementation()).to.equal(entry.implementation); + } + } + proof.excludedContracts = excluded.map((entry) => ({ + ...entry, + unchangedDuringInstallation: true, + })); + proof.oracle.originalFeeds = await Promise.all( + [0, 1].map((index) => priceFeed.getPriceFeedAtIndex(index)) + ); + proof.oracle.productionImplementation = await priceFeed.getImplementation(); + const testOracle = await ( + await ethers.getContractFactory("ExternalPriceFeedTester") + ).deploy(); + await testOracle.waitForDeployment(); + await (await testOracle.setLatestAnswer(BigInt(proof.oracle.price), true)).wait(); + await ( + await priceFeed + .connect(await impersonate(await priceFeed.getOwner())) + .setAddress(0, testOracle.target) + ).wait(); + expect(await priceFeed.getImplementation()).to.equal( + proof.oracle.productionImplementation + ); + proof.oracle.fixture = { + address: testOracle.target, + price: proof.oracle.price, + scope: "test-only primary adapter, production Zero PriceFeed implementation unchanged", + }; + }); + + beforeEach(async function () { + snapshot = await network.provider.send("evm_snapshot"); + await network.provider.send("evm_setNextBlockTimestamp", [ + (await ethers.provider.getBlock("latest")).timestamp + 1, + ]); + }); + + afterEach(async function () { + await network.provider.send("evm_revert", [snapshot]); + }); + + after(function () { + const output = process.env.ZERO_RELEASE_FORK_REPORT; + if (output) fs.writeFileSync(output, JSON.stringify(proof, null, 2)); + }); + + async function open(actor) { + await network.provider.send("evm_setNextBlockTimestamp", [ + (await ethers.provider.getBlock("latest")).timestamp + 1, + ]); + await ( + await borrower + .connect(actor) + .openTrove( + ethers.parseEther("1"), + ethers.parseEther("2000"), + ethers.ZeroAddress, + ethers.ZeroAddress, + { value: ethers.parseEther("10") } + ) + ).wait(); + } + + it("live proxies preserve their state and constructor immutables through the five-action installation", function () { + expect(proof.zeroStoragePrefixes).to.have.length(3); + expect(proof.steps.filter((step) => step.method).map((step) => step.method)).to.deep.equal( + [ + "setImplementation", + "setImplementation", + "setPerimeterOps", + "setExitDelayQueue", + "setImplementation", + ] + ); + }); + + it("live withdrawal records the real fee quote and releases only after 24 hours", async function () { + await open(alice); + const gross = ethers.parseEther("1"); + const quote = await controller.quoteExitFee( + WITHDRAW, + ethers.ZeroAddress, + alice.address, + gross + ); + const receipt = await ( + await borrower + .connect(alice) + .withdrawColl(gross, ethers.ZeroAddress, ethers.ZeroAddress) + ).wait(); + const request = await requestFromReceipt(queue, receipt); + expect(request.amount).to.equal(quote.netAmount); + expect(request.unlockAt - request.createdAt).to.equal(BigInt(DELAY)); + expect(request.owner).to.equal(alice.address); + await expect(queue.connect(alice).executeExit(1)).to.be.revertedWithCustomError( + queue, + "NotUnlocked" + ); + await network.provider.send("evm_setNextBlockTimestamp", [Number(request.unlockAt)]); + await network.provider.send("evm_mine"); + await (await queue.connect(alice).executeExit(1)).wait(); + expect((await queue.getRequest(1)).status).to.equal(2n); + expect(await queue.totalEscrowed(ethers.ZeroAddress)).to.equal(0n); + proof.withdrawal = { + gross: gross.toString(), + net: request.amount.toString(), + fee: quote.feeAmount.toString(), + released: true, + }; + }); + + it("live collateral adjustment uses the installed real queue", async function () { + await open(alice); + await ( + await borrower + .connect(alice) + .adjustTrove( + ethers.parseEther("1"), + ethers.parseEther("1"), + 0, + false, + ethers.ZeroAddress, + ethers.ZeroAddress + ) + ).wait(); + expect(await queue.lastRequestId()).to.equal(1n); + proof.adjustment = { queued: true }; + }); + + it("live close clears debt while holding the collateral in the real queue", async function () { + await open(alice); + await open(whale); + const token = await ethers.getContractAt("ZUSDToken", record("ZUSDToken").address); + const repayment = + (await manager.getTroveDebt(alice.address)) - (await borrower.ZUSD_GAS_COMPENSATION()); + const shortfall = repayment - (await token.balanceOf(alice.address)); + expect(shortfall).to.be.greaterThan(0n); + await (await token.connect(whale).transfer(alice.address, shortfall)).wait(); + await (await borrower.connect(alice).closeTrove()).wait(); + expect(await manager.getTroveStatus(alice.address)).to.equal(2n); + expect(await queue.lastRequestId()).to.equal(1n); + proof.close = { + debtClosed: true, + collateralHeld: true, + repayment: repayment.toString(), + fundedShortfall: shortfall.toString(), + }; + }); + + it("live queue rejection leaves the original collateral and balances intact and permits retry", async function () { + await open(alice); + await (await queue.removeAllowedSource(borrower.target)).wait(); + const collateral = await manager.getTroveColl(alice.address); + const pool = await ethers.getContractAt("ActivePool", record("ActivePool").address); + const poolBalance = await ethers.provider.getBalance(pool.target); + await expect( + borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), ethers.ZeroAddress, ethers.ZeroAddress) + ).to.be.revertedWithCustomError(queue, "UnregisteredSource"); + expect(await manager.getTroveColl(alice.address)).to.equal(collateral); + expect(await ethers.provider.getBalance(pool.target)).to.equal(poolBalance); + expect(await queue.lastRequestId()).to.equal(0n); + await (await queue.addAllowedSource(borrower.target)).wait(); + await ( + await borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), ethers.ZeroAddress, ethers.ZeroAddress) + ).wait(); + expect(await queue.lastRequestId()).to.equal(1n); + proof.rejectionRollback = { unchanged: true, retrySucceeded: true }; + }); + + it("emergency disable leaves the live withdrawal usable without touching queue recording", async function () { + await open(alice); + await (await controller.setSecurityPerimeterEnabled(false)).wait(); + await (await queue.removeAllowedSource(borrower.target)).wait(); + await ( + await borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), ethers.ZeroAddress, ethers.ZeroAddress) + ).wait(); + expect(await queue.lastRequestId()).to.equal(0n); + proof.disabled = { withdrawalSucceeded: true, queueUntouched: true }; + }); + + it("funded live surplus settles fee-first into the real queue and rolls back rejected consumption", async function () { + const gross = ethers.parseEther("1"); + const activePool = await borrower.activePool(); + await ( + await ( + await impersonate(activePool) + ).sendTransaction({ to: surplusPool.target, value: gross }) + ).wait(); + await ( + await surplusPool + .connect(await impersonate(manager.target)) + .accountSurplus(alice.address, gross) + ).wait(); + const quote = await controller.quoteExitFee( + ethers.id("PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS"), + ethers.ZeroAddress, + alice.address, + gross + ); + const poolBefore = await ethers.provider.getBalance(surplusPool.target); + const feeReceiver = await controller.feeReceiver(); + const feeBefore = await ethers.provider.getBalance(feeReceiver); + await (await queue.removeAllowedSource(borrower.target)).wait(); + await expect(borrower.connect(alice).claimCollateral()).to.be.revertedWithCustomError( + queue, + "UnregisteredSource" + ); + expect(await surplusPool.getCollateral(alice.address)).to.equal(gross); + expect(await ethers.provider.getBalance(surplusPool.target)).to.equal(poolBefore); + expect(await ethers.provider.getBalance(feeReceiver)).to.equal(feeBefore); + await (await queue.addAllowedSource(borrower.target)).wait(); + const receipt = await (await borrower.connect(alice).claimCollateral()).wait(); + const request = await requestFromReceipt(queue, receipt); + expect(request.amount).to.equal(quote.netAmount); + expect(request.owner).to.equal(alice.address); + expect(await surplusPool.getCollateral(alice.address)).to.equal(0n); + expect(await ethers.provider.getBalance(surplusPool.target)).to.equal(poolBefore - gross); + expect(await ethers.provider.getBalance(feeReceiver)).to.equal( + feeBefore + quote.feeAmount + ); + proof.surplus = { + gross: gross.toString(), + net: request.amount.toString(), + fee: quote.feeAmount.toString(), + rejectionRolledBack: true, + fixture: + "funding/accounting through impersonated authorised ActivePool and TroveManager; not a liquidation proof", + }; + }); + + it("host implementation rollback preserves an already queued claim and its eventual payout", async function () { + await open(alice); + const receipt = await ( + await borrower + .connect(alice) + .withdrawColl(ethers.parseEther("1"), ethers.ZeroAddress, ethers.ZeroAddress) + ).wait(); + const request = await requestFromReceipt(queue, receipt); + await (await controller.setSecurityPerimeterEnabled(false)).wait(); + for (const [name, host] of [ + ["BorrowerOperations", borrower], + ["CollSurplusPool", surplusPool], + ["TroveManager", manager], + ]) { + await ( + await host.connect(owner).setImplementation(originalImplementations[name]) + ).wait(); + expect(await host.getImplementation()).to.equal(originalImplementations[name]); + } + expect(await queue.totalEscrowed(ethers.ZeroAddress)).to.equal(request.amount); + await network.provider.send("evm_setNextBlockTimestamp", [Number(request.unlockAt)]); + await network.provider.send("evm_mine"); + const balance = await ethers.provider.getBalance(alice.address); + const payout = await (await queue.connect(alice).executeExit(1)).wait(); + expect(await ethers.provider.getBalance(alice.address)).to.equal( + balance + request.amount - payout.gasUsed * payout.gasPrice + ); + expect(await queue.totalEscrowed(ethers.ZeroAddress)).to.equal(0n); + proof.hostRollback = { + originalImplementationsRestored: true, + queuedFundsPreserved: true, + payoutSucceeded: true, + }; + }); +}); diff --git a/tests-release/ZeroNueExit.rollback.test.js b/tests-release/ZeroNueExit.rollback.test.js new file mode 100644 index 0000000..6b2da13 --- /dev/null +++ b/tests-release/ZeroNueExit.rollback.test.js @@ -0,0 +1,323 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const { expect } = require("chai"); +const { ethers } = require("hardhat"); +const { SignatureTransfer } = require("@uniswap/permit2-sdk"); +const { fixture } = require("./helpers/composition"); +const { requestFromReceipt, WITHDRAW, DELAY } = require("./helpers/core"); + +const reportPath = process.env.ZERO_NUE_WRAPPER_REPORT; +const results = []; + +async function setup(isClose) { + const ctx = await fixture(); + const masset = await (await ethers.getContractFactory("MassetManagerTester")).deploy(); + await masset.waitForDeployment(); + const nue = await ethers.getContractAt("NueMockToken", await masset.getToken()); + const zusd = await ethers.getContractAt("ZUSDToken", ctx.contracts.zusdToken.address); + const tm = await ethers.getContractAt("TroveManager", ctx.contracts.troveManager.address); + const ap = await ethers.getContractAt("ActivePool", ctx.contracts.activePool.address); + const permit2 = await ethers.getContractAt("Permit2", ctx.contracts.permit2.address); + await (await ctx.borrower.setMassetManagerAddress(masset.target)).wait(); + await ctx.open(ctx.alice, ethers.parseEther("5")); + await ctx.open(ctx.whale, ethers.parseEther("5"), ethers.parseEther("10000")); + + const reserve = BigInt(await ctx.borrower.ZUSD_GAS_COMPENSATION()); + const amount = isClose + ? BigInt(await tm.getTroveDebt(ctx.alice.address)) - reserve + : ethers.parseEther("50"); + if (isClose) await (await zusd.connect(ctx.whale).transfer(ctx.alice.address, amount)).wait(); + await (await zusd.connect(ctx.alice).approve(masset.target, amount)).wait(); + await (await masset.connect(ctx.alice).mintTo(zusd.target, amount, ctx.alice.address)).wait(); + assert.equal(await nue.balanceOf(ctx.alice.address), amount); + return { ctx, masset, nue, zusd, tm, ap, permit2, amount }; +} + +async function signERC2612(ctx, nue, amount) { + const nonce = BigInt(await nue.nonces(ctx.alice.address)); + const chainId = Number((await ethers.provider.getNetwork()).chainId); + const deadline = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600); + const domain = { + name: await nue.name(), + version: "1", + chainId, + verifyingContract: nue.target, + }; + const types = { + Permit: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, + ], + }; + const signature = await ctx.alice.signTypedData(domain, types, { + owner: ctx.alice.address, + spender: ctx.borrower.target, + value: amount, + nonce, + deadline, + }); + const { v, r, s } = ethers.Signature.from(signature); + return { params: { deadline, v, r, s }, nonce }; +} + +async function signPermit2(ctx, nue, permit2, amount, nonce) { + await (await nue.connect(ctx.alice).approve(permit2.target, ethers.MaxUint256)).wait(); + const deadline = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600); + const typed = { + permitted: { token: nue.target, amount }, + spender: ctx.borrower.target.toLowerCase(), + nonce, + deadline, + }; + const chainId = (await ethers.provider.getNetwork()).chainId; + const { domain, types, values } = SignatureTransfer.getPermitData( + typed, + permit2.target, + chainId + ); + const signature = await ctx.alice.signTypedData(domain, types, values); + return { permit: { permitted: typed.permitted, nonce, deadline }, signature, nonce }; +} + +async function state(env, nonce) { + const { ctx, masset, nue, zusd, tm, ap, permit2 } = env; + const word = nonce >> 8n; + return { + troveStatus: BigInt(await tm.getTroveStatus(ctx.alice.address)), + troveColl: BigInt(await tm.getTroveColl(ctx.alice.address)), + troveDebt: BigInt(await tm.getTroveDebt(ctx.alice.address)), + troveStake: BigInt(await tm.getTroveStake(ctx.alice.address)), + activeETH: BigInt(await ap.getETH()), + activeRaw: await ethers.provider.getBalance(ap.target), + activeDebt: BigInt(await ap.getZUSDDebt()), + aliceNue: await nue.balanceOf(ctx.alice.address), + aliceZusd: await zusd.balanceOf(ctx.alice.address), + massetZusd: await zusd.balanceOf(masset.target), + borrowerZusd: await zusd.balanceOf(ctx.borrower.target), + eip2612Nonce: await nue.nonces(ctx.alice.address), + permit2Bitmap: await permit2.nonceBitmap(ctx.alice.address, word), + feeReceiverRBTC: await ethers.provider.getBalance(ctx.feeReceiver.address), + queueRBTC: await ethers.provider.getBalance(ctx.queue.target), + queueEscrow: await ctx.queue.totalEscrowed(ethers.ZeroAddress), + lastRequestId: await ctx.queue.lastRequestId(), + }; +} + +function serial(value) { + return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, item.toString()])); +} + +describe("Real-core Zero NUE collateral-out wrappers", function () { + it("ZUSD-to-DLLR borrowing stays debt-only with fee and delay armed", async function () { + const env = await setup(false); + const { ctx, tm, nue } = env; + await (await ctx.queue.removeAllowedSource(ctx.borrower.target)).wait(); + const before = await state(env, 0n); + const borrowed = ethers.parseEther("100"); + await ( + await ctx.borrower + .connect(ctx.alice) + .withdrawZusdAndConvertToDLLR( + ethers.parseEther("1"), + borrowed, + ethers.ZeroAddress, + ethers.ZeroAddress + ) + ).wait(); + const after = await state(env, 0n); + assert.equal(after.lastRequestId, before.lastRequestId); + assert.equal(after.queueEscrow, before.queueEscrow); + assert.equal(after.queueRBTC, before.queueRBTC); + assert.equal(after.feeReceiverRBTC, before.feeReceiverRBTC); + assert.equal(after.activeETH, before.activeETH); + assert.equal(after.troveColl, before.troveColl); + assert(after.troveDebt > before.troveDebt + borrowed); + assert.equal(after.activeDebt - before.activeDebt, after.troveDebt - before.troveDebt); + assert.equal(await nue.balanceOf(ctx.alice.address), before.aliceNue + borrowed); + assert.equal(await tm.getTroveStatus(ctx.alice.address), 1n); + results.push({ + name: "withdrawZusdAndConvertToDLLR", + before: serial(before), + afterRetry: serial(after), + }); + if (reportPath) fs.writeFileSync(reportPath, JSON.stringify(results, null, 2) + "\n"); + }); + + for (const usePermit2 of [false, true]) { + const name = usePermit2 ? "repayZusdFromDLLRWithPermit2" : "repayZusdFromDLLR"; + it(`${name} stays debt-only with fee and delay armed`, async function () { + const env = await setup(false); + const { ctx, nue, permit2, amount } = env; + const nonce = 765432n; + const signed = usePermit2 + ? await signPermit2(ctx, nue, permit2, amount, nonce) + : await signERC2612(ctx, nue, amount); + await (await ctx.queue.removeAllowedSource(ctx.borrower.target)).wait(); + const before = await state(env, usePermit2 ? nonce : signed.nonce); + await ( + await (usePermit2 + ? ctx.borrower + .connect(ctx.alice) + .repayZusdFromDLLRWithPermit2( + amount, + ethers.ZeroAddress, + ethers.ZeroAddress, + signed.permit, + signed.signature + ) + : ctx.borrower + .connect(ctx.alice) + .repayZusdFromDLLR( + amount, + ethers.ZeroAddress, + ethers.ZeroAddress, + signed.params + )) + ).wait(); + const after = await state(env, usePermit2 ? nonce : signed.nonce); + assert.equal(after.lastRequestId, before.lastRequestId); + assert.equal(after.queueEscrow, before.queueEscrow); + assert.equal(after.queueRBTC, before.queueRBTC); + assert.equal(after.feeReceiverRBTC, before.feeReceiverRBTC); + assert.equal(after.activeETH, before.activeETH); + assert.equal(after.troveColl, before.troveColl); + assert.equal(after.troveDebt, before.troveDebt - amount); + assert.equal(after.activeDebt, before.activeDebt - amount); + assert.equal(after.aliceNue, before.aliceNue - amount); + assert.equal(after.massetZusd, before.massetZusd - amount); + if (usePermit2) { + const bit = 1n << (nonce & 255n); + assert.equal((after.permit2Bitmap ^ before.permit2Bitmap) & bit, bit); + assert.equal(after.eip2612Nonce, before.eip2612Nonce); + } else { + assert.equal(after.eip2612Nonce, before.eip2612Nonce + 1n); + assert.equal(after.permit2Bitmap, before.permit2Bitmap); + } + results.push({ name, before: serial(before), afterRetry: serial(after) }); + if (reportPath) fs.writeFileSync(reportPath, JSON.stringify(results, null, 2) + "\n"); + }); + } + + for (const isClose of [false, true]) { + for (const usePermit2 of [false, true]) { + const name = `${isClose ? "closeNueTrove" : "adjustNueTrove"}${ + usePermit2 ? "WithPermit2" : "" + }`; + it(`${name}: refusal rolls back conversion, nonce, trove and pools; same permit retries`, async function () { + assert(!process.env.ZERO_RELEASE_FORK_RPC, "run without a fork"); + const env = await setup(isClose); + const { ctx, nue, permit2, tm, ap, amount } = env; + const nonce = 123456n + BigInt(isClose ? 100 : 0); + const signed = usePermit2 + ? await signPermit2(ctx, nue, permit2, amount, nonce) + : await signERC2612(ctx, nue, amount); + const trackedNonce = usePermit2 ? nonce : signed.nonce; + const gross = isClose + ? BigInt(await tm.getTroveColl(ctx.alice.address)) + : ethers.parseEther("0.5"); + const call = () => { + if (isClose) + return usePermit2 + ? ctx.borrower + .connect(ctx.alice) + .closeNueTroveWithPermit2(signed.permit, signed.signature) + : ctx.borrower.connect(ctx.alice).closeNueTrove(signed.params); + return usePermit2 + ? ctx.borrower + .connect(ctx.alice) + .adjustNueTroveWithPermit2( + ethers.parseEther("1"), + gross, + amount, + false, + ethers.ZeroAddress, + ethers.ZeroAddress, + signed.permit, + signed.signature + ) + : ctx.borrower + .connect(ctx.alice) + .adjustNueTrove( + ethers.parseEther("1"), + gross, + amount, + false, + ethers.ZeroAddress, + ethers.ZeroAddress, + signed.params + ); + }; + + await (await ctx.queue.removeAllowedSource(ctx.borrower.target)).wait(); + const before = await state(env, trackedNonce); + await expect(call()).to.be.revertedWithCustomError( + ctx.queue, + "UnregisteredSource" + ); + const failed = await state(env, trackedNonce); + assert.deepEqual( + failed, + before, + "record refusal must roll back all token and pool effects" + ); + await (await ctx.queue.addAllowedSource(ctx.borrower.target)).wait(); + const receipt = await (await call()).wait(); + const request = await requestFromReceipt(ctx.queue, receipt); + const after = await state(env, trackedNonce); + const fee = (gross * 50n) / 10000n; + const net = gross - fee; + assert.equal(request.amount, net); + assert.equal(request.originator, ctx.alice.address); + assert.equal(request.owner, ctx.alice.address); + assert.equal(request.receiver, ctx.alice.address); + assert.equal(request.surfaceId, WITHDRAW); + assert.equal(request.unlockAt - request.createdAt, BigInt(DELAY)); + assert.equal(after.activeETH, before.activeETH - gross); + assert.equal(after.activeRaw, before.activeRaw - gross); + assert.equal(after.feeReceiverRBTC, before.feeReceiverRBTC + fee); + assert.equal(after.queueRBTC, before.queueRBTC + net); + assert.equal(after.queueEscrow, before.queueEscrow + net); + assert.equal(after.lastRequestId, before.lastRequestId + 1n); + assert.equal(after.aliceNue, before.aliceNue - amount); + assert.equal(after.aliceZusd, before.aliceZusd); + assert.equal(after.massetZusd, before.massetZusd - amount); + assert.equal(after.borrowerZusd, before.borrowerZusd); + if (isClose) { + assert.equal(after.troveStatus, 2n); + assert.equal(after.troveColl, 0n); + assert.equal(after.troveDebt, 0n); + assert.equal(after.troveStake, 0n); + assert.equal(after.activeDebt, before.activeDebt - before.troveDebt); + } else { + assert.equal(after.troveStatus, 1n); + assert.equal(after.troveColl, before.troveColl - gross); + assert.equal(after.troveDebt, before.troveDebt - amount); + assert.equal(after.activeDebt, before.activeDebt - amount); + } + if (usePermit2) { + const bit = 1n << (nonce & 255n); + assert.equal((after.permit2Bitmap ^ before.permit2Bitmap) & bit, bit); + assert.equal(after.eip2612Nonce, before.eip2612Nonce); + } else { + assert.equal(after.eip2612Nonce, before.eip2612Nonce + 1n); + assert.equal(after.permit2Bitmap, before.permit2Bitmap); + } + results.push({ + name, + gross: gross.toString(), + amount: amount.toString(), + fee: fee.toString(), + net: net.toString(), + before: serial(before), + afterRefusal: serial(failed), + afterRetry: serial(after), + }); + if (reportPath) + fs.writeFileSync(reportPath, JSON.stringify(results, null, 2) + "\n"); + }); + } + } +}); diff --git a/tests-release/ZeroSurplusClaim.liquidation.test.js b/tests-release/ZeroSurplusClaim.liquidation.test.js new file mode 100644 index 0000000..5c26323 --- /dev/null +++ b/tests-release/ZeroSurplusClaim.liquidation.test.js @@ -0,0 +1,205 @@ +const assert = require("assert").strict, + fs = require("fs"); +const { ethers, network } = require("hardhat"); +const dh = require("../utils/js/deploymentHelpers.js"); +const { TestHelper: th } = require("../utils/js/testHelpers.js"); +const { deployCore, SURPLUS } = require("./helpers/core.js"); +const variants = ["plain", "charged", "queued"], + modes = ["liquidate", "liquidateTroves", "batchLiquidateTroves"]; +const results = []; +let c, + owner, + bob, + carol, + whale, + other, + fee, + bo, + tm, + ap, + dp, + csp, + receiver, + core, + baseline, + gross, + poolBefore; +const value = (x) => BigInt(x.toString()); +async function state() { + return { + activeETH: value(await ap.getETH()), + defaultETH: value(await dp.getETH()), + activeDebt: value(await ap.getZUSDDebt()), + defaultDebt: value(await dp.getZUSDDebt()), + stakesSnapshot: value(await tm.totalStakesSnapshot()), + collateralSnapshot: value(await tm.totalCollateralSnapshot()), + bobStatus: value(await tm.getTroveStatus(bob.address)), + carolColl: value(await tm.getTroveColl(carol.address)), + carolDebt: value(await tm.getTroveDebt(carol.address)), + carolStake: value(await tm.getTroveStake(carol.address)), + carolPendingETH: value(await tm.getPendingETHReward(carol.address)), + carolPendingDebt: value(await tm.getPendingZUSDDebtReward(carol.address)), + surplusClaim: value(await csp.getCollateral(receiver.target)), + surplusTracked: value(await csp.getETH()), + surplusNative: value(await ethers.provider.getBalance(csp.target)), + feeBalance: value(await ethers.provider.getBalance(fee.address)), + receiverBalance: value(await ethers.provider.getBalance(receiver.target)), + queueEscrow: value(await core.queue.totalEscrowed(ethers.ZeroAddress)), + queueBalance: value(await ethers.provider.getBalance(core.queue.target)), + }; +} +const encode = (x) => + JSON.parse(JSON.stringify(x, (_, v) => (typeof v === "bigint" ? v.toString() : v))); +describe("Ordinary surplus claimant settlement with public liquidation callback", function () { + before(async () => { + assert(!process.env.ZERO_RELEASE_FORK_RPC); + [owner, bob, carol, whale, other, fee] = await ethers.getSigners(); + c = await dh.deployLiquityCoreHardhat(); + const zero = await dh.deployZEROTesterContractsHardhat(owner.address); + await zero.zeroToken.unprotectedMint(owner.address, th.dec(20, 24)); + await dh.connectZEROContracts(zero); + await dh.connectCoreContracts(c, zero); + await dh.connectZEROContractsToCore(zero, c); + bo = await ethers.getContractAt("BorrowerOperations", c.borrowerOperations.address); + tm = await ethers.getContractAt("TroveManager", c.troveManager.address); + ap = await ethers.getContractAt("ActivePool", c.activePool.address); + dp = await ethers.getContractAt("DefaultPool", c.defaultPool.address); + csp = await ethers.getContractAt("CollSurplusPool", c.collSurplusPool.address); + core = await deployCore({ + owner, + source: bo.target, + wrbtc: c.wrbtcTokenTester.address, + feeReceiver: fee.address, + }); + await (await core.queue.setMinimumDelaySeconds(86400)).wait(); + await (await bo.setExitFeeController(core.controller.target)).wait(); + await (await bo.setExitDelayQueue(core.queue.target)).wait(); + receiver = await ( + await ethers.getContractFactory("ClaimLiquidationObserver") + ).deploy(bo.target, tm.target, csp.target); + await receiver.waitForDeployment(); + async function open(a, ratio, amount = ethers.parseEther("2000")) { + const borrowingFee = value(await tm.getBorrowingFeeWithDecay(amount)); + const reserve = await bo.ZUSD_GAS_COMPENSATION(), + price = value(await c.priceFeedTestnet.getPrice()); + const collateral = + (ethers.parseEther(ratio) * (amount + borrowingFee + reserve)) / price; + if (a === receiver) await (await receiver.open(amount, { value: collateral })).wait(); + else + await ( + await bo + .connect(a) + .openTrove( + ethers.parseEther("1"), + amount, + ethers.ZeroAddress, + ethers.ZeroAddress, + { value: collateral } + ) + ).wait(); + } + await open(receiver, "1.6"); + await open(bob, "2"); + await open(other, "2.1"); + await open(carol, "3"); + await open(whale, "10", ethers.parseEther("20000")); + await network.provider.send("evm_increaseTime", [1209601]); + await network.provider.send("evm_mine"); + const net = + value(await tm.getTroveDebt(receiver.target)) - + value(await bo.ZUSD_GAS_COMPENSATION()); + await th.redeemCollateralAndGetTxObject(whale.address, c, th.toBN(net.toString())); + gross = value(await csp.getCollateral(receiver.target)); + poolBefore = value(await csp.getETH()); + assert(gross > 0n); + assert.equal(value(await tm.getTroveStatus(receiver.target)), 4n); + await c.priceFeedTestnet.setPrice(th.dec(100, 18)); + const price = ethers.parseEther("100"); + assert((await tm.getCurrentICR(bob.address, price)) < (await tm.MCR())); + assert.equal(await tm.checkRecoveryMode(price), false); + assert.equal(value(await core.queue.minimumDelaySeconds()), 86400n); + baseline = await network.provider.send("evm_snapshot"); + }); + async function replay(variant, mode, callback) { + await network.provider.send("evm_revert", [baseline]); + baseline = await network.provider.send("evm_snapshot"); + await (await core.controller.setSurfacePolicy(SURPLUS, [variant !== "plain", 50])).wait(); + await (await core.controller.setSecurityPerimeterEnabled(variant === "queued")).wait(); + await (await receiver.configure(bob.address, mode, callback)).wait(); + const before = await state(); + const receipt = await (await receiver.claim()).wait(); + assert.equal(value(await csp.getCollateral(receiver.target)), 0n); + assert.equal(value(await csp.getETH()), poolBefore - gross); + const expectedFee = variant === "plain" ? 0n : (gross * 50n) / 10000n, + net = gross - expectedFee; + assert.equal((await state()).feeBalance - before.feeBalance, expectedFee); + if (variant === "queued") { + assert.equal(await receiver.observed(), false); + assert.equal((await state()).receiverBalance, before.receiverBalance); + assert.equal(value(await core.queue.totalEscrowed(ethers.ZeroAddress)), net); + assert.equal(value(await ethers.provider.getBalance(core.queue.target)), net); + const id = await core.queue.lastRequestId(), + r = await core.queue.getRequest(id); + assert.equal(r.owner, receiver.target); + assert.equal(r.receiver, receiver.target); + assert.equal(value(r.amount), net); + assert.equal(value(r.unlockAt) - value(r.createdAt), 86400n); + await network.provider.send("evm_increaseTime", [86401]); + await network.provider.send("evm_mine"); + await (await core.queue.connect(other).executeExit(id)).wait(); + assert.equal(value((await core.queue.getRequest(id)).status), 2n); + } + if (!callback) await (await receiver.settleThenLiquidate()).wait(); + assert.equal(await receiver.succeeded(), true, "Real liquidation must succeed"); + assert.equal(await receiver.observed(), callback); + if (callback) { + assert.equal( + value(await receiver.claimAtCallback()), + 0n, + "Claim must be cleared at callback" + ); + assert.equal( + value(await receiver.trackedAtCallback()), + poolBefore - gross, + "Full gross tracked debit must precede callback" + ); + assert.equal(value(await receiver.nativeAtCallback()), poolBefore - gross); + assert.equal(value(await receiver.payoutAtCallback()), net); + } + const settled = await state(); + assert.equal(settled.bobStatus, 3n); + assert.equal(settled.queueEscrow, 0n); + assert.equal(settled.queueBalance, 0n); + assert(settled.carolPendingETH > 0n && settled.carolPendingDebt > 0n); + await ( + await bo.connect(carol).addColl(ethers.ZeroAddress, ethers.ZeroAddress, { + value: ethers.parseEther("0.1"), + }) + ).wait(); + const adjusted = await state(); + assert.equal(adjusted.carolPendingETH, 0n); + assert.equal(adjusted.carolPendingDebt, 0n); + await (await tm.connect(owner).liquidate(other.address)).wait(); + assert.equal(value(await tm.getTroveStatus(other.address)), 3n); + const redistributed = await state(); + assert(redistributed.carolPendingETH > 0n && redistributed.carolPendingDebt > 0n); + return { before, settled, adjusted, redistributed, gross, expectedFee, net }; + } + for (const variant of variants) + for (let mode = 0; mode < modes.length; mode++) + it(`${variant} claim then ${modes[mode]} observes settled accounting`, async () => { + const passive = await replay(variant, mode, false), + callback = await replay(variant, mode, true); + assert.deepEqual( + callback, + passive, + "Callback and after-payout composition must have identical economic state" + ); + results.push(encode({ variant, entry: modes[mode], passive, callback })); + if (process.env.ZERO_CLAIM_COMPARISON_REPORT) + fs.writeFileSync( + process.env.ZERO_CLAIM_COMPARISON_REPORT, + JSON.stringify(results, null, 2) + "\n" + ); + }); +}); diff --git a/tests-release/helpers/composition.js b/tests-release/helpers/composition.js new file mode 100644 index 0000000..5c66f65 --- /dev/null +++ b/tests-release/helpers/composition.js @@ -0,0 +1,511 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); +const { ethers, network } = require("hardhat"); +const deploymentHelper = require("../../utils/js/deploymentHelpers.js"); +const { TestHelper: th, TimeValues } = require("../../utils/js/testHelpers.js"); +const { deployCore, requestFromReceipt, WITHDRAW, SURPLUS, DELAY } = require("./core"); +const { verifyZeroReleaseArtifacts } = require("./zero"); + +function random(seed) { + let state = seed >>> 0; + assert(state !== 0, "Seed must be nonzero"); + return (maximum) => { + state ^= state << 13; + state ^= state >>> 17; + state ^= state << 5; + return (state >>> 0) % maximum; + }; +} + +async function fixture() { + assert(!process.env.ZERO_RELEASE_FORK_RPC, "Composition proof must run without a fork"); + const provenance = await verifyZeroReleaseArtifacts(); + const [owner, alice, bob, carol, whale, outsider, feeReceiver] = await ethers.getSigners(); + const contracts = await deploymentHelper.deployLiquityCoreHardhat(); + const zero = await deploymentHelper.deployZEROTesterContractsHardhat(owner.address); + await zero.zeroToken.unprotectedMint(owner.address, th.dec(20, 24)); + await deploymentHelper.connectZEROContracts(zero); + await deploymentHelper.connectCoreContracts(contracts, zero); + await deploymentHelper.connectZEROContractsToCore(zero, contracts); + const borrower = await ethers.getContractAt( + "BorrowerOperations", + contracts.borrowerOperations.address + ); + const core = await deployCore({ + owner, + source: borrower.target, + wrbtc: contracts.wrbtcTokenTester.address, + feeReceiver: feeReceiver.address, + }); + await (await borrower.setExitFeeController(core.controller.target)).wait(); + await (await borrower.setExitDelayQueue(core.queue.target)).wait(); + const boScript = await ( + await ethers.getContractFactory("BorrowerOperationsScript") + ).deploy(borrower.target); + const wrapperScript = await ( + await ethers.getContractFactory("BorrowerWrappersScript") + ).deploy( + borrower.target, + contracts.troveManager.address, + zero.zeroStaking.address, + contracts.stabilityPool.address, + contracts.priceFeedTestnet.address, + contracts.zusdToken.address, + zero.zeroToken.address + ); + const proxyFactory = await (await ethers.getContractFactory("DSProxyFactory")).deploy(); + const built = await (await proxyFactory.connect(alice)["build()"]()).wait(); + const created = built.logs + .map((log) => { + try { + return proxyFactory.interface.parseLog(log); + } catch { + return null; + } + }) + .find((log) => log?.name === "Created"); + const proxy = await ethers.getContractAt("DSProxy", created.args.proxy, alice); + const contractActor = { address: proxy.target, signer: alice, proxy }; + const nonPayable = await (await ethers.getContractFactory("NonPayable")).deploy(); + if (process.env.ZERO_COMPOSITION_MUTANT_FILE) { + const filename = process.env.ZERO_COMPOSITION_MUTANT_FILE; + assert(path.isAbsolute(filename), "Mutation artifact must have an absolute path"); + assert( + !filename.startsWith(path.resolve(__dirname, "../..") + path.sep), + "Mutation must stay outside the repository" + ); + const artifact = JSON.parse(fs.readFileSync(filename, "utf8")); + assert.equal(artifact.contractName, "BorrowerOperationsPerimeterOps"); + assert(artifact.mutation && artifact.originalInputSha256, "Mutation manifest is required"); + const mutated = await new ethers.ContractFactory( + artifact.abi, + artifact.bytecode, + owner + ).deploy(); + await (await borrower.setPerimeterOps(mutated.target)).wait(); + } + const ctx = { + contracts, + zero, + borrower, + ...core, + owner, + alice, + bob, + carol, + whale, + outsider, + feeReceiver, + boScript, + wrapperScript, + contractActor, + nonPayable, + provenance, + }; + ctx.invoke = async (actor, name, args = [], overrides = {}) => { + if (actor.proxy) + return actor.proxy["execute(address,bytes)"]( + boScript.target, + boScript.interface.encodeFunctionData(name, args), + overrides + ); + return borrower.connect(actor)[name](...args, overrides); + }; + ctx.open = async ( + actor, + ratio = ethers.parseEther("3"), + amount = ethers.parseEther("2000") + ) => { + const fee = BigInt( + (await contracts.troveManager.getBorrowingFeeWithDecay(amount.toString())).toString() + ); + const reserve = await borrower.ZUSD_GAS_COMPENSATION(); + const price = BigInt((await contracts.priceFeedTestnet.getPrice()).toString()); + await ( + await ctx.invoke( + actor, + "openTrove", + [ethers.parseEther("1"), amount, ethers.ZeroAddress, ethers.ZeroAddress], + { value: (ratio * (amount + fee + reserve)) / price } + ) + ).wait(); + return ( + BigInt((await contracts.troveManager.getTroveDebt(actor.address)).toString()) - reserve + ); + }; + ctx.createSurplus = async (actor) => { + const netDebt = await ctx.open(actor, ethers.parseEther("2")); + if (String(await contracts.troveManager.getTroveStatus(whale.address)) !== "1") + await ctx.open(whale, ethers.parseEther("100"), ethers.parseEther("100000")); + await th.fastForwardTime(TimeValues.SECONDS_IN_ONE_WEEK * 2, web3.currentProvider); + await th.redeemCollateralAndGetTxObject( + whale.address, + contracts, + th.toBN(netDebt.toString()) + ); + const gross = BigInt( + (await contracts.collSurplusPool.getCollateral(actor.address)).toString() + ); + assert(gross > 0n, "Real redemption must produce positive surplus"); + return gross; + }; + return ctx; +} + +function requestIdentity(request) { + return [ + request.amount, + request.surfaceId, + request.token, + request.subProduct, + request.originator, + request.owner, + request.receiver, + request.createdAt, + request.unlockAt, + ].map(String); +} + +async function balances(ctx, actor, surface = WITHDRAW) { + const pool = surface === SURPLUS ? ctx.contracts.collSurplusPool : ctx.contracts.activePool; + return { + pool: await ethers.provider.getBalance(pool.address), + tracked: BigInt((await pool.getETH()).toString()), + debt: BigInt((await ctx.contracts.activePool.getZUSDDebt()).toString()), + collateral: BigInt( + (await ctx.contracts.troveManager.getTroveColl(actor.address)).toString() + ), + troveDebt: BigInt( + (await ctx.contracts.troveManager.getTroveDebt(actor.address)).toString() + ), + status: String(await ctx.contracts.troveManager.getTroveStatus(actor.address)), + surplus: BigInt( + (await ctx.contracts.collSurplusPool.getCollateral(actor.address)).toString() + ), + queue: await ethers.provider.getBalance(ctx.queue.target), + liability: await ctx.queue.totalEscrowed(ethers.ZeroAddress), + last: await ctx.queue.lastRequestId(), + fee: await ethers.provider.getBalance(ctx.feeReceiver.address), + actor: await ethers.provider.getBalance(actor.address), + }; +} + +class Model { + constructor(ctx) { + this.ctx = ctx; + this.requests = new Map(); + this.trace = []; + this.counts = {}; + this.contractCalls = 0; + this.receipts = []; + this.evidence = []; + } + + mark(name, details = {}) { + this.trace.push({ command: name, ...details }); + this.counts[name] = (this.counts[name] || 0) + 1; + } + + async settle(actor, operation, gross, held, feeFails = false) { + const surface = operation === "surplus" ? SURPLUS : WITHDRAW; + const before = await balances(this.ctx, actor, surface); + const args = + operation === "withdraw" + ? [gross, ethers.ZeroAddress, ethers.ZeroAddress] + : operation === "adjust" + ? [ethers.parseEther("1"), gross, 0, false, ethers.ZeroAddress, ethers.ZeroAddress] + : []; + const method = + operation === "withdraw" + ? "withdrawColl" + : operation === "adjust" + ? "adjustTrove" + : "claimCollateral"; + this.mark(operation, { actor: actor.address, gross: gross.toString(), held, feeFails }); + this.contractCalls++; + const receipt = await (await this.ctx.invoke(actor, method, args)).wait(); + this.receipts.push(receipt.hash); + const after = await balances(this.ctx, actor, surface); + const fee = after.fee - before.fee; + const gas = + receipt.from.toLowerCase() === actor.address.toLowerCase() + ? receipt.gasUsed * receipt.gasPrice + : 0n; + const paid = after.actor - before.actor + gas; + this.evidence.push({ + operation, + actor: actor.address, + surface, + gross, + held, + feeFails, + before, + after, + receivedFee: fee, + directPayment: paid, + receipt: { + hash: receipt.hash, + blockNumber: receipt.blockNumber, + from: receipt.from, + to: receipt.to, + gasUsed: receipt.gasUsed, + gasPrice: receipt.gasPrice, + }, + }); + assert.equal(before.pool - after.pool, gross, "Pool must debit exactly gross"); + assert.equal(before.tracked - after.tracked, gross, "Tracked pool debit must equal gross"); + assert.equal( + after.debt, + before.debt, + "Collateral-only operation must preserve system debt" + ); + assert.equal( + after.troveDebt, + before.troveDebt, + "Collateral-only operation must preserve trove debt" + ); + if (surface === WITHDRAW) + assert.equal( + before.collateral - after.collateral, + gross, + "Trove collateral debit must equal gross" + ); + else assert.equal(after.surplus, 0n, "Committed claim must consume its surplus"); + if (feeFails) assert.equal(fee, 0n, "Noncharging fee leg must not consume value"); + else + assert.equal( + fee, + (gross * 50n) / 10000n, + "Active configured fee must be received exactly" + ); + if (held) { + assert.equal(paid, 0n, "Resolved hold must never pay actor directly"); + assert.equal( + after.last, + before.last + 1n, + "Held settlement must create exactly one request" + ); + const request = await requestFromReceipt(this.ctx.queue, receipt); + const identity = requestIdentity(request); + assert.equal( + request.amount, + gross - fee, + "Actual fee plus stored request must conserve gross" + ); + for (const key of ["originator", "owner", "receiver"]) + assert.equal( + request[key], + actor.address, + "Recorded actors must be actual Zero caller" + ); + assert.equal(request.surfaceId, surface, "Zero surface must be preserved"); + assert.equal(request.subProduct, ethers.ZeroAddress); + assert.equal(request.token, ethers.ZeroAddress); + assert.equal(request.unlockAt - request.createdAt, BigInt(DELAY)); + assert.equal( + after.queue - before.queue, + request.amount, + "Native custody must equal recorded amount" + ); + assert.equal( + after.liability - before.liability, + request.amount, + "Liability must equal recorded amount" + ); + this.requests.set(after.last.toString(), { + identity, + amount: request.amount, + receiver: actor.address, + terminal: false, + }); + } else { + assert.equal(after.last, before.last, "Direct settlement must not create requests"); + assert.equal( + after.queue, + before.queue, + "Direct settlement must not touch queue custody" + ); + assert.equal(after.liability, before.liability); + assert.equal(fee + paid, gross, "Fee and direct user payout must conserve gross"); + } + await this.invariants(); + return after.last; + } + + async rejected(actor, operation, error) { + const surface = operation === "surplus" ? SURPLUS : WITHDRAW; + const before = await balances(this.ctx, actor, surface); + const args = + operation === "surplus" + ? [] + : [ethers.parseEther("0.01"), ethers.ZeroAddress, ethers.ZeroAddress]; + const method = operation === "surplus" ? "claimCollateral" : "withdrawColl"; + this.mark("record-reject", { actor: actor.address, operation }); + this.contractCalls++; + const { expect } = require("chai"); + let rejectedHash; + const transaction = this.ctx + .invoke(actor, method, args, { gasLimit: 8000000 }) + .catch((failure) => { + rejectedHash = failure.transactionHash || failure.data?.transactionHash; + throw failure; + }); + await expect(transaction).to.be.revertedWithCustomError(this.ctx.queue, error); + const after = await balances(this.ctx, actor, surface); + delete before.actor; + delete after.actor; + assert.deepEqual( + after, + before, + "Record rejection must roll back host, positive fee, pools and queue" + ); + this.evidence.push({ + operation: "record-reject", + actor: actor.address, + surface, + expectedError: error, + rejectedHash: rejectedHash || null, + before, + after, + }); + await this.invariants(); + } + + async release(id, executor, expectedError) { + const stored = this.requests.get(id.toString()); + assert(stored, "Release must target a modeled request"); + const before = await ethers.provider.getBalance(stored.receiver); + const liability = await this.ctx.queue.totalEscrowed(ethers.ZeroAddress); + const raw = await ethers.provider.getBalance(this.ctx.queue.target); + this.contractCalls++; + if (expectedError) { + this.mark(expectedError, { id: id.toString(), executor: executor.address }); + const { expect } = require("chai"); + await expect( + this.ctx.queue.connect(executor).executeExit(id, { gasLimit: 8000000 }) + ).to.be.revertedWithCustomError(this.ctx.queue, expectedError); + assert.equal(await this.ctx.queue.totalEscrowed(ethers.ZeroAddress), liability); + assert.equal(await ethers.provider.getBalance(this.ctx.queue.target), raw); + } else { + this.mark("release", { id: id.toString(), executor: executor.address }); + const receipt = await (await this.ctx.queue.connect(executor).executeExit(id)).wait(); + this.receipts.push(receipt.hash); + const gas = + executor.address.toLowerCase() === stored.receiver.toLowerCase() + ? receipt.gasUsed * receipt.gasPrice + : 0n; + assert.equal( + await ethers.provider.getBalance(stored.receiver), + before + stored.amount - gas, + "Release must pay only immutable receiver exactly once" + ); + assert.equal( + await this.ctx.queue.totalEscrowed(ethers.ZeroAddress), + liability - stored.amount + ); + assert.equal( + await ethers.provider.getBalance(this.ctx.queue.target), + raw - stored.amount + ); + this.evidence.push({ + operation: "release", + id, + executor: executor.address, + receiver: stored.receiver, + amount: stored.amount, + receiverBefore: before, + receiverAfter: await ethers.provider.getBalance(stored.receiver), + liabilityBefore: liability, + liabilityAfter: await this.ctx.queue.totalEscrowed(ethers.ZeroAddress), + receipt: { + hash: receipt.hash, + blockNumber: receipt.blockNumber, + from: receipt.from, + to: receipt.to, + gasUsed: receipt.gasUsed, + gasPrice: receipt.gasPrice, + }, + }); + stored.terminal = true; + } + await this.invariants(); + } + + async control(name, transaction) { + this.mark(name); + this.contractCalls++; + const receipt = await (await transaction()).wait(); + this.receipts.push(receipt.hash); + this.evidence.push({ + operation: name, + receipt: { + hash: receipt.hash, + blockNumber: receipt.blockNumber, + from: receipt.from, + to: receipt.to, + }, + }); + await this.invariants(); + } + + async mature(id) { + const request = await this.ctx.queue.getRequest(id); + const current = await ethers.provider.getBlock("latest"); + await network.provider.send("evm_setNextBlockTimestamp", [ + Number( + request.unlockAt > BigInt(current.timestamp) + ? request.unlockAt + : BigInt(current.timestamp + 1) + ), + ]); + await network.provider.send("evm_mine"); + this.mark("mature"); + } + + async invariants() { + let liability = 0n; + for (const [id, expected] of this.requests) { + const request = await this.ctx.queue.getRequest(id); + assert.deepEqual( + requestIdentity(request), + expected.identity, + "Request metadata must remain immutable" + ); + assert.equal( + request.status, + expected.terminal ? 2n : 1n, + "Request status must match committed model transition" + ); + if (!expected.terminal) liability += expected.amount; + } + assert.equal( + await this.ctx.queue.lastRequestId(), + BigInt(this.requests.size), + "Every request must be accounted in model" + ); + assert.equal( + await this.ctx.queue.totalEscrowed(ethers.ZeroAddress), + liability, + "Queue liability must equal outstanding request sum" + ); + assert.equal( + await ethers.provider.getBalance(this.ctx.queue.target), + liability, + "Undonated native custody must exactly back requests" + ); + for (const pool of [this.ctx.contracts.activePool, this.ctx.contracts.collSurplusPool]) + assert.equal( + await ethers.provider.getBalance(pool.address), + BigInt((await pool.getETH()).toString()), + "Raw pool balance must match accounting" + ); + assert.equal( + await ethers.provider.getBalance(this.ctx.borrower.target), + 0n, + "BorrowerOperations must not strand native collateral" + ); + } +} + +module.exports = { fixture, random, balances, Model, WITHDRAW, SURPLUS, DELAY }; diff --git a/tests-release/helpers/core.js b/tests-release/helpers/core.js new file mode 100644 index 0000000..e8d088b --- /dev/null +++ b/tests-release/helpers/core.js @@ -0,0 +1,116 @@ +const fs = require("fs"); +const path = require("path"); +const { createHash } = require("crypto"); +const { ethers } = require("hardhat"); + +const WITHDRAW = ethers.id("PERIMETER_SURFACE_ZERO_WITHDRAW_COLL"); +const SURPLUS = ethers.id("PERIMETER_SURFACE_ZERO_CLAIM_SURPLUS"); +const DELAY = 86400; + +function coreArtifact(name) { + const directory = process.env.CORE_RELEASE_ARTIFACTS_DIR; + if (!directory || !path.isAbsolute(directory)) { + throw new Error("CORE_RELEASE_ARTIFACTS_DIR must name the exact compiled core release"); + } + const manifestPath = process.env.CORE_RELEASE_MANIFEST; + const revision = process.env.CORE_RELEASE_REVISION; + if ( + !manifestPath || + !path.isAbsolute(manifestPath) || + !/^[0-9a-f]{40}$/.test(revision || "") + ) { + throw new Error("CORE_RELEASE_MANIFEST and exact CORE_RELEASE_REVISION are required"); + } + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); + if (manifest.head !== revision) + throw new Error("Core release revision does not match its manifest"); + const receipt = manifest.artifacts.find((artifact) => artifact.name === name); + const bytes = fs.readFileSync(path.join(directory, `${name}.sol`, `${name}.json`)); + if (!receipt || createHash("sha256").update(bytes).digest("hex") !== receipt.artifact_sha256) { + throw new Error(`Core release artifact hash mismatch: ${name}`); + } + if (receipt.compiler !== "0.8.20+commit.a1b79de6" || receipt.evm_version !== "paris") { + throw new Error(`Unexpected production compiler/EVM: ${name}`); + } + const artifact = JSON.parse(bytes.toString("utf8")); + if (!artifact.bytecode?.object || !artifact.deployedBytecode?.object) { + throw new Error(`Missing compiled core bytecode: ${name}`); + } + return artifact; +} + +async function deployCoreContract(name, signer, args = []) { + const artifact = coreArtifact(name); + const factory = new ethers.ContractFactory(artifact.abi, artifact.bytecode.object, signer); + const contract = await factory.deploy(...args); + await contract.waitForDeployment(); + const runtime = await ethers.provider.getCode(contract.target); + if (runtime === "0x" || (runtime.length - 2) / 2 > 24576) { + throw new Error(`Invalid production runtime size: ${name}`); + } + return contract; +} + +async function deployCore({ + owner, + source, + wrbtc, + feeReceiver, + controllerAddress, + configure = true, +}) { + const controllerImplementation = await deployCoreContract("ExitFeeController", owner); + const controllerInterface = new ethers.Interface(coreArtifact("ExitFeeController").abi); + let controller; + if (controllerAddress) { + controller = new ethers.Contract(controllerAddress, controllerInterface, owner); + await (await controller.upgradeTo(controllerImplementation.target)).wait(); + } else { + const proxy = await deployCoreContract("ERC1967Proxy", owner, [ + controllerImplementation.target, + controllerInterface.encodeFunctionData("initialize", [owner.address]), + ]); + controller = new ethers.Contract(proxy.target, controllerInterface, owner); + } + const queueImplementation = await deployCoreContract("ExitDelayQueue", owner); + const queueInterface = new ethers.Interface(coreArtifact("ExitDelayQueue").abi); + const queueProxy = await deployCoreContract("ERC1967Proxy", owner, [ + queueImplementation.target, + queueInterface.encodeFunctionData("initialize", [ + owner.address, + owner.address, + wrbtc, + 60, + [source], + ]), + ]); + const queue = new ethers.Contract(queueProxy.target, queueInterface, owner); + if (configure) { + await (await controller.setGlobalDelaySeconds(DELAY)).wait(); + await (await controller.setSurfacePolicy(WITHDRAW, [true, 50])).wait(); + await (await controller.setSurfacePolicy(SURPLUS, [true, 50])).wait(); + await (await controller.setFeeReceiver(feeReceiver)).wait(); + await (await controller.setExitFeeEnabled(true)).wait(); + await (await controller.setSecurityPerimeterEnabled(true)).wait(); + } + return { controller, queue, controllerImplementation, queueImplementation }; +} + +async function requestFromReceipt(queue, receipt) { + for (const log of receipt.logs) { + if (log.address.toLowerCase() !== queue.target.toLowerCase()) continue; + const parsed = queue.interface.parseLog(log); + if (parsed?.name === "ExitQueued") return queue.getRequest(parsed.args.id); + } + throw new Error("ExitQueued was not emitted by the real queue"); +} + +module.exports = { + coreArtifact, + deployCore, + deployCoreContract, + requestFromReceipt, + WITHDRAW, + SURPLUS, + DELAY, +}; diff --git a/tests-release/helpers/zero.js b/tests-release/helpers/zero.js new file mode 100644 index 0000000..07c191f --- /dev/null +++ b/tests-release/helpers/zero.js @@ -0,0 +1,57 @@ +const fs = require("fs"); +const path = require("path"); +const { createHash } = require("crypto"); +const { artifacts } = require("hardhat"); + +async function verifyZeroReleaseArtifacts() { + const verifiedSources = new Set(); + const receipts = []; + for (const name of [ + "BorrowerOperations", + "CollSurplusPool", + "TroveManager", + "BorrowerOperationsPerimeterOps", + ]) { + const artifact = await artifacts.readArtifact(name); + const qualified = `${artifact.sourceName}:${name}`; + const build = await artifacts.getBuildInfo(qualified); + if ( + !build || + build.solcVersion !== "0.6.11" || + !build.input.settings.optimizer.enabled || + build.input.settings.optimizer.runs !== 100 + ) { + throw new Error(`Unexpected Zero release compiler/settings: ${name}`); + } + for (const [sourceName, source] of Object.entries(build.input.sources)) { + if (verifiedSources.has(sourceName)) continue; + const direct = path.resolve(__dirname, "../..", sourceName); + const filename = fs.existsSync(direct) + ? direct + : path.resolve(__dirname, "../..", "node_modules", sourceName); + if (fs.readFileSync(filename, "utf8") !== source.content) { + throw new Error(`Stale Zero release artifact source: ${sourceName}`); + } + verifiedSources.add(sourceName); + } + const output = build.output.contracts[artifact.sourceName][name]; + if ( + artifact.bytecode !== `0x${output.evm.bytecode.object}` || + artifact.deployedBytecode !== `0x${output.evm.deployedBytecode.object}` + ) { + throw new Error(`Zero release artifact/build mismatch: ${name}`); + } + receipts.push({ + name, + compiler: build.solcLongVersion, + optimizerRuns: 100, + compilerInputSha256: createHash("sha256") + .update(JSON.stringify(build.input)) + .digest("hex"), + artifactSha256: createHash("sha256").update(JSON.stringify(artifact)).digest("hex"), + }); + } + return { receipts, sourceFilesCompared: verifiedSources.size }; +} + +module.exports = { verifyZeroReleaseArtifacts }; diff --git a/tests/js/TroveManager_RecoveryMode_BatchLiquidationTest.js b/tests/js/TroveManager_RecoveryMode_BatchLiquidationTest.js new file mode 100644 index 0000000..152575d --- /dev/null +++ b/tests/js/TroveManager_RecoveryMode_BatchLiquidationTest.js @@ -0,0 +1,223 @@ +const deploymentHelper = require("../../utils/js/deploymentHelpers.js"); +const testHelpers = require("../../utils/js/testHelpers.js"); + +const th = testHelpers.TestHelper; +const mv = testHelpers.MoneyValues; +const { toBN, dec } = th; +const ZERO_ADDRESS = th.ZERO_ADDRESS || "0x0000000000000000000000000000000000000000"; + +const TroveManagerTester = artifacts.require("./TroveManagerTester"); +const ZUSDToken = artifacts.require("./ZUSDToken.sol"); + +/* + * Ported from Liquity's TroveManager_RecoveryMode_Batch_Liqudation_Test.js + * (liquity/dev, fix commits b9dcdbd3 2021-05-19 and 47fb4d56 2021-09-03). + * + * Covers the running system-collateral tracker used by the Recovery Mode + * liquidation loops. Before the fix, _getTotalFromBatchLiquidate_RecoveryMode + * only subtracted collToSendToSP, so after a capped liquidation the TCR seen by + * the next trove in the batch was too high and a trove above the real TCR could + * be liquidated ("A trove over TCR is not liquidated" fails on the old code). + */ +contract("TroveManager - in Recovery Mode - back to normal mode in 1 tx", async accounts => { + const [bountyAddress, lpRewardsAddress, multisig] = accounts.slice(997, 1000); + const [owner, alice, bob, carol, dennis, erin, freddy, greta, harry, ida, whale] = accounts; + + let contracts; + let troveManager; + let stabilityPool; + let priceFeed; + let sortedTroves; + + const openTrove = async params => th.openTrove(contracts, params); + + beforeEach(async () => { + contracts = await deploymentHelper.deployLiquityCore(); + contracts.troveManager = await TroveManagerTester.new(contracts.permit2.address); + contracts.zusdToken = await ZUSDToken.new(); + await contracts.zusdToken.initialize( + contracts.troveManager.address, + contracts.stabilityPool.address, + contracts.borrowerOperations.address + ); + const ZEROContracts = await deploymentHelper.deployZEROContracts(multisig); + + troveManager = contracts.troveManager; + stabilityPool = contracts.stabilityPool; + priceFeed = contracts.priceFeedTestnet; + sortedTroves = contracts.sortedTroves; + + await deploymentHelper.connectZEROContracts(ZEROContracts); + await deploymentHelper.connectCoreContracts(contracts, ZEROContracts); + await deploymentHelper.connectZEROContractsToCore(ZEROContracts, contracts); + }); + + context("Batch liquidations", () => { + const setup = async () => { + const { collateral: A_coll, totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(296, 16)), extraParams: { from: alice } }); + const { collateral: B_coll, totalDebt: B_totalDebt } = await openTrove({ ICR: toBN(dec(280, 16)), extraParams: { from: bob } }); + const { collateral: C_coll, totalDebt: C_totalDebt } = await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: carol } }); + + const totalLiquidatedDebt = A_totalDebt.add(B_totalDebt).add(C_totalDebt); + const spDeposit = totalLiquidatedDebt.add(toBN(dec(1, 18))); + + await openTrove({ ICR: toBN(dec(340, 16)), extraZUSDAmount: spDeposit, extraParams: { from: whale } }); + await stabilityPool.provideToSP(spDeposit, ZERO_ADDRESS, { from: whale }); + + // Price drops + await priceFeed.setPrice(dec(100, 18)); + const price = await priceFeed.getPrice(); + const TCR = await th.getTCR(contracts); + + // Check Recovery Mode is active + assert.isTrue(await th.checkRecoveryMode(contracts)); + + // Check troves A, B are in range 110% < ICR < TCR, C is below 100% + const ICR_A = await troveManager.getCurrentICR(alice, price); + const ICR_B = await troveManager.getCurrentICR(bob, price); + const ICR_C = await troveManager.getCurrentICR(carol, price); + + assert.isTrue(ICR_A.gt(mv._MCR) && ICR_A.lt(TCR)); + assert.isTrue(ICR_B.gt(mv._MCR) && ICR_B.lt(TCR)); + assert.isTrue(ICR_C.lt(mv._ICR100)); + + return { A_coll, A_totalDebt, B_coll, B_totalDebt, C_coll, C_totalDebt, totalLiquidatedDebt, price }; + }; + + it("First trove only doesn't get out of Recovery Mode", async () => { + await setup(); + await troveManager.batchLiquidateTroves([alice]); + assert.isTrue(await th.checkRecoveryMode(contracts)); + }); + + it("Two troves over MCR are liquidated", async () => { + await setup(); + const tx = await troveManager.batchLiquidateTroves([alice, bob, carol]); + + const liquidationEvents = th.getAllEventsByName(tx, "TroveLiquidated"); + assert.equal(liquidationEvents.length, 3, "Not enough liquidations"); + + assert.isFalse(await sortedTroves.contains(alice)); + assert.isFalse(await sortedTroves.contains(bob)); + assert.isFalse(await sortedTroves.contains(carol)); + + // Status enum element idx 3 = closed by liquidation + assert.equal((await troveManager.Troves(alice))[3], "3"); + assert.equal((await troveManager.Troves(bob))[3], "3"); + assert.equal((await troveManager.Troves(carol))[3], "3"); + }); + + it("Stability Pool profit matches", async () => { + const { A_totalDebt, C_coll, price } = await setup(); + + const spEthBefore = await stabilityPool.getETH(); + const spZusdBefore = await stabilityPool.getTotalZUSDDeposits(); + + await troveManager.batchLiquidateTroves([alice, carol]); + + assert.isFalse(await sortedTroves.contains(alice)); + assert.isFalse(await sortedTroves.contains(carol)); + assert.equal((await troveManager.Troves(alice))[3], "3"); + assert.equal((await troveManager.Troves(carol))[3], "3"); + + const spEthAfter = await stabilityPool.getETH(); + const spZusdAfter = await stabilityPool.getTotalZUSDDeposits(); + + // liquidated collateral with the gas compensation subtracted + const expectedCollateralLiquidatedA = th.applyLiquidationFee(A_totalDebt.mul(mv._MCR).div(price)); + const expectedGainInZUSD = expectedCollateralLiquidatedA.mul(price).div(mv._1e18BN).sub(A_totalDebt); + const realGainInZUSD = spEthAfter.sub(spEthBefore).mul(price).div(mv._1e18BN).sub(spZusdBefore.sub(spZusdAfter)); + + assert.equal(spEthAfter.sub(spEthBefore).toString(), expectedCollateralLiquidatedA.toString(), "Stability Pool RBTC doesn't match"); + assert.equal(spZusdBefore.sub(spZusdAfter).toString(), A_totalDebt.toString(), "Stability Pool ZUSD doesn't match"); + assert.equal(realGainInZUSD.toString(), expectedGainInZUSD.toString(), "Stability Pool gains don't match"); + }); + + it("A trove over TCR is not liquidated", async () => { + const { totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(280, 16)), extraParams: { from: alice } }); + const { totalDebt: B_totalDebt } = await openTrove({ ICR: toBN(dec(276, 16)), extraParams: { from: bob } }); + const { totalDebt: C_totalDebt } = await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: carol } }); + + const totalLiquidatedDebt = A_totalDebt.add(B_totalDebt).add(C_totalDebt); + + await openTrove({ ICR: toBN(dec(310, 16)), extraZUSDAmount: totalLiquidatedDebt, extraParams: { from: whale } }); + await stabilityPool.provideToSP(totalLiquidatedDebt, ZERO_ADDRESS, { from: whale }); + + // Price drops + await priceFeed.setPrice(dec(100, 18)); + const price = await priceFeed.getPrice(); + const TCR = await th.getTCR(contracts); + + assert.isTrue(await th.checkRecoveryMode(contracts)); + + // A above TCR, B in 110% < ICR < TCR, C below 100% + const ICR_A = await troveManager.getCurrentICR(alice, price); + const ICR_B = await troveManager.getCurrentICR(bob, price); + const ICR_C = await troveManager.getCurrentICR(carol, price); + + assert.isTrue(ICR_A.gt(TCR)); + assert.isTrue(ICR_B.gt(mv._MCR) && ICR_B.lt(TCR)); + assert.isTrue(ICR_C.lt(mv._ICR100)); + + const tx = await troveManager.batchLiquidateTroves([bob, alice]); + + const liquidationEvents = th.getAllEventsByName(tx, "TroveLiquidated"); + assert.equal(liquidationEvents.length, 1, "Not enough liquidations"); + + // Only Bob's trove removed + assert.isTrue(await sortedTroves.contains(alice)); + assert.isFalse(await sortedTroves.contains(bob)); + assert.isTrue(await sortedTroves.contains(carol)); + + assert.equal((await troveManager.Troves(bob))[3], "3"); + assert.equal((await troveManager.Troves(alice))[3], "1"); + assert.equal((await troveManager.Troves(carol))[3], "1"); + }); + }); + + context("Sequential liquidations", () => { + const setup = async () => { + const { collateral: A_coll, totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(299, 16)), extraParams: { from: alice } }); + const { collateral: B_coll, totalDebt: B_totalDebt } = await openTrove({ ICR: toBN(dec(298, 16)), extraParams: { from: bob } }); + + const totalLiquidatedDebt = A_totalDebt.add(B_totalDebt); + const spDeposit = totalLiquidatedDebt.add(toBN(dec(1, 18))); + + await openTrove({ ICR: toBN(dec(300, 16)), extraZUSDAmount: spDeposit, extraParams: { from: whale } }); + await stabilityPool.provideToSP(spDeposit, ZERO_ADDRESS, { from: whale }); + + await priceFeed.setPrice(dec(100, 18)); + const price = await priceFeed.getPrice(); + const TCR = await th.getTCR(contracts); + + assert.isTrue(await th.checkRecoveryMode(contracts)); + + const ICR_A = await troveManager.getCurrentICR(alice, price); + const ICR_B = await troveManager.getCurrentICR(bob, price); + + assert.isTrue(ICR_A.gt(mv._MCR) && ICR_A.lt(TCR)); + assert.isTrue(ICR_B.gt(mv._MCR) && ICR_B.lt(TCR)); + + return { A_coll, A_totalDebt, B_coll, B_totalDebt, totalLiquidatedDebt, price }; + }; + + it("First trove only doesn't get out of Recovery Mode", async () => { + await setup(); + await troveManager.liquidateTroves(1); + assert.isTrue(await th.checkRecoveryMode(contracts)); + }); + + it("Two troves over MCR are liquidated", async () => { + await setup(); + const tx = await troveManager.liquidateTroves(10); + + const liquidationEvents = th.getAllEventsByName(tx, "TroveLiquidated"); + assert.equal(liquidationEvents.length, 2, "Not enough liquidations"); + + assert.isFalse(await sortedTroves.contains(alice)); + assert.isFalse(await sortedTroves.contains(bob)); + assert.equal((await troveManager.Troves(alice))[3], "3"); + assert.equal((await troveManager.Troves(bob))[3], "3"); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 6c17148..99e026d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,11 +16,16 @@ "es2016", "es2017" ], - "baseUrl": ".", "rootDir": ".", "paths": { "@utils/*": [ - "test-utils/*" + "./test-utils/*" + ], + "scripts/*": [ + "./scripts/*" + ], + "types/*": [ + "./types/*" ] }, "allowJs": false 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();