From 38a06e2a88f53e72c0d39031dd5edbda057aaf7a Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 22 Sep 2023 09:37:49 -0300 Subject: [PATCH 001/111] Add initial LockstakeEngine/Urn/Clipper logic + tests --- .github/workflows/test.yml | 4 +- foundry.toml | 4 + src/LockstakeClipper.sol | 483 +++++++++ src/LockstakeEngine.sol | 337 ++++++ src/LockstakeUrn.sol | 74 ++ test/LockstakeClipper.t.sol | 1611 ++++++++++++++++++++++++++++ test/LockstakeEngine.t.sol | 782 ++++++++++++++ test/mocks/DelegateMock.sol | 45 + test/mocks/GemMock.sol | 80 ++ test/mocks/LockstakeEngineMock.sol | 31 + test/mocks/NstJoinMock.sol | 29 + test/mocks/NstMock.sol | 80 ++ test/mocks/PipMock.sol | 20 + test/mocks/StakingRewardsMock.sol | 53 + 14 files changed, 3632 insertions(+), 1 deletion(-) create mode 100644 src/LockstakeClipper.sol create mode 100644 src/LockstakeEngine.sol create mode 100644 src/LockstakeUrn.sol create mode 100644 test/LockstakeClipper.t.sol create mode 100644 test/LockstakeEngine.t.sol create mode 100644 test/mocks/DelegateMock.sol create mode 100644 test/mocks/GemMock.sol create mode 100644 test/mocks/LockstakeEngineMock.sol create mode 100644 test/mocks/NstJoinMock.sol create mode 100644 test/mocks/NstMock.sol create mode 100644 test/mocks/PipMock.sol create mode 100644 test/mocks/StakingRewardsMock.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09880b1d..5f538bb1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: test -on: workflow_dispatch +on: [push, pull_request] env: FOUNDRY_PROFILE: ci @@ -32,3 +32,5 @@ jobs: run: | forge test -vvv id: test + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} diff --git a/foundry.toml b/foundry.toml index e883058f..1bc5d09b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,9 @@ src = "src" out = "out" libs = ["lib"] +solc = "0.8.16" +optimizer = true +optimizer_runs = 200 +verbosity = 1 # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol new file mode 100644 index 00000000..6d83ebfb --- /dev/null +++ b/src/LockstakeClipper.sol @@ -0,0 +1,483 @@ +// SPDX-FileCopyrightText: © 2021 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.16; + +interface VatLike { + function suck(address, address, uint256) external; + function move(address, address, uint256) external; + function flux(bytes32, address, address, uint256) external; + function slip(bytes32, address, int256) external; + function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); +} + +interface PipLike { + function peek() external returns (bytes32, bool); +} + +interface SpotterLike { + function par() external returns (uint256); + function ilks(bytes32) external returns (PipLike, uint256); +} + +interface DogLike { + function chop(bytes32) external returns (uint256); + function digs(bytes32, uint256) external; +} + +interface ClipperCallee { + function clipperCall(address, uint256, uint256, bytes calldata) external; +} + +interface AbacusLike { + function price(uint256, uint256) external view returns (uint256); +} + +interface LockstakeEngineLike { + function ilk() external view returns (bytes32); + function onKick(address, uint256) external; + function onTake(address, address, uint256) external; + function onTakeLeftovers(address, uint256, uint256) external; + function onYank(address, uint256) external; +} + +// Clipper for use with the manager / proxy paradigm +contract LockstakeClipper { + // --- Auth --- + mapping (address => uint256) public wards; + function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } + function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } + modifier auth { + require(wards[msg.sender] == 1, "LockstakeClipper/not-authorized"); + _; + } + + // --- Data --- + bytes32 immutable public ilk; // Collateral type of this LockstakeClipper + VatLike immutable public vat; // Core CDP Engine + LockstakeEngineLike immutable public engine; // Lockstake Engine + + DogLike public dog; // Liquidation module + address public vow; // Recipient of dai raised in auctions + SpotterLike public spotter; // Collateral price module + AbacusLike public calc; // Current price calculator + + uint256 public buf; // Multiplicative factor to increase starting price [ray] + uint256 public tail; // Time elapsed before auction reset [seconds] + uint256 public cusp; // Percentage drop before auction reset [ray] + uint64 public chip; // Percentage of tab to suck from vow to incentivize keepers [wad] + uint192 public tip; // Flat fee to suck from vow to incentivize keepers [rad] + uint256 public chost; // Cache the ilk dust times the ilk chop to prevent excessive SLOADs [rad] + + uint256 public kicks; // Total auctions + uint256[] public active; // Array of active auction ids + + struct Sale { + uint256 pos; // Index in active array + uint256 tab; // Dai to raise [rad] + uint256 lot; // collateral to sell [wad] + uint256 tot; // static registry of tot collateral to sell [wad] + address usr; // Liquidated CDP + uint96 tic; // Auction start time + uint256 top; // Starting price [ray] + } + mapping(uint256 => Sale) public sales; + + uint256 internal locked; + + // Levels for circuit breaker + // 0: no breaker + // 1: no new kick() + // 2: no new kick() or redo() + // 3: no new kick(), redo(), or take() + uint256 public stopped = 0; + + // --- Events --- + event Rely(address indexed usr); + event Deny(address indexed usr); + + event File(bytes32 indexed what, uint256 data); + event File(bytes32 indexed what, address data); + + event Kick( + uint256 indexed id, + uint256 top, + uint256 tab, + uint256 lot, + address indexed usr, + address indexed kpr, + uint256 coin + ); + event Take( + uint256 indexed id, + uint256 max, + uint256 price, + uint256 owe, + uint256 tab, + uint256 lot, + address indexed usr + ); + event Redo( + uint256 indexed id, + uint256 top, + uint256 tab, + uint256 lot, + address indexed usr, + address indexed kpr, + uint256 coin + ); + + event Yank(uint256 id); + + // --- Init --- + constructor(address vat_, address spotter_, address dog_, address engine_) { + vat = VatLike(vat_); + spotter = SpotterLike(spotter_); + dog = DogLike(dog_); + engine = LockstakeEngineLike(engine_); + ilk = engine.ilk(); + buf = RAY; + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Synchronization --- + modifier lock { + require(locked == 0, "LockstakeClipper/system-locked"); + locked = 1; + _; + locked = 0; + } + + modifier isStopped(uint256 level) { + require(stopped < level, "LockstakeClipper/stopped-incorrect"); + _; + } + + // --- Administration --- + function file(bytes32 what, uint256 data) external auth lock { + if (what == "buf") buf = data; + else if (what == "tail") tail = data; // Time elapsed before auction reset + else if (what == "cusp") cusp = data; // Percentage drop before auction reset + else if (what == "chip") chip = uint64(data); // Percentage of tab to incentivize (max: 2^64 - 1 => 18.xxx WAD = 18xx%) + else if (what == "tip") tip = uint192(data); // Flat fee to incentivize keepers (max: 2^192 - 1 => 6.277T RAD) + else if (what == "stopped") stopped = data; // Set breaker (0, 1, 2, or 3) + else revert("LockstakeClipper/file-unrecognized-param"); + emit File(what, data); + } + function file(bytes32 what, address data) external auth lock { + if (what == "spotter") spotter = SpotterLike(data); + else if (what == "dog") dog = DogLike(data); + else if (what == "vow") vow = data; + else if (what == "calc") calc = AbacusLike(data); + else revert("LockstakeClipper/file-unrecognized-param"); + emit File(what, data); + } + + // --- Math --- + uint256 constant BLN = 10 ** 9; + uint256 constant WAD = 10 ** 18; + uint256 constant RAY = 10 ** 27; + + function min(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x <= y ? x : y; + } + function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x * y / WAD; + } + function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x * y / RAY; + } + function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x * RAY / y; + } + + // --- Auction --- + + // get the price directly from the OSM + // Could get this from rmul(Vat.ilks(ilk).spot, Spotter.mat()) instead, but + // if mat has changed since the last poke, the resulting value will be + // incorrect. + function getFeedPrice() internal returns (uint256 feedPrice) { + (PipLike pip, ) = spotter.ilks(ilk); + (bytes32 val, bool has) = pip.peek(); + require(has, "LockstakeClipper/invalid-price"); + feedPrice = rdiv(uint256(val) * BLN, spotter.par()); + } + + // start an auction + // note: trusts the caller to transfer collateral to the contract + // The starting price `top` is obtained as follows: + // + // top = val * buf / par + // + // Where `val` is the collateral's unitary value in USD, `buf` is a + // multiplicative factor to increase the starting price, and `par` is a + // reference per DAI. + function kick( + uint256 tab, // Debt [rad] + uint256 lot, // Collateral [wad] + address usr, // Address that will receive any leftover collateral; additionally assumed here to be the liquidated Vault. + address kpr // Address that will receive incentives + ) external auth lock isStopped(1) returns (uint256 id) { + // Input validation + require(tab > 0, "LockstakeClipper/zero-tab"); + require(lot > 0, "LockstakeClipper/zero-lot"); + require(usr != address(0), "LockstakeClipper/zero-usr"); + id = ++kicks; + require(id > 0, "LockstakeClipper/overflow"); + + active.push(id); + + sales[id].pos = active.length - 1; + + sales[id].tab = tab; + sales[id].lot = lot; + sales[id].tot = lot; + sales[id].usr = usr; + sales[id].tic = uint96(block.timestamp); + + uint256 top; + top = rmul(getFeedPrice(), buf); + require(top > 0, "LockstakeClipper/zero-top-price"); + sales[id].top = top; + + // incentive to kick auction + uint256 _tip = tip; + uint256 _chip = chip; + uint256 coin; + if (_tip > 0 || _chip > 0) { + coin = _tip + wmul(tab, _chip); + vat.suck(vow, kpr, coin); + } + + // Trigger proxy manager liquidation call-back + engine.onKick(usr, lot); + + emit Kick(id, top, tab, lot, usr, kpr, coin); + } + + // Reset an auction + // See `kick` above for an explanation of the computation of `top`. + function redo( + uint256 id, // id of the auction to reset + address kpr // Address that will receive incentives + ) external lock isStopped(2) { + // Read auction data + address usr = sales[id].usr; + uint96 tic = sales[id].tic; + uint256 top = sales[id].top; + + require(usr != address(0), "LockstakeClipper/not-running-auction"); + + // Check that auction needs reset + // and compute current price [ray] + (bool done,) = status(tic, top); + require(done, "LockstakeClipper/cannot-reset"); + + uint256 tab = sales[id].tab; + uint256 lot = sales[id].lot; + sales[id].tic = uint96(block.timestamp); + + uint256 feedPrice = getFeedPrice(); + top = rmul(feedPrice, buf); + require(top > 0, "LockstakeClipper/zero-top-price"); + sales[id].top = top; + + // incentive to redo auction + uint256 _tip = tip; + uint256 _chip = chip; + uint256 coin; + if (_tip > 0 || _chip > 0) { + uint256 _chost = chost; + if (tab >= _chost && lot * feedPrice >= _chost) { + coin = _tip + wmul(tab, _chip); + vat.suck(vow, kpr, coin); + } + } + + emit Redo(id, top, tab, lot, usr, kpr, coin); + } + + // Buy up to `amt` of collateral from the auction indexed by `id`. + // + // Auctions will not collect more DAI than their assigned DAI target,`tab`; + // thus, if `amt` would cost more DAI than `tab` at the current price, the + // amount of collateral purchased will instead be just enough to collect `tab` DAI. + // + // To avoid partial purchases resulting in very small leftover auctions that will + // never be cleared, any partial purchase must leave at least `LockstakeClipper.chost` + // remaining DAI target. `chost` is an asynchronously updated value equal to + // (Vat.dust * Dog.chop(ilk) / WAD) where the values are understood to be determined + // by whatever they were when LockstakeClipper.upchost() was last called. Purchase amounts + // will be minimally decreased when necessary to respect this limit; i.e., if the + // specified `amt` would leave `tab < chost` but `tab > 0`, the amount actually + // purchased will be such that `tab == chost`. + // + // If `tab <= chost`, partial purchases are no longer possible; that is, the remaining + // collateral can only be purchased entirely, or not at all. + function take( + uint256 id, // Auction id + uint256 amt, // Upper limit on amount of collateral to buy [wad] + uint256 max, // Maximum acceptable price (DAI / collateral) [ray] + address who, // Receiver of collateral and external call address + bytes calldata data // Data to pass in external call; if length 0, no call is done + ) external lock isStopped(3) { + + address usr = sales[id].usr; + uint96 tic = sales[id].tic; + + require(usr != address(0), "LockstakeClipper/not-running-auction"); + + uint256 price; + { + bool done; + (done, price) = status(tic, sales[id].top); + + // Check that auction doesn't need reset + require(!done, "LockstakeClipper/needs-reset"); + } + + // Ensure price is acceptable to buyer + require(max >= price, "LockstakeClipper/too-expensive"); + + uint256 lot = sales[id].lot; + uint256 tab = sales[id].tab; + uint256 owe; + + { + // Purchase as much as possible, up to amt + uint256 slice = min(lot, amt); // slice <= lot + + // DAI needed to buy a slice of this sale + owe = slice * price; + + // Don't collect more than tab of DAI + if (owe > tab) { + // Total debt will be paid + owe = tab; // owe' <= owe + // Adjust slice + slice = owe / price; // slice' = owe' / price <= owe / price == slice <= lot + } else if (owe < tab && slice < lot) { + // If slice == lot => auction completed => dust doesn't matter + uint256 _chost = chost; + if (tab - owe < _chost) { // safe as owe < tab + // If tab <= chost, buyers have to take the entire lot. + require(tab > _chost, "LockstakeClipper/no-partial-purchase"); + // Adjust amount to pay + owe = tab - _chost; // owe' <= owe + // Adjust slice + slice = owe / price; // slice' = owe' / price < owe / price == slice < lot + } + } + + // Calculate remaining tab after operation + tab = tab - owe; // safe since owe <= tab + // Calculate remaining lot after operation + lot = lot - slice; + + // Send collateral to who + vat.slip(ilk, address(this), -int256(slice)); + engine.onTake(usr, who, slice); + + // Do external call (if data is defined) but to be + // extremely careful we don't allow to do it to the two + // contracts which the LockstakeClipper needs to be authorized + DogLike dog_ = dog; + if (data.length > 0 && who != address(vat) && who != address(dog_)) { + ClipperCallee(who).clipperCall(msg.sender, owe, slice, data); + } + + // Get DAI from caller + vat.move(msg.sender, vow, owe); + + // Removes Dai out for liquidation from accumulator + dog_.digs(ilk, lot == 0 ? tab + owe : owe); + } + + if (lot == 0) { + _remove(id); + } else if (tab == 0) { + uint256 tot = sales[id].tot; + vat.slip(ilk, address(this), -int256(lot)); + engine.onTakeLeftovers(usr, tot, lot); + _remove(id); + } else { + sales[id].tab = tab; + sales[id].lot = lot; + } + + emit Take(id, max, price, owe, tab, lot, usr); + } + + function _remove(uint256 id) internal { + uint256 _move = active[active.length - 1]; + if (id != _move) { + uint256 _index = sales[id].pos; + active[_index] = _move; + sales[_move].pos = _index; + } + active.pop(); + delete sales[id]; + } + + // The number of active auctions + function count() external view returns (uint256) { + return active.length; + } + + // Return the entire array of active auctions + function list() external view returns (uint256[] memory) { + return active; + } + + // Externally returns boolean for if an auction needs a redo and also the current price + function getStatus(uint256 id) external view returns (bool needsRedo, uint256 price, uint256 lot, uint256 tab) { + // Read auction data + address usr = sales[id].usr; + uint96 tic = sales[id].tic; + + bool done; + (done, price) = status(tic, sales[id].top); + + needsRedo = usr != address(0) && done; + lot = sales[id].lot; + tab = sales[id].tab; + } + + // Internally returns boolean for if an auction needs a redo + function status(uint96 tic, uint256 top) internal view returns (bool done, uint256 price) { + price = calc.price(top, block.timestamp - tic); + done = (block.timestamp - tic > tail || rdiv(price, top) < cusp); + } + + // Public function to update the cached dust*chop value. + function upchost() external { + (,,,, uint256 _dust) = VatLike(vat).ilks(ilk); + chost = wmul(_dust, dog.chop(ilk)); + } + + // Cancel an auction during ES or via governance action. + function yank(uint256 id) external auth lock { + require(sales[id].usr != address(0), "LockstakeClipper/not-running-auction"); + dog.digs(ilk, sales[id].tab); + uint256 lot = sales[id].lot; + vat.slip(ilk, address(this), -int256(lot)); + engine.onYank(sales[id].usr, lot); + _remove(id); + emit Yank(id); + } +} diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol new file mode 100644 index 00000000..66e3fece --- /dev/null +++ b/src/LockstakeEngine.sol @@ -0,0 +1,337 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.16; + +import { LockstakeUrn } from "src/LockstakeUrn.sol"; + +interface DelegateFactoryLike { + function gov() external view returns (GemLike); + function isDelegate(address) external returns (uint256); +} + +interface DelegateLike { + function lock(uint256) external; + function free(uint256) external; +} + +interface VatLike { + function urns(bytes32, address) external view returns (uint256, uint256); + function hope(address) external; + function slip(bytes32, address, int256) external; + function frob(bytes32, address, address, address, int256, int256) external; +} + +interface NstJoinLike { + function vat() external view returns (VatLike); + function nst() external view returns (GemLike); + function join(address, uint256) external; + function exit(address, uint256) external; +} + +interface GemLike { + function balanceOf(address) external view returns (uint256); + function approve(address, uint256) external; + function transfer(address, uint256) external; + function transferFrom(address, address, uint256) external; + function mint(address, uint256) external; + function burn(address, uint256) external; +} + +interface JugLike { + function drip(bytes32) external returns (uint256); +} + +contract LockstakeEngine { + // --- storage variables --- + + mapping(address => uint256) public wards; // usr => 1 == access + mapping(address => uint256) public farms; // farm => 1 == whitelisted + mapping(address => uint256) public urnsAmt; // usr => amount + mapping(address => address) public urnOwners; // urn => owner + mapping(address => address) public urnDelegates; // urn => current associated delegare + mapping(address => address) public selectedFarm; // urn => current selected farm + JugLike public jug; + + // --- constants --- + + uint256 constant WAD = 10**18; + uint256 constant RAY = 10**27; + + // --- immutables --- + + DelegateFactoryLike immutable public delegateFactory; + VatLike immutable public vat; + NstJoinLike immutable public nstJoin; + GemLike immutable public nst; + bytes32 immutable public ilk; + GemLike immutable public ngt; + GemLike immutable public stkNgt; + uint256 immutable public fee; + + // --- events --- + + event Rely(address indexed usr); + event Deny(address indexed usr); + event File(bytes32 indexed what, address data); + event AddFarm(address farm); + event DelFarm(address farm); + event Open(address indexed owner, address indexed delegate, address urn); + event Lock(address indexed urn, uint256 wad); + event Free(address indexed urn, uint256 wad, uint256 burn); + event Move(address indexed urn, address indexed delegate); + event Draw(address indexed urn, uint256 wad); + event Wipe(address indexed urn, uint256 wad); + event SelectFarm(address indexed urn, address farm); + event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); + event Withdraw(address indexed urn, address indexed farm, uint256 amt); + event GetReward(address indexed urn, address indexed farm); + event OnKick(address indexed urn, uint256 wad); + event OnTake(address indexed urn, address indexed who, uint256 wad); + event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn); + event OnYank(address indexed urn, uint256 wad); + + // --- modifiers --- + + modifier auth { + require(wards[msg.sender] == 1, "LockstakeEngine/not-authorized"); + _; + } + + modifier urnOwner(address urn) { + require(urnOwners[urn] == msg.sender, "LockstakeEngine/not-urn-owner"); + _; + } + + // --- constructor --- + + constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkNgt_, uint256 fee_) { + delegateFactory = DelegateFactoryLike(delegateFactory_); + nstJoin = NstJoinLike(nstJoin_); + vat = nstJoin.vat(); + nst = nstJoin.nst(); + ilk = ilk_; + ngt = delegateFactory.gov(); + stkNgt = GemLike(stkNgt_); + fee = fee_; + nst.approve(nstJoin_, type(uint256).max); + vat.hope(nstJoin_); + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- math --- + + function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { + unchecked { + z = x != 0 ? ((x - 1) / y) + 1 : 0; + } + } + + // --- administration --- + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + function file(bytes32 what, address data) external auth { + if (what == "jug") { + jug = JugLike(data); + } else revert("LockstakeEngine/file-unrecognized-param"); + emit File(what, data); + } + + function addFarm(address farm) external auth { + farms[farm] = 1; + emit AddFarm(farm); + } + + function delFarm(address farm) external auth { + farms[farm] = 0; + emit DelFarm(farm); + } + + // --- getters --- + + function getUrn( + address owner, + uint256 index + ) external view returns (address urn) { + uint256 salt = uint256(keccak256(abi.encode(owner, index))); + bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkNgt))); + urn = address(uint160(uint256( + keccak256( + abi.encodePacked(bytes1(0xff), address(this), salt, codeHash) + ) + ))); + } + + // --- urn/delegation functions --- + + function open(address delegate) external returns (address urn) { + require(delegateFactory.isDelegate(delegate) == 1, "LockstateEngine/not-valid-delegate"); + uint256 salt = uint256(keccak256(abi.encode(msg.sender, urnsAmt[msg.sender]++))); + bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkNgt)); + assembly { + urn := create2(0, add(code, 0x20), mload(code), salt) + } + require(urn != address(0), "LockstateEngine/urn-creation-failed"); + urnOwners[urn] = msg.sender; + urnDelegates[urn] = delegate; + emit Open(msg.sender, delegate, urn); + } + + function lock(address urn, uint256 wad) external urnOwner(urn) { + require(wad <= uint256(type(int256).max), "LockstateEngine/wad-overflow"); + ngt.transferFrom(msg.sender, address(this), wad); + address delegate = urnDelegates[urn]; + ngt.approve(address(delegate), wad); + DelegateLike(delegate).lock(wad); + // TODO: define if we want an internal registry to register how much is locked per user, + // the vat.slip and stkNgt balance act already as a registry so probably not needed an extra one + vat.slip(ilk, urn, int256(wad)); + vat.frob(ilk, urn, urn, address(0), int256(wad), 0); + stkNgt.mint(urn, wad); + emit Lock(urn, wad); + } + + function free(address urn, uint256 wad) external urnOwner(urn) { + require(wad <= uint256(type(int256).max), "LockstateEngine/wad-overflow"); + vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); + vat.slip(ilk, urn, -int256(wad)); + stkNgt.burn(urn, wad); + address delegate = urnDelegates[urn]; + DelegateLike(delegate).free(wad); + uint256 burn = wad * fee / WAD; + ngt.burn(address(this), burn); + ngt.transfer(msg.sender, wad - burn); + emit Free(urn, wad, burn); + } + + function move(address urn, address delegate) external urnOwner(urn) { + require(delegateFactory.isDelegate(delegate) == 1, "LockstateEngine/not-valid-delegate"); + address prevDelegate = urnDelegates[urn]; + require(prevDelegate != delegate, "LockstateEngine/same-delegate"); + (uint256 wad,) = vat.urns(ilk, urn); + DelegateLike(prevDelegate).free(wad); + ngt.approve(address(delegate), wad); + DelegateLike(delegate).lock(wad); + urnDelegates[urn] = delegate; + emit Move(urn, delegate); + } + + // --- loan functions --- + + function draw(address urn, uint256 wad) external urnOwner(urn) { + uint256 rate = jug.drip(ilk); + uint256 dart = _divup(wad * RAY, rate); + require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); + vat.frob(ilk, urn, address(0), address(this), 0, int256(dart)); + nstJoin.exit(msg.sender, wad); + emit Draw(urn, wad); + } + + function wipe(address urn, uint256 wad) external urnOwner(urn) { + nst.transferFrom(msg.sender, address(this), wad); + nstJoin.join(address(this), wad); + uint256 rate = jug.drip(ilk); + uint256 dart = wad * RAY / rate; + require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); + vat.frob(ilk, urn, address(0), address(this), 0, -int256(dart)); + emit Wipe(urn, wad); + } + + // --- staking functions --- + + function selectFarm(address urn, address farm) external urnOwner(urn) { + require(farms[farm] == 1, "LockstakeEngine/non-existing-farm"); + address selectedFarmUrn = selectedFarm[urn]; + require(selectedFarmUrn == address(0) || GemLike(selectedFarmUrn).balanceOf(address(urn)) == 0, "LockstakeEngine/withdraw-first"); + selectedFarm[urn] = farm; + emit SelectFarm(urn, farm); + } + + function stake(address urn, uint256 wad, uint16 ref) external urnOwner(urn) { + address selectedFarmUrn = selectedFarm[urn]; + require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm"); + LockstakeUrn(urn).stake(selectedFarmUrn, wad, ref); + emit Stake(urn, selectedFarmUrn, wad, ref); + } + + function withdraw(address urn, uint256 amt) external urnOwner(urn) { + address selectedFarmUrn = selectedFarm[urn]; + require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm"); + LockstakeUrn(urn).withdraw(selectedFarmUrn, amt); + emit Withdraw(urn, selectedFarmUrn, amt); + } + + function getReward(address urn, address farm) external urnOwner(urn) { + LockstakeUrn(urn).getReward(farm, msg.sender); + emit GetReward(urn, farm); + } + + // --- liquidation callback functions --- + + function onKick(address urn, uint256 wad) external auth { + address selectedFarmUrn = selectedFarm[urn]; + if (selectedFarmUrn != address(0)){ + uint256 freed = GemLike(stkNgt).balanceOf(address(urn)); + if (wad > freed) { + LockstakeUrn(urn).withdraw(selectedFarmUrn, wad - freed); + } + } + stkNgt.burn(urn, wad); // Burn the whole liquidated amount of staking token + DelegateLike(urnDelegates[urn]).free(wad); // Undelegate liquidated amount and retain NGT + // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper + emit OnKick(urn, wad); + } + + function onTake(address urn, address who, uint256 wad) external auth { + ngt.transfer(who, wad); // Free NGT to the auction buyer + emit OnTake(urn, who, wad); + } + + function onTakeLeftovers(address urn, uint256 tot, uint256 left) external auth { + uint256 burn = (tot - left) * fee / WAD; + if (burn > left) { + burn = left; + left = 0; + } else { + unchecked { left = left - burn; } + } + ngt.burn(address(this), burn); // Burn NGT + if (left > 0) { + address delegate = urnDelegates[urn]; + ngt.approve(address(delegate), left); + DelegateLike(delegate).lock(left); + vat.slip(ilk, urn, int256(left)); + vat.frob(ilk, urn, urn, address(0), int256(left), 0); + stkNgt.mint(urn, left); + } + emit OnTakeLeftovers(urn, tot, left, burn); + } + + function onYank(address urn, uint256 wad) external auth { + ngt.burn(address(this), wad); + emit OnYank(urn, wad); + } +} diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol new file mode 100644 index 00000000..b0e88916 --- /dev/null +++ b/src/LockstakeUrn.sol @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.16; + +interface VatLike { + function hope(address) external; +} + +interface GemLike { + function balanceOf(address) external view returns (uint256); + function approve(address, uint256) external; + function transfer(address, uint256) external; +} + +interface StakingRewardsLike { + function rewardsToken() external view returns (GemLike); + function stake(uint256, uint16) external; + function withdraw(uint256) external; + function getReward() external; +} + +contract LockstakeUrn { + // --- immutables --- + + address immutable public engine; + GemLike immutable public stkNgt; + + // --- modifiers --- + + modifier isEngine { + require(msg.sender == engine, "LockstakeUrn/not-engine"); + _; + } + + // --- constructor --- + + constructor(address vat_, address stkNgt_) { + engine = msg.sender; + stkNgt = GemLike(stkNgt_); + VatLike(vat_).hope(msg.sender); + stkNgt.approve(msg.sender, type(uint256).max); + } + + // --- staking functions --- + + function stake(address farm, uint256 wad, uint16 ref) external isEngine { + stkNgt.approve(farm, wad); + StakingRewardsLike(farm).stake(wad, ref); + } + + function withdraw(address farm, uint256 amt) external isEngine{ + StakingRewardsLike(farm).withdraw(amt); + } + + function getReward(address farm, address usr) external isEngine { + StakingRewardsLike(farm).getReward(); + GemLike rewardsToken = StakingRewardsLike(farm).rewardsToken(); + rewardsToken.transfer(usr, rewardsToken.balanceOf(address(this))); + } +} diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol new file mode 100644 index 00000000..7f85409b --- /dev/null +++ b/test/LockstakeClipper.t.sol @@ -0,0 +1,1611 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +import "dss-test/DssTest.sol"; + +import { LockstakeClipper } from "src/LockstakeClipper.sol"; +import { LockstakeEngineMock } from "test/mocks/LockstakeEngineMock.sol"; +import { PipMock } from "test/mocks/PipMock.sol"; + +contract BadGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall(address sender, uint256 owe, uint256 slice, bytes calldata data) + external { + sender; owe; slice; data; + clip.take({ // attempt reentrancy + id: 1, + amt: 25 ether, + max: 5 ether * 10E27, + who: address(this), + data: "" + }); + } +} + +contract RedoGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + owe; slice; data; + clip.redo(1, sender); + } +} + +contract KickGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.kick(1, 1, address(0), address(0)); + } +} + +contract FileUintGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.file("stopped", 1); + } +} + +contract FileAddrGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.file("vow", address(123)); + } +} + +contract YankGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.yank(1); + } +} + +contract PublicClip is LockstakeClipper { + + constructor(address vat, address spot, address dog, address engine) LockstakeClipper(vat, spot, dog, engine) {} + + function add() public returns (uint256 id) { + id = ++kicks; + active.push(id); + sales[id].pos = active.length - 1; + } + + function remove(uint256 id) public { + _remove(id); + } +} + +interface ChainlogLike { + function getAddress(bytes32) external view returns (address); +} + +interface VatLike { + function dai(address) external view returns (uint256); + function gem(bytes32, address) external view returns (uint256); + function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); + function urns(bytes32, address) external view returns (uint256, uint256); + function rely(address) external; + function file(bytes32, bytes32, uint256) external; + function init(bytes32) external; + function hope(address) external; + function frob(bytes32, address, address, address, int256, int256) external; + function slip(bytes32, address, int256) external; + function suck(address, address, uint256) external; + function fold(bytes32, address, int256) external; +} + +interface GemLike { + function balanceOf(address) external view returns (uint256); +} + +interface DogLike { + function Dirt() external view returns (uint256); + function chop(bytes32) external view returns (uint256); + function ilks(bytes32) external view returns (address, uint256, uint256, uint256); + function rely(address) external; + function file(bytes32, uint256) external; + function file(bytes32, bytes32, address) external; + function file(bytes32, bytes32, uint256) external; + function bark(bytes32, address, address) external returns (uint256); +} + +interface SpotterLike { + function file(bytes32, bytes32, address) external; + function file(bytes32, bytes32, uint256) external; + function poke(bytes32) external; +} + +interface CalcFabLike { + function newLinearDecrease(address) external returns (address); + function newStairstepExponentialDecrease(address) external returns (address); +} + +interface CalcLike { + function file(bytes32, uint256) external; +} + +interface VowLike { + +} + +contract ClipperTest is DssTest { + address pauseProxy; + VatLike vat; + DogLike dog; + SpotterLike spot; + VowLike vow; + PipMock pip; + GemLike dai; + + LockstakeClipper clip; + + // Exchange exchange; + + address constant LOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; + + address ali; + address bob; + address che; + + bytes32 constant ilk = "LSE"; + uint256 constant price = 5 ether; + + uint256 constant startTime = 604411200; // Used to avoid issues with `block.timestamp` + + function _ink(bytes32 ilk_, address urn_) internal view returns (uint256) { + (uint256 ink_,) = vat.urns(ilk_, urn_); + return ink_; + } + function _art(bytes32 ilk_, address urn_) internal view returns (uint256) { + (,uint256 art_) = vat.urns(ilk_, urn_); + return art_; + } + + function ray(uint256 wad) internal pure returns (uint256) { + return wad * 10 ** 9; + } + + function rad(uint256 wad) internal pure returns (uint256) { + return wad * 10 ** 27; + } + + modifier takeSetup { + address calc = CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newStairstepExponentialDecrease(address(this)); + CalcLike(calc).file("cut", RAY - ray(0.01 ether)); // 1% decrease + CalcLike(calc).file("step", 1); // Decrease every 1 second + + clip.file("buf", ray(1.25 ether)); // 25% Initial price buffer + clip.file("calc", address(calc)); // File price contract + clip.file("cusp", ray(0.3 ether)); // 70% drop before reset + clip.file("tail", 3600); // 1 hour before reset + + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); + + assertEq(clip.kicks(), 0); + dog.bark(ilk, address(this), address(this)); + assertEq(clip.kicks(), 1); + + (ink, art) = vat.urns(ilk, address(this)); + assertEq(ink, 0); + assertEq(art, 0); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, rad(110 ether)); + assertEq(sale.lot, 40 ether); + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(5 ether)); // $4 plus 25% + + assertEq(vat.gem(ilk, ali), 0); + assertEq(vat.dai(ali), rad(1000 ether)); + assertEq(vat.gem(ilk, bob), 0); + assertEq(vat.dai(bob), rad(1000 ether)); + + _; + } + + function setUp() public { + vm.createSelectFork(vm.envString("ETH_RPC_URL")); + vm.warp(startTime); + + pauseProxy = ChainlogLike(LOG).getAddress("MCD_PAUSE_PROXY"); + vat = VatLike(ChainlogLike(LOG).getAddress("MCD_VAT")); + spot = SpotterLike(ChainlogLike(LOG).getAddress("MCD_SPOT")); + vow = VowLike(ChainlogLike(LOG).getAddress("MCD_VOW")); + dog = DogLike(ChainlogLike(LOG).getAddress("MCD_DOG")); + dai = GemLike(ChainlogLike(LOG).getAddress("MCD_DAI")); + + pip = new PipMock(); + pip.setPrice(price); // Spot = $2.5 + + vm.prank(pauseProxy); vat.init(ilk); + + vm.prank(pauseProxy); spot.file(ilk, "pip", address(pip)); + vm.prank(pauseProxy); spot.file(ilk, "mat", ray(2 ether)); // 200% liquidation ratio for easier test calcs + spot.poke(ilk); + + vm.prank(pauseProxy); vat.file(ilk, "dust", rad(20 ether)); // $20 dust + vm.prank(pauseProxy); vat.file(ilk, "line", rad(10000 ether)); + // vm.prank(pauseProxy); vat.file("Line", rad(10000 ether)); + + vm.prank(pauseProxy); dog.file(ilk, "chop", 1.1 ether); // 10% chop + vm.prank(pauseProxy); dog.file(ilk, "hole", rad(1000 ether)); + // vm.prank(pauseProxy); dog.file("Hole", rad(1000 ether)); + + LockstakeEngineMock engine = new LockstakeEngineMock(address(vat), ilk); + vm.prank(pauseProxy); vat.rely(address(engine)); + + // dust and chop filed previously so clip.chost will be set correctly + clip = new LockstakeClipper(address(vat), address(spot), address(dog), address(engine)); + clip.upchost(); + clip.rely(address(dog)); + + vm.prank(pauseProxy); dog.file(ilk, "clip", address(clip)); + vm.prank(pauseProxy); dog.rely(address(clip)); + vm.prank(pauseProxy); vat.rely(address(clip)); + + vm.prank(pauseProxy); vat.slip(ilk, address(this), int256(1000 ether)); + + assertEq(vat.gem(ilk, address(this)), 1000 ether); + assertEq(vat.dai(address(this)), 0); + vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); + assertEq(vat.gem(ilk, address(this)), 960 ether); + assertEq(vat.dai(address(this)), rad(100 ether)); + + pip.setPrice(4 ether); // Spot = $2 + spot.poke(ilk); // Now unsafe + + ali = address(111); + bob = address(222); + che = address(333); + + vat.hope(address(clip)); + vm.prank(ali); vat.hope(address(clip)); + vm.prank(bob); vat.hope(address(clip)); + + vm.prank(pauseProxy); vat.suck(address(0), address(this), rad(1000 ether)); + vm.prank(pauseProxy); vat.suck(address(0), address(ali), rad(1000 ether)); + vm.prank(pauseProxy); vat.suck(address(0), address(bob), rad(1000 ether)); + } + + function testChangeDog() public { + assertTrue(address(clip.dog()) != address(123)); + clip.file("dog", address(123)); + assertEq(address(clip.dog()), address(123)); + } + + function testGetChop() public { + uint256 chop = dog.chop(ilk); + (, uint256 chop2,,) = dog.ilks(ilk); + assertEq(chop, chop2); + } + + function testKick() public { + clip.file("tip", rad(100 ether)); // Flat fee of 100 DAI + clip.file("chip", 0); // No linear increase + + assertEq(clip.kicks(), 0); + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 960 ether); + assertEq(vat.dai(ali), rad(1000 ether)); + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); + + vm.prank(ali); dog.bark(ilk, address(this), address(ali)); + + assertEq(clip.kicks(), 1); + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, rad(110 ether)); + assertEq(sale.lot, 40 ether); + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(4 ether)); + assertEq(vat.gem(ilk, address(this)), 960 ether); + assertEq(vat.dai(ali), rad(1100 ether)); // Paid "tip" amount of DAI for calling bark() + (ink, art) = vat.urns(ilk, address(this)); + assertEq(ink, 0 ether); + assertEq(art, 0 ether); + + pip.setPrice(price); // Spot = $2.5 + spot.poke(ilk); // Now safe + + vm.warp(startTime + 100); + vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); + + pip.setPrice(4 ether); // Spot = $2 + spot.poke(ilk); // Now unsafe + + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(2); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 920 ether); + + clip.file(bytes32("buf"), ray(1.25 ether)); // 25% Initial price buffer + + clip.file("tip", rad(100 ether)); // Flat fee of 100 DAI + clip.file("chip", 0.02 ether); // Linear increase of 2% of tab + + assertEq(vat.dai(bob), rad(1000 ether)); + + vm.prank(bob); dog.bark(ilk, address(this), address(bob)); + + assertEq(clip.kicks(), 2); + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(2); + assertEq(sale.pos, 1); + assertEq(sale.tab, rad(110 ether)); + assertEq(sale.lot, 40 ether); + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(5 ether)); + assertEq(vat.gem(ilk, address(this)), 920 ether); + (ink, art) = vat.urns(ilk, address(this)); + assertEq(ink, 0 ether); + assertEq(art, 0 ether); + + assertEq(vat.dai(bob), rad(1000 ether) + rad(100 ether) + sale.tab * 0.02 ether / WAD); // Paid (tip + due * chip) amount of DAI for calling bark() + } + + function testRevertsKickZeroPrice() public { + pip.setPrice(0); + vm.expectRevert("LockstakeClipper/zero-top-price"); + dog.bark(ilk, address(this), address(this)); + } + + function testRevertsRedoZeroPrice() public { + _auctionResetSetup(1 hours); + + pip.setPrice(0); + + vm.warp(startTime + 1801 seconds); + (bool needsRedo,,,) = clip.getStatus(1); + assertTrue(needsRedo); + vm.expectRevert("LockstakeClipper/zero-top-price"); + clip.redo(1, address(this)); + } + + function testKickBasic() public { + clip.kick(1 ether, 2 ether, address(1), address(this)); + } + + function testRevertsKickZeroTab() public { + vm.expectRevert("LockstakeClipper/zero-tab"); + clip.kick(0, 2 ether, address(1), address(this)); + } + + function testRevertsKickZeroLot() public { + vm.expectRevert("LockstakeClipper/zero-lot"); + clip.kick(1 ether, 0, address(1), address(this)); + } + + function testRevertsKickZeroUsr() public { + vm.expectRevert("LockstakeClipper/zero-usr"); + clip.kick(1 ether, 2 ether, address(0), address(this)); + } + + function testBarkNotLeavingDust() public { + vm.prank(pauseProxy); dog.file(ilk, "hole", rad(80 ether)); // Makes room = 80 WAD + vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) + + assertEq(clip.kicks(), 0); + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); + + dog.bark(ilk, address(this), address(this)); // art - dart = 100 - 80 = dust (= 20) + + assertEq(clip.kicks(), 1); + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, rad(80 ether)); // No chop + assertEq(sale.lot, 32 ether); + assertEq(sale.tot, 32 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(4 ether)); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (ink, art) = vat.urns(ilk, address(this)); + assertEq(ink, 8 ether); + assertEq(art, 20 ether); + } + + function testBarkNotLeavingDustOverHole() public { + vm.prank(pauseProxy); dog.file(ilk, "hole", rad(80 ether) + ray(1 ether)); // Makes room = 80 WAD + 1 wei + vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) + + assertEq(clip.kicks(), 0); + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); + + dog.bark(ilk, address(this), address(this)); // art - dart = 100 - (80 + 1 wei) < dust (= 20) then the whole debt is taken + + assertEq(clip.kicks(), 1); + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, rad(100 ether)); // No chop + assertEq(sale.lot, 40 ether); + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(4 ether)); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (ink, art) = vat.urns(ilk, address(this)); + assertEq(ink, 0 ether); + assertEq(art, 0 ether); + } + + function testBarkNotLeavingDustRate() public { + vm.prank(pauseProxy); vat.fold(ilk, address(vow), int256(ray(0.02 ether))); + (, uint256 rate,,,) = vat.ilks(ilk); + assertEq(rate, ray(1.02 ether)); + + vm.prank(pauseProxy); dog.file(ilk, "hole", 100 * RAD); // Makes room = 100 RAD + vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop for precise calculations + vm.prank(pauseProxy); vat.file(ilk, "dust", 20 * RAD); // 20 DAI minimum Vault debt + clip.upchost(); + + assertEq(clip.kicks(), 0); + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); // Full debt is 102 DAI since rate = 1.02 * RAY + + // (art - dart) * rate ~= 2 RAD < dust = 20 RAD + // => remnant would be dusty, so a full liquidation occurs. + dog.bark(ilk, address(this), address(this)); + + assertEq(clip.kicks(), 1); + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 100 ether * rate); // No chop + assertEq(sale.lot, 40 ether); + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(4 ether)); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (ink, art) = vat.urns(ilk, address(this)); + assertEq(ink, 0); + assertEq(art, 0); + } + + function testBarkOnlyLeavingDustOverHoleRate() public { + vm.prank(pauseProxy); vat.fold(ilk, address(vow), int256(ray(0.02 ether))); + (, uint256 rate,,,) = vat.ilks(ilk); + assertEq(rate, ray(1.02 ether)); + + vm.prank(pauseProxy); dog.file(ilk, "hole", 816 * RAD / 10); // Makes room = 81.6 RAD => dart = 80 + vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop for precise calculations + vm.prank(pauseProxy); vat.file(ilk, "dust", 204 * RAD / 10); // 20.4 DAI dust + clip.upchost(); + + assertEq(clip.kicks(), 0); + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); + + // (art - dart) * rate = 20.4 RAD == dust + // => marginal threshold at which partial liquidation is acceptable + dog.bark(ilk, address(this), address(this)); + + assertEq(clip.kicks(), 1); + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 816 * RAD / 10); // Equal to ilk.hole + assertEq(sale.lot, 32 ether); + assertEq(sale.tot, 32 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(4 ether)); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (ink, art) = vat.urns(ilk, address(this)); + assertEq(ink, 8 ether); + assertEq(art, 20 ether); + (,,,, uint256 dust) = vat.ilks(ilk); + assertEq(art * rate, dust); + } + + function testHolehole() public { + assertEq(dog.Dirt(), 0); + (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, 0); + + dog.bark(ilk, address(this), address(this)); + + (, uint256 tab,,,,,) = clip.sales(1); + + assertEq(dog.Dirt(), tab); + (,,, dirt) = dog.ilks(ilk); + assertEq(dirt, tab); + + bytes32 ilk2 = "LSE2"; + LockstakeEngineMock engine = new LockstakeEngineMock(address(vat), ilk2); + vm.prank(pauseProxy); vat.rely(address(engine)); + LockstakeClipper clip2 = new LockstakeClipper(address(vat), address(spot), address(dog), address(engine)); + clip2.upchost(); + clip2.rely(address(dog)); + + vm.prank(pauseProxy); dog.file(ilk2, "clip", address(clip2)); + vm.prank(pauseProxy); dog.file(ilk2, "chop", 1.1 ether); + vm.prank(pauseProxy); dog.file(ilk2, "hole", rad(1000 ether)); + vm.prank(pauseProxy); dog.rely(address(clip2)); + + vm.prank(pauseProxy); vat.init(ilk2); + vm.prank(pauseProxy); vat.rely(address(clip2)); + vm.prank(pauseProxy); vat.file(ilk2, "line", rad(100 ether)); + + vm.prank(pauseProxy); vat.slip(ilk2, address(this), 40 ether); + + PipMock pip2 = new PipMock(); + pip2.setPrice(price); // Spot = $2.5 + + vm.prank(pauseProxy); spot.file(ilk2, "pip", address(pip2)); + vm.prank(pauseProxy); spot.file(ilk2, "mat", ray(2 ether)); + spot.poke(ilk2); + vat.frob(ilk2, address(this), address(this), address(this), 40 ether, 100 ether); + pip2.setPrice(4 ether); // Spot = $2 + spot.poke(ilk2); + + dog.bark(ilk2, address(this), address(this)); + + (, uint256 tab2,,,,,) = clip2.sales(1); + + assertEq(dog.Dirt(), tab + tab2); + (,,, dirt) = dog.ilks(ilk); + (,,, uint256 dirt2) = dog.ilks(ilk2); + assertEq(dirt, tab); + assertEq(dirt2, tab2); + } + + function testPartialLiquidationHoleLimit() public { + vm.prank(pauseProxy); dog.file("Hole", rad(75 ether)); + + assertEq(_ink(ilk, address(this)), 40 ether); + assertEq(_art(ilk, address(this)), 100 ether); + + assertEq(dog.Dirt(), 0); + (,uint256 chop,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, 0); + + dog.bark(ilk, address(this), address(this)); + + LockstakeClipper.Sale memory sale; + (, sale.tab, sale.lot,,,,) = clip.sales(1); + + (, uint256 rate,,,) = vat.ilks(ilk); + + assertEq(sale.lot, 40 ether * (sale.tab * WAD / rate / chop) / 100 ether); + assertEq(sale.tab, rad(75 ether) - ray(0.2 ether)); // 0.2 RAY rounding error + + assertEq(_ink(ilk, address(this)), 40 ether - sale.lot); + assertEq(_art(ilk, address(this)), 100 ether - sale.tab * WAD / rate / chop); + + assertEq(dog.Dirt(), sale.tab); + (,,, dirt) = dog.ilks(ilk); + assertEq(dirt, sale.tab); + } + + function testPartialLiquidationholeLimit() public { + vm.prank(pauseProxy); dog.file(ilk, "hole", rad(75 ether)); + + assertEq(_ink(ilk, address(this)), 40 ether); + assertEq(_art(ilk, address(this)), 100 ether); + + assertEq(dog.Dirt(), 0); + (,uint256 chop,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, 0); + + dog.bark(ilk, address(this), address(this)); + + LockstakeClipper.Sale memory sale; + (, sale.tab, sale.lot,,,,) = clip.sales(1); + + (, uint256 rate,,,) = vat.ilks(ilk); + + assertEq(sale.lot, 40 ether * (sale.tab * WAD / rate / chop) / 100 ether); + assertEq(sale.tab, rad(75 ether) - ray(0.2 ether)); // 0.2 RAY rounding error + + assertEq(_ink(ilk, address(this)), 40 ether - sale.lot); + assertEq(_art(ilk, address(this)), 100 ether - sale.tab * WAD / rate / chop); + + assertEq(dog.Dirt(), sale.tab); + (,,, dirt) = dog.ilks(ilk); + assertEq(dirt, sale.tab); + } + + function testTakeZeroUsr() public takeSetup { + // Auction id 2 is unpopulated. + (,,,, address usr,,) = clip.sales(2); + assertEq(usr, address(0)); + vm.expectRevert("LockstakeClipper/not-running-auction"); + clip.take(2, 25 ether, ray(5 ether), address(ali), ""); + } + + function testTakeOverTab() public takeSetup { + // Bid so owe (= 25 * 5 = 125 RAD) > tab (= 110 RAD) + // Readjusts slice to be tab/top = 25 + vm.prank(ali); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + + assertEq(vat.gem(ilk, ali), 22 ether); // Didn't take whole lot + assertEq(vat.dai(ali), rad(890 ether)); // Didn't pay more than tab (110) + assertEq(vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr + + // Assert auction ends + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + + assertEq(dog.Dirt(), 0); + (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, 0); + } + + function testTakeAtTab() public takeSetup { + // Bid so owe (= 22 * 5 = 110 RAD) == tab (= 110 RAD) + vm.prank(ali); clip.take({ + id: 1, + amt: 22 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + + assertEq(vat.gem(ilk, ali), 22 ether); // Didn't take whole lot + assertEq(vat.dai(ali), rad(890 ether)); // Paid full tab (110) + assertEq(vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr + + // Assert auction ends + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + + assertEq(dog.Dirt(), 0); + (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, 0); + } + + function testTakeUnderTab() public takeSetup { + // Bid so owe (= 11 * 5 = 55 RAD) < tab (= 110 RAD) + vm.prank(ali); clip.take({ + id: 1, + amt: 11 ether, // Half of tab at $110 + max: ray(5 ether), + who: address(ali), + data: "" + }); + + assertEq(vat.gem(ilk, ali), 11 ether); // Didn't take whole lot + assertEq(vat.dai(ali), rad(945 ether)); // Paid half tab (55) + assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) + + // Assert auction DOES NOT end + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, rad(55 ether)); // 110 - 5 * 11 + assertEq(sale.lot, 29 ether); // 40 - 11 + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(5 ether)); + + assertEq(dog.Dirt(), sale.tab); + (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, sale.tab); + } + + function testTakeFullLotPartialTab() public takeSetup { + vm.warp(block.timestamp + 69); // approx 50% price decline + // Bid to purchase entire lot less than tab (~2.5 * 40 ~= 100 < 110) + vm.prank(ali); clip.take({ + id: 1, + amt: 40 ether, // purchase all collateral + max: ray(2.5 ether), + who: address(ali), + data: "" + }); + + assertEq(vat.gem(ilk, ali), 40 ether); // Took entire lot + assertLt(vat.dai(ali) - rad(900 ether), rad(0.1 ether)); // Paid about 100 ether + assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned + + // Assert auction ends + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + + // All dirt should be cleared, since the auction has ended, even though < 100% of tab was collected + assertEq(dog.Dirt(), 0); + (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, 0); + } + + function testRevertsTakeBidTooLow() public takeSetup { + // Bid so max (= 4) < price (= top = 5) (fails with "Clipper/too-expensive") + vm.expectRevert("LockstakeClipper/too-expensive"); + vm.prank(ali); clip.take({ + id: 1, + amt: 22 ether, + max: ray(4 ether), + who: address(ali), + data: "" + }); + } + + function testTakeBidRecalculatesDueToChostCheck() public takeSetup { + (, uint256 tab, uint256 lot,,,,) = clip.sales(1); + assertEq(tab, rad(110 ether)); + assertEq(lot, 40 ether); + + (, uint256 _price, uint256 _lot, uint256 _tab) = clip.getStatus(1); + assertEq(_lot, lot); + assertEq(_tab, tab); + assertEq(_price, ray(5 ether)); + + // Bid for an amount that would leave less than chost remaining tab--bid will be decreased + // to leave tab == chost post-execution. + vm.prank(ali); clip.take({ + id: 1, + amt: 18 * WAD, // Costs 90 DAI at current price; 110 - 90 == 20 < 22 == chost + max: ray(5 ether), + who: address(ali), + data: "" + }); + + (, tab, lot,,,,) = clip.sales(1); + assertEq(tab, clip.chost()); + assertEq(lot, 40 ether - (110 * RAD - clip.chost()) / _price); + } + + function testTakeBidAvoidsRecalculateDueNoMoreLot() public takeSetup { + vm.warp(block.timestamp + 60); // Reducing the price + + (, uint256 tab, uint256 lot,,,,) = clip.sales(1); + assertEq(tab, rad(110 ether)); + assertEq(lot, 40 ether); + + (, uint256 _price,,) = clip.getStatus(1); + assertEq(_price, 2735783211953807380973706855); // 2.73 RAY + + // Bid so owe (= (22 - 1wei) * 5 = 110 RAD - 1) < tab (= 110 RAD) + // 1 < 20 RAD => owe = 110 RAD - 20 RAD + vm.prank(ali); clip.take({ + id: 1, + amt: 40 ether, + max: ray(2.8 ether), + who: address(ali), + data: "" + }); + + // 40 * 2.73 = 109.42... + // It means a very low amount of tab (< dust) would remain but doesn't matter + // as the auction is finished because there isn't more lot + (, tab, lot,,,,) = clip.sales(1); + assertEq(tab, 0); + assertEq(lot, 0); + } + + function testTakeBidFailsNoPartialAllowed() public takeSetup { + (, uint256 _price,,) = clip.getStatus(1); + assertEq(_price, ray(5 ether)); + + clip.take({ + id: 1, + amt: 17.6 ether, + max: ray(5 ether), + who: address(this), + data: "" + }); + + (, uint256 tab, uint256 lot,,,,) = clip.sales(1); + assertEq(tab, rad(22 ether)); + assertEq(lot, 22.4 ether); + assertTrue(!(tab > clip.chost())); + + vm.expectRevert("LockstakeClipper/no-partial-purchase"); + clip.take({ + id: 1, + amt: 1 ether, // partial purchase attempt when !(tab > chost) + max: ray(5 ether), + who: address(this), + data: "" + }); + + clip.take({ + id: 1, + amt: tab / _price, // This time take the whole tab + max: ray(5 ether), + who: address(this), + data: "" + }); + } + + function testTakeMultipleBidsDifferentPrices() public takeSetup { + // Bid so owe (= 10 * 5 = 50 RAD) < tab (= 110 RAD) + vm.prank(ali); clip.take({ + id: 1, + amt: 10 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + + assertEq(vat.gem(ilk, ali), 10 ether); // Didn't take whole lot + assertEq(vat.dai(ali), rad(950 ether)); // Paid some tab (50) + assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) + + // Assert auction DOES NOT end + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, rad(60 ether)); // 110 - 5 * 10 + assertEq(sale.lot, 30 ether); // 40 - 10 + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(5 ether)); + + vm.warp(block.timestamp + 30); + + (, uint256 _price, uint256 _lot,) = clip.getStatus(1); + vm.prank(bob); clip.take({ + id: 1, + amt: _lot, // Buy the rest of the lot + max: ray(_price), // 5 * 0.99 ** 30 = 3.698501866941401 RAY => max > price + who: address(bob), + data: "" + }); + + // Assert auction is over + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + + uint256 expectedGem = (RAY * 60 ether) / _price; // tab / price + assertEq(vat.gem(ilk, bob), expectedGem); // Didn't take whole lot + assertEq(vat.dai(bob), rad(940 ether)); // Paid rest of tab (60) + + uint256 lotReturn = 30 ether - expectedGem; // lot - loaf.tab / max = 15 + assertEq(vat.gem(ilk, address(this)), 960 ether + lotReturn); // Collateral returned (10 WAD) + } + + function _auctionResetSetup(uint256 tau) internal { + address calc = CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newLinearDecrease(address(this)); + CalcLike(calc).file("tau", tau); // tau hours till zero is reached (used to test tail) + + vm.prank(pauseProxy); vat.file(ilk, "dust", rad(20 ether)); // $20 dust + + clip.file("buf", ray(1.25 ether)); // 25% Initial price buffer + clip.file("calc", address(calc)); // File price contract + clip.file("cusp", ray(0.5 ether)); // 50% drop before reset + clip.file("tail", 3600); // 1 hour before reset + + assertEq(clip.kicks(), 0); + dog.bark(ilk, address(this), address(this)); + assertEq(clip.kicks(), 1); + } + + function testAuctionResetTail() public { + _auctionResetSetup(10 hours); // 10 hours till zero is reached (used to test tail) + + pip.setPrice(3 ether); // Spot = $1.50 (update price before reset is called) + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.tic, startTime); + assertEq(sale.top, ray(5 ether)); // $4 spot + 25% buffer = $5 (wasn't affected by poke) + + vm.warp(startTime + 3600 seconds); + (bool needsRedo,,,) = clip.getStatus(1); + assertTrue(!needsRedo); + vm.expectRevert("LockstakeClipper/cannot-reset"); + clip.redo(1, address(this)); + vm.warp(startTime + 3601 seconds); + (needsRedo,,,) = clip.getStatus(1); + assertTrue(needsRedo); + clip.redo(1, address(this)); + + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.tic, startTime + 3601 seconds); // (block.timestamp) + assertEq(sale.top, ray(3.75 ether)); // $3 spot + 25% buffer = $5 (used most recent OSM price) + } + + function testAuctionResetCusp() public { + _auctionResetSetup(1 hours); // 1 hour till zero is reached (used to test cusp) + + pip.setPrice(3 ether); // Spot = $1.50 (update price before reset is called) + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.tic, startTime); + assertEq(sale.top, ray(5 ether)); // $4 spot + 25% buffer = $5 (wasn't affected by poke) + + vm.warp(startTime + 1800 seconds); + (bool needsRedo,,,) = clip.getStatus(1); + assertTrue(!needsRedo); + vm.expectRevert("LockstakeClipper/cannot-reset"); + clip.redo(1, address(this)); + vm.warp(startTime + 1801 seconds); + (needsRedo,,,) = clip.getStatus(1); + assertTrue(needsRedo); + clip.redo(1, address(this)); + + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.tic, startTime + 1801 seconds); // (block.timestamp) + assertEq(sale.top, ray(3.75 ether)); // $3 spot + 25% buffer = $3.75 (used most recent OSM price) + } + + function testAuctionResetTailTwice() public { + _auctionResetSetup(10 hours); // 10 hours till zero is reached (used to test tail) + + vm.warp(startTime + 3601 seconds); + clip.redo(1, address(this)); + + vm.expectRevert("LockstakeClipper/cannot-reset"); + clip.redo(1, address(this)); + } + + function testAuctionResetCuspTwice() public { + _auctionResetSetup(1 hours); // 1 hour till zero is reached (used to test cusp) + + vm.warp(startTime + 1801 seconds); // Price goes below 50% "cusp" after 30min01sec + clip.redo(1, address(this)); + + vm.expectRevert("LockstakeClipper/cannot-reset"); + clip.redo(1, address(this)); + } + + function testRedoZeroUsr() public { + // Can't reset a non-existent auction. + vm.expectRevert("LockstakeClipper/not-running-auction"); + clip.redo(1, address(this)); + } + + function testSetBreaker() public { + clip.file("stopped", 1); + assertEq(clip.stopped(), 1); + clip.file("stopped", 2); + assertEq(clip.stopped(), 2); + clip.file("stopped", 3); + assertEq(clip.stopped(), 3); + clip.file("stopped", 0); + assertEq(clip.stopped(), 0); + } + + function testStoppedKick() public { + assertEq(clip.kicks(), 0); + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); + + // Any level of stoppage prevents kicking. + clip.file("stopped", 1); + vm.expectRevert("LockstakeClipper/stopped-incorrect"); + dog.bark(ilk, address(this), address(this)); + + clip.file("stopped", 2); + vm.expectRevert("LockstakeClipper/stopped-incorrect"); + dog.bark(ilk, address(this), address(this)); + + clip.file("stopped", 3); + vm.expectRevert("LockstakeClipper/stopped-incorrect"); + dog.bark(ilk, address(this), address(this)); + + clip.file("stopped", 0); + dog.bark(ilk, address(this), address(this)); + } + + // At a stopped == 1 we are ok to take + function testStopped1Take() public takeSetup { + clip.file("stopped", 1); + // Bid so owe (= 25 * 5 = 125 RAD) > tab (= 110 RAD) + // Readjusts slice to be tab/top = 25 + vm.prank(ali); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + } + + function testStopped2Take() public takeSetup { + clip.file("stopped", 2); + // Bid so owe (= 25 * 5 = 125 RAD) > tab (= 110 RAD) + // Readjusts slice to be tab/top = 25 + vm.prank(ali); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + } + + function testFailStopped3Take() public takeSetup { + clip.file("stopped", 3); + // Bid so owe (= 25 * 5 = 125 RAD) > tab (= 110 RAD) + // Readjusts slice to be tab/top = 25 + vm.prank(ali); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + } + + function testStopped1AuctionResetTail() public { + _auctionResetSetup(10 hours); // 10 hours till zero is reached (used to test tail) + + clip.file("stopped", 1); + + pip.setPrice(3 ether); // Spot = $1.50 (update price before reset is called) + + (,,,,, uint96 ticBefore, uint256 topBefore) = clip.sales(1); + assertEq(uint256(ticBefore), startTime); + assertEq(topBefore, ray(5 ether)); // $4 spot + 25% buffer = $5 (wasn't affected by poke) + + vm.warp(startTime + 3600 seconds); + vm.expectRevert("LockstakeClipper/cannot-reset"); + clip.redo(1, address(this)); + vm.warp(startTime + 3601 seconds); + clip.redo(1, address(this)); + + (,,,,, uint96 ticAfter, uint256 topAfter) = clip.sales(1); + assertEq(uint256(ticAfter), startTime + 3601 seconds); // (block.timestamp) + assertEq(topAfter, ray(3.75 ether)); // $3 spot + 25% buffer = $5 (used most recent OSM price) + } + + function testStopped2AuctionResetTail() public { + _auctionResetSetup(10 hours); // 10 hours till zero is reached (used to test tail) + + clip.file("stopped", 2); + + pip.setPrice(3 ether); // Spot = $1.50 (update price before reset is called) + + (,,,,, uint96 ticBefore, uint256 topBefore) = clip.sales(1); + assertEq(uint256(ticBefore), startTime); + assertEq(topBefore, ray(5 ether)); // $4 spot + 25% buffer = $5 (wasn't affected by poke) + + vm.warp(startTime + 3601 seconds); + (bool needsRedo,,,) = clip.getStatus(1); + assertTrue(needsRedo); // Redo possible if circuit breaker not set + vm.expectRevert("LockstakeClipper/stopped-incorrect"); + clip.redo(1, address(this)); // Redo fails because of circuit breaker + } + + function testStopped3AuctionResetTail() public { + _auctionResetSetup(10 hours); // 10 hours till zero is reached (used to test tail) + + clip.file("stopped", 3); + + pip.setPrice(3 ether); // Spot = $1.50 (update price before reset is called) + + (,,,,, uint96 ticBefore, uint256 topBefore) = clip.sales(1); + assertEq(uint256(ticBefore), startTime); + assertEq(topBefore, ray(5 ether)); // $4 spot + 25% buffer = $5 (wasn't affected by poke) + + vm.warp(startTime + 3601 seconds); + (bool needsRedo,,,) = clip.getStatus(1); + assertTrue(needsRedo); // Redo possible if circuit breaker not set + vm.expectRevert("LockstakeClipper/stopped-incorrect"); + clip.redo(1, address(this)); // Redo fails because of circuit breaker + } + + function testRedoIncentive() public takeSetup { + clip.file("tip", rad(100 ether)); // Flat fee of 100 DAI + clip.file("chip", 0); // No linear increase + + (, uint256 tab, uint256 lot,,,,) = clip.sales(1); + + assertEq(tab, rad(110 ether)); + assertEq(lot, 40 ether); + + vm.warp(block.timestamp + 300); + clip.redo(1, address(123)); + assertEq(vat.dai(address(123)), clip.tip()); + + clip.file("chip", 0.02 ether); // Reward 2% of tab + vm.warp(block.timestamp + 300); + clip.redo(1, address(234)); + assertEq(vat.dai(address(234)), clip.tip() + clip.chip() * tab / WAD); + + clip.file("tip", 0); // No more flat fee + vm.warp(block.timestamp + 300); + clip.redo(1, address(345)); + assertEq(vat.dai(address(345)), clip.chip() * tab / WAD); + + vm.prank(pauseProxy); vat.file(ilk, "dust", rad(100 ether) + 1); // ensure wmul(dust, chop) > 110 DAI (tab) + clip.upchost(); + assertEq(clip.chost(), 110 * RAD + 1); + + vm.warp(block.timestamp + 300); + clip.redo(1, address(456)); + assertEq(vat.dai(address(456)), 0); + + // Set dust so that wmul(dust, chop) is well below tab to check the dusty lot case. + vm.prank(pauseProxy); vat.file(ilk, "dust", rad(20 ether)); // $20 dust + clip.upchost(); + assertEq(clip.chost(), 22 * RAD); + + vm.warp(block.timestamp + 100); // Reducing the price + + (, uint256 _price,,) = clip.getStatus(1); + assertEq(_price, 1830161706366147524653080130); // 1.83 RAY + + clip.take({ + id: 1, + amt: 38 ether, + max: ray(5 ether), + who: address(this), + data: "" + }); + + (, tab, lot,,,,) = clip.sales(1); + + assertEq(tab, rad(110 ether) - 38 ether * _price); // > 22 DAI chost + // When auction is reset the current price of lot + // is calculated from oracle price ($4) to see if dusty + assertEq(lot, 2 ether); // (2 * $4) < $20 quivalent (dusty collateral) + + vm.warp(block.timestamp + 300); + clip.redo(1, address(567)); + assertEq(vat.dai(address(567)), 0); + } + + function testIncentiveMaxValues() public { + clip.file("chip", 2 ** 64 - 1); + clip.file("tip", 2 ** 192 - 1); + + assertEq(uint256(clip.chip()), uint256(18.446744073709551615 * 10 ** 18)); + assertEq(uint256(clip.tip()), uint256(6277101735386.680763835789423207666416102355444464034512895 * 10 ** 45)); + + clip.file("chip", 2 ** 64); + clip.file("tip", 2 ** 192); + + assertEq(uint256(clip.chip()), 0); + assertEq(uint256(clip.tip()), 0); + } + + function testClipperYank() public takeSetup { + (,, uint256 lot,, address usr,,) = clip.sales(1); + uint256 prevUsrGemBalance = vat.gem(ilk, address(usr)); + uint256 prevClipperGemBalance = vat.gem(ilk, address(clip)); + + uint startGas = gasleft(); + clip.yank(1); + uint endGas = gasleft(); + emit log_named_uint("yank gas", startGas - endGas); + + // Assert that the auction was deleted. + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + + // Assert that callback to clear dirt was successful. + assertEq(dog.Dirt(), 0); + (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dirt, 0); + + // Collateral is destroyed + assertEq(vat.gem(ilk, address(usr)), prevUsrGemBalance); + assertEq(vat.gem(ilk, address(clip)), prevClipperGemBalance - lot); + } + + function testRemoveId() public { + LockstakeEngineMock engine = new LockstakeEngineMock(address(vat), "random"); + PublicClip pclip = new PublicClip(address(vat), address(spot), address(dog), address(engine)); + uint256 pos; + + pclip.add(); + pclip.add(); + uint256 id = pclip.add(); + pclip.add(); + pclip.add(); + + // [1,2,3,4,5] + assertEq(pclip.count(), 5); // 5 elements added + assertEq(pclip.active(0), 1); + assertEq(pclip.active(1), 2); + assertEq(pclip.active(2), 3); + assertEq(pclip.active(3), 4); + assertEq(pclip.active(4), 5); + + pclip.remove(id); + + // [1,2,5,4] + assertEq(pclip.count(), 4); + assertEq(pclip.active(0), 1); + assertEq(pclip.active(1), 2); + assertEq(pclip.active(2), 5); // Swapped last for middle + (pos,,,,,,) = pclip.sales(5); + assertEq(pos, 2); + assertEq(pclip.active(3), 4); + + pclip.remove(4); + + // [1,2,5] + assertEq(pclip.count(), 3); + + (pos,,,,,,) = pclip.sales(1); + assertEq(pos, 0); // Sale 1 in slot 0 + assertEq(pclip.active(0), 1); + + (pos,,,,,,) = pclip.sales(2); + assertEq(pos, 1); // Sale 2 in slot 1 + assertEq(pclip.active(1), 2); + + (pos,,,,,,) = pclip.sales(5); + assertEq(pos, 2); // Sale 5 in slot 2 + assertEq(pclip.active(2), 5); // Final element removed + + (pos,,,,,,) = pclip.sales(4); + assertEq(pos, 0); // Sale 4 was deleted. Returns 0 + + vm.expectRevert(); + pclip.active(9); // Fail because id is out of range + } + + // function testRevertsNotEnoughDai() public takeSetup { + // vm.expectRevert(); + // vm.prank(che); clip.take({ + // id: 1, + // amt: 25 ether, + // max: ray(5 ether), + // who: address(che), + // data: "" + // }); + // } + + // function testFlashsale() public takeSetup { + // address che = address(new Trader(clip, vat, gold, goldJoin, dai, daiJoin, exchange)); + // assertEq(vat.dai(che), 0); + // assertEq(dai.balanceOf(che), 0); + // vm.prank(che); clip.take({ + // id: 1, + // amt: 25 ether, + // max: ray(5 ether), + // who: address(che), + // data: "hey" + // }); + // assertEq(vat.dai(che), 0); + // assertTrue(dai.balanceOf(che) > 0); // Che turned a profit + // } + + function testRevertsReentrancyTake() public takeSetup { + BadGuy usr = new BadGuy(clip); + vm.prank(address(usr)); vat.hope(address(clip)); + vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + + vm.expectRevert("LockstakeClipper/system-locked"); + vm.prank(address(usr)); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(usr), + data: "hey" + }); + } + + function testRevertsReentrancyRedo() public takeSetup { + RedoGuy usr = new RedoGuy(clip); + vm.prank(address(usr)); vat.hope(address(clip)); + vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + + vm.expectRevert("LockstakeClipper/system-locked"); + vm.prank(address(usr)); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(usr), + data: "hey" + }); + } + + function testRevertsReentrancyKick() public takeSetup { + KickGuy usr = new KickGuy(clip); + vm.prank(address(usr)); vat.hope(address(clip)); + vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + clip.rely(address(usr)); + + vm.expectRevert("LockstakeClipper/system-locked"); + vm.prank(address(usr)); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(usr), + data: "hey" + }); + } + + function testRevertsReentrancyFileUint() public takeSetup { + FileUintGuy usr = new FileUintGuy(clip); + vm.prank(address(usr)); vat.hope(address(clip)); + vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + clip.rely(address(usr)); + + vm.expectRevert("LockstakeClipper/system-locked"); + vm.prank(address(usr)); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(usr), + data: "hey" + }); + } + + function testRevertsReentrancyFileAddr() public takeSetup { + FileAddrGuy usr = new FileAddrGuy(clip); + vm.prank(address(usr)); vat.hope(address(clip)); + vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + clip.rely(address(usr)); + + vm.expectRevert("LockstakeClipper/system-locked"); + vm.prank(address(usr)); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(usr), + data: "hey" + }); + } + + function testRevertsReentrancyYank() public takeSetup { + YankGuy usr = new YankGuy(clip); + vm.prank(address(usr)); vat.hope(address(clip)); + vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + clip.rely(address(usr)); + + vm.expectRevert("LockstakeClipper/system-locked"); + vm.prank(address(usr)); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(usr), + data: "hey" + }); + } + + // function testRevertsTakeImpersonation() public takeSetup { // should fail, but works + // vm.expectRevert(); + // vm.prank(address(bob)); clip.take({ + // id: 1, + // amt: 99999999999999 ether, + // max: ray(99999999999999 ether), + // who: address(ali), + // data: "" + // }); + // } + + function testGasBarkKick() public { + // Assertions to make sure setup is as expected. + assertEq(clip.kicks(), 0); + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + assertEq(vat.gem(ilk, address(this)), 960 ether); + assertEq(vat.dai(ali), rad(1000 ether)); + (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(ink, 40 ether); + assertEq(art, 100 ether); + + uint256 preGas = gasleft(); + vm.prank(ali); dog.bark(ilk, address(this), address(ali)); + uint256 diffGas = preGas - gasleft(); + emit log_named_uint("bark with kick gas", diffGas); + } + + function testGasPartialTake() public takeSetup { + uint256 preGas = gasleft(); + // Bid so owe (= 11 * 5 = 55 RAD) < tab (= 110 RAD) + vm.prank(ali); clip.take({ + id: 1, + amt: 11 ether, // Half of tab at $110 + max: ray(5 ether), + who: address(ali), + data: "" + }); + uint256 diffGas = preGas - gasleft(); + emit log_named_uint("partial take gas", diffGas); + + assertEq(vat.gem(ilk, ali), 11 ether); // Didn't take whole lot + assertEq(vat.dai(ali), rad(945 ether)); // Paid half tab (55) + assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) + + // Assert auction DOES NOT end + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, rad(55 ether)); // 110 - 5 * 11 + assertEq(sale.lot, 29 ether); // 40 - 11 + assertEq(sale.tot, 40 ether); + assertEq(sale.usr, address(this)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, ray(5 ether)); + } + + function testGasFullTake() public takeSetup { + uint256 preGas = gasleft(); + // Bid so owe (= 25 * 5 = 125 RAD) > tab (= 110 RAD) + // Readjusts slice to be tab/top = 25 + vm.prank(ali); clip.take({ + id: 1, + amt: 25 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + uint256 diffGas = preGas - gasleft(); + emit log_named_uint("full take gas", diffGas); + + assertEq(vat.gem(ilk, ali), 22 ether); // Didn't take whole lot + assertEq(vat.dai(ali), rad(890 ether)); // Didn't pay more than tab (110) + assertEq(vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr + + // Assert auction ends + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + } +} diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol new file mode 100644 index 00000000..e37335b0 --- /dev/null +++ b/test/LockstakeEngine.t.sol @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +import "dss-test/DssTest.sol"; +import { LockstakeEngine } from "src/LockstakeEngine.sol"; +import { LockstakeClipper } from "src/LockstakeClipper.sol"; +import { PipMock } from "test/mocks/PipMock.sol"; +import { DelegateFactoryMock, DelegateMock } from "test/mocks/DelegateMock.sol"; +import { GemMock } from "test/mocks/GemMock.sol"; +import { NstMock } from "test/mocks/NstMock.sol"; +import { NstJoinMock } from "test/mocks/NstJoinMock.sol"; +import { StakingRewardsMock } from "test/mocks/StakingRewardsMock.sol"; + +interface ChainlogLike { + function getAddress(bytes32) external view returns (address); +} + +interface VatLike { + function dai(address) external view returns (uint256); + function gem(bytes32, address) external view returns (uint256); + function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); + function urns(bytes32, address) external view returns (uint256, uint256); + function rely(address) external; + function file(bytes32, bytes32, uint256) external; + function init(bytes32) external; + function hope(address) external; + function suck(address, address, uint256) external; +} + +interface SpotterLike { + function file(bytes32, bytes32, address) external; + function file(bytes32, bytes32, uint256) external; + function poke(bytes32) external; +} + +interface JugLike { + function file(bytes32, bytes32, uint256) external; + function init(bytes32) external; +} + +interface DogLike { + function rely(address) external; + function file(bytes32, bytes32, address) external; + function file(bytes32, bytes32, uint256) external; + function bark(bytes32, address, address) external returns (uint256); +} + +interface CalcFabLike { + function newLinearDecrease(address) external returns (address); +} + +interface CalcLike { + function file(bytes32, uint256) external; +} + +contract AllocatorVaultTest is DssTest { + using stdStorage for StdStorage; + + address public pauseProxy; + address public vat; + address public spot; + address public dog; + GemMock public ngt; + address public jug; + LockstakeEngine public engine; + LockstakeClipper public clip; + PipMock public pip; + DelegateFactoryMock public delFactory; + NstMock public nst; + NstJoinMock public nstJoin; + GemMock public stkNgt; + GemMock public rTok; + StakingRewardsMock public farm; + bytes32 public ilk = "LSE"; + address public voter; + address public voterDelegate; + + address constant LOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; + + event AddFarm(address farm); + event DelFarm(address farm); + event Open(address indexed owner, address indexed delegate, address urn); + event Lock(address indexed urn, uint256 wad); + event Free(address indexed urn, uint256 wad, uint256 burn); + event Move(address indexed urn, address indexed delegate); + event Draw(address indexed urn, uint256 wad); + event Wipe(address indexed urn, uint256 wad); + event SelectFarm(address indexed urn, address farm); + event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); + event Withdraw(address indexed urn, address indexed farm, uint256 amt); + event GetReward(address indexed urn, address indexed farm); + event OnKick(address indexed urn, uint256 wad); + event OnTake(address indexed urn, address indexed who, uint256 wad); + event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn); + + function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { + unchecked { + z = x != 0 ? ((x - 1) / y) + 1 : 0; + } + } + + function setUp() public { + vm.createSelectFork(vm.envString("ETH_RPC_URL")); + + pauseProxy = ChainlogLike(LOG).getAddress("MCD_PAUSE_PROXY"); + vat = ChainlogLike(LOG).getAddress("MCD_VAT"); + spot = ChainlogLike(LOG).getAddress("MCD_SPOT"); + dog = ChainlogLike(LOG).getAddress("MCD_DOG"); + ngt = GemMock(ChainlogLike(LOG).getAddress("MCD_GOV")); + jug = ChainlogLike(LOG).getAddress("MCD_JUG"); + nst = new NstMock(); + nstJoin = new NstJoinMock(vat, address(nst)); + stkNgt = new GemMock(0); + rTok = new GemMock(0); + farm = new StakingRewardsMock(address(rTok), address(stkNgt)); + + pip = new PipMock(); + delFactory = new DelegateFactoryMock(address(ngt)); + voter = address(123); + vm.prank(voter); voterDelegate = delFactory.create(); + + vm.startPrank(pauseProxy); + engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(stkNgt), 15 * WAD / 100); + engine.file("jug", jug); + VatLike(vat).rely(address(engine)); + VatLike(vat).init(ilk); + JugLike(jug).init(ilk); + JugLike(jug).file(ilk, "duty", 1001 * 10**27 / 1000); + SpotterLike(spot).file(ilk, "pip", address(pip)); + SpotterLike(spot).file(ilk, "mat", 3 * 10**27); // 300% coll ratio + pip.setPrice(0.1 * 10**18); // 1 NGT = 0.1 USD + SpotterLike(spot).poke(ilk); + VatLike(vat).file(ilk, "line", 1_000_000 * 10**45); + vm.stopPrank(); + + deal(address(ngt), address(this), 100_000 * 10**18, true); + + // Add some existing DAI assigned to nstJoin to avoid a particular error + stdstore.target(address(vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); + } + + function _ink(bytes32 ilk_, address urn) internal view returns (uint256 ink) { + (ink,) = VatLike(vat).urns(ilk_, urn); + } + + function _art(bytes32 ilk_, address urn) internal view returns (uint256 art) { + (, art) = VatLike(vat).urns(ilk_, urn); + } + + function _rate(bytes32 ilk_) internal view returns (uint256 rate) { + (, rate,,,) = VatLike(vat).ilks(ilk_); + } + + function testAuth() public { + checkAuth(address(engine), "LockstakeEngine"); + } + + function testModifiers() public { + bytes4[] memory authedMethods = new bytes4[](6); + authedMethods[0] = engine.addFarm.selector; + authedMethods[1] = engine.delFarm.selector; + authedMethods[2] = engine.onKick.selector; + authedMethods[3] = engine.onTake.selector; + authedMethods[4] = engine.onTakeLeftovers.selector; + authedMethods[5] = engine.onYank.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); + vm.stopPrank(); + + bytes4[] memory urnOwnersMethods = new bytes4[](8); + urnOwnersMethods[0] = engine.lock.selector; + urnOwnersMethods[1] = engine.free.selector; + urnOwnersMethods[2] = engine.draw.selector; + urnOwnersMethods[3] = engine.wipe.selector; + urnOwnersMethods[4] = engine.selectFarm.selector; + urnOwnersMethods[5] = engine.stake.selector; + urnOwnersMethods[6] = engine.withdraw.selector; + urnOwnersMethods[7] = engine.getReward.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(engine), "LockstakeEngine/not-urn-owner", urnOwnersMethods); + vm.stopPrank(); + } + + function testFile() public { + checkFileAddress(address(engine), "LockstakeEngine", ["jug"]); + } + + function testAddDelFarm() public { + assertEq(engine.farms(address(1111)), 0); + vm.expectEmit(true, true, true, true); + emit AddFarm(address(1111)); + vm.prank(pauseProxy); engine.addFarm(address(1111)); + assertEq(engine.farms(address(1111)), 1); + vm.expectEmit(true, true, true, true); + emit DelFarm(address(1111)); + vm.prank(pauseProxy); engine.delFarm(address(1111)); + assertEq(engine.farms(address(1111)), 0); + } + + function testOpen() public { + assertEq(engine.urnsAmt(address(this)), 0); + address urn = engine.getUrn(address(this), 0); + vm.expectEmit(true, true, true, true); + emit Open(address(this), voterDelegate, urn); + assertEq(engine.open(voterDelegate), urn); + assertEq(engine.urnsAmt(address(this)), 1); + assertEq(engine.getUrn(address(this), 1), engine.open(voterDelegate)); + assertEq(engine.urnsAmt(address(this)), 2); + assertEq(engine.getUrn(address(this), 2), engine.open(voterDelegate)); + assertEq(engine.urnsAmt(address(this)), 3); + } + + function testLockFree() public { + uint256 initialSupply = ngt.totalSupply(); + assertEq(ngt.balanceOf(address(this)), 100_000 * 10**18); + address urn = engine.open(voterDelegate); + assertEq(_ink(ilk, urn), 0); + assertEq(stkNgt.balanceOf(urn), 0); + ngt.approve(address(engine), 100_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit Lock(urn, 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18); + assertEq(_ink(ilk, urn), 100_000 * 10**18); + assertEq(stkNgt.balanceOf(urn), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(this)), 0); + assertEq(ngt.totalSupply(), initialSupply); + vm.expectEmit(true, true, true, true); + emit Free(urn, 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); + engine.free(urn, 40_000 * 10**18); + assertEq(_ink(ilk, urn), 60_000 * 10**18); + assertEq(stkNgt.balanceOf(urn), 60_000 * 10**18); + assertEq(ngt.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); + assertEq(ngt.totalSupply(), initialSupply - 40_000 * 10**18 * 15 / 100); + } + + function testMove() public { + address urn = engine.open(voterDelegate); + vm.prank(address(888)); address voterDelegate2 = delFactory.create(); + ngt.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18); + assertEq(DelegateMock(voterDelegate).stake(address(engine)), 100_000 * 10**18); + assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); + assertEq(ngt.balanceOf(voterDelegate), 100_000 * 10**18); + assertEq(ngt.balanceOf(voterDelegate2), 0); + engine.move(urn, voterDelegate2); + assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); + assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 100_000 * 10**18); + assertEq(ngt.balanceOf(voterDelegate), 0); + assertEq(ngt.balanceOf(voterDelegate2), 100_000 * 10**18); + } + + function testDrawWipe() public { + deal(address(ngt), address(this), 100_000 * 10**18, true); + address urn = engine.open(voterDelegate); + ngt.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18); + assertEq(_art(ilk, urn), 0); + vm.expectEmit(true, true, true, true); + emit Draw(urn, 50 * 10**18); + engine.draw(urn, 50 * 10**18); + assertEq(_art(ilk, urn), 50 * 10**18); + assertEq(_rate(ilk), 10**27); + assertEq(nst.balanceOf(address(this)), 50 * 10**18); + vm.warp(block.timestamp + 1); + vm.expectEmit(true, true, true, true); + emit Draw(urn, 50 * 10**18); + engine.draw(urn, 50 * 10**18); + uint256 art = _art(ilk, urn); + uint256 expectedArt = 50 * 10**18 + _divup(50 * 10**18 * 1000, 1001); + assertEq(art, expectedArt); + uint256 rate = _rate(ilk); + assertEq(rate, 1001 * 10**27 / 1000); + assertEq(nst.balanceOf(address(this)), 100 * 10**18); + assertGt(art * rate, 100.05 * 10**45); + assertLt(art * rate, 100.06 * 10**45); + vm.expectRevert("Nst/insufficient-balance"); + engine.wipe(urn, 100.06 * 10**18); + deal(address(nst), address(this), 100.06 * 10**18, true); + assertEq(nst.balanceOf(address(this)), 100.06 * 10**18); + nst.approve(address(engine), 100.06 * 10**18); + vm.expectRevert(); + engine.wipe(urn, 100.06 * 10**18); // It will try to wipe more art than existing, then reverts + vm.expectEmit(true, true, true, true); + emit Wipe(urn, 100.05 * 10**18); + engine.wipe(urn, 100.05 * 10**18); + assertEq(nst.balanceOf(address(this)), 0.01 * 10**18); + assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe + } + + function testSelectFarm() public { + StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkNgt)); + address urn = engine.open(voterDelegate); + assertEq(engine.selectedFarm(urn), address(0)); + vm.expectRevert("LockstakeEngine/non-existing-farm"); + engine.selectFarm(urn, address(farm)); + vm.prank(pauseProxy); engine.addFarm(address(farm)); + vm.expectEmit(true, true, true, true); + emit SelectFarm(urn, address(farm)); + engine.selectFarm(urn, address(farm)); + assertEq(engine.selectedFarm(urn), address(farm)); + vm.prank(pauseProxy); engine.addFarm(address(farm2)); + engine.selectFarm(urn, address(farm2)); + assertEq(engine.selectedFarm(urn), address(farm2)); + ngt.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18); + engine.stake(urn, 100_000, 1); + vm.expectRevert("LockstakeEngine/withdraw-first"); + engine.selectFarm(urn, address(farm)); + engine.withdraw(urn, 100_000); + engine.selectFarm(urn, address(farm)); + } + + function testStakeWithdraw() public { + vm.prank(pauseProxy); engine.addFarm(address(farm)); + address urn = engine.open(voterDelegate); + ngt.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18); + vm.expectRevert("LockstakeEngine/missing-selected-farm"); + engine.stake(urn, 100_000, 1); + vm.expectRevert("LockstakeEngine/missing-selected-farm"); + engine.withdraw(urn, 0); + engine.selectFarm(urn, address(farm)); + assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + vm.expectEmit(true, true, true, true); + emit Stake(urn, address(farm), 60_000 * 10**18, 1); + engine.stake(urn, 60_000 * 10**18, 1); + assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(farm.balanceOf(address(urn)), 60_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit Withdraw(urn, address(farm), 15_000 * 10**18); + engine.withdraw(urn, 15_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 55_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 45_000 * 10**18); + assertEq(farm.balanceOf(address(urn)), 45_000 * 10**18); + } + + function testGetReward() public { + vm.prank(pauseProxy); engine.addFarm(address(farm)); + address urn = engine.open(voterDelegate); + farm.setReward(address(urn), 20_000); + assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 0); + vm.expectEmit(true, true, true, true); + emit GetReward(urn, address(farm)); + engine.getReward(urn, address(farm)); + assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 20_000); + } + + function _clipperSetUp() internal returns (address urn) { + vm.startPrank(pauseProxy); + engine.addFarm(address(farm)); + clip = new LockstakeClipper(vat, spot, dog, address(engine)); + clip.file("vow", ChainlogLike(LOG).getAddress("MCD_VOW")); + engine.rely(address(clip)); + clip.upchost(); + DogLike(dog).file(ilk, "clip", address(clip)); + clip.rely(address(dog)); + DogLike(dog).rely(address(clip)); + VatLike(vat).rely(address(clip)); + + CalcLike calc = CalcLike(CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newLinearDecrease(address(pauseProxy))); + calc.file("tau", 100); + clip.file("buf", 1.25 * 10**27); // 25% Initial price buffer + clip.file("calc", address(calc)); // File price contract + clip.file("cusp", 0.2 * 10**27); // 80% drop before reset + clip.file("tail", 3600); // 1 hour before reset + DogLike(dog).file(ilk, "chop", 1 ether); // 0% chop + DogLike(dog).file(ilk, "hole", 10_000 * 10**45); + vm.stopPrank(); + + urn = engine.open(voterDelegate); + ngt.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18); + engine.draw(urn, 2_000 * 10**18); + assertEq(_ink(ilk, urn), 100_000 * 10**18); + assertEq(_art(ilk, urn), 2_000 * 10**18); + } + + function _forceLiquidation(address urn) internal returns (uint256 id) { + pip.setPrice(0.05 * 10**18); // Force liquidation + SpotterLike(spot).poke(ilk); + assertEq(clip.kicks(), 0); + id = DogLike(dog).bark(ilk, address(urn), address(this)); + assertEq(clip.kicks(), 1); + } + + function testOnKickFullNoStaked() public { + address urn = _clipperSetUp(); + + assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 2_000 * 10**45); + assertEq(sale.lot, 100_000 * 10**18); + assertEq(sale.tot, 100_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + } + + function testOnKickPartialNoStaked() public { + address urn = _clipperSetUp(); + + vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 500 * 10**45); + assertEq(sale.lot, 25_000 * 10**18); + assertEq(sale.tot, 25_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 75_000 * 10**18); + assertEq(_art(ilk, urn), 1_500 * 10**18); + assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 75_000 * 10**18); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 25_000 * 10**18); + } + + function testOnKickFullStakedPartial() public { + address urn = _clipperSetUp(); + + engine.selectFarm(urn, address(farm)); + engine.stake(urn, 60_000 * 10**18, 1); + assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 2_000 * 10**45); + assertEq(sale.lot, 100_000 * 10**18); + assertEq(sale.tot, 100_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.balanceOf(address(farm)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + } + + function testOnKickPartialStakedPartialNoWithdraw() public { + address urn = _clipperSetUp(); + + engine.selectFarm(urn, address(farm)); + engine.stake(urn, 60_000 * 10**18, 1); + assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + + vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 500 * 10**45); + assertEq(sale.lot, 25_000 * 10**18); + assertEq(sale.tot, 25_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 75_000 * 10**18); + assertEq(_art(ilk, urn), 1_500 * 10**18); + assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 15_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 25_000 * 10**18); + } + + function testOnKickPartialStakedPartialWithdraw() public { + address urn = _clipperSetUp(); + + engine.selectFarm(urn, address(farm)); + engine.stake(urn, 80_000 * 10**18, 1); + assertEq(stkNgt.balanceOf(address(urn)), 20_000 * 10**18); + assertEq(stkNgt.balanceOf(address(farm)), 80_000 * 10**18); + + vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 500 * 10**45); + assertEq(sale.lot, 25_000 * 10**18); + assertEq(sale.tot, 25_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 75_000 * 10**18); + assertEq(_art(ilk, urn), 1_500 * 10**18); + assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.balanceOf(address(farm)), 75_000 * 10**18); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 25_000 * 10**18); + } + + function testOnTake() public { + address urn = _clipperSetUp(); + + assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + + uint256 ngtInitialSupply = ngt.totalSupply(); + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); + uint256 vowInitialBalance = VatLike(vat).dai(vow); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 2_000 * 10**45); + assertEq(sale.lot, 100_000 * 10**18); + assertEq(sale.tot, 100_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + + address buyer = address(888); + vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); + vm.prank(buyer); VatLike(vat).hope(address(clip)); + assertEq(ngt.balanceOf(buyer), 0); + vm.prank(buyer); clip.take(id, 20_000 * 10**18, type(uint256).max, buyer, ""); + assertEq(ngt.balanceOf(buyer), 20_000 * 10**18); + + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, (2_000 - 20_000 * 0.05 * 1.25) * 10**45); + assertEq(sale.lot, 80_000 * 10**18); + assertEq(sale.tot, 100_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 80_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 80_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + + vm.prank(buyer); clip.take(id, 12_000 * 10**18, type(uint256).max, buyer, ""); + assertEq(ngt.balanceOf(buyer), 32_000 * 10**18); + + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + + assertEq(_ink(ilk, urn), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 0); + + assertEq(ngt.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(ngt.totalSupply(), ngtInitialSupply - 32_000 * 0.15 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 32_000 * 1.15 * 10**18); + assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); + } + + function testOnTakePartialBurn() public { + address urn = _clipperSetUp(); + + assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + + uint256 ngtInitialSupply = ngt.totalSupply(); + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); + uint256 vowInitialBalance = VatLike(vat).dai(vow); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 2_000 * 10**45); + assertEq(sale.lot, 100_000 * 10**18); + assertEq(sale.tot, 100_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + + vm.warp(block.timestamp + 65); // Time passes to let the auction price to crash + + address buyer = address(888); + vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); + vm.prank(buyer); VatLike(vat).hope(address(clip)); + assertEq(ngt.balanceOf(buyer), 0); + vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); + assertEq(ngt.balanceOf(buyer), 91428571428571428571428); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 0); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(ngt.totalSupply(), ngtInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); + } + + function testOnTakeNoBurn() public { + address urn = _clipperSetUp(); + + assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + + uint256 ngtInitialSupply = ngt.totalSupply(); + uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); + uint256 vowInitialBalance = VatLike(vat).dai(vow); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 2_000 * 10**45); + assertEq(sale.lot, 100_000 * 10**18); + assertEq(sale.tot, 100_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + + vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash + + address buyer = address(888); + vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); + vm.prank(buyer); VatLike(vat).hope(address(clip)); + assertEq(ngt.balanceOf(buyer), 0); + vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); + assertEq(ngt.balanceOf(buyer), 100_000 * 10**18); + + assertEq(_ink(ilk, urn), 0); + assertEq(_art(ilk, urn), 0); + assertEq(VatLike(vat).gem(ilk, address(clip)), 0); + + assertEq(ngt.balanceOf(address(voterDelegate)), 0); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(ngt.totalSupply(), ngtInitialSupply); // Can't burn anything + assertEq(stkNgt.balanceOf(address(urn)), 0); + assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertLt(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt + } + + function testOnYank() public { + address urn = _clipperSetUp(); + + assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(engine)), 0); + assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + + uint256 ngtInitialSupply = ngt.totalSupply(); + + uint256 id = _forceLiquidation(urn); + + LockstakeClipper.Sale memory sale; + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 2_000 * 10**45); + assertEq(sale.lot, 100_000 * 10**18); + assertEq(sale.tot, 100_000 * 10**18); + assertEq(sale.usr, address(urn)); + assertEq(sale.tic, block.timestamp); + assertEq(sale.top, pip.read() * (1.25 * 10**9)); + + vm.prank(pauseProxy); clip.yank(id); + + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); + assertEq(sale.pos, 0); + assertEq(sale.tab, 0); + assertEq(sale.lot, 0); + assertEq(sale.tot, 0); + assertEq(sale.usr, address(0)); + assertEq(sale.tic, 0); + assertEq(sale.top, 0); + + assertEq(ngt.totalSupply(), ngtInitialSupply - 100_000 * 10**18); + } +} diff --git a/test/mocks/DelegateMock.sol b/test/mocks/DelegateMock.sol new file mode 100644 index 00000000..2a995c6a --- /dev/null +++ b/test/mocks/DelegateMock.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +interface GemLike { + function transfer(address, uint256) external; + function transferFrom(address, address, uint256) external; +} + +contract DelegateFactoryMock { + mapping(address => uint256) public isDelegate; + address immutable public gov; + + constructor(address _gov) { + gov = _gov; + } + + function create() external returns (address delegate) { + delegate = address(new DelegateMock(gov)); + require(delegate != address(0), "DelegateFactory/creation-failed"); + isDelegate[delegate] = 1; + } +} + +contract DelegateMock { + mapping(address => uint256) public stake; + + GemLike immutable public gov; + + constructor(address gov_) { + gov = GemLike(gov_); + } + + // --- NGT owner functions + + function lock(uint256 wad) external { + gov.transferFrom(msg.sender, address(this), wad); + stake[msg.sender] = stake[msg.sender] + wad; + } + + function free(uint256 wad) external { + stake[msg.sender] -= wad; + gov.transfer(msg.sender, wad); + } +} diff --git a/test/mocks/GemMock.sol b/test/mocks/GemMock.sol new file mode 100644 index 00000000..789e88a4 --- /dev/null +++ b/test/mocks/GemMock.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +contract GemMock { + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + uint256 public totalSupply; + + constructor(uint256 initialSupply) { + mint(msg.sender, initialSupply); + } + + function approve(address spender, uint256 value) external returns (bool) { + allowance[msg.sender][spender] = value; + return true; + } + + function transfer(address to, uint256 value) external returns (bool) { + uint256 balance = balanceOf[msg.sender]; + require(balance >= value, "Gem/insufficient-balance"); + + unchecked { + balanceOf[msg.sender] = balance - value; + balanceOf[to] += value; + } + return true; + } + + function transferFrom(address from, address to, uint256 value) external returns (bool) { + uint256 balance = balanceOf[from]; + require(balance >= value, "Gem/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "Gem/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; + balanceOf[to] += value; + } + return true; + } + + function mint(address to, uint256 value) public { + unchecked { + balanceOf[to] = balanceOf[to] + value; + } + totalSupply = totalSupply + value; + } + + function burn(address from, uint256 value) external { + uint256 balance = balanceOf[from]; + require(balance >= value, "Gem/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "Gem/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; + totalSupply = totalSupply - value; + } + } +} diff --git a/test/mocks/LockstakeEngineMock.sol b/test/mocks/LockstakeEngineMock.sol new file mode 100644 index 00000000..6029954d --- /dev/null +++ b/test/mocks/LockstakeEngineMock.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +interface VatLike { + function slip(bytes32, address, int256) external; +} + +contract LockstakeEngineMock { + VatLike immutable public vat; + bytes32 immutable public ilk; + + constructor(address vat_, bytes32 ilk_) { + vat = VatLike(vat_); + ilk = ilk_; + } + + function onKick(address, uint256) external { + } + + function onTake(address, address who, uint256 wad) external { + VatLike(vat).slip(ilk, who, int256(wad)); + } + + function onTakeLeftovers(address urn, uint256, uint256 left) external { + VatLike(vat).slip(ilk, urn, int256(left)); + } + + function onYank(address, uint256) external { + } +} diff --git a/test/mocks/NstJoinMock.sol b/test/mocks/NstJoinMock.sol new file mode 100644 index 00000000..1452ced8 --- /dev/null +++ b/test/mocks/NstJoinMock.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +import { GemMock } from "test/mocks/GemMock.sol"; + +interface VatLike { + function move(address, address, uint256) external; +} + +contract NstJoinMock { + VatLike public vat; + GemMock public nst; + + constructor(address vat_, address nst_) { + vat = VatLike(vat_); + nst = GemMock(nst_); + } + + function join(address usr, uint256 wad) external { + vat.move(address(this), usr, wad * 10**27); + nst.burn(msg.sender, wad); + } + + function exit(address usr, uint256 wad) external { + vat.move(msg.sender, address(this), wad * 10**27); + nst.mint(usr, wad); + } +} diff --git a/test/mocks/NstMock.sol b/test/mocks/NstMock.sol new file mode 100644 index 00000000..ac6b3336 --- /dev/null +++ b/test/mocks/NstMock.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +contract NstMock { + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + uint256 public totalSupply; + + constructor() { + mint(msg.sender, 0); + } + + function approve(address spender, uint256 value) external returns (bool) { + allowance[msg.sender][spender] = value; + return true; + } + + function transfer(address to, uint256 value) external returns (bool) { + uint256 balance = balanceOf[msg.sender]; + require(balance >= value, "Nst/insufficient-balance"); + + unchecked { + balanceOf[msg.sender] = balance - value; + balanceOf[to] += value; + } + return true; + } + + function transferFrom(address from, address to, uint256 value) external returns (bool) { + uint256 balance = balanceOf[from]; + require(balance >= value, "Nst/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "Nst/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; + balanceOf[to] += value; + } + return true; + } + + function mint(address to, uint256 value) public { + unchecked { + balanceOf[to] = balanceOf[to] + value; + } + totalSupply = totalSupply + value; + } + + function burn(address from, uint256 value) external { + uint256 balance = balanceOf[from]; + require(balance >= value, "Nst/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "Nst/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; + totalSupply = totalSupply - value; + } + } +} diff --git a/test/mocks/PipMock.sol b/test/mocks/PipMock.sol new file mode 100644 index 00000000..47684e88 --- /dev/null +++ b/test/mocks/PipMock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +contract PipMock { + uint256 price; + + function setPrice(uint256 price_) external { + price = price_; + } + + function read() external view returns (uint256 price_) { + price_ = price; + } + + function peek() external view returns (uint256 price_, bool ok) { + ok = true; + price_ = price; + } +} diff --git a/test/mocks/StakingRewardsMock.sol b/test/mocks/StakingRewardsMock.sol new file mode 100644 index 00000000..fd87a79e --- /dev/null +++ b/test/mocks/StakingRewardsMock.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +interface GemLike { + function transfer(address, uint256) external; + function transferFrom(address, address, uint256) external; + function mint(address, uint256) external; +} + +contract StakingRewardsMock { + GemLike public immutable rewardsToken; + GemLike public immutable stakingToken; + + uint256 public totalSupply; + mapping(address => uint256) public balanceOf; + mapping(address => uint256) public rewards; + + constructor( + address _rewardsToken, + address _stakingToken + ) { + rewardsToken = GemLike(_rewardsToken); + stakingToken = GemLike(_stakingToken); + } + + function stake(uint256 amount, uint16) external { + require(amount > 0, "Cannot stake 0"); + totalSupply = totalSupply + amount; + balanceOf[msg.sender] = balanceOf[msg.sender] + amount; + stakingToken.transferFrom(msg.sender, address(this), amount); + } + + function withdraw(uint256 amount) external { + require(amount > 0, "Cannot withdraw 0"); + totalSupply = totalSupply - amount; + balanceOf[msg.sender] = balanceOf[msg.sender] - amount; + stakingToken.transfer(msg.sender, amount); + } + + function setReward(address usr, uint256 amount) public { + rewardsToken.mint(address(this), amount); + rewards[usr] += amount; + } + + function getReward() public { + uint256 reward = rewards[msg.sender]; + if (reward > 0) { + rewards[msg.sender] = 0; + rewardsToken.transfer(msg.sender, reward); + } + } +} From c2d9b4fd4d55e48e166122ae9df55def2a2312fc Mon Sep 17 00:00:00 2001 From: sunbreak Date: Wed, 22 Nov 2023 13:42:56 -0300 Subject: [PATCH 002/111] Rename ngt to more general gov wording --- src/LockstakeEngine.sol | 46 +++--- src/LockstakeUrn.sol | 10 +- test/LockstakeEngine.t.sol | 270 ++++++++++++++++++------------------ test/mocks/DelegateMock.sol | 2 +- 4 files changed, 164 insertions(+), 164 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 66e3fece..fdddf8f1 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -78,8 +78,8 @@ contract LockstakeEngine { NstJoinLike immutable public nstJoin; GemLike immutable public nst; bytes32 immutable public ilk; - GemLike immutable public ngt; - GemLike immutable public stkNgt; + GemLike immutable public gov; + GemLike immutable public stkGov; uint256 immutable public fee; // --- events --- @@ -118,14 +118,14 @@ contract LockstakeEngine { // --- constructor --- - constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkNgt_, uint256 fee_) { + constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkGov_, uint256 fee_) { delegateFactory = DelegateFactoryLike(delegateFactory_); nstJoin = NstJoinLike(nstJoin_); vat = nstJoin.vat(); nst = nstJoin.nst(); ilk = ilk_; - ngt = delegateFactory.gov(); - stkNgt = GemLike(stkNgt_); + gov = delegateFactory.gov(); + stkGov = GemLike(stkGov_); fee = fee_; nst.approve(nstJoin_, type(uint256).max); vat.hope(nstJoin_); @@ -177,7 +177,7 @@ contract LockstakeEngine { uint256 index ) external view returns (address urn) { uint256 salt = uint256(keccak256(abi.encode(owner, index))); - bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkNgt))); + bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov))); urn = address(uint160(uint256( keccak256( abi.encodePacked(bytes1(0xff), address(this), salt, codeHash) @@ -190,7 +190,7 @@ contract LockstakeEngine { function open(address delegate) external returns (address urn) { require(delegateFactory.isDelegate(delegate) == 1, "LockstateEngine/not-valid-delegate"); uint256 salt = uint256(keccak256(abi.encode(msg.sender, urnsAmt[msg.sender]++))); - bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkNgt)); + bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov)); assembly { urn := create2(0, add(code, 0x20), mload(code), salt) } @@ -202,15 +202,15 @@ contract LockstakeEngine { function lock(address urn, uint256 wad) external urnOwner(urn) { require(wad <= uint256(type(int256).max), "LockstateEngine/wad-overflow"); - ngt.transferFrom(msg.sender, address(this), wad); + gov.transferFrom(msg.sender, address(this), wad); address delegate = urnDelegates[urn]; - ngt.approve(address(delegate), wad); + gov.approve(address(delegate), wad); DelegateLike(delegate).lock(wad); // TODO: define if we want an internal registry to register how much is locked per user, - // the vat.slip and stkNgt balance act already as a registry so probably not needed an extra one + // the vat.slip and stkGov balance act already as a registry so probably not needed an extra one vat.slip(ilk, urn, int256(wad)); vat.frob(ilk, urn, urn, address(0), int256(wad), 0); - stkNgt.mint(urn, wad); + stkGov.mint(urn, wad); emit Lock(urn, wad); } @@ -218,12 +218,12 @@ contract LockstakeEngine { require(wad <= uint256(type(int256).max), "LockstateEngine/wad-overflow"); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); - stkNgt.burn(urn, wad); + stkGov.burn(urn, wad); address delegate = urnDelegates[urn]; DelegateLike(delegate).free(wad); uint256 burn = wad * fee / WAD; - ngt.burn(address(this), burn); - ngt.transfer(msg.sender, wad - burn); + gov.burn(address(this), burn); + gov.transfer(msg.sender, wad - burn); emit Free(urn, wad, burn); } @@ -233,7 +233,7 @@ contract LockstakeEngine { require(prevDelegate != delegate, "LockstateEngine/same-delegate"); (uint256 wad,) = vat.urns(ilk, urn); DelegateLike(prevDelegate).free(wad); - ngt.approve(address(delegate), wad); + gov.approve(address(delegate), wad); DelegateLike(delegate).lock(wad); urnDelegates[urn] = delegate; emit Move(urn, delegate); @@ -294,19 +294,19 @@ contract LockstakeEngine { function onKick(address urn, uint256 wad) external auth { address selectedFarmUrn = selectedFarm[urn]; if (selectedFarmUrn != address(0)){ - uint256 freed = GemLike(stkNgt).balanceOf(address(urn)); + uint256 freed = GemLike(stkGov).balanceOf(address(urn)); if (wad > freed) { LockstakeUrn(urn).withdraw(selectedFarmUrn, wad - freed); } } - stkNgt.burn(urn, wad); // Burn the whole liquidated amount of staking token - DelegateLike(urnDelegates[urn]).free(wad); // Undelegate liquidated amount and retain NGT + stkGov.burn(urn, wad); // Burn the whole liquidated amount of staking token + DelegateLike(urnDelegates[urn]).free(wad); // Undelegate liquidated amount and retain GOV // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper emit OnKick(urn, wad); } function onTake(address urn, address who, uint256 wad) external auth { - ngt.transfer(who, wad); // Free NGT to the auction buyer + gov.transfer(who, wad); // Free GOV to the auction buyer emit OnTake(urn, who, wad); } @@ -318,20 +318,20 @@ contract LockstakeEngine { } else { unchecked { left = left - burn; } } - ngt.burn(address(this), burn); // Burn NGT + gov.burn(address(this), burn); // Burn GOV if (left > 0) { address delegate = urnDelegates[urn]; - ngt.approve(address(delegate), left); + gov.approve(address(delegate), left); DelegateLike(delegate).lock(left); vat.slip(ilk, urn, int256(left)); vat.frob(ilk, urn, urn, address(0), int256(left), 0); - stkNgt.mint(urn, left); + stkGov.mint(urn, left); } emit OnTakeLeftovers(urn, tot, left, burn); } function onYank(address urn, uint256 wad) external auth { - ngt.burn(address(this), wad); + gov.burn(address(this), wad); emit OnYank(urn, wad); } } diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index b0e88916..b579eb15 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -37,7 +37,7 @@ contract LockstakeUrn { // --- immutables --- address immutable public engine; - GemLike immutable public stkNgt; + GemLike immutable public stkGov; // --- modifiers --- @@ -48,17 +48,17 @@ contract LockstakeUrn { // --- constructor --- - constructor(address vat_, address stkNgt_) { + constructor(address vat_, address stkGov_) { engine = msg.sender; - stkNgt = GemLike(stkNgt_); + stkGov = GemLike(stkGov_); VatLike(vat_).hope(msg.sender); - stkNgt.approve(msg.sender, type(uint256).max); + stkGov.approve(msg.sender, type(uint256).max); } // --- staking functions --- function stake(address farm, uint256 wad, uint16 ref) external isEngine { - stkNgt.approve(farm, wad); + stkGov.approve(farm, wad); StakingRewardsLike(farm).stake(wad, ref); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index e37335b0..33a41834 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -61,7 +61,7 @@ contract AllocatorVaultTest is DssTest { address public vat; address public spot; address public dog; - GemMock public ngt; + GemMock public gov; address public jug; LockstakeEngine public engine; LockstakeClipper public clip; @@ -69,7 +69,7 @@ contract AllocatorVaultTest is DssTest { DelegateFactoryMock public delFactory; NstMock public nst; NstJoinMock public nstJoin; - GemMock public stkNgt; + GemMock public stkGov; GemMock public rTok; StakingRewardsMock public farm; bytes32 public ilk = "LSE"; @@ -107,21 +107,21 @@ contract AllocatorVaultTest is DssTest { vat = ChainlogLike(LOG).getAddress("MCD_VAT"); spot = ChainlogLike(LOG).getAddress("MCD_SPOT"); dog = ChainlogLike(LOG).getAddress("MCD_DOG"); - ngt = GemMock(ChainlogLike(LOG).getAddress("MCD_GOV")); + gov = GemMock(ChainlogLike(LOG).getAddress("MCD_GOV")); jug = ChainlogLike(LOG).getAddress("MCD_JUG"); nst = new NstMock(); nstJoin = new NstJoinMock(vat, address(nst)); - stkNgt = new GemMock(0); + stkGov = new GemMock(0); rTok = new GemMock(0); - farm = new StakingRewardsMock(address(rTok), address(stkNgt)); + farm = new StakingRewardsMock(address(rTok), address(stkGov)); pip = new PipMock(); - delFactory = new DelegateFactoryMock(address(ngt)); + delFactory = new DelegateFactoryMock(address(gov)); voter = address(123); vm.prank(voter); voterDelegate = delFactory.create(); vm.startPrank(pauseProxy); - engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(stkNgt), 15 * WAD / 100); + engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(stkGov), 15 * WAD / 100); engine.file("jug", jug); VatLike(vat).rely(address(engine)); VatLike(vat).init(ilk); @@ -129,12 +129,12 @@ contract AllocatorVaultTest is DssTest { JugLike(jug).file(ilk, "duty", 1001 * 10**27 / 1000); SpotterLike(spot).file(ilk, "pip", address(pip)); SpotterLike(spot).file(ilk, "mat", 3 * 10**27); // 300% coll ratio - pip.setPrice(0.1 * 10**18); // 1 NGT = 0.1 USD + pip.setPrice(0.1 * 10**18); // 1 GOV = 0.1 USD SpotterLike(spot).poke(ilk); VatLike(vat).file(ilk, "line", 1_000_000 * 10**45); vm.stopPrank(); - deal(address(ngt), address(this), 100_000 * 10**18, true); + deal(address(gov), address(this), 100_000 * 10**18, true); // Add some existing DAI assigned to nstJoin to avoid a particular error stdstore.target(address(vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); @@ -214,48 +214,48 @@ contract AllocatorVaultTest is DssTest { } function testLockFree() public { - uint256 initialSupply = ngt.totalSupply(); - assertEq(ngt.balanceOf(address(this)), 100_000 * 10**18); + uint256 initialSupply = gov.totalSupply(); + assertEq(gov.balanceOf(address(this)), 100_000 * 10**18); address urn = engine.open(voterDelegate); assertEq(_ink(ilk, urn), 0); - assertEq(stkNgt.balanceOf(urn), 0); - ngt.approve(address(engine), 100_000 * 10**18); + assertEq(stkGov.balanceOf(urn), 0); + gov.approve(address(engine), 100_000 * 10**18); vm.expectEmit(true, true, true, true); emit Lock(urn, 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); - assertEq(stkNgt.balanceOf(urn), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(this)), 0); - assertEq(ngt.totalSupply(), initialSupply); + assertEq(stkGov.balanceOf(urn), 100_000 * 10**18); + assertEq(gov.balanceOf(address(this)), 0); + assertEq(gov.totalSupply(), initialSupply); vm.expectEmit(true, true, true, true); emit Free(urn, 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); engine.free(urn, 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); - assertEq(stkNgt.balanceOf(urn), 60_000 * 10**18); - assertEq(ngt.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); - assertEq(ngt.totalSupply(), initialSupply - 40_000 * 10**18 * 15 / 100); + assertEq(stkGov.balanceOf(urn), 60_000 * 10**18); + assertEq(gov.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); + assertEq(gov.totalSupply(), initialSupply - 40_000 * 10**18 * 15 / 100); } function testMove() public { address urn = engine.open(voterDelegate); vm.prank(address(888)); address voterDelegate2 = delFactory.create(); - ngt.approve(address(engine), 100_000 * 10**18); + gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(DelegateMock(voterDelegate).stake(address(engine)), 100_000 * 10**18); assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); - assertEq(ngt.balanceOf(voterDelegate), 100_000 * 10**18); - assertEq(ngt.balanceOf(voterDelegate2), 0); + assertEq(gov.balanceOf(voterDelegate), 100_000 * 10**18); + assertEq(gov.balanceOf(voterDelegate2), 0); engine.move(urn, voterDelegate2); assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 100_000 * 10**18); - assertEq(ngt.balanceOf(voterDelegate), 0); - assertEq(ngt.balanceOf(voterDelegate2), 100_000 * 10**18); + assertEq(gov.balanceOf(voterDelegate), 0); + assertEq(gov.balanceOf(voterDelegate2), 100_000 * 10**18); } function testDrawWipe() public { - deal(address(ngt), address(this), 100_000 * 10**18, true); + deal(address(gov), address(this), 100_000 * 10**18, true); address urn = engine.open(voterDelegate); - ngt.approve(address(engine), 100_000 * 10**18); + gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(_art(ilk, urn), 0); vm.expectEmit(true, true, true, true); @@ -291,7 +291,7 @@ contract AllocatorVaultTest is DssTest { } function testSelectFarm() public { - StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkNgt)); + StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkGov)); address urn = engine.open(voterDelegate); assertEq(engine.selectedFarm(urn), address(0)); vm.expectRevert("LockstakeEngine/non-existing-farm"); @@ -304,7 +304,7 @@ contract AllocatorVaultTest is DssTest { vm.prank(pauseProxy); engine.addFarm(address(farm2)); engine.selectFarm(urn, address(farm2)); assertEq(engine.selectedFarm(urn), address(farm2)); - ngt.approve(address(engine), 100_000 * 10**18); + gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); engine.stake(urn, 100_000, 1); vm.expectRevert("LockstakeEngine/withdraw-first"); @@ -316,27 +316,27 @@ contract AllocatorVaultTest is DssTest { function testStakeWithdraw() public { vm.prank(pauseProxy); engine.addFarm(address(farm)); address urn = engine.open(voterDelegate); - ngt.approve(address(engine), 100_000 * 10**18); + gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); vm.expectRevert("LockstakeEngine/missing-selected-farm"); engine.stake(urn, 100_000, 1); vm.expectRevert("LockstakeEngine/missing-selected-farm"); engine.withdraw(urn, 0); engine.selectFarm(urn, address(farm)); - assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 0); + assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); vm.expectEmit(true, true, true, true); emit Stake(urn, address(farm), 60_000 * 10**18, 1); engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 60_000 * 10**18); vm.expectEmit(true, true, true, true); emit Withdraw(urn, address(farm), 15_000 * 10**18); engine.withdraw(urn, 15_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 55_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 45_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 55_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 45_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 45_000 * 10**18); } @@ -374,7 +374,7 @@ contract AllocatorVaultTest is DssTest { vm.stopPrank(); urn = engine.open(voterDelegate); - ngt.approve(address(engine), 100_000 * 10**18); + gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); engine.draw(urn, 2_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); @@ -392,10 +392,10 @@ contract AllocatorVaultTest is DssTest { function testOnKickFullNoStaked() public { address urn = _clipperSetUp(); - assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); + uint256 stkGovInitialSupply = stkGov.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -413,10 +413,10 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); } function testOnKickPartialNoStaked() public { @@ -424,7 +424,7 @@ contract AllocatorVaultTest is DssTest { vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + uint256 stkGovInitialSupply = stkGov.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -442,10 +442,10 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 25_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 75_000 * 10**18); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 25_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 75_000 * 10**18); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); } function testOnKickFullStakedPartial() public { @@ -453,14 +453,14 @@ contract AllocatorVaultTest is DssTest { engine.selectFarm(urn, address(farm)); engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); + uint256 stkGovInitialSupply = stkGov.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -478,11 +478,11 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.balanceOf(address(farm)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.balanceOf(address(farm)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); } function testOnKickPartialStakedPartialNoWithdraw() public { @@ -490,12 +490,12 @@ contract AllocatorVaultTest is DssTest { engine.selectFarm(urn, address(farm)); engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkNgt.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + uint256 stkGovInitialSupply = stkGov.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -513,11 +513,11 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 25_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 15_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 60_000 * 10**18); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 25_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 15_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); } function testOnKickPartialStakedPartialWithdraw() public { @@ -525,12 +525,12 @@ contract AllocatorVaultTest is DssTest { engine.selectFarm(urn, address(farm)); engine.stake(urn, 80_000 * 10**18, 1); - assertEq(stkNgt.balanceOf(address(urn)), 20_000 * 10**18); - assertEq(stkNgt.balanceOf(address(farm)), 80_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 20_000 * 10**18); + assertEq(stkGov.balanceOf(address(farm)), 80_000 * 10**18); vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + uint256 stkGovInitialSupply = stkGov.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -548,22 +548,22 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 25_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.balanceOf(address(farm)), 75_000 * 10**18); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 25_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.balanceOf(address(farm)), 75_000 * 10**18); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); } function testOnTake() public { address urn = _clipperSetUp(); - assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); - uint256 ngtInitialSupply = ngt.totalSupply(); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + uint256 govInitialSupply = gov.totalSupply(); + uint256 stkGovInitialSupply = stkGov.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); @@ -583,17 +583,17 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); address buyer = address(888); vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); - assertEq(ngt.balanceOf(buyer), 0); + assertEq(gov.balanceOf(buyer), 0); vm.prank(buyer); clip.take(id, 20_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(ngt.balanceOf(buyer), 20_000 * 10**18); + assertEq(gov.balanceOf(buyer), 20_000 * 10**18); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); assertEq(sale.pos, 0); @@ -608,13 +608,13 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 80_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 80_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 80_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); vm.prank(buyer); clip.take(id, 12_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(ngt.balanceOf(buyer), 32_000 * 10**18); + assertEq(gov.balanceOf(buyer), 32_000 * 10**18); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); assertEq(sale.pos, 0); @@ -629,23 +629,23 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); - assertEq(ngt.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(ngt.totalSupply(), ngtInitialSupply - 32_000 * 0.15 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 32_000 * 1.15 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(gov.totalSupply(), govInitialSupply - 32_000 * 0.15 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 32_000 * 1.15 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } function testOnTakePartialBurn() public { address urn = _clipperSetUp(); - assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); - uint256 ngtInitialSupply = ngt.totalSupply(); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + uint256 govInitialSupply = gov.totalSupply(); + uint256 stkGovInitialSupply = stkGov.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); @@ -665,41 +665,41 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 65); // Time passes to let the auction price to crash address buyer = address(888); vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); - assertEq(ngt.balanceOf(buyer), 0); + assertEq(gov.balanceOf(buyer), 0); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(ngt.balanceOf(buyer), 91428571428571428571428); + assertEq(gov.balanceOf(buyer), 91428571428571428571428); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(ngt.totalSupply(), ngtInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(gov.totalSupply(), govInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } function testOnTakeNoBurn() public { address urn = _clipperSetUp(); - assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); - uint256 ngtInitialSupply = ngt.totalSupply(); - uint256 stkNgtInitialSupply = stkNgt.totalSupply(); + uint256 govInitialSupply = gov.totalSupply(); + uint256 stkGovInitialSupply = stkGov.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); @@ -719,40 +719,40 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash address buyer = address(888); vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); - assertEq(ngt.balanceOf(buyer), 0); + assertEq(gov.balanceOf(buyer), 0); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(ngt.balanceOf(buyer), 100_000 * 10**18); + assertEq(gov.balanceOf(buyer), 100_000 * 10**18); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); - assertEq(ngt.balanceOf(address(voterDelegate)), 0); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(ngt.totalSupply(), ngtInitialSupply); // Can't burn anything - assertEq(stkNgt.balanceOf(address(urn)), 0); - assertEq(stkNgt.totalSupply(), stkNgtInitialSupply - 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(gov.totalSupply(), govInitialSupply); // Can't burn anything + assertEq(stkGov.balanceOf(address(urn)), 0); + assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); assertLt(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt } function testOnYank() public { address urn = _clipperSetUp(); - assertEq(ngt.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(ngt.balanceOf(address(engine)), 0); - assertEq(stkNgt.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); - uint256 ngtInitialSupply = ngt.totalSupply(); + uint256 govInitialSupply = gov.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -777,6 +777,6 @@ contract AllocatorVaultTest is DssTest { assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(ngt.totalSupply(), ngtInitialSupply - 100_000 * 10**18); + assertEq(gov.totalSupply(), govInitialSupply - 100_000 * 10**18); } } diff --git a/test/mocks/DelegateMock.sol b/test/mocks/DelegateMock.sol index 2a995c6a..f63a615b 100644 --- a/test/mocks/DelegateMock.sol +++ b/test/mocks/DelegateMock.sol @@ -31,7 +31,7 @@ contract DelegateMock { gov = GemLike(gov_); } - // --- NGT owner functions + // --- GOV owner functions function lock(uint256 wad) external { gov.transferFrom(msg.sender, address(this), wad); From b305879aec7158c9a2f0a10dda02f46f0525b2a7 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Thu, 23 Nov 2023 17:24:51 -0300 Subject: [PATCH 003/111] Fix error message --- src/LockstakeEngine.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index fdddf8f1..d57a9599 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -188,20 +188,20 @@ contract LockstakeEngine { // --- urn/delegation functions --- function open(address delegate) external returns (address urn) { - require(delegateFactory.isDelegate(delegate) == 1, "LockstateEngine/not-valid-delegate"); + require(delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); uint256 salt = uint256(keccak256(abi.encode(msg.sender, urnsAmt[msg.sender]++))); bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov)); assembly { urn := create2(0, add(code, 0x20), mload(code), salt) } - require(urn != address(0), "LockstateEngine/urn-creation-failed"); + require(urn != address(0), "LockstakeEngine/urn-creation-failed"); urnOwners[urn] = msg.sender; urnDelegates[urn] = delegate; emit Open(msg.sender, delegate, urn); } function lock(address urn, uint256 wad) external urnOwner(urn) { - require(wad <= uint256(type(int256).max), "LockstateEngine/wad-overflow"); + require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); gov.transferFrom(msg.sender, address(this), wad); address delegate = urnDelegates[urn]; gov.approve(address(delegate), wad); @@ -215,7 +215,7 @@ contract LockstakeEngine { } function free(address urn, uint256 wad) external urnOwner(urn) { - require(wad <= uint256(type(int256).max), "LockstateEngine/wad-overflow"); + require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); stkGov.burn(urn, wad); @@ -228,9 +228,9 @@ contract LockstakeEngine { } function move(address urn, address delegate) external urnOwner(urn) { - require(delegateFactory.isDelegate(delegate) == 1, "LockstateEngine/not-valid-delegate"); + require(delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); address prevDelegate = urnDelegates[urn]; - require(prevDelegate != delegate, "LockstateEngine/same-delegate"); + require(prevDelegate != delegate, "LockstakeEngine/same-delegate"); (uint256 wad,) = vat.urns(ilk, urn); DelegateLike(prevDelegate).free(wad); gov.approve(address(delegate), wad); From a0f93ed15b56af9fd96d0e5a2115f5141abb92e4 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 24 Nov 2023 09:36:17 -0300 Subject: [PATCH 004/111] Fix typo --- src/LockstakeEngine.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index d57a9599..7faf367d 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -62,7 +62,7 @@ contract LockstakeEngine { mapping(address => uint256) public farms; // farm => 1 == whitelisted mapping(address => uint256) public urnsAmt; // usr => amount mapping(address => address) public urnOwners; // urn => owner - mapping(address => address) public urnDelegates; // urn => current associated delegare + mapping(address => address) public urnDelegates; // urn => current associated delegate mapping(address => address) public selectedFarm; // urn => current selected farm JugLike public jug; From 642445b4017b56542ac5a579d501d35b233bfe5e Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:43:48 -0300 Subject: [PATCH 005/111] Create urn and allow to operate without defined delegate (#3) * Create urn and allow to operate without defined delegate * fix more error messages * Option to remove delegate * Move delegate function * Improve test coverage --- src/LockstakeEngine.sol | 59 ++++--- test/LockstakeEngine.t.sol | 306 ++++++++++++++++++++++++++++--------- 2 files changed, 273 insertions(+), 92 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 7faf367d..29faf294 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -89,10 +89,10 @@ contract LockstakeEngine { event File(bytes32 indexed what, address data); event AddFarm(address farm); event DelFarm(address farm); - event Open(address indexed owner, address indexed delegate, address urn); + event Open(address indexed owner, address urn); + event Delegate(address indexed urn, address indexed delegate); event Lock(address indexed urn, uint256 wad); event Free(address indexed urn, uint256 wad, uint256 burn); - event Move(address indexed urn, address indexed delegate); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); event SelectFarm(address indexed urn, address farm); @@ -187,8 +187,7 @@ contract LockstakeEngine { // --- urn/delegation functions --- - function open(address delegate) external returns (address urn) { - require(delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); + function open() external returns (address urn) { uint256 salt = uint256(keccak256(abi.encode(msg.sender, urnsAmt[msg.sender]++))); bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov)); assembly { @@ -196,16 +195,17 @@ contract LockstakeEngine { } require(urn != address(0), "LockstakeEngine/urn-creation-failed"); urnOwners[urn] = msg.sender; - urnDelegates[urn] = delegate; - emit Open(msg.sender, delegate, urn); + emit Open(msg.sender, urn); } function lock(address urn, uint256 wad) external urnOwner(urn) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); gov.transferFrom(msg.sender, address(this), wad); - address delegate = urnDelegates[urn]; - gov.approve(address(delegate), wad); - DelegateLike(delegate).lock(wad); + address delegate_ = urnDelegates[urn]; + if (delegate_ != address(0)) { + gov.approve(address(delegate_), wad); + DelegateLike(delegate_).lock(wad); + } // TODO: define if we want an internal registry to register how much is locked per user, // the vat.slip and stkGov balance act already as a registry so probably not needed an extra one vat.slip(ilk, urn, int256(wad)); @@ -219,24 +219,32 @@ contract LockstakeEngine { vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); stkGov.burn(urn, wad); - address delegate = urnDelegates[urn]; - DelegateLike(delegate).free(wad); + address delegate_ = urnDelegates[urn]; + if (delegate_ != address(0)) { + DelegateLike(delegate_).free(wad); + } uint256 burn = wad * fee / WAD; gov.burn(address(this), burn); gov.transfer(msg.sender, wad - burn); emit Free(urn, wad, burn); } - function move(address urn, address delegate) external urnOwner(urn) { - require(delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); + function delegate(address urn, address delegate_) external urnOwner(urn) { + require(delegate_ == address(0) || delegateFactory.isDelegate(delegate_) == 1, "LockstakeEngine/not-valid-delegate"); address prevDelegate = urnDelegates[urn]; - require(prevDelegate != delegate, "LockstakeEngine/same-delegate"); + require(prevDelegate != delegate_, "LockstakeEngine/same-delegate"); (uint256 wad,) = vat.urns(ilk, urn); - DelegateLike(prevDelegate).free(wad); - gov.approve(address(delegate), wad); - DelegateLike(delegate).lock(wad); - urnDelegates[urn] = delegate; - emit Move(urn, delegate); + if (wad > 0) { + if (prevDelegate != address(0)) { + DelegateLike(prevDelegate).free(wad); + } + if (delegate_ != address(0)) { + gov.approve(address(delegate_), wad); + DelegateLike(delegate_).lock(wad); + } + } + urnDelegates[urn] = delegate_; + emit Delegate(urn, delegate_); } // --- loan functions --- @@ -300,7 +308,10 @@ contract LockstakeEngine { } } stkGov.burn(urn, wad); // Burn the whole liquidated amount of staking token - DelegateLike(urnDelegates[urn]).free(wad); // Undelegate liquidated amount and retain GOV + address delegate_ = urnDelegates[urn]; + if (delegate_ != address(0)) { + DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain NGT + } // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper emit OnKick(urn, wad); } @@ -320,9 +331,11 @@ contract LockstakeEngine { } gov.burn(address(this), burn); // Burn GOV if (left > 0) { - address delegate = urnDelegates[urn]; - gov.approve(address(delegate), left); - DelegateLike(delegate).lock(left); + address delegate_ = urnDelegates[urn]; + if (delegate_ != address(0)) { + gov.approve(address(delegate_), left); + DelegateLike(delegate_).lock(left); + } vat.slip(ilk, urn, int256(left)); vat.frob(ilk, urn, urn, address(0), int256(left), 0); stkGov.mint(urn, left); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 33a41834..c6abf068 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -80,10 +80,10 @@ contract AllocatorVaultTest is DssTest { event AddFarm(address farm); event DelFarm(address farm); - event Open(address indexed owner, address indexed delegate, address urn); + event Open(address indexed owner, address urn); event Lock(address indexed urn, uint256 wad); event Free(address indexed urn, uint256 wad, uint256 burn); - event Move(address indexed urn, address indexed delegate); + event Delegate(address indexed urn, address indexed delegate_); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); event SelectFarm(address indexed urn, address farm); @@ -169,15 +169,16 @@ contract AllocatorVaultTest is DssTest { checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); - bytes4[] memory urnOwnersMethods = new bytes4[](8); + bytes4[] memory urnOwnersMethods = new bytes4[](9); urnOwnersMethods[0] = engine.lock.selector; urnOwnersMethods[1] = engine.free.selector; - urnOwnersMethods[2] = engine.draw.selector; - urnOwnersMethods[3] = engine.wipe.selector; - urnOwnersMethods[4] = engine.selectFarm.selector; - urnOwnersMethods[5] = engine.stake.selector; - urnOwnersMethods[6] = engine.withdraw.selector; - urnOwnersMethods[7] = engine.getReward.selector; + urnOwnersMethods[2] = engine.delegate.selector; + urnOwnersMethods[3] = engine.draw.selector; + urnOwnersMethods[4] = engine.wipe.selector; + urnOwnersMethods[5] = engine.selectFarm.selector; + urnOwnersMethods[6] = engine.stake.selector; + urnOwnersMethods[7] = engine.withdraw.selector; + urnOwnersMethods[8] = engine.getReward.selector; vm.startPrank(address(0xBEEF)); checkModifier(address(engine), "LockstakeEngine/not-urn-owner", urnOwnersMethods); @@ -204,19 +205,22 @@ contract AllocatorVaultTest is DssTest { assertEq(engine.urnsAmt(address(this)), 0); address urn = engine.getUrn(address(this), 0); vm.expectEmit(true, true, true, true); - emit Open(address(this), voterDelegate, urn); - assertEq(engine.open(voterDelegate), urn); + emit Open(address(this), urn); + assertEq(engine.open(), urn); assertEq(engine.urnsAmt(address(this)), 1); - assertEq(engine.getUrn(address(this), 1), engine.open(voterDelegate)); + assertEq(engine.getUrn(address(this), 1), engine.open()); assertEq(engine.urnsAmt(address(this)), 2); - assertEq(engine.getUrn(address(this), 2), engine.open(voterDelegate)); + assertEq(engine.getUrn(address(this), 2), engine.open()); assertEq(engine.urnsAmt(address(this)), 3); } - function testLockFree() public { + function _testLockFree(bool withDelegate) internal { uint256 initialSupply = gov.totalSupply(); assertEq(gov.balanceOf(address(this)), 100_000 * 10**18); - address urn = engine.open(voterDelegate); + address urn = engine.open(); + if (withDelegate) { + engine.delegate(urn, voterDelegate); + } assertEq(_ink(ilk, urn), 0); assertEq(stkGov.balanceOf(urn), 0); gov.approve(address(engine), 100_000 * 10**18); @@ -226,6 +230,12 @@ contract AllocatorVaultTest is DssTest { assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(stkGov.balanceOf(urn), 100_000 * 10**18); assertEq(gov.balanceOf(address(this)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(gov.totalSupply(), initialSupply); vm.expectEmit(true, true, true, true); emit Free(urn, 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); @@ -233,11 +243,27 @@ contract AllocatorVaultTest is DssTest { assertEq(_ink(ilk, urn), 60_000 * 10**18); assertEq(stkGov.balanceOf(urn), 60_000 * 10**18); assertEq(gov.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); + if (withDelegate) { + assertEq(gov.balanceOf(address(engine)), 0); + assertEq(gov.balanceOf(address(voterDelegate)), 60_000 * 10**18); + } else { + assertEq(gov.balanceOf(address(engine)), 60_000 * 10**18); + } assertEq(gov.totalSupply(), initialSupply - 40_000 * 10**18 * 15 / 100); } - function testMove() public { - address urn = engine.open(voterDelegate); + function testLockFreeNoDelegate() public { + _testLockFree(false); + } + + function testLockFreeWithDelegate() public { + _testLockFree(true); + } + + function testDelegate() public { + address urn = engine.open(); + engine.delegate(urn, voterDelegate); + assertEq(engine.urnDelegates(urn), voterDelegate); vm.prank(address(888)); address voterDelegate2 = delFactory.create(); gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); @@ -245,16 +271,26 @@ contract AllocatorVaultTest is DssTest { assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); assertEq(gov.balanceOf(voterDelegate), 100_000 * 10**18); assertEq(gov.balanceOf(voterDelegate2), 0); - engine.move(urn, voterDelegate2); + assertEq(gov.balanceOf(address(engine)), 0); + engine.delegate(urn, voterDelegate2); + assertEq(engine.urnDelegates(urn), voterDelegate2); assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 100_000 * 10**18); assertEq(gov.balanceOf(voterDelegate), 0); assertEq(gov.balanceOf(voterDelegate2), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + engine.delegate(urn, address(0)); + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); + assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); + assertEq(gov.balanceOf(voterDelegate), 0); + assertEq(gov.balanceOf(voterDelegate2), 0); + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); } function testDrawWipe() public { deal(address(gov), address(this), 100_000 * 10**18, true); - address urn = engine.open(voterDelegate); + address urn = engine.open(); gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(_art(ilk, urn), 0); @@ -292,7 +328,7 @@ contract AllocatorVaultTest is DssTest { function testSelectFarm() public { StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkGov)); - address urn = engine.open(voterDelegate); + address urn = engine.open(); assertEq(engine.selectedFarm(urn), address(0)); vm.expectRevert("LockstakeEngine/non-existing-farm"); engine.selectFarm(urn, address(farm)); @@ -315,7 +351,7 @@ contract AllocatorVaultTest is DssTest { function testStakeWithdraw() public { vm.prank(pauseProxy); engine.addFarm(address(farm)); - address urn = engine.open(voterDelegate); + address urn = engine.open(); gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); vm.expectRevert("LockstakeEngine/missing-selected-farm"); @@ -342,7 +378,7 @@ contract AllocatorVaultTest is DssTest { function testGetReward() public { vm.prank(pauseProxy); engine.addFarm(address(farm)); - address urn = engine.open(voterDelegate); + address urn = engine.open(); farm.setReward(address(urn), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 0); vm.expectEmit(true, true, true, true); @@ -351,7 +387,7 @@ contract AllocatorVaultTest is DssTest { assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 20_000); } - function _clipperSetUp() internal returns (address urn) { + function _clipperSetUp(bool withDelegate) internal returns (address urn) { vm.startPrank(pauseProxy); engine.addFarm(address(farm)); clip = new LockstakeClipper(vat, spot, dog, address(engine)); @@ -373,7 +409,10 @@ contract AllocatorVaultTest is DssTest { DogLike(dog).file(ilk, "hole", 10_000 * 10**45); vm.stopPrank(); - urn = engine.open(voterDelegate); + urn = engine.open(); + if (withDelegate) { + engine.delegate(urn, voterDelegate); + } gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); engine.draw(urn, 2_000 * 10**18); @@ -389,11 +428,15 @@ contract AllocatorVaultTest is DssTest { assertEq(clip.kicks(), 1); } - function testOnKickFullNoStaked() public { - address urn = _clipperSetUp(); + function _testOnKickFullNoStaked(bool withDelegate) internal { + address urn = _clipperSetUp(withDelegate); - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); uint256 stkGovInitialSupply = stkGov.totalSupply(); @@ -413,14 +456,24 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkGov.balanceOf(address(urn)), 0); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); } - function testOnKickPartialNoStaked() public { - address urn = _clipperSetUp(); + function testOnKickFullNoStakedNoDelegate() public { + _testOnKickFullNoStaked(false); + } + + function testOnKickFullNoStakedWithDelegate() public { + _testOnKickFullNoStaked(true); + } + + function _testOnKickPartialNoStaked(bool withDelegate) internal { + address urn = _clipperSetUp(withDelegate); vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); @@ -442,22 +495,38 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 75_000 * 10**18); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); } - function testOnKickFullStakedPartial() public { - address urn = _clipperSetUp(); + function testOnKickPartialNoStakedNoDelegate() public { + _testOnKickPartialNoStaked(false); + } + + function testOnKickPartialNoStakedWithDelegate() public { + _testOnKickPartialNoStaked(true); + } + + function _testOnKickFullStakedPartial(bool withDelegate) private { + address urn = _clipperSetUp(withDelegate); engine.selectFarm(urn, address(farm)); engine.stake(urn, 60_000 * 10**18, 1); assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); uint256 stkGovInitialSupply = stkGov.totalSupply(); @@ -478,15 +547,25 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkGov.balanceOf(address(urn)), 0); assertEq(stkGov.balanceOf(address(farm)), 0); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); } - function testOnKickPartialStakedPartialNoWithdraw() public { - address urn = _clipperSetUp(); + function testOnKickFullStakedPartialNoDelegate() public { + _testOnKickFullStakedPartial(false); + } + + function testOnKickFullStakedPartialWithDelegate() public { + _testOnKickFullStakedPartial(true); + } + + function _testOnKickPartialStakedPartialNoWithdraw(bool withDelegate) internal { + address urn = _clipperSetUp(withDelegate); engine.selectFarm(urn, address(farm)); engine.stake(urn, 60_000 * 10**18, 1); @@ -513,15 +592,27 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 15_000 * 10**18); assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); } - function testOnKickPartialStakedPartialWithdraw() public { - address urn = _clipperSetUp(); + function testOnKickPartialStakedPartialNoWithdrawNoDelegate() public { + _testOnKickPartialStakedPartialNoWithdraw(false); + } + + function testOnKickPartialStakedPartialNoWithdrawWithDelegate() public { + _testOnKickPartialStakedPartialNoWithdraw(true); + } + + function _testOnKickPartialStakedPartialWithdraw(bool withDelegate) public { + address urn = _clipperSetUp(withDelegate); engine.selectFarm(urn, address(farm)); engine.stake(urn, 80_000 * 10**18, 1); @@ -548,18 +639,34 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 0); assertEq(stkGov.balanceOf(address(farm)), 75_000 * 10**18); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); } - function testOnTake() public { - address urn = _clipperSetUp(); + function testOnKickPartialStakedPartialWithdrawNoDelegate() public { + _testOnKickPartialStakedPartialWithdraw(false); + } - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + function testOnKickPartialStakedPartialWithdrawWithDelegate() public { + _testOnKickPartialStakedPartialWithdraw(true); + } + + function _testOnTake(bool withDelegate) internal { + address urn = _clipperSetUp(withDelegate); + + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); uint256 govInitialSupply = gov.totalSupply(); @@ -583,6 +690,9 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(voterDelegate)), 0); assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkGov.balanceOf(address(urn)), 0); @@ -608,7 +718,9 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 80_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(engine)), 80_000 * 10**18); assertEq(stkGov.balanceOf(address(urn)), 0); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); @@ -629,19 +741,35 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); - assertEq(gov.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + } else { + assertEq(gov.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); + } assertEq(gov.totalSupply(), govInitialSupply - 32_000 * 0.15 * 10**18); assertEq(stkGov.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 32_000 * 1.15 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } - function testOnTakePartialBurn() public { - address urn = _clipperSetUp(); + function testOnTakeNoDelegate() public { + _testOnTake(false); + } - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + function testOnTakeWithDelegate() public { + _testOnTake(true); + } + + function _testOnTakePartialBurn(bool withDelegate) internal { + address urn = _clipperSetUp(withDelegate); + + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); uint256 govInitialSupply = gov.totalSupply(); @@ -665,7 +793,9 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkGov.balanceOf(address(urn)), 0); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); @@ -683,7 +813,9 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); - assertEq(gov.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(engine)), 0); assertEq(gov.totalSupply(), govInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 assertEq(stkGov.balanceOf(address(urn)), 0); @@ -691,11 +823,23 @@ contract AllocatorVaultTest is DssTest { assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } - function testOnTakeNoBurn() public { - address urn = _clipperSetUp(); + function testOnTakePartialBurnNoDelegate() public { + _testOnTakePartialBurn(false); + } - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + function testOnTakePartialBurnWithDelegate() public { + _testOnTakePartialBurn(true); + } + + function _testOnTakeNoBurn(bool withDelegate) internal { + address urn = _clipperSetUp(withDelegate); + + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); uint256 govInitialSupply = gov.totalSupply(); @@ -719,7 +863,9 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkGov.balanceOf(address(urn)), 0); assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); @@ -737,7 +883,9 @@ contract AllocatorVaultTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); - assertEq(gov.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 0); + } assertEq(gov.balanceOf(address(engine)), 0); assertEq(gov.totalSupply(), govInitialSupply); // Can't burn anything assertEq(stkGov.balanceOf(address(urn)), 0); @@ -745,11 +893,23 @@ contract AllocatorVaultTest is DssTest { assertLt(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt } - function testOnYank() public { - address urn = _clipperSetUp(); + function testOnTakeNoBurnNoDelegate() public { + _testOnTakeNoBurn(false); + } - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + function testOnTakeNoBurnWithDelegate() public { + _testOnTakeNoBurn(true); + } + + function _testOnYank(bool withDelegate) internal { + address urn = _clipperSetUp(withDelegate); + + if (withDelegate) { + assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 0); + } else { + assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + } assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); uint256 govInitialSupply = gov.totalSupply(); @@ -779,4 +939,12 @@ contract AllocatorVaultTest is DssTest { assertEq(gov.totalSupply(), govInitialSupply - 100_000 * 10**18); } + + function testOnYankNoDelegate() public { + _testOnYank(false); + } + + function testOnYankWithDelegate() public { + _testOnYank(true); + } } From b69c955f13c240bf855444de521f52da4ceef847 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Tue, 28 Nov 2023 13:28:16 -0300 Subject: [PATCH 006/111] Add some variable renaming + test some error messages --- src/LockstakeEngine.sol | 4 ++-- test/LockstakeEngine.t.sol | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 29faf294..9b2d8c17 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -60,7 +60,7 @@ contract LockstakeEngine { mapping(address => uint256) public wards; // usr => 1 == access mapping(address => uint256) public farms; // farm => 1 == whitelisted - mapping(address => uint256) public urnsAmt; // usr => amount + mapping(address => uint256) public usrAmts; // usr => urns amount mapping(address => address) public urnOwners; // urn => owner mapping(address => address) public urnDelegates; // urn => current associated delegate mapping(address => address) public selectedFarm; // urn => current selected farm @@ -188,7 +188,7 @@ contract LockstakeEngine { // --- urn/delegation functions --- function open() external returns (address urn) { - uint256 salt = uint256(keccak256(abi.encode(msg.sender, urnsAmt[msg.sender]++))); + uint256 salt = uint256(keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++))); bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov)); assembly { urn := create2(0, add(code, 0x20), mload(code), salt) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index c6abf068..85f1d3cb 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -202,22 +202,26 @@ contract AllocatorVaultTest is DssTest { } function testOpen() public { - assertEq(engine.urnsAmt(address(this)), 0); + assertEq(engine.usrAmts(address(this)), 0); address urn = engine.getUrn(address(this), 0); vm.expectEmit(true, true, true, true); emit Open(address(this), urn); assertEq(engine.open(), urn); - assertEq(engine.urnsAmt(address(this)), 1); + assertEq(engine.usrAmts(address(this)), 1); assertEq(engine.getUrn(address(this), 1), engine.open()); - assertEq(engine.urnsAmt(address(this)), 2); + assertEq(engine.usrAmts(address(this)), 2); assertEq(engine.getUrn(address(this), 2), engine.open()); - assertEq(engine.urnsAmt(address(this)), 3); + assertEq(engine.usrAmts(address(this)), 3); } function _testLockFree(bool withDelegate) internal { uint256 initialSupply = gov.totalSupply(); assertEq(gov.balanceOf(address(this)), 100_000 * 10**18); address urn = engine.open(); + vm.expectRevert("LockstakeEngine/wad-overflow"); + engine.lock(urn, uint256(type(int256).max) + 1); + vm.expectRevert("LockstakeEngine/wad-overflow"); + engine.free(urn, uint256(type(int256).max) + 1); if (withDelegate) { engine.delegate(urn, voterDelegate); } @@ -262,6 +266,10 @@ contract AllocatorVaultTest is DssTest { function testDelegate() public { address urn = engine.open(); + vm.expectRevert("LockstakeEngine/not-valid-delegate"); + engine.delegate(urn, address(111)); + engine.delegate(urn, voterDelegate); + vm.expectRevert("LockstakeEngine/same-delegate"); engine.delegate(urn, voterDelegate); assertEq(engine.urnDelegates(urn), voterDelegate); vm.prank(address(888)); address voterDelegate2 = delFactory.create(); From 5cda4c3fe8930085d11323e96df010282758e08d Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 8 Dec 2023 12:14:50 -0300 Subject: [PATCH 007/111] Minors --- src/LockstakeEngine.sol | 8 ++++---- src/LockstakeUrn.sol | 4 ++-- test/LockstakeEngine.t.sol | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 9b2d8c17..fc4e06a5 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -97,7 +97,7 @@ contract LockstakeEngine { event Wipe(address indexed urn, uint256 wad); event SelectFarm(address indexed urn, address farm); event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); - event Withdraw(address indexed urn, address indexed farm, uint256 amt); + event Withdraw(address indexed urn, address indexed farm, uint256 wad); event GetReward(address indexed urn, address indexed farm); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); @@ -285,11 +285,11 @@ contract LockstakeEngine { emit Stake(urn, selectedFarmUrn, wad, ref); } - function withdraw(address urn, uint256 amt) external urnOwner(urn) { + function withdraw(address urn, uint256 wad) external urnOwner(urn) { address selectedFarmUrn = selectedFarm[urn]; require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm"); - LockstakeUrn(urn).withdraw(selectedFarmUrn, amt); - emit Withdraw(urn, selectedFarmUrn, amt); + LockstakeUrn(urn).withdraw(selectedFarmUrn, wad); + emit Withdraw(urn, selectedFarmUrn, wad); } function getReward(address urn, address farm) external urnOwner(urn) { diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index b579eb15..07033482 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -62,8 +62,8 @@ contract LockstakeUrn { StakingRewardsLike(farm).stake(wad, ref); } - function withdraw(address farm, uint256 amt) external isEngine{ - StakingRewardsLike(farm).withdraw(amt); + function withdraw(address farm, uint256 wad) external isEngine { + StakingRewardsLike(farm).withdraw(wad); } function getReward(address farm, address usr) external isEngine { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 85f1d3cb..a966a8d6 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -88,7 +88,7 @@ contract AllocatorVaultTest is DssTest { event Wipe(address indexed urn, uint256 wad); event SelectFarm(address indexed urn, address farm); event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); - event Withdraw(address indexed urn, address indexed farm, uint256 amt); + event Withdraw(address indexed urn, address indexed farm, uint256 wad); event GetReward(address indexed urn, address indexed farm); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); From d3e4a6bf2780a69a58d2d652c3a38d2848198f06 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 8 Dec 2023 15:02:35 -0300 Subject: [PATCH 008/111] Use solidity to crete urn --- src/LockstakeEngine.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index fc4e06a5..ef54d39e 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -188,11 +188,8 @@ contract LockstakeEngine { // --- urn/delegation functions --- function open() external returns (address urn) { - uint256 salt = uint256(keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++))); - bytes memory code = abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov)); - assembly { - urn := create2(0, add(code, 0x20), mload(code), salt) - } + bytes32 salt = keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++)); + urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkGov))); require(urn != address(0), "LockstakeEngine/urn-creation-failed"); urnOwners[urn] = msg.sender; emit Open(msg.sender, urn); From fae2bea510430b95786c87127365f1b8f31149d8 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 8 Dec 2023 15:45:44 -0300 Subject: [PATCH 009/111] Add receiver parameter to free --- src/LockstakeEngine.sol | 4 ++-- test/LockstakeEngine.t.sol | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index ef54d39e..27944787 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -211,7 +211,7 @@ contract LockstakeEngine { emit Lock(urn, wad); } - function free(address urn, uint256 wad) external urnOwner(urn) { + function free(address urn, address to, uint256 wad) external urnOwner(urn) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); @@ -222,7 +222,7 @@ contract LockstakeEngine { } uint256 burn = wad * fee / WAD; gov.burn(address(this), burn); - gov.transfer(msg.sender, wad - burn); + gov.transfer(to, wad - burn); emit Free(urn, wad, burn); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index a966a8d6..a6bac065 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -221,7 +221,7 @@ contract AllocatorVaultTest is DssTest { vm.expectRevert("LockstakeEngine/wad-overflow"); engine.lock(urn, uint256(type(int256).max) + 1); vm.expectRevert("LockstakeEngine/wad-overflow"); - engine.free(urn, uint256(type(int256).max) + 1); + engine.free(urn, address(this), uint256(type(int256).max) + 1); if (withDelegate) { engine.delegate(urn, voterDelegate); } @@ -243,17 +243,21 @@ contract AllocatorVaultTest is DssTest { assertEq(gov.totalSupply(), initialSupply); vm.expectEmit(true, true, true, true); emit Free(urn, 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); - engine.free(urn, 40_000 * 10**18); + engine.free(urn, address(this), 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); assertEq(stkGov.balanceOf(urn), 60_000 * 10**18); assertEq(gov.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); + engine.free(urn, address(123), 10_000 * 10**18); + assertEq(_ink(ilk, urn), 50_000 * 10**18); + assertEq(stkGov.balanceOf(urn), 50_000 * 10**18); + assertEq(gov.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(gov.balanceOf(address(engine)), 0); - assertEq(gov.balanceOf(address(voterDelegate)), 60_000 * 10**18); + assertEq(gov.balanceOf(address(voterDelegate)), 50_000 * 10**18); } else { - assertEq(gov.balanceOf(address(engine)), 60_000 * 10**18); + assertEq(gov.balanceOf(address(engine)), 50_000 * 10**18); } - assertEq(gov.totalSupply(), initialSupply - 40_000 * 10**18 * 15 / 100); + assertEq(gov.totalSupply(), initialSupply - 50_000 * 10**18 * 15 / 100); } function testLockFreeNoDelegate() public { From 35ccc9d8d456935ae0524d83590dc89c6f824d9b Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 8 Dec 2023 16:10:17 -0300 Subject: [PATCH 010/111] Modify event for extra parameter of free --- src/LockstakeEngine.sol | 4 ++-- test/LockstakeEngine.t.sol | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 27944787..85f092a1 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -92,7 +92,7 @@ contract LockstakeEngine { event Open(address indexed owner, address urn); event Delegate(address indexed urn, address indexed delegate); event Lock(address indexed urn, uint256 wad); - event Free(address indexed urn, uint256 wad, uint256 burn); + event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); event SelectFarm(address indexed urn, address farm); @@ -223,7 +223,7 @@ contract LockstakeEngine { uint256 burn = wad * fee / WAD; gov.burn(address(this), burn); gov.transfer(to, wad - burn); - emit Free(urn, wad, burn); + emit Free(urn, to, wad, burn); } function delegate(address urn, address delegate_) external urnOwner(urn) { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index a6bac065..e905062e 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -82,7 +82,7 @@ contract AllocatorVaultTest is DssTest { event DelFarm(address farm); event Open(address indexed owner, address urn); event Lock(address indexed urn, uint256 wad); - event Free(address indexed urn, uint256 wad, uint256 burn); + event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event Delegate(address indexed urn, address indexed delegate_); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); @@ -242,11 +242,13 @@ contract AllocatorVaultTest is DssTest { } assertEq(gov.totalSupply(), initialSupply); vm.expectEmit(true, true, true, true); - emit Free(urn, 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); + emit Free(urn, address(this), 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); engine.free(urn, address(this), 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); assertEq(stkGov.balanceOf(urn), 60_000 * 10**18); assertEq(gov.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); + vm.expectEmit(true, true, true, true); + emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 15 / 100); engine.free(urn, address(123), 10_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); assertEq(stkGov.balanceOf(urn), 50_000 * 10**18); From 7bfb4125250384823d8f5f2cf7a06676f32e9c19 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 8 Dec 2023 16:12:42 -0300 Subject: [PATCH 011/111] Add receiver parameter to getReward --- src/LockstakeEngine.sol | 8 ++++---- src/LockstakeUrn.sol | 5 +++-- test/LockstakeEngine.t.sol | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 85f092a1..efbbaf6c 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -98,7 +98,7 @@ contract LockstakeEngine { event SelectFarm(address indexed urn, address farm); event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); event Withdraw(address indexed urn, address indexed farm, uint256 wad); - event GetReward(address indexed urn, address indexed farm); + event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn); @@ -289,9 +289,9 @@ contract LockstakeEngine { emit Withdraw(urn, selectedFarmUrn, wad); } - function getReward(address urn, address farm) external urnOwner(urn) { - LockstakeUrn(urn).getReward(farm, msg.sender); - emit GetReward(urn, farm); + function getReward(address urn, address farm, address to) external urnOwner(urn) { + uint256 amt = LockstakeUrn(urn).getReward(farm, to); + emit GetReward(urn, farm, to, amt); } // --- liquidation callback functions --- diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index 07033482..1c1ea4d4 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -66,9 +66,10 @@ contract LockstakeUrn { StakingRewardsLike(farm).withdraw(wad); } - function getReward(address farm, address usr) external isEngine { + function getReward(address farm, address to) external isEngine returns (uint256 amt) { StakingRewardsLike(farm).getReward(); GemLike rewardsToken = StakingRewardsLike(farm).rewardsToken(); - rewardsToken.transfer(usr, rewardsToken.balanceOf(address(this))); + amt = rewardsToken.balanceOf(address(this)); + rewardsToken.transfer(to, amt); } } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index e905062e..7a80791d 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -89,7 +89,7 @@ contract AllocatorVaultTest is DssTest { event SelectFarm(address indexed urn, address farm); event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); event Withdraw(address indexed urn, address indexed farm, uint256 wad); - event GetReward(address indexed urn, address indexed farm); + event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn); @@ -396,9 +396,9 @@ contract AllocatorVaultTest is DssTest { farm.setReward(address(urn), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 0); vm.expectEmit(true, true, true, true); - emit GetReward(urn, address(farm)); - engine.getReward(urn, address(farm)); - assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 20_000); + emit GetReward(urn, address(farm), address(123), 20_000); + engine.getReward(urn, address(farm), address(123)); + assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 20_000); } function _clipperSetUp(bool withDelegate) internal returns (address urn) { From 2527ae74cf4047a1cab78bee79851c51dc13df7d Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 8 Dec 2023 16:26:04 -0300 Subject: [PATCH 012/111] Don't allow to keep staking when farm is removed by admin --- src/LockstakeEngine.sol | 1 + test/LockstakeEngine.t.sol | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index efbbaf6c..a29a7b6f 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -278,6 +278,7 @@ contract LockstakeEngine { function stake(address urn, uint256 wad, uint16 ref) external urnOwner(urn) { address selectedFarmUrn = selectedFarm[urn]; require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm"); + require(farms[selectedFarmUrn] == 1, "LockstakeEngine/selected-farm-not-available-anymore"); LockstakeUrn(urn).stake(selectedFarmUrn, wad, ref); emit Stake(urn, selectedFarmUrn, wad, ref); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 7a80791d..81eaf92e 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -382,6 +382,9 @@ contract AllocatorVaultTest is DssTest { assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 60_000 * 10**18); + vm.prank(pauseProxy); engine.delFarm(address(farm)); + vm.expectRevert("LockstakeEngine/selected-farm-not-available-anymore"); + engine.stake(urn, 10_000 * 10**18, 1); vm.expectEmit(true, true, true, true); emit Withdraw(urn, address(farm), 15_000 * 10**18); engine.withdraw(urn, 15_000 * 10**18); From 4464593df851186f91b852308701d01dfa218783 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Fri, 8 Dec 2023 16:59:19 -0300 Subject: [PATCH 013/111] Change order in free to match reversed one from lock --- src/LockstakeEngine.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index a29a7b6f..fdcd0945 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -213,16 +213,16 @@ contract LockstakeEngine { function free(address urn, address to, uint256 wad) external urnOwner(urn) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); + stkGov.burn(urn, wad); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); - stkGov.burn(urn, wad); address delegate_ = urnDelegates[urn]; if (delegate_ != address(0)) { DelegateLike(delegate_).free(wad); } uint256 burn = wad * fee / WAD; - gov.burn(address(this), burn); gov.transfer(to, wad - burn); + gov.burn(address(this), burn); emit Free(urn, to, wad, burn); } From 6ab1d9ec5c95e5b283c85edc3e60839b63750bf5 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Sun, 10 Dec 2023 16:20:25 -0300 Subject: [PATCH 014/111] Change comment Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/LockstakeEngine.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index fdcd0945..1c8c27b7 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -308,7 +308,7 @@ contract LockstakeEngine { stkGov.burn(urn, wad); // Burn the whole liquidated amount of staking token address delegate_ = urnDelegates[urn]; if (delegate_ != address(0)) { - DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain NGT + DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain the GOV tokens } // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper emit OnKick(urn, wad); From c2bff63c0bde4779e9fc3850264d9a64bead40fd Mon Sep 17 00:00:00 2001 From: sunbreak Date: Tue, 12 Dec 2023 16:42:28 -0300 Subject: [PATCH 015/111] selectedFarm => urnFarms --- src/LockstakeEngine.sol | 32 ++++++++++++++++---------------- test/LockstakeEngine.t.sol | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 1c8c27b7..31cab4a4 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -63,7 +63,7 @@ contract LockstakeEngine { mapping(address => uint256) public usrAmts; // usr => urns amount mapping(address => address) public urnOwners; // urn => owner mapping(address => address) public urnDelegates; // urn => current associated delegate - mapping(address => address) public selectedFarm; // urn => current selected farm + mapping(address => address) public urnFarms; // urn => current selected farm JugLike public jug; // --- constants --- @@ -269,25 +269,25 @@ contract LockstakeEngine { function selectFarm(address urn, address farm) external urnOwner(urn) { require(farms[farm] == 1, "LockstakeEngine/non-existing-farm"); - address selectedFarmUrn = selectedFarm[urn]; - require(selectedFarmUrn == address(0) || GemLike(selectedFarmUrn).balanceOf(address(urn)) == 0, "LockstakeEngine/withdraw-first"); - selectedFarm[urn] = farm; + address urnFarm = urnFarms[urn]; + require(urnFarm == address(0) || GemLike(urnFarm).balanceOf(address(urn)) == 0, "LockstakeEngine/withdraw-first"); + urnFarms[urn] = farm; emit SelectFarm(urn, farm); } function stake(address urn, uint256 wad, uint16 ref) external urnOwner(urn) { - address selectedFarmUrn = selectedFarm[urn]; - require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm"); - require(farms[selectedFarmUrn] == 1, "LockstakeEngine/selected-farm-not-available-anymore"); - LockstakeUrn(urn).stake(selectedFarmUrn, wad, ref); - emit Stake(urn, selectedFarmUrn, wad, ref); + address urnFarm = urnFarms[urn]; + require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm"); + require(farms[urnFarm] == 1, "LockstakeEngine/selected-farm-not-available-anymore"); + LockstakeUrn(urn).stake(urnFarm, wad, ref); + emit Stake(urn, urnFarm, wad, ref); } function withdraw(address urn, uint256 wad) external urnOwner(urn) { - address selectedFarmUrn = selectedFarm[urn]; - require(selectedFarmUrn != address(0), "LockstakeEngine/missing-selected-farm"); - LockstakeUrn(urn).withdraw(selectedFarmUrn, wad); - emit Withdraw(urn, selectedFarmUrn, wad); + address urnFarm = urnFarms[urn]; + require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm"); + LockstakeUrn(urn).withdraw(urnFarm, wad); + emit Withdraw(urn, urnFarm, wad); } function getReward(address urn, address farm, address to) external urnOwner(urn) { @@ -298,11 +298,11 @@ contract LockstakeEngine { // --- liquidation callback functions --- function onKick(address urn, uint256 wad) external auth { - address selectedFarmUrn = selectedFarm[urn]; - if (selectedFarmUrn != address(0)){ + address urnFarm = urnFarms[urn]; + if (urnFarm != address(0)){ uint256 freed = GemLike(stkGov).balanceOf(address(urn)); if (wad > freed) { - LockstakeUrn(urn).withdraw(selectedFarmUrn, wad - freed); + LockstakeUrn(urn).withdraw(urnFarm, wad - freed); } } stkGov.burn(urn, wad); // Burn the whole liquidated amount of staking token diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 81eaf92e..2927572a 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -343,17 +343,17 @@ contract AllocatorVaultTest is DssTest { function testSelectFarm() public { StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkGov)); address urn = engine.open(); - assertEq(engine.selectedFarm(urn), address(0)); + assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/non-existing-farm"); engine.selectFarm(urn, address(farm)); vm.prank(pauseProxy); engine.addFarm(address(farm)); vm.expectEmit(true, true, true, true); emit SelectFarm(urn, address(farm)); engine.selectFarm(urn, address(farm)); - assertEq(engine.selectedFarm(urn), address(farm)); + assertEq(engine.urnFarms(urn), address(farm)); vm.prank(pauseProxy); engine.addFarm(address(farm2)); engine.selectFarm(urn, address(farm2)); - assertEq(engine.selectedFarm(urn), address(farm2)); + assertEq(engine.urnFarms(urn), address(farm2)); gov.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); engine.stake(urn, 100_000, 1); From c7fa3759dd67e6b1724f8e69228a9f6055d63864 Mon Sep 17 00:00:00 2001 From: sunbreak Date: Tue, 12 Dec 2023 16:54:56 -0300 Subject: [PATCH 016/111] Remove unnecessary require --- src/LockstakeEngine.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 31cab4a4..36a0326c 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -190,7 +190,6 @@ contract LockstakeEngine { function open() external returns (address urn) { bytes32 salt = keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++)); urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkGov))); - require(urn != address(0), "LockstakeEngine/urn-creation-failed"); urnOwners[urn] = msg.sender; emit Open(msg.sender, urn); } From fae51fb82c518e1b0cbb238c1a257b2feeaf9982 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:44:46 -0300 Subject: [PATCH 017/111] Allow other address to do urn management (#5) * Allow urn management to another address * Add tests * Add isUrnAuth getter * Remove README * Extend testHopeNope * Remove line Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/LockstakeEngine.sol | 64 ++++++++++++++++++++++++-------------- test/LockstakeEngine.t.sol | 60 ++++++++++++++++++++++++++++------- 2 files changed, 90 insertions(+), 34 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 36a0326c..24bbc293 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -58,13 +58,14 @@ interface JugLike { contract LockstakeEngine { // --- storage variables --- - mapping(address => uint256) public wards; // usr => 1 == access - mapping(address => uint256) public farms; // farm => 1 == whitelisted - mapping(address => uint256) public usrAmts; // usr => urns amount - mapping(address => address) public urnOwners; // urn => owner - mapping(address => address) public urnDelegates; // urn => current associated delegate - mapping(address => address) public urnFarms; // urn => current selected farm - JugLike public jug; + mapping(address => uint256) public wards; // usr => 1 == access + mapping(address => uint256) public farms; // farm => 1 == whitelisted + mapping(address => uint256) public usrAmts; // usr => urns amount + mapping(address => address) public urnOwners; // urn => owner + mapping(address => mapping(address => uint256)) public urnCan; // urn => usr => allowed (1 = yes, 0 = no) + mapping(address => address) public urnDelegates; // urn => current associated delegate + mapping(address => address) public urnFarms; // urn => current selected farm + JugLike public jug; // --- constants --- @@ -90,6 +91,8 @@ contract LockstakeEngine { event AddFarm(address farm); event DelFarm(address farm); event Open(address indexed owner, address urn); + event Hope(address indexed urn, address indexed usr); + event Nope(address indexed urn, address indexed usr); event Delegate(address indexed urn, address indexed delegate); event Lock(address indexed urn, uint256 wad); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); @@ -111,8 +114,8 @@ contract LockstakeEngine { _; } - modifier urnOwner(address urn) { - require(urnOwners[urn] == msg.sender, "LockstakeEngine/not-urn-owner"); + modifier urnAuth(address urn) { + require(_urnAuth(urn, msg.sender), "LockstakeEngine/urn-not-authorized"); _; } @@ -133,7 +136,7 @@ contract LockstakeEngine { emit Rely(msg.sender); } - // --- math --- + // --- internals --- function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { @@ -141,6 +144,10 @@ contract LockstakeEngine { } } + function _urnAuth(address urn, address usr) internal view returns (bool ok) { + ok = urnOwners[urn] == usr || urnCan[urn][usr] == 1; + } + // --- administration --- function rely(address usr) external auth { @@ -172,10 +179,7 @@ contract LockstakeEngine { // --- getters --- - function getUrn( - address owner, - uint256 index - ) external view returns (address urn) { + function getUrn(address owner, uint256 index) external view returns (address urn) { uint256 salt = uint256(keccak256(abi.encode(owner, index))); bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov))); urn = address(uint160(uint256( @@ -185,6 +189,10 @@ contract LockstakeEngine { ))); } + function isUrnAuth(address urn, address usr) external view returns (bool ok) { + ok = _urnAuth(urn, usr); + } + // --- urn/delegation functions --- function open() external returns (address urn) { @@ -194,7 +202,17 @@ contract LockstakeEngine { emit Open(msg.sender, urn); } - function lock(address urn, uint256 wad) external urnOwner(urn) { + function hope(address urn, address usr) external urnAuth(urn) { + urnCan[urn][usr] = 1; + emit Hope(urn, usr); + } + + function nope(address urn, address usr) external urnAuth(urn) { + urnCan[urn][usr] = 0; + emit Nope(urn, usr); + } + + function lock(address urn, uint256 wad) external urnAuth(urn) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); gov.transferFrom(msg.sender, address(this), wad); address delegate_ = urnDelegates[urn]; @@ -210,7 +228,7 @@ contract LockstakeEngine { emit Lock(urn, wad); } - function free(address urn, address to, uint256 wad) external urnOwner(urn) { + function free(address urn, address to, uint256 wad) external urnAuth(urn) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); stkGov.burn(urn, wad); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); @@ -225,7 +243,7 @@ contract LockstakeEngine { emit Free(urn, to, wad, burn); } - function delegate(address urn, address delegate_) external urnOwner(urn) { + function delegate(address urn, address delegate_) external urnAuth(urn) { require(delegate_ == address(0) || delegateFactory.isDelegate(delegate_) == 1, "LockstakeEngine/not-valid-delegate"); address prevDelegate = urnDelegates[urn]; require(prevDelegate != delegate_, "LockstakeEngine/same-delegate"); @@ -245,7 +263,7 @@ contract LockstakeEngine { // --- loan functions --- - function draw(address urn, uint256 wad) external urnOwner(urn) { + function draw(address urn, uint256 wad) external urnAuth(urn) { uint256 rate = jug.drip(ilk); uint256 dart = _divup(wad * RAY, rate); require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); @@ -254,7 +272,7 @@ contract LockstakeEngine { emit Draw(urn, wad); } - function wipe(address urn, uint256 wad) external urnOwner(urn) { + function wipe(address urn, uint256 wad) external urnAuth(urn) { nst.transferFrom(msg.sender, address(this), wad); nstJoin.join(address(this), wad); uint256 rate = jug.drip(ilk); @@ -266,7 +284,7 @@ contract LockstakeEngine { // --- staking functions --- - function selectFarm(address urn, address farm) external urnOwner(urn) { + function selectFarm(address urn, address farm) external urnAuth(urn) { require(farms[farm] == 1, "LockstakeEngine/non-existing-farm"); address urnFarm = urnFarms[urn]; require(urnFarm == address(0) || GemLike(urnFarm).balanceOf(address(urn)) == 0, "LockstakeEngine/withdraw-first"); @@ -274,7 +292,7 @@ contract LockstakeEngine { emit SelectFarm(urn, farm); } - function stake(address urn, uint256 wad, uint16 ref) external urnOwner(urn) { + function stake(address urn, uint256 wad, uint16 ref) external urnAuth(urn) { address urnFarm = urnFarms[urn]; require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm"); require(farms[urnFarm] == 1, "LockstakeEngine/selected-farm-not-available-anymore"); @@ -282,14 +300,14 @@ contract LockstakeEngine { emit Stake(urn, urnFarm, wad, ref); } - function withdraw(address urn, uint256 wad) external urnOwner(urn) { + function withdraw(address urn, uint256 wad) external urnAuth(urn) { address urnFarm = urnFarms[urn]; require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm"); LockstakeUrn(urn).withdraw(urnFarm, wad); emit Withdraw(urn, urnFarm, wad); } - function getReward(address urn, address farm, address to) external urnOwner(urn) { + function getReward(address urn, address farm, address to) external urnAuth(urn) { uint256 amt = LockstakeUrn(urn).getReward(farm, to); emit GetReward(urn, farm, to, amt); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 2927572a..1e205ff8 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -169,19 +169,21 @@ contract AllocatorVaultTest is DssTest { checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); - bytes4[] memory urnOwnersMethods = new bytes4[](9); - urnOwnersMethods[0] = engine.lock.selector; - urnOwnersMethods[1] = engine.free.selector; - urnOwnersMethods[2] = engine.delegate.selector; - urnOwnersMethods[3] = engine.draw.selector; - urnOwnersMethods[4] = engine.wipe.selector; - urnOwnersMethods[5] = engine.selectFarm.selector; - urnOwnersMethods[6] = engine.stake.selector; - urnOwnersMethods[7] = engine.withdraw.selector; - urnOwnersMethods[8] = engine.getReward.selector; + bytes4[] memory urnOwnersMethods = new bytes4[](11); + urnOwnersMethods[0] = engine.hope.selector; + urnOwnersMethods[1] = engine.nope.selector; + urnOwnersMethods[2] = engine.lock.selector; + urnOwnersMethods[3] = engine.free.selector; + urnOwnersMethods[4] = engine.delegate.selector; + urnOwnersMethods[5] = engine.draw.selector; + urnOwnersMethods[6] = engine.wipe.selector; + urnOwnersMethods[7] = engine.selectFarm.selector; + urnOwnersMethods[8] = engine.stake.selector; + urnOwnersMethods[9] = engine.withdraw.selector; + urnOwnersMethods[10] = engine.getReward.selector; vm.startPrank(address(0xBEEF)); - checkModifier(address(engine), "LockstakeEngine/not-urn-owner", urnOwnersMethods); + checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); vm.stopPrank(); } @@ -214,6 +216,42 @@ contract AllocatorVaultTest is DssTest { assertEq(engine.usrAmts(address(this)), 3); } + function testHopeNope() public { + address urnOwner = address(123); + address urnAuthed = address(456); + vm.prank(pauseProxy); engine.addFarm(address(farm)); + gov.transfer(urnAuthed, 100_000 * 10**18); + vm.startPrank(urnOwner); + address urn = engine.open(); + assertTrue(engine.isUrnAuth(urn, urnOwner)); + assertTrue(!engine.isUrnAuth(urn, urnAuthed)); + assertEq(engine.urnCan(urn, urnAuthed), 0); + engine.hope(urn, urnAuthed); + assertEq(engine.urnCan(urn, urnAuthed), 1); + assertTrue(engine.isUrnAuth(urn, urnAuthed)); + vm.stopPrank(); + vm.startPrank(urnAuthed); + engine.hope(urn, address(789)); + gov.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18); + assertEq(_ink(ilk, urn), 100_000 * 10**18); + engine.free(urn, address(this), 50_000 * 10**18); + assertEq(_ink(ilk, urn), 50_000 * 10**18); + engine.delegate(urn, voterDelegate); + assertEq(engine.urnDelegates(urn), voterDelegate); + engine.draw(urn, 1); + nst.approve(address(engine), 1); + engine.wipe(urn, 1); + engine.selectFarm(urn, address(farm)); + engine.stake(urn, 1, 0); + engine.withdraw(urn, 1); + engine.getReward(urn, address(farm), address(0)); + engine.nope(urn, urnAuthed); + assertEq(engine.urnCan(urn, urnAuthed), 0); + assertTrue(!engine.isUrnAuth(urn, urnAuthed)); + vm.stopPrank(); + } + function _testLockFree(bool withDelegate) internal { uint256 initialSupply = gov.totalSupply(); assertEq(gov.balanceOf(address(this)), 100_000 * 10**18); From 87b9a6818b4143b47d5715dae90add5bb8b723f1 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 20 Dec 2023 07:45:26 -0300 Subject: [PATCH 018/111] Test name fixes Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- test/LockstakeClipper.t.sol | 2 +- test/LockstakeEngine.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index 7f85409b..5c1c50d9 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -171,7 +171,7 @@ interface VowLike { } -contract ClipperTest is DssTest { +contract LockstakeClipperTest is DssTest { address pauseProxy; VatLike vat; DogLike dog; diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 1e205ff8..103fba20 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -54,7 +54,7 @@ interface CalcLike { function file(bytes32, uint256) external; } -contract AllocatorVaultTest is DssTest { +contract LockstakeEngineTest is DssTest { using stdStorage for StdStorage; address public pauseProxy; From bc63780074ecaf8a3c542d547b7bbff9ffce75f1 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 20 Dec 2023 19:08:56 +0200 Subject: [PATCH 019/111] Add multicall and converter in LockstakeEngine (#7) * Support multicall for LockstakeEngine * Sketch use of converter inside LockstakeEngine * Send out ngt from converter, separate events * Add index parameter to open() * Approve tokens to mkrNgt in the constructor * gov => mkr, stkGov => stkMkr * public => external Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> * Change revert message * new line at end of file Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> * Put ngt functions after regular ones * Change order of events as well Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> * ngt tests and testing open with wrong index --------- Co-authored-by: sunbreak Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> --- src/LockstakeEngine.sol | 100 +++++--- src/Multicall.sol | 26 +++ test/LockstakeEngine.t.sol | 451 ++++++++++++++++++++++--------------- test/mocks/MkrNgtMock.sol | 32 +++ 4 files changed, 399 insertions(+), 210 deletions(-) create mode 100644 src/Multicall.sol create mode 100644 test/mocks/MkrNgtMock.sol diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 24bbc293..922b550e 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -17,6 +17,7 @@ pragma solidity ^0.8.16; import { LockstakeUrn } from "src/LockstakeUrn.sol"; +import { Multicall } from "src/Multicall.sol"; interface DelegateFactoryLike { function gov() external view returns (GemLike); @@ -55,7 +56,14 @@ interface JugLike { function drip(bytes32) external returns (uint256); } -contract LockstakeEngine { +interface MkrNgtLike { + function rate() external view returns (uint256); + function ngt() external view returns (address); + function ngtToMkr(address, uint256) external; + function mkrToNgt(address, uint256) external; +} + +contract LockstakeEngine is Multicall { // --- storage variables --- mapping(address => uint256) public wards; // usr => 1 == access @@ -79,9 +87,12 @@ contract LockstakeEngine { NstJoinLike immutable public nstJoin; GemLike immutable public nst; bytes32 immutable public ilk; - GemLike immutable public gov; - GemLike immutable public stkGov; + GemLike immutable public mkr; + GemLike immutable public stkMkr; uint256 immutable public fee; + MkrNgtLike immutable public mkrNgt; + GemLike immutable public ngt; + uint256 immutable public mkrNgtRate; // --- events --- @@ -95,7 +106,9 @@ contract LockstakeEngine { event Nope(address indexed urn, address indexed usr); event Delegate(address indexed urn, address indexed delegate); event Lock(address indexed urn, uint256 wad); + event LockNgt(address indexed urn, uint256 ngtWad); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); + event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); event SelectFarm(address indexed urn, address farm); @@ -121,17 +134,23 @@ contract LockstakeEngine { // --- constructor --- - constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkGov_, uint256 fee_) { + constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkMkr_, uint256 fee_, address mkrNgt_) { delegateFactory = DelegateFactoryLike(delegateFactory_); nstJoin = NstJoinLike(nstJoin_); vat = nstJoin.vat(); nst = nstJoin.nst(); ilk = ilk_; - gov = delegateFactory.gov(); - stkGov = GemLike(stkGov_); + mkr = delegateFactory.gov(); + stkMkr = GemLike(stkMkr_); fee = fee_; nst.approve(nstJoin_, type(uint256).max); vat.hope(nstJoin_); + mkrNgt = MkrNgtLike(mkrNgt_); + ngt = GemLike(mkrNgt.ngt()); + ngt.approve(address(mkrNgt), type(uint256).max); + mkr.approve(address(mkrNgt), type(uint256).max); + mkrNgtRate = mkrNgt.rate(); + wards[msg.sender] = 1; emit Rely(msg.sender); } @@ -181,7 +200,7 @@ contract LockstakeEngine { function getUrn(address owner, uint256 index) external view returns (address urn) { uint256 salt = uint256(keccak256(abi.encode(owner, index))); - bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkGov))); + bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkMkr))); urn = address(uint160(uint256( keccak256( abi.encodePacked(bytes1(0xff), address(this), salt, codeHash) @@ -192,12 +211,12 @@ contract LockstakeEngine { function isUrnAuth(address urn, address usr) external view returns (bool ok) { ok = _urnAuth(urn, usr); } - // --- urn/delegation functions --- - function open() external returns (address urn) { - bytes32 salt = keccak256(abi.encode(msg.sender, usrAmts[msg.sender]++)); - urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkGov))); + function open(uint256 index) external returns (address urn) { + require(index == usrAmts[msg.sender]++, "LockstakeEngine/wrong-urn-index"); + bytes32 salt = keccak256(abi.encode(msg.sender, index)); + urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkMkr))); urnOwners[urn] = msg.sender; emit Open(msg.sender, urn); } @@ -213,24 +232,48 @@ contract LockstakeEngine { } function lock(address urn, uint256 wad) external urnAuth(urn) { + mkr.transferFrom(msg.sender, address(this), wad); + _lock(urn, wad); + emit Lock(urn, wad); + } + + function lockNgt(address urn, uint256 ngtWad) external urnAuth(urn) { + ngt.transferFrom(msg.sender, address(this), ngtWad); + mkrNgt.ngtToMkr(address(this), ngtWad); + _lock(urn, ngtWad / mkrNgtRate); + emit LockNgt(urn, ngtWad); + } + + function _lock(address urn, uint256 wad) internal { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); - gov.transferFrom(msg.sender, address(this), wad); address delegate_ = urnDelegates[urn]; if (delegate_ != address(0)) { - gov.approve(address(delegate_), wad); + mkr.approve(address(delegate_), wad); DelegateLike(delegate_).lock(wad); } // TODO: define if we want an internal registry to register how much is locked per user, - // the vat.slip and stkGov balance act already as a registry so probably not needed an extra one + // the vat.slip and stkMkr balance act already as a registry so probably not needed an extra one vat.slip(ilk, urn, int256(wad)); vat.frob(ilk, urn, urn, address(0), int256(wad), 0); - stkGov.mint(urn, wad); - emit Lock(urn, wad); + stkMkr.mint(urn, wad); } function free(address urn, address to, uint256 wad) external urnAuth(urn) { + uint256 freed = _free(urn, wad); + mkr.transfer(to, freed); + emit Free(urn, to, wad, wad - freed); + } + + function freeNgt(address urn, address to, uint256 ngtWad) external urnAuth(urn) { + uint256 wad = ngtWad / mkrNgtRate; + uint256 freed = _free(urn, wad); + mkrNgt.mkrToNgt(to, freed); + emit FreeNgt(urn, to, ngtWad, wad - freed); + } + + function _free(address urn, uint256 wad) internal returns (uint256 freed) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); - stkGov.burn(urn, wad); + stkMkr.burn(urn, wad); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); address delegate_ = urnDelegates[urn]; @@ -238,9 +281,8 @@ contract LockstakeEngine { DelegateLike(delegate_).free(wad); } uint256 burn = wad * fee / WAD; - gov.transfer(to, wad - burn); - gov.burn(address(this), burn); - emit Free(urn, to, wad, burn); + mkr.burn(address(this), burn); + freed = wad - burn; } function delegate(address urn, address delegate_) external urnAuth(urn) { @@ -253,7 +295,7 @@ contract LockstakeEngine { DelegateLike(prevDelegate).free(wad); } if (delegate_ != address(0)) { - gov.approve(address(delegate_), wad); + mkr.approve(address(delegate_), wad); DelegateLike(delegate_).lock(wad); } } @@ -317,22 +359,22 @@ contract LockstakeEngine { function onKick(address urn, uint256 wad) external auth { address urnFarm = urnFarms[urn]; if (urnFarm != address(0)){ - uint256 freed = GemLike(stkGov).balanceOf(address(urn)); + uint256 freed = GemLike(stkMkr).balanceOf(address(urn)); if (wad > freed) { LockstakeUrn(urn).withdraw(urnFarm, wad - freed); } } - stkGov.burn(urn, wad); // Burn the whole liquidated amount of staking token + stkMkr.burn(urn, wad); // Burn the whole liquidated amount of staking token address delegate_ = urnDelegates[urn]; if (delegate_ != address(0)) { - DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain the GOV tokens + DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain the MKR tokens } // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper emit OnKick(urn, wad); } function onTake(address urn, address who, uint256 wad) external auth { - gov.transfer(who, wad); // Free GOV to the auction buyer + mkr.transfer(who, wad); // Free MKR to the auction buyer emit OnTake(urn, who, wad); } @@ -344,22 +386,22 @@ contract LockstakeEngine { } else { unchecked { left = left - burn; } } - gov.burn(address(this), burn); // Burn GOV + mkr.burn(address(this), burn); // Burn MKR if (left > 0) { address delegate_ = urnDelegates[urn]; if (delegate_ != address(0)) { - gov.approve(address(delegate_), left); + mkr.approve(address(delegate_), left); DelegateLike(delegate_).lock(left); } vat.slip(ilk, urn, int256(left)); vat.frob(ilk, urn, urn, address(0), int256(left), 0); - stkGov.mint(urn, left); + stkMkr.mint(urn, left); } emit OnTakeLeftovers(urn, tot, left, burn); } function onYank(address urn, uint256 wad) external auth { - gov.burn(address(this), wad); + mkr.burn(address(this), wad); emit OnYank(urn, wad); } } diff --git a/src/Multicall.sol b/src/Multicall.sol new file mode 100644 index 00000000..fbb38114 --- /dev/null +++ b/src/Multicall.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on https://github.com/Uniswap/v3-periphery/blob/697c2474757ea89fec12a4e6db16a574fe259610/contracts/base/Multicall.sol + +pragma solidity ^0.8.16; + +// Enables calling multiple methods in a single call to the contract +abstract contract Multicall { + function multicall(bytes[] calldata data) external returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 103fba20..88428972 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -11,6 +11,7 @@ import { GemMock } from "test/mocks/GemMock.sol"; import { NstMock } from "test/mocks/NstMock.sol"; import { NstJoinMock } from "test/mocks/NstJoinMock.sol"; import { StakingRewardsMock } from "test/mocks/StakingRewardsMock.sol"; +import { MkrNgtMock } from "test/mocks/MkrNgtMock.sol"; interface ChainlogLike { function getAddress(bytes32) external view returns (address); @@ -61,7 +62,7 @@ contract LockstakeEngineTest is DssTest { address public vat; address public spot; address public dog; - GemMock public gov; + GemMock public mkr; address public jug; LockstakeEngine public engine; LockstakeClipper public clip; @@ -69,9 +70,11 @@ contract LockstakeEngineTest is DssTest { DelegateFactoryMock public delFactory; NstMock public nst; NstJoinMock public nstJoin; - GemMock public stkGov; + GemMock public stkMkr; GemMock public rTok; StakingRewardsMock public farm; + MkrNgtMock public mkrNgt; + GemMock public ngt; bytes32 public ilk = "LSE"; address public voter; address public voterDelegate; @@ -82,7 +85,9 @@ contract LockstakeEngineTest is DssTest { event DelFarm(address farm); event Open(address indexed owner, address urn); event Lock(address indexed urn, uint256 wad); + event LockNgt(address indexed urn, uint256 ngtWad); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); + event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); event Delegate(address indexed urn, address indexed delegate_); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); @@ -107,21 +112,23 @@ contract LockstakeEngineTest is DssTest { vat = ChainlogLike(LOG).getAddress("MCD_VAT"); spot = ChainlogLike(LOG).getAddress("MCD_SPOT"); dog = ChainlogLike(LOG).getAddress("MCD_DOG"); - gov = GemMock(ChainlogLike(LOG).getAddress("MCD_GOV")); + mkr = new GemMock(0); jug = ChainlogLike(LOG).getAddress("MCD_JUG"); nst = new NstMock(); nstJoin = new NstJoinMock(vat, address(nst)); - stkGov = new GemMock(0); + stkMkr = new GemMock(0); rTok = new GemMock(0); - farm = new StakingRewardsMock(address(rTok), address(stkGov)); + farm = new StakingRewardsMock(address(rTok), address(stkMkr)); + ngt = new GemMock(0); + mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 25_000); pip = new PipMock(); - delFactory = new DelegateFactoryMock(address(gov)); + delFactory = new DelegateFactoryMock(address(mkr)); voter = address(123); vm.prank(voter); voterDelegate = delFactory.create(); vm.startPrank(pauseProxy); - engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(stkGov), 15 * WAD / 100); + engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(stkMkr), 15 * WAD / 100, address(mkrNgt)); engine.file("jug", jug); VatLike(vat).rely(address(engine)); VatLike(vat).init(ilk); @@ -129,12 +136,13 @@ contract LockstakeEngineTest is DssTest { JugLike(jug).file(ilk, "duty", 1001 * 10**27 / 1000); SpotterLike(spot).file(ilk, "pip", address(pip)); SpotterLike(spot).file(ilk, "mat", 3 * 10**27); // 300% coll ratio - pip.setPrice(0.1 * 10**18); // 1 GOV = 0.1 USD + pip.setPrice(1500 * 10**18); // 1 MKR = 1500 USD SpotterLike(spot).poke(ilk); VatLike(vat).file(ilk, "line", 1_000_000 * 10**45); vm.stopPrank(); - deal(address(gov), address(this), 100_000 * 10**18, true); + deal(address(mkr), address(this), 100_000 * 10**18, true); + deal(address(ngt), address(this), 100_000 * 25_000 * 10**18, true); // Add some existing DAI assigned to nstJoin to avoid a particular error stdstore.target(address(vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); @@ -169,18 +177,20 @@ contract LockstakeEngineTest is DssTest { checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); - bytes4[] memory urnOwnersMethods = new bytes4[](11); + bytes4[] memory urnOwnersMethods = new bytes4[](13); urnOwnersMethods[0] = engine.hope.selector; urnOwnersMethods[1] = engine.nope.selector; urnOwnersMethods[2] = engine.lock.selector; - urnOwnersMethods[3] = engine.free.selector; - urnOwnersMethods[4] = engine.delegate.selector; - urnOwnersMethods[5] = engine.draw.selector; - urnOwnersMethods[6] = engine.wipe.selector; - urnOwnersMethods[7] = engine.selectFarm.selector; - urnOwnersMethods[8] = engine.stake.selector; - urnOwnersMethods[9] = engine.withdraw.selector; - urnOwnersMethods[10] = engine.getReward.selector; + urnOwnersMethods[3] = engine.lockNgt.selector; + urnOwnersMethods[4] = engine.free.selector; + urnOwnersMethods[5] = engine.freeNgt.selector; + urnOwnersMethods[6] = engine.delegate.selector; + urnOwnersMethods[7] = engine.draw.selector; + urnOwnersMethods[8] = engine.wipe.selector; + urnOwnersMethods[9] = engine.selectFarm.selector; + urnOwnersMethods[10] = engine.stake.selector; + urnOwnersMethods[11] = engine.withdraw.selector; + urnOwnersMethods[12] = engine.getReward.selector; vm.startPrank(address(0xBEEF)); checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); @@ -206,13 +216,17 @@ contract LockstakeEngineTest is DssTest { function testOpen() public { assertEq(engine.usrAmts(address(this)), 0); address urn = engine.getUrn(address(this), 0); + vm.expectRevert("LockstakeEngine/wrong-urn-index"); + engine.open(1); vm.expectEmit(true, true, true, true); emit Open(address(this), urn); - assertEq(engine.open(), urn); + assertEq(engine.open(0), urn); assertEq(engine.usrAmts(address(this)), 1); - assertEq(engine.getUrn(address(this), 1), engine.open()); + vm.expectRevert("LockstakeEngine/wrong-urn-index"); + engine.open(2); + assertEq(engine.getUrn(address(this), 1), engine.open(1)); assertEq(engine.usrAmts(address(this)), 2); - assertEq(engine.getUrn(address(this), 2), engine.open()); + assertEq(engine.getUrn(address(this), 2), engine.open(2)); assertEq(engine.usrAmts(address(this)), 3); } @@ -220,9 +234,10 @@ contract LockstakeEngineTest is DssTest { address urnOwner = address(123); address urnAuthed = address(456); vm.prank(pauseProxy); engine.addFarm(address(farm)); - gov.transfer(urnAuthed, 100_000 * 10**18); + mkr.transfer(urnAuthed, 100_000 * 10**18); + ngt.transfer(urnAuthed, 100_000 * 25_000 * 10**18); vm.startPrank(urnOwner); - address urn = engine.open(); + address urn = engine.open(0); assertTrue(engine.isUrnAuth(urn, urnOwner)); assertTrue(!engine.isUrnAuth(urn, urnAuthed)); assertEq(engine.urnCan(urn, urnAuthed), 0); @@ -232,11 +247,16 @@ contract LockstakeEngineTest is DssTest { vm.stopPrank(); vm.startPrank(urnAuthed); engine.hope(urn, address(789)); - gov.approve(address(engine), 100_000 * 10**18); + mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.free(urn, address(this), 50_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); + ngt.approve(address(engine), 100_000 * 25_000 * 10**18); + engine.lockNgt(urn, 100_000 * 25_000 * 10**18); + assertEq(_ink(ilk, urn), 150_000 * 10**18); + engine.freeNgt(urn, address(this), 50_000 * 25_000 * 10**18); + assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.delegate(urn, voterDelegate); assertEq(engine.urnDelegates(urn), voterDelegate); engine.draw(urn, 1); @@ -253,51 +273,54 @@ contract LockstakeEngineTest is DssTest { } function _testLockFree(bool withDelegate) internal { - uint256 initialSupply = gov.totalSupply(); - assertEq(gov.balanceOf(address(this)), 100_000 * 10**18); - address urn = engine.open(); + uint256 initialSupply = mkr.totalSupply(); + address urn = engine.open(0); + deal(address(mkr), address(this), uint256(type(int256).max) + 1); // deal mkr to allow reaching the overflow revert + mkr.approve(address(engine), uint256(type(int256).max) + 1); vm.expectRevert("LockstakeEngine/wad-overflow"); engine.lock(urn, uint256(type(int256).max) + 1); + deal(address(mkr), address(this), 100_000 * 10**18); // back to normal mkr balance and allowance + mkr.approve(address(engine), 100_000 * 10**18); vm.expectRevert("LockstakeEngine/wad-overflow"); engine.free(urn, address(this), uint256(type(int256).max) + 1); if (withDelegate) { engine.delegate(urn, voterDelegate); } assertEq(_ink(ilk, urn), 0); - assertEq(stkGov.balanceOf(urn), 0); - gov.approve(address(engine), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(urn), 0); + mkr.approve(address(engine), 100_000 * 10**18); vm.expectEmit(true, true, true, true); emit Lock(urn, 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); - assertEq(stkGov.balanceOf(urn), 100_000 * 10**18); - assertEq(gov.balanceOf(address(this)), 0); + assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(this)), 0); if (withDelegate) { - assertEq(gov.balanceOf(address(engine)), 0); - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(gov.totalSupply(), initialSupply); + assertEq(mkr.totalSupply(), initialSupply); vm.expectEmit(true, true, true, true); emit Free(urn, address(this), 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); engine.free(urn, address(this), 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); - assertEq(stkGov.balanceOf(urn), 60_000 * 10**18); - assertEq(gov.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); + assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); vm.expectEmit(true, true, true, true); emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 15 / 100); engine.free(urn, address(123), 10_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); - assertEq(stkGov.balanceOf(urn), 50_000 * 10**18); - assertEq(gov.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100); + assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100); if (withDelegate) { - assertEq(gov.balanceOf(address(engine)), 0); - assertEq(gov.balanceOf(address(voterDelegate)), 50_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 50_000 * 10**18); } else { - assertEq(gov.balanceOf(address(engine)), 50_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } - assertEq(gov.totalSupply(), initialSupply - 50_000 * 10**18 * 15 / 100); + assertEq(mkr.totalSupply(), initialSupply - 50_000 * 10**18 * 15 / 100); } function testLockFreeNoDelegate() public { @@ -308,8 +331,60 @@ contract LockstakeEngineTest is DssTest { _testLockFree(true); } + function _testLockFreeNgt(bool withDelegate) internal { + uint256 initialSupply = ngt.totalSupply(); + address urn = engine.open(0); + // Note: wad-overflow cannot be reached for lockNgt and freeNgt as we these functions will first divide by the rate + if (withDelegate) { + engine.delegate(urn, voterDelegate); + } + assertEq(_ink(ilk, urn), 0); + assertEq(stkMkr.balanceOf(urn), 0); + ngt.approve(address(engine), 100_000 * 25_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit LockNgt(urn, 100_000 * 25_000 * 10**18); + engine.lockNgt(urn, 100_000 * 25_000 * 10**18); + assertEq(_ink(ilk, urn), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + assertEq(ngt.balanceOf(address(this)), 0); + if (withDelegate) { + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + } else { + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + } + assertEq(ngt.totalSupply(), initialSupply - 100_000 * 25_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit FreeNgt(urn, address(this), 40_000 * 25_000 * 10**18, 40_000 * 10**18 * 15 / 100); + engine.freeNgt(urn, address(this), 40_000 * 25_000 * 10**18); + assertEq(_ink(ilk, urn), 60_000 * 10**18); + assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + assertEq(ngt.balanceOf(address(this)), 40_000 * 25_000 * 10**18 - 40_000 * 25_000 * 10**18 * 15 / 100); + vm.expectEmit(true, true, true, true); + emit FreeNgt(urn, address(123), 10_000 * 25_000 * 10**18, 10_000 * 10**18 * 15 / 100); + engine.freeNgt(urn, address(123), 10_000 * 25_000 * 10**18); + assertEq(_ink(ilk, urn), 50_000 * 10**18); + assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + assertEq(ngt.balanceOf(address(123)), 10_000 * 25_000 * 10**18 - 10_000 * 25_000 * 10**18 * 15 / 100); + if (withDelegate) { + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 50_000 * 10**18); + } else { + assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); + } + assertEq(ngt.totalSupply(), initialSupply - (100_000 - 50_000) * 25_000 * 10**18 - 50_000 * 25_000 * 10**18 * 15 / 100); + } + + function testLockFreeNgtNoDelegate() public { + _testLockFreeNgt(false); + } + + function testLockFreeNgtWithDelegate() public { + _testLockFreeNgt(true); + } + function testDelegate() public { - address urn = engine.open(); + address urn = engine.open(0); vm.expectRevert("LockstakeEngine/not-valid-delegate"); engine.delegate(urn, address(111)); engine.delegate(urn, voterDelegate); @@ -317,33 +392,33 @@ contract LockstakeEngineTest is DssTest { engine.delegate(urn, voterDelegate); assertEq(engine.urnDelegates(urn), voterDelegate); vm.prank(address(888)); address voterDelegate2 = delFactory.create(); - gov.approve(address(engine), 100_000 * 10**18); + mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(DelegateMock(voterDelegate).stake(address(engine)), 100_000 * 10**18); assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); - assertEq(gov.balanceOf(voterDelegate), 100_000 * 10**18); - assertEq(gov.balanceOf(voterDelegate2), 0); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate2), 0); + assertEq(mkr.balanceOf(address(engine)), 0); engine.delegate(urn, voterDelegate2); assertEq(engine.urnDelegates(urn), voterDelegate2); assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 100_000 * 10**18); - assertEq(gov.balanceOf(voterDelegate), 0); - assertEq(gov.balanceOf(voterDelegate2), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voterDelegate2), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); engine.delegate(urn, address(0)); assertEq(engine.urnDelegates(urn), address(0)); assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); - assertEq(gov.balanceOf(voterDelegate), 0); - assertEq(gov.balanceOf(voterDelegate2), 0); - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voterDelegate2), 0); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } function testDrawWipe() public { - deal(address(gov), address(this), 100_000 * 10**18, true); - address urn = engine.open(); - gov.approve(address(engine), 100_000 * 10**18); + deal(address(mkr), address(this), 100_000 * 10**18, true); + address urn = engine.open(0); + mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); assertEq(_art(ilk, urn), 0); vm.expectEmit(true, true, true, true); @@ -379,8 +454,8 @@ contract LockstakeEngineTest is DssTest { } function testSelectFarm() public { - StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkGov)); - address urn = engine.open(); + StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkMkr)); + address urn = engine.open(0); assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/non-existing-farm"); engine.selectFarm(urn, address(farm)); @@ -392,7 +467,7 @@ contract LockstakeEngineTest is DssTest { vm.prank(pauseProxy); engine.addFarm(address(farm2)); engine.selectFarm(urn, address(farm2)); assertEq(engine.urnFarms(urn), address(farm2)); - gov.approve(address(engine), 100_000 * 10**18); + mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); engine.stake(urn, 100_000, 1); vm.expectRevert("LockstakeEngine/withdraw-first"); @@ -403,22 +478,22 @@ contract LockstakeEngineTest is DssTest { function testStakeWithdraw() public { vm.prank(pauseProxy); engine.addFarm(address(farm)); - address urn = engine.open(); - gov.approve(address(engine), 100_000 * 10**18); + address urn = engine.open(0); + mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); vm.expectRevert("LockstakeEngine/missing-selected-farm"); engine.stake(urn, 100_000, 1); vm.expectRevert("LockstakeEngine/missing-selected-farm"); engine.withdraw(urn, 0); engine.selectFarm(urn, address(farm)); - assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 0); + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); vm.expectEmit(true, true, true, true); emit Stake(urn, address(farm), 60_000 * 10**18, 1); engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 60_000 * 10**18); vm.prank(pauseProxy); engine.delFarm(address(farm)); vm.expectRevert("LockstakeEngine/selected-farm-not-available-anymore"); @@ -426,14 +501,28 @@ contract LockstakeEngineTest is DssTest { vm.expectEmit(true, true, true, true); emit Withdraw(urn, address(farm), 15_000 * 10**18); engine.withdraw(urn, 15_000 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), 55_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 45_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 55_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 45_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 45_000 * 10**18); } + function testOpenLockStakeMulticall() public { + vm.prank(pauseProxy); engine.addFarm(address(farm)); + mkr.approve(address(engine), 100_000 * 10**18); + + address urn = engine.getUrn(address(this), 0); + + bytes[] memory callsToExecute = new bytes[](4); + callsToExecute[0] = abi.encodeWithSignature("open(uint256)", 0); + callsToExecute[1] = abi.encodeWithSignature("lock(address,uint256)", urn, 100_000 * 10**18); + callsToExecute[2] = abi.encodeWithSignature("selectFarm(address,address)", urn, address(farm)); + callsToExecute[3] = abi.encodeWithSignature("stake(address,uint256,uint16)", urn, 100_000 * 10**18, 1); + engine.multicall(callsToExecute); + } + function testGetReward() public { vm.prank(pauseProxy); engine.addFarm(address(farm)); - address urn = engine.open(); + address urn = engine.open(0); farm.setReward(address(urn), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 0); vm.expectEmit(true, true, true, true); @@ -464,11 +553,11 @@ contract LockstakeEngineTest is DssTest { DogLike(dog).file(ilk, "hole", 10_000 * 10**45); vm.stopPrank(); - urn = engine.open(); + urn = engine.open(0); if (withDelegate) { engine.delegate(urn, voterDelegate); } - gov.approve(address(engine), 100_000 * 10**18); + mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18); engine.draw(urn, 2_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); @@ -487,13 +576,13 @@ contract LockstakeEngineTest is DssTest { address urn = _clipperSetUp(withDelegate); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -512,11 +601,11 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); } function testOnKickFullNoStakedNoDelegate() public { @@ -532,7 +621,7 @@ contract LockstakeEngineTest is DssTest { vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -551,13 +640,13 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(mkr.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 25_000 * 10**18); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 75_000 * 10**18); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); } function testOnKickPartialNoStakedNoDelegate() public { @@ -573,18 +662,18 @@ contract LockstakeEngineTest is DssTest { engine.selectFarm(urn, address(farm)); engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -603,12 +692,12 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.balanceOf(address(farm)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); } function testOnKickFullStakedPartialNoDelegate() public { @@ -624,12 +713,12 @@ contract LockstakeEngineTest is DssTest { engine.selectFarm(urn, address(farm)); engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkGov.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -648,14 +737,14 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(mkr.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 25_000 * 10**18); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 15_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 60_000 * 10**18); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 15_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); } function testOnKickPartialStakedPartialNoWithdrawNoDelegate() public { @@ -671,12 +760,12 @@ contract LockstakeEngineTest is DssTest { engine.selectFarm(urn, address(farm)); engine.stake(urn, 80_000 * 10**18, 1); - assertEq(stkGov.balanceOf(address(urn)), 20_000 * 10**18); - assertEq(stkGov.balanceOf(address(farm)), 80_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 20_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 80_000 * 10**18); vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -695,14 +784,14 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(mkr.balanceOf(address(voterDelegate)), 75_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 25_000 * 10**18); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.balanceOf(address(farm)), 75_000 * 10**18); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 25_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 75_000 * 10**18); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); } function testOnKickPartialStakedPartialWithdrawNoDelegate() public { @@ -717,15 +806,15 @@ contract LockstakeEngineTest is DssTest { address urn = _clipperSetUp(withDelegate); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); - uint256 govInitialSupply = gov.totalSupply(); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + uint256 mkrInitialSupply = mkr.totalSupply(); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); @@ -746,19 +835,19 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(voterDelegate)), 0); - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); address buyer = address(888); vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); - assertEq(gov.balanceOf(buyer), 0); + assertEq(mkr.balanceOf(buyer), 0); vm.prank(buyer); clip.take(id, 20_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(gov.balanceOf(buyer), 20_000 * 10**18); + assertEq(mkr.balanceOf(buyer), 20_000 * 10**18); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); assertEq(sale.pos, 0); @@ -774,14 +863,14 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 80_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(engine)), 80_000 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 80_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.prank(buyer); clip.take(id, 12_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(gov.balanceOf(buyer), 32_000 * 10**18); + assertEq(mkr.balanceOf(buyer), 32_000 * 10**18); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); assertEq(sale.pos, 0); @@ -797,14 +886,14 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 0); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(gov.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(mkr.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); } - assertEq(gov.totalSupply(), govInitialSupply - 32_000 * 0.15 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 32_000 * 1.15 * 10**18); + assertEq(mkr.totalSupply(), mkrInitialSupply - 32_000 * 0.15 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 1.15 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } @@ -820,15 +909,15 @@ contract LockstakeEngineTest is DssTest { address urn = _clipperSetUp(withDelegate); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); - uint256 govInitialSupply = gov.totalSupply(); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + uint256 mkrInitialSupply = mkr.totalSupply(); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); @@ -849,32 +938,32 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 65); // Time passes to let the auction price to crash address buyer = address(888); vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); - assertEq(gov.balanceOf(buyer), 0); + assertEq(mkr.balanceOf(buyer), 0); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(gov.balanceOf(buyer), 91428571428571428571428); + assertEq(mkr.balanceOf(buyer), 91428571428571428571428); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(engine)), 0); - assertEq(gov.totalSupply(), govInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.totalSupply(), mkrInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } @@ -890,15 +979,15 @@ contract LockstakeEngineTest is DssTest { address urn = _clipperSetUp(withDelegate); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); - uint256 govInitialSupply = gov.totalSupply(); - uint256 stkGovInitialSupply = stkGov.totalSupply(); + uint256 mkrInitialSupply = mkr.totalSupply(); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); @@ -919,32 +1008,32 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash address buyer = address(888); vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); - assertEq(gov.balanceOf(buyer), 0); + assertEq(mkr.balanceOf(buyer), 0); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); - assertEq(gov.balanceOf(buyer), 100_000 * 10**18); + assertEq(mkr.balanceOf(buyer), 100_000 * 10**18); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(gov.balanceOf(address(engine)), 0); - assertEq(gov.totalSupply(), govInitialSupply); // Can't burn anything - assertEq(stkGov.balanceOf(address(urn)), 0); - assertEq(stkGov.totalSupply(), stkGovInitialSupply - 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.totalSupply(), mkrInitialSupply); // Can't burn anything + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); assertLt(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt } @@ -960,14 +1049,14 @@ contract LockstakeEngineTest is DssTest { address urn = _clipperSetUp(withDelegate); if (withDelegate) { - assertEq(gov.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(gov.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(gov.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkGov.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); - uint256 govInitialSupply = gov.totalSupply(); + uint256 mkrInitialSupply = mkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -992,7 +1081,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(gov.totalSupply(), govInitialSupply - 100_000 * 10**18); + assertEq(mkr.totalSupply(), mkrInitialSupply - 100_000 * 10**18); } function testOnYankNoDelegate() public { diff --git a/test/mocks/MkrNgtMock.sol b/test/mocks/MkrNgtMock.sol new file mode 100644 index 00000000..32cdfc73 --- /dev/null +++ b/test/mocks/MkrNgtMock.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +interface GemLike { + function burn(address, uint256) external; + function mint(address, uint256) external; +} + +contract MkrNgtMock { + GemLike public immutable mkr; + GemLike public immutable ngt; + uint256 public immutable rate; + + constructor(address mkr_, address ngt_, uint256 rate_) { + mkr = GemLike(mkr_); + ngt = GemLike(ngt_); + rate = rate_; + } + + function mkrToNgt(address usr, uint256 mkrAmt) external { + mkr.burn(msg.sender, mkrAmt); + uint256 ngtAmt = mkrAmt * rate; + ngt.mint(usr, ngtAmt); + } + + function ngtToMkr(address usr, uint256 ngtAmt) external { + ngt.burn(msg.sender, ngtAmt); + uint256 mkrAmt = ngtAmt / rate; + mkr.mint(usr, mkrAmt); + } +} From 27766ef42eed281f4e15a944ea381a1d1c3b13bb Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 9 Jan 2024 07:40:25 -0300 Subject: [PATCH 020/111] Stake everything or nothing (#25) * Stake with lock, withdraw with free * clean farm onTakeLeftovers * Add missing farm reset in onTakeLeftovers * Reset farm and delegate on each onKick and onTakeLeftovers * Fixes on _selectFarm * Minor move of call * Fix issue in onKick + reorder some functions * First round of adapting tests for new structure * Fix for onTakeLeftovers * Revert on selecting the same farm as existing one * Use ink also for farm operations * Use local var for ink before kick * Modify comment - correctly undelegate and unstake --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/LockstakeEngine.sol | 162 +++++++------- test/LockstakeEngine.t.sol | 433 ++++++++++++++++++------------------- 2 files changed, 291 insertions(+), 304 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 922b550e..9f02e405 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -44,7 +44,6 @@ interface NstJoinLike { } interface GemLike { - function balanceOf(address) external view returns (uint256); function approve(address, uint256) external; function transfer(address, uint256) external; function transferFrom(address, address, uint256) external; @@ -104,14 +103,14 @@ contract LockstakeEngine is Multicall { event Open(address indexed owner, address urn); event Hope(address indexed urn, address indexed usr); event Nope(address indexed urn, address indexed usr); - event Delegate(address indexed urn, address indexed delegate); - event Lock(address indexed urn, uint256 wad); - event LockNgt(address indexed urn, uint256 ngtWad); + event SelectDelegate(address indexed urn, address indexed delegate); + event SelectFarm(address indexed urn, address farm, uint16 ref); + event Lock(address indexed urn, uint256 wad, uint16 ref); + event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); - event SelectFarm(address indexed urn, address farm); event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); event Withdraw(address indexed urn, address indexed farm, uint256 wad); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); @@ -211,7 +210,8 @@ contract LockstakeEngine is Multicall { function isUrnAuth(address urn, address usr) external view returns (bool ok) { ok = _urnAuth(urn, usr); } - // --- urn/delegation functions --- + + // --- urn management functions --- function open(uint256 index) external returns (address urn) { require(index == usrAmts[msg.sender]++, "LockstakeEngine/wrong-urn-index"); @@ -231,31 +231,81 @@ contract LockstakeEngine is Multicall { emit Nope(urn, usr); } - function lock(address urn, uint256 wad) external urnAuth(urn) { + // --- delegation/staking functions --- + + function selectDelegate(address urn, address delegate) external urnAuth(urn) { + require(delegate == address(0) || delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); + address prevDelegate = urnDelegates[urn]; + require(prevDelegate != delegate, "LockstakeEngine/same-delegate"); + (uint256 ink,) = vat.urns(ilk, urn); + _selectDelegate(urn, ink, prevDelegate, delegate); + emit SelectDelegate(urn, delegate); + } + + function _selectDelegate(address urn, uint256 wad, address prevDelegate, address delegate) internal { + if (wad > 0) { + if (prevDelegate != address(0)) { + DelegateLike(prevDelegate).free(wad); + } + if (delegate != address(0)) { + mkr.approve(address(delegate), wad); + DelegateLike(delegate).lock(wad); + } + } + urnDelegates[urn] = delegate; + } + + function selectFarm(address urn, address farm, uint16 ref) external urnAuth(urn) { + require(farm == address(0) || farms[farm] == 1, "LockstakeEngine/non-existing-farm"); + address prevFarm = urnFarms[urn]; + require(prevFarm != farm, "LockstakeEngine/same-farm"); + (uint256 ink,) = vat.urns(ilk, urn); + _selectFarm(urn, ink, prevFarm, farm, ref); + emit SelectFarm(urn, farm, ref); + } + + function _selectFarm(address urn, uint256 wad, address prevFarm, address farm, uint16 ref) internal { + if (wad > 0) { + if (prevFarm != address(0)) { + LockstakeUrn(urn).withdraw(prevFarm, wad); + } + if (farm != address(0)) { + LockstakeUrn(urn).stake(farm, wad, ref); + } + } + urnFarms[urn] = farm; + } + + function lock(address urn, uint256 wad, uint16 ref) external urnAuth(urn) { mkr.transferFrom(msg.sender, address(this), wad); - _lock(urn, wad); - emit Lock(urn, wad); + _lock(urn, wad, ref); + emit Lock(urn, wad, ref); } - function lockNgt(address urn, uint256 ngtWad) external urnAuth(urn) { + function lockNgt(address urn, uint256 ngtWad, uint16 ref) external urnAuth(urn) { ngt.transferFrom(msg.sender, address(this), ngtWad); mkrNgt.ngtToMkr(address(this), ngtWad); - _lock(urn, ngtWad / mkrNgtRate); - emit LockNgt(urn, ngtWad); + _lock(urn, ngtWad / mkrNgtRate, ref); + emit LockNgt(urn, ngtWad, ref); } - function _lock(address urn, uint256 wad) internal { + function _lock(address urn, uint256 wad, uint16 ref) internal { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); - address delegate_ = urnDelegates[urn]; - if (delegate_ != address(0)) { - mkr.approve(address(delegate_), wad); - DelegateLike(delegate_).lock(wad); + address delegate = urnDelegates[urn]; + if (delegate != address(0)) { + mkr.approve(address(delegate), wad); + DelegateLike(delegate).lock(wad); } // TODO: define if we want an internal registry to register how much is locked per user, // the vat.slip and stkMkr balance act already as a registry so probably not needed an extra one vat.slip(ilk, urn, int256(wad)); vat.frob(ilk, urn, urn, address(0), int256(wad), 0); stkMkr.mint(urn, wad); + address urnFarm = urnFarms[urn]; + if (urnFarm != address(0)) { + require(farms[urnFarm] == 1, "Lockstake/farm-not-whitelisted-anymore"); + LockstakeUrn(urn).stake(urnFarm, wad, ref); + } } function free(address urn, address to, uint256 wad) external urnAuth(urn) { @@ -273,36 +323,22 @@ contract LockstakeEngine is Multicall { function _free(address urn, uint256 wad) internal returns (uint256 freed) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); + address urnFarm = urnFarms[urn]; + if (urnFarm != address(0)) { + LockstakeUrn(urn).withdraw(urnFarm, wad); + } stkMkr.burn(urn, wad); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); - address delegate_ = urnDelegates[urn]; - if (delegate_ != address(0)) { - DelegateLike(delegate_).free(wad); + address delegate = urnDelegates[urn]; + if (delegate != address(0)) { + DelegateLike(delegate).free(wad); } uint256 burn = wad * fee / WAD; mkr.burn(address(this), burn); freed = wad - burn; } - function delegate(address urn, address delegate_) external urnAuth(urn) { - require(delegate_ == address(0) || delegateFactory.isDelegate(delegate_) == 1, "LockstakeEngine/not-valid-delegate"); - address prevDelegate = urnDelegates[urn]; - require(prevDelegate != delegate_, "LockstakeEngine/same-delegate"); - (uint256 wad,) = vat.urns(ilk, urn); - if (wad > 0) { - if (prevDelegate != address(0)) { - DelegateLike(prevDelegate).free(wad); - } - if (delegate_ != address(0)) { - mkr.approve(address(delegate_), wad); - DelegateLike(delegate_).lock(wad); - } - } - urnDelegates[urn] = delegate_; - emit Delegate(urn, delegate_); - } - // --- loan functions --- function draw(address urn, uint256 wad) external urnAuth(urn) { @@ -324,30 +360,7 @@ contract LockstakeEngine is Multicall { emit Wipe(urn, wad); } - // --- staking functions --- - - function selectFarm(address urn, address farm) external urnAuth(urn) { - require(farms[farm] == 1, "LockstakeEngine/non-existing-farm"); - address urnFarm = urnFarms[urn]; - require(urnFarm == address(0) || GemLike(urnFarm).balanceOf(address(urn)) == 0, "LockstakeEngine/withdraw-first"); - urnFarms[urn] = farm; - emit SelectFarm(urn, farm); - } - - function stake(address urn, uint256 wad, uint16 ref) external urnAuth(urn) { - address urnFarm = urnFarms[urn]; - require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm"); - require(farms[urnFarm] == 1, "LockstakeEngine/selected-farm-not-available-anymore"); - LockstakeUrn(urn).stake(urnFarm, wad, ref); - emit Stake(urn, urnFarm, wad, ref); - } - - function withdraw(address urn, uint256 wad) external urnAuth(urn) { - address urnFarm = urnFarms[urn]; - require(urnFarm != address(0), "LockstakeEngine/missing-selected-farm"); - LockstakeUrn(urn).withdraw(urnFarm, wad); - emit Withdraw(urn, urnFarm, wad); - } + // --- staking rewards function --- function getReward(address urn, address farm, address to) external urnAuth(urn) { uint256 amt = LockstakeUrn(urn).getReward(farm, to); @@ -357,18 +370,11 @@ contract LockstakeEngine is Multicall { // --- liquidation callback functions --- function onKick(address urn, uint256 wad) external auth { - address urnFarm = urnFarms[urn]; - if (urnFarm != address(0)){ - uint256 freed = GemLike(stkMkr).balanceOf(address(urn)); - if (wad > freed) { - LockstakeUrn(urn).withdraw(urnFarm, wad - freed); - } - } - stkMkr.burn(urn, wad); // Burn the whole liquidated amount of staking token - address delegate_ = urnDelegates[urn]; - if (delegate_ != address(0)) { - DelegateLike(delegate_).free(wad); // Undelegate liquidated amount and retain the MKR tokens - } + (uint256 ink,) = vat.urns(ilk, urn); + uint256 inkBeforeKick = ink + wad; + _selectDelegate(urn, inkBeforeKick, urnDelegates[urn], address(0)); + _selectFarm(urn, inkBeforeKick, urnFarms[urn], address(0), 0); + stkMkr.burn(urn, wad); // Burn the liquidated amount of staking token // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper emit OnKick(urn, wad); } @@ -388,14 +394,12 @@ contract LockstakeEngine is Multicall { } mkr.burn(address(this), burn); // Burn MKR if (left > 0) { - address delegate_ = urnDelegates[urn]; - if (delegate_ != address(0)) { - mkr.approve(address(delegate_), left); - DelegateLike(delegate_).lock(left); - } + (uint256 ink,) = vat.urns(ilk, urn); // Get the ink value before adding the left to correctly undelegate and unstake vat.slip(ilk, urn, int256(left)); vat.frob(ilk, urn, urn, address(0), int256(left), 0); stkMkr.mint(urn, left); + _selectDelegate(urn, ink, urnDelegates[urn], address(0)); + _selectFarm(urn, ink, urnFarms[urn], address(0), 0); } emit OnTakeLeftovers(urn, tot, left, burn); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 88428972..f0d3bde2 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -84,16 +84,14 @@ contract LockstakeEngineTest is DssTest { event AddFarm(address farm); event DelFarm(address farm); event Open(address indexed owner, address urn); - event Lock(address indexed urn, uint256 wad); - event LockNgt(address indexed urn, uint256 ngtWad); + event SelectDelegate(address indexed urn, address indexed delegate_); + event SelectFarm(address indexed urn, address farm, uint16 ref); + event Lock(address indexed urn, uint256 wad, uint16 ref); + event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); - event Delegate(address indexed urn, address indexed delegate_); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); - event SelectFarm(address indexed urn, address farm); - event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); - event Withdraw(address indexed urn, address indexed farm, uint256 wad); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); @@ -177,20 +175,18 @@ contract LockstakeEngineTest is DssTest { checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); - bytes4[] memory urnOwnersMethods = new bytes4[](13); + bytes4[] memory urnOwnersMethods = new bytes4[](11); urnOwnersMethods[0] = engine.hope.selector; urnOwnersMethods[1] = engine.nope.selector; urnOwnersMethods[2] = engine.lock.selector; urnOwnersMethods[3] = engine.lockNgt.selector; urnOwnersMethods[4] = engine.free.selector; urnOwnersMethods[5] = engine.freeNgt.selector; - urnOwnersMethods[6] = engine.delegate.selector; + urnOwnersMethods[6] = engine.selectDelegate.selector; urnOwnersMethods[7] = engine.draw.selector; urnOwnersMethods[8] = engine.wipe.selector; urnOwnersMethods[9] = engine.selectFarm.selector; - urnOwnersMethods[10] = engine.stake.selector; - urnOwnersMethods[11] = engine.withdraw.selector; - urnOwnersMethods[12] = engine.getReward.selector; + urnOwnersMethods[10] = engine.getReward.selector; vm.startPrank(address(0xBEEF)); checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); @@ -248,23 +244,21 @@ contract LockstakeEngineTest is DssTest { vm.startPrank(urnAuthed); engine.hope(urn, address(789)); mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18, 0); assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.free(urn, address(this), 50_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); ngt.approve(address(engine), 100_000 * 25_000 * 10**18); - engine.lockNgt(urn, 100_000 * 25_000 * 10**18); + engine.lockNgt(urn, 100_000 * 25_000 * 10**18, 0); assertEq(_ink(ilk, urn), 150_000 * 10**18); engine.freeNgt(urn, address(this), 50_000 * 25_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); - engine.delegate(urn, voterDelegate); + engine.selectDelegate(urn, voterDelegate); assertEq(engine.urnDelegates(urn), voterDelegate); engine.draw(urn, 1); nst.approve(address(engine), 1); engine.wipe(urn, 1); - engine.selectFarm(urn, address(farm)); - engine.stake(urn, 1, 0); - engine.withdraw(urn, 1); + engine.selectFarm(urn, address(farm), 0); engine.getReward(urn, address(farm), address(0)); engine.nope(urn, urnAuthed); assertEq(engine.urnCan(urn, urnAuthed), 0); @@ -272,28 +266,102 @@ contract LockstakeEngineTest is DssTest { vm.stopPrank(); } - function _testLockFree(bool withDelegate) internal { - uint256 initialSupply = mkr.totalSupply(); + function testSelectDelegate() public { + address urn = engine.open(0); + vm.expectRevert("LockstakeEngine/not-valid-delegate"); + engine.selectDelegate(urn, address(111)); + engine.selectDelegate(urn, voterDelegate); + vm.expectRevert("LockstakeEngine/same-delegate"); + engine.selectDelegate(urn, voterDelegate); + assertEq(engine.urnDelegates(urn), voterDelegate); + vm.prank(address(888)); address voterDelegate2 = delFactory.create(); + mkr.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18, 5); + assertEq(DelegateMock(voterDelegate).stake(address(engine)), 100_000 * 10**18); + assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate2), 0); + assertEq(mkr.balanceOf(address(engine)), 0); + vm.expectEmit(true, true, true, true); + emit SelectDelegate(urn, voterDelegate2); + engine.selectDelegate(urn, voterDelegate2); + assertEq(engine.urnDelegates(urn), voterDelegate2); + assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); + assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voterDelegate2), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); + engine.selectDelegate(urn, address(0)); + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); + assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voterDelegate2), 0); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + } + + function testSelectFarm() public { + StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkMkr)); + address urn = engine.open(0); + assertEq(engine.urnFarms(urn), address(0)); + vm.expectRevert("LockstakeEngine/non-existing-farm"); + engine.selectFarm(urn, address(farm), 5); + vm.prank(pauseProxy); engine.addFarm(address(farm)); + vm.expectEmit(true, true, true, true); + emit SelectFarm(urn, address(farm), 5); + engine.selectFarm(urn, address(farm), 5); + assertEq(engine.urnFarms(urn), address(farm)); + vm.expectRevert("LockstakeEngine/same-farm"); + engine.selectFarm(urn, address(farm), 5); + vm.prank(pauseProxy); engine.addFarm(address(farm2)); + engine.selectFarm(urn, address(farm2), 5); + assertEq(engine.urnFarms(urn), address(farm2)); + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(stkMkr.balanceOf(address(farm2)), 0); + mkr.approve(address(engine), 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18, 5); + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(stkMkr.balanceOf(address(farm2)), 100_000 * 10**18); + assertEq(farm.balanceOf(urn), 0); + assertEq(farm2.balanceOf(urn), 100_000 * 10**18); + engine.selectFarm(urn, address(farm), 5); + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm2)), 0); + assertEq(farm.balanceOf(urn), 100_000 * 10**18); + assertEq(farm2.balanceOf(urn), 0); + } + + function _testLockFree(bool withDelegate, bool withStaking) internal { + uint256 initialMkrSupply = mkr.totalSupply(); address urn = engine.open(0); deal(address(mkr), address(this), uint256(type(int256).max) + 1); // deal mkr to allow reaching the overflow revert mkr.approve(address(engine), uint256(type(int256).max) + 1); vm.expectRevert("LockstakeEngine/wad-overflow"); - engine.lock(urn, uint256(type(int256).max) + 1); + engine.lock(urn, uint256(type(int256).max) + 1, 5); deal(address(mkr), address(this), 100_000 * 10**18); // back to normal mkr balance and allowance mkr.approve(address(engine), 100_000 * 10**18); vm.expectRevert("LockstakeEngine/wad-overflow"); engine.free(urn, address(this), uint256(type(int256).max) + 1); if (withDelegate) { - engine.delegate(urn, voterDelegate); + engine.selectDelegate(urn, voterDelegate); + } + if (withStaking) { + vm.prank(pauseProxy); engine.addFarm(address(farm)); + engine.selectFarm(urn, address(farm), 0); } assertEq(_ink(ilk, urn), 0); assertEq(stkMkr.balanceOf(urn), 0); mkr.approve(address(engine), 100_000 * 10**18); vm.expectEmit(true, true, true, true); - emit Lock(urn, 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18); + emit Lock(urn, 100_000 * 10**18, 5); + engine.lock(urn, 100_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(farm.balanceOf(urn), 100_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + } assertEq(mkr.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); @@ -301,18 +369,28 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(mkr.totalSupply(), initialSupply); + assertEq(mkr.totalSupply(), initialMkrSupply); vm.expectEmit(true, true, true, true); emit Free(urn, address(this), 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); engine.free(urn, address(this), 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); - assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(farm.balanceOf(urn), 60_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + } assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); vm.expectEmit(true, true, true, true); emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 15 / 100); engine.free(urn, address(123), 10_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); - assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 50_000 * 10**18); + assertEq(farm.balanceOf(urn), 50_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + } assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); @@ -320,32 +398,49 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } - assertEq(mkr.totalSupply(), initialSupply - 50_000 * 10**18 * 15 / 100); + assertEq(mkr.totalSupply(), initialMkrSupply - 50_000 * 10**18 * 15 / 100); + } + + function testLockFreeNoDelegateNoStaking() public { + _testLockFree(false, false); } - function testLockFreeNoDelegate() public { - _testLockFree(false); + function testLockFreeWithDelegateNoStaking() public { + _testLockFree(true, false); } - function testLockFreeWithDelegate() public { - _testLockFree(true); + function testLockFreeNoDelegateWithStaking() public { + _testLockFree(false, true); } - function _testLockFreeNgt(bool withDelegate) internal { + function testLockFreeWithDelegateWithStaking() public { + _testLockFree(true, true); + } + + function _testLockFreeNgt(bool withDelegate, bool withStaking) internal { uint256 initialSupply = ngt.totalSupply(); address urn = engine.open(0); // Note: wad-overflow cannot be reached for lockNgt and freeNgt as we these functions will first divide by the rate if (withDelegate) { - engine.delegate(urn, voterDelegate); + engine.selectDelegate(urn, voterDelegate); + } + if (withStaking) { + vm.prank(pauseProxy); engine.addFarm(address(farm)); + engine.selectFarm(urn, address(farm), 0); } assertEq(_ink(ilk, urn), 0); assertEq(stkMkr.balanceOf(urn), 0); ngt.approve(address(engine), 100_000 * 25_000 * 10**18); vm.expectEmit(true, true, true, true); - emit LockNgt(urn, 100_000 * 25_000 * 10**18); - engine.lockNgt(urn, 100_000 * 25_000 * 10**18); + emit LockNgt(urn, 100_000 * 25_000 * 10**18, 5); + engine.lockNgt(urn, 100_000 * 25_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(farm.balanceOf(urn), 100_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + } assertEq(ngt.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); @@ -358,13 +453,23 @@ contract LockstakeEngineTest is DssTest { emit FreeNgt(urn, address(this), 40_000 * 25_000 * 10**18, 40_000 * 10**18 * 15 / 100); engine.freeNgt(urn, address(this), 40_000 * 25_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); - assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(farm.balanceOf(urn), 60_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + } assertEq(ngt.balanceOf(address(this)), 40_000 * 25_000 * 10**18 - 40_000 * 25_000 * 10**18 * 15 / 100); vm.expectEmit(true, true, true, true); emit FreeNgt(urn, address(123), 10_000 * 25_000 * 10**18, 10_000 * 10**18 * 15 / 100); engine.freeNgt(urn, address(123), 10_000 * 25_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); - assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 50_000 * 10**18); + assertEq(farm.balanceOf(urn), 50_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + } assertEq(ngt.balanceOf(address(123)), 10_000 * 25_000 * 10**18 - 10_000 * 25_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); @@ -375,51 +480,27 @@ contract LockstakeEngineTest is DssTest { assertEq(ngt.totalSupply(), initialSupply - (100_000 - 50_000) * 25_000 * 10**18 - 50_000 * 25_000 * 10**18 * 15 / 100); } - function testLockFreeNgtNoDelegate() public { - _testLockFreeNgt(false); + function testLockFreeNgtNoDelegateNoStaking() public { + _testLockFreeNgt(false, false); } - function testLockFreeNgtWithDelegate() public { - _testLockFreeNgt(true); + function testLockFreeNgtWithDelegateNoStaking() public { + _testLockFreeNgt(true, false); } - function testDelegate() public { - address urn = engine.open(0); - vm.expectRevert("LockstakeEngine/not-valid-delegate"); - engine.delegate(urn, address(111)); - engine.delegate(urn, voterDelegate); - vm.expectRevert("LockstakeEngine/same-delegate"); - engine.delegate(urn, voterDelegate); - assertEq(engine.urnDelegates(urn), voterDelegate); - vm.prank(address(888)); address voterDelegate2 = delFactory.create(); - mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18); - assertEq(DelegateMock(voterDelegate).stake(address(engine)), 100_000 * 10**18); - assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); - assertEq(mkr.balanceOf(voterDelegate2), 0); - assertEq(mkr.balanceOf(address(engine)), 0); - engine.delegate(urn, voterDelegate2); - assertEq(engine.urnDelegates(urn), voterDelegate2); - assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); - assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 100_000 * 10**18); - assertEq(mkr.balanceOf(voterDelegate), 0); - assertEq(mkr.balanceOf(voterDelegate2), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - engine.delegate(urn, address(0)); - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); - assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 0); - assertEq(mkr.balanceOf(voterDelegate2), 0); - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + function testLockFreeNgtNoDelegateWithStaking() public { + _testLockFreeNgt(false, true); + } + + function testLockFreeNgtWithDelegateWithStaking() public { + _testLockFreeNgt(true, true); } function testDrawWipe() public { deal(address(mkr), address(this), 100_000 * 10**18, true); address urn = engine.open(0); mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18, 5); assertEq(_art(ilk, urn), 0); vm.expectEmit(true, true, true, true); emit Draw(urn, 50 * 10**18); @@ -453,70 +534,16 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe } - function testSelectFarm() public { - StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkMkr)); - address urn = engine.open(0); - assertEq(engine.urnFarms(urn), address(0)); - vm.expectRevert("LockstakeEngine/non-existing-farm"); - engine.selectFarm(urn, address(farm)); - vm.prank(pauseProxy); engine.addFarm(address(farm)); - vm.expectEmit(true, true, true, true); - emit SelectFarm(urn, address(farm)); - engine.selectFarm(urn, address(farm)); - assertEq(engine.urnFarms(urn), address(farm)); - vm.prank(pauseProxy); engine.addFarm(address(farm2)); - engine.selectFarm(urn, address(farm2)); - assertEq(engine.urnFarms(urn), address(farm2)); - mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18); - engine.stake(urn, 100_000, 1); - vm.expectRevert("LockstakeEngine/withdraw-first"); - engine.selectFarm(urn, address(farm)); - engine.withdraw(urn, 100_000); - engine.selectFarm(urn, address(farm)); - } - - function testStakeWithdraw() public { - vm.prank(pauseProxy); engine.addFarm(address(farm)); - address urn = engine.open(0); - mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18); - vm.expectRevert("LockstakeEngine/missing-selected-farm"); - engine.stake(urn, 100_000, 1); - vm.expectRevert("LockstakeEngine/missing-selected-farm"); - engine.withdraw(urn, 0); - engine.selectFarm(urn, address(farm)); - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 0); - assertEq(farm.balanceOf(address(urn)), 0); - vm.expectEmit(true, true, true, true); - emit Stake(urn, address(farm), 60_000 * 10**18, 1); - engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); - assertEq(farm.balanceOf(address(urn)), 60_000 * 10**18); - vm.prank(pauseProxy); engine.delFarm(address(farm)); - vm.expectRevert("LockstakeEngine/selected-farm-not-available-anymore"); - engine.stake(urn, 10_000 * 10**18, 1); - vm.expectEmit(true, true, true, true); - emit Withdraw(urn, address(farm), 15_000 * 10**18); - engine.withdraw(urn, 15_000 * 10**18); - assertEq(stkMkr.balanceOf(address(urn)), 55_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 45_000 * 10**18); - assertEq(farm.balanceOf(address(urn)), 45_000 * 10**18); - } - function testOpenLockStakeMulticall() public { vm.prank(pauseProxy); engine.addFarm(address(farm)); mkr.approve(address(engine), 100_000 * 10**18); address urn = engine.getUrn(address(this), 0); - bytes[] memory callsToExecute = new bytes[](4); + bytes[] memory callsToExecute = new bytes[](3); callsToExecute[0] = abi.encodeWithSignature("open(uint256)", 0); - callsToExecute[1] = abi.encodeWithSignature("lock(address,uint256)", urn, 100_000 * 10**18); - callsToExecute[2] = abi.encodeWithSignature("selectFarm(address,address)", urn, address(farm)); - callsToExecute[3] = abi.encodeWithSignature("stake(address,uint256,uint16)", urn, 100_000 * 10**18, 1); + callsToExecute[1] = abi.encodeWithSignature("lock(address,uint256,uint16)", urn, 100_000 * 10**18, uint16(5)); + callsToExecute[2] = abi.encodeWithSignature("selectFarm(address,address,uint16)", urn, address(farm), uint16(5)); engine.multicall(callsToExecute); } @@ -555,10 +582,10 @@ contract LockstakeEngineTest is DssTest { urn = engine.open(0); if (withDelegate) { - engine.delegate(urn, voterDelegate); + engine.selectDelegate(urn, voterDelegate); } mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18); + engine.lock(urn, 100_000 * 10**18, 5); engine.draw(urn, 2_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(_art(ilk, urn), 2_000 * 10**18); @@ -576,9 +603,11 @@ contract LockstakeEngineTest is DssTest { address urn = _clipperSetUp(withDelegate); if (withDelegate) { + assertEq(engine.urnDelegates(urn), address(voterDelegate)); assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); assertEq(mkr.balanceOf(address(engine)), 0); } else { + assertEq(engine.urnDelegates(urn), address(0)); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); @@ -600,9 +629,8 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); - } + assertEq(engine.urnDelegates(urn), address(0)); // Always undelegates everything + assertEq(mkr.balanceOf(address(voterDelegate)), 0); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); @@ -619,10 +647,19 @@ contract LockstakeEngineTest is DssTest { function _testOnKickPartialNoStaked(bool withDelegate) internal { address urn = _clipperSetUp(withDelegate); - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - + if (withDelegate) { + assertEq(engine.urnDelegates(urn), address(voterDelegate)); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); + } else { + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + } + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); + vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -639,12 +676,9 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 25_000 * 10**18); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } + assertEq(engine.urnDelegates(urn), address(0)); // Always undelegates everything + assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); } @@ -657,13 +691,11 @@ contract LockstakeEngineTest is DssTest { _testOnKickPartialNoStaked(true); } - function _testOnKickFullStakedPartial(bool withDelegate) private { + function _testOnKickFullStaked(bool withDelegate) private { address urn = _clipperSetUp(withDelegate); - engine.selectFarm(urn, address(farm)); - engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + engine.selectFarm(urn, address(farm), 5); + assertEq(engine.urnFarms(urn), address(farm)); if (withDelegate) { assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); @@ -671,8 +703,8 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -691,82 +723,41 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); - } + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + assertEq(engine.urnFarms(urn), address(0)); assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); } - function testOnKickFullStakedPartialNoDelegate() public { - _testOnKickFullStakedPartial(false); + function testOnKickFullStakedNoDelegate() public { + _testOnKickFullStaked(false); } - function testOnKickFullStakedPartialWithDelegate() public { - _testOnKickFullStakedPartial(true); + function testOnKickFullStakedWithDelegate() public { + _testOnKickFullStaked(true); } - function _testOnKickPartialStakedPartialNoWithdraw(bool withDelegate) internal { + function _testOnKickPartialStaked(bool withDelegate) internal { address urn = _clipperSetUp(withDelegate); - engine.selectFarm(urn, address(farm)); - engine.stake(urn, 60_000 * 10**18, 1); - assertEq(stkMkr.balanceOf(address(urn)), 40_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); - - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - - uint256 id = _forceLiquidation(urn); - - LockstakeClipper.Sale memory sale; - (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); - assertEq(sale.pos, 0); - assertEq(sale.tab, 500 * 10**45); - assertEq(sale.lot, 25_000 * 10**18); - assertEq(sale.tot, 25_000 * 10**18); - assertEq(sale.usr, address(urn)); - assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); - - assertEq(_ink(ilk, urn), 75_000 * 10**18); - assertEq(_art(ilk, urn), 1_500 * 10**18); - assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); + engine.selectFarm(urn, address(farm), 5); + assertEq(engine.urnFarms(urn), address(farm)); if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 25_000 * 10**18); + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkMkr.balanceOf(address(urn)), 15_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); - } - - function testOnKickPartialStakedPartialNoWithdrawNoDelegate() public { - _testOnKickPartialStakedPartialNoWithdraw(false); - } - - function testOnKickPartialStakedPartialNoWithdrawWithDelegate() public { - _testOnKickPartialStakedPartialNoWithdraw(true); - } - - function _testOnKickPartialStakedPartialWithdraw(bool withDelegate) public { - address urn = _clipperSetUp(withDelegate); - - engine.selectFarm(urn, address(farm)); - engine.stake(urn, 80_000 * 10**18, 1); - assertEq(stkMkr.balanceOf(address(urn)), 20_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 80_000 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + uint256 stkMkrInitialSupply = stkMkr.totalSupply(); vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -783,23 +774,20 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 75_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 25_000 * 10**18); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 75_000 * 10**18); + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(engine.urnFarms(urn), address(0)); + assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); } - function testOnKickPartialStakedPartialWithdrawNoDelegate() public { - _testOnKickPartialStakedPartialWithdraw(false); + function testOnKickPartialStakedNoDelegate() public { + _testOnKickPartialStaked(false); } - function testOnKickPartialStakedPartialWithdrawWithDelegate() public { - _testOnKickPartialStakedPartialWithdraw(true); + function testOnKickPartialStakedWithDelegate() public { + _testOnKickPartialStaked(true); } function _testOnTake(bool withDelegate) internal { @@ -885,12 +873,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 0); - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(mkr.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); - } + assertEq(mkr.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(mkr.totalSupply(), mkrInitialSupply - 32_000 * 0.15 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 1.15 * 10**18); From 917420f09bd3e65d7cacee1f90af55fd91062e28 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 9 Jan 2024 08:09:00 -0300 Subject: [PATCH 021/111] Missign stkGov => stkMkr renaming --- src/LockstakeUrn.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index 1c1ea4d4..622d0f6a 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -37,7 +37,7 @@ contract LockstakeUrn { // --- immutables --- address immutable public engine; - GemLike immutable public stkGov; + GemLike immutable public stkMkr; // --- modifiers --- @@ -48,17 +48,17 @@ contract LockstakeUrn { // --- constructor --- - constructor(address vat_, address stkGov_) { + constructor(address vat_, address stkMkr_) { engine = msg.sender; - stkGov = GemLike(stkGov_); + stkMkr = GemLike(stkMkr_); VatLike(vat_).hope(msg.sender); - stkGov.approve(msg.sender, type(uint256).max); + stkMkr.approve(msg.sender, type(uint256).max); } // --- staking functions --- function stake(address farm, uint256 wad, uint16 ref) external isEngine { - stkGov.approve(farm, wad); + stkMkr.approve(farm, wad); StakingRewardsLike(farm).stake(wad, ref); } From 5f6051be55c7869b6dde63db887b64074e050fd8 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:00:16 +0000 Subject: [PATCH 022/111] Use EIP1167 for urn (#8) * Support multicall for LockstakeEngine * Sketch use of converter inside LockstakeEngine * Send out ngt from converter, separate events * Add index parameter to open() * Approve tokens to mkrNgt in the constructor * gov => mkr, stkGov => stkMkr * Use EIP1167 for urn * Fix rebase conflict * Minor formatting * Small _initCode reformatting * Remove unnecessary create2 success check * Add LockstakeUrn init and ctor tests * FIx vat.can interface --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> Co-authored-by: sunbreak Co-authored-by: telome <> --- src/LockstakeEngine.sol | 21 ++++++++++++++++++--- src/LockstakeUrn.sol | 9 +++++++-- test/LockstakeEngine.t.sol | 14 ++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 9f02e405..85d62d96 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -92,6 +92,7 @@ contract LockstakeEngine is Multicall { MkrNgtLike immutable public mkrNgt; GemLike immutable public ngt; uint256 immutable public mkrNgtRate; + address immutable public urnImplementation; // --- events --- @@ -149,6 +150,7 @@ contract LockstakeEngine is Multicall { ngt.approve(address(mkrNgt), type(uint256).max); mkr.approve(address(mkrNgt), type(uint256).max); mkrNgtRate = mkrNgt.rate(); + urnImplementation = address(new LockstakeUrn(address(vat), stkMkr_)); wards[msg.sender] = 1; emit Rely(msg.sender); @@ -166,6 +168,17 @@ contract LockstakeEngine is Multicall { ok = urnOwners[urn] == usr || urnCan[urn][usr] == 1; } + // See the reference implementation in https://eips.ethereum.org/EIPS/eip-1167 + function _initCode() internal view returns (bytes memory code) { + code = new bytes(0x37); + bytes20 impl = bytes20(urnImplementation); + assembly { + mstore(add(code, 0x20), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(code, add(0x20, 0x14)), impl) + mstore(add(code, add(0x20, 0x28)), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + } + } + // --- administration --- function rely(address usr) external auth { @@ -199,7 +212,7 @@ contract LockstakeEngine is Multicall { function getUrn(address owner, uint256 index) external view returns (address urn) { uint256 salt = uint256(keccak256(abi.encode(owner, index))); - bytes32 codeHash = keccak256(abi.encodePacked(type(LockstakeUrn).creationCode, abi.encode(vat, stkMkr))); + bytes32 codeHash = keccak256(abi.encodePacked(_initCode())); urn = address(uint160(uint256( keccak256( abi.encodePacked(bytes1(0xff), address(this), salt, codeHash) @@ -215,8 +228,10 @@ contract LockstakeEngine is Multicall { function open(uint256 index) external returns (address urn) { require(index == usrAmts[msg.sender]++, "LockstakeEngine/wrong-urn-index"); - bytes32 salt = keccak256(abi.encode(msg.sender, index)); - urn = address(new LockstakeUrn{salt: salt}(address(vat), address(stkMkr))); + uint256 salt = uint256(keccak256(abi.encode(msg.sender, index))); + bytes memory initCode = _initCode(); + assembly { urn := create2(0, add(initCode, 0x20), 0x37, salt) } + LockstakeUrn(urn).init(); // would revert if create2 had failed urnOwners[urn] = msg.sender; emit Open(msg.sender, urn); } diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index 622d0f6a..9a09117b 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -38,6 +38,7 @@ contract LockstakeUrn { address immutable public engine; GemLike immutable public stkMkr; + VatLike immutable public vat; // --- modifiers --- @@ -46,12 +47,16 @@ contract LockstakeUrn { _; } - // --- constructor --- + // --- constructor & init --- constructor(address vat_, address stkMkr_) { engine = msg.sender; + vat = VatLike(vat_); stkMkr = GemLike(stkMkr_); - VatLike(vat_).hope(msg.sender); + } + + function init() external isEngine { + vat.hope(msg.sender); stkMkr.approve(msg.sender, type(uint256).max); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index f0d3bde2..3fbc6b2f 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.16; import "dss-test/DssTest.sol"; import { LockstakeEngine } from "src/LockstakeEngine.sol"; import { LockstakeClipper } from "src/LockstakeClipper.sol"; +import { LockstakeUrn } from "src/LockstakeUrn.sol"; import { PipMock } from "test/mocks/PipMock.sol"; import { DelegateFactoryMock, DelegateMock } from "test/mocks/DelegateMock.sol"; import { GemMock } from "test/mocks/GemMock.sol"; @@ -18,6 +19,7 @@ interface ChainlogLike { } interface VatLike { + function can(address, address) external view returns (uint256); function dai(address) external view returns (uint256); function gem(bytes32, address) external view returns (uint256); function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); @@ -214,12 +216,24 @@ contract LockstakeEngineTest is DssTest { address urn = engine.getUrn(address(this), 0); vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(1); + + assertEq(VatLike(vat).can(urn, address(engine)), 0); + assertEq(stkMkr.allowance(urn, address(engine)), 0); vm.expectEmit(true, true, true, true); emit Open(address(this), urn); assertEq(engine.open(0), urn); assertEq(engine.usrAmts(address(this)), 1); + assertEq(VatLike(vat).can(urn, address(engine)), 1); + assertEq(stkMkr.allowance(urn, address(engine)), type(uint256).max); + assertEq(LockstakeUrn(urn).engine(), address(engine)); + assertEq(address(LockstakeUrn(urn).stkMkr()), address(stkMkr)); + assertEq(address(LockstakeUrn(urn).vat()), vat); + vm.expectRevert("LockstakeUrn/not-engine"); + LockstakeUrn(urn).init(); + vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(2); + assertEq(engine.getUrn(address(this), 1), engine.open(1)); assertEq(engine.usrAmts(address(this)), 2); assertEq(engine.getUrn(address(this), 2), engine.open(2)); From e5359c8d51050b5dbf78c257dbfe25685c10861c Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 9 Jan 2024 10:43:28 -0300 Subject: [PATCH 023/111] Add engine to the excluded contracts for clipper callback --- src/LockstakeClipper.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index 6d83ebfb..eb90abba 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -394,10 +394,10 @@ contract LockstakeClipper { engine.onTake(usr, who, slice); // Do external call (if data is defined) but to be - // extremely careful we don't allow to do it to the two + // extremely careful we don't allow to do it to the three // contracts which the LockstakeClipper needs to be authorized DogLike dog_ = dog; - if (data.length > 0 && who != address(vat) && who != address(dog_)) { + if (data.length > 0 && who != address(vat) && who != address(dog_) && who != address(engine)) { ClipperCallee(who).clipperCall(msg.sender, owe, slice, data); } From 9ee2177ef5f260c7e0a15647feb4305ef742b7bc Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 9 Jan 2024 11:01:13 -0300 Subject: [PATCH 024/111] Remove unused events --- src/LockstakeEngine.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 85d62d96..2dfae751 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -112,8 +112,6 @@ contract LockstakeEngine is Multicall { event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); event Draw(address indexed urn, uint256 wad); event Wipe(address indexed urn, uint256 wad); - event Stake(address indexed urn, address indexed farm, uint256 wad, uint16 ref); - event Withdraw(address indexed urn, address indexed farm, uint256 wad); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); From 1507751523250f5a4b11d2880c3689c3dafa4fe0 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 9 Jan 2024 11:20:19 -0300 Subject: [PATCH 025/111] Add index value to Open event --- src/LockstakeEngine.sol | 4 ++-- test/LockstakeEngine.t.sol | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 2dfae751..b981bea9 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -101,7 +101,7 @@ contract LockstakeEngine is Multicall { event File(bytes32 indexed what, address data); event AddFarm(address farm); event DelFarm(address farm); - event Open(address indexed owner, address urn); + event Open(address indexed owner, uint256 indexed index, address urn); event Hope(address indexed urn, address indexed usr); event Nope(address indexed urn, address indexed usr); event SelectDelegate(address indexed urn, address indexed delegate); @@ -231,7 +231,7 @@ contract LockstakeEngine is Multicall { assembly { urn := create2(0, add(initCode, 0x20), 0x37, salt) } LockstakeUrn(urn).init(); // would revert if create2 had failed urnOwners[urn] = msg.sender; - emit Open(msg.sender, urn); + emit Open(msg.sender, index, urn); } function hope(address urn, address usr) external urnAuth(urn) { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 3fbc6b2f..f09b8b12 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -85,7 +85,7 @@ contract LockstakeEngineTest is DssTest { event AddFarm(address farm); event DelFarm(address farm); - event Open(address indexed owner, address urn); + event Open(address indexed owner, uint256 indexed index, address urn); event SelectDelegate(address indexed urn, address indexed delegate_); event SelectFarm(address indexed urn, address farm, uint16 ref); event Lock(address indexed urn, uint256 wad, uint16 ref); @@ -220,7 +220,7 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).can(urn, address(engine)), 0); assertEq(stkMkr.allowance(urn, address(engine)), 0); vm.expectEmit(true, true, true, true); - emit Open(address(this), urn); + emit Open(address(this), 0, urn); assertEq(engine.open(0), urn); assertEq(engine.usrAmts(address(this)), 1); assertEq(VatLike(vat).can(urn, address(engine)), 1); @@ -234,8 +234,10 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(2); + emit Open(address(this), 1, engine.getUrn(address(this), 1)); assertEq(engine.getUrn(address(this), 1), engine.open(1)); assertEq(engine.usrAmts(address(this)), 2); + emit Open(address(this), 1, engine.getUrn(address(this), 2)); assertEq(engine.getUrn(address(this), 2), engine.open(2)); assertEq(engine.usrAmts(address(this)), 3); } From f825162b0f63d649a27bf89f9da812a95afd40da Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 9 Jan 2024 11:21:25 -0300 Subject: [PATCH 026/111] Remove unnecessary casting --- src/LockstakeEngine.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index b981bea9..6a7e22f5 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -261,7 +261,7 @@ contract LockstakeEngine is Multicall { DelegateLike(prevDelegate).free(wad); } if (delegate != address(0)) { - mkr.approve(address(delegate), wad); + mkr.approve(delegate, wad); DelegateLike(delegate).lock(wad); } } @@ -306,7 +306,7 @@ contract LockstakeEngine is Multicall { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); address delegate = urnDelegates[urn]; if (delegate != address(0)) { - mkr.approve(address(delegate), wad); + mkr.approve(delegate, wad); DelegateLike(delegate).lock(wad); } // TODO: define if we want an internal registry to register how much is locked per user, From a1e9bb23ab4dbb457cc51730b1157188a2efd61f Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 9 Jan 2024 12:20:25 -0300 Subject: [PATCH 027/111] Add to param to draw --- src/LockstakeEngine.sol | 8 ++++---- test/LockstakeEngine.t.sol | 18 +++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 6a7e22f5..29fdc2ce 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -110,7 +110,7 @@ contract LockstakeEngine is Multicall { event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); - event Draw(address indexed urn, uint256 wad); + event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); @@ -354,13 +354,13 @@ contract LockstakeEngine is Multicall { // --- loan functions --- - function draw(address urn, uint256 wad) external urnAuth(urn) { + function draw(address urn, address to, uint256 wad) external urnAuth(urn) { uint256 rate = jug.drip(ilk); uint256 dart = _divup(wad * RAY, rate); require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); vat.frob(ilk, urn, address(0), address(this), 0, int256(dart)); - nstJoin.exit(msg.sender, wad); - emit Draw(urn, wad); + nstJoin.exit(to, wad); + emit Draw(urn, to, wad); } function wipe(address urn, uint256 wad) external urnAuth(urn) { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index f09b8b12..d013ffcf 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -92,7 +92,7 @@ contract LockstakeEngineTest is DssTest { event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); - event Draw(address indexed urn, uint256 wad); + event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); @@ -271,7 +271,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.selectDelegate(urn, voterDelegate); assertEq(engine.urnDelegates(urn), voterDelegate); - engine.draw(urn, 1); + engine.draw(urn, address(urnAuthed), 1); nst.approve(address(engine), 1); engine.wipe(urn, 1); engine.selectFarm(urn, address(farm), 0); @@ -519,15 +519,15 @@ contract LockstakeEngineTest is DssTest { engine.lock(urn, 100_000 * 10**18, 5); assertEq(_art(ilk, urn), 0); vm.expectEmit(true, true, true, true); - emit Draw(urn, 50 * 10**18); - engine.draw(urn, 50 * 10**18); + emit Draw(urn, address(this), 50 * 10**18); + engine.draw(urn, address(this), 50 * 10**18); assertEq(_art(ilk, urn), 50 * 10**18); assertEq(_rate(ilk), 10**27); assertEq(nst.balanceOf(address(this)), 50 * 10**18); vm.warp(block.timestamp + 1); vm.expectEmit(true, true, true, true); - emit Draw(urn, 50 * 10**18); - engine.draw(urn, 50 * 10**18); + emit Draw(urn, address(this), 50 * 10**18); + engine.draw(urn, address(this), 50 * 10**18); uint256 art = _art(ilk, urn); uint256 expectedArt = 50 * 10**18 + _divup(50 * 10**18 * 1000, 1001); assertEq(art, expectedArt); @@ -548,6 +548,10 @@ contract LockstakeEngineTest is DssTest { engine.wipe(urn, 100.05 * 10**18); assertEq(nst.balanceOf(address(this)), 0.01 * 10**18); assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe + assertEq(nst.balanceOf(address(123)), 0); + emit Draw(urn, address(123), 50 * 10**18); + engine.draw(urn, address(123), 50 * 10**18); + assertEq(nst.balanceOf(address(123)), 50 * 10**18); } function testOpenLockStakeMulticall() public { @@ -602,7 +606,7 @@ contract LockstakeEngineTest is DssTest { } mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18, 5); - engine.draw(urn, 2_000 * 10**18); + engine.draw(urn, address(this), 2_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(_art(ilk, urn), 2_000 * 10**18); } From 098c1594800b97e90eae0cbc41c97d9450702937 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:02:39 -0300 Subject: [PATCH 028/111] Fix in test Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- test/LockstakeEngine.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index d013ffcf..1cccc104 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -234,10 +234,12 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(2); + vm.expectEmit(true, true, true, true); emit Open(address(this), 1, engine.getUrn(address(this), 1)); assertEq(engine.getUrn(address(this), 1), engine.open(1)); assertEq(engine.usrAmts(address(this)), 2); - emit Open(address(this), 1, engine.getUrn(address(this), 2)); + vm.expectEmit(true, true, true, true); + emit Open(address(this), 2, engine.getUrn(address(this), 2)); assertEq(engine.getUrn(address(this), 2), engine.open(2)); assertEq(engine.usrAmts(address(this)), 3); } From 876c2ccb1ebdfcf8a5264d50309ce32feb279605 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 10 Jan 2024 10:23:28 -0300 Subject: [PATCH 029/111] Add require fee <= WAD and use unchecked for substracting burn amount + add test constructor --- src/LockstakeEngine.sol | 3 ++- test/LockstakeEngine.t.sol | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 29fdc2ce..6c2d99b8 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -133,6 +133,7 @@ contract LockstakeEngine is Multicall { // --- constructor --- constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkMkr_, uint256 fee_, address mkrNgt_) { + require(fee_ <= WAD, "LockstakeEngine/fee-over-wad"); delegateFactory = DelegateFactoryLike(delegateFactory_); nstJoin = NstJoinLike(nstJoin_); vat = nstJoin.vat(); @@ -349,7 +350,7 @@ contract LockstakeEngine is Multicall { } uint256 burn = wad * fee / WAD; mkr.burn(address(this), burn); - freed = wad - burn; + unchecked { freed = wad - burn; } // burn <= wad always } // --- loan functions --- diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 1cccc104..1270d53b 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -160,6 +160,26 @@ contract LockstakeEngineTest is DssTest { (, rate,,,) = VatLike(vat).ilks(ilk_); } + function testConstructor() public { + vm.expectRevert("LockstakeEngine/fee-over-wad"); + new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), WAD + 1, address(mkrNgt)); + vm.expectEmit(true, true, true, true); + emit Rely(address(this)); + LockstakeEngine e = new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), 100, address(mkrNgt)); + assertEq(address(e.delegateFactory()), address(delFactory)); + assertEq(address(e.nstJoin()), address(nstJoin)); + assertEq(e.ilk(), "aaa"); + assertEq(address(e.mkr()), address(mkr)); + assertEq(address(e.stkMkr()), address(stkMkr)); + assertEq(e.fee(), 100); + assertEq(address(e.mkrNgt()), address(mkrNgt)); + assertEq(address(e.ngt()), address(ngt)); + assertEq(ngt.allowance(address(e), address(mkrNgt)), type(uint256).max); + assertEq(mkr.allowance(address(e), address(mkrNgt)), type(uint256).max); + assertEq(e.mkrNgtRate(), 25_000); + assertEq(e.wards(address(this)), 1); + } + function testAuth() public { checkAuth(address(engine), "LockstakeEngine"); } From 3b9455e97bccf9029ed49f913919b5a242191bd0 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:03:08 -0300 Subject: [PATCH 030/111] Minor comment change Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/LockstakeEngine.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 6c2d99b8..b2109be6 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -350,7 +350,7 @@ contract LockstakeEngine is Multicall { } uint256 burn = wad * fee / WAD; mkr.burn(address(this), burn); - unchecked { freed = wad - burn; } // burn <= wad always + unchecked { freed = wad - burn; } // burn <= WAD always } // --- loan functions --- From c02ea6d2475e12bb7c4b6f434076f0f74b392fc6 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 10 Jan 2024 12:33:27 -0300 Subject: [PATCH 031/111] Add test for clipper --- test/LockstakeClipper.t.sol | 58 +++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index 5c1c50d9..658d8d79 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -180,6 +180,7 @@ contract LockstakeClipperTest is DssTest { PipMock pip; GemLike dai; + LockstakeEngineMock engine; LockstakeClipper clip; // Exchange exchange; @@ -280,7 +281,7 @@ contract LockstakeClipperTest is DssTest { vm.prank(pauseProxy); dog.file(ilk, "hole", rad(1000 ether)); // vm.prank(pauseProxy); dog.file("Hole", rad(1000 ether)); - LockstakeEngineMock engine = new LockstakeEngineMock(address(vat), ilk); + engine = new LockstakeEngineMock(address(vat), ilk); vm.prank(pauseProxy); vat.rely(address(engine)); // dust and chop filed previously so clip.chost will be set correctly @@ -623,9 +624,9 @@ contract LockstakeClipperTest is DssTest { assertEq(dirt, tab); bytes32 ilk2 = "LSE2"; - LockstakeEngineMock engine = new LockstakeEngineMock(address(vat), ilk2); - vm.prank(pauseProxy); vat.rely(address(engine)); - LockstakeClipper clip2 = new LockstakeClipper(address(vat), address(spot), address(dog), address(engine)); + LockstakeEngineMock engine2 = new LockstakeEngineMock(address(vat), ilk2); + vm.prank(pauseProxy); vat.rely(address(engine2)); + LockstakeClipper clip2 = new LockstakeClipper(address(vat), address(spot), address(dog), address(engine2)); clip2.upchost(); clip2.rely(address(dog)); @@ -786,6 +787,51 @@ contract LockstakeClipperTest is DssTest { assertEq(dirt, 0); } + function testTakeEmptyDataOrForbiddenWho() public takeSetup { + vm.expectRevert(); // Reverts as who is a random address that do not implement clipperCall + vm.prank(ali); clip.take({ + id: 1, + amt: 11 ether, + max: ray(5 ether), + who: address(123), + data: "aaa" + }); + uint256 snapshotId = vm.snapshot(); + // This one won't revert as has empty data + vm.prank(ali); clip.take({ + id: 1, + amt: 11 ether, + max: ray(5 ether), + who: address(123), + data: "" + }); + vm.revertTo(snapshotId); + // The following ones won't revert as are the forbidden addresses and the clipperCall will be ignored + vm.prank(ali); clip.take({ + id: 1, + amt: 11 ether, + max: ray(5 ether), + who: address(dog), + data: "aaa" + }); + vm.revertTo(snapshotId); + vm.prank(ali); clip.take({ + id: 1, + amt: 11 ether, + max: ray(5 ether), + who: address(vat), + data: "aaa" + }); + vm.revertTo(snapshotId); + vm.prank(ali); clip.take({ + id: 1, + amt: 11 ether, + max: ray(5 ether), + who: address(engine), + data: "aaa" + }); + } + function testTakeUnderTab() public takeSetup { // Bid so owe (= 11 * 5 = 55 RAD) < tab (= 110 RAD) vm.prank(ali); clip.take({ @@ -1342,8 +1388,8 @@ contract LockstakeClipperTest is DssTest { } function testRemoveId() public { - LockstakeEngineMock engine = new LockstakeEngineMock(address(vat), "random"); - PublicClip pclip = new PublicClip(address(vat), address(spot), address(dog), address(engine)); + LockstakeEngineMock engine2 = new LockstakeEngineMock(address(vat), "random"); + PublicClip pclip = new PublicClip(address(vat), address(spot), address(dog), address(engine2)); uint256 pos; pclip.add(); From f71497d602230c75d856bebb8bb5ee7cb71b1bd8 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 11 Jan 2024 09:00:14 -0300 Subject: [PATCH 032/111] Add overflow checks --- src/LockstakeClipper.sol | 9 +++++---- src/LockstakeEngine.sol | 1 + test/LockstakeClipper.t.sol | 5 +++++ test/LockstakeEngine.t.sol | 5 +++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index eb90abba..ca05deb0 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -234,11 +234,12 @@ contract LockstakeClipper { address kpr // Address that will receive incentives ) external auth lock isStopped(1) returns (uint256 id) { // Input validation - require(tab > 0, "LockstakeClipper/zero-tab"); - require(lot > 0, "LockstakeClipper/zero-lot"); - require(usr != address(0), "LockstakeClipper/zero-usr"); + require(tab > 0, "LockstakeClipper/zero-tab"); + require(lot > 0, "LockstakeClipper/zero-lot"); + require(lot <= uint256(type(int256).max), "LockstakeClipper/over-maxint-lot"); // This is ensured by the dog but we still prefer to be explicit + require(usr != address(0), "LockstakeClipper/zero-usr"); id = ++kicks; - require(id > 0, "LockstakeClipper/overflow"); + require(id > 0, "LockstakeClipper/overflow"); active.push(id); diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index b2109be6..3b490b94 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -399,6 +399,7 @@ contract LockstakeEngine is Multicall { } function onTakeLeftovers(address urn, uint256 tot, uint256 left) external auth { + require(left <= uint256(type(int256).max), "LockstakeEngine/left-over-maxint"); // This is ensured by the dog and clip but we still prefer to be explicit uint256 burn = (tot - left) * fee / WAD; if (burn > left) { burn = left; diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index 658d8d79..ee4f84b3 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -443,6 +443,11 @@ contract LockstakeClipperTest is DssTest { clip.kick(1 ether, 0, address(1), address(this)); } + function testRevertsKickLotOverMaxInt() public { + vm.expectRevert("LockstakeClipper/over-maxint-lot"); + clip.kick(1 ether, uint256(type(int256).max) + 1, address(1), address(this)); + } + function testRevertsKickZeroUsr() public { vm.expectRevert("LockstakeClipper/zero-usr"); clip.kick(1 ether, 2 ether, address(0), address(this)); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 1270d53b..a343fe78 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -1070,6 +1070,11 @@ contract LockstakeEngineTest is DssTest { _testOnTakeNoBurn(true); } + function testOnTakeLeftoversOverflow() public { + vm.expectRevert("LockstakeEngine/left-over-maxint"); + vm.prank(address(pauseProxy)); engine.onTakeLeftovers(address(1), 0, uint256(type(int256).max) + 1); + } + function _testOnYank(bool withDelegate) internal { address urn = _clipperSetUp(withDelegate); From e3b30799f5fdd5094c587eed2d7b318728e74562 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:01:00 -0300 Subject: [PATCH 033/111] Comment changes Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/LockstakeEngine.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 3b490b94..a4b073e8 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -310,8 +310,6 @@ contract LockstakeEngine is Multicall { mkr.approve(delegate, wad); DelegateLike(delegate).lock(wad); } - // TODO: define if we want an internal registry to register how much is locked per user, - // the vat.slip and stkMkr balance act already as a registry so probably not needed an extra one vat.slip(ilk, urn, int256(wad)); vat.frob(ilk, urn, urn, address(0), int256(wad), 0); stkMkr.mint(urn, wad); @@ -407,7 +405,7 @@ contract LockstakeEngine is Multicall { } else { unchecked { left = left - burn; } } - mkr.burn(address(this), burn); // Burn MKR + mkr.burn(address(this), burn); if (left > 0) { (uint256 ink,) = vat.urns(ilk, urn); // Get the ink value before adding the left to correctly undelegate and unstake vat.slip(ilk, urn, int256(left)); From 6286d598b15641e860bfabefe5b46da3c55d7369 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 15 Jan 2024 09:05:56 -0300 Subject: [PATCH 034/111] Add return value to getReward --- src/LockstakeEngine.sol | 4 ++-- test/LockstakeEngine.t.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index a4b073e8..fed50a3a 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -374,8 +374,8 @@ contract LockstakeEngine is Multicall { // --- staking rewards function --- - function getReward(address urn, address farm, address to) external urnAuth(urn) { - uint256 amt = LockstakeUrn(urn).getReward(farm, to); + function getReward(address urn, address farm, address to) external urnAuth(urn) returns (uint256 amt) { + amt = LockstakeUrn(urn).getReward(farm, to); emit GetReward(urn, farm, to, amt); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index a343fe78..18f44897 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -596,7 +596,7 @@ contract LockstakeEngineTest is DssTest { assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 0); vm.expectEmit(true, true, true, true); emit GetReward(urn, address(farm), address(123), 20_000); - engine.getReward(urn, address(farm), address(123)); + assertEq(engine.getReward(urn, address(farm), address(123)), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 20_000); } From 5db99ecf7baa6451dd3a25139071daa885ecd152 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 15 Jan 2024 09:07:22 -0300 Subject: [PATCH 035/111] Move comment --- src/LockstakeEngine.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index fed50a3a..cda9c06f 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -382,12 +382,12 @@ contract LockstakeEngine is Multicall { // --- liquidation callback functions --- function onKick(address urn, uint256 wad) external auth { + // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper (uint256 ink,) = vat.urns(ilk, urn); uint256 inkBeforeKick = ink + wad; _selectDelegate(urn, inkBeforeKick, urnDelegates[urn], address(0)); _selectFarm(urn, inkBeforeKick, urnFarms[urn], address(0), 0); stkMkr.burn(urn, wad); // Burn the liquidated amount of staking token - // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper emit OnKick(urn, wad); } From 15eb08f44391c9fc789017c8bc10a9dc021c1e44 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 15 Jan 2024 10:46:21 -0300 Subject: [PATCH 036/111] Some changes to onTakeLeftovers --- src/LockstakeClipper.sol | 2 +- src/LockstakeEngine.sol | 25 +++++++++++++------------ test/LockstakeEngine.t.sol | 4 ++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index ca05deb0..753d7f12 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -414,7 +414,7 @@ contract LockstakeClipper { } else if (tab == 0) { uint256 tot = sales[id].tot; vat.slip(ilk, address(this), -int256(lot)); - engine.onTakeLeftovers(usr, tot, lot); + engine.onTakeLeftovers(usr, tot - lot, lot); _remove(id); } else { sales[id].tab = tab; diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index cda9c06f..e35cf58e 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -115,7 +115,7 @@ contract LockstakeEngine is Multicall { event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); - event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn); + event OnTakeLeftovers(address indexed urn, uint256 sold, uint256 burn, uint256 refund); event OnYank(address indexed urn, uint256 wad); // --- modifiers --- @@ -396,25 +396,26 @@ contract LockstakeEngine is Multicall { emit OnTake(urn, who, wad); } - function onTakeLeftovers(address urn, uint256 tot, uint256 left) external auth { - require(left <= uint256(type(int256).max), "LockstakeEngine/left-over-maxint"); // This is ensured by the dog and clip but we still prefer to be explicit - uint256 burn = (tot - left) * fee / WAD; + function onTakeLeftovers(address urn, uint256 sold, uint256 left) external auth { + uint256 burn = sold * fee / WAD; + uint256 refund; if (burn > left) { burn = left; - left = 0; + refund = 0; } else { - unchecked { left = left - burn; } + unchecked { refund = left - burn; } } mkr.burn(address(this), burn); - if (left > 0) { - (uint256 ink,) = vat.urns(ilk, urn); // Get the ink value before adding the left to correctly undelegate and unstake - vat.slip(ilk, urn, int256(left)); - vat.frob(ilk, urn, urn, address(0), int256(left), 0); - stkMkr.mint(urn, left); + if (refund > 0) { + require(refund <= uint256(type(int256).max), "LockstakeEngine/refund-over-maxint"); // This is ensured by the dog and clip but we still prefer to be explicit + (uint256 ink,) = vat.urns(ilk, urn); // Get the ink value before adding the refund to correctly undelegate and unstake + vat.slip(ilk, urn, int256(refund)); + vat.frob(ilk, urn, urn, address(0), int256(refund), 0); + stkMkr.mint(urn, refund); _selectDelegate(urn, ink, urnDelegates[urn], address(0)); _selectFarm(urn, ink, urnFarms[urn], address(0), 0); } - emit OnTakeLeftovers(urn, tot, left, burn); + emit OnTakeLeftovers(urn, sold, burn, refund); } function onYank(address urn, uint256 wad) external auth { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 18f44897..bd778635 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -97,7 +97,7 @@ contract LockstakeEngineTest is DssTest { event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); - event OnTakeLeftovers(address indexed urn, uint256 tot, uint256 left, uint256 burn); + event OnTakeLeftovers(address indexed urn, uint256 sold, uint256 burn, uint256 refund); function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { @@ -1071,7 +1071,7 @@ contract LockstakeEngineTest is DssTest { } function testOnTakeLeftoversOverflow() public { - vm.expectRevert("LockstakeEngine/left-over-maxint"); + vm.expectRevert("LockstakeEngine/refund-over-maxint"); vm.prank(address(pauseProxy)); engine.onTakeLeftovers(address(1), 0, uint256(type(int256).max) + 1); } From 81613e9a5e410adbbb659b0b45169be05afa3756 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 15 Jan 2024 12:54:38 -0300 Subject: [PATCH 037/111] Improve testing structure --- test/LockstakeEngine.t.sol | 298 +++++++++++++++++-------------------- 1 file changed, 140 insertions(+), 158 deletions(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index bd778635..1aa860e8 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -600,7 +600,7 @@ contract LockstakeEngineTest is DssTest { assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 20_000); } - function _clipperSetUp(bool withDelegate) internal returns (address urn) { + function _clipperSetUp(bool withDelegate, bool withStaking) internal returns (address urn) { vm.startPrank(pauseProxy); engine.addFarm(address(farm)); clip = new LockstakeClipper(vat, spot, dog, address(engine)); @@ -626,11 +626,28 @@ contract LockstakeEngineTest is DssTest { if (withDelegate) { engine.selectDelegate(urn, voterDelegate); } + if (withStaking) { + engine.selectFarm(urn, address(farm), 0); + } mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18, 5); engine.draw(urn, address(this), 2_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(_art(ilk, urn), 2_000 * 10**18); + + if (withDelegate) { + assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 0); + } else { + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + } + if (withStaking) { + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + } } function _forceLiquidation(address urn) internal returns (uint256 id) { @@ -641,8 +658,8 @@ contract LockstakeEngineTest is DssTest { assertEq(clip.kicks(), 1); } - function _testOnKickFullNoStaked(bool withDelegate) internal { - address urn = _clipperSetUp(withDelegate); + function _testOnKickFull(bool withDelegate, bool withStaking) internal { + address urn = _clipperSetUp(withDelegate, withStaking); if (withDelegate) { assertEq(engine.urnDelegates(urn), address(voterDelegate)); @@ -652,7 +669,13 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnDelegates(urn), address(0)); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + } uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -674,20 +697,32 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnDelegates(urn), address(0)); // Always undelegates everything assertEq(mkr.balanceOf(address(voterDelegate)), 0); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); } function testOnKickFullNoStakedNoDelegate() public { - _testOnKickFullNoStaked(false); + _testOnKickFull(false, false); } function testOnKickFullNoStakedWithDelegate() public { - _testOnKickFullNoStaked(true); + _testOnKickFull(true, false); } - function _testOnKickPartialNoStaked(bool withDelegate) internal { - address urn = _clipperSetUp(withDelegate); + function testOnKickFullStakedNoDelegate() public { + _testOnKickFull(false, true); + } + + function testOnKickFullStakedWithDelegate() public { + _testOnKickFull(true, true); + } + + function _testOnKickPartial(bool withDelegate, bool withStaking) internal { + address urn = _clipperSetUp(withDelegate, withStaking); if (withDelegate) { assertEq(engine.urnDelegates(urn), address(voterDelegate)); @@ -697,7 +732,13 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnDelegates(urn), address(0)); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + } uint256 stkMkrInitialSupply = stkMkr.totalSupply(); vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); @@ -722,126 +763,31 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(voterDelegate)), 0); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); } function testOnKickPartialNoStakedNoDelegate() public { - _testOnKickPartialNoStaked(false); + _testOnKickPartial(false, false); } function testOnKickPartialNoStakedWithDelegate() public { - _testOnKickPartialNoStaked(true); - } - - function _testOnKickFullStaked(bool withDelegate) private { - address urn = _clipperSetUp(withDelegate); - - engine.selectFarm(urn, address(farm), 5); - assertEq(engine.urnFarms(urn), address(farm)); - - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - - uint256 id = _forceLiquidation(urn); - - LockstakeClipper.Sale memory sale; - (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); - assertEq(sale.pos, 0); - assertEq(sale.tab, 2_000 * 10**45); - assertEq(sale.lot, 100_000 * 10**18); - assertEq(sale.tot, 100_000 * 10**18); - assertEq(sale.usr, address(urn)); - assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); - - assertEq(_ink(ilk, urn), 0); - assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(address(voterDelegate)), 0); - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(engine.urnFarms(urn), address(0)); - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); - } - - function testOnKickFullStakedNoDelegate() public { - _testOnKickFullStaked(false); - } - - function testOnKickFullStakedWithDelegate() public { - _testOnKickFullStaked(true); - } - - function _testOnKickPartialStaked(bool withDelegate) internal { - address urn = _clipperSetUp(withDelegate); - - engine.selectFarm(urn, address(farm), 5); - assertEq(engine.urnFarms(urn), address(farm)); - - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - - uint256 id = _forceLiquidation(urn); - - LockstakeClipper.Sale memory sale; - (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); - assertEq(sale.pos, 0); - assertEq(sale.tab, 500 * 10**45); - assertEq(sale.lot, 25_000 * 10**18); - assertEq(sale.tot, 25_000 * 10**18); - assertEq(sale.usr, address(urn)); - assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); - - assertEq(_ink(ilk, urn), 75_000 * 10**18); - assertEq(_art(ilk, urn), 1_500 * 10**18); - assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(address(voterDelegate)), 0); - assertEq(engine.urnFarms(urn), address(0)); - assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); + _testOnKickPartial(true, false); } function testOnKickPartialStakedNoDelegate() public { - _testOnKickPartialStaked(false); + _testOnKickPartial(false, true); } function testOnKickPartialStakedWithDelegate() public { - _testOnKickPartialStaked(true); + _testOnKickPartial(true, true); } - function _testOnTake(bool withDelegate) internal { - address urn = _clipperSetUp(withDelegate); - - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + function _testOnTake(bool withDelegate, bool withStaking) internal { + address urn = _clipperSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); @@ -870,6 +816,10 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(voterDelegate)), 0); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), 0); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); address buyer = address(888); @@ -897,6 +847,10 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 80_000 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), 0); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.prank(buyer); clip.take(id, 12_000 * 10**18, type(uint256).max, buyer, ""); @@ -918,28 +872,32 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(mkr.totalSupply(), mkrInitialSupply - 32_000 * 0.15 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 1.15 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } - function testOnTakeNoDelegate() public { - _testOnTake(false); + function testOnTakeNoStakedNoDelegate() public { + _testOnTake(false, false); } - function testOnTakeWithDelegate() public { - _testOnTake(true); + function testOnTakeNoStakedWithDelegate() public { + _testOnTake(true, false); } - function _testOnTakePartialBurn(bool withDelegate) internal { - address urn = _clipperSetUp(withDelegate); + function testOnTakeStakedNoDelegate() public { + _testOnTake(false, true); + } - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + function testOnTakeStakedWithDelegate() public { + _testOnTake(true, true); + } + + function _testOnTakePartialBurn(bool withDelegate, bool withStaking) internal { + address urn = _clipperSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); @@ -967,6 +925,10 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), 0); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 65); // Time passes to let the auction price to crash @@ -988,28 +950,32 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 assertEq(stkMkr.balanceOf(address(urn)), 0); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } - function testOnTakePartialBurnNoDelegate() public { - _testOnTakePartialBurn(false); + function testOnTakePartialBurnNoStakedNoDelegate() public { + _testOnTakePartialBurn(false, false); } - function testOnTakePartialBurnWithDelegate() public { - _testOnTakePartialBurn(true); + function testOnTakePartialBurnNoStakedWithDelegate() public { + _testOnTakePartialBurn(true, false); } - function _testOnTakeNoBurn(bool withDelegate) internal { - address urn = _clipperSetUp(withDelegate); + function testOnTakePartialBurnStakedNoDelegate() public { + _testOnTakePartialBurn(false, true); + } - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + function testOnTakePartialBurnStakedWithDelegate() public { + _testOnTakePartialBurn(true, true); + } + + function _testOnTakeNoBurn(bool withDelegate, bool withStaking) internal { + address urn = _clipperSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); @@ -1037,6 +1003,10 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); assertEq(stkMkr.balanceOf(address(urn)), 0); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash @@ -1058,16 +1028,28 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply); // Can't burn anything assertEq(stkMkr.balanceOf(address(urn)), 0); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(farm.balanceOf(address(urn)), 0); + } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); assertLt(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt } - function testOnTakeNoBurnNoDelegate() public { - _testOnTakeNoBurn(false); + function testOnTakeNoBurnNoStakedNoDelegate() public { + _testOnTakeNoBurn(false, false); } - function testOnTakeNoBurnWithDelegate() public { - _testOnTakeNoBurn(true); + function testOnTakeNoBurnNoStakedWithDelegate() public { + _testOnTakeNoBurn(true, false); + } + + function testOnTakeNoBurnStakedNoDelegate() public { + _testOnTakeNoBurn(false, true); + } + + function testOnTakeNoBurnStakedWithDelegate() public { + _testOnTakeNoBurn(true, true); } function testOnTakeLeftoversOverflow() public { @@ -1075,16 +1057,8 @@ contract LockstakeEngineTest is DssTest { vm.prank(address(pauseProxy)); engine.onTakeLeftovers(address(1), 0, uint256(type(int256).max) + 1); } - function _testOnYank(bool withDelegate) internal { - address urn = _clipperSetUp(withDelegate); - - if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + function _testOnYank(bool withDelegate, bool withStaking) internal { + address urn = _clipperSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); @@ -1114,11 +1088,19 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.totalSupply(), mkrInitialSupply - 100_000 * 10**18); } - function testOnYankNoDelegate() public { - _testOnYank(false); + function testOnYankNoStakedNoDelegate() public { + _testOnYank(false, false); + } + + function testOnYankNoStakedWithDelegate() public { + _testOnYank(true, false); + } + + function testOnYankStakedNoDelegate() public { + _testOnYank(false, true); } - function testOnYankWithDelegate() public { - _testOnYank(true); + function testOnYankStakedWithDelegate() public { + _testOnYank(true, true); } } From 32309af5ff0b9f61f742aa503124f2afe4d54017 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 15 Jan 2024 23:54:42 -0300 Subject: [PATCH 038/111] Check missing events in tests --- test/LockstakeEngine.t.sol | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 1aa860e8..8ddb4b16 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -43,6 +43,7 @@ interface JugLike { } interface DogLike { + function ilks(bytes32) external view returns (address, uint256, uint256, uint256); function rely(address) external; function file(bytes32, bytes32, address) external; function file(bytes32, bytes32, uint256) external; @@ -86,6 +87,8 @@ contract LockstakeEngineTest is DssTest { event AddFarm(address farm); event DelFarm(address farm); event Open(address indexed owner, uint256 indexed index, address urn); + event Hope(address indexed urn, address indexed usr); + event Nope(address indexed urn, address indexed usr); event SelectDelegate(address indexed urn, address indexed delegate_); event SelectFarm(address indexed urn, address farm, uint16 ref); event Lock(address indexed urn, uint256 wad, uint16 ref); @@ -98,6 +101,7 @@ contract LockstakeEngineTest is DssTest { event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); event OnTakeLeftovers(address indexed urn, uint256 sold, uint256 burn, uint256 refund); + event OnYank(address indexed urn, uint256 wad); function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { @@ -275,11 +279,15 @@ contract LockstakeEngineTest is DssTest { assertTrue(engine.isUrnAuth(urn, urnOwner)); assertTrue(!engine.isUrnAuth(urn, urnAuthed)); assertEq(engine.urnCan(urn, urnAuthed), 0); + vm.expectEmit(true, true, true, true); + emit Hope(urn, urnAuthed); engine.hope(urn, urnAuthed); assertEq(engine.urnCan(urn, urnAuthed), 1); assertTrue(engine.isUrnAuth(urn, urnAuthed)); vm.stopPrank(); vm.startPrank(urnAuthed); + vm.expectEmit(true, true, true, true); + emit Hope(urn, address(789)); engine.hope(urn, address(789)); mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18, 0); @@ -298,6 +306,8 @@ contract LockstakeEngineTest is DssTest { engine.wipe(urn, 1); engine.selectFarm(urn, address(farm), 0); engine.getReward(urn, address(farm), address(0)); + vm.expectEmit(true, true, true, true); + emit Nope(urn, urnAuthed); engine.nope(urn, urnAuthed); assertEq(engine.urnCan(urn, urnAuthed), 0); assertTrue(!engine.isUrnAuth(urn, urnAuthed)); @@ -654,6 +664,10 @@ contract LockstakeEngineTest is DssTest { pip.setPrice(0.05 * 10**18); // Force liquidation SpotterLike(spot).poke(ilk); assertEq(clip.kicks(), 0); + (,, uint256 hole,) = DogLike(dog).ilks(ilk); + uint256 kicked = hole < 2_000 * 10**45 ? 100_000 * 10**18 * hole / (2_000 * 10**45) : 100_000 * 10**18; + vm.expectEmit(true, true, true, true); + emit OnKick(urn, kicked); id = DogLike(dog).bark(ilk, address(urn), address(this)); assertEq(clip.kicks(), 1); } @@ -826,6 +840,8 @@ contract LockstakeEngineTest is DssTest { vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); assertEq(mkr.balanceOf(buyer), 0); + vm.expectEmit(true, true, true, true); + emit OnTake(urn, buyer, 20_000 * 10**18); vm.prank(buyer); clip.take(id, 20_000 * 10**18, type(uint256).max, buyer, ""); assertEq(mkr.balanceOf(buyer), 20_000 * 10**18); @@ -853,6 +869,10 @@ contract LockstakeEngineTest is DssTest { } assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit OnTake(urn, buyer, 12_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit OnTakeLeftovers(urn, 32_000 * 10**18, 32_000 * 10**18 * engine.fee() / WAD, 100_000 * 10**18 - 32_000 * 10**18 - 32_000 * 10**18 * engine.fee() / WAD); vm.prank(buyer); clip.take(id, 12_000 * 10**18, type(uint256).max, buyer, ""); assertEq(mkr.balanceOf(buyer), 32_000 * 10**18); @@ -937,6 +957,10 @@ contract LockstakeEngineTest is DssTest { vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); assertEq(mkr.balanceOf(buyer), 0); + vm.expectEmit(true, true, true, true); + emit OnTake(urn, buyer, 91428571428571428571428); + vm.expectEmit(true, true, true, true); + emit OnTakeLeftovers(urn, 91428571428571428571428, 100_000 * 10**18 - 91428571428571428571428, 0); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); assertEq(mkr.balanceOf(buyer), 91428571428571428571428); @@ -1015,6 +1039,8 @@ contract LockstakeEngineTest is DssTest { vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); vm.prank(buyer); VatLike(vat).hope(address(clip)); assertEq(mkr.balanceOf(buyer), 0); + vm.expectEmit(true, true, true, true); + emit OnTake(urn, buyer, 100_000 * 10**18); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); assertEq(mkr.balanceOf(buyer), 100_000 * 10**18); @@ -1074,6 +1100,8 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tic, block.timestamp); assertEq(sale.top, pip.read() * (1.25 * 10**9)); + vm.expectEmit(true, true, true, true); + emit OnYank(urn, 100_000 * 10**18); vm.prank(pauseProxy); clip.yank(id); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); From 977123e9b322bc6883aa939808994c9e8774d808 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 16 Jan 2024 18:00:43 -0300 Subject: [PATCH 039/111] Improve testConstructor --- src/LockstakeEngine.sol | 8 ++++---- test/LockstakeEngine.t.sol | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index e35cf58e..7b601199 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -142,14 +142,14 @@ contract LockstakeEngine is Multicall { mkr = delegateFactory.gov(); stkMkr = GemLike(stkMkr_); fee = fee_; - nst.approve(nstJoin_, type(uint256).max); - vat.hope(nstJoin_); mkrNgt = MkrNgtLike(mkrNgt_); ngt = GemLike(mkrNgt.ngt()); - ngt.approve(address(mkrNgt), type(uint256).max); - mkr.approve(address(mkrNgt), type(uint256).max); mkrNgtRate = mkrNgt.rate(); urnImplementation = address(new LockstakeUrn(address(vat), stkMkr_)); + vat.hope(nstJoin_); + nst.approve(nstJoin_, type(uint256).max); + ngt.approve(address(mkrNgt), type(uint256).max); + mkr.approve(address(mkrNgt), type(uint256).max); wards[msg.sender] = 1; emit Rely(msg.sender); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 8ddb4b16..082e0756 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -172,15 +172,20 @@ contract LockstakeEngineTest is DssTest { LockstakeEngine e = new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), 100, address(mkrNgt)); assertEq(address(e.delegateFactory()), address(delFactory)); assertEq(address(e.nstJoin()), address(nstJoin)); + assertEq(address(e.vat()), vat); + assertEq(address(e.nst()), address(nst)); assertEq(e.ilk(), "aaa"); assertEq(address(e.mkr()), address(mkr)); assertEq(address(e.stkMkr()), address(stkMkr)); assertEq(e.fee(), 100); assertEq(address(e.mkrNgt()), address(mkrNgt)); assertEq(address(e.ngt()), address(ngt)); - assertEq(ngt.allowance(address(e), address(mkrNgt)), type(uint256).max); - assertEq(mkr.allowance(address(e), address(mkrNgt)), type(uint256).max); assertEq(e.mkrNgtRate(), 25_000); + // TODO: Add something to verify urnImplementation + assertEq(VatLike(vat).can(address(e), address(nstJoin)), 1); + assertEq(nst.allowance(address(e), address(nstJoin)), type(uint256).max); + assertEq(ngt.allowance(address(e), address(mkrNgt)), type(uint256).max); + assertEq(mkr.allowance(address(e), address(mkrNgt)), type(uint256).max); assertEq(e.wards(address(this)), 1); } From 28143e92a06acbb96338f8d8d96240910ad54934 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 17 Jan 2024 08:07:58 -0300 Subject: [PATCH 040/111] Check urnImplementation in testConstructor --- test/LockstakeEngine.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 082e0756..9680257d 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -181,7 +181,9 @@ contract LockstakeEngineTest is DssTest { assertEq(address(e.mkrNgt()), address(mkrNgt)); assertEq(address(e.ngt()), address(ngt)); assertEq(e.mkrNgtRate(), 25_000); - // TODO: Add something to verify urnImplementation + assertEq(LockstakeUrn(e.urnImplementation()).engine(), address(e)); + assertEq(address(LockstakeUrn(e.urnImplementation()).vat()), vat); + assertEq(address(LockstakeUrn(e.urnImplementation()).stkMkr()), address(stkMkr)); assertEq(VatLike(vat).can(address(e), address(nstJoin)), 1); assertEq(nst.allowance(address(e), address(nstJoin)), type(uint256).max); assertEq(ngt.allowance(address(e), address(mkrNgt)), type(uint256).max); From 60a54e08ddaa23360046e6906fca3fb647b3fb1a Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 17 Jan 2024 12:11:11 -0300 Subject: [PATCH 041/111] Improve tests --- test/LockstakeEngine.t.sol | 100 ++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 35 deletions(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 9680257d..2cdd0c30 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -124,7 +124,7 @@ contract LockstakeEngineTest is DssTest { rTok = new GemMock(0); farm = new StakingRewardsMock(address(rTok), address(stkMkr)); ngt = new GemMock(0); - mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 25_000); + mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 24_000); pip = new PipMock(); delFactory = new DelegateFactoryMock(address(mkr)); @@ -146,7 +146,7 @@ contract LockstakeEngineTest is DssTest { vm.stopPrank(); deal(address(mkr), address(this), 100_000 * 10**18, true); - deal(address(ngt), address(this), 100_000 * 25_000 * 10**18, true); + deal(address(ngt), address(this), 100_000 * 24_000 * 10**18, true); // Add some existing DAI assigned to nstJoin to avoid a particular error stdstore.target(address(vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); @@ -180,7 +180,7 @@ contract LockstakeEngineTest is DssTest { assertEq(e.fee(), 100); assertEq(address(e.mkrNgt()), address(mkrNgt)); assertEq(address(e.ngt()), address(ngt)); - assertEq(e.mkrNgtRate(), 25_000); + assertEq(e.mkrNgtRate(), 24_000); assertEq(LockstakeUrn(e.urnImplementation()).engine(), address(e)); assertEq(address(LockstakeUrn(e.urnImplementation()).vat()), vat); assertEq(address(LockstakeUrn(e.urnImplementation()).stkMkr()), address(stkMkr)); @@ -195,6 +195,10 @@ contract LockstakeEngineTest is DssTest { checkAuth(address(engine), "LockstakeEngine"); } + function testFile() public { + checkFileAddress(address(engine), "LockstakeEngine", ["jug"]); + } + function testModifiers() public { bytes4[] memory authedMethods = new bytes4[](6); authedMethods[0] = engine.addFarm.selector; @@ -211,14 +215,14 @@ contract LockstakeEngineTest is DssTest { bytes4[] memory urnOwnersMethods = new bytes4[](11); urnOwnersMethods[0] = engine.hope.selector; urnOwnersMethods[1] = engine.nope.selector; - urnOwnersMethods[2] = engine.lock.selector; - urnOwnersMethods[3] = engine.lockNgt.selector; - urnOwnersMethods[4] = engine.free.selector; - urnOwnersMethods[5] = engine.freeNgt.selector; - urnOwnersMethods[6] = engine.selectDelegate.selector; - urnOwnersMethods[7] = engine.draw.selector; - urnOwnersMethods[8] = engine.wipe.selector; - urnOwnersMethods[9] = engine.selectFarm.selector; + urnOwnersMethods[2] = engine.selectDelegate.selector; + urnOwnersMethods[3] = engine.selectFarm.selector; + urnOwnersMethods[4] = engine.lock.selector; + urnOwnersMethods[5] = engine.lockNgt.selector; + urnOwnersMethods[6] = engine.free.selector; + urnOwnersMethods[7] = engine.freeNgt.selector; + urnOwnersMethods[8] = engine.draw.selector; + urnOwnersMethods[9] = engine.wipe.selector; urnOwnersMethods[10] = engine.getReward.selector; vm.startPrank(address(0xBEEF)); @@ -226,10 +230,6 @@ contract LockstakeEngineTest is DssTest { vm.stopPrank(); } - function testFile() public { - checkFileAddress(address(engine), "LockstakeEngine", ["jug"]); - } - function testAddDelFarm() public { assertEq(engine.farms(address(1111)), 0); vm.expectEmit(true, true, true, true); @@ -267,11 +267,11 @@ contract LockstakeEngineTest is DssTest { vm.expectEmit(true, true, true, true); emit Open(address(this), 1, engine.getUrn(address(this), 1)); - assertEq(engine.getUrn(address(this), 1), engine.open(1)); + assertEq(engine.open(1), engine.getUrn(address(this), 1)); assertEq(engine.usrAmts(address(this)), 2); vm.expectEmit(true, true, true, true); emit Open(address(this), 2, engine.getUrn(address(this), 2)); - assertEq(engine.getUrn(address(this), 2), engine.open(2)); + assertEq(engine.open(2), engine.getUrn(address(this), 2)); assertEq(engine.usrAmts(address(this)), 3); } @@ -280,7 +280,7 @@ contract LockstakeEngineTest is DssTest { address urnAuthed = address(456); vm.prank(pauseProxy); engine.addFarm(address(farm)); mkr.transfer(urnAuthed, 100_000 * 10**18); - ngt.transfer(urnAuthed, 100_000 * 25_000 * 10**18); + ngt.transfer(urnAuthed, 100_000 * 24_000 * 10**18); vm.startPrank(urnOwner); address urn = engine.open(0); assertTrue(engine.isUrnAuth(urn, urnOwner)); @@ -301,10 +301,10 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.free(urn, address(this), 50_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); - ngt.approve(address(engine), 100_000 * 25_000 * 10**18); - engine.lockNgt(urn, 100_000 * 25_000 * 10**18, 0); + ngt.approve(address(engine), 100_000 * 24_000 * 10**18); + engine.lockNgt(urn, 100_000 * 24_000 * 10**18, 0); assertEq(_ink(ilk, urn), 150_000 * 10**18); - engine.freeNgt(urn, address(this), 50_000 * 25_000 * 10**18); + engine.freeNgt(urn, address(this), 50_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.selectDelegate(urn, voterDelegate); assertEq(engine.urnDelegates(urn), voterDelegate); @@ -325,6 +325,8 @@ contract LockstakeEngineTest is DssTest { address urn = engine.open(0); vm.expectRevert("LockstakeEngine/not-valid-delegate"); engine.selectDelegate(urn, address(111)); + vm.expectEmit(true, true, true, true); + emit SelectDelegate(urn, voterDelegate); engine.selectDelegate(urn, voterDelegate); vm.expectRevert("LockstakeEngine/same-delegate"); engine.selectDelegate(urn, voterDelegate); @@ -454,6 +456,12 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } assertEq(mkr.totalSupply(), initialMkrSupply - 50_000 * 10**18 * 15 / 100); + if (withStaking) { + mkr.approve(address(engine), 1); + vm.prank(pauseProxy); engine.delFarm(address(farm)); + vm.expectRevert("Lockstake/farm-not-whitelisted-anymore"); + engine.lock(urn, 1, 0); + } } function testLockFreeNoDelegateNoStaking() public { @@ -473,9 +481,9 @@ contract LockstakeEngineTest is DssTest { } function _testLockFreeNgt(bool withDelegate, bool withStaking) internal { - uint256 initialSupply = ngt.totalSupply(); + uint256 initialNgtSupply = ngt.totalSupply(); address urn = engine.open(0); - // Note: wad-overflow cannot be reached for lockNgt and freeNgt as we these functions will first divide by the rate + // Note: wad-overflow cannot be reached for lockNgt and freeNgt as we these functions and the value of rate (>=3) the MKR amount will be always lower if (withDelegate) { engine.selectDelegate(urn, voterDelegate); } @@ -485,10 +493,10 @@ contract LockstakeEngineTest is DssTest { } assertEq(_ink(ilk, urn), 0); assertEq(stkMkr.balanceOf(urn), 0); - ngt.approve(address(engine), 100_000 * 25_000 * 10**18); + ngt.approve(address(engine), 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); - emit LockNgt(urn, 100_000 * 25_000 * 10**18, 5); - engine.lockNgt(urn, 100_000 * 25_000 * 10**18, 5); + emit LockNgt(urn, 100_000 * 24_000 * 10**18, 5); + engine.lockNgt(urn, 100_000 * 24_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); @@ -503,10 +511,10 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(ngt.totalSupply(), initialSupply - 100_000 * 25_000 * 10**18); + assertEq(ngt.totalSupply(), initialNgtSupply - 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); - emit FreeNgt(urn, address(this), 40_000 * 25_000 * 10**18, 40_000 * 10**18 * 15 / 100); - engine.freeNgt(urn, address(this), 40_000 * 25_000 * 10**18); + emit FreeNgt(urn, address(this), 40_000 * 24_000 * 10**18, 40_000 * 10**18 * 15 / 100); + engine.freeNgt(urn, address(this), 40_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); @@ -514,10 +522,10 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); } - assertEq(ngt.balanceOf(address(this)), 40_000 * 25_000 * 10**18 - 40_000 * 25_000 * 10**18 * 15 / 100); + assertEq(ngt.balanceOf(address(this)), 40_000 * 24_000 * 10**18 - 40_000 * 24_000 * 10**18 * 15 / 100); vm.expectEmit(true, true, true, true); - emit FreeNgt(urn, address(123), 10_000 * 25_000 * 10**18, 10_000 * 10**18 * 15 / 100); - engine.freeNgt(urn, address(123), 10_000 * 25_000 * 10**18); + emit FreeNgt(urn, address(123), 10_000 * 24_000 * 10**18, 10_000 * 10**18 * 15 / 100); + engine.freeNgt(urn, address(123), 10_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 50_000 * 10**18); @@ -525,14 +533,20 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); } - assertEq(ngt.balanceOf(address(123)), 10_000 * 25_000 * 10**18 - 10_000 * 25_000 * 10**18 * 15 / 100); + assertEq(ngt.balanceOf(address(123)), 10_000 * 24_000 * 10**18 - 10_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.balanceOf(address(voterDelegate)), 50_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } - assertEq(ngt.totalSupply(), initialSupply - (100_000 - 50_000) * 25_000 * 10**18 - 50_000 * 25_000 * 10**18 * 15 / 100); + assertEq(ngt.totalSupply(), initialNgtSupply - (100_000 - 50_000) * 24_000 * 10**18 - 50_000 * 24_000 * 10**18 * 15 / 100); + if (withStaking) { + ngt.approve(address(engine), 24_000); + vm.prank(pauseProxy); engine.delFarm(address(farm)); + vm.expectRevert("Lockstake/farm-not-whitelisted-anymore"); + engine.lockNgt(urn, 24_000, 0); + } } function testLockFreeNgtNoDelegateNoStaking() public { @@ -599,18 +613,34 @@ contract LockstakeEngineTest is DssTest { address urn = engine.getUrn(address(this), 0); + assertEq(engine.usrAmts(address(this)), 0); + assertEq(_ink(ilk, urn), 0); + assertEq(farm.balanceOf(address(urn)), 0); + assertEq(stkMkr.balanceOf(address(farm)), 0); + + vm.expectEmit(true, true, true, true); + emit Open(address(this), 0 , urn); + vm.expectEmit(true, true, true, true); + emit Lock(urn, 100_000 * 10**18, uint16(5)); + vm.expectEmit(true, true, true, true); + emit SelectFarm(urn, address(farm), uint16(5)); bytes[] memory callsToExecute = new bytes[](3); callsToExecute[0] = abi.encodeWithSignature("open(uint256)", 0); callsToExecute[1] = abi.encodeWithSignature("lock(address,uint256,uint16)", urn, 100_000 * 10**18, uint16(5)); callsToExecute[2] = abi.encodeWithSignature("selectFarm(address,address,uint16)", urn, address(farm), uint16(5)); engine.multicall(callsToExecute); + + assertEq(engine.usrAmts(address(this)), 1); + assertEq(_ink(ilk, urn), 100_000 * 10**18); + assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); } function testGetReward() public { vm.prank(pauseProxy); engine.addFarm(address(farm)); address urn = engine.open(0); farm.setReward(address(urn), 20_000); - assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(this)), 0); + assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 0); vm.expectEmit(true, true, true, true); emit GetReward(urn, address(farm), address(123), 20_000); assertEq(engine.getReward(urn, address(farm), address(123)), 20_000); From b60cf82d375c6d18c1c952d345c7dc5debe3b97a Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 18 Jan 2024 11:19:18 -0300 Subject: [PATCH 042/111] Remove comment --- src/LockstakeEngine.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 7b601199..26dcd9a6 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -387,7 +387,7 @@ contract LockstakeEngine is Multicall { uint256 inkBeforeKick = ink + wad; _selectDelegate(urn, inkBeforeKick, urnDelegates[urn], address(0)); _selectFarm(urn, inkBeforeKick, urnFarms[urn], address(0), 0); - stkMkr.burn(urn, wad); // Burn the liquidated amount of staking token + stkMkr.burn(urn, wad); emit OnKick(urn, wad); } From 64a14dc2a4332c2a80510e9bc8574272cb86dfdc Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 18 Jan 2024 23:17:12 -0300 Subject: [PATCH 043/111] More minor test changes + new one --- test/LockstakeEngine.t.sol | 158 ++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 80 deletions(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 2cdd0c30..e5ad5d30 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -683,9 +683,11 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 2_000 * 10**18); if (withDelegate) { + assertEq(engine.urnDelegates(urn), address(voterDelegate)); assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); assertEq(mkr.balanceOf(address(engine)), 0); } else { + assertEq(engine.urnDelegates(urn), address(0)); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } if (withStaking) { @@ -711,24 +713,7 @@ contract LockstakeEngineTest is DssTest { function _testOnKickFull(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); - - if (withDelegate) { - assertEq(engine.urnDelegates(urn), address(voterDelegate)); - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - if (withStaking) { - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); - assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); - } else { - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); - } uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -745,8 +730,10 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 0); assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); - assertEq(engine.urnDelegates(urn), address(0)); // Always undelegates everything - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); + } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); @@ -756,44 +743,26 @@ contract LockstakeEngineTest is DssTest { assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); } - function testOnKickFullNoStakedNoDelegate() public { + function testOnKickFullNoStakingNoDelegate() public { _testOnKickFull(false, false); } - function testOnKickFullNoStakedWithDelegate() public { + function testOnKickFullNoStakingWithDelegate() public { _testOnKickFull(true, false); } - function testOnKickFullStakedNoDelegate() public { + function testOnKickFullWithStakingNoDelegate() public { _testOnKickFull(false, true); } - function testOnKickFullStakedWithDelegate() public { + function testOnKickFullWithStakingWithDelegate() public { _testOnKickFull(true, true); } function _testOnKickPartial(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); - - if (withDelegate) { - assertEq(engine.urnDelegates(urn), address(voterDelegate)); - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); - assertEq(mkr.balanceOf(address(engine)), 0); - } else { - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - } - if (withStaking) { - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); - assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); - } else { - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); - } uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -810,41 +779,41 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 1_500 * 10**18); assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); - assertEq(engine.urnDelegates(urn), address(0)); // Always undelegates everything - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + if (withDelegate) { + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(mkr.balanceOf(address(voterDelegate)), 0); + } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); } - function testOnKickPartialNoStakedNoDelegate() public { + function testOnKickPartialNoStakingNoDelegate() public { _testOnKickPartial(false, false); } - function testOnKickPartialNoStakedWithDelegate() public { + function testOnKickPartialNoStakingWithDelegate() public { _testOnKickPartial(true, false); } - function testOnKickPartialStakedNoDelegate() public { + function testOnKickPartialWithStakingNoDelegate() public { _testOnKickPartial(false, true); } - function testOnKickPartialStakedWithDelegate() public { + function testOnKickPartialWithStakingWithDelegate() public { _testOnKickPartial(true, true); } function _testOnTake(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); - uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); - uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -864,13 +833,12 @@ contract LockstakeEngineTest is DssTest { if (withDelegate) { assertEq(mkr.balanceOf(address(voterDelegate)), 0); } - assertEq(mkr.balanceOf(address(voterDelegate)), 0); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(address(urn)), 0); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); address buyer = address(888); @@ -899,11 +867,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(voterDelegate)), 0); } assertEq(mkr.balanceOf(address(engine)), 80_000 * 10**18); - assertEq(stkMkr.balanceOf(address(urn)), 0); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.expectEmit(true, true, true, true); @@ -928,39 +896,73 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(mkr.totalSupply(), mkrInitialSupply - 32_000 * 0.15 * 10**18); - assertEq(stkMkr.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 1.15 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } - function testOnTakeNoStakedNoDelegate() public { + function testOnTakeNoWithStakingNoDelegate() public { _testOnTake(false, false); } - function testOnTakeNoStakedWithDelegate() public { + function testOnTakeNoWithStakingWithDelegate() public { _testOnTake(true, false); } - function testOnTakeStakedNoDelegate() public { + function testOnTakeWithStakingNoDelegate() public { _testOnTake(false, true); } - function testOnTakeStakedWithDelegate() public { + function testOnTakeWithStakingWithDelegate() public { _testOnTake(true, true); } + function testOnTakeResetsAgainDelegateAndFarm() public { + address urn = _clipperSetUp(true, true); + + assertEq(engine.urnDelegates(urn), voterDelegate); + assertEq(engine.urnFarms(urn), address(farm)); + assertEq(farm.balanceOf(urn), 100_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); + + vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + uint256 id = _forceLiquidation(urn); + + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(engine.urnFarms(urn), address(0)); + assertEq(farm.balanceOf(urn), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); + + // User locks again MKR on Delegate and Farm + engine.selectDelegate(urn, voterDelegate); + engine.selectFarm(urn, address(farm), 0); + + assertEq(engine.urnDelegates(urn), voterDelegate); + assertEq(engine.urnFarms(urn), address(farm)); + assertEq(farm.balanceOf(urn), 75_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate), 75_000 * 10**18); + + address buyer = address(888); + vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); + vm.prank(buyer); VatLike(vat).hope(address(clip)); + vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); + + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(engine.urnFarms(urn), address(0)); + assertEq(farm.balanceOf(urn), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); + } + function _testOnTakePartialBurn(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); - uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); - uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -981,11 +983,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(voterDelegate)), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(address(urn)), 0); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 65); // Time passes to let the auction price to crash @@ -1010,39 +1012,37 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 - assertEq(stkMkr.balanceOf(address(urn)), 0); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); } - function testOnTakePartialBurnNoStakedNoDelegate() public { + function testOnTakePartialBurnNoStakingNoDelegate() public { _testOnTakePartialBurn(false, false); } - function testOnTakePartialBurnNoStakedWithDelegate() public { + function testOnTakePartialBurnNoStakingWithDelegate() public { _testOnTakePartialBurn(true, false); } - function testOnTakePartialBurnStakedNoDelegate() public { + function testOnTakePartialBurnWithStakingNoDelegate() public { _testOnTakePartialBurn(false, true); } - function testOnTakePartialBurnStakedWithDelegate() public { + function testOnTakePartialBurnWithStakingWithDelegate() public { _testOnTakePartialBurn(true, true); } function _testOnTakeNoBurn(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); - uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); uint256 vowInitialBalance = VatLike(vat).dai(vow); - uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -1063,11 +1063,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(voterDelegate)), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(address(urn)), 0); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash @@ -1090,28 +1090,28 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply); // Can't burn anything - assertEq(stkMkr.balanceOf(address(urn)), 0); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } + assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); assertLt(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt } - function testOnTakeNoBurnNoStakedNoDelegate() public { + function testOnTakeNoBurnNoStakingNoDelegate() public { _testOnTakeNoBurn(false, false); } - function testOnTakeNoBurnNoStakedWithDelegate() public { + function testOnTakeNoBurnNoStakingWithDelegate() public { _testOnTakeNoBurn(true, false); } - function testOnTakeNoBurnStakedNoDelegate() public { + function testOnTakeNoBurnWithStakingNoDelegate() public { _testOnTakeNoBurn(false, true); } - function testOnTakeNoBurnStakedWithDelegate() public { + function testOnTakeNoBurnWithStakingWithDelegate() public { _testOnTakeNoBurn(true, true); } @@ -1122,9 +1122,7 @@ contract LockstakeEngineTest is DssTest { function _testOnYank(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); - uint256 mkrInitialSupply = mkr.totalSupply(); - uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -1153,19 +1151,19 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.totalSupply(), mkrInitialSupply - 100_000 * 10**18); } - function testOnYankNoStakedNoDelegate() public { + function testOnYankNoStakingNoDelegate() public { _testOnYank(false, false); } - function testOnYankNoStakedWithDelegate() public { + function testOnYankNoStakingWithDelegate() public { _testOnYank(true, false); } - function testOnYankStakedNoDelegate() public { + function testOnYankWithStakingNoDelegate() public { _testOnYank(false, true); } - function testOnYankStakedWithDelegate() public { + function testOnYankWithStakingWithDelegate() public { _testOnYank(true, true); } } From 4113d3b7a5b7b58a54ae959006768f3dd029e054 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:00:53 -0300 Subject: [PATCH 044/111] Minor changes in tests Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- test/LockstakeEngine.t.sol | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index e5ad5d30..18ff209f 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -149,7 +149,7 @@ contract LockstakeEngineTest is DssTest { deal(address(ngt), address(this), 100_000 * 24_000 * 10**18, true); // Add some existing DAI assigned to nstJoin to avoid a particular error - stdstore.target(address(vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); + stdstore.target(vat).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); } function _ink(bytes32 ilk_, address urn) internal view returns (uint256 ink) { @@ -225,6 +225,7 @@ contract LockstakeEngineTest is DssTest { urnOwnersMethods[9] = engine.wipe.selector; urnOwnersMethods[10] = engine.getReward.selector; + // this checks the case where sender is a ward, hoping is checked separately on testHopeNope vm.startPrank(address(0xBEEF)); checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); vm.stopPrank(); @@ -422,7 +423,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } @@ -451,7 +452,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(address(voterDelegate)), 50_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate), 50_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } @@ -483,7 +484,7 @@ contract LockstakeEngineTest is DssTest { function _testLockFreeNgt(bool withDelegate, bool withStaking) internal { uint256 initialNgtSupply = ngt.totalSupply(); address urn = engine.open(0); - // Note: wad-overflow cannot be reached for lockNgt and freeNgt as we these functions and the value of rate (>=3) the MKR amount will be always lower + // Note: wad-overflow cannot be reached for lockNgt and freeNgt as with these functions and the value of rate (>=3) the MKR amount will be always lower if (withDelegate) { engine.selectDelegate(urn, voterDelegate); } @@ -507,7 +508,7 @@ contract LockstakeEngineTest is DssTest { assertEq(ngt.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } @@ -536,7 +537,7 @@ contract LockstakeEngineTest is DssTest { assertEq(ngt.balanceOf(address(123)), 10_000 * 24_000 * 10**18 - 10_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(address(voterDelegate)), 50_000 * 10**18); + assertEq(mkr.balanceOf(voterDelegate), 50_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } @@ -655,11 +656,11 @@ contract LockstakeEngineTest is DssTest { engine.rely(address(clip)); clip.upchost(); DogLike(dog).file(ilk, "clip", address(clip)); - clip.rely(address(dog)); + clip.rely(dog); DogLike(dog).rely(address(clip)); VatLike(vat).rely(address(clip)); - CalcLike calc = CalcLike(CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newLinearDecrease(address(pauseProxy))); + CalcLike calc = CalcLike(CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newLinearDecrease(pauseProxy)); calc.file("tau", 100); clip.file("buf", 1.25 * 10**27); // 25% Initial price buffer clip.file("calc", address(calc)); // File price contract @@ -683,8 +684,8 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 2_000 * 10**18); if (withDelegate) { - assertEq(engine.urnDelegates(urn), address(voterDelegate)); - assertEq(mkr.balanceOf(address(voterDelegate)), 100_000 * 10**18); + assertEq(engine.urnDelegates(urn), voterDelegate); + assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); assertEq(mkr.balanceOf(address(engine)), 0); } else { assertEq(engine.urnDelegates(urn), address(0)); @@ -732,7 +733,7 @@ contract LockstakeEngineTest is DssTest { if (withDelegate) { assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -781,7 +782,7 @@ contract LockstakeEngineTest is DssTest { if (withDelegate) { assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -831,7 +832,7 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -864,7 +865,7 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 80_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 80_000 * 10**18); if (withStaking) { @@ -980,7 +981,7 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -1008,7 +1009,7 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 0); if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 @@ -1060,7 +1061,7 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -1086,7 +1087,7 @@ contract LockstakeEngineTest is DssTest { assertEq(VatLike(vat).gem(ilk, address(clip)), 0); if (withDelegate) { - assertEq(mkr.balanceOf(address(voterDelegate)), 0); + assertEq(mkr.balanceOf(voterDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply); // Can't burn anything @@ -1117,7 +1118,7 @@ contract LockstakeEngineTest is DssTest { function testOnTakeLeftoversOverflow() public { vm.expectRevert("LockstakeEngine/refund-over-maxint"); - vm.prank(address(pauseProxy)); engine.onTakeLeftovers(address(1), 0, uint256(type(int256).max) + 1); + vm.prank(pauseProxy); engine.onTakeLeftovers(address(1), 0, uint256(type(int256).max) + 1); } function _testOnYank(bool withDelegate, bool withStaking) internal { From e807ca2e22779a8e619821492acdff0f944a3b62 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:02:43 +0200 Subject: [PATCH 045/111] Blocking farm/delegate re-selection during auctions (#27) * Blocking farm/delegate re-selection during auctions * Rename to onRemove, use it on yank, use _min * Test auctions counter and more onRemove events * Simplify test + minor changes --------- Co-authored-by: sunbreak1211 --- src/LockstakeClipper.sol | 6 +- src/LockstakeEngine.sol | 42 ++++++----- test/LockstakeEngine.t.sol | 114 ++++++++++++++++++----------- test/mocks/LockstakeEngineMock.sol | 2 +- 4 files changed, 101 insertions(+), 63 deletions(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index 753d7f12..9981e335 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -50,7 +50,7 @@ interface LockstakeEngineLike { function ilk() external view returns (bytes32); function onKick(address, uint256) external; function onTake(address, address, uint256) external; - function onTakeLeftovers(address, uint256, uint256) external; + function onRemove(address, uint256, uint256) external; function onYank(address, uint256) external; } @@ -410,11 +410,13 @@ contract LockstakeClipper { } if (lot == 0) { + uint256 tot = sales[id].tot; + engine.onRemove(usr, tot, 0); _remove(id); } else if (tab == 0) { uint256 tot = sales[id].tot; vat.slip(ilk, address(this), -int256(lot)); - engine.onTakeLeftovers(usr, tot - lot, lot); + engine.onRemove(usr, tot - lot, lot); _remove(id); } else { sales[id].tab = tab; diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 26dcd9a6..614c955a 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -72,6 +72,7 @@ contract LockstakeEngine is Multicall { mapping(address => mapping(address => uint256)) public urnCan; // urn => usr => allowed (1 = yes, 0 = no) mapping(address => address) public urnDelegates; // urn => current associated delegate mapping(address => address) public urnFarms; // urn => current selected farm + mapping(address => uint256) public urnAuctions; // urn => amount of ongoing liquidations JugLike public jug; // --- constants --- @@ -115,7 +116,7 @@ contract LockstakeEngine is Multicall { event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); - event OnTakeLeftovers(address indexed urn, uint256 sold, uint256 burn, uint256 refund); + event OnRemove(address indexed urn, uint256 sold, uint256 burn, uint256 refund); event OnYank(address indexed urn, uint256 wad); // --- modifiers --- @@ -157,6 +158,10 @@ contract LockstakeEngine is Multicall { // --- internals --- + function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x <= y ? x : y; + } + function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { z = x != 0 ? ((x - 1) / y) + 1 : 0; @@ -248,6 +253,7 @@ contract LockstakeEngine is Multicall { // --- delegation/staking functions --- function selectDelegate(address urn, address delegate) external urnAuth(urn) { + require(urnAuctions[urn] == 0, "LockstakeEngine/urn-in-auction"); require(delegate == address(0) || delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); address prevDelegate = urnDelegates[urn]; require(prevDelegate != delegate, "LockstakeEngine/same-delegate"); @@ -270,6 +276,7 @@ contract LockstakeEngine is Multicall { } function selectFarm(address urn, address farm, uint16 ref) external urnAuth(urn) { + require(urnAuctions[urn] == 0, "LockstakeEngine/urn-in-auction"); require(farm == address(0) || farms[farm] == 1, "LockstakeEngine/non-existing-farm"); address prevFarm = urnFarms[urn]; require(prevFarm != farm, "LockstakeEngine/same-farm"); @@ -388,6 +395,7 @@ contract LockstakeEngine is Multicall { _selectDelegate(urn, inkBeforeKick, urnDelegates[urn], address(0)); _selectFarm(urn, inkBeforeKick, urnFarms[urn], address(0), 0); stkMkr.burn(urn, wad); + urnAuctions[urn]++; emit OnKick(urn, wad); } @@ -396,30 +404,28 @@ contract LockstakeEngine is Multicall { emit OnTake(urn, who, wad); } - function onTakeLeftovers(address urn, uint256 sold, uint256 left) external auth { - uint256 burn = sold * fee / WAD; + function onRemove(address urn, uint256 sold, uint256 left) external auth { + uint256 burn; uint256 refund; - if (burn > left) { - burn = left; - refund = 0; - } else { + if (left > 0) { + burn = _min(sold * fee / WAD, left); + mkr.burn(address(this), burn); unchecked { refund = left - burn; } + if (refund > 0) { + // The following is ensured by the dog and clip but we still prefer to be explicit + require(refund <= uint256(type(int256).max), "LockstakeEngine/refund-over-maxint"); + vat.slip(ilk, urn, int256(refund)); + vat.frob(ilk, urn, urn, address(0), int256(refund), 0); + stkMkr.mint(urn, refund); + } } - mkr.burn(address(this), burn); - if (refund > 0) { - require(refund <= uint256(type(int256).max), "LockstakeEngine/refund-over-maxint"); // This is ensured by the dog and clip but we still prefer to be explicit - (uint256 ink,) = vat.urns(ilk, urn); // Get the ink value before adding the refund to correctly undelegate and unstake - vat.slip(ilk, urn, int256(refund)); - vat.frob(ilk, urn, urn, address(0), int256(refund), 0); - stkMkr.mint(urn, refund); - _selectDelegate(urn, ink, urnDelegates[urn], address(0)); - _selectFarm(urn, ink, urnFarms[urn], address(0), 0); - } - emit OnTakeLeftovers(urn, sold, burn, refund); + urnAuctions[urn]--; + emit OnRemove(urn, sold, burn, refund); } function onYank(address urn, uint256 wad) external auth { mkr.burn(address(this), wad); + urnAuctions[urn]--; emit OnYank(urn, wad); } } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 18ff209f..ba146c5c 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -100,7 +100,7 @@ contract LockstakeEngineTest is DssTest { event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); - event OnTakeLeftovers(address indexed urn, uint256 sold, uint256 burn, uint256 refund); + event OnRemove(address indexed urn, uint256 sold, uint256 burn, uint256 refund); event OnYank(address indexed urn, uint256 wad); function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { @@ -205,7 +205,7 @@ contract LockstakeEngineTest is DssTest { authedMethods[1] = engine.delFarm.selector; authedMethods[2] = engine.onKick.selector; authedMethods[3] = engine.onTake.selector; - authedMethods[4] = engine.onTakeLeftovers.selector; + authedMethods[4] = engine.onRemove.selector; authedMethods[5] = engine.onYank.selector; vm.startPrank(address(0xBEEF)); @@ -704,12 +704,14 @@ contract LockstakeEngineTest is DssTest { pip.setPrice(0.05 * 10**18); // Force liquidation SpotterLike(spot).poke(ilk); assertEq(clip.kicks(), 0); + assertEq(engine.urnAuctions(urn), 0); (,, uint256 hole,) = DogLike(dog).ilks(ilk); uint256 kicked = hole < 2_000 * 10**45 ? 100_000 * 10**18 * hole / (2_000 * 10**45) : 100_000 * 10**18; vm.expectEmit(true, true, true, true); emit OnKick(urn, kicked); id = DogLike(dog).bark(ilk, address(urn), address(this)); assertEq(clip.kicks(), 1); + assertEq(engine.urnAuctions(urn), 1); } function _testOnKickFull(bool withDelegate, bool withStaking) internal { @@ -878,9 +880,10 @@ contract LockstakeEngineTest is DssTest { vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 12_000 * 10**18); vm.expectEmit(true, true, true, true); - emit OnTakeLeftovers(urn, 32_000 * 10**18, 32_000 * 10**18 * engine.fee() / WAD, 100_000 * 10**18 - 32_000 * 10**18 - 32_000 * 10**18 * engine.fee() / WAD); + emit OnRemove(urn, 32_000 * 10**18, 32_000 * 10**18 * engine.fee() / WAD, 100_000 * 10**18 - 32_000 * 10**18 - 32_000 * 10**18 * engine.fee() / WAD); vm.prank(buyer); clip.take(id, 12_000 * 10**18, type(uint256).max, buyer, ""); assertEq(mkr.balanceOf(buyer), 32_000 * 10**18); + assertEq(engine.urnAuctions(urn), 0); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); assertEq(sale.pos, 0); @@ -922,42 +925,6 @@ contract LockstakeEngineTest is DssTest { _testOnTake(true, true); } - function testOnTakeResetsAgainDelegateAndFarm() public { - address urn = _clipperSetUp(true, true); - - assertEq(engine.urnDelegates(urn), voterDelegate); - assertEq(engine.urnFarms(urn), address(farm)); - assertEq(farm.balanceOf(urn), 100_000 * 10**18); - assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); - - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); - uint256 id = _forceLiquidation(urn); - - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(engine.urnFarms(urn), address(0)); - assertEq(farm.balanceOf(urn), 0); - assertEq(mkr.balanceOf(voterDelegate), 0); - - // User locks again MKR on Delegate and Farm - engine.selectDelegate(urn, voterDelegate); - engine.selectFarm(urn, address(farm), 0); - - assertEq(engine.urnDelegates(urn), voterDelegate); - assertEq(engine.urnFarms(urn), address(farm)); - assertEq(farm.balanceOf(urn), 75_000 * 10**18); - assertEq(mkr.balanceOf(voterDelegate), 75_000 * 10**18); - - address buyer = address(888); - vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); - vm.prank(buyer); VatLike(vat).hope(address(clip)); - vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); - - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(engine.urnFarms(urn), address(0)); - assertEq(farm.balanceOf(urn), 0); - assertEq(mkr.balanceOf(voterDelegate), 0); - } - function _testOnTakePartialBurn(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); @@ -1000,9 +967,10 @@ contract LockstakeEngineTest is DssTest { vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 91428571428571428571428); vm.expectEmit(true, true, true, true); - emit OnTakeLeftovers(urn, 91428571428571428571428, 100_000 * 10**18 - 91428571428571428571428, 0); + emit OnRemove(urn, 91428571428571428571428, 100_000 * 10**18 - 91428571428571428571428, 0); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); assertEq(mkr.balanceOf(buyer), 91428571428571428571428); + assertEq(engine.urnAuctions(urn), 0); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); @@ -1079,8 +1047,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(buyer), 0); vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 100_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit OnRemove(urn, 100_000 * 10**18, 0, 0); vm.prank(buyer); clip.take(id, 100_000 * 10**18, type(uint256).max, buyer, ""); assertEq(mkr.balanceOf(buyer), 100_000 * 10**18); + assertEq(engine.urnAuctions(urn), 0); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); @@ -1116,9 +1087,67 @@ contract LockstakeEngineTest is DssTest { _testOnTakeNoBurn(true, true); } - function testOnTakeLeftoversOverflow() public { + function testCannotSelectDuringAuction() public { + address urn = _clipperSetUp(true, true); + + assertEq(engine.urnDelegates(urn), voterDelegate); + assertEq(engine.urnFarms(urn), address(farm)); + + vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + uint256 id1 = _forceLiquidation(urn); + + assertEq(engine.urnDelegates(urn), address(0)); + assertEq(engine.urnFarms(urn), address(0)); + + vm.expectRevert("LockstakeEngine/urn-in-auction"); + engine.selectDelegate(urn, voterDelegate); + vm.expectRevert("LockstakeEngine/urn-in-auction"); + engine.selectFarm(urn, address(farm), 0); + + vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 1000 * 10**45); + uint256 id2 = DogLike(dog).bark(ilk, urn, address(this)); + + assertEq(engine.urnAuctions(urn), 2); + + vm.expectRevert("LockstakeEngine/urn-in-auction"); + engine.selectDelegate(urn, voterDelegate); + vm.expectRevert("LockstakeEngine/urn-in-auction"); + engine.selectFarm(urn, address(farm), 0); + + // Take with left > 0 + address buyer = address(888); + vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 4_000 * 10**45); + vm.prank(buyer); VatLike(vat).hope(address(clip)); + vm.expectEmit(true, true, true, true); + emit OnTake(urn, buyer, 8_000 * 10**18); // 500 / (0.05 * 1.25 ) + vm.expectEmit(true, true, true, true); + emit OnRemove(urn, 8_000 * 10**18, 8_000 * 10**18 * engine.fee() / WAD, 25_000 * 10**18 - 8_000 * 10**18 - 8_000 * 10**18 * engine.fee() / WAD); + vm.prank(buyer); clip.take(id1, 25_000 * 10**18, type(uint256).max, buyer, ""); + assertEq(engine.urnAuctions(urn), 1); + + vm.expectRevert("LockstakeEngine/urn-in-auction"); + engine.selectDelegate(urn, voterDelegate); + vm.expectRevert("LockstakeEngine/urn-in-auction"); + engine.selectFarm(urn, address(farm), 0); + + vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash + + // Take with left == 0 + vm.expectEmit(true, true, true, true); + emit OnTake(urn, buyer, 25_000 * 10**18); + vm.expectEmit(true, true, true, true); + emit OnRemove(urn, 25_000 * 10**18, 0, 0); + vm.prank(buyer); clip.take(id2, 25_000 * 10**18, type(uint256).max, buyer, ""); + assertEq(engine.urnAuctions(urn), 0); + + // Can select delegate and farm again + engine.selectDelegate(urn, voterDelegate); + engine.selectFarm(urn, address(farm), 0); + } + + function testOnRemoveOverflow() public { vm.expectRevert("LockstakeEngine/refund-over-maxint"); - vm.prank(pauseProxy); engine.onTakeLeftovers(address(1), 0, uint256(type(int256).max) + 1); + vm.prank(pauseProxy); engine.onRemove(address(1), 0, uint256(type(int256).max) + 1); } function _testOnYank(bool withDelegate, bool withStaking) internal { @@ -1148,6 +1177,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); + assertEq(engine.urnAuctions(urn), 0); assertEq(mkr.totalSupply(), mkrInitialSupply - 100_000 * 10**18); } diff --git a/test/mocks/LockstakeEngineMock.sol b/test/mocks/LockstakeEngineMock.sol index 6029954d..7f57d569 100644 --- a/test/mocks/LockstakeEngineMock.sol +++ b/test/mocks/LockstakeEngineMock.sol @@ -22,7 +22,7 @@ contract LockstakeEngineMock { VatLike(vat).slip(ilk, who, int256(wad)); } - function onTakeLeftovers(address urn, uint256, uint256 left) external { + function onRemove(address urn, uint256, uint256 left) external { VatLike(vat).slip(ilk, urn, int256(left)); } From bbe569d6b8a5a6944592109b9a8a94c1092be117 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:44:05 +0200 Subject: [PATCH 046/111] Migrate through user function (#26) * Add migrate(), no tests yet * Assume migrator is hoped * Use both auth and urnAuth modifiers in freeNoFee Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> * _fee => fee_, add tests * minor changes * Fix comments Co-authored-by: telome <130504305+telome@users.noreply.github.com> --------- Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Co-authored-by: sunbreak1211 Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- src/LockstakeEngine.sol | 19 ++++-- test/LockstakeEngine.t.sol | 133 ++++++++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 14 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 614c955a..e3fd37a4 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -111,6 +111,7 @@ contract LockstakeEngine is Multicall { event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); + event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); @@ -328,19 +329,25 @@ contract LockstakeEngine is Multicall { } function free(address urn, address to, uint256 wad) external urnAuth(urn) { - uint256 freed = _free(urn, wad); + uint256 freed = _free(urn, wad, fee); mkr.transfer(to, freed); emit Free(urn, to, wad, wad - freed); } function freeNgt(address urn, address to, uint256 ngtWad) external urnAuth(urn) { uint256 wad = ngtWad / mkrNgtRate; - uint256 freed = _free(urn, wad); + uint256 freed = _free(urn, wad, fee); mkrNgt.mkrToNgt(to, freed); emit FreeNgt(urn, to, ngtWad, wad - freed); } - function _free(address urn, uint256 wad) internal returns (uint256 freed) { + function freeNoFee(address urn, address to, uint256 wad) external auth urnAuth(urn) { + _free(urn, wad, 0); + mkr.transfer(to, wad); + emit FreeNoFee(urn, to, wad); + } + + function _free(address urn, uint256 wad, uint256 fee_) internal returns (uint256 freed) { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); address urnFarm = urnFarms[urn]; if (urnFarm != address(0)) { @@ -353,8 +360,10 @@ contract LockstakeEngine is Multicall { if (delegate != address(0)) { DelegateLike(delegate).free(wad); } - uint256 burn = wad * fee / WAD; - mkr.burn(address(this), burn); + uint256 burn = wad * fee_ / WAD; + if (burn > 0) { + mkr.burn(address(this), burn); + } unchecked { freed = wad - burn; } // burn <= WAD always } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index ba146c5c..613c67fe 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -95,6 +95,7 @@ contract LockstakeEngineTest is DssTest { event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); + event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); @@ -200,14 +201,16 @@ contract LockstakeEngineTest is DssTest { } function testModifiers() public { - bytes4[] memory authedMethods = new bytes4[](6); + bytes4[] memory authedMethods = new bytes4[](7); authedMethods[0] = engine.addFarm.selector; authedMethods[1] = engine.delFarm.selector; - authedMethods[2] = engine.onKick.selector; - authedMethods[3] = engine.onTake.selector; - authedMethods[4] = engine.onRemove.selector; - authedMethods[5] = engine.onYank.selector; + authedMethods[2] = engine.freeNoFee.selector; + authedMethods[3] = engine.onKick.selector; + authedMethods[4] = engine.onTake.selector; + authedMethods[5] = engine.onRemove.selector; + authedMethods[6] = engine.onYank.selector; + // this checks the case where sender is not authed vm.startPrank(address(0xBEEF)); checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); @@ -225,10 +228,19 @@ contract LockstakeEngineTest is DssTest { urnOwnersMethods[9] = engine.wipe.selector; urnOwnersMethods[10] = engine.getReward.selector; - // this checks the case where sender is a ward, hoping is checked separately on testHopeNope + // this checks the case when sender is not the urn owner and not hoped, the hoped case is checked in testHopeNope and the urn owner case in the specific tests vm.startPrank(address(0xBEEF)); checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); vm.stopPrank(); + + bytes4[] memory authedAndUrnOwnersMethods = new bytes4[](1); + authedAndUrnOwnersMethods[0] = engine.freeNoFee.selector; + + // this checks the case when sender is relied but is not the urn owner and is not hoped, the hoped case is checked in testHopeNope and the urn owner case in the specific tests + vm.prank(pauseProxy); engine.rely(address(0x123)); + vm.startPrank(address(0x123)); + checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); + vm.stopPrank(); } function testAddDelFarm() public { @@ -279,7 +291,11 @@ contract LockstakeEngineTest is DssTest { function testHopeNope() public { address urnOwner = address(123); address urnAuthed = address(456); - vm.prank(pauseProxy); engine.addFarm(address(farm)); + address authedAndUrnAuthed = address(789); + vm.startPrank(pauseProxy); + engine.rely(authedAndUrnAuthed); + engine.addFarm(address(farm)); + vm.stopPrank(); mkr.transfer(urnAuthed, 100_000 * 10**18); ngt.transfer(urnAuthed, 100_000 * 24_000 * 10**18); vm.startPrank(urnOwner); @@ -292,11 +308,12 @@ contract LockstakeEngineTest is DssTest { engine.hope(urn, urnAuthed); assertEq(engine.urnCan(urn, urnAuthed), 1); assertTrue(engine.isUrnAuth(urn, urnAuthed)); + engine.hope(urn, authedAndUrnAuthed); vm.stopPrank(); vm.startPrank(urnAuthed); vm.expectEmit(true, true, true, true); - emit Hope(urn, address(789)); - engine.hope(urn, address(789)); + emit Hope(urn, address(1111)); + engine.hope(urn, address(1111)); mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18, 0); assertEq(_ink(ilk, urn), 100_000 * 10**18); @@ -320,6 +337,8 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnCan(urn, urnAuthed), 0); assertTrue(!engine.isUrnAuth(urn, urnAuthed)); vm.stopPrank(); + vm.prank(authedAndUrnAuthed); engine.freeNoFee(urn, address(this), 50_000 * 10**18); + assertEq(_ink(ilk, urn), 50_000 * 10**18); } function testSelectDelegate() public { @@ -439,6 +458,12 @@ contract LockstakeEngineTest is DssTest { assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); } assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); + if (withDelegate) { + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 60_000 * 10**18); + } else { + assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); + } vm.expectEmit(true, true, true, true); emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 15 / 100); engine.free(urn, address(123), 10_000 * 10**18); @@ -524,6 +549,12 @@ contract LockstakeEngineTest is DssTest { assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); } assertEq(ngt.balanceOf(address(this)), 40_000 * 24_000 * 10**18 - 40_000 * 24_000 * 10**18 * 15 / 100); + if (withDelegate) { + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 60_000 * 10**18); + } else { + assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); + } vm.expectEmit(true, true, true, true); emit FreeNgt(urn, address(123), 10_000 * 24_000 * 10**18, 10_000 * 10**18 * 15 / 100); engine.freeNgt(urn, address(123), 10_000 * 24_000 * 10**18); @@ -566,6 +597,90 @@ contract LockstakeEngineTest is DssTest { _testLockFreeNgt(true, true); } + function _testFreeNoFee(bool withDelegate, bool withStaking) internal { + vm.prank(pauseProxy); engine.rely(address(this)); + uint256 initialMkrSupply = mkr.totalSupply(); + address urn = engine.open(0); + deal(address(mkr), address(this), 100_000 * 10**18); + mkr.approve(address(engine), 100_000 * 10**18); + vm.expectRevert("LockstakeEngine/wad-overflow"); + engine.freeNoFee(urn, address(this), uint256(type(int256).max) + 1); + if (withDelegate) { + engine.selectDelegate(urn, voterDelegate); + } + if (withStaking) { + vm.prank(pauseProxy); engine.addFarm(address(farm)); + engine.selectFarm(urn, address(farm), 0); + } + engine.lock(urn, 100_000 * 10**18, 5); + assertEq(_ink(ilk, urn), 100_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(farm.balanceOf(urn), 100_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + } + assertEq(mkr.balanceOf(address(this)), 0); + if (withDelegate) { + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + } else { + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); + } + assertEq(mkr.totalSupply(), initialMkrSupply); + vm.expectEmit(true, true, true, true); + emit FreeNoFee(urn, address(this), 40_000 * 10**18); + engine.freeNoFee(urn, address(this), 40_000 * 10**18); + assertEq(_ink(ilk, urn), 60_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(farm.balanceOf(urn), 60_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + } + assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18); + if (withDelegate) { + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 60_000 * 10**18); + } else { + assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); + } + vm.expectEmit(true, true, true, true); + emit FreeNoFee(urn, address(123), 10_000 * 10**18); + engine.freeNoFee(urn, address(123), 10_000 * 10**18); + assertEq(_ink(ilk, urn), 50_000 * 10**18); + if (withStaking) { + assertEq(stkMkr.balanceOf(address(farm)), 50_000 * 10**18); + assertEq(farm.balanceOf(urn), 50_000 * 10**18); + } else { + assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + } + assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18); + if (withDelegate) { + assertEq(mkr.balanceOf(address(engine)), 0); + assertEq(mkr.balanceOf(voterDelegate), 50_000 * 10**18); + } else { + assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); + } + assertEq(mkr.totalSupply(), initialMkrSupply); + } + + function testFreeNoFeeNoDelegateNoStaking() public { + _testFreeNoFee(false, false); + } + + function testFreeNoFeeWithDelegateNoStaking() public { + _testFreeNoFee(true, false); + } + + function testFreeNoFeeNoDelegateWithStaking() public { + _testFreeNoFee(false, true); + } + + function testFreeNoFeeWithDelegateWithStaking() public { + _testFreeNoFee(true, true); + } + function testDrawWipe() public { deal(address(mkr), address(this), 100_000 * 10**18, true); address urn = engine.open(0); From 0ae0af02d26e8991fd38a02f2761420203f2b78e Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 30 Jan 2024 07:31:30 -0300 Subject: [PATCH 047/111] Implement a more standard yank (#28) * Implement a more standard yank * Remove onYank * Minor change * Remove exit * Change comment * Add check Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/LockstakeClipper.sol | 7 ++--- src/LockstakeEngine.sol | 7 ----- test/LockstakeClipper.t.sol | 8 ++++-- test/LockstakeEngine.t.sol | 46 +++++++----------------------- test/mocks/LockstakeEngineMock.sol | 3 -- 5 files changed, 20 insertions(+), 51 deletions(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index 9981e335..686c91e9 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -51,7 +51,6 @@ interface LockstakeEngineLike { function onKick(address, uint256) external; function onTake(address, address, uint256) external; function onRemove(address, uint256, uint256) external; - function onYank(address, uint256) external; } // Clipper for use with the manager / proxy paradigm @@ -473,13 +472,13 @@ contract LockstakeClipper { chost = wmul(_dust, dog.chop(ilk)); } - // Cancel an auction during ES or via governance action. + // Cancel an auction during End.cage or via other governance action. function yank(uint256 id) external auth lock { require(sales[id].usr != address(0), "LockstakeClipper/not-running-auction"); dog.digs(ilk, sales[id].tab); uint256 lot = sales[id].lot; - vat.slip(ilk, address(this), -int256(lot)); - engine.onYank(sales[id].usr, lot); + vat.flux(ilk, address(this), msg.sender, lot); + engine.onRemove(sales[id].usr, 0, 0); _remove(id); emit Yank(id); } diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index e3fd37a4..692cdbaa 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -118,7 +118,6 @@ contract LockstakeEngine is Multicall { event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); event OnRemove(address indexed urn, uint256 sold, uint256 burn, uint256 refund); - event OnYank(address indexed urn, uint256 wad); // --- modifiers --- @@ -431,10 +430,4 @@ contract LockstakeEngine is Multicall { urnAuctions[urn]--; emit OnRemove(urn, sold, burn, refund); } - - function onYank(address urn, uint256 wad) external auth { - mkr.burn(address(this), wad); - urnAuctions[urn]--; - emit OnYank(urn, wad); - } } diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index ee4f84b3..ff456f15 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -1363,11 +1363,14 @@ contract LockstakeClipperTest is DssTest { function testClipperYank() public takeSetup { (,, uint256 lot,, address usr,,) = clip.sales(1); + address caller = address(123); + clip.rely(caller); uint256 prevUsrGemBalance = vat.gem(ilk, address(usr)); + uint256 prevCallerGemBalance = vat.gem(ilk, address(caller)); uint256 prevClipperGemBalance = vat.gem(ilk, address(clip)); uint startGas = gasleft(); - clip.yank(1); + vm.prank(caller); clip.yank(1); uint endGas = gasleft(); emit log_named_uint("yank gas", startGas - endGas); @@ -1387,8 +1390,9 @@ contract LockstakeClipperTest is DssTest { (,,, uint256 dirt) = dog.ilks(ilk); assertEq(dirt, 0); - // Collateral is destroyed + // Assert transfer of gem. assertEq(vat.gem(ilk, address(usr)), prevUsrGemBalance); + assertEq(vat.gem(ilk, address(caller)), prevCallerGemBalance + lot); assertEq(vat.gem(ilk, address(clip)), prevClipperGemBalance - lot); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 613c67fe..85f59de9 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -102,7 +102,6 @@ contract LockstakeEngineTest is DssTest { event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); event OnRemove(address indexed urn, uint256 sold, uint256 burn, uint256 refund); - event OnYank(address indexed urn, uint256 wad); function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { @@ -201,14 +200,13 @@ contract LockstakeEngineTest is DssTest { } function testModifiers() public { - bytes4[] memory authedMethods = new bytes4[](7); + bytes4[] memory authedMethods = new bytes4[](6); authedMethods[0] = engine.addFarm.selector; authedMethods[1] = engine.delFarm.selector; authedMethods[2] = engine.freeNoFee.selector; authedMethods[3] = engine.onKick.selector; authedMethods[4] = engine.onTake.selector; authedMethods[5] = engine.onRemove.selector; - authedMethods[6] = engine.onYank.selector; // this checks the case where sender is not authed vm.startPrank(address(0xBEEF)); @@ -1265,51 +1263,29 @@ contract LockstakeEngineTest is DssTest { vm.prank(pauseProxy); engine.onRemove(address(1), 0, uint256(type(int256).max) + 1); } - function _testOnYank(bool withDelegate, bool withStaking) internal { + function _testYank(bool withDelegate, bool withStaking) internal { address urn = _clipperSetUp(withDelegate, withStaking); - uint256 mkrInitialSupply = mkr.totalSupply(); uint256 id = _forceLiquidation(urn); - LockstakeClipper.Sale memory sale; - (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); - assertEq(sale.pos, 0); - assertEq(sale.tab, 2_000 * 10**45); - assertEq(sale.lot, 100_000 * 10**18); - assertEq(sale.tot, 100_000 * 10**18); - assertEq(sale.usr, address(urn)); - assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); - vm.expectEmit(true, true, true, true); - emit OnYank(urn, 100_000 * 10**18); + emit OnRemove(urn, 0, 0, 0); vm.prank(pauseProxy); clip.yank(id); - - (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(id); - assertEq(sale.pos, 0); - assertEq(sale.tab, 0); - assertEq(sale.lot, 0); - assertEq(sale.tot, 0); - assertEq(sale.usr, address(0)); - assertEq(sale.tic, 0); - assertEq(sale.top, 0); assertEq(engine.urnAuctions(urn), 0); - - assertEq(mkr.totalSupply(), mkrInitialSupply - 100_000 * 10**18); } - function testOnYankNoStakingNoDelegate() public { - _testOnYank(false, false); + function testYankNoStakingNoDelegate() public { + _testYank(false, false); } - function testOnYankNoStakingWithDelegate() public { - _testOnYank(true, false); + function testYankNoStakingWithDelegate() public { + _testYank(true, false); } - function testOnYankWithStakingNoDelegate() public { - _testOnYank(false, true); + function testYankWithStakingNoDelegate() public { + _testYank(false, true); } - function testOnYankWithStakingWithDelegate() public { - _testOnYank(true, true); + function testYankWithStakingWithDelegate() public { + _testYank(true, true); } } diff --git a/test/mocks/LockstakeEngineMock.sol b/test/mocks/LockstakeEngineMock.sol index 7f57d569..ed7b6a93 100644 --- a/test/mocks/LockstakeEngineMock.sol +++ b/test/mocks/LockstakeEngineMock.sol @@ -25,7 +25,4 @@ contract LockstakeEngineMock { function onRemove(address urn, uint256, uint256 left) external { VatLike(vat).slip(ilk, urn, int256(left)); } - - function onYank(address, uint256) external { - } } From e1c68dbb10541e6c5a7c9775ddce7503f027e371 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 1 Feb 2024 13:50:24 -0300 Subject: [PATCH 048/111] Upgrade dss-test --- lib/dss-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dss-test b/lib/dss-test index df7b13ea..36ff4adb 160000 --- a/lib/dss-test +++ b/lib/dss-test @@ -1 +1 @@ -Subproject commit df7b13ead253f4b831df2464f7d74f28a091a790 +Subproject commit 36ff4adbcb35760614e0d2df864026991c23d028 From ed8192ddb1b2d67cf1a02a665bd328fca805163d Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:02:16 -0300 Subject: [PATCH 049/111] Add deploy scripts + improvements on tests (#29) * Add deploy scripts * Fixes + changes in tests * Minor changes Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> * Minor change * Add sanity checks * Add clipperMom and lineMom support * Use PIP_MKR * Change class * no join for ilk-registry * Add missing chop sanity check --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- deploy/LockstakeDeploy.sol | 50 ++++ deploy/LockstakeInit.sol | 235 +++++++++++++++++ deploy/LockstakeInstance.sol | 23 ++ test/LockstakeClipper.t.sol | 449 ++++++++++++++++---------------- test/LockstakeEngine.t.sol | 486 ++++++++++++++++++++++------------- 5 files changed, 841 insertions(+), 402 deletions(-) create mode 100644 deploy/LockstakeDeploy.sol create mode 100644 deploy/LockstakeInit.sol create mode 100644 deploy/LockstakeInstance.sol diff --git a/deploy/LockstakeDeploy.sol b/deploy/LockstakeDeploy.sol new file mode 100644 index 00000000..d7d43934 --- /dev/null +++ b/deploy/LockstakeDeploy.sol @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.16; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { MCD, DssInstance } from "dss-test/MCD.sol"; +import { LockstakeInstance } from "./LockstakeInstance.sol"; +import { LockstakeEngine } from "src/LockstakeEngine.sol"; +import { LockstakeClipper } from "src/LockstakeClipper.sol"; + +// Deploy a Lockstake instance +library LockstakeDeploy { + + function deployLockstake( + address deployer, + address owner, + address delegateFactory, + address nstJoin, + bytes32 ilk, + address stkMkr, + uint256 fee, + address mkrNgt, + bytes4 calcSig + ) internal returns (LockstakeInstance memory lockstakeInstance) { + DssInstance memory dss = MCD.loadFromChainlog(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); + + lockstakeInstance.engine = address(new LockstakeEngine(delegateFactory, nstJoin, ilk, stkMkr, fee, mkrNgt)); + lockstakeInstance.clipper = address(new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), lockstakeInstance.engine)); + (bool ok, bytes memory returnV) = dss.chainlog.getAddress("CALC_FAB").call(abi.encodeWithSelector(calcSig, owner)); + require(ok); + lockstakeInstance.clipperCalc = abi.decode(returnV, (address)); + + ScriptTools.switchOwner(lockstakeInstance.engine, deployer, owner); + ScriptTools.switchOwner(lockstakeInstance.clipper, deployer, owner); + } +} diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol new file mode 100644 index 00000000..55a58868 --- /dev/null +++ b/deploy/LockstakeInit.sol @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import { DssInstance } from "dss-test/MCD.sol"; +import { LockstakeInstance } from "./LockstakeInstance.sol"; + +interface LockstakeEngineLike { + function delegateFactory() external view returns (address); + function vat() external view returns (address); + function nstJoin() external view returns (address); + function nst() external view returns (address); + function ilk() external view returns (bytes32); + function mkr() external view returns (address); + function stkMkr() external view returns (address); + function fee() external view returns (uint256); + function mkrNgt() external view returns (address); + function ngt() external view returns (address); + function rely(address) external; + function file(bytes32, address) external; + function addFarm(address) external; +} + +interface LockstakeClipperLike { + function vat() external view returns (address); + function dog() external view returns (address); + function spotter() external view returns (address); + function engine() external view returns (address); + function ilk() external view returns (bytes32); + function rely(address) external; + function file(bytes32, address) external; + function file(bytes32, uint256) external; + function upchost() external; +} + +interface PipLike { + function kiss(address) external; +} + +interface CalcLike { + function file(bytes32, uint256) external; +} + +interface AutoLineLike { + function setIlk(bytes32, uint256, uint256, uint256) external; +} + +interface LineMomLike { + function addIlk(bytes32) external; +} + +interface ClipperMomLike { + function setPriceTolerance(address, uint256) external; +} + +interface IlkRegistryLike { + function put( + bytes32 _ilk, + address _join, + address _gem, + uint256 _dec, + uint256 _class, + address _pip, + address _xlip, + string memory _name, + string memory _symbol + ) external; +} + +struct LockstakeConfig { + bytes32 ilk; + address delegateFactory; + address nstJoin; + address nst; + address mkr; + address stkMkr; + address mkrNgt; + address ngt; + address[] farms; + uint256 fee; + uint256 maxLine; + uint256 gap; + uint256 ttl; + uint256 dust; + uint256 duty; + uint256 mat; + uint256 buf; + uint256 tail; + uint256 cusp; + uint256 chip; + uint256 tip; + uint256 stopped; + uint256 chop; + uint256 hole; + uint256 tau; + uint256 cut; + uint256 step; + bool lineMom; + uint256 tolerance; + string name; + string symbol; +} + +library LockstakeInit { + uint256 constant internal RATES_ONE_HUNDRED_PCT = 1000000021979553151239153027; + uint256 constant internal WAD = 10**18; + uint256 constant internal RAY = 10**27; + + function initLockstake( + DssInstance memory dss, + LockstakeInstance memory lockstakeInstance, + LockstakeConfig memory cfg + ) internal { + LockstakeEngineLike engine = LockstakeEngineLike(lockstakeInstance.engine); + LockstakeClipperLike clipper = LockstakeClipperLike(lockstakeInstance.clipper); + CalcLike calc = CalcLike(lockstakeInstance.clipperCalc); + + // Sanity checks + require(engine.delegateFactory() == cfg.delegateFactory, "Engine delegateFactory mismatch"); + require(engine.vat() == address(dss.vat), "Engine vat mismatch"); + require(engine.nstJoin() == cfg.nstJoin, "Engine nstJoin mismatch"); + require(engine.nst() == cfg.nst, "Engine nst mismatch"); + require(engine.ilk() == cfg.ilk, "Engine ilk mismatch"); + require(engine.mkr() == cfg.mkr, "Engine mkr mismatch"); + require(engine.stkMkr() == cfg.stkMkr, "Engine stkMkr mismatch"); + require(engine.fee() == cfg.fee, "Engine fee mismatch"); + require(engine.mkrNgt() == cfg.mkrNgt, "Engine mkrNgt mismatch"); + require(engine.ngt() == cfg.ngt, "Engine ngt mismatch"); + require(clipper.ilk() == cfg.ilk, "Clipper ilk mismatch"); + require(clipper.vat() == address(dss.vat), "Clipper vat mismatch"); + require(clipper.engine() == address(engine), "Clipper engine mismatch"); + require(clipper.dog() == address(dss.dog), "Clipper dog mismatch"); + require(clipper.spotter() == address(dss.spotter), "Clipper spotter mismatch"); + + require(cfg.dust <= cfg.hole, "dust greater than hole"); + require(cfg.duty >= RAY && cfg.duty <= RATES_ONE_HUNDRED_PCT, "duty out of boundaries"); + require(cfg.mat >= RAY && cfg.mat < 10 * RAY, "mat out of boundaries"); + require(cfg.buf >= RAY && cfg.buf < 10 * RAY, "buf out of boundaries"); + require(cfg.cusp < RAY, "cusp negative drop value"); + require(cfg.chip < WAD, "chip equal or greater than 100%"); + require(cfg.chop >= WAD && cfg.chop < 2 * WAD, "chop out of boundaries"); + require(cfg.tolerance < RAY, "tolerance equal or greater than 100%"); + + dss.vat.init(cfg.ilk); + dss.vat.file(cfg.ilk, "line", cfg.gap); + dss.vat.file("Line", dss.vat.Line() + cfg.gap); + dss.vat.file(cfg.ilk, "dust", cfg.dust); + dss.vat.rely(address(engine)); + dss.vat.rely(address(clipper)); + + AutoLineLike(dss.chainlog.getAddress("MCD_IAM_AUTO_LINE")).setIlk(cfg.ilk, cfg.maxLine, cfg.gap, cfg.ttl); + + dss.jug.init(cfg.ilk); + dss.jug.file(cfg.ilk, "duty", cfg.duty); + + address pip = dss.chainlog.getAddress("PIP_MKR"); + address clipperMom = dss.chainlog.getAddress("CLIPPER_MOM"); + PipLike(pip).kiss(address(dss.spotter)); + PipLike(pip).kiss(address(clipper)); + PipLike(pip).kiss(clipperMom); + PipLike(pip).kiss(address(dss.end)); + // TODO: If a sticky oracle wrapper is implemented we will need to also kiss the source to it + // If an osm is implemented instead we also need the source to kiss the osm and add the OsmMom permissions + + dss.spotter.file(cfg.ilk, "mat", cfg.mat); + dss.spotter.file(cfg.ilk, "pip", pip); + dss.spotter.poke(cfg.ilk); + + dss.dog.file(cfg.ilk, "clip", address(clipper)); + dss.dog.file(cfg.ilk, "chop", cfg.chop); + dss.dog.file(cfg.ilk, "hole", cfg.hole); + dss.dog.rely(address(clipper)); + + engine.file("jug", address(dss.jug)); + for (uint256 i = 0; i < cfg.farms.length; i++) { + engine.addFarm(cfg.farms[i]); + } + engine.rely(address(clipper)); + + clipper.file("buf", cfg.buf); + clipper.file("tail", cfg.tail); + clipper.file("cusp", cfg.cusp); + clipper.file("chip", cfg.chip); + clipper.file("tip", cfg.tip); + clipper.file("stopped", cfg.stopped); + clipper.file("vow", address(dss.vow)); + clipper.file("calc", address(calc)); + clipper.upchost(); + clipper.rely(address(dss.dog)); + clipper.rely(address(dss.end)); + clipper.rely(clipperMom); + + if (cfg.tau > 0) calc.file("tau", cfg.tau); + if (cfg.cut > 0) calc.file("cut", cfg.cut); + if (cfg.step > 0) calc.file("step", cfg.step); + + if (cfg.lineMom) { + LineMomLike(dss.chainlog.getAddress("LINE_MOM")).addIlk(cfg.ilk); + } + + if (cfg.tolerance > 0) { + ClipperMomLike(clipperMom).setPriceTolerance(address(clipper), cfg.tolerance); + } + + IlkRegistryLike(dss.chainlog.getAddress("ILK_REGISTRY")).put( + cfg.ilk, + address(0), + cfg.mkr, + 18, + 7, // New class + pip, + address(clipper), + cfg.name, + cfg.symbol + ); + + dss.chainlog.setAddress("LOCKSTAKE_ENGINE", address(engine)); + dss.chainlog.setAddress("LOCKSTAKE_CLIP", address(clipper)); + dss.chainlog.setAddress("LOCKSTAKE_CLIP_CALC", address(calc)); + } +} diff --git a/deploy/LockstakeInstance.sol b/deploy/LockstakeInstance.sol new file mode 100644 index 00000000..91252543 --- /dev/null +++ b/deploy/LockstakeInstance.sol @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +struct LockstakeInstance { + address engine; + address clipper; + address clipperCalc; +} diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index ff456f15..4bcce1fa 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -118,10 +118,6 @@ contract PublicClip is LockstakeClipper { } } -interface ChainlogLike { - function getAddress(bytes32) external view returns (address); -} - interface VatLike { function dai(address) external view returns (uint256); function gem(bytes32, address) external view returns (uint256); @@ -172,11 +168,8 @@ interface VowLike { } contract LockstakeClipperTest is DssTest { + DssInstance dss; address pauseProxy; - VatLike vat; - DogLike dog; - SpotterLike spot; - VowLike vow; PipMock pip; GemLike dai; @@ -197,11 +190,11 @@ contract LockstakeClipperTest is DssTest { uint256 constant startTime = 604411200; // Used to avoid issues with `block.timestamp` function _ink(bytes32 ilk_, address urn_) internal view returns (uint256) { - (uint256 ink_,) = vat.urns(ilk_, urn_); + (uint256 ink_,) = dss.vat.urns(ilk_, urn_); return ink_; } function _art(bytes32 ilk_, address urn_) internal view returns (uint256) { - (,uint256 art_) = vat.urns(ilk_, urn_); + (,uint256 art_) = dss.vat.urns(ilk_, urn_); return art_; } @@ -214,7 +207,7 @@ contract LockstakeClipperTest is DssTest { } modifier takeSetup { - address calc = CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newStairstepExponentialDecrease(address(this)); + address calc = CalcFabLike(dss.chainlog.getAddress("CALC_FAB")).newStairstepExponentialDecrease(address(this)); CalcLike(calc).file("cut", RAY - ray(0.01 ether)); // 1% decrease CalcLike(calc).file("step", 1); // Decrease every 1 second @@ -223,15 +216,15 @@ contract LockstakeClipperTest is DssTest { clip.file("cusp", ray(0.3 ether)); // 70% drop before reset clip.file("tail", 3600); // 1 hour before reset - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); assertEq(clip.kicks(), 0); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); assertEq(clip.kicks(), 1); - (ink, art) = vat.urns(ilk, address(this)); + (ink, art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 0); assertEq(art, 0); @@ -245,10 +238,10 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(5 ether)); // $4 plus 25% - assertEq(vat.gem(ilk, ali), 0); - assertEq(vat.dai(ali), rad(1000 ether)); - assertEq(vat.gem(ilk, bob), 0); - assertEq(vat.dai(bob), rad(1000 ether)); + assertEq(dss.vat.gem(ilk, ali), 0); + assertEq(dss.vat.dai(ali), rad(1000 ether)); + assertEq(dss.vat.gem(ilk, bob), 0); + assertEq(dss.vat.dai(bob), rad(1000 ether)); _; } @@ -257,64 +250,68 @@ contract LockstakeClipperTest is DssTest { vm.createSelectFork(vm.envString("ETH_RPC_URL")); vm.warp(startTime); - pauseProxy = ChainlogLike(LOG).getAddress("MCD_PAUSE_PROXY"); - vat = VatLike(ChainlogLike(LOG).getAddress("MCD_VAT")); - spot = SpotterLike(ChainlogLike(LOG).getAddress("MCD_SPOT")); - vow = VowLike(ChainlogLike(LOG).getAddress("MCD_VOW")); - dog = DogLike(ChainlogLike(LOG).getAddress("MCD_DOG")); - dai = GemLike(ChainlogLike(LOG).getAddress("MCD_DAI")); + dss = MCD.loadFromChainlog(LOG); + + pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); + dai = GemLike(dss.chainlog.getAddress("MCD_DAI")); pip = new PipMock(); pip.setPrice(price); // Spot = $2.5 - vm.prank(pauseProxy); vat.init(ilk); + vm.startPrank(pauseProxy); + dss.vat.init(ilk); - vm.prank(pauseProxy); spot.file(ilk, "pip", address(pip)); - vm.prank(pauseProxy); spot.file(ilk, "mat", ray(2 ether)); // 200% liquidation ratio for easier test calcs - spot.poke(ilk); + dss.spotter.file(ilk, "pip", address(pip)); + dss.spotter.file(ilk, "mat", ray(2 ether)); // 200% liquidation ratio for easier test calcs + dss.spotter.poke(ilk); - vm.prank(pauseProxy); vat.file(ilk, "dust", rad(20 ether)); // $20 dust - vm.prank(pauseProxy); vat.file(ilk, "line", rad(10000 ether)); - // vm.prank(pauseProxy); vat.file("Line", rad(10000 ether)); + dss.vat.file(ilk, "dust", rad(20 ether)); // $20 dust + dss.vat.file(ilk, "line", rad(10000 ether)); + dss.vat.file("Line", dss.vat.Line() + rad(10000 ether)); - vm.prank(pauseProxy); dog.file(ilk, "chop", 1.1 ether); // 10% chop - vm.prank(pauseProxy); dog.file(ilk, "hole", rad(1000 ether)); - // vm.prank(pauseProxy); dog.file("Hole", rad(1000 ether)); + dss.dog.file(ilk, "chop", 1.1 ether); // 10% chop + dss.dog.file(ilk, "hole", rad(1000 ether)); + dss.dog.file("Hole", dss.dog.Dirt() + rad(1000 ether)); - engine = new LockstakeEngineMock(address(vat), ilk); - vm.prank(pauseProxy); vat.rely(address(engine)); + engine = new LockstakeEngineMock(address(dss.vat), ilk); + dss.vat.rely(address(engine)); + vm.stopPrank(); // dust and chop filed previously so clip.chost will be set correctly - clip = new LockstakeClipper(address(vat), address(spot), address(dog), address(engine)); + clip = new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), address(engine)); clip.upchost(); - clip.rely(address(dog)); + clip.rely(address(dss.dog)); - vm.prank(pauseProxy); dog.file(ilk, "clip", address(clip)); - vm.prank(pauseProxy); dog.rely(address(clip)); - vm.prank(pauseProxy); vat.rely(address(clip)); + vm.startPrank(pauseProxy); + dss.dog.file(ilk, "clip", address(clip)); + dss.dog.rely(address(clip)); + dss.vat.rely(address(clip)); - vm.prank(pauseProxy); vat.slip(ilk, address(this), int256(1000 ether)); + dss.vat.slip(ilk, address(this), int256(1000 ether)); + vm.stopPrank(); - assertEq(vat.gem(ilk, address(this)), 1000 ether); - assertEq(vat.dai(address(this)), 0); - vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); - assertEq(vat.gem(ilk, address(this)), 960 ether); - assertEq(vat.dai(address(this)), rad(100 ether)); + assertEq(dss.vat.gem(ilk, address(this)), 1000 ether); + assertEq(dss.vat.dai(address(this)), 0); + dss.vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + assertEq(dss.vat.dai(address(this)), rad(100 ether)); pip.setPrice(4 ether); // Spot = $2 - spot.poke(ilk); // Now unsafe + dss.spotter.poke(ilk); // Now unsafe ali = address(111); bob = address(222); che = address(333); - vat.hope(address(clip)); - vm.prank(ali); vat.hope(address(clip)); - vm.prank(bob); vat.hope(address(clip)); + dss.vat.hope(address(clip)); + vm.prank(ali); dss.vat.hope(address(clip)); + vm.prank(bob); dss.vat.hope(address(clip)); - vm.prank(pauseProxy); vat.suck(address(0), address(this), rad(1000 ether)); - vm.prank(pauseProxy); vat.suck(address(0), address(ali), rad(1000 ether)); - vm.prank(pauseProxy); vat.suck(address(0), address(bob), rad(1000 ether)); + vm.startPrank(pauseProxy); + dss.vat.suck(address(0), address(this), rad(1000 ether)); + dss.vat.suck(address(0), address(ali), rad(1000 ether)); + dss.vat.suck(address(0), address(bob), rad(1000 ether)); + vm.stopPrank(); } function testChangeDog() public { @@ -324,8 +321,8 @@ contract LockstakeClipperTest is DssTest { } function testGetChop() public { - uint256 chop = dog.chop(ilk); - (, uint256 chop2,,) = dog.ilks(ilk); + uint256 chop = dss.dog.chop(ilk); + (, uint256 chop2,,) = dss.dog.ilks(ilk); assertEq(chop, chop2); } @@ -343,13 +340,13 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 960 ether); - assertEq(vat.dai(ali), rad(1000 ether)); - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + assertEq(dss.vat.dai(ali), rad(1000 ether)); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); - vm.prank(ali); dog.bark(ilk, address(this), address(ali)); + vm.prank(ali); dss.dog.bark(ilk, address(this), address(ali)); assertEq(clip.kicks(), 1); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); @@ -360,20 +357,20 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(this)); assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(4 ether)); - assertEq(vat.gem(ilk, address(this)), 960 ether); - assertEq(vat.dai(ali), rad(1100 ether)); // Paid "tip" amount of DAI for calling bark() - (ink, art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + assertEq(dss.vat.dai(ali), rad(1100 ether)); // Paid "tip" amount of DAI for calling bark() + (ink, art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 0 ether); assertEq(art, 0 ether); pip.setPrice(price); // Spot = $2.5 - spot.poke(ilk); // Now safe + dss.spotter.poke(ilk); // Now safe vm.warp(startTime + 100); - vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); + dss.vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); pip.setPrice(4 ether); // Spot = $2 - spot.poke(ilk); // Now unsafe + dss.spotter.poke(ilk); // Now unsafe (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(2); assertEq(sale.pos, 0); @@ -383,16 +380,16 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 920 ether); + assertEq(dss.vat.gem(ilk, address(this)), 920 ether); clip.file(bytes32("buf"), ray(1.25 ether)); // 25% Initial price buffer clip.file("tip", rad(100 ether)); // Flat fee of 100 DAI clip.file("chip", 0.02 ether); // Linear increase of 2% of tab - assertEq(vat.dai(bob), rad(1000 ether)); + assertEq(dss.vat.dai(bob), rad(1000 ether)); - vm.prank(bob); dog.bark(ilk, address(this), address(bob)); + vm.prank(bob); dss.dog.bark(ilk, address(this), address(bob)); assertEq(clip.kicks(), 2); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(2); @@ -403,18 +400,18 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(this)); assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(5 ether)); - assertEq(vat.gem(ilk, address(this)), 920 ether); - (ink, art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 920 ether); + (ink, art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 0 ether); assertEq(art, 0 ether); - assertEq(vat.dai(bob), rad(1000 ether) + rad(100 ether) + sale.tab * 0.02 ether / WAD); // Paid (tip + due * chip) amount of DAI for calling bark() + assertEq(dss.vat.dai(bob), rad(1000 ether) + rad(100 ether) + sale.tab * 0.02 ether / WAD); // Paid (tip + due * chip) amount of DAI for calling bark() } function testRevertsKickZeroPrice() public { pip.setPrice(0); vm.expectRevert("LockstakeClipper/zero-top-price"); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); } function testRevertsRedoZeroPrice() public { @@ -454,8 +451,8 @@ contract LockstakeClipperTest is DssTest { } function testBarkNotLeavingDust() public { - vm.prank(pauseProxy); dog.file(ilk, "hole", rad(80 ether)); // Makes room = 80 WAD - vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", rad(80 ether)); // Makes room = 80 WAD + vm.prank(pauseProxy); dss.dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) assertEq(clip.kicks(), 0); LockstakeClipper.Sale memory sale; @@ -467,12 +464,12 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); - dog.bark(ilk, address(this), address(this)); // art - dart = 100 - 80 = dust (= 20) + dss.dog.bark(ilk, address(this), address(this)); // art - dart = 100 - 80 = dust (= 20) assertEq(clip.kicks(), 1); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); @@ -483,15 +480,15 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(this)); assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(4 ether)); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (ink, art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (ink, art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 8 ether); assertEq(art, 20 ether); } function testBarkNotLeavingDustOverHole() public { - vm.prank(pauseProxy); dog.file(ilk, "hole", rad(80 ether) + ray(1 ether)); // Makes room = 80 WAD + 1 wei - vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", rad(80 ether) + ray(1 ether)); // Makes room = 80 WAD + 1 wei + vm.prank(pauseProxy); dss.dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) assertEq(clip.kicks(), 0); LockstakeClipper.Sale memory sale; @@ -503,12 +500,12 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); - dog.bark(ilk, address(this), address(this)); // art - dart = 100 - (80 + 1 wei) < dust (= 20) then the whole debt is taken + dss.dog.bark(ilk, address(this), address(this)); // art - dart = 100 - (80 + 1 wei) < dust (= 20) then the whole debt is taken assertEq(clip.kicks(), 1); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); @@ -519,20 +516,20 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(this)); assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(4 ether)); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (ink, art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (ink, art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 0 ether); assertEq(art, 0 ether); } function testBarkNotLeavingDustRate() public { - vm.prank(pauseProxy); vat.fold(ilk, address(vow), int256(ray(0.02 ether))); - (, uint256 rate,,,) = vat.ilks(ilk); + vm.prank(pauseProxy); dss.vat.fold(ilk, address(dss.vow), int256(ray(0.02 ether))); + (, uint256 rate,,,) = dss.vat.ilks(ilk); assertEq(rate, ray(1.02 ether)); - vm.prank(pauseProxy); dog.file(ilk, "hole", 100 * RAD); // Makes room = 100 RAD - vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop for precise calculations - vm.prank(pauseProxy); vat.file(ilk, "dust", 20 * RAD); // 20 DAI minimum Vault debt + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 100 * RAD); // Makes room = 100 RAD + vm.prank(pauseProxy); dss.dog.file(ilk, "chop", 1 ether); // 0% chop for precise calculations + vm.prank(pauseProxy); dss.vat.file(ilk, "dust", 20 * RAD); // 20 DAI minimum Vault debt clip.upchost(); assertEq(clip.kicks(), 0); @@ -545,14 +542,14 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); // Full debt is 102 DAI since rate = 1.02 * RAY // (art - dart) * rate ~= 2 RAD < dust = 20 RAD // => remnant would be dusty, so a full liquidation occurs. - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); assertEq(clip.kicks(), 1); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); @@ -563,20 +560,20 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(this)); assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(4 ether)); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (ink, art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (ink, art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 0); assertEq(art, 0); } function testBarkOnlyLeavingDustOverHoleRate() public { - vm.prank(pauseProxy); vat.fold(ilk, address(vow), int256(ray(0.02 ether))); - (, uint256 rate,,,) = vat.ilks(ilk); + vm.prank(pauseProxy); dss.vat.fold(ilk, address(dss.vow), int256(ray(0.02 ether))); + (, uint256 rate,,,) = dss.vat.ilks(ilk); assertEq(rate, ray(1.02 ether)); - vm.prank(pauseProxy); dog.file(ilk, "hole", 816 * RAD / 10); // Makes room = 81.6 RAD => dart = 80 - vm.prank(pauseProxy); dog.file(ilk, "chop", 1 ether); // 0% chop for precise calculations - vm.prank(pauseProxy); vat.file(ilk, "dust", 204 * RAD / 10); // 20.4 DAI dust + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 816 * RAD / 10); // Makes room = 81.6 RAD => dart = 80 + vm.prank(pauseProxy); dss.dog.file(ilk, "chop", 1 ether); // 0% chop for precise calculations + vm.prank(pauseProxy); dss.vat.file(ilk, "dust", 204 * RAD / 10); // 20.4 DAI dust clip.upchost(); assertEq(clip.kicks(), 0); @@ -589,14 +586,14 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); // (art - dart) * rate = 20.4 RAD == dust // => marginal threshold at which partial liquidation is acceptable - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); assertEq(clip.kicks(), 1); (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(1); @@ -607,82 +604,82 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(this)); assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(4 ether)); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (ink, art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (ink, art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 8 ether); assertEq(art, 20 ether); - (,,,, uint256 dust) = vat.ilks(ilk); + (,,,, uint256 dust) = dss.vat.ilks(ilk); assertEq(art * rate, dust); } function testHolehole() public { - assertEq(dog.Dirt(), 0); - (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), 0); + (,,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, 0); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); (, uint256 tab,,,,,) = clip.sales(1); - assertEq(dog.Dirt(), tab); - (,,, dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), tab); + (,,, dirt) = dss.dog.ilks(ilk); assertEq(dirt, tab); bytes32 ilk2 = "LSE2"; - LockstakeEngineMock engine2 = new LockstakeEngineMock(address(vat), ilk2); - vm.prank(pauseProxy); vat.rely(address(engine2)); - LockstakeClipper clip2 = new LockstakeClipper(address(vat), address(spot), address(dog), address(engine2)); + LockstakeEngineMock engine2 = new LockstakeEngineMock(address(dss.vat), ilk2); + vm.prank(pauseProxy); dss.vat.rely(address(engine2)); + LockstakeClipper clip2 = new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), address(engine2)); clip2.upchost(); - clip2.rely(address(dog)); + clip2.rely(address(dss.dog)); - vm.prank(pauseProxy); dog.file(ilk2, "clip", address(clip2)); - vm.prank(pauseProxy); dog.file(ilk2, "chop", 1.1 ether); - vm.prank(pauseProxy); dog.file(ilk2, "hole", rad(1000 ether)); - vm.prank(pauseProxy); dog.rely(address(clip2)); + vm.prank(pauseProxy); dss.dog.file(ilk2, "clip", address(clip2)); + vm.prank(pauseProxy); dss.dog.file(ilk2, "chop", 1.1 ether); + vm.prank(pauseProxy); dss.dog.file(ilk2, "hole", rad(1000 ether)); + vm.prank(pauseProxy); dss.dog.rely(address(clip2)); - vm.prank(pauseProxy); vat.init(ilk2); - vm.prank(pauseProxy); vat.rely(address(clip2)); - vm.prank(pauseProxy); vat.file(ilk2, "line", rad(100 ether)); + vm.prank(pauseProxy); dss.vat.init(ilk2); + vm.prank(pauseProxy); dss.vat.rely(address(clip2)); + vm.prank(pauseProxy); dss.vat.file(ilk2, "line", rad(100 ether)); - vm.prank(pauseProxy); vat.slip(ilk2, address(this), 40 ether); + vm.prank(pauseProxy); dss.vat.slip(ilk2, address(this), 40 ether); PipMock pip2 = new PipMock(); pip2.setPrice(price); // Spot = $2.5 - vm.prank(pauseProxy); spot.file(ilk2, "pip", address(pip2)); - vm.prank(pauseProxy); spot.file(ilk2, "mat", ray(2 ether)); - spot.poke(ilk2); - vat.frob(ilk2, address(this), address(this), address(this), 40 ether, 100 ether); + vm.prank(pauseProxy); dss.spotter.file(ilk2, "pip", address(pip2)); + vm.prank(pauseProxy); dss.spotter.file(ilk2, "mat", ray(2 ether)); + dss.spotter.poke(ilk2); + dss.vat.frob(ilk2, address(this), address(this), address(this), 40 ether, 100 ether); pip2.setPrice(4 ether); // Spot = $2 - spot.poke(ilk2); + dss.spotter.poke(ilk2); - dog.bark(ilk2, address(this), address(this)); + dss.dog.bark(ilk2, address(this), address(this)); (, uint256 tab2,,,,,) = clip2.sales(1); - assertEq(dog.Dirt(), tab + tab2); - (,,, dirt) = dog.ilks(ilk); - (,,, uint256 dirt2) = dog.ilks(ilk2); + assertEq(dss.dog.Dirt(), tab + tab2); + (,,, dirt) = dss.dog.ilks(ilk); + (,,, uint256 dirt2) = dss.dog.ilks(ilk2); assertEq(dirt, tab); assertEq(dirt2, tab2); } function testPartialLiquidationHoleLimit() public { - vm.prank(pauseProxy); dog.file("Hole", rad(75 ether)); + vm.prank(pauseProxy); dss.dog.file("Hole", rad(75 ether)); assertEq(_ink(ilk, address(this)), 40 ether); assertEq(_art(ilk, address(this)), 100 ether); - assertEq(dog.Dirt(), 0); - (,uint256 chop,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), 0); + (,uint256 chop,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, 0); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); LockstakeClipper.Sale memory sale; (, sale.tab, sale.lot,,,,) = clip.sales(1); - (, uint256 rate,,,) = vat.ilks(ilk); + (, uint256 rate,,,) = dss.vat.ilks(ilk); assertEq(sale.lot, 40 ether * (sale.tab * WAD / rate / chop) / 100 ether); assertEq(sale.tab, rad(75 ether) - ray(0.2 ether)); // 0.2 RAY rounding error @@ -690,27 +687,27 @@ contract LockstakeClipperTest is DssTest { assertEq(_ink(ilk, address(this)), 40 ether - sale.lot); assertEq(_art(ilk, address(this)), 100 ether - sale.tab * WAD / rate / chop); - assertEq(dog.Dirt(), sale.tab); - (,,, dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), sale.tab); + (,,, dirt) = dss.dog.ilks(ilk); assertEq(dirt, sale.tab); } function testPartialLiquidationholeLimit() public { - vm.prank(pauseProxy); dog.file(ilk, "hole", rad(75 ether)); + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", rad(75 ether)); assertEq(_ink(ilk, address(this)), 40 ether); assertEq(_art(ilk, address(this)), 100 ether); - assertEq(dog.Dirt(), 0); - (,uint256 chop,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), 0); + (,uint256 chop,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, 0); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); LockstakeClipper.Sale memory sale; (, sale.tab, sale.lot,,,,) = clip.sales(1); - (, uint256 rate,,,) = vat.ilks(ilk); + (, uint256 rate,,,) = dss.vat.ilks(ilk); assertEq(sale.lot, 40 ether * (sale.tab * WAD / rate / chop) / 100 ether); assertEq(sale.tab, rad(75 ether) - ray(0.2 ether)); // 0.2 RAY rounding error @@ -718,8 +715,8 @@ contract LockstakeClipperTest is DssTest { assertEq(_ink(ilk, address(this)), 40 ether - sale.lot); assertEq(_art(ilk, address(this)), 100 ether - sale.tab * WAD / rate / chop); - assertEq(dog.Dirt(), sale.tab); - (,,, dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), sale.tab); + (,,, dirt) = dss.dog.ilks(ilk); assertEq(dirt, sale.tab); } @@ -742,9 +739,9 @@ contract LockstakeClipperTest is DssTest { data: "" }); - assertEq(vat.gem(ilk, ali), 22 ether); // Didn't take whole lot - assertEq(vat.dai(ali), rad(890 ether)); // Didn't pay more than tab (110) - assertEq(vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr + assertEq(dss.vat.gem(ilk, ali), 22 ether); // Didn't take whole lot + assertEq(dss.vat.dai(ali), rad(890 ether)); // Didn't pay more than tab (110) + assertEq(dss.vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr // Assert auction ends LockstakeClipper.Sale memory sale; @@ -757,8 +754,8 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(dog.Dirt(), 0); - (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), 0); + (,,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, 0); } @@ -772,9 +769,9 @@ contract LockstakeClipperTest is DssTest { data: "" }); - assertEq(vat.gem(ilk, ali), 22 ether); // Didn't take whole lot - assertEq(vat.dai(ali), rad(890 ether)); // Paid full tab (110) - assertEq(vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr + assertEq(dss.vat.gem(ilk, ali), 22 ether); // Didn't take whole lot + assertEq(dss.vat.dai(ali), rad(890 ether)); // Paid full tab (110) + assertEq(dss.vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr // Assert auction ends LockstakeClipper.Sale memory sale; @@ -787,8 +784,8 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(dog.Dirt(), 0); - (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), 0); + (,,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, 0); } @@ -816,7 +813,7 @@ contract LockstakeClipperTest is DssTest { id: 1, amt: 11 ether, max: ray(5 ether), - who: address(dog), + who: address(dss.dog), data: "aaa" }); vm.revertTo(snapshotId); @@ -824,7 +821,7 @@ contract LockstakeClipperTest is DssTest { id: 1, amt: 11 ether, max: ray(5 ether), - who: address(vat), + who: address(dss.vat), data: "aaa" }); vm.revertTo(snapshotId); @@ -847,9 +844,9 @@ contract LockstakeClipperTest is DssTest { data: "" }); - assertEq(vat.gem(ilk, ali), 11 ether); // Didn't take whole lot - assertEq(vat.dai(ali), rad(945 ether)); // Paid half tab (55) - assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) + assertEq(dss.vat.gem(ilk, ali), 11 ether); // Didn't take whole lot + assertEq(dss.vat.dai(ali), rad(945 ether)); // Paid half tab (55) + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) // Assert auction DOES NOT end LockstakeClipper.Sale memory sale; @@ -862,8 +859,8 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.tic, block.timestamp); assertEq(sale.top, ray(5 ether)); - assertEq(dog.Dirt(), sale.tab); - (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), sale.tab); + (,,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, sale.tab); } @@ -878,9 +875,9 @@ contract LockstakeClipperTest is DssTest { data: "" }); - assertEq(vat.gem(ilk, ali), 40 ether); // Took entire lot - assertLt(vat.dai(ali) - rad(900 ether), rad(0.1 ether)); // Paid about 100 ether - assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned + assertEq(dss.vat.gem(ilk, ali), 40 ether); // Took entire lot + assertLt(dss.vat.dai(ali) - rad(900 ether), rad(0.1 ether)); // Paid about 100 ether + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); // Collateral not returned // Assert auction ends LockstakeClipper.Sale memory sale; @@ -894,8 +891,8 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.top, 0); // All dirt should be cleared, since the auction has ended, even though < 100% of tab was collected - assertEq(dog.Dirt(), 0); - (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), 0); + (,,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, 0); } @@ -1009,9 +1006,9 @@ contract LockstakeClipperTest is DssTest { data: "" }); - assertEq(vat.gem(ilk, ali), 10 ether); // Didn't take whole lot - assertEq(vat.dai(ali), rad(950 ether)); // Paid some tab (50) - assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) + assertEq(dss.vat.gem(ilk, ali), 10 ether); // Didn't take whole lot + assertEq(dss.vat.dai(ali), rad(950 ether)); // Paid some tab (50) + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) // Assert auction DOES NOT end LockstakeClipper.Sale memory sale; @@ -1046,18 +1043,18 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.top, 0); uint256 expectedGem = (RAY * 60 ether) / _price; // tab / price - assertEq(vat.gem(ilk, bob), expectedGem); // Didn't take whole lot - assertEq(vat.dai(bob), rad(940 ether)); // Paid rest of tab (60) + assertEq(dss.vat.gem(ilk, bob), expectedGem); // Didn't take whole lot + assertEq(dss.vat.dai(bob), rad(940 ether)); // Paid rest of tab (60) uint256 lotReturn = 30 ether - expectedGem; // lot - loaf.tab / max = 15 - assertEq(vat.gem(ilk, address(this)), 960 ether + lotReturn); // Collateral returned (10 WAD) + assertEq(dss.vat.gem(ilk, address(this)), 960 ether + lotReturn); // Collateral returned (10 WAD) } function _auctionResetSetup(uint256 tau) internal { - address calc = CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newLinearDecrease(address(this)); + address calc = CalcFabLike(dss.chainlog.getAddress("CALC_FAB")).newLinearDecrease(address(this)); CalcLike(calc).file("tau", tau); // tau hours till zero is reached (used to test tail) - vm.prank(pauseProxy); vat.file(ilk, "dust", rad(20 ether)); // $20 dust + vm.prank(pauseProxy); dss.vat.file(ilk, "dust", rad(20 ether)); // $20 dust clip.file("buf", ray(1.25 ether)); // 25% Initial price buffer clip.file("calc", address(calc)); // File price contract @@ -1065,7 +1062,7 @@ contract LockstakeClipperTest is DssTest { clip.file("tail", 3600); // 1 hour before reset assertEq(clip.kicks(), 0); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); assertEq(clip.kicks(), 1); } @@ -1166,26 +1163,26 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 960 ether); - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); // Any level of stoppage prevents kicking. clip.file("stopped", 1); vm.expectRevert("LockstakeClipper/stopped-incorrect"); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); clip.file("stopped", 2); vm.expectRevert("LockstakeClipper/stopped-incorrect"); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); clip.file("stopped", 3); vm.expectRevert("LockstakeClipper/stopped-incorrect"); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); clip.file("stopped", 0); - dog.bark(ilk, address(this), address(this)); + dss.dog.bark(ilk, address(this), address(this)); } // At a stopped == 1 we are ok to take @@ -1297,28 +1294,28 @@ contract LockstakeClipperTest is DssTest { vm.warp(block.timestamp + 300); clip.redo(1, address(123)); - assertEq(vat.dai(address(123)), clip.tip()); + assertEq(dss.vat.dai(address(123)), clip.tip()); clip.file("chip", 0.02 ether); // Reward 2% of tab vm.warp(block.timestamp + 300); clip.redo(1, address(234)); - assertEq(vat.dai(address(234)), clip.tip() + clip.chip() * tab / WAD); + assertEq(dss.vat.dai(address(234)), clip.tip() + clip.chip() * tab / WAD); clip.file("tip", 0); // No more flat fee vm.warp(block.timestamp + 300); clip.redo(1, address(345)); - assertEq(vat.dai(address(345)), clip.chip() * tab / WAD); + assertEq(dss.vat.dai(address(345)), clip.chip() * tab / WAD); - vm.prank(pauseProxy); vat.file(ilk, "dust", rad(100 ether) + 1); // ensure wmul(dust, chop) > 110 DAI (tab) + vm.prank(pauseProxy); dss.vat.file(ilk, "dust", rad(100 ether) + 1); // ensure wmul(dust, chop) > 110 DAI (tab) clip.upchost(); assertEq(clip.chost(), 110 * RAD + 1); vm.warp(block.timestamp + 300); clip.redo(1, address(456)); - assertEq(vat.dai(address(456)), 0); + assertEq(dss.vat.dai(address(456)), 0); // Set dust so that wmul(dust, chop) is well below tab to check the dusty lot case. - vm.prank(pauseProxy); vat.file(ilk, "dust", rad(20 ether)); // $20 dust + vm.prank(pauseProxy); dss.vat.file(ilk, "dust", rad(20 ether)); // $20 dust clip.upchost(); assertEq(clip.chost(), 22 * RAD); @@ -1344,7 +1341,7 @@ contract LockstakeClipperTest is DssTest { vm.warp(block.timestamp + 300); clip.redo(1, address(567)); - assertEq(vat.dai(address(567)), 0); + assertEq(dss.vat.dai(address(567)), 0); } function testIncentiveMaxValues() public { @@ -1365,9 +1362,9 @@ contract LockstakeClipperTest is DssTest { (,, uint256 lot,, address usr,,) = clip.sales(1); address caller = address(123); clip.rely(caller); - uint256 prevUsrGemBalance = vat.gem(ilk, address(usr)); - uint256 prevCallerGemBalance = vat.gem(ilk, address(caller)); - uint256 prevClipperGemBalance = vat.gem(ilk, address(clip)); + uint256 prevUsrGemBalance = dss.vat.gem(ilk, address(usr)); + uint256 prevCallerGemBalance = dss.vat.gem(ilk, address(caller)); + uint256 prevClipperGemBalance = dss.vat.gem(ilk, address(clip)); uint startGas = gasleft(); vm.prank(caller); clip.yank(1); @@ -1386,19 +1383,19 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.top, 0); // Assert that callback to clear dirt was successful. - assertEq(dog.Dirt(), 0); - (,,, uint256 dirt) = dog.ilks(ilk); + assertEq(dss.dog.Dirt(), 0); + (,,, uint256 dirt) = dss.dog.ilks(ilk); assertEq(dirt, 0); // Assert transfer of gem. - assertEq(vat.gem(ilk, address(usr)), prevUsrGemBalance); - assertEq(vat.gem(ilk, address(caller)), prevCallerGemBalance + lot); - assertEq(vat.gem(ilk, address(clip)), prevClipperGemBalance - lot); + assertEq(dss.vat.gem(ilk, address(usr)), prevUsrGemBalance); + assertEq(dss.vat.gem(ilk, address(caller)), prevCallerGemBalance + lot); + assertEq(dss.vat.gem(ilk, address(clip)), prevClipperGemBalance - lot); } function testRemoveId() public { - LockstakeEngineMock engine2 = new LockstakeEngineMock(address(vat), "random"); - PublicClip pclip = new PublicClip(address(vat), address(spot), address(dog), address(engine2)); + LockstakeEngineMock engine2 = new LockstakeEngineMock(address(dss.vat), "random"); + PublicClip pclip = new PublicClip(address(dss.vat), address(dss.spotter), address(dss.dog), address(engine2)); uint256 pos; pclip.add(); @@ -1463,7 +1460,7 @@ contract LockstakeClipperTest is DssTest { // function testFlashsale() public takeSetup { // address che = address(new Trader(clip, vat, gold, goldJoin, dai, daiJoin, exchange)); - // assertEq(vat.dai(che), 0); + // assertEq(dss.vat.dai(che), 0); // assertEq(dai.balanceOf(che), 0); // vm.prank(che); clip.take({ // id: 1, @@ -1472,14 +1469,14 @@ contract LockstakeClipperTest is DssTest { // who: address(che), // data: "hey" // }); - // assertEq(vat.dai(che), 0); + // assertEq(dss.vat.dai(che), 0); // assertTrue(dai.balanceOf(che) > 0); // Che turned a profit // } function testRevertsReentrancyTake() public takeSetup { BadGuy usr = new BadGuy(clip); - vm.prank(address(usr)); vat.hope(address(clip)); - vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + vm.prank(address(usr)); dss.vat.hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), address(usr), rad(1000 ether)); vm.expectRevert("LockstakeClipper/system-locked"); vm.prank(address(usr)); clip.take({ @@ -1493,8 +1490,8 @@ contract LockstakeClipperTest is DssTest { function testRevertsReentrancyRedo() public takeSetup { RedoGuy usr = new RedoGuy(clip); - vm.prank(address(usr)); vat.hope(address(clip)); - vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + vm.prank(address(usr)); dss.vat.hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), address(usr), rad(1000 ether)); vm.expectRevert("LockstakeClipper/system-locked"); vm.prank(address(usr)); clip.take({ @@ -1508,8 +1505,8 @@ contract LockstakeClipperTest is DssTest { function testRevertsReentrancyKick() public takeSetup { KickGuy usr = new KickGuy(clip); - vm.prank(address(usr)); vat.hope(address(clip)); - vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + vm.prank(address(usr)); dss.vat.hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), address(usr), rad(1000 ether)); clip.rely(address(usr)); vm.expectRevert("LockstakeClipper/system-locked"); @@ -1524,8 +1521,8 @@ contract LockstakeClipperTest is DssTest { function testRevertsReentrancyFileUint() public takeSetup { FileUintGuy usr = new FileUintGuy(clip); - vm.prank(address(usr)); vat.hope(address(clip)); - vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + vm.prank(address(usr)); dss.vat.hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), address(usr), rad(1000 ether)); clip.rely(address(usr)); vm.expectRevert("LockstakeClipper/system-locked"); @@ -1540,8 +1537,8 @@ contract LockstakeClipperTest is DssTest { function testRevertsReentrancyFileAddr() public takeSetup { FileAddrGuy usr = new FileAddrGuy(clip); - vm.prank(address(usr)); vat.hope(address(clip)); - vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + vm.prank(address(usr)); dss.vat.hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), address(usr), rad(1000 ether)); clip.rely(address(usr)); vm.expectRevert("LockstakeClipper/system-locked"); @@ -1556,8 +1553,8 @@ contract LockstakeClipperTest is DssTest { function testRevertsReentrancyYank() public takeSetup { YankGuy usr = new YankGuy(clip); - vm.prank(address(usr)); vat.hope(address(clip)); - vm.prank(pauseProxy); vat.suck(address(0), address(usr), rad(1000 ether)); + vm.prank(address(usr)); dss.vat.hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), address(usr), rad(1000 ether)); clip.rely(address(usr)); vm.expectRevert("LockstakeClipper/system-locked"); @@ -1593,14 +1590,14 @@ contract LockstakeClipperTest is DssTest { assertEq(sale.usr, address(0)); assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(vat.gem(ilk, address(this)), 960 ether); - assertEq(vat.dai(ali), rad(1000 ether)); - (uint256 ink, uint256 art) = vat.urns(ilk, address(this)); + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); + assertEq(dss.vat.dai(ali), rad(1000 ether)); + (uint256 ink, uint256 art) = dss.vat.urns(ilk, address(this)); assertEq(ink, 40 ether); assertEq(art, 100 ether); uint256 preGas = gasleft(); - vm.prank(ali); dog.bark(ilk, address(this), address(ali)); + vm.prank(ali); dss.dog.bark(ilk, address(this), address(ali)); uint256 diffGas = preGas - gasleft(); emit log_named_uint("bark with kick gas", diffGas); } @@ -1618,9 +1615,9 @@ contract LockstakeClipperTest is DssTest { uint256 diffGas = preGas - gasleft(); emit log_named_uint("partial take gas", diffGas); - assertEq(vat.gem(ilk, ali), 11 ether); // Didn't take whole lot - assertEq(vat.dai(ali), rad(945 ether)); // Paid half tab (55) - assertEq(vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) + assertEq(dss.vat.gem(ilk, ali), 11 ether); // Didn't take whole lot + assertEq(dss.vat.dai(ali), rad(945 ether)); // Paid half tab (55) + assertEq(dss.vat.gem(ilk, address(this)), 960 ether); // Collateral not returned (yet) // Assert auction DOES NOT end LockstakeClipper.Sale memory sale; @@ -1648,9 +1645,9 @@ contract LockstakeClipperTest is DssTest { uint256 diffGas = preGas - gasleft(); emit log_named_uint("full take gas", diffGas); - assertEq(vat.gem(ilk, ali), 22 ether); // Didn't take whole lot - assertEq(vat.dai(ali), rad(890 ether)); // Didn't pay more than tab (110) - assertEq(vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr + assertEq(dss.vat.gem(ilk, ali), 22 ether); // Didn't take whole lot + assertEq(dss.vat.dai(ali), rad(890 ether)); // Didn't pay more than tab (110) + assertEq(dss.vat.gem(ilk, address(this)), 978 ether); // 960 + (40 - 22) returned to usr // Assert auction ends LockstakeClipper.Sale memory sale; diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 85f59de9..652059a5 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -3,10 +3,12 @@ pragma solidity ^0.8.16; import "dss-test/DssTest.sol"; +import { LockstakeDeploy } from "deploy/LockstakeDeploy.sol"; +import { LockstakeInit, LockstakeConfig, LockstakeInstance } from "deploy/LockstakeInit.sol"; import { LockstakeEngine } from "src/LockstakeEngine.sol"; import { LockstakeClipper } from "src/LockstakeClipper.sol"; import { LockstakeUrn } from "src/LockstakeUrn.sol"; -import { PipMock } from "test/mocks/PipMock.sol"; +import "dss-interfaces/Interfaces.sol"; import { DelegateFactoryMock, DelegateMock } from "test/mocks/DelegateMock.sol"; import { GemMock } from "test/mocks/GemMock.sol"; import { NstMock } from "test/mocks/NstMock.sol"; @@ -14,73 +16,39 @@ import { NstJoinMock } from "test/mocks/NstJoinMock.sol"; import { StakingRewardsMock } from "test/mocks/StakingRewardsMock.sol"; import { MkrNgtMock } from "test/mocks/MkrNgtMock.sol"; -interface ChainlogLike { - function getAddress(bytes32) external view returns (address); -} - -interface VatLike { - function can(address, address) external view returns (uint256); - function dai(address) external view returns (uint256); - function gem(bytes32, address) external view returns (uint256); - function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); - function urns(bytes32, address) external view returns (uint256, uint256); - function rely(address) external; - function file(bytes32, bytes32, uint256) external; - function init(bytes32) external; - function hope(address) external; - function suck(address, address, uint256) external; -} - -interface SpotterLike { - function file(bytes32, bytes32, address) external; - function file(bytes32, bytes32, uint256) external; - function poke(bytes32) external; -} - -interface JugLike { - function file(bytes32, bytes32, uint256) external; - function init(bytes32) external; -} - -interface DogLike { - function ilks(bytes32) external view returns (address, uint256, uint256, uint256); - function rely(address) external; - function file(bytes32, bytes32, address) external; - function file(bytes32, bytes32, uint256) external; - function bark(bytes32, address, address) external returns (uint256); -} - interface CalcFabLike { function newLinearDecrease(address) external returns (address); } -interface CalcLike { - function file(bytes32, uint256) external; +interface LineMomLike { + function ilks(bytes32) external view returns (uint256); } contract LockstakeEngineTest is DssTest { using stdStorage for StdStorage; - address public pauseProxy; - address public vat; - address public spot; - address public dog; - GemMock public mkr; - address public jug; - LockstakeEngine public engine; - LockstakeClipper public clip; - PipMock public pip; - DelegateFactoryMock public delFactory; - NstMock public nst; - NstJoinMock public nstJoin; - GemMock public stkMkr; - GemMock public rTok; - StakingRewardsMock public farm; - MkrNgtMock public mkrNgt; - GemMock public ngt; - bytes32 public ilk = "LSE"; - address public voter; - address public voterDelegate; + DssInstance dss; + address pauseProxy; + GemMock mkr; + LockstakeEngine engine; + LockstakeClipper clip; + address calc; + MedianAbstract pip; + DelegateFactoryMock delFactory; + NstMock nst; + NstJoinMock nstJoin; + GemMock stkMkr; + GemMock rTok; + StakingRewardsMock farm; + MkrNgtMock mkrNgt; + GemMock ngt; + bytes32 ilk = "LSE"; + address voter; + address voterDelegate; + + LockstakeConfig cfg; + + uint256 prevLine; address constant LOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; @@ -112,56 +80,252 @@ contract LockstakeEngineTest is DssTest { function setUp() public { vm.createSelectFork(vm.envString("ETH_RPC_URL")); - pauseProxy = ChainlogLike(LOG).getAddress("MCD_PAUSE_PROXY"); - vat = ChainlogLike(LOG).getAddress("MCD_VAT"); - spot = ChainlogLike(LOG).getAddress("MCD_SPOT"); - dog = ChainlogLike(LOG).getAddress("MCD_DOG"); + dss = MCD.loadFromChainlog(LOG); + + pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); + pip = MedianAbstract(dss.chainlog.getAddress("PIP_MKR")); mkr = new GemMock(0); - jug = ChainlogLike(LOG).getAddress("MCD_JUG"); nst = new NstMock(); - nstJoin = new NstJoinMock(vat, address(nst)); + nstJoin = new NstJoinMock(address(dss.vat), address(nst)); stkMkr = new GemMock(0); rTok = new GemMock(0); farm = new StakingRewardsMock(address(rTok), address(stkMkr)); ngt = new GemMock(0); mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 24_000); - pip = new PipMock(); delFactory = new DelegateFactoryMock(address(mkr)); voter = address(123); vm.prank(voter); voterDelegate = delFactory.create(); + vm.prank(pauseProxy); pip.kiss(address(this)); + vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); + + LockstakeInstance memory instance = LockstakeDeploy.deployLockstake( + address(this), + pauseProxy, + address(delFactory), + address(nstJoin), + ilk, + address(stkMkr), + 15 * WAD / 100, + address(mkrNgt), + bytes4(abi.encodeWithSignature("newLinearDecrease(address)")) + ); + + engine = LockstakeEngine(instance.engine); + clip = LockstakeClipper(instance.clipper); + calc = instance.clipperCalc; + + address[] memory farms = new address[](2); + farms[0] = address(farm); + farms[1] = address(1111111); // Just to test that more than 1 farm is correctly whitelisted + + cfg = LockstakeConfig({ + ilk: ilk, + delegateFactory: address(delFactory), + nstJoin: address(nstJoin), + nst: address(nstJoin.nst()), + mkr: address(mkr), + stkMkr: address(stkMkr), + mkrNgt: address(mkrNgt), + ngt: address(ngt), + farms: farms, + fee: 15 * WAD / 100, + maxLine: 10_000_000 * 10**45, + gap: 1_000_000 * 10**45, + ttl: 1 days, + dust: 50, + duty: 100000001 * 10**27 / 100000000, + mat: 3 * 10**27, + buf: 1.25 * 10**27, // 25% Initial price buffer + tail: 3600, // 1 hour before reset + cusp: 0.2 * 10**27, // 80% drop before reset + chip: 2 * WAD / 100, + tip: 3, + stopped: 0, + chop: 1 ether, + hole: 10_000 * 10**45, + tau: 100, + cut: 0, + step: 0, + lineMom: true, + tolerance: 0.5 * 10**27, + name: "LOCKSTAKE", + symbol: "LMKR" + }); + + prevLine = dss.vat.Line(); + vm.startPrank(pauseProxy); - engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(stkMkr), 15 * WAD / 100, address(mkrNgt)); - engine.file("jug", jug); - VatLike(vat).rely(address(engine)); - VatLike(vat).init(ilk); - JugLike(jug).init(ilk); - JugLike(jug).file(ilk, "duty", 1001 * 10**27 / 1000); - SpotterLike(spot).file(ilk, "pip", address(pip)); - SpotterLike(spot).file(ilk, "mat", 3 * 10**27); // 300% coll ratio - pip.setPrice(1500 * 10**18); // 1 MKR = 1500 USD - SpotterLike(spot).poke(ilk); - VatLike(vat).file(ilk, "line", 1_000_000 * 10**45); + LockstakeInit.initLockstake(dss, instance, cfg); vm.stopPrank(); deal(address(mkr), address(this), 100_000 * 10**18, true); deal(address(ngt), address(this), 100_000 * 24_000 * 10**18, true); // Add some existing DAI assigned to nstJoin to avoid a particular error - stdstore.target(vat).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); + stdstore.target(address(dss.vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); } function _ink(bytes32 ilk_, address urn) internal view returns (uint256 ink) { - (ink,) = VatLike(vat).urns(ilk_, urn); + (ink,) = dss.vat.urns(ilk_, urn); } function _art(bytes32 ilk_, address urn) internal view returns (uint256 art) { - (, art) = VatLike(vat).urns(ilk_, urn); + (, art) = dss.vat.urns(ilk_, urn); } function _rate(bytes32 ilk_) internal view returns (uint256 rate) { - (, rate,,,) = VatLike(vat).ilks(ilk_); + (, rate,,,) = dss.vat.ilks(ilk_); + } + + function _spot(bytes32 ilk_) internal view returns (uint256 spot) { + (,, spot,,) = dss.vat.ilks(ilk_); + } + + function _line(bytes32 ilk_) internal view returns (uint256 line) { + (,,, line,) = dss.vat.ilks(ilk_); + } + + function _dust(bytes32 ilk_) internal view returns (uint256 dust) { + (,,,, dust) = dss.vat.ilks(ilk_); + } + + function _duty(bytes32 ilk_) internal view returns (uint256 duty) { + (duty,) = dss.jug.ilks(ilk_); + } + + function _rho(bytes32 ilk_) internal view returns (uint256 rho) { + (, rho) = dss.jug.ilks(ilk_); + } + + function _pip(bytes32 ilk_) internal view returns (address pipV) { + (pipV,) = dss.spotter.ilks(ilk_); + } + + function _mat(bytes32 ilk_) internal view returns (uint256 mat) { + (, mat) = dss.spotter.ilks(ilk_); + } + + function _clip(bytes32 ilk_) internal view returns (address clipV) { + (clipV,,,) = dss.dog.ilks(ilk_); + } + + function _chop(bytes32 ilk_) internal view returns (uint256 chop) { + (, chop,,) = dss.dog.ilks(ilk_); + } + + function _hole(bytes32 ilk_) internal view returns (uint256 hole) { + (,, hole,) = dss.dog.ilks(ilk_); + } + + function testDeployAndInit() public { + assertEq(address(engine.delegateFactory()), address(delFactory)); + assertEq(address(engine.vat()), address(dss.vat)); + assertEq(address(engine.nstJoin()), address(nstJoin)); + assertEq(address(engine.nst()), address(nst)); + assertEq(engine.ilk(), ilk); + assertEq(address(engine.mkr()), address(mkr)); + assertEq(address(engine.stkMkr()), address(stkMkr)); + assertEq(engine.fee(), 15 * WAD / 100); + assertEq(address(engine.mkrNgt()), address(mkrNgt)); + assertEq(address(engine.ngt()), address(ngt)); + assertEq(engine.mkrNgtRate(), 24_000); + assertEq(LockstakeUrn(engine.urnImplementation()).engine(), address(engine)); + assertEq(address(LockstakeUrn(engine.urnImplementation()).vat()), address(dss.vat)); + assertEq(address(LockstakeUrn(engine.urnImplementation()).stkMkr()), address(stkMkr)); + + assertEq(clip.ilk(), ilk); + assertEq(address(clip.vat()), address(dss.vat)); + assertEq(address(clip.engine()), address(engine)); + + assertEq(_rate(ilk), 10**27); + assertEq(dss.vat.Line(), prevLine + 1_000_000 * 10**45); + assertEq(_line(ilk), 1_000_000 * 10**45); + assertEq(_dust(ilk), 50); + assertEq(dss.vat.wards(address(engine)), 1); + assertEq(dss.vat.wards(address(clip)), 1); + (uint256 maxline, uint256 gap, uint256 ttl,,) = DssAutoLineAbstract(dss.chainlog.getAddress("MCD_IAM_AUTO_LINE")).ilks(ilk); + assertEq(maxline, 10_000_000 * 10**45); + assertEq(gap, 1_000_000 * 10**45); + assertEq(ttl, 1 days); + assertEq(_rho(ilk), block.timestamp); + assertEq(_duty(ilk), 100000001 * 10**27 / 100000000); + address clipperMom = dss.chainlog.getAddress("CLIPPER_MOM"); + assertEq(pip.bud(address(dss.spotter)), 1); + assertEq(pip.bud(address(clip)), 1); + assertEq(pip.bud(address(clipperMom)), 1); + assertEq(pip.bud(address(dss.end)), 1); + assertEq(_mat(ilk), 3 * 10**27); + assertEq(_pip(ilk), address(pip)); + assertEq(_spot(ilk), (1500 / 3) * 10**27); + assertEq(_clip(ilk), address(clip)); + assertEq(_chop(ilk), 1 ether); + assertEq(_hole(ilk), 10_000 * 10**45); + assertEq(dss.dog.wards(address(clip)), 1); + assertEq(address(engine.jug()), address(dss.jug)); + assertEq(engine.farms(address(farm)), 1); + assertEq(engine.farms(address(1111111)), 1); + assertEq(engine.wards(address(clip)), 1); + assertEq(clip.buf(), 1.25 * 10**27); + assertEq(clip.tail(), 3600); + assertEq(clip.cusp(), 0.2 * 10**27); + assertEq(clip.chip(), 2 * WAD / 100); + assertEq(clip.tip(), 3); + assertEq(clip.stopped(), 0); + assertEq(clip.vow(), address(dss.vow)); + assertEq(address(clip.calc()), calc); + assertEq(clip.chost(), 50 * 1 ether / 10**18); + assertEq(clip.wards(address(dss.dog)), 1); + assertEq(clip.wards(address(dss.end)), 1); + assertEq(clip.wards(clipperMom), 1); + assertEq(LinearDecreaseAbstract(calc).tau(), 100); + assertEq(LineMomLike(dss.chainlog.getAddress("LINE_MOM")).ilks(ilk), 1); + assertEq(ClipperMomAbstract(clipperMom).tolerance(address(clip)), 0.5 * 10**27); + + ( + string memory name, + string memory symbol, + uint256 class, + uint256 dec, + address gem, + address pipV, + address join, + address xlip + ) = IlkRegistryAbstract(dss.chainlog.getAddress("ILK_REGISTRY")).info(ilk); + assertEq(name, "LOCKSTAKE"); + assertEq(symbol, "LMKR"); + assertEq(class, 7); + assertEq(gem, address(mkr)); + assertEq(dec, 18); + assertEq(pipV, address(pip)); + assertEq(join, address(0)); + assertEq(xlip, address(clip)); + + assertEq(dss.chainlog.getAddress("LOCKSTAKE_ENGINE"), address(engine)); + assertEq(dss.chainlog.getAddress("LOCKSTAKE_CLIP"), address(clip)); + assertEq(dss.chainlog.getAddress("LOCKSTAKE_CLIP_CALC"), address(calc)); + + LockstakeInstance memory instance2 = LockstakeDeploy.deployLockstake( + address(this), + pauseProxy, + address(delFactory), + address(nstJoin), + "eee", + address(stkMkr), + 15 * WAD / 100, + address(mkrNgt), + bytes4(abi.encodeWithSignature("newStairstepExponentialDecrease(address)")) + ); + cfg.ilk = "eee"; + cfg.tau = 0; + cfg.cut = 10**27; + cfg.step = 1; + vm.startPrank(pauseProxy); + LockstakeInit.initLockstake(dss, instance2, cfg); + vm.stopPrank(); + assertEq(StairstepExponentialDecreaseAbstract(instance2.clipperCalc).cut(), 10**27); + assertEq(StairstepExponentialDecreaseAbstract(instance2.clipperCalc).step(), 1); } function testConstructor() public { @@ -172,7 +336,7 @@ contract LockstakeEngineTest is DssTest { LockstakeEngine e = new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), 100, address(mkrNgt)); assertEq(address(e.delegateFactory()), address(delFactory)); assertEq(address(e.nstJoin()), address(nstJoin)); - assertEq(address(e.vat()), vat); + assertEq(address(e.vat()), address(dss.vat)); assertEq(address(e.nst()), address(nst)); assertEq(e.ilk(), "aaa"); assertEq(address(e.mkr()), address(mkr)); @@ -182,9 +346,9 @@ contract LockstakeEngineTest is DssTest { assertEq(address(e.ngt()), address(ngt)); assertEq(e.mkrNgtRate(), 24_000); assertEq(LockstakeUrn(e.urnImplementation()).engine(), address(e)); - assertEq(address(LockstakeUrn(e.urnImplementation()).vat()), vat); + assertEq(address(LockstakeUrn(e.urnImplementation()).vat()), address(dss.vat)); assertEq(address(LockstakeUrn(e.urnImplementation()).stkMkr()), address(stkMkr)); - assertEq(VatLike(vat).can(address(e), address(nstJoin)), 1); + assertEq(dss.vat.can(address(e), address(nstJoin)), 1); assertEq(nst.allowance(address(e), address(nstJoin)), type(uint256).max); assertEq(ngt.allowance(address(e), address(mkrNgt)), type(uint256).max); assertEq(mkr.allowance(address(e), address(mkrNgt)), type(uint256).max); @@ -259,17 +423,17 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(1); - assertEq(VatLike(vat).can(urn, address(engine)), 0); + assertEq(dss.vat.can(urn, address(engine)), 0); assertEq(stkMkr.allowance(urn, address(engine)), 0); vm.expectEmit(true, true, true, true); emit Open(address(this), 0, urn); assertEq(engine.open(0), urn); assertEq(engine.usrAmts(address(this)), 1); - assertEq(VatLike(vat).can(urn, address(engine)), 1); + assertEq(dss.vat.can(urn, address(engine)), 1); assertEq(stkMkr.allowance(urn, address(engine)), type(uint256).max); assertEq(LockstakeUrn(urn).engine(), address(engine)); assertEq(address(LockstakeUrn(urn).stkMkr()), address(stkMkr)); - assertEq(address(LockstakeUrn(urn).vat()), vat); + assertEq(address(LockstakeUrn(urn).vat()), address(dss.vat)); vm.expectRevert("LockstakeUrn/not-engine"); LockstakeUrn(urn).init(); @@ -292,7 +456,6 @@ contract LockstakeEngineTest is DssTest { address authedAndUrnAuthed = address(789); vm.startPrank(pauseProxy); engine.rely(authedAndUrnAuthed); - engine.addFarm(address(farm)); vm.stopPrank(); mkr.transfer(urnAuthed, 100_000 * 10**18); ngt.transfer(urnAuthed, 100_000 * 24_000 * 10**18); @@ -380,17 +543,14 @@ contract LockstakeEngineTest is DssTest { address urn = engine.open(0); assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/non-existing-farm"); - engine.selectFarm(urn, address(farm), 5); - vm.prank(pauseProxy); engine.addFarm(address(farm)); - vm.expectEmit(true, true, true, true); - emit SelectFarm(urn, address(farm), 5); - engine.selectFarm(urn, address(farm), 5); - assertEq(engine.urnFarms(urn), address(farm)); - vm.expectRevert("LockstakeEngine/same-farm"); - engine.selectFarm(urn, address(farm), 5); + engine.selectFarm(urn, address(farm2), 5); vm.prank(pauseProxy); engine.addFarm(address(farm2)); + vm.expectEmit(true, true, true, true); + emit SelectFarm(urn, address(farm2), 5); engine.selectFarm(urn, address(farm2), 5); assertEq(engine.urnFarms(urn), address(farm2)); + vm.expectRevert("LockstakeEngine/same-farm"); + engine.selectFarm(urn, address(farm2), 5); assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(stkMkr.balanceOf(address(farm2)), 0); mkr.approve(address(engine), 100_000 * 10**18); @@ -421,7 +581,6 @@ contract LockstakeEngineTest is DssTest { engine.selectDelegate(urn, voterDelegate); } if (withStaking) { - vm.prank(pauseProxy); engine.addFarm(address(farm)); engine.selectFarm(urn, address(farm), 0); } assertEq(_ink(ilk, urn), 0); @@ -512,7 +671,6 @@ contract LockstakeEngineTest is DssTest { engine.selectDelegate(urn, voterDelegate); } if (withStaking) { - vm.prank(pauseProxy); engine.addFarm(address(farm)); engine.selectFarm(urn, address(farm), 0); } assertEq(_ink(ilk, urn), 0); @@ -607,7 +765,6 @@ contract LockstakeEngineTest is DssTest { engine.selectDelegate(urn, voterDelegate); } if (withStaking) { - vm.prank(pauseProxy); engine.addFarm(address(farm)); engine.selectFarm(urn, address(farm), 0); } engine.lock(urn, 100_000 * 10**18, 5); @@ -696,24 +853,24 @@ contract LockstakeEngineTest is DssTest { emit Draw(urn, address(this), 50 * 10**18); engine.draw(urn, address(this), 50 * 10**18); uint256 art = _art(ilk, urn); - uint256 expectedArt = 50 * 10**18 + _divup(50 * 10**18 * 1000, 1001); + uint256 expectedArt = 50 * 10**18 + _divup(50 * 10**18 * 100000000, 100000001); assertEq(art, expectedArt); uint256 rate = _rate(ilk); - assertEq(rate, 1001 * 10**27 / 1000); + assertEq(rate, 100000001 * 10**27 / 100000000); assertEq(nst.balanceOf(address(this)), 100 * 10**18); - assertGt(art * rate, 100.05 * 10**45); - assertLt(art * rate, 100.06 * 10**45); + assertGt(art * rate, 100.0000005 * 10**45); + assertLt(art * rate, 100.0000006 * 10**45); vm.expectRevert("Nst/insufficient-balance"); - engine.wipe(urn, 100.06 * 10**18); - deal(address(nst), address(this), 100.06 * 10**18, true); - assertEq(nst.balanceOf(address(this)), 100.06 * 10**18); - nst.approve(address(engine), 100.06 * 10**18); + engine.wipe(urn, 100.0000006 * 10**18); + deal(address(nst), address(this), 100.0000006 * 10**18, true); + assertEq(nst.balanceOf(address(this)), 100.0000006 * 10**18); + nst.approve(address(engine), 100.0000006 * 10**18); vm.expectRevert(); - engine.wipe(urn, 100.06 * 10**18); // It will try to wipe more art than existing, then reverts + engine.wipe(urn, 100.0000006 * 10**18); // It will try to wipe more art than existing, then reverts vm.expectEmit(true, true, true, true); - emit Wipe(urn, 100.05 * 10**18); - engine.wipe(urn, 100.05 * 10**18); - assertEq(nst.balanceOf(address(this)), 0.01 * 10**18); + emit Wipe(urn, 100.0000005 * 10**18); + engine.wipe(urn, 100.0000005 * 10**18); + assertEq(nst.balanceOf(address(this)), 0.0000001 * 10**18); assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe assertEq(nst.balanceOf(address(123)), 0); emit Draw(urn, address(123), 50 * 10**18); @@ -722,7 +879,6 @@ contract LockstakeEngineTest is DssTest { } function testOpenLockStakeMulticall() public { - vm.prank(pauseProxy); engine.addFarm(address(farm)); mkr.approve(address(engine), 100_000 * 10**18); address urn = engine.getUrn(address(this), 0); @@ -751,7 +907,6 @@ contract LockstakeEngineTest is DssTest { } function testGetReward() public { - vm.prank(pauseProxy); engine.addFarm(address(farm)); address urn = engine.open(0); farm.setReward(address(urn), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 0); @@ -761,28 +916,7 @@ contract LockstakeEngineTest is DssTest { assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 20_000); } - function _clipperSetUp(bool withDelegate, bool withStaking) internal returns (address urn) { - vm.startPrank(pauseProxy); - engine.addFarm(address(farm)); - clip = new LockstakeClipper(vat, spot, dog, address(engine)); - clip.file("vow", ChainlogLike(LOG).getAddress("MCD_VOW")); - engine.rely(address(clip)); - clip.upchost(); - DogLike(dog).file(ilk, "clip", address(clip)); - clip.rely(dog); - DogLike(dog).rely(address(clip)); - VatLike(vat).rely(address(clip)); - - CalcLike calc = CalcLike(CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newLinearDecrease(pauseProxy)); - calc.file("tau", 100); - clip.file("buf", 1.25 * 10**27); // 25% Initial price buffer - clip.file("calc", address(calc)); // File price contract - clip.file("cusp", 0.2 * 10**27); // 80% drop before reset - clip.file("tail", 3600); // 1 hour before reset - DogLike(dog).file(ilk, "chop", 1 ether); // 0% chop - DogLike(dog).file(ilk, "hole", 10_000 * 10**45); - vm.stopPrank(); - + function _urnSetUp(bool withDelegate, bool withStaking) internal returns (address urn) { urn = engine.open(0); if (withDelegate) { engine.selectDelegate(urn, voterDelegate); @@ -814,21 +948,21 @@ contract LockstakeEngineTest is DssTest { } function _forceLiquidation(address urn) internal returns (uint256 id) { - pip.setPrice(0.05 * 10**18); // Force liquidation - SpotterLike(spot).poke(ilk); + vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(0.05 * 10**18))); // Force liquidation + dss.spotter.poke(ilk); assertEq(clip.kicks(), 0); assertEq(engine.urnAuctions(urn), 0); - (,, uint256 hole,) = DogLike(dog).ilks(ilk); + (,, uint256 hole,) = dss.dog.ilks(ilk); uint256 kicked = hole < 2_000 * 10**45 ? 100_000 * 10**18 * hole / (2_000 * 10**45) : 100_000 * 10**18; vm.expectEmit(true, true, true, true); emit OnKick(urn, kicked); - id = DogLike(dog).bark(ilk, address(urn), address(this)); + id = dss.dog.bark(ilk, address(urn), address(this)); assertEq(clip.kicks(), 1); assertEq(engine.urnAuctions(urn), 1); } function _testOnKickFull(bool withDelegate, bool withStaking) internal { - address urn = _clipperSetUp(withDelegate, withStaking); + address urn = _urnSetUp(withDelegate, withStaking); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); uint256 id = _forceLiquidation(urn); @@ -844,7 +978,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { assertEq(engine.urnDelegates(urn), address(0)); @@ -876,9 +1010,9 @@ contract LockstakeEngineTest is DssTest { } function _testOnKickPartial(bool withDelegate, bool withStaking) internal { - address urn = _clipperSetUp(withDelegate, withStaking); + address urn = _urnSetUp(withDelegate, withStaking); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 500 * 10**45); uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -893,7 +1027,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 75_000 * 10**18); assertEq(_art(ilk, urn), 1_500 * 10**18); - assertEq(VatLike(vat).gem(ilk, address(clip)), 25_000 * 10**18); + assertEq(dss.vat.gem(ilk, address(clip)), 25_000 * 10**18); if (withDelegate) { assertEq(engine.urnDelegates(urn), address(0)); @@ -925,11 +1059,11 @@ contract LockstakeEngineTest is DssTest { } function _testOnTake(bool withDelegate, bool withStaking) internal { - address urn = _clipperSetUp(withDelegate, withStaking); + address urn = _urnSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); - uint256 vowInitialBalance = VatLike(vat).dai(vow); + address vow = address(dss.vow); + uint256 vowInitialBalance = dss.vat.dai(vow); uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -944,7 +1078,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { assertEq(mkr.balanceOf(voterDelegate), 0); @@ -958,8 +1092,8 @@ contract LockstakeEngineTest is DssTest { assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); address buyer = address(888); - vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); - vm.prank(buyer); VatLike(vat).hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), buyer, 2_000 * 10**45); + vm.prank(buyer); dss.vat.hope(address(clip)); assertEq(mkr.balanceOf(buyer), 0); vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 20_000 * 10**18); @@ -977,7 +1111,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 80_000 * 10**18); + assertEq(dss.vat.gem(ilk, address(clip)), 80_000 * 10**18); if (withDelegate) { assertEq(mkr.balanceOf(voterDelegate), 0); @@ -1009,7 +1143,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), (100_000 - 32_000 * 1.15) * 10**18); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 0); + assertEq(dss.vat.gem(ilk, address(clip)), 0); assertEq(mkr.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(mkr.totalSupply(), mkrInitialSupply - 32_000 * 0.15 * 10**18); @@ -1019,7 +1153,7 @@ contract LockstakeEngineTest is DssTest { } assertEq(stkMkr.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 1.15 * 10**18); - assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); + assertEq(dss.vat.dai(vow), vowInitialBalance + 2_000 * 10**45); } function testOnTakeNoWithStakingNoDelegate() public { @@ -1039,11 +1173,11 @@ contract LockstakeEngineTest is DssTest { } function _testOnTakePartialBurn(bool withDelegate, bool withStaking) internal { - address urn = _clipperSetUp(withDelegate, withStaking); + address urn = _urnSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); - uint256 vowInitialBalance = VatLike(vat).dai(vow); + address vow = address(dss.vow); + uint256 vowInitialBalance = dss.vat.dai(vow); uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -1058,7 +1192,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { assertEq(mkr.balanceOf(voterDelegate), 0); @@ -1074,8 +1208,8 @@ contract LockstakeEngineTest is DssTest { vm.warp(block.timestamp + 65); // Time passes to let the auction price to crash address buyer = address(888); - vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); - vm.prank(buyer); VatLike(vat).hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), buyer, 2_000 * 10**45); + vm.prank(buyer); dss.vat.hope(address(clip)); assertEq(mkr.balanceOf(buyer), 0); vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 91428571428571428571428); @@ -1087,7 +1221,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 0); + assertEq(dss.vat.gem(ilk, address(clip)), 0); if (withDelegate) { assertEq(mkr.balanceOf(voterDelegate), 0); @@ -1100,7 +1234,7 @@ contract LockstakeEngineTest is DssTest { } assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); - assertEq(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); + assertEq(dss.vat.dai(vow), vowInitialBalance + 2_000 * 10**45); } function testOnTakePartialBurnNoStakingNoDelegate() public { @@ -1120,11 +1254,11 @@ contract LockstakeEngineTest is DssTest { } function _testOnTakeNoBurn(bool withDelegate, bool withStaking) internal { - address urn = _clipperSetUp(withDelegate, withStaking); + address urn = _urnSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); uint256 stkMkrInitialSupply = stkMkr.totalSupply(); - address vow = address(ChainlogLike(LOG).getAddress("MCD_VOW")); - uint256 vowInitialBalance = VatLike(vat).dai(vow); + address vow = address(dss.vow); + uint256 vowInitialBalance = dss.vat.dai(vow); uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -1139,7 +1273,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 100_000 * 10**18); + assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { assertEq(mkr.balanceOf(voterDelegate), 0); @@ -1155,8 +1289,8 @@ contract LockstakeEngineTest is DssTest { vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash address buyer = address(888); - vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 2_000 * 10**45); - vm.prank(buyer); VatLike(vat).hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), buyer, 2_000 * 10**45); + vm.prank(buyer); dss.vat.hope(address(clip)); assertEq(mkr.balanceOf(buyer), 0); vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 100_000 * 10**18); @@ -1168,7 +1302,7 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); - assertEq(VatLike(vat).gem(ilk, address(clip)), 0); + assertEq(dss.vat.gem(ilk, address(clip)), 0); if (withDelegate) { assertEq(mkr.balanceOf(voterDelegate), 0); @@ -1181,7 +1315,7 @@ contract LockstakeEngineTest is DssTest { } assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); - assertLt(VatLike(vat).dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt + assertLt(dss.vat.dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt } function testOnTakeNoBurnNoStakingNoDelegate() public { @@ -1201,12 +1335,12 @@ contract LockstakeEngineTest is DssTest { } function testCannotSelectDuringAuction() public { - address urn = _clipperSetUp(true, true); + address urn = _urnSetUp(true, true); assertEq(engine.urnDelegates(urn), voterDelegate); assertEq(engine.urnFarms(urn), address(farm)); - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 500 * 10**45); + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 500 * 10**45); uint256 id1 = _forceLiquidation(urn); assertEq(engine.urnDelegates(urn), address(0)); @@ -1217,8 +1351,8 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/urn-in-auction"); engine.selectFarm(urn, address(farm), 0); - vm.prank(pauseProxy); DogLike(dog).file(ilk, "hole", 1000 * 10**45); - uint256 id2 = DogLike(dog).bark(ilk, urn, address(this)); + vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 1000 * 10**45); + uint256 id2 = dss.dog.bark(ilk, urn, address(this)); assertEq(engine.urnAuctions(urn), 2); @@ -1229,8 +1363,8 @@ contract LockstakeEngineTest is DssTest { // Take with left > 0 address buyer = address(888); - vm.prank(pauseProxy); VatLike(vat).suck(address(0), buyer, 4_000 * 10**45); - vm.prank(buyer); VatLike(vat).hope(address(clip)); + vm.prank(pauseProxy); dss.vat.suck(address(0), buyer, 4_000 * 10**45); + vm.prank(buyer); dss.vat.hope(address(clip)); vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 8_000 * 10**18); // 500 / (0.05 * 1.25 ) vm.expectEmit(true, true, true, true); @@ -1264,7 +1398,7 @@ contract LockstakeEngineTest is DssTest { } function _testYank(bool withDelegate, bool withStaking) internal { - address urn = _clipperSetUp(withDelegate, withStaking); + address urn = _urnSetUp(withDelegate, withStaking); uint256 id = _forceLiquidation(urn); vm.expectEmit(true, true, true, true); From 153289c075e888ea1e3860eb6e8fa81d51c63bbb Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 6 Feb 2024 07:57:03 +0200 Subject: [PATCH 050/111] Verify farm for reward with enum (#33) * Verify farm for reward with enum * assert => assertTrue * ADDED => ACTIVE * Check that can get reward after farm is deleted --- src/LockstakeEngine.sol | 15 +++++++++------ test/LockstakeEngine.t.sol | 25 +++++++++++++++++-------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 692cdbaa..42b47baf 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -66,7 +66,7 @@ contract LockstakeEngine is Multicall { // --- storage variables --- mapping(address => uint256) public wards; // usr => 1 == access - mapping(address => uint256) public farms; // farm => 1 == whitelisted + mapping(address => FarmStatus) public farms; // farm => FarmStatus mapping(address => uint256) public usrAmts; // usr => urns amount mapping(address => address) public urnOwners; // urn => owner mapping(address => mapping(address => uint256)) public urnCan; // urn => usr => allowed (1 = yes, 0 = no) @@ -75,11 +75,13 @@ contract LockstakeEngine is Multicall { mapping(address => uint256) public urnAuctions; // urn => amount of ongoing liquidations JugLike public jug; - // --- constants --- + // --- constants and enums --- uint256 constant WAD = 10**18; uint256 constant RAY = 10**27; + enum FarmStatus { UNSUPPORTED, ACTIVE, DELETED } + // --- immutables --- DelegateFactoryLike immutable public delegateFactory; @@ -203,12 +205,12 @@ contract LockstakeEngine is Multicall { } function addFarm(address farm) external auth { - farms[farm] = 1; + farms[farm] = FarmStatus.ACTIVE; emit AddFarm(farm); } function delFarm(address farm) external auth { - farms[farm] = 0; + farms[farm] = FarmStatus.DELETED; emit DelFarm(farm); } @@ -277,7 +279,7 @@ contract LockstakeEngine is Multicall { function selectFarm(address urn, address farm, uint16 ref) external urnAuth(urn) { require(urnAuctions[urn] == 0, "LockstakeEngine/urn-in-auction"); - require(farm == address(0) || farms[farm] == 1, "LockstakeEngine/non-existing-farm"); + require(farm == address(0) || farms[farm] == FarmStatus.ACTIVE, "LockstakeEngine/farm-unsupported-or-deleted"); address prevFarm = urnFarms[urn]; require(prevFarm != farm, "LockstakeEngine/same-farm"); (uint256 ink,) = vat.urns(ilk, urn); @@ -322,7 +324,7 @@ contract LockstakeEngine is Multicall { stkMkr.mint(urn, wad); address urnFarm = urnFarms[urn]; if (urnFarm != address(0)) { - require(farms[urnFarm] == 1, "Lockstake/farm-not-whitelisted-anymore"); + require(farms[urnFarm] == FarmStatus.ACTIVE, "LockstakeEngine/farm-deleted"); LockstakeUrn(urn).stake(urnFarm, wad, ref); } } @@ -390,6 +392,7 @@ contract LockstakeEngine is Multicall { // --- staking rewards function --- function getReward(address urn, address farm, address to) external urnAuth(urn) returns (uint256 amt) { + require(farms[farm] > FarmStatus.UNSUPPORTED, "LockstakeEngine/farm-unsupported"); amt = LockstakeUrn(urn).getReward(farm, to); emit GetReward(urn, farm, to, amt); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 652059a5..99a39f31 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -264,8 +264,8 @@ contract LockstakeEngineTest is DssTest { assertEq(_hole(ilk), 10_000 * 10**45); assertEq(dss.dog.wards(address(clip)), 1); assertEq(address(engine.jug()), address(dss.jug)); - assertEq(engine.farms(address(farm)), 1); - assertEq(engine.farms(address(1111111)), 1); + assertTrue(engine.farms(address(farm)) == LockstakeEngine.FarmStatus.ACTIVE); + assertTrue(engine.farms(address(1111111)) == LockstakeEngine.FarmStatus.ACTIVE); assertEq(engine.wards(address(clip)), 1); assertEq(clip.buf(), 1.25 * 10**27); assertEq(clip.tail(), 3600); @@ -406,15 +406,15 @@ contract LockstakeEngineTest is DssTest { } function testAddDelFarm() public { - assertEq(engine.farms(address(1111)), 0); + assertTrue(engine.farms(address(1111)) == LockstakeEngine.FarmStatus.UNSUPPORTED); vm.expectEmit(true, true, true, true); emit AddFarm(address(1111)); vm.prank(pauseProxy); engine.addFarm(address(1111)); - assertEq(engine.farms(address(1111)), 1); + assertTrue(engine.farms(address(1111)) == LockstakeEngine.FarmStatus.ACTIVE); vm.expectEmit(true, true, true, true); emit DelFarm(address(1111)); vm.prank(pauseProxy); engine.delFarm(address(1111)); - assertEq(engine.farms(address(1111)), 0); + assertTrue(engine.farms(address(1111)) == LockstakeEngine.FarmStatus.DELETED); } function testOpen() public { @@ -542,7 +542,7 @@ contract LockstakeEngineTest is DssTest { StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkMkr)); address urn = engine.open(0); assertEq(engine.urnFarms(urn), address(0)); - vm.expectRevert("LockstakeEngine/non-existing-farm"); + vm.expectRevert("LockstakeEngine/farm-unsupported-or-deleted"); engine.selectFarm(urn, address(farm2), 5); vm.prank(pauseProxy); engine.addFarm(address(farm2)); vm.expectEmit(true, true, true, true); @@ -564,6 +564,9 @@ contract LockstakeEngineTest is DssTest { assertEq(stkMkr.balanceOf(address(farm2)), 0); assertEq(farm.balanceOf(urn), 100_000 * 10**18); assertEq(farm2.balanceOf(urn), 0); + vm.prank(pauseProxy); engine.delFarm(address(farm2)); + vm.expectRevert("LockstakeEngine/farm-unsupported-or-deleted"); + engine.selectFarm(urn, address(farm2), 5); } function _testLockFree(bool withDelegate, bool withStaking) internal { @@ -642,7 +645,7 @@ contract LockstakeEngineTest is DssTest { if (withStaking) { mkr.approve(address(engine), 1); vm.prank(pauseProxy); engine.delFarm(address(farm)); - vm.expectRevert("Lockstake/farm-not-whitelisted-anymore"); + vm.expectRevert("LockstakeEngine/farm-deleted"); engine.lock(urn, 1, 0); } } @@ -732,7 +735,7 @@ contract LockstakeEngineTest is DssTest { if (withStaking) { ngt.approve(address(engine), 24_000); vm.prank(pauseProxy); engine.delFarm(address(farm)); - vm.expectRevert("Lockstake/farm-not-whitelisted-anymore"); + vm.expectRevert("LockstakeEngine/farm-deleted"); engine.lockNgt(urn, 24_000, 0); } } @@ -908,12 +911,18 @@ contract LockstakeEngineTest is DssTest { function testGetReward() public { address urn = engine.open(0); + vm.expectRevert("LockstakeEngine/farm-unsupported"); + engine.getReward(urn, address(456), address(123)); farm.setReward(address(urn), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 0); vm.expectEmit(true, true, true, true); emit GetReward(urn, address(farm), address(123), 20_000); assertEq(engine.getReward(urn, address(farm), address(123)), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 20_000); + vm.prank(pauseProxy); engine.delFarm(address(farm)); + farm.setReward(address(urn), 30_000); + assertEq(engine.getReward(urn, address(farm), address(123)), 30_000); // Can get reward after farm is deleted + assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 50_000); } function _urnSetUp(bool withDelegate, bool withStaking) internal returns (address urn) { From 9ef08e3dd46487964c20ce98c14d5150008b02a2 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 12 Feb 2024 13:23:54 -0300 Subject: [PATCH 051/111] Fix burn calculation in onRemove --- src/LockstakeEngine.sol | 2 +- test/LockstakeEngine.t.sol | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 42b47baf..a2027e2d 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -419,7 +419,7 @@ contract LockstakeEngine is Multicall { uint256 burn; uint256 refund; if (left > 0) { - burn = _min(sold * fee / WAD, left); + burn = _min(sold * fee / (WAD - fee), left); mkr.burn(address(this), burn); unchecked { refund = left - burn; } if (refund > 0) { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 99a39f31..7af600bc 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -1133,11 +1133,13 @@ contract LockstakeEngineTest is DssTest { assertEq(stkMkr.balanceOf(address(urn)), 0); assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + uint256 burn = 32_000 * 10**18 * engine.fee() / (WAD - engine.fee()); vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 12_000 * 10**18); vm.expectEmit(true, true, true, true); - emit OnRemove(urn, 32_000 * 10**18, 32_000 * 10**18 * engine.fee() / WAD, 100_000 * 10**18 - 32_000 * 10**18 - 32_000 * 10**18 * engine.fee() / WAD); + emit OnRemove(urn, 32_000 * 10**18, burn, 100_000 * 10**18 - 32_000 * 10**18 - burn); vm.prank(buyer); clip.take(id, 12_000 * 10**18, type(uint256).max, buyer, ""); + assertEq(burn, (32_000 * 10**18 + burn) * engine.fee() / WAD); assertEq(mkr.balanceOf(buyer), 32_000 * 10**18); assertEq(engine.urnAuctions(urn), 0); @@ -1150,18 +1152,18 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tic, 0); assertEq(sale.top, 0); - assertEq(_ink(ilk, urn), (100_000 - 32_000 * 1.15) * 10**18); + assertEq(_ink(ilk, urn), 100_000 * 10**18 - 32_000 * 10**18 - burn); assertEq(_art(ilk, urn), 0); assertEq(dss.vat.gem(ilk, address(clip)), 0); - assertEq(mkr.balanceOf(address(engine)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(mkr.totalSupply(), mkrInitialSupply - 32_000 * 0.15 * 10**18); + assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18 - 32_000 * 10**18 - burn); + assertEq(mkr.totalSupply(), mkrInitialSupply - burn); if (withStaking) { assertEq(stkMkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), (100_000 - 32_000 * 1.15) * 10**18); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 1.15 * 10**18); + assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18 - 32_000 * 10**18 - burn); + assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 10**18 - burn); assertEq(dss.vat.dai(vow), vowInitialBalance + 2_000 * 10**45); } @@ -1374,10 +1376,11 @@ contract LockstakeEngineTest is DssTest { address buyer = address(888); vm.prank(pauseProxy); dss.vat.suck(address(0), buyer, 4_000 * 10**45); vm.prank(buyer); dss.vat.hope(address(clip)); + uint256 burn = 8_000 * 10**18 * engine.fee() / (WAD - engine.fee()); vm.expectEmit(true, true, true, true); emit OnTake(urn, buyer, 8_000 * 10**18); // 500 / (0.05 * 1.25 ) vm.expectEmit(true, true, true, true); - emit OnRemove(urn, 8_000 * 10**18, 8_000 * 10**18 * engine.fee() / WAD, 25_000 * 10**18 - 8_000 * 10**18 - 8_000 * 10**18 * engine.fee() / WAD); + emit OnRemove(urn, 8_000 * 10**18, burn, 25_000 * 10**18 - 8_000 * 10**18 - burn); vm.prank(buyer); clip.take(id1, 25_000 * 10**18, type(uint256).max, buyer, ""); assertEq(engine.urnAuctions(urn), 1); From 7e8b1f93517063289d697252cc2daa22771dd0aa Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 12 Feb 2024 14:42:22 -0300 Subject: [PATCH 052/111] Make wipe permissionless and not calling drip anymore --- src/LockstakeEngine.sol | 5 +++-- test/LockstakeEngine.t.sol | 27 ++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index a2027e2d..0e89e31b 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -30,6 +30,7 @@ interface DelegateLike { } interface VatLike { + function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); function urns(bytes32, address) external view returns (uint256, uint256); function hope(address) external; function slip(bytes32, address, int256) external; @@ -379,10 +380,10 @@ contract LockstakeEngine is Multicall { emit Draw(urn, to, wad); } - function wipe(address urn, uint256 wad) external urnAuth(urn) { + function wipe(address urn, uint256 wad) external { nst.transferFrom(msg.sender, address(this), wad); nstJoin.join(address(this), wad); - uint256 rate = jug.drip(ilk); + (, uint256 rate,,,) = vat.ilks(ilk); uint256 dart = wad * RAY / rate; require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); vat.frob(ilk, urn, address(0), address(this), 0, -int256(dart)); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 7af600bc..4024d264 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -377,7 +377,7 @@ contract LockstakeEngineTest is DssTest { checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); - bytes4[] memory urnOwnersMethods = new bytes4[](11); + bytes4[] memory urnOwnersMethods = new bytes4[](10); urnOwnersMethods[0] = engine.hope.selector; urnOwnersMethods[1] = engine.nope.selector; urnOwnersMethods[2] = engine.selectDelegate.selector; @@ -387,8 +387,7 @@ contract LockstakeEngineTest is DssTest { urnOwnersMethods[6] = engine.free.selector; urnOwnersMethods[7] = engine.freeNgt.selector; urnOwnersMethods[8] = engine.draw.selector; - urnOwnersMethods[9] = engine.wipe.selector; - urnOwnersMethods[10] = engine.getReward.selector; + urnOwnersMethods[9] = engine.getReward.selector; // this checks the case when sender is not the urn owner and not hoped, the hoped case is checked in testHopeNope and the urn owner case in the specific tests vm.startPrank(address(0xBEEF)); @@ -865,20 +864,22 @@ contract LockstakeEngineTest is DssTest { assertLt(art * rate, 100.0000006 * 10**45); vm.expectRevert("Nst/insufficient-balance"); engine.wipe(urn, 100.0000006 * 10**18); - deal(address(nst), address(this), 100.0000006 * 10**18, true); - assertEq(nst.balanceOf(address(this)), 100.0000006 * 10**18); - nst.approve(address(engine), 100.0000006 * 10**18); + address anyone = address(1221121); + deal(address(nst), anyone, 100.0000006 * 10**18, true); + assertEq(nst.balanceOf(anyone), 100.0000006 * 10**18); + vm.prank(anyone); nst.approve(address(engine), 100.0000006 * 10**18); vm.expectRevert(); - engine.wipe(urn, 100.0000006 * 10**18); // It will try to wipe more art than existing, then reverts + vm.prank(anyone); engine.wipe(urn, 100.0000006 * 10**18); // It will try to wipe more art than existing, then reverts vm.expectEmit(true, true, true, true); emit Wipe(urn, 100.0000005 * 10**18); - engine.wipe(urn, 100.0000005 * 10**18); - assertEq(nst.balanceOf(address(this)), 0.0000001 * 10**18); + vm.prank(anyone); engine.wipe(urn, 100.0000005 * 10**18); + assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18); assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe - assertEq(nst.balanceOf(address(123)), 0); - emit Draw(urn, address(123), 50 * 10**18); - engine.draw(urn, address(123), 50 * 10**18); - assertEq(nst.balanceOf(address(123)), 50 * 10**18); + address other = address(123); + assertEq(nst.balanceOf(other), 0); + emit Draw(urn, other, 50 * 10**18); + engine.draw(urn, other, 50 * 10**18); + assertEq(nst.balanceOf(other), 50 * 10**18); } function testOpenLockStakeMulticall() public { From b64a032468734ca3b2e4bb044073e71753f4ba0b Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 19 Feb 2024 18:44:49 -0300 Subject: [PATCH 053/111] Add wipeAll --- src/LockstakeEngine.sol | 12 ++++++++++++ test/LockstakeEngine.t.sol | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 0e89e31b..c8b89683 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -117,6 +117,7 @@ contract LockstakeEngine is Multicall { event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); + event WipeAll(address indexed urn); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); @@ -390,6 +391,17 @@ contract LockstakeEngine is Multicall { emit Wipe(urn, wad); } + function wipeAll(address urn) external { + (, uint256 art) = vat.urns(ilk, urn); + require(art <= uint256(type(int256).max), "LockstakeEngine/overflow"); + (, uint256 rate,,,) = vat.ilks(ilk); + uint256 wad = _divup(art * rate, RAY); + nst.transferFrom(msg.sender, address(this), wad); + nstJoin.join(address(this), wad); + vat.frob(ilk, urn, address(0), address(this), 0, -int256(art)); + emit WipeAll(urn); + } + // --- staking rewards function --- function getReward(address urn, address farm, address to) external urnAuth(urn) returns (uint256 amt) { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 4024d264..f0673f7a 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -66,6 +66,7 @@ contract LockstakeEngineTest is DssTest { event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); + event WipeAll(address indexed urn); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); @@ -874,7 +875,11 @@ contract LockstakeEngineTest is DssTest { emit Wipe(urn, 100.0000005 * 10**18); vm.prank(anyone); engine.wipe(urn, 100.0000005 * 10**18); assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18); - assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe + assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe via this regular function + emit WipeAll(urn); + vm.prank(anyone); engine.wipeAll(urn); + assertEq(_art(ilk, urn), 0); + assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18 - _divup(rate, RAY)); address other = address(123); assertEq(nst.balanceOf(other), 0); emit Draw(urn, other, 50 * 10**18); From b59f967cb63d58ae7d44c8edaf09c6617d53ab04 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 20 Feb 2024 23:51:36 -0300 Subject: [PATCH 054/111] Check urn safety to allow change delegate --- src/LockstakeEngine.sol | 4 +++- test/LockstakeEngine.t.sol | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index c8b89683..a88121af 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -261,7 +261,9 @@ contract LockstakeEngine is Multicall { require(delegate == address(0) || delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); address prevDelegate = urnDelegates[urn]; require(prevDelegate != delegate, "LockstakeEngine/same-delegate"); - (uint256 ink,) = vat.urns(ilk, urn); + (, uint256 rate, uint256 spot,,) = vat.ilks(ilk); + (uint256 ink, uint256 art) = vat.urns(ilk, urn); + require(ink * spot >= art * rate, "LockstakeEngine/urn-unsafe"); _selectDelegate(urn, ink, prevDelegate, delegate); emit SelectDelegate(urn, delegate); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index f0673f7a..515f24d8 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -1410,6 +1410,27 @@ contract LockstakeEngineTest is DssTest { engine.selectFarm(urn, address(farm), 0); } + function testUrnUnsafe() public { + address urn = _urnSetUp(true, true); + + assertEq(engine.urnDelegates(urn), voterDelegate); + + address voterDelegate2 = delFactory.create(); + + vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(0.05 * 10**18))); // Force urn unsafe + dss.spotter.poke(ilk); + + vm.expectRevert("LockstakeEngine/urn-unsafe"); + engine.selectDelegate(urn, voterDelegate2); + + vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); // Back to safety + dss.spotter.poke(ilk); + + engine.selectDelegate(urn, voterDelegate2); + + assertEq(engine.urnDelegates(urn), voterDelegate2); + } + function testOnRemoveOverflow() public { vm.expectRevert("LockstakeEngine/refund-over-maxint"); vm.prank(pauseProxy); engine.onRemove(address(1), 0, uint256(type(int256).max) + 1); From 6fbdca9080d7e8c1cd2c56ae93e37564519bdf72 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 21 Feb 2024 13:47:44 -0300 Subject: [PATCH 055/111] Force fee to be less than WAD --- src/LockstakeEngine.sol | 2 +- test/LockstakeEngine.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index a88121af..a49270e1 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -138,7 +138,7 @@ contract LockstakeEngine is Multicall { // --- constructor --- constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkMkr_, uint256 fee_, address mkrNgt_) { - require(fee_ <= WAD, "LockstakeEngine/fee-over-wad"); + require(fee_ < WAD, "LockstakeEngine/fee-equal-or-greater-wad"); delegateFactory = DelegateFactoryLike(delegateFactory_); nstJoin = NstJoinLike(nstJoin_); vat = nstJoin.vat(); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 515f24d8..8ef4778f 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -330,8 +330,8 @@ contract LockstakeEngineTest is DssTest { } function testConstructor() public { - vm.expectRevert("LockstakeEngine/fee-over-wad"); - new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), WAD + 1, address(mkrNgt)); + vm.expectRevert("LockstakeEngine/fee-equal-or-greater-wad"); + new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), WAD, address(mkrNgt)); vm.expectEmit(true, true, true, true); emit Rely(address(this)); LockstakeEngine e = new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), 100, address(mkrNgt)); From e2814c93129577f2482680431faa4f64159af4aa Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 23 Feb 2024 10:13:39 -0300 Subject: [PATCH 056/111] Minor changes to wipeAll + improving test --- src/LockstakeEngine.sol | 7 +++---- test/LockstakeEngine.t.sol | 27 ++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index a49270e1..072d907d 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -117,7 +117,6 @@ contract LockstakeEngine is Multicall { event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); - event WipeAll(address indexed urn); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); @@ -393,15 +392,15 @@ contract LockstakeEngine is Multicall { emit Wipe(urn, wad); } - function wipeAll(address urn) external { + function wipeAll(address urn) external returns (uint256 wad) { (, uint256 art) = vat.urns(ilk, urn); require(art <= uint256(type(int256).max), "LockstakeEngine/overflow"); (, uint256 rate,,,) = vat.ilks(ilk); - uint256 wad = _divup(art * rate, RAY); + wad = _divup(art * rate, RAY); nst.transferFrom(msg.sender, address(this), wad); nstJoin.join(address(this), wad); vat.frob(ilk, urn, address(0), address(this), 0, -int256(art)); - emit WipeAll(urn); + emit Wipe(urn, wad); } // --- staking rewards function --- diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 8ef4778f..cf26bece 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -66,7 +66,6 @@ contract LockstakeEngineTest is DssTest { event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); - event WipeAll(address indexed urn); event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); @@ -876,8 +875,8 @@ contract LockstakeEngineTest is DssTest { vm.prank(anyone); engine.wipe(urn, 100.0000005 * 10**18); assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18); assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe via this regular function - emit WipeAll(urn); - vm.prank(anyone); engine.wipeAll(urn); + emit Wipe(urn, _divup(rate, RAY)); + vm.prank(anyone); assertEq(engine.wipeAll(urn), _divup(rate, RAY)); assertEq(_art(ilk, urn), 0); assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18 - _divup(rate, RAY)); address other = address(123); @@ -885,6 +884,28 @@ contract LockstakeEngineTest is DssTest { emit Draw(urn, other, 50 * 10**18); engine.draw(urn, other, 50 * 10**18); assertEq(nst.balanceOf(other), 50 * 10**18); + // Check overflows + vm.store( + address(dss.vat), + bytes32(uint256(keccak256(abi.encode(ilk, uint256(2)))) + 1), + bytes32(uint256(1)) + ); + assertEq(_rate(ilk), 1); + vm.expectRevert("LockstakeEngine/overflow"); + engine.draw(urn, address(this), uint256(type(int256).max) / RAY + 1); + stdstore.target(address(dss.vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(uint256(type(int256).max) + RAY); + deal(address(nst), address(this), uint256(type(int256).max) / RAY + 1, true); + nst.approve(address(engine), uint256(type(int256).max) / RAY + 1); + vm.expectRevert("LockstakeEngine/overflow"); + engine.wipe(urn, uint256(type(int256).max) / RAY + 1); + vm.store( + address(dss.vat), + bytes32(uint256(keccak256(abi.encode(urn, keccak256(abi.encode(ilk, uint256(3)))))) + 1), + bytes32(uint256(type(int256).max) + 1) + ); + assertEq(_art(ilk, urn), uint256(type(int256).max) + 1); + vm.expectRevert("LockstakeEngine/overflow"); + engine.wipeAll(urn); } function testOpenLockStakeMulticall() public { From 55921bcb097937366c1cae9b9d155016d8778dbe Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:06:54 -0300 Subject: [PATCH 057/111] Minor changes to test Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- test/LockstakeEngine.t.sol | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index cf26bece..5c6bc240 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -885,11 +885,7 @@ contract LockstakeEngineTest is DssTest { engine.draw(urn, other, 50 * 10**18); assertEq(nst.balanceOf(other), 50 * 10**18); // Check overflows - vm.store( - address(dss.vat), - bytes32(uint256(keccak256(abi.encode(ilk, uint256(2)))) + 1), - bytes32(uint256(1)) - ); + stdstore.target(address(dss.vat)).sig("ilks(bytes32)").with_key(ilk).depth(1).checked_write(1); assertEq(_rate(ilk), 1); vm.expectRevert("LockstakeEngine/overflow"); engine.draw(urn, address(this), uint256(type(int256).max) / RAY + 1); @@ -898,11 +894,7 @@ contract LockstakeEngineTest is DssTest { nst.approve(address(engine), uint256(type(int256).max) / RAY + 1); vm.expectRevert("LockstakeEngine/overflow"); engine.wipe(urn, uint256(type(int256).max) / RAY + 1); - vm.store( - address(dss.vat), - bytes32(uint256(keccak256(abi.encode(urn, keccak256(abi.encode(ilk, uint256(3)))))) + 1), - bytes32(uint256(type(int256).max) + 1) - ); + stdstore.target(address(dss.vat)).sig("urns(bytes32,address)").with_key(ilk).with_key(urn).depth(1).checked_write(uint256(type(int256).max) + 1); assertEq(_art(ilk, urn), uint256(type(int256).max) + 1); vm.expectRevert("LockstakeEngine/overflow"); engine.wipeAll(urn); From 290e1083e97c6dc82d79d15316f5156ecf69927e Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 4 Mar 2024 12:27:11 -0300 Subject: [PATCH 058/111] Allow selectDelegate when urn is unsafe but new address is address(0) --- src/LockstakeEngine.sol | 6 ++++-- test/LockstakeEngine.t.sol | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 072d907d..b5e6ef89 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -260,9 +260,11 @@ contract LockstakeEngine is Multicall { require(delegate == address(0) || delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); address prevDelegate = urnDelegates[urn]; require(prevDelegate != delegate, "LockstakeEngine/same-delegate"); - (, uint256 rate, uint256 spot,,) = vat.ilks(ilk); (uint256 ink, uint256 art) = vat.urns(ilk, urn); - require(ink * spot >= art * rate, "LockstakeEngine/urn-unsafe"); + if (art > 0 && delegate != address(0)) { + (, uint256 rate, uint256 spot,,) = vat.ilks(ilk); + require(ink * spot >= art * rate, "LockstakeEngine/urn-unsafe"); + } _selectDelegate(urn, ink, prevDelegate, delegate); emit SelectDelegate(urn, delegate); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 5c6bc240..d7ad520b 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -1436,6 +1436,11 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/urn-unsafe"); engine.selectDelegate(urn, voterDelegate2); + engine.selectDelegate(urn, address(0)); + + vm.expectRevert("LockstakeEngine/urn-unsafe"); + engine.selectDelegate(urn, voterDelegate2); + vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); // Back to safety dss.spotter.poke(ilk); From 0c7aebfe3876487334491d55a455853231d46774 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 13 Mar 2024 08:22:26 -0300 Subject: [PATCH 059/111] Fix: query mkr address from a correct contract --- deploy/LockstakeDeploy.sol | 2 +- src/LockstakeEngine.sol | 10 +++++----- test/LockstakeEngine.t.sol | 4 ++-- test/mocks/DelegateMock.sol | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/deploy/LockstakeDeploy.sol b/deploy/LockstakeDeploy.sol index d7d43934..df12b638 100644 --- a/deploy/LockstakeDeploy.sol +++ b/deploy/LockstakeDeploy.sol @@ -38,7 +38,7 @@ library LockstakeDeploy { ) internal returns (LockstakeInstance memory lockstakeInstance) { DssInstance memory dss = MCD.loadFromChainlog(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); - lockstakeInstance.engine = address(new LockstakeEngine(delegateFactory, nstJoin, ilk, stkMkr, fee, mkrNgt)); + lockstakeInstance.engine = address(new LockstakeEngine(delegateFactory, nstJoin, ilk, mkrNgt, stkMkr, fee)); lockstakeInstance.clipper = address(new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), lockstakeInstance.engine)); (bool ok, bytes memory returnV) = dss.chainlog.getAddress("CALC_FAB").call(abi.encodeWithSelector(calcSig, owner)); require(ok); diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index b5e6ef89..289c1a62 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -20,7 +20,6 @@ import { LockstakeUrn } from "src/LockstakeUrn.sol"; import { Multicall } from "src/Multicall.sol"; interface DelegateFactoryLike { - function gov() external view returns (GemLike); function isDelegate(address) external returns (uint256); } @@ -58,6 +57,7 @@ interface JugLike { interface MkrNgtLike { function rate() external view returns (uint256); + function mkr() external view returns (address); function ngt() external view returns (address); function ngtToMkr(address, uint256) external; function mkrToNgt(address, uint256) external; @@ -136,19 +136,19 @@ contract LockstakeEngine is Multicall { // --- constructor --- - constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address stkMkr_, uint256 fee_, address mkrNgt_) { + constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address stkMkr_, uint256 fee_) { require(fee_ < WAD, "LockstakeEngine/fee-equal-or-greater-wad"); delegateFactory = DelegateFactoryLike(delegateFactory_); nstJoin = NstJoinLike(nstJoin_); vat = nstJoin.vat(); nst = nstJoin.nst(); ilk = ilk_; - mkr = delegateFactory.gov(); - stkMkr = GemLike(stkMkr_); - fee = fee_; mkrNgt = MkrNgtLike(mkrNgt_); + mkr = GemLike(mkrNgt.mkr()); ngt = GemLike(mkrNgt.ngt()); mkrNgtRate = mkrNgt.rate(); + stkMkr = GemLike(stkMkr_); + fee = fee_; urnImplementation = address(new LockstakeUrn(address(vat), stkMkr_)); vat.hope(nstJoin_); nst.approve(nstJoin_, type(uint256).max); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index d7ad520b..d9da6134 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -330,10 +330,10 @@ contract LockstakeEngineTest is DssTest { function testConstructor() public { vm.expectRevert("LockstakeEngine/fee-equal-or-greater-wad"); - new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), WAD, address(mkrNgt)); + new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), WAD); vm.expectEmit(true, true, true, true); emit Rely(address(this)); - LockstakeEngine e = new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(stkMkr), 100, address(mkrNgt)); + LockstakeEngine e = new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), 100); assertEq(address(e.delegateFactory()), address(delFactory)); assertEq(address(e.nstJoin()), address(nstJoin)); assertEq(address(e.vat()), address(dss.vat)); diff --git a/test/mocks/DelegateMock.sol b/test/mocks/DelegateMock.sol index f63a615b..50a9e87e 100644 --- a/test/mocks/DelegateMock.sol +++ b/test/mocks/DelegateMock.sol @@ -9,7 +9,7 @@ interface GemLike { contract DelegateFactoryMock { mapping(address => uint256) public isDelegate; - address immutable public gov; + address immutable internal gov; constructor(address _gov) { gov = _gov; From 48dd100cdb645edbff161332af1c2073c2455ff6 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 13 Mar 2024 08:23:45 -0300 Subject: [PATCH 060/111] Change visibility for better consistency --- test/mocks/DelegateMock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocks/DelegateMock.sol b/test/mocks/DelegateMock.sol index 50a9e87e..44dfb954 100644 --- a/test/mocks/DelegateMock.sol +++ b/test/mocks/DelegateMock.sol @@ -9,7 +9,7 @@ interface GemLike { contract DelegateFactoryMock { mapping(address => uint256) public isDelegate; - address immutable internal gov; + address immutable private gov; constructor(address _gov) { gov = _gov; From 9c33a83f4eeb9169da4bbcbc3c6a70fd4535c8b9 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:31:11 -0300 Subject: [PATCH 061/111] Fix VoteDelegateFactory mock and do a full renaming (#35) * Fix VoteDelegateFactory mock and do a full renaming * change error message --- deploy/LockstakeDeploy.sol | 4 +- deploy/LockstakeInit.sol | 34 +-- src/LockstakeEngine.sol | 96 ++++----- test/LockstakeEngine.t.sol | 196 +++++++++--------- ...{DelegateMock.sol => VoteDelegateMock.sol} | 13 +- 5 files changed, 171 insertions(+), 172 deletions(-) rename test/mocks/{DelegateMock.sol => VoteDelegateMock.sol} (70%) diff --git a/deploy/LockstakeDeploy.sol b/deploy/LockstakeDeploy.sol index df12b638..87170a13 100644 --- a/deploy/LockstakeDeploy.sol +++ b/deploy/LockstakeDeploy.sol @@ -28,7 +28,7 @@ library LockstakeDeploy { function deployLockstake( address deployer, address owner, - address delegateFactory, + address voteDelegateFactory, address nstJoin, bytes32 ilk, address stkMkr, @@ -38,7 +38,7 @@ library LockstakeDeploy { ) internal returns (LockstakeInstance memory lockstakeInstance) { DssInstance memory dss = MCD.loadFromChainlog(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); - lockstakeInstance.engine = address(new LockstakeEngine(delegateFactory, nstJoin, ilk, mkrNgt, stkMkr, fee)); + lockstakeInstance.engine = address(new LockstakeEngine(voteDelegateFactory, nstJoin, ilk, mkrNgt, stkMkr, fee)); lockstakeInstance.clipper = address(new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), lockstakeInstance.engine)); (bool ok, bytes memory returnV) = dss.chainlog.getAddress("CALC_FAB").call(abi.encodeWithSelector(calcSig, owner)); require(ok); diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index 55a58868..9658f60e 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -20,7 +20,7 @@ import { DssInstance } from "dss-test/MCD.sol"; import { LockstakeInstance } from "./LockstakeInstance.sol"; interface LockstakeEngineLike { - function delegateFactory() external view returns (address); + function voteDelegateFactory() external view returns (address); function vat() external view returns (address); function nstJoin() external view returns (address); function nst() external view returns (address); @@ -83,7 +83,7 @@ interface IlkRegistryLike { struct LockstakeConfig { bytes32 ilk; - address delegateFactory; + address voteDelegateFactory; address nstJoin; address nst; address mkr; @@ -130,21 +130,21 @@ library LockstakeInit { CalcLike calc = CalcLike(lockstakeInstance.clipperCalc); // Sanity checks - require(engine.delegateFactory() == cfg.delegateFactory, "Engine delegateFactory mismatch"); - require(engine.vat() == address(dss.vat), "Engine vat mismatch"); - require(engine.nstJoin() == cfg.nstJoin, "Engine nstJoin mismatch"); - require(engine.nst() == cfg.nst, "Engine nst mismatch"); - require(engine.ilk() == cfg.ilk, "Engine ilk mismatch"); - require(engine.mkr() == cfg.mkr, "Engine mkr mismatch"); - require(engine.stkMkr() == cfg.stkMkr, "Engine stkMkr mismatch"); - require(engine.fee() == cfg.fee, "Engine fee mismatch"); - require(engine.mkrNgt() == cfg.mkrNgt, "Engine mkrNgt mismatch"); - require(engine.ngt() == cfg.ngt, "Engine ngt mismatch"); - require(clipper.ilk() == cfg.ilk, "Clipper ilk mismatch"); - require(clipper.vat() == address(dss.vat), "Clipper vat mismatch"); - require(clipper.engine() == address(engine), "Clipper engine mismatch"); - require(clipper.dog() == address(dss.dog), "Clipper dog mismatch"); - require(clipper.spotter() == address(dss.spotter), "Clipper spotter mismatch"); + require(engine.voteDelegateFactory() == cfg.voteDelegateFactory, "Engine voteDelegateFactory mismatch"); + require(engine.vat() == address(dss.vat), "Engine vat mismatch"); + require(engine.nstJoin() == cfg.nstJoin, "Engine nstJoin mismatch"); + require(engine.nst() == cfg.nst, "Engine nst mismatch"); + require(engine.ilk() == cfg.ilk, "Engine ilk mismatch"); + require(engine.mkr() == cfg.mkr, "Engine mkr mismatch"); + require(engine.stkMkr() == cfg.stkMkr, "Engine stkMkr mismatch"); + require(engine.fee() == cfg.fee, "Engine fee mismatch"); + require(engine.mkrNgt() == cfg.mkrNgt, "Engine mkrNgt mismatch"); + require(engine.ngt() == cfg.ngt, "Engine ngt mismatch"); + require(clipper.ilk() == cfg.ilk, "Clipper ilk mismatch"); + require(clipper.vat() == address(dss.vat), "Clipper vat mismatch"); + require(clipper.engine() == address(engine), "Clipper engine mismatch"); + require(clipper.dog() == address(dss.dog), "Clipper dog mismatch"); + require(clipper.spotter() == address(dss.spotter), "Clipper spotter mismatch"); require(cfg.dust <= cfg.hole, "dust greater than hole"); require(cfg.duty >= RAY && cfg.duty <= RATES_ONE_HUNDRED_PCT, "duty out of boundaries"); diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 289c1a62..f233ef38 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -19,11 +19,11 @@ pragma solidity ^0.8.16; import { LockstakeUrn } from "src/LockstakeUrn.sol"; import { Multicall } from "src/Multicall.sol"; -interface DelegateFactoryLike { - function isDelegate(address) external returns (uint256); +interface VoteDelegateFactoryLike { + function created(address) external returns (uint256); } -interface DelegateLike { +interface VoteDelegateLike { function lock(uint256) external; function free(uint256) external; } @@ -66,14 +66,14 @@ interface MkrNgtLike { contract LockstakeEngine is Multicall { // --- storage variables --- - mapping(address => uint256) public wards; // usr => 1 == access - mapping(address => FarmStatus) public farms; // farm => FarmStatus - mapping(address => uint256) public usrAmts; // usr => urns amount - mapping(address => address) public urnOwners; // urn => owner - mapping(address => mapping(address => uint256)) public urnCan; // urn => usr => allowed (1 = yes, 0 = no) - mapping(address => address) public urnDelegates; // urn => current associated delegate - mapping(address => address) public urnFarms; // urn => current selected farm - mapping(address => uint256) public urnAuctions; // urn => amount of ongoing liquidations + mapping(address => uint256) public wards; // usr => 1 == access + mapping(address => FarmStatus) public farms; // farm => FarmStatus + mapping(address => uint256) public usrAmts; // usr => urns amount + mapping(address => address) public urnOwners; // urn => owner + mapping(address => mapping(address => uint256)) public urnCan; // urn => usr => allowed (1 = yes, 0 = no) + mapping(address => address) public urnVoteDelegates; // urn => current associated voteDelegate + mapping(address => address) public urnFarms; // urn => current selected farm + mapping(address => uint256) public urnAuctions; // urn => amount of ongoing liquidations JugLike public jug; // --- constants and enums --- @@ -85,18 +85,18 @@ contract LockstakeEngine is Multicall { // --- immutables --- - DelegateFactoryLike immutable public delegateFactory; - VatLike immutable public vat; - NstJoinLike immutable public nstJoin; - GemLike immutable public nst; - bytes32 immutable public ilk; - GemLike immutable public mkr; - GemLike immutable public stkMkr; - uint256 immutable public fee; - MkrNgtLike immutable public mkrNgt; - GemLike immutable public ngt; - uint256 immutable public mkrNgtRate; - address immutable public urnImplementation; + VoteDelegateFactoryLike immutable public voteDelegateFactory; + VatLike immutable public vat; + NstJoinLike immutable public nstJoin; + GemLike immutable public nst; + bytes32 immutable public ilk; + GemLike immutable public mkr; + GemLike immutable public stkMkr; + uint256 immutable public fee; + MkrNgtLike immutable public mkrNgt; + GemLike immutable public ngt; + uint256 immutable public mkrNgtRate; + address immutable public urnImplementation; // --- events --- @@ -108,7 +108,7 @@ contract LockstakeEngine is Multicall { event Open(address indexed owner, uint256 indexed index, address urn); event Hope(address indexed urn, address indexed usr); event Nope(address indexed urn, address indexed usr); - event SelectDelegate(address indexed urn, address indexed delegate); + event SelectVoteDelegate(address indexed urn, address indexed voteDelegate); event SelectFarm(address indexed urn, address farm, uint16 ref); event Lock(address indexed urn, uint256 wad, uint16 ref); event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); @@ -136,9 +136,9 @@ contract LockstakeEngine is Multicall { // --- constructor --- - constructor(address delegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address stkMkr_, uint256 fee_) { + constructor(address voteDelegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address stkMkr_, uint256 fee_) { require(fee_ < WAD, "LockstakeEngine/fee-equal-or-greater-wad"); - delegateFactory = DelegateFactoryLike(delegateFactory_); + voteDelegateFactory = VoteDelegateFactoryLike(voteDelegateFactory_); nstJoin = NstJoinLike(nstJoin_); vat = nstJoin.vat(); nst = nstJoin.nst(); @@ -255,31 +255,31 @@ contract LockstakeEngine is Multicall { // --- delegation/staking functions --- - function selectDelegate(address urn, address delegate) external urnAuth(urn) { + function selectVoteDelegate(address urn, address voteDelegate) external urnAuth(urn) { require(urnAuctions[urn] == 0, "LockstakeEngine/urn-in-auction"); - require(delegate == address(0) || delegateFactory.isDelegate(delegate) == 1, "LockstakeEngine/not-valid-delegate"); - address prevDelegate = urnDelegates[urn]; - require(prevDelegate != delegate, "LockstakeEngine/same-delegate"); + require(voteDelegate == address(0) || voteDelegateFactory.created(voteDelegate) == 1, "LockstakeEngine/not-valid-vote-delegate"); + address prevVoteDelegate = urnVoteDelegates[urn]; + require(prevVoteDelegate != voteDelegate, "LockstakeEngine/same-vote-delegate"); (uint256 ink, uint256 art) = vat.urns(ilk, urn); - if (art > 0 && delegate != address(0)) { + if (art > 0 && voteDelegate != address(0)) { (, uint256 rate, uint256 spot,,) = vat.ilks(ilk); require(ink * spot >= art * rate, "LockstakeEngine/urn-unsafe"); } - _selectDelegate(urn, ink, prevDelegate, delegate); - emit SelectDelegate(urn, delegate); + _selectVoteDelegate(urn, ink, prevVoteDelegate, voteDelegate); + emit SelectVoteDelegate(urn, voteDelegate); } - function _selectDelegate(address urn, uint256 wad, address prevDelegate, address delegate) internal { + function _selectVoteDelegate(address urn, uint256 wad, address prevVoteDelegate, address voteDelegate) internal { if (wad > 0) { - if (prevDelegate != address(0)) { - DelegateLike(prevDelegate).free(wad); + if (prevVoteDelegate != address(0)) { + VoteDelegateLike(prevVoteDelegate).free(wad); } - if (delegate != address(0)) { - mkr.approve(delegate, wad); - DelegateLike(delegate).lock(wad); + if (voteDelegate != address(0)) { + mkr.approve(voteDelegate, wad); + VoteDelegateLike(voteDelegate).lock(wad); } } - urnDelegates[urn] = delegate; + urnVoteDelegates[urn] = voteDelegate; } function selectFarm(address urn, address farm, uint16 ref) external urnAuth(urn) { @@ -319,10 +319,10 @@ contract LockstakeEngine is Multicall { function _lock(address urn, uint256 wad, uint16 ref) internal { require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); - address delegate = urnDelegates[urn]; - if (delegate != address(0)) { - mkr.approve(delegate, wad); - DelegateLike(delegate).lock(wad); + address voteDelegate = urnVoteDelegates[urn]; + if (voteDelegate != address(0)) { + mkr.approve(voteDelegate, wad); + VoteDelegateLike(voteDelegate).lock(wad); } vat.slip(ilk, urn, int256(wad)); vat.frob(ilk, urn, urn, address(0), int256(wad), 0); @@ -362,9 +362,9 @@ contract LockstakeEngine is Multicall { stkMkr.burn(urn, wad); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); - address delegate = urnDelegates[urn]; - if (delegate != address(0)) { - DelegateLike(delegate).free(wad); + address voteDelegate = urnVoteDelegates[urn]; + if (voteDelegate != address(0)) { + VoteDelegateLike(voteDelegate).free(wad); } uint256 burn = wad * fee_ / WAD; if (burn > 0) { @@ -419,7 +419,7 @@ contract LockstakeEngine is Multicall { // Urn confiscation happens in Dog contract where ilk vat.gem is sent to the LockstakeClipper (uint256 ink,) = vat.urns(ilk, urn); uint256 inkBeforeKick = ink + wad; - _selectDelegate(urn, inkBeforeKick, urnDelegates[urn], address(0)); + _selectVoteDelegate(urn, inkBeforeKick, urnVoteDelegates[urn], address(0)); _selectFarm(urn, inkBeforeKick, urnFarms[urn], address(0), 0); stkMkr.burn(urn, wad); urnAuctions[urn]++; diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index d9da6134..c996d48a 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -9,7 +9,7 @@ import { LockstakeEngine } from "src/LockstakeEngine.sol"; import { LockstakeClipper } from "src/LockstakeClipper.sol"; import { LockstakeUrn } from "src/LockstakeUrn.sol"; import "dss-interfaces/Interfaces.sol"; -import { DelegateFactoryMock, DelegateMock } from "test/mocks/DelegateMock.sol"; +import { VoteDelegateFactoryMock, VoteDelegateMock } from "test/mocks/VoteDelegateMock.sol"; import { GemMock } from "test/mocks/GemMock.sol"; import { NstMock } from "test/mocks/NstMock.sol"; import { NstJoinMock } from "test/mocks/NstJoinMock.sol"; @@ -27,24 +27,24 @@ interface LineMomLike { contract LockstakeEngineTest is DssTest { using stdStorage for StdStorage; - DssInstance dss; - address pauseProxy; - GemMock mkr; - LockstakeEngine engine; - LockstakeClipper clip; - address calc; - MedianAbstract pip; - DelegateFactoryMock delFactory; - NstMock nst; - NstJoinMock nstJoin; - GemMock stkMkr; - GemMock rTok; - StakingRewardsMock farm; - MkrNgtMock mkrNgt; - GemMock ngt; - bytes32 ilk = "LSE"; - address voter; - address voterDelegate; + DssInstance dss; + address pauseProxy; + GemMock mkr; + LockstakeEngine engine; + LockstakeClipper clip; + address calc; + MedianAbstract pip; + VoteDelegateFactoryMock voteDelegateFactory; + NstMock nst; + NstJoinMock nstJoin; + GemMock stkMkr; + GemMock rTok; + StakingRewardsMock farm; + MkrNgtMock mkrNgt; + GemMock ngt; + bytes32 ilk = "LSE"; + address voter; + address voteDelegate; LockstakeConfig cfg; @@ -57,7 +57,7 @@ contract LockstakeEngineTest is DssTest { event Open(address indexed owner, uint256 indexed index, address urn); event Hope(address indexed urn, address indexed usr); event Nope(address indexed urn, address indexed usr); - event SelectDelegate(address indexed urn, address indexed delegate_); + event SelectVoteDelegate(address indexed urn, address indexed voteDelegate_); event SelectFarm(address indexed urn, address farm, uint16 ref); event Lock(address indexed urn, uint256 wad, uint16 ref); event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); @@ -93,9 +93,9 @@ contract LockstakeEngineTest is DssTest { ngt = new GemMock(0); mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 24_000); - delFactory = new DelegateFactoryMock(address(mkr)); + voteDelegateFactory = new VoteDelegateFactoryMock(address(mkr)); voter = address(123); - vm.prank(voter); voterDelegate = delFactory.create(); + vm.prank(voter); voteDelegate = voteDelegateFactory.create(); vm.prank(pauseProxy); pip.kiss(address(this)); vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); @@ -103,7 +103,7 @@ contract LockstakeEngineTest is DssTest { LockstakeInstance memory instance = LockstakeDeploy.deployLockstake( address(this), pauseProxy, - address(delFactory), + address(voteDelegateFactory), address(nstJoin), ilk, address(stkMkr), @@ -122,7 +122,7 @@ contract LockstakeEngineTest is DssTest { cfg = LockstakeConfig({ ilk: ilk, - delegateFactory: address(delFactory), + voteDelegateFactory: address(voteDelegateFactory), nstJoin: address(nstJoin), nst: address(nstJoin.nst()), mkr: address(mkr), @@ -220,7 +220,7 @@ contract LockstakeEngineTest is DssTest { } function testDeployAndInit() public { - assertEq(address(engine.delegateFactory()), address(delFactory)); + assertEq(address(engine.voteDelegateFactory()), address(voteDelegateFactory)); assertEq(address(engine.vat()), address(dss.vat)); assertEq(address(engine.nstJoin()), address(nstJoin)); assertEq(address(engine.nst()), address(nst)); @@ -309,7 +309,7 @@ contract LockstakeEngineTest is DssTest { LockstakeInstance memory instance2 = LockstakeDeploy.deployLockstake( address(this), pauseProxy, - address(delFactory), + address(voteDelegateFactory), address(nstJoin), "eee", address(stkMkr), @@ -330,11 +330,11 @@ contract LockstakeEngineTest is DssTest { function testConstructor() public { vm.expectRevert("LockstakeEngine/fee-equal-or-greater-wad"); - new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), WAD); + new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), WAD); vm.expectEmit(true, true, true, true); emit Rely(address(this)); - LockstakeEngine e = new LockstakeEngine(address(delFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), 100); - assertEq(address(e.delegateFactory()), address(delFactory)); + LockstakeEngine e = new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), 100); + assertEq(address(e.voteDelegateFactory()), address(voteDelegateFactory)); assertEq(address(e.nstJoin()), address(nstJoin)); assertEq(address(e.vat()), address(dss.vat)); assertEq(address(e.nst()), address(nst)); @@ -380,7 +380,7 @@ contract LockstakeEngineTest is DssTest { bytes4[] memory urnOwnersMethods = new bytes4[](10); urnOwnersMethods[0] = engine.hope.selector; urnOwnersMethods[1] = engine.nope.selector; - urnOwnersMethods[2] = engine.selectDelegate.selector; + urnOwnersMethods[2] = engine.selectVoteDelegate.selector; urnOwnersMethods[3] = engine.selectFarm.selector; urnOwnersMethods[4] = engine.lock.selector; urnOwnersMethods[5] = engine.lockNgt.selector; @@ -484,8 +484,8 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 150_000 * 10**18); engine.freeNgt(urn, address(this), 50_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); - engine.selectDelegate(urn, voterDelegate); - assertEq(engine.urnDelegates(urn), voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); + assertEq(engine.urnVoteDelegates(urn), voteDelegate); engine.draw(urn, address(urnAuthed), 1); nst.approve(address(engine), 1); engine.wipe(urn, 1); @@ -501,39 +501,39 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 50_000 * 10**18); } - function testSelectDelegate() public { + function testSelectVoteDelegate() public { address urn = engine.open(0); - vm.expectRevert("LockstakeEngine/not-valid-delegate"); - engine.selectDelegate(urn, address(111)); + vm.expectRevert("LockstakeEngine/not-valid-vote-delegate"); + engine.selectVoteDelegate(urn, address(111)); vm.expectEmit(true, true, true, true); - emit SelectDelegate(urn, voterDelegate); - engine.selectDelegate(urn, voterDelegate); - vm.expectRevert("LockstakeEngine/same-delegate"); - engine.selectDelegate(urn, voterDelegate); - assertEq(engine.urnDelegates(urn), voterDelegate); - vm.prank(address(888)); address voterDelegate2 = delFactory.create(); + emit SelectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(urn, voteDelegate); + vm.expectRevert("LockstakeEngine/same-vote-delegate"); + engine.selectVoteDelegate(urn, voteDelegate); + assertEq(engine.urnVoteDelegates(urn), voteDelegate); + vm.prank(address(888)); address voteDelegate2 = voteDelegateFactory.create(); mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18, 5); - assertEq(DelegateMock(voterDelegate).stake(address(engine)), 100_000 * 10**18); - assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); - assertEq(mkr.balanceOf(voterDelegate2), 0); + assertEq(VoteDelegateMock(voteDelegate).stake(address(engine)), 100_000 * 10**18); + assertEq(VoteDelegateMock(voteDelegate2).stake(address(engine)), 0); + assertEq(mkr.balanceOf(voteDelegate), 100_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate2), 0); assertEq(mkr.balanceOf(address(engine)), 0); vm.expectEmit(true, true, true, true); - emit SelectDelegate(urn, voterDelegate2); - engine.selectDelegate(urn, voterDelegate2); - assertEq(engine.urnDelegates(urn), voterDelegate2); - assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); - assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 100_000 * 10**18); - assertEq(mkr.balanceOf(voterDelegate), 0); - assertEq(mkr.balanceOf(voterDelegate2), 100_000 * 10**18); + emit SelectVoteDelegate(urn, voteDelegate2); + engine.selectVoteDelegate(urn, voteDelegate2); + assertEq(engine.urnVoteDelegates(urn), voteDelegate2); + assertEq(VoteDelegateMock(voteDelegate).stake(address(engine)), 0); + assertEq(VoteDelegateMock(voteDelegate2).stake(address(engine)), 100_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate2), 100_000 * 10**18); assertEq(mkr.balanceOf(address(engine)), 0); - engine.selectDelegate(urn, address(0)); - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(DelegateMock(voterDelegate).stake(address(engine)), 0); - assertEq(DelegateMock(voterDelegate2).stake(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 0); - assertEq(mkr.balanceOf(voterDelegate2), 0); + engine.selectVoteDelegate(urn, address(0)); + assertEq(engine.urnVoteDelegates(urn), address(0)); + assertEq(VoteDelegateMock(voteDelegate).stake(address(engine)), 0); + assertEq(VoteDelegateMock(voteDelegate2).stake(address(engine)), 0); + assertEq(mkr.balanceOf(voteDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate2), 0); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } @@ -580,7 +580,7 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/wad-overflow"); engine.free(urn, address(this), uint256(type(int256).max) + 1); if (withDelegate) { - engine.selectDelegate(urn, voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); } if (withStaking) { engine.selectFarm(urn, address(farm), 0); @@ -601,7 +601,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + assertEq(mkr.balanceOf(voteDelegate), 100_000 * 10**18); // Remains in voteDelegate as it is a mock (otherwise it would be in the Chief) } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } @@ -619,7 +619,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 60_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate), 60_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } @@ -636,7 +636,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 50_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate), 50_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } @@ -670,7 +670,7 @@ contract LockstakeEngineTest is DssTest { address urn = engine.open(0); // Note: wad-overflow cannot be reached for lockNgt and freeNgt as with these functions and the value of rate (>=3) the MKR amount will be always lower if (withDelegate) { - engine.selectDelegate(urn, voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); } if (withStaking) { engine.selectFarm(urn, address(farm), 0); @@ -691,7 +691,7 @@ contract LockstakeEngineTest is DssTest { assertEq(ngt.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + assertEq(mkr.balanceOf(voteDelegate), 100_000 * 10**18); // Remains in voteDelegate as it is a mock (otherwise it would be in the Chief) } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } @@ -709,7 +709,7 @@ contract LockstakeEngineTest is DssTest { assertEq(ngt.balanceOf(address(this)), 40_000 * 24_000 * 10**18 - 40_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 60_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate), 60_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } @@ -726,7 +726,7 @@ contract LockstakeEngineTest is DssTest { assertEq(ngt.balanceOf(address(123)), 10_000 * 24_000 * 10**18 - 10_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 50_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate), 50_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } @@ -764,7 +764,7 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/wad-overflow"); engine.freeNoFee(urn, address(this), uint256(type(int256).max) + 1); if (withDelegate) { - engine.selectDelegate(urn, voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); } if (withStaking) { engine.selectFarm(urn, address(farm), 0); @@ -780,7 +780,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); // Remains in delegate as it is a mock (otherwise it would be in the Chief) + assertEq(mkr.balanceOf(voteDelegate), 100_000 * 10**18); // Remains in voteDelegate as it is a mock (otherwise it would be in the Chief) } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } @@ -798,7 +798,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 60_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate), 60_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } @@ -815,7 +815,7 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); - assertEq(mkr.balanceOf(voterDelegate), 50_000 * 10**18); + assertEq(mkr.balanceOf(voteDelegate), 50_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } @@ -947,7 +947,7 @@ contract LockstakeEngineTest is DssTest { function _urnSetUp(bool withDelegate, bool withStaking) internal returns (address urn) { urn = engine.open(0); if (withDelegate) { - engine.selectDelegate(urn, voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); } if (withStaking) { engine.selectFarm(urn, address(farm), 0); @@ -959,11 +959,11 @@ contract LockstakeEngineTest is DssTest { assertEq(_art(ilk, urn), 2_000 * 10**18); if (withDelegate) { - assertEq(engine.urnDelegates(urn), voterDelegate); - assertEq(mkr.balanceOf(voterDelegate), 100_000 * 10**18); + assertEq(engine.urnVoteDelegates(urn), voteDelegate); + assertEq(mkr.balanceOf(voteDelegate), 100_000 * 10**18); assertEq(mkr.balanceOf(address(engine)), 0); } else { - assertEq(engine.urnDelegates(urn), address(0)); + assertEq(engine.urnVoteDelegates(urn), address(0)); assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } if (withStaking) { @@ -1009,8 +1009,8 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(engine.urnVoteDelegates(urn), address(0)); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -1058,8 +1058,8 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 25_000 * 10**18); if (withDelegate) { - assertEq(engine.urnDelegates(urn), address(0)); - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(engine.urnVoteDelegates(urn), address(0)); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -1109,7 +1109,7 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -1142,7 +1142,7 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 80_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 80_000 * 10**18); if (withStaking) { @@ -1225,7 +1225,7 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -1254,7 +1254,7 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 0); if (withDelegate) { - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 @@ -1306,7 +1306,7 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 100_000 * 10**18); if (withDelegate) { - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { @@ -1335,7 +1335,7 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), 0); if (withDelegate) { - assertEq(mkr.balanceOf(voterDelegate), 0); + assertEq(mkr.balanceOf(voteDelegate), 0); } assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply); // Can't burn anything @@ -1367,17 +1367,17 @@ contract LockstakeEngineTest is DssTest { function testCannotSelectDuringAuction() public { address urn = _urnSetUp(true, true); - assertEq(engine.urnDelegates(urn), voterDelegate); + assertEq(engine.urnVoteDelegates(urn), voteDelegate); assertEq(engine.urnFarms(urn), address(farm)); vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 500 * 10**45); uint256 id1 = _forceLiquidation(urn); - assertEq(engine.urnDelegates(urn), address(0)); + assertEq(engine.urnVoteDelegates(urn), address(0)); assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectDelegate(urn, voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); vm.expectRevert("LockstakeEngine/urn-in-auction"); engine.selectFarm(urn, address(farm), 0); @@ -1387,7 +1387,7 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnAuctions(urn), 2); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectDelegate(urn, voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); vm.expectRevert("LockstakeEngine/urn-in-auction"); engine.selectFarm(urn, address(farm), 0); @@ -1404,7 +1404,7 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnAuctions(urn), 1); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectDelegate(urn, voterDelegate); + engine.selectVoteDelegate(urn, voteDelegate); vm.expectRevert("LockstakeEngine/urn-in-auction"); engine.selectFarm(urn, address(farm), 0); @@ -1418,35 +1418,35 @@ contract LockstakeEngineTest is DssTest { vm.prank(buyer); clip.take(id2, 25_000 * 10**18, type(uint256).max, buyer, ""); assertEq(engine.urnAuctions(urn), 0); - // Can select delegate and farm again - engine.selectDelegate(urn, voterDelegate); + // Can select voteDelegate and farm again + engine.selectVoteDelegate(urn, voteDelegate); engine.selectFarm(urn, address(farm), 0); } function testUrnUnsafe() public { address urn = _urnSetUp(true, true); - assertEq(engine.urnDelegates(urn), voterDelegate); + assertEq(engine.urnVoteDelegates(urn), voteDelegate); - address voterDelegate2 = delFactory.create(); + address voteDelegate2 = voteDelegateFactory.create(); vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(0.05 * 10**18))); // Force urn unsafe dss.spotter.poke(ilk); vm.expectRevert("LockstakeEngine/urn-unsafe"); - engine.selectDelegate(urn, voterDelegate2); + engine.selectVoteDelegate(urn, voteDelegate2); - engine.selectDelegate(urn, address(0)); + engine.selectVoteDelegate(urn, address(0)); vm.expectRevert("LockstakeEngine/urn-unsafe"); - engine.selectDelegate(urn, voterDelegate2); + engine.selectVoteDelegate(urn, voteDelegate2); vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); // Back to safety dss.spotter.poke(ilk); - engine.selectDelegate(urn, voterDelegate2); + engine.selectVoteDelegate(urn, voteDelegate2); - assertEq(engine.urnDelegates(urn), voterDelegate2); + assertEq(engine.urnVoteDelegates(urn), voteDelegate2); } function testOnRemoveOverflow() public { diff --git a/test/mocks/DelegateMock.sol b/test/mocks/VoteDelegateMock.sol similarity index 70% rename from test/mocks/DelegateMock.sol rename to test/mocks/VoteDelegateMock.sol index 44dfb954..8926f27e 100644 --- a/test/mocks/DelegateMock.sol +++ b/test/mocks/VoteDelegateMock.sol @@ -7,22 +7,21 @@ interface GemLike { function transferFrom(address, address, uint256) external; } -contract DelegateFactoryMock { - mapping(address => uint256) public isDelegate; +contract VoteDelegateFactoryMock { + mapping(address => uint256) public created; address immutable private gov; constructor(address _gov) { gov = _gov; } - function create() external returns (address delegate) { - delegate = address(new DelegateMock(gov)); - require(delegate != address(0), "DelegateFactory/creation-failed"); - isDelegate[delegate] = 1; + function create() external returns (address voteDelegate) { + voteDelegate = address(new VoteDelegateMock(gov)); + created[voteDelegate] = 1; } } -contract DelegateMock { +contract VoteDelegateMock { mapping(address => uint256) public stake; GemLike immutable public gov; From 86ea4a8d9c2d071fd5a0ead5195670d434c23c47 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 19 Mar 2024 08:32:55 -0300 Subject: [PATCH 062/111] Add LockstakeMkr (#36) * Add LockstakeMkr * Update test/LockstakeEngine.t.sol Co-authored-by: telome <130504305+telome@users.noreply.github.com> * Remove spacing * Update test/LockstakeMkr.t.sol Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --------- Co-authored-by: telome <130504305+telome@users.noreply.github.com> Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- .gitmodules | 3 + deploy/LockstakeDeploy.sol | 6 +- deploy/LockstakeInit.sol | 12 ++- deploy/LockstakeInstance.sol | 1 + lib/token-tests | 1 + src/LockstakeEngine.sol | 16 ++-- src/LockstakeMkr.sol | 146 ++++++++++++++++++++++++++++++++ src/LockstakeUrn.sol | 10 +-- test/LockstakeEngine.t.sol | 157 +++++++++++++++++------------------ test/LockstakeMkr.t.sol | 18 ++++ 10 files changed, 273 insertions(+), 97 deletions(-) create mode 160000 lib/token-tests create mode 100644 src/LockstakeMkr.sol create mode 100644 test/LockstakeMkr.t.sol diff --git a/.gitmodules b/.gitmodules index a2df3f1f..e155df29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/dss-test"] path = lib/dss-test url = https://github.com/makerdao/dss-test +[submodule "lib/token-tests"] + path = lib/token-tests + url = https://github.com/makerdao/token-tests/ diff --git a/deploy/LockstakeDeploy.sol b/deploy/LockstakeDeploy.sol index 87170a13..2a261a52 100644 --- a/deploy/LockstakeDeploy.sol +++ b/deploy/LockstakeDeploy.sol @@ -19,6 +19,7 @@ pragma solidity ^0.8.16; import { ScriptTools } from "dss-test/ScriptTools.sol"; import { MCD, DssInstance } from "dss-test/MCD.sol"; import { LockstakeInstance } from "./LockstakeInstance.sol"; +import { LockstakeMkr } from "src/LockstakeMkr.sol"; import { LockstakeEngine } from "src/LockstakeEngine.sol"; import { LockstakeClipper } from "src/LockstakeClipper.sol"; @@ -31,19 +32,20 @@ library LockstakeDeploy { address voteDelegateFactory, address nstJoin, bytes32 ilk, - address stkMkr, uint256 fee, address mkrNgt, bytes4 calcSig ) internal returns (LockstakeInstance memory lockstakeInstance) { DssInstance memory dss = MCD.loadFromChainlog(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); - lockstakeInstance.engine = address(new LockstakeEngine(voteDelegateFactory, nstJoin, ilk, mkrNgt, stkMkr, fee)); + lockstakeInstance.lsmkr = address(new LockstakeMkr()); + lockstakeInstance.engine = address(new LockstakeEngine(voteDelegateFactory, nstJoin, ilk, mkrNgt, lockstakeInstance.lsmkr, fee)); lockstakeInstance.clipper = address(new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), lockstakeInstance.engine)); (bool ok, bytes memory returnV) = dss.chainlog.getAddress("CALC_FAB").call(abi.encodeWithSelector(calcSig, owner)); require(ok); lockstakeInstance.clipperCalc = abi.decode(returnV, (address)); + ScriptTools.switchOwner(lockstakeInstance.lsmkr, deployer, owner); ScriptTools.switchOwner(lockstakeInstance.engine, deployer, owner); ScriptTools.switchOwner(lockstakeInstance.clipper, deployer, owner); } diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index 9658f60e..e03a86fd 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -19,6 +19,10 @@ pragma solidity >=0.8.0; import { DssInstance } from "dss-test/MCD.sol"; import { LockstakeInstance } from "./LockstakeInstance.sol"; +interface LockstakeMkrLike { + function rely(address) external; +} + interface LockstakeEngineLike { function voteDelegateFactory() external view returns (address); function vat() external view returns (address); @@ -26,7 +30,7 @@ interface LockstakeEngineLike { function nst() external view returns (address); function ilk() external view returns (bytes32); function mkr() external view returns (address); - function stkMkr() external view returns (address); + function lsmkr() external view returns (address); function fee() external view returns (uint256); function mkrNgt() external view returns (address); function ngt() external view returns (address); @@ -87,7 +91,6 @@ struct LockstakeConfig { address nstJoin; address nst; address mkr; - address stkMkr; address mkrNgt; address ngt; address[] farms; @@ -136,7 +139,7 @@ library LockstakeInit { require(engine.nst() == cfg.nst, "Engine nst mismatch"); require(engine.ilk() == cfg.ilk, "Engine ilk mismatch"); require(engine.mkr() == cfg.mkr, "Engine mkr mismatch"); - require(engine.stkMkr() == cfg.stkMkr, "Engine stkMkr mismatch"); + require(engine.lsmkr() == lockstakeInstance.lsmkr, "Engine lsmkr mismatch"); require(engine.fee() == cfg.fee, "Engine fee mismatch"); require(engine.mkrNgt() == cfg.mkrNgt, "Engine mkrNgt mismatch"); require(engine.ngt() == cfg.ngt, "Engine ngt mismatch"); @@ -185,6 +188,8 @@ library LockstakeInit { dss.dog.file(cfg.ilk, "hole", cfg.hole); dss.dog.rely(address(clipper)); + LockstakeMkrLike(lockstakeInstance.lsmkr).rely(address(engine)); + engine.file("jug", address(dss.jug)); for (uint256 i = 0; i < cfg.farms.length; i++) { engine.addFarm(cfg.farms[i]); @@ -228,6 +233,7 @@ library LockstakeInit { cfg.symbol ); + dss.chainlog.setAddress("LOCKSTAKE_MKR", lockstakeInstance.lsmkr); dss.chainlog.setAddress("LOCKSTAKE_ENGINE", address(engine)); dss.chainlog.setAddress("LOCKSTAKE_CLIP", address(clipper)); dss.chainlog.setAddress("LOCKSTAKE_CLIP_CALC", address(calc)); diff --git a/deploy/LockstakeInstance.sol b/deploy/LockstakeInstance.sol index 91252543..d249711d 100644 --- a/deploy/LockstakeInstance.sol +++ b/deploy/LockstakeInstance.sol @@ -17,6 +17,7 @@ pragma solidity >=0.8.0; struct LockstakeInstance { + address lsmkr; address engine; address clipper; address clipperCalc; diff --git a/lib/token-tests b/lib/token-tests new file mode 160000 index 00000000..51cf4dba --- /dev/null +++ b/lib/token-tests @@ -0,0 +1 @@ +Subproject commit 51cf4dbaa3e468827263dcb099ba7974c31941bb diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index f233ef38..56bac366 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -91,7 +91,7 @@ contract LockstakeEngine is Multicall { GemLike immutable public nst; bytes32 immutable public ilk; GemLike immutable public mkr; - GemLike immutable public stkMkr; + GemLike immutable public lsmkr; uint256 immutable public fee; MkrNgtLike immutable public mkrNgt; GemLike immutable public ngt; @@ -136,7 +136,7 @@ contract LockstakeEngine is Multicall { // --- constructor --- - constructor(address voteDelegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address stkMkr_, uint256 fee_) { + constructor(address voteDelegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address lsmkr_, uint256 fee_) { require(fee_ < WAD, "LockstakeEngine/fee-equal-or-greater-wad"); voteDelegateFactory = VoteDelegateFactoryLike(voteDelegateFactory_); nstJoin = NstJoinLike(nstJoin_); @@ -147,9 +147,9 @@ contract LockstakeEngine is Multicall { mkr = GemLike(mkrNgt.mkr()); ngt = GemLike(mkrNgt.ngt()); mkrNgtRate = mkrNgt.rate(); - stkMkr = GemLike(stkMkr_); + lsmkr = GemLike(lsmkr_); fee = fee_; - urnImplementation = address(new LockstakeUrn(address(vat), stkMkr_)); + urnImplementation = address(new LockstakeUrn(address(vat), lsmkr_)); vat.hope(nstJoin_); nst.approve(nstJoin_, type(uint256).max); ngt.approve(address(mkrNgt), type(uint256).max); @@ -326,7 +326,7 @@ contract LockstakeEngine is Multicall { } vat.slip(ilk, urn, int256(wad)); vat.frob(ilk, urn, urn, address(0), int256(wad), 0); - stkMkr.mint(urn, wad); + lsmkr.mint(urn, wad); address urnFarm = urnFarms[urn]; if (urnFarm != address(0)) { require(farms[urnFarm] == FarmStatus.ACTIVE, "LockstakeEngine/farm-deleted"); @@ -359,7 +359,7 @@ contract LockstakeEngine is Multicall { if (urnFarm != address(0)) { LockstakeUrn(urn).withdraw(urnFarm, wad); } - stkMkr.burn(urn, wad); + lsmkr.burn(urn, wad); vat.frob(ilk, urn, urn, address(0), -int256(wad), 0); vat.slip(ilk, urn, -int256(wad)); address voteDelegate = urnVoteDelegates[urn]; @@ -421,7 +421,7 @@ contract LockstakeEngine is Multicall { uint256 inkBeforeKick = ink + wad; _selectVoteDelegate(urn, inkBeforeKick, urnVoteDelegates[urn], address(0)); _selectFarm(urn, inkBeforeKick, urnFarms[urn], address(0), 0); - stkMkr.burn(urn, wad); + lsmkr.burn(urn, wad); urnAuctions[urn]++; emit OnKick(urn, wad); } @@ -443,7 +443,7 @@ contract LockstakeEngine is Multicall { require(refund <= uint256(type(int256).max), "LockstakeEngine/refund-over-maxint"); vat.slip(ilk, urn, int256(refund)); vat.frob(ilk, urn, urn, address(0), int256(refund), 0); - stkMkr.mint(urn, refund); + lsmkr.mint(urn, refund); } } urnAuctions[urn]--; diff --git a/src/LockstakeMkr.sol b/src/LockstakeMkr.sol new file mode 100644 index 00000000..3bee6b46 --- /dev/null +++ b/src/LockstakeMkr.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/// LockstakeMkr.sol -- LockstakeMkr token + +// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico +// Copyright (C) 2023 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.16; + +contract LockstakeMkr { + mapping (address => uint256) public wards; + + // --- ERC20 Data --- + string public constant name = "LockstakeMkr"; + string public constant symbol = "LSMKR"; + string public constant version = "1"; + uint8 public constant decimals = 18; + uint256 public totalSupply; + + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + // --- Events --- + event Rely(address indexed usr); + event Deny(address indexed usr); + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + modifier auth { + require(wards[msg.sender] == 1, "LockstakeMkr/not-authorized"); + _; + } + + constructor() { + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Administration --- + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + // --- ERC20 Mutations --- + function transfer(address to, uint256 value) external returns (bool) { + require(to != address(0) && to != address(this), "LockstakeMkr/invalid-address"); + uint256 balance = balanceOf[msg.sender]; + require(balance >= value, "LockstakeMkr/insufficient-balance"); + + unchecked { + balanceOf[msg.sender] = balance - value; + balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply + } + + emit Transfer(msg.sender, to, value); + + return true; + } + + function transferFrom(address from, address to, uint256 value) external returns (bool) { + require(to != address(0) && to != address(this), "LockstakeMkr/invalid-address"); + uint256 balance = balanceOf[from]; + require(balance >= value, "LockstakeMkr/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "LockstakeMkr/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; + balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply + } + + emit Transfer(from, to, value); + + return true; + } + + function approve(address spender, uint256 value) external returns (bool) { + allowance[msg.sender][spender] = value; + + emit Approval(msg.sender, spender, value); + + return true; + } + + // --- Mint/Burn --- + function mint(address to, uint256 value) external auth { + require(to != address(0) && to != address(this), "LockstakeMkr/invalid-address"); + unchecked { + balanceOf[to] = balanceOf[to] + value; // note: we don't need an overflow check here b/c balanceOf[to] <= totalSupply and there is an overflow check below + } + totalSupply = totalSupply + value; + + emit Transfer(address(0), to, value); + } + + function burn(address from, uint256 value) external { + uint256 balance = balanceOf[from]; + require(balance >= value, "LockstakeMkr/insufficient-balance"); + + if (from != msg.sender) { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + require(allowed >= value, "LockstakeMkr/insufficient-allowance"); + + unchecked { + allowance[from][msg.sender] = allowed - value; + } + } + } + + unchecked { + balanceOf[from] = balance - value; // note: we don't need overflow checks b/c require(balance >= value) and balance <= totalSupply + totalSupply = totalSupply - value; + } + + emit Transfer(from, address(0), value); + } +} diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index 9a09117b..ab8c6ffe 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -37,7 +37,7 @@ contract LockstakeUrn { // --- immutables --- address immutable public engine; - GemLike immutable public stkMkr; + GemLike immutable public lsmkr; VatLike immutable public vat; // --- modifiers --- @@ -49,21 +49,21 @@ contract LockstakeUrn { // --- constructor & init --- - constructor(address vat_, address stkMkr_) { + constructor(address vat_, address lsmkr_) { engine = msg.sender; vat = VatLike(vat_); - stkMkr = GemLike(stkMkr_); + lsmkr = GemLike(lsmkr_); } function init() external isEngine { vat.hope(msg.sender); - stkMkr.approve(msg.sender, type(uint256).max); + lsmkr.approve(msg.sender, type(uint256).max); } // --- staking functions --- function stake(address farm, uint256 wad, uint16 ref) external isEngine { - stkMkr.approve(farm, wad); + lsmkr.approve(farm, wad); StakingRewardsLike(farm).stake(wad, ref); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index c996d48a..b16ec07b 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.16; import "dss-test/DssTest.sol"; import { LockstakeDeploy } from "deploy/LockstakeDeploy.sol"; import { LockstakeInit, LockstakeConfig, LockstakeInstance } from "deploy/LockstakeInit.sol"; +import { LockstakeMkr } from "src/LockstakeMkr.sol"; import { LockstakeEngine } from "src/LockstakeEngine.sol"; import { LockstakeClipper } from "src/LockstakeClipper.sol"; import { LockstakeUrn } from "src/LockstakeUrn.sol"; @@ -30,6 +31,7 @@ contract LockstakeEngineTest is DssTest { DssInstance dss; address pauseProxy; GemMock mkr; + LockstakeMkr lsmkr; LockstakeEngine engine; LockstakeClipper clip; address calc; @@ -37,7 +39,6 @@ contract LockstakeEngineTest is DssTest { VoteDelegateFactoryMock voteDelegateFactory; NstMock nst; NstJoinMock nstJoin; - GemMock stkMkr; GemMock rTok; StakingRewardsMock farm; MkrNgtMock mkrNgt; @@ -87,9 +88,7 @@ contract LockstakeEngineTest is DssTest { mkr = new GemMock(0); nst = new NstMock(); nstJoin = new NstJoinMock(address(dss.vat), address(nst)); - stkMkr = new GemMock(0); rTok = new GemMock(0); - farm = new StakingRewardsMock(address(rTok), address(stkMkr)); ngt = new GemMock(0); mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 24_000); @@ -106,7 +105,6 @@ contract LockstakeEngineTest is DssTest { address(voteDelegateFactory), address(nstJoin), ilk, - address(stkMkr), 15 * WAD / 100, address(mkrNgt), bytes4(abi.encodeWithSignature("newLinearDecrease(address)")) @@ -115,6 +113,8 @@ contract LockstakeEngineTest is DssTest { engine = LockstakeEngine(instance.engine); clip = LockstakeClipper(instance.clipper); calc = instance.clipperCalc; + lsmkr = LockstakeMkr(instance.lsmkr); + farm = new StakingRewardsMock(address(rTok), address(lsmkr)); address[] memory farms = new address[](2); farms[0] = address(farm); @@ -126,7 +126,6 @@ contract LockstakeEngineTest is DssTest { nstJoin: address(nstJoin), nst: address(nstJoin.nst()), mkr: address(mkr), - stkMkr: address(stkMkr), mkrNgt: address(mkrNgt), ngt: address(ngt), farms: farms, @@ -226,14 +225,13 @@ contract LockstakeEngineTest is DssTest { assertEq(address(engine.nst()), address(nst)); assertEq(engine.ilk(), ilk); assertEq(address(engine.mkr()), address(mkr)); - assertEq(address(engine.stkMkr()), address(stkMkr)); assertEq(engine.fee(), 15 * WAD / 100); assertEq(address(engine.mkrNgt()), address(mkrNgt)); assertEq(address(engine.ngt()), address(ngt)); assertEq(engine.mkrNgtRate(), 24_000); assertEq(LockstakeUrn(engine.urnImplementation()).engine(), address(engine)); assertEq(address(LockstakeUrn(engine.urnImplementation()).vat()), address(dss.vat)); - assertEq(address(LockstakeUrn(engine.urnImplementation()).stkMkr()), address(stkMkr)); + assertEq(address(LockstakeUrn(engine.urnImplementation()).lsmkr()), address(lsmkr)); assertEq(clip.ilk(), ilk); assertEq(address(clip.vat()), address(dss.vat)); @@ -302,6 +300,7 @@ contract LockstakeEngineTest is DssTest { assertEq(join, address(0)); assertEq(xlip, address(clip)); + assertEq(dss.chainlog.getAddress("LOCKSTAKE_MKR"), address(lsmkr)); assertEq(dss.chainlog.getAddress("LOCKSTAKE_ENGINE"), address(engine)); assertEq(dss.chainlog.getAddress("LOCKSTAKE_CLIP"), address(clip)); assertEq(dss.chainlog.getAddress("LOCKSTAKE_CLIP_CALC"), address(calc)); @@ -312,7 +311,6 @@ contract LockstakeEngineTest is DssTest { address(voteDelegateFactory), address(nstJoin), "eee", - address(stkMkr), 15 * WAD / 100, address(mkrNgt), bytes4(abi.encodeWithSignature("newStairstepExponentialDecrease(address)")) @@ -329,25 +327,26 @@ contract LockstakeEngineTest is DssTest { } function testConstructor() public { + address lsmkr2 = address(new GemMock(0)); vm.expectRevert("LockstakeEngine/fee-equal-or-greater-wad"); - new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), WAD); + new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), lsmkr2, WAD); vm.expectEmit(true, true, true, true); emit Rely(address(this)); - LockstakeEngine e = new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), address(stkMkr), 100); + LockstakeEngine e = new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), lsmkr2, 100); assertEq(address(e.voteDelegateFactory()), address(voteDelegateFactory)); assertEq(address(e.nstJoin()), address(nstJoin)); assertEq(address(e.vat()), address(dss.vat)); assertEq(address(e.nst()), address(nst)); assertEq(e.ilk(), "aaa"); assertEq(address(e.mkr()), address(mkr)); - assertEq(address(e.stkMkr()), address(stkMkr)); + assertEq(address(e.lsmkr()), lsmkr2); assertEq(e.fee(), 100); assertEq(address(e.mkrNgt()), address(mkrNgt)); assertEq(address(e.ngt()), address(ngt)); assertEq(e.mkrNgtRate(), 24_000); assertEq(LockstakeUrn(e.urnImplementation()).engine(), address(e)); assertEq(address(LockstakeUrn(e.urnImplementation()).vat()), address(dss.vat)); - assertEq(address(LockstakeUrn(e.urnImplementation()).stkMkr()), address(stkMkr)); + assertEq(address(LockstakeUrn(e.urnImplementation()).lsmkr()), lsmkr2); assertEq(dss.vat.can(address(e), address(nstJoin)), 1); assertEq(nst.allowance(address(e), address(nstJoin)), type(uint256).max); assertEq(ngt.allowance(address(e), address(mkrNgt)), type(uint256).max); @@ -423,15 +422,15 @@ contract LockstakeEngineTest is DssTest { engine.open(1); assertEq(dss.vat.can(urn, address(engine)), 0); - assertEq(stkMkr.allowance(urn, address(engine)), 0); + assertEq(lsmkr.allowance(urn, address(engine)), 0); vm.expectEmit(true, true, true, true); emit Open(address(this), 0, urn); assertEq(engine.open(0), urn); assertEq(engine.usrAmts(address(this)), 1); assertEq(dss.vat.can(urn, address(engine)), 1); - assertEq(stkMkr.allowance(urn, address(engine)), type(uint256).max); + assertEq(lsmkr.allowance(urn, address(engine)), type(uint256).max); assertEq(LockstakeUrn(urn).engine(), address(engine)); - assertEq(address(LockstakeUrn(urn).stkMkr()), address(stkMkr)); + assertEq(address(LockstakeUrn(urn).lsmkr()), address(lsmkr)); assertEq(address(LockstakeUrn(urn).vat()), address(dss.vat)); vm.expectRevert("LockstakeUrn/not-engine"); LockstakeUrn(urn).init(); @@ -538,7 +537,7 @@ contract LockstakeEngineTest is DssTest { } function testSelectFarm() public { - StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(stkMkr)); + StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(lsmkr)); address urn = engine.open(0); assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/farm-unsupported-or-deleted"); @@ -550,17 +549,17 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnFarms(urn), address(farm2)); vm.expectRevert("LockstakeEngine/same-farm"); engine.selectFarm(urn, address(farm2), 5); - assertEq(stkMkr.balanceOf(address(farm)), 0); - assertEq(stkMkr.balanceOf(address(farm2)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm2)), 0); mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18, 5); - assertEq(stkMkr.balanceOf(address(farm)), 0); - assertEq(stkMkr.balanceOf(address(farm2)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm2)), 100_000 * 10**18); assertEq(farm.balanceOf(urn), 0); assertEq(farm2.balanceOf(urn), 100_000 * 10**18); engine.selectFarm(urn, address(farm), 5); - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm2)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm2)), 0); assertEq(farm.balanceOf(urn), 100_000 * 10**18); assertEq(farm2.balanceOf(urn), 0); vm.prank(pauseProxy); engine.delFarm(address(farm2)); @@ -586,17 +585,17 @@ contract LockstakeEngineTest is DssTest { engine.selectFarm(urn, address(farm), 0); } assertEq(_ink(ilk, urn), 0); - assertEq(stkMkr.balanceOf(urn), 0); + assertEq(lsmkr.balanceOf(urn), 0); mkr.approve(address(engine), 100_000 * 10**18); vm.expectEmit(true, true, true, true); emit Lock(urn, 100_000 * 10**18, 5); engine.lock(urn, 100_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); assertEq(farm.balanceOf(urn), 100_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 100_000 * 10**18); } assertEq(mkr.balanceOf(address(this)), 0); if (withDelegate) { @@ -611,10 +610,10 @@ contract LockstakeEngineTest is DssTest { engine.free(urn, address(this), 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); assertEq(farm.balanceOf(urn), 60_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 60_000 * 10**18); } assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18 - 40_000 * 10**18 * 15 / 100); if (withDelegate) { @@ -628,10 +627,10 @@ contract LockstakeEngineTest is DssTest { engine.free(urn, address(123), 10_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 50_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); assertEq(farm.balanceOf(urn), 50_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 50_000 * 10**18); } assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18 - 10_000 * 10**18 * 15 / 100); if (withDelegate) { @@ -676,17 +675,17 @@ contract LockstakeEngineTest is DssTest { engine.selectFarm(urn, address(farm), 0); } assertEq(_ink(ilk, urn), 0); - assertEq(stkMkr.balanceOf(urn), 0); + assertEq(lsmkr.balanceOf(urn), 0); ngt.approve(address(engine), 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); emit LockNgt(urn, 100_000 * 24_000 * 10**18, 5); engine.lockNgt(urn, 100_000 * 24_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); assertEq(farm.balanceOf(urn), 100_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 100_000 * 10**18); } assertEq(ngt.balanceOf(address(this)), 0); if (withDelegate) { @@ -701,10 +700,10 @@ contract LockstakeEngineTest is DssTest { engine.freeNgt(urn, address(this), 40_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); assertEq(farm.balanceOf(urn), 60_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 60_000 * 10**18); } assertEq(ngt.balanceOf(address(this)), 40_000 * 24_000 * 10**18 - 40_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { @@ -718,10 +717,10 @@ contract LockstakeEngineTest is DssTest { engine.freeNgt(urn, address(123), 10_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 50_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); assertEq(farm.balanceOf(urn), 50_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 50_000 * 10**18); } assertEq(ngt.balanceOf(address(123)), 10_000 * 24_000 * 10**18 - 10_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { @@ -772,10 +771,10 @@ contract LockstakeEngineTest is DssTest { engine.lock(urn, 100_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); assertEq(farm.balanceOf(urn), 100_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 100_000 * 10**18); } assertEq(mkr.balanceOf(address(this)), 0); if (withDelegate) { @@ -790,10 +789,10 @@ contract LockstakeEngineTest is DssTest { engine.freeNoFee(urn, address(this), 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 60_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); assertEq(farm.balanceOf(urn), 60_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 60_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 60_000 * 10**18); } assertEq(mkr.balanceOf(address(this)), 40_000 * 10**18); if (withDelegate) { @@ -807,10 +806,10 @@ contract LockstakeEngineTest is DssTest { engine.freeNoFee(urn, address(123), 10_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 50_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); assertEq(farm.balanceOf(urn), 50_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(urn), 50_000 * 10**18); + assertEq(lsmkr.balanceOf(urn), 50_000 * 10**18); } assertEq(mkr.balanceOf(address(123)), 10_000 * 10**18); if (withDelegate) { @@ -908,7 +907,7 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.usrAmts(address(this)), 0); assertEq(_ink(ilk, urn), 0); assertEq(farm.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); vm.expectEmit(true, true, true, true); emit Open(address(this), 0 , urn); @@ -925,7 +924,7 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.usrAmts(address(this)), 1); assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); } function testGetReward() public { @@ -967,11 +966,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } if (withStaking) { - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.balanceOf(address(farm)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); } else { - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 100_000 * 10**18); } } @@ -991,7 +990,7 @@ contract LockstakeEngineTest is DssTest { function _testOnKickFull(bool withDelegate, bool withStaking) internal { address urn = _urnSetUp(withDelegate, withStaking); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); + uint256 lsmkrInitialSupply = lsmkr.totalSupply(); uint256 id = _forceLiquidation(urn); LockstakeClipper.Sale memory sale; @@ -1014,11 +1013,11 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 100_000 * 10**18); } function testOnKickFullNoStakingNoDelegate() public { @@ -1039,7 +1038,7 @@ contract LockstakeEngineTest is DssTest { function _testOnKickPartial(bool withDelegate, bool withStaking) internal { address urn = _urnSetUp(withDelegate, withStaking); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); + uint256 lsmkrInitialSupply = lsmkr.totalSupply(); vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 500 * 10**45); uint256 id = _forceLiquidation(urn); @@ -1063,11 +1062,11 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 75_000 * 10**18); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 25_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 75_000 * 10**18); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 25_000 * 10**18); } function testOnKickPartialNoStakingNoDelegate() public { @@ -1089,7 +1088,7 @@ contract LockstakeEngineTest is DssTest { function _testOnTake(bool withDelegate, bool withStaking) internal { address urn = _urnSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); + uint256 lsmkrInitialSupply = lsmkr.totalSupply(); address vow = address(dss.vow); uint256 vowInitialBalance = dss.vat.dai(vow); uint256 id = _forceLiquidation(urn); @@ -1113,11 +1112,11 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 100_000 * 10**18); address buyer = address(888); vm.prank(pauseProxy); dss.vat.suck(address(0), buyer, 2_000 * 10**45); @@ -1146,11 +1145,11 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 80_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 100_000 * 10**18); uint256 burn = 32_000 * 10**18 * engine.fee() / (WAD - engine.fee()); vm.expectEmit(true, true, true, true); @@ -1178,11 +1177,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18 - 32_000 * 10**18 - burn); assertEq(mkr.totalSupply(), mkrInitialSupply - burn); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 100_000 * 10**18 - 32_000 * 10**18 - burn); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 32_000 * 10**18 - burn); + assertEq(lsmkr.balanceOf(address(urn)), 100_000 * 10**18 - 32_000 * 10**18 - burn); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 32_000 * 10**18 - burn); assertEq(dss.vat.dai(vow), vowInitialBalance + 2_000 * 10**45); } @@ -1205,7 +1204,7 @@ contract LockstakeEngineTest is DssTest { function _testOnTakePartialBurn(bool withDelegate, bool withStaking) internal { address urn = _urnSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); + uint256 lsmkrInitialSupply = lsmkr.totalSupply(); address vow = address(dss.vow); uint256 vowInitialBalance = dss.vat.dai(vow); uint256 id = _forceLiquidation(urn); @@ -1229,11 +1228,11 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 65); // Time passes to let the auction price to crash @@ -1259,11 +1258,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply - (100_000 * 10**18 - 91428571428571428571428)); // Can't burn 15% of 91428571428571428571428 if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 100_000 * 10**18); assertEq(dss.vat.dai(vow), vowInitialBalance + 2_000 * 10**45); } @@ -1286,7 +1285,7 @@ contract LockstakeEngineTest is DssTest { function _testOnTakeNoBurn(bool withDelegate, bool withStaking) internal { address urn = _urnSetUp(withDelegate, withStaking); uint256 mkrInitialSupply = mkr.totalSupply(); - uint256 stkMkrInitialSupply = stkMkr.totalSupply(); + uint256 lsmkrInitialSupply = lsmkr.totalSupply(); address vow = address(dss.vow); uint256 vowInitialBalance = dss.vat.dai(vow); uint256 id = _forceLiquidation(urn); @@ -1310,11 +1309,11 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 100_000 * 10**18); vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash @@ -1340,11 +1339,11 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.totalSupply(), mkrInitialSupply); // Can't burn anything if (withStaking) { - assertEq(stkMkr.balanceOf(address(farm)), 0); + assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(farm.balanceOf(address(urn)), 0); } - assertEq(stkMkr.balanceOf(address(urn)), 0); - assertEq(stkMkr.totalSupply(), stkMkrInitialSupply - 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(urn)), 0); + assertEq(lsmkr.totalSupply(), lsmkrInitialSupply - 100_000 * 10**18); assertLt(dss.vat.dai(vow), vowInitialBalance + 2_000 * 10**45); // Doesn't recover full debt } diff --git a/test/LockstakeMkr.t.sol b/test/LockstakeMkr.t.sol new file mode 100644 index 00000000..78727961 --- /dev/null +++ b/test/LockstakeMkr.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +import "token-tests/TokenChecks.sol"; +import { LockstakeMkr } from "src/LockstakeMkr.sol"; + +contract LockstakeMkrTest is TokenChecks { + address internal lockstakeMkr = address(new LockstakeMkr()); + + function testBulkMintBurn() public { + checkBulkMintBurn(lockstakeMkr, "LockstakeMkr"); + } + + function testBulkERC20() public { + checkBulkERC20(lockstakeMkr, "LockstakeMkr", "LockstakeMkr", "LSMKR", "1", 18); + } +} From 675c75095a32a4f1a1016dbfa3b64c3d9a053c7a Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Thu, 4 Apr 2024 08:57:20 -0300 Subject: [PATCH 063/111] Minor text changes Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> --- src/LockstakeClipper.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index 686c91e9..07869ea8 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -53,7 +53,7 @@ interface LockstakeEngineLike { function onRemove(address, uint256, uint256) external; } -// Clipper for use with the manager / proxy paradigm +// Clipper for use with the Lockstake Engine contract LockstakeClipper { // --- Auth --- mapping (address => uint256) public wards; @@ -88,7 +88,7 @@ contract LockstakeClipper { uint256 pos; // Index in active array uint256 tab; // Dai to raise [rad] uint256 lot; // collateral to sell [wad] - uint256 tot; // static registry of tot collateral to sell [wad] + uint256 tot; // static registry of total collateral to sell [wad] address usr; // Liquidated CDP uint96 tic; // Auction start time uint256 top; // Starting price [ray] @@ -264,7 +264,7 @@ contract LockstakeClipper { vat.suck(vow, kpr, coin); } - // Trigger proxy manager liquidation call-back + // Trigger engine liquidation call-back engine.onKick(usr, lot); emit Kick(id, top, tab, lot, usr, kpr, coin); From 4e5945cd0fade30804189fa661fd249f056a5521 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 4 Apr 2024 09:00:25 -0300 Subject: [PATCH 064/111] Return address cast in interface --- src/LockstakeEngine.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 56bac366..259f8ecd 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -57,8 +57,8 @@ interface JugLike { interface MkrNgtLike { function rate() external view returns (uint256); - function mkr() external view returns (address); - function ngt() external view returns (address); + function mkr() external view returns (GemLike); + function ngt() external view returns (GemLike); function ngtToMkr(address, uint256) external; function mkrToNgt(address, uint256) external; } @@ -144,8 +144,8 @@ contract LockstakeEngine is Multicall { nst = nstJoin.nst(); ilk = ilk_; mkrNgt = MkrNgtLike(mkrNgt_); - mkr = GemLike(mkrNgt.mkr()); - ngt = GemLike(mkrNgt.ngt()); + mkr = mkrNgt.mkr(); + ngt = mkrNgt.ngt(); mkrNgtRate = mkrNgt.rate(); lsmkr = GemLike(lsmkr_); fee = fee_; From 8de47d82cbe403f17f209ec290dd6a4a0635926d Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 4 Apr 2024 09:03:31 -0300 Subject: [PATCH 065/111] LSMKR => lsMKR --- src/LockstakeMkr.sol | 2 +- test/LockstakeMkr.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LockstakeMkr.sol b/src/LockstakeMkr.sol index 3bee6b46..28dac4a1 100644 --- a/src/LockstakeMkr.sol +++ b/src/LockstakeMkr.sol @@ -25,7 +25,7 @@ contract LockstakeMkr { // --- ERC20 Data --- string public constant name = "LockstakeMkr"; - string public constant symbol = "LSMKR"; + string public constant symbol = "lsMKR"; string public constant version = "1"; uint8 public constant decimals = 18; uint256 public totalSupply; diff --git a/test/LockstakeMkr.t.sol b/test/LockstakeMkr.t.sol index 78727961..5e8fee8c 100644 --- a/test/LockstakeMkr.t.sol +++ b/test/LockstakeMkr.t.sol @@ -13,6 +13,6 @@ contract LockstakeMkrTest is TokenChecks { } function testBulkERC20() public { - checkBulkERC20(lockstakeMkr, "LockstakeMkr", "LockstakeMkr", "LSMKR", "1", 18); + checkBulkERC20(lockstakeMkr, "LockstakeMkr", "LockstakeMkr", "lsMKR", "1", 18); } } From 9ce2160088737b6535290575d7eb8cca8856c576 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 4 Apr 2024 09:29:33 -0300 Subject: [PATCH 066/111] Change error message --- src/LockstakeEngine.sol | 4 ++-- test/LockstakeEngine.t.sol | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 259f8ecd..4f8c0638 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -318,7 +318,7 @@ contract LockstakeEngine is Multicall { } function _lock(address urn, uint256 wad, uint16 ref) internal { - require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); + require(wad <= uint256(type(int256).max), "LockstakeEngine/overflow"); address voteDelegate = urnVoteDelegates[urn]; if (voteDelegate != address(0)) { mkr.approve(voteDelegate, wad); @@ -354,7 +354,7 @@ contract LockstakeEngine is Multicall { } function _free(address urn, uint256 wad, uint256 fee_) internal returns (uint256 freed) { - require(wad <= uint256(type(int256).max), "LockstakeEngine/wad-overflow"); + require(wad <= uint256(type(int256).max), "LockstakeEngine/overflow"); address urnFarm = urnFarms[urn]; if (urnFarm != address(0)) { LockstakeUrn(urn).withdraw(urnFarm, wad); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index b16ec07b..57db6ee6 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -572,11 +572,11 @@ contract LockstakeEngineTest is DssTest { address urn = engine.open(0); deal(address(mkr), address(this), uint256(type(int256).max) + 1); // deal mkr to allow reaching the overflow revert mkr.approve(address(engine), uint256(type(int256).max) + 1); - vm.expectRevert("LockstakeEngine/wad-overflow"); + vm.expectRevert("LockstakeEngine/overflow"); engine.lock(urn, uint256(type(int256).max) + 1, 5); deal(address(mkr), address(this), 100_000 * 10**18); // back to normal mkr balance and allowance mkr.approve(address(engine), 100_000 * 10**18); - vm.expectRevert("LockstakeEngine/wad-overflow"); + vm.expectRevert("LockstakeEngine/overflow"); engine.free(urn, address(this), uint256(type(int256).max) + 1); if (withDelegate) { engine.selectVoteDelegate(urn, voteDelegate); @@ -667,7 +667,7 @@ contract LockstakeEngineTest is DssTest { function _testLockFreeNgt(bool withDelegate, bool withStaking) internal { uint256 initialNgtSupply = ngt.totalSupply(); address urn = engine.open(0); - // Note: wad-overflow cannot be reached for lockNgt and freeNgt as with these functions and the value of rate (>=3) the MKR amount will be always lower + // Note: overflow cannot be reached for lockNgt and freeNgt as with these functions and the value of rate (>=3) the MKR amount will be always lower if (withDelegate) { engine.selectVoteDelegate(urn, voteDelegate); } @@ -760,7 +760,7 @@ contract LockstakeEngineTest is DssTest { address urn = engine.open(0); deal(address(mkr), address(this), 100_000 * 10**18); mkr.approve(address(engine), 100_000 * 10**18); - vm.expectRevert("LockstakeEngine/wad-overflow"); + vm.expectRevert("LockstakeEngine/overflow"); engine.freeNoFee(urn, address(this), uint256(type(int256).max) + 1); if (withDelegate) { engine.selectVoteDelegate(urn, voteDelegate); From 91dddc55ab91fd9b06b555a1d081f1730a891d44 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 4 Apr 2024 09:30:46 -0300 Subject: [PATCH 067/111] Minor change --- test/LockstakeEngine.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 57db6ee6..cacf1048 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.16; import "dss-test/DssTest.sol"; +import "dss-interfaces/Interfaces.sol"; import { LockstakeDeploy } from "deploy/LockstakeDeploy.sol"; import { LockstakeInit, LockstakeConfig, LockstakeInstance } from "deploy/LockstakeInit.sol"; import { LockstakeMkr } from "src/LockstakeMkr.sol"; import { LockstakeEngine } from "src/LockstakeEngine.sol"; import { LockstakeClipper } from "src/LockstakeClipper.sol"; import { LockstakeUrn } from "src/LockstakeUrn.sol"; -import "dss-interfaces/Interfaces.sol"; import { VoteDelegateFactoryMock, VoteDelegateMock } from "test/mocks/VoteDelegateMock.sol"; import { GemMock } from "test/mocks/GemMock.sol"; import { NstMock } from "test/mocks/NstMock.sol"; From cd64098f44e5af5a0b5b1b6458801de114eda39b Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Thu, 4 Apr 2024 13:50:46 -0300 Subject: [PATCH 068/111] Add comment to _divup --- src/LockstakeEngine.sol | 1 + test/LockstakeEngine.t.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 4f8c0638..fb91f629 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -166,6 +166,7 @@ contract LockstakeEngine is Multicall { } function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { + // Note: _divup(0,0) will return 0 differing from natural solidity division unchecked { z = x != 0 ? ((x - 1) / y) + 1 : 0; } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index cacf1048..f7f99f61 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -73,6 +73,7 @@ contract LockstakeEngineTest is DssTest { event OnRemove(address indexed urn, uint256 sold, uint256 burn, uint256 refund); function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { + // Note: _divup(0,0) will return 0 differing from natural solidity division unchecked { z = x != 0 ? ((x - 1) / y) + 1 : 0; } From 5cc4fa34b053bd0c470c3dcd2eebb920d25161fc Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Fri, 5 Apr 2024 11:29:39 -0300 Subject: [PATCH 069/111] Comment changes Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- src/LockstakeClipper.sol | 2 +- src/LockstakeEngine.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index 07869ea8..ee04079e 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -206,7 +206,7 @@ contract LockstakeClipper { // --- Auction --- - // get the price directly from the OSM + // get the price directly from the pip // Could get this from rmul(Vat.ilks(ilk).spot, Spotter.mat()) instead, but // if mat has changed since the last poke, the resulting value will be // incorrect. diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index fb91f629..88fc115e 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -371,7 +371,7 @@ contract LockstakeEngine is Multicall { if (burn > 0) { mkr.burn(address(this), burn); } - unchecked { freed = wad - burn; } // burn <= WAD always + unchecked { freed = wad - burn; } // burn <= wad always } // --- loan functions --- From e5a44bf3b6e99bbaa19321c68f951a2dea765382 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 8 Apr 2024 08:30:19 -0300 Subject: [PATCH 070/111] Minor changes to free and freeNgt --- src/LockstakeEngine.sol | 15 ++++++++------- test/LockstakeEngine.t.sol | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 88fc115e..d9ce669f 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -112,8 +112,8 @@ contract LockstakeEngine is Multicall { event SelectFarm(address indexed urn, address farm, uint16 ref); event Lock(address indexed urn, uint256 wad, uint16 ref); event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); - event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); - event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); + event Free(address indexed urn, address indexed to, uint256 wad, uint256 freed); + event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 ngtFreed); event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); @@ -335,17 +335,18 @@ contract LockstakeEngine is Multicall { } } - function free(address urn, address to, uint256 wad) external urnAuth(urn) { - uint256 freed = _free(urn, wad, fee); + function free(address urn, address to, uint256 wad) external urnAuth(urn) returns (uint256 freed) { + freed = _free(urn, wad, fee); mkr.transfer(to, freed); - emit Free(urn, to, wad, wad - freed); + emit Free(urn, to, wad, freed); } - function freeNgt(address urn, address to, uint256 ngtWad) external urnAuth(urn) { + function freeNgt(address urn, address to, uint256 ngtWad) external urnAuth(urn) returns (uint256 ngtFreed) { uint256 wad = ngtWad / mkrNgtRate; uint256 freed = _free(urn, wad, fee); + ngtFreed = freed * mkrNgtRate; mkrNgt.mkrToNgt(to, freed); - emit FreeNgt(urn, to, ngtWad, wad - freed); + emit FreeNgt(urn, to, ngtWad, ngtFreed); } function freeNoFee(address urn, address to, uint256 wad) external auth urnAuth(urn) { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index f7f99f61..2d31517d 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -62,8 +62,8 @@ contract LockstakeEngineTest is DssTest { event SelectFarm(address indexed urn, address farm, uint16 ref); event Lock(address indexed urn, uint256 wad, uint16 ref); event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); - event Free(address indexed urn, address indexed to, uint256 wad, uint256 burn); - event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 burn); + event Free(address indexed urn, address indexed to, uint256 wad, uint256 freed); + event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 ngtFreed); event FreeNoFee(address indexed urn, address indexed to, uint256 wad); event Draw(address indexed urn, address indexed to, uint256 wad); event Wipe(address indexed urn, uint256 wad); @@ -607,8 +607,8 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.totalSupply(), initialMkrSupply); vm.expectEmit(true, true, true, true); - emit Free(urn, address(this), 40_000 * 10**18, 40_000 * 10**18 * 15 / 100); - engine.free(urn, address(this), 40_000 * 10**18); + emit Free(urn, address(this), 40_000 * 10**18, 40_000 * 10**18 * 85 / 100); + assertEq(engine.free(urn, address(this), 40_000 * 10**18), 40_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); @@ -624,8 +624,8 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } vm.expectEmit(true, true, true, true); - emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 15 / 100); - engine.free(urn, address(123), 10_000 * 10**18); + emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 85 / 100); + assertEq(engine.free(urn, address(123), 10_000 * 10**18), 10_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); @@ -697,8 +697,8 @@ contract LockstakeEngineTest is DssTest { } assertEq(ngt.totalSupply(), initialNgtSupply - 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); - emit FreeNgt(urn, address(this), 40_000 * 24_000 * 10**18, 40_000 * 10**18 * 15 / 100); - engine.freeNgt(urn, address(this), 40_000 * 24_000 * 10**18); + emit FreeNgt(urn, address(this), 40_000 * 24_000 * 10**18, 40_000 * 24_000 * 10**18 * 85 / 100); + assertEq(engine.freeNgt(urn, address(this), 40_000 * 24_000 * 10**18), 40_000 * 24_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); @@ -714,8 +714,8 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } vm.expectEmit(true, true, true, true); - emit FreeNgt(urn, address(123), 10_000 * 24_000 * 10**18, 10_000 * 10**18 * 15 / 100); - engine.freeNgt(urn, address(123), 10_000 * 24_000 * 10**18); + emit FreeNgt(urn, address(123), 10_000 * 24_000 * 10**18, 10_000 * 24_000 * 10**18 * 85 / 100); + assertEq(engine.freeNgt(urn, address(123), 10_000 * 24_000 * 10**18), 10_000 * 24_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); From 136b4358b2622b667831c34ab3d3add0ea9201c9 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 8 Apr 2024 12:13:33 -0300 Subject: [PATCH 071/111] Change error message --- src/LockstakeEngine.sol | 2 +- test/LockstakeEngine.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index d9ce669f..264abfe6 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -442,7 +442,7 @@ contract LockstakeEngine is Multicall { unchecked { refund = left - burn; } if (refund > 0) { // The following is ensured by the dog and clip but we still prefer to be explicit - require(refund <= uint256(type(int256).max), "LockstakeEngine/refund-over-maxint"); + require(refund <= uint256(type(int256).max), "LockstakeEngine/overflow"); vat.slip(ilk, urn, int256(refund)); vat.frob(ilk, urn, urn, address(0), int256(refund), 0); lsmkr.mint(urn, refund); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 2d31517d..0688e86f 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -1450,7 +1450,7 @@ contract LockstakeEngineTest is DssTest { } function testOnRemoveOverflow() public { - vm.expectRevert("LockstakeEngine/refund-over-maxint"); + vm.expectRevert("LockstakeEngine/overflow"); vm.prank(pauseProxy); engine.onRemove(address(1), 0, uint256(type(int256).max) + 1); } From a21be78009d80a94f1b84a44ecc04aef74fc40ea Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:16:46 +0300 Subject: [PATCH 072/111] Lockstake Engine Readme (#6) * Start working on overview * More technical specs * Add more content to the spec/overview * Add diagrams and sticky example * More overview updates * Some fixes after review * Update spec * Align overview to merging lock and stake, sent version * Added minimal mat calc * Update overview - hop in splitter, delay window * Update overview * Apply suggestions from code review Co-authored-by: telome <130504305+telome@users.noreply.github.com> * Pushing sticky oracle, auto max line updates * Address some of Yaron's comments * Remove SB funds from AutoMaxLine documentation * Minor change * Add Trusted Farms and Reward Tokens part * Add kick gas comparison * Clarifications for ceiling IAM and sticky oracle * Bump overview commits, add clarification on bark cost * Yank TBD parts * lockstake-overview.md => README.md * git checkout origin/dev lib/dss-test * Remove Empty lines * Apply suggestions from code review Co-authored-by: telome <130504305+telome@users.noreply.github.com> Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> * Remove links to same repo, except for benchmarks * Remove flapper parts * Add comment that small amounts could bypass the exit fees * SLE => LSE, drop Sagittarius --------- Co-authored-by: telome <130504305+telome@users.noreply.github.com> Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> --- README.md | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..f48f52f7 --- /dev/null +++ b/README.md @@ -0,0 +1,222 @@ +# Lockstake Engine + +A technical description of the components of the LockStake Engine (LSE). + +## 1. LockstakeEngine + +The LockstakeEngine is the main contract in the set of contracts that implement and support the LSE. On a high level, it supports locking MKR in the contract, and using it to: +* Vote through a delegate contract. +* Farm NST or SDAO tokens. +* Borrow NST through a vault. + +When withdrawing back the MKR the user has to pay an exit fee. + +There is also support for locking and freeing NGT instead of MKR. + +**System Attributes:** + +* A single user address can open multiple positions (each denoted as `urn`). +* Each `urn` relates to zero or one chosen delegate contract, zero or one chosen farm, and one vault. +* MKR cannot be moved outside of an `urn` or between `urn`s without paying the exit fee. +* At any time the `urn`'s entire locked MKR amount is either staked or not, and is either delegated or not. +* Staking rewards are not part of the collateral, and are still claimable after freeing from the engine, changing a farm or being liquidated. +* The entire locked MKR amount is also credited as collateral for the user. However, the user itself decides if and how much NST to borrow, and should be aware of liquidation risk. +* A user can delegate control of an `urn` that it controls to another EOA/contract. This is helpful for supporting manager-type contracts that can be built on top of the engine. +* Once a vault goes into liquidation, its MKR is undelegated and unstaked. It and can only be re-delegated or re-staked once there are no more auctions for it. + +**User Functions:** + +* `open(uint256 index)` - Create a new `urn` for the sender. The `index` parameter specifies how many `urn`s have been created so far by the user (should be 0 for the first call). It is used to avoid race conditions. +* `hope(address urn, address usr)` - Allow `usr` to also manage the sender's controlled `urn`. +* `nope(address urn, address usr)` - Disallow `usr` from managing the sender's controlled `urn`. +* `lock(address urn, uint256 wad, uint16 ref)` - Deposit `wad` amount of MKR into the `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. +* `lockNgt(address urn, uint256 ngtWad, uint16 ref)` - Deposit `ngtWad` amount of NGT. The NGT is first converted to MKR, which then gets deposited into the `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. +* `free(address urn, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `urn` to the `to` address (which will receive it minus the exit fee). This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. +* `freeNgt(address urn, address to, uint256 ngtWad)` - Withdraw `ngtWad - ngtWad % mkrNgtRate` amount of NGT to the `to` address. In practice, a proportional amount of MKR is first freed from the `urn` (minus the exit fee), then gets converted to NGT and sent out. This will undelegate the MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. Note that freeing NGT is possible even if the position was previously entered via regular locking (using MKR), and vice-vera. +* `freeNoFee(address urn, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `urn` to the `to` address without paying any fee. This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. This function can only be called by an address which was both authorized on the contract by governance and for which the urn owner has called `hope`. It is useful for implementing a migration contract that will move the funds to another engine contract (if ever needed). +* `selectDelegate(address urn, address delegate)` - Choose which delegate contract to delegate the `urn`'s entire MKR amount to. In case it is `address(0)` the MKR will stay (or become) undelegated. +* `selectFarm(address urn, address farm, uint16 ref)` - Select which farm (from the whitelisted ones) to stake the `urn`'s MKR to (along with the `ref` code). In case it is `address(0)` the MKR will stay (or become) unstaked. +* `draw(address urn, address to, uint256 wad)` - Generate `wad` amount of NST using the `urn`’s MKR as collateral and send it to the `to` address. +* `wipe(address urn, uint256 wad)` - Repay `wad` amount of NST backed by the `urn`’s MKR. +* `getReward(address urn, address farm, address to)` - Claim the reward generated from a farm on behalf of the `urn` and send it to the specified `to` address. +* `multicall(bytes[] calldata data)` - Batch multiple methods in a single call to the contract. + +**Sequence Diagram:** + +Below is a diagram of a typical user sequence for winding up an LSE position. + +For simplicity it does not include all external messages, internal operations or token interactions. + +```mermaid +sequenceDiagram + Actor user + participant engine + participant urn0 + participant delegate0 + participant farm0 + participant vat + + user->>engine: open(0) + engine-->>urn0: (creation) + engine-->>user: return `urn0` address + + user->>engine: lock(`urn0`, 10, 0) + engine-->>vat: vat.frob(ilk, `urn0`, `urn0`, address(0), 10, 0) // lock collateral + + user->>engine: selectDelegate(`urn0`, `delegate0`) + engine-->>delegate0: lock(10) + + user->>engine: selectFarm(`urn0`, `farm0`, `ref`) + engine-->>urn0: stake(`farm0`, 10, `ref`) + urn0-->>farm0: stake(10, `ref`); + + + user->>engine: draw(`urn0`, `user`, 1000) + engine-->>vat: vat.frob(ilk, `urn0`, address(0), address(this), 0, 1000) // borrow +``` + +**Multicall:** + +LockstakeEngine implements a function, which allows batching several function calls. + +For example, a typical flow for a user (or an app/front-end) would be to first query `index=usrAmts(usr)` and `urn=getUrn(usr, index)` off-chain to retrieve the expected `index` and `urn` address, then use these to perform a multicall sequence that includes `open`, `selectFarm`, `lock` and `stake`. + +This way, locking and farm-staking can be achieved in only 2 transactions (including the token approval). + +Note that since the `index` is first fetched off-chain and there is no support for passing return values between batched calls, there could be race conditions for calling `open`. For example, `open` can be called twice by the user (e.g. in two different contexts) with the second `usrAmts` query happening before the first `open` call has been confirmed. This would lead to both calls using the same `urn` for `selectFarm`, `lock` and `stake`. + +To mitigate this, the `index` parameter for `open` is used to make sure the multicall transaction creates the intended `urn`. + +**Minimal Proxies:** + +Upon calling `open`, an `urn` contract is deployed for each position. The `urn` contracts are controlled by the engine and represent each user position for farming, delegation and borrowing. This deployment process uses the [ERC-1167 minimal proxy pattern](https://eips.ethereum.org/EIPS/eip-1167), which helps reduce the `open` gas consumption by around 70%. + +**Liquidation Callbacks:** + +The following functions are called from the LockstakeClipper (see below) throughout the liquidation process. + +* `onKick(address urn, uint256 wad)` - Undelegate and unstake the entire `urn`'s MKR amount. +* `onTake(address urn, address who, uint256 wad)` - Transfer MKR to the liquidation auction buyer. +* `onRemove(address urn, uint256 sold, uint256 left)` - Burn a proportional amount of the MKR which was bought in the auction and return the rest to the `urn`. + +**Configurable Parameters:** + +* `farms` - Whitelisted set of farms to choose from. +* `jug` - The Dai lending rate calculation module. + + +## 2. LockstakeClipper + +A modified version of the Liquidations 2.0 Clipper contract, which uses specific callbacks to the LockstakeEngine on certain events. This follows the same paradigm which was introduced in [proxy-manager-clipper](https://github.com/makerdao/proxy-manager-clipper/blob/67b7b5661c01bb09d771803a2be48f0455cd3bd3/src/ProxyManagerClipper.sol) (used for [dss-crop-join](https://github.com/makerdao/dss-crop-join)). + +Specifically, the LockstakeEngine is called upon a beginning of an auction (`onKick`), a sell of collateral (`onTake`), and when the auction is concluded (`onRemove`). + +The LSE liquidation process differs from the usual liquidations by the fact that it sends the taker callee the collateral (MKR) in the form of ERC20 tokens and not `vat.gem`. + +**Exit Fee on Liquidation** + +For a liquidated position the relative exit fee is burned from the MKR (collateral) leftovers upon completion of the auction. To ensure enough MKR is left, and also prevent incentives for self-liquidation, the ilk's liquidation ratio (`mat`) must be set high enough. We calculate below the minimal `mat` (while ignoring parameters resolution for simplicity): + +To be able to liquidate we need the vault to be liquidate-able. The point where that happens is: +`① ink * price / mat = debt` + +The debt to be auctioned is enlarged (by the penalty) to `debt * chop` (where typically `chop` is 113%). If we assume the auction selling is at market price and that the market price didn't move since the auction trigger, then the amount of collateral sold is: +`debt * chop / price` + +Since we need to make sure that only up to `(1-fee)` of the total collateral is sold (where `fee` will typically be 15%), we require: +`② debt * chop / price < (1-fee) * ink` + +From ① and ② we get the requirement on `mat`: +`mat > chop / (1 - fee)` + +For the mentioned examples of `chop` and `fee` we get: +`mat > 1.13 / 0.85 ~= 133%` + +**Trusted Farms and Reward Tokens** + +It is assumed that the farm owner is trusted, the reward token implementation is non-malicious, and that the reward token minter/s are not malicious. Therefore, theoretic attacks, in which for example the reward rate is inflated to a point where the farm mechanics block liquidations, are assumed non-feasible. + +**Liquidation Bark Gas Benchmarks** + +Delegate: N, Staking: N - 483492 gas +Delegate: Y, Staking: Y, Yays: 1 - 614242 gas +Delegate: Y, Staking: Y, Yays: 5 - 646522 gas +Measured on: https://github.com/makerdao/lockstake/pull/38/commits/046b1a3c684b178dbd4a8dd8b3fb6e036a485115 + +For reference, a regular collateral bark cost is around 450K. +Source: https://docs.google.com/spreadsheets/d/1ifb9ePno6KHNNGQA8s6u8KG7BRWa7fhUYH3Z5JGOxag/edit#gid=0 + +Note that the increased gas cost should be taken into consideration when determining liquidation incentives, along with the dust amount. + +**Configurable Parameters (similar to a regular Clipper):** + +* `dog` - Liquidation module. +* `vow` - Recipient of DAI raised in auctions. +* `spotter` - Collateral price module. +* `calc` - Current price calculator. +* `buf` - Multiplicative factor to increase starting price. +* `tail` - Time elapsed before auction reset. +* `cusp` - Percentage drop before auction reset. +* `chip` - Percentage of tab to suck from vow to incentivize keepers. +* `tip` - Flat fee to suck from vow to incentivize keepers. +* `chost` - Cache the ilk dust times the ilk chop to prevent excessive SLOADs. + +## 3. Vote Delegation +### 3.a. VoteDelegate + +The LSE integrates with the current [VoteDelegate](https://github.com/makerdao/vote-delegate/blob/c2345b78376d5b0bb24749a97f82fe9171b53394/src/VoteDelegate.sol) contracts almost as is. However, there are two changes done: +* In order to support long-term locking the delegate's expiration functionality needs to be removed. +* In order to protect against an attack vector of delaying liquidations or blocking freeing of MKR, an on-demand window where locking MKR is blocked is introduced. The need for this stems from the Chief's flash loan protection, which doesn't allow to free MKR from a delegate in case MKR locking was already done in the same block. + +### 3.b. VoteDelegateFactory + +Since the VoteDelegate code is being modified (as described above), the factory also needs to be re-deployed. + +Note that it is important for the LSE to only allow using VoteDelegate contracts from the factory, so it can be made sure that liquidations can not be blocked. + +Up to date implementation: https://github.com/makerdao/vote-delegate/tree/v2/src + +## 4. Keepers Support + +In general participating in MKR liquidations should be pretty straightforward using the existing on-chain liquidity. However there is a small caveat: + +Current Makerdao ecosystem keepers expect receiving collateral in the form of `vat.gem` (usually to a keeper arbitrage callee contract), which they then need to `exit` to ERC20 from. However the LSE liquidation mechanism sends the MKR directly in the form of ERC20, which requires a slight change in the keepers mode of operation. + +For example, keepers using the Maker supplied [exchange-callee for Uniswap V2](https://github.com/makerdao/exchange-callees/blob/3b080ecd4169fe09a59be51e2f85ddcea3242461/src/UniswapV2Callee.sol#L109) would need to use a version that gets the `gem` instead of the `gemJoin` and does not call `gemJoin.exit`. +Additionaly, the callee might need to convert the MKR to NGT, in case it interacts with the NST/NGT Uniswap pool. + +## 5. Splitter + +The Splitter contract is in charge of distributing the Surplus Buffer funds on each `vow.flap` to the Smart Burn Engine (SBE) and the LSE's NST farm. The total amount sent each time is `vow.bump`. + +To accomplish this, it exposes a `kick` operation to be triggered periodically. Its logic withdraws DAI from the `vow` and splits it in two parts. The first part (`burn`) is sent to the underlying `flapper` contract to be processed by the SBE. The second part (`WAD - burn`) is distributed as reward to a `farm` contract. Note that `burn == 1 WAD` indicates funneling 100% of the DAI to the SBE without sending any rewards to the farm. + +When sending DAI to the farm, the splitter also calls `farm.notifyRewardAmount` to update the farm contract on the new rewards distribution. This resets the farming distribution period to the governance configured duration and sets the rewards rate according to the sent reward amount and rewards leftovers from the previous distribution (in case there are any). + +The Splitter implements rate-limiting using a `hop` parameter. + +**Configurable Parameters:** +* `flapper` - The underlying burner strategy (e.g. the address of `FlapperUniV2SwapOnly`). +* `burn` - The percentage of the `vow.bump` to be moved to the underlying `flapper`. For example, a value of 0.70 \* `WAD` corresponds to a funneling 70% of the DAI to the burn engine. +* `hop` - Minimal time between kicks. + +Up to date implementation: https://github.com/makerdao/dss-flappers/commit/c946c39ec94bff29c6a118cd702ffaa0f23f3d4a``` + +## 6. StakingRewards + +The LSE uses a Maker modified [version](https://github.com/makerdao/endgame-toolkit/blob/master/README.md#stakingrewards) of the Synthetix Staking Reward as the farm for distributing NST to stakers. + +For compatibility with the SBE, the assumption is that the duration of each farming distribution (`farm.rewardsDuration`) is similar to the flapper's cooldown period (`flap.hop`). This in practice divides the overall farming reward distribution to a set of smaller non overlapping distributions. It also allows for periods where there is no distribution at all. + +The StakingRewards contract `setRewardsDuration` function was modified to enable governance to change the farming distribution duration even if the previous distribution has not finished. This now supports changing it simultaneously with the SBE cooldown period (through a governance spell). + +**Configurable Parameters:** +* `rewardsDistribution` - The address which is allowed to start a rewards distribution. Will be set to the splitter. +* `rewardsDuration` - The amount of seconds each distribution should take. + +Up to date implementation: https://github.com/makerdao/endgame-toolkit/commit/1a857ee888d859b3b08e52ee12f721d1f3ce80c6 + +## General Notes +* In many of the modules, such as the splitter and the flappers, NST can replace DAI. This will usually require a deployment of the contract with NstJoin as a replacement of the DaiJoin address. +* The LSE assumes that the ESM threshold is set large enough prior to its deployment, so Emergency Shutdown can never be called. +* Freeing very small amounts could bypass the exit fees (due to the rounding down) but since the LSE is meant to only be deployed on Ethereum, this is assumed to not be economically viable. From cda77bb3e623a612eb746d12c1ede1555bc970d7 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:09:20 +0100 Subject: [PATCH 073/111] Update token-tests (#40) Co-authored-by: telome <> --- lib/token-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/token-tests b/lib/token-tests index 51cf4dba..159dc32e 160000 --- a/lib/token-tests +++ b/lib/token-tests @@ -1 +1 @@ -Subproject commit 51cf4dbaa3e468827263dcb099ba7974c31941bb +Subproject commit 159dc32e7df7f03f9bdab3845918fa2b2f4babb9 From 437258c522c3461e8be570bc5cf7a672de910093 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:15:06 +0300 Subject: [PATCH 074/111] selectDelegate => selectVoteDelegate in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f48f52f7..842da3db 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ There is also support for locking and freeing NGT instead of MKR. * `free(address urn, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `urn` to the `to` address (which will receive it minus the exit fee). This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. * `freeNgt(address urn, address to, uint256 ngtWad)` - Withdraw `ngtWad - ngtWad % mkrNgtRate` amount of NGT to the `to` address. In practice, a proportional amount of MKR is first freed from the `urn` (minus the exit fee), then gets converted to NGT and sent out. This will undelegate the MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. Note that freeing NGT is possible even if the position was previously entered via regular locking (using MKR), and vice-vera. * `freeNoFee(address urn, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `urn` to the `to` address without paying any fee. This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. This function can only be called by an address which was both authorized on the contract by governance and for which the urn owner has called `hope`. It is useful for implementing a migration contract that will move the funds to another engine contract (if ever needed). -* `selectDelegate(address urn, address delegate)` - Choose which delegate contract to delegate the `urn`'s entire MKR amount to. In case it is `address(0)` the MKR will stay (or become) undelegated. +* `selectVoteDelegate(address urn, address voteDelegate)` - Choose which delegate contract to delegate the `urn`'s entire MKR amount to. In case it is `address(0)` the MKR will stay (or become) undelegated. * `selectFarm(address urn, address farm, uint16 ref)` - Select which farm (from the whitelisted ones) to stake the `urn`'s MKR to (along with the `ref` code). In case it is `address(0)` the MKR will stay (or become) unstaked. * `draw(address urn, address to, uint256 wad)` - Generate `wad` amount of NST using the `urn`’s MKR as collateral and send it to the `to` address. * `wipe(address urn, uint256 wad)` - Repay `wad` amount of NST backed by the `urn`’s MKR. @@ -63,7 +63,7 @@ sequenceDiagram user->>engine: lock(`urn0`, 10, 0) engine-->>vat: vat.frob(ilk, `urn0`, `urn0`, address(0), 10, 0) // lock collateral - user->>engine: selectDelegate(`urn0`, `delegate0`) + user->>engine: selectVoteDelegate(`urn0`, `delegate0`) engine-->>delegate0: lock(10) user->>engine: selectFarm(`urn0`, `farm0`, `ref`) From 6ce726ebc43cb84d2308c30358577f2a8325791b Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:24:59 -0300 Subject: [PATCH 075/111] Fix spacings Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- test/LockstakeClipper.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index 4bcce1fa..bdf802a8 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -364,13 +364,13 @@ contract LockstakeClipperTest is DssTest { assertEq(art, 0 ether); pip.setPrice(price); // Spot = $2.5 - dss.spotter.poke(ilk); // Now safe + dss.spotter.poke(ilk); // Now safe vm.warp(startTime + 100); dss.vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); pip.setPrice(4 ether); // Spot = $2 - dss.spotter.poke(ilk); // Now unsafe + dss.spotter.poke(ilk); // Now unsafe (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(2); assertEq(sale.pos, 0); From e44f62b17ef1a9169d0fcadf6388ee9dbe42333a Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 10 Apr 2024 17:32:55 -0300 Subject: [PATCH 076/111] Add unchecked in clipper.kick --- src/LockstakeClipper.sol | 2 +- test/LockstakeClipper.t.sol | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index ee04079e..d31e705c 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -237,7 +237,7 @@ contract LockstakeClipper { require(lot > 0, "LockstakeClipper/zero-lot"); require(lot <= uint256(type(int256).max), "LockstakeClipper/over-maxint-lot"); // This is ensured by the dog but we still prefer to be explicit require(usr != address(0), "LockstakeClipper/zero-usr"); - id = ++kicks; + unchecked { id = ++kicks; } require(id > 0, "LockstakeClipper/overflow"); active.push(id); diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index bdf802a8..0a6b88d8 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -168,6 +168,8 @@ interface VowLike { } contract LockstakeClipperTest is DssTest { + using stdStorage for StdStorage; + DssInstance dss; address pauseProxy; PipMock pip; @@ -450,6 +452,12 @@ contract LockstakeClipperTest is DssTest { clip.kick(1 ether, 2 ether, address(0), address(this)); } + function testRevertsKickKicksOverflow() public { + stdstore.target(address(clip)).sig("kicks()").checked_write(type(uint256).max); + vm.expectRevert("LockstakeClipper/overflow"); + clip.kick(1 ether, 2 ether, address(1), address(this)); + } + function testBarkNotLeavingDust() public { vm.prank(pauseProxy); dss.dog.file(ilk, "hole", rad(80 ether)); // Makes room = 80 WAD vm.prank(pauseProxy); dss.dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) From 65d53d6cf00c11b7ce7243e2c56b77585acb0a59 Mon Sep 17 00:00:00 2001 From: telome <130504305+telome@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:31:26 +0100 Subject: [PATCH 077/111] Improve LockstakeClipper test coverage (#41) * Improve LockstakeClipper test coverage * Add testRevertsYankZeroUsr * Add testConstructor * Add checks to testConstructor * Fix testConstructor --------- Co-authored-by: telome <> --- test/LockstakeClipper.t.sol | 119 +++++++++++++++++++++++++++++++++--- test/mocks/PipMock.sol | 2 +- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index 0a6b88d8..565fc8be 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -299,7 +299,7 @@ contract LockstakeClipperTest is DssTest { assertEq(dss.vat.dai(address(this)), rad(100 ether)); pip.setPrice(4 ether); // Spot = $2 - dss.spotter.poke(ilk); // Now unsafe + dss.spotter.poke(ilk); // Now unsafe ali = address(111); bob = address(222); @@ -316,6 +316,41 @@ contract LockstakeClipperTest is DssTest { vm.stopPrank(); } + function testConstructor() public { + vm.expectEmit(true, true, true, true); + emit Rely(address(this)); + LockstakeClipper c = new LockstakeClipper(address(111), address(222), address(333), address(engine)); + assertEq(address(c.vat()), address(111)); + assertEq(address(c.spotter()), address(222)); + assertEq(address(c.dog()), address(333)); + assertEq(address(c.engine()), address(engine)); + assertEq(c.ilk(), ilk); + assertEq(c.buf(), RAY); + assertEq(c.wards(address(this)), 1); + } + + function testAuth() public { + checkAuth(address(clip), "LockstakeClipper"); + } + + function testFileUint() public { + checkFileUint(address(clip), "LockstakeClipper", ["buf", "tail", "cusp", "chip", "tip", "stopped"]); + } + + function testFileAddress() public { + checkFileAddress(address(clip), "LockstakeClipper", ["spotter", "dog", "vow", "calc"]); + } + + function testAuthModifiers() public { + bytes4[] memory authedMethods = new bytes4[](2); + authedMethods[0] = clip.kick.selector; + authedMethods[1] = clip.yank.selector; + + vm.startPrank(address(0xBEEF)); + checkModifier(address(clip), "LockstakeClipper/not-authorized", authedMethods); + vm.stopPrank(); + } + function testChangeDog() public { assertTrue(address(clip.dog()) != address(123)); clip.file("dog", address(123)); @@ -365,7 +400,7 @@ contract LockstakeClipperTest is DssTest { assertEq(ink, 0 ether); assertEq(art, 0 ether); - pip.setPrice(price); // Spot = $2.5 + pip.setPrice(price); // Spot = $2.5 dss.spotter.poke(ilk); // Now safe vm.warp(startTime + 100); @@ -407,11 +442,28 @@ contract LockstakeClipperTest is DssTest { assertEq(ink, 0 ether); assertEq(art, 0 ether); - assertEq(dss.vat.dai(bob), rad(1000 ether) + rad(100 ether) + sale.tab * 0.02 ether / WAD); // Paid (tip + due * chip) amount of DAI for calling bark() + uint256 bobVatDai = rad(1000 ether) + rad(100 ether) + sale.tab * 0.02 ether / WAD; + assertEq(dss.vat.dai(bob), bobVatDai); // Paid (tip + due * chip) amount of DAI for calling bark() + + pip.setPrice(price); // Spot = $2.5 + dss.spotter.poke(ilk); // Now safe + + dss.vat.frob(ilk, address(this), address(this), address(this), 40 ether, 100 ether); + + pip.setPrice(4 ether); // Spot = $2 + dss.spotter.poke(ilk); // Now unsafe + + clip.file("tip", 0); + clip.file("chip", 0); + + vm.prank(bob); dss.dog.bark(ilk, address(this), address(bob)); + + assertEq(clip.kicks(), 3); + assertEq(dss.vat.dai(bob), bobVatDai); // no incentive received } function testRevertsKickZeroPrice() public { - pip.setPrice(0); + clip.file("buf", 0); vm.expectRevert("LockstakeClipper/zero-top-price"); dss.dog.bark(ilk, address(this), address(this)); } @@ -419,7 +471,7 @@ contract LockstakeClipperTest is DssTest { function testRevertsRedoZeroPrice() public { _auctionResetSetup(1 hours); - pip.setPrice(0); + clip.file("buf", 0); vm.warp(startTime + 1801 seconds); (bool needsRedo,,,) = clip.getStatus(1); @@ -458,6 +510,12 @@ contract LockstakeClipperTest is DssTest { clip.kick(1 ether, 2 ether, address(1), address(this)); } + function testRevertsKickInvalidPrice() public { + pip.setPrice(0); + vm.expectRevert("LockstakeClipper/invalid-price"); + clip.kick(1 ether, 2 ether, address(1), address(this)); + } + function testBarkNotLeavingDust() public { vm.prank(pauseProxy); dss.dog.file(ilk, "hole", rad(80 ether)); // Makes room = 80 WAD vm.prank(pauseProxy); dss.dog.file(ilk, "chop", 1 ether); // 0% chop (for precise calculations) @@ -728,7 +786,7 @@ contract LockstakeClipperTest is DssTest { assertEq(dirt, sale.tab); } - function testTakeZeroUsr() public takeSetup { + function testRevertsTakeZeroUsr() public takeSetup { // Auction id 2 is unpopulated. (,,,, address usr,,) = clip.sales(2); assertEq(usr, address(0)); @@ -904,6 +962,18 @@ contract LockstakeClipperTest is DssTest { assertEq(dirt, 0); } + function testRevertsTakeNeedsReset() public takeSetup { + vm.warp(block.timestamp + 3601); + vm.expectRevert("LockstakeClipper/needs-reset"); + vm.prank(ali); clip.take({ + id: 1, + amt: 22 ether, + max: ray(5 ether), + who: address(ali), + data: "" + }); + } + function testRevertsTakeBidTooLow() public takeSetup { // Bid so max (= 4) < price (= top = 5) (fails with "Clipper/too-expensive") vm.expectRevert("LockstakeClipper/too-expensive"); @@ -1144,12 +1214,21 @@ contract LockstakeClipperTest is DssTest { clip.redo(1, address(this)); } - function testRedoZeroUsr() public { + function testRevertsRedoZeroUsr() public { // Can't reset a non-existent auction. vm.expectRevert("LockstakeClipper/not-running-auction"); clip.redo(1, address(this)); } + function testRevertsRedoInvalidPrice() public { + _auctionResetSetup(1 hours); + vm.warp(startTime + 3601 seconds); + pip.setPrice(0); + + vm.expectRevert("LockstakeClipper/invalid-price"); + clip.redo(1, address(this)); + } + function testSetBreaker() public { clip.file("stopped", 1); assertEq(clip.stopped(), 1); @@ -1309,18 +1388,24 @@ contract LockstakeClipperTest is DssTest { clip.redo(1, address(234)); assertEq(dss.vat.dai(address(234)), clip.tip() + clip.chip() * tab / WAD); - clip.file("tip", 0); // No more flat fee + clip.file("tip", 0); // No more flat fee vm.warp(block.timestamp + 300); clip.redo(1, address(345)); assertEq(dss.vat.dai(address(345)), clip.chip() * tab / WAD); + clip.file("chip", 0); // No more incentive + vm.warp(block.timestamp + 300); + clip.redo(1, address(456)); + assertEq(dss.vat.dai(address(456)), 0); + vm.prank(pauseProxy); dss.vat.file(ilk, "dust", rad(100 ether) + 1); // ensure wmul(dust, chop) > 110 DAI (tab) clip.upchost(); assertEq(clip.chost(), 110 * RAD + 1); + clip.file("tip", rad(100 ether)); // Flat fee of 100 DAI vm.warp(block.timestamp + 300); - clip.redo(1, address(456)); - assertEq(dss.vat.dai(address(456)), 0); + clip.redo(1, address(567)); + assertEq(dss.vat.dai(address(567)), 0); // Set dust so that wmul(dust, chop) is well below tab to check the dusty lot case. vm.prank(pauseProxy); dss.vat.file(ilk, "dust", rad(20 ether)); // $20 dust @@ -1401,6 +1486,14 @@ contract LockstakeClipperTest is DssTest { assertEq(dss.vat.gem(ilk, address(clip)), prevClipperGemBalance - lot); } + function testRevertsYankZeroUsr() public takeSetup { + // Auction id 2 is unpopulated. + (,,,, address usr,,) = clip.sales(2); + assertEq(usr, address(0)); + vm.expectRevert("LockstakeClipper/not-running-auction"); + clip.yank(2); + } + function testRemoveId() public { LockstakeEngineMock engine2 = new LockstakeEngineMock(address(dss.vat), "random"); PublicClip pclip = new PublicClip(address(dss.vat), address(dss.spotter), address(dss.dog), address(engine2)); @@ -1419,6 +1512,12 @@ contract LockstakeClipperTest is DssTest { assertEq(pclip.active(2), 3); assertEq(pclip.active(3), 4); assertEq(pclip.active(4), 5); + uint256[] memory list = pclip.list(); + assertEq(list[0], 1); + assertEq(list[1], 2); + assertEq(list[2], 3); + assertEq(list[3], 4); + assertEq(list[4], 5); pclip.remove(id); diff --git a/test/mocks/PipMock.sol b/test/mocks/PipMock.sol index 47684e88..a065b0ba 100644 --- a/test/mocks/PipMock.sol +++ b/test/mocks/PipMock.sol @@ -14,7 +14,7 @@ contract PipMock { } function peek() external view returns (uint256 price_, bool ok) { - ok = true; + ok = price > 0; price_ = price; } } From 8716226d863b92c966046c60a2335423cd18760c Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:21:17 -0300 Subject: [PATCH 078/111] Make lock/lockNgt permissionless (#43) * Make lock/lockNgt permissionless * Fix spacing --- src/LockstakeEngine.sol | 5 +++-- test/LockstakeEngine.t.sol | 27 ++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 264abfe6..d6f67908 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -305,13 +305,13 @@ contract LockstakeEngine is Multicall { urnFarms[urn] = farm; } - function lock(address urn, uint256 wad, uint16 ref) external urnAuth(urn) { + function lock(address urn, uint256 wad, uint16 ref) external { mkr.transferFrom(msg.sender, address(this), wad); _lock(urn, wad, ref); emit Lock(urn, wad, ref); } - function lockNgt(address urn, uint256 ngtWad, uint16 ref) external urnAuth(urn) { + function lockNgt(address urn, uint256 ngtWad, uint16 ref) external { ngt.transferFrom(msg.sender, address(this), ngtWad); mkrNgt.ngtToMkr(address(this), ngtWad); _lock(urn, ngtWad / mkrNgtRate, ref); @@ -319,6 +319,7 @@ contract LockstakeEngine is Multicall { } function _lock(address urn, uint256 wad, uint16 ref) internal { + require(urnOwners[urn] != address(0), "LockstakeEngine/invalid-urn"); require(wad <= uint256(type(int256).max), "LockstakeEngine/overflow"); address voteDelegate = urnVoteDelegates[urn]; if (voteDelegate != address(0)) { diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 0688e86f..8d168f1c 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -377,17 +377,15 @@ contract LockstakeEngineTest is DssTest { checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); - bytes4[] memory urnOwnersMethods = new bytes4[](10); - urnOwnersMethods[0] = engine.hope.selector; - urnOwnersMethods[1] = engine.nope.selector; - urnOwnersMethods[2] = engine.selectVoteDelegate.selector; - urnOwnersMethods[3] = engine.selectFarm.selector; - urnOwnersMethods[4] = engine.lock.selector; - urnOwnersMethods[5] = engine.lockNgt.selector; - urnOwnersMethods[6] = engine.free.selector; - urnOwnersMethods[7] = engine.freeNgt.selector; - urnOwnersMethods[8] = engine.draw.selector; - urnOwnersMethods[9] = engine.getReward.selector; + bytes4[] memory urnOwnersMethods = new bytes4[](8); + urnOwnersMethods[0] = engine.hope.selector; + urnOwnersMethods[1] = engine.nope.selector; + urnOwnersMethods[2] = engine.selectVoteDelegate.selector; + urnOwnersMethods[3] = engine.selectFarm.selector; + urnOwnersMethods[4] = engine.free.selector; + urnOwnersMethods[5] = engine.freeNgt.selector; + urnOwnersMethods[6] = engine.draw.selector; + urnOwnersMethods[7] = engine.getReward.selector; // this checks the case when sender is not the urn owner and not hoped, the hoped case is checked in testHopeNope and the urn owner case in the specific tests vm.startPrank(address(0xBEEF)); @@ -587,10 +585,13 @@ contract LockstakeEngineTest is DssTest { } assertEq(_ink(ilk, urn), 0); assertEq(lsmkr.balanceOf(urn), 0); - mkr.approve(address(engine), 100_000 * 10**18); + mkr.transfer(address(123), 100_000 * 10**18); + vm.prank(address(123)); mkr.approve(address(engine), 100_000 * 10**18); + vm.expectRevert("LockstakeEngine/invalid-urn"); + vm.prank(address(123)); engine.lock(address(456), 100_000 * 10**18, 5); vm.expectEmit(true, true, true, true); emit Lock(urn, 100_000 * 10**18, 5); - engine.lock(urn, 100_000 * 10**18, 5); + vm.prank(address(123)); engine.lock(urn, 100_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); From f07f45c0d1a47b9e3f82a1db29f4f800c21eaef3 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:22:25 +0300 Subject: [PATCH 079/111] README fixes for CS audit (#44) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 842da3db..a2b55df8 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ There is also support for locking and freeing NGT instead of MKR. * `selectFarm(address urn, address farm, uint16 ref)` - Select which farm (from the whitelisted ones) to stake the `urn`'s MKR to (along with the `ref` code). In case it is `address(0)` the MKR will stay (or become) unstaked. * `draw(address urn, address to, uint256 wad)` - Generate `wad` amount of NST using the `urn`’s MKR as collateral and send it to the `to` address. * `wipe(address urn, uint256 wad)` - Repay `wad` amount of NST backed by the `urn`’s MKR. +* `wipeAll(address urn)` - Repay the amount of NST that is needed to wipe the `urn`s entire debt. * `getReward(address urn, address farm, address to)` - Claim the reward generated from a farm on behalf of the `urn` and send it to the specified `to` address. * `multicall(bytes[] calldata data)` - Batch multiple methods in a single call to the contract. @@ -159,7 +160,8 @@ Note that the increased gas cost should be taken into consideration when determi * `cusp` - Percentage drop before auction reset. * `chip` - Percentage of tab to suck from vow to incentivize keepers. * `tip` - Flat fee to suck from vow to incentivize keepers. -* `chost` - Cache the ilk dust times the ilk chop to prevent excessive SLOADs. +* `stopped` - Level used to disable various types of functionality. +* `chost` - Cached value of the ilk dust times the ilk chop. Set through `upchost()`. ## 3. Vote Delegation ### 3.a. VoteDelegate From 2c71bf9cd94e40e2c8cf806239ed937de6313617 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 16 Apr 2024 09:15:23 -0300 Subject: [PATCH 080/111] Remove comments --- deploy/LockstakeInit.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index e03a86fd..ef86e712 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -176,8 +176,6 @@ library LockstakeInit { PipLike(pip).kiss(address(clipper)); PipLike(pip).kiss(clipperMom); PipLike(pip).kiss(address(dss.end)); - // TODO: If a sticky oracle wrapper is implemented we will need to also kiss the source to it - // If an osm is implemented instead we also need the source to kiss the osm and add the OsmMom permissions dss.spotter.file(cfg.ilk, "mat", cfg.mat); dss.spotter.file(cfg.ilk, "pip", pip); From 7fdfd5cb7c26e41f98b108865303fbe59d36952b Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:32:51 -0300 Subject: [PATCH 081/111] Deployment scripts: Add OsmMom (#46) * Deployment scripts: Add OsmMom * Change comment --- deploy/LockstakeInit.sol | 11 +++++++++++ test/LockstakeEngine.t.sol | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index ef86e712..ebf26de0 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -53,6 +53,7 @@ interface LockstakeClipperLike { interface PipLike { function kiss(address) external; + function rely(address) external; } interface CalcLike { @@ -63,6 +64,10 @@ interface AutoLineLike { function setIlk(bytes32, uint256, uint256, uint256) external; } +interface OsmMomLike { + function setOsm(bytes32, address) external; +} + interface LineMomLike { function addIlk(bytes32) external; } @@ -176,6 +181,12 @@ library LockstakeInit { PipLike(pip).kiss(address(clipper)); PipLike(pip).kiss(clipperMom); PipLike(pip).kiss(address(dss.end)); + // This assumes pip is a standard Osm sourced by a Median + { + address osmMom = dss.chainlog.getAddress("OSM_MOM"); + PipLike(pip).rely(osmMom); + OsmMomLike(osmMom).setOsm(cfg.ilk, pip); + } dss.spotter.file(cfg.ilk, "mat", cfg.mat); dss.spotter.file(cfg.ilk, "pip", pip); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 8d168f1c..9e0eedd8 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -250,10 +250,13 @@ contract LockstakeEngineTest is DssTest { assertEq(ttl, 1 days); assertEq(_rho(ilk), block.timestamp); assertEq(_duty(ilk), 100000001 * 10**27 / 100000000); + address osmMom = dss.chainlog.getAddress("OSM_MOM"); address clipperMom = dss.chainlog.getAddress("CLIPPER_MOM"); + assertEq(OsmMomAbstract(osmMom).osms(ilk), address(pip)); + assertEq(pip.wards(osmMom), 1); assertEq(pip.bud(address(dss.spotter)), 1); assertEq(pip.bud(address(clip)), 1); - assertEq(pip.bud(address(clipperMom)), 1); + assertEq(pip.bud(clipperMom), 1); assertEq(pip.bud(address(dss.end)), 1); assertEq(_mat(ilk), 3 * 10**27); assertEq(_pip(ilk), address(pip)); From e967ab20cfb68ce1840a2344f69b63288054b0d3 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:59:07 +0300 Subject: [PATCH 082/111] Invariant Testing (#11) * Stake with lock, withdraw with free * clean farm onTakeLeftovers * Add missing farm reset in onTakeLeftovers * Reset farm and delegate on each onKick and onTakeLeftovers * Fixes on _selectFarm * Minor move of call * Fix issue in onKick + reorder some functions * First round of adapting tests for new structure * Fix for onTakeLeftovers * Invariant testing - wip * Add more invariants * Add kicking * Add take * Fix wipe, add invariant_call_summary * Add yank support * Minor changes * Align to dev, add test/handlers/LockstakeHandler.sol * Minor changes * Simplify * Align to dev, add defaults foundry.toml * With foundry.toml * Read vote delegate stake instead of balance * Apply suggestions from code review Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> * More bindings * Update test/handlers/LockstakeHandler.sol Co-authored-by: telome <130504305+telome@users.noreply.github.com> * Fix to use handler.delegatedTo and not rely on balance * assert => assertTrue * Remove incorrect function inherited by the Handler (#42) * Remove excludeArtifact * Bind free and freeNgt by ink minus debt * Apply suggestions from code review use spot in upper bound calcs Co-authored-by: telome <130504305+telome@users.noreply.github.com> * wipe from handler Co-authored-by: telome <130504305+telome@users.noreply.github.com> * lock from handler * Fixes and changes for invariant tests (#45) * Fixes and changes for invariant tests * Use wad * Fix typo and renaming * Simplify structure * Changes to draw and wipe * Uncomment lines * revert on free to engine --------- Co-authored-by: oldchili <130549691+oldchili@users.noreply.github.com> * Add warp * Update test/handlers/LockstakeHandler.sol Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> * Lower duty * Fix for draw and wipe --------- Co-authored-by: sunbreak Co-authored-by: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- foundry.toml | 4 + test/LockstakeEngine-invariants.t.sol | 283 +++++++++++++++++++++ test/handlers/LockstakeHandler.sol | 351 ++++++++++++++++++++++++++ 3 files changed, 638 insertions(+) create mode 100644 test/LockstakeEngine-invariants.t.sol create mode 100644 test/handlers/LockstakeHandler.sol diff --git a/foundry.toml b/foundry.toml index 1bc5d09b..2b5ddf3f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,4 +7,8 @@ optimizer = true optimizer_runs = 200 verbosity = 1 +invariant.runs = 1 +invariant.depth = 100 +invariant.preserve_state = true # For using cheats in handlers (https://github.com/foundry-rs/foundry/pull/7219) + # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/test/LockstakeEngine-invariants.t.sol b/test/LockstakeEngine-invariants.t.sol new file mode 100644 index 00000000..b309665a --- /dev/null +++ b/test/LockstakeEngine-invariants.t.sol @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.16; + +import "dss-test/DssTest.sol"; +import { LockstakeEngine } from "src/LockstakeEngine.sol"; +import { LockstakeClipper } from "src/LockstakeClipper.sol"; +import { LockstakeMkr } from "src/LockstakeMkr.sol"; +import { PipMock } from "test/mocks/PipMock.sol"; +import { VoteDelegateFactoryMock, VoteDelegateMock } from "test/mocks/VoteDelegateMock.sol"; +import { GemMock } from "test/mocks/GemMock.sol"; +import { NstMock } from "test/mocks/NstMock.sol"; +import { NstJoinMock } from "test/mocks/NstJoinMock.sol"; +import { StakingRewardsMock } from "test/mocks/StakingRewardsMock.sol"; +import { MkrNgtMock } from "test/mocks/MkrNgtMock.sol"; +import { LockstakeHandler } from "test/handlers/LockstakeHandler.sol"; + +interface ChainlogLike { + function getAddress(bytes32) external view returns (address); +} + +interface VatLike { + function gem(bytes32, address) external view returns (uint256); + function urns(bytes32, address) external view returns (uint256, uint256); + function rely(address) external; + function file(bytes32, bytes32, uint256) external; + function file(bytes32, uint256) external; + function init(bytes32) external; +} + +interface SpotterLike { + function file(bytes32, bytes32, address) external; + function file(bytes32, bytes32, uint256) external; + function poke(bytes32) external; +} + +interface JugLike { + function file(bytes32, bytes32, uint256) external; + function init(bytes32) external; +} + +interface DogLike { + function rely(address) external; + function file(bytes32, bytes32, address) external; + function file(bytes32, bytes32, uint256) external; +} + +interface CalcFabLike { + function newStairstepExponentialDecrease(address) external returns (address); +} + +interface CalcLike { + function file(bytes32, uint256) external; +} + +contract LockstakeEngineIntegrationTest is DssTest { + using stdStorage for StdStorage; + + address public pauseProxy; + VatLike public vat; + address public spot; + DogLike public dog; + GemMock public mkr; + address public jug; + LockstakeEngine public engine; + address public urn; + LockstakeClipper public clip; + PipMock public pip; + VoteDelegateFactoryMock public delFactory; + NstMock public nst; + NstJoinMock public nstJoin; + LockstakeMkr public lsmkr; + GemMock public rTok; + StakingRewardsMock public farm0; + StakingRewardsMock public farm1; + MkrNgtMock public mkrNgt; + GemMock public ngt; + bytes32 public ilk = "LSE"; + address public voter0; + address public voter1; + address public voterDelegate0; + address public voterDelegate1; + + LockstakeHandler public handler; + + address constant LOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F; + + function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { + unchecked { + z = x != 0 ? ((x - 1) / y) + 1 : 0; + } + } + + function ray(uint256 wad) internal pure returns (uint256) { + return wad * 10 ** 9; + } + + function rad(uint256 wad) internal pure returns (uint256) { + return wad * 10 ** 27; + } + + function setUp() public { + vm.createSelectFork(vm.envString("ETH_RPC_URL")); + + pauseProxy = ChainlogLike(LOG).getAddress("MCD_PAUSE_PROXY"); + vat = VatLike(ChainlogLike(LOG).getAddress("MCD_VAT")); + spot = ChainlogLike(LOG).getAddress("MCD_SPOT"); + dog = DogLike(ChainlogLike(LOG).getAddress("MCD_DOG")); + mkr = new GemMock(0); + jug = ChainlogLike(LOG).getAddress("MCD_JUG"); + nst = new NstMock(); + nstJoin = new NstJoinMock(address(vat), address(nst)); + lsmkr = new LockstakeMkr(); + rTok = new GemMock(0); + farm0 = new StakingRewardsMock(address(rTok), address(lsmkr)); + farm1 = new StakingRewardsMock(address(rTok), address(lsmkr)); + ngt = new GemMock(0); + mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 25_000); + + pip = new PipMock(); + delFactory = new VoteDelegateFactoryMock(address(mkr)); + voter0 = address(123); + voter1 = address(456); + vm.prank(voter0); voterDelegate0 = delFactory.create(); + vm.prank(voter1); voterDelegate1 = delFactory.create(); + + vm.startPrank(pauseProxy); + engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(mkrNgt), address(lsmkr), 15 * WAD / 100); + engine.file("jug", jug); + vat.rely(address(engine)); + vat.init(ilk); + JugLike(jug).init(ilk); + JugLike(jug).file(ilk, "duty", 1000000021979553151239153027); // 100% APY + SpotterLike(spot).file(ilk, "pip", address(pip)); + SpotterLike(spot).file(ilk, "mat", 3 * 10**27); // 300% coll ratio + pip.setPrice(1000 * 10**18); // 1 MKR = 1000 USD + SpotterLike(spot).poke(ilk); + vat.file(ilk, "dust", rad(20 ether)); // $20 dust + + vat.file(ilk, "line", type(uint256).max); + vat.file("Line", type(uint256).max); + + // dog and clip setup + dog.file(ilk, "chop", 1.1 ether); // 10% chop + dog.file(ilk, "hole", rad(1_000_000 ether)); + + // dust and chop filed previously so clip.chost will be set correctly + clip = new LockstakeClipper(address(vat), spot, address(dog), address(engine)); + clip.upchost(); + clip.rely(address(dog)); + + dog.file(ilk, "clip", address(clip)); + dog.rely(address(clip)); + vat.rely(address(clip)); + engine.rely(address(clip)); + + // setup for take + address calc = CalcFabLike(ChainlogLike(LOG).getAddress("CALC_FAB")).newStairstepExponentialDecrease(pauseProxy); + CalcLike(calc).file("cut", RAY - ray(0.01 ether)); // 1% decrease + CalcLike(calc).file("step", 1); // Decrease every 1 second + + clip.file("buf", ray(1.25 ether)); // 25% Initial price buffer + clip.file("calc", address(calc)); // File price contract + clip.file("cusp", ray(0.3 ether)); // 70% drop before reset + clip.file("tail", 3600); // 1 hour before reset + vm.stopPrank(); + + lsmkr.rely(address(engine)); + + deal(address(mkr), address(this), 100_000 * 10**18, true); + deal(address(ngt), address(this), 100_000 * 25_000 * 10**18, true); + + // Add some existing DAI assigned to nstJoin to avoid a particular error + stdstore.target(address(vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); + + address[] memory voteDelegates = new address[](2); + voteDelegates[0] = voterDelegate0; + voteDelegates[1] = voterDelegate1; + + address[] memory farms = new address[](2); + farms[0] = address(farm0); + farms[1] = address(farm1); + + urn = engine.open(0); + handler = new LockstakeHandler( + vm, + address(engine), + urn, + address(spot), + address(dog), + pauseProxy, + voteDelegates, + farms + ); + + // uncomment and fill to only call specific functions +// bytes4[] memory selectors = new bytes4[](5); +// selectors[0] = LockstakeHandler.lock.selector; +// selectors[1] = LockstakeHandler.draw.selector; +// selectors[2] = LockstakeHandler.selectVoteDelegate.selector; +// selectors[3] = LockstakeHandler.dropPriceAndBark.selector; +// selectors[4] = LockstakeHandler.yank.selector; +// +// targetSelector(FuzzSelector({ +// addr: address(handler), +// selectors: selectors +// })); + + targetContract(address(handler)); // invariant tests should fuzz only handler functions + } + + function invariant_system_mkr_equals_ink() public { + (uint256 ink,) = vat.urns(ilk, urn); + assertEq(mkr.balanceOf(address(engine)) + handler.sumDelegated() - vat.gem(ilk, address(clip)) - vat.gem(ilk, pauseProxy), ink); + } + + function invariant_system_mkr_equals_lsmkr_total_supply() public { + assertEq(mkr.balanceOf(address(engine)) + handler.sumDelegated() - vat.gem(ilk, address(clip)) - vat.gem(ilk, pauseProxy), lsmkr.totalSupply()); + } + + function invariant_delegation_exclusiveness() public { + assertLe(handler.numDelegated(), 1); + } + + function invariant_delegation_all_or_nothing() public { + address urnDelegate = engine.urnVoteDelegates(urn); + (uint256 ink,) = vat.urns(ilk, urn); + + if (urnDelegate == address(0)) { + assertEq(mkr.balanceOf(address(engine)) - vat.gem(ilk, address(clip)) - vat.gem(ilk, pauseProxy), ink); + } else { + assertEq(mkr.balanceOf(address(engine)) - vat.gem(ilk, address(clip)) - vat.gem(ilk, pauseProxy), 0); + assertEq(handler.delegatedTo(urnDelegate), ink); + } + } + + function invariant_staking_exclusiveness() public { + assertLe(handler.numStakedForUrn(handler.urn()), 1); + } + + function invariant_staking_all_or_nothing() public { + address urnFarm = engine.urnFarms(urn); + (uint256 ink,) = vat.urns(ilk, urn); + + if (urnFarm == address(0)) { + assertEq(lsmkr.balanceOf(urn), ink); + } else { + assertEq(lsmkr.balanceOf(urn), 0); + assertEq(GemMock(urnFarm).balanceOf(urn), ink); + } + } + + function invariant_no_delegation_or_staking_during_auction() public { + assertTrue( + engine.urnAuctions(urn) == 0 || + engine.urnVoteDelegates(urn) == address(0) && engine.urnFarms(urn) == address(0) + ); + } + + function invariant_call_summary() private view { // make external to enable + console.log("------------------"); + + console.log("\nCall Summary\n"); + console.log("addFarm", handler.numCalls("addFarm")); + console.log("selectFarm", handler.numCalls("selectFarm")); + console.log("selectVoteDelegate", handler.numCalls("selectVoteDelegate")); + console.log("lock", handler.numCalls("lock")); + console.log("lockNgt", handler.numCalls("lockNgt")); + console.log("free", handler.numCalls("free")); + console.log("freeNgt", handler.numCalls("freeNgt")); + console.log("draw", handler.numCalls("draw")); + console.log("wipe", handler.numCalls("wipe")); + console.log("dropPriceAndBark", handler.numCalls("dropPriceAndBark")); + console.log("take", handler.numCalls("take")); + console.log("yank", handler.numCalls("yank")); + console.log("warp", handler.numCalls("warp")); + console.log("total count", handler.numCalls("addFarm") + handler.numCalls("selectFarm") + handler.numCalls("selectVoteDelegate") + + handler.numCalls("lock") + handler.numCalls("lockNgt") + handler.numCalls("free") + + handler.numCalls("freeNgt") + handler.numCalls("draw") + handler.numCalls("wipe") + + handler.numCalls("dropPriceAndBark") + handler.numCalls("take") + handler.numCalls("yank") + + handler.numCalls("warp")); + } +} diff --git a/test/handlers/LockstakeHandler.sol b/test/handlers/LockstakeHandler.sol new file mode 100644 index 00000000..4eb1fa81 --- /dev/null +++ b/test/handlers/LockstakeHandler.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +import "dss-test/DssTest.sol"; + +import { LockstakeEngine } from "src/LockstakeEngine.sol"; +import { LockstakeClipper } from "src/LockstakeClipper.sol"; +import { GemMock } from "test/mocks/GemMock.sol"; +import { PipMock } from "test/mocks/PipMock.sol"; + +interface VatLike { + function urns(bytes32, address) external view returns (uint256, uint256); + function ilks(bytes32) external view returns (uint256, uint256, uint256, uint256, uint256); + function hope(address) external; + function suck(address, address, uint256) external; +} + +interface JugLike { + function ilks(bytes32) external view returns (uint256, uint256); +} + +interface SpotterLike { + function ilks(bytes32) external view returns (address, uint256); + function poke(bytes32) external; +} + +interface VoteDelegateLike { + function stake(address) external view returns (uint256); +} + +interface DogLike { + function bark(bytes32, address, address) external returns (uint256); + function ilks(bytes32) external view returns (address, uint256, uint256, uint256); +} + +contract LockstakeHandler is StdUtils, StdCheats { + Vm vm; + + LockstakeEngine public engine; + GemMock public mkr; + GemMock public ngt; + GemMock public nst; + bytes32 public ilk; + VatLike public vat; + JugLike public jug; + SpotterLike public spot; + DogLike public dog; + LockstakeClipper public clip; + + address public pauseProxy; + address public urn; + address public urnOwner; + address[] public voteDelegates; + address[] public farms; + uint256 public mkrNgtRate; + address public anyone = address(123); + + mapping(bytes32 => uint256) public numCalls; + + uint256 constant RAY = 10 ** 27; + + modifier callAsAnyone() { + vm.startPrank(anyone); + _; + vm.stopPrank(); + } + + modifier callAsUrnOwner() { + vm.startPrank(urnOwner); + _; + vm.stopPrank(); + } + + modifier callAsPauseProxy() { + vm.startPrank(pauseProxy); + _; + vm.stopPrank(); + } + + constructor( + Vm vm_, + address engine_, + address urn_, + address spot_, + address dog_, + address pauseProxy_, + address[] memory voteDelegates_, + address[] memory farms_ + ) { + vm = vm_; + engine = LockstakeEngine(engine_); + mkr = GemMock(address(engine.mkr())); + ngt = GemMock(address(engine.ngt())); + nst = GemMock(address(engine.nst())); + pauseProxy = pauseProxy_; + ilk = engine.ilk(); + vat = VatLike(address(engine.vat())); + jug = JugLike(address(engine.jug())); + spot = SpotterLike(spot_); + dog = DogLike(dog_); + + (address clip_, , , ) = dog.ilks(ilk); + clip = LockstakeClipper(clip_); + urn = urn_; + urnOwner = engine.urnOwners(urn); + mkrNgtRate = engine.mkrNgtRate(); + + vat.hope(address(clip)); + + for (uint256 i = 0; i < voteDelegates_.length ; i++) { + voteDelegates.push(voteDelegates_[i]); + } + voteDelegates.push(address(0)); + + for (uint256 i = 0; i < farms_.length ; i++) { + farms.push(farms_[i]); + } + farms.push(address(0)); + } + + function _rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) { + assembly { + switch x case 0 {switch n case 0 {z := b} default {z := 0}} + default { + switch mod(n, 2) case 0 { z := b } default { z := x } + let half := div(b, 2) // for rounding. + for { n := div(n, 2) } n { n := div(n,2) } { + let xx := mul(x, x) + if iszero(eq(div(xx, x), x)) { revert(0,0) } + let xxRound := add(xx, half) + if lt(xxRound, xx) { revert(0,0) } + x := div(xxRound, b) + if mod(n,2) { + let zx := mul(z, x) + if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } + let zxRound := add(zx, half) + if lt(zxRound, zx) { revert(0,0) } + z := div(zxRound, b) + } + } + } + } + } + + function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) { + // Note: _divup(0,0) will return 0 differing from natural solidity division + unchecked { + z = x != 0 ? ((x - 1) / y) + 1 : 0; + } + } + + function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x < y ? x : y; + } + + function _getRandomVoteDelegate(uint256 voteDelegateIndex) internal view returns (address) { + return voteDelegates[bound(voteDelegateIndex, 0, voteDelegates.length - 1)]; + } + + function _getRandomFarm(uint256 farmIndex) internal view returns (address) { + return farms[bound(farmIndex, 0, farms.length - 1)]; + } + + function _getRandomAuctionId(uint256 auctionIndex) internal view returns (uint256) { + uint256[] memory active = clip.list(); + return active[bound(auctionIndex, 0, active.length - 1)]; + } + + function delegatedTo(address voteDelegate) external view returns (uint256) { + return VoteDelegateLike(voteDelegate).stake(address(engine)); + } + + function sumDelegated() external view returns (uint256 sum) { + for (uint256 i = 0; i < voteDelegates.length; i++) { + if (voteDelegates[i] == address(0)) continue; + sum += VoteDelegateLike(voteDelegates[i]).stake(address(engine)); + } + } + + // note: There is no way to get the amount delegated per urn from the actual unmodified vote delegate contract, + // so we currently just return the total num of voteDelegates that anyone delegated to. + // In practice it means that invariant_delegation_exclusiveness can only work when there is one urn. + function numDelegated() external view returns (uint256 num) { + for (uint256 i = 0; i < voteDelegates.length; i++) { + if (voteDelegates[i] == address(0)) continue; + if (VoteDelegateLike(voteDelegates[i]).stake(address(engine)) > 0) num++; + } + } + + function numStakedForUrn(address urn_) external view returns (uint256 num) { + for (uint256 i = 0; i < farms.length; i++) { + if (farms[i] == address(0)) continue; + if (GemMock(farms[i]).balanceOf(urn_) > 0) num++; + } + } + + function addFarm(uint256 farmIndex) callAsPauseProxy() external { + numCalls["addFarm"]++; + engine.addFarm(_getRandomFarm(farmIndex)); + } + + function selectFarm(uint16 ref, uint256 farmIndex) callAsUrnOwner() external { + numCalls["selectFarm"]++; + engine.selectFarm(urn, _getRandomFarm(farmIndex), ref); + } + + function selectVoteDelegate(uint256 voteDelegateIndex) callAsUrnOwner() external { + numCalls["selectVoteDelegate"]++; + engine.selectVoteDelegate(urn, _getRandomVoteDelegate(voteDelegateIndex)); + } + + function lock(uint256 wad, uint16 ref) external callAsAnyone { + numCalls["lock"]++; + + // wad = bound(wad, 0, uint256(type(int256).max) / 10**18) * 10**18; + (uint256 ink,) = vat.urns(ilk, urn); + (,, uint256 spotPrice,,) = vat.ilks(ilk); + wad = bound(wad, 0, _min( + uint256(type(int256).max), + type(uint256).max / spotPrice - ink + ) / 10**18 + ) * 10**18; + + deal(address(mkr), anyone, wad); + mkr.approve(address(engine), wad); + + engine.lock(urn, wad, ref); + } + + function lockNgt(uint256 ngtWad, uint16 ref) external callAsAnyone { + numCalls["lockNgt"]++; + + // ngtWad = bound(ngtWad, 0, uint256(type(int256).max) / 10**18) * 10**18; + (uint256 ink,) = vat.urns(ilk, urn); + (,, uint256 spotPrice,,) = vat.ilks(ilk); + ngtWad = bound(ngtWad, 0, _min( + uint256(type(int256).max), + _min( + type(uint256).max / spotPrice - ink, + type(uint256).max / mkrNgtRate + ) + ) / 10**18 + ) * 10**18 * mkrNgtRate; + + deal(address(ngt), anyone, ngtWad); + ngt.approve(address(engine), ngtWad); + + engine.lockNgt(urn, ngtWad, ref); + } + + function free(address to, uint256 wad) external callAsUrnOwner() { + numCalls["free"]++; + + if (to == address(engine)) { revert("free-to-engine-unsupported"); } + + (uint256 ink, uint256 art) = vat.urns(ilk, urn); + (, uint256 rate, uint256 spotPrice,,) = vat.ilks(ilk); + wad = bound(wad, 0, ink - _divup(art * rate, spotPrice)); + + engine.free(urn, to, wad); + } + + function freeNgt(address to, uint256 ngtWad) external callAsUrnOwner() { + numCalls["freeNgt"]++; + + (uint256 ink, uint256 art ) = vat.urns(ilk, urn); + (, uint256 rate, uint256 spotPrice,,) = vat.ilks(ilk); + ngtWad = bound(ngtWad, 0, (ink - _divup(art * rate, spotPrice)) * mkrNgtRate); + + engine.freeNgt(urn, to, ngtWad); + } + + function draw(uint256 wad) external callAsUrnOwner() { + numCalls["draw"]++; + + (uint256 ink, uint256 art) = vat.urns(ilk, urn); + (uint256 Art, uint256 rate, uint256 spotPrice,, uint256 dust) = vat.ilks(ilk); + (uint256 duty, uint256 rho) = jug.ilks(ilk); + rate = _rpow(duty, block.timestamp - rho, RAY) * rate / RAY; + if (ink * spotPrice < art * rate) { revert("unsafe-fee-accumulation-or-price-drop"); } + wad = bound(wad, art > 0 ? 0 : dust / RAY, _min( + (ink * spotPrice - art * rate) / RAY, + _min( + uint256(type(int256).max) / RAY, + (type(uint256).max - Art) * rate / RAY + ) + )); + + engine.draw(urn, address(this), wad); + } + + function wipe(uint256 wad) external callAsAnyone { + numCalls["wipe"]++; + + (, uint256 art) = vat.urns(ilk, urn); + (, uint256 rate,,, uint256 dust) = vat.ilks(ilk); + wad = bound(wad, 0, art > 0 ? _min( + (art * rate - dust) / RAY , + uint256(type(int256).max) / RAY + ) + : 0); + + deal(address(nst), anyone, wad); + nst.approve(address(engine), wad); + + engine.wipe(urn, wad); + } + + function dropPriceAndBark() external { + numCalls["dropPriceAndBark"]++; + (uint256 ink, uint256 art) = vat.urns(ilk, urn); + (address pip, uint256 mat) = spot.ilks(ilk); + (, uint256 rate,,,) = vat.ilks(ilk); + + uint256 minCollateralizedPrice = ((art * rate / RAY) * mat / ink) / 10**9; + PipMock(pip).setPrice(minCollateralizedPrice - 1); + spot.poke(ilk); + + dog.bark(ilk, urn, address(0)); + } + + function take(uint256 auctionIndex) external { + numCalls["take"]++; + LockstakeClipper.Sale memory sale; + uint256 auctionId = _getRandomAuctionId(auctionIndex); + (sale.pos, sale.tab, sale.lot, sale.tot, sale.usr, sale.tic, sale.top) = clip.sales(auctionId); + + vm.startPrank(pauseProxy); // we use startPrank as cannot override an ongoing prank with a single vm.prank + vat.suck(address(0), address(this), sale.tab); + vm.stopPrank(); + + clip.take({ + id: auctionId, + amt: sale.lot, + max: type(uint256).max, + who: address(this), + data: "" + }); + } + + function yank(uint256 auctionIndex) external callAsPauseProxy() { + numCalls["yank"]++; + clip.yank(_getRandomAuctionId(auctionIndex)); + } + + function warp(uint256 secs) external { + numCalls["warp"]++; + secs = bound(secs, 0, 365 days); + vm.warp(block.timestamp + secs); + } +} From ca0c577018dd664e2399e7d842364f2e5dac235f Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:02:31 -0300 Subject: [PATCH 083/111] Use solc 0.8.21 + upgrade token-tests and fix warnings due to it + remove direct dss-test dependency (#47) --- .gitmodules | 3 --- deploy/LockstakeDeploy.sol | 2 +- foundry.toml | 2 +- lib/dss-test | 1 - lib/token-tests | 2 +- src/LockstakeClipper.sol | 2 +- src/LockstakeEngine.sol | 2 +- src/LockstakeMkr.sol | 2 +- src/LockstakeUrn.sol | 2 +- src/Multicall.sol | 2 +- test/LockstakeClipper.t.sol | 4 ++-- test/LockstakeEngine-invariants.t.sol | 16 ++++++++-------- test/LockstakeEngine.t.sol | 2 +- test/LockstakeMkr.t.sol | 2 +- test/handlers/LockstakeHandler.sol | 2 +- test/mocks/GemMock.sol | 2 +- test/mocks/LockstakeEngineMock.sol | 2 +- test/mocks/MkrNgtMock.sol | 2 +- test/mocks/NstJoinMock.sol | 2 +- test/mocks/NstMock.sol | 2 +- test/mocks/PipMock.sol | 2 +- test/mocks/StakingRewardsMock.sol | 2 +- test/mocks/VoteDelegateMock.sol | 2 +- 23 files changed, 29 insertions(+), 33 deletions(-) delete mode 160000 lib/dss-test diff --git a/.gitmodules b/.gitmodules index e155df29..33cefb58 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "lib/dss-test"] - path = lib/dss-test - url = https://github.com/makerdao/dss-test [submodule "lib/token-tests"] path = lib/token-tests url = https://github.com/makerdao/token-tests/ diff --git a/deploy/LockstakeDeploy.sol b/deploy/LockstakeDeploy.sol index 2a261a52..d8c42a38 100644 --- a/deploy/LockstakeDeploy.sol +++ b/deploy/LockstakeDeploy.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import { ScriptTools } from "dss-test/ScriptTools.sol"; import { MCD, DssInstance } from "dss-test/MCD.sol"; diff --git a/foundry.toml b/foundry.toml index 2b5ddf3f..39143a1f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ src = "src" out = "out" libs = ["lib"] -solc = "0.8.16" +solc = "0.8.21" optimizer = true optimizer_runs = 200 verbosity = 1 diff --git a/lib/dss-test b/lib/dss-test deleted file mode 160000 index 36ff4adb..00000000 --- a/lib/dss-test +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 36ff4adbcb35760614e0d2df864026991c23d028 diff --git a/lib/token-tests b/lib/token-tests index 159dc32e..6bdd5a38 160000 --- a/lib/token-tests +++ b/lib/token-tests @@ -1 +1 @@ -Subproject commit 159dc32e7df7f03f9bdab3845918fa2b2f4babb9 +Subproject commit 6bdd5a38586970acf27166c3205093fd3450d530 diff --git a/src/LockstakeClipper.sol b/src/LockstakeClipper.sol index d31e705c..d2e61011 100644 --- a/src/LockstakeClipper.sol +++ b/src/LockstakeClipper.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; interface VatLike { function suck(address, address, uint256) external; diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index d6f67908..c6ed1952 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import { LockstakeUrn } from "src/LockstakeUrn.sol"; import { Multicall } from "src/Multicall.sol"; diff --git a/src/LockstakeMkr.sol b/src/LockstakeMkr.sol index 28dac4a1..92087faf 100644 --- a/src/LockstakeMkr.sol +++ b/src/LockstakeMkr.sol @@ -18,7 +18,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; contract LockstakeMkr { mapping (address => uint256) public wards; diff --git a/src/LockstakeUrn.sol b/src/LockstakeUrn.sol index ab8c6ffe..c26e3a5e 100644 --- a/src/LockstakeUrn.sol +++ b/src/LockstakeUrn.sol @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; interface VatLike { function hope(address) external; diff --git a/src/Multicall.sol b/src/Multicall.sol index fbb38114..943213ab 100644 --- a/src/Multicall.sol +++ b/src/Multicall.sol @@ -2,7 +2,7 @@ // Based on https://github.com/Uniswap/v3-periphery/blob/697c2474757ea89fec12a4e6db16a574fe259610/contracts/base/Multicall.sol -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; // Enables calling multiple methods in a single call to the contract abstract contract Multicall { diff --git a/test/LockstakeClipper.t.sol b/test/LockstakeClipper.t.sol index 565fc8be..1d97e1f9 100644 --- a/test/LockstakeClipper.t.sol +++ b/test/LockstakeClipper.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; @@ -357,7 +357,7 @@ contract LockstakeClipperTest is DssTest { assertEq(address(clip.dog()), address(123)); } - function testGetChop() public { + function testGetChop() public view { uint256 chop = dss.dog.chop(ilk); (, uint256 chop2,,) = dss.dog.ilks(ilk); assertEq(chop, chop2); diff --git a/test/LockstakeEngine-invariants.t.sol b/test/LockstakeEngine-invariants.t.sol index b309665a..997c67f5 100644 --- a/test/LockstakeEngine-invariants.t.sol +++ b/test/LockstakeEngine-invariants.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; import { LockstakeEngine } from "src/LockstakeEngine.sol"; @@ -209,20 +209,20 @@ contract LockstakeEngineIntegrationTest is DssTest { targetContract(address(handler)); // invariant tests should fuzz only handler functions } - function invariant_system_mkr_equals_ink() public { + function invariant_system_mkr_equals_ink() public view { (uint256 ink,) = vat.urns(ilk, urn); assertEq(mkr.balanceOf(address(engine)) + handler.sumDelegated() - vat.gem(ilk, address(clip)) - vat.gem(ilk, pauseProxy), ink); } - function invariant_system_mkr_equals_lsmkr_total_supply() public { + function invariant_system_mkr_equals_lsmkr_total_supply() public view { assertEq(mkr.balanceOf(address(engine)) + handler.sumDelegated() - vat.gem(ilk, address(clip)) - vat.gem(ilk, pauseProxy), lsmkr.totalSupply()); } - function invariant_delegation_exclusiveness() public { + function invariant_delegation_exclusiveness() public view { assertLe(handler.numDelegated(), 1); } - function invariant_delegation_all_or_nothing() public { + function invariant_delegation_all_or_nothing() public view { address urnDelegate = engine.urnVoteDelegates(urn); (uint256 ink,) = vat.urns(ilk, urn); @@ -234,11 +234,11 @@ contract LockstakeEngineIntegrationTest is DssTest { } } - function invariant_staking_exclusiveness() public { + function invariant_staking_exclusiveness() public view { assertLe(handler.numStakedForUrn(handler.urn()), 1); } - function invariant_staking_all_or_nothing() public { + function invariant_staking_all_or_nothing() public view { address urnFarm = engine.urnFarms(urn); (uint256 ink,) = vat.urns(ilk, urn); @@ -250,7 +250,7 @@ contract LockstakeEngineIntegrationTest is DssTest { } } - function invariant_no_delegation_or_staking_during_auction() public { + function invariant_no_delegation_or_staking_during_auction() public view { assertTrue( engine.urnAuctions(urn) == 0 || engine.urnVoteDelegates(urn) == address(0) && engine.urnFarms(urn) == address(0) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 9e0eedd8..22f23bc8 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; import "dss-interfaces/Interfaces.sol"; diff --git a/test/LockstakeMkr.t.sol b/test/LockstakeMkr.t.sol index 5e8fee8c..ec712fe9 100644 --- a/test/LockstakeMkr.t.sol +++ b/test/LockstakeMkr.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import "token-tests/TokenChecks.sol"; import { LockstakeMkr } from "src/LockstakeMkr.sol"; diff --git a/test/handlers/LockstakeHandler.sol b/test/handlers/LockstakeHandler.sol index 4eb1fa81..cf3e2edd 100644 --- a/test/handlers/LockstakeHandler.sol +++ b/test/handlers/LockstakeHandler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import "dss-test/DssTest.sol"; diff --git a/test/mocks/GemMock.sol b/test/mocks/GemMock.sol index 789e88a4..853605b7 100644 --- a/test/mocks/GemMock.sol +++ b/test/mocks/GemMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; contract GemMock { mapping (address => uint256) public balanceOf; diff --git a/test/mocks/LockstakeEngineMock.sol b/test/mocks/LockstakeEngineMock.sol index ed7b6a93..41c01fe8 100644 --- a/test/mocks/LockstakeEngineMock.sol +++ b/test/mocks/LockstakeEngineMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; interface VatLike { function slip(bytes32, address, int256) external; diff --git a/test/mocks/MkrNgtMock.sol b/test/mocks/MkrNgtMock.sol index 32cdfc73..a0700e3a 100644 --- a/test/mocks/MkrNgtMock.sol +++ b/test/mocks/MkrNgtMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; interface GemLike { function burn(address, uint256) external; diff --git a/test/mocks/NstJoinMock.sol b/test/mocks/NstJoinMock.sol index 1452ced8..5c3f25f6 100644 --- a/test/mocks/NstJoinMock.sol +++ b/test/mocks/NstJoinMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; import { GemMock } from "test/mocks/GemMock.sol"; diff --git a/test/mocks/NstMock.sol b/test/mocks/NstMock.sol index ac6b3336..a5bf6158 100644 --- a/test/mocks/NstMock.sol +++ b/test/mocks/NstMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; contract NstMock { mapping (address => uint256) public balanceOf; diff --git a/test/mocks/PipMock.sol b/test/mocks/PipMock.sol index a065b0ba..68d90ee8 100644 --- a/test/mocks/PipMock.sol +++ b/test/mocks/PipMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; contract PipMock { uint256 price; diff --git a/test/mocks/StakingRewardsMock.sol b/test/mocks/StakingRewardsMock.sol index fd87a79e..e95e5852 100644 --- a/test/mocks/StakingRewardsMock.sol +++ b/test/mocks/StakingRewardsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; interface GemLike { function transfer(address, uint256) external; diff --git a/test/mocks/VoteDelegateMock.sol b/test/mocks/VoteDelegateMock.sol index 8926f27e..794555ce 100644 --- a/test/mocks/VoteDelegateMock.sol +++ b/test/mocks/VoteDelegateMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.16; +pragma solidity ^0.8.21; interface GemLike { function transfer(address, uint256) external; From 2d9163c38c1bf23b8df363b7a7d9dfb74275af7e Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 3 May 2024 13:50:05 -0300 Subject: [PATCH 084/111] Use real MKR in tests --- test/LockstakeEngine.t.sol | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 22f23bc8..9c207f93 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -25,12 +25,16 @@ interface LineMomLike { function ilks(bytes32) external view returns (uint256); } +interface MkrAuthorityLike { + function rely(address) external; +} + contract LockstakeEngineTest is DssTest { using stdStorage for StdStorage; DssInstance dss; address pauseProxy; - GemMock mkr; + DSTokenAbstract mkr; LockstakeMkr lsmkr; LockstakeEngine engine; LockstakeClipper clip; @@ -86,12 +90,15 @@ contract LockstakeEngineTest is DssTest { pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); pip = MedianAbstract(dss.chainlog.getAddress("PIP_MKR")); - mkr = new GemMock(0); + mkr = DSTokenAbstract(dss.chainlog.getAddress("MCD_GOV")); nst = new NstMock(); nstJoin = new NstJoinMock(address(dss.vat), address(nst)); rTok = new GemMock(0); ngt = new GemMock(0); mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 24_000); + vm.startPrank(pauseProxy); + MkrAuthorityLike(mkr.authority()).rely(address(mkrNgt)); + vm.stopPrank(); voteDelegateFactory = new VoteDelegateFactoryMock(address(mkr)); voter = address(123); From 05ee579be3a33d8fc5bc86ba26af9f51bbdf7092 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 13 May 2024 16:43:14 -0300 Subject: [PATCH 085/111] Check staking token for added farms in scripts (#49) --- deploy/LockstakeInit.sol | 5 +++++ test/LockstakeEngine.t.sol | 36 ++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index ebf26de0..a171cd26 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -76,6 +76,10 @@ interface ClipperMomLike { function setPriceTolerance(address, uint256) external; } +interface StakingRewardsLike { + function stakingToken() external view returns (address); +} + interface IlkRegistryLike { function put( bytes32 _ilk, @@ -201,6 +205,7 @@ library LockstakeInit { engine.file("jug", address(dss.jug)); for (uint256 i = 0; i < cfg.farms.length; i++) { + require(StakingRewardsLike(cfg.farms[i]).stakingToken() == lockstakeInstance.lsmkr, "Farm staking token mismatch"); engine.addFarm(cfg.farms[i]); } engine.rely(address(clipper)); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 9c207f93..3cd8088b 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -45,6 +45,7 @@ contract LockstakeEngineTest is DssTest { NstJoinMock nstJoin; GemMock rTok; StakingRewardsMock farm; + StakingRewardsMock farm2; MkrNgtMock mkrNgt; GemMock ngt; bytes32 ilk = "LSE"; @@ -123,10 +124,11 @@ contract LockstakeEngineTest is DssTest { calc = instance.clipperCalc; lsmkr = LockstakeMkr(instance.lsmkr); farm = new StakingRewardsMock(address(rTok), address(lsmkr)); + farm2 = new StakingRewardsMock(address(rTok), address(lsmkr)); address[] memory farms = new address[](2); farms[0] = address(farm); - farms[1] = address(1111111); // Just to test that more than 1 farm is correctly whitelisted + farms[1] = address(farm2); cfg = LockstakeConfig({ ilk: ilk, @@ -274,7 +276,7 @@ contract LockstakeEngineTest is DssTest { assertEq(dss.dog.wards(address(clip)), 1); assertEq(address(engine.jug()), address(dss.jug)); assertTrue(engine.farms(address(farm)) == LockstakeEngine.FarmStatus.ACTIVE); - assertTrue(engine.farms(address(1111111)) == LockstakeEngine.FarmStatus.ACTIVE); + assertTrue(engine.farms(address(farm2)) == LockstakeEngine.FarmStatus.ACTIVE); assertEq(engine.wards(address(clip)), 1); assertEq(clip.buf(), 1.25 * 10**27); assertEq(clip.tail(), 3600); @@ -330,6 +332,8 @@ contract LockstakeEngineTest is DssTest { cfg.tau = 0; cfg.cut = 10**27; cfg.step = 1; + cfg.farms[0] = address(new StakingRewardsMock(address(rTok), address(instance2.lsmkr))); + cfg.farms[1] = address(new StakingRewardsMock(address(rTok), address(instance2.lsmkr))); vm.startPrank(pauseProxy); LockstakeInit.initLockstake(dss, instance2, cfg); vm.stopPrank(); @@ -546,34 +550,34 @@ contract LockstakeEngineTest is DssTest { } function testSelectFarm() public { - StakingRewardsMock farm2 = new StakingRewardsMock(address(rTok), address(lsmkr)); + StakingRewardsMock farm3 = new StakingRewardsMock(address(rTok), address(lsmkr)); address urn = engine.open(0); assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/farm-unsupported-or-deleted"); - engine.selectFarm(urn, address(farm2), 5); - vm.prank(pauseProxy); engine.addFarm(address(farm2)); + engine.selectFarm(urn, address(farm3), 5); + vm.prank(pauseProxy); engine.addFarm(address(farm3)); vm.expectEmit(true, true, true, true); - emit SelectFarm(urn, address(farm2), 5); - engine.selectFarm(urn, address(farm2), 5); - assertEq(engine.urnFarms(urn), address(farm2)); + emit SelectFarm(urn, address(farm3), 5); + engine.selectFarm(urn, address(farm3), 5); + assertEq(engine.urnFarms(urn), address(farm3)); vm.expectRevert("LockstakeEngine/same-farm"); - engine.selectFarm(urn, address(farm2), 5); + engine.selectFarm(urn, address(farm3), 5); assertEq(lsmkr.balanceOf(address(farm)), 0); - assertEq(lsmkr.balanceOf(address(farm2)), 0); + assertEq(lsmkr.balanceOf(address(farm3)), 0); mkr.approve(address(engine), 100_000 * 10**18); engine.lock(urn, 100_000 * 10**18, 5); assertEq(lsmkr.balanceOf(address(farm)), 0); - assertEq(lsmkr.balanceOf(address(farm2)), 100_000 * 10**18); + assertEq(lsmkr.balanceOf(address(farm3)), 100_000 * 10**18); assertEq(farm.balanceOf(urn), 0); - assertEq(farm2.balanceOf(urn), 100_000 * 10**18); + assertEq(farm3.balanceOf(urn), 100_000 * 10**18); engine.selectFarm(urn, address(farm), 5); assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); - assertEq(lsmkr.balanceOf(address(farm2)), 0); + assertEq(lsmkr.balanceOf(address(farm3)), 0); assertEq(farm.balanceOf(urn), 100_000 * 10**18); - assertEq(farm2.balanceOf(urn), 0); - vm.prank(pauseProxy); engine.delFarm(address(farm2)); + assertEq(farm3.balanceOf(urn), 0); + vm.prank(pauseProxy); engine.delFarm(address(farm3)); vm.expectRevert("LockstakeEngine/farm-unsupported-or-deleted"); - engine.selectFarm(urn, address(farm2), 5); + engine.selectFarm(urn, address(farm3), 5); } function _testLockFree(bool withDelegate, bool withStaking) internal { From d8b2aeb0fa266779021a5087f33ed5c5a0c5ff12 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 20 May 2024 10:15:42 -0300 Subject: [PATCH 086/111] Add CS audit --- ...-ChainSecurity_MakerDAO_Lockstake_audit.pdf | Bin 0 -> 551584 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/20240508-ChainSecurity_MakerDAO_Lockstake_audit.pdf diff --git a/audit/20240508-ChainSecurity_MakerDAO_Lockstake_audit.pdf b/audit/20240508-ChainSecurity_MakerDAO_Lockstake_audit.pdf new file mode 100644 index 0000000000000000000000000000000000000000..531ac168c7b0047674a9c1b2743c1f5c68b6b5bf GIT binary patch literal 551584 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/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 BQos+hsnJmxqum2oPaLFiYrS`YuDQ!q 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�mFb+%3x^~=7A1c1mL}6>)!}3fZo`#0YY$Y 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

6F6G+|*JPkZ zoFO=UKW<>x9}nllmO;0RR~MX_-{@}=$);f* zb)K4EsZ*JuAyLr=cnWzlP5D$}wS;G$ttz-s2Gm5^-iN1dxkOBgUg2a@?{$KB?H4J8 zSZ>6dmX^n8B~~}?Sq#PIr^dxN=78b0^5DGNuyJ~qSle63vmX`^N9edI82PTqISR>v z|3u0f?kfckk-^nTyY<0) zQaqxMfpysi&qUot;w>!79!pB? zJ$zytby@Vrg-$L>v7Aydr1q)eaSMOYv%=cvT{$>8@*!Vd@>{Pia;cVpAWC}$VK3Q z6HM9joSA1YmxW))KcRa!56fxoTaB#41dTe^5a&nio`-M^ZGUSsk6N)~R`$|{!tyAY zS=y4ezC>a_L8CRjI3A5>uPm2`j>gI$+hlG!ufK0a_L>pS+v|_E+^WDg#h{IeD6~BY z=<90vu#`7NaY+Zhp17U0E&-Xg9clI&E_sX(iv6N^xk;v-b{%JR7~#tqW+kdg6j*en zFlUeDa^?jw`5T(fNkt*eMx}flD9q(q<2l?rWQZEnbK0|V;bssSqSF$-V+eUK&a7F=q(n``INnAAo@Midd9B5}mtwxKP8>zNf z1K#-wBY&)*l&MP_R&Tzg4Iq_E8%+@m;%yNR!Eee;M^c{W87j(oQ*v64#1MuM95~;n z^quv{W7#3V(V$=xDQS&NaL{EGZSNu#ow1$>uv-k#?LB3O@4mLuNz z;}p&mxB|V)7U)#e%H5_78W1jEGYg?6Vao4wmI@NU82|Vl`4xL`EZpTZj3k{wy_D!% z${1w5gsgz65OoHdvL5%Nj6kK-O zY4Ra)-FIlctWi&RxczrYw=jt-yC93(z#94&A4B7fB%kW51=_lPg3(n!J|2wpwC$(BAQ(P@>UIr0XjR3%i%e z5zT`gDiJB+={m%?nLojqV`&0@e;iP5n76OERk&V#d;V^v0g1??5jfKV(odTR_857- zqpUMnUD`%YSlviuNyUAN#LmeNM$)X&68Yg#6ixa^ynF{GZcwF$a-|kYE~MfE8QK`5 zt{uRj+(1#g&h{+E@~-IXl(w0607ZtT*edL^t2mJSks-Np<&HDJRoq}$kj1P~LDI!6 zp(zQ^FrrVE#t(P_?beF_>H9QS@cT?W;GcfkaZnb~s`h#wqH%$|QSGi=Ewglu9K`YCE<`{3!hZOClU*GRB2$Hz z#iP-QbV)`rA3NvurOgkKrW=Mebhl4V;T<*bLaQe*-$nq9ssN^1A!f_BFYe9+!m~Sz zW|rw%^$we#gZYngN8Ulp3N9y+z83<2e}SW?ZKes%j2I6#=_kI5L}oHMDz0s5+=4+F zNhE$H(r(IPm6AKQ`r#zyyZ6XGS3)cg6Nh~^i|c9~m;F+A6May>DHDOcul{uF0BZ=l zTgt`jyQZPOob}4{exLoh3M{gYev6rjH_#`HANk)o?luZ3Di&X2uVisGaBP@28-H5E zpyJxA!at6l;l5b0YmnTecFz|~-{3rX960+}F&=K>HGqydAMFV{FyuFTKrN-gYURB+ zd^kOdIIEEF6O0n~%^1}(rOnk9c2S^`f}CQ=AnIE3+SFfkP#Ks9QGM8wJ}R?5BbH}z zvAtLyY+$%%k-hh(IHtNVY(IWWYnU#GvVaVM14@=G%EgLUv>Z-o8ja=T(p;eM7ND|u zDIm__GP3!4UbApWh|jPyMD{ag+o%<7FgN)pib{MlsLNqRaS<@sIFr5Ld7pQ->yW7T zXtp+*A7C490~j%IYRKsEt_c_~fFhHW&Rvi{qVdhC?lS zIhUk?_0_wE_sAqan!I)m?danhT`oH^P412u-n=r`B}^Y#>%$%!ZSr*bqBi+maG@ex zIPMno5p>gp(de^f;rU0Y_V6N!9Ucq+OV&YA{jowkM z=jk7d`xrcOt?a=lx}{z?e|jIqYo@j}a=1Jerbl5caZ#34ZYiQ@>Sm|U`n3JIi!-U3 z)yz$IaFlabX(5#bXxU#o<0?pbn8xsnka360`jmJ7+X5VIJ+nPjf0B1{Qu>4GDxE|K z1A+%EZ<9%cC^fh0aLy;k4iW5zD6v;0)LXQ11a1Ht$OY7T`yW@SG+?9KdYt2$ZoYVC z?Ph7VWcM2$D4RO@t={n?yX+$&;@PZGi*?H}D75=Ix-i&xDh{9n>K=~&!>Is)R+yE6v-AFY;({> zNC~N39@4qSnjVUa8_77t-CQwtOGuMk1{va@N+JeYoX1alj_Ln-A!D;KH3|v{lYk>8 znU_=_G6;pFW^)SY{DfMBvSPB9rm2QTDR?aPJWaOBa4pUu#!_LX+F;r4P0O-WN~9;I zC1aU&$(NIc`D)zFu3Ru{4b{*U$YfoKq{L3`9MA&n_io-@=wgG-Rn~8H*4s_}9crT# zS*w+lqia;P>2R?oBeUL5o)9?O9opofA6agUJAJbOdM$2{38IVvk+y>whGO)C??UZe z3K`q>K+Z4SX9J>qh-&EFt~F$0yqm&1 zEqB(MSH@$4-aUE&w5;yl62nCeF0xiio}ZU(hmKk5s}dhVZ1}gne}DJZwKV;$4gD>+D9dwG7Ck{Iis@KV%ro`@^su7z&(PBL5-Ie@@pXbu8? z_X`Y54T-0JNp5dnnCbz+IvB zU;T29PWTZ%>M`qHfnFPJKMoW?n$qqeekcvuo$)-T%@hi~#rD_`L3VEujv$;o-4xr| zscUd4^98#*zK3FQETz2zlEu4zJ(yj`;6KC|*7q%Zhbl0$cvq6|t2>#fqN4ApSJt3# z=)>o#O==HOpUgW9r!r)1#L<(u=G3X$1f;}IXPZ6;{4--y_Q~LqMpTx^{ZYZ|GGe_k zLl*C>SWOfetteV>nokGrDh@F^*UajH_9ISuaNr?~zAX(I4UX#>fem?LgD&wuH~R3?S6C zLoHN}KBm^#ubRwMecFbj)@wh<JZ*O1%D-Bd|V=8~TaGmrdUq!cf9NkMYX^h>6c( z+`2@OdvukA6*v)qWn9s#dq7X1iT^On*JXJpV%&`9|L43;@8UM3+`3x^z zC2Dd>6N@M$%0rt{thJeYt(|Uukm}0lI{T=tDVJaVC`{vwA7VopVvdU{s%UZI*xQp= zN6O_WBY@CPo9^w zEk!pL_z~2t=&W%q9wu9Z(}cG*S8Ts@cg@yUk#72x=H30hu``=HSe>Kt$hhS7V7q6% zC#;g8a54WTv0!+_di1wooV3b9+}+HgPo4VmlFSBXQ=isv7rubA8}zQB97o@D(R{h_ z-A^dP>e8Si`{ZWTsk5-Cq5V;3jR3&CuL*wNbrxf|if<=usBsm=3p>$Wf?LUMz(owa zH_+3AB+!H?KEO*2sF>UZGLo6}9(1s9RDt5b=4+1DWr#3^QQi@Y@{TJiBH@qY z1crCHb=>v+k z5YZgtTB$Dz^V47Qr(7K;cz@2pQFDu)D?ohxgJQvNTZ4FW2QBJqd-%1g;u2rvn@n6u ztMtY|B`(}MC~e1ttTN%dnmj3fw8qWtcl>Tlt9&!p&Mfd7(?3W^*54~#?qL%%pTBjB znNzT;`p7oDFr6ofE(BidMVR+P21x+jdWFF$x1}s{)^p&V{M5Zhfc^RbJm^DXv&gI2 zZ+d5Q(Fm5L7vskAbfObP$)9hQvF`2TtL{a|{e7d*`fBujzB{t77nWtU^l-H8SLQcZrCdoLbU*)&l{hs)Cfp_3JSES z;>4bEF?8EQ=tm{UL>}VckNy}BS-f#z(JEFV~vHPDB9sQ8m~ zCi#RZV1SQlyNnoTlcO;id>QJ)t#`UG?>QY~(W!9y@XvQZD#INhJ?(>C* zxTb8ePv}i|t@e?c!Iaw8_5-P&)ORBxGE|qr+V?Yo2kx%Nt$cE_3lXeTqkgw z55{$i*?H{*-mJJqyYD&(Blo!GqV(g*#VOaG(d>p=3VXA?^#YG<#=udR#=dBn;r_QQ&+Mymrq><)jtSc(sMPIRo~2fwkDKk2|n zx(*~Et-JZriDb-nB0oi6yHJD~J=l)sa zjwWf6G0Dm*p6&=I$)JiVy>m@h9pqKpjeJ`^hw8Jl`e51$C*!!L#wPAG8}y=&`$jwu4jXk+=6r7MRsT{l=C)b)j`j9leP~H%`Q?GR z977faDZ?h%K<5Z~<*wz*)IrmswEDP^gOZig{FELTLY>2Eos7QY2WDo6SM*6w3H}!v z48@UdUIjAej{=z&aS*U`XDg9fVjhNd9g(Sz8eIs+1&%PLsF_&@Bbqq9W^B-VS?;36hjpU6ME4nL@`4lBR99AT?-Tg}C$*}P-e5;+B?QV$ZMF%C%=dg9Y#1GXtHU(B z#!gb%SnIVw+12H7w%nwWS0Fb?b0-l|nx+}x&*l;Kx}SN$3#w&d*k>vyd;xO&0UTr= z3qPc;zaqqI-4(S^%iAos$Q@_&mTs<0V})F+N;2foPjJG-NRfdK_>1CkSQ8Em6lT(L zfZ&*Sl5}q_ES#?>qfoa+Ib&{u{*SkM3BAxhw-lvoQ%%yBF5sBByJgt!wOOyX_Wj!) zW|r;PH$IrsI0}P;z#?Q>I)2>rx(Xo}@3ZY_91YJ@A>f@z;Ias(yUd=Txh|bvk{a<1 z4$|yw9BVVK2{aHGH&}vbVLQn2jxX(-FOF}%?*1^(}+HF!M{s|3{ z5A8x^AS$iR@iig_7+q7HMxyCD(W4QPch~%RN!f)7h?apjFVcfuyIqa0K#jFrxjfC4 z4U7tSDdI42iWJ&A;^4_q&@z(KhzhxuI70N$n^3!9q>CtQdn*I0-1Qv@)ZiA=s?rlV zNFj4bb(gJ+IV;_50nO>;S)RZ+Kk|1O%kvPtLxsty{*#C4zquIzF!zGQ{h4q3^Y*`D-F|^?01D{O%3KUDPQ?E;>-G$~Jwt0R zB-?++y8Q&*0GWSEV==wxkNqOt{wACCbN}D@A~XMm!g*l?r12NU;x|?LbHU-!u+}g()BDRE|BNyH zu8qsaTHnkPkCY6LlE+d<<9T)B)v(sVBjshGp`xLqq6a9p(a|%}GmukJkpTYxzV11X zGO3NJmAQ_Vfv$m$wlW#ul(3G29bnb6dMOXky#J~w03W4oqXk&xNTqbl%`B|}y~LB? z<;N2-(6rRBbin(FXJ=W{(QwB9e8Qt%kE3hUOHv>e924C%$~P&|F-)Q0sZIQ zKRVCZ^m$ABue*BByHR+(+zlp`q=|J=UwRfh^ zZ6sHM|E|BHzDx}45FwyYxIB)Hkfm;0WA!njy1g;0jR_C{NmwAj#vwN6uixK$c~({x z0Fvr1`9hj3{=q}?|90fk>oKte7~803MgCy$6S`&gd8t* zKmLCd3|s$EPhOuXEEHS{$5VhF1IcX8p-FmW>VY&EciR2r&g6Q9h$pu|vcR=KvLN%g zW$sYJol&qHM=!k#dS?`OZUr{;^44O3>fo{GdC~6r9l5LkckWF`7uFd5YC6uZWO;hm zb;(Qls25q$SWi;_(nHjHp!Y?$riZOxC;}Ybb1cqSdU^!syZN9(0`rayy0yn?DQ|Ls zFBHYy{WyA@2k~8x^WG!4Vzh!K?Ec}n9Scex0W41`-TOgr7{S0|tqUEtNh0pzA4G=} zk@uBsROy?NZ@t7y!F0=d%zEH~-08<343u6OD6#D2fRfkpgbszcJy1MxD1*V`%86j;$vHNW zig)s*9;i}@1S0~z1bXpaexVqlBt_98u!L{?`vR?5mSGVKVICItWJ~v?EG}6Du@}k{ z2gec(|FiN^4P*{N;jOG%aa#$1{-t|T0O(%{=Igc<{p(;9Afo`)aSG%hbSMP%TV*Et ztv=h8p%n|69$NO69EX-y8%hF$Bc&F0HP9*qAWb;)UcL;3A%IM2LO>?{O9@R$Sj;=J zszxcGs3V!f!6@RHPE@j|ZP|wXWbmYGIBf`L85riC6C2lS2yOb#g7h`Nf1dYW$p7J9;prh!Q4GkukgJA7O80(>j4Qk>Ai z`b>F@E<@j}mrMyqvfNqeQt072kQ%uo4|0de0wgQ)nCg`~soWq?*AiKaK$apkBT)0X z3=ScXo`wQWX-K#(s;=l4f!ID6+jL6~G!ENpA%ajH3D$Pgj?Y*r#NDDA+|xZC$Gvj_ zg%OwKFBK>Cl$8?dU-~SZw?0#-C}Js^d?UZrFBE2qd*K)Qf}p>Z^s-z}^(27wx^ykb zsU@!3xnsQ!1cqujj5!F9{Lu0LAYbav0#x+6>88RSb)yL^668=3Cg6|Wpa2yetZ&w5 z`mF*}$+!MYSD??7VcnK5SuD?1bTv&pMFTtGl38iRUNZa1;#*nVt0=U&gMJOX)#TB{ zqTYBYE1v0Y1C#@h5&#=WmBCOa&(`jKFsc*Xy3I1H?%?88)$4YfvPzd8muw zG!(Idh(Qr27ble0@@)&x2K{d&%WXo6Z{Z#4)&f8p?F1W@1%MStJs)wT zP85~A+bn$s0Q3$4;LjeJgWRCEUH6_`bltn6Mj-x5NAwH*g?_6)Q@H8J08qs|{ht85 z1fXRtuUjYe)&j3P?6-$r<2As`%hG#_J+s2ZKiP84psi5i(gS8&q$`$Kw%d90|vL=H<1u2|*NCXS#Ot@vJt5wHEp z=*7}Q(Q^wt_mKx*CN%20b5OC87TMr`UEoo}?QbNe!u(_XO4hgHOy=FL`L=86O4lB> z#0H2vsY{52{0rCYTGg{_m4dF7WxDoqcGcmlo?YYmQvYJ)>&f+r<55;jheHmjD z)5Wqy9U1-K=}ngX={@c)+~;}r{*o2Yj_v*1?5p?BetJekgZxKFhQHe&lJtz4-bi7b zxMoZ*d%b@4ANR zJb6p($FSY+6mE^L#0C6vyPa>hH`EmJi!Bd3uvnVXxwX_TR>Ygb1Nxoz=-_T}p!)4} z)9uimuve&YS3thL%y!rF?CpB`al4y*oQM6C#x?I}K;OUo39HH5z5EQXX<(f5dUQ<=5}8GxFE+r)Lib_r?8j zAdk%yg#f8`5ku%VN3H{Yg4AM0YY&yHQ1}eQR2cV4Fs6HE(YcsSXA^+ky&PTiCbNEj z(3{N1ooVM{bPUE}iY5@3Ml?e>Ui*JM%OrlmvMd|PfO8g9Nw`}+9K>Z;0zc`n%R}7F zjSNi6*|C*LMMwqc%q8h{&DD7EH9SBsOFPeplyS@2z0P>lDKc(%pE7KltE?qN3i|J9 zRP)CB5|}CSlujIDjxuItheAgzTXqYFi9=58QJ0pRo8{8oQ-3AP#W=>@mUqLeu;mE< z&05_o%f9-VFvs2E(>y!h-#}gupkZ&+#smCPv39f&3A0SNM2P;Jo#vQ zZ1(NqX0g)%vTZh5&9eP=p25#e@RnOf^S0drp1$X?o9x)0(JYh${8Woc4u1khm6w*a z3U>W|zPVYf)`ZxdfXZRQ@~dj+5A(W}^vCqk6_r;ox!dvEjGtEkEbI=^@d=09XPaLy zRHvLl=o@DH414UJx<{&OK8ZFB*rOEPTYe&5@bt}wX+Ohe^HVpA{_rHyG-R`H zRtQ=*dR-?=H*p1dJW&c$euj+(B76jMRd#sn?HsYoZ`Zr|2{>!#`3zeNl6CFYL}jcM zrDCpJJD``hM;h4AMXntHkubRc598~D%x)eTx!*4Go;GcU{|TKr6WxOMdDZ)9Em_sc z4>8PW5-Y(OVZqTjBPNHHFsOo+{h_+&a<}x-e3aQ06@wHBy31<>HLt*-$J4hel#iWk zA_|JAMIYyz*Dt>#*4V3^{H<4_j1Wj!lBjWR6E*w@^B#WlAxAiSJ)a_oZ6?dbe?v;# z;Oym}-6cCKFRik>f@rAzm>V?Ov=I{0zoN%;ioj{SS$R- z4jpUSA@QYnlxfGwkeEeAg%-S292NB#?d-e#CYyV4e{013 z>~^#Mw3v~nFj=`Bfvha2J?@Qiaw+(`wYI{t$d6kS>A%gU@{}vJSY>Not+U7gF&HgL zSs>aTj|w6-W2Vk#BLl*(cCUCb6n=K3(4$mTPU z-r;$vo>`7{mZ&C0r`B}6lv9B4Wp~#LGB7st9q&B7 zwIbK;`i?vhQ5?35oBeV(Sl} zRFwtFnru$bRHu5*iv){)meW(cZnf}sW_t?UJr1ayeJ#|2w}%_&5zxNN^TQ1&V6Gzg zr$3r-n-o{MMjWGG8KmWUadmylNutxM1F=c~MeGoc2~i#vUF^+5at=_yKh0$|x~@Nm zYXCa$7RuS7gyns5Va!e8@oav(T;G?-&-{n+nsSyNy0J*rf-hO78?O8+00BXU;ZbwRb$}9u=A|PMf%+D^?KLb9&w&_GN4Kr?u9D@*p z?9+ZJ$g2cpG2bded*$0;q(NB)&BcC~&DN`b74C4moIuRX_4tIbVwv3l?EMCI#JY4f zP%7b3l3aEQ#&tSJd3!UU+QaUkPg4m;J1!xih6J+|$;8`ccMCRrCH6AmF+e~Voa1`t zLO)k_crz!DZ>DyL=Zc$)?RL+;EZ__@OGtM{klVN*v|8uCUf;>j?z8I&lU>Z`t86lx z0h#T5*3SOM{G81-`E*Lcp;L2N)K5%d7Aj|TIiJriCR6yzT235J53pg?TUqvW?%}Ct z)XWGAoF1AE0QS|&bp;Z1xaSVl8;mGUx^cI?b-sY3ZRT(m1f0$G1i=cMtDmoaK}8@u z^#f4fPByxh1XY#uMJWPa!Plt{?*o_98$&C*uc|QS-9|5M)K80O14nmQJK&1~8;po^Bby1z^M;PSg?@PYegB~?+;E~vuZqez8 z*#d?vlNgvJ?4^VYiJJy{Lw`ITT9;Rl&m5!7T4(2Tkr)x^)5AY2|F{ay z5q)i>jn!e}RHHe=J1wF&|2r`{f46XZ_vdGALX_?Raxtj&}z`v@ICC2zt%khSJQ0VIibYfR*ifQiiKN#a^zI7Di z$YaKsr}1JXqwYxaC;tWGT-AsOUuqF*fGNX?ne_wkKnBImVOaX_KN{o4 z$1x6XA_kd_mT?SDF##)8VrhdudkilO1HpSSJZu58sit25*aK1#?bb>p1@TTu@VE=Q6~v`4en1> zdffD_C*K4-Wx1CX^+O~SmcBeFUq&PA?Xm97f`160?XnJ_u0WPj|R0)E=@G zAGI^$%vV=b{}YGDa~$N;B`$dJa`WRU86u8pc;^U_n&PFMUW;=^T}^Y0;`mOlH|zb? z^%|dvF3kjQdc(a(0!NC&>9A!7m0DsoB+udq7m5$TH!Fv`UF_D>S-imI=zd;87RkgKSXbU`J;hSDGBLj9 z#m1Sun_S@Ur#>b2J+au~hZK*RMLuy(Zq=EE(`Ek2Isqf}Qf$7v#^WRosM7Q;Vgke$ zs@|qkocRMJ0SD+>EFc2Oi;uz`$@aQ&o*u!=UBaWDFpWfTX_+wM(zvwDbtdED zvU@QqCPhK&Ri~KDFNekDwCKzyjWBd)^w9hyFHqx6i{N~qst6Kh zX{l^Gy(Ug_x(cocM+VC@j9q@2r%UNnlx_Zq2HPYnCukrZ2S z;CC3++HR&VygKq~xu974rVTE+eU}G(wYs7#e)IR=d_3^uA2zGXFTWwmSi%mL@i!LM zjn}IO4{+w^hhsnmjZLVi1}ciee@mctDFDyluONJcA)*nTh#(jn{=oHF4Zb$7s{?yYj zMN;1joE;`K_9dn-Lo6i{c)1{oo!Hyi=DyVZRc|Z*IG>wR}C<+^|QYZG|Y@kYSO%e)%RlR~yW9&Bt>M^VfJ?ftP!k zHMj`fIDyf1xwh{3YH_WL2R?9~dcxn#2~WWGc!K&BbU;rZuT{eK$Jz75By55A9fE{F z%ArIvo>RsFAsgjcIcvSQt%v2Mlaq_6oR5J?P#2b&8poRx#@d|$nNeKa!x;%g&Bs@XI89(4cMxO) z2_1|G9gBagw@C0J&Z}xFh~V%iYn_0b$n;-QVF9d|eZE8zpB70R>R>$Ftrb^)bXCqe z-R}4^`L$?nS0Aq6WOvTlW-kS0#mUf+zhnn<8xAy>zKt8^aL?@rWMncu|kg7`loGCJ8sG)8q6xnEbO~fb~_G>4Z zkp3A#joKy~-msKDeuGi-Aq>Z?v1X9PFNNy zOy#YCEE{j0rVHUUWPMsmON9ho?7wE}>=8dqTgxJt{XoA-HrsDVH6qgr2U4;;Y0Sa# z82sYJED=AWcNFUT@jb#0vI{bU5J}c|Bxhx`1|tR2$XN4@@DXyiY(5d5;8#pj`{>7nPx_rlI~;oWOVnlukN!Rhwm=_iGD1v!l)G zUvb@u15SOv0=mdH;Il%-s)9Ue)23%9*aQaDMsusI6`YO_=;sZxonASP#{~}{7X_mO zu~Lt9|4@jBJHU&ju#G>7v*>X}0;4RVi*YK+eomEa(@ShHN=#S|bppXyQfjakhlKd0 z2q%NTz1r1{|3k+8Jc|%JV7pu_Zh{=Oqm%L?Zc#)oKeTAl$vuH=maah?v#qtnwZIWc zEO#CEsDR#W<8U}pk}0yDuajVjGrdEHio5(y6xB) zrMc9qq8x(Gxs)<<+w4WgGAI;rLp>6(@fs0zN+^q8$Z>?~qq;i_MlH4KQ{Brk(g#ti z&w@~e-^$YvinfVT)2U=VsM4ytMT00+N2_EoM!C8Z`$P5;8P@VX6)A}_0?!nERM${m zgfwW&9Z0+c=ZZqILqA!mJ>Cp=q`{yR^dxXpV;19AtBCo5Vn&EXOqEF&51}r7oc->s zYmuSAknRQ~UNht0z;1$ulH6RUJp_3}m^GYV=pwXrcYkYlOjVtk6QMmrw=R$D2URyz zK))4nQDJx^22eSk-8F@I%0*R5u%mu@r`sc|PUyTa2s=MAfb3nUaa!RvA z-S>so$C1{+6 zgD$n&QX)et0cM4!FtSp~< zbxpErTMD)Tz_>{@`6o9 zsy4A^PfM(S)x1?tOq6}rn!dEUD+&KofMac<){(OYOqN>lMhdJ5ggBz~RdoQpFh6T& zf1%(L3JLgh%ho1DHUj`4C&h-0PHez1UGIf73Q-ry14zvF?5$kDt0k%q_fWRHoL}z5 z0v@9Swy9GVXDJB)Kjmgc`4(I0oLSR+6&l*^fOtkQjyMZswdoDblv*`$<3p2WiO8!asfa9pz_z5=>COR# zMsAB%O7`$ z^5S1^lIQpad*@9C%7Jkt$sFd3ZzJ+8YVUq!G?M#>LvpOf`6lG6=|ow<eLYu-jSL+}YYg)UD^{`uv27E5hK zBspia5n3h`0d*LGRm!z_%ZaP^HIW_cwkKG3bIE+SUhi5(<>-L!s3}h6h!KwXmd8aa z$99q@T6f*uElX;;Kt?X}Cv7Me27HT6UW8n) z{@Nsy!P-9x8Z7hDUqMW06pDE?Li|P^XivICKD6jUZSt%s;$6YQk1V-Q0-R z$CziN>Nr(c$clnA5|BiHuH~8_q6qE2KBovAqPhGy*`0}$P2pDx56*EGTSZ&%i#stR z_>2g@G1XtA8F@kba=HJxc2?09m>D)LaZQ!UQRb~00Cy-+dP`clak3_>Lm&W`7NnMW z8Xg5LKqlx*Rvpf!v^=gD7a8-(Y$0`C z>$Lag3qaPX!|(bFK#Ej|hI00%U#>1eOw*re`#w=zOi|mS#4C<;K5tJ=_^^Dc#XPW1 zsFN^UP~9d1t8X2>?Qbq^Rom zE_4+4zNGX@K%GX~D(Rct2-6iY8KRM{sVF+2mauKY<*l(2t4F&6u=VP|f;{~-9N)PB z5v1|%p;ocb=^j^dB5bG=kXo+p>RoY z@P%le+;PlJl5{lrgibEfI`yMhUJZu`A=qz;-%`R7BpDp8MtDI=yn|L!?A`hUN(4Y+ zRE(wGztUKBRe^AdZ$d~_Boxt)*@NZUF$BjZg6e&o$_xjC!&>r?ewHr2{XzP|XYuX! zy`=FZ->!;;mN^~5g&)MguE!L~>G!nwsAW?hs78TKK?mG^wLnzg6I1lGMv z{5t8pjw9LUfsd>3ACylq$*N1Bj9*q2^#D8p7o?==A&bc%%IrxjnS=#rMvzI2HeqTDK|Y$=Wl<Epu(w~S9 zQ^1o~RDu*)Is@{QOnk0TYv3UYF5;OG@p=2IEksv5>LN+kpm!^18T0qMO(3(RCIHSWN%oFEb9g4^@O$0IE4JS&YArWmkGUpmRZKg>}fLvxv> zjnj0Jx*T!DqIc`tv;8e#3dvv6opLMHcLb?c_u2=i zFPyKTj$eKE(n8xXbQfX-w}7mli3q-;gw=LSzhgd18V&_9gtXag31jVq&V^s1e)E0l zI!3hg`w70N?>Fn8jR7>WWmM6v(WY1ohyIvqZCC|Yxc=7EXg5KFLAm)H5w%Y=s=ITf zM|pQFo!^gmie5%>3l-LWev3S88CsB7RY3b`zupjqYC7H7Q?-efi~lxqlr?5YhiiTE zzxWB0k?9Vw_Jn(*5cZf5Y3Gz^_xs}$Bwx=R46%%*2n#pwBQu1_|DaYT0R7D{1TNuv z65b#_bdi;@=xZiAQk2|04J$5P6tZ+i1LxQJ8Z+8#Lu0L%LCgs+l0YpI$~9j7QhF8n zL;6D)j#TtKe6rCBsk}}1Rf_bfP0nx4ny8fw5EN$3X;>*gw8S$m1VDZ6XWQL974)pa zv;DU~yKNw*TNN=-J9=hF6rg~hv};wpVUG)v02-w9S1;eb`me8F{wc*|BnCf&acYQ# zU^TxZ>?seGh1oTO=$^f|~$|Z7YeKo8p%I z#)CiIrbxj4%QuV^X-*D7Hb4JajjidGY(?$gl+Mm0>Y)3mN2`Y*J;ws;k#lu9 zaq&;wMLke|F?Q!Yobf(zver3y$ha8vbc{8!RKwIZ1d>c5Q(MEQ6D7x2rAdv#PEKnK z2hiH9MigJ9H^w_U0`bq1^!RWWYP`befE!Dy5TlikM#dhbVM~yAiJvmY*DI;2V!P|g zJ^AtFdjjOeD8ReIYfFI10>)lxdUc zEK1))unZ39j1To#mIuT1udf=N?h|%oB=T7VO9i!z`}qk?RNsS0DT5~b!e})S;gl3Y zJy45|95}QY(}c}}xLU4>_^7<nrTiROd$2x&>4F9{6W~@j;DbL7^8K7uw$w*m4CPo;(?PXks;DY(pF}B zxukM5u7U1Dyg50Qeh#WJv|#pf)UOT4fhP}-^5vWm{BnM~mTd0lj(+(+zM&kKSQv1@ z7?<8ms&rY>oL;<(z3~qOtY{IPe@ol)D-oyC;orac{=Zz{LHv^sEs6xAfvq9jnEY!j zvW?fFI|^p5+Uw9AJdy=}hU+l=#g9>_;dkfX$f0o)EF0xniHn_h41TfR4&Ds)r~+U; z)X_1VEc$C-o{`_kFLikY>RWSx1nhyepo6EtCN0lC@;08T|MGTmn+_6#J6CXjtDU`T zUVKYLZk?IqVb=#^jytFNgQTXPBa;t0$j@S~8$azRoq>RQMv!}rZE{>IkRQi^vR0IS z5KI>&P#isDaSEpAS3P9%uud3-gQ_CSh|609=%A^Xz{KjE*HySUvzCal9)c&_>h2Mi zXgUG9x^xb3zg#>hZ5Kof(E=_Vzmc=>*zd_HD^Z|Jygc{y0cADRM6zlJ7dvvEN;r#T z%=uOLsT+ypSUHn{R_)y)o`k#i2yj07ZUxA-IBSP2CjzuqY!|OORltuYO$g151vh(N z`mSE{-}e>UhoJXno@$ZCulw9O{59%GL=+QiaPWq>{Te@HiABooGB5T}Iex&>*ukA& z?E!y@OAHR2FL8;d#U+Mjd=HLG>qAOHC5h3l1tf#x&S6vUSGC`sVawyc<1 zMfSt+*k4gc-y`y0qE^0s*KjmCA-1iKibPswjWBkxH~G6ktY}pQ07+Q1Wbc41INfhg z5EG`?r-&T8QwD7b@wlB=6uIj9Kdnve+Jl=Ehg7;rvBlYE-eI@Dl`{Nw!8^V(0A&ov z&bSCGz)5v=rS?R$Q6m={N6Q48udfy1Cz$QGyWiKzT-m3|a!&B|q|2E|6^ z><-BxMAyzQ$zS_M(TlWxZ@a07ZIt@-AhqT*z&0|N29Wzb)sV7>!d4L58RA8Uv=LXt z5m**7+@Rlmuit=cRS|J_P1yTL;b5157n;K;p@LY!F?K;H`HkIVLFP{e~-_ zeM5&jLuO4BydX&sL%d&kfCCvZqv5D*8~tms+BkzuP?pOAAsFw&zo)@oQ_ zFrm~Bz!K~nz2zM!fkw*xZB^84K;uT|QoHcXI&zp(49pdJ-3UH=|w`J#}cLcmv8E zJ7lQE7O7!Z#+D&%~fl*Ax)L zJP75rW^?~G)LH2O03upCs|PF>BQAsC*7knAUVg-*_>#t}#)#;y5GCy$db@&cn>o>Y z4qps9$v#cFBUk2!U~LldrXc9{0hC>e8amXe$1S0%zH5S8@GHiQc~-7#=Z;)ZfI=-3-xNWSC<^kKWFgo+jTmvA z7CgGV`|9QEZ@v=b*-A`4MyZ~ZI1^EdN{3`(Ni!8Wb|{HBMWs5*a&phVQArf4I_;zu z$wM%JiU#h0>oEW&FYxvlzDIb`Bju_1{3CfPdVGks#On*^-yEy`9Uvul*zui6qVhyW zPQjn59sy?}B@Kz2TTCAUY7FiUlJ%e_il<(1`Q<_V%lmh=Y#DX03F0tp+>tbYo{cQ9 zz&z~$QwzCb80c6cfS%wneOXHCp{;hp6-0dA--^vL#2rPk0?+v#v0RE@))M`5`L+Kh zDs{A#XmHK3FeQ&_?ybXNFWeT?#C8l64qD0#O{Jm76!dEA8^0r!sLoMZ�wIA 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 E2k71_G5`Po literal 0 HcmV?d00001 From 735e1e85ca706534a77d8e1582df0d3248cbd2b6 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 20 May 2024 16:33:29 +0300 Subject: [PATCH 087/111] Readme comment that converter is assumed to work as expected (#50) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a2b55df8..c4cc183c 100644 --- a/README.md +++ b/README.md @@ -222,3 +222,4 @@ Up to date implementation: https://github.com/makerdao/endgame-toolkit/commit/1a * In many of the modules, such as the splitter and the flappers, NST can replace DAI. This will usually require a deployment of the contract with NstJoin as a replacement of the DaiJoin address. * The LSE assumes that the ESM threshold is set large enough prior to its deployment, so Emergency Shutdown can never be called. * Freeing very small amounts could bypass the exit fees (due to the rounding down) but since the LSE is meant to only be deployed on Ethereum, this is assumed to not be economically viable. +* It is assumed that MKR to/from NGT conversions are not blocked. From d93b7f785e07c750c71277f6441c9a73d6732973 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 3 Jun 2024 10:54:06 -0300 Subject: [PATCH 088/111] Use named params for LockstakeEngine mappings --- src/LockstakeEngine.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index c6ed1952..00d34bb7 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -66,15 +66,15 @@ interface MkrNgtLike { contract LockstakeEngine is Multicall { // --- storage variables --- - mapping(address => uint256) public wards; // usr => 1 == access - mapping(address => FarmStatus) public farms; // farm => FarmStatus - mapping(address => uint256) public usrAmts; // usr => urns amount - mapping(address => address) public urnOwners; // urn => owner - mapping(address => mapping(address => uint256)) public urnCan; // urn => usr => allowed (1 = yes, 0 = no) - mapping(address => address) public urnVoteDelegates; // urn => current associated voteDelegate - mapping(address => address) public urnFarms; // urn => current selected farm - mapping(address => uint256) public urnAuctions; // urn => amount of ongoing liquidations - JugLike public jug; + mapping(address usr => uint256 allowed) public wards; + mapping(address farm => FarmStatus) public farms; + mapping(address usr => uint256 amount) public usrAmts; + mapping(address urn => address owner) public urnOwners; + mapping(address urn => mapping(address usr => uint256 allowed)) public urnCan; + mapping(address urn => address voteDelegate) public urnVoteDelegates; + mapping(address urn => address farm) public urnFarms; + mapping(address urn => uint256 amount) public urnAuctions; + JugLike public jug; // --- constants and enums --- From 5132023e9f712947b2282afbcd3d91ed0426c6bb Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 3 Jun 2024 11:04:22 -0300 Subject: [PATCH 089/111] Add comment in README about Chief's IOU tokens --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c4cc183c..7654242f 100644 --- a/README.md +++ b/README.md @@ -166,8 +166,9 @@ Note that the increased gas cost should be taken into consideration when determi ## 3. Vote Delegation ### 3.a. VoteDelegate -The LSE integrates with the current [VoteDelegate](https://github.com/makerdao/vote-delegate/blob/c2345b78376d5b0bb24749a97f82fe9171b53394/src/VoteDelegate.sol) contracts almost as is. However, there are two changes done: -* In order to support long-term locking the delegate's expiration functionality needs to be removed. +The LSE integrates with the current [VoteDelegate](https://github.com/makerdao/vote-delegate/blob/c2345b78376d5b0bb24749a97f82fe9171b53394/src/VoteDelegate.sol) contracts almost as is. However, there are three changes done: +* In order to support long-term locking, the delegate's expiration functionality needs to be removed. +* In order to simplify the logic, the IOU tokens generated by DSChief are kept in the new VoteDelegate contract. * In order to protect against an attack vector of delaying liquidations or blocking freeing of MKR, an on-demand window where locking MKR is blocked is introduced. The need for this stems from the Chief's flash loan protection, which doesn't allow to free MKR from a delegate in case MKR locking was already done in the same block. ### 3.b. VoteDelegateFactory From d2e3c5969e7489abba18998734a5a990dad39add Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 3 Jun 2024 11:09:44 -0300 Subject: [PATCH 090/111] Add comment in README about redelegation and restaking --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7654242f..70983e55 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Upon calling `open`, an `urn` contract is deployed for each position. The `urn` The following functions are called from the LockstakeClipper (see below) throughout the liquidation process. -* `onKick(address urn, uint256 wad)` - Undelegate and unstake the entire `urn`'s MKR amount. +* `onKick(address urn, uint256 wad)` - Undelegate and unstake the entire `urn`'s MKR amount. Users need to manually delegate and stake again if there are leftovers after liquidation finishes. * `onTake(address urn, address who, uint256 wad)` - Transfer MKR to the liquidation auction buyer. * `onRemove(address urn, uint256 sold, uint256 left)` - Burn a proportional amount of the MKR which was bought in the auction and return the rest to the `urn`. From f240c5918aeb090aa057dd77d8e01a549c413cd6 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 3 Jun 2024 11:12:26 -0300 Subject: [PATCH 091/111] Add comment in getUrn --- src/LockstakeEngine.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 00d34bb7..bb4864ab 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -219,6 +219,7 @@ contract LockstakeEngine is Multicall { // --- getters --- function getUrn(address owner, uint256 index) external view returns (address urn) { + // NOTE: this function will succeed returning the address even if the urn for the specified index hasn't been created yet uint256 salt = uint256(keccak256(abi.encode(owner, index))); bytes32 codeHash = keccak256(abi.encodePacked(_initCode())); urn = address(uint160(uint256( From 98fd54201956b2c344acbb404e7e9f66d3e72790 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 3 Jun 2024 11:23:13 -0300 Subject: [PATCH 092/111] Add check to deploy scripts --- deploy/LockstakeInit.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index a171cd26..6b1b5f24 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -158,6 +158,7 @@ library LockstakeInit { require(clipper.dog() == address(dss.dog), "Clipper dog mismatch"); require(clipper.spotter() == address(dss.spotter), "Clipper spotter mismatch"); + require(cfg.gap <= cfg.maxLine, "gap greater than max line"); require(cfg.dust <= cfg.hole, "dust greater than hole"); require(cfg.duty >= RAY && cfg.duty <= RATES_ONE_HUNDRED_PCT, "duty out of boundaries"); require(cfg.mat >= RAY && cfg.mat < 10 * RAY, "mat out of boundaries"); From d3878d4075e61190496c931bd68a7ad919263746 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 3 Jun 2024 12:29:14 -0300 Subject: [PATCH 093/111] Extend Multicall revert handling --- src/Multicall.sol | 8 +++----- test/LockstakeEngine.t.sol | 9 +++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Multicall.sol b/src/Multicall.sol index 943213ab..bc16a50a 100644 --- a/src/Multicall.sol +++ b/src/Multicall.sol @@ -12,12 +12,10 @@ abstract contract Multicall { (bool success, bytes memory result) = address(this).delegatecall(data[i]); if (!success) { - // Next 5 lines from https://ethereum.stackexchange.com/a/83577 - if (result.length < 68) revert(); - assembly { - result := add(result, 0x04) + if (result.length == 0) revert("multicall failed"); + assembly ("memory-safe") { + revert(add(32, result), mload(result)) } - revert(abi.decode(result, (string))); } results[i] = result; diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 3cd8088b..83fa75df 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -941,6 +941,15 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); + + bytes[] memory revertExecute = new bytes[](1); + revertExecute[0] = abi.encodeWithSignature("open(uint256)", 2); + vm.expectRevert("LockstakeEngine/wrong-urn-index"); + engine.multicall(revertExecute); + + revertExecute[0] = abi.encodeWithSignature("onRemove(address,uint256,uint256)", urn, uint256(0), uint256(0)); + vm.expectRevert(stdError.arithmeticError); + vm.prank(pauseProxy); engine.multicall(revertExecute); } function testGetReward() public { From 739c497fe7c8ad5a1876987218b6b085cc5db4d1 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 3 Jun 2024 12:34:28 -0300 Subject: [PATCH 094/111] Minor change to a mock --- test/mocks/VoteDelegateMock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocks/VoteDelegateMock.sol b/test/mocks/VoteDelegateMock.sol index 794555ce..0d724ddb 100644 --- a/test/mocks/VoteDelegateMock.sol +++ b/test/mocks/VoteDelegateMock.sol @@ -34,7 +34,7 @@ contract VoteDelegateMock { function lock(uint256 wad) external { gov.transferFrom(msg.sender, address(this), wad); - stake[msg.sender] = stake[msg.sender] + wad; + stake[msg.sender] += wad; } function free(uint256 wad) external { From f83e7fb353b9777f9ce496f8c8d1e6e4b95807bc Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 4 Jun 2024 18:11:52 -0300 Subject: [PATCH 095/111] Minor changes --- src/LockstakeEngine.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index bb4864ab..4bfb4f14 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -68,12 +68,12 @@ contract LockstakeEngine is Multicall { mapping(address usr => uint256 allowed) public wards; mapping(address farm => FarmStatus) public farms; - mapping(address usr => uint256 amount) public usrAmts; + mapping(address usr => uint256 urnsCount) public usrAmts; mapping(address urn => address owner) public urnOwners; mapping(address urn => mapping(address usr => uint256 allowed)) public urnCan; mapping(address urn => address voteDelegate) public urnVoteDelegates; mapping(address urn => address farm) public urnFarms; - mapping(address urn => uint256 amount) public urnAuctions; + mapping(address urn => uint256 auctionsCount) public urnAuctions; JugLike public jug; // --- constants and enums --- @@ -218,8 +218,8 @@ contract LockstakeEngine is Multicall { // --- getters --- + // NOTE: this function will succeed returning the address even if the urn for the specified index hasn't been created yet function getUrn(address owner, uint256 index) external view returns (address urn) { - // NOTE: this function will succeed returning the address even if the urn for the specified index hasn't been created yet uint256 salt = uint256(keccak256(abi.encode(owner, index))); bytes32 codeHash = keccak256(abi.encodePacked(_initCode())); urn = address(uint160(uint256( From de66d6fc3b7478f0b5c88fbcfe79bbe899be65e4 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Wed, 5 Jun 2024 08:36:26 -0300 Subject: [PATCH 096/111] Check in init script for tip --- deploy/LockstakeInit.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index 6b1b5f24..7e781ad6 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -131,6 +131,7 @@ library LockstakeInit { uint256 constant internal RATES_ONE_HUNDRED_PCT = 1000000021979553151239153027; uint256 constant internal WAD = 10**18; uint256 constant internal RAY = 10**27; + uint256 constant internal RAD = 10**45; function initLockstake( DssInstance memory dss, @@ -165,6 +166,7 @@ library LockstakeInit { require(cfg.buf >= RAY && cfg.buf < 10 * RAY, "buf out of boundaries"); require(cfg.cusp < RAY, "cusp negative drop value"); require(cfg.chip < WAD, "chip equal or greater than 100%"); + require(cfg.tip <= 1_000 * RAD, "tip out of boundaries"); require(cfg.chop >= WAD && cfg.chop < 2 * WAD, "chop out of boundaries"); require(cfg.tolerance < RAY, "tolerance equal or greater than 100%"); From f11051e203b132053edfe76e850892fa870571e8 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:41:16 +0300 Subject: [PATCH 097/111] Add Cantina report (#52) --- audit/20240626-cantina-report-maker-LSE.pdf | Bin 0 -> 531251 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/20240626-cantina-report-maker-LSE.pdf diff --git a/audit/20240626-cantina-report-maker-LSE.pdf b/audit/20240626-cantina-report-maker-LSE.pdf new file mode 100644 index 0000000000000000000000000000000000000000..574d1a87cb8ccaf63d931ed502e4a45f92ff1a35 GIT binary patch literal 531251 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�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

-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

LN`9}k&D^9eA?&8nGhGaZl;4?B)T^L zjXasf0gDdXnG8!6>W_B)wz!wItrQEQ_gS55H}m>PoPo|Nr$;|WEOtL09$KD5HU16V zg54C9q4lX)$%@wFvYC5}xN=l1z%rks3B5`lTikm7zPWj zXHpK!4xAyH@yQJJ9cY(lP8g%n&UI%Vt{Pu2j42Y`wnQBaAs;MXh!Z@2m5hCw0Sa;} zk`k2}d!*VUp7tVGpC6EQ1Wv~v#jXYLZn@|k6~_k605D6f*PRuJv=0VFC87qhJ0;uY9rg!PKXN3koMey<+!Gf;Y)!50La3B4d1 zI%N;S^l*#_9(^?f^hv6-#IhBK9gTBs&j4Z1q$wymB~ zd>%lq)~yI{XJq?%lorrdJtKy!{4S!d+3EnRKQ_wUedlE1MM==_%wVm}J&?P(v0v?* zP2r&jAY%~cdNt9I&1dliOKW6dMX_Q>G^{R~11-q20XW7R4mAL;z836mAq4__af1Zy zo+R+ecUg@QtV+?O$Bl8^AG&na)r$}89CKu3^Lr#Qy0U%Z_Q0U1L^A#q65W2@(tNu1 ziFK8ZXm_>{+nkTeRN%MZ(ptlVgg3`lUMWkKQZUxDeQPrLq{*)cW;1c&IK6nlDnw@p zl-+1K_ZlYCiMS>^D0%5Mj@*!>L$JtVku6jK@MN1&nJp4lSC+Jw#fFm)3U`i&r;;h` z>|cZ?DsxMKfK1>y8EeE+7A;pd4E{QkZ~kY!!o#DKbxV$$_tOmaGU|!t#@Igk><6C@ zM3T_8_Z3hrF?d@c{8~!Ii9S_i zUgj5qzH9QV;0vKYbYMjNBka^!R*6FIpRHIIgVUrh&qWG6GU;pcY-z!IH2?fU*Jj!# zoizDU!Sw3jvc&_PQ1}gRdC@_zPX`<<$4{*&@BLf(C9|EO;zDME;9O6orWrK!5c9a+ zKaf&>r~c4?;5LNW!}PuZU@zg|!6a7}Gu%M4P1PuaYFnP6+JSFc0_W#+p&vq2rGY3F z`@j(bs+*>p8s}-KMu%PcUS&$c^v8pg)5=My_JY5%QY>z-|B9F*mj7B95(ad->;5pj zCumZ3d*YdPYGaNNc6TGg;{t5{$tV!&1d$YdC<2f$|5(p&I~BrK;3a#j!_%g$ZW&A3 zrCsX-QPT+|9I58XT(=0`h@EQPNpr+QgYi@mRK1&e^dXmT9MkW%`fhgP;9}ctU`BXRe z>bLw&4O&^}f|xPY-x#}rc7|r))L+D+k`gSPLQXE_e)2w!CJo8s43}k=+j`QowbfAg zR?~qG-bH`ijuPHPegntf5F`F~N$&4*+`n<~KQOqW(;wVh<_`)@_m^ty;OKoEv zg2W8W?EkTx*QsG?yUvFCd8ONHyRKKuupP}Whi}cY36NPF#(I_Q;1Vk~|F;t)APMhjLwIi2L83N@^j;xXI;*v9) z?vi0X9f(lkH>@!V1Hc}oL63mn2cTtFg5#?eGL@RpV(TZ%kplqn^P>kSHwbDepQQx2 zho7HRK3}&u2WbZ&9FYuP!QA(;pg@#5<&K08aDf1(Ltg_5R$2~g!OtAnjz3QX0U1Bv zcP&`(79bSL17gZvPiU};KHMyrWoXn7pmRtd1|ApOO(7Iu&`#Ia2nnVo#tfS(A%tq2 zy=upnoe7PL&yqim`Uebxm`aR3eu2SKpBRuz-H$ftIz>J~f?P_{rb0s*H?iQ$jJ{O- zb92ey-HBXJY+thC5uutAS0DV5q6{ry)lL9@piMX7DnXYeVhoeJp?SqDcko*v3r}o7 zf{N+fW?x8u-`hZvs}@`c*g2+DJQ{>h1wi%QSk}IC{}AKAQi#OF>KO!cFhgGj!rxv& zlqFOnk}bi%gK}L&S^%}_3$Sa)s6${7O-5(MU`GTT$06TA?frzlUF#lji zI3)#c3Hoq*yql}b%+CH z`--@;p@guGd6!_9#W=6z;WoFb2{J9MA=;$*{H4U=Rag8~I~jMhK^K#UkQ|C%G%9thZM|Wag%DX|*D$#z3C^K*jkgPl6M1aXXVPaoR-peEZYL z(5XhnWhi#zq-nL`uyERZ`%SfhJ8NmW<2{)&J;7sJdYy+}wBv=h<%wBe6HX#la7MWkK6;};*99&_@~NXcN6@EiK@)~R6lYe8 zI#$eEO+Jg1NA;Y(O}(sZdAHd7h2b(t-QY3T?@IA!zU@@{_mZ;pvM+3+1D-{PjzRWt zX~q?y{3C~0lJJ4(qSCx(YI;sm`g<~ySpRnEQ=6lSEEb-Sx@#QwGuWg0O7@lpK^{RO5D_e?} z>28G^1_|fraP_~-PgHuQ=gDQ$`|Cgt2#k%a0W+ItY{k;yHZ_#2y9ya&>kMU9#y>ZM z*?Zc>_8rz29LCcKdp`<^hHcT6IUK#4AB@-Z7J%lmc&M6&Kb3;rp` z33QBu)Fy9{n|Ga2W?8zGK7Q0{e(@@~%{8YPp>yxCWpceOzt^yyi-zOwkY;iRt3NkI zH|f=!YGdn`JJ)e!(O^sI&VRkj&S}^mFaDaEz{!OT!^=hEz~aT*HVWRqe6OB5J@&H` zq9`d~SG_BP7kS@Hz0?&QGf(kn0i(lCU{aC5O;?I8cjUc)Cwer=U`CkKN@Tq(tyM0e>~G1bOabX%euj4Xu*Zmvd8(&o`3q-S9vl7J zr*G4$(`5)(+SOk!$y#k8E%X~u5qhQMSwa1+@*?LXoV6!yu6i?bqGX{zBu| z;CALH?1ZSdaN{VXhQh(>D;+v^AEoi#e*r;0+?IH9uUR~GHe>H}**<&_tzX4Pq(#4! zL?B*E(pW62ves^IdveB6Y20(BKGRrorj}j2Q~JBNeCI?h`@g;FgGzdpQ_y737OL|5 z=J?f3y%!4|twZ5Km~XC+q`XlRcq^0Os|oAwEBz*Rj?J_ZxzlrsO2dKq0VbA&jgI= zCE`z|PvQO+((i|u`T2TFZ4*%4C_!uO$!gYhIjXnkF<}%+TGtNoooR=o(g`E*yi$^x z>cI78Y&Ac)H-tW8)S3v5rU-m`BCBS|uU(Lco@HTNQ_&t8!^ck~1EEsxIzwZ5d*ql@ zJS4Nz@N<<;GE})|FS2p=YpMD&_`zcBBVz-n#8#1}zlg@zGR5V+^J78#fW5X#H>2vm z2Q2B(Lvk>dI2M&=^vEFcviCniAz0V?g#fdNLgP?)C_VRH0ju(BC~hAHlLf7oUs}$? zj=@);Q=^L_#?~T9rcrq2lkqx#aRHqIx5{d|0ed5Zd2u})4qxEG9Yau0)47kBrONHj zd%xCt4(E3cb1C-fFcpkLW#4$e+P@F)Q<QLg7_M%lmkqZ%S!?xlVbC&lnBb!=pNfg*x4jB%B}lq6lqd zr~z%1|8)SgIzkOSNn>blMx9q%(=(KStVGy`*e+$;cI$}d2(#jD#6?H;}gFzd-iiXjo6yCn|W1IvjU_;{@peLC71Ct?M}Ji z!Q7Hg!+p8FGIvy+7qh@Mry^S`Oq|2J=rcJGhZ@Q%yXKwu8>q?bze}X_tbdvD|4(BL z9XtDfiY<1kNII^wz<0f?-l%vK74Rc^9WJeft;=vtuAGry0Rj;~86wUAs_Pw(+- zIA}MQk}iVw0JU#-b)nFzG-ub9ESr*@5g}{So==z8N5HW~Fl3;%jO8zqQ2hC3H=?A< zK5ZHQG;iA8C(8&T_a$mp+}@E*_6};W`69Nyb9-BQUu#iqZ+ecwej3NYYR}GmSqu~J!PT(qN8}U-L{zNulceD^2 z%pip@AhGEdW%uh1H}T}?4k~E^`hg#dg(DOjAFOp*$9PU^atPRF8YG1YaC9wj$BKrzqT+d+UH4em(QZYrXXoQY#Zx?A_5(|NZ2r&RQqeMX=q z5oK~2Y2Ohg8-5%9cI$)d*1ckNFYQ>vQzt2_4^G%kE{Qw)F~mT$kUe`)v(Pm<^EC3O z;tV8$`szm1DNJ^LLzM;GH~%O?)*(8RRna}eRP3rpEG42?A4*h&oiZ3cyf&hK3qJpt zSdB>G35kVP>#o0BX_`A2p&iBtyP&!LFF84_y(JoIr$QiwZZ~^RZ&YuWCNOclhFa~* zi9q@tn%|KkuAQWl%XR$oJR=OtG1E|cj{DK*5EkpSBQvfQ!WDyCPbEFfDde=i zOO$> zX~qYsW1|yha*-IQ@0Viz8()?{2;^0lKdji;SbW7jTjE6J63 zqBoRSUbA0cEi-*M+t4XQ`m)VJ4^$oNV}6$X=R}+&wt(3@FD*%iy7VZv>HuW|=w^Q#yD*#Z`71 z0;|j8-M&+Oowp{wwELcuFS~Cb!EieQx+RqcPL%a*_p#i6fxJUsH{1G7d;=fAp1&}C z{rQI`ym#Z9Y@^(!1=_rOo4$;6!}k}?0JgnU&2)$VR>}t^?#I3;^+*8bX22&CQPwcL zK#|?_=;nBbs-q&s`5$-_+uzrX{nx@J1{UW3*pPgv@yG5Vg80Yo!S9Bb2UsDK&D9}W zAH|rW*RhI2ITC<_j$QW?K`<6dT)pYnrwU#NleGB{aKBnGF5>h}ZPDdhWqDp2n=io* zW$)n5#E{PMtGDXG)o5PFJ%lLq=h9|1k3LbKfe!qgP%yeUk89HlFB!U_`s{kb$#7-w zui-tk`luW+h|V9Lj%P#?q1yhT+D_V6)4M2Hn?{e?%?tvuvfk}6J@I;*eKw~C)RS{c zTP#O3vFbmldICaC#I;gpH92W1<0OU}r7rhPh~}wmjJ^h82F68HOsrLmq*0Goms>{1 z1a6&2Mmb-_Rn+ef6OGe;q#W^(MfO5*-zS7Ra(c@EA=9^ip4XWj9E)801S2SZb49l7 z#I^~gc%Wp&-49M4t`Z0WIpICR{B(6j+8(8hL0mNcinc`DVgG=HVfXxceqm~leEASx z5>K>FOwvk|a}f}he0i==H?<=pxHW~%$jX`yX7ciPBfDaR66FCl(MI+Xs@blho9N)m z4%jtEOiQAVi3#1}Te#tuCv_(aPKx|4 zHZw)RPkfGRv&+U0xPN&Op{=L`%$;28I6!VrYSQ>PQ1{}Cp?g`jw3$@+NX^?17*=5`q*lU@EJASfw#E^159>CZKDe4~8g6_M%Tn+P1cJID$zpo_-J=6_?IZt% z611f=z2U}1Tm!mswAEh!{j_@ri5)o-g7D~t>yPj!1f4XPxnCYoM|ku$1ev-H_Hz|gze9^^CKhj@|vSP{%M8YYVC8jBTB?q^t$s2 zj0?(Qs2R6b)YA0x%?QU-Sf?thaa4i@&0J&Dk--z2^O+dvXTLhW?zF{xpWQ%(=k3#@ zEM7IumQTlv_3ioadHJtycl(;j0A`7WZ%;)2g$@RrcO&@(hveyb%O^He9o!RqsE)7b zTi@N0$wS{IExJ1^A69y`o@16KA6Bb%tLM#|b*5J$>UrW>Q_xZSEc5;cMc_kLsCnOH z{AnK2X)c!tTN2-?d8wRGuAj2$4kn?bfqmH6e5StsGb)VGtP8+UTps9{;U7Pct}Gzk zLJ@c(7KJC8+Sj=xwR+He>rNm>{s6E1Y*BX8gAS_1Z5*?l*URd4EDdpuARAV}^@SLY z0W^K+P-4SX&T)bZD0wyDZtp7+4Zkh5CCDl4W1EcjhG3-O11HHY@i$1mxN=49;(S2` z^UA;i6vA?2-cc#vkF4d_?%>KeW4OJ|&)bbdSS?p`=a>{Z>dBmGDc?^e`FRMSGOI2U|adQbm}fP$X&!19J670^M3ZcStTi8m!FZ zVB#3ewMf6%r(mXZY1HPbtPjN}$g}U|G=?AoKM~BTyKUn#IR_MG+94Ihx?Egd`83jh z<6G;FH1r##BZ&uVePa#e(?T`4z+z<}@3o3x$~yZkR0CzQU$vVr%|H9|%nLJrNc@#> zB;dmD(VbOx@j!VKozNS)=cNM2aRy4+^wh}3KmUxlYjU!2)2op%%J>Zq~EnIl-eDV=IUaY9aH z0+^jBIou@&4l>bA+iX#cxr*b|#|NB8Fz0FqbI1p0SZj%evlK-nyoh`afRVZ)1VdDf z*ci7C6=OTw5Z!rLW?ISP4c^Bxx5ou0f*opap;Mu`qE|%`82us88c$xyIO#-!Kxj$ZowU6AI?J7R|_nNxlc7k$~e~elE zPBGiIxMtJFRhmoHX2Gf6uZWJb05a=_WvDs}B8eZj12zgYqNP8S`T_CyXSwEL>!LZ9 z;Gw03#{5KiA@+n)wnNxp93zl2TYiI+R^Z_GjqXvLlrwSELgKW-;I_k{8YcHWf&#%aroHL`N7chm|@TI+*9 zQbl~!;t=c7xB{Fg5Aa6b=YNU&f?VbnUP9uL05e(|v15L>zyt#Z-@{!Rg9JYQ4nG-k z4?gNDm$s6RQh+0A%4$ruPTY8l2Ni}QT?MV|HbOcPxqnXfO9w1OT?c?FB)HBMkFfEf zgi0N0i=vDWTC0kt*2MOU?C00p6T7CEPDQTH%KRCn{r>G|&o%@X!up_p6HI^z5`x!P zOi2R>^w6VkM-?Jod_4ghVYo;rubPQxY>Whpf$T9Pcwz+%8em+&H3SdYi!Y95@^p^) zQ^R!3t!&vscX;^Kjx1^r1n~p)g zvuw$=m}EqyRx`O``i`1@UL!E`&}3cB;3^W>8274{ykuD08?l$fJmm(AXcChl8Y9AW z60@dHHFJFzWx)!*0|}OBr;&xQvu}#kRH4(sg$5Wvcn=WNI@wyM%?BNTWt*~Ii=73K z^@)IV)?OjiCcuSj)PQ;R2TADTBzA-{s4zMnNJNnJlf#@PLUf~uuO*`uGu%|o>XCzC z4^{RBxE^3?3nx$cDdaes-8w5BcopP=Rk@CQ831?cQ?|)UO5pE96T9VMcS@EZg;CO3 z0+H(o56Uxds$E6oxE=I_9e{KXQnNp>s(Gk)uJ0V!@I#Z#XQV&Bh| z=axpql!E%mF;O;71MRh*H2Ju@mue{$MUt=&oiFW{HoRjFNtp-Zo(wn9LTBUT18O@@ zo?EerGqd!etuC{$O=1i(iay6Z86T>~ya#KgT%F!Gdq^m>1S1};G3Z^My~92L4U#O8 zh(LX*K5yYyN!Vus>MOdRr=bjnek^NI!hnEUiF0a!voQ7(O&AXhjQB(=&I?Pc${~ER zn@|c27DaaPvU}~=!hwV|1U!Dg?5h;8sG5*y`2f?$Y{5vP_0LA{w8Whhjp@zeZ<_KW zL;Y;LO>}2yqxC8#@ON`H2rmW)m0r)U>8Ev3kCN^s^23! zg)cjfJTj(XQ`n+lzj1 z&PmatmU%^%b%@<*97oyO#&ZE`qc_mXe4OA;u8J_Y^bb>kQM-stKoWWNbg>cT(qhgm zx0Qk6Gsp~hz~87$KjIBxsm=MlugCO<-jpzJu!5#|!WP|9PUI44!y(AH^MSNv)1y04 zVaIQkV=14#*@V*j5qn)?>tK}wdVDPw+5tqTFC`1dF6+^NjfcsyeYx{M3c>FaJ*BGp zFJph$J-r#l)7@k<;i`31i7OlNcIC>`1^>zwKK{5x%|s0^j8|8qRZdLuu+OGU!lzhk z6EI?dHY`4(isVyjTgq9ke94&xV#*?*Lk-NVKM+|R?Tyj=;f+|U$7idiYilHx&cDM8 zSeS2Ufepf!jXFN(@-tq%v`j)d3i14HC&14Jj8^Gag)_yD8Q(IQqYD{zn`6yFuA6|s zYS|rUr(1sYVoZG$Bub?T1~`NnaZO>}?*X2oaW3Z!IC3L&HQ9Pn}S{oCCgLsHG*`K@Qs+ft#qeXAIEo znf`F_x$8zkPQ9Kb(M0Ma^}+9O@^P>~^+bAk!ty9%yHhba*nlR4!p}){zn1;#lmU6p z$qC^JPbKZi6e0XT_SiNg2Kzwu1P5y7H!y`C)&_tBM0sa=N&#V5>64*Hrh@_5p@Lb` znNA(AB|7sf3gKaFg_HTyBD{u&r{^(0~fP&>;EFgt{pS$fGweFHf_%Xx9~ zSW)_U3XsGVd<-^P6o}V9F31FB$Y+cDAdM*(4b0DStQwJ5W!=>QjDc+A_V(Hs1=tKQ zFOPBL0@zOT@<7*E47=3~N@dms{Y2YuYIM(9m=a@qL6JZH%c}0IhinU^_p^V7-XA{z zVlZ#Bb{lJ_1+LVkCD9Cz@G?l}VppIkaLeddNQ}cKEKPLLkz?spS088G8A!~~F!)d3 zc7L`tASoc&m43bGkJl}rvYTB9}@U7dV>qG{U^m#&;7-pa9{D+Mnwulu^1dW3Yjm-Z)j-U%VHq zvOtJi0-aGTe3!W``{&d;T3baObAcN5a=3s1*)d~i!(B7Ct#iTxSE*On2hT=Rz-LRwVwcuB zC&E#ZIs#@?n7|e=z3o?M2r^f5+SMr_eOb@fvZV9>e8pCyvBc5h0smuAKy!RQ7+S29 zJXb%ThF?XzQsy>=xC97D{UXb;yDSTVH|+jcE|XdekxEZST8bP+nagy@B!}v8jhnW6`?`q z0+09GK+SF@g<|4z4CK1&1mmqbDV}zIjcn#V8 zGFfY~AS+AmKmlTFMc;1ktjbG3nGZ7hf#}fhMF9vwrCwcvgLWu_N7ZnA{!mr-FJi#J zAuBlCIbKx=_r%>;#-<_(SkT`x11-S_$h2Zg{Rki!ZH5rBYL4v9`%_unA38uoTe$D& zxBi0&y}yspxFwHxKtuPc*>1H5AyxBUC~AQqOX!ZR%89pi2J8S_9=EmwkA12$_P^99 zD!!_{>wcYmRC_W!Q*EI05U=gK4FP-2%X8)9$Kg3io0-Cka{m;#0E~`umjby(5h&W& z1;RE3e%=ZX-|>j(HFnm&dDzQ5|=z~7(;1x?aE1631Wa5_zI$258GZ!n+uP8baiLtPZqI7xh=J&fjKSdGLk#`?+{{30# z=X0b$#t9EDklupF`3msnfjsajK3_ndUt-km&)`&HuaICdMaL0Rexfy~p*XGH9IcRg zM}(ADb`V|TTeX!T-J1X*-M5av@0Vc8El$($Kd9>aRGZr@76jpN!sWC5U}vv#*8^D1 zRZ%-0*6|_=#FJ8$bGxW5AB;ZtS5%naC9PzOu)1sMJ=!Xg3)fn1`N@ff;QKWcZZhrb6b7MwLih2te(#|035;`M?C1FRu?7FMXn zb2N^BfjkUfCp~XIrE<~Hj#Fhytz$6a2q13BoC<;^o_x{33V)cvIie;yzitl}ck1x> zfQ^);i|X79Y^;>~2?t@WumS6$OqJ+J{%%&jmy8jkc=!nAvzPBzCX8c3GDdi5irSzt zm2{Q;c-;jYeBn%m)!;({BZKX@{PxEE<+E;xgQf3^&GFhHRqYVY@zxowce5NyaZXGK z%?kEB`d=-*X)em0*eB4SO9p;KXkOY?k0n0inagO*lrw|BuDCW2B4Xi&Dvkk0p-?TkNf+;-!(cp5D!RNt*T}x2&N#oH# zQg29+rc7~D9XM%?a|15!qw(ASCNqc}WGiPu9PI+0w zh_}S_?xtsR(V}U}Q^QMBXA{+UfD)K}i2aB`8_v~gPi&{%Gp+{f+0iUpM zl2g8A5nc@m=ecj2NB^>p0I~5CaQE4x(3~<@uKvtj#1aNnEA?OINc7>TONmT~9iec5jBANsty0(ss<^9H{OVb$bI3xvY7?pOpNtQ( zUuKYwJ%YlWcg4^hE1P}xJJa<)Kmklex-GN~Y{8@F_N%SRh_u)NQK7ZJ!)$3Q&n+uf zHdPCbvar8~C)p$wnI&Ls#&kW$2se5Ou0ycpuYR0(2dz2e0>bbMH!^+C$d0=h#Fm@% zMc|;+WKURW7ekF6_m_MV34vci23IJmPJ5jMT|B+Shg~KVOkH4q6i+YNO_THYZHp@e zBXxoF%qNeW^aRdqn{U7%*ci}z$&1)q;?_Zew%<9Itf=xfGUEii+)x-3BUGny&uxoN zRzjWnF6DgQ>wf;vi3PV*VX#SSW2@ckB8zB`sk!r~5C(E&q;0${B)r?-Oke~cUYnOu zBNsJcq8ghDbJuGG-O0iBKM=L`g6N%q0u6Y#SNpls9fi}=)5=RYc~E%zIq*)7$=V+~ zM76kT?Z_X4uTO?{jB5G5-H_tX-#=7c^4%QSoa>ppXuIJ6VU4t8DsmHTeIPmJO{Tzm z_KjsuON?=1VQ(zO7}F!lE!EgGf14)G1paWJJv?-do%Jj@1%q&>J`IOR?A4pvwzDn> z4C5%iS!{Zo6L3ax9(6;eb#o7!tyircoys^8ToQ58H<%FMj9Hxv+Xn6l@kx#ehV+p* zK6Jv^Hu`69!~(DgDM~hRB2K-kupg9euv=?48O~y=eS@SVmp+!7feY<2?-}L4s*OXl zS*+?U;s9@*Ax&V$s>^qp|G+H$>Eb}}pvJf7ChHw9h0nCe;qY$`XU>@SjvGE7lGd$p zb0hXsrwvN?9*x9z5z_$0{;iVGQJC)I->!$6qCNMz2K4#U-w6UMIXl0I@0F5=c2k2L z2KRT9AK${*Ve{Y|5iR?pq;Avj*0s!2ou4d2MTwm2A2fY^@%`@Qq^M>=0rkRR-E6NG zn0UH{Om5}BfKiqp*N+Ul(fn9 zd5Q0wH1`_0uLWjK$Pn#p&$1a=%o}F1!|})KWTV)#z!6Kt={qk|ZqRX4#U_oo$rg3s zT0yK+O(Mm`WgKpRmF!CP@*Fa<5z5IhdQa%@%AK~W@n*zR6eC9u&EuNm^f8WF!3Wv9 zR;0^Q5sOXa?^P{ScBB##Tb232MC803$w~;u@qhevcpF(vKc6rmioO zR}ak&Ba%2@)svQkuQH8!zPJl^#|B|RGo8JafPLPu~gX~#-6`Mkw6|HN?qvhV+G(vNTN(@`{N^#Hk-qg`NL z@51>g)@$S4{R=lxt5I*Be;z&n>)7tpOa#)ETz748${uEwqp`s%85Tbw_zpGmOi&Og z65LMn3tz`jx~AsDrrI$P%rbe)nD<+5Ij2#@_dV~qtDrSZ_N+A?OL|var4K=vc%Px! z5y;YXRgEDYY3+NLZ@Y@itozFx3<>kTB`b)~8>7al53Q2U3O-Iq`k2ongiO#3_1$EU z5?UYkC4~l^)up;GwVg0YxpdRVQ0l52y-!_=QH@A+-z~>mwdrx~NEwul*FL$uAvWdD z)@$SWHn8NWt$zNYQ%^DzGi ziuic@!gQFe!^?V@u+`W6=UX zOYGA++K}HSaG;i#odwQOv?mSVK@23Ubeg<=>R_b z*^Su%$?2sy60kiJT2}voDks5Q_D(#!0y@<1OKxCb-FRYP`utui?|y#&HjDLOxUz9% zN|FnOWSkJsnPB7kxm|JmX9+4fGYuG9zO1j?FF@{UA{~xlSMwgWc|=?yESl@BY6%iN zsg8MyRPir zB-3gd)|NWr4j@m3o5`-da8bc^srhI~T6%@-4LF%8>o^@4 zFeyJ5eEO*?0(6K~o6j)Luh5^6Jv8iUJJJsPIL8R*?I?}s7W=-g;b5wvoZ7p1iXaf* z`Db_KT#elrV;Yk~wjyaLBs~u=HH~WpbINGRa;@vAVP8 z$2l8ik`}ux55gAkQI+wWR8{ZW{Glmh&74_$>QdD8v0Ry$hAvsP%4JwJ=OtVMT-rEd z%^9uuIBu)GvS}Q8VeGUI;D`5(@Qq65tquJ{TFM7j4em9XOTnXGmN*)rj9Za#fFS#o zXiqY>>tN&1btxU3Z(qLY74zrU!^wzQNE}v0;8rdjgi~GWFmr_--KeQ@<7iv{?AaWf zWud?k43KdWueKa;Lc&9lXex33{lhnp89xalM?TyT@+v*nXn@>nbQ*7g`*Jac_m z2KlLWv{+e@`dOAu`cO)+h|7#{&ui*L6F@*7p*npw@-* zB{9z2s{jlRu+BwdyQp<(`2FV+@B3k>T$h7x{!;oq*Jy359od3k2)xA_*7ije+&?{) z!D`6Hj|T&8Pe{stzf-r7-hLzaVgBzk8UN}r;b!6Z)8OY=OVj?Z9+SDsbr)i+5;Sr9 z&9gp~RRX5MBMN%j78E%mv5u&&5J((?1eN07r%U!=Bs2q9k_vVW28rPt`V%YDza7W3 z@a3ZEB2n6s_&_xvIA^rZZ;;lBZnC{(m4Ge1pR_o6h$g>sMhmCVLvUYtS*Fw&pY7p? zI_*h?W7|pY9t=vy$nxpLH~k#J0R>?kRfM{ygKS^2UGI{bk4bmgtG9-lsUasxZVbvmZ@L-46|HWew^j6D4%%lw~KfJmW{~n+pV^I|oj5MQ3d;#+BdgX~9cYk6YN30T)1ECKMy)gnG9M(wPyM z8!CsdvCa-k)DEPPC8;X}8~2Th+E4#ftj~o|W+Y&N65ZZjBCwA#vl?7bD*55AjBGrH zX>J@OdSJ1^65VR0hWdS^Io@V{dB|Y;#&ZzOX}`5l6D}HrQpF?-V&qbUtUf^8KSOh@ z`UYZ%MCpUWYw3F?u5!#wmv#QKOIG7?IobA2mckfA>!dw#rmVt`TZNyABxWg{lKJSp zIxI7$dI9#s7KbhN9@~$t92AdeIf$Gw1v~kLvEhPrTM=XZr^V@^Xnda?GscUPpWCEC z)`abBFSII#^G4YZnO$`z8SMr^fNUkaBNUfx=P@If2z=D-%V!_QQf};``zyi~GPTUq-3tS2C zV19zS$=}|eZZ@8~cuGQtQ_-5i+L(+u2>C?S(Q^kC;AL7M2WNXYUbgE-;tJY-uC3KP z<#w&*!14_`_B64o><{ql47x?Ya`Huig+{-g%Q(JHGHq?Qs=ziT?03npBk@4=98Klz zCGcD`swoU)2U{T|GX#r+$00cCaYvv;uy&-XlL{tWIyCQ)(T6&jKul#sOv0{0L2}aH zaC3373Ofgs=`~OuiGGfkTB^%dZLBD_%}<~nX@in$6XI7zqSntJ zbrt^KeKpQ@m|8m3AEA&p2Pqafj1VgzfSwH@eAIy`gFW4iNs*Y*{pW@5Kt3M<|1+DR zT#?UK{FPfUTU3#PZcf!~{I_d>1$`98FnwQ=4`dkQbxiv+fWjY%K}*xWcZ|P7!P$8t zBo%SV&(J9D;R;XORNiN<-5xf&#HK8=JF&lz&=H&2H==}ze<2ePHY$YdV5%Pc733tS zuuaUta?D$<=x_i4?(-*imrI6mfTug}w1M)6k69}l`eaV$lDJW#mU`OL3FXZ@w4vbf ztZ@O09I@qE1x`@v>ObJklz;f}3)HDAPiRr^q^-m#S?Az+j2eBP;C8^fTltb|bZYd4 z;j@xcC`Wm~r?!~(+482E5mpCY<1`m+b#wI#-Qk%2Da*uHFVxQ`u~(g+-WKT21Ekk|E@!vOy$93*^B!;${X z0_FAA_c8|#>Dqc#aQgg@MNLDY8y9#cXC*_86$A)9BDU=gBRIEAhr`4E)#QdFrRoiH zKZP^`iB|RxNG3`TLr1H?{WpLU_?)QUJWo2L)h~o2f3>_hRT2P7ZxNY%1N%RV<}rm|{O)Z!V!@*bMTU|5u-VB2-{ zvAXs#k!!6ElU*{eyHwpMK2hK0O~*5AM*qeEjNX@X-*Z@~yDHi~_d1Xdl7<(T$7T=X zR#o@zRh7kxyFqSIJo$5T$)@QP`ce!IyJKmya@RMx6=VVwgC3Zz>38cQFMj?)NoU6b zSB3J|+3kD$SJ;`qC))Y6u4>07u@=A_FRPvBjft(+QGmI38EOe&C-Wf!{Zfm#7XV>dt@b3DCYP+a z6H2VOzw^LF+SU&Xy8>7iV7~<>u)#vV&Z9$25*9$TVzX2XrDLwef>B|G{Ji8TH*{P6teBNS%|21=G@TXz3Q~u);tp|sGVo@1OJ=0yX>FaUvsAN8kg8cDg)uko2lnG-Ks?zWlx>-GW ztyG0pZZF&`tBdY?sIa_jM!il|7$>|vi$%^#&pMqx!4sPWeB)6k0~F}Km1NPTod}J^ zi!f({ju(ia>#)y(3d>M*>ZgDMbB+)Ekkj`lj5uZ$boidocQVN+u6S+E7of%<>oNap z-kip-b^Zf=yoaI946}XAT`Xhj6~#8{>k{~p^WJAYWfcd)d|m29jk8XKdZzcI!j$%{ z{D%*Q-#mVTS7{wAM>jRBLzCDOF(BNAiN>BVW4_1nTwNbfaDXv^K|y;i3XSL*jfdOZ zf&N}_3cr_*sKK>qcC4^VeMAdF%W-T@ogqL05WN%+57jgchFUJMQ0rXFCa)|IY1Jk4 zjXeY?2JtHugf8y{%+oTA^vd)N-B35v5pwyO5vE0>oacwDdg6a{2KJWQb1{Px(b>rL z+eN@Gl!bS3q4^AeVU#Q8$kV^Ga5SJVm8N9Xvi29^(BC5+@{?Lhv~)Ts=nEj96wG>2 ziCM~Es64TUI#yL@q%XAFn+t(1J142pnEr^tVtk0awd<*-1)~yg zz;~86c8$HZi2pHsU->8^7GjKkt1Z)Hc%nff zob*UwmUhoTyeJ<3eryzvZ$~sS=GMvbd2roVVj?B-K#u*p>s?@epZHAIJoZLac&UXd zCr>a>^bX=_zD}D_a3-Ae^Ag_8bu;VCI|#jXPu=xE#iWf)hIL(lINRf9_bqlmMCYeY z=rTcppZ8cdjjC&^mO8ZU<-_g22e`Gm1aO(_`d=iJaztY6a&F}6opW!STDf(+ZhYAs z%XZ5FS9rufWy*Zz%gK>qaSvgqMVW?wyfZeng3Or2(vM2M%435g`#Tp^{F3+#+v*S=+@b1riky7h_+$k=UUQB;GGW`YJrH3 zXK0{FHEj6Cm>wFE5{$k<;VF#hLo+#bHs?i2^o-xlaDMze4E9uD2iVaXKTh$pR*w$E zTER_dtvo0UgiES+k#&*8M>k-p9}@EFuv8Q=*@zZ!i z!!C^B&Z3d!k1I3RmuHeJve{Enq6rY*JeL}OEFzE_wONq}4$z&4a}|R*v zM3sjFOcrbJKT0f9lMj+Z+(gq$4%||gl=Hs zFiPqO8YrEf0jE_|SCbYPl&Vl=J{-kKhFQ9Ys#*B#4ZADSJH-TD>C;4wY%Wl3{&KL3 zUz&zi6UxJ5-TJHIBp{b@_TzVIo%-~cuePmrW9kd@zvSvacluA7`?kvXB@h1`qYSQHxHtjMc8naFW2SS-h z^B+wIW(T?rbyy&M@hM8C%5X`|!~ohZ98l5cPB#@(Y=y5r<}*o?un43pwl=D0(P58D zYO@q6sX!?vT)7 zRF)7QdXrX)+*b}NY=}(8M`0#-_*x2go^Ky&=~mMQ&Pc|@kO$QXLFW{cVfipc>PKZg zL4ji_x#U!@Rmk_q>_vu&j5Q=(F@5s27>AH|hi2n#;G1hYdQY~UUTW*i>L!7b20p@V zEq7P0>zHRJFc|NoTU~4IcKvAqXds5xNbDDWm9uwI`d$2^TvaIUyk+mIPzhQx5$l&=55hHu^SMAf`TB&piMhmIOu1?O+;ko zhK)6~(;G?>ah_1BVBZ?u!>5=}!Xc1U6gc#FdY!T` zz_)%gL%J7fWt{$|e*F3=NEYJ;lg@ue4W5{JoV_BxFk+aO#-o$#(k5tkk*Y}fK1c!9 ztvP0NY<9F%Zed*Nw|FHS$5XFV&;p0cc@Ek9TT#i6jV61(%vRkJ|uqEs_ePgNwRseS8k z+D3=49L3}cUqI9cDCZNAo8(TB9A7KcJ_3cc|i-yh@_dYyG%oqzOBD!F99qN}-R#vb7J92T5PNZxt} zPkjr!U*LifGneCmOD)J;2TzPVkq8^BTWY4oy)gm$DBNuA_1J(kd;Cs^K1p%jz8S92 zsj;#G`Y^#d?5DLlksu3B$5Xm}9UamOLKh<@4%`ojQZ||T_~TyPSZDl*UtV*&Rso|| z9R)b^B#xop;^i3X2r&f_STCojtnw_GLY-IfKvsD&fvqFo5+g=W3jax8W?|W5w~PHW_!QfRLcBWEt6w;iAV8J0U2kWcB%k zEEp7gNAI;U;QJGQ*fU2cR4roXAjS1M zdw2q6o|p5FDnL0cJyOOzh0%Tk_)nHg<4~sf68inTK*(Xn#jXgt0oCtqj>@v_+0-k@ zN#<)aJS)h2vg;T6Yusx#GdivHS9$lXU{k56{YGZs?yD@E&Fu)W!dt7yiZNPrIg}?>g%C?l}At{JU?aB~$v9IS2f5oP}cIC4} zTe7auock6p_nbhbIlmudxe(CRxGuUR#dQ+hIDO9o0V?EZ)x=~SjJvI=ea%5zZ^U5V_9fe}{V<;=2yt>iBhY1MH>jtM>MIN@`ntsY5PWxP1|0`kJrxi@!BL z{&r(||Cs0$n-ka?9N3B-dNHnex;;akAuOe^5#C3HEkW}O3S2zyewF5C^%~b8XuUH^ zKkU&nkd+W6KxO?|G^ty*__Ax2nl?`R+H|8=G)B(K*x4~Vv@>v08puUK62!W6i2qx3 zSJL@9^&Yh@i~i#s1aoF5y6SNIlx@Oz7t7$(mxGU$2fPlWffRxv%_o^A=Edy^s;7Km zN%xnai{?t1Y|ynGsB4jUO!umS9`YASiQ1kjRyww^Q$=QBEX8cmKtX#On#CO*P%Vx1 z6p7FJotnJGqf0?3jXcxH`cF+^1qy&70+IWazAD3mBGzkKN8p{qr^5u3Kzz%al3rsa zkD*`rftk#RamMQxURj_$qblWW60Xa`z?+H&C9oG|T()x|K3cLIByHTT8+o{HA zn(+?K0X$LpRQ9wz|FCf)i&U2me$zR>k8!hFJR0I&?Li7fhKy?Acgx;aFeMVHo#QFY z3x7|M_so;RvSor4>PqRD&R3# z;;<-5gzR+d9mR!)p+>0V=f<7qrn?Y2MpKnsJ~q?}P^{~t=Ij~7Gg*Xj!3LqjVhGN) z_$LdJdM}`woZzSwe&Tmk4%m6pjjl(y^16ns=rFY3#_1-xMd1$nDZAIiubI9j`WSQ! z@$Lf{Q`XmT?1I-tKE1Y@Q)t@jP%lPmL>*m58HN5@u_HCjD;w6}hKu6P*f`w)icYnb zUL2Ya>%`0jB4Nyi-?_7Vdy{y7@3OEDrnHJA^IGS~nDkXsnwb+X>j)y4Wmh%ny!Ym~ z5>qNf2-|>EEQ`RIChg;m!i}`-q?9{_C=E{`BxOGy&ralZ$18Y@oZ-X@+HgqGNdZ9G z8}j~;GhWz~!j3$`YzPFXD0)%-LSp=-Hl@A9*1}v%T^Q%YuY^`Gkd_Ga#sN#6zKG@{ z%6^tY_o*Ur^wn#)k~5DyNdKWSlC25kossre{1EB7we{W6*YR+vY-apQMZQakxZ{F7 zWw)98cU`O|hGv|fehHfXd1}(F@h6{>9sVp`*ZFN|`bo(tse% z-r(u+8aACY2Ho?3;%4D%zZTmBEv8eS!TlMQdqP*&w!FKa)&%sk3SPC#vpes@x+9gt z-|u+Z>1D5f{W#5WpSg@3^cm~j?NPSYwWik>lCLezn}|97XTNn9mPNZM?M(&t^~#%6 z1*p8s#tx}L=lHwVD;cJ>s;c6L{x{&uP;L`oaKc}ah=1XSXF~FgG!J`!Zf`K;oPR7M zXZr2rbp@8jTEos}5ecJ{g?bbkM0|u3PGd&XI}Rc4?6L>9Up{Y5IJpnZE<(5XD6TO> zg!H|Gj3GLzoMmopJX=*`MkbdV4%##_9%rFH~xo~ASTiV_o{sISsuM6n2W5$pEkvZGx9llL_bRLnlLJ5U$ z7?pU|9lb9wcLus|^@p;(wIYOt^`8||lK=ZYcUF#BRu*-T0*Fl!RHFdmPz5z8NHA~H zZL_c_fC-5tsn{_k#!@GGH2Fk>C$_;Tq)D$$t7qpGvBgWT(ROHdMoF)Uk}fKAk|YdI zi+XqZcyFKpE(M@(UXU(cP-b2{&)Q$Q1%9EhhY=&fWqBiC#P-+O zlmD&Pm6`L;X$c=?M)nZ|LQTliNEB?qjzBpqh(<}F_4F{x)YX90EX=bfBld{GbH6}J z!5bJUy2h&iPQFL)HDuq3G9JnG%Z;OkBc{}mDCS4vR$H6gkZGo>HqMt|cue!tJwJ94 zh#r`;PoRKbl|;VHLx5Jw@}2Iq-e5nogv4a=r9P{vp6;?{sBCx|f)9)DOaYG+;@*Qk zCgxAwIbCkRef)8HOVGkFP&$p<5qto|4q#nNOFY1YvAPWXjS+FN$)zGWq zdthxy&5TLaENF|&sK1L3E86+R(fTySY708eW+n^7I+U3b*!SgzU8fb|W}c=^ zJ=?wQl-(7zSp73kjfu+m=TnowcwWe;54sZNDS8Bok(3yiAJ!-TDzS-<&+GLNa&xY~ zKg}Oi2H00t+WCBx@bn)DA=pH6+i*P7az4xu$FvKL?3r?eGBY=m7I?(rYJZFRH)iyh zIk^5BhJ6Qnc(vm6_wA0gK2 z*jUA|xMp_o$5J_+$=TH*$(l5fD(AE8S8>|bq?x55i)8%W%&!E0TCy zx^6-yx@s5TIX8^o-y-~tJsSTB;r~`(b&when(XzQi%5kq6S;C$lDAJaoCXXqod;t5b^?MO9-3; zRJ#KJzE9xL(9bZ4h)04WPu||%s6|1jd3(e)Inc1b+h6~r8AFWQg(-=JIVctY!#06| z^Av=g6Ok2$Zh|lRP%m zFTfG;o#%iE=?lsY$_Vj1@tHu8z*+l-KsRv!1T>Sne1#wr7^)rlTX}A_H^K()zo$z{ z{v#UVG}~`t{fC8(hkHfBPx46O^V#nqPS64% zfSo%BgO~$QV70t#zu%&q>kZ18|J=^}m(;z!6&AeM-;WhQCD;ZYgpeTluh#;xZM;4g zy#2oG16>?Oe1Y9Q57cW0#BRTZ<^w(*PLG}QcN&nJ{!{l;AR&CRCx~Ca1AZIl2>$^8 zS>zL+FA^ZJlv&9W0x&7Oyr6V@f}UXtgxvnt9KlKk);C_` zbzRW^>G45-?-2S=M*YAd?Y5u3-ntdS#d+Vd$fGI32|I+vWJLKXUW0vX>#DQ9Gv|>KvboIM`m$hKE zoFt5+p$_uTiuczj&iBvJucp{9Df#_n-J*ZSuA05#i@e z0EhHk#0rl@*Wxpu=0zDj8W;m^e-F4=fSRB?!QGL5sDeRUcTgO7na@B3m^`AEE<(Ianp^jm>$8aQ)RQ3MgX*jP2G%J6QN#nImbZk=^)-{RRD z;D&|y&v`~U#wP_(#DH2yFL3&j+UWO9ef{Z!IApRnPA4s7IYW;WqcX(@*gJ`WVV0Ws z7CNXQN4Q=am z!ql&f#L&oZQq(A%WzIw6+@|klWN^8_B_U<3HdF|qQp?xp258)*?1Ez4#F+&Yj}jM; zduG+Njq^XIHe3|6s`1Zvmek(uej!ldLj3WblmH-n1YJ5x)Mm4x6X=Mh@Hzg5>EzS3 zn5Sjl?OsE+_yb^rQ)lZfPW?Xup?@1FD1%ppKqvnj4*=J|dZ3v9H>J+`AC(#r@d6R? z)u+H}qx7!fb|jtiL9k{9>fbyH@)G;p3uz#mAz}_Ztyf$B6aBw>BPynom z0!5$n1;9F=kbv5rcLLonyRff6iT`EN2!X;pZxQ|$5aMR}V?1Cy#`h2WyADT-Rj@$L zg*`EvDS-!I_W4^tpfI6Aqqd=SX~hRo8C2S+fy<3CHBTv!*O40= z%6TRHnjC!c;MN7nDIO0XD3&*g#aHaxdS)p0rb0Crp)Iy-6M$r48SJ4Z@uG+UFU!xXY8mQA_HAJiQcI6q1Il(t zl6zQruVIYFJ1F>InS-|&@)kh)*QKsMFplhFe}_Y)7^yPds7dGpWI!GXAUf^8qoFa9 zRjbxPB5yl{7)K3Kfyz79QTdt$H=!7%1f#epyFI#F-~%Vzk=e|(|C(hEL=|kFSnHYG7L01GZK)a zhVT2GM52=;tQNN%dWFdZq6VTqkA!I3yy}B)6m|<{GSfbevPw1soE?Ve7H6d)sg=`{AzdV-+YH=ld!?ceC2VdngEX2_24{Ual?zMzrx zPZKb1uty;zlLwj{>iP?3i19%Dbr zh!FBKeG5}|h{$tyXt)TG`<^>@=`KROS}ExwOdYw*S^y{1!;Y~WVuN9eqPF0x^NS1u zA2D|J-F=eGlu*IPJrg7cH2{O_NW1}4ybNu!8d8Uq68@#6v4)=i@>!;^&6n6sZ~-KE zsfssP!0{GD{MXrqKe2*?tu|dC9RCe;<_`}E%iwOD6Yr6Qf+(YDB(A+6(M4wAaBu~y z@|jgXk0fqNp9T4;Y(0T^yPe0KE;3}fchc4l1U5eS?-ZS|E~2qFm4<*O<(45ec( z6617Q1AFqMw^&B_P+^6nYc&~Sy}}$HJQ^t-hWumt=2Pn>pLN|Vr#|dQy2T0fdP8l` zw|Mdf0%iVp#QA?mpsJ&<$&$ZPMVd;m%6tDv6=}1pLFfFJt30NtzWnHeeGW?t2q+V< zAn(?i7dY#8&^i|6-ItukMwcjL#Lq;r;w>&PY)ie4$W6Ky4(oUENpj?kpFaO#OgI{_ zSxYoHh5fnrTq0fdK7I{|K%w(U!w>x|NqHT+`PO9oV~ofh!A-T4Ac@o z>5qHjgdN|d5_?sJxov?%-@gXD&d{(8fd)J@6~3EzG|zxu0g@!t00BYJD|qtK|4LH< zb8sK&cj)c%cMe6_U3BybVp2-*rx@F?wC(Dj3aoJ#LDm4H7h;1944qI4rhwx2koI&xxGqlAy-dQIoPB z@%%o#%WQ(`)^-A@%__8l(;;Lbf}MgbBQ{Mmb)p2&$SuA_`5S1F1@w1o;UAcVe?4}U z$^`ps`a%K3{CD<<<2CJ#P97xl@4R>QKl0v5|6|_!H_Ro<>(HlX#~sWg@eQ%ye8(M_ zcQOhI$Q2*}z+pY>CJ`V8wErORP2+hw@*JgA{{e`U~VQNG1W)dNLuI<*pC(2!{CySa`=lU+Vb0602%H&^3~j z)hfC5y1sKBbi$CQ$)lC-!x-Pg*?`)Lvp35nL^d6v3}Ni04WVqL*$%OpdxR5h%TV^q z$x^{Zk4q1R)P{k0xoSJCG=RC{O@ z=#zvd28GgQCWR&$>)g@xC?jLUgR}w!Wj=23G`+NMl=Ox%7H6p@g-rg)vkXQtV%R3@ zb81?y#!4a%W*%wG?$v(X&g8ZRs=T#?Z_ZYh?8OxoNr(RY+?8P_Ax<|WmdStVaxglx zw0GF_Ra<%f{~`jghm&0J{qsIYO=gA{9_Op$yR7vjuWns8 zif%tj=Bynlbm(u|Ek%S#XdaEpp$)jei~} z$d2&*1(l%J{~_^y>F5K}y%CVi(f^CUw+4UIB#YMq{scELILCb9eb#3?6R|Vz@?l2e zTXc2mvia3et#cHCQ@B?)h%%XciIMIglz4gVCHEr@ZP$;3s98$}9b$me%j0Roiuti{ zFE|7EWVg$TL{;km_}ZI+1UndlN4B@}9-c)FpU?P*(CZGFkw_J+TD=;ZU?mP`TKn=) zwVo9pSH@)UF(vH{?9~*_FK_YUE!fEM=i1L0_78j@D*jqY0*Mc#b;xqi$50_K`)I*_ z)C`LM_>pGun9J1uamdAV*P z1V;GmhlzNfa6L4Vc)mnvvKDT5yY~>cc#L3OMh8C(bm zeI}0)W6ta241RO-oCQoIK+}>a!pIRWCULOao#YJ>Z@LHxtu_4?ys18NOUa7b-x>vjX0qX2-K{|8D0b2n8-((&Xr2rt*e5Wjl}4PSAyv zZ{KZjZunTjr6$Er*rjjDZ;Nn;71}~@0VkV1=)*5wq>za$Md*4Lm|y*cc5pAw%^!57 zjf^$8MT_|xRDP13DFj&|b~rnItL#%?>pZnaZ~bIVshjg*5{wZr!CT1g4$d2(`PLjU z=Ud2->(BK;cChs|rhE;d$&ouXe?X37ZmSa`XX`_AL$b4fAW<LyI7W{*>BF>p2p+D_ zO?eWn1GM_#fZ`4utiSLNA)Y z_D^laS;``f(9<2Gf+krPVhSdm)evV=`sD-8se8wFbKU2KTa`YXx?`z4rQHH@3`}Q$ z_sD#2!A8zEVB>!hIf;(^!!r7B#A6^#AEB~PgMyj!Sb>-_f0dbivJ$XDz6#l+M{Wv- z#1VB?*VH58pnX&C+4Q~FWA>Oh@!QM!1v7vB<+u}h8iYHti}!2zBsMde077 z?`*3@!V zg1LdwuE@~#)&@g@=FohL(zkFS*PjPW>;r$jKL+G$lLf<*$!Gm%xMhk^NFlT!_yS;E z4UoPbLj{9DxJFHvK7DQg)c_mR^j$2}c_i$}@wbEmj`=XPRpm~F30qr4SA6y8O zWU#@7vm%A^d%|%ojB_gqq7YO@K0EcKFxV4O_yiBLs%vF>twRA>5B0LK&mpSZkBTNT z=NG-0?^3Y7RMcN~m%I-ElC>CvW$P3~RVN_imEFY%cZZHdiHy!B!rmah_u7Koy@N(m zl8t|h8*f3x|DfT9aquscjN!l00{3n3z~}@$i9@tp8lm0?7c!mT1Mn3lE2h<*kAA<$ za|iHXm4wsU-UYm*=}#n*UznLe0b&dnADx787jG7=B3Dq+{S8D}J}}qP!?PE@dP~7* z2CD5`3G?IFMxl5#SG5j3q@K@!Wah<r|6A0*g%~;h94`?5m00~Ni}EkT*i@xI(D7XyA}gdsCrC5${ZhvDJqz6cTHp+jX67?i-O!t3~rki%D1{a-Mr1{Rwa1dH~O- zh$KT!idM{(@y12YE|DSNt(TUs_!D4JigV~KqThgqETBJ!$?PM1a^0^)ubWzJ!6;=!&u9f8B5t2jAWg$FCkl$q(PyQJt;C{-I|o}Q=sdmnn=KOA!${<^Qp^o9+t^vJ9-CQEyoH^|wsdrqYs~bCe~)IAdZ*c*N>>%Z;Z3=S`j=OQ#9}B1CkRaq z(keIhoc9nr(LCv={;dK1Q6#Y;{s^~s#d-IpR%IN8n@z$lkt5_Nb7&OlgYQ6hELXz$ z$JgK39y@U4pux$)`=K1K%5-Hh`3{|hAJ?0k4cijNv3wWL6ojU|xg4x)s$kcOdUnYQ z3N~$q!jVh#%@rgOmfQl&(b<%&YTGZ zz%xml_bdW4YH&76A*%Zp&`g}4b)|Re#}en^BUtb!q+%E~hI)XF$M8__cszwvgW=(6 zcbtULkJWl1Bwd`zqDmFG{j^D@~JA6RUr+rZ#BcVHxzIOdw-=dqR}CSBCY#wTH_dZS)q(0JVA zOy9@Tq8s<#txYX|_xSVFz2X)Of6R_`mJc{GU$516G)QI9ZE35kR2`naVza)ibx;0j ziHm85IcqDB8XeY*LamGk>}pUC?#sm!=n?OaYHYh!!0W;N=}vi2FE&k0lMH1$6e`@b za@#jN2K@jDP3_{0(niiOri%@KwX1K|zS_wWYQ`$e_%n@oZwIzdFb88EozA66N6{Xg zBJ9l8bM0wZ-y-0!ULDtSkG(z_eU6)J|0l!eT>6=O7`iP;nJp+&znAwD6ZAOBSWnxTni@+EPOX0_I5CV0 z=23@w`=O>+t){HFp%E;s;CUX5oMsjM_LRRNknXX8xjjzHWDl*XmXT4I7qbRlu*y1g8MF+RzdAK6y^ir@k*m*rKY>IvLZw0 zTR%6Sb%G%?^lOc+&w}M;wTyvYGMFNR=bh0KOfVP}@f&KT)@OC`x2RMqpD}xWmg6+T z>(u9=EfOyaN0U#;C)N(e7U)-soy@b3T3hhGb#9^}Tl(e}V+$KULkX4KYtf+Q#zWJg zN(uFwdQT2M3*rIL+TZleGE~WCeoe87ZwDUh9;}!T8}7RR_Xz9^Lv7kjt){z!CUwwV zBYYiZ`Jtts0%blF5L|`%OK%$<;pEJ`{J1&m{8m?{RSSNrE9Zicmn5^$wWok)Q1C}{ z`+r9htJ=?Xg{>#nqEv$Qd4h3bn3^Wc&`{MRHGzfgRS~uj-o-Q#8Ot;uiN(!^-S4W! zOzAV;@C-J$qReW|0RW2BKK}ziP9*`nR6CxdfVBL0q+vuik&$c z`D(*q5(*_ZFn#{Z#xu^88t>@+rq`18d)Qcag@PGk_7?#vLZ}z1n^<4(L|>pkY4YaK zS*Ct_h0j&dyRqwcc<$N}aeS~uNa5Jp9jx7_LJeWPAI#}=Is6Z`J|*Vq?@TW)kXoyv zctM{-QOEo_PH;n~bRBmvyv^GJ2elRuIB+$PNapdA{a`f>IV?Bi=qNkG*uq?P#;uHt z34Xa%fL`N>xW+|;9EzvVNZW+POXc>Swc6dfdne_Go0U%t_DsfDog2~F)YlmQ@X4Jg z195vUC>#kUen}+AHF^|~CQN-w`eG^f0oFYqKijr6$WChXjE6m9kc!&M$z~uQ6XEto z^m*?7ZEkr?fW_41?71X!Ew#S#XYV^U`#$yXvqfj`5;;0hI&fQR1Kv#%7B;alCV;)1 zX<)o6Pmy(3SL+WkAvsGaVX`{uampq?xb8c$Ak_LmKt}n^D=)RIkCERGLPov&=EeAg z#3En1>!J@gU865STd*5m;;KG*;;CLJ%U7GuxT~HDnL7)tirs`Go=D9HK7Ntg;nngv zrjIjy=e+d5nfYCsA5;XkTHow_U)hlycuu-`93@;$O6j`*O?W zn$L{i+1pqv_~2i59?c^LaOqxxSPGT@5*?bhfJtU^Fl5*QG8B$%A#ch~@{DHzF#^{{ zyB$1I}oz$20Lxnx8VMmDpN`FmSe)i>e{ z=LX(JmO!D#2+b2sX+d{85=BqEh~JeW;knD-KeYgzM|TCuwzcYI_d2hnkOwyUGN~48 z_NHm?(EG;8Pi)H)*fg&cZBFS66S{TVIj!(2N5VGTx=WWSWme-?DqfbpiD4j(#lIX! zI{N6DLahZPI$Ze^vh;!$(f_Sc?Y9?{_upPnG#ZIU6G_n{D1;*0F)|sb!T}*&0O|pN z$sU+NASV#GI*b+zP86es#_hrA`;)Cv`by++R6O|s!eS1H1lv`?^4emGk$2iHyQg<(Ra@E9&fiUu??QW#vVOtvu@pO9)=7`ImyX-qxy0tE`jY6$_r-W zog;6#TIaN%jAO0 zs$=Ypn9NtB;wm+1JHzXC+wPh2C;R{SV1%3t5Y36tUuUF{N=S3ATbKCIEiz@}M6Xx> zj_UnVBN;C^4%*ZkgMKCEg}X+2^fuYshBX@ND%r|)Jq&se%Q4+Ltx$OGw4JRQ3(v!v z+&3>O&<+Qu+MyPWSUN2*5xu*Yo|l~vJlyWJh2Ko+FsH-*H|9>FC-1D& zk5l^YYsqPbz9`pd)aU&E;14Owxo_|TmOAJ5e{B-SDzL$$lN`nv9M%TK>Xy_)zbl)A>(;GG9U8mFBlZ*3rHA*Bzp+fUeyYLy<&-_sZ^6=u6Fds?xGbls|Kdi^t>{o7>Yi@f+ZbZGZVbR0&a zc&V?qzRUm%gxyvrLiIn?s4^J8%8vIvHJoNd0(mqyzT|GxR-2~dFx=V&wH6SNbTusv zGR+zu;OzlZGZt3}2}zxmupIzf9^neTZY;BCp?I#f5$II%b=oaiAH2ue4zt^P6qhJY zSOztpX$!3H(Ye=~niX))+hSDsV^VxeYC6aHJK{BcbGn~n$B&PtjK|gGUtUk4%mt-5 zd{aJq{Qc;G2ZQ|Eb|SBl2(bNbY?!tB&natloZ9&Lk2t%w5H4-Ww!_`f8kO?6dgpM> znxvz90-rZ?myb_uH=WHqAzi_U)s@XS8-cTEk1aLgTKJ}gGa9okbsw4w!MW~ z9w^AFvhsih?1iH}E)1Q=xkC2&z`-Z@bXG#NDc!FP!pufKWgS&gy^IpJj5|=9Lh-xD zB7gMm5in`Y<4p)+nxa#k4$vF)9k38@{^<2UROj%Prv`@`RoT*ykFwWtLAnwx6Hk_5xgZ%(+#PjE_~X~oNGO_Mjpl`?JD<@ z53zT9g5#^ZOcfr{B@a(ltov?tzw*9Or%DB^Q@EBT7@nl6a}a7tAd&0JYUXZ=X^BnK7YLd!rjucL%AToo_I)RcvW!niA{r z)9E`Kf@~Fxp5x}+oyX+hq?Nb9$5{AsFSpDsP`+pPNl|lua9%I}dpIXta!qN(J(apc zVf5?b)mk#YHv78beB4s&HSSzLx9yEF>8VW_Ovc#m8N#~9QW9!qATX-}e&rQEnU>`= zXYo_QV;1#w)bCpj3yZiy!)bSkZJYKfVfL~i!GlR923~_TPCO5}c8|H_|9JEEj7u%| zhmk|gUryVyVE3!GZMYKfLN1xF_KvB1_Hj?Z_5wQPZDOpF^-=0~kveqS9$(E0qH*}xp}PS`kc-uNbzJQEh*+=%Pq`$YR5I~61&-@t)f+5mvcKPQGVPf z#-rLYl=z$H!t>T7alGeOJ#N%@NhLA3?W>`l-u~u|82B4P8~-p+u6a9hV}RCc?dVw*sDKnX7}$=b1NMhO!>Y?QU9mNT-n?#7a8= z7cSy>YuTr_R(~7%awx)Prk$2HfR+i|ehJ*^vy(uyI z38`hF?{#a_vr%oP#TnS@&=S|Adg~6c2@|zL&%ZF;eW88RPZ69^lr^oEpw4`!TTc0a z(nct=Av~=7(j3qgB(!A~NyfI);Rn@2ed#E{!6wE+`orsf!qd>$mq!Un;Hj4p^%l$L z+5Wz%#UmsB8QmHMdio^u&d%?-`#hwz8tj}YPZch`#WlZ>iN6%LrQ_OVYar8 zpN|g_Y&OR=ys|#S_pE1w!m&6s3u~~jMos^|*7&xE*}QYN6yNxXooO79uaz$T!ciKR zIn;0S>|O7{`RuX619J%3ZG>(pyP;gC{K{SDQmBR}*wFZ%n{1Z(o@Mm0eBv2=S_R9@ zBD4&RF4+AanML0L%q#%FB3Wn2WFFEuwo#rG<-Mp45W!(%4P^p^tCRl|6D(;KC|Let z0Z4cRE%=M@_|t;_On6+hAoLSJB$EXVtuel9+T~G;IXCZci#t#zK=>qirIpv7mv`ak zCj4bW3GAOH#7kHFD`yj=uYfg@c?y|K@8pjUuBrk+dN%-LQ%;WR3#qslk~2?i?WK@r zQL!QJ$vlRvS0e4lsyz_5*n4gK3*Tx5!=@t^NycLlx)20!MR}-X$)EIN|8MO5jsmR( zVBJL0Y*t8)U^MS1ejc9Qkc=#QG$9p0AP_SE3f%+{@px_dXci7cW5|o-eVPpiNy?!e zOg@V3Ur0piY|SE9N3B~g5Mqnvp>iVN2)C6c)t9%}qE*QjzlqkHyeP(jplz|1=<+p- zZvuCiiyD66y0($UKFRcZR@Bl^q4axk+GRPRTGHtk;f52P47d-gUUEzPHrmU!Y1<-`9qa>lj@Dgl1K*tdq~IHT z%?JMa7Q<o*1{!|6QAN?H-@RLCigv2Eg zgow3#>!4O13JoazfmZ}CE9eye5+FC2O_x`_pp*+Ahku;C#=N7!7b z4gTBG=mz&w1*@E)Y*{fNB|A+ z%;UM1sTqNV(kQ6CfPw+|l~WUz_rh{Py1`~yx`D=A(NSC-b0udU0I=X=1t4m1ng~{^ zsm$VruPUkxs~NZ}G!XtOyLl-Myj`u{6_ zSmQM(YiGN#YIN|*Neh-S1Wtjxd5Qn)?Y{{^;Y(2g$~mus|GRu%=$xFLmjwZwL4^!@ zA1#h$k(>uens>1H&q6Kp2sBw5fN{SKs;W~WvQ^}QdS3=CNS9f1ROl&UyCR;L9MuT` z)fpi6KyKEo`cOrIF;+`6UIWNL4}h8>;?do{i$Gtj!V*INKoQg~`~8Hiq8H^?b48X3 z-y&Q7S(sm-@%>hq|Ap`!0lIKJ7^YyNfiN!w8@AK4lL8vVTGxb~%Iiy`%GchFy{m+LF2#qCNml^M$Y&tTe6^ z!6YPVu-4$w5xWjW_ZApbt)rIhV}B=nQn(GiW+SqWJ|2y0Hy>;}nkpb9Vi?gmTq^RJ ze|G4{kl$u=kywd>VC{>!CT*Ll5tq)^XOxU9z8)$rG?z8%6Tcvxcg4j|_1t0cxTy?| z)D2fTVqa}PNq<~E``g-%T4c-XWcJJ(?6%jrJ^eWCSaR-alRwL?gFS;&KTe?T@0!qR zcF5`)D~lQnylTL6?||;ZOkWlN(RmJbno}6D`Jz{dT&b*8DAZ+z%uTF(IhGFqSXcb( z0Icx5<3Ke#zCs0>m>u{uqQKH102?9V=AVGlFcbtOYz_{#Amdypuf1FCTJ}Iv2)vLxaape@^NZ<6D6=5&h{}>5S&dm> z*AQaaED>iMw&_A+C)G^pC0Bf#ckYGo z>+Vk9F@x(KGCyHX^xyB4Xp`Y%edT*_;8~T^o@--U4rRFobqc?F8fp5ug!n<*a!rJ2 zYBlq@)?_q!pelu4DTcKDkb-!dj6XKuc4ECL%ZpN-Qit|2ijfRgRcqR&$s1<#L1QJI zG62k)XnYT1(-fgFi~P##ugee0PY1em7FXs#X%EVhJZ8>37EucTjOgxCY_`g^>WI(a zHjZJ}Af||md*~u*2vHeS1RsOned5v#_|+Uc@HYMOLWum+C-QqkHLESW5RZPo5G?UP z^(0JD;IRhzI<*Hg%k9Gu#D)%AtHABEX%m~YvxShDExbC2fw5_xMt~;a5eTjLAy%IL z#pYXPJ5wyw7C~X0RS*{chg(FQO#)Cuz%2O0f5-MxhS%Y-upTDd4XP8rcpBVEMZ6#3 z6fD~GKEA3ciamop91AL*F;XJCoy4ypoetoyQOJ}|0ChyXA2m9~zqqYy1FaxLu@n`d zpbjXBwo=ExY&95wS}z2kORy9{Q{fZ(EA-cQp@F;qtisn1V0l0^?U)PzN!9ZJ1%J)6 z(6i7v*|X5G6V*dR&!U3yMbGHNnvjkeyJ+sMwu8=RUBfZlVV^}r_tJLPHmJ>o!Z%7Q zZxl*sl3}IOLNj!}#u@JKXB?+*dZOL%X0H*?La{-b0}dzBiVco^Oxj0G*%;}&Ijt#A^;ucq_Yhk?&u4US#O1 z>wuH2eb?Vs+wbT)24i~h);+lO$jIP6?BSzHM6&d}-K@b6&!@Fx%nC9b2M1WDldbuo ztc8Ft3iy?06--+ey5$&JJ*^qfx3Lq}8QR!2w!aJQAz9OROX?Y(%qO&0dpK*!-Y{VA zr8EAh?KXOqAS;NbL_V<|+j+lLTk5Vv`KVpsF}b~R!Hq?&x28YbIk@*lbfC?Bt{+v~ z?Z*nEp7cajym*$G*8OtYa{q{Avz_CIgHA+wr8pG^RXy!!k| z=Ae8K3ZIX^p`J=e&+cPWF@Fh{*f3YmS~?#TaqPr#t3hMlcTJeAX6fefTVeu5OvsUXN!w^auOyA*K1pwe-)g(X^2%B6CDj9Z| z4MEug1#$l5QC?7>*ktjS0>wfo%=1e{B0k~Y)A<0U0Wf$t%My=uOT^&A%pB+$!tXkup|E`f#?6m!0*^eU=FAP zu#s9p^T-DnTvIT03a+5v2OmMrBiK$dHVx~XgEPEp&vD}CMowp*$#!Q{-j2=ptiWgaT57woU$OH`p-B8OvnGid__-S zY4s^b@;s0gvOan$Wc$392AGf6(9sB6ud^P3-~mFqvD@(`*Ahe47e-+Vt?pINQ-i{0 zqmC9G83Na+8FgtAQ}%cswsfs$IF;8xnGA)sK!*HEQOBWp88F?z_3*YyvU;h=P$u=- zXGWh!o;BQ}*=+O-hV=B(nLTz-yc1LsVNUyw?81xUg1do>2DL%)y#4XAMdlS&Ui4SD zyJ|&}1F4Uw_Bl2<*P&2?uX>hjVDby6&?^V*$Mzu3>k9?dwkn4K?fiQ4TE!I0ZjFMF zmsYP+_vjhh9v}DXV7U|A`u)i0;Q2dJM?5dO&k!HpPXu=Lelu%tuYO*|@jlgLCsgv4 z00p@~;fR$Qzbk%$@v66bkS$;fQRo}9$ur^lzv;r1H(%(p8eNmPKK6<3S%DU>ZAK#`B_@Fv+MMUAz{(iZK=lnmxpOA0Ane z0{7bM9fi%5HGxXqg02Z7i1@QG=&#tdh$n+NWx2wKSNI4ry#HC^`y0a>4c2GS@c;&2 z1W>A~FmR>%<5nJsIvkT%;# z3Wqw^P{3;y9Rf?uaM;`;0*lPa!?MZkE^7$U356rA!6XmEpN;?&P-Ge?HK-W{7HeJV=Xd5nodYObCjSR*7GRD>XE|Rk!uii& z`4lKA{Ts9E@AU~DY4AzU5zb~r76P@y0G8_o;zj%M7_e+HgF%osEtXmi$16adXDIl! z3gy#dj~1Hr7+NDF!uR=riJK1hO->isL@Jgwrmr@(Z33k_=zYP75V@bFL+y5`M*<%| zJ1Bp((v2Mq=g{Mi2z)O8vB3b?{td4rzAoQ6mD9uGw8VPM1(`>Fn}l0>9Fd>6Ck=Jyq+f);r&12j45wX~|q+L+4>rm3jU02f^6`0ajx-_=YvdnTkRf-a4B1 zJ*iSz7d#p79OfGYFA?6pA8IF|5ZNj?7t6o|{%a<BfPX})w}ZicX#yLtGn0PbI)ETO-&|W zKcBsh2bluiEE&+MM9=ZSWm#;VbRNz|S4lqAYcDIdRd&uRn?v>(CL5oA@Aqbqr95b+ zdJmY){{9f4RW#ze zP;pgD%_BX|rj8K1qyz<_6jvVJFByqncu;E1!G=$-f@yL3kpup z0Q_LqmjVla4U7=U00@-k5Mzi5l;I?75RC1n!0mf2;y48?MtGxVEq6m%28ERVBr64; zKb>WcsEFckF9}rI?H@165HAl8t=DWG?9!S8Fi0>#!_$kIbuRKObS-jDc3n)=cuv+< zQ94lwH5+sa!ZBGj`DkEv4sLD~1B5gqPBANB_h1?{7d82hQ#Evn=!8wFN}Z3jlcdSdl#*c_q7ExLulp`W9k0!aWutwBAz5)2}gi?V+Vxc??ii#;&s57tR$paHiWicl*FnnG7F0Wm^WQ>W-<`^0_O|dg zZ@aCx2fI5lZhxM`<-=#>54yi>e*p^0LXNb~C(S;nvq7 zx;(ze7%~_BgH??Fu~NayhhL*N)?889o^hnIrjFt8$H(yVJIr2Jm)-GpzZora;bUh` zimqPiJ%ZQ^6Uq8Htvn5nIoeBV%BB<`qV@)YhfV1P0Y3KfCU!z+yo*2mGD zEtf7uvP-#=noOZne$7+&fE3@k_GfFbHrGK@*6uEK?ME_c>y<;4>+d*@I-OsaV$7a$ zAvfKpd9wPb;Pm#WE}xH&id19ImA*kfel*kzN=0tpHdDUm%6^r-Dd%24=jh_E6&wD- z*q}DO-Ht)PRKwz}RgCNdPr5Bv^W3~xPNs1vn@>BUYSyt9kv?SiBy=GFxa6oVR3;A0 zzy8mn0i43mCJwr|kV;(4l2>97@uj&XaVWR6)T2nJ14kfgF&!1)YfY=QVnLsZ%{n@i zF#@Z_^rO7M;V87wVDXBLM|w$c@WbMVQG3MS-i2l^OX4~WXdpc5e=^QFzbXmpio#yY2}2L zWqTGfr^{Y;WksyNjY^6DutPw$^E}9_9bz#LpxzIef`KBp#AH`;5Drxhj@skVY$pvT z@gYI20>*1o)m7v%c<2`}p8=;E!;;Bl&t)kV!E0)45Vk@}e*Z~h-hYiKK7l&UGl0%K z5e!8^y?^2)FOMd<6B&saYQ|uTZc=nR=d&;wGg26QR#f-Q3$~yq)36?m&&UUq3AR}S z!Fa8WOWiK8M}`pmM@6u)<@aN{ihRWXa9gJ_-?8%miNuyQ6|x?bUgJkbVwKQnZZLtm zUlWgS2f;@&cyzcl8NQH$D6?$%pQMtjrXWqFH)5Lh?QzZfL66p`nUUAXE--zx_LUb_|` zc_eLKD3*^Jw3QMDbXiH)oW>YC+b&&Hf)&SlUz+Fs$nk2D?$%aa38-~|!dt5t^Ravc zz&3fX3JB5y7OQ|iX#qSse=!?YL<<-NyHLM_%m4tOq0J}|ZGz(*F`xz$rX5j0O%AzV zE!a@%Oh)zaA^TBy`|7Gm5O4ONoa9U1t0E^*ctx%LurGAl$#-!xT3=z6s9xFR8PeC@ z#R72670YC(MSwzFf6@cdlH8EB=ABMR|DK!6RQafC14ZvY&_^qJUFB@Uv0b ze37U5GU#GK$p9jlqpEJjE&^#5wM`HM9TmYy;rF|}ib)oxj}{cABD$6}xUcXHf&$FH z3~vw#q*0sOpN8UDoj`q+J5#Re>j!xY2w(S|c2ji;(8_#lHuE~FmN$;+MVepYehFLa z7YhR1+ltRwMPAC^r9UWmIVIZzR7GIFne2s%eEl-*3#Z95ts%Q;dsGgvW>%+{PP68E z14}m&$Nqo`HqdJtF4s7^_Ho>C`?o^nI3zpSMP>r-Ridh{rj()xjP$$izC8~g(Op%w8b{Zlx~jM9BNry+`nO9&nUNI z&zt5ec`+`%XKyI%I$y$`UWmC&Tzf#mxY+7YOuLY6ai8Jgw{yns@?ZB!-nd?BeWEh; z7=t7(0G^OEjMRP45Y5tBa5m8|`+`=d2Ac11SnF**U9PM($X$;HN!%|E&E1YuYhgOM>$Vf>B3%9?E(skhTgQYC;Q$x|hZmj3E1Wk)Gvqt10~|)Ba+Drc>QH zV9uAv?(H|328NDz{M1u8jl~OcvaKyQy2%ZPEBtWOK7~$y$G#;Y$W*~V^M}08-E5i! zrGxAR(PY~)_EdKZ+q$!eKKn5Q6BWQSM+8m=|9HcwX2bhpJR!85Rh>E@IR4(}`b?~* z(_8Hi@2RKWgkv*4#P4E0K30AUes-UI7w0jft!_|D0Dh* z`xX%wte&PDdWz0fX?-1|KIXVNiQGD24>74x5n9fB<$2t_W(#5>naRR@N-Tn6GUU;wsvYxT=-%5}ZcJ0b{{~ZiNq1&5aRy7tfnKbLd)g=>3T`dj~(3 zsyXd5zh?AN{G&#~Q)y7DSkOG}v4lh__d%`=I|O_Gl_C&E16cdB_z3rGze)<-!$(;i0k@2Kr`_`2@>&i6Gt?j#-V zZt7&(U7WQ8LU~9LtjGT5Ocd}dUk%fiVq*j-Zr(|>_jWveEIe}^KJex``FGm!o!raf zDwT-c?$zrm4R{Psbl>rIvTPVu0;*=8ot5imuGoFb!=U`ENIAd$BPtWqyS(RX%(#OGruRIn{wQOA z&YZQUQLsK_R{5N}1)o@^kxGm$_ieF)dBYsFkYMKidf08>-V)`DKPbnd9z5S_xLe;K zVCL~4`Tq9|Q`oL@KZE;OW)4t{Ze@s@B)^LJaS@52y9Fl*N0}$P*Rr=W51{DlVt*X~ za4;gzn=6TAq!m2_=Ttjk+uYJ(fM zgIM|6OzV3KQX(fk(ua;en(QnN%vX2DA?I3tyks=7o_uNC@YMc^(nAg=m(Ti-!vn|l zAp|oN|63{3h^^oSGG`MgW_0gi?_eGXG@7lG%w}H{G6ly;X9~!G67V#(b5mGZjkVf` z^%1orCRfc4vF&p^Brw>Reuf^NWFyA;P(V&$YyA*`vF=L1+7s8lgQN87o0OTq*Nfiz z@j_74bet)O%2XI4ZD5~mTN9WwqyLbdmyJ_pntqVZ;WbqlWxO-!^c_m}mGaiFK0E^{ zJF10W785*>)gL#YAG`i5bKqN;1Wq(n+bM zWScKHiDf;NR)R7NBFIr$a!V^|OrSY$Q1wB>L9xfz?x@tYRsUlZU= z{Nfqgt9qB;1GFivcaP&g>LGx`BvC`y=M<_INGL>s?4n^0!mw*R_4c!Cw}{%^8a+bJ4@tFUhzDcV665R_39-6vRv=MQFLpmG?3q zq^Ts6(a25?WQwdn$hoSd4hXmeAZ`MU((00#ur^3!q@RSx=Wgu1V$r zJDdQ^9_Bgu?3`=T#KzB!f#Y{3yM^Sx_EG2x_`JiWiRJcH9cc56DIJDtSUJ7OoYsjw zcJQNVgsMX|Os}N(-Fkj#bDVdCT#cirDa3ZFK%wtdj9brqBEO)T--@j)0;SoKD`8jC z6SysQFlp5=a9DFiGl?IEX~oqx`*2~a`)~{xMSiWLr)`^zDZuXhp8nJWW*eg}K zq73Cy)BLYg8qK87h}j+D^4KgRVc&jA`*5S@^>0aBHny$;5?7`+`5ZHNcN$)=XuO{O zWbhihxwYe$=;o|XnGO`RJIeM&KawcmJ1fI<%5$PAzh3U{7@X{veCq5wAYv>C-lDTO)lk>do>5$pS^}C*7_V~N1KrIy%;#fsm zcj@Hg>6|!q7_64qHq8Gln(u^azxj1wZ^SfhUhhI1fNl_soybFVqVRy7qX2>f4QS~e zLnIfW*hf)RdH}&w&AL$&w#EfNf-uB83GvJ)Z_VaqxDm$dv7z0^zap&aYbg=tJ4T5n zR%z1Bu=GC$X|P#Knf)&hLkytS0SdMdR?!x*^m2jgAoABbB>$s(ur@$Iv`*}uO}&DJ=$pgIMI>Ak*$DD%>KOg>#(AkkeJJ!1C>uR?fESR zzY~6Vxu|#L(vbxe20x^hZ0!`~jcUqp?fLNeOZl{}x@((t+Vv+!7o1(TcxqfZe&|`+ z?T#OD<&|;MC|H1ECt{xNF{2-pDh@ch_P0P@el>a@|GRSmUXCdsYXl(D!`C{4&6pJG zr#kSg6mt@aSt6N&7y{d+oAk3I4t+~k6#TDW$ELQe|x2T|M^Ot`Bx_AG(b~f0syKB zwLT*xzS=AZ#t~19#)0t#0Q3+g;T$tdrS8FO7YHePMKFv0yDh6|6#VtY{qJ5H3`iiG z1weWUMthcA1lVFk=Z6t7sM$cCOY+f2&{@Z0rsDCrStIiCZjtO?nxjJMym{J!?(pj0 ziDFOqcFM7if@>SboGgU1uf>IixaSHE+$56k3xQB^@hBEcM>9gbLr`dIHDdlty9vIv zh`)}a?>}g{E7?r2v6gSa~ygo^R8McQp&Z9`7bBK5{l9NcK7jdAw z5x4WAlcC$uh%PzH+lzKd4x^T!pNoD1l<{~fS|6M?zX+21!w;MNZax7I zMQ~lkIOvnDIbcUWm>WE^-^V4XH4xsesv089E8-YR&lQ>ti=k4#zDEo0O~S`)_xC$U zr^6OHRrg`y>(=jX8Hp0-!;!IN=dIhoWNCScqshMRt^Nj0Oz|)Cx%GYdovjy=rT9l_ zRwet)_-9<=^vojpVdVa&J5;c+$lC~um>Y^-q26YX%w8+r0d7z}Y{h?T^oaW~aD(|( zo!$KuW5V5y*kkibevd}aoM$*9bxNybj)n2AO#2-vINNh-DV^;ky@KXZ2%ZB0OrrmG zEff@29&#T_k~ga(q4n;*5l%TH;1~qY2p45|Vfa}1INcgaCS8V9!`qCalHGZINP8iJ zb_GL=ro&WZOn+k!a1gk7xu@Tn>0)1D>aFB{T|wjbzAv(4T)$uKAdZ~x3lP~>J3@Cb z@k_xS_4}GW9rl?3%Cwu%GI?r7+Xl)I>NOu7fw7njK4_Ba zH?1|vT&Plw`bfv{6c*m-sNttmxhZw?5$Z$z+@>?n9R&)uzEK!toRb@Mtmap*9mlF` z-LFz-n-DFBd%HXE2JbI*&fQAX+*sE>rKjmRb^1Pxqc6Z8>PSJMI8dl!)nViR1ic_Y zx0J&UmSK554RdmnY9ir=c=XYlc>pVJ0lxhl>5GrO)Ts3pC}W_Y*ea5LFCH%zVhzMi zK#}PtP-J=slN3jtE)(Zf>3DD)s#{qkm>Nzb89DM8xQ#Qqi0vMEawX2SF;wK3)#Da# zrz!VAeOW;#1+4M~sl5!RG;U`!*(siKss+hi+8f`>zD{YJBCDuc9`}{lCRuK{%XsWd z``lWGsQZUAvL*^Z7x~@J4^!2Cg6G| z`QHb#FkueW~?*)y)oG zHo5OCH1%$$a2>2XKl8;~U+ry^gwE~kWBP;e#|(NMa8Usm+V^T%=XUK^84iZ24Ph6n zYhk3d@N>kVB!;z4dkcB4Wpy6iagk3iJ**oT=C!ul{lXjeicf|-VOcNa7#tBD7JhAu zy6l#YZvOpp_7L)C1aMe7#OYXhfRedbk#{X(qbcBWW`x%XpZo{Cz$aC}@iA4X$Bctb ztvCbI|LyKNgJ(Nx;8EAQlU;?fv~OAFmy&kw5|B&f8$B$rq4#=6YEWrExF8#e@1)zm z^L~5R7h>|Dk^y${ezMdsCg5b)iK_H6s_@esQt^=(%K&bsc$pjcOT~jB3G>0NlIMuI zhv>c@guT4$^HB(29JaThe;1nNx!$v6^2ZIWPg~R8OV;~BBVq)r6_=rgcMGqS>#|U1L5&8<>0LxlbJTK~v2y#~#hl zKj!KlI=0q^wJGG3T1VxH>C#jCE}nQ^@X|`ddQ|LC?Uc$!M?h-KY*zoX{w(HZ_w9mn zThsFV5oWAeW3F4K!yfHVVzuQ=bmoPWd(f2RN{ZHa7WHF5<@Uz@Oj6i^F2qbcbLh1%6NaO;~JwuCr zChI6}1a3G?EJ8`D!8Y=EankZ2h|_QagBG=Z_&^@Ykp|O~cLw9w$?}VhT%+|4c@T#H zO4U_i72|P_B^4Xjk=P;u5k2zhV{oA{y3=*NsRv}e&i&;(Zi}30c{sdnRQyPI>P~51 z*U*HuB|{I3#sHw{<%`CnwG3IK-j!Xqx<8%GGCo>xi>&s!H0xy#^B22E-zsFq?>{~K z=$?j9}y>^lZe>h75^@~ggJ#c{*KzFtI` zv=`yBN8=WSoTBHxX*-OtUt6+-9L_n|bebp4HVel!R$)YekVc|mFXrfp?*JB-35umW1XUH1jiNU%hNqli zM1sy?G|?O-c*Z5?SefHVgn zNVC)~BHJq+d4$?AFHpCS0wyEC3hlBK)@=J$h_+P%SVaEas8uxdW&Q-0$j%L1qR+hO zKhlrN7J}0QB}~dn@HCIvW>XPk+a(@=h>zIG_JEgT_tpB~#@W)bsA_y{n_kxL0q5SX z2VolYDU}M3obHAZP_7|b=$3FRuTcp>ke|4D&^=t;wPL(=m)1E)3#o#_sT{U=B9FN< zVa=fDw8^16N_G#P$G^b)%dF!*k!m8uu-^B;soTTJ#|N4tfaA^UGCOXp%`?5IFn-zL z``L*63ww}zwYbZ5Z9i#_^TzvT)N)!>9)TFENPtqol?VU1QWv_s*9=aaxV?qRP7T`{ z_PW#chW*N{a9*hJ{)1UTm;5c4?O-99?Q!2=E+|(Ar)f=LlVgsuRadFUM}ygLKp8dy zAQK?b_NldPRZWP@nL69*oLaE%%Knle;Fw3S}}9?5m{nk;D|Hw zrx$2nJc~zPCE_hkhR1BEBS4*ZCy?a3y!?EYq*+G|a&fOq(>`PVyN zgRO^~=?Wqo*GGo7I5{X#z8$MPwQh^C1XglGY4(=L!vuyOPg&!}Sft{nN#{v-yCnAC zF8J7UNN?8{o79|ZgBcb_wRdvX?S`3$6s!@Op*PhRnA6I5zpDzi zGO1h4F{xrsPUA!QGlN=Lhbwi~N8BFZVp6@T*>GC?LMVItogF!3Eq1zS4?I(SYuajA(3U%_~oPl*qRc1Nq?dDg!bQ;g} z(=_i~>tf2e9(hmn%?RSo0fPA&r`Y!e}A0zWxX{bp`uSMb*|-bR-TTw|DH2c0cZolFm8C^{Qyt(u{+)CzJE753~>$=AToyX zn^*pJn?7D-+|y{Efrylr+YJ7S%?{&+^q3Cs7X3>tGbkpMFpgxG>3D0#Lr8^!8r42OHTQ*Gj z6n-*b(?(tsoKj{vwgvGGDAP?45q`zC*UqYNeU5DoAao>o`WtLmRDwbo`Nb>}syc zrOZuQmoI5xuue9E9(N3K8_7Q?O(*g_`LGs`BDK;K1IQd967(GB|rDi5`I% z11L0=5en5LV3{d6uc{59vp|3p944%S@BBAB`ro|xO$!oTWRT+=aRsPGi}F(N!x(Xi zB;7ZNMk0D)enm*fgJCQq56`ENJ%})kj8uREviJdK&^0W&^t!w~P@o%9V|W!aT$N~y zW;|Ebh^;E4=OFvE8F{*(wO;!w8L1_PqDA(4$`>OXny$VYVENRZL%7$Tnv88Eu8FA` zl-`&`cg}*;b+$$GJ^p#~vz^DhZaGO>3`xXWT&c`W=0DnzwT^4h-$*kyo>lXRK`QUv zlLe~veKyX`!x4R8l_qz~)aojNtIs{5iMQ@kG?&3D@P z!ZqJ<=sb@_d_fiLZsh%{lXdIoVWMYDAOxlf3Q&2#KPeXdw;moqlb;U*#99j5YnY=> z-D?aUAB-a@5UX_k$t8kj3c-aasre6yxcedvNgt9x<|Hfyq-GJZSy3nMWA7V?T*lmU zC6fz@lUS`yIK!Dp3d!VEjNJPA<#D!*bR_E!8kb zJmk3)wS5M5wZkhA-u!XB+nwuFIi{`Ig}SbsAImG_^kUc;MgA`fC4R9KEVv^IFWtb&VPVi8Ei5 zzlv@cY4)lU!c3~(>e*8@B6D5%Xu@gw~Ky8Ra2drElk-#>Px(WL)} z!0}U~Tk5((OC-`?mL`<%3@q`#&~P>3`^2!+x8bekN0jKQj?=L`AYJ1s>1|s7f0TV= zkSI;F=GeAv+qP}nK4aUqZJe>s%$%`p+qP$RzP{f!+*l>9I-`FiOr4Us50&3g zx{)NJl%UJBI5;4?eJ`bv9^z+_a{#v|4gY~IkcH_Vw8{TYn$w*4|IniJ>mX#3h&g@& zS!9$}FmAyA6K`%=U_qW)bV%V9oQy{3m`w|m%C_BbZ}8-kHPG)oK}gOM&H>4Zd&^h| zJy-#Ah}C)EP--b>Htr~8Mrq9S8fpKN{B^>acRze0+YK?yYwS3B3tM0YqM-#q&la+& zCS(~TB^c>FMZLeZ95LCQ>jByX4)Z`o#zg)WB^ejbUw;YcoxE7Xz%P}m!wprJP<^h@ zF{io9vpk`LQ~FW${u+OVs0`Kzz$eC}|DV>=f6y=)S^och5C7g30vP@aD)H~=#{Yte z()=e()c>`duKW;*ykHY(;6+{rwBd#S%f}4F$c?ZJeCvnM4L@*?jbU|=umF$n`)9rF z{i*za8N#Rkb8#6IliK))(ft#_GSg4mg8$z=uj!Ac^(Qy7=r>hJiKTb&1mq1}7HxdI z8kC^XeON zPfFi%-GE2JYq>7jasRt`SFeY!XPcx3->xa4Y4%v`937J_(j9Xed+f3Ybz$SJN!h8jB8Dt7kek4CF{VxxYckYdZiuiCK1P=98MM-nn zc$f(5xB7!hv<&kH71}ClK^rehYZz1J_smyd7fovr&lo%hxWzvpfLQ*C&H2BRZ@%Q; zAz%MeGrfX;+>woBlpusV|HmA>MB|Q7n^@%jV^mIb6ce3UItt2J^|4QC%W9+}u4 znE?J6ZbzfLV7;zX8N==CpuA;|FGPas%6?VA$DD!me?`#oJE0^G*-j@vM5d)N&3XkQ z;k=(qNP)Gin!t_zA~5I{4APu~Udn_55a*n_0iV1ic8i7^Y_pD53$aGN^v`{N;}q4tG4~;`C)3+&Ub6O^0j;A z{>Ok&USKwWj0pikXakc)2@r=_K%nzR)T!gf!gQp2&P3l@{e-`NCipm_5PN?a zvMh;rwgNsE8ct8(h2k|(HJbD`m`EIMUHSFsGcadVjMG&~*&3oL;hBJqLf|3d2bmLH9~Q$4w#q2p5{K|x1CXoT?qkMIz4 z`ICkErztQx!l}}{@roS$&17tdqzEaj7_hjLFc7_g9E4=jf#_ZV0lU2>QG2@@dQ-OPY|1O>M0*l}<%1hL$X2niHflvI557U(1H13RgVaKE|9& z82~^^nmh=GFL@|c?{x718p0*gm@xK=fmY6jEDh*bp zs30#>5D7QgADmu(p;(z%Z`Ui4&OybfI$BVgfk_)1YC4m^I11Bh z0yt6EQYE<)o{%k(g%2wbEQA&hM}J}>$qz{q3gIp|$PDfyT}~{hcQ6A$lQ|>qBbgB22I9jq$dyHsj>+^|uU2Y)jfgNH~r>>f<2>IqGHkc5ts#&CE zyuv~|E2W0kJaui8c5ff2C&Sl^(|Mi0cdMRWPQESPogeR6d5>@B@0U;Whdp#@!{qDF zdFqdEANzO4_XV96S@(yhuW$W_Evv5IKzP@~WJ4}7vzjZ;d)Co`VTQljyG}Yx5tbFv zH#;DQis0D@o9c5m{pqlg7y~J6M?QTDdwe<9= zujw(HtYaLZV`Q4)sAJ<1QU{_I_CB6dsxSF+a=#t@cC)K|0v{!nif$RAts4+fiG(Nd zRGQ#}ENqW`&e^RsL9zFSsyDcXCatzbQ0b%{KWx$nw0f7E_|8&Re~XQTbw?=xH`H3FFlo&{8}ACO3UN?slk;T*3GwtEEyT|m8M}mv62ODIUl~X-CY(_bo{wGdVzO| z7Lcj@m64NkwZ=e3BDmv`n`;mtdb49qd@HWVnX$;VnFDmv-yU90o{+JOTJgtkad~{~ z=rwA+Tbb(V)}TWI`OQEMHQ@H&5r=qQT)|VkH~`(Q)e@Y;{7boZG3`AQvkMFrP>$>mF1ot6(sZguFQGm#?>iD{_&B zch2yUpx(lbnG)MQe!9SHimu>n>J~jfHg-jiEkisG*Ehwl|S}*kX_>S567fIYw%8R1Tl^d7M zlC4Ag8C@H@liw^Szmwq#8w00sZIr$pE*XDK4pH9_V|DW9;^t+$e9oBmfF~wPoIL7) z-ZhVEQJb;iatPQC@*}5osVrk$^3c`JeR1ZdQX3zQS#)rj!P#7AY{W1MH*BZRg5sD; zF5ADEW8^xzKN(9d+r5#)%Ja=&+&3RZ-1)=fjbgk5(cLtGliZ|dco^gvSe-lH&MeY- zmB`2v^2J-qIL>oL875}y-)dFcE>)$lwm|Ey{k4$_Q_P+CQW!Q|iTLe?nL|0a0X=D) z?)iQ9>g?5ECnp~!2GX(*+*E`h)8Vpu_}4vh4e}ZfYEJ^DzL~6T%6QDi=I)%N$;!t@ zXr2rJlD_v1xJ@DdL050RMpnVxvNESWZsg+gbFbQd6yaIZFQU2fL2)fu%w@N3&eiejWFxVuHT2o-8Is?6DnS>-wJ>huj$Uj;+2LpoTm+T zTUd(V<8j?R)Gw#U*PVbR)J^(NcN=0+l>x5d+_P5d4FYI!R$#vX0ESM%0QT$10g3<} zW&zqP0Cac&=VZP%~&U?&g0^hv3=NMt5TkV3JPy9P3rQy#xKrd|PbydmM$IHbbJ^vE^dXL1t z$hmEG%jfgF0SETxvILML_-(}3=|IXzJ11?fWNQP|L25p?nss8B6A?EO8%;VY_?}6B zB&SoOW{r05L#1_$Y0p#DA|M(lgHaOKAoxC&Voa4^@z7#}SKvBdKkEw?SWDi|)KnU{ z8(yuZKOJah#G>*P2y^PKUog5N)rYKFjus+sq3aWsZ1LKz0t z3!g85So#1o!8+MeM6LT!=ofs#WvSpBv?CU<_aJE*Y^pSQ-&Pmm`s-U3pA=dc2oz?Bba1g+VyJ1A_S~eJ;v9^T4|acNaSORSX^!d$cC_+M`Fx z?-#7(p=ZjLRx3wG*Gj*|j{X-n>hRnQ0^#21HIMxA>C=>tJ5hBg&iwrT>EMDRk8qSx zs%lmLsGkI@q%f*7cf#S@)$66s?S8RsUAXi^H?D_!yVHrmOZqLIbz%D$*U?5E<|S#e zG{|R$?q0610WdeUt-1hY-A-Av(dMp%e5O<*3Zn~$E)M$CW*{Xs2Dq76CtBJeVCp}P zQz(Ve(I75;i2`c_CLh5L@Ak2GN;J*`Y52P26@Q$-3LsfV3fbwX5K6EQK$c)iRgp@I zN{*7q9Zvln?m`=bS&kH3MLQVPY%B1j6|npGgHnu{1LzXc1u&VSEeZQ-_Svk(tX#@0 zCURsJrtQKl7Dh{>xq;#cN#rClvNHBsc3Hv9Ko(GIs3nvM(F*IV25d08%tj+^pkRPF zKn%w7A%8n)cH_VMQTV_klc9|H)+8Y?Eq)tTp|Sbh_l*8dLq3)q1emU}JC0aG470uTF<7~B(LE*6vI zlc^!;LE&^nZjQNg24j|gf05&Nn2H$rOg+$jiBaUtlszR@A*yr&q@^u^XHA?Rb`Hp2)( zX&UZ;o;w=uhB@w~v(A?m#l)NaV*G?3=k1$a(M>vxb9PK~#!0ebo;EER33zKiSFPB+ z!q~+3vQ0HHGOx=r+$VRlC_yZ*O2GIk&wEnrFzpjD9CWoyofwu5z|1LR704EbxpOLT zHlDzZ$sfkdmryfh)td8;e2{!m3p5Lxx)(hvLPbm|U(4`N7zo7381b``{&M~-OoYIM z+=Rfpm_P6?WF~|=WTtqd1%uEM4A#%fK%-%QnBo=2vK}V{G82Nq=z=`SdLucX5;GuR zB^!*1I)zbykU1F4aL1n~GYNqieSv`cKrv4$M`17_rsD$~MrG5IK0uIzp_%+4c$^Se z&RGB<5)?twr4ch{f=5OKN6eeZEC`FH#b<-Vvu1*$;R?y}?6Em#Lhvy;CD#7#rxo`A zD|8dU&IU7!%mp)>2oZRy#F}vk(@Z;rYGmxf)Vqc<4|HHo#RrnPG=;-5XA`nbIfSmT z^keQ#z?g>@VNM|o|1`?|Jd&NW3*%__bNlO0Bl=H+PWDfOWhiqo0rw!LQhpAc66K1V zcv*U)oS%NdHcN(W{Z-|zQHX!{`G+tH)Xtx7r%L3e%G&0(U<`95+;>%(f$6R5HtQD7 ze!g`5o(Fb>4Iy-*X$@Kt;iH05YQ|QRo546YM zf|(e$$PLNbw`Q5f6Jz(R-X5Lj6xtUCWHOK#sr20eY@gvQi+&HZu|-~nQ{cksXS#Xi zUfJ!?E7^h)P1Z8Z3mT=j9tCR?{zlorgG}3M`5L9CbvG?LL>TVc4hi$QmSi9n&~#%x zlmpBb>ZPwxmyyQ;1-cn`)S1*wshllgzw)xiG8;b15@^$9f`!pPM~e2@}t=3n5N|FgRNod2_Cb_UT*ZVILz zT@gtCa*Ho%=N_5cz%{YFf@@&;x#eHoC72A2HfDb(m?r%*OmhL5$C-f5QZHfBR|6+d z(>0%%4a~r8ewPk{vsVA|P7slG^izjPm2LlRr?v&>HGJ2BX>4W%2JyFJi+v*}D_$M` zDEQt>>{t4l39_8NsIA-PyZ77sWG#m12!Cn$hY*-RHjLe$uN6Ro_`Bm2;5$q_QHcaY ziEvK)l%*sK-&gi|mauq2sS%X!Fx5})!;VHwg?1g{jl4?(3QqO9%zLu?N(ZUujhN<{ z9&Q=@>cwm@j+EE?V$$;~1gtGoG()T+lrE7BY1eE(RWKWyaWAB#UkMY9xOW&%i^!rQ z+(FczIVE~}MNXEwCwM5J0hq-B^X^SA{1YlzX8+aX#PSbGWEcq;**Sh}P5)tFV*1w= z%m1=B{XpXW-9y00!SSCs{QtMJB2(G0Rz)Z}Hi~|aam0Df9@w=f{(ITC*Q-gEm{$pKwVRd5U@t5F_nAhCu#Ut1^ zgEue)u4A~bYrJJ>1ky-PZ~wkL+4)F|D@n-7pcL46x-V-?s86Xv#|bG9DoSfi2`Qn5 z7nm698(SQEpYLB7S%fgSvxoLy3qGnfk%7t6pLhU}1P6!KX7(=+YTP%^?C~@RE&`Cq zT+Q;CGjFO#-=Pmdf}27!`<@3uhGr)BUz=N8OaO;q`DV%ZHZ^i)G;d=61v=;bL}6!7 zb!K30ZgzbJG&}*q(9Glrijk$MvDy##Emd-KXaQybmY$jM9d#I^lT*V3$R!gkBO_y@ zqeG)J0Z`h*_dV#DHz$jGVcYcur}}~e2tYwpR6|5B_S`f4jgOCr2rlmr56lb=j_w~2 z6ao3SZ*<^;Q=QJvfBSo?+{EP3uhaOATW4)+1MtQkwbU;5TW#;F78gX}yE%x(|2s#r zw|8-D0OkjG*bdntb0I_aGw^SI?c41L(hVEiZ*i7KeJen}QP2<4=kHx}6LZt^NB!LI z%VNu?THxF7Oo7L|pmUHfc{8+$jkr~uk7^z{z`tx)BqHp?2Z8bPJZ*|pszirje&jJ1F!&8 zUtw*0zbZdu^Eg$%$=CoCF74kI^1tx+zN!r#*nQL(zOwkJ(?7yi_-)DHTJjpdpP}xL z+rcxxfcaH3KQF&44)o1;?Zc+YCcj-@Z00t`KJgL#GH^GtzY%Xvjc(yTP>f#jP2N$A zP7f_CpQdB^maD%4^3H621d`2nK;GNM*UWOfbC7h~QuINi)C{!7IXzE%dmw%cpxhrY?t7rgC7-W6S5TvHD)pY#Xs z#y{hedq$3?g zorU3IbZZrN@PGRb-WLu?Sij5USLsBT705#K|e26OcjeBL-)X623Y zR8d<-vuN%maM~+~-pU3WK{h#4*@68vy0=@|7$HUEu0yv5qctz$Ybvv^3WkG~TB&ncr6>V)>%-V5TFYqhW+EQ!OI@5OwG@+AUtUobA7M0%jjtaWUS6y>hZkey037u zg4?Pnw#-8u@2d>~sE*yi>IU_$`?M@xSC1LqY_G~<5MOL?Y|&_@rwywfn+gh`Z8p(m zM5$>NPGr%=3UkXURdpVrZKyUPiZ6g4<>mX zL?p1D$O|N34hNIT8S4P0To`@k2_ajF$>T*vYd{0Sb{QNhb{ z$xiyXD(>T90dPy*4u#Eml}9;c?3)}1*Y}B>OOVn!XyGcW1O|rk-?QlaYUA;9v#W3A z%}F151*x^~NW9kyJ;z$2tUP7p4Qq97LluB$ryIqO71K5lWXVsp!#(MHSAwwtT(&?Q zeDVukKy(dd?kch+qFBNm(_pUcr-gwVeOF&4$uBts2ky)gh)Q)^?oDhLn8sn) z`LZx@R%D@|5|xbS57nAYptMd>X4SHa$@2m3oX>JQW)!TXeO+z2qDCf;1(W_>$YNF< zPRGN~sfB`%KXD$GvZ#NIHSk2pCZ9%+h@@XAci9RsD<^xK%$mL#C|Z4OMa|Vtxrn`) zJV93y#`FsCJBy+nCxXB+nKqLu{6Tf@aESBMkstSO*5I0NoZ&ijbUX9Wj9e_$rB=j^ z0k*<<_3pj)oEMt}(fiU2C#ibj+}#D zbS<8|{4_i*F-9fMx|`95olHZPxIml|h+Er2zdH=x{Xq&T%qtqwv`+s*9@brUowc`z*n0C4o0zhsc0Ta+*d3 z9jk~=l{%DZxCL>eAyjH<2Fns@`zOVaZ<_5Lh(i{GSy$VdCA~hB1-TxO|8Yz9tF|2S zT96@1o473>{4pI`*DEQmYA#!xQFv?`Rkd%G(`d4Xsj7wF0oB16N8I^_9R*#$rv=m|du;J}ubCnEbq(Wg%z8GI7b_768-&QaBe%u(30=plBobedCh zEz*Xw4NJtk{hMGe?!h;A)s|;?WjBRK7r7_JihU~@6>>XV(fVIyIZiB&j2cPA?G-r5 zu@aVEQgFw|7a9}D`;T;TSIx{#iho`Xvr1tiEi#JqF`&yJb72?4yc3#Fhs!%uo=?-+}#Qg>DjET&1|Uf@9z zacx@CE6~D$)U!9|ooYmnHNrnoXIx^1_UCOq9r3Q~+Dgl8^rv(I2I6b^yW}^TSq&KO zS~+1W!XkqgW4Zq_4lz9ywY!F-Itu5A6%IdP=tDOs=)kwGnK{WYW9c+a#_?!!nm+gr zI9jW@abde~EP}b#B)BohoDCrFmCY|){`nGHraxZPX*KWJJ}O?jC&kRRhhrqc;2@HD z?h5(L_rH7=!L<`VJ>8!=E9khDG~6#k=fBmt%^gq7e0+dgX~;T?vO35p8#)YUvtVt9 zah;zS<`X=Kf- zcVB!EKJ1=$?at-YrZ@z_sjrOg*=<&6+5{(z8YS6Fm$*wk&!Y4CYc#fa`7iNFh0dvm zCR8~dsnURy$hZynfJj7+~F@G8O#u5 zzPb@FWnWP(H*;z;X$7JYH@re77hU7Lr+g0D8~b)LLtWx2J^kH67;*G5!x9bDoh&VE z)W}kntIszGQH`KkZ)^I|IV3(&h_)*h%JuCw_rCgFJqIk^IJDWrl|2Dx0MhJ;^M+&7 z56ajQJJ-wgzTT4Bnh>c|o`$X@7WruYG!|gnsO39ZzCZ58Kde~+rLcDj8wZx$TSg^K z63E@jW3SG{xde)Td!G_6rZtk@ZN?kv`6R4a4L50cfa{kXlhRMsK8viykQY%q!7r z1@!)?gZB8msZxVq@&RI5yX3651NwTp>C$!+t(2sMb7h*CAjfr3?vh;Fr$CaN+wcNq zkapUwm{SITX?F8?PV=OxZD~|Rhhnc8BgWu^vAhcKzvi^PFep~?3Ch&_I7xA&=y>XU%~ZrA>kGtnam$taQTId4_Uj}{)IMn zkqf0GZUHGod9oPk&Q;J5e|pQUCc0rTXZXrv>XgNS9EZs5CO}SBN4L}6(J)Qql`OoN zg3MTgQY77|oe!5@g#XL~qD=T@KeL{x2$*r##r;SdLp$Cg&#V{HI(&PU*f~-TnpRzu22Pa%VT^1R5)uX&&&)^@0m7Z%&DP5g>` z&luy*Xt_zDiUaW5Mmw$h8+D!*(yx()9ajtj6)F@)WE&{y@k9X+p{Mt3-&^=rOt$_+mu z1UeQUyt4q>7e6LX2`+H_+&KR(!&~pP3W}Sp83E_3$m5&wzz7zAa&M^^F~H>=0gQ8=;=x|B6u z%^tJb$L>BHdjojjGnIf?z>6Atv^PpkAr)GT8!kcfym$rI)NXM~MVZ@l>3dQo9|;@t zeLiEvCSsu7#k@$%iI#+J>Ez<6UiF#YAsx{yg?QPstaM42_};DHW5U^uscfbMxX{`s zhM46x52Aa^-H0Y+&IhBVT8uC2^s7#|?tY_$UlD53hEOjqr4%1Y4kyK7?!^w;mXEmI zPDCCx=->e&hTGqIXgOkIr8A}np^s^^d_`9}(`)d*Ll+wN*znxR-UQ$!Q;}h^4jE*3 zLF_U&=xO;C8e}CZiX<1p<->gmYzj%6_F5HXPFpP?A=1~Y%cJ^N7h%WtR;8d!|^AoVz`7+li5#lXbN5qa&)F@ z(|6^`IE&wh#9lcWdo&4%y!B!B#Zz4KCBv&m%u(#g?CE)d8Wu z^XYU+Ro}tZNv0Pb`wh`;5y8w45H_1xVbDtt%C9^fEl43nBAD%n)EfJ7nF%xQdpcpzb*q|G3k(iT`qg9>LhxhaQnK;0l2-V1VWOuOFFvxu71!{P#UDWwzd zffnQ1hHWex9f73dB%FiaJ?zVmMIKS>WMj32cLr6_f$VnK9SueCmnbUZ{$gj|2GI%XQaVDdAR}r)#qPn$HL4WP_MmMO{8?s7QPVa(FeF{ftn=V{RY`l+w zo7r`mcB%cP$8nGHT1_CIoE9t}Nm)aU2hBGM6ex_uVwEBn7NT*vBN^Y9hb&`3okDv% zLK5IEWq+3@Gl1!)4p75t^rc5C+n!@VoKs)}TeyosCRwq8B^}_#3=E@m9mmJp=g|P| zUsDUlEhqU-eF7dxk8ebU$QcQzQ@=HC7Uy$lwz=YpSCaMpD2w?t(@i09%83ayYh3c= zY6#L6UXhJP91}e7xC0@OU+1mDptnI311m=A*0DVTE6hC#@vJmr3XEtH!6cXFG6k+Z zdAdB{#QA(=0)3v*BcBCe)c7ms4AR20o>#~1E{uoDu!ip+ko>3g)>Hhb9#=+AWNBGS z1(blJ8%mW1nFa7YCBG)ie&^Q&#_YkJEWH#jg-(CK2sT`)w=-;s8rb0a=$H{>uDn=I z>uQA8UN}B;k;SFK)0dBB;7lo_pxE(ngdpTtfhk=GleSHm#3j^&OXWshrWqmv^(WKL z=Lu&V!FvmG(HSdIXi7N*vIrnlY;(4my>(zw9ILM5S?yVzzjdFfC;1d)JQ-bn{rLvh zwZJs(sxf(@e)-&Rx`Qr^OO$Vk2B5V*;KH;AVFdGY*x;3Z7RMdMaSOXC8qKh`Ip-qB z9kCFeutzI`9hRao1zN&}8vfxP*YSxP&YdEhOoD|2sfF*LE_1n%)aV`Ss5;7svo!q2 zlUhX$62{|myhoA*O}k^ZIqxl;uh3YiMb<2;n%JGru9S{puKDD7*`|6xDO@g@`Z8y7 zv`SA+A!a%1R!UFHi&9D(FLwd>_19w|F*>HDCFP385ycS3k%-%>3MOCBj<}?16UoVz zeIMwzE*d^DBv)NYM7)0wme!bX!||CrT7n5Bg7X03*PG5vUyqIxIRio{^97>eERREm zFLkVPi%G1#E)#GSYFL$Ygf0}ZMUcmHCYvoPN}YA`xyFWDMlA3>^$FGt&?j-q5+#bU znWpd&jUmMop%vBe(r+&MWsJ8Bmjui~1e{jzwGm4p*|)gM8;iNH=tm~<0^+|$9fc#2 z3%_LG>E(kzJqvLsRI3SJd1{mLMDevhg9z5_?UXYI15GGUN@oo zrhOoqc~+^V^?}fM4C&6s=QrSCz_xnix{Opnd3wi`*5;JQZ)h_Us1PPU&fhmvK(yMc zz<6E z788#Tdr*t8D>H$8Mn|7&(N^JIV%+;m3CyM_Zpdr}wreQ5u?nZ<|2-=!hWHEfTZ?-+ z@lW|smZElr6nT}o-{Q&G`cI5J5dndvP zoUXS8b|;n=(HUSR;iSwMf`C_U5%G%tclJQJQNuR(gYwHkW;z?$BT#PlY{)<4Qq z;)p{cZE_{q{{(Lgb+^o}-cTx;&&bY}?29yO?+Z2nDC4KWf+h4yrKA$!v6(?xr$moP zs0z@&FLslsgzI|RBtK>fU(rT3b1e5%@^FlPDW zBl!Y4Cfl_640p1o<@1<$N^8x#ZbC_ad()gg6B53@-6T<<#2p3nk(HkOQ+YW|D$b~e zjVhh3Z}?z%b1_}yFYnurtZ|lR9pNK)w<8@y89L+7??a1HPoi=*??VF>FS?mdxzbQw zx&=hvcW8AkbeH{yQNOKy*67Wf@NXjiIy!+_r5CWNj_}HRGbPlon7mv3rm80BL=S>c zW}wiz3>VP0amq{Zpvvc4Z>xidl6S``!HsL^qa%&B!X4bZ@`R+aw0thV7RRCH?eHgC zYQ9-3XRQw{)IO#>r>NX-Z6 zfmVL}(f%?J{bL9veR;1T7T7hzQKAI5+t{Izo+e`ERfB>9+3{DD2mhGwrp2`!tw4_; z*RE@Zhbkg-bUn}2yp;wFH<#a>#dZv+sSuVvAmpSIA2VYkRUu{O3U;f6e>&AOG*c@Q z29{IKPo3G5HgGq>Fyep^i@ae5{%)GV8ZQqbRZD?tfCJt1Vto#as?Z^gcMV$)pnDoA zc1^Cgr0H{;)V?n%fK{i0LaBcCjA{%Ja_{)5oAon3p-Ux0HK|Awc|%k&s7t-xg^I-u zrQH%VF#Q32GY?cPlpNqR1La{hrCh_hM7?k{uW!BEqc!~1-J99zr0fW9uOoB6sE*sD zqGAw6o?j(lu;~<#3yax~^}@^<4eta3a)Vp=$bbRLJ{6Ne z1v`%#5l0#6skq3Zwyg)_T&*h}!d6q|g0)H-FRVN@0qI-3KWlBGWbCZCz&-O%grW^0C7!> zU<7YiV!Hc!LaLtwx~`AFh6DfltXMUd)8U`Vcy3V$W3j(7t}|?e*TX44gG6fU>mM?@ zexQpNvmEb>gh{OF16N@_m5lD9Quhbe2#jb=VJUHp)}>8QpVs8!eaEZn2LD-KZ{&H(y8<5Vv9rXwn3JpF7ec!Wur`vpn5bBNDp^N-TY7=rP9>gv4?ulwi@q?`Zep(Hi`s$k0fC>5_ZMsZ`RqDE7jUy zZOs>(I9sea4qMcL8$3WNq5O!-@&MrutN!aZzYu-Ow2+g$gJtz4k=oY>rTyS9( z5G+RLG{SOQ!aE%(ENO2;Ko?xvyfpv5WfH038At&fr^?RqDf&W)*kp^J$|cRu_B3arIs`dR}F z)DM@Z%MmRsHO>2zTC2`ysYYiFgKU@XDtCqub@elm20=CaS%J2vOno>0QiL)D=C6FG zk)=?^RpbvbG=qcfSE~#uYLckZHxyfUq^H>A{horJ=5e679Z9UU| z8Q(3pRXd2%JP|p5qNdTOGGWemB4NH>G5gdAZ1bpQpGTR<=v>1n(?SewaT-oQk(}z& zTeo*SvOIOa5%PQxLdjeNOA5eLnvDk%*>kHC?D^GOTW)#q4w?E4K65ajf4-vsWHIn% z)0nuGGq_mLg=kn8QS{QyJm|=sosdfXTJ>K-!3lfON=x0yu!*xmb~m-R%KZLCwl+e9 z1|GEHQObRNTKELR;qLy9F0}1yVvWipZjA<2V#Z+!qs|2@h0x0qHPS&fnBDNte?W)n+%WExQUE&D)bG_ublXJ(Ph zs$9knPscXK8~I!Nrns$mDCf(V@zrl%`y@50ldVYBBV7@Xf+gq<_b z8d1}1h_4vp291I_IAa7kdMJU$ZJq$)%VTXmY?o#g1=)(_jh5_d%PhvP+ZR20E)*X2 zl%h`8=i#Yz5g)+^IIFxQa6vB4v`!MJ=rtkXe|9bm@E6D75T=pxs&8-fFb6x=ZINiM z(v_pKMkB;brL=ZshPem#?4vgcgCo;F+qI0%2lVUUfM970<8PP|U_BvdE-)k{Ai8pr z30tno64Di}(uCxw@#uh zP$Lb+vVVgORq!)g)J4Clv*U!mU3v-{>|?su^3qVeExhacD^!Eu-dpTzL1(I~Rhe#U z-%XsIL0)!Fe#RM1RLZ%SbtlXW3>BfG-|c+f>7p%;X}C6aE1ItWDirR(*^xt`gfa3$ z2AiDyi*5Q|L71=JczWy@nJ?_dpDEwNxYoQ+fs3fdY$H2Jt-yzJAq)>uoRmo%kvS!X zlr}O_i(MgBwc=L(J;B_PEIsyheyU-E3*0fYHXH?H&uz_lq*?2V7f@LH#=6CV;=Y~i ztZlV%S2I5*JkNHwj-x1_>WM@vE4v;Cq6CZ5CYR141RxIdMU@uSE9aZjS##zzPS{Im z6moP(Btb(V1uI+Y*^FBm=x(+KupsxKp2eL-rRLH+1c@kGw;bbAjm>M={gaisdrBu$ z`PBIptN_h^OMneK3V>u4^=0@QKolmo}hy8 zALtHQM3rf-w<`PXmT9~0H~Eny`8k=T;IEY|y@a+Eei4I!-wHd-u9*^hLi0ijK*=QIjM`3+IH45-LVA4o1*^CKwq6Z$lz%I{hyzQkV>!T0b0*5t;jcODX ziz+TIv@@&4HH%w)Q6Va^U7{y9nuY=_z!2c zY&i{Jo$PHa`=fG9xN0akj&yQ#rrF@cq4CS1E~9A0NRok!9-|ktl^o;fB5vgNtQ=M)9m33mu2zm=|4DccGKiocG-R_ zJQQV94X*O0y2~o0t9$b#lh~z12ookYPgqjXe4zjhsb)!W?_NVIEeweq^IMg3?aDa} zmFceT8|MmT+rB3FRP)!ptMXG;e-{E<6dCSDWRLv9j(l4TgaW@P+&s8uPemMB3v1)K ztAPdq9lFc#g=onSgTw6GrJ-hm`xN8RDD&b`WY;ob)Ggusd;zdv1Ba`9l|b$Qh&A}l zy?ImAxUaGk_Gj_Au}u%d6^KW%PVQ5v;IT{Zd-E7;JYQhAd%u66Jr_{oT&%yp_udWz zNCF7ozSEjPYC>m5^R%KUl_bo`i;@_7AWP0YUSi7hkXneXSW&nqFOCoS)JRE=M;#ia za1}IW|I{+$TJ-(Eo%xJbvPEU;)>LtK8>QIJ$b;7#u>|M&p0eMC5u(>Xv_YYNIOdp! zv=eE5;((w-jWgA1Vm+d_W5NPX>Z)lC$oS+icb9uhm7`{cDpZUOV9X2EDH5F*;{Fd? zea(avQQsLCypVe1s^OV+Fe+cu7NI?#8;1Pmwf~4t1uV~UOJYX!q{Q)KM0+Wz)9Lcf zRDpI)-+mZ-QUWM`gxc^611;;W03f18B8@<{cwp0X+P)MaREVaS^NDtYH{r0FoU55g z3s+|%dOSEX733L$bIT@i<+HI@0JFlPbMNt1cF^1`64wpovtma?Nhm)a7zWJ5dyM2X zZYrB$>o6!FO$P5a#mWWu+oG@&as^-wk>e};f3>&;^2^_1=BXiUt)!__7NNP!74()mT-xG9b2 zDD~7YZ(Y&JP+;ExF4of6A)zQ64;2rZ_3I`30kr@x8d`G#tIEX;MlqHXbho}SpEhO$ zH^o_!inY^T%D?@-b-yJK6L4~;G~^@XK$=*Cf@j=QeJU>wr>$DVvedn$`{MAJJhE2b zw?m~ljc;8%5rYf2`+Sc3q3rcEED4^(5Huv94ph@T{-~6jSyh8Q8(nw_ItIav(La=k ziJY7H2;?^9oRbBB!XIf^<~yO8BoLov^juLUQGq4h>4EE)mU69R5cXi$Q+Ds*3m zC{CqD&~zEtFZjD3y=?cwHFyqtNdhlE2Mo&ZFYv~6Vqj+G&$h+`p{f9fSE03R$rx1Q zk5grO`AnY+WN@*akq}*6^WTUFVhjl)A>b0@hxMRi>gqo}SSa-_VibGl?|Rzlm!Ak_ z6)vOw55nFt#up|C^Bvo^ZQHi(Ib-{bZQHhO+qVC6#q~di>2#%@ zuKGQh8JI>0v_u8=iR!}*E&8^GW_ha6qI-oCheX?}v7rfINH{VV$1*fpVDc?GF#F!ziBjuXy+9X|;^5+CWtMvHa z#rkimrjR~-SXiz>>#(we|3X8yY7uoqT&zs*yI_b*c5}LSQWT&Q-i6zjHy%X@2zSZ2 z4U0EvJYQ!ALM+r;!o&Ruj)|@D@@6uu0?}~is}q%gqtI!{YfVZ+*>%?Vm0^U1m79Ah zaEe>6Fj*smrX&)V;iO;jSF5?qfutc`}VbL_pqV3Mubb|sjl$VLbo(*-8q7pnT&%j_#*>!FQ#6PO3iSGsDp-WpzvAUy zK09)uB*3hF@YD4OU}bZBrn+s<-?8s0as$B8B`skej}&xI%jX~@B%Vy`Y)DgQTj7rc znY|C@p-{gMRq!9GU72+fhUElFbA~)q&;S z$m2yJErCC(geGLmd1vbWT{ief*qo!N8z*kys0G+W?G@DH#+ zU&yW27#8HFM@A?P+?Z5UjrEXgp&n7pK=esHNRv<`b$&OST-Q5nJSH*Z(%-zpUc+^@s2*F zMyOw@iwSLc=r2DulZ?lRbPh#z+OpTp$f~=KuhmJjKfPq6c?*3e>oJ&V8#vy@SL~y| zar?XAG8FeQTl=5rU08xQIY#HBR|6b4ZH*4EB*tR?_VFzj+;9P;2^GSSSH}Li!p>LM zx9Ly>#ny^mXX+RT|8BY}>Bxk%96Vc^)NljP4qnN|l`LjI9?&v-@lAT*@ZOa)b&Q`d zgUE`Ub}|I|@UcxjP@Wb)=yr||6;|!~GPGfbVSE}!56SC+t6J;nq?QCfCJ~@YYx#d? z*tnC4YXV1H`f5~4)L4g57_!YK32niF$APbwGWjzF-S(N$w5Y&Shr14z#1WZ7Krz*N z@9I4QV)Z$Kje&_b;R*)L}D8qm8odGTUc;0QaWnAXvd$a%(F2^t~tchHNUPd#!I>@Nyzv8DtZxSK@{ ztGO5!6O10}^cv0;hGaRkv3>Jne8C4CD$Gk3U?AN4W(=NjUJQ_DZUw$>(o%w(dXYF;4TdA9p0RF(fErG~7Y1F)JVhL3iJiAWM*|PjUVeXrIzI2(2(;2Ryjt%1a~r zbkn02=@pgZN3q-rG**uC^kY&z=$LyG3_)axL(%;W5U4K1Jj#dn%bO+Pj;BTD2g0ThL<+{KCxcsDo}usL9yjZm{?>O`&W2?SbjX*Wcjw zv3a#k^<&Q5s0I0%P|etk)ULnVv9TjybGJF{c?S}`xr7CS*BU0VZo|R=nLM*83=S{c z^jCT@32>spf_5-gZl_4uW=&Y11Hwz}0T4G+`$N-Cloh#i>w9K#-TRVg(m(#n$aTGh z71X8I6{QzDalquSZQ??8SZ}4`j2Qig(oUON=h&K-94@O$ zq5IiT4W7ahi|wJ4XOzgi(qEc;c*5yqEJeaov$DxSMCCj8h7)E8ju3hEVkNljMj&yZ19hB)VGp4Sdx1}6aQr8Qf z>TNHf(^PYZ5qw=%Loi}VtjN)CN2FRN(U`bHp7&7^QWTJojv`hJl)`VL-Vst(NC%# zHBUe4;uNyN)ux87Wqa~!7)Rx&w8IciH0+P)vPLY?D9g%2Y;cCbxwoc=oQq(VOremB z-hP-8v1?$l#!)QdGilMpDUtU)ogqK6&l)&_-T85OPNDN?9xOa3%6RrZ(`6oUA==6Zl;Q4odxtla=N}#WNLs4)IFN@PZoA(mixB<&a=XQ{Khl-V z9Zkos@3RIR(6ZfL;)jj%C=sy-x&ctNOaA|Tv;QdPYme7$~UkrEOHSt2i zek?wIU>;2->Sr~+9F@Aicls>@yXU?yNNL6PTX~{uS{V~JgNhQ3;)Wn-&>p3Uh}S#K z30BHxDU}gHIVSlPFp`rF6$LP|SIKj$PLYB`zpmVsQ2SQ6Hn#?ac!RV#=BxQqYchZe zo*yXqqnhx-8n}qzFV(UN>taji%6;*7GQMacxi2h#a&scfG5&>GC+aHYG9xp2L9; zVXzlWY0^fYvP@vRp}O}-B_2Z~iPR0BZ<-W=2uFlE_fJTDg1jE{#e_K`;l7zOYu9G| zJ@MsE0ypPML1)*af#3th!`}Nn69;gLregW9hkL#$oOKY&`%`7m2@y@IdHCJWXgp`y zO)_qi(Ty90f4%n+5|z*B+a=w5ssw*(ZR#f&-m)2){1nJfio_V z(f$bIgjZIVhZbqGB>=gtsu2Ht+3>{BGQ1vw2m0M&Sb$obCmUl!ViK7GQJv#t2R?_* zl4{CNk}5Gp<8;20d1ZiSN$s8fO%J)I&>(ZeBaRMv$xLH9aMqXUlN*u-&I1xDFTs4_ zSQ97K6;H`Hl_6Z*;#r4}xJXU*)!U6v#uXi(m`uUC==Axp=^){tJT+5_#psu56zOiDX_HWyF^5i>l!Z zU8-u%96}NXFe+`q?y)BbeSFu*DVCtJA}ma^LMl3uz4bwbgxUtKe9|Gtv6VfTWM#)Y zRi#7%Vf9SBMjQ$|Fw`%pZ(cR5wqW>9mirPi(&wY#~Pp8yG*9+v6_ zJz?|KPCNJ;3FU<+kvCH~J!VbrbCV$2WOh4OUfQ+vUx%yq-5m(B?R~H*v!C19sm>p_b*B!B zi=aTEEJFQ0g<#MkCEg#9Q@F9qRgAA>*j7r(cc%Z_4N+PQ&3lYfM&eja39hb)dA zghG7LwP-f5xKd98gyVc%%BQ0VXv@1YS+fyngDkMaj5mqQ}+CU!^G1X~9tN-B$0Ra%1|FKXfTr91Vf? zP$4~NIs@UlxA3s*6RC)Dmw3=;d<4ub9`(Y>`yHrbH#-&TxT|=eE zU(?6aL3BRVS|<{1hnoSu6|x|!?CCJ~gREE%b}od-D*I)&GZB_@z1RWX%9GgH@CNPg z9D?*xYt-S6ZKZ!rks{*NFE+NqJCTW2OW!d9>aA1QTBWmFrLwg{cOyfM}km7rg1V07X6b-gTb@VfyWX{aVgPWO-3Uv?`dfj zOl4Za22enz=GZkIOo*iuZJ}aKt9@^i@YK{Hjx|nteNzp?xq01KU&yyaSgG4ol~^{r z5(Ri)hap2aX>^9>v1@yiG*kCt14{Q!!Y>)~IA15MLY-Pj#n(pKXPS0TLeScQ+X@GF zZZ9f0J`uHJ>LC~2tzNEipCoZf;#*8Y;lCAmYpU(!D0DgImMhGLu2~~}g}G**_+4(! zA3jCsI=M;>g~gT)c|Hd#?fS{E-e&L&I_L%+!^-==qn>W{zCjQQ4ZU(z3>3(s>s{T+ z^&H<}41J$YJQI=m%4a*3t*5dWB|78{3$ok@xLX7&tm(WkA|Mw%F~mDeMz@~4KYX=g0};R^R_PV{PN(F-~1^#BlqGAOW_)Sbs3^8ZDy_^ zLvnIeoQXE}xB6&KJ!Q4(+NGB?8d0On%MF=o(LCliWa8Q=87)Jv(o<4*and*@c{QYDF*ve>60 z6EXF43z^lixQjU(Qns22N0<1n^?;kX#f36WcJ&UQec>>>kS3NjadG6Xv zo-rfmA$5oG0UT7dCF;!nlx$M%6oHWa{^(hX@rF1OBoCMn z<7>oL#ou_|M*$qWW^HX2Pb3kZ>A`3*P2&#w$o3V2N7FKCpB3J*4` z(cM@gW%@W^vt~ zbm>U83ZC>3fsSf>=0e+W;5h1{;Y;+0L>!rLhaX3l$&Jin0Xd4WXDIX z)6fZtbjSgm= zJsGTxwXxE{B1A@#5am>u)`*tnzc3+!!nbljAmT#I21p~IWdW0F)GqAKLDt*fvM`HhZP_}&EB}ACS`=62twjjbWtfC%^nUvag`9kw#-%5g)3_`KTbHBQ6P2-%D%}y;GK_$7iD8+?puLD` zMmi(pe%Bzgxl}Y1Jtep0{={T!XQ%#0XES@+oVc%ipy>a`Zac#$#+QY;8`5|<(I@-( zovmMl|5+nOR*&Q8yiBUXv>~rlUS20Px4oiQ2NQuM0t+MyogL0jnGG)d=Uze~V--Uc zd@f(#Tnmf5y(gO=c+5T0%s@K6bYMagrIS+(kO10k8B*P$D>G+2TsFGl5h&e1>2cuH zbX^Gl^EZ}0MTTKli*n*0vq!3k*ldt#((z{W%_U^*yz<(2+x3!CXQ+fdPHs1WX(|#r zJo^y`s@9he;3@^nnt*6kK?70{-H4_Rt9Z*-7Re;^B{8Ttg4wWN$ggE?n|EBRNwx+N zTYrIEIII503H4C=uMzPSM7x`_i8awCu4I1le=qrh3aZXhIa19{e8-tT!)6BEB=Qj# zSQ32A&H*j4djv1>v7gt(`7``bX1+`C^1bJjw)MlkSEO|0h1eaNNake0k1Tf9mc1e# z!m`d2i8n3w1)6vR>+?uBEJT{}6_*FN=jG|8aj{9nRny;nW!*X#RG+~eH-;VbQMSj9 z6McN+i@hv_T=Ix*0ri%syH!^QANq+ENvZZ?iq!@nq}a-u#a?-4%*@$4GGlTI>3*J; z(Cv-fs=xX83G;?+QnEz2AmdNaLwaCkyj&Hra$37Y3eIYWz@MX&yu*zN;v?0l0ZuCM zi4HZ3!Uzi3%q6evrgY?gVa)k~kQG&#sx!0Bd|Z3tt9gH0*^}taI8%D$HniEE%Q2h&7r8SckiietA5L^qP!o7M^$c7>CJD zDK0`S#O#;AzX!Qq#`BvW>L5us2HC)KMFt-m39e3ru~1(_ADTF#zmLj9qC5bUCMslx zt5nexSRc6!c^D3P3d+4MsHml|Wn1aVgjmaEF_E7$GuBF99@OeNf-mv`2^0Nn5APx;`UwR}sCTR=1*KN*os;-L5I68L4I156%#T=Yf1x z^fuv0XV5RwHg*Wg>Co%oF_W^3SMt&>z*m$40(DQlT;ik6YIMLi3skGF(`GaK-FlvJKz@w}K$S6B^uh+`p^rEp?QFgt@11_;n$r7Cf{D-!~t)Z2-@2h zhuWd>ILFV_Qo(8CS6{64uaU>fEf*++$8tz}5Z05Ylo&{4trz|hc7OjlPcEM~@IhVu z1TS@jW(3SB1776Hw3!G3yl#a`6g^ymrdJq!Am+fvVH~DsNwVud)@m&^N@VS%bZwP7RareJX+I8qFs); zvF}R+Z=#OT_qxHcRT#q$PYXz5Xh;sclt$HO>mm#@(_D@qx!Wcuz!s## zV8d!Ae5KY3q806E^ZKGCJTG`M z;F4EBH&jGI2vlH#5R^Vx3Pq6=BMp&EA_9#*PtcNZ0!gnn5>kRepU3T^!JhB!tdEO1 z%W3=C)^G2R_cbf>KjvnzN?wzFDSSEz#2~2vbRbnlvN4pfdkCw~LnkJ9x-IMgDWQQC z5D*P2KAiVGmCkVq&i93m)EKT80U z$Wb|#7~aC@AN>U>A=KzcGODi~gCHHC1@S)#U?g-QkU)ZV7e-DD6M`?0qQp%B4YUNe zl|-2ZQ4(M)ClPa}_qOAorKi2KOKV zhWZ!N^FxU{e8-jY-|t=;Q1IZAv$GO66wuH6S1>+QU2Y?m>%g9@Jk$-av@h@Fbb9UR z&-8zV$jZXN2dFSnL<=7n(0_XYSKtYw(}0J84E{3MK!|=>8k8zPYS)xH$g*{7rKY;}l7-jk&)#xchxx+1OXFE#Mv*bqK&b&AN=&ErV zh!|I-F2HGj2k37bX*AK|a%?YhLbZnb++!XBjZyoidJ_IV^4R;ReF7XQc0!BZtL#E)NbLLPYQ8C0zRWinQB&T7 zu|co}E>~JlEuu; zzqunLU&EY@U6^IJQBH`qwV17#L60Nr8eMk!={mX3`v>u;sXk#nmlPu_%(xn&zi@_R zmg*G)0JnujEfT^Ou5FX1d|ALrR)^VLq|RV)F3o7O;vhk4U;q#0hr?Hy`7RTrJ&_$q zO>uBd;*R$=#gmT%t3HvTDlcTU+P)P&ly10Nr?p#Q1`+|z!uzx<5R?k!0a6S&A>ly;G)pGA3{o_;dM40lxz=Vs3D_xU#`mn}NIHPZfd#+NX0(tuF? zWpg*Me7o2TMf49J=B-rR(n*#vTBuIioK}&;d=~7oX>7Aoa2$Y5J=zU}>r`VsKo7Yq zj~1=o*urMc4L$Z{?K~Ty_%>vIjzamxG{?t{X~Ght3o4%7uJ6o*q2LM^7}Z-ytVd>R zmDiU9%iHFFcX750byqdg?SWq&ouX=m)W&05Ym7s=s@6=b?V_sBo&I$z8zzQ=^v{=3 zb5dkHNfGH_d$?RNjqu_>{DT9gUQUG-^KA?<-rXrq`5_e^iYQaUztW}|1A zSw8np`46kn?CNQ)4`w>Y;jkT81dy+=k^f=tXFZB`fxV^gGJ zUQvrjJ%mSqO|GUj@0goG3Qm?bf0g^f0Jc~$=9n3!TD!0zOEpo;MII)}pt)k`Bf8Zj zptOg*iE53QG>>f!kjk5_`KO4ZNMPQLL;}@BzIfl~E%kkca)y4>y7ZoO*I8B+sVd0$ z!O~+z_PQIlg_~vPTUy*~IcWQ5sMB~ZJ@^YBeDlLb_xp@LcO|*?Rto?r|Bq}srEdh; zdG%k#vHo@rouzbgu&w$`$pzgAC;l9`MjVHBr<8?G7<)ryndQ)5@8NxpzDCOY$b)d zw-LPHZj~_5Et3A{72m#SLk#NK%yU*XDzP$nC#X+u`DA+7TJ5{7lR)}Gy#w$mb$XgN z#FyZ|vYVupid|PD8Tw&7{+!75(o>G*MV;j@ja$`&Y8hvl5z$i*qxd~nX}-S?NA@}- zj9J_GK3@@N#;;^wa=e?MOFBAf8#wdI!;QwZ&6}8wyYM|_`;E{y$A+z!%D!9Ec#)k^stB=Y%dET?>szZ>;3MlJJDunvsyFU&)j7|e_S5L0)I=RX z28x?&z!Kvgb|FTet+c;VkZ$PooUWX*;Ue{@p|B@BDrb2HbVF)#m1P-Jvcw3mnzhu1 z`(bzf{CW*OoRU*BwH{vED%bg9LoeMAJ?@ao3yof!EX(O>EG0cYIKh*fY;Ypb&9FUz3Sg?lR=xsx}IN@~yV zdzYMSdB)ru@)`-{o(W<*nad5`I}&DCoK{LT_xlGw7Nl-#J7WH0;1m~<%j`{(r@Oat z97LdV;>9bB*S1?(zOKf5DD`8PMR}`D035|8a!EGY{smec>5Lu>J$g5W?4pizc>(2= z;(4%i)9t!|QXE~wJ*zY(++{`H&7S&xm0z{50A}eg*pmihg(n5Clj>Q*sUVnTG<$^P zh8x)0>(49E!dP~!ktZRYHpkJCHjdu^ZkdN97ux_C;jg{r^Co&+Vuo}(Om$@H3NpS; znK*Pc*Qs6?B6D^8e)H#d!>UCVA{^n%7R_F?fXf~`>3A(V+gP`dXr0~z#tQ;t?k9!& z(m6rsvU2i^RtTVakfKnYvpb1e{NC=}sWOoRXPi{dJ$Ao0bbxFU_G`C?v|u}%=fC;< z_zniLvjm1XaWWN}q)x(kTUlKL*x3+9IT_2pi?mRAgz?jNwQz#g53G6#CovihvgHos z$^E@Uu>~Bic0WG;iLPIT(tcFpHDHJ3p5U+6Zw!VG2sA!kzx<-t;j0!0(W`ndGR|99 zCwmbvpckw!{k>yG%&hWjyB&W_%oAmhXLP$n%*)!uyPx2o%=Rwl+nB>n}x%D|*O%sC@{n zUehA0sirP^r?*a*yFtFvyYZsOhKQF?agfuP9A4+Vv!OE}X|`Y|dNV+?lgS2V7?dQQ z6C$ZnwQW3ElkE~IM>TvikL%dCbLW`EKfnk-!fPd(-oeeBh6s|#Ci=}BD^~g})y@aG zzcim9Hf004i*D#}cx;*37wI#Uu4h2&dmEEg-EB{rbu(56N*AQr*}OOn4K8eK@MqbU z<7JPs_;poq!iu7OvT>vCf|{xmau(m209v=Q&BQT2A3cnXtXVUP4WJC+Fm~_Fo%$8B zPpLD<$FZ!!^SWKJsobTXq;vb7{5(enP?kcYL2Gq(RB;Wqe3y(?KYSSO-B%1e-E?DL zHP=O9gjMS$r*xqy{UB=0&wPi;tEAB69A7gqb6+!T7yRKFYi$b8$BhBM$C} zyS9db*|%;RVVdf*>Q@3fiZIZKBpB?i*vZklwPmz;~y0u-LHL= z{zgSY=VUMFKbc}`FvfXS)_z>8r|G@jEKi3)VHl@BI^laCeErsG$7LpVDNX^(ypm14 zjdQ~vtpV+?Vghkw{M?5&W8^fe*MJ+Ll|=%+C@ZWZCKumzd_PAejfnuk-c;yosP~T$ z4(qCWw_4KJGv{YLw9e8R$JX-N3RS|_T?kAufmdk4WKKI%?=1Coa1INPZ3CUnREWig zB*}|AU`S|uP;wfW7bu-@vLC;G9~3M5L)t;y#rbUS{{B>~Sc=4{2)T1M`NV?=Uv;t2 z=Cr}*Bt~^BlVu)79-L+F-Caec>C78rTIyO-rrou2ReX;snechzYG<9II={q3+S@r1 zH7$h+02OqKSSP!>#zw;@Dkl|`y~-k!-zQ8NG@?eJz}iKvgtdv;@zulgNeH_&#~gU% z_;O>e{fM@tPuhtv|9KX~pK%94dYj*v>a^rOPjj3PmJR0nVZ_Nc8GqaxMM=!azP=p| zjDt1{c*k+m=MU!E^=r(fCA3v+WNb^7jgGvzc3N7o;Z!nA1XXRg5pI)2Mv@07hsayX z`Y`{FL%$SGQJ&(X`d{awe6nm-a3Xuu>9`c9=+5TQE!{ot12j)NBM;^Fn>Ned-xh5E ziftJrgooRVT4dKXh5}SrY7;%pMpxw(Cxk=7ZG-&zb$iO}+82>G7!;)}B`_@E>?BuA zkb-^wk{d=!C>9sCU1~0w6qxI!K%$3E+J82KiYxc@?aCy~mVb!Cr85fe3G%Qh3zX}% zzU9zZWdtZ^PTr!**c}~8Ww{5XYZo0!gEw|X6PaUy5AE&S$Bw+rnkqWm`-e(eGsycibx%`J=*yefa_@x(rR> zU-=A(NI!600?s_1n`u1=TQd8YMN`081bNZ{>(zYpg>$)KO9As%Pl`s`Qlr-qIcc5}B(U{P@q=R2PGBcL4qfKy8gPbv85vJsC7uTJfQP34l465ZN z=0yZ?cK(!^+&*|GkrEms&tX83Jj2Acc_=@cop`2@OG5bmGi3thu8rOa2w#_Z#^Ahf zbuF@oeR*1y!f4Y1KjZTGGh5VZP{q^Dn^Lt2UGI9VQGw{>mR8-KvE2x1hkz_j4cCGAGEvN%Yq<}n;td!#ggUE(n-g|8?@a8+&JCkIk$ePmpa`S z)iAZSCGH-o?x>9OuXt2wu0#*z??5qaz9uD!eW(3QQhS#v!dOL-jFGwt+?`UPO*def$sD?30*;{H~f>-(-w*Chcdo0fn-3H6Dcmn0@)GGDWuxK-B9bNHDh8kxa z#;o!FV^wWW?;EUw4*haj9wMgHjtVCk{2k9T2-3U{j*IPascehD9GaQ&c!b7DNT>|z zX6jBeI2Tz@sSw8EYWzWEq?$Ae#AbF?5E&SaH{vPQ2IhrVR?;lJo;>t2&v7uTBUKiOR2o-!+q~gtU5<;8so5@f_t*|6L;{e(4(o!Cz@a@vD-Z}%8NI) zTJ%BlPX~$K35H3oSpxN>ZhE9xJ}-JzhP^N4`5NnjNB)OUqGOB042*}{*L4liPlvNT zQ=`S*83VW4RN1bgL$juZ9+=%ns_nm^Kr0#HN86^gMJEX>hNan=BolY{K#DfE{L~n@ zHNY3(#%A2^*&@AX8tptQ2FP%EDmpmHxaMOrw%<YGYuwIMq5yWOAG)=s~OTw4=d1 z6(&R95U9Bg1pHGdKmo~w!wZW7Zs=zjfUcQ|!_o`%Bsu~%L2{)$07jE~F`l}50y<6u ziMc1^LuIbkKOcybB~7@B`&|UXi&O$v@KCB0xM0e_Wj$_74pP}EAmc-)7`|Fe2oVhg z06b7qhhhUjxj@ojIu3TB$?P}cQs{Rf5s85C0N0cp!%CB2IRBu+hxQ#ylPMD^k%4?p zKjC?qYY&A`5`XqoTuz{Xu(k$f7N(?bUeUk8OY0Gr0*JuD@Sq{lQc_odN?1?^b83=c zkRCMUW#RVsz6XDG<0v`<2N(&t1oI6IzWi^HxEh;9vH#V}oDii3jDk}BoZ`$4rS5B; z1ilGQzv*vG#7lh{e~!c%8jUmEE#K{BU^##!{@}j4FA3fY5{3#ud40^-6ib zgmUlZdwHPTv=hKdYavMzb+Wpbff!Ag!sX)5`S3}ZnGrz&BW0+t0sWU|5rUJ1Xqv(9 z?u)~KW;iE%n`zY5fy~=bKLU}U%zz}`QPqHrRs@A9Hg6%nWD&IlSu)?#{A=$c?6n2i;Ekn6u@430~9fZv@5EZ&EmDIqM>WwPVR!}Y3< zJ3dp|y@#wD5tN6|3ms6fLlQ!Td!9K$M+gf*gSsfRg~yP9N`OY&zU5q6gjhz$nlwdX zo*ZifD7ZOp(s-W z43+A(M%s;wY2nGg7v1xM>(f_k1FXn1y&k>It#n?W&bz9AweHQX&OqJor(zzPe&`?E`Kyc~pf&Pkq3gl;W7_)#H&GBo!kxSAAk{exd1cRK%Ny>` z8%h%79%mcP!=~6(_Cd^U@v-9W;E&SIf+aQV0s%T9l;5-12Z6X+_1Ixt5Kq0T?78Wa zI_b`4ofBPlG%QtDWQgs_6YEl)U@c~w2Mr4^|BBNgJ|kIk6stM3z=|-y%R_gFS?7vT zxi$FoIP!1KLvUgkiO}9y96Ah@xSG;)GCBS#G~}WHQlzxJMt`;F7jCrQ4}~CCp~rB+ zZZ*YM)@>gP{4%9fGhTw~`g$T)%(kgidBvXn#7SYcy|fKqa$(udYS46_KZr+rEOK9A zgxG0*a}>fa%qJQlA_q>M&c+0^Nv$HjcVQIf2B?d%l@y?MZ5QC-j{-(VOQ2)(euZj+`MIEdveDpXdN*S)OiC@bZqce=xm0ZNZXD1*n zBAq|0%)YukqbXXM&hgjv*q^A=-Jd03j|q!O?rZLA^gG9&Y(r%o2IlGNFm1QEV zoF0)=rkXND->B981D;b^geBa)1{J6q_-}@O2(VQWVkOpt2T;(XPc+={7c-a7O+XH0 zbCCC)WBVSz8HyaEOhEC_=hM!P)1ilxyv|3X>`iz~?L%bmJN*xd{EBSGDp3M4&jq?EV+*q*YZJOlvkjfVk zz0{HJCW1WY9F5^~@aqGDLj;DHk5!CS{YDP-JXkUVZmpCHK9>Vf zk~%K~z+ARgjN{J*b<)GWSS8(NfOo^E!0UNiP)beP06p{uFMXzB7>UW*q*vb24NAs5s16P^V zTNN2z9B$MtE}pho%o9#3moV#)rGonM8v9mF?xU&pPp?=%Ygm3iR$Dgy&6nh>RP>qA z{kIDGd12reBW6x0ZNFf|7K|59u-3|;nD4j6D36?`N#R=q(#XESrI(E-p4AdkByI+c zx5ahYEt4g>Va_*9ARGF~%t;uner8byrQj_$5&_SWlt4L))oT&@ZY4`et4~Qsq43rH zY%kr!>#ahbN~L%H?dp3)hQBi#2M|it`&Y}+mXk$ARq9aGN$pVPJg4Xvdc3qbY&JoQ zuo)+cfpy5=%V!EMbv&l~P$xp=p6>Va>SHJ-*5+$epDqk1>v>{sBphC(Mr2sN$^uf^ z?Ba_i-2LD(H*clIhIc(xTj#{9YBzP?|566dWm#=|Vlgxh`!~{HY{raAF3Ggc$U#@w zHOrOOoVA8?lpX=0%nrWBX`-*Uy4Qdwa#iH>`6MBR_hNjpz$a<)>wioPjz{BfXJV+x z@ei5ek?FeA+Ol1zW{tref?maHGy=h9O!wwUyH?=Q^6jmPp>_xh-xnx{ra>2+cG{-l zj@?BsVG4?Ee>Ti3mTT9pw~I>Q-#Y*z*nLm-zofW6sp+E}{*rF@gPEfed1tSzdFJ$s z%0(~*Pv)0G9WoQvmX`Bw(5=l3h7aGu!zyQ45s*?&Ftaru?*l76qdnG_Bpbi#J}bR) zg9Ju1xxD%BvAMM4^WLxbVt2Ix-B;6Sp}eG*;T2p(q8OT5=y{RhVnRp|L#QEFO_~3} z*fm560xZe4ZQHhO+qP}@v~63{wr$(CZF^_;y?^_M%~RF2sty@Z84)KjyxP^46Nr$m z2CoF_Sl2@H)305Zz@rYZ9JkcUFNhk)6kN(HQUgndcJb)c7Lfel|n;sMGs|R=4v@iAzRc}1PY}^T#;b5(31}!Vt z-;sxBK2>STKSc-5Lx$hQ(b}^TN&hgVn1EWIaP)M{1UepX{yZoxKZONG;(cxH-r5B} z^G42YEqF0ruM3aqy<7g!K%d29!lN8%0ncjeWB6lcTS&gR);>3tK`yrFRH zsp}>c0%s}oLPMkT;_?QS!gOuHxUL8u zde`u2fm%MU%THTda(B^3Kt`?Ug^FJ6rgiiy#TRhTT6{nq;UMUGQQjf6OMx{gd@yr# zX=khhG;_@*ba#(8YvCcn===oAhl+>t!v_0rwF}=5vu``46Aa@oX?@G-_EoM$y@uMR zqTgw{7itWYG1ltyZ6c-o#s}Y1OlDthibdJ$eVFwy{@L$X`-`PoqO1{X<#P$sk#Kma zqqLo~D+pu7jzVxZ-$Tn}H99;g+v=6`nONx*k*8p*gOp2@yr98yrwkmekm%O^z zOOCNwaLwv?UEz9{-;#FEHgSyJ=2jV!|2mFbS_yWEFV{o+CVO6s$?dt;wpNlRLI#2F zIW7M+YrR$n{%UURsuRtQNVl(~C2wqVcNJ~~>CELJ`9 z$Ln}XbjqleyxKss3FK^#aGz4MHkss zF>o^fXZJ5|kA;KjzXDoDR!(+Sg8!}m2cY!;RlvDeAVtdt==XJiyg}O9&Oxwoa1ZSK zzv2d2-MhJ=dDXo6{n_3qw2EBx`gl_3RTY+^D3utrHz)>2sc&d1HOwzQ1sT%}k%5u1mi6@&&M?=!3VQat37kJy`c;X+J3R^x%qp-nF)@Y%fzZ^@{6q1G4z8u4 z^oL>&=`Db-;aLa3f11+B)EeXq9MVe+j13^9zPp>195-h_Z1RpCy*h`mnshEV_93XK zt~ek+B?NSCVFxZX0MEdn&>nsc7%M|#;GY;vYJ3sY>GDhvdG%hOZRQ!}Ra*BbH{5*eY+39M~_fq|g` z$U;LygD=?B^gBK^19k6*KWgGU^DBzrJI}>YT9~@87o5?d?&p;u_E(4K&p|Dz63~x1 zDJHcyvM<4(gWq;=S}!!O?>P9--&wYw&bsiR_ILi1k~eFEK7C~1AJ+R{sO4q`M)&vH zyWp|5c$az11y|yxj--&|jvEwTN^MVAvy>tLGTN(2f(gKL3pr zTG&4=CuFGtoYdS&y(0iVFk=t;e5A(6zRc43Ion}v(Z6?qw6H%>0d>VaDFG41h<_47 zQ1h*=D=&ehfA2jo_*2g&02tmWj$r_}{8>tinwuJaB>*+J*E)guc1W)Dvy4mu=k9+^ zKgf+h=ODiXZ2-9X`NMGq$e+L+0nUPdgVxRfU5NYmI{@ zoYj2b58Vg2{N}^cdHK(QqksPrq{8R*q`Z&v{n{Fu{Y)Ae-jnAA`~=nY@9u@^_3@+f zd3-$%e?z`ujNi%kjY@=)#rjXaP1+tCe6|M1Ar$dsnOs!)9~+n z`oqZJSJUFsJpFXwi}@D~-4!G+<1gT|R{x|vJN5v$toU!?^H#aq2Be+{4DuT>F@H^3 zee$!IedHU!HZA|Qjr$8}{u(mZe>75`z#CRiU3Fz zK3GnQ-i;W&=Z5hxw!{mul|}dvr#Dvk<*Gc|bq?jTi`QqLk!rUY-{J^a%SN?p+#M-M z@#M-ZN}UnAiss;f-2o{eNfITnmR@kTuZV#U<*~%+%gD{ zcL((n&@6un@QTXvK5A(_=hW)a^k%jnV`wW9oF7cx51n8auuCt%Y9l zS`=7|{(6r=oYE*n^era~Bq3Qn;w0xd4p1jSq36MVCcbl*}sB7D9f z`bnZ_oQ-K}HPga}OBL5CrnQknG5tXYBUpqQ&Rfl`XWA>C!z{j2>YRr6vO1Z>Vmm2o zXLPkyhgpO(;~f5GFLb63{I24vmX{}A_5ndqg%w~S8HXlX=b{{$fi=!(~^`89P$-iIZ!|&Lp6pv|^hRe9zI~%kAd=@M@lHat7xHdF0X{ zmSpIvM-?BNWpf`=gwXPP^<6=Q6}$GbkE1zFhT>GsVfLl6xC;zT?Q=F%C(`Z_)`rem zIF)}f7s7H8ws$^`FV-1HOQ8MXJD7kS_v*+$JqlHs0zI`3A~^?doOM$QZwixw_dG-4 zBGf?4)MlaAHLNdp z^|KVa{nKYs$arM~tN&yYL3L+jcw@VCpw#){N0Tmzc>L@^h*(c@23i;6*$07YEO&7x zC?7|GQ;A~QMI|&ws)C9hyS;(qT8zJ+Wj9vfnnb~ERQ^oAT&8izbyKG4GTGL~Loli+ z!2{>eDQkZ`!%>#%qO$}D6T%eVmS~#&W{AtY1*{@v%W8tSUuvEnzq(3->yr9vc4pZy z_(_P6CCQPXxDxJ|(#g8uaO#Cxd`L<>^UQ(;Nt?+C7OZA%CCG>LHpI~D45PDLn%>c0 zNv%N$dD8Vi1l27dtPI}F=nwbEFfgg*Mb}UJ@rOsI%Z_wtT-k?ySjn@Y=a-z+%OaWV zw@CNx6kR)7fKN13M0z469MGZPa4XLX4tz^h+}FQsX==ENx18mZT>Ip7AOFO~-q_%q zf%~tl@RRV{l)7N-gQ!Av|1xv2lT6p69}-T-0&2b1s8C=Ihc12dVxrkEs3awC%n{UK zqL4@m@F<8+8Vpbzn$?W$Fq>@An|(g%Aq0eatQ{Z-d9~> z#LgMc$MoS4Iv zv^F6ZXg_kLvOidWMDIGDJaD9P&ekS+zM#~a5-b3E>L5v~*G#L9A9D7|?wBJ**B3_h zy!uM?MfBebd8DY4z!*D#fTOKfWE?f=d5?xWn&ihPU7SyxtSZ!Ld4C8QKZC5>{cqG_ zEXlh0LlI1=UT_oHI+j|si8VWsiJfl>77vqxrnV`ie3gSMTQClmY5zTq9;>o!NwQ1c z%sayXZ9GS8DTUvZ;Pf1HrK(7M>eW=MxfhKFXn-3H{&obp%g)Z32|UXZBZmnNBe-IZ z-&B{akJpXdf{I5_(9+3au=v__)wKkKVONhzGfr5InG=fsDUJ~<=OwRWqQJ^`(?vgu zg5Li1yco11OP~zcntI-q=gmJ55PDI#AjX!94-PKr!>yh#^-;U8K2;OZ19txi)?#NtuMgy)OrXl6+Q8K* zdBc`u-;#5&XmexbJ9(H6ez1uO6#Pu$oA)3F#{n0r8EF4V3+H9lE`u^TDiFBs zx0d-naF?esF8fT};IDolZU8d}xX{lznb@}|jzSW3P#$V%abkjIOKGW=8~}ZSikiu| zdpD4V`b_4OFt5JElf^#%5jTAHlv@G=CU#H^iI%$~wvGA@LvBKtaoNvhZg*if=fijJ{=!QcRA1U=1wbzK(YWGHuqi|Vs%3ayv9Ws=3IbCj}8_UT2 zZ@HnTT$$!QmCqpP?neS3cpL=E$wiV@-V?~a@Q06m4ilg2 z!lo!Mh;re(^byo)@NwO&l!bF1mP*c-PWNWBAXBG9n1B>`*(qb!9cLpbM_s^L*6F0f zPs_zFIxpIBSk{L`PmK+XZzvw|4&|y^w$+8R+DTK!Gx-L zG~g?&WT!+_wCX*#pmLCDWUs7H*_YW9p**F}z(-^L{~!|2VzeTN$iv|uF&$1=j#19iIF(R05jN%>lI(>oNPoAX^T0m#yc4%gvV$b`6&1w? zpadND9i6J^Z+5O={23apTM7{M+M4q1c^Hm1SEiB)%y`T(nBzC;iYTu0@)1kOtc=;h zaSyZ{+&`w(Q3@DVx}f8Pv^Oy5OpBN9p4AdgnAgzp-HVf z9mVx65^y=}%pFd%e4uC4Ye9a9G;JS}w~3#!Gc%k}C$asyUdM=+6DkL7zHo^mSitM6 zMl+Sha`3rB_VSfKeha*1be-)bBOC_azJG`6P13_4!EG#Z4Prd)josM`nU%j@&%(5p zTI~Pku5)^>4$`6NA;Mvbg~`pI5?3SZxJ`nkJ;dQ?S@a_GisUyfSu}Oo>5_Tj#TW&S zK6bpOmb!{J;}w(n*HCjYte0dXvNF;*q)M6b$j+>apM_c0qvNn(a%q#{E-%{y1rg2x z`*ZbjO0v;_D{fz~R_6~-D?jBv&1vNuHC9WjO%bf+Ecx6hPszr`c;(d+ZpCNhACRRU z>0Fg9fZh;3K^Vttek`7hWs)|#P$*s(e+&GLvnZimLmoW=Ci<`lQI>$kTyR`3w&T

+w7aTML32lR~I_c9!8*06jUoaP0w=*pZ*sm3c3NS}T_~o8?n_U9FvcnRqEa*Cf3= znhad;;COv%A3-^JMHATOcd3Yuvc&6BBQaE_b*KjBPA}n{YM(p6zEJsr>wBhc%T`f| zEeK}y=rI%uhHpzK%P6Mx8$Clh)pMlTsCDq$V9?ET%Fu^T{v-m&;>^!f}a2 zH`YfkVR!*sV$eSFL$*Z-)et9YjA<|T=^>ZyF$&Qy%8#P-*yH{tONQmo0GX{Kx0TxT z7hQ+bE~N@#Y3qrZEIjl7=TCYQ&9v zqdkyzB?JFv6AN8UgOg2>;JDr`Q}C%Q$Q;wbEPetdwdr&j?p!6MH#F6?<%AioEpx-; zD+UbW1F>#l9{nFNfsOlyeasS{$9j+6|6n4(Zhg>xIi(^qd9d+>!OE8jVkAPG7ZYKU(5OQg<0GE z0Wic#tnw6+5_dh>-t39hsAH<Q(I9E1t|U65>(FmUaCKgWrrMHNr5MCtG!(3Jn&$LX&69iXV>Xuk zo-~UOQ3zFQ<^sB(ffNo7%EL{Gti)zBiBm8CT&5!)fo6x5f>B#U?HKw1ftX3pC#>QW z+OFahcJt2#Z%D1eOz2Y`pPSE;>YL8~$fQ1VVA1xuo_7V;iS}i>8;0oLZT3#6s7{(y z@&q;NXg>P3=g*Yohe{p-DTUEw(`}GRJ~Zypcf9WO=92W)Ntw~E&rC0T8%XeOpC{@# zfGu1qs7aViE2AP-?n5UyNeC5XpoBwn>uIk?yz-O< z5{{({5~V5m|inX>L`z-bmMclUqYTiADD! zi%fj_sU71zmkro>IcC^CH`yD;uKbQU{2cDB-KJEI%^{ozrCtiU2a(Szrq0v9db!7l z6{Jvdq12>Fnl}d!SKdW|`i?*83Uz}K(8HKXjNMzehRwElM872%kw4m^Z$~7aSuP99 zLU)O>c0C4djUb-2Jwp-oJrGv7*fIxQ!b$4FlY9NaYx!7#W~yP`JkJLB%ExnADcI(N zVmp)(B`;yo*_$RJ$+E6Ls}c{1Y|H;z@PwkJoQSJ}-m@M!Sa&(iZ;wbd(NMLkBT+k;Yw$DqJ9H33BBE3Ob6G;%ga^~R8 zAmo}6|0b>g?InB9k$HZwc#eS+oGBsn z5@+%b({m0b!kQrKot=`+ghk5k_k$(d`4(Z>@ed< ziBa`(Ksc(u2uB?XXxIW!pHvxNFTYj=4CrL$+-bFMfz#|}h`Dk3gEvty5+PyI2DsTE zpQXoaikazmHzZV}!Iu^1UF8V^9C=~|zw{1*uJ0(;&_db+yI-b^fO|qU4+1X$lSp(s z*drxAHT|c>Q32KQCPcVbX*$Cr;e3t$E4ph&bgzIr{i=)~7CLq1s#2|URKb8@7+{`9 zOoa|?$E~1934KWFWwq_xb=dtkyMTHN>)t3XoWdYMr))ZQ%q-37U&PG@yxjDao;gsK z`6RFOGQ+Ui$9MW0)RCw_;rm4_t;$lu*58FyS|u`7y+2^2X-Z9hEs|ivfU#TRqE%Au zy)O@4!&k0kpC7jGBX_QkP4-&@T4ZoRxSvt4$KAo+X&3ZO5)oF$&y^y|v#5O4RyJpe z^(`BSf!^(H7S;@HWpzl5yFj~FRa?IL%E%J#?-|D3CeZ4XcOjdz*@`3Bjf~?G1Xz== za&mba+jn*hm%DSR&swZ+&j50?3Uu1vszHW3SePwiYZCfTZJ|~AtZRtGU#>735!KoC z!eQ`a4{jwJ+RMUTvGFAAGoHrzFvh0JQhtySb=BD@+^o*FEL7O?@IciZttlcD<1wjJ z5YvjgOf8J_qA*tANe9{{?6CYUbwE6R`3`B8G)qYdZ~l&4?1!8#FvUw72Xv))}IC_E@j_pF3m z-?NV$I4jE}qupK+Y}ir4+3=<~bxE$EchLQ1tby^oxJ8nmQg+CFiE_k}F2@#8cPKE@ z#3cD>7uUCXL40x#OhgFK- z?22Er^T*1%ECdX3DWa8qNd6JhM-jILoBqO4fMetmg@H>>81X2Ty6=U)WK1E0{mXrV z!iq6PmA_C_c;tDPX(#r5t_EaI$EM6RI!+$;dp1^hG4j(wAWdrK#?k$a*E&Sei zF_*%4(PY%WUuI<}cOOa73gwEAuG==XFrOdSjzZ|3;nTYZT5ON+EcXBdGONPis**U# z*;7@&U>t7N00TJT+#4wJ3syi8VbbBh;royIxIl3vBl-VQ!{otZQA zu0UTHY*8nJBc&!PAAdTILl)9k+*-%^sag|DQcJ&JfRX~jg)6u4`N&Qt_X^oHjYswI zW2a8y#+=lmiRVRNcns=%SF2Ae!)0?ih%_vn@+MpN;udL!X(wTcV^})BttSD+eR9$R zr>86{UC8aF8<^pYr@u`@U)tn^n1Ab_;O*RG&hlRl6RE|Y>fIRP_*o!fplZs~C$Fg# z=y=Gqr$Uz&>PcO_7`P`c)KJ1Al6OVM+XyF4yUdfxh}^Y^E6o%BOf3H)g12r*ATS+G zRDfMlqe11^DNi^k&a7__p7bJN%UZ2ujF3bo5wM(XU02(iZv%vS<9;;n)`Vv1_jM~Y zE`-|4ElAf4B3}U0sI-XRGZEt#7OD!th?ru#sxA4@xzNGHDKjqsLQMUb-<^%#F+rNL%X@=V5m-)$prrwLCy?4WQ-frq;gds^{%%+PVJ#Y!h@|w z*$;-+=z7N2x(02y$e5LzD80lI7c*vf76@oyH04JDAE!hyD=T?U%|qpT6Cx0jCdjNP zfo@_HGrGwsO(X%titk!-Uoj*Y?Qw1;Wqz6yY7L=6KI{PzC zOV|2j+0zgUEJu$XBpncbYgDrI5>_E0TSk{4Lq6Ww*%-9Ca0^}rK_1wChguZWfF^#| zu)F7MmCMyRFRjF_XQ_wu z0hXp)e8D5TG}7GG{yUZTBF$cmi(**fE8=?G`Jjt|Dp53TzIzvz*k_|PIeoO&ciA80L#eH;l$9WfL&m!}o!g0Zh}zkLY+cAN z*tEG3yU2gk#Yzzv=ic90q(r=nXMb;V7E8#V;#GkxSOMCT82!U;4v4qG8nJajEa8v^ zqdKe71n)XiPK&c6($R@y&U77W2{|9aDEa8X!I7G zvM_)rh>h>Lf2J5?$|Ta8abHAlnU~a}CgFE-$U5*(v!HWmjDQIm> zau{BE{!Xqhn-71FH~u&w3@=HhrfjWHnAfj{cs3{FEyx8fm8~jlI>P#Sev6(zFSToo zxp*c6lmo#R`6hPXoPc6=DX-RpbxTnmrAkmr!m&M)aVbLG8xh(PG;sNE9|~?gQZL$; zF8#{x>bHC@+XTUS6aCT9qBi_qVylD&IyR7d#PZ+?iPt7bzk&3czh(nYPIT+*N@GGI z%ntgQ(lgsk=Yp&#erY$psi!czhz>SY89#VZe{f97)h1t`0)zQrCnJq9{vt0_DA%xaT8=rC+J5C1V1FMd2w=Jyxs~ zH;j!AhZHE62pUiiy_X~!^8F)a~188W2ZP?vhmB)Qg^UR-{V{c1zV_!C*{g ziL$;_$G_Yrl{_{nh;>`i7NVaLgoJN6lOj|<_02-;p?=jPE}%xbOdMU!6rv4040c7K ztY6zZ>4(>y`vkGV1vNr5w&pB%LsLd3;QG8|Dq76m!U;3$8@1{BXf4&#m0qjj2RKe% z6*rhrmp9RO+X=LX){7Q2#Qeao;}gc?zMU?vBSmlkfIm}M32J}0aH2|!__oh`QM;KI z0)MU-b08bDrT12DYk286J#B|u9a{F(>TO?iY>6k}Eu9zE$(_$a?G(vI#uqok;*yCV zCv=ra%BdiC#h&vp?r6#&{O*KjVUm@nm%z*kq&xaDi{nKGa{FjExa<714!ZvUyDIoE z>v!k?_d$9`lW;wimtkkUg06RHX#tU z$oAajmG4o7fcO zs7$(l~Z`N$X&)V#Q#E1k8goNB=U?^8*#lkb)0(%M=W-8 z!n-MrI0z~0P6Ip*54iyxkotD4hkpPI`?(~t(s~W)x3GaJOF~g9tOQ1|l3`U7@Mb{j z!^jn(ago?6m{GwZ?M)^efx&H`gr*f~e~eJW>?A=!pRNH2>E)#``sN-J#%VS16%JDy zZoP8%KuOQp`vt}sAw`y-7D2?8LUX_cdAci3v3uuF{`^CMPPxx&)I6Jgh43tPsW4qv zr3UTxbU+iKMri-q3deUJmc)u9?r=9P)0t=L?!Kd=9oVXFHzWiOV*zhT;64laGG7X= z0L%8R%XY!*uIxR;OaDXBa%W3vn{J`70fgnU?>PrYSw&!2UZE0+>OT&)Qq{;P&o72> ztGTTl4@8KmTsNj5d}TV7`AVJLzj8*iUnF#GzR78KG<0#}yd&j-yQ9g>E=u}Oy7~En z*rZ=B#cY3~YtENuL84o%ncIO3_20>$jho<=2jmETU@^kPu z!-_;sroXEbcPATsUixrC*6nT?-7PddY}F;&@_dmq#}gd>x8|El)|LHQ8sN*jXf_-O z*#RGsafVRmlshJsPlK#4a9P+U!{Ew_RMX3(oq>*`W>MT}A9dkHz}|lC&OAh0FQe2P zQ->Lmc`oFY2<@yTy1Yxc?5r+a3)k(`wmB~6ntJ&34*!x`)slUL+Y~_mT3DbQ!kA59 z;Ts9=K{`c)1m2e=3Qr=?qCsO6^{pwjb`N?O@3cOQXlI3X3xVjlj!oYeUkeAQiU_TNN+e8y&eddVW2&wLeCU4!%r_v3ZJJk4%wBbjbmSdw-upL63R}2Xn0PlAiVqW zrw%Y8PLEAPJH&b%EM_I~4>LLx*z=*_7|CKlw!cN2#-`dCF^-k)#todY{3W)2o!1#M{I{^YXo(D$DNh=|0Y;B1!{Mj8+S?PYhIcgYus$Q5k z_t{gIfyx34zKK9H>18_z%i`z7aUZ#x1_`o zis8#=Q}H`LeF9glF@Wc8_$&6L)$dQ}qOY#+;lYu&IgRgPmD#!x&`CIhvf4OuM+SL} zQ?=NP1ETbR{fO_6@e~eVuekgZeRvP8E(e)MI5bg6dYjpO8o)|}WfJ^?4~NTkMp20zecZ9Sf?1f_ z`G|%{ABL{rm{!_q8qnxRY>1(V)*7NyoC>~aw%Kj+-IYJ!l6qscC*t?onxelfm`u$B zn#r}T*K-Kkyc*4vnzjVcHN#N-7`&k`rNWAT%!>JiSSy(ERT{{Nxfa4+#<|AIu!fX* zg^hxaHK)SOf!Qu#u3Jn5lTtOQ8cUNRY-AX2Rd4!QZtWq6L*&-Rb-QCWBHN|<}(_F^a zV2+OOQ8xNNQrE%u3>en7b%a{UU)KrAO}v}~?#1hE-`+3F2X(uV%qgeMV0-HH;+F$8Fgmz#n+K)mO^0DzAtE|4uH-Pn zh5Bjnun(||4+0!cb=>-UM5LmEMWk{_Q5dt@2)Vu7Kha9=NWbe;E~C=UF$_q%AgB8E zT+zPhtoZ)$`(YGO8y#9oN}+J;fSJiV2gRxnNgP_-lrleoqbeZk_CmKB>I^DAO{ISx1nZD@@1t;K9w-D0N1#0lrcqyn>VW}m40#$|}- zC#@>opp25gFi35MN2$k58e*;R*k<{x#`NXpW_H)Mm0yp_ zFsXYZj=EWy*-b_zv>UN}fx3%!-7dWe+wh7}46X+h6yvj^=)FbqIGug`_6sd&GBMT$7OIKM0y6*G7 zWw|bvSjkZ^G0AoooIEd6TvV+=TWs32Y=7S8inYvF~&t?#je8eX9Q)Bt#j7>xS z1K}*bX{2z6?wHM@N~T*8u>&VIar||OgcTHG2n~G5Yrzkq!Y~(pWf*_n#w}pSW+I#6 zDFGL#>P?b55OvjAZPTUxWg^mvp~7#<88HO&WFP~YdKF~7%@FKoT>L`WVIhj#$O z;aMA}A*x7MAbT~Hnv$H?iBmw_(w+rf8^*+RVi~avc4j>!r%_}P9@j1po&-vwH?S{NTE{RJa04BL_J6s1rMc}^Da8*`x$|6@Xx z9eQGp2?@Y}k>uFp0W(Z=`S7xlDbj|J= zMDa*JgDaguGcSQL0{dL7vr)5?Fy9rAMtF#*yw<{oADQ07;ONVJH!GOYVOi_yDbCOd zf9U$bYo;`%&Ie7iqsR&)&&;oN%g8z~)2u60kkx04 z(@(R3ns}%p;e3Iob_OV^qde#wnM!*t?I`w61B>v^+lJeV$5gqcryU!qQve$uQiqZ@ zNCkP#woD=A2b{%R&h%2;ZkdCkR}1~0K^piaw2&eRLwhCSMsak>LiK_c6sfdfEu;^c z(x&WNgy8vd@mb4SF1P!X5~GD4{*bPDhO8?5O2Ns@FgzN0ypBeOQG{5hI5a`lv*lRM zh0tv~FPhlv+{wC+;G#CTZyAp^d_yaim|z??WO$`%V<|#h)HFE+u&gMgVF~~)qAbt) zmIG!ssPy=cFd-(iu>9weu!5HF0Z3?MS&k zM)Jn64VrYX<{SNu(qIP%x7nox*c-b_yGzm2(XLm=(Gk%C*Qz_N@$K&d6 z6hs*rHw!aRHU-QjMufX_Zz`POSe_~nEVuw3;irdh^goPvlb61Nh4QBHpYK}+3=_hw zH)rtmuXBB)?>+MEZMw{urrBs!x(j!K@x#`qvEFKwa>vAv>4OF8PoU4{x2)xmegOy} zMieqv!+dFIK-C`NOv-m!Yx&O1qEk5>k5vRP@hHo;DdQTAjbhYy(y3Vo2KL_Ml}7X} zkv+;A;5DP7Do)9N)uDRF?c=O58`=E1x=VPagKIS7)klUI>(t)N|5=X3u^62)Q#IT+ z`2aeKKy6#&s=)>w_tP;~+lJ%*I06ic@cb}TBUFW+9EJ$=Oit!K}ZOs8u7oH#F zj+K))=Ad_ECAj(I@lxu%ysZh~PE{W}tVaa9f?=!VPtZqqndm?5>3CqikVZOzIdfYO ziPI((AtH|3f@q(WgH2oAOibMl<|%gGoDJ!hJ)c4wDHk;$=GrM&w|g=-=U@Z#>nb3X z9@U%N=vP7KFt1mQvnsIkMz$A^( z^ywKyp#vV$N2gxB=f365sl*~CgVNvXPpG5xdQ1Ez+T}J$0N=Y zO=6^Q{N+iz$9!Q$zAW9t?LS^Z7*PUTU?nO63BP1Hc!Alj( z#d|i)!(zxjBqh`vcpo^=J*R2xhw?Xm@|5-{Y+Ja^xzdLys^;-N3U{R^N`XaBT~O`Y z*Y)jCij`s{a8IENW@lbsCE$@UHeHH%csNW>Rw)pqvHZvu-Gk1gXM4h}3*>!9Q&|g9y?MDhz_QnQs zBL8*14(!uz$t!e}>mO`bsV_Kj6y$C7fw%@Od~NdBnY`#bC`B3v1cYQ_O+82N3SO75 zwtM@!dlgSKTrXh`Dtz5?3P_`qL7M!M7DRjzKH^PsZgJT>Du4firyTOrdN4r!_Nf?Z z6y#d8Bf1zl=p75GUOe?I>a{xO_DxvvFdIA3Q1dz1Wi>VC)Vv>iq@xOjAXXNHriAXR zGm^Esk`^ay1+NRq&HjrsB&$&9)WPY3wG%ZabhK|0v~#t(?CNGXkqpx8ce!Bs^MRcs ze8K4hPMS~XsC=oSpqZD~w}rK7R_3$7jO4@hkvkY(bn={l((OPpyVLDX5)$v)XvyrJ zOfOe5o@A#!s9b!xBO^(r(O-1AUu`Y6cq6Tg_)SHR9>%Xo5bX6x5!N9CpoU@WWD#=S zrImqO*BPLBh}_Vp{AazaWUcyjId!)6s$@c^oiK@zpKa=`J53?e4jj^RvI?O> z_C3M5ykEjcltfX<_N=exCAu-`p254~$OK#yx9A)o5WtojAzH9_MVZ$-xK^=Br!>r* zcP@^neD{FG0sHleWV{gua~e;FM2?sT^;!Sf;Bb3Edb-4ulKc2PE>;6E+iim;7OS9GM5r^|8S`pSi&d+0{wL&e?V~msU0lsUe@K+LP7Ja z<>4PD%m7l;f0oTlsl)y=^mx$#1IvkHs&~&=!~&d1g47*1=7wH>UGB}BpQfk_9~NVn zpgUKG`(C>L0<{{mT?tr^a08#PLz9th3kj4(htP4XQ|abUNaLH`yV2Ujf|tsF3z3FN z2%Mwx7f)$&XY2Qs?huoXvxcQU$KVlLyeZ%>25lvx5j|MpS9LAgyW1MVGD?=yxxV$I z{2ZlPUutd`btm{Ot~Z)81XnfGm9tRaSa?hOEl z9Eh+hrQ8waB~J$YSU;j(>!JOqKSX>?be!kJgpS*LFlZ_t;k2FU#B9lo%mwmuhK69e z2zsGZas&s`(4HIJaW0q%e>G^+*_>&Hw&28Lk`~S^zOiYiSw6>AI+0Tadg2`gPo%os ziV5kJBa4$e6^cT@*YZg0V_%jUj46M6oZX4-6LLjlMln8}_qAH0Z@RomU0Rij!ss~E zJm{*6;3b6mx)eE2nLeI55lj79$foL$tVWJu^ysJE(Uv8dnP8$wsnk#mgtAO!M$|y1 zC|*goyx&IwU7f^L+M9Nv7~h@+Xti)J1+x36Vc8ya@LXqR6cpMH*B|8%z0EiC1vU-X zVx`let)E{t-ERFkkgAJ(T4JNz8qCE72XadUpd3tVvTd7%%+bm2HNh(d# zI-6yE4dcrXuidSc^uQ{W$79+aFQ_mNG%j-{T1TrDRM@=}$}^>hc#g4yh?C(&*Wxy= zR>GkNee0=`)b9s;gb6&CNMrJHR1=ikn8%i!F-dX8!FF*@G9Pm?nDjGajdm2Jj5}2i zlL$sJ!?GH-AaM`8MqN_4X+d%^ypH$YD5LAn^sNniI0@mCUwfcx~qD6L(xCS*M_BG+`t{)1(sV-J(s2EJQ$jR%y8Oi;G4sV ze(pO`>Rc&1zsdOMVydjXJyHVL)2&RM*R;8-TUufi3ucmo@pS-DK-)-Dj`8>3aS-WW zK){E#@FzIrm!eNo-zQ0XcA$wtjI@JbVmYxP5M$K&c;{XWRLdL;uB~(g1gAG$*q|n1 z+3{f@p@-UfbYLxG=lXf!_`YLg%3j+_Y8Q(LF|wqn&aeIwZ&xx!`|07fTEQ`MD%jp( zgpgF>KY|=l=hhg})H8Ui!D`2&^DA}_il}kc-f7lba8eIsva`xAkX#*yPQ_d>CvnwE)vy?^HeIjE64Y5-D!B+JXyvp$%_t|B zk>r{Q^X@id#AagXRRT60PQM)u+!ROaxuI5T$ml*Szmw$gXnCArLu483fTa%U?$`pS z*Hhzwd9Vp(s%e<${MJ#kdl7j3O_m*~ryoh%`ya;MDY}!W+t&^|>DWofw(X>2+qP}n zw$ZU|+qV9(ZR^Y4=bZ8Bz1SBuYE@m=GiHtTThE*s#on?zeeg(%CcZL?t;)Qc(BH=E z8qNUPj;(HKZ@N=1)#-a_19He^fk-6DJdIfo8OtF$>+~+wL5d)R-=~8{qf!rdmv!L# z_N0R8d5nN;9{{e|vU&a1O}0gej3sQ$r+sEoH>6dV>~le(7&YZD)vwH)_b65kQ=G+y z(SFz0U=dF5vka*;%XKW9e`A{9h$GpZ9ehwr#z{g}{Hb>oEtC`s9~IQUR0|39hZ{wG z{Sc0Ewg-?3dsqp59T@vpU&?^?t&Dn%$J9MsSqm=HKio5#A%Nj*=3KL}Q2XEGC(p^o zjA`ldI}y4E43&8id)u@!-vc$4Y~sziL&mM`bkTouhZoZ#3|tnP5e1e#hQO=~_yXQj zU?n@ejzN|2=T;(IR7oOoGH`Vh?FMvuDK-QTv7ys?two<_b+)}nzU zVxAKs1BW_sZLZ)E z>PJ`;YgTSOK7tq6GDZzMSuu}_g8J?uqd|@nfv-xI*43z|FTOx5q!A$h$!}->4}LoX z69?=6?YFaVFtYuhr~hZaHdc1_|7ExT5A^nEC2*xA%a&p;<-Isiv6b1)P1poRdeEp| z7zSp@`D~P@5r zP7i>~hYBTd1Ap;L03yM`fkgyDARmMPgF>pU#}q`QjpMTcQBe#FB}0Kr{ICTgfro_j zzoQ65AkTus{x$pH01OEU3?@VZK1l4(?}v{-eoKcB5JdV1a~Bc-%;^Or%?`6C-dhgl zdVlZ7$w?qL@^OyZt3UG_AubMK|1}v-&Mw%E|MD-r9~|T^n7wee8N359M&D%~LeMs! zgq;`{0fJ4!#|JMjuNS_mEN^RQ0W}$XFGPeL2!;SR0qzz8{myYOkair^!LyMUSS64l zS?RHD4`42$@`C&VK*CAVIkQZXp2m4?rWK+-!q> zH$sAe0qK?UfapQk2lYdKRmV68V*6~A9(wY71E=t#Jc9xC=JxpZs27}ohYViEd719`VJ4w{uLI8d^2OvZiiw?02I?BZUc@2yk39{@@-rGE%CaI@zpT?EqwQF zCAKs%bf}uPulnH|^tW0Ly@wA7#k>oF9QdSHp3(1`wr zUKAzW9X#mOufOM0PFA^;4y+Phc$Mq3c+$3KOt!3zUmwA{mF(fT1w0uX>`N4;h|be= z@p%AwXkwcnj!eQTOxNmG%W(e;0%2qUrg=u-3|ZaP2``_ z`)}j#c@D5W=sL2r%#aPqV7_u_-^fb&e~-JH81cir;{v=02RIbxb=Z{gaKK;j?QRN#kQ7&T(EypK`=D_GqlM97YX0IlH zB484VaCyw$9_)?qZVr26ctk1-{V$xg8_apFp2wAjn2EN|y5Hp#pR_U)Wcr=HBMI_{ zs<+()53pD~x6oG#Yn$9E(xf)6OlVGBQ^Cr3myD>qK|i4nHXm^8WtqDK*Csp#)?;8=r(m|1;DsD-=B0&iD)V{7;C-9AEY=7dKI zRlnSaCQtJYs1}CCNV3vyZU&3xc$%=_R{q`*156){1%#J^O=HIlnKp^)49W8G-?=nv zZn=-BNLRFCri_*+aUVwu(V*}GSEsKpNLRt3Q+R2vgnc5aPR=|E;;#NqyxI$2oKe~V zrpBctDCBGpS!Tbf^!iGsfm+~v?bmN8MAO#nVTjPlBkIAFc3RvfLDX z@mI8VlY>H*2u$om&SH(uLRZtMkBMkvYi^)rkzZCJsoE{@-xt++_OzeB`e{Cv_q1W= zU-VHF`%Z}e97KlC)oV@23eLZsT)wt>RI@?IW{Q6_%tUXd*##bh!ig8(S zyxr7No4)|ug8ZJU3}g&Gu*-;lACe`5qG!ydpMAX*id*Z0fLM~D?>m+`c9JOghMHbk zit(L|R~V?+5NFN>N%udpp47-h^q;o&B@AnHzHcv_8~b4 zEvl84$VW2ADnFWvNmS(VG8HmDF(jBj09RPIealAG4N9>8(HC)z9%|^wkR41^%KW^uqB8aZO0{er zjn2Sqg*Wd&&K{_QgniQ!%%+spoH{isX7ezj>`#--F%$$85` z@Y@o>YqKqFv&+0ye|Qq}Y)x3tq$^zPFKhWgAsq&T+nXF_8fVF-uYX;-S(VJ&3y)p* zwRX!Blk3^COkkUJK}(AcsL|mRm|Z<7EUELW>-q?=LcQvk63s4(+%n`q161Bz1#hT@ zR+>c8h(6b#d^EpAzwHmRC=*mBu-LTCq{8VPSTBZoS@Xm1gyoi=LB(;MLf5ZR`}qVM z)2@Y6!jY9{4A$k?FAAJ@rmN9e)7*=D-11X7J8f_BI|}t%Ai`Xhjn*d0jR;U3J2f5V zu@8|0ko)cx)=@ilSW3{V3v^5KI>PM&f2iYVH?;D>SdX;8oRcHrsno^LfxElUgSS){oQC&<;`dS;ykYW*E z=5}OeYRQjk0B$x%`e6NR}q>a08_F^e585_2&eG6OBE;aaR?W7He2(!>^Uo;vIbN%9| z1Nr+6Nxx*qQsBejCK~LAQb;1(64$yxHb2(gN-9%|y2jg!ZAfSMmVG;g%Q)RSMI0AW z`)}a^wiiW;2N~ZBiUsEm5deuYXiQbSu3p;;wyK4 zqmno|uWNqQ`|Js~cc22A$o({R(H>Y8Yei8^%;|*dSIAk_1gp+1SMvZexZvtFEb)Dd zc=0_HhzA^XS`tm{Z!S8fbLnGd*+C&sG7J9t=3ilw@Q%Fm%DgVp$ZfMv7QEc6=!S(-s@kpE4} zw*Z~H<+vq{FAM{z`G$Os5z&2Y>nU~G_{#hnI3Q7j)LG401YJ*&SzD>kYj9Dd%&au z5&Ij&61NosD4@`@xbLrsM6NTv^sSmg0fx;4gdv zdzvsY$8eFbShBvSQDg6xYnMsWO>YXZe&S!phK1x*Q}Dly{; zGgeUMmGjw__8Q3kUN#X^s3Ib9{dI1Q0PL4Xk)ce5Dq!7AG&4e%O~{b~&W*!24lv7g zrG=V>fSDM(YSTawt(JLCOZYKXWqMdHuT^qfM&wKch4dPYMQ15vV$7#~#OTnA0}1%} zASc|RuG|J;I=v2#8)7~SGZ>Xc#@pDthlv(!`X>?+OgxY9;AMr}OyOgz{>MO6(@L47 zH_xY|3!c;T9)kfv2vjYR3k`+fz~F!fngj36qIE^qDC!cVSYeFyP{bK7XJ)dyy=T+n z_iK|3PDC?9w2|KgA|7~B|29<0HqeWQhG)Cozu*d_gHxJItL>SZ<9+D%=0!L;$fkFp z{~Z!D2PMSt#9n~tdiY26nn~QQdWZFbP;~OkUZxxIOd0RB;dFa&P4h^iVNS@QU`uT3 z_oZ^IM*eu*?1MP{st*n(Gn2G8)zK(198X26N7T-LXArdLT>u7Ab!ka|Z562_5&Y9c zG%yrObli=MRWGXSZW91Psafx?N9ttMc%5&*PE$q`T;R}WI>_Y}nmt#PkRN0!P?b2; z1&r>F1}b2mNbKJ-fq9S5g28~LZhSyxAHiMQm}P+YhFAIj*(vV?l-Kb6j&uRIaD?({ znfzA#cu&%EXQq}9YFhIkgw^tItC8lm+1zD&P=&cf?`tZxZs?}(Q`a9!dQesf@hdHc zS?T9YAugy>ZlezBB|qv5xzvYqe@xhIFDhrXikmOw46+&~|E3Z|bz&1GL#qu|*>b@8F3^ z=V|kq*`|-Rh9EULo7haX7tEVX8`OA&0{KV9vJ!el@^3ytI{1P(nr;Ja-^skiuPDKR zRi#AQ`rr8gN0qit#`_(|Lh#MUL+Ou2W7W+DH^rqw3Y~*IYuk)q7$!mG+>UCLn`+PP zy$Q^*_atg@fJGHDI1h%?T(zl_T;zn$w-}9c*&CJsVkW5JJ{w0Ro#}lh1ep}qW3QvF z6?sS4?6m+dqh5%cI4Z7JSX4v~Cy^A>+aNZ5{hQ2#IyTllL`|0Yv9q!8CxrV9d^5I* z=7Dz@X66EetJ-U(Ljh{FfS=ZCI_D#NqMvI(ha zE4f&St*L)~8`k01)s)-RWr=dRo&WRlSH}1#S8p>NY%#c$E^X`HWIUTOa#WoeT8g8B zJ7xhiqaYC==0&afIN-wR%H%S8BqcknlcK{Cj*NDeXrNBScHelr*lr9xJ2s&ELY`FE zT+g{!%>>i@_UwRQe2UonB={cWO}d#bXMHZGN2LG<$!F5j1&;lEP&aYWXGYGX4y!0j z{Q+1DJW}G?3x4}q)QY< zt>OnEDEgro9ZdIDN91=jSXE&Sla|@1*8b(9S9rh-DAL8%!+>93Ug-6hQ@Lf_nG)0sZl15tw7z40LY=E*#4D;w%g_H%+4I4WuyhbG z^3MbsPQGQLM1A;YtVzS}Ju|W!zy+_PI5+fGWch|+I;h=?RjL3nX#H(y<6*sU52Wo} zIWg$X!oPQkyJ<5?aM{O_TqHP&q&-VJJ0u%lUa19fv|=4^qw_0>mGTZ6N_TG2rpGYq z8a;n#_JR*a&xKN@E<@^5Sj)Fcn?>=FWTu{ff;Z?=thf+d-vycBi=6he!?==K1AUNR z9~L#n&Z5gVYJp9AH$~LOnkRz;av|uL?6FL^-Zd5UQqmG>Xzpk+6RU^125zXu;XQLX zq_5tnPO)HfmV^KBGZ$T|UVJ(r0fTZ}TynG|%=gU|B30Kn9tOv6C_ZIUd@J4QJNN4Y zJAsaxtjMIxk!DZ+`-;azd5#MSMrh{)Iw=H!7uR``S`{}b+`9AW>N0zT{n(mQM00Fe ze5I%)&Be8U=@7B^!++UIDNX%0b0N!=v839gNlfSJHmn{a89N5Z=9&BOLA|Rq%k+_6 zW}*iYoqrJow<0S_v(OZtX6VydI2avi5||OeJ;WdV%#DM(pKm!FpRJ6XzmU_`)f}|o zx)fO#LYS+#X~UA9S!XR@cDAWo4v6)^PS!fe zb-7ZCvAw6y{g}hSMUosuH$e}J;a9pou?oXeCP$m-v>KVGf=L)qgd;rk68sSKX@kQf zqtpAi5(;`>em~)k8KIPAGViythD80B2wtx__A7*XLjJkV`S`eY=A*Rcx91}xOk*Q0 znuCO{naMIWl{Kd{^t$7w(wODG1}O7c}O3o9(F{O58H^W}|)3)ZH^=7AevD-pY4);0&`Bc_`Xn|0AqNaSRzM2TXl_eY^k+B9RabZ>QUQI(rYqkNk&>-H@ zV})P}SZ0#8n2l54Y8rB4qEaIY%}0}3JMeGA0DFhwK&aO%itgQH;}EYbpGuBE-EjCR zt80_eA!&!H-YRMQB>7>^9?Otld>s1JJ6)F&viK{s0Dx_qne89+lau-T@PZYJKFO(R za9RA9i75wU$oXw>W*@F)$etqb&ve=eE(pVpau=hP!&PIkiL%y8)5Z??l#vklN{1UQ z)KFINU#${f!1f&6cp}v^;KVug78G=}Q>$CdJbPkR2A8qVy~Zod$=P7qra@o(lK2l4n8JPi~yi z&A5lp-@}n9WdRQ5VtI!OO!Xsl(`}0(4?V|hq%z@7Rh4@Ykll^xaVNP1N#h`)gUxUa zof6xx^2COK_tD#3$w3MG2H7}f8!%vAomw-nmiRu^^VX}ObAgJ5V?|5JgF47ziEPlh z8$Dj!R4ndLW+(Wq5(7KKkJ$%=!R7GQMz4_F*AYN}Cys!<$*-Cgm_T#XW0pcFFrngK zxu}HLltN<1Wd`M@fD2u_=pK9xf5b8lnB#hP()Zi5o2oITc4|OA09Mg5(8m>Nhop0T+phC;7fwx zv1wFwLD@p6)DA2nis7#FR&7M85noTOj^-NQrd!{_$GnuZgQ2lC==$9D0k(4_709H92U=H0_o5?8X!lyIcnJ5DI5BN87 zeJkI&t-m(C_F?X~q<^9YuT!MYHGbVI(m&XGBPL*nysM<>!-g8kbt@`R4?_)ZDimT> z(N-~7a#IPK8Z0TpRX2(C;*U8+%RShH>-}~ItRMRR`7S=#iKz+sT#uMqkfLIYJV5?A zs9?VrZ2?_`f~w~kPUt4Tpy^iW6@6QW8rhDLQ&O~D!n3&+5XX?}$CJ;W{)N(gkRKxU zkk!_7JHs5luuL3$M)22R>tJ}`J)-&cY$|u&Upyl{;}2!%=Id4q`}ScZ6tn&6hCl(% z*tYLEDU=cd&BAIZ-V^|37+r1wQ%uN4Bl(GgG0|(8Jgo-%F|THq0dT@Bmn?)*hei=9 z%4yR1R{(YvD_%ebz>^SlzxL10%-cP=$~@-JW408M@UL$8WNo~5163=B=WposD$}k1 z5U#P%|Hp8R?f(_7vD5#@aE*hJ^}kz3|4+g-7jPw|>nBz**McN$VleEGJi(-2z+->g zf0_6Zn1tnZBqbyzAr)c?x0477X#ADnRN^;J`o?(P`o8wr*4S<_sbV_bd|!LteBYdL zoZdZ}^@kcAm|KQKWriZc2MZAcjmVHQWB>v|1Qx{aF9ZPI#G;V*YSNQ7fOd~!gbE73 z(fQ|Kfd}-gCFo($O2S2e&v0e@;vn&fX=#WFVEuvm5yBRG{LI+Q`F|gU2Z3hj@#91b zv?e-G64iT4}y&tyM29AH}3Ur+oauM(5lMe{t=Gh8} zpmw%r;WPRx88ZA02d1I!FIPve9Isdsj$Wjb0MI(+b03B+45VM)PLCfwnJY6Z?$K{S z&c4bzzwP7!AXIm4pb+aDThA+28!_}Ku2r1VhYI07UGv(K%BTRU{OoK*@+hv;nzy7l z8%V5+x_8L0^hzSA%iz2Bj#}aTo#vhlKhIW3UVZ{?9s~;sUjjAcQ(r3=NErNI@kr?C zs1OL%K>ZuTIyb5w-gxMPwk5N4OwEBneCUnPdXd&3h<=U2df#H70R0?Dp#39%$eZ`= z7~g8K;2F=7^WR1z8Hmlk%d5K`1YN+2kSU%5KnE+=+pb4y9({AW7aJ zJgg@OWEBhSN3$ONu%ad`1oYPmW&M1nww8C^_n_t0&T(M;f(^Dd^V{-Ma{)4ltv}>p zfO-AMrBzVd?OZ&XJ2JI*oql)E!BZ)~-nI}U#FTVq=8IRp_^h=e0JZaOeS4b=}afGc0 z)0*8Ip+LT-QGvzhYplyNEOHr6?;mo`{39{rKJJtm^X)by!^ys;s6WiC1u+HAOw!M@ zs-E<3`w}pWS891b2N)MkhnQ&%?RbQ3rk^WnvddF5A z#_bEc3Rn)pe=Xy5?{~AGF(jrI*XSW@rTimz9d5@IPIbi6?(bDP6x>MLe=q@M|5j0! z6g&|nn{ZE6)-xP1;eF2(WN|@+Rvj%KZAmQWPU(O}{$poy13v8|&k6oY?^qf{oU9nC!_0M^g=K)4Q6Z$=QR2FPsDXuE4&Vo<=dGF~FRAV=QKbKA= zMwT5xq`xiXUE4X2_pTgIfxUM9dsQ_(pcM4b?o|O>&!rr@Y;cvD@x{ek1w;2}*(Sn- zDK?(6&wFl08gpEc5!lDC|*I79&#o%XYC+vzoL?S zi*CU#G4bU$l`3~WpM0e`DqDhqwN!L9rOHNnXuWQ&>Ywb9G{MX)< z)HzPrt$-XlzYy;^foOyLLA(oQOE$AdI=Jea=In$y^J0WMIX**LYU34;oL*V6gc^x9 zH&*A|@u@d0r%Gq@;0~h}Kr^Fjuy>o*{O}jfS^4!+< zJaQOhP*&@vnb^QpYePlL>Gm08289*ZYE*wHlp7%pfR|o>nlu;N$vs? z*j7eTk9M><(`R=L#(T@+S+fZx zVx&7G5?@tUR{$Fy%?B%!*$rj?M5*#C?(k|>v}P-V61pDf4MCmjPjo9{5=9S z#2(f5bsp5s0&_{FSCLnIH*w>Am2rHk7B(0?58>suT~rB~w(}-Y>Vz+qPt{zUMZ7oc zGgpOvhZNpz>7pSrf;au!QMtnK0`fa|blu+6udywOFCZLS&$hsFZ()A2=;lFUsD+L6 zwNd*K@b;n8Yn(*%g|g7fYvh;@9f@l}3q3z{p6zafH;Lryha4}^G5w3aeeV#|uMu(O zmJI~kle4kBqz2JS-GB8D&ws*Bqc@4>CI#)ZutahfDA%gd<@aviH**9Va2S`~bSTyjP*(rcL1KMp8;;c7vuGWbXZZ5A^C6)I@B&|RA~)e?t0)N__d@g7_; zT2xF0^Zumv9|u8ghN2T?O@>K|+LuhW9oJq%7;$)_E2QCPrD6#Nk`FDZE zGz4+6u*KvR>>fg^Z?p<@a<)k!V#^`6I-|kh(V7W!HAT>HqH8laxFh50;_=%x^JMb!Rd$%IDG!Xk{oKdDmkzQ1;s7R! z=VXPgys{kM47d{;>YZfO6H=4(WP5?b6Kham0xSe1^inLql&Ncuwc@$|7W>Lac+uhxKNgkmFiw?8U3Z zdjH8~MkrM?asMLo=gD#0SSr(Ez)bL5>cPKEL@D_U)x2hT^TIGCROj!{IQX$5IMGM? zJ*@_}A@JainvpeEdxnb$2)zeu?YuNQcN7K(%VkI=q{LbKgwEUqFxSjzC>afgy>tM}cEMF^O5TEhE8;M~=-c+~rQ!O=8o!l)Ea-&!=7C`rt2xM`m3* z)OA%OGkIU{3+BJn-n5whcIcMdv+vtzrj@>?Cc#b-Hz84*mqwG}n9lS1XF3FAHId@c z$xX(SXk6&uqCN$-aS9gWQtXJQ_N6rQj1|?&=Cq~`JJ@ zsTa*Hn99x`y#J6Os8UZHD^8k&6P(DrMmN6A_KE{BHu~?{77IUE%A>Q%4sD3=3v%#P zoUa@Lg=tCyo#KXejZ#dMhD(%=db*BQ%Zim%Naec2UpP#o(e_2%0*%yHkzNafH|_B% z!Pt|)^Lv%h-ve`;l85U#4`GeqIV13+)g|dx5Of-X;pS5=G-YiF3sWQhm$GceO3GIE zg0zJLM!nW0(;q{C`^u~}9m*a7iKKleHq;-$&+b_&f}EJ#4bKOV8*Q`SsXQY7C5t<; zl@<$Y4kv3)>AF*lu9-SVw9QNrA^WU@#mc@FuVSOInU~kA{Tidm2Q;+017C*{Ur6mt zL_%HWx-573w0Nr}58YTLPJiK}OX;{vMm5FS!aQ=B&lwWjnUqEgQ#m(h(j0sHaw|=A z3L~b(x5Jn@>aw?Jso{B2On|jD1c(80Gh}Hap@)iQj;`Qv_hNV|8Q;H~y#^EgTMedM zEdw{2ev1c{sy6}tY?bFsbZchB>XOAzZNyW8$%5Ot;}$ffUmg36PRx(bnW<yhX`QIZV~h)JkzjuQpWGP;Udmtl*EMp{)c4?7Nf{?V zHPIDpC>Aj7ye7<@im-*|1iW91Adn@8&FLctJZ4Jrtr`OLZDRN+sfmaZ^pwwzq+$9? zu1_Xt?q=>=fU8jCtehhJCa@7RpIh1bznFXfXwIs|Tz?yJ(=GV~U_!qxBSs+esND~r zH0Un38fQGfc2L7E1BzcR0(zU5s5y{lolHa#>Ri)UA#jIqN(^>rZ`$9aJiUQUHQlr% zeY_*~x0G~|gNuF@{!BE(h0lfYRhK^o7}{hOYdxI{6|P%Bw1;#$Lj9OnMmm}5Z$5CU zCR3W!Z1Q>gKhtZP%$C}ZZM8%G^h}@^jdD3%IvJiXG_8OhkNXn_rgcz@t80(K71J}_ zHl4i3hbT=C$chILjDy(-iNfHNxN8M6Hyc@!g1G1V-zwBzST}ZUpF(eNezr)P3T4s^w@242BQ4g$#}Ob=Y7%t%O> z7`tkCCHa_zYzFLMhuArz)N;DWWXp%QT#1t`XyNnXSh)F=xwLX1X#I0x+pGRQ*XGl# z2z`dnvH54QEIu7!Q8fxS8BYxZxcWC26SalejCK3)R=K^ukA0_pwKge*aktyKDlK$D zi)dNt-#Aj7p9w53T?83y$25kR6l8*<{Bl5X)D#;!u1E4-)%I0d%BZ}@JVoBUz}_tE zbW+InO)pny>+LGBdS@Q@iiqK_>TvnHEzdxnq@Pbnu8xKhlve>jy3{{|th%yA@u(fj zZYm)1lZp@(4yj3Da%bLSnb(Usi8Dl(K1Rb3 z(=I$<4NZiehF2+qFSo^#?zM$^{=r-9){PNhj@p?w8f-hWRpr_b(;e7(i;r2L0U66u z-&AV+P396R4=>CefJ_mP#thF6fEQc1*yMWzCv}(DijW3i2WTO-fQe$gVuecoM&2Jd z0`YL-*2wl=E#5tRyc;Oen)p>WF_0KdU2xaf6<6}rMZ~q|G@sjDtpo5akqT?j)J5ukySo>+YK?UHzioSw0FruSYX}02mLh&k&^a zm-tuip=9FnH^i$j=9*dJNOz%Cc98j7_2g2Fd(QcfdPO$xh~y4TLS~w4hC1#N1X(v91Jh{%*F7OdKYEIlfnm4rb85pzT9eMw0 zW5g)kW2@Gd5!6zpl!#a4J@jw4@bdAvnck8STrSV@H6UdikAf9=b_5=lul`(u{$6mJ z$+W>Iy)Jkt6yi~8lU%$bdvMksZgc6OAgQ_opmzOc&GdVZf6REK-*=Ys@owLHPC#vn zcg)u56gS|7OLWhOYbztvdWPGB+-z>%zV`XmQ^_0=VB!ut;(i6%Or0pUu*cJZ5gh_g z^&v(iPVRnU@c{1g?gf4!p<9vVT{6htf^W%#Wj)>)1d2gsJMZ-681F-Z@+oigt zzn--Ws>jJvfnC3lv&fZf+kJo=@KHNqdXb_QjxaFS*spZK1)C9{7O%`!aGChG;EPhY zt`CN|7ko-(bLUUHDZHhfrx+QvI3L%Va4S?9+B0Od_xt*RHnLjoSt?@9 z`u@ASZJH7fRgJ_IFA<)yY`k5>a6Jd#K{-_(s8}c6R2#$)B2gSknMil91D_#QD{bMJb$4Xv zfJ~M~TpA7zO702lUUU1a&S8RIQ3!EV1=>nWfO!jb>sN&%{O_AqXdKn0K^+zBl}->+9?H4s`O6h05QP9Yt0@)b_sZOIMPuj(&qMd%$B9jYmUf_;$;4$`m zqh{&fjZlx0FqYRe{3WSU-ZlbmT)3N35N?})@Y%HRY9`J4F6`9ubV*m0UC*8(!|9l) z^6ES^Wl!d=Lw&hY<2jG~K-s}TtcXEpdzUhU`utYTKnJh9<&gmRcY1I?K12|6aVh;2 zvo{QRUDXlaKl0UaMT6HS2gTZ9c54Do7@a=^+iKD)?9C46%br#iQN5Ss)*LSyf8tq} zQFa1dj}AH3{p)sv@stkaVML{sGfEL!Ju4WhD}bTfXNQ#~^jQhTBr7n7Al_e{vvQZH z5bo2;Xpvi4q;&)dR_fi8M)(9Zw3Jt#XU0<2P*)All6))u3d2)RfRt-%>&%k0-aA0Y zVd~{6Nw8us$bh3tZ)&EV!l9Vn=NOemX+It`t zMu~98vzn=$wR7VLorUde9%)Jg`tgN-0O>O?&+;3BO3T5wYX@jd=Uyy6psi;ZCjX0D z01B=teYL=OIyrTC8iL3{Sz2u85c%vdngpP|kFy{%H z)Z8=U3A7^>%fq+Vqw3t?_FmVS_`KG~Nfc>UqGc6gDuQUutwk>BjV0EWYO&7s8`>8q z)S0+oep0faA|oAr9EUR;6f`O^e1egoMM zUu@=5pAECsH3`#yownwrwnfc<#2PoU=;d4Akspu}KD^0%Seq@n^E$r~6Tg*6eNH=w!NLA#K@<<=;jv-T0(kiS$U2<`<=qm{z=Td`+^p>rDT|!ovDZ zLtdHJ>%|~Sej$L-@jn*%nR@bW4P&+}cGQkG$l#!+rjd8enNVJTk6t*-smpA?!vIXvy5E{+ehQ)$6V&RMCQ z6v@bJzq7YAkg0VSJ9Ls9!kx;Yy05L>qU+ok1K0gwq!? z3EFFz^6%{COPG-D0?T}Ew(g_<^mRhqX|nyeoeJHoTbFAQgg#GWp3f?sAb9cTd9$?Y;P zLe3mfa=*3fHh)dAIoWugB-9}Bq+njsEn%~!Jg^DG*!fFx_V~=}>ECho<81gCpTLNg zL&aQG-U>kxx@xdaw`AO;RwNhnYr%U=@Lj zFI$$-EGM*Daz?o>WZAw$`XI%1EzdE@*F3Zqo?iGxDmT@Lz zQelMYj*BW0k_}Zw``aTj+C;UXoyG!S+C0OPa)y$x_53oS4>73~y%r08(gB_xg7+MM zjrWPAI=jL9{DtjNQNv*0?*Qpv^x=cC!GREQYQ;!Mk@XLA(zw z75C_m)|egIt)=g8dF~i7sj~4O^iDM^ya2Ut*}L6C_7BNET<0EmzWEVH43|)Sna`K? z63@gdU)zO;Q~J<{oBNv7rJB8KCByzNH}~jlJX?kMGTvS#=C2w0sS*2^{7KU)XKJg^ zZd_S0eHpYpAsU;loLE7=aGXal$8h!XuT|{>bZ^%Z46+PY+k28M+>m1i^_rzU&rWvT zVm60+3UzK7@3feK$o6l)tH%)40YYMEOhLzX2eSHtd8pjpwN=p{6Ypy?^;%b~y(1LO^@2bc(DmSHhq zTbT8%#!#7>^&0gWC&MsoY?~{))-HT-NX3b(MKW1~KA|vpAM0uTnEGM4&r$8MpU$$* zVeR@>fl~pRAez<4J$qYdtM)j37BimVijJG0Y}hm=8Bw0{Y?Xb;q!rfCupx>Q2nD;Z zf0!|HbWvV*NRznHJD{g9IwafZ(u^t4kqyIE=|yN=#xNYMhB4Y!QTJ)+4-LdxXfO=B zY5pLO)T@})=4U=7*faz{od4OMt7kEMse?7lkab-$R*d;5fu735bnwHd&nDWI8j>4#BmbUh9iY%-Wq3 zpXR7Nkis7O(~)%$WjZQ8y?Rb;dT>f?x+gY$b46@=;+nwZ#5wk7EBhb{kaZw+Px8NB zd5rz>>^gwyPR;6jPGEX)8%y^fF?IV{-jbN^X?~$2rzisJbRNLd>D-Mto{;!vtl3R7 zC(44#IuiTZVQwW8g{sh>(Ku$P$iQ>b*jemZ-}p$lu9!zauy-+@Ko0MEV)wsm9&+c* z3FCR-Z0h8`Jg$;{16}5v^!_JAf4tKF&y?jKME|#4`aej(la>2oG9?f??@{mJsI1`z znTC52)Kf6%=Ox_N$eZ&4glHygG0Qs;%@0r4OiP{Kqi4K3*>1nQkw@S!FZQ3#pG%fn zE_&1qC%^1`;4XdF%0RR~DYWPEXM8f?_itpQRmg`$A^gN}qki84FSZY`j1Zifg{ zm8w(=Tmp-3MQ)^b&?sT5hoXl94n+!4(7&Ex3Wi-}NpE3O*0-8KWn$H5)@zoANDt7l zR`+aL^R^@tC$9W2N;m(T(s4iJewP2DRN`WZV#RVN%Kmqjqb;0Oo6JXmC)Q5FIiw2L zmSk4MIb7?J8N&iGAhKN_Hill^8q3DTj@23W%rYBk@8F7R{NJ~}nN&Fg^2l3B&tuP6_7-(T0@=QH6mZ%lg) z)7Tl%%jx?*D3wfkI?L%l3^PB3IZl&wCM7U&rzAK+{p`GnIt*7UI1Do>NldS5N=%P7 zBxQvYaSV7;a0vSTT&5KNy(Vf=a43HLfim@fq3fCb!)anx-w(+T?Ej0?pXL9P)2ts( zW&Vp(V&4pP$N%P3^uIW@*h(f06`?ccqqB1nnRVacX9tWrx+jS?}yAe zvp<|NXm3}%IByH_0cqdeE&eA?ng0`~|MOCstSoJt$^TPIshUH@PW?SrFw`g~knlw6 z7fF?^mAjO|SSgfYXjel&-mfzMlqf6-de%O2cc!~v`=@5IV3U-P`d^kLv2r z6$Q~XQZ+2*OcGS7aDga+{$R*pB=E+?VAk&cQ%BWe_kTW4bR>EOjUr@vM*4af@WcRl z1M49sZn$Ms4Ad5-|6NBH^%f^@Fl=gm>gc*v4_hMfo@#+KR{zj{6Ds9L)Q|dKggRU- zF|1JS1lhx7IoLw|7oky}mOEKLgraw*vXiVp??e;~Ng>t(?W17Zly?Z*Oy*->#p^EZ@rt9ot&B3XKbzC?Eg7)W>3%V^Ibd}!*AW^ z)M5hhuj5Sldgva5&;hmE2T!$;den)mC(T8tr>m-*g0}ouQES0$`O;U)_yfC{H=yh3 zSkPc`vm&}XI9-(U&o(}-JlGD@x^JjH9n-CJloH%x=lVWhcZcQtL6z? zWwGrl^oTH{gDA;KwYp4_&<3(ogIM(vv^qPkL%F)kP8 zss`#t{s9dUyBbQO5fXB1==o!qoAQ89vM{*<-)q z8P_E06$B!oZs(_A{(&{f9ZH z<^Btz)b0O9)C-Mh(*K3%*&jr2McVaqimK)tYnr(BN}Kbb?I-9!8rbHs`|VP+#5F4D zTn#OCo4w6DquxID8sv@2nO{DfQ(ayNzasLamJq1H%XEPfmhJTqXBK-fih* zm)|Co+x1fk4&Dg0=>N=`{6SSGqo1hI1;W9rDxlPVt8p`|wxS~50k=pg-JY#68S(Z% zZhY^1#+NgH;Os*ic4EZUk{$FXif%ep`lN{no^(ph-)34bRyQNnhX@ao2?ZWcG(KzZ z-|%0g%QCvw*Q;o~N`0%N(j>5xPcJH%lQF3_=<52_Xgk4Z>ICR2bJpv!0Ie01Fl{s* zny9@q+G3r3@FL8nrXZ9}D_TpjfjCd|eMm~va1T`qHZ(Nv4-Wxf_zBpVnX;3tK7V##-NQBWHry5qU1A=D7CSmbOO*NX~)*}XxQnDYs{u}Ie{ zj7W`EPO_zFSF9mFIV7FG=fDZBkR=kccrNpg)`hMv&rBEVg>2K~;q9gHDqVKLk4O0*x3G5^St2J0^Jg8Ux z538UB3?1%BkJ)h0&)H;<8|RA%Y>p;-o=pJ`pGqt%F{3(bd}tj^^=Okjllp9EGCmJ% zbptxJDTiW1t!_Np!IO+K8ag+rX; z6;lx616IBnxVUuvICl)A7`X7L0>{%o9T*%r78jOiLDzP(WVXmvd=SokIjpjIDbPJ3 zLw}7Spt7g?cZ>@Bl~F-~e~tJ2ujH=Ui&b9Y)+>EGqKMNa`8V_m>`Y5Z)ELc9_8ryr z-Bs_(nW_@9$Pf4Q!H5jY85#d*-|Uu=mW$!@-P!FQNo8Iyu9sfd2!cOvwDAXB=KpGG zZZ78 zy5PyOpOgciyVwLvkhiuQ;sqaxpWQftsN2uyTiSEJ#|{ST!eYeT62IC)% z9m~uMXsC)IP#-O4p4G&pG+xZ4zVKQP`u4y$Yc6B60(w4|;Ybe*slieD=|BFW(QN=Q@(-friAx4!RTl@`@xtA`ln6DQsvrnKnckjg$EPUz8mKhm@dfMVX z`HGRqv77R_PDe~1zHEWBmZYm9?+DrJ1`^?5c~DH@xf4Tj!(hytdPD8$ijYnu>p0Ym zVBHyO?UP~>`R416AGis0@6|}eV}Felm>Usug7olDrV0o%#-%hKCDJke)J%fl5M&Ib z0l`HvL0JHE$t3=gkEl6eUZRX&Q;jvNUVuxh_fQ}tX1=AH*9BCfB>nkmOeRNzc}%AG znypWTFShtMdX(*|{8s`D4nn>2UL&rdKo>#oUEjIXO1ABufB@X4I!vDHWx=(}o3Q%S zxhp&@#IE4q;UxH1oCJjb`_`k=b2XP0G)^1(K1A<+pnJktUg9#%JbEqUX#CEhR$oz7 zkW@+YVbtx_$z!r|J#k{S?8SkW13dS$ZBNk=-THd6lfIcuaqrZ&&u@3mcFE`G*G+xx z9Yw{Ajb|Lh#GzGFZ5;Jp7H2cS#m^`EOMRJjVrJ~3)j>d-E3NvMp%Oc{^S4>aYC64d zZwe&{Jw=z7+NL^3+=4`G2(8R{<=KKAgFiK&#>6O~8ZjrG-gy$(2q}z}5em@1qu;9A zs(?*1j6Pv9)J^R?Nyvt;a%1+-4jox!s`>wVzy3yTacg-daPaQhY6fqP20x%=92*-R-8Z(Xg=w89Y92nvr-o1>FcuYxx z*Zz|CSxq=2aI|q03%+CJ0Wm{7>>uXR;yO`KBRTGY3wp;Rh5(${qViPfEW@uuUi-;o zM9NdOKJ2s~fXh;&SnnP2BBh!@mO{m-`@YnuT=||(*YpRu&Yg|Mj1}Gjj1|q-_1hIF z*IG&1D10{ICdB4}FwETEl6WYfMSh2CczEdzYmvy*1#7CamSU+Pv6+RlRDd(<>B7(G z!abFS<3M+lIP}l>>oymuhP`c|m)n-rN^Vg+fuV|7FU|S4Or|_@8pckicfRIqC~-4@ znLqOUxq|i$&6#&Z8|Xxvw5B&A>E*L75)2>Z*6`5CWIrV=C;MIDhO*?XeY*2&hi9&X ztqgNDlwWzc!px1Z+K)NU3s;v?)kDoi2!ibCuSb3NzxOSr8a`;}b32M_k&wl@sFmGM^WGb#bQ+6RJ66G_;z1P9;=nU% zjg>ZEJI0>nW8tUJ`1#q!q^a_xx6%RhJ;+drm0Px3(QU8ZGl$avh6VLbp|^d3wCXNw z8~O0pyazZyyYEmp5yGp3GEMV>v2KNrlgpejEhOd4gS>Tr`-s)pY5Y72mGyMokQK%z2m+1)aKncj02TW8- zt7ZLSJ;S)zWTt@eBh^Hy0ecn<{_q1=2&tr82-kgsdxRlpkMA)^E^*P`+bT(^_qq4t z-eU~9dujxlhGh}=B?&k&3GYoX!%F2Mr3gA9Vm`_2-4uQ*=_@cpru-HB1X@aIgKlHV z0ho|VYr+E}Jlr~1F7`d~9gGlTOxL9~I=pah!Z<>w+Ht=73?XNR(nBPf&2wjg|pAZLt`qwi)< z!#)Ug&uvcG!D20^=>3on>76c1mr=qloncRIUrIX^?@eAjG;M5=#tHyf9lQN_Rbxk<}U2pEs@9-G;QZj z)8;Pf)eU3D&Rx%%VaA~V^E{$y;(Q}9>es2G$A-N!4{od8vG!5|b&rq#FyR%Z*9c>4 z_>?!5IaHwgj%Z|4{%?>L{LhY2O@xcJrxhFlV0E>&c@8(=0&(;6a`W-A;qpj%df2%j zSlM`<+j}^|(f>N0FbBAm2f8GKfLnT?m#JE!s{p+1J?sE>zOHZt0&c^`qXPH!c0pLX zv!d7W4@Y|!XIV=RIDl1FgpZexpI3lSfR7gp0SR&N@;&3_eTMc^bFu#aT%IE=U0vbU z03LZuM|U_bkLGhZ18#XwM@N{Yv-6(=cy4F!4nTkZc_5qsJvhQ0y=efL8^jMnCma-j zfVud%1^#`6=nCk4SGD(o1JHZL$72om;*o{J>@A(y054oEJ?z}!mH@ARtwVqt%+3GD zg$-9i;?FsCfjQvvga7m5_#Ax#06ZEn2OSS5Tpo1*1fAee-X7uZ0pJ$`|NC()03Sq% z|G&LS)M_~EJSR^Ym{ajf!5_16<_u^-{fbl64R%p9eb7TpIUfC{frfzM&Fb{UC2|YM zhKNKOe1)0!*_fB2gmov>P2L-ZsijA~nCxJ1A%XZoW!!4BG5X41B(le3k0J~qwbQ%uHI;1OGY8E7Jb{P-6 zd|W~bB;zNi{ZSCE#`F2924HK0KiEU{jG#8;P}8$_}M0nr69=Gbg2LnLQQ~>uWX> z(bdBLQZFr)_7&d%FhdB$@nC=HCxGF&l9+&?pR=TVc>u;nOZ!U_DDlhbyP-bZLTti*+&f#coj zxhBdCa-{T%6H%kFfNU1wDkaVLkpgv5_vn=3W$QkumdW^G<5f9D?2SzVDJjP)CHff3 zbAd9(%j~4oaXPTcTKNu&LPd27wJrhAI-hHwufex|nc&+N6Tx2J*7DfdqH@aR3tg|U z*ZGXWf!>Jg`R&<&z>JUEVfhHpmj}9Dx?uWe4D|2Jr;9Nid$Z|>Cz(y9XV%7Qu%P9s z7fpGs7GHC0>Y;1h6P_}e-Jx$~XhmO%817T4)v>2UZI|hy_I-Y=2{Kmta-a8lC5%u4 zKh?4a%^TY2e z?kJw`YTXs*koH?Y|Evn%ufeMvL7h_prseKthon=+L7Dn;=$RW76~0-)Jp)bW9gi1G z^nFp8Q02DeOwWUBR+8VQ2Se9Hw-LeX!H3YJ(g11-KeLeJ9w4&PJMNiAo6m#NCv(`J)Hx!GQ9p9dh)CXWJia#MtT|}p6D(pWH0bmF)}NM@=OaxMF+de`x{gsyn=#(*!EPZ&5{xe!252vKpLzt@e6Cid%Y; zlR&_xLOXp#l%3WIUR-3|K1+7VH8GBYV=apap-bsC_~sTuF2t$k)UN&8aKAl zuFt06YVhiqFL;4qP&N0UP0;EzO-lw0>xkbSjP?a{)2AIccc@mB&gMJlZ;r(OfUNq( z%rnHyYr+JXsP1F6YX%eLax<`h_Pn;W)TZ1ab>;q- zU(~zH;o5db6e}cY(46GgiUW1`ra4nJ9(CPG;aY-P6n1sy62(E&Rnr*JONZPolBd z7zu#%pfMx1EpGlVPmSXEE>E3VkDQ`0hscOkJ5K`)K@b_&Wqdc=#ZrR_15&_nLab|q zGHD-Y#<$V_wq{^btqL{FxJWr27WdxlQ}qh4;zLaqGPplA6WiPY=LDP4XF@-|`{u1U z>!cbwK~ajD)+hUzxcPdaD#k~MU#lYn=9GeRHCV-&UUdY*$12sfIH%+n6k&u3i9Vle z>9Ofc7(Aw<6zL(qT!}XKLY!2y+MQASfV)>gL3GqACHZg@iW3(cL(|n4C)mrF#Ix!j zk8)A%p$s;2N`YQ;nA8Ybz%`N0Z;PEsz}v0XfF6AxnfoBr zZy&4ZOB|g|!||!J-GebEb~dMwnOXGVw31i+i=exyVIvb|>J4OGWw^0A7Lk+ay zHn==W&em`rfH5zCUl3x7%cEoO2me#V<t?16jD0i#7a@ymt3MH!qxUeSSQh^i9@))Ab%)#W%K# zg6lZJs@62sE>@XUshn1ix8p%xi85NaB|xT+q(;QbY@du^~u_}SorPRwzzjQ|0vYr_@n`_-Jt znzO8!8On0j=ihBftWqrl?Ibxf)12Cw_(P8+gQrY=s+E>P8#v2+Rj3|3bxMB31qgc) zCrwHorLEpweMH-QF0SJ={ve}if7~cjAnfNb?GH&~JDmEjzlRxiLkNdSM5Ju{Lb-=2 znxwpc-JcmI`?d0loA6`Yl{DdJ#Bw9Qu_lxhl&lque&Z=OC(=&g$l@M;_*dTKkE3sw zT|ccu_R%kI_<6?PUc4@Tvx7+$2mSI<%q1kLp)Ne`hTbs#^mKB8zqUB8)f3dZ{n7j5 z@*5LQw{lY{On=I?-vpJD`JF8gy5FlD4F)shqUoQGgvF{^rn5GIF7vYO=Ud!*o0VB6 zZ*_KF^C#37<>|K6;u5Ub6Xy^nFkx;r7|cI@Trirwu+No8p=qu0T3qgQd`ib$BT)S0 zBzeTP$)Ja>8E^fRKdiVkyVTENv$@IO%F7EE?KOJlUR92#?3Vib{kGnSQ{1mM^SuA;wXzlbLd<>@l!5XwZE>>g^iQH*xSz$`M#(K`fkH4ZaJs)R!-nla=TK0ACcZdF z< zb4h9Unw-Y3sXDD6Vaq@@#7RNf|8mC zm}8EVRze9@3N^Z=<>PE(yUTCa`BvUppDT0Du$Zl9`6dK%2CBBSJ#YESoq9t2-8hb7 z4gHFe13k|Jfz5#8C)XScAAx#3on{9MLCYJlx^KH2v|Eb83}y|>nLW3SqX}4GR-~0T zXWlVt)(CLIXL#$mMa{Ibavc}ttnlSB`@x9O0Z8L&tzli5@w|Wk)ai3h=-2r3pBGHr zbz$#zIk~+GV~3RQKe|F@&xuO~Xvyrb5kz&FRo7J%Pv<-%e1#0F>B>7;+2?Z$=cg}J zNi?JE{S?bn5nZu~a%qZQWm}mgU3u{6>QnZ)gwFM_`t`K&HNW09l0#j4SO>3*rzYeWcu$Ho9i8TUhVJ-}pcw<-DduXs8aCZOD9l6+?k%ls}a+_IpdJzFu^hD3@jD zmmm#=FNz3?hH#cPTIYOstr3jJ-Hoia%?Ys$b|$3>E2hs~d*t)t1orB*2IP7jrdbhM zdF<4tqfhe7l$AWxy5l~*kb2}+3YQjIL~&rAn#%{;-6O~&DUh+U!#Jg=AF2y1{%w`E uNlM!LE2y6twpq_*P literal 0 HcmV?d00001 From 84d5b984933e3b443e8f8993920e21e6284636c9 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:30:27 +0300 Subject: [PATCH 098/111] Add a few Readme comments (#53) * Add a few Readme comments * Update README.md Co-authored-by: telome <130504305+telome@users.noreply.github.com> --------- Co-authored-by: telome <130504305+telome@users.noreply.github.com> --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 70983e55..5913b1e0 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,9 @@ From ① and ② we get the requirement on `mat`: For the mentioned examples of `chop` and `fee` we get: `mat > 1.13 / 0.85 ~= 133%` +Note that in practice the `mat` value is expected to be significantly larger and have buffers over this rough calculation. +It should take into account market fluctuations and protocol safety, especially considering that the governance token is used as collateral. + **Trusted Farms and Reward Tokens** It is assumed that the farm owner is trusted, the reward token implementation is non-malicious, and that the reward token minter/s are not malicious. Therefore, theoretic attacks, in which for example the reward rate is inflated to a point where the farm mechanics block liquidations, are assumed non-feasible. @@ -223,4 +226,5 @@ Up to date implementation: https://github.com/makerdao/endgame-toolkit/commit/1a * In many of the modules, such as the splitter and the flappers, NST can replace DAI. This will usually require a deployment of the contract with NstJoin as a replacement of the DaiJoin address. * The LSE assumes that the ESM threshold is set large enough prior to its deployment, so Emergency Shutdown can never be called. * Freeing very small amounts could bypass the exit fees (due to the rounding down) but since the LSE is meant to only be deployed on Ethereum, this is assumed to not be economically viable. +* As opposed to other collateral types, if a user notices an upcoming governance action that can hurt their position (or that they just don't like), they can not exit their position without losing the exit fee. * It is assumed that MKR to/from NGT conversions are not blocked. From 39dbea91e47911ddbf1122d5edd8c4a99934b059 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:12:48 +0300 Subject: [PATCH 099/111] Remove up to date implementation links (#54) --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 5913b1e0..51201483 100644 --- a/README.md +++ b/README.md @@ -180,8 +180,6 @@ Since the VoteDelegate code is being modified (as described above), the factory Note that it is important for the LSE to only allow using VoteDelegate contracts from the factory, so it can be made sure that liquidations can not be blocked. -Up to date implementation: https://github.com/makerdao/vote-delegate/tree/v2/src - ## 4. Keepers Support In general participating in MKR liquidations should be pretty straightforward using the existing on-chain liquidity. However there is a small caveat: @@ -206,8 +204,6 @@ The Splitter implements rate-limiting using a `hop` parameter. * `burn` - The percentage of the `vow.bump` to be moved to the underlying `flapper`. For example, a value of 0.70 \* `WAD` corresponds to a funneling 70% of the DAI to the burn engine. * `hop` - Minimal time between kicks. -Up to date implementation: https://github.com/makerdao/dss-flappers/commit/c946c39ec94bff29c6a118cd702ffaa0f23f3d4a``` - ## 6. StakingRewards The LSE uses a Maker modified [version](https://github.com/makerdao/endgame-toolkit/blob/master/README.md#stakingrewards) of the Synthetix Staking Reward as the farm for distributing NST to stakers. @@ -220,8 +216,6 @@ The StakingRewards contract `setRewardsDuration` function was modified to enable * `rewardsDistribution` - The address which is allowed to start a rewards distribution. Will be set to the splitter. * `rewardsDuration` - The amount of seconds each distribution should take. -Up to date implementation: https://github.com/makerdao/endgame-toolkit/commit/1a857ee888d859b3b08e52ee12f721d1f3ce80c6 - ## General Notes * In many of the modules, such as the splitter and the flappers, NST can replace DAI. This will usually require a deployment of the contract with NstJoin as a replacement of the DaiJoin address. * The LSE assumes that the ESM threshold is set large enough prior to its deployment, so Emergency Shutdown can never be called. From cbb2671ef707192c6b705ffeb34f5d48fffeda50 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:20:35 +0300 Subject: [PATCH 100/111] Update CS Report (#56) --- ...hainSecurity_MakerDAO_Lockstake_audit.pdf} | Bin 551584 -> 553966 bytes 1 file changed, 0 insertions(+), 0 deletions(-) rename audit/{20240508-ChainSecurity_MakerDAO_Lockstake_audit.pdf => 20240730-ChainSecurity_MakerDAO_Lockstake_audit.pdf} (97%) diff --git a/audit/20240508-ChainSecurity_MakerDAO_Lockstake_audit.pdf b/audit/20240730-ChainSecurity_MakerDAO_Lockstake_audit.pdf similarity index 97% rename from audit/20240508-ChainSecurity_MakerDAO_Lockstake_audit.pdf rename to audit/20240730-ChainSecurity_MakerDAO_Lockstake_audit.pdf index 531ac168c7b0047674a9c1b2743c1f5c68b6b5bf..0c9713b3d9e0a89a334db657ff96447acb00d390 100644 GIT binary patch delta 7636 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%0Dw!5)ZdL)c##Ebe0OOjz) z50zj`4=r}1lcdRHK;K)gtV*NBxWq;6=EMi!$6M~U2c;Dmg5#d6kuZVvPx_Yo=?rOD zW@6yI`BFCN(D{Z++&UL*T(<$AR}{Qq`c)V{tmuA0qhiZ1X*6v4g|UvUL_sJU_~F;C zCbs-~$-L#1EqDCzn`hYT|bw%UgNEGyw$~9+j;90wy^HJ&DHsFnV0}JZ$ z>>E{&tu}1o)GyXc<8kV}_yb$(rChA^YPt5m^0wi(KSzOJx@YR8t}LAQ^O%$$QY`o) zCOI7nzvJ+K@_ISKe|4rX6PM2a4Up-)s7{qRCJ#%B3@byPdIBrlT#k+U%{*!m75c;W^19DLJAlj&F%rOu}-8h-@9e;xfX))n0j zu7r~z=%CKLYeR~I$c{7wb$nO|hD2AfL>TA{;;-wf_XLqTp{lAWioRYNk?>l}pPm9^ zO)ZEFf{zcRsd|Q@+8sd;4MjJDC>V;J#&oPXS&*0p)6&N>-AbDepKs%f>qu@f10OYm ztTt4|4wBGNH7(7s`W-7)ZJx4HkTEpHNkz3o(G5+*ds0x*RMU{r9R6mLI-!n_&1o_n zuUJ7nGmTq@?x=c4P|?7pq{$4Pvr`Qlb~^kfOOLy$1y+m;HccymU`HbB8K%u2cvFp7 zNb5A2o*pI(A2ow|wvvvFtp<_q&<)$*hgYg$8xQS(%v92egi*H>D@iQG#M^t&X5GlY#Xy9&wm3_u+rt-_;x$| zL&xJ_z(Y*bX($-FmX<~htWtyIGIU$vZymmoxDDNA!9&dGKp{WqgNppG0s$FjsdO1X z>4SQ~bc@cUqkuAg%6j-GasXEhvK3{z7K9&VW`G~4hmV74&KSX5!S$?ur|To_ZL#K- zrZ(?A|KXW(T4n!JOA*n|AeI)qsLEQl0--lt{(m v)>i%ikDSCmC{-+Je!2+@B$NN;V?%qat$kBl)2ax1Wil3-Fk#lh*^&PTFbxRP delta 6850 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 From 5d3eea58580fd447e7f97bb1da7ff45a7151391b Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:27:10 +0300 Subject: [PATCH 101/111] Configurable exit fee (#55) --- README.md | 1 + deploy/LockstakeDeploy.sol | 3 +-- deploy/LockstakeInit.sol | 3 ++- src/LockstakeEngine.sol | 18 +++++++++++++----- test/LockstakeEngine-invariants.t.sol | 3 ++- test/LockstakeEngine.t.sol | 11 +++++------ 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 51201483..700582a3 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ The following functions are called from the LockstakeClipper (see below) through * `farms` - Whitelisted set of farms to choose from. * `jug` - The Dai lending rate calculation module. +* `fee` - Exit fee. ## 2. LockstakeClipper diff --git a/deploy/LockstakeDeploy.sol b/deploy/LockstakeDeploy.sol index d8c42a38..bf97dd88 100644 --- a/deploy/LockstakeDeploy.sol +++ b/deploy/LockstakeDeploy.sol @@ -32,14 +32,13 @@ library LockstakeDeploy { address voteDelegateFactory, address nstJoin, bytes32 ilk, - uint256 fee, address mkrNgt, bytes4 calcSig ) internal returns (LockstakeInstance memory lockstakeInstance) { DssInstance memory dss = MCD.loadFromChainlog(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); lockstakeInstance.lsmkr = address(new LockstakeMkr()); - lockstakeInstance.engine = address(new LockstakeEngine(voteDelegateFactory, nstJoin, ilk, mkrNgt, lockstakeInstance.lsmkr, fee)); + lockstakeInstance.engine = address(new LockstakeEngine(voteDelegateFactory, nstJoin, ilk, mkrNgt, lockstakeInstance.lsmkr)); lockstakeInstance.clipper = address(new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), lockstakeInstance.engine)); (bool ok, bytes memory returnV) = dss.chainlog.getAddress("CALC_FAB").call(abi.encodeWithSelector(calcSig, owner)); require(ok); diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index 7e781ad6..e0740e2b 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -36,6 +36,7 @@ interface LockstakeEngineLike { function ngt() external view returns (address); function rely(address) external; function file(bytes32, address) external; + function file(bytes32, uint256) external; function addFarm(address) external; } @@ -150,7 +151,6 @@ library LockstakeInit { require(engine.ilk() == cfg.ilk, "Engine ilk mismatch"); require(engine.mkr() == cfg.mkr, "Engine mkr mismatch"); require(engine.lsmkr() == lockstakeInstance.lsmkr, "Engine lsmkr mismatch"); - require(engine.fee() == cfg.fee, "Engine fee mismatch"); require(engine.mkrNgt() == cfg.mkrNgt, "Engine mkrNgt mismatch"); require(engine.ngt() == cfg.ngt, "Engine ngt mismatch"); require(clipper.ilk() == cfg.ilk, "Clipper ilk mismatch"); @@ -207,6 +207,7 @@ library LockstakeInit { LockstakeMkrLike(lockstakeInstance.lsmkr).rely(address(engine)); engine.file("jug", address(dss.jug)); + engine.file("fee", cfg.fee); for (uint256 i = 0; i < cfg.farms.length; i++) { require(StakingRewardsLike(cfg.farms[i]).stakingToken() == lockstakeInstance.lsmkr, "Farm staking token mismatch"); engine.addFarm(cfg.farms[i]); diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index 4bfb4f14..bfd5b087 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -75,6 +75,7 @@ contract LockstakeEngine is Multicall { mapping(address urn => address farm) public urnFarms; mapping(address urn => uint256 auctionsCount) public urnAuctions; JugLike public jug; + uint256 public fee; // --- constants and enums --- @@ -92,7 +93,6 @@ contract LockstakeEngine is Multicall { bytes32 immutable public ilk; GemLike immutable public mkr; GemLike immutable public lsmkr; - uint256 immutable public fee; MkrNgtLike immutable public mkrNgt; GemLike immutable public ngt; uint256 immutable public mkrNgtRate; @@ -103,6 +103,7 @@ contract LockstakeEngine is Multicall { event Rely(address indexed usr); event Deny(address indexed usr); event File(bytes32 indexed what, address data); + event File(bytes32 indexed what, uint256 data); event AddFarm(address farm); event DelFarm(address farm); event Open(address indexed owner, uint256 indexed index, address urn); @@ -136,8 +137,7 @@ contract LockstakeEngine is Multicall { // --- constructor --- - constructor(address voteDelegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address lsmkr_, uint256 fee_) { - require(fee_ < WAD, "LockstakeEngine/fee-equal-or-greater-wad"); + constructor(address voteDelegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address lsmkr_) { voteDelegateFactory = VoteDelegateFactoryLike(voteDelegateFactory_); nstJoin = NstJoinLike(nstJoin_); vat = nstJoin.vat(); @@ -148,7 +148,6 @@ contract LockstakeEngine is Multicall { ngt = mkrNgt.ngt(); mkrNgtRate = mkrNgt.rate(); lsmkr = GemLike(lsmkr_); - fee = fee_; urnImplementation = address(new LockstakeUrn(address(vat), lsmkr_)); vat.hope(nstJoin_); nst.approve(nstJoin_, type(uint256).max); @@ -206,6 +205,14 @@ contract LockstakeEngine is Multicall { emit File(what, data); } + function file(bytes32 what, uint256 data) external auth { + if (what == "fee") { + require(data < WAD, "LockstakeEngine/fee-equal-or-greater-wad"); + fee = data; + } else revert("LockstakeEngine/file-unrecognized-param"); + emit File(what, data); + } + function addFarm(address farm) external auth { farms[farm] = FarmStatus.ACTIVE; emit AddFarm(farm); @@ -439,7 +446,8 @@ contract LockstakeEngine is Multicall { uint256 burn; uint256 refund; if (left > 0) { - burn = _min(sold * fee / (WAD - fee), left); + uint256 fee_ = fee; + burn = _min(sold * fee_ / (WAD - fee_), left); mkr.burn(address(this), burn); unchecked { refund = left - burn; } if (refund > 0) { diff --git a/test/LockstakeEngine-invariants.t.sol b/test/LockstakeEngine-invariants.t.sol index 997c67f5..1e45eb91 100644 --- a/test/LockstakeEngine-invariants.t.sol +++ b/test/LockstakeEngine-invariants.t.sol @@ -125,8 +125,9 @@ contract LockstakeEngineIntegrationTest is DssTest { vm.prank(voter1); voterDelegate1 = delFactory.create(); vm.startPrank(pauseProxy); - engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(mkrNgt), address(lsmkr), 15 * WAD / 100); + engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(mkrNgt), address(lsmkr)); engine.file("jug", jug); + engine.file("fee", 15 * WAD / 100); vat.rely(address(engine)); vat.init(ilk); JugLike(jug).init(ilk); diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 83fa75df..db6364aa 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -114,7 +114,6 @@ contract LockstakeEngineTest is DssTest { address(voteDelegateFactory), address(nstJoin), ilk, - 15 * WAD / 100, address(mkrNgt), bytes4(abi.encodeWithSignature("newLinearDecrease(address)")) ); @@ -324,7 +323,6 @@ contract LockstakeEngineTest is DssTest { address(voteDelegateFactory), address(nstJoin), "eee", - 15 * WAD / 100, address(mkrNgt), bytes4(abi.encodeWithSignature("newStairstepExponentialDecrease(address)")) ); @@ -343,11 +341,9 @@ contract LockstakeEngineTest is DssTest { function testConstructor() public { address lsmkr2 = address(new GemMock(0)); - vm.expectRevert("LockstakeEngine/fee-equal-or-greater-wad"); - new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), lsmkr2, WAD); vm.expectEmit(true, true, true, true); emit Rely(address(this)); - LockstakeEngine e = new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), lsmkr2, 100); + LockstakeEngine e = new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), lsmkr2); assertEq(address(e.voteDelegateFactory()), address(voteDelegateFactory)); assertEq(address(e.nstJoin()), address(nstJoin)); assertEq(address(e.vat()), address(dss.vat)); @@ -355,7 +351,6 @@ contract LockstakeEngineTest is DssTest { assertEq(e.ilk(), "aaa"); assertEq(address(e.mkr()), address(mkr)); assertEq(address(e.lsmkr()), lsmkr2); - assertEq(e.fee(), 100); assertEq(address(e.mkrNgt()), address(mkrNgt)); assertEq(address(e.ngt()), address(ngt)); assertEq(e.mkrNgtRate(), 24_000); @@ -375,6 +370,10 @@ contract LockstakeEngineTest is DssTest { function testFile() public { checkFileAddress(address(engine), "LockstakeEngine", ["jug"]); + checkFileUint(address(engine), "LockstakeEngine", ["fee"]); + + vm.expectRevert("LockstakeEngine/fee-equal-or-greater-wad"); + vm.prank(pauseProxy); engine.file("fee", WAD); } function testModifiers() public { From 8b16eac9a8b1bc42a720a140918c4f80d52d52c1 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Thu, 22 Aug 2024 07:48:49 -0300 Subject: [PATCH 102/111] Post contest updates (#57) * Use owner/index as key for user functions * Fix in README * Renaming * Change to isUrnAuth * Minor tests change * Use grab instead of frob in onRemove * Use jug.drip to update rate in selectVoteDelegate --- README.md | 38 +-- src/LockstakeEngine.sol | 160 ++++++------ test/LockstakeEngine-invariants.t.sol | 3 +- test/LockstakeEngine.t.sol | 348 ++++++++++++++------------ test/handlers/LockstakeHandler.sol | 29 ++- 5 files changed, 315 insertions(+), 263 deletions(-) diff --git a/README.md b/README.md index 700582a3..02781beb 100644 --- a/README.md +++ b/README.md @@ -27,19 +27,19 @@ There is also support for locking and freeing NGT instead of MKR. **User Functions:** * `open(uint256 index)` - Create a new `urn` for the sender. The `index` parameter specifies how many `urn`s have been created so far by the user (should be 0 for the first call). It is used to avoid race conditions. -* `hope(address urn, address usr)` - Allow `usr` to also manage the sender's controlled `urn`. -* `nope(address urn, address usr)` - Disallow `usr` from managing the sender's controlled `urn`. -* `lock(address urn, uint256 wad, uint16 ref)` - Deposit `wad` amount of MKR into the `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. -* `lockNgt(address urn, uint256 ngtWad, uint16 ref)` - Deposit `ngtWad` amount of NGT. The NGT is first converted to MKR, which then gets deposited into the `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. -* `free(address urn, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `urn` to the `to` address (which will receive it minus the exit fee). This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. -* `freeNgt(address urn, address to, uint256 ngtWad)` - Withdraw `ngtWad - ngtWad % mkrNgtRate` amount of NGT to the `to` address. In practice, a proportional amount of MKR is first freed from the `urn` (minus the exit fee), then gets converted to NGT and sent out. This will undelegate the MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. Note that freeing NGT is possible even if the position was previously entered via regular locking (using MKR), and vice-vera. -* `freeNoFee(address urn, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `urn` to the `to` address without paying any fee. This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. This function can only be called by an address which was both authorized on the contract by governance and for which the urn owner has called `hope`. It is useful for implementing a migration contract that will move the funds to another engine contract (if ever needed). -* `selectVoteDelegate(address urn, address voteDelegate)` - Choose which delegate contract to delegate the `urn`'s entire MKR amount to. In case it is `address(0)` the MKR will stay (or become) undelegated. -* `selectFarm(address urn, address farm, uint16 ref)` - Select which farm (from the whitelisted ones) to stake the `urn`'s MKR to (along with the `ref` code). In case it is `address(0)` the MKR will stay (or become) unstaked. -* `draw(address urn, address to, uint256 wad)` - Generate `wad` amount of NST using the `urn`’s MKR as collateral and send it to the `to` address. -* `wipe(address urn, uint256 wad)` - Repay `wad` amount of NST backed by the `urn`’s MKR. -* `wipeAll(address urn)` - Repay the amount of NST that is needed to wipe the `urn`s entire debt. -* `getReward(address urn, address farm, address to)` - Claim the reward generated from a farm on behalf of the `urn` and send it to the specified `to` address. +* `hope(address owner, uint256 index, address usr)` - Allow `usr` to also manage the `owner-index` `urn`. +* `nope(address owner, uint256 index, address usr)` - Disallow `usr` from managing the `owner-index` `urn`. +* `lock(address owner, uint256 index, uint256 wad, uint16 ref)` - Deposit `wad` amount of MKR into the `owner-index` `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. +* `lockNgt(address owner, uint256 index, uint256 ngtWad, uint16 ref)` - Deposit `ngtWad` amount of NGT. The NGT is first converted to MKR, which then gets deposited into the `owner-index` `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. +* `free(address owner, uint256 index, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `owner-index` `urn` to the `to` address (which will receive it minus the exit fee). This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. +* `freeNgt(address owner, uint256 index, address to, uint256 ngtWad)` - Withdraw `ngtWad - ngtWad % mkrNgtRate` amount of NGT to the `to` address. In practice, a proportional amount of MKR is first freed from the `owner-index` `urn` (minus the exit fee), then gets converted to NGT and sent out. This will undelegate the MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. Note that freeing NGT is possible even if the position was previously entered via regular locking (using MKR), and vice-vera. +* `freeNoFee(address owner, uint256 index, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `owner-index` `urn` to the `to` address without paying any fee. This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. This function can only be called by an address which was both authorized on the contract by governance and for which the urn owner has called `hope`. It is useful for implementing a migration contract that will move the funds to another engine contract (if ever needed). +* `selectVoteDelegate(address owner, uint256 index, address voteDelegate)` - Choose which delegate contract to delegate the `owner-index` `urn`'s entire MKR amount to. In case it is `address(0)` the MKR will stay (or become) undelegated. +* `selectFarm(address owner, uint256 index, address farm, uint16 ref)` - Select which farm (from the whitelisted ones) to stake the `owner-index` `urn`'s MKR to (along with the `ref` code). In case it is `address(0)` the MKR will stay (or become) unstaked. +* `draw(address owner, uint256 index, address to, uint256 wad)` - Generate `wad` amount of NST using the `owner-index` `urn`’s MKR as collateral and send it to the `to` address. +* `wipe(address owner, uint256 index, uint256 wad)` - Repay `wad` amount of NST backed by the `owner-index` `urn`’s MKR. +* `wipeAll(address owner, uint256 index)` - Repay the amount of NST that is needed to wipe the `owner-index` `urn`’s entire debt. +* `getReward(address owner, uint256 index, address farm, address to)` - Claim the reward generated from a farm on behalf of the `owner-index` `urn` and send it to the specified `to` address. * `multicall(bytes[] calldata data)` - Batch multiple methods in a single call to the contract. **Sequence Diagram:** @@ -61,18 +61,18 @@ sequenceDiagram engine-->>urn0: (creation) engine-->>user: return `urn0` address - user->>engine: lock(`urn0`, 10, 0) + user->>engine: lock(`user`, 0, 10, 0) engine-->>vat: vat.frob(ilk, `urn0`, `urn0`, address(0), 10, 0) // lock collateral - user->>engine: selectVoteDelegate(`urn0`, `delegate0`) + user->>engine: selectVoteDelegate(`user`, 0, `delegate0`) engine-->>delegate0: lock(10) - user->>engine: selectFarm(`urn0`, `farm0`, `ref`) + user->>engine: selectFarm(`user`, 0, `farm0`, `ref`) engine-->>urn0: stake(`farm0`, 10, `ref`) urn0-->>farm0: stake(10, `ref`); - user->>engine: draw(`urn0`, `user`, 1000) + user->>engine: draw(`user`, 0, `user`, 1000) engine-->>vat: vat.frob(ilk, `urn0`, address(0), address(this), 0, 1000) // borrow ``` @@ -80,11 +80,11 @@ sequenceDiagram LockstakeEngine implements a function, which allows batching several function calls. -For example, a typical flow for a user (or an app/front-end) would be to first query `index=usrAmts(usr)` and `urn=getUrn(usr, index)` off-chain to retrieve the expected `index` and `urn` address, then use these to perform a multicall sequence that includes `open`, `selectFarm`, `lock` and `stake`. +For example, a typical flow for a user (or an app/front-end) would be to first query `index=ownerUrnsCount(usr)` off-chain to retrieve the expected `index`, then use it to perform a multicall sequence that includes `open`, `selectFarm`, `lock` and `stake`. This way, locking and farm-staking can be achieved in only 2 transactions (including the token approval). -Note that since the `index` is first fetched off-chain and there is no support for passing return values between batched calls, there could be race conditions for calling `open`. For example, `open` can be called twice by the user (e.g. in two different contexts) with the second `usrAmts` query happening before the first `open` call has been confirmed. This would lead to both calls using the same `urn` for `selectFarm`, `lock` and `stake`. +Note that since the `index` is first fetched off-chain and there is no support for passing return values between batched calls, there could be race conditions for calling `open`. For example, `open` can be called twice by the user (e.g. in two different contexts) with the second `ownerUrnsCount` query happening before the first `open` call has been confirmed. This would lead to both calls using the same `urn` for `selectFarm`, `lock` and `stake`. To mitigate this, the `index` parameter for `open` is used to make sure the multicall transaction creates the intended `urn`. diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index bfd5b087..f5054cc4 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -34,6 +34,7 @@ interface VatLike { function hope(address) external; function slip(bytes32, address, int256) external; function frob(bytes32, address, address, address, int256, int256) external; + function grab(bytes32, address, address, address, int256, int256) external; } interface NstJoinLike { @@ -66,16 +67,17 @@ interface MkrNgtLike { contract LockstakeEngine is Multicall { // --- storage variables --- - mapping(address usr => uint256 allowed) public wards; - mapping(address farm => FarmStatus) public farms; - mapping(address usr => uint256 urnsCount) public usrAmts; - mapping(address urn => address owner) public urnOwners; - mapping(address urn => mapping(address usr => uint256 allowed)) public urnCan; - mapping(address urn => address voteDelegate) public urnVoteDelegates; - mapping(address urn => address farm) public urnFarms; - mapping(address urn => uint256 auctionsCount) public urnAuctions; - JugLike public jug; - uint256 public fee; + mapping(address usr => uint256 allowed) public wards; + mapping(address farm => FarmStatus) public farms; + mapping(address owner => uint256 count) public ownerUrnsCount; + mapping(address owner => mapping(uint256 index => address urn)) public ownerUrns; + mapping(address urn => address owner) public urnOwners; + mapping(address urn => mapping(address usr => uint256 allowed)) public urnCan; + mapping(address urn => address voteDelegate) public urnVoteDelegates; + mapping(address urn => address farm) public urnFarms; + mapping(address urn => uint256 auctionsCount) public urnAuctions; + JugLike public jug; + uint256 public fee; // --- constants and enums --- @@ -107,18 +109,18 @@ contract LockstakeEngine is Multicall { event AddFarm(address farm); event DelFarm(address farm); event Open(address indexed owner, uint256 indexed index, address urn); - event Hope(address indexed urn, address indexed usr); - event Nope(address indexed urn, address indexed usr); - event SelectVoteDelegate(address indexed urn, address indexed voteDelegate); - event SelectFarm(address indexed urn, address farm, uint16 ref); - event Lock(address indexed urn, uint256 wad, uint16 ref); - event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); - event Free(address indexed urn, address indexed to, uint256 wad, uint256 freed); - event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 ngtFreed); - event FreeNoFee(address indexed urn, address indexed to, uint256 wad); - event Draw(address indexed urn, address indexed to, uint256 wad); - event Wipe(address indexed urn, uint256 wad); - event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); + event Hope(address indexed owner, uint256 indexed index, address indexed usr); + event Nope(address indexed owner, uint256 indexed index, address indexed usr); + event SelectVoteDelegate(address indexed owner, uint256 indexed index, address indexed voteDelegate); + event SelectFarm(address indexed owner, uint256 indexed index, address indexed farm, uint16 ref); + event Lock(address indexed owner, uint256 indexed index, uint256 wad, uint16 ref); + event LockNgt(address indexed owner, uint256 indexed index, uint256 ngtWad, uint16 ref); + event Free(address indexed owner, uint256 indexed index, address to, uint256 wad, uint256 freed); + event FreeNgt(address indexed owner, uint256 indexed index, address to, uint256 ngtWad, uint256 ngtFreed); + event FreeNoFee(address indexed owner, uint256 indexed index, address to, uint256 wad); + event Draw(address indexed owner, uint256 indexed index, address to, uint256 wad); + event Wipe(address indexed owner, uint256 indexed index, uint256 wad); + event GetReward(address indexed owner, uint256 indexed index, address indexed farm, address to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); event OnRemove(address indexed urn, uint256 sold, uint256 burn, uint256 refund); @@ -130,11 +132,6 @@ contract LockstakeEngine is Multicall { _; } - modifier urnAuth(address urn) { - require(_urnAuth(urn, msg.sender), "LockstakeEngine/urn-not-authorized"); - _; - } - // --- constructor --- constructor(address voteDelegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address lsmkr_) { @@ -171,8 +168,18 @@ contract LockstakeEngine is Multicall { } } - function _urnAuth(address urn, address usr) internal view returns (bool ok) { - ok = urnOwners[urn] == usr || urnCan[urn][usr] == 1; + function _urnAuth(address owner, address urn, address usr) internal view returns (bool ok) { + ok = owner == usr || urnCan[urn][usr] == 1; + } + + function _getUrn(address owner, uint256 index) internal view returns (address urn) { + urn = ownerUrns[owner][index]; + require(urn != address(0), "LockstakeEngine/invalid-urn"); + } + + function _getAuthedUrn(address owner, uint256 index) internal view returns (address urn) { + urn = _getUrn(owner, index); + require(_urnAuth(owner, urn, msg.sender), "LockstakeEngine/urn-not-authorized"); } // See the reference implementation in https://eips.ethereum.org/EIPS/eip-1167 @@ -225,57 +232,49 @@ contract LockstakeEngine is Multicall { // --- getters --- - // NOTE: this function will succeed returning the address even if the urn for the specified index hasn't been created yet - function getUrn(address owner, uint256 index) external view returns (address urn) { - uint256 salt = uint256(keccak256(abi.encode(owner, index))); - bytes32 codeHash = keccak256(abi.encodePacked(_initCode())); - urn = address(uint160(uint256( - keccak256( - abi.encodePacked(bytes1(0xff), address(this), salt, codeHash) - ) - ))); - } - - function isUrnAuth(address urn, address usr) external view returns (bool ok) { - ok = _urnAuth(urn, usr); + function isUrnAuth(address owner, uint256 index, address usr) external view returns (bool ok) { + ok = _urnAuth(owner, _getUrn(owner, index), usr); } // --- urn management functions --- function open(uint256 index) external returns (address urn) { - require(index == usrAmts[msg.sender]++, "LockstakeEngine/wrong-urn-index"); - uint256 salt = uint256(keccak256(abi.encode(msg.sender, index))); + require(index == ownerUrnsCount[msg.sender]++, "LockstakeEngine/wrong-urn-index"); bytes memory initCode = _initCode(); - assembly { urn := create2(0, add(initCode, 0x20), 0x37, salt) } - LockstakeUrn(urn).init(); // would revert if create2 had failed + assembly { urn := create(0, add(initCode, 0x20), 0x37) } + LockstakeUrn(urn).init(); // would revert if create had failed + ownerUrns[msg.sender][index] = urn; urnOwners[urn] = msg.sender; emit Open(msg.sender, index, urn); } - function hope(address urn, address usr) external urnAuth(urn) { + function hope(address owner, uint256 index, address usr) external { + address urn = _getAuthedUrn(owner, index); urnCan[urn][usr] = 1; - emit Hope(urn, usr); + emit Hope(owner, index, usr); } - function nope(address urn, address usr) external urnAuth(urn) { + function nope(address owner, uint256 index, address usr) external { + address urn = _getAuthedUrn(owner, index); urnCan[urn][usr] = 0; - emit Nope(urn, usr); + emit Nope(owner, index, usr); } // --- delegation/staking functions --- - function selectVoteDelegate(address urn, address voteDelegate) external urnAuth(urn) { + function selectVoteDelegate(address owner, uint256 index, address voteDelegate) external { + address urn = _getAuthedUrn(owner, index); require(urnAuctions[urn] == 0, "LockstakeEngine/urn-in-auction"); require(voteDelegate == address(0) || voteDelegateFactory.created(voteDelegate) == 1, "LockstakeEngine/not-valid-vote-delegate"); address prevVoteDelegate = urnVoteDelegates[urn]; require(prevVoteDelegate != voteDelegate, "LockstakeEngine/same-vote-delegate"); (uint256 ink, uint256 art) = vat.urns(ilk, urn); if (art > 0 && voteDelegate != address(0)) { - (, uint256 rate, uint256 spot,,) = vat.ilks(ilk); - require(ink * spot >= art * rate, "LockstakeEngine/urn-unsafe"); + (,, uint256 spot,,) = vat.ilks(ilk); + require(ink * spot >= art * jug.drip(ilk), "LockstakeEngine/urn-unsafe"); } _selectVoteDelegate(urn, ink, prevVoteDelegate, voteDelegate); - emit SelectVoteDelegate(urn, voteDelegate); + emit SelectVoteDelegate(owner, index, voteDelegate); } function _selectVoteDelegate(address urn, uint256 wad, address prevVoteDelegate, address voteDelegate) internal { @@ -291,14 +290,15 @@ contract LockstakeEngine is Multicall { urnVoteDelegates[urn] = voteDelegate; } - function selectFarm(address urn, address farm, uint16 ref) external urnAuth(urn) { + function selectFarm(address owner, uint256 index, address farm, uint16 ref) external { + address urn = _getAuthedUrn(owner, index); require(urnAuctions[urn] == 0, "LockstakeEngine/urn-in-auction"); require(farm == address(0) || farms[farm] == FarmStatus.ACTIVE, "LockstakeEngine/farm-unsupported-or-deleted"); address prevFarm = urnFarms[urn]; require(prevFarm != farm, "LockstakeEngine/same-farm"); (uint256 ink,) = vat.urns(ilk, urn); _selectFarm(urn, ink, prevFarm, farm, ref); - emit SelectFarm(urn, farm, ref); + emit SelectFarm(owner, index, farm, ref); } function _selectFarm(address urn, uint256 wad, address prevFarm, address farm, uint16 ref) internal { @@ -313,21 +313,22 @@ contract LockstakeEngine is Multicall { urnFarms[urn] = farm; } - function lock(address urn, uint256 wad, uint16 ref) external { + function lock(address owner, uint256 index, uint256 wad, uint16 ref) external { + address urn = _getUrn(owner, index); mkr.transferFrom(msg.sender, address(this), wad); _lock(urn, wad, ref); - emit Lock(urn, wad, ref); + emit Lock(owner, index, wad, ref); } - function lockNgt(address urn, uint256 ngtWad, uint16 ref) external { + function lockNgt(address owner, uint256 index, uint256 ngtWad, uint16 ref) external { + address urn = _getUrn(owner, index); ngt.transferFrom(msg.sender, address(this), ngtWad); mkrNgt.ngtToMkr(address(this), ngtWad); _lock(urn, ngtWad / mkrNgtRate, ref); - emit LockNgt(urn, ngtWad, ref); + emit LockNgt(owner, index, ngtWad, ref); } function _lock(address urn, uint256 wad, uint16 ref) internal { - require(urnOwners[urn] != address(0), "LockstakeEngine/invalid-urn"); require(wad <= uint256(type(int256).max), "LockstakeEngine/overflow"); address voteDelegate = urnVoteDelegates[urn]; if (voteDelegate != address(0)) { @@ -344,24 +345,27 @@ contract LockstakeEngine is Multicall { } } - function free(address urn, address to, uint256 wad) external urnAuth(urn) returns (uint256 freed) { + function free(address owner, uint256 index, address to, uint256 wad) external returns (uint256 freed) { + address urn = _getAuthedUrn(owner, index); freed = _free(urn, wad, fee); mkr.transfer(to, freed); - emit Free(urn, to, wad, freed); + emit Free(owner, index, to, wad, freed); } - function freeNgt(address urn, address to, uint256 ngtWad) external urnAuth(urn) returns (uint256 ngtFreed) { + function freeNgt(address owner, uint256 index, address to, uint256 ngtWad) external returns (uint256 ngtFreed) { + address urn = _getAuthedUrn(owner, index); uint256 wad = ngtWad / mkrNgtRate; uint256 freed = _free(urn, wad, fee); ngtFreed = freed * mkrNgtRate; mkrNgt.mkrToNgt(to, freed); - emit FreeNgt(urn, to, ngtWad, ngtFreed); + emit FreeNgt(owner, index, to, ngtWad, ngtFreed); } - function freeNoFee(address urn, address to, uint256 wad) external auth urnAuth(urn) { + function freeNoFee(address owner, uint256 index, address to, uint256 wad) external auth { + address urn = _getAuthedUrn(owner, index); _free(urn, wad, 0); mkr.transfer(to, wad); - emit FreeNoFee(urn, to, wad); + emit FreeNoFee(owner, index, to, wad); } function _free(address urn, uint256 wad, uint256 fee_) internal returns (uint256 freed) { @@ -386,26 +390,29 @@ contract LockstakeEngine is Multicall { // --- loan functions --- - function draw(address urn, address to, uint256 wad) external urnAuth(urn) { + function draw(address owner, uint256 index, address to, uint256 wad) external { + address urn = _getAuthedUrn(owner, index); uint256 rate = jug.drip(ilk); uint256 dart = _divup(wad * RAY, rate); require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); vat.frob(ilk, urn, address(0), address(this), 0, int256(dart)); nstJoin.exit(to, wad); - emit Draw(urn, to, wad); + emit Draw(owner, index, to, wad); } - function wipe(address urn, uint256 wad) external { + function wipe(address owner, uint256 index, uint256 wad) external { + address urn = _getUrn(owner, index); nst.transferFrom(msg.sender, address(this), wad); nstJoin.join(address(this), wad); (, uint256 rate,,,) = vat.ilks(ilk); uint256 dart = wad * RAY / rate; require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); vat.frob(ilk, urn, address(0), address(this), 0, -int256(dart)); - emit Wipe(urn, wad); + emit Wipe(owner, index, wad); } - function wipeAll(address urn) external returns (uint256 wad) { + function wipeAll(address owner, uint256 index) external returns (uint256 wad) { + address urn = _getUrn(owner, index); (, uint256 art) = vat.urns(ilk, urn); require(art <= uint256(type(int256).max), "LockstakeEngine/overflow"); (, uint256 rate,,,) = vat.ilks(ilk); @@ -413,15 +420,16 @@ contract LockstakeEngine is Multicall { nst.transferFrom(msg.sender, address(this), wad); nstJoin.join(address(this), wad); vat.frob(ilk, urn, address(0), address(this), 0, -int256(art)); - emit Wipe(urn, wad); + emit Wipe(owner, index, wad); } // --- staking rewards function --- - function getReward(address urn, address farm, address to) external urnAuth(urn) returns (uint256 amt) { + function getReward(address owner, uint256 index, address farm, address to) external returns (uint256 amt) { + address urn = _getAuthedUrn(owner, index); require(farms[farm] > FarmStatus.UNSUPPORTED, "LockstakeEngine/farm-unsupported"); amt = LockstakeUrn(urn).getReward(farm, to); - emit GetReward(urn, farm, to, amt); + emit GetReward(owner, index, farm, to, amt); } // --- liquidation callback functions --- @@ -454,7 +462,7 @@ contract LockstakeEngine is Multicall { // The following is ensured by the dog and clip but we still prefer to be explicit require(refund <= uint256(type(int256).max), "LockstakeEngine/overflow"); vat.slip(ilk, urn, int256(refund)); - vat.frob(ilk, urn, urn, address(0), int256(refund), 0); + vat.grab(ilk, urn, urn, address(0), int256(refund), 0); lsmkr.mint(urn, refund); } } diff --git a/test/LockstakeEngine-invariants.t.sol b/test/LockstakeEngine-invariants.t.sol index 1e45eb91..4466cf45 100644 --- a/test/LockstakeEngine-invariants.t.sol +++ b/test/LockstakeEngine-invariants.t.sol @@ -186,7 +186,8 @@ contract LockstakeEngineIntegrationTest is DssTest { handler = new LockstakeHandler( vm, address(engine), - urn, + address(this), + 0, address(spot), address(dog), pauseProxy, diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index db6364aa..97e007c1 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -61,18 +61,18 @@ contract LockstakeEngineTest is DssTest { event AddFarm(address farm); event DelFarm(address farm); event Open(address indexed owner, uint256 indexed index, address urn); - event Hope(address indexed urn, address indexed usr); - event Nope(address indexed urn, address indexed usr); - event SelectVoteDelegate(address indexed urn, address indexed voteDelegate_); - event SelectFarm(address indexed urn, address farm, uint16 ref); - event Lock(address indexed urn, uint256 wad, uint16 ref); - event LockNgt(address indexed urn, uint256 ngtWad, uint16 ref); - event Free(address indexed urn, address indexed to, uint256 wad, uint256 freed); - event FreeNgt(address indexed urn, address indexed to, uint256 ngtWad, uint256 ngtFreed); - event FreeNoFee(address indexed urn, address indexed to, uint256 wad); - event Draw(address indexed urn, address indexed to, uint256 wad); - event Wipe(address indexed urn, uint256 wad); - event GetReward(address indexed urn, address indexed farm, address indexed to, uint256 amt); + event Hope(address indexed owner, uint256 indexed index, address indexed usr); + event Nope(address indexed owner, uint256 indexed index, address indexed usr); + event SelectVoteDelegate(address indexed owner, uint256 indexed index, address indexed voteDelegate_); + event SelectFarm(address indexed owner, uint256 indexed index, address indexed farm, uint16 ref); + event Lock(address indexed owner, uint256 indexed index, uint256 wad, uint16 ref); + event LockNgt(address indexed owner, uint256 indexed index, uint256 ngtWad, uint16 ref); + event Free(address indexed owner, uint256 indexed index, address to, uint256 wad, uint256 freed); + event FreeNgt(address indexed owner, uint256 indexed index, address to, uint256 ngtWad, uint256 ngtFreed); + event FreeNoFee(address indexed owner, uint256 indexed index, address to, uint256 wad); + event Draw(address indexed owner, uint256 indexed index, address to, uint256 wad); + event Wipe(address indexed owner, uint256 indexed index, uint256 wad); + event GetReward(address indexed owner, uint256 indexed index, address indexed farm, address to, uint256 amt); event OnKick(address indexed urn, uint256 wad); event OnTake(address indexed urn, address indexed who, uint256 wad); event OnRemove(address indexed urn, uint256 sold, uint256 burn, uint256 refund); @@ -389,30 +389,6 @@ contract LockstakeEngineTest is DssTest { vm.startPrank(address(0xBEEF)); checkModifier(address(engine), "LockstakeEngine/not-authorized", authedMethods); vm.stopPrank(); - - bytes4[] memory urnOwnersMethods = new bytes4[](8); - urnOwnersMethods[0] = engine.hope.selector; - urnOwnersMethods[1] = engine.nope.selector; - urnOwnersMethods[2] = engine.selectVoteDelegate.selector; - urnOwnersMethods[3] = engine.selectFarm.selector; - urnOwnersMethods[4] = engine.free.selector; - urnOwnersMethods[5] = engine.freeNgt.selector; - urnOwnersMethods[6] = engine.draw.selector; - urnOwnersMethods[7] = engine.getReward.selector; - - // this checks the case when sender is not the urn owner and not hoped, the hoped case is checked in testHopeNope and the urn owner case in the specific tests - vm.startPrank(address(0xBEEF)); - checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); - vm.stopPrank(); - - bytes4[] memory authedAndUrnOwnersMethods = new bytes4[](1); - authedAndUrnOwnersMethods[0] = engine.freeNoFee.selector; - - // this checks the case when sender is relied but is not the urn owner and is not hoped, the hoped case is checked in testHopeNope and the urn owner case in the specific tests - vm.prank(pauseProxy); engine.rely(address(0x123)); - vm.startPrank(address(0x123)); - checkModifier(address(engine), "LockstakeEngine/urn-not-authorized", urnOwnersMethods); - vm.stopPrank(); } function testAddDelFarm() public { @@ -428,8 +404,8 @@ contract LockstakeEngineTest is DssTest { } function testOpen() public { - assertEq(engine.usrAmts(address(this)), 0); - address urn = engine.getUrn(address(this), 0); + assertEq(engine.ownerUrnsCount(address(this)), 0); + address urn = vm.computeCreateAddress(address(engine), vm.getNonce(address(engine))); vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(1); @@ -438,7 +414,7 @@ contract LockstakeEngineTest is DssTest { vm.expectEmit(true, true, true, true); emit Open(address(this), 0, urn); assertEq(engine.open(0), urn); - assertEq(engine.usrAmts(address(this)), 1); + assertEq(engine.ownerUrnsCount(address(this)), 1); assertEq(dss.vat.can(urn, address(engine)), 1); assertEq(lsmkr.allowance(urn, address(engine)), type(uint256).max); assertEq(LockstakeUrn(urn).engine(), address(engine)); @@ -450,14 +426,74 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/wrong-urn-index"); engine.open(2); + address urn2 = vm.computeCreateAddress(address(engine), vm.getNonce(address(engine))); vm.expectEmit(true, true, true, true); - emit Open(address(this), 1, engine.getUrn(address(this), 1)); - assertEq(engine.open(1), engine.getUrn(address(this), 1)); - assertEq(engine.usrAmts(address(this)), 2); + emit Open(address(this), 1, urn2); + assertEq(engine.open(1), urn2); + assertEq(engine.ownerUrnsCount(address(this)), 2); + address urn3 = vm.computeCreateAddress(address(engine), vm.getNonce(address(engine))); vm.expectEmit(true, true, true, true); - emit Open(address(this), 2, engine.getUrn(address(this), 2)); - assertEq(engine.open(2), engine.getUrn(address(this), 2)); - assertEq(engine.usrAmts(address(this)), 3); + emit Open(address(this), 2, urn3); + assertEq(engine.open(2), urn3); + assertEq(engine.ownerUrnsCount(address(this)), 3); + } + + function testInvalidUrn() public { + assertEq(engine.ownerUrns(address(this), 0), address(0)); + address urn = engine.open(0); + assertEq(engine.ownerUrns(address(this), 0), urn); + assertEq(engine.ownerUrns(address(this), 1), address(0)); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.isUrnAuth(address(this), 1, address(123)); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.hope(address(this), 1, address(123)); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.nope(address(this), 1, address(123)); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.selectVoteDelegate(address(this), 1, address(123)); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.selectFarm(address(this), 1, address(123), 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.lock(address(this), 1, 1, 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.lockNgt(address(this), 1, 1, 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.free(address(this), 1, address(123), 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.freeNgt(address(this), 1, address(123), 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + vm.prank(pauseProxy); engine.freeNoFee(address(this), 1, address(123), 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.draw(address(this), 1, address(123), 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.wipe(address(this), 1, 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.wipeAll(address(this), 1); + vm.expectRevert("LockstakeEngine/invalid-urn"); + engine.getReward(address(this), 1, address(123), address(456)); + } + + function testUrnNotAuthorized() public { + vm.prank(address(123)); engine.open(0); + + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.hope(address(123), 0, address(this)); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.nope(address(123), 0, address(this)); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.selectVoteDelegate(address(123), 0, address(123)); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.selectFarm(address(123), 0, address(123), 1); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.free(address(123), 0, address(123), 1); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.freeNgt(address(123), 0, address(123), 1); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + vm.prank(pauseProxy); engine.freeNoFee(address(123), 0, address(123), 1); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.draw(address(123), 0, address(123), 1); + vm.expectRevert("LockstakeEngine/urn-not-authorized"); + engine.getReward(address(123), 0, address(123), address(456)); } function testHopeNope() public { @@ -471,75 +507,81 @@ contract LockstakeEngineTest is DssTest { ngt.transfer(urnAuthed, 100_000 * 24_000 * 10**18); vm.startPrank(urnOwner); address urn = engine.open(0); - assertTrue(engine.isUrnAuth(urn, urnOwner)); - assertTrue(!engine.isUrnAuth(urn, urnAuthed)); + assertTrue(engine.isUrnAuth(urnOwner, 0, urnOwner)); + assertTrue(!engine.isUrnAuth(urnOwner, 0, urnAuthed)); assertEq(engine.urnCan(urn, urnAuthed), 0); vm.expectEmit(true, true, true, true); - emit Hope(urn, urnAuthed); - engine.hope(urn, urnAuthed); + emit Hope(urnOwner, 0, urnAuthed); + engine.hope(urnOwner, 0, urnAuthed); assertEq(engine.urnCan(urn, urnAuthed), 1); - assertTrue(engine.isUrnAuth(urn, urnAuthed)); - engine.hope(urn, authedAndUrnAuthed); + assertTrue(engine.isUrnAuth(urnOwner, 0, urnAuthed)); + engine.hope(urnOwner, 0, authedAndUrnAuthed); vm.stopPrank(); vm.startPrank(urnAuthed); vm.expectEmit(true, true, true, true); - emit Hope(urn, address(1111)); - engine.hope(urn, address(1111)); + emit Hope(urnOwner, 0, address(1111)); + engine.hope(urnOwner, 0, address(1111)); mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18, 0); + engine.lock(urnOwner, 0, 100_000 * 10**18, 0); assertEq(_ink(ilk, urn), 100_000 * 10**18); - engine.free(urn, address(this), 50_000 * 10**18); + engine.free(urnOwner, 0, address(this), 50_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); ngt.approve(address(engine), 100_000 * 24_000 * 10**18); - engine.lockNgt(urn, 100_000 * 24_000 * 10**18, 0); + engine.lockNgt(urnOwner, 0, 100_000 * 24_000 * 10**18, 0); assertEq(_ink(ilk, urn), 150_000 * 10**18); - engine.freeNgt(urn, address(this), 50_000 * 24_000 * 10**18); + engine.freeNgt(urnOwner, 0, address(this), 50_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(urnOwner, 0, voteDelegate); assertEq(engine.urnVoteDelegates(urn), voteDelegate); - engine.draw(urn, address(urnAuthed), 1); + engine.draw(urnOwner, 0, address(urnAuthed), 1); nst.approve(address(engine), 1); - engine.wipe(urn, 1); - engine.selectFarm(urn, address(farm), 0); - engine.getReward(urn, address(farm), address(0)); + engine.wipe(urnOwner, 0, 1); + engine.selectFarm(urnOwner, 0, address(farm), 0); + engine.getReward(urnOwner, 0, address(farm), address(0)); vm.expectEmit(true, true, true, true); - emit Nope(urn, urnAuthed); - engine.nope(urn, urnAuthed); + emit Nope(urnOwner, 0, urnAuthed); + engine.nope(urnOwner, 0, urnAuthed); assertEq(engine.urnCan(urn, urnAuthed), 0); - assertTrue(!engine.isUrnAuth(urn, urnAuthed)); + assertTrue(!engine.isUrnAuth(urnOwner, 0, urnAuthed)); vm.stopPrank(); - vm.prank(authedAndUrnAuthed); engine.freeNoFee(urn, address(this), 50_000 * 10**18); + vm.prank(authedAndUrnAuthed); engine.freeNoFee(urnOwner, 0, address(this), 50_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); } function testSelectVoteDelegate() public { address urn = engine.open(0); vm.expectRevert("LockstakeEngine/not-valid-vote-delegate"); - engine.selectVoteDelegate(urn, address(111)); + engine.selectVoteDelegate(address(this), 0, address(111)); vm.expectEmit(true, true, true, true); - emit SelectVoteDelegate(urn, voteDelegate); - engine.selectVoteDelegate(urn, voteDelegate); + emit SelectVoteDelegate(address(this), 0, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); vm.expectRevert("LockstakeEngine/same-vote-delegate"); - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); assertEq(engine.urnVoteDelegates(urn), voteDelegate); vm.prank(address(888)); address voteDelegate2 = voteDelegateFactory.create(); mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18, 5); + engine.lock(address(this), 0, 100_000 * 10**18, 5); + engine.draw(address(this), 0, address(this), 10_000 * 10**18); assertEq(VoteDelegateMock(voteDelegate).stake(address(engine)), 100_000 * 10**18); assertEq(VoteDelegateMock(voteDelegate2).stake(address(engine)), 0); assertEq(mkr.balanceOf(voteDelegate), 100_000 * 10**18); assertEq(mkr.balanceOf(voteDelegate2), 0); assertEq(mkr.balanceOf(address(engine)), 0); + dss.jug.drip(ilk); + (, uint256 rateA,,,) = dss.vat.ilks(ilk); + vm.warp(block.timestamp + 20); vm.expectEmit(true, true, true, true); - emit SelectVoteDelegate(urn, voteDelegate2); - engine.selectVoteDelegate(urn, voteDelegate2); + emit SelectVoteDelegate(address(this), 0, voteDelegate2); + engine.selectVoteDelegate(address(this), 0, voteDelegate2); + (, uint256 rateB,,,) = dss.vat.ilks(ilk); + assertGt(rateB, rateA); assertEq(engine.urnVoteDelegates(urn), voteDelegate2); assertEq(VoteDelegateMock(voteDelegate).stake(address(engine)), 0); assertEq(VoteDelegateMock(voteDelegate2).stake(address(engine)), 100_000 * 10**18); assertEq(mkr.balanceOf(voteDelegate), 0); assertEq(mkr.balanceOf(voteDelegate2), 100_000 * 10**18); assertEq(mkr.balanceOf(address(engine)), 0); - engine.selectVoteDelegate(urn, address(0)); + engine.selectVoteDelegate(address(this), 0, address(0)); assertEq(engine.urnVoteDelegates(urn), address(0)); assertEq(VoteDelegateMock(voteDelegate).stake(address(engine)), 0); assertEq(VoteDelegateMock(voteDelegate2).stake(address(engine)), 0); @@ -553,30 +595,30 @@ contract LockstakeEngineTest is DssTest { address urn = engine.open(0); assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/farm-unsupported-or-deleted"); - engine.selectFarm(urn, address(farm3), 5); + engine.selectFarm(address(this), 0, address(farm3), 5); vm.prank(pauseProxy); engine.addFarm(address(farm3)); vm.expectEmit(true, true, true, true); - emit SelectFarm(urn, address(farm3), 5); - engine.selectFarm(urn, address(farm3), 5); + emit SelectFarm(address(this), 0, address(farm3), 5); + engine.selectFarm(address(this), 0, address(farm3), 5); assertEq(engine.urnFarms(urn), address(farm3)); vm.expectRevert("LockstakeEngine/same-farm"); - engine.selectFarm(urn, address(farm3), 5); + engine.selectFarm(address(this), 0, address(farm3), 5); assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(lsmkr.balanceOf(address(farm3)), 0); mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18, 5); + engine.lock(address(this), 0, 100_000 * 10**18, 5); assertEq(lsmkr.balanceOf(address(farm)), 0); assertEq(lsmkr.balanceOf(address(farm3)), 100_000 * 10**18); assertEq(farm.balanceOf(urn), 0); assertEq(farm3.balanceOf(urn), 100_000 * 10**18); - engine.selectFarm(urn, address(farm), 5); + engine.selectFarm(address(this), 0, address(farm), 5); assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); assertEq(lsmkr.balanceOf(address(farm3)), 0); assertEq(farm.balanceOf(urn), 100_000 * 10**18); assertEq(farm3.balanceOf(urn), 0); vm.prank(pauseProxy); engine.delFarm(address(farm3)); vm.expectRevert("LockstakeEngine/farm-unsupported-or-deleted"); - engine.selectFarm(urn, address(farm3), 5); + engine.selectFarm(address(this), 0, address(farm3), 5); } function _testLockFree(bool withDelegate, bool withStaking) internal { @@ -585,26 +627,24 @@ contract LockstakeEngineTest is DssTest { deal(address(mkr), address(this), uint256(type(int256).max) + 1); // deal mkr to allow reaching the overflow revert mkr.approve(address(engine), uint256(type(int256).max) + 1); vm.expectRevert("LockstakeEngine/overflow"); - engine.lock(urn, uint256(type(int256).max) + 1, 5); + engine.lock(address(this), 0, uint256(type(int256).max) + 1, 5); deal(address(mkr), address(this), 100_000 * 10**18); // back to normal mkr balance and allowance mkr.approve(address(engine), 100_000 * 10**18); vm.expectRevert("LockstakeEngine/overflow"); - engine.free(urn, address(this), uint256(type(int256).max) + 1); + engine.free(address(this), 0, address(this), uint256(type(int256).max) + 1); if (withDelegate) { - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); } if (withStaking) { - engine.selectFarm(urn, address(farm), 0); + engine.selectFarm(address(this), 0, address(farm), 0); } assertEq(_ink(ilk, urn), 0); assertEq(lsmkr.balanceOf(urn), 0); mkr.transfer(address(123), 100_000 * 10**18); vm.prank(address(123)); mkr.approve(address(engine), 100_000 * 10**18); - vm.expectRevert("LockstakeEngine/invalid-urn"); - vm.prank(address(123)); engine.lock(address(456), 100_000 * 10**18, 5); vm.expectEmit(true, true, true, true); - emit Lock(urn, 100_000 * 10**18, 5); - vm.prank(address(123)); engine.lock(urn, 100_000 * 10**18, 5); + emit Lock(address(this), 0, 100_000 * 10**18, 5); + vm.prank(address(123)); engine.lock(address(this), 0, 100_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); @@ -621,8 +661,8 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.totalSupply(), initialMkrSupply); vm.expectEmit(true, true, true, true); - emit Free(urn, address(this), 40_000 * 10**18, 40_000 * 10**18 * 85 / 100); - assertEq(engine.free(urn, address(this), 40_000 * 10**18), 40_000 * 10**18 * 85 / 100); + emit Free(address(this), 0, address(this), 40_000 * 10**18, 40_000 * 10**18 * 85 / 100); + assertEq(engine.free(address(this), 0, address(this), 40_000 * 10**18), 40_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); @@ -638,8 +678,8 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } vm.expectEmit(true, true, true, true); - emit Free(urn, address(123), 10_000 * 10**18, 10_000 * 10**18 * 85 / 100); - assertEq(engine.free(urn, address(123), 10_000 * 10**18), 10_000 * 10**18 * 85 / 100); + emit Free(address(this), 0, address(123), 10_000 * 10**18, 10_000 * 10**18 * 85 / 100); + assertEq(engine.free(address(this), 0, address(123), 10_000 * 10**18), 10_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); @@ -659,7 +699,7 @@ contract LockstakeEngineTest is DssTest { mkr.approve(address(engine), 1); vm.prank(pauseProxy); engine.delFarm(address(farm)); vm.expectRevert("LockstakeEngine/farm-deleted"); - engine.lock(urn, 1, 0); + engine.lock(address(this), 0, 1, 0); } } @@ -684,17 +724,17 @@ contract LockstakeEngineTest is DssTest { address urn = engine.open(0); // Note: overflow cannot be reached for lockNgt and freeNgt as with these functions and the value of rate (>=3) the MKR amount will be always lower if (withDelegate) { - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); } if (withStaking) { - engine.selectFarm(urn, address(farm), 0); + engine.selectFarm(address(this), 0, address(farm), 0); } assertEq(_ink(ilk, urn), 0); assertEq(lsmkr.balanceOf(urn), 0); ngt.approve(address(engine), 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); - emit LockNgt(urn, 100_000 * 24_000 * 10**18, 5); - engine.lockNgt(urn, 100_000 * 24_000 * 10**18, 5); + emit LockNgt(address(this), 0, 100_000 * 24_000 * 10**18, 5); + engine.lockNgt(address(this), 0, 100_000 * 24_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); @@ -711,8 +751,8 @@ contract LockstakeEngineTest is DssTest { } assertEq(ngt.totalSupply(), initialNgtSupply - 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); - emit FreeNgt(urn, address(this), 40_000 * 24_000 * 10**18, 40_000 * 24_000 * 10**18 * 85 / 100); - assertEq(engine.freeNgt(urn, address(this), 40_000 * 24_000 * 10**18), 40_000 * 24_000 * 10**18 * 85 / 100); + emit FreeNgt(address(this), 0, address(this), 40_000 * 24_000 * 10**18, 40_000 * 24_000 * 10**18 * 85 / 100); + assertEq(engine.freeNgt(address(this), 0, address(this), 40_000 * 24_000 * 10**18), 40_000 * 24_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); @@ -728,8 +768,8 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } vm.expectEmit(true, true, true, true); - emit FreeNgt(urn, address(123), 10_000 * 24_000 * 10**18, 10_000 * 24_000 * 10**18 * 85 / 100); - assertEq(engine.freeNgt(urn, address(123), 10_000 * 24_000 * 10**18), 10_000 * 24_000 * 10**18 * 85 / 100); + emit FreeNgt(address(this), 0, address(123), 10_000 * 24_000 * 10**18, 10_000 * 24_000 * 10**18 * 85 / 100); + assertEq(engine.freeNgt(address(this), 0, address(123), 10_000 * 24_000 * 10**18), 10_000 * 24_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); @@ -749,7 +789,7 @@ contract LockstakeEngineTest is DssTest { ngt.approve(address(engine), 24_000); vm.prank(pauseProxy); engine.delFarm(address(farm)); vm.expectRevert("LockstakeEngine/farm-deleted"); - engine.lockNgt(urn, 24_000, 0); + engine.lockNgt(address(this), 0, 24_000, 0); } } @@ -776,14 +816,14 @@ contract LockstakeEngineTest is DssTest { deal(address(mkr), address(this), 100_000 * 10**18); mkr.approve(address(engine), 100_000 * 10**18); vm.expectRevert("LockstakeEngine/overflow"); - engine.freeNoFee(urn, address(this), uint256(type(int256).max) + 1); + engine.freeNoFee(address(this), 0, address(this), uint256(type(int256).max) + 1); if (withDelegate) { - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); } if (withStaking) { - engine.selectFarm(urn, address(farm), 0); + engine.selectFarm(address(this), 0, address(farm), 0); } - engine.lock(urn, 100_000 * 10**18, 5); + engine.lock(address(this), 0, 100_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); @@ -800,8 +840,8 @@ contract LockstakeEngineTest is DssTest { } assertEq(mkr.totalSupply(), initialMkrSupply); vm.expectEmit(true, true, true, true); - emit FreeNoFee(urn, address(this), 40_000 * 10**18); - engine.freeNoFee(urn, address(this), 40_000 * 10**18); + emit FreeNoFee(address(this), 0, address(this), 40_000 * 10**18); + engine.freeNoFee(address(this), 0, address(this), 40_000 * 10**18); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); @@ -817,8 +857,8 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } vm.expectEmit(true, true, true, true); - emit FreeNoFee(urn, address(123), 10_000 * 10**18); - engine.freeNoFee(urn, address(123), 10_000 * 10**18); + emit FreeNoFee(address(this), 0, address(123), 10_000 * 10**18); + engine.freeNoFee(address(this), 0, address(123), 10_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); @@ -856,18 +896,18 @@ contract LockstakeEngineTest is DssTest { deal(address(mkr), address(this), 100_000 * 10**18, true); address urn = engine.open(0); mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18, 5); + engine.lock(address(this), 0, 100_000 * 10**18, 5); assertEq(_art(ilk, urn), 0); vm.expectEmit(true, true, true, true); - emit Draw(urn, address(this), 50 * 10**18); - engine.draw(urn, address(this), 50 * 10**18); + emit Draw(address(this), 0, address(this), 50 * 10**18); + engine.draw(address(this), 0, address(this), 50 * 10**18); assertEq(_art(ilk, urn), 50 * 10**18); assertEq(_rate(ilk), 10**27); assertEq(nst.balanceOf(address(this)), 50 * 10**18); vm.warp(block.timestamp + 1); vm.expectEmit(true, true, true, true); - emit Draw(urn, address(this), 50 * 10**18); - engine.draw(urn, address(this), 50 * 10**18); + emit Draw(address(this), 0, address(this), 50 * 10**18); + engine.draw(address(this), 0, address(this), 50 * 10**18); uint256 art = _art(ilk, urn); uint256 expectedArt = 50 * 10**18 + _divup(50 * 10**18 * 100000000, 100000001); assertEq(art, expectedArt); @@ -877,49 +917,49 @@ contract LockstakeEngineTest is DssTest { assertGt(art * rate, 100.0000005 * 10**45); assertLt(art * rate, 100.0000006 * 10**45); vm.expectRevert("Nst/insufficient-balance"); - engine.wipe(urn, 100.0000006 * 10**18); + engine.wipe(address(this), 0, 100.0000006 * 10**18); address anyone = address(1221121); deal(address(nst), anyone, 100.0000006 * 10**18, true); assertEq(nst.balanceOf(anyone), 100.0000006 * 10**18); vm.prank(anyone); nst.approve(address(engine), 100.0000006 * 10**18); vm.expectRevert(); - vm.prank(anyone); engine.wipe(urn, 100.0000006 * 10**18); // It will try to wipe more art than existing, then reverts + vm.prank(anyone); engine.wipe(address(this), 0, 100.0000006 * 10**18); // It will try to wipe more art than existing, then reverts vm.expectEmit(true, true, true, true); - emit Wipe(urn, 100.0000005 * 10**18); - vm.prank(anyone); engine.wipe(urn, 100.0000005 * 10**18); + emit Wipe(address(this), 0, 100.0000005 * 10**18); + vm.prank(anyone); engine.wipe(address(this), 0, 100.0000005 * 10**18); assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18); assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe via this regular function - emit Wipe(urn, _divup(rate, RAY)); - vm.prank(anyone); assertEq(engine.wipeAll(urn), _divup(rate, RAY)); + emit Wipe(address(this), 0, _divup(rate, RAY)); + vm.prank(anyone); assertEq(engine.wipeAll(address(this), 0), _divup(rate, RAY)); assertEq(_art(ilk, urn), 0); assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18 - _divup(rate, RAY)); address other = address(123); assertEq(nst.balanceOf(other), 0); - emit Draw(urn, other, 50 * 10**18); - engine.draw(urn, other, 50 * 10**18); + emit Draw(address(this), 0, other, 50 * 10**18); + engine.draw(address(this), 0, other, 50 * 10**18); assertEq(nst.balanceOf(other), 50 * 10**18); // Check overflows stdstore.target(address(dss.vat)).sig("ilks(bytes32)").with_key(ilk).depth(1).checked_write(1); assertEq(_rate(ilk), 1); vm.expectRevert("LockstakeEngine/overflow"); - engine.draw(urn, address(this), uint256(type(int256).max) / RAY + 1); + engine.draw(address(this), 0, address(this), uint256(type(int256).max) / RAY + 1); stdstore.target(address(dss.vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(uint256(type(int256).max) + RAY); deal(address(nst), address(this), uint256(type(int256).max) / RAY + 1, true); nst.approve(address(engine), uint256(type(int256).max) / RAY + 1); vm.expectRevert("LockstakeEngine/overflow"); - engine.wipe(urn, uint256(type(int256).max) / RAY + 1); + engine.wipe(address(this), 0, uint256(type(int256).max) / RAY + 1); stdstore.target(address(dss.vat)).sig("urns(bytes32,address)").with_key(ilk).with_key(urn).depth(1).checked_write(uint256(type(int256).max) + 1); assertEq(_art(ilk, urn), uint256(type(int256).max) + 1); vm.expectRevert("LockstakeEngine/overflow"); - engine.wipeAll(urn); + engine.wipeAll(address(this), 0); } function testOpenLockStakeMulticall() public { mkr.approve(address(engine), 100_000 * 10**18); - address urn = engine.getUrn(address(this), 0); + address urn = vm.computeCreateAddress(address(engine), vm.getNonce(address(engine))); - assertEq(engine.usrAmts(address(this)), 0); + assertEq(engine.ownerUrnsCount(address(this)), 0); assertEq(_ink(ilk, urn), 0); assertEq(farm.balanceOf(address(urn)), 0); assertEq(lsmkr.balanceOf(address(farm)), 0); @@ -927,16 +967,16 @@ contract LockstakeEngineTest is DssTest { vm.expectEmit(true, true, true, true); emit Open(address(this), 0 , urn); vm.expectEmit(true, true, true, true); - emit Lock(urn, 100_000 * 10**18, uint16(5)); + emit Lock(address(this), 0, 100_000 * 10**18, uint16(5)); vm.expectEmit(true, true, true, true); - emit SelectFarm(urn, address(farm), uint16(5)); + emit SelectFarm(address(this), 0, address(farm), uint16(5)); bytes[] memory callsToExecute = new bytes[](3); callsToExecute[0] = abi.encodeWithSignature("open(uint256)", 0); - callsToExecute[1] = abi.encodeWithSignature("lock(address,uint256,uint16)", urn, 100_000 * 10**18, uint16(5)); - callsToExecute[2] = abi.encodeWithSignature("selectFarm(address,address,uint16)", urn, address(farm), uint16(5)); + callsToExecute[1] = abi.encodeWithSignature("lock(address,uint256,uint256,uint16)", address(this), 0, 100_000 * 10**18, uint16(5)); + callsToExecute[2] = abi.encodeWithSignature("selectFarm(address,uint256,address,uint16)", address(this), 0, address(farm), uint16(5)); engine.multicall(callsToExecute); - assertEq(engine.usrAmts(address(this)), 1); + assertEq(engine.ownerUrnsCount(address(this)), 1); assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(farm.balanceOf(address(urn)), 100_000 * 10**18); assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); @@ -954,30 +994,30 @@ contract LockstakeEngineTest is DssTest { function testGetReward() public { address urn = engine.open(0); vm.expectRevert("LockstakeEngine/farm-unsupported"); - engine.getReward(urn, address(456), address(123)); + engine.getReward(address(this), 0, address(456), address(123)); farm.setReward(address(urn), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 0); vm.expectEmit(true, true, true, true); - emit GetReward(urn, address(farm), address(123), 20_000); - assertEq(engine.getReward(urn, address(farm), address(123)), 20_000); + emit GetReward(address(this), 0, address(farm), address(123), 20_000); + assertEq(engine.getReward(address(this), 0, address(farm), address(123)), 20_000); assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 20_000); vm.prank(pauseProxy); engine.delFarm(address(farm)); farm.setReward(address(urn), 30_000); - assertEq(engine.getReward(urn, address(farm), address(123)), 30_000); // Can get reward after farm is deleted + assertEq(engine.getReward(address(this), 0, address(farm), address(123)), 30_000); // Can get reward after farm is deleted assertEq(GemMock(address(farm.rewardsToken())).balanceOf(address(123)), 50_000); } function _urnSetUp(bool withDelegate, bool withStaking) internal returns (address urn) { urn = engine.open(0); if (withDelegate) { - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); } if (withStaking) { - engine.selectFarm(urn, address(farm), 0); + engine.selectFarm(address(this), 0, address(farm), 0); } mkr.approve(address(engine), 100_000 * 10**18); - engine.lock(urn, 100_000 * 10**18, 5); - engine.draw(urn, address(this), 2_000 * 10**18); + engine.lock(address(this), 0, 100_000 * 10**18, 5); + engine.draw(address(this), 0, address(this), 2_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); assertEq(_art(ilk, urn), 2_000 * 10**18); @@ -1400,9 +1440,9 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnFarms(urn), address(0)); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectFarm(urn, address(farm), 0); + engine.selectFarm(address(this), 0, address(farm), 0); vm.prank(pauseProxy); dss.dog.file(ilk, "hole", 1000 * 10**45); uint256 id2 = dss.dog.bark(ilk, urn, address(this)); @@ -1410,9 +1450,9 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnAuctions(urn), 2); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectFarm(urn, address(farm), 0); + engine.selectFarm(address(this), 0, address(farm), 0); // Take with left > 0 address buyer = address(888); @@ -1427,9 +1467,9 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnAuctions(urn), 1); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectVoteDelegate(urn, voteDelegate); + engine.selectVoteDelegate(address(this), 0, voteDelegate); vm.expectRevert("LockstakeEngine/urn-in-auction"); - engine.selectFarm(urn, address(farm), 0); + engine.selectFarm(address(this), 0, address(farm), 0); vm.warp(block.timestamp + 80); // Time passes to let the auction price to crash @@ -1442,8 +1482,8 @@ contract LockstakeEngineTest is DssTest { assertEq(engine.urnAuctions(urn), 0); // Can select voteDelegate and farm again - engine.selectVoteDelegate(urn, voteDelegate); - engine.selectFarm(urn, address(farm), 0); + engine.selectVoteDelegate(address(this), 0, voteDelegate); + engine.selectFarm(address(this), 0, address(farm), 0); } function testUrnUnsafe() public { @@ -1457,17 +1497,17 @@ contract LockstakeEngineTest is DssTest { dss.spotter.poke(ilk); vm.expectRevert("LockstakeEngine/urn-unsafe"); - engine.selectVoteDelegate(urn, voteDelegate2); + engine.selectVoteDelegate(address(this), 0, voteDelegate2); - engine.selectVoteDelegate(urn, address(0)); + engine.selectVoteDelegate(address(this), 0, address(0)); vm.expectRevert("LockstakeEngine/urn-unsafe"); - engine.selectVoteDelegate(urn, voteDelegate2); + engine.selectVoteDelegate(address(this), 0, voteDelegate2); vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); // Back to safety dss.spotter.poke(ilk); - engine.selectVoteDelegate(urn, voteDelegate2); + engine.selectVoteDelegate(address(this), 0, voteDelegate2); assertEq(engine.urnVoteDelegates(urn), voteDelegate2); } diff --git a/test/handlers/LockstakeHandler.sol b/test/handlers/LockstakeHandler.sol index cf3e2edd..1ab601c7 100644 --- a/test/handlers/LockstakeHandler.sol +++ b/test/handlers/LockstakeHandler.sol @@ -48,8 +48,9 @@ contract LockstakeHandler is StdUtils, StdCheats { LockstakeClipper public clip; address public pauseProxy; + address public owner; + uint256 public index; address public urn; - address public urnOwner; address[] public voteDelegates; address[] public farms; uint256 public mkrNgtRate; @@ -66,7 +67,7 @@ contract LockstakeHandler is StdUtils, StdCheats { } modifier callAsUrnOwner() { - vm.startPrank(urnOwner); + vm.startPrank(owner); _; vm.stopPrank(); } @@ -80,7 +81,8 @@ contract LockstakeHandler is StdUtils, StdCheats { constructor( Vm vm_, address engine_, - address urn_, + address owner_, + uint256 index_, address spot_, address dog_, address pauseProxy_, @@ -101,8 +103,9 @@ contract LockstakeHandler is StdUtils, StdCheats { (address clip_, , , ) = dog.ilks(ilk); clip = LockstakeClipper(clip_); - urn = urn_; - urnOwner = engine.urnOwners(urn); + owner = owner_; + index = index_; + urn = engine.ownerUrns(owner, index); mkrNgtRate = engine.mkrNgtRate(); vat.hope(address(clip)); @@ -201,12 +204,12 @@ contract LockstakeHandler is StdUtils, StdCheats { function selectFarm(uint16 ref, uint256 farmIndex) callAsUrnOwner() external { numCalls["selectFarm"]++; - engine.selectFarm(urn, _getRandomFarm(farmIndex), ref); + engine.selectFarm(owner, index, _getRandomFarm(farmIndex), ref); } function selectVoteDelegate(uint256 voteDelegateIndex) callAsUrnOwner() external { numCalls["selectVoteDelegate"]++; - engine.selectVoteDelegate(urn, _getRandomVoteDelegate(voteDelegateIndex)); + engine.selectVoteDelegate(owner, index, _getRandomVoteDelegate(voteDelegateIndex)); } function lock(uint256 wad, uint16 ref) external callAsAnyone { @@ -224,7 +227,7 @@ contract LockstakeHandler is StdUtils, StdCheats { deal(address(mkr), anyone, wad); mkr.approve(address(engine), wad); - engine.lock(urn, wad, ref); + engine.lock(owner, index, wad, ref); } function lockNgt(uint256 ngtWad, uint16 ref) external callAsAnyone { @@ -245,7 +248,7 @@ contract LockstakeHandler is StdUtils, StdCheats { deal(address(ngt), anyone, ngtWad); ngt.approve(address(engine), ngtWad); - engine.lockNgt(urn, ngtWad, ref); + engine.lockNgt(owner, index, ngtWad, ref); } function free(address to, uint256 wad) external callAsUrnOwner() { @@ -257,7 +260,7 @@ contract LockstakeHandler is StdUtils, StdCheats { (, uint256 rate, uint256 spotPrice,,) = vat.ilks(ilk); wad = bound(wad, 0, ink - _divup(art * rate, spotPrice)); - engine.free(urn, to, wad); + engine.free(owner, index, to, wad); } function freeNgt(address to, uint256 ngtWad) external callAsUrnOwner() { @@ -267,7 +270,7 @@ contract LockstakeHandler is StdUtils, StdCheats { (, uint256 rate, uint256 spotPrice,,) = vat.ilks(ilk); ngtWad = bound(ngtWad, 0, (ink - _divup(art * rate, spotPrice)) * mkrNgtRate); - engine.freeNgt(urn, to, ngtWad); + engine.freeNgt(owner, index, to, ngtWad); } function draw(uint256 wad) external callAsUrnOwner() { @@ -286,7 +289,7 @@ contract LockstakeHandler is StdUtils, StdCheats { ) )); - engine.draw(urn, address(this), wad); + engine.draw(owner, index, address(this), wad); } function wipe(uint256 wad) external callAsAnyone { @@ -303,7 +306,7 @@ contract LockstakeHandler is StdUtils, StdCheats { deal(address(nst), anyone, wad); nst.approve(address(engine), wad); - engine.wipe(urn, wad); + engine.wipe(owner, index, wad); } function dropPriceAndBark() external { From 7c71318623f5d6732457fd0c247a1f1760960011 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:52:20 -0300 Subject: [PATCH 103/111] Renaming (#59) --- README.md | 28 +-- deploy/LockstakeDeploy.sol | 6 +- deploy/LockstakeInit.sol | 24 +-- src/LockstakeEngine.sol | 80 ++++---- test/LockstakeEngine-invariants.t.sol | 38 ++-- test/LockstakeEngine.t.sol | 172 +++++++++--------- test/handlers/LockstakeHandler.sol | 42 ++--- test/mocks/MkrNgtMock.sol | 32 ---- test/mocks/MkrSkyMock.sol | 32 ++++ .../{NstJoinMock.sol => UsdsJoinMock.sol} | 14 +- test/mocks/{NstMock.sol => UsdsMock.sol} | 12 +- 11 files changed, 240 insertions(+), 240 deletions(-) delete mode 100644 test/mocks/MkrNgtMock.sol create mode 100644 test/mocks/MkrSkyMock.sol rename test/mocks/{NstJoinMock.sol => UsdsJoinMock.sol} (67%) rename test/mocks/{NstMock.sol => UsdsMock.sol} (84%) diff --git a/README.md b/README.md index 02781beb..78426fb1 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ A technical description of the components of the LockStake Engine (LSE). The LockstakeEngine is the main contract in the set of contracts that implement and support the LSE. On a high level, it supports locking MKR in the contract, and using it to: * Vote through a delegate contract. -* Farm NST or SDAO tokens. -* Borrow NST through a vault. +* Farm USDS or SDAO tokens. +* Borrow USDS through a vault. When withdrawing back the MKR the user has to pay an exit fee. -There is also support for locking and freeing NGT instead of MKR. +There is also support for locking and freeing SKY instead of MKR. **System Attributes:** @@ -20,7 +20,7 @@ There is also support for locking and freeing NGT instead of MKR. * MKR cannot be moved outside of an `urn` or between `urn`s without paying the exit fee. * At any time the `urn`'s entire locked MKR amount is either staked or not, and is either delegated or not. * Staking rewards are not part of the collateral, and are still claimable after freeing from the engine, changing a farm or being liquidated. -* The entire locked MKR amount is also credited as collateral for the user. However, the user itself decides if and how much NST to borrow, and should be aware of liquidation risk. +* The entire locked MKR amount is also credited as collateral for the user. However, the user itself decides if and how much USDS to borrow, and should be aware of liquidation risk. * A user can delegate control of an `urn` that it controls to another EOA/contract. This is helpful for supporting manager-type contracts that can be built on top of the engine. * Once a vault goes into liquidation, its MKR is undelegated and unstaked. It and can only be re-delegated or re-staked once there are no more auctions for it. @@ -30,15 +30,15 @@ There is also support for locking and freeing NGT instead of MKR. * `hope(address owner, uint256 index, address usr)` - Allow `usr` to also manage the `owner-index` `urn`. * `nope(address owner, uint256 index, address usr)` - Disallow `usr` from managing the `owner-index` `urn`. * `lock(address owner, uint256 index, uint256 wad, uint16 ref)` - Deposit `wad` amount of MKR into the `owner-index` `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. -* `lockNgt(address owner, uint256 index, uint256 ngtWad, uint16 ref)` - Deposit `ngtWad` amount of NGT. The NGT is first converted to MKR, which then gets deposited into the `owner-index` `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. +* `lockSky(address owner, uint256 index, uint256 skyWad, uint16 ref)` - Deposit `skyWad` amount of SKY. The SKY is first converted to MKR, which then gets deposited into the `owner-index` `urn`. This also delegates the MKR to the chosen delegate (if such exists) and stakes it to the chosen farm (if such exists) using the `ref` code. * `free(address owner, uint256 index, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `owner-index` `urn` to the `to` address (which will receive it minus the exit fee). This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. -* `freeNgt(address owner, uint256 index, address to, uint256 ngtWad)` - Withdraw `ngtWad - ngtWad % mkrNgtRate` amount of NGT to the `to` address. In practice, a proportional amount of MKR is first freed from the `owner-index` `urn` (minus the exit fee), then gets converted to NGT and sent out. This will undelegate the MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. Note that freeing NGT is possible even if the position was previously entered via regular locking (using MKR), and vice-vera. +* `freeSky(address owner, uint256 index, address to, uint256 skyWad)` - Withdraw `skyWad - skyWad % mkrSkyRate` amount of SKY to the `to` address. In practice, a proportional amount of MKR is first freed from the `owner-index` `urn` (minus the exit fee), then gets converted to SKY and sent out. This will undelegate the MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. Note that freeing SKY is possible even if the position was previously entered via regular locking (using MKR), and vice-vera. * `freeNoFee(address owner, uint256 index, address to, uint256 wad)` - Withdraw `wad` amount of MKR from the `owner-index` `urn` to the `to` address without paying any fee. This will undelegate the requested amount of MKR (if a delegate was chosen) and unstake it (if a farm was chosen). It will require the user to pay down debt beforehand if needed. This function can only be called by an address which was both authorized on the contract by governance and for which the urn owner has called `hope`. It is useful for implementing a migration contract that will move the funds to another engine contract (if ever needed). * `selectVoteDelegate(address owner, uint256 index, address voteDelegate)` - Choose which delegate contract to delegate the `owner-index` `urn`'s entire MKR amount to. In case it is `address(0)` the MKR will stay (or become) undelegated. * `selectFarm(address owner, uint256 index, address farm, uint16 ref)` - Select which farm (from the whitelisted ones) to stake the `owner-index` `urn`'s MKR to (along with the `ref` code). In case it is `address(0)` the MKR will stay (or become) unstaked. -* `draw(address owner, uint256 index, address to, uint256 wad)` - Generate `wad` amount of NST using the `owner-index` `urn`’s MKR as collateral and send it to the `to` address. -* `wipe(address owner, uint256 index, uint256 wad)` - Repay `wad` amount of NST backed by the `owner-index` `urn`’s MKR. -* `wipeAll(address owner, uint256 index)` - Repay the amount of NST that is needed to wipe the `owner-index` `urn`’s entire debt. +* `draw(address owner, uint256 index, address to, uint256 wad)` - Generate `wad` amount of USDS using the `owner-index` `urn`’s MKR as collateral and send it to the `to` address. +* `wipe(address owner, uint256 index, uint256 wad)` - Repay `wad` amount of USDS backed by the `owner-index` `urn`’s MKR. +* `wipeAll(address owner, uint256 index)` - Repay the amount of USDS that is needed to wipe the `owner-index` `urn`’s entire debt. * `getReward(address owner, uint256 index, address farm, address to)` - Claim the reward generated from a farm on behalf of the `owner-index` `urn` and send it to the specified `to` address. * `multicall(bytes[] calldata data)` - Batch multiple methods in a single call to the contract. @@ -188,11 +188,11 @@ In general participating in MKR liquidations should be pretty straightforward us Current Makerdao ecosystem keepers expect receiving collateral in the form of `vat.gem` (usually to a keeper arbitrage callee contract), which they then need to `exit` to ERC20 from. However the LSE liquidation mechanism sends the MKR directly in the form of ERC20, which requires a slight change in the keepers mode of operation. For example, keepers using the Maker supplied [exchange-callee for Uniswap V2](https://github.com/makerdao/exchange-callees/blob/3b080ecd4169fe09a59be51e2f85ddcea3242461/src/UniswapV2Callee.sol#L109) would need to use a version that gets the `gem` instead of the `gemJoin` and does not call `gemJoin.exit`. -Additionaly, the callee might need to convert the MKR to NGT, in case it interacts with the NST/NGT Uniswap pool. +Additionaly, the callee might need to convert the MKR to SKY, in case it interacts with the USDS/SKY Uniswap pool. ## 5. Splitter -The Splitter contract is in charge of distributing the Surplus Buffer funds on each `vow.flap` to the Smart Burn Engine (SBE) and the LSE's NST farm. The total amount sent each time is `vow.bump`. +The Splitter contract is in charge of distributing the Surplus Buffer funds on each `vow.flap` to the Smart Burn Engine (SBE) and the LSE's USDS farm. The total amount sent each time is `vow.bump`. To accomplish this, it exposes a `kick` operation to be triggered periodically. Its logic withdraws DAI from the `vow` and splits it in two parts. The first part (`burn`) is sent to the underlying `flapper` contract to be processed by the SBE. The second part (`WAD - burn`) is distributed as reward to a `farm` contract. Note that `burn == 1 WAD` indicates funneling 100% of the DAI to the SBE without sending any rewards to the farm. @@ -207,7 +207,7 @@ The Splitter implements rate-limiting using a `hop` parameter. ## 6. StakingRewards -The LSE uses a Maker modified [version](https://github.com/makerdao/endgame-toolkit/blob/master/README.md#stakingrewards) of the Synthetix Staking Reward as the farm for distributing NST to stakers. +The LSE uses a Maker modified [version](https://github.com/makerdao/endgame-toolkit/blob/master/README.md#stakingrewards) of the Synthetix Staking Reward as the farm for distributing USDS to stakers. For compatibility with the SBE, the assumption is that the duration of each farming distribution (`farm.rewardsDuration`) is similar to the flapper's cooldown period (`flap.hop`). This in practice divides the overall farming reward distribution to a set of smaller non overlapping distributions. It also allows for periods where there is no distribution at all. @@ -218,8 +218,8 @@ The StakingRewards contract `setRewardsDuration` function was modified to enable * `rewardsDuration` - The amount of seconds each distribution should take. ## General Notes -* In many of the modules, such as the splitter and the flappers, NST can replace DAI. This will usually require a deployment of the contract with NstJoin as a replacement of the DaiJoin address. +* In many of the modules, such as the splitter and the flappers, USDS can replace DAI. This will usually require a deployment of the contract with UsdsJoin as a replacement of the DaiJoin address. * The LSE assumes that the ESM threshold is set large enough prior to its deployment, so Emergency Shutdown can never be called. * Freeing very small amounts could bypass the exit fees (due to the rounding down) but since the LSE is meant to only be deployed on Ethereum, this is assumed to not be economically viable. * As opposed to other collateral types, if a user notices an upcoming governance action that can hurt their position (or that they just don't like), they can not exit their position without losing the exit fee. -* It is assumed that MKR to/from NGT conversions are not blocked. +* It is assumed that MKR to/from SKY conversions are not blocked. diff --git a/deploy/LockstakeDeploy.sol b/deploy/LockstakeDeploy.sol index bf97dd88..fde6baaa 100644 --- a/deploy/LockstakeDeploy.sol +++ b/deploy/LockstakeDeploy.sol @@ -30,15 +30,15 @@ library LockstakeDeploy { address deployer, address owner, address voteDelegateFactory, - address nstJoin, + address usdsJoin, bytes32 ilk, - address mkrNgt, + address mkrSky, bytes4 calcSig ) internal returns (LockstakeInstance memory lockstakeInstance) { DssInstance memory dss = MCD.loadFromChainlog(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); lockstakeInstance.lsmkr = address(new LockstakeMkr()); - lockstakeInstance.engine = address(new LockstakeEngine(voteDelegateFactory, nstJoin, ilk, mkrNgt, lockstakeInstance.lsmkr)); + lockstakeInstance.engine = address(new LockstakeEngine(voteDelegateFactory, usdsJoin, ilk, mkrSky, lockstakeInstance.lsmkr)); lockstakeInstance.clipper = address(new LockstakeClipper(address(dss.vat), address(dss.spotter), address(dss.dog), lockstakeInstance.engine)); (bool ok, bytes memory returnV) = dss.chainlog.getAddress("CALC_FAB").call(abi.encodeWithSelector(calcSig, owner)); require(ok); diff --git a/deploy/LockstakeInit.sol b/deploy/LockstakeInit.sol index e0740e2b..57809d25 100644 --- a/deploy/LockstakeInit.sol +++ b/deploy/LockstakeInit.sol @@ -26,14 +26,14 @@ interface LockstakeMkrLike { interface LockstakeEngineLike { function voteDelegateFactory() external view returns (address); function vat() external view returns (address); - function nstJoin() external view returns (address); - function nst() external view returns (address); + function usdsJoin() external view returns (address); + function usds() external view returns (address); function ilk() external view returns (bytes32); function mkr() external view returns (address); function lsmkr() external view returns (address); function fee() external view returns (uint256); - function mkrNgt() external view returns (address); - function ngt() external view returns (address); + function mkrSky() external view returns (address); + function sky() external view returns (address); function rely(address) external; function file(bytes32, address) external; function file(bytes32, uint256) external; @@ -98,11 +98,11 @@ interface IlkRegistryLike { struct LockstakeConfig { bytes32 ilk; address voteDelegateFactory; - address nstJoin; - address nst; + address usdsJoin; + address usds; address mkr; - address mkrNgt; - address ngt; + address mkrSky; + address sky; address[] farms; uint256 fee; uint256 maxLine; @@ -146,13 +146,13 @@ library LockstakeInit { // Sanity checks require(engine.voteDelegateFactory() == cfg.voteDelegateFactory, "Engine voteDelegateFactory mismatch"); require(engine.vat() == address(dss.vat), "Engine vat mismatch"); - require(engine.nstJoin() == cfg.nstJoin, "Engine nstJoin mismatch"); - require(engine.nst() == cfg.nst, "Engine nst mismatch"); + require(engine.usdsJoin() == cfg.usdsJoin, "Engine usdsJoin mismatch"); + require(engine.usds() == cfg.usds, "Engine usds mismatch"); require(engine.ilk() == cfg.ilk, "Engine ilk mismatch"); require(engine.mkr() == cfg.mkr, "Engine mkr mismatch"); require(engine.lsmkr() == lockstakeInstance.lsmkr, "Engine lsmkr mismatch"); - require(engine.mkrNgt() == cfg.mkrNgt, "Engine mkrNgt mismatch"); - require(engine.ngt() == cfg.ngt, "Engine ngt mismatch"); + require(engine.mkrSky() == cfg.mkrSky, "Engine mkrSky mismatch"); + require(engine.sky() == cfg.sky, "Engine sky mismatch"); require(clipper.ilk() == cfg.ilk, "Clipper ilk mismatch"); require(clipper.vat() == address(dss.vat), "Clipper vat mismatch"); require(clipper.engine() == address(engine), "Clipper engine mismatch"); diff --git a/src/LockstakeEngine.sol b/src/LockstakeEngine.sol index f5054cc4..e4e019bd 100644 --- a/src/LockstakeEngine.sol +++ b/src/LockstakeEngine.sol @@ -37,9 +37,9 @@ interface VatLike { function grab(bytes32, address, address, address, int256, int256) external; } -interface NstJoinLike { +interface UsdsJoinLike { function vat() external view returns (VatLike); - function nst() external view returns (GemLike); + function usds() external view returns (GemLike); function join(address, uint256) external; function exit(address, uint256) external; } @@ -56,12 +56,12 @@ interface JugLike { function drip(bytes32) external returns (uint256); } -interface MkrNgtLike { +interface MkrSkyLike { function rate() external view returns (uint256); function mkr() external view returns (GemLike); - function ngt() external view returns (GemLike); - function ngtToMkr(address, uint256) external; - function mkrToNgt(address, uint256) external; + function sky() external view returns (GemLike); + function skyToMkr(address, uint256) external; + function mkrToSky(address, uint256) external; } contract LockstakeEngine is Multicall { @@ -90,14 +90,14 @@ contract LockstakeEngine is Multicall { VoteDelegateFactoryLike immutable public voteDelegateFactory; VatLike immutable public vat; - NstJoinLike immutable public nstJoin; - GemLike immutable public nst; + UsdsJoinLike immutable public usdsJoin; + GemLike immutable public usds; bytes32 immutable public ilk; GemLike immutable public mkr; GemLike immutable public lsmkr; - MkrNgtLike immutable public mkrNgt; - GemLike immutable public ngt; - uint256 immutable public mkrNgtRate; + MkrSkyLike immutable public mkrSky; + GemLike immutable public sky; + uint256 immutable public mkrSkyRate; address immutable public urnImplementation; // --- events --- @@ -114,9 +114,9 @@ contract LockstakeEngine is Multicall { event SelectVoteDelegate(address indexed owner, uint256 indexed index, address indexed voteDelegate); event SelectFarm(address indexed owner, uint256 indexed index, address indexed farm, uint16 ref); event Lock(address indexed owner, uint256 indexed index, uint256 wad, uint16 ref); - event LockNgt(address indexed owner, uint256 indexed index, uint256 ngtWad, uint16 ref); + event LockSky(address indexed owner, uint256 indexed index, uint256 skyWad, uint16 ref); event Free(address indexed owner, uint256 indexed index, address to, uint256 wad, uint256 freed); - event FreeNgt(address indexed owner, uint256 indexed index, address to, uint256 ngtWad, uint256 ngtFreed); + event FreeSky(address indexed owner, uint256 indexed index, address to, uint256 skyWad, uint256 skyFreed); event FreeNoFee(address indexed owner, uint256 indexed index, address to, uint256 wad); event Draw(address indexed owner, uint256 indexed index, address to, uint256 wad); event Wipe(address indexed owner, uint256 indexed index, uint256 wad); @@ -134,22 +134,22 @@ contract LockstakeEngine is Multicall { // --- constructor --- - constructor(address voteDelegateFactory_, address nstJoin_, bytes32 ilk_, address mkrNgt_, address lsmkr_) { + constructor(address voteDelegateFactory_, address usdsJoin_, bytes32 ilk_, address mkrSky_, address lsmkr_) { voteDelegateFactory = VoteDelegateFactoryLike(voteDelegateFactory_); - nstJoin = NstJoinLike(nstJoin_); - vat = nstJoin.vat(); - nst = nstJoin.nst(); + usdsJoin = UsdsJoinLike(usdsJoin_); + vat = usdsJoin.vat(); + usds = usdsJoin.usds(); ilk = ilk_; - mkrNgt = MkrNgtLike(mkrNgt_); - mkr = mkrNgt.mkr(); - ngt = mkrNgt.ngt(); - mkrNgtRate = mkrNgt.rate(); + mkrSky = MkrSkyLike(mkrSky_); + mkr = mkrSky.mkr(); + sky = mkrSky.sky(); + mkrSkyRate = mkrSky.rate(); lsmkr = GemLike(lsmkr_); urnImplementation = address(new LockstakeUrn(address(vat), lsmkr_)); - vat.hope(nstJoin_); - nst.approve(nstJoin_, type(uint256).max); - ngt.approve(address(mkrNgt), type(uint256).max); - mkr.approve(address(mkrNgt), type(uint256).max); + vat.hope(usdsJoin_); + usds.approve(usdsJoin_, type(uint256).max); + sky.approve(address(mkrSky), type(uint256).max); + mkr.approve(address(mkrSky), type(uint256).max); wards[msg.sender] = 1; emit Rely(msg.sender); @@ -320,12 +320,12 @@ contract LockstakeEngine is Multicall { emit Lock(owner, index, wad, ref); } - function lockNgt(address owner, uint256 index, uint256 ngtWad, uint16 ref) external { + function lockSky(address owner, uint256 index, uint256 skyWad, uint16 ref) external { address urn = _getUrn(owner, index); - ngt.transferFrom(msg.sender, address(this), ngtWad); - mkrNgt.ngtToMkr(address(this), ngtWad); - _lock(urn, ngtWad / mkrNgtRate, ref); - emit LockNgt(owner, index, ngtWad, ref); + sky.transferFrom(msg.sender, address(this), skyWad); + mkrSky.skyToMkr(address(this), skyWad); + _lock(urn, skyWad / mkrSkyRate, ref); + emit LockSky(owner, index, skyWad, ref); } function _lock(address urn, uint256 wad, uint16 ref) internal { @@ -352,13 +352,13 @@ contract LockstakeEngine is Multicall { emit Free(owner, index, to, wad, freed); } - function freeNgt(address owner, uint256 index, address to, uint256 ngtWad) external returns (uint256 ngtFreed) { + function freeSky(address owner, uint256 index, address to, uint256 skyWad) external returns (uint256 skyFreed) { address urn = _getAuthedUrn(owner, index); - uint256 wad = ngtWad / mkrNgtRate; + uint256 wad = skyWad / mkrSkyRate; uint256 freed = _free(urn, wad, fee); - ngtFreed = freed * mkrNgtRate; - mkrNgt.mkrToNgt(to, freed); - emit FreeNgt(owner, index, to, ngtWad, ngtFreed); + skyFreed = freed * mkrSkyRate; + mkrSky.mkrToSky(to, freed); + emit FreeSky(owner, index, to, skyWad, skyFreed); } function freeNoFee(address owner, uint256 index, address to, uint256 wad) external auth { @@ -396,14 +396,14 @@ contract LockstakeEngine is Multicall { uint256 dart = _divup(wad * RAY, rate); require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); vat.frob(ilk, urn, address(0), address(this), 0, int256(dart)); - nstJoin.exit(to, wad); + usdsJoin.exit(to, wad); emit Draw(owner, index, to, wad); } function wipe(address owner, uint256 index, uint256 wad) external { address urn = _getUrn(owner, index); - nst.transferFrom(msg.sender, address(this), wad); - nstJoin.join(address(this), wad); + usds.transferFrom(msg.sender, address(this), wad); + usdsJoin.join(address(this), wad); (, uint256 rate,,,) = vat.ilks(ilk); uint256 dart = wad * RAY / rate; require(dart <= uint256(type(int256).max), "LockstakeEngine/overflow"); @@ -417,8 +417,8 @@ contract LockstakeEngine is Multicall { require(art <= uint256(type(int256).max), "LockstakeEngine/overflow"); (, uint256 rate,,,) = vat.ilks(ilk); wad = _divup(art * rate, RAY); - nst.transferFrom(msg.sender, address(this), wad); - nstJoin.join(address(this), wad); + usds.transferFrom(msg.sender, address(this), wad); + usdsJoin.join(address(this), wad); vat.frob(ilk, urn, address(0), address(this), 0, -int256(art)); emit Wipe(owner, index, wad); } diff --git a/test/LockstakeEngine-invariants.t.sol b/test/LockstakeEngine-invariants.t.sol index 4466cf45..855fa91b 100644 --- a/test/LockstakeEngine-invariants.t.sol +++ b/test/LockstakeEngine-invariants.t.sol @@ -9,10 +9,10 @@ import { LockstakeMkr } from "src/LockstakeMkr.sol"; import { PipMock } from "test/mocks/PipMock.sol"; import { VoteDelegateFactoryMock, VoteDelegateMock } from "test/mocks/VoteDelegateMock.sol"; import { GemMock } from "test/mocks/GemMock.sol"; -import { NstMock } from "test/mocks/NstMock.sol"; -import { NstJoinMock } from "test/mocks/NstJoinMock.sol"; +import { UsdsMock } from "test/mocks/UsdsMock.sol"; +import { UsdsJoinMock } from "test/mocks/UsdsJoinMock.sol"; import { StakingRewardsMock } from "test/mocks/StakingRewardsMock.sol"; -import { MkrNgtMock } from "test/mocks/MkrNgtMock.sol"; +import { MkrSkyMock } from "test/mocks/MkrSkyMock.sol"; import { LockstakeHandler } from "test/handlers/LockstakeHandler.sol"; interface ChainlogLike { @@ -67,14 +67,14 @@ contract LockstakeEngineIntegrationTest is DssTest { LockstakeClipper public clip; PipMock public pip; VoteDelegateFactoryMock public delFactory; - NstMock public nst; - NstJoinMock public nstJoin; + UsdsMock public usds; + UsdsJoinMock public usdsJoin; LockstakeMkr public lsmkr; GemMock public rTok; StakingRewardsMock public farm0; StakingRewardsMock public farm1; - MkrNgtMock public mkrNgt; - GemMock public ngt; + MkrSkyMock public mkrSky; + GemMock public sky; bytes32 public ilk = "LSE"; address public voter0; address public voter1; @@ -108,14 +108,14 @@ contract LockstakeEngineIntegrationTest is DssTest { dog = DogLike(ChainlogLike(LOG).getAddress("MCD_DOG")); mkr = new GemMock(0); jug = ChainlogLike(LOG).getAddress("MCD_JUG"); - nst = new NstMock(); - nstJoin = new NstJoinMock(address(vat), address(nst)); + usds = new UsdsMock(); + usdsJoin = new UsdsJoinMock(address(vat), address(usds)); lsmkr = new LockstakeMkr(); rTok = new GemMock(0); farm0 = new StakingRewardsMock(address(rTok), address(lsmkr)); farm1 = new StakingRewardsMock(address(rTok), address(lsmkr)); - ngt = new GemMock(0); - mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 25_000); + sky = new GemMock(0); + mkrSky = new MkrSkyMock(address(mkr), address(sky), 25_000); pip = new PipMock(); delFactory = new VoteDelegateFactoryMock(address(mkr)); @@ -125,7 +125,7 @@ contract LockstakeEngineIntegrationTest is DssTest { vm.prank(voter1); voterDelegate1 = delFactory.create(); vm.startPrank(pauseProxy); - engine = new LockstakeEngine(address(delFactory), address(nstJoin), ilk, address(mkrNgt), address(lsmkr)); + engine = new LockstakeEngine(address(delFactory), address(usdsJoin), ilk, address(mkrSky), address(lsmkr)); engine.file("jug", jug); engine.file("fee", 15 * WAD / 100); vat.rely(address(engine)); @@ -169,10 +169,10 @@ contract LockstakeEngineIntegrationTest is DssTest { lsmkr.rely(address(engine)); deal(address(mkr), address(this), 100_000 * 10**18, true); - deal(address(ngt), address(this), 100_000 * 25_000 * 10**18, true); + deal(address(sky), address(this), 100_000 * 25_000 * 10**18, true); - // Add some existing DAI assigned to nstJoin to avoid a particular error - stdstore.target(address(vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); + // Add some existing DAI assigned to usdsJoin to avoid a particular error + stdstore.target(address(vat)).sig("dai(address)").with_key(address(usdsJoin)).depth(0).checked_write(100_000 * RAD); address[] memory voteDelegates = new address[](2); voteDelegates[0] = voterDelegate0; @@ -267,9 +267,9 @@ contract LockstakeEngineIntegrationTest is DssTest { console.log("selectFarm", handler.numCalls("selectFarm")); console.log("selectVoteDelegate", handler.numCalls("selectVoteDelegate")); console.log("lock", handler.numCalls("lock")); - console.log("lockNgt", handler.numCalls("lockNgt")); + console.log("lockSky", handler.numCalls("lockSky")); console.log("free", handler.numCalls("free")); - console.log("freeNgt", handler.numCalls("freeNgt")); + console.log("freeSky", handler.numCalls("freeSky")); console.log("draw", handler.numCalls("draw")); console.log("wipe", handler.numCalls("wipe")); console.log("dropPriceAndBark", handler.numCalls("dropPriceAndBark")); @@ -277,8 +277,8 @@ contract LockstakeEngineIntegrationTest is DssTest { console.log("yank", handler.numCalls("yank")); console.log("warp", handler.numCalls("warp")); console.log("total count", handler.numCalls("addFarm") + handler.numCalls("selectFarm") + handler.numCalls("selectVoteDelegate") + - handler.numCalls("lock") + handler.numCalls("lockNgt") + handler.numCalls("free") + - handler.numCalls("freeNgt") + handler.numCalls("draw") + handler.numCalls("wipe") + + handler.numCalls("lock") + handler.numCalls("lockSky") + handler.numCalls("free") + + handler.numCalls("freeSky") + handler.numCalls("draw") + handler.numCalls("wipe") + handler.numCalls("dropPriceAndBark") + handler.numCalls("take") + handler.numCalls("yank") + handler.numCalls("warp")); } diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 97e007c1..01d399bc 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -12,10 +12,10 @@ import { LockstakeClipper } from "src/LockstakeClipper.sol"; import { LockstakeUrn } from "src/LockstakeUrn.sol"; import { VoteDelegateFactoryMock, VoteDelegateMock } from "test/mocks/VoteDelegateMock.sol"; import { GemMock } from "test/mocks/GemMock.sol"; -import { NstMock } from "test/mocks/NstMock.sol"; -import { NstJoinMock } from "test/mocks/NstJoinMock.sol"; +import { UsdsMock } from "test/mocks/UsdsMock.sol"; +import { UsdsJoinMock } from "test/mocks/UsdsJoinMock.sol"; import { StakingRewardsMock } from "test/mocks/StakingRewardsMock.sol"; -import { MkrNgtMock } from "test/mocks/MkrNgtMock.sol"; +import { MkrSkyMock } from "test/mocks/MkrSkyMock.sol"; interface CalcFabLike { function newLinearDecrease(address) external returns (address); @@ -41,13 +41,13 @@ contract LockstakeEngineTest is DssTest { address calc; MedianAbstract pip; VoteDelegateFactoryMock voteDelegateFactory; - NstMock nst; - NstJoinMock nstJoin; + UsdsMock usds; + UsdsJoinMock usdsJoin; GemMock rTok; StakingRewardsMock farm; StakingRewardsMock farm2; - MkrNgtMock mkrNgt; - GemMock ngt; + MkrSkyMock mkrSky; + GemMock sky; bytes32 ilk = "LSE"; address voter; address voteDelegate; @@ -66,9 +66,9 @@ contract LockstakeEngineTest is DssTest { event SelectVoteDelegate(address indexed owner, uint256 indexed index, address indexed voteDelegate_); event SelectFarm(address indexed owner, uint256 indexed index, address indexed farm, uint16 ref); event Lock(address indexed owner, uint256 indexed index, uint256 wad, uint16 ref); - event LockNgt(address indexed owner, uint256 indexed index, uint256 ngtWad, uint16 ref); + event LockSky(address indexed owner, uint256 indexed index, uint256 skyWad, uint16 ref); event Free(address indexed owner, uint256 indexed index, address to, uint256 wad, uint256 freed); - event FreeNgt(address indexed owner, uint256 indexed index, address to, uint256 ngtWad, uint256 ngtFreed); + event FreeSky(address indexed owner, uint256 indexed index, address to, uint256 skyWad, uint256 skyFreed); event FreeNoFee(address indexed owner, uint256 indexed index, address to, uint256 wad); event Draw(address indexed owner, uint256 indexed index, address to, uint256 wad); event Wipe(address indexed owner, uint256 indexed index, uint256 wad); @@ -92,13 +92,13 @@ contract LockstakeEngineTest is DssTest { pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); pip = MedianAbstract(dss.chainlog.getAddress("PIP_MKR")); mkr = DSTokenAbstract(dss.chainlog.getAddress("MCD_GOV")); - nst = new NstMock(); - nstJoin = new NstJoinMock(address(dss.vat), address(nst)); + usds = new UsdsMock(); + usdsJoin = new UsdsJoinMock(address(dss.vat), address(usds)); rTok = new GemMock(0); - ngt = new GemMock(0); - mkrNgt = new MkrNgtMock(address(mkr), address(ngt), 24_000); + sky = new GemMock(0); + mkrSky = new MkrSkyMock(address(mkr), address(sky), 24_000); vm.startPrank(pauseProxy); - MkrAuthorityLike(mkr.authority()).rely(address(mkrNgt)); + MkrAuthorityLike(mkr.authority()).rely(address(mkrSky)); vm.stopPrank(); voteDelegateFactory = new VoteDelegateFactoryMock(address(mkr)); @@ -112,9 +112,9 @@ contract LockstakeEngineTest is DssTest { address(this), pauseProxy, address(voteDelegateFactory), - address(nstJoin), + address(usdsJoin), ilk, - address(mkrNgt), + address(mkrSky), bytes4(abi.encodeWithSignature("newLinearDecrease(address)")) ); @@ -132,11 +132,11 @@ contract LockstakeEngineTest is DssTest { cfg = LockstakeConfig({ ilk: ilk, voteDelegateFactory: address(voteDelegateFactory), - nstJoin: address(nstJoin), - nst: address(nstJoin.nst()), + usdsJoin: address(usdsJoin), + usds: address(usdsJoin.usds()), mkr: address(mkr), - mkrNgt: address(mkrNgt), - ngt: address(ngt), + mkrSky: address(mkrSky), + sky: address(sky), farms: farms, fee: 15 * WAD / 100, maxLine: 10_000_000 * 10**45, @@ -169,10 +169,10 @@ contract LockstakeEngineTest is DssTest { vm.stopPrank(); deal(address(mkr), address(this), 100_000 * 10**18, true); - deal(address(ngt), address(this), 100_000 * 24_000 * 10**18, true); + deal(address(sky), address(this), 100_000 * 24_000 * 10**18, true); - // Add some existing DAI assigned to nstJoin to avoid a particular error - stdstore.target(address(dss.vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(100_000 * RAD); + // Add some existing DAI assigned to usdsJoin to avoid a particular error + stdstore.target(address(dss.vat)).sig("dai(address)").with_key(address(usdsJoin)).depth(0).checked_write(100_000 * RAD); } function _ink(bytes32 ilk_, address urn) internal view returns (uint256 ink) { @@ -230,14 +230,14 @@ contract LockstakeEngineTest is DssTest { function testDeployAndInit() public { assertEq(address(engine.voteDelegateFactory()), address(voteDelegateFactory)); assertEq(address(engine.vat()), address(dss.vat)); - assertEq(address(engine.nstJoin()), address(nstJoin)); - assertEq(address(engine.nst()), address(nst)); + assertEq(address(engine.usdsJoin()), address(usdsJoin)); + assertEq(address(engine.usds()), address(usds)); assertEq(engine.ilk(), ilk); assertEq(address(engine.mkr()), address(mkr)); assertEq(engine.fee(), 15 * WAD / 100); - assertEq(address(engine.mkrNgt()), address(mkrNgt)); - assertEq(address(engine.ngt()), address(ngt)); - assertEq(engine.mkrNgtRate(), 24_000); + assertEq(address(engine.mkrSky()), address(mkrSky)); + assertEq(address(engine.sky()), address(sky)); + assertEq(engine.mkrSkyRate(), 24_000); assertEq(LockstakeUrn(engine.urnImplementation()).engine(), address(engine)); assertEq(address(LockstakeUrn(engine.urnImplementation()).vat()), address(dss.vat)); assertEq(address(LockstakeUrn(engine.urnImplementation()).lsmkr()), address(lsmkr)); @@ -321,9 +321,9 @@ contract LockstakeEngineTest is DssTest { address(this), pauseProxy, address(voteDelegateFactory), - address(nstJoin), + address(usdsJoin), "eee", - address(mkrNgt), + address(mkrSky), bytes4(abi.encodeWithSignature("newStairstepExponentialDecrease(address)")) ); cfg.ilk = "eee"; @@ -343,24 +343,24 @@ contract LockstakeEngineTest is DssTest { address lsmkr2 = address(new GemMock(0)); vm.expectEmit(true, true, true, true); emit Rely(address(this)); - LockstakeEngine e = new LockstakeEngine(address(voteDelegateFactory), address(nstJoin), "aaa", address(mkrNgt), lsmkr2); + LockstakeEngine e = new LockstakeEngine(address(voteDelegateFactory), address(usdsJoin), "aaa", address(mkrSky), lsmkr2); assertEq(address(e.voteDelegateFactory()), address(voteDelegateFactory)); - assertEq(address(e.nstJoin()), address(nstJoin)); + assertEq(address(e.usdsJoin()), address(usdsJoin)); assertEq(address(e.vat()), address(dss.vat)); - assertEq(address(e.nst()), address(nst)); + assertEq(address(e.usds()), address(usds)); assertEq(e.ilk(), "aaa"); assertEq(address(e.mkr()), address(mkr)); assertEq(address(e.lsmkr()), lsmkr2); - assertEq(address(e.mkrNgt()), address(mkrNgt)); - assertEq(address(e.ngt()), address(ngt)); - assertEq(e.mkrNgtRate(), 24_000); + assertEq(address(e.mkrSky()), address(mkrSky)); + assertEq(address(e.sky()), address(sky)); + assertEq(e.mkrSkyRate(), 24_000); assertEq(LockstakeUrn(e.urnImplementation()).engine(), address(e)); assertEq(address(LockstakeUrn(e.urnImplementation()).vat()), address(dss.vat)); assertEq(address(LockstakeUrn(e.urnImplementation()).lsmkr()), lsmkr2); - assertEq(dss.vat.can(address(e), address(nstJoin)), 1); - assertEq(nst.allowance(address(e), address(nstJoin)), type(uint256).max); - assertEq(ngt.allowance(address(e), address(mkrNgt)), type(uint256).max); - assertEq(mkr.allowance(address(e), address(mkrNgt)), type(uint256).max); + assertEq(dss.vat.can(address(e), address(usdsJoin)), 1); + assertEq(usds.allowance(address(e), address(usdsJoin)), type(uint256).max); + assertEq(sky.allowance(address(e), address(mkrSky)), type(uint256).max); + assertEq(mkr.allowance(address(e), address(mkrSky)), type(uint256).max); assertEq(e.wards(address(this)), 1); } @@ -456,11 +456,11 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/invalid-urn"); engine.lock(address(this), 1, 1, 1); vm.expectRevert("LockstakeEngine/invalid-urn"); - engine.lockNgt(address(this), 1, 1, 1); + engine.lockSky(address(this), 1, 1, 1); vm.expectRevert("LockstakeEngine/invalid-urn"); engine.free(address(this), 1, address(123), 1); vm.expectRevert("LockstakeEngine/invalid-urn"); - engine.freeNgt(address(this), 1, address(123), 1); + engine.freeSky(address(this), 1, address(123), 1); vm.expectRevert("LockstakeEngine/invalid-urn"); vm.prank(pauseProxy); engine.freeNoFee(address(this), 1, address(123), 1); vm.expectRevert("LockstakeEngine/invalid-urn"); @@ -487,7 +487,7 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/urn-not-authorized"); engine.free(address(123), 0, address(123), 1); vm.expectRevert("LockstakeEngine/urn-not-authorized"); - engine.freeNgt(address(123), 0, address(123), 1); + engine.freeSky(address(123), 0, address(123), 1); vm.expectRevert("LockstakeEngine/urn-not-authorized"); vm.prank(pauseProxy); engine.freeNoFee(address(123), 0, address(123), 1); vm.expectRevert("LockstakeEngine/urn-not-authorized"); @@ -504,7 +504,7 @@ contract LockstakeEngineTest is DssTest { engine.rely(authedAndUrnAuthed); vm.stopPrank(); mkr.transfer(urnAuthed, 100_000 * 10**18); - ngt.transfer(urnAuthed, 100_000 * 24_000 * 10**18); + sky.transfer(urnAuthed, 100_000 * 24_000 * 10**18); vm.startPrank(urnOwner); address urn = engine.open(0); assertTrue(engine.isUrnAuth(urnOwner, 0, urnOwner)); @@ -526,15 +526,15 @@ contract LockstakeEngineTest is DssTest { assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.free(urnOwner, 0, address(this), 50_000 * 10**18); assertEq(_ink(ilk, urn), 50_000 * 10**18); - ngt.approve(address(engine), 100_000 * 24_000 * 10**18); - engine.lockNgt(urnOwner, 0, 100_000 * 24_000 * 10**18, 0); + sky.approve(address(engine), 100_000 * 24_000 * 10**18); + engine.lockSky(urnOwner, 0, 100_000 * 24_000 * 10**18, 0); assertEq(_ink(ilk, urn), 150_000 * 10**18); - engine.freeNgt(urnOwner, 0, address(this), 50_000 * 24_000 * 10**18); + engine.freeSky(urnOwner, 0, address(this), 50_000 * 24_000 * 10**18); assertEq(_ink(ilk, urn), 100_000 * 10**18); engine.selectVoteDelegate(urnOwner, 0, voteDelegate); assertEq(engine.urnVoteDelegates(urn), voteDelegate); engine.draw(urnOwner, 0, address(urnAuthed), 1); - nst.approve(address(engine), 1); + usds.approve(address(engine), 1); engine.wipe(urnOwner, 0, 1); engine.selectFarm(urnOwner, 0, address(farm), 0); engine.getReward(urnOwner, 0, address(farm), address(0)); @@ -719,10 +719,10 @@ contract LockstakeEngineTest is DssTest { _testLockFree(true, true); } - function _testLockFreeNgt(bool withDelegate, bool withStaking) internal { - uint256 initialNgtSupply = ngt.totalSupply(); + function _testLockFreeSky(bool withDelegate, bool withStaking) internal { + uint256 initialSkySupply = sky.totalSupply(); address urn = engine.open(0); - // Note: overflow cannot be reached for lockNgt and freeNgt as with these functions and the value of rate (>=3) the MKR amount will be always lower + // Note: overflow cannot be reached for lockSky and freeSky as with these functions and the value of rate (>=3) the MKR amount will be always lower if (withDelegate) { engine.selectVoteDelegate(address(this), 0, voteDelegate); } @@ -731,10 +731,10 @@ contract LockstakeEngineTest is DssTest { } assertEq(_ink(ilk, urn), 0); assertEq(lsmkr.balanceOf(urn), 0); - ngt.approve(address(engine), 100_000 * 24_000 * 10**18); + sky.approve(address(engine), 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); - emit LockNgt(address(this), 0, 100_000 * 24_000 * 10**18, 5); - engine.lockNgt(address(this), 0, 100_000 * 24_000 * 10**18, 5); + emit LockSky(address(this), 0, 100_000 * 24_000 * 10**18, 5); + engine.lockSky(address(this), 0, 100_000 * 24_000 * 10**18, 5); assertEq(_ink(ilk, urn), 100_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 100_000 * 10**18); @@ -742,17 +742,17 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(lsmkr.balanceOf(urn), 100_000 * 10**18); } - assertEq(ngt.balanceOf(address(this)), 0); + assertEq(sky.balanceOf(address(this)), 0); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.balanceOf(voteDelegate), 100_000 * 10**18); // Remains in voteDelegate as it is a mock (otherwise it would be in the Chief) } else { assertEq(mkr.balanceOf(address(engine)), 100_000 * 10**18); } - assertEq(ngt.totalSupply(), initialNgtSupply - 100_000 * 24_000 * 10**18); + assertEq(sky.totalSupply(), initialSkySupply - 100_000 * 24_000 * 10**18); vm.expectEmit(true, true, true, true); - emit FreeNgt(address(this), 0, address(this), 40_000 * 24_000 * 10**18, 40_000 * 24_000 * 10**18 * 85 / 100); - assertEq(engine.freeNgt(address(this), 0, address(this), 40_000 * 24_000 * 10**18), 40_000 * 24_000 * 10**18 * 85 / 100); + emit FreeSky(address(this), 0, address(this), 40_000 * 24_000 * 10**18, 40_000 * 24_000 * 10**18 * 85 / 100); + assertEq(engine.freeSky(address(this), 0, address(this), 40_000 * 24_000 * 10**18), 40_000 * 24_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 60_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 60_000 * 10**18); @@ -760,7 +760,7 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(lsmkr.balanceOf(urn), 60_000 * 10**18); } - assertEq(ngt.balanceOf(address(this)), 40_000 * 24_000 * 10**18 - 40_000 * 24_000 * 10**18 * 15 / 100); + assertEq(sky.balanceOf(address(this)), 40_000 * 24_000 * 10**18 - 40_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.balanceOf(voteDelegate), 60_000 * 10**18); @@ -768,8 +768,8 @@ contract LockstakeEngineTest is DssTest { assertEq(mkr.balanceOf(address(engine)), 60_000 * 10**18); } vm.expectEmit(true, true, true, true); - emit FreeNgt(address(this), 0, address(123), 10_000 * 24_000 * 10**18, 10_000 * 24_000 * 10**18 * 85 / 100); - assertEq(engine.freeNgt(address(this), 0, address(123), 10_000 * 24_000 * 10**18), 10_000 * 24_000 * 10**18 * 85 / 100); + emit FreeSky(address(this), 0, address(123), 10_000 * 24_000 * 10**18, 10_000 * 24_000 * 10**18 * 85 / 100); + assertEq(engine.freeSky(address(this), 0, address(123), 10_000 * 24_000 * 10**18), 10_000 * 24_000 * 10**18 * 85 / 100); assertEq(_ink(ilk, urn), 50_000 * 10**18); if (withStaking) { assertEq(lsmkr.balanceOf(address(farm)), 50_000 * 10**18); @@ -777,36 +777,36 @@ contract LockstakeEngineTest is DssTest { } else { assertEq(lsmkr.balanceOf(urn), 50_000 * 10**18); } - assertEq(ngt.balanceOf(address(123)), 10_000 * 24_000 * 10**18 - 10_000 * 24_000 * 10**18 * 15 / 100); + assertEq(sky.balanceOf(address(123)), 10_000 * 24_000 * 10**18 - 10_000 * 24_000 * 10**18 * 15 / 100); if (withDelegate) { assertEq(mkr.balanceOf(address(engine)), 0); assertEq(mkr.balanceOf(voteDelegate), 50_000 * 10**18); } else { assertEq(mkr.balanceOf(address(engine)), 50_000 * 10**18); } - assertEq(ngt.totalSupply(), initialNgtSupply - (100_000 - 50_000) * 24_000 * 10**18 - 50_000 * 24_000 * 10**18 * 15 / 100); + assertEq(sky.totalSupply(), initialSkySupply - (100_000 - 50_000) * 24_000 * 10**18 - 50_000 * 24_000 * 10**18 * 15 / 100); if (withStaking) { - ngt.approve(address(engine), 24_000); + sky.approve(address(engine), 24_000); vm.prank(pauseProxy); engine.delFarm(address(farm)); vm.expectRevert("LockstakeEngine/farm-deleted"); - engine.lockNgt(address(this), 0, 24_000, 0); + engine.lockSky(address(this), 0, 24_000, 0); } } - function testLockFreeNgtNoDelegateNoStaking() public { - _testLockFreeNgt(false, false); + function testLockFreeSkyNoDelegateNoStaking() public { + _testLockFreeSky(false, false); } - function testLockFreeNgtWithDelegateNoStaking() public { - _testLockFreeNgt(true, false); + function testLockFreeSkyWithDelegateNoStaking() public { + _testLockFreeSky(true, false); } - function testLockFreeNgtNoDelegateWithStaking() public { - _testLockFreeNgt(false, true); + function testLockFreeSkyNoDelegateWithStaking() public { + _testLockFreeSky(false, true); } - function testLockFreeNgtWithDelegateWithStaking() public { - _testLockFreeNgt(true, true); + function testLockFreeSkyWithDelegateWithStaking() public { + _testLockFreeSky(true, true); } function _testFreeNoFee(bool withDelegate, bool withStaking) internal { @@ -903,7 +903,7 @@ contract LockstakeEngineTest is DssTest { engine.draw(address(this), 0, address(this), 50 * 10**18); assertEq(_art(ilk, urn), 50 * 10**18); assertEq(_rate(ilk), 10**27); - assertEq(nst.balanceOf(address(this)), 50 * 10**18); + assertEq(usds.balanceOf(address(this)), 50 * 10**18); vm.warp(block.timestamp + 1); vm.expectEmit(true, true, true, true); emit Draw(address(this), 0, address(this), 50 * 10**18); @@ -913,39 +913,39 @@ contract LockstakeEngineTest is DssTest { assertEq(art, expectedArt); uint256 rate = _rate(ilk); assertEq(rate, 100000001 * 10**27 / 100000000); - assertEq(nst.balanceOf(address(this)), 100 * 10**18); + assertEq(usds.balanceOf(address(this)), 100 * 10**18); assertGt(art * rate, 100.0000005 * 10**45); assertLt(art * rate, 100.0000006 * 10**45); - vm.expectRevert("Nst/insufficient-balance"); + vm.expectRevert("Usds/insufficient-balance"); engine.wipe(address(this), 0, 100.0000006 * 10**18); address anyone = address(1221121); - deal(address(nst), anyone, 100.0000006 * 10**18, true); - assertEq(nst.balanceOf(anyone), 100.0000006 * 10**18); - vm.prank(anyone); nst.approve(address(engine), 100.0000006 * 10**18); + deal(address(usds), anyone, 100.0000006 * 10**18, true); + assertEq(usds.balanceOf(anyone), 100.0000006 * 10**18); + vm.prank(anyone); usds.approve(address(engine), 100.0000006 * 10**18); vm.expectRevert(); vm.prank(anyone); engine.wipe(address(this), 0, 100.0000006 * 10**18); // It will try to wipe more art than existing, then reverts vm.expectEmit(true, true, true, true); emit Wipe(address(this), 0, 100.0000005 * 10**18); vm.prank(anyone); engine.wipe(address(this), 0, 100.0000005 * 10**18); - assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18); + assertEq(usds.balanceOf(anyone), 0.0000001 * 10**18); assertEq(_art(ilk, urn), 1); // Dust which is impossible to wipe via this regular function emit Wipe(address(this), 0, _divup(rate, RAY)); vm.prank(anyone); assertEq(engine.wipeAll(address(this), 0), _divup(rate, RAY)); assertEq(_art(ilk, urn), 0); - assertEq(nst.balanceOf(anyone), 0.0000001 * 10**18 - _divup(rate, RAY)); + assertEq(usds.balanceOf(anyone), 0.0000001 * 10**18 - _divup(rate, RAY)); address other = address(123); - assertEq(nst.balanceOf(other), 0); + assertEq(usds.balanceOf(other), 0); emit Draw(address(this), 0, other, 50 * 10**18); engine.draw(address(this), 0, other, 50 * 10**18); - assertEq(nst.balanceOf(other), 50 * 10**18); + assertEq(usds.balanceOf(other), 50 * 10**18); // Check overflows stdstore.target(address(dss.vat)).sig("ilks(bytes32)").with_key(ilk).depth(1).checked_write(1); assertEq(_rate(ilk), 1); vm.expectRevert("LockstakeEngine/overflow"); engine.draw(address(this), 0, address(this), uint256(type(int256).max) / RAY + 1); - stdstore.target(address(dss.vat)).sig("dai(address)").with_key(address(nstJoin)).depth(0).checked_write(uint256(type(int256).max) + RAY); - deal(address(nst), address(this), uint256(type(int256).max) / RAY + 1, true); - nst.approve(address(engine), uint256(type(int256).max) / RAY + 1); + stdstore.target(address(dss.vat)).sig("dai(address)").with_key(address(usdsJoin)).depth(0).checked_write(uint256(type(int256).max) + RAY); + deal(address(usds), address(this), uint256(type(int256).max) / RAY + 1, true); + usds.approve(address(engine), uint256(type(int256).max) / RAY + 1); vm.expectRevert("LockstakeEngine/overflow"); engine.wipe(address(this), 0, uint256(type(int256).max) / RAY + 1); stdstore.target(address(dss.vat)).sig("urns(bytes32,address)").with_key(ilk).with_key(urn).depth(1).checked_write(uint256(type(int256).max) + 1); diff --git a/test/handlers/LockstakeHandler.sol b/test/handlers/LockstakeHandler.sol index 1ab601c7..6e0068ee 100644 --- a/test/handlers/LockstakeHandler.sol +++ b/test/handlers/LockstakeHandler.sol @@ -38,8 +38,8 @@ contract LockstakeHandler is StdUtils, StdCheats { LockstakeEngine public engine; GemMock public mkr; - GemMock public ngt; - GemMock public nst; + GemMock public sky; + GemMock public usds; bytes32 public ilk; VatLike public vat; JugLike public jug; @@ -53,7 +53,7 @@ contract LockstakeHandler is StdUtils, StdCheats { address public urn; address[] public voteDelegates; address[] public farms; - uint256 public mkrNgtRate; + uint256 public mkrSkyRate; address public anyone = address(123); mapping(bytes32 => uint256) public numCalls; @@ -92,8 +92,8 @@ contract LockstakeHandler is StdUtils, StdCheats { vm = vm_; engine = LockstakeEngine(engine_); mkr = GemMock(address(engine.mkr())); - ngt = GemMock(address(engine.ngt())); - nst = GemMock(address(engine.nst())); + sky = GemMock(address(engine.sky())); + usds = GemMock(address(engine.usds())); pauseProxy = pauseProxy_; ilk = engine.ilk(); vat = VatLike(address(engine.vat())); @@ -106,7 +106,7 @@ contract LockstakeHandler is StdUtils, StdCheats { owner = owner_; index = index_; urn = engine.ownerUrns(owner, index); - mkrNgtRate = engine.mkrNgtRate(); + mkrSkyRate = engine.mkrSkyRate(); vat.hope(address(clip)); @@ -230,25 +230,25 @@ contract LockstakeHandler is StdUtils, StdCheats { engine.lock(owner, index, wad, ref); } - function lockNgt(uint256 ngtWad, uint16 ref) external callAsAnyone { - numCalls["lockNgt"]++; + function lockSky(uint256 skyWad, uint16 ref) external callAsAnyone { + numCalls["lockSky"]++; - // ngtWad = bound(ngtWad, 0, uint256(type(int256).max) / 10**18) * 10**18; + // skyWad = bound(skyWad, 0, uint256(type(int256).max) / 10**18) * 10**18; (uint256 ink,) = vat.urns(ilk, urn); (,, uint256 spotPrice,,) = vat.ilks(ilk); - ngtWad = bound(ngtWad, 0, _min( + skyWad = bound(skyWad, 0, _min( uint256(type(int256).max), _min( type(uint256).max / spotPrice - ink, - type(uint256).max / mkrNgtRate + type(uint256).max / mkrSkyRate ) ) / 10**18 - ) * 10**18 * mkrNgtRate; + ) * 10**18 * mkrSkyRate; - deal(address(ngt), anyone, ngtWad); - ngt.approve(address(engine), ngtWad); + deal(address(sky), anyone, skyWad); + sky.approve(address(engine), skyWad); - engine.lockNgt(owner, index, ngtWad, ref); + engine.lockSky(owner, index, skyWad, ref); } function free(address to, uint256 wad) external callAsUrnOwner() { @@ -263,14 +263,14 @@ contract LockstakeHandler is StdUtils, StdCheats { engine.free(owner, index, to, wad); } - function freeNgt(address to, uint256 ngtWad) external callAsUrnOwner() { - numCalls["freeNgt"]++; + function freeSky(address to, uint256 skyWad) external callAsUrnOwner() { + numCalls["freeSky"]++; (uint256 ink, uint256 art ) = vat.urns(ilk, urn); (, uint256 rate, uint256 spotPrice,,) = vat.ilks(ilk); - ngtWad = bound(ngtWad, 0, (ink - _divup(art * rate, spotPrice)) * mkrNgtRate); + skyWad = bound(skyWad, 0, (ink - _divup(art * rate, spotPrice)) * mkrSkyRate); - engine.freeNgt(owner, index, to, ngtWad); + engine.freeSky(owner, index, to, skyWad); } function draw(uint256 wad) external callAsUrnOwner() { @@ -303,8 +303,8 @@ contract LockstakeHandler is StdUtils, StdCheats { ) : 0); - deal(address(nst), anyone, wad); - nst.approve(address(engine), wad); + deal(address(usds), anyone, wad); + usds.approve(address(engine), wad); engine.wipe(owner, index, wad); } diff --git a/test/mocks/MkrNgtMock.sol b/test/mocks/MkrNgtMock.sol deleted file mode 100644 index a0700e3a..00000000 --- a/test/mocks/MkrNgtMock.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.21; - -interface GemLike { - function burn(address, uint256) external; - function mint(address, uint256) external; -} - -contract MkrNgtMock { - GemLike public immutable mkr; - GemLike public immutable ngt; - uint256 public immutable rate; - - constructor(address mkr_, address ngt_, uint256 rate_) { - mkr = GemLike(mkr_); - ngt = GemLike(ngt_); - rate = rate_; - } - - function mkrToNgt(address usr, uint256 mkrAmt) external { - mkr.burn(msg.sender, mkrAmt); - uint256 ngtAmt = mkrAmt * rate; - ngt.mint(usr, ngtAmt); - } - - function ngtToMkr(address usr, uint256 ngtAmt) external { - ngt.burn(msg.sender, ngtAmt); - uint256 mkrAmt = ngtAmt / rate; - mkr.mint(usr, mkrAmt); - } -} diff --git a/test/mocks/MkrSkyMock.sol b/test/mocks/MkrSkyMock.sol new file mode 100644 index 00000000..f86c054e --- /dev/null +++ b/test/mocks/MkrSkyMock.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.21; + +interface GemLike { + function burn(address, uint256) external; + function mint(address, uint256) external; +} + +contract MkrSkyMock { + GemLike public immutable mkr; + GemLike public immutable sky; + uint256 public immutable rate; + + constructor(address mkr_, address sky_, uint256 rate_) { + mkr = GemLike(mkr_); + sky = GemLike(sky_); + rate = rate_; + } + + function mkrToSky(address usr, uint256 mkrAmt) external { + mkr.burn(msg.sender, mkrAmt); + uint256 skyAmt = mkrAmt * rate; + sky.mint(usr, skyAmt); + } + + function skyToMkr(address usr, uint256 skyAmt) external { + sky.burn(msg.sender, skyAmt); + uint256 mkrAmt = skyAmt / rate; + mkr.mint(usr, mkrAmt); + } +} diff --git a/test/mocks/NstJoinMock.sol b/test/mocks/UsdsJoinMock.sol similarity index 67% rename from test/mocks/NstJoinMock.sol rename to test/mocks/UsdsJoinMock.sol index 5c3f25f6..6ab21bb2 100644 --- a/test/mocks/NstJoinMock.sol +++ b/test/mocks/UsdsJoinMock.sol @@ -8,22 +8,22 @@ interface VatLike { function move(address, address, uint256) external; } -contract NstJoinMock { +contract UsdsJoinMock { VatLike public vat; - GemMock public nst; + GemMock public usds; - constructor(address vat_, address nst_) { - vat = VatLike(vat_); - nst = GemMock(nst_); + constructor(address vat_, address usds_) { + vat = VatLike(vat_); + usds = GemMock(usds_); } function join(address usr, uint256 wad) external { vat.move(address(this), usr, wad * 10**27); - nst.burn(msg.sender, wad); + usds.burn(msg.sender, wad); } function exit(address usr, uint256 wad) external { vat.move(msg.sender, address(this), wad * 10**27); - nst.mint(usr, wad); + usds.mint(usr, wad); } } diff --git a/test/mocks/NstMock.sol b/test/mocks/UsdsMock.sol similarity index 84% rename from test/mocks/NstMock.sol rename to test/mocks/UsdsMock.sol index a5bf6158..45bcdc19 100644 --- a/test/mocks/NstMock.sol +++ b/test/mocks/UsdsMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.21; -contract NstMock { +contract UsdsMock { mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; @@ -19,7 +19,7 @@ contract NstMock { function transfer(address to, uint256 value) external returns (bool) { uint256 balance = balanceOf[msg.sender]; - require(balance >= value, "Nst/insufficient-balance"); + require(balance >= value, "Usds/insufficient-balance"); unchecked { balanceOf[msg.sender] = balance - value; @@ -30,12 +30,12 @@ contract NstMock { function transferFrom(address from, address to, uint256 value) external returns (bool) { uint256 balance = balanceOf[from]; - require(balance >= value, "Nst/insufficient-balance"); + require(balance >= value, "Usds/insufficient-balance"); if (from != msg.sender) { uint256 allowed = allowance[from][msg.sender]; if (allowed != type(uint256).max) { - require(allowed >= value, "Nst/insufficient-allowance"); + require(allowed >= value, "Usds/insufficient-allowance"); unchecked { allowance[from][msg.sender] = allowed - value; @@ -59,12 +59,12 @@ contract NstMock { function burn(address from, uint256 value) external { uint256 balance = balanceOf[from]; - require(balance >= value, "Nst/insufficient-balance"); + require(balance >= value, "Usds/insufficient-balance"); if (from != msg.sender) { uint256 allowed = allowance[from][msg.sender]; if (allowed != type(uint256).max) { - require(allowed >= value, "Nst/insufficient-allowance"); + require(allowed >= value, "Usds/insufficient-allowance"); unchecked { allowance[from][msg.sender] = allowed - value; From 7a93cb40f40f861bb8aa947e4e5fc54ac1b23d22 Mon Sep 17 00:00:00 2001 From: sunbreak1211 <129470872+sunbreak1211@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:43:43 -0300 Subject: [PATCH 104/111] Update CS audit report (#60) --- ...hainSecurity_MakerDAO_Lockstake_audit.pdf} | Bin 553966 -> 560665 bytes 1 file changed, 0 insertions(+), 0 deletions(-) rename audit/{20240730-ChainSecurity_MakerDAO_Lockstake_audit.pdf => 20240909-ChainSecurity_MakerDAO_Lockstake_audit.pdf} (95%) diff --git a/audit/20240730-ChainSecurity_MakerDAO_Lockstake_audit.pdf b/audit/20240909-ChainSecurity_MakerDAO_Lockstake_audit.pdf similarity index 95% rename from audit/20240730-ChainSecurity_MakerDAO_Lockstake_audit.pdf rename to audit/20240909-ChainSecurity_MakerDAO_Lockstake_audit.pdf index 0c9713b3d9e0a89a334db657ff96447acb00d390..f42eed5877d0f3715c770752d5b1ed9c8ac0bfe3 100644 GIT binary patch delta 16487 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 delta 13682 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 From 304c8852aba9bec7fce3d69b7aa305d3d15823bc Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:50:18 +0300 Subject: [PATCH 105/111] Add new Cantina audit report (#61) --- ...240909-cantina-report-maker-LSE-updates.pdf | Bin 0 -> 464230 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/20240909-cantina-report-maker-LSE-updates.pdf diff --git a/audit/20240909-cantina-report-maker-LSE-updates.pdf b/audit/20240909-cantina-report-maker-LSE-updates.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ade4cae455bb660b14aedeafa15049eb4116c6ac GIT binary patch literal 464230 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

)GYSf=CZE#1q3& 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 F{|Ad&%NYOw literal 0 HcmV?d00001 From 445cd304bab69ff79c15ab947cb48bbe8aadf400 Mon Sep 17 00:00:00 2001 From: oldchili <130549691+oldchili@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:44:44 +0300 Subject: [PATCH 106/111] Add Sherlock Report (#62) --- ...-Sherlock_MakerDAO_Endgame_Audit_Report.pdf | Bin 0 -> 317011 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/20240917-Sherlock_MakerDAO_Endgame_Audit_Report.pdf diff --git a/audit/20240917-Sherlock_MakerDAO_Endgame_Audit_Report.pdf b/audit/20240917-Sherlock_MakerDAO_Endgame_Audit_Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a1c7bf95fad33047e636e023467dd2956e702353 GIT binary patch literal 317011 zcmagGXFyZQ7dIS*4MvfwYZNIeO%npTr` z%ZbQ{IFK){R#90kt!HI#<9tCxVULUm^S{;7S`=GntIHzNTDF$XRtK!kIh?mzt){m6 zit}YFOS{$X-(MXwH9SM&^eHKM8}_FB@SG7bB)HZ!Mx3^vA}zQnCFRnqv5}tE z))rUJJ9p$CKaZoiRy%QN>8#)EVRZDNKHk(3|M%UTi6)(qto)tpBpnaYx0OBefBG{d zPw_9VHP@Vj=yMV4LQ>xej&@tlKhSuXe7o$e;h~m=<_Cru?Sn%*C>G;Mr*7XEI+{{s zGAH)X=1%5kMWu$r+AEX{1o{NU4@e$+bj&wzu{Ot`E#jh1+xp!^fr0Sd;hLs}!DoLw zda|K9Q^DZWO%)<}KxYWX{x4xI6;PMmAq#}(_?^h3$5zNM(7`v%`D%kt}wJ~@h> z4prOMmOsVI*KyirNA_Rq&!oqUb7zd{;(Nkhg)2r)v~%5zV@va!?WgnNPw>bk5iFi} z_2L3ZvA32A?MIT6MGR)zqXg?D=i7=6i<8P4bGFtFP}TaWAC}4}nMK6S8`{d|t@lOL zvezgb;(TLyq3B|e?dz$3kId~l9Yd&_-*rC3z^O=flS^IBC*5VQ!n}he2Wy-CYsY&U zE_F(d&D=gn_FJbr;M`rJP-uG3z(oDtgU)fPS#icGHCy>uw~#fGY8g@v8QQ+ria6!Z zbw3q-^npiBN|($&vE|IpCr|y*>m6gW5>tsJwq!kJDW@`!a9^YY2+F!eRyz6{DWkTZV!Un&$;s=X&C=&xj z4vHyRE)}L)=Y8oVct{?bi0+ttNxf99VZL>PJJ*FvbFWuD3b}oD@pMEtZY;;BFG1_Z zb#93mlCEc}<=lhy@&WpPO-ga+hB6-2E($DMPElWNWq+RK63=!i&p2HS4$hake#t7I(C*eR;#5?P|iajk@hIctd>4xMX|Zy41dVVua@3NalT?? zb$P#oouh-jmA$iw;%e#r4z>=LjU6q|0mTDWu9S0DN456>O?}HNmtYzRmOc6>IQPiO zv%>TL9h`%0Z3f#;J?gPbh*ET#&(?YTt?mt*ePYIq7D;y3CIj8a-#tuQ_g}gcUD0&F zn&1@AU!k~}@W4XAmv(w=VY2tw7pl#d&l#=(#X(bU&&74ZpPaeu+~E**w_@^{*HFR@ zQJrv6onuma3k*-^>3AtMI)rIYeO3P(Jq_Q{)c4_=M~}F=m}Io-IY;La;eWB=-?mfq z0hV8w zA&)jXa4%WvVvjtdhQ=I|-V=TgId#d9|JmRd&wiQ)dY|m~QLR#<#p_;a&{b@qAGwi~ zmD}*>o#?w-*R($q?LXA|WGXi=Z@0Iae@uzdnC{ zxCr9)XtVC^;k%a;_!LQZ5M7FTQ9 zx6$Guub)|285d@_@usfEC}pEMCxJ=%II6LB(jV2am=XP{##FG(hYwE zdVXelg~!PuC-#CV1tdFGY?$N^;qL?~&-2F4LN?G;wV9AJ#~irk1|a5{Nd#wsHG=PAYmYsX`%DczxC|#p|+Uf?_;iW!Qd=Y(8w(5 zssGrym`eC~MeJeBCZ@^+RqENbI57)(DvA9k?0Y?aB1@_96n~M8%-=()pQfAsFbAZz zDv=z;G?7s!t6zrQ`;S$xSn)%f)J_SoXx#fx;={Lxz1o7m)J7_iX3K*y=jgL}0G6Pu z|Ah!8d!!O7wK=P)%fsrt=z+QdVF;}apvE@nJ-P| z{#$qiqOM{EOH*?7>@4N1@6QwZGYHnw2sY$gnjb3j-6saCQ4-?f&%So@V@B4|D%lke z=d$CKFr6f~MQ(135ohuI*gE>RRKHCtOJe%wZhb#yU>uGABX76QoCWd03ZUy!E$8al=}ZR)b6Om5>ZB2N68>f{SGh%1TyPO?20@O9_^m zy~e`z@v{fwEFoET{OBmQg`&530MT=9sxD~U))BahspAiSsP9Yi?2o`PNh8)-iyj2w zn5m^|k3R0-vw!@3a!G5-d73l(%PA)FN4wyCt(i`5DwFc_Z+#yk1vprefzT{6OjELK z1v+IddHQ@bjw#M|b%+n#LO+eE)W;Xav}aNYEQx@Qp9?ceehy9k>My#|8NkhA0Gyw> zMsk|*%6fLz5@DDP{RBgav5QM>VW+@fwkPWGr{}n4pR5EtuJM8dUoIN;6~B%d>6;mY>uRt+fdyK7Vn^N=C8U(bFx=jyppLiWK1SG%8}{e#_Zo?Agoyf9u5}UPv35Aq2idy z|M!c!qWZf#6}VXb47}i&nf3boa}M~!0Od239dIZ3&@Wa*IecHi3;V%nI3t7N;jh?6GPLIp^Z$u~*^wm@WJ;Fiy1{9fuEMk)?omg#{IpHu80*fBwzN zjeb6HL@Yy?bOU|n438ww?+#(b=6}&GFm5BoK`)rc)D>k9zY^eJ{(+3jRE$Y_hbP2~ zhU7N9O^NSJ;TceZ+n=RSMtSXo)2NZa`zoS@_`QAmRj?RFn__m^*&!PDf3p{VHqOny zD3qPa?ti?2;?n1Xu$?~npn3meAcUzAc{NU5nRMv(a535pzkLSoFs8B14#(tF=u`XX zeR0-u$eS>UAJ4&m0a^Ff%55Oi)&Wp|FhyQyIq8>4?p3h0Zvp178fQXGi&v>D!<< zW)NI|sX#OU^2j+c6FZ_^Ag2-pqj_RZeMS)eKK`49qh`~&Tf8Rdg6nW5nwiQj$QO#k ze=A5<+RKYxfpjvO3OOH%iE(=LA$6b(NMf4!^R(^}Gw%G>XNro3KF%`)DCeVV0ZPKO z-OWshepM*nA3clqAEr_tPrLWgAtRcuWfH6sk^s+O8e#&aM5b0Io1#1~{w4+q73Wv} zJ8kbc7B%z&n8vh<92TeR&hqT!fQZG_^1sO;^IuVNyO83E=C$mc9}u}y7c zC(bV)X`fR6TX&a@?oZlBkh`nr7ulM^H&=3Y6yfG$q0WGV~O_8=JpCEJ^{rgA!j z7}vR&qM&uW6iJos&K^D6!Os8%+HYnm5+MjCU#u*M*%4C}#v_>S=|pvCMB z824!C@9@D~99aeUL80_FG9G$J0XTz$e0N8*>(3*I`;GyFg?Z30@^3T%rEF%DmGt3o zO-4)?CVvD*FbOA^DG%2)9C_{5w8BV^*mfG?44GcmK~0}t#~R2=d5B9aZZOUN&Uj4b z5tk8nAT2xRGxcu8awf!xe?Cg)?Zt=dGL#3*3Y~D6BCz1a82PPsBs``C#DPh^GYT(k z0Hy?!U%LCQ{Dk7`xqDyfZyA_xrgirz$bki=$lh<}~C8M-GdaJvvs0G9+fD zQ_LiXa}NM)8Tr~7#UL6}SD(U$q{ylbz6x?|RY8yiTfXnbVA_M<)3F5+f2%Q=!E_vq zX%ssM^5Ae>a%9J8hMTaCXyg;{ox!xb!nI7bVt$*b$IRT$HQ`ANKp7mnnJ$7>N0hrU zNDg#B-&h*PS;iAZwP)0-4}JG+{_;0uKg?af38fLV0FUEvk2WIc+9!y-0nLhtTAXy$ zWpa;sHzpt!YuEy;acUcNK7OK#f0o)gM}T;WR42_20dar^x#E*=00#-6T`F2#=%CMet%h z(^!>EPmNYJz$}4kBBX2Z^W=RjQz4JWW&xJD$x^#txVcXL1>cD=eH4$l(LowymYhOB z!f2!)&s2ditnq*HR8$lW;(PYULN*^O2X=!-&@9Ym^t~tek|Way;Wvag;cyzqe#-%K zz<1U7bfr<91S{>Jl7R?w(b%1Y-TF263t;2HjfU{DVcMa0MY3szGWs$ME~DkEe?eTg{Qs8Q(VW5t2oLi7Wis?CFl!*@1fMWBCY*)`plzgM zoEgGQiK=In6h~TT!vsL?lPGVqcp#E$8l(l$_KpkEJm(1>op5C;{>@iojQxL25#GXn z7O7zMXhQ)rm=dA3WRg&~q-kJ=b9X{sz+`0{!`Y-$%xwvWyR5~j$3JU`#8$7puS7~W zX)bAF&YA$Z18(Z^i>{S+ht~~?pfSu#aPcHEPZws#?|2-JZW=c;+!C8k3z z3uS|;JKQk@^1=^$;qD4fmdMI;IMFytrm`yIy~{g8Z0o?4X7dL$2Wd_ja2gn9R4Xf( z0Ri0TQ&fSL06vEQU`u@@Ou{l4Zoo|E(CJReOi^!C`oc?#PX2)VNbB&sG&chP31u{j zi}j}==So4=#KOXGo`#^ZQ#0Q&ryTr2OI%>dZLxfUPgj}j*ery0Kb+tGIAX*_8kUxE z5oK9>_s&MLW4>V|6(~SbM96z-7aDjJ;Sw())(TKo3|b+77;%wUqJSPWBwZC)Cf@gI zh&KSMoM0Z$bmX;Np)AX$8*sLh$ak2C4~KgZPkiPg_T!nC>bOr38k_fxC7i}L15UGz zBqA%sj5MXSnNs8Cg33I4D%97)a0_cEu`PRZkHAmdF$Lw#it+J_OFq=KKPKLfEOyh> z|Ay!3-JR@<-M`?8bkExCWpd`6`7&B6Q7MgG=ThwE<(?@AgUy0z0n>55XW=LBFT2fTHSV#QCYZUbM%@pPmFlm*5l7@)qO1r46ywknF9w2HQv3L(ND8Z}W| zQr~?WUGMNB$5=HSVUdk`K=7NVGbNzK4V!q!y%ZkcK0axTvvqEx+7P-^#eGO=GKxTyDS@S6Y`KYCd==39Po-I4ffw4(X(PU&S zXL3leVSi$+Js4Fv6~mv#6vV=MS`h+H0uhpl5hn_!sf`FA(Q)sC7jiPCw??pD1G3^EQSzHploYF1zcVlj1>A=TJUU6$(*fOb#?Tz zs~9};^NHuX&ysWw?P->#I*>fw+kk%7n&M%ghpGDzY67AK{HYRhEV-?JKhw@GQI25| z`K@{o1JlQ7vI`?6e!lYT*iOQS7t8-3hN3kv1-S3FZ4-ZQoN#;U<*!-w3{iMx5=vRQ z5v@c2Fz5tj(Rs{P_3XQmoTYcM=)rz#q;|wAiZMM3_^8DEB@91QxKtwK{Uj3OS&#oh z!r~Z{aK!17y>kn-+pl7V0S-8Zb6l+u^+ZvaRqNEB{p9y6jJcC)2BSYJWSQe-&aqfN zc~(hAUj!3OG?jpH=O>Kz6r;^m_BFGHvcM$+;bF;eWB(+^LZU@4vQ%m6{LomseWJ!y z>CN$H*KSB-FRpMN{UJp$m~Sq*kg~J~wzsT)Ud;j5QOj7_5;dQqeuFOsjcS*7(2V_) zNpZsKCO2hom>vxF_otqVE~y5?&Av`EG`wYa^g4gQAX^ka+jW<|#15h5$?J=HV=h2W z!@fz65gL==9kN5^Nu1Ak0bq+QCSoIpRd?y}FDzVNyc<=}ydMmD-Rzua?WU43QvZ9j zx$hWC37Dv^>X#loVb%lciIj793Shi4JgUWM@>8m%)4Sa9`36@q(wxQcH-Xlj@kAoY z=wv;6Ofh2XiIcA5L`K*V7=0VC%baW(!@Nmdkksq4Py%btm6s*?hWo zMpX=pGm|ned&k1?M9DNlgA(cfV#iMjNbNkeDFL_a)MFMu1Ady8)6c4B#**KTSiZrb z@`#;;?^Cj6=`Xx3)rAc8x!xi-;7OlbJN#owT}v)?uKb4qXU;o{|J*>ok+dzcn?^4_ za7o6BrqOcI#BgF{|7WwNGjnPO&?~>g9D!t-#t#FgtN&CKQ=}tqzYnJbEQlYL_9FP? z@9aTcfAE?d-OB-9Q@mJVMGk%H3MHohWTrJJn3aO)_&i=V zE34&hSfjQiD-0Ch;=^68Q4BL7JDHN4Dj% zVmLeO0|_Z5eNa}@N?MRPygcyn+obQs)jx#~pX;ZI&?x>#&dt3Q@+Nv6!%|j5u`^F| z)F%N_`mAi(LY=0ZL;bT$bmTYgPP%kZQow~x-|BzLYDPNLV-(w+`bq=0qy6|~Y)IEK zCzO}{8|96KQ`?ovqkz?F4qxsQS-y5API_Jzr32d;R0N^%m; z{j+sxgGKMq@vJEn#3%xge>Y)FpikyM#RuwKs2*M~9{HaC5Z>j=7FdvW_?hiJdX<&Qk2>>pkZ&DlsT&Yj7e@X&MVEdv3lFZJmBbS_o~9kL;g}<-~Z+KFqP<`HQ^8 zL1!BM(c%o1Yc+IeWLGd$N%>B&SNc`X-rZRnf+|-jL5y)dexBCIYi4c*Q#%RJ>9PC~ zQF_81d($9io4WK%ck>_bOeF;M>AqFg=A3^XIJkUgK|Ykl+`!8TE!qDWCPThsj>;Ez z13bz}qi>8?`d$_b=MC_V78zSFghfg{y9q;g-(}|nLw+tcM;A@hm0jmHKHw~7=^V@T-ED5meTmhpmizo2bl#tW_i)a0tbEX_)l+FB zG1tDw{K@QG=L~gv*=yzjHE zGVNtxoHJGTPLBQZjLpQ2iL*K)wEbCTHgo)iC5muN9Vej(hPF5y#dV{^{b}Exbz6L* zT7QxTo41sX`Ci=VJoe=UKifVf_84=1M)P*f+L}mq9Ik!hpR&$_jS3wXxlRk!rG7QE z;)b?@^@hB#v@cSEMe)3BaKcM`mg)fc$FItO&_3V!mD@qi=fFUWT8mS%-niKiXxFoMLdB0r>niHs zv31r(hW86iBglIpt~nYi&8|_@^zZnTX4<{__qOn5zB)Wn+XE zlShY6kN7^N(ER8KJQs>5?WHoiDs z8X!5Bi4)BN9$z-Zwmby3LBO&Uh%N??n*y4_W~y|Edz>F_PLS)$i@r^6y!m{g*oCAI z7MzjM&uR$P%$)1!dRlA=OER}kk-d{%2HWtqR;#I5if++@N<(f4n|pol`KQbf+l5mJ zTvNyEZ6pC@pS(n~$e(=#1!x3B^79xg1a!CNI~!FaZJ)GRP2Hhr&vZt=2l6Gvyq&Gh&`Q!hj@B&MZ(LwcgbQN97|AmN%m zs@WJZb8j0nbA#iJCBe-5w<`3P4joHm4n`Qcv^|Yb z@rmN!Et-ms_qobgH3HA5rY1csYZJ+0$z-}dC7@ZhOws=^heJH^7pmS(EED$hGItMT z-z0k%mT~|{(3yPL!bXyyYTYezqL7449-Q1JQD6ic@04mi|gBG8-@-t z9D1Pk*s8+rwGgbz^jNG4X}aIF?#$(4z$D$9aRJ4REq!D_>qnOqq@_^qhTF^y~ZWH zP-CDZhAdLLZehXjM=BxLMQn>Osd8zo!0$@BwiW(4c1Hnf69azt*wP?P6JLioRRcO& z@qZ@EUZwcQMJ;y9k+XUz{#|Nm2~9{`S9NIgUgZr)UuvIM#D20SH4WBXy_Est$M}yZ zNEi^f6v;APu@!&C?h^J-d3T9S%^9AeH$t5RsT3vT^Qim>ihirjl}6+1F*fYlZr?Ue zX!ulfYNBH<@>-WayTE3DbYlS}U@!(Ua82x4S^M#%gB=!^AR}Pr!Y~cjJ_Xlzz~XT9ep&lr%| z!3ZL$QEn~2nIMu*G62Zj8u~EyRpQbKKC-8Yb(78_%@q?6)Dp8;{0)(*XU`9Z-6{Ss zM|Mx$Q& z$>2FQ_%oq%0i3O1&A>M^Z<7jzrk{Y0ScWs{LVkjFk!c?zx~jyFFro@I+;jeIKGwvT zKk!V!kTd*UN3Ts-z4M#D#?nJBk&!frSwNJ>Jdoj07DAe8`gPX?m}_`WS!s9tvdS(% z-qXuBx{`JSm>Asq;%Ar>8fRhwkiqHh^0#M=aZn8Y+0Zfh@d?TE&kMby0aR4=oE^GA zqxY>YRTGXR>6hM)=f48b0^msz60jbu^5v%6(R%iq)-3PCn2^y=91kOMZummvblpoH zEl=8=5o`(0g<1ZaR{MC#^{S=sm!dUZgb>$3>yN)2@&cxfr@rAIp$Xji*f3M663f)j z!-xx`=^+VCC0D+fJ>DBE2*a27ZO+{Y?fdk_XwKY} z0gZ^^r#$OEqq2GPT^*|HfluAI-V*?#3Dd^GJ=I+5IvYtfTha*j9qRg7(5@!a z`~pT?0=a%)e`C~XH2qXs<`#m$!lNFvFUm4IFoInfS~l~sT#I zF7+BNxPh&9JXXBdMM4b06pd#XuQK{Ptt@N8iBjYP@uoq?&p}s(8_Ts0MR7r83FYre zo7oJ}JL{wbuQ3qIW(NGCw~}fDd~XHvvOQ73%3sv#T;(2}C{X?$E{v=OkQ2$?zA*p& zLi;sf1KUp#|1}`xIEn+)-YBkE;+@5oO;y-7MvfB?8uw7&hF@Paa2k-EJCZB?7x=q- zU!vf|c5ta<3@5>v7$`L%gF-7cFmh4(%wyEz+99Vu>f9%ifpkJWd*YXV5R~nD{E8%c zmYeC$SzuYSHMAdKea%SgwOOB_3{j&t4B8=ls6iMmuuh2BiAhHBUmZuxc~8B;Y>Z;a zz7GwJj1{j;5W6q9V((T*F{tLj!2vo%vNtTM(R^on5$b^bRr?L*(~#{X@WTc3qHw5? z3#H>i-~0~qbP_sI09VF9>1o3k-43z%=gJAmD2^YK@T;S~ZW;Q#2jU4=r5qInZ7C_Sca12gn|u{ zIp&=J@v)R)5%`7AIm$dsZjd^KRH7HfHBMQ%;bI8!z$?+t?#>LkqQ<{4L%Esygaev9 zW8P1(j4r(%Z5l@WXTvOmnuG4!GIyB+Bslv^8@dZ!Gqm4IE{0uF5au55;RNS|jYQXf zWIruPvj%g^t83%}H(Krk22`%yyb&%jnk#6~3_p97XXrYrMXR$p%w4WFV1x_OZ>k zHiXJ~50hU5911}y1LQz`;h*dV0?Q<2veRo?Z-bOlq; zvDz18zaLTD#A|!VSt?&$&;?*{Z8)BcN;_%VA)d+`APfn*IG5l6@48w-qy{(Z~Xd>IsDvgS>Lqb3dVHRreiiH;L1;gUp zVC)GH0wHzZzJV54whG3og*M=zFP{TKi07BNK}Jv`;sp!N7?*}(gVj*-ywV4>gdQ^i znHTluw<$aFCeTT#npyu)SCHmQ2GTX`A=pH(CI4NGc_1$RrD}B=;YM4gS<5vwL=*xd zl`tyc!F_`7rs0c%H~MABNe|M9jmTCRkJ5PVhn7YXCKf z6QC|KO^8_5-M9RFTSqb_8g4=0K2rNiGjTr^K|YNLxbN3CaAd4bv*l7u7WBT}!Q~6J zpEMK#gSIq_IY^)m{Ix-4u>JPcU2T-NG}CdHC(8^b!zkUA(2vg=0QhJ8Y_U4j@F++k z^CWE!qW!z;!y7dS9RgMNo_@XxS&h2b%7|injyQ)~QDcptDx75)0SDEN(^Uy&+r0%R zI=7cOK6Dr46Zzb^vLq_FM@C(J=cv&NP2c;`#ZQDY>pm|{jZF^Dj7(fwm7q=?>9JX5 z*2B(tYY=S?z%1?k^3PVle-F)+DjVff%ip3e7DSpT{yp5{)<)z|m;* z*O)}^PQo?V<34WJ{C6io19DTzd~?^#ZcU+XiK@(iY4_P%C5C$eHwd&&@cUKEQbO-` zE*C9T1t)dNs*^^QB!VdZ=J9+B?qnH9vc~IWgMZbl6wgB6Gk670rEJ;x>w!rsv)YH0 z%fG~v7w0?3$;1!zC(LfD0rRwGfMKI^yU`cK8iWT~>A>;gG#G7V+078hE zlqq@e#z%J)SobiAxvUyfd|$j$jlS#*?IZ-~*!N)QGNO-CF_XXtaTakU+9D~zlqzl9 zPx1e@k(78qICWF~!WAEBhRIZeY+3(C68(ChC-0;9 zF~E28l8n07n)$xA5&2M}QM^FwfC0a)3YjjfY!%OfL;%INz{Bb6Pa{@bs7E+80-bxs z&ufPp8q)+?<0vI}?NJ-V?`TO@-sSYd>Y%Hi!XtdqSuM{`lz@LC?4Yq8mJ|lv+d!N0 zgFW>uz&bdLIKk(~2H?qS5t*-WS*~rNV9XgheIH!{l!bXJm)=dI9iOL1woFL^pYXSO zsj&26Pn&(sX$UHpp*}$(XZYL9z9R$E^+FwXfFlDt0`D(>m~ua@d^YXXPH5jPUN27A zZ@LW+2i;jhu-;_^yXby_bDvIpWt3Qa3RIaVLS;cae5!>zJH zJG7Vj=aLTz5FVx;(~9&c)I+~ zb~@Et-b+p2C>-^!ni|#=+6ESAak`Jxhs52%Kr<7tUaMyxh$V8y5l5%j4v$@qPTWiQ z83Fw6EXs12fRn5_-?Phb{liMpEf=-$9R8uP_H3 z4LU;i3jFjFtT`7WU|$oV?KFYLn3JYtV%o`2;^_Ca!_|3E-M|%!N0mWWH@Ggnl`3x^ zlo#gPL#9(IEILjogGXa{4quWwVeFs2gjP_<4?sYPiLZk^kVbjFBhNU#B&S-nthRm{ zoksqxkk8SUM)8MB7+-{w;L{zlQQHPrhE$({Zn458n%@(wEBmu>pB1e%v(Gy^Q5b7F zJ;8iW5p|A7I|1?UPJy-xr_l zJ`d}hmma)`R1q{sskl7@S81TNu?>$YU8+Q0s-&zfep;Dgg&04u@b)&mgg`1|c^x#A0 zU~q0-kG|6NJGS9@m!hX<9?yglISb8w!gsXDYF<{E^5_WC^sZ<3sb_ETJOs59g8vvG zM_lqOLgqMug#1ahM3IN#1#DHXSvfl;uM^wwLoc`$z220m_M*-UTsJhFfrh7-CAaVl z=VDe{E%&JAPGLCCI#q`ZflL)}a~b%+z%#4iIlU;GG_ui6Yy}ev!8kZ3Eh>xiwQI$$ zJa5DQTbwmiHQbRVoFS@L?13FxB40|}QIfjo1NDHYLcRk!QwcWqFApO+i)(S_D(lO9 zRLh$`b&)1=FKr{{5*S+VrmU=Q1{PnJ6}!uOxstKgZLe9A(Bh6CL07ef8zW^=t^(7t zP^3>A-5#zkYQ4vE9yYnWkkD14+;%2&qROnP`OD4bJ)_ji2wDLY!<6%eyO!CM1^wys zd`8#YGCMqq#@>7nUV#v=X0xw8gMrSXQwasPq@$J{C+3oj{a>2HUBIm2HD14r8j%sW zKB3_dSN|U7SW<&9Lia)tIECt*Nb=K%rr2f(i#O)==&i_Yo9t}gf93+WW47|LgsnA7 zXz?-vV&laFu4Di<)aZ>EDhA-U8`<#$9~{uooxr0ozPlEfUjN8LUb&Z*}U%7_XC%b2Ze z$%`WGE454AdtUJdRGKyEObjHdPH&T%=MT`Fl)wu{d4v+N(jj88aWc)iAL3Dy^NV$l zLVgRJ_o3TAB5yb3YL`HmjLRl{DuQo^E(D`MiI{K9DKSKXiEP7b7G`_A>$>ZP)Us4Htfd$W~zs`I_Ep0_eUeNJp`8~AbyRHw_Ng9eW?eZAqHzwv^kX&012tdrM7GH z&IgroV}Y^q9Rl14mye@xsEA~b<)f!(!{HcQ<_d$&tokk>TsUOBWs}Y9tV`YiD7>a6 z$=r6PQlZbgZ8<3nsX-$G%G{pYidwLnx0U|u7Jp5hEK^w42A!43OskNCQUed4+lfN$!$_LL{?cGdpfbpS0@ZkR?e(13o5gX7V))IFb<>gE}@xcb-*eS z?lgH(mezzs&x_j@?^MG*~`GR%xuWdA%h>d@@rtX zbcQGl(N$!5jkUQzUVfP>h*!qdat?G#_?KhaE>O_e&>M- z>uX0^kR8ffKl|a6M3v?7d~aIfo{cW{3pNQnVqp#k|2EuXfETV<{K@U;@QulRwD8%0 zi+9QN`E1fF7D}OVHCzh&a(daVeM#x$!igKZT6SOiWjx?I7bzP$ zIP_kzWQ|92zD~4x)JQ>3p$=4O(0o@%hvCIfq9o>)4jioEUjL8Tw}8R-F3MHvI@rfl zl3%eKbIRQ>H%uR#Se`0au9~uOvx_(DNot7WXY*IA9da30KiW#YxbSP>ftGN^TyR-d zCk-+u+zMtWfBQ$}@SVlz_-d;327__N#8dzFI-VMr#&=JZymV%i6(+)t0CG4#M%uy1 zk$aM+=Ds5l-A`MSKl_$y?dn-$5Yg4}~|ltf{&| zmiP4Iy&H>XE)U6yI(EFUXJ9wM<0OgPlrnf zkYCkR#oVWr3{|#ht&%8E_hF3f45)zHMVMa?4n-t3-oLFDVm?#oVzk)gqo#z^+a+)wXEo|=wbUxbVAA3YuwdNWAM3bP{xT3Vb~Dr{PmE$iLj z#%8XJIQ!_`m$+x~TXwVbS|vBQiq{RTDYv<|Y7EEXd4w2O#)+Togii&awF6F->uF@< zeHlKG$!gYl1Muv!uFQp$Ipr@C)9!8PCf~YY*|O7_SD;M~-)>c@p9i293@4oyx1zXS z`Yy(s*K2VC+PA>kiYBl}!QFw5<+t)=jb;Oal&??Em@2x2j``H+(qRq=xAu={q3O(y z*QYXU^6V3Q>{=_}`+!yik|O2~xJ@aC#gy!UOE;M2U4EsppLs2Cf#&Ck_4_qA9*Cbb zQ);S0SLVFbYTK}42{lt!MA#sVEVjUZti&bC*Y51Jj*Xje5_H%T;Jm$JQZ+_Ei!=Q# zh~rxc*A_7`=+7zs#f#Cvt|Z#7O|By*HvftcQLl|FUTlI6EiW5J+jfA#Gqd2;9JJz$ zf}|e*`GnRyv=?dw+L;(a@!p`I0GBtg*;p%#$;uG~IE|o$g9;{(zikPkBZm{6EjWr@ zbu&{M?SG3fXvBn}Nx)ll{eKtM^!8kq)m)O}C~R+K)qg^y(E}s{xE}yFzzI^^o(eU< z#lPy6^Rp)!%UkL4WOQ#5mbmqz+0I8W8{;^It*KVFEbVA~j{wP{SpA3~b}r$1q-5%j zA%DcebvOe7eFAKjz(Y5a>i#S9nHyUuF$=o?3%6eHpM{>}b?Ovp>tg&GG8V7Z=y(FR%Nraps3Y)B~=7o}|P~t5Aw=5Zb$LSwR{-|FK z-=%RVfx7+j-dmUZGJfc;MAs!W&U5_rE_*m}1iy(Sg1L`pvGkPlUVWZUa1OX2aMF-) zQ!0*#F}}(!S|qygsut%n8^AK>Jms+wfc)fzNT^l+@txZvf!5t{8w2a%)wA=eGkOe7 zQVEadQ2%{iN?2XwL_g>aSUhL|$MslWFhZZI%d^;~u?>H$$eCxbv0@|1=r42&D2_XX zc+L0Gna6p;z$)Zz)UqI&j5p>0>M|g_G+c|tSpG}YxTl#+JD8^+H!-HKIG3D zPe`AC-Zisj+NNW3Pd2Cr;Ym27UE&Qej0*;wqoX(|d$q?qXd;4M-Xwh%kx+SJM@x*Y zXNR7&8C=WQDiBlEPCe_%fK%(Y7(Bx8VnZSn50^RtNY21b{PP$3ETOSua{d? zi+ry5Ma9PTU+p)yY<;Y#u}$NPg<+z0c3SQAlYA+0T7K2})0^iXg`ZTvH}oVqbKuU) zSCtDbx5b=xsY$or+Q-(DsB9@ZUO#U}wIR#c9pv0BlNfYWcEz0ERDz>j*g$4qWabH8 zHp)a6+8F}MuWbMj!kn?AzjRW^fd#cN;vRz~meGT3KgZI`0DXmPr z`NFGP@h_AqX@nwq1DZy(-&TBFSXIFy4@smxhQ&#=Qe5(@eV}~Cf&anotPsaNxpM7 znx5C`CS-Ylv+ktQD$2^LwY&ilb?hGxz0l))YZn{@dPycU<;iOHT{=w@c^L4lj(sa0 zKC-BoYrVdUe5dZfiD(U)pWZgIg{}tDuJfG4ldQ5K+uR_{-G@1(Z(RXp1c;u?wpTyE zSt*Mcgr!R#t21rkJgHa)c=heypn}Ap70qSAS5w%;U30X6s0e%tmKn;Az0vHED6m&Q zIvyFcf&nF;SYiqrEajnVWIf>6Gq*dzi>UJ}-StHeg~2H!HGCElX= z*Ji!W@>QnxIG8h85Y<7tX|>Xosg9o6a(ke&Iezy6Q0xVXa~N~8O>vXrt% zpQk;r_icBmv|+op3W!S&qgH|sd|=y@${y3gnYMI*^HCTv;+;pjO&I<#Wu^8jaF0X$ zGAWVQrVpIh7`zoP+FaJjK4fS?S*azA-0LQ9z=up z+|4K zmt057J^g4J4Bh<*c~gG9Nc4jL7BS?^i)JTPF(a*?=kz$AiQY*@!@(27 zg37?WKjb*3C`0q#r#H~0@Br>vS%l+W28b^q!#v^xhAXW!`Pxgra!F2N*(k8Js6UIl4h zdUVF;!eKD8pJ<`7m}7q!ux?L%v|6Cm%ISxA0^tDX(%6h|@juma3il0YiE_2i?GG*- z(!^9J;4$TV`EN*Ipe>L<)JG9aILbz^J)aI~8!tqP?f{Y`7}SL)1_* z0%{vS)Lk|94N)c6WY~DwZN7{A6Uo4kO%mRIfvgcOxJQo=JAZByZ@|1wgX6wb&((Xx zEckc>PRyw!6J!;LUdVfo^BsEG3E3&(GN>2+Zwp{jwG^O zU&B>}EOYX9p8AnvZtOrtBxmJQZ-fe82>djQBqMgpwdwNS*Bu*SB~A~hQOQzJA=xc zc>!@eWZMtJ$w=hE+lI%7Jg0|r_fdZYo~ZclEAXadF)_nfV4!1ZBGdIH9q7Cj;L+nG zc3qFlOfB!qF9-=7vG;zkY_@0!jvcGq3)=yNgNX33x?%PHqlIsh4ST+++MLRH6D|17 z`hfatu8tFihMh471NyU-VhjyBr~}7VW!JxoWY@|zHa2Z~zFK>3(DXiEj?aT-HLf%+ zJx)omNZ=uUcj2x4_xZqM7rP4gR?dGgH2L!Pk6naq>lL+Lm6>v{c%v$IVH>%0*t{ue z$SISOEA@2w!)V)#-50OtqB3#8nv1-4WQ~_syJ`Y8C3Nn!Ne>9 zfmR#KOClxdLZb;PJGSCgzFt?Q=sRn3&nNy(vgp`~|1q6f;H5wkc@10ARL2{==DV_G zb+dok?%EzN6dI<%Dt?{WA?xn&@`@(st>|Z&QOdVIJk%$)y3djwix~6b^HA zjd@HdE}wrNM0DhS^SLK)gR9T^fd$xSw&DkSRKJ+e{C*8baee$eBW~<2Ox|PRo7+^Y znijO8Qy)T2rdakW+8QBqsy(vQb-a5$N<0xgQg)G7wskbjewUO6slYvb4WrADW(|oB zoTX6BKt#XvYaM$Fn>%AUSeNGaZz>v;C9COTH}a3Z2pKXgjDwJXJFY(%ISq{8K zO!l;tP^n!{vREE2s|FKJ-s5_*d{5khZ)EJX>K0?4V^jSSJ9q;o>)8XW)YHJF2~vVN z)U-o#zx`u(XiC8DE3=`<-3OcSGgbfO8SsDhdgUxauDP|QkOK)k?IWg1^9!kw5gS!I zAin!eb$1e^_ZbvoqFH6Ct4rd0Hgs+fuHrbr$yb<_*6jIpg!>{E{gBa8z$qnRHTv6O z4`d!ya~%L5=XChLO60_eS?q(-?i;umUpqZxDcW8_C7^4dCl#3aVA}NVb#MkVE-Ix; zxN8H~LpCE%9GtNIk#UqJ@?h=oydsJmZ0~Oh2)Y`{4!|P<`B7fAb)ZCRzw`yyg6Xf) z#{Nk5Jv>)Cu zK*|c&7;YLZe>y?YJKrY~1$-Uqx1IiUJevMb$@o7^uB27st9^1H=zy~9itQ-6lsC%f z??hTVT9g&X0Phf1C0mGH^?rt23_|`|LqlrwY1K2Zo8l0DFFHrFu*Lkdjva%UdC!+{ z<#@NnTgXW@FK@_gdKR>TB6f9 z##i&*gv zXbr``zu}#s3DglO1pHyNnILq0=Qv-bDmV?t%5xBrNS%Bwbgt~yc}1`tZ@^ck47+az zmwl!A)v=#1I?S;}pN?WhR6bw($M)SN#FE+G4+Rpu0ZuQ5KU_CWBRp9S{-VUoc9iCq z#D7}?^!h@6U@{V#ClC>B6=15 zz2-ak1Dtj~R1uWYcg8aL%TJV*hdK11NEq_WWsv?yDXq)W$Hnm>jnh`Pk3Owai_LzG z47!^)AlqB0;oH;Ydh4`HUp~1nE@alyeZB(hYEOEd)woUgYj^QhgicZjm8PkL)OX(v zO`xO_(f0#Pg#6lKL>xb*ofEtwDVFc+VGfqtyoZ>9G*6V&_mkabHoB7Nr?$SAXyPC!rvOiX7cS39-2JGE!XeQ>Mya+kk1t z+l&oDhCON#7`dHL+ z3gONH8h!Bmda1!vxsJoPw&H)yNZ^g3SIK`zJxcukx~Tr<|3f*DA&7^g6PHrKy}&P` zKox-_<%)Tpu6M;X0NYMq-z9&Tmu;K`l(^uMmicCWD0Ks*DJv_4Na^MRjh^SdAOMKB zAZK6|`9Vm3zujdk-rTII-gc;U1LW%W(Zp+!?9c{K5>Y>es%7;fB={1t>Qd*v=u!sH zzgjeUHsH3b?mgGAyi=$o=I$~W@GpV$2x40Nj~PO?l1D$TU;})=iUUBzY!=E-70_3B zVzcLAkY=3tDwf|T^N=nmDA24S&%xGn$pD{_Lh#1#9#084KKTtSt4cOfx4RxWx%P|K^}O$MM*L|cH}vGkV!%&yMJTdD#MqWfu88K) z$SQr-XdL_iN|L8c+Fk}OdqQXpQ1Ee8gjVDTzX&YaVoe<13P;Cd?_cBGX>v37b8U8# zN}gwoDRb3MMr1{Bt)o{D6Z*pf(>z@+Z%XdDpyG0}Ra!lRL z`E_Z7?kXDvsjK-BR^G~n)ysDS%Y`lrhYg(m#I5l{_;KpUH(J3eX~mpvJ4(^QB5qhDs*(ZbDVJK!w>v$qAhN2>_?V!994-3`#^j6s33q%v|>B=?(cx}CNz z5%vSy@-C!A6dmN`J%!N5RyIKKHsb$6F%RhpXc^LyNZ1838z9;)F$a2F{g~XrppLt% z0HmOx08T7Dk~5ir&nSH zG3957jrOsx9MZ5@O}ewN2e`PC`7T&)%uu%BuJ2s zon=nsUfIg_!|ac)6y&-i{iVvLa3NQ=E}>GUB8fMG`w^lCK}Bb@e_V@6R1haKg;z0N zY4dYN!eD&?56iAL-mQ-fTqdMMvL6MSa~X6;@a&EOzh6KyVW8_Ai6Gz=Lq3xptKhT$ zUrbq&P568zn-AxJxrADkAqJf)N-o_>@jbKs_HLmOJCtKVsinnHTQXvWrPBfie<1)1 z=RO_}M|7xLsKkZN03*|$&HxNPBbx@R1PK^v16liI1*%!ojb(}2{I&kVjc(waKdfRB zbAP$>9S1HC87EMS2cF!$7cWAYu=dSzkge~F;EvLZ5jp=P8)!~%u_m}3^LKu;72gV{ zq_vO>MT%T3p6c;)cFhi3G7(^q-EtOiAXxtGo>_rB>&6SVAOd(CJOi(Ge!_;r>*?Gv zb#%-6M+(ar(QO&b%g#}$4<%-Piy_qYzG(1lh4(Y}o41fTA@5XxF^`-&TlnD-GGgH? zhxe)~6AM6RLWFOe9c(@kC*V#4wzz&J%2L6PT}Q9GX-E2{ox5xw1R1n|6`HpNHqUtk zf>q4>MQSJ74dk*F%F-pZoca8Oh+AHqpJ2cvdn{BPWnYo#`u?5^CCfG;%pMQdSftoK zN&>9Z*jx8OIf+ntlM%@}^&gN|bJ)3`94qluD6?H%?&h?p|43x}wva%rt$&zVgn`te z1$0gL{El!VT*pG=rJnFk9N5AS1SbSoMPi$^FWBa%%$6|VO#0iWiRSwJFO&+@xDcZ> z>dCjlY5--J-4xl+4+lmpD!M}zw5)YXTkrF~Sa=KUU$%l)w5i*wHF8J12YB2YH*f#c z4jl#Z&iA>fh5+obg`M2o9xj*T0<|6i3w?L~FsFMVNVit5zzx;b=u(a6!4grHfM+G_ zH+)PY++Gd9(N6*57ePhnuW32Jae`4A@fcS)1q`a0k*@@tQe!+ft1>ABG`zXg%v@ zc}-j6H*J`kF+?X@h`D`=EJDl}D3Li@TsJ!JP!KO@wHk5QN8q8pm$2A?Y9DZ=O?dO( z-H`MDCm4XvmPb*07J>_PEX7ND;g;EQ!`OC`Pi?N-=U|1%U4=5HSVgEz4fFHlHf%iT z|B;KSnf*4PGOc~>_gTg@?ARh352S*=tsm++KsXrD#p=8oJRp&b4+lrHb})q9<|7{8 zV6_R-yokzrIir02Z2~dKdp~Nw8X{}N|-p}jw2?JHk2^{W&T zl-<2gq2H@^*KwiPDpga}hA0Iq-FiZ>jo=3I{TW&rz-n9YSM^JAfAT;1Nwzls+6}<2 zY@>{BPf#i)aR%gXtKK>v7wsgx@zc^@iTN|kTw4HWX z#|8Y@%S?r`7e+t^GBYv-po+)e7MSZDSxjAMS=IMCb#+_UY7A>3rgOyMw5+?v7Tf6y zw41=xWAr)?uiQTXs6dWtPgoSnQ1F=+1#y_<3i8-sf?0p*O%fJNV7_;5#4R@WtDG?+ z+C4(zpPWlxOx2SW0jETHIqlxD~5hXSr6yM-8#T2k&@39UGt$@#p}TT+O;c=OD0b#E_~2S_4jGFv*bxu zJZleA`V(#Dr<7IVXUSF&`HWpwIngyuO05CS2K4sIFVRzQ($eptK+_mYgRl2Ho5-m#APFGfrK~{KoCIl1ZowB0F64bOd`4C>+r1hPpMiYMe3=mzYa#wB9yJXO+ zv%ESU`7}@5jCW{*84T93SMOLHaODL?8Y2H;OJwd@DE)DDf<&uw9@~(Oi;3iQjrhfD zFFa`*Ff>=0kBP+xB$n`fSUPP)B~KmIZ79>2r_C86=l=*3bQ8|GN}`=N6krd<3_^beM>nU?aZw4P|$e4nJoEd)yiXH<6b! zBH?XTp$a}y%D{3$X_C$8QqDK<)mGGa*N8z1_hP7YApge9$L^l767Nob>(+_!{*Xer zw;uA2=g*(o0ena$;MWjQK0bSwASPBHA*i8sti1t?MX7Cj^K*53%D#SQp8;#T;=I6lYIreT>Ig77$n3K| zS7RljEP{SuH0i9)o90s;Xw5pZ!sH|em0;EKh5EkS~mMsvn zyc1_z&o*G zlB7^}IfneJlYK3)bE~X<`@0)m%qt&CJe_`{n9w*mLR9k`wy~;lwBnERiF`b9b|#II5R!h6Y~5%@H0#J5N;33oz4O) zoEL893V~Hneg4fmU^xW%4#^qQ@wRTSa-DsX^A}JFv6w!+rlwYB`R{kuzK~k2zX$nY zXb_7*xLqS2a6n(|R`6FrQKcKY)^ul8qt~9cI;>f0+!N5v5DV707MJLnw0&p(_4I39 ze7aK5e|4~*K-4XG>OoFjPUu~x_K=S8yaw;DZ`5*e=kr`29sgV{C1}Z=Uk)Nyb$!6- zZx0}Vz_kxyp)~sZ%s+^?1*?Oa0nU&BF)7%7%o((Qtj=PrVf^0((gc7%Ef@e#kAeVu zGs~-l_{K2cy-^&(Jf>ZRDFrLzPYEx;e59Z$j9McLK<-2J_PN$85$9cIShP9ZE5`fP zX_Q!W*=@f@y~%J!tIl9+%+=qiZI*loZyWQiD~wiH_81`jlyCePbsdTM+X+Y3uTiW>_|1-G}c-x%)et8ee)}siSDYatG zh^Xil@(%*Ft_-h|hLmr;{Hn%L_ByNUlQ!R&I}HTZroZ_arF0lJn{H)WIL=C7P7i?? zIr;4xnm*_ZZZBjhE;?Pa1GDDW-X#>!@1z92D3I?N1?wGulHW8?Uhtegif`FyNZOAJyCS-&cA|+Z?hqd`JEu+jy(p2x>1rboge3P-3f6wK% zxL=!y=Yd~lnUinD)v78&>fzKcjVZ8*09yHz6am`6V&P=>rJ`V!f)EKZ)cCPqTibI)E=0_7g$BGm~{(cWy{L&R$*%)thmSY7OC0Epz#84f# z)W46Y>TASf0S2{t7@AI0+Z%Kb0SPs-e=`w?CAfUsbFn)?xn3cdtF#6{^JwzkI1gg< zgthONH(}lY!0zZ7_8eV=rIB`yC4cFD9?7i((Ht(Z>L`1YkBLaKJ%8I~$zTah2>hie zl!cusj5$S8)?P<(QYpxs5$PVz$W;-pJTG}cs458=8S^pB;;klDB?_7883}Ly{K0Td z0X$cRL5rLBU-v*TS4Ioxf%jp0`+EBJISID8`dMDUd^0vZU8|wea(Be6uOm90O~>pH zgBz{^gxPtiKk;{1+QW=F|FOByvAul>ygOW#>p!X>VJn&CB2_J23o?`AGoQY^%|Uj0&A2(_k~1Rf^KDNzYYw^^nk)z znC9>kf+CL&mMmoDFeC%%0>am`Dgp6hdX1!2{J)x??ftpi$gn|2NWctvZ$F9C^0zRm z)#MadPyN!E3PXVxg8#z)42)mvk9UBrWLND$5ZGhhoTCqTWK#EQ885FKSwD_TBMPAn zcG&+}Qmout0XY`)LQ4Ii4`wFb}zb#9=xx$fEc4+;OQigfUZgVX=SpNc-e0xS34 z@l+ImcHwjsWJ}l}dER|ubR{d=^tQ6K?W&ew`(}xly+J7ltPq> zx(%n1ht=<4%jG343X-qUvLCAsOJc8%>$2SJ80C$h8Sv41~$Hh9oD+bm~3jq zW5^V6Uuuefu*B1c5~?Tu&NbkPACw?tVaRffsbL>wbGTm9}FIxx|z&*Nk-wRgC(Y*gd0KZmqRX} zUwuzhS0s{f-N%>O1i&;C`u&_;Xkl^-Z0cIu8Bjpt*VQ{MbP>l~wWKsr()+(r4xkL@ z`Wm%IK;iWvS(Eb|WG_Gx>0v(~bY~$Y*j~G(3OQ5jAfnI#ka)L-rnnO+wYy- z2564Q3r!JMYR(!)gU|13j>G#Tpt$B4@>58Y$q^E*oE)&s6v2(#9Oqa;b{MP22BToN zoN5qt6I|GPGGXfVWK^lg(Q98WWhsc`-alJfwSd*j0aT(j@Gx_bc(nUgS<=gu#s4f` z9&U;SnON;^bh_9OTjj|I5+i2EexC}OAX?AuV zT-(>P2ioYP{J0-`S?6~uk&by3`cI3Vv4(yWmsH4di4geW<`mK0zco($sh4^vC@Yf$ zOQxs^p0k9G#8dg~jjnUhSpiN4EL+`Q(~HBov$JM_st&w9dkyOwv>HqKZuQNdJRF8S z&UfHPeVj{md|_wM9RUS9-TL(susbvbE49%6;1bnX`qW0RDZ@H)@TB1XJgNIQl)w;v zzn|4E{Bi=$3L@2YtY)0**fZ-(Ac_KRENcwI;e@teBA`yYX@f^W666?i)si`@CpLc@ z50?0c{r@z)=;k&&ewi#=#$POadmZ z#dH&%>NwOJ|Fea#^9^Fta0j$hV9D7k8ab;NPwd|gTK1wsWbMS36O+enmDo+6Y}fxMtu2nQ_t49DdqawuhDujX3m|Z0=RT>SB==@Wq+LJ8P-rEMXZ(9Cs-qTV_LKmt8=)|XptNW!dISTJfkgGLSRGkpl*-s zIq3R{Brw4FgP2AD(!b{9cq48qEScp1jtLbEVx>CovAfVxf%`+fsH~I4?&pKD1_aie1~H~ zb|w7?N8!jD0tK>o`1}_0!Zo3p#cVHpAKAX!b7?NR(BoRRs0(Z?>gjG5GdaQ!?}`KO z3cWAzsRIuJR%^Nn3*b4#ecq69!c+v0Yw<+w*IrGXQ~ZJQ4WoT-xH~2IFX@S5IM3K{sXpajLyGuiRr-MCZI0{-*G1%{tbQ6 zF^?3^=fFLA#B^Wa16pr`aEH=z+)U+HhjrX`*eW%Mgh(TJxjM^Hd1BXmErER*Eo1W8 z$FE(#bh1O__C`B;Ll&P-+zY&`4H1@D!xsDb=J5M+B73XllQ;vvaO^{{Rc$*qWOiIO16ZUUAI zB$LJNFl%ib2c+$P{UQe2UskQab;BOhM4)Wb;5Bi8f5@A*efds8B?gDK{X$U8qGc(M zj|SirxOO)dOA7aA&RESheJ5&p3~og-3}d52pZr@0(!99U`BBox39&LEKsOV=t}m*a zB?ZuCIQW4p$o2q(NB2~pL?lb1YhDbO#;^Q_|JX2(O*kBT0xNm2z$mWN3O4=DqYaA^ z+5gA(dTEP5I)Ec$XzSfQc-ku;2*jP6agQMwaIMm?wDdD2Eq^N;aEN5IN~RuB{wD)y z>MjKvyHi+$9W+-dKmT6>_QRG9IGo&8O6f#g{9FR+qo6l~m$8Oo&e=W)ML-Pz(CTIl zqvVMPDnb~u0KXl=hZ$^GgLKUF(ZVy7lyMGTf3^6E4z$&)d5607up@o;9~`dn5c1rw zuxlBkRkdOJ4g7pU68OCRr2`m`l+G!f#4@}l52p0r2GCP3FZ#=%!X3oQz2>V5;aQOo z$m3%H5zPl7=9}MI>Xg2V;J@i8oXS5KXmxmp0{}DSlND|$VgPdjQ&BFFxIsn))l24 zpY3`w|LiCE_smO3F}NSRn5buAv3i&z1vy`-I;2i`Fr6Yr;k^_i0-Nt-p8&=FYb(HS z$3jPT@T5X`0s!{*KLea<44Y7*%`ji-wg-RLM$GCjYjG{umod`UaQ)Y+7>m`&`*@|$ zjk?Pk@vxGLVL+55a1KBNNx+XlQg{!u7JOjG#pBRZNBHBuogS73f6~F^cRPaC@%l@} zYu2h;#PkAR0RC!<9HP|p3E}p*>|J8crAYD6-{c2Sj&L4W{)RX#`NoVqwdd$D{T9r` z37Fx%I%#_nxHkYzGld0)HxOHpS0LNZJbpZr-g@tS6;@bq*eI`B8p|9CGKx<%80v}3 z0S<>$FSr7<&GJX!b%%*%O{hNNN?8t9lCu=f{uh7rdbqDC`b@_+i_*wlj z9Df@g&I6=hAiEE8GfdPAb3b5#wLqm@8U;55=5P7_>0={2d!x8@AEAWu%c?+Z|-2EVAFjqMY1HhuP{R3H_YX_i@ z&vHjy0!a1~-5tOhs#`ek2BZ)zUu)mL7z3K0r=-71OjuAazm8qlQm?I&FY&Z#>j zY5?CU*$}4!XSlCM84mHi@MWp@0e%}R30ylR4<17f;qx1C-3i+SHr*-F zv*(L|jjHgsEPdefXa@D3#y4~DSknmy1NLD!A3h;}7pdNF`~GA?X&LF>t+mn6 zlf+nBI20FE6R&1YPBgjG0aqe6o z!o0DvKzzEb`fxG>*4D??p_5P#%(@Rw9dM~d;dS32w5JdofQ+P&=Ypt$r8I@Jn5W`2 z%3<$3+KhoEF`Ty~2-53tHZI_=8gZ&S6Eeh3I&}!`Kf<~Ol+BpT!Rv460Zb;MgXy<* z^$?;>LONW9zbKMq_nQ7LV1v7Iqci|=BLTYM$>QqgoX~?Og+dPwVc>`SdP&}gpqF;o zeE6T+;~t@AF1WoqvD>2gud64{qGLUYgX1~L;q8$a1I>SOoH=RW*VJG zJX>`UQfgUJoBsXrmIlSg77c(PVj0DeOoG;g92i3v|Ek(YgWzI{YIaMxx5U(JI@nI|m#Y z{BtIPqhc<-G(HpFvGa=Orv$8Ipud;uf%mAW{jhj^% ze((ef^BAiK0g%W8AQ8eWUf>n6p7dw@3+CcJsz+iHh2D286sce&6`Z8mw`X?xcZAd4-cg5@GvNna63qfg@OWVbJaC>3SAiT-0P)VFH4G%mgEf4x=FSR!6;H z>vS)^S!3Cf+JIP?az2&?iyp8XnZSl5et!r(bb>yi1Ve^B(Exq0r51F|J`#CVlm4G5 z$Jxt3CHQ>HcPNZ}g4OH8Sv-&;r2bn#EV+a({iO}{0kh>&gD=O$p{KZE02)El(uH*> zHcEFw3CFC0>plvyGM@Mi_`Z56pTeNP(^8pOw4=@(%T&NMz`Llov3)D4Q+qKc0&OU( zb%$ZfpzYq!_%~2g}DnEn}TMx^XMp+_PT%TbDh0u%nKpy5L4+_G#g@kafCY z+d;`Tbkz|agmcj+EQxk$^8>H)r>lZ|$OR*50lWb|7XEpAqL^@Ydqh?de=0-3eiIe< z-tGYB%tMJiX8?r#dV6?rD>@UWgdSDGswQMZ|3Iz#Y%3*WOWxOSG#rG`6gnN309yvVA;i&oJ^JnqkA$GRpwBoo zXTJYdr(U{Z*A(rqNbH5EXH_;FS?0g<7Yz2|({Hz&?4)@X76|z2f^n z-76?Lg;%b17TaM2OP0?y(V3c7l59hPK9a612gX4HWfIX_1omVsl+U7H?}c9)J+4-( zmv6%aTAk9X_KQ|Rce?$Bmt4!faBPZZJS>Id!N8>oWEBw4M z7X7&Ho&0`Zx3Ptm%*!bogQpvN4vkic3yXK0LQ~x5_XvV-)#sEVxN`Ynr&kP6#QRS(3EWTsz}uP zyQ`&SCF{gJa;^I-x+TzlyCrRCPBW}4$X98Qm;UyAaPE~+W7ut(ud(DlKla{VCnum` z3VMYU1*u!Z1RLgn2Kt#E2b9 zlyrxe%>8Z?ejG|gom|U81$O*Lbw#4FHoWcyD3qlP;l1!vyCM-DL}l4nGl2l;&vxk> zPEneGgaO3qTEECHVNx!}LAABG=5Up2b{T!Lqg>>W+a)I=D}p|$s&dGjbg`$3$QbEU zE>g`MIAjd2Qyj1(+Lz6&s2nnR7Y!b>fx(qE*vttR9h<;MP61>VXp}K$02jb z+Qxx;RYc~HjhO@W0M*>ig36(;&aot^^XH+CqjrL)3!j>CF?|%W*f&4rTean%AK!mE zlIeQs4q|N|Z-)F6GP&s0VD{l&M$tP;M(TB!&gwF@DaI<&nE6k>OCBaBC$@7{mTy_) z2w7|KM(EV3-JRn+qS_hLW77jBy*^%?+86VCl4d?ElMPNAPx3F`Y&d9HqtkdXgCo0CGvw1{`;)tC!@`qN+N@R0e0;>z zI_Nz~pZ_s13=z8IRZcLOCcw|kui(0uY6$mp$dA`QWpa0V{+dqXT}6K*qO|t?+?IhG zZ)qVDIe8-Jh8>RFsj?vV+ZK5w#|)#hfa5ctZ171NGwko5KGFSBgDUqgehjIJ%@RdI zZZGP%(rjjActZR7vTXS8U)@o+W)JwVkv~r*5XQc3?t7)#WE)vTYVh2aQ^o#NacV8T z54kIOOwSzM{ox6?0)>^3~q;i7FC`2k6COJK+^|Rn< z#to|Y+fy^VLGa(_daZC%nC0egE6}VRhVG^5f?zW{l`uQ2m4~=m`1)-h2k|>4Y?681 zit=HUb5Z28>-ZwVKbGH6OTA5?>kLE@JTq@|zz6#`>z8H2DW2#Q0XXGg=DpN?yQtzT zt$e%Ed{?lA_!^2-#Lz;NQ*{GZvV|00=zDi#H&y)RGaK~PH%~+E47ifn^6cqSa#z~E z6DmGvmM3KbzW0}VEN2T*cheot6L4G?*^{H0E-GR41Z-nHuL%yeHg%bV5j&^< zIk^x+yS~tuw_tbzx~lB+F0<_PBu&<-HqHvf962w3*6{?TVNdy0cVGo@uT2o<2TiyyiFoQbD2+thHs5_mEv;%HF- zRu#$PjMzzKX2pC!@2vrHTe`Zuc>QbCg7 zAtJHS&*WLXfw_wq8X3ZsJa?pt(%A85TIr|clheg`;lPthyvV087;+_N(#Ov$!GLg> zECPoeU1MPm>7Q2kd1Tr?^OC?JH%UC_)B0)_&xu`26~Kf~&q~KrZc@cD-P$|4My_Jp zFXN8gNjrZ9g+8=O;0TfdQ-JM$=DR>}AWgU`OrV1M?s_v2HZ@4X$nYW`Ma~C})l3D0 z<}S;Q(lj#U`1p_jVxs9u?GBu`xbfq+)N0o4->%Holl@qOt_M{<&}91jE4p-Mwxd>h z42`*ZL>T*L1O)<2_N2gih>ez!p z-kx;)Bm9YrjQ$-1#PC8ZM_-Bi$sYhZ(s^qmncLRI&C-ia1eP4F{7vxg7j| zdG?x!aNB)MAE)O?N#ew`3b5B3+M2-dtn?G+%X!l*%E&*sh`iEf#_Q;K5yM+fcuwrR zv8ZmR5j{}gG1g4hM4Y-!|aQKfoz1Ug5B&hg1lVa*)6ICNAK=F=!);MlU352?d-)gSS7cBmAC8ZiMQ z<2ys7u#Y}Q+Q}dUrXK^tXZx7%1T~R{L!{CwL>zyF%Uo}kB*VaF#<_Ty)xajsDUuMc zG=YB6An+|7^p`lL3vz)FMuyL{n39?H*q6PaaoPlhb7Kh6ZwH*<2m+V@ZUrIWYn$$3AEUCE(m0O#3gshj~ydCb_b=$?V5OeM5Vu zMU)6erbxP*%q%S(6oTlxL0yNu@`E-tJ6&ou%%8yti4)Twf$nRB8EZ`h7bo~+m_p}n zPObDLdou>Fu6_4S@qRam1YT(=5dktov2A5`$gr+45yn`_iLOir;lsAWpSU3QbmXa8 z>0;#IP5g7iZ?{#4ajgvu$8&0?iy0{9!c?_Ou~Q7_dUo1%Pz!J{*m6B%;;e&;h+(4y z!~)E)6scqF8qO@HAFs+-WAMlNu(xRDrT4tcb_8y5)jo1@H@f{zyx+e)Gmtoal0*#F zBT<$~Kcr@aJXrgJX1u;ahl7i4Kd-d*vJ{(WV$pcDfy}eeLvWjrH>WnyhoiGX3T@=^ z*&%D6Q;Zz@N<()%3%E^&Sw71jvtp>FS%^)Z{*LZ4dA5=`yQV_tHo0cl+en+^^gUt` z96s2@TcLA8Q}*30>d-{Q+ODvUWoRr_BS&zqgP%ta4)9Mwu&k96V8XOH_E671QK5iR ze;o}64J9e)JV5_Bl#+R1m{KW~Bi0q4`*%p?Hku(#zxCJFWo&^D$7KT(sOP!WNFj~! zv=uO$p4-t>_H7OKlRgGv1F8NQ0SP5__DVJsfRL+$L)?MkN%rKd4NxPOYrE(%Rv9@$ ze1={2$U)|E@}M#6B^MV$uCn{7!Kg1u3^nlW?McH(aq?6@EvApx70g;ECF5DX--MJKV0o zkjjYMzi1fp!ZB17U8UAQ^LyYWu6^-y6Ec)_j*yixOro(;trFZNFaYj#hMnf5_VOe=p02tbJNN`u5YODfUI) zkdL0ic_s57F13lS4EcEVuwOz+=Vz7a5mD`--He>CCLzm7t(z!z0aWp4@!a{s%4^vG zsDAIb(b@(j`*Sd1Dna>)ARJ=jpHfHg;^HkrUc&j9f+5@r?Tha(WPP5p1EhOtSJ}d+ z8ZJOcXHxnQ7&y^nh$^l&Nm~NK~iS`F}AqV8DH|S;fsRrJ5%;US~&`@JkfgE-l~N zC#hD_rjPehjn4J}LSDWrJp5bZ_=Y=g>$|Yz(pxPzi6RO+eq136eU`WEF>-z`0`ImS^X?0o*+J{&%NgGAPO; zQV?mB?|MOTxD%XV_li53$`p;EILX>}uxf`BJaMX#;D%aj!vf0DbL4N$}QI7V>YRcz3 z8=Af+21=<2czL~}SAxoeNe!>LVrPQJoi!GP*KVG7f7ewwu_<_M>hHG=W;t;*7Ziqe z8efI)gM&KkF5G>&GCGI3@U?UQ!fn9211UP4F|?O7=HDuGwF?IbWsQGSehODjpa=&=}paA!U>u=Ga8P5&<;|7i)Z^MeO^fE7k`UD+vX9}u) zsMD2j*4Xt7ILkjHIfRkL96(09+&-{9dL`Xcqb%#Vkzr#@#*>hIW5;|v(x~E()cykP zP-;Tq700(yZbJvRS-*{W`n5r(K1ga-@tKs{BECF1VQ0vMP0o0oKZ!z|?I!r&)i4$7 zuU0%(ZE`-&z>Lvs8wVqu=^sc6w+`i?+j4{L8f#1wauqM1AT#=~j^VR~@3>+BX@Xev z2J@Fo{8Lvyj%7Ww+qH#92lWO0(Grr&l1ZZ&QNcg}xv=!NeeWhyEwXr`#`zHRrZs^De% z423a;3BJ|0b&rwbo_?c4S?MRQo}Rx8gARuwe7`)~!{f?u2z7rP`9h7(Z30D@%&5>u z?U9gg>pztwC!*jme!qd?30>$1oAiwGO1dsBTk-5bpxWIFpKn+(j8HED4bR;-G#g>1 z`sFL0kpu7AwcoH{6c@1mq)$}&EVYc{;}f>4qEf|nUlvoZN>U((c7nkxI9agwWqN9L z)~dkbj=i$Mnl`RP!ScH~MuywjDFy;cRvFngOkTIeP3P7Gl5SFG)yjAQl`39}7@9}= z@M*O%RulyXI59(N6jYiN@A#mnr&Lb?DEqyXb~LbfF2)8Lp&&wi=oZDGqlG^|6N`k$ z(eTT&wR4!{wTuT8Bg2{)g5DZo#ldWw8sulZf=cw{t4Ng0q zcD7@5LxJ&4YE$7ANXH_Id1m9*ef|8LR(u2z~fo z=&MjJx{kHu?izkUH@|09 zGNY(1!7g^6pn=4`sF+PYG6>>)@p8n37uizkU*wu|rpTIbT^U^%bW-{5G3?b=-KN zubPeR#A#jo*}IW4^oKj2_uXT=AJ-5sE$|VBcjE`0s_C|-r5oqHE*YG=b*(TUNXpG6 zq^GK;9Q~~(m7A!tVqT8dfFt{<@>61Bf|k+%1Fu2tL6+>m7$)gYpaX%atdEg z@gcX+*A14TtFO#v&}=+PMw!usAnFq#2^(AD?0h9-U7GRK7qOuG;in|PAj*ENy2g$_ z@4j^1L>V_{d}zMiAP+}xv~D)eQ@=QH!R=B;-r=Cb4@X5Uq>*)9DZX(=Qww)<(!v$! z4;wzYMr5adY`8&6ptkt6st{GQ!>BDjtt!eEH9CU3%4Y0dIT%m%fmwAzsa|p9RxzUV z_2tLS?ga;E}9%t;h4M>$nOOj;9tDlWzMzdX<>6My?lBKj> z7wHC?l=O8%wiJzdO;W(JnsyoG4_EKjk4AZFfgave^M%>YSItO0^&*N=5o&h!y*2|x zmBI`2#$d4k-dJvW9v76m>q6q;FIGc+|G1(exr`Ruz9*JQS1du%z^7%ioLDprQ*_<9 zk-k%OYG(TdFbrwtiy>AUO{R4 zCoiJ}&U|HQjtDL;NKFIFD32`|b$YTkc((GlC08;}UcI_b2nzayK!u~D3;!~@|9r7w z9I=hB0@4n9;N2i<5kM@i8FR+Z^rI58)F<~u6@?6un+KIpmw(NdGB2=+;`*JDokUY&3Y=Wav}NfvX(2MVPT*%(&Xp@|)cjl`~TZ zpYQylZ&j>LPfpiu73y-1r`eS7w(-*+o^zT?Qzc5j#bp;~+niRaf4+*XL`MBwrD>dV zlO){Nr*)yrxg*QQLrrOu&B2rGL^rvSU#9^r-M&dEq%uY&1r%5MRcn6-oK$mTXz}ib z7^;kN$(1~>2(?=Xd}-Qr5(PI@sN>jr+eJTxtqZJ+q|O#y$vYgeo2leM6hP^-(x4zwx4% z992eNvC#hhFq(si;{FH{JEhH>;Q$H$l>d{d&bC0R`QEau4`FI&bQw7}46AV~q^hUS zU5g;`P)x{-k8s|)KvFo>{58~T|3R9~v`X=QRmPgYy9v}@l`>gXdNKz%fQaJmh`ZzX z@=BsxVWXdV!joFO(oSkhXU6jED>*h>mFn|qXx96QKt7NHsJpyT*cqR#NDy;_L<%~( zMK#wblf~2T?fm}CoEM$tOEuVo#M+`(MoYO_P{5#Zx@bwjzE-vG%=*ic8&5jaP>UqE(vRZeDu`_pbr1co%Cd61|;^?fO*)}1P z0=DmHk_^c3r3rfzH5@l&RoUoU=28JsK0`=~#r!S6UT^lita zqM4P3`rztcYm65k_I=7l#lU&jORbx!4K%mjw|(z~ zM@AxK=$%-DG`T#xiIU883iR+=ylrd5R?#_})hQpUR#6ysYO}6voiOLc$8PqYEox~p zJnPLTe$1PHtD^B}rAwvli4vKXQprdiTvU_4Bu`}oh)mU29knE?czmH=u1R&0c(;*r zJx_nNb?7!accq4>*3XKtx&G&U^88!V%BLG5SJU+j zSQgu4cQx&jz~wvFW$nb90`#G%|ysBXh~Xuy~)%Lj%W|uL&Mw77W*$Eup+_ zeZAkrCU;R&q)XCC#*_7U7oW0gys_Kp&+*xNvze3b@sYbDPKog=21vQ}Rx+Avys3ly zoS8gbnQe<9REG9r!XR`bS~hxQS(Bd)>Kn6}XLw^*((5<9Lc?)nztm+|ms^L*(}(qi z3c`0-MZ_z*8^_WH;t40%{n+i|>f)&Ek5dbOO}ut_2Df|lo4$#@J%kYZJ7M9#h03 zzKY~kZ1)oYDsGpOIKsoJ=+k=Ac^pd&<0jhdLPn6MHC$)I<^G-`{07H;y5pQv zK6sbCD7WS(&|jau7OuP6W5}sSFRebR6as-)ur2KJNF~F;D6edL^b2Fh&r!-2-Un)Op^v-mnaPS=9lTPY$)TF2m|q_y4_T zQZk8Nb#AqdYsbmUL>g&vjEZeUre`+uvqSo;FbYNbr}-VO~v{?wNJw zsH3%vJD>ZMoK6sINt2ZonBd9o$gvUQb+f8D7hulNKB?Uu$-)99z<`Se=J#I_!Sf2pRFoYpU?wmO3Um28^;NrB2T z+VOqqQkUKw`nBCgnA4K)kp&q!i@K}uwWASrLKL56fpuOrmF(GN#)@YGxhiQ*Yb6k; zc@6LPTwnGM>MVcG9UA(5Khk=NcQvQ1)@y^j`}~}2z-$Fw90 zek1sc8asy2A%Ntxu99U*o5TgPZN5OGwwUpTmZN&^Q)%f9l3h)ui?5kc#NA8by6nW* zmEiCqiU(x_r}5?X#G<|9XL_tOxpxwSdxMsn`5kt&A0)@pA$#47y3W z>T4EDj0JDGTSksi102E|TN)Nmy%nNQV_prfUF_`ZAVtJm)Zvqf5`h~zPrSa82O0)x zo7mNlUJU1yEyV)^IiV-p9!HH-DPD9we*m*IsFGJ&;H!Nvvyd38R?+Fz{p3~pqRVwb za|s)wfg*#SQ;E#z`C`2=^E!@OCL)t*48?T##QKWLu_~W6$HoqKm6G>b@Nl@iV^0bC z>FTPyqi?JyA(p6X9GQK^{on0<@>BC=EylYyzHa^edEr=1?{ncXo4Mb=e*H=hmQpGD z$-d4&@pVwEi}v|v>v?CRGeB7n*IA#}PN2n5AC~j_CZYJlI@l;Qft+$qYNnrdpuPU?2 zalx+|ElS4v3}JADbm(b<2vy@;`=UUx998mn zabd!|cCeIShYaN!l`+%1@MYY*MQNkwmFK6%c2=mD(q8Td=vq{h-CHWc2DDO~1^emU z6`mdwGZOnF=3o6f<}&-GpSmkL+h#skctHSiO^*9>K||%Bvaq`&_xzZpM=G^sv1nYzE;q3bsq8D3Jsy6_XJ#iI=0EQ_b@g+0j|dH5C1Of??a~ z^-6nheGI#M>5T+*vsLrWkrFLRw>+|zsC}^yCv9pD;n0Pzs+Hb(4YTQ7 zsS(7T_#Zbu&7s3DWVwCQ|KCVh9z&T zEWJL}@wpQw)Hf+Nz}1#;I}+o53N!%uxmO6t2ra+ezCSi_jNu5VYcnBGYbtk}HJLL8 zvL8nRp1LcdNQ&s-q!?H*T#AlP>oQh}C>lq^XL?rY3lZHM%)@KHi9iaj~FY8Rdl+f_L3RK&EMQO>7c@G zEBv4XmZmF0KVlqVY89?4L^QeXvCjNmB`*7l9wRkv5D2+@>XnHhm(oLk1C8WWQ6J7a z-au3t8HU>&TA9s9vjJl5K2^hMdG_&aW}mGHw6^irXVev`z2?4wP`Vd*>Ugr4i~k>I z-yPLd7wjul5u`|$st8g-3rH^_B7%Sl0*Ocq1XKb@k={W-DJms^Gz&%P2^y5%1wv6! zAkvg79YlJ2`^4|N_rA66TkGEUk3vq)-m_6f!7^@EToSH{7(&P_z{8V0 zyMsw_Pw4#6B;-Kwouzd25LmGv))~ZK+mK#nIv$(hWC|!4{J?n|xiR6ZT4=A}TDB$q zacA(0whN(8)f5{S@|Tu;P1l`ajWw!u*t*q!4lUnJmxm@ z5FHV)$ugut?Xt)b+8bIJN0{o9%^i_W&Y2}6nb=75q4!8%oN(cT{+*=E7IjxMBf9>r!_TT;^p2-Rc?H&Evqk>s<|`Vk7?*k6SvX6or>I)0|Okbxr5H0i5-e5FT&8Uz z4avtaopyVyr~^N%z3?5zvjz}M-omNdSR8LeHDR&8_3t7Nv;YzXJsvkI=4FYi{ww3u7>)uKYHn zE%WwwP51$8kElUafj5d$ryY=WPfW7$zM68INCnO4z~^WawTAoJWL&M;U7HQy6>AOy z?Y5>|MT1S&?*>6b1Yxu=K@uFwt!HASMFr%d2BQ#!V6AtqoM7$`&n|9FZDpL73_6tfJ`reAg(Cs*q zC=VFi)6v9`L7tkuz*S^rs*+=_8qvDc$v2Bl32kyDe7mSRf3o1keELNB4)WlgNpCct zRAkp!T}_oZ4ZVhRZGNZp<=03H*SWPZ8jov%e_5??GQ z({^2*)Xx3nhP(G90v=06c7AKWgADXvtbQk^dK4#XIJnc|L)Z$=*SY=UYm}U2&Hnnp zVNglIsuc?>NQez-TWyThtsPD{oWKp_zYbRU*o;qo(Q|HmXW>(R-~p#n_NTRbep^Eo zcReao@m1&x3qMOW3pFj>5BiORA9r_OR#a`li(p(?y)+J1RRDI)->bWLus`(Vu1T<) zMjJ1~#<>dq-*|um)_hjswY(#Xprm3jNO0r#WOsgGH>c9;7MkwaA&b6g84oO9EC5RTJK zqHvf)aCXr}mM_e-Byay|>T42_!;WnKd8+Q%iN6L;-B+U}06iBE?RvV+cHZ?&P|mnT zcVW@lp@Vw}g2uF?0)O!-?0H-5Lo27;0b3E6#aKzu&RBAup%ko2F{m+1&YqMEvg8}` zXsrdg?2Wh9Kh)@P+qi{$%;_w|{_IzY@4?BJ21jxD$Xvcq4GigjcjuEzlZY>NMA>IF zs~kl-4_^1uKwpg*T%*n2Xt(+StG#GeSYXc&tKxt%O6%Ej0=q0{u!}qlkLy6>a=;c3 zQ@zE9@~|b1xHUy(h!Pb-5H_Djk|dc?=g!4xF7{Q`Z_iLSUCwRC8w!GuzGarphXzo$ zDS`=RPFEq8FyY$E7k*PWX$FmedN2@jnkC)odrTd^V$vU1{<<2u(G3|M!&@*5ZOntM zp;DfdFq@UHn4f>wbQBfS90Y8YS_0S=S-Ag-jucPH0dEe{c1k20yVw`?bgJfGsmK?)wR@ zkijfL1zD^!1?-pe-U0CWLjd`BqYwb<~aWh3AUhfGf@y7xq5|t_ycu`+~Lp zir`?y9Kq#lX)I1u*ZcY;8}!4f`ryr%dm!#nRlf=Le1tX}d~opdmSSNl((4;5gB%Jd zoT_R~c!<^|7!NfnRO_kn-h*wu8#4F- zsc@{hwF{p^cj7~Hv-Z+M7)0t#mdKWx*<)X7d1FfVD_sr z-h!zGLI(A`rCSe>1ghw`ICNFm;2i|vpwaW&Dv+!oK{Gn;5n2%-Q7vq-dHe0at@lxW zC;+lH08|R45cxo)=FF^P^tUHnF0@Z@08t(g*!1zaNA?eGR`V?r28P|(hkB(W+~@h-zgwb60r=pe79;wRa=tu{zZ%c(8>iRY}7Cf zG5!@ue(%Zv6IEMh^02o4Eo!@an4L(A5N1^*2GzvR>BON&lz6_)1lnNZC{X}|{5)G3 z#hZkF9fp7br>4>%gI&HEDs}&F(gT2YRT~n3Q_Xm%Yow1njzYeCv?!8Iq5U|TBn3h_ zVE{pa37Lr4W4H-~<|0)Y#`_%C(KS~e^E+Bm8v2Z5*r7g+~p_!U+;J1Q9}ciVS?%g zRIV=e1yNz+fP$8Q>I;*1nv{%9Q1KpE5H}8UtIF3R zXHtQn3@=gSI4->mQ`4vVNPwcEg`8FCVnRtL9#&h2qlMHOWMqhWj5w!qOpj`fMX;MS z1$c{*c)46ZPZhi9pYJ^Y1(r~x*i^w3*P9Z!g_N(Sy#ldaRHy*}I-qg_V3d{eukAV_ zHipF0it@TVVCkTSq8N`kf$J;35ecf>c+`p0l(@oj@X5EIu65Ic;V_<6ac-OIXPb?T ze*2%+=)lu~q6sl#Yqh?}F=8vBZzzcdhujc^@S9~x-?SDskbT5xxgJW$P=ura9-{2^8N}+ua(f|9fEs5=#%y0y{JQ88^^p@$t z*c%rdhQPe5M4Uc>1Ba>>wUlh%biD7L)qGI|=eRevxW}q;=X&eA?QZ7*3j7PE=`^Q+V*m82NZtC`z zMd=cob4|sIOf9#o4l54_#$!mULv`nJ4Usm?65yM0b43SN*T&~PWK%qYrLx8=r0+c) z(SX@^^fPS}hZJ2$t9j5~eXoF3vRk%at?}L1OmET?v(6ZcFPyuxGI43?Wc`mt(zwu{ ziFDT*RSB046lqX!1OH;#_*_a%PtwE`O}Cq=;i33N;hv)v9G|B((1+z8JTv#UryZkO zLK}Yd0uJx*dphkcWz%Qe0O6<2!{=s+KVyaz{iZ+}kam`&rjBa_`Wp}H?UZKf`~lMk z+pDb>%M)ioz(}UJfJu5u0P$3o*w5tqXQ^vM-=5^8+6YeE304orK%_~z98;X28?MVC zJWWSi0g^RIe0CrK_d=65Blz&@T4se+3CyB!a+`IC8E#KH{956)zdnrvqbSS#Vnvn4 zfT*aoQ2Flo9;gTC!ZQKv6A$G|Wu89|2>4T+0?>-07oUq}#b*f#Y+!{?o8$7Rn&l%H zfL{AD4LVw-_(b9kHv8O|zb|gVggIS|r~$5n00dD_EvR6J9XYK2aA$1j77m6W)S7RN zX3ODV*v0&D!41D}^ckR_lP0<=4xBn~^VJ2GyCckOgG)_$n-4yYTSyJC{r zB`ZOT-&^WZNp8c-!G=i7?;nQ^ZhHjZ?T$e^CG(yj^h?=d_k$h1mn4vG9Vp~SH?K9$ zZ{r1lf4pictU6#-0FDP2-gtxu37{Bo0b$Gup$Jm%&*gCkFuzj)_lP73e`CQeifL&?u=1 zvP+w$aLJqH3u9siB-cXwQ4Bvwmo6Sd^`S|cieZCikzR7ZAm(9+tX@`obdky+LIcGZ z`oC*Dh7R7t!a&bjGjy$prn5H`UbFFx%F{LV0lMmAC zAu9ubWhp}SFW5y=IcoItGE1MW{+`T!0%9Mj(5AeVU4}3-OndQ$m0>7s@Fv1*ot2^7 zmXr(`5{cmDCVWuodz3_`O)3jf%DjESg$AZVP8-b|egNgo7+uJgL_%pF1?FaKH$EQ$ z;FQG0g-N?M`iwRN!Af||)?dPh1-B4Ycw$+i0goVzj5)Dy9PKr8+17iAZn}s`g7L7S za4?x8xcv*53|Q5ucp41hbvTfn5f{hw9P7 z)WWK^=_1>43$2jFhXK6Q%B;G)Lz!=lb*|JG0Rd3e5k%M6U0J(cvDe&= zPcKRUWh06R;czr4cr~Q`>;DZtF%KycORD;Kh$zwvRsvuO_)4M(af1q1g9r5r{@G}0 zNbd(8bm32f7(@n|D#Iok=n5zosn{;hlD=PQPYOCs;KVMngG)&yV&l?td9(m3Of0%9 z6+LuYIPn;f@KK+ZNEzNm=J~nR+dn)(jIA_5F9Z??`Q8jk3$1hie~AR|)Y zB+!TY7nh<>_=VXm8+L8RAi32j$JYjh56fdV12@VqCGj4D3A)n;%Ok$CI#LA z1;lRWxU6)jaG)9@1-R@+JYN=|732&2tBAhUoJyw=3VY&dSj2{NRiXVs0ILTiJ3){Z zjN?Rw<1kErIZEA>&8)i3wP{gmie0>yY`_Dn+Vb2kd?;2mJiEVSNNI}%&~($kn9;1g zxfzes#vH-M@u6ZIN%IMLpb+{?KO@l#=-opx)$dQyhijlwR8ScVE6Iz>7qk3<>Bhgk zfQf;nA8Y35!Ka`NNaKL30;nN0i3_A-Qqg0U>%sL**cQBzjKQ3fy-Ahq#Y%S z(Brz03X!I8pkV+I{VaV}xMrVmj*AveI`roFNui1GdM~Eh6_1VyA4EP6zU$I~f2)n5 z!Wu$nD=V1Ace+S1ZHy2$4wG!aNsd_{2=Z9TCF&-CA+0}FubTl^Y}QWgNKylYPB#F2 z6JGl4+N!+>TZZwrpo;qPA^zOp^6S#;RbhuB#)$6t87z#-7p#qBZ&qv)x8{fO|6WyQJGvkA<6G;+MD5ev}}E4uZ1T>C(j7z5Sn2 zvFNL_Y9cbCi_}fPi7mGjE>8StxSI&-t`xm$c&dZmjnKoOg$RPbedzV6=OilF*Yo>i z8=eW2*ht2sOHoI{Pod5U5?1(zNo+R3>VWosL&< zn5~{-cNV%*KM#~+cHnmt*#+QW8we7dMcTZRJB654gBCtrB|&k*VBea{2oA0fZiL)9 zQ@D=S0@BoSkFZnL9)k8Z14usxooRCnr5HI#I?kNVPORna-`Mlp2Pc~QVLw|1>~b@0 ze3W**GRB^R6wExp$t`%73>w_X1q?rIcMH^ef@3`+?RkKwTrGOV(}JaLxbV(YED2ja zhJk{yF)LR#aP1CkRR$Ejg>WA=3aHIdBbk6WGyvN+*N5pMg-a5YzmMGip3?#jg;4&ubSXk;3i_*@hQ&Nz?N_r zZTQe`nWI;Zse<0(2RBlxmJ}%p?LlP?s9-zv=Yzp4TA#$01b|WU_rJX~UOuKs@5ElWe>I3i2#U6O`%qA%Q-aSO_<`J}I8I z(j;;QYiXCv0N7b*K-#&;)*LptbXvg(%V(tG-$wR>8w#vS4KjDgZV+G&>;r%2P?)MK zB`pvM0+{{P=}+6jt@tjpb{iy&Cj!!0>t7BpgR9wiAfqxeZw2b2^t2(2vT^X_5z#_e z?w9Q#u$;SmK5Xc2@EF;)LaFzfVt5D}2Xj%5F_?;)Gl&*EvLbC1#5kn_PU zJO{Tw?LE|hGS3*d7@5Cp-t zNgDt~4$sOkIe>hkJ!#w}LlR&rm#=+!&Y(C&_wXqWqi6j*Mx_Ces^pZ2o&dL>Elz=2 zLk=3;3~+0miOyx1ibDG`Ou4jK-z$)~4;zdnV;h$hm6v5jcw5SITq5;}>9_?!mT1XL z^cdg3%&SzrG<327rE@DNK1nEZgjt*=PWMoQ%8;hSE`-8);FAyv`E?6O;lysv8E~Fl z7Uza+0@(6tASty=oX=^(>&t`4g_9+AM2`bNB&?wTsyIO}`5Y)sO=+?Zf*1$DOZ64A zcF%U;p%y?60$d!V#H9e$ODl(B-T)JU;s%E>X|Y3}w*bPO-l!NzGc8{b#+v?Nw)i2Q z#vtg$hcz4rHQq63xrc~C``=sRd(^}w!-Rd76@6d<#T}yG>x-Pr3W!FpfvYYt=)}8f z75X9`Ae49IK(}5(-343#917M0oBB9(0tPY%Cv>y+NpNTpPkTKwKm%R9+dc3)-#sDK z3u5!$*HwFZr_D(DkwPcvAhmy5ILH?`VJ*q=ZAJY!{!1ZT@^nmA_}}JF`-tj zE=b)c*wQdknm{0EP9COH0#aTVwwc0d3Pz=W}@Q5+e6_Gp)~59Ehk4EOTbRJl{OZNJJqY zB!H2%7BYqCy9?(Shz>o_kwUfkmT=FbzHd z<=cpjJ_SnAfmK@!f#j?^c@{zw_3NT*G66rFR!QTp_P?!NWyN7AY*uK9UQB>tNemD% z;xK3q<>mtq@>LC3q1Wkt@wC0u8%-UcFdKz@|J)S*F_`7xmZ@%d@%bh)1$w{)eDJ?% zI#XqFq7i(ucmZq!X#aq-3Wvt~tEvrU{M?|N^i&k!fI^!%H?)l*eaIz~&V$7uxHG0jgD=LklRVXJvR&oG@DyoN4~e9G0$0wBpKnqWv#tz_zgPJC6|V zQwG;2;Rr8E2L3l6OySl=2_(QmaFuaT28h)hL0|?;sYrbR6s-Xq zUeOUbC}|NJs=zWc|K$qRsFFYO6c8bFt^iBX5(Lt_14dLhd~6}?;gHlTHhYb$q zV|-y2;aYl{zktdEs0yrQ(HZEz1d7y%Abj9))C8(@X$rT%Oms*CYNL&bAX@|r1|kQ@ zzGjmB6N+R6(Kvj1Lq(Y^`eu>c+Z+}8)0NBQ=Nz4QostCdX&nVb@v7IXeE|fcXu%{v zE$mT1#D=lxCjip}eNu>@+Z|)7sBk|HLwa(4(j3k4ey3MSxY zpvdMKE0Eac0@9nOZi>z-1@PHbc;89_7zZE&hi(9z*MjsG$kwTxGW-mHmt@vr!h&`5 z?stIY`9C826%4s~#Eip0Dc1&bI&^6b7&ho=(RuJ#Bnr?+Xh_=U zAzp@T9grM71Ay4hvqKT4@Z4}MaGf?7K{$i}7Eri6%p;fu8`q^~u$)c!ZSue>V8z;h z_u+f6r4;PKF8<|rp>Hf&CI>vBLZJ9l#djs_f!iv~As zDRAO!3}qN|W6ojWZi4N$`!$I#>Ny^WlL_2U1_C4~ zVnLbq#(>beI5%XONT#*p1WhtUkh-%GZwZ^!KWzI*mgGhgcoVw$4&!+YArtae2k~(W za4!e=D)0h92&-NW+=|G3@|M_#RzL&UF$CcO7N8MxYhe#?+G;iUK~{!cJD^PPQY&FO zfV>pyllJkc_+UuoMyp0E^Z~*pdV#Cj3Y*X-Y6AIyTf!dU|ExC?8v0}1R&fFpjnKqO zGNKOO{e7k7Kd6jFOM?gr?|J)=Q|?HZLcbuF(D~5Ah%r9b-oV2)|yO8?0@J zo7i7ye;Hco13C1QDA3Ad;D^vbZ0*B;J~OM)p9aPs5Guh;55WmjeFF$`J3?2UKnP{L^2cBj zh*%es)#7*)z$(XxB5*XPb-jqdiQ*hQdEVpA1gJFxGe#Rcn$Llla&-Jh*|$^IxnX zFW=M=ob?uB*rFwq(C455!>@t-15n7qZgFt_6d=Q031%Kq0M7s^-ef#&J`Ex1xnuv% z_;)ENBu#-8WP>51L0JU_D$_<3Fi^D+h)4G?>3}g0f$INO?B=Kd9*Qj0k>&SA3D3Y0 z=m_vkTKiqcKzoZ(M^eS~OCCqirvjKZXlc*N7QC_QG8`7QZvEoibYMdWB87!)Nvbdl z5OE)pAxjA34fbhl1X>^i&k3AB$$o)b*h?_NA`IMi2c&3Wu7Z~!9k2sO3GyefXwwLo zs_0!1Po{;G9~$`rJFw5irto{&=->v>q2OxtgTKQR0SmeCobFyG8npHJXDv~R-PjNM z)8Rah?Dsmd?2scnkaSK020_v(`>Xj!aVb!L2*~ewrPk9$0^xyR5Cy;F?$0y~D13hLp&L5wW; z`*p$qeWp9&FVnydCahOOip`@}$5sz6kwcWwMM`SCBnt?XO#Smk=Ap)!+`pznH5#P< z*#6w5LnU(&vGVqE_P{tm%o|anN15I!y@!>Y-}oN;U#3Vhh3l86AQbeXfzbhxF(3euAv) zDw+cH#URK*xAMhn8hlMNZAmtNRT84Zd9S?E&CHLBAu`4`1Wi?x;Om$idlnLL2)XYD zK>{o3n%wyw;?H6<)JOmZ`VWBfVL5pzs*q7WYfHKgvjB%$(D=cYBn2^D51{~}Nnk(G zMS}Oh3#twGZJ@RP=L_I{@>qa$z#ThMyKHoz2&5)|X+4RxT-IHm@D|*_F`b6S?M4tf zP2r>LC$S^HZY@sS12lqu8rLDG`p<0o(}iR*9|M;J#?;1P(hBXNz9L>A90&S-fTDFM z)&QKv95D8e9sVU9P?Ga`f4;h)mZKf-`b2`fA{m%J27K;P|6}tE^4}E!Eu|wCJOo%p z2L+p*md*>%%JoM4aiGM;3NU|iZx5J-0r-MDRd@w0 z57fw{yoWB*IL{^o{d()~ZU@aDBA{9@xqqey!=C`&4bc5}df-k}$fMHyzX*u>PYO;! z6aW?i3P5ud8OVtA8Umc*yaD|AzpY6gQd7Z(I^xQ~Yk%56`;cPDnaNC%=O|3PRV{jf--7fI1s@+B{VPJ6%ZZw-~5ChJH9OhNu;bGqwi-dVT$) z3<>1jDSRHC2;n4s{bEn@-W0>qLgT#+)1rh7QcgrZW)fmwu4O0r_dowOIT6$cfbV4v zCJ*BYJ)gRHo4ngTva^uq)0T+_L8qb|gM17%`N4L-A~dk?fCTxuzs#>VXf-#RlZufW zV^9;Auj(Ke1zN+MIA^*Z1R9(ebIJUAN@gwS#xe(C(PuXuECH@gzUk6H-gY|STGt^C zKF-EBkf}coyqH-+Eqlmc{_RuIN;IuN;MM~crwyiVYM!~XYwitsonfRym3 zIcbD!!qi7-$@KaWBB2GJEvBYD0sMh}Z-! z%utZUc>6Oc5DP++qIz8*`V}<*RHm0!5(#yn5o6#|Ys6Q9PmFm?2o5A`uz4TO@QF{khRLXBt_qs(i8J)3lUtF0(aY zX>gepN4)lHQ@wN+>sk3$!_j$E^495jnY<*Mcc<7f#>IBr_h=vGR_InfNXRg%KG~Z))!pd?>It|KeeYQ35r1E+r*l~&4>1isWm(E7qGcBm#u12dp zxA>h}dlQ;GK@MCFYyG54ur8~olI+4;ahJJFE>apGqhw`8j=DWfFUE}girmiEfTm4q zI$~1Pu5$D&>y6MeXBt=5C~=i7m1|bFju@O^xdTxta3fUoOyeYY(A%Q&8-b-r^*hsb z8^w4<;}zwNOA$E7qbT<12R}iQV8JQ#ncY7Nv%D0M2A-UMw7w%pqPCffW%hWaEQow` z+>?dHAxkLQRH(nob&@6It+K!ZzX0If1lNnko1bSxLajljJEyOy{Pbz(cO9Sox2{Hq z$&N5_u(l>)qGI5i>2VRxb6cB!$UI;n~qUr+|m>I*5=fkCLhH=2>6^$LORr8 z5$Vs?_pVY*V^;1By!9#;dno%h@A=L9IxAP02@$V2a4tdRo|LJ^-X^b$;ul4sryV!% zUs-8`m#eL;Hdk&;0io-UpyS3>A_1qr-F$Q0g^vGYtR&5#PLa@^H9YxW&@Rpam^uM82`L z=EReh?%0j>aWs#Yx{4>$q!Piy{h%wHv3lph!tDs@-=k%_)LLN{##*u5`Cmlr?!f>% ztF>uj+l53i+#S3(d&M}d`ngz-3Nt2bpbTQ$&)=+1RSQGrjsA*HF#56rZ%JJ_7l@VT zwnG^>yrgWMWEl*THQc?TGG+BuD3;E*!$Ge?7O;XP_K~b_^B`MaTB4qcj`bJ%SUQh6 zN4+)h%>MY_XEp|}trCr96+P7L!k)8qo;8eWFuYiKuR5_M^(&6$R#nP@F-|SZh(@(? zH^l9d;&6chKx;T?b}Zx?|aChKZ#4Q2{Y9PAwN zS)t@F+8Jz?6TAg{n%#q>sRq6$`euAN z{`HB5&G6NQ%Pz6ALH&S^(;Tf4l3aaHhkkIawZ|)CmEwPK_3ale-sHNef7FJ)ResXz z(<;R=%a{%6zGtJEv9j+ok8|}EHFCt>ZChlFy}SK8lj5_-8DIdP(%Cg~Tod7V&7QBG zVXr4-u0|VcaNc=_FZS+$>xSfa>B!-wD@*~G+Mm#t`f&NA-TU6Hn$T7G2`pN2nX$Oc zSNY2o8KuQ{naAbbgXVdlnRPvXPv{#>>c+Nz0gPr@zJ3%aKIBJDHE9AyQI%HUe(g^{qzN z>Ufs^$h{as>@}yVzZn}#-nCQiIgC&CdTs0sP;dW?ykze$QGvHh$!0HZpJ~aPU@5K| ztB+UIr>YY2GdyC@GQ!0Qw%mD1-ML@AaKhK>8zO^zUv?>N#Q6ae67Ll_C>!g(=I6o0 z>cLFQRNsZwv2`{B?hJoqjV`fd#zYV&!$($KX=L;E3yXMw-R@n}iCSzG>VK8*`z2T{ zuI|9B>V+_+Omg63GI2ZZIq~r|=ZClw5t_c7!TZVgw60Ry6g(7o3(Q8&^8)MmuU|@# z96SxUab4MgP2sD^Ge=A$_pUsI-u`-?9j3}3YG-w+2yU(+WJWDdVeo#1o}Fw5kD8YK z_Q|2L7+rMST>l`#;Zd_LG4)CKNa~zyjI9BgvbRU%e5pVB9K&H&!sjQ-_m8H|*#`gn z46?FJ7Fo-4Z4@8Hu%7y#3_{*!DRQ=Y0&}IFeOx9{SG;{HZM1~%X*o(Se>hw4NS+_@ z_M>*#d;no41|L24>TQ$IvQSJnEP%PJD0X_hFBp|xCYU{b3IU79oNwAw>8+5V_$-yuST8EgXfQmB{imsrQYpLYHk8Te#te{&1~E-`zkl# zG2$i4w(~i%#T(z91Dslu5|_t4b9`h;d{sEYD@qgnm?lK54~Pnw%86@fYVOu7gN-!J zb4oVxS8O_yqCL*p84$)ZO|-Aj$G*kEA0ymQBfhgVrwyJbxE3cy$|}bM$~cUT+MlSI zI))?=8*iOX?d(C*&J+Z!= z(}p-`7Ks}_=T{Dg1W5?(Gas9y&p)WIHwY0MX5c|+E5=iguI7V{{F62d;dQiA9#bGR zb|+L;Ir`-RW^XJ+t?`sXm0T>slvDJXd5-a`%gu zResolk9>NWE0LM;3Sf@o%+CeHaSAcV-uhFO4XM>@lRhiQoDlORl&E4p8Sg*m-3x?% z0r!KatEeW_s&WkwG|n?fgMV54!cymCe|5#=eGd2)SNy$~x9fFFn&HSij^G-XUg5CT zy|2sa<#M}bue{;QgBazT#S*Objq{$tuA}(#78|^zwZ6K#3uv=r^*IAyf+2=}Q?kDuQne0`6hXR2h!YUyF1@A1r68>iks{37x@vSp7J=)l>SEvT(RD1^tcj*Wv_U zoG@?iVOiGAp1{2bRN8geZsMJ)QOV8E_#&T$Qk8-q3-4$dLnqHl``kK$eV84=&M#ii zj^2l_do3X&dnJE2JQ!(maN-nbfqy}4L@oNaEZf>6YkX2hPhE-LIH}jYRHBiAoUWrX zOSzeFuKjm#_%qL`3TK|*-{;y5V{_&;CnH~Kd5Cpe;|$cWcfU{BhB9#zGZqOv1);2g zdzTUt<&|R35qTqW*WY!h-7q6{F$wv)NYK*%He@})^gKsn^f7uIw_w%e>7ix-a|&v{ zeAzcwk63>O&1M+7~<&ESAS|)QrR5^S5ySlW5K7z)2Zt^mGCc4@Y+b_3Wd+EK4G2`<`j4f~qWS;X% zN;>+b=5_Oil2@p9vAfblN;-}E>06oKq_*s=WQl=kXK!ny5gxkpo5NV2p}*@3q#@15 znZw1e6v2`*oD3KDT0L8v^cf@FwULGu?c*o~{?LP?Z+PxR?zFNcqQ9daA^_Do%Gfomc2P9$K_C;H?Sup_!KJG)i;D(6lzybSSi#{ z?RkD*B-t&ETF-My4l8?^RCI~tAVgrqcR2yZ_@YMBxJw<)by-=|=G zGI6(JS=ozUciei!^kYaQJ8slgK|8KZ;w<+X((> zSGX}^*D5`fo@*ypj+@O?RJTa|WHy}A_ttwM=Y1vbmrA6Hbj(9JqqZIz^tPKzRr?dU zh4VkUukrr)eZw75g4GZHbNCqT=)hAZD1q=gk<#~DvU%_b@?j1tZNl*y?+5s6=Ot6@ zi_3QL{(iHu?Nq_>&XH4WkFDnDKHaS;wnzGJMG`=Fi0A@$%9+;N;kJzGFLQefR#Y`C z`R`9~g&mY42#u}%pMvd{XlQT8F&$f_*wJsn^Ye=!3bQ^P^q6H&{Zz?T_Uc%jzcOrU zVbjN`pTp6Fno&Jrdm z8eNO8@;KBoWAS1qiq4=nokiJBDXCsMgAFtojz8;PgSEKoxBVfd%l$iwYRNwSJYVz` zPU7z-#~&UlD;JQH{FwZOHU-Y=F@pj|#hAyz1}`~Jx4=UYk>f8khHvs?)v<;)pKd;J z)3g$PI6mKv=w`d-9+vl^?%l^%+Z|t;;MwbCKZ4Y*_n-7v%%5imZ6zA8K8ac8HIDZN zV#Kj@EpX~UhxlhvXb-y4UBA6xaTb>XjcIgM2BWWnR;e+K)VC&h*tj0=mFa*n^wLnz zo1a1{lAkw!hj4){!$JI=Kd!p!-3xltFC+)z9+$8kU$p;q>_)~dqp#sSJ|!P%p0&Vu zC{u!1NV*E|p5Exgd9u_pmZFcXWqI(V_c#pwEJn0OqVFix&hTO*(E_1Dt2ggv>QNU{ z^(oE0vo>lT^S*F!x$5^4$($e;kon!O_j2agR*Z-m=e3%ppU1Q^&+f&rk~VqwW)Gc= zTHxCovWKd&O-FO;CZ0y1AG&BaO}isU9#J>Qm6z~NvK{uFRu7bUkYxR24ez&Nv=oG@ zPnr_hFNtj{(% z>&$g~&7b7^BDt01Z=Ct^x|l$c6Hbc|I;S#)KYL`|l8DsocxV^>u#vZL*J=d1mTd za8cD}Cr;WUyc%4qnLzD~*d_TPgW6g9aqFqD@?rF02jqvWo5mqwZz7=-G&RW>X)J7 zZKdbkz<0`0GbktB`B|J%qb6FzH>PNeGdP3&)>vUDD~3?8mNRvzrZ}Bgs=C#N^RY5J zO~czX@A=_9&K?<>{G%zQ1Bp;RU7=oRc>A~61L+pHGNS*~Sfqw=8H`2xz|`cCDRpJ} z!JAG;LZd>ig5<=0!A_1&M~&%JQvj>6L^m*ry$; zZMCcR+_F?(=3&XI`T71L01V|tqCwvtyabGHFn0*fV}FyaBg;_jyqK0^ctjQDXK8MSkeGK{#zn3C(>trZ=8UES1e}p4pkI?N;IYG8&xXmVt>w{ zS8}gbN*CKUz)M%98a-+-Z(j_=aS8@H+--paKG5haiJt*?Lx1qK80IUoT65F9ojfb} z6G8iYDOZaFCb7pliwjlw_SutFr!Cd^0?t4lYZxigd*wLx=WB|CAy2>XUm5Tf_wgxP zQh>b>agN1RiQm@zgmRsXN%Ukz20%2p&Rw}H-7sZDBvX;hH%~vlmG?I?6M2dK#3Q>6 zpDtr-44aGMlBYAA3b-tYz#TT>3oI@upUAOEYiik&uA!?{ICZ0)TMXQ-<)UNMYR5DG zK5{T@>nMEUL7JHE&JaLArx6^(OHLnXdQ@^i#kEyn(Jb}+4_3@I;d4e<*G|h&0&ZdT zb+az^JFiDw;PbZ$%~7IQQV0IswZ zW~uY*Z5QPp>iO&A4D7KZqT+PKkY_62J^i|V@kXt>kETSUm7R+m)1n@3gby2y58SqU zB|q?rjY#c4QoX`~{i%JwOCfWVxOM!kwy9+6TLEk7iqw9P@#PI(xel!ggsTndbBe zuL{|d?HS*W7Jr-Z#PZrx5M6SA;fFSGFq7<8Nk*u$*e@ilDufv;;MLt4= zf!O^Co~bg}if6VdvXXbx@KwLA==^KycE`E1M2`1@^_bk0I9Ph5y-fDc&+?lkb*MBh znCd7orTF9!B2wXq&QAS({R;sYN%aT{CQY_yXr6#C>3Jg_so?MQ98G9CrF||#UjqB6 z0X|*fP5OjSd6}v7+COTW^}PNvJRt-2nsM=E5Vjn4`mkn9^NWELCC;AYE`2tJLMr+e zUkh9o;r066I~YC1(&kr28vL*8n?=g3%DQojmwlX-zDMir*E=1~oLO?b6zwtL*T9c0 zUY~lL?|4E7XQ@}i{1V~t9L*Vd*kh($b2mMu2&R4Y1-$Egl7Y2o+EpveWB^(;%`KQ$ zSiJLvU%fAKF589rG@Xq7(pOwKI-TmnE7;u9Wg(Wn%5nNH7PwAYtonU#y*BPQnkSxN z(UJ|asIv?fYp~1bhHizpNAe)6s)A+YbDuQ9v1s|d`9}#UHl4S{?9_=~GZT;D%hu-? zx9oPZOFM${Z{a$ri7!<9zsx*LUb#_~NZd{C&p%i;;VfjHaIj+X38m8=6uzu}LOCvD1`ZH0*m z{8KUTy2FokS$%p!f`9Hix4^5Aky%u_&L(=u$Q{+%1u;WoYiP?lzHjw+Ttgdl`^$%zB+R8^YAA)*0Qvf8~wnY57)WV zw${CRJ3voLs0F@@#4_#C-VzZbUgzxfaqBv9u6j;$oN;z)c44Yh`l8SK74xl%jR67{ z{pK?r!re^nc4O188(vlXj!889J^rnBl~Jyvs|(Z1kI`RY+CAZch3PpkZB3RagYTE_ z1S6{~DsY^l4?8bB@UriZ;^ykZCXOvzuc>g-> zbR%$(B~SDhLhS-B_9*eiueY0dd&0tKWKvX{f;Z=3)5!bb8_S7oOs^NwPLuWlBdvkn zGH>>OV|uTqSiN(%io9;ucsD<1e5q&ig$~k7byU8u`9OaYNnF`JKBw% z52XD*Ao{cXdW)UkKSaM#78UF^#?Dr>)2^+Jt|{)dU_p?A(bDfeR+;bs60 z&zYuX_>|`miIMF$>@8=$UsaK0!xPClE~a8q>bq(K2_)^o6SE%-c*Bh6PEo2*R9eL6 zQO34*iGEu)f4zDLi9PA~Yp3zKmpz&h87^}g`)YWQeY-VH?@oYFP7_XuafdQZh} zILOi@$gDP@-1|Ceg%Yg3Ta)#9CtV9!TPQ>(!B~eDtLNFcai56hNap}T?5#U$~WYK^MW*@Exu-EFm^MF8C7ClZ#&PO={p)bl*3{TLKkiSs*}?&c-L8> zhr5_X`KB0t4znw}BdClO9sVU@O0ZWny?crpAI2z^pWhm`PCS}Su13K%krbg!nJqNS zzEXKN<6tm~S$+HB;hAqla7ik5q0{pFq2QacJ(u{Dv5o%w&U5k4*KD$Bdgv#C=~X8P z?WutvjMk5#WB#a;c_)K-PF&SxHA*hcD4z+jjajcQ8`GZWSFhbdH3=xk9iz5lycK#Qa*~7dX#x9%#by}? zRYrrZYJXeej(YnVlrNw$O~+;w;t13#@ACa*2RpSd#!YGO3rjh5Tb8}6qSIuBQXorU z9(_>6@xhfV7w{pjuWM0$0q%;?;0Pa?Z#dtJXSxYIkM?BGnE9vgsrUq$ z4=%<4TU*rI48Ay6pQQ%cNF2p*PJ#Z_0hhQ~aBI(QoX3iU0T%&t+cboF#(>d;yWZO= z({f+hG$+%DjYT>9VUg*1s59*5x8|eg zHv8jJW67stJ98{sX}Iz2mBZKgoE|%%y|YztnxpQ*{yx@a5@o(d8FT9@Dc9?Z%j)sM zMe4^8Ldl_?gY*g=PpkD%#52@zw-T?Fu0I0=yz8@QvPTJaL#ml?P&3h5Jpaan!~XwW z-u5f&KdamRBsJX^_k0b2_9s?H2wL`-2iI>^u4Y>(90RK~WaPzq~TV2Vwh{kC>4} zkh}g%v;S-|6&!o#^)Jo;fxrj!eIPU1F`t5D?&X5!H1B=ulN|hzgixfsejYOOY0@ce{20edLk14D)#f=4|eiV6I{ zn`ZnA4K*76d?XF~{>PR7zoGsQT3_E;Sj_+bcJYsI2#0G>IR6W7vz1b&!_WT-O;9X0 zMCza5acpS*74wnENvvdnbB|>Oo1K<|rla_7dY=uKkA|3|jg$rBiQbZF{U1)_`o6xv zeXhzjCn9lO^e%fo)F-og3{7dzaw_I%2J@(>y594T>}y}Wr|&z8D>(cQk+!NHV5Eq& zbWwD1MRkcz3R^if6}m?Vx$3E)syk!fW~doQpF2XHw3^3RctCuzz&g<3Oj;N$AzWRb zZi_&@ifyl*Az?{kbds7j^>a7d`FW zd+W${QVZ|{NrDB1S}duJO^k9pZ&U@BXurmABnD{Ux2k0yWn3PExhwl7#zihdPzTCn2jQ#J5VuT=dvQ`G3SmnZl2n(77Eli8+=BW( zasHqhkx@%yOZbP1lW9m&4!R_mZ3UvNC{7De?xCyi${0P zZ|xxeTg+tFznhO;OSfS<>MmksCNJIfFg= z)Kr$gp)WI?JP(zE(lEA7ET$$rJ22XxOVaX3ZUO>rEtUBi7LkoS4_8R^OOFqJ=uGO3 z8j`o~R1)hV-B4qJd{&vIVVlXk5Y-rWyq zV=+I9j;=$9vAk)W!xESxXi(<>O2d!BV1$rp4Nb;OW3v;2=9K5C5g zb)u%Il6U822gferU)#ca`s&Byp$>?$;2_Ti5q9fM7LD%6eD9 zF>?#-kL8od6bS3D;ocpiEC%)5W`frg`RkbyGQNFu%_;1Yzgl-2B z_O^GPOg-FFtHfCaRCaufxbgX`Z0Ec;YQ)&qZgVQ;Tu?|V@`+rjsL2kf+vg8|wk(lv zt!trn;ZHbRf9qg-?JY`9@s2XnC6$g;aZ;U2DP@xlz;lnPg6p&9@H%{rdV47T%PnAi z(Kh$)h|^+RpWV&(su?W1P0_s0rJOPcHqD)vGH^kNNRRBEclH+6XO2fbmiu-M365n; zN@XCNK`KU8>eIQ?;NE#SYL7PFyWx^!Tis95R1Ec*+oQ}Iv zS=4bY&$23+E$CB-G&I%q*kP8Yaax(nuv{9f9fIC*8jrM$axV_gsan48v%+N@d!Naz zlghEiit&e6?(Z6+aH`0 zbW$s15;N(AC418ST@C&7skS?7~J#TsCrK0C9Lj`?Y}?jIQFpmLrIE4 z9D*rc?-xm@lFm&{hK!^<)^i(zEwvSBOfBZ?>9$_l>E_x4`x_)qlKxszntJeVx{aEQ zuYm0pG_u6Te`-z{g_R&p8`edWP=WQEyBvA^si3RrS-ED6&O3z~-EI;uD8ybBEV`fq zI(?#|u|UA{?bgULDpy0DiQ6UR^W}haX&DVjnTjfi4gc`~9>i0ApYv-61!hUXNIlLG z_qUT6$VV&l=?wh}fxXRn{7F+RZ-qcdbE=hdvo;?l>33d;5;EYk#0iPrNYVxj6pZR9 z7#b6l+6;5aGiXyzHr&a z*;L=rGC)5)ltxJb!VwR6%QIl%&=v*i^&?)Fa#Rc{pTB`v7$fCy>fNFp#U=Q!R<*MbABUUDFnLm zMaoAWzhw?m?xHlb5bL(CIvB8Uf0rmi27m$yh`>G^fl@`)DY41V`KIf2bCJx2I zweH4|cMCu4kDUm-H1?>Xoe7DKl_Sp6M7OKFaE)LJLBFiJd7D+%ES zzlZkI4sr$h!R7)=wq>jWiOySra#JgDw*KpPo%7{wc}oHH$z7-G4LUj|$EAfI%9HdG zifC1uD*bcq0$@C*Z_YMwBT;SGZzcF9puj^9+%^JFmK(nNj&C&8o0SEl=t5gJURXKr0#9VVt%Z}JxYUHp+2A40)uUd}c; zBO59Hr5c_C6rDNJI(6*I(-g6f^RsjUm9PiAwb_(IYB<MsJt8yXU+CL0UD#vu^wKTUcXzMWDCfG(QOV}qq1{8EKpdKUTVTZCrvu`ZT zH3>V%R0(IMo+3y4Gg)=qw&Vsn;}Vd4Tzd83yJL#qIABptcC@XUDSgHV&fvxG$b$5nje7If)>FNk(*UP2#B(w>lq+v*(U4g(?f)iO*sI3O=t# zCVH_p$1Hv){s44rTzgYrzM2~xfvbbgNS(xSEc<#|l9$SMD(eRYU2OPXpuIB0wIKcN zj~3vJeYeeBkMeN;V|17)j9>y@3ZNs(`>e{P=(3?v>c>YW@i>O+lvuOP#ICIq`?}yy za5nY4lgH=Qwa{j=r-5Qyp1 z^28lRez<2D3W(s}1k~q$Ys|vI>yotdT}yVpqsf4D?Ejj%kf1)k-`+-BJ1K-6Uw^fGAeB)$Af)@Fv=vwoaN zyCQhy!E|1{;xj8)Zi!4*ESEO9aU(Q_0>;Tei`eibTt(W#qdeczr=#mLim2>S=3uCo zyW6U)#wgyLz-sQSFsBn>;NDmxbqP2G`|swK(5b%JAMQZm8@@<{NoWi7i=Rq4*)zDf zFgK|l+##JEuC3Oavk{{V(lcyWwJ&z;E(jrx%WIHBzUm@OMYYWFoXcf}pr@02YBZwG zq*56jV+{h6R9tbMyHd1l`zW(9hwL_j+tZ6^F`9XzzFvM!{X8c1{=#w+%XjIsjd$?< zLYKdKVo-rskQkVzjTi`Hk_NlLD_ zW@UI-m_O587^Ew0;UE2&@4OsrcYh6JMup@Cg*4LX1t2tY8U=(b5s{ExlfJ63IqTmh z{eZ0)HdSN|hoOgZ`*kkLA*#|rrt;j`FwyrOIjtNDSep)f_zHnnFzG@B6QV7?vZl7Y zbh{euwt~bzsR4pYK9nK#wKZzf?dw#PLbEU+?anV7rrnjvqrvtCKTC#8%Sm@ZNXd?5 zI(?y)NIH)Bu}@K*-gG`TwQ!KiRBs+Qw3?atbg^OZ)lUn%-yb7nGYq5SYhrcUp=Q{G zIY?*^GoYkzlHq^gx+-00fArfD$so@Ux!k-g<*)B5L)uF$6ix%^GNA$oJ=x}lmaAjW zbFLDvn=zR=_gwM|s>}5;ypzlT>aDhWOK*(52**CFp9sjLnmn*>Pg6$Y?47B!|NNpn z;8k>44G9X}ip~l&pgmp0v^z##1R_SZjCobiDq8prrC-(jkZZ`UGqmH-kKF z#HU|fy!*V(!>>Y-k2E&GPPcD`EcEQI%DV7%8U>2##+GoZs4^9f)?FECkqBby8GtD< zu)i=Wu;6{QV^-h6h5=ZFK)T@eU_j|0!_61q0Xg4L5gcyYcmI%@kz>Kcy=htr@+5zs zXtoPd7L$_bI}e+)?S0YK(J`Vf zHG-$^Os8*4jt3(vsqEv>6%d@xKS2fJRiV49T`;z(juas)`^zZu!0q~U(qv|V%J+Hk z5dbmTZQ@>-bAnviVV+~%KGsG`=HtAkjbN`FNu1F-0QICWo)J>;S+rjGvS)z4vCsy+ zsTODX0b*l!^;Ef=chdiy08sgy3Z?>=%li?+l8QNO+d}e3s zhcb)hQXW3%`wVOid(TC4Y(+t%$rj`kB8i7cy9XuopHzya`5HA-cE~S(SKUoVX8a10 zC$UefSeH@y4hh(px*{v-b0^l8p{R^RsQ97@@OQoOPp{(KAdKdDWzDo9ML!e>`MKS| z-M{qksMJR(9!J?RMqI|ke&eSaA7}u&Z2E>68>qXr%ntFegP84N#I*n$&5uc^$Cy`y zZwD`4{PJ2)-$>*ZAGeS_mM-(v)O8e2HwE-&+1IGGk>wL?ia?_8u%2fiNIMViZQ zB}p-v(crDx?$fP{IQDBL6_*KboJO=T;@dc?)i6uzTYh#lYd{3s?pzSl z74Yoa1R@P2Hvb_FFB`Db1rMT+*_>T`1ccLcLW_kR9`f*4fx_5*m) z1a?M=s~P}v)dIG2z~%I^em5C;QN96`bDX3yHqkle&pN4gE|Y_$ zlE$f$4PLyE) z;RHaY@JPG4ED=`yB(6)q2h|QJ-8p%EgG@IYo7E*;9(F8J58eZ>L2knP$K!>=Wv|sU zeDO#q$!coX+2Z>|bO_PpNd4 zX;e^;AYOqcGTu4IgQrquR9l2Bgjs z9mWdwQh4W+zCDrRKMH!5X-c<^nrXVMt|a4=Ilr8+&|Kk_Mno)g1AF331>kcy5Is+u zyG%j=S&f#|_*Vs*DM^vK@pkQ0eF10mIu_&o0l`N=g+u9P4G(*uQ=!21L>M6I;WT+o2eEx-80{A0yhb z2C7Il6wbI!KoPN-rHpj;D~?4fY-T2r*JPaxs9T3>US38I0sd0Qx~`2ufm*5k_ObV7 z9ko=)UFY3lfc&B@+~C17cCqwMTs`iiok3|GAoKW&!6lZwO-G=igWTl4{iv|)7sl>8 zXkdw_Rqc;YIHbv{Vie$UHRIdGap5ubnrNnjNk~Q>M-agbjKa4hWJc^XMNPurJ70i; zbfC6zQWQmz?!A0yAg?Y;qYL}g*7H%j5j6Hy!|4TFZbVx3uL>juSd3E?Vl8vbeOqeF2(SWJ_e` z4BngVMfV9Wz{PVWtriCKxVdknUbS{t`)g8io|=^!y(KNpKkLWnpIBLpNTA=f%wiD)7|26txK^R{h=q2rG^w4o1t1w92EB$r zNf(I|gn+Fo3q0nb;fMi*3Pz&*ILd=Bdkg2;;&ei;X-JLQoW19bc5JOZ_lNCNCCvi{ zH({xuwVxih9_a0-{LY)V4R>se;}o}P_*(C0jZcUVm0mdi{h>?BV@dtB@nQ{wrz61e zT6f;K#rDx(D+>l&5x4v|kRlwJvxVUrd9*#~PS(rP{b@|?QPj&jp~ z=3eJuXr%d|_)+XqAO?<)XQ?R$IPB)qBPl5=i5@ILbE?(akLvrTc|G;M;)-^0o7EaQ zi7PuXUvbaPJq#Tw*;dK;xZa)4=?62{%N1@_+>YvcoTH(|=};B$)udTxpOS<@Gf5BL zpFHCUY#%yF;Y(cY;(%a22IqMpKAx6v5c)hXW?_`6^N}Pwo zVl^6d?QH6*Xo!zy5(h{_syH1T<%ga$=5OW(m**7SlC}y)uEQ(CoY;Pprs>a+xo>-h zTMJlwZZ5-jgwiAR?J)?z9Yqu@P-K&?5s{vuwaq1yo<&8UhDG9?0b7Una@;oA!rzQp!E~8gk(-e=m_Z^ zL+N+~k;XEz(8Xv;+H$OTjE_b= z7lF+Z7(Q``1wptWheSb9^LefX;UsW}0`1bfD~#Tp)Sn0${5p8X z1sb2S3$|~I4OsUqWEWv`&NbLcf8!Hz`D;+FyNQ)~Cw#|47GS6fU;^)tBfwX$qTwee ziV903#LqD^o!5Eut=)+vOj>NiAipQ6*iJvfEigv*;l=B_{SqOxtv8`hT&i}=u8NRx z(&2lm2{kEt8u&2-wW8$#K^&jNsyPYciJ%7EBas3ZVqC=^J)Rk|{6D+)zLa&?_DuHo z<7wt?W!m-sfNXI9{14!%cF~tz~oYLTlt9Kx}ej0m8o|QEh|`E zTpq-n0E`v)mCol~uas|QG`ndgk8nwPH{O`o*7q(o0HYoojG%R|$nD|4SXTl0WL*s9 zkQDp79hg>PRYMf-B6XL(2l~Q8!WkoK89oP53sp+P_iO~5J7(ig6}&n+^LDK3Y0X|wZGFii zM}$J|ZP`5UqjY(yoLpX2}3ujgI6%MthrLI(G3GSP}?>~tOuKJi?pI6$hrsY zf?cYSI9zAdx>)Ktd+gP9eH+W7*|KSe+2NZbOT<-1IQ2SI`-olf4OW$BS+*CKdV4A( z$%rem0`q8U!U48UD|9Tvm9@)~T-$X91$)?%WuV@sOC`%LS29MeP3v`73wn0<>drHcxi4%m6p1*;ox%9 zb#$G{qth#sSEe8(jsW!-s;#bb@2<(L3+EjeTX!ox{`13c6P#X0x8>$DU8Ur#?-Ga# z_)wg|zRd%C4l}p)+48GQJCwtVsJb_d=TqQt+J#2kYF0kp^Sj(b(_pevA@FrF-r??h z-|LI3yZu3@7v?biu*|3Fphh3;7zJ8Src|}n#RUFE#@+!EoYe7Q@G`UKI+Zr*%k_+R zyO(57RSmvBo1FAAB=zFP5C)PkPI$HXXC)sGAhRb>jPlixhM3J#KZA0}awfQO!Gj-C zcELT-BcG)sjYb9Ro^>)L-elL*_02%mi1Xh6<=)M_|I7EVLP}B^GO6lVXF{SKI|;)! z7aJqz`MT~MMDRe(FdJ62o1l-AIW;R$btD1LuZlcHnYe@kuAHm-Mh4ad_PyA4{1^i; zoO!t;!JlRNQFy^+e<*1TgfLobnX--awQ?LXt~3S{;an~=wXLc@onGX0CBwvlGnv@35NwXENc_bLE)F9u|~am6%14*)TclpOOWlPB;dTV)@(tu0X35Q?vbSpP{7VK1g1;jA=CNBUr8e3uNEa zNj$zW&&ac6bzl)I!pw=q21_nPez;Ly+Tt7Razn0 z^JMYWE){L3?5E?KVjCK2CLSg|A|6+m2ivPm8el907ql+!Gqi-~f?D83q5giDaADQI zyk2i_cV(u_=-@E?;asJ@x0btcw=$9qxj1pZs+1q~C<$FFA3n5~1y0$^K`u2 zSxNTMh>wO5{)*>kS`j2yZ75F(CZKj;)?)QZf&5jrysl9ke9Lce7v1qyM?1MOOE`M; zg23T?E!b_931slWnhvLxh&u1uDOueK>eAZN{e3_3-KhhZQ6Gw-Ao3umkI(4!M_UYF zWSQ^R27jh~S!4h8mHkZ&P56Z14T8keX7om+59@nA?m>(P(>vYxc4u{`gcGjO^1TEn z$`zw41FbX{9f~o2F1NVD-GNN5oXqS>81X3+83b6QcT!Qz4Tf1qm#2UE?8k%_(=HFE znH*baqiMHZpT!?ok3Y520et8_ba&i-l_T+Y=+!gIFr>g?(WT;{{uP{?nBgEzN_5Y+ ztzv;)o@Vl?M;G$@K~86p1;S!*T)BQU(!B0=6206_zaO@#(cm))z7)K> zsNCi(hjo|0iEY3@+^qluurp;i?eeDZ{9`tw{}Rvj4}`}!A;z+Fdnmqg@ocKCLo^Z( zUNX1qA(z=%J$x!uQC0^MmjvrpzWUDCQJS$PyTNWTcvi7zz z*mc_2(tX!PWgVg#P?C>W#JN>>$q`u7yEFBSw(gJPP4*b)8aOvmymVQa_Nn|En5`pZ}tpXhl#91h{@ za<-VLVj>D&T59}Vp8R6OibKY>VXM;!!4>hgIT0h zmQ0Ftka+9GkGpNFJZXZMZCNL>E<%S0E3(!Ql7A;=>q(ByYS>nC1H+P5d91Ki+o<81 zr~SrIN-2=L=-?Y~b8Af`yI$-^o}RWIN}kj@!dVcv(+v?#q!JyQ3I|lQSd1=zS}+2F z+s&#KVynUc`eq!C*msU65rY?93UZXD923MS^=r56`QXHW9 zv=2#xELBw`*4Cj8r!x720iDudsZZA4PtNr7XKp@}MPA(m-wezw?3F(# z<-mg)BX%q`eZc14&V`=fSo;E^e5Bf)-vAN<6P9y&p4*>2JA6+U4W-}#?76aML`aIp zv_&{I!S^_eQs&aa^rywI>S(t>?!uB3D25JT!AW` z=z9=MC+9u^!G&M)%**SD?{dueM&gL_Ixbk9!;Lc(MsVD~EwMWoChB9Fq5%&4E>bk4 zgZ{lUj{Wy$C7;U@L67s{VQS;_#WE} z7pY+00b6oz^KNZv{1aPjpufBOCq-VN$G`kcBSxv89CM&-P;(9z#D{9FG))Z@qyl}{ zVUeIMB=X%2=RI#cDReMls}($5C9Qoq9l5~XX%YvjRF@8Q5!!wE@H6gQe@I z#6_g)%C{tqZrp~HISp+-_uD)eM3;uQfp3td_&ev_R^O6({|1MyIfd(5!{hZ|oWG4W zSk_5icbh$cqLAy-DwT1=kGvT@iXPhP!j1?eQZ71+g2W_m+2isV^y8}=cA^TSxWm-v zfxqeZ5_|5v*AG&`#eWFI_qv5}&zJ@L`K;O1=hpA+>Yls`waXGgtUEDevZO^%nQj*|F=j-?Qk->R>NlcfnUc~oX_Abw|C%{z(sXuezE;bgCHktfQiCC^Pwy{Wd~d*PQP0uuBF`80J#Z z-tYaRKi$eXpK{y(U1}`uWq_s1rGrl)BJ+x)DH`gv9c4Njso35*!w@N6spZMAxkgZ{ zz9Etf*8Q*|B`H6{hT&pRs?ukYMI_|ti>2F>(MAarREi&P=Z>~bdm^|PO1rr8ElTL4 z@U*8Pb8y-L3qF$&=Wf4fBGbA;`QVoI*?Kwh`SOYqn?f>dJZ5g~XD*7+BjT?btj|*( zGAfzMeqN=9R{toSZ~rCnXM@kOP1KnnDEd>dxloWctr42wbP)2t;$|2+0eji4e0@3(?}hz!hQIfQdeQieo?5^GCa>)_`EYR zxRQ0DEi!lO_DCx*MAri8UB*pV}3wl#6GTxxoj7ss(blzu!QOZ+ zZxI$kn{hYc!)$%HF)ADhBN<88X4E!{B01wUAo*i`3ZaK)+nt#jiX_h|E8qOXg-=9X z>3Ng=uz$LK|6@6tYAbGQJ^Le&b8I0E(I1ldf(-QQVG>uf13K+!tw=rDE;ccIeKj9A zeZHPCstZm}9kNH=+WRA;GA(K>>%nuRs=l!AEdCs}g_Z8njC9Q}K1~GDf4&Sjj*YIU428Bb>|b;=dp>}KwQjsj`kaX%9&oKizla>=&xIKGQam2a=8&J z^HI*&;**34tA-O=eM=AT-D2E|hwT#z!P7;7S9&-bHJLi2r4sTPP%;xS1X zfdcOc^=9*gq5bW%&*u}8pH1%U%$*DZD(9NjBFMk#v8&64fp$K)<4C8u=RjDHv%Dn2 zv*hXz_bU#7v72eDR>_=|BmctZjuA=pM&IspSoV+Xh*HMW6BcEgNY0l;@U4yR}{ z4jFWxhXww@fzD6_JvgA9&{XVs^fm>InO0(0!J8|DZ&_@_P)(fX6zbFRv+}!wN|p`i zqQL}XL1<@|TQ#CKK4{Q&m@DK+-J$IStSIGX8xvPZE&W1cU^=^t zA>(zp9A3Qi%==+R!4jISDy|HTL$w)IXYKi#X{J)3+Iu*PpFjsNpG6`=kSb@^^P(}G zDH^(ayC0lsi*$h=lP=^2AZ~nFyU!o*^XG`%z8?-5lkyT`z;NJ;Vt(a)eN;$J&&QdDE1da-y zrvz@?fIi)0rCZ3n9fgItRNI}G2X~E$?59`z-JJqEB#U9Jx!vw^_ns?zLr^7+Et;2eq3%4FoFAG>#{%&oWPMs;-b zz&LyOD(^P{Z~L;{NEC+Pj>7F`Kw@Qim8jjo6>Yhmmzk9ZCYvMW(ptLFMRJ8Dh?vIQ zVXt;bu#knL6}~VWMo2uiY~m^Tf%RAC{a;LZ)x&D*giJlfZYXR9=_GTRE^%7i!p;wS z(y9*2_m_1g1K)>ADv zAFj&X@wg`@sVXXessSs*-;~66F_PR_#Z>?7%4GI$MSU_)NL8w;1ZnA10(MjEL#kNR zICZifdc(G%d{s=#`SsUS!%?@pwe8Myx!8YyM4_p&$BFc{JybeJN=dS5lb}@5c?@&w zwMke)xdC+`?a!rOZ7FLY>G-}a#P_p(_T>CsT82}FLveyQSFeTPxy2p#TC%XTrfkQd z#AZ@@Su3Bl_l0N)E7i$W`O12|qqOp{)DA=+UR{1s4yUbDpdmZGKFw_AbT+rQN?+hi zc_H$#AD?P-?^z(bE7g-6n97mj**v$cGP;es<@77U1#}jH2?_-Wqk)=bQial8Lh-Vf z4}>n0m3mq7gPznPBud_s-9dyF*$Y2g@0#az#b*RCn=XP~z7%%gWNC%Jfy4K^iCuFb z>Shl<@^WAtXR-m<5G7KJF1Wrmzf;&~FLwtKMztZyZ!74id_BcQdxQM}?;%0Y3ISkM zdTWf-D|O`D=yDs2zUj2P3q;jrW?L7bTH(zHD>{#Q|GGj8&tK)m5=qeKT76id8q+ze z8P^EgkgCV5Epm8Zg>za%B{RA#W)I25W#hIr zQ1LzxS{(hoETZPpf~2=HjQXmi7B#34IuI;>31C}yD$LHvez+d+l#CCr=9r~b zAZ4zNjVv>fawO65bML4oYVbS3Tvl;}Os0u_DmN4{k?K)-X}J5P3JHg@{MhP)3R@M0 zKpO|b1S|60#n-vuVFVa4y8^)ji8NI)dCw{f<7c?Xza5`HM}VMu0{x^i!uDz17xVHE ze&%vd`3)o@-+kcGUx`!)w$fec1R&53Pn8PRD{JR-b?~!3rq3n5JK9YzBNFLRDmL1@ zMwRbwu7rn!Tmp~z|5Scq#0!pC;4KdYP;Sl|xWU~r!ur4|^#qY_T#&HsNh6MXzunu! zmz)n#6p7~U9RBUQ%1c5hpee7P7d>po1b}c zwf4KXdUQdFk$%SgBq8Ska7|Q4E-Pd^R=O;83l_DMfu|6u$|K)w#@HWdT=JK^Gg)rW z3ePSX?Zm(Ji%dkpXSwh%L6SXCk-<>};KZUWf_1gPHRbf2r~0F%H&jCOflId5r>{?Y zawLpFjDC3&{E^#YUh8UwfzuY}rI^)XYDU^$Nhz(4?`MB9aJeR;cTH*_xfDze6aH|2 z6x;GpTm>Lc&gEs5q?Q57hNb(ilRg@;VM^?}os(-$74sn2%-nZr&oZ9+D#@s*>r}D< z8!N{eR+<&y6`m-W^e&=Bk;zZ9>3&BvxMnbQ3&%|HR@7Xw0KTvJeY0~(w zt2-6gl^Ey@&EnPBNW1Bx!C8Qsst<+`c{o%w#!LLixHSN-1s$^`#JIbi1@L zK_N>pqCIjaqHNZqnQwUh;HhCPa|{h$qKesKmB!iUbP%Be4&@9P=w$RlZOa-1{hoSY zr3N!ha$UU68IvM4kes$4PfEK#Og-Acc?e1YYORTy7J=+q@bO?dqXyvtJudzmaR#v z!t04W)4u2_?Y=?$+Y9#LteD)FBHHQ`T3Jq3Q_;KL{VqPwbJIaRqYR!t9qyy^CJO^8 zK~=T}o8u|SfquFw5~*OUg&Kx-n84(j@L&ssN>j!2^l=~c(9nE0Rj0RJ1lEi8YlksV&klUHl~_vTY~R*~0w#pz&hd5nZb!-# za5(nV>{>v_==&VFH=Tat8%PxJ<zcx9xC$@aWY z_DJWvtzVUqEGXfq&zi|eJ+?rCgo*Kc5kuK0S?-fRtI9c^1@4}yML!qEye~`F+&=FM z{6<9U$yAHZSvN~Rx1n>@2!94rZotICGQb7Ia1DmPCqjoy4*X>#VYH% zP6k1PTHr(+-IWCfv48CER#eIxh=#h*#Q5~8lZ*p$U9w9p;<{*_cWGXO*E{{quK#P+ z3FwNEGpZ$LHe73~*O^&L<>?^?y@wdG9@_~9EIU{J=Ct^+?7ZdW*_aiab&ZK!hVf|% z*t55kjfj%(#d^L>>LR<++G@BPVUJ_6*2$375ZPo7EEV4B zZ2f|v4i(J^qv{X%bJF2r_u$T68JppuGjvrJ=6X2P+v2H98!=;@DmfFYjKnOfV8J&H z3A9s;QKD7)f^ z{+kQQIBK_@mMgA4%Qs;BYzq3$K{Z$*;sCyGjQdy=*4IP@?+IpX+1-V_g=KR-_6}VJ zUSrz%bpRSwkzli~ynP#)mbO^?Ylq_o9DSU7F6Kslr#UXQuf-v5asyZ}!!> z&`cqS-o~a_@ths8z%3$!1)+GsKVz;d#3P)Cl z`1h~UeMj=N2{p>x@E!~v4lJ6Z)h<9(>%_n(rv4qtSh=A{Xq7~VTt4RFgoiO->*X(TPQNU;u@}u(%Kb<6ZKu8+1BB_`WY#irbi_~)pgHQ z@S!r7!8PQkpGce1?Allc4~Gxgh_{p~mDHz;v~WdDeZ{D=H*%2i%%N~;lTfiVy z_-didc9;9>>)b{QO+!!Glha|n5=H4w^zLtLSldhH_gRht^8l1vdGU3uX8Y4su*v5= z0y#cs@sqPG=1jE?w$GKZ<-_&`%t^G|pX|eT=$C7zs`TY%*Ax4$_;zhOW7$n`V3nvC zySmHp%#Sr%t>5kgxVPFw)~c*}*9R_4r5Bs%jLuS{!#21EdJ;e-7OsI=*Y59>!wUwu z{^X_bo*gNpHmvxr5Xi{mhwHzA?`<|TNmO$p{jfUNFQnG!qEdEsDF66J3!AFn!nn4m zvn+(6n;W6-tASy`9HpCjcW?V#3Sryj&QfYe=-oh@mhGA$%KjpO%k}Q_yE0qk3fmZ> zi#ClL{Ik<)!Z`l46>-kOuHk6YBwGp@{G80rGW)&Szv~Xez2|`e%wxJTuk>RB4E<(~ z7SC-fbstwb#&Cl~29wqHzc_pAu(+C~Uo^oXxVyW%y9IX`bZ~bFFu1z~2oNL$2?QS? zID@-ekl;>mcf0fMea^Y(yWf7Ed+wb-*7WLL)2pVts-?Q>S3XCs49d}4c_+{^n(tm_H9VThW@A!p({GfpeZ^ zXyXgc^YUxGKckaB4+#pe{hY62y;r{cO3YN>pj_E5U zA9pJW72ASgj3@bzTh%8I8tPFDN_fxeOQ3FvJ~pSiJZGWx#?Q`~`+v{qwaO|?SGV|P z2tY|Jq92;~3z_@36&!z0ZIl5lW;C!bSOT=NDd+I2GMYVJ1^VyvK06!r@FDGMpvDN# ztwwUL44i(-KUfh>6inBkG3;M8t0CQtf^s2ACZHmU1kJVFpaQfj^`*GhK}wwqH3=V# zc|^|o_>|UoGF>YXVEn9Nkh|TckeQj0=+NZ|VS3T**F`<eorHJ_MDwdsy!)j*T`O?Cw-2=<(Kz(4W1$Rllst z&cQ_cK}oWzWX`v)RWr&&!PEdXW;Dftx=m%j%=)j3d5fLke*8@8(H28=>O{xXWSRDi-BI{2d(8lFJ*1ExzPy_I`O2z5X{q>}$k1HR2te$&kYv(JU6n*D_ zZg==lH^;$I&4oo<3X@bv&O>9U8qPMwoSJCOcOX7sgxWtC4a{ruth3*cp96}dk!?4Q zrlNYXu)hsX-uHP-PWqACoA#QNP(-sIMiLp`odD)Rma&(hbYug^kxP|(L$_~$!7D{f zJ@xL2y`L*%6>E??Dqt*MUUC%!ETl&^5U9Q_iC;A=YGlBLZf;TpURtla37;q%+d z;3^TNIULPhYw zam%s~-#d3bPve^(<6ko>l|hv2&dP}sF!JD4-@C@teT~J_muDKYjX=49Vy<##l&u&C z!9aq;$N}6XWjO99TVVPfDdw9Eev~qGTiRy-P>#vP6)_n zIV$fZ$X~T5v;TDMT?x$W^3FACQzIljQKb9ODo z=kAI}W?-4BmsEPXg&r}uI&QAoszZQV{*V{7X5DIOeIoFA>Ec*nY?i_ktr!dazP&gf zBuw_!$3XYna)6<2EDv4{xoLddx9iSvE4?)iATOs=`ygq@^lEUDbfV?R8w*u%-{(%W$K(RkB3~RY3n^`k*fGc*M*833w3z3k>6(j zYiA>2IKCEk@1Q)tY>Kocy!+W%@GyvpVmT8PT{sz%h1LSjZSVU5U)$Kv_@Oc`LaW2K zRuAV0lSE=G_7nx`OWUnZ@P%0Ya~HPO#xLKA@2kV{7FB8p_vAn+kx2Q+pB!o{Ti@(|K%H_|O-LP&ZlX>^qn$g6M~ zPWlQ%{SG7C9Fx(C@*r;p7bB?{w>#nOLOMRv>ju|8o-ailQUS`gvjTA(CljE2XY@Uo zDYe?}64hNtZ3UrpJoi%kr*OdQ$4i zj~0&1r_#O$cj)|Bh;z5|3qy9Zlv;ORmIC=1oGC|cSlx8P8b1t`*V&xMdDddpYGx~i zR7>lom1|X1I!=K&4jXFYZEyQd=Fd0Dw7WwSYz3ohuy@t{KM-0X>U|QwPUv{b{hsc3 z_0{TwDct(9W{>B)52RgQZIe*|v*>@xL>>*0Phrk3F!88o21n>kl8Ujsdjo%l=q)Ms zT!D%6tM_rj(wnDnddtfRTStreTxEdEUbAI9rsMa1{F6%uU~Cz4WOReIoRJP!>RSa) zBv}jQ$|S`^#b(0Y$#9?CiU1MIQTNaHqM5ppXDL+Nm7ovKOTSe3dZ*=I#5!aMAgtTs z6V2O`Z9kPoEVf~Z_PeaQw9c=sR$c-GoPneR7&Lmyu^>5W;ENyxCWpXgeIs2`%2b?F zzt6+dL@|@@gorT;0VH?bZDEE)Z1Z~_hxp~sh1L05x+m&3)J$gDtYZ(Jy)(?{Y#3$I z+_0Y|W&homH0B=o@}VVG%jnK~kSjlu&hsXOSkeE@k~%277O|nV$1!)Lt~EkCqSc8$ z(!r)M^6PcQMft>Dwv?so5sKyceHhdw)c_^g$~asu*mO8r5L?z*yPM;#NB%_yPz<1!K~r>fj-No6Gh9*rw#HpQJI)=cp%@9sr^ z7|!yJbqJJCjjH_Smv**_0t}pJHTEtl%8Lma{8+xjEfCSg5iF1bqd_X9(S!>Qj>eWCmT0*GTTr zC6K}1KOkr1ldF3M`J(>se;gC4bBF1TbX&pgKq#ACfLst9TZ1sbpiSlg^3-a*K5UzTj&zn< zaOeH1j-P7}U+#ru8lsk$oBrnG5k1iB)eQKezb&$ESh}GKDqd?*XO~@Zjd) zVyI@zl@(KwuZbUMz)q`yEE&EhI#@r+;yI!Gopj{!k-8}3OSorO!m4$+^f~LsQMRpG z8--lC4b&lA2kv9hXBb=>0}99MM>OvH1OiK{oqa3d6Yqbo~sKVq&oD2RyWbqXGn$C8VJd zbv(8^8QiF(81xeszJ{ZsPGkaFJ3$hfTF(ho6}vVZS?y5B;H?3eW1db zXg&Dbka715=6KjuHgF%gBAf6jwj8<-zR;D%wp=f5@KLHwVU@4oR|PeZV|b@aqX9#M z;lU(#=HuG;==%Sr1G;iE%<7NOefrT|Ebr)C|515XWXq}ci~D}6{%$OuZhsHD&sTCtpu5k&)ep$y&G6Vlz-oO7F=Xo` z1+b5EZ3U}2%Z4uo{X14vOiGY^I^-Mgc_Iv z)tC!|iT%C;nrVHh7Xh-}M|l>_leifL@olDRaN;Q=0=!;o3$!VUF2~;+wEE??q2h|9 zJnd#aVX`x~(8Xfsx4wM)Jq!>xTp|2D9+JOc8t*SR&1Uz*8NRc4sJ$;ts(>%NJG$js z;h+D1lnan~*vR{FHuX7}4xqGqBK_&d#u=;cLNCZo_5LM=;oS)_ zEpf@cy~G`F4NL%)pa#-uqQCm2xc-(VB; z2;lkL8Dg!_N8jheCW^gIygxV49@_C<69jAlzMf7{M3p@<7>kLMC>0|%Zj+-UQ?S*O?oU&dkD zaEa40i5g%^Sbbe?>S z1wh{nb#(l8bqE1Qig1AF_CCb?J1-9UnBioP5ER(PE^jgpU{P4r8B(VPSDPxd;MZp5 z$b^cW7Cw(0td8txbwdyaeZoy5F>7!@fPWQ{i{sB^Csv2g$ALFo2s2N6H_JV5h!)10 z@4Xd-^a$=03n9~H!oM2)v~C)K(>ojGKf@R64ddg)cowV&2{Wqw$5JZ})R5;(r!;~D zJQ;%jA2j_#CrrgZr!gdHfTx*I=)f6>=J=HoJVhPk)=|2lyAW~ayPs~&J;O(I$`N@> zANYM@4eSnNde(y?9mUaK9O@6@jdixZoSfF5Df-?-C&#T@NFmHG>K0BkN&OyIx)_IG z@5*O>eJqa4YVBeXG;coPcn97X|0d*mFmXfIu}r*8Q~*`Y!N=n>Q=8&~ZVah8wGyzQ z1pTWW<7A;O(f^P~3vE>BACQVL!geAIZIECA-egd}6c0&a&G8V`sYLlT&o9&6smMyY zZQlEFPl<#s|J|4unv4PTMt;6OPj_`{#W<(=yjY3Y;bY5x4z*sRSTyY!J;=H?D%n7i zh;yp~KS^kW{k!SJBL8W+=6{BD7?BkX)vwTw`-cjJf0Ojj`UvVCrIK2?rF++xJj`Me z$O`^~2ds_uo%_|pavv9Bt#r;T3t2*1vb*rE$$<|VwvUdc&7+1R0_ zo8@F0;_&;3=g{;3(ms>=h3{O7&Cp4=neV>-f!yJm^-b1J6?f(u=lR ziE=Fif^YgJ!nR=wAF+NJ@Bo0aBlINUUwzTXf(`A9f7%2(CjBF5@|gO^1Jhly_fXr3 zoH5x?H4#1S&m}$W#z!Zq{tQL)Oqh9_U9z7)tV5ZRm?^3su{ywVHZX)D6hXPPKnnz5 zzlU~66Pb9653}7jbi2J z4G9>_Pwj#Gz+Q0d4p{=C7?!|HihwtExZ>@Z>lkqE1A_Fr ztR}062?E84R`lY!)9C(3Q~X=}|2OvduN(^{@*nnvD!SskE1=6}SMmGPve={MTC!gG zqTk(uylDVCn$|@tbsR-0;jd}K6+V}>CZfIw5}ySy1#hbX`pACzs<|kBydZdvD0&%0 zt=C^yzp#5?^=V@(=7p4LCs<%Np7q{=53^AuvaMN#J+rQu@7o~`lpjDhr)67wC9D_i zh<@7RvDB$hk-i_nV-A4gvK>X-kuiiB`l>#PT7o|n#g^-`EoZpvcm0Q2ws z%pw(x(6*xmMKPQkgkZr`dhfg-rjb)llCIcBYM1vUs?Gt@ly-$$%l}G|m)_*4D6e&T z+fj3SP{Z7rubOZi{6Wq=KN*xkq=)HzpnO7`wdJ)o*lceelWi)=w)Vq2)jCHfe}h3o zIX+Y_evDVSPnWiuw8AwsEh8L9sEn_)&Xt3d`=p*c^NGsB6>C-ze;k*7 zx9MdGcmWC7lSetc`YAGlbrz=ew_L{6y{Zz!L4M?MgeEyaEkl}bpq1{&?+8fodMIfra>Tpkr%50Gy!1Jn!lHp_i)mV%?9+`?-^* zw9QrbyCi8$m87Mc`0|3`DYrGBe8Pacv@}!B8-!2JqH(%=qjfgEJdn|3jC<2f5aOWt zF}ENf0k5I>d*IjQBt9(|i&DC!7(2e+qh!F;gY?ngoPobCJYc^PoOT8reOjUYKf$8p z6{`=qOn|RJ>2nmktI8cjZnG7mI7=iGXHIK|wN~G2m0kux*xKr0GF|@8;ItI{$hWUlr~tA))i$+KEp| z-IGTF8ICy=qKUNze>574^S-0-ZDV)`YAc}8^Cr)Htu*#xIP#`6;LjQD$8;1(cO?GB zy))0F6WbkB$f3V!6cP(*K^lnIw8z${0Tkot6=!pmLJm<~ z^bZK`F@EjuUuB%`7x?wHddJ{_c8-Gpp%Y)WqiC=lQU_TF?ssW~js-8qO)*|95~Vi8 zW>0IOSZNM;zwfa_jo1#6hDS*Nd4hRcR1-N?E?!YGqLC*Ia&_`-B?w$2!CgD{1u2rS z>G1t#-r1=C9lplX;N?0^N6xG@79^_^v+-%9*?9biYhPfg^v}*ADw%oR` zTc5~@zAf-a(K3g!Ys-w`TfkqrhB7pTI!Z;fJ=#!DJ8;>V1E2!$^Kb9e*o5y{>vl1T zTy0A)N2_>>QR8>6I=;{V%hY~@9XL0 zLfR;Rr^Nw7#K1A z@t{%0AZf9C{L|jEW_9m}{~}JI{KBM~fwicQxMo91?8ryl#!bB6yNb!3%t{9p9k<0(_e?ALHrsC$r+>^c@CW{bSc!>Cre-p z#3{c7x|P;y?6|vxsRn&z_g{MspRN;0ws@je}OEcj&oSi6=6wy;fqku^DJKl)%m zstEXCQgoR2dt#PFadhBV+VCQPiKTm#Ag895pCrm9P`4rHml3S%=yiO2MpP3BE+VYKB6O;0?58LYI&uBMs{_ig&WY=iB- zD{UM80!P^?wd7-p+; zFS?a3jb{gRnk2rl*7XE*2I{B}G<*v9BHO^5;axV#F+P{A31IbpTt+D0{ecS`uh21< zdtKbtdZ6$UKX@0Q{7rUTRj53e*;n}ULP}=(f(|%WGq9s>pIXa4Yv@otE^*_}GY3<3 zb`yZ(H*2G{bFH&}cYn$+1Xg>t?THir;oPP1^M`uyD}xk~%`eJ$4x-S|1Q`z9A2>Vl zGZL?~pIMF{0)BrpDN|=smzE%sXn3d?9KfO4Z*Tu>N9?m_$hyOVZNcqf)%9DOYP4M+ zAvTZuFn)NE=l5&^?NWu-N&20(dUz|*t<)N_yK)1dp)@ZAafbS#uyv~9by~fK4EreR zh(Vw2q{G$T8hEIDea1E+XEQnc}Ea*#icV!7=9sI22zW9 zQg2n^XHA!i1askY`*jw-T{K@Zk20nKgv%QDO1>31gp`4g4n*0<>vYQyYSN0suH@)J z&dxLyLo8WLesxGMi{Cyr$~Y9G$DVk`F4j5|{H&)>H(Kv8SKe@&XJ=RCnmF{8 zrY#0-bXSfS+s|w=HP!mHzWD`-$)aO+H6ov}58RG>upUSc!fGdHb*2GS4tr|@z$jOAHA!UOnq;2r-y69d zmK;ni#$8l7wY(YCNf0*wvF^0%WuC0CV*#%a>#LjlEdO-yZOAd?FFHJRcOLjr4Zbd8 zT3=xrFn_&6fBds91+wr>2a1UmkdhotiR1rN90mmQj|K4u;3lp*X!fQEP_5im`QHp< z`1Za%k&s|KmL1wP4>k~`Ws5VJx_t5Lu`d)Ie)#b2zkN2;)E2n@TkikN*m!rJgY`de z{_FQM?#c3h_+g01VF2jAy{Aik;pHA3TERLXhvs7vDMhd5{dYl+ouLaX()Sy+cEND} ztMYT5PvV{M@7{I!{C@hsb2rNV2KZt8uZR9gfI?39JKz78tbgBhg&w?kqWy=0|3?<4 z?VH+v&B8dRV9Prz`wy7;*%Nm^YoHU{W{ZT~ zh#s}UEYsg3MFYi38Dv3*@rkX#PJlyA-8K3d*F4Z>)b>Q-R8^#yUcR+#_D}FL^9qXv zU>8cCa858~CGGr>(FQXkoYZ1!g3V*i`hr+d7FN+T>ErqB^%am%iNEgay6EIp$JxT{ zxV}Q1rz$Q-KtYwv?~ZOO_ixJN2Y-+4&KMs(FV&fHdA>N(exb%Bb6)+a{*9!Q_vWJA zS~Izlt)b)D?VjL#4ffmOJi#cb(bp<%v#YKwhJOhQ*)A zB@A-2l+bCHwkMikzaZ%EXo{}$d~OYRxlnqQn-I2MYS5*a0Nktur#@{BY(I=1gudQB z-|4<9md7u*)PU*#Ym31>QNe6#)s7m5Vx1kelL{D3#F)Gj*3jWBOYiS6Exu$V#nfBf zHFNLiUUx}Azg{eZIa_MM9W1(i5f*Do{Wy&jtHtVvu4J>>dlmcj#Kf#FjdSU7b^+k= zxAI*rS(4QnJy(4=&k?vw1|pK#J&mv6d=tyvYfKFHUrp6 z!CoJ36o0rUlAusZ7M-N3GaU_&&)!Kfb+@*+q?aALQItbeB#Oedo`1d8L?Mc7z)=?E~!o}X3gI^)YX{!^7 zB4JVXkrlKlwWstsAN!UoZh9<`?Df?s>=@eSRoGlSQar-jWMWATl$&@JE9liOGCgP6etq|n*Ul$qXRQwU!Uz>F#Ow+Ql+RONgmx==T8$J!B)>8va_q;_+@4iE8VL+jA%etvcu_$4!GxA9^-YJKreWbonr}dgO_2*ICNYs4Mwr+o( z+yyHDA~&OV?Y5Z5w=9PqGk~rVc^3ki7I!=l7Rp~NVe_eOg4Pb;<|f8WD~3g_AXih`|YO(cU)s*;q4$w9UB2(^3HgHK5S z3xgg1jN7cbE<%ok87qUBf~(f3JYAqf>h!1mJG)5_4} z#SZ>T-1sLUgf)buIA>RRid$zbz^)EDX?iGON0k~bt716`YIK0XH^z~mjZ*X>dkWkh zJpS-R7u{vxc2MU2$7a!zBpEVYnA!|<-@p`^+~O)U$nUvy7dF4=MS?`MBcid;hGYD8 zNMOi-+Q*Zy1Nl(nYtC7@pZ*k{W{EjzSd*>5$g@FGeaD3yJQMJqt|MRge0fs!LJ>U+ zy&(%Kxfh=SP_$ZC&QaGVZMVDkGw#|quF#?m74?=iD|fy|yF-xk(a3ee+L3pO<8dK8 z>5WaRufGIf1uYq?UN2F0D4_zYutpYPaMy}jHewJ4p*xC3Rt}^Q_nBa|)j4DU0gF8G z<50b_SVj-VYHwr#`#U1{{@Uh}wGg8gOe#P6&4G)m0WE!_mS02Jo&^VZRi}4$UM3!u zI=xubD-P%`yBScAy&6l!CTUl>f!YnddQ#>1?kZ!i${Sglc)7&(#I+HV!PfZ@N!;Cj zS5D+sRI8fTG7allHrBsozYIdRvVtGpR??nf&Ge-!`X3l)9-lU=XFfEh_)*h@W) z82Vfy9BRjk=6g?xdE{+e=D_RPlK7`L8Gp3YL1FM%$^ilEvdivt`Ev&kX5tIN<+&>5 zNRsH6d*=Y1DFI6GDJ^C?8IRlPrqSqA^fyDGZg*|It2MEsNje4!-h9LRrb|sq5#zIJ z_e!MzlWf)oUBhg*`9=$K-L0RJYo)Z9TpzMOM}x`c=aksM<;M2gCH*-QFL#$ zdE0_W=KtPCMe^QBOjn)YklCxTOsDMCW0TpFs}y!>iBIF*j@-Gi2=fSwW8i%@4>VY- zxvvJ-*ki!NTo|@gwT%FK&#@BB(_cX7>@3Jma1Ray(I!{|tc!STyjfE(Rzw;=!PZSMsszqS3;_TC2~*W|tL^yU+j9D;UK6=rUKoQZJE zY(^f;x_99klbjE0Re(uZe!CunA^bk0b~Mv{-*%#LW^xNi=msD*mGg zNMpbqNO_Xk0G_5 z8&c7O3<)<|!?~Y8km#r3&s!J5r%kePgF*vF1Z=Pz@QJU?v!lFG4Byj;B=l~P$O)&0 z&q0gqp!`ib9E$HAs=a8Lf6wm|>`{~hqFIPz&J~8Ank4Snh=J6iY9Gi0CPj&X#3!fn z651D{_`)aYFQWK~#ogi3MDx$&6BnW!!WZTelgidFB#Y;%8z~v0Q)ZeD;IEhp&z)|= z7@>j!3w~cMB-HPdkuXtC=3exkLa&MaKZr6e&h5v&K%CQG$Y8#~B(DoiN)Uv;!iB2Q zPgv)_!H1Fu1UZq`ZW|*Eby7=)eV=$^m@r5hd`>xDLsGo z0g24jOcbH9FB*5q8i|&%&F^d+kF zqwr!(&@#5EI&Q^LN8h+Bi}!^%dS$F7_-cAlB*}}ccd1B^5KYaacl{3r9F4pI@Vz|@ zBCs_TZ>#;ITV^Q!bm5YBtJQ0(1J$x&i~x6~zgOqxm7mgcQ8S3=NjOETlZ^0R5 zJv6!}sNP1B@^xtYglsp|l8M5$`_1u>Cb=}_^DZ7zABOMOxgi-8xa9jwOMG6yk{~!N zdPL1k{*Fd$Z68_!(>>FdtUH{{T&<*(L4WU-gmGy$Hw+7NAxD11;aO}*4#B`MxiTa_ zDBlew>PwyuXzHohqq6y|J4P9ryoVVRd}&uDMcybmPld^S+0_t7K5ALO?>nR%>>cd9 zl|Pj;Q)MYa`XNJ%F+vj3<6dP$dWp~D`E^E&2>xbHcCN^5cpgs1Zz?BMi!$?FUpS{g zhp(dM;YPtZ?2)h^u!%+66C9so;Y+;dS^MY~lXr*MjGVS?o;UY&XjgrSJd+tJX^ejL z>6;53OB7$ma@xb{Dl<4Iy0GJudbDK=Q!*;k961~($ zYI{%V6%n0v;_ZCN7>l_(SLHGybSQ~cWG8*F#KT`4rJvvo-3`rck5{g;CW=bGb&k}xUtVsA;PG0fje-S4 z7k#eIc~@w3B-24Eq3apZ=1+Yn*|+G(MdLov0|iq$k0(Y&`gUd7=@>n zBfagoyVt6pDD7+G=(nTuJ+K3rF)QWKK@J7AapfE98KIz?x?J)txXZt&-RTRLfyG=~ zbfS*jFuw}S^#$zxb!OT~c25=gW_7t*U1KOeW zpVmZ~_t9f_=qdB-Ho>8l)Ri#%5(Rloqx?B2_)$wwpHxy?u0>r{H-|E*4Y78qOd@kp zB5tw&anp2VuIT-s&8|%B;5jn{@4uqveTh?8J-V_u3iAp{+n2RsRige;wED%BfT2Gp zJ4#0O8SiOOZ`bH^iNemgtqGBn^U?|}pE8(8m_HJ$XzTNiVksWB z;&EcnX8hfQau7#o2fUI0NPdr?$E9vou)D|BPKJzRg1X zo``fKGLghs|EXVazTos`aVADYmDTufaJyPX{h#@@#EiZ|IyK8<^#(> zUtizbBIUDh#DAfBpMjr>E+ONxi%c|hF!e;TeEz?w)Vb5V2GBPr` z*%EqGW?IHqirkYV&Htoa+tCon%h$7MfFQh@+fM1u&U*U!N_Jl4sa*LpN)O=eEU$5*SC?nqCOR9+K zj45NwqEtE!k)PsDCoW0MujU$-kLVls63OFfF-Xj_iO&#_6OvdnOoNzyQB_bI+oZ9yq|Xj5k-EY9FL!?O z{fpZoz<8U>_(?_peeUPz`Way)vYDA2{I+)dgyPh*UnuEmR!QMm_~pfbx`NO0euQj9`o+bx zmN*&j=e=V;Ym)VWYHj!dJ-mCs4f6@ZrcJ6s8TaWQHnr8p_;8N%RZOM9CqY#c*rMp= zh%Md5^-tC#G#XrI^Ik6(F7#)Ot2HfmyZxD)+6#E!k5*m$+nO2QiTe;0x4>dH`tZ^P zU^HoIP4(4hF3a`{!g1T4z4@cpZO~z9aroh-+F5fOP&_xszcIQ=MSK?8D4Y>I9VpA0 z{T0m%v(TxdL5_Bs28931M@QLwVf7VVIZ7~G+5kTgw)GA`XV)6{bn;gMptadR5iMi5 zkt3M1y_OWXPNqcKApJD&KvU|I{^Kh=laaiq1i$7YW%%}Qe*WQ3wfu;hk2xkwKAGom zMoI`gQ3_O_x8fMfwRZ3CV+-Gfzl&9%ytVN@**wTcf^}eAD#SSN8^zm;>C`gE#gol? zWTJHeCdqEZEN@gLW-KJ)sp_opjyW8JZ?sl_>7FSmf-{L~&&*mIOk<3sk{88@PPMnl z0>MOUhoD@Mapy(Ks@sNTx1@e7vCfwkSqI_U!@|tet6Gb~*}#=*Sn&8Gkc5FmQ>5K{ ze)E)RA}KXHC(csbk(}IeXaRO@Ak{HRO~Irv~{wFve5eW0{p}(k*22smS#}nut+}kL{_^E_p*#u@ zn=z+zLhqco+d)hO8eLE(p?6_|RB_ir%y*sVlQ2w%KX35+g|8ZG|6zRL<^2!iivS2nSxKPH5a<3)`7K|7!NH%2S<^=aZ#2H!fa zkPQ!h>75#FY8#L1`osjJKrjnSt@>jEcSSem^Ug@Hn{xy05s}d4&aL0IeIF7{HY^wV zkTBis^mxnyg3twq5Z65}VQglRI-Dyo%>(WcXM*`M@_zZ==Y})q^?VPnC~hq7kvl0{ znO8o&{I#v!#gwM+3T@>x3NVr=B7Z`V8-Jq{bRNfTQ8tgtN+e+Vtg81GpP3j7{D`=4 z0tMQGf(;~)a3<@!D7-XL&kaRg@LyT}2r~A3dvy79qXxT~D_k71q8e-d+aZx6$%8jI z^D{=bb7}ZB6-S+FX;=VRQVa6yqqr=&Q%%CD45m~o`h^YlF5rMUOK$WNq+8Lqqj^w~ z;w(sN!E!@E!};PK^-stN18#eJ?}jsIYhH^9ZdywVdE?;m zJS7E=f|{B6TR-QGOeP^=oCnp#Qy65>OhT;!AwO=IV5-%``n&maNJ3KPA1AW(>*_Zy z2P!3cVtRTV(U=D0LAvBT7hO-oRjFTCIZFq9t%O~t2z6bmc(hE>p9JTWh#Y>jpw6Lu zG%Q`XW&cw7gOC-2e~1_Es=ifeM)eEZ%#T*)t!Jv2>esvsnf%xp?t( zyZCelCgj9(ZgckD&@$@&i`~%mqpT5(%JP26CM!%7FE3BAjrA$c-|eH-N2ky8 zy&N2-G`_3X7x#Ay$FqMYB-S#GGOD+(5+ASX6;eaJU@A>dc5ic))+|6E7Z?O_IMo-g zbh|6(_28qHavf4rQGGqHJFrqfs_cy!~z-+Qn7J}-+-q> zPqI{alxTE_qad$lL|`scv8c3e#LpPHN=^)Yfju0I5ihzdn``o_qcC zu0m)fy80+hV6rLxhqItkDvGBjs3Oo`torUGcl?zpXI$OM%hByUxqRH&Bhu%6q8Mj2QK7sVTmVWJJiovqqE_{w2dnKLMx|huSNpEz6aoFWl~ znQ<0BkCurmRQrjqA|_hv~#GWxWrVp33tEg3%e3_&xjSm7eEhqvCd2%)ZlqchRXf zfX0>BSFyIY4&tW zkeub=(v@sv%y|4%Y&G zy!_n%$1Cv@lhx#91&`o6Qz=q(BzXN#!NG!@nCpHEi=$-dZQlGy{7?BQf;^(ig=$~6 zRz-JQ)$!UC%Y?)nsAoA8(a$IB`fPN4yaM@y`-}T!q~epo3UUb+ts(0=TO8#H*!>p; zpuLe~3e%SpJSK$#MwWW({#y1^)0gu&zp(nQ`1&cg^c0JB|C=V5j%WRAnh=U6d3@6k z&&03oLZT~gvjS&|bn(#>Z!JKyV3=AZrMvCH6t{}W9GKo zf{mv!r|!JTzNHo#p zJ4R>HME|0biEZ0@;!HHLZQHiZiEUdG+qU_{P9~Vxd?v}wd+xbw-F5%>!}(HO-D}sc z_pa5|YuBy~$%dfleqzG!FW>~7^sbbU7mf`K{6Pv%J~%fL+R~&9`c8e}@qFhAl<2A7 z^fCvORiRQ(p=1h5J76^Pi~(|H5HrJz!AU?0nReOOJZjCFL%B3T8D_$IUQtarW!RaM zdezi0xxNuJORGunG`$!~Wn&l3JL&VyDhO@$hQOmu7@0^Vg}F@`hPAJO(G!aG4#ohn zk>;6UW!0n&b{X(v@%!J%^NZ-Qei8MmXs!k`n=PpXq?rUn^TuSCHwcy&yj`LezB=`S(FzxL1SS;&lIFFGXx`bBzpQX;?-&OcgLSaI zc)=2ssSGtoaDtCrxE*3u?=}g95VWBHs|s4I-)$(&iDYC49qL83rGHh~k^}a!xMiv_ zlxh52QYB@=2|Q5aGF%+pM+wl)%3af3pR+Qk)$GG4yvAj_?K`2;PdtMkOd3-!c1_`2?-pob*=}jvfY_x~xgmqQs1vS~l@7KoI#&W&`bt#h%(6U$1;Y!| zwYcIraU)}L_b=OVL|FMhLNCY$z_-u_#vqxnX%vlL>Pbiyr9DSTU`n~m@<$I)G5Py? z&2fDQWVS@U@(9bN%t%USPcVv~nM)7Eo1kWxX7YXmW2nJtBb7l76pkuIQT%a3eIE2FY-Z@6_eob&mB2S`L2<3@{oti|X|FA8b(8zbKq@9!M0dGCI zoAg5Dk#-R0Ff$RO4y@-t=;viyJm}Uj*n|vuWxLi2HLm-%(LZDBisKou@SU9w1DDu zY_(io_Z!Ep2RZEWye{wDsXeft!!Wp?Bc8DMMR13Hst;WX%P=ncP4SxZ@#FC7Y{RbS zea6oo)O<4Iu{Qqcglx1WtmJD8$JN$Eqp(^ghTs-;=`J~C%?9leFKxb=yivYF%$?7wOL8==xOHaZgH&b~`~2{3pl7YP+H$RKW*vd(>O| zMrU?NA-?A2+22>)){|yyaZ+}WA^rPOz%$UR3%bZ-rj^?Z)$i~1xqmoW0AaFrALDg6 z->3H_MZK_Nu?K0_ayrqeC*`Q(g?$RpBA|5DvBbHp#^l~k2}U0+&$-DZv7ek^%=12c zw^mj%?eT8In-TrIZCFRqLth}SY`({z$L(p@@^r*fVQje#*273Vt`^*Wf7%au$5 zi<00aI#(eL^D8k&V&IaE>IHE6&o%5Y&QuP*rlOX1b_I z?T&_?RQ0OJ%lizDPhBW>s2r#=6?gjthDmDHZ+$YRrus<-#I4+#6@|_dZh}@@LfFi{ zhZ97P3%gvFHO8VO`JGmb$|goFsXADZKb*WSr6Xlh1H+WE@L$%C69@pbB&jS*#yyWZ zZetWGn~E%~Bejrh7OdCkeD1LN8YfGL#aSXw(t-LfKzxHWhN%2qJ%qCOv{9sjsu&ie zsJ3nAG|vhR2`Kr{i`Rh|K*m5tt=$*z2FJWNaU97384RT@+n8X{{8LCxcOyLmPmKMK z?q@>quCu6hMRNn}GO_9OL8hmxh?MyMM z;N8j-w)G_%?m1U2fzZIy}j3OnKr7A3rsp-#{R>`Vd?j_7%ojOjg z^*a_9)oOxO&nk%;Sxj5K1YIeNM#Aod)*p^I&W$Q}coauamjvO%EQ$$2)s7=NUQjz{ zy(GS09coGxi+BLyq5#c`7_2a$?9)$O6>TeVA_{yQJY%6kGpfbtZ`n|b+fXocg4Lvp8JO{A?Z3WA%bwah{TksMcC;OG1 zEN%+030?*^t4q~ZEy57u(S1!i zF*6(-#=2_)-C2f3Nf__ENpR^|c2lYw2h7Upr&LEJj)J7tF%%-hg~ z-!9Ksk>*dlw*xeN;cciMhYVg|^Th+~EbS=z?pmon1)Xg|rcONt(!(26rqw`WoZEQd zPgT~bWHYdCoWu$0Da8|EVHp?RV@&lR3d=i|ItoDzGo#27jLac4*XZMlih8lIaaTFY z+wGq&`F#{R>dZ2Kq=-pr&8@U-<&^6SAB3_@yNj6UF(_0N%jYmu8-e0#&e}8td8a;^ zKBB4fLbg7cd5R;h3FMumbBW}O#vFc!Tm@ob31$}(qkqLyji9;gEeSG=vamyuA;MMauud>Nd zyKf_Bilr-6rC%U$mM{RaI5*z@;)&Vtw6+IC`J?(q`m`gkZTSKC>@ z1AT$6=Ps+;;!M}ybF6nU4%Yqccu|(-?z;JYgo6R!SMQGtiS~NV-TQX(IEjdVx@T3( zbKV?)J-yDm^Xa}m2EO)GWvke>BAs+YW`@bQ8FT5HItDrABM+5`r__%5d5~T#gZHC0 zZkw#xy`uU@E~;!nx9_J)ypqwa8@bOs#K=aXdVL}*=`zLOH1zp1b}cUy+9`-|4jHAS zh};bxW*il&`hPG<5CdrXf9}J=c6b+0WnYW_EvF-wFudKwLEI@$HwU)cph! z;83x9iS^UI>G+=(KA{f2h*v8G#+L&NqftXD7n4r$goRO@AGi*-yWBP}KzZn8Q%vtr ztJEm>I`u0a=S`rU_v+{Kva6j-b^i>|lb$Qy9t;FoXdMp&nF^}k{d3^qmEt|k!V!oK z2T9|o@eK>6iB}G8FCI4 zOdOyE!`)oVz5kMiURMPa+eOs>I6v#8Hf!t!-JkmUy*k>4nFCgTEE9Fs>)9+yx^7Dx zge=cfFV^N_lGnJrmt31%ENR;jZ~Q?ya7UTUL*&f9e9 zo~0cJj@V7YdTYaDFM)R2M?QXiA|v>P(kV);ncH=e($68wSzq?@bW8rHpO;}_gxFgR zKMX=Q@$GV*pIZxI5#{aOI4P`(>7L|^dm@O_1t|P%*+0z#CfK@wNV=Af&D6kyp987j zlnX@Tkj`}_N*W8WDtA+vd~ZJQgR?RNzj8G9eOz-&DAc zzxv?}#966Ao6#Pezwrl|P}#An9Jxwk=k*lCDbS}x-vYN$ZOl9cmGlLS9MP9WG{Dhsj2G0 zf%zZGgHLyDE(@&gzI&T}ZDbTcm$z_u1*_pd|5>ekdUmq<_w+0py-!`;_+A*i?|b_0 zAoT_8A!=v4GjEnEL$>LHx6eZ zkT(|Qu|=x-wy%YB@wBshzszj!yZLpl`VRTEuDav2xQKmCfZ`cDcgK(D&Hj!aax3;^5lg=?O=rID8dUUqYC} z?pL_4W(&pzoMV}eZ1CjkWEd(%zUFA1noT3f7kke>BTnN)(V)ho3=DO_HQ5+aW%xPo z@s&ThqoSK~;~!^mGEC2xj)x30yP=|KtMMHb24xVgl3PYp9o(4&*JCs8?^FI(LZa|N zoeOUaek?g>gA3Lr7NcTKNp+^6W-TT}*d#eXLy%qDfLx@+fmvsDf#Ggj%Y{1r*x+ho z7=|DXK6*5#`r4(mxr~=XQ2L~8}+J$;mU>30*r$J z=#`e(-#mZ&Tv9ps(o@XFXOchwrq2c1`< zC!U$spNYT?@r-r7B$(eys@LM_;MVZT)yd8xB=pW(iotFoSZA{kb6Q=6pX|(RvSz~g zR2)O|hPD}j^NetI8EXh4#rnxVzB8AT1S)9{^D3oPamwQ1`^gXCNV5tNPez3yy%ESZ zt!ScpL^t}@mvp&7WivG)#y57)HQU#m1t+c3i)&4Avo5)=V3Ns|2-I9R!?^|wE)jWc zDjhN+PEJ1BtD2-kb#wSiOd0YZoUq46!keHn{YpBU^|c2ZH3I#;^FlCN#~AmThExoj zWQ~H7 zMMK7(-!zasS7DVS-_V2Y2$WBMEP&yRg(60rkppJ<+IvTJ49m$F#%-}jaytSEww*gp zX^+u3X`YPBsjP52 z1JUvv4$ElgIGuq6vj1z~K^b?(|3l^ZD=|CIn9&KCb8`3}1f+cy_{3pZd4$XsJK`(m z)&quXWH#1~uZ4Y+Fjfavs>G-&s0{cE48Ms-Pt%Ueo)=7Tk9flbhDg4I7Bvvo+LRu^M ztbhivvIY!OReVF!Y{4py!Mlxd*<2cWDDN{-4(^|smuS&4YoZS6f;5PoZmgZ5r`pT- z_6*xpu9Nzsyh2!4>WRmhe9dxHUtncdb5!_5UBhe&Q20oUg{^Z~sE{@XC5_|lZg8&0 ztlajwVJK_kw(`XQjc}kG2f=8<6WM!YW6^U-4VNZHlgAbeD}m^8vUD;$H1cUtjJZ&n zLgESlpctZP8r`T50m~3ISHWmT4a_DH(UEvA8**VM%N7njD0}{uGeya!0fCmj$^*=( zr{JK9d?86WIy-Wkf+h{c%^erVm~`Gq{HY{KWrJ_(8cpGW&}?t=*EVT=N>poL-X~L% z8oE`&F|(*liDV0ESN(N?6adB_Bj$uhQ?<5(?FC-W%uquLA%Sk#3e(nD4SXcI=D{-DIunDu` z@&p-McJL$_@h_@zOE3v$=Y_9Nl`-o!MVIaiLQsqiB~$hR!!a_F3bijd;%il)E~IyL zu_4R7d^LWr({5#jk(uYRA_iBmvjFyeSaGbZKQNvY{|)(N{y>o&^4}VmPoE>RfcwG| z75a+Awt&j2pJW5~>p0;?pa1n=g8;vOH~014kIY|(!2ddLnO_ovc)tYAXUfl;J7ijxnR^f}Bn4fGQ zsew~vY<61)py^!Hg{}!l|82R5vC?ipN)rH#B)L#V!L=9*w6Ya8nbnHf>K^-p^O~8Z z_O-MdP)Jt~1A>c^S`NwP!~uHNlw_J1X5Ew`n$hQzI1s{`8BK<0u%<9J$yf2pFbHeE zHXHJY>Y4k@$Q%DPwAUg$+>M!U#GI9r!?2be$<7@$5Ju5Sc$X!7S_ygA>rq@DAYF(;jO*|$TbD$6G``(3CI`1KpL#IszjPHh5Z{ttMQehIYJ7~a zyX$UfQi4MDu&@-=^o7b5BS4OjZmF~3&TGVYKodfalAcT!il0jCxEDVEF_!+5330?b z6m+LFDEbYmXjt+qk#xO#Q+F>#y9A!Na8gsP6QK$zv}5dryxAA9Z?fzHzVoZWBr7zjq9$ zpgs#hf5;#KNIjApO5WYf0czdd@jV&WlgvTtPINC18?jDwu~$iV>bomO&Hq#?)O)P5 z42CnL=O=XZWFAh#fxoh$9d<5f6AdxR$NqN1K6S?#3|Fg}vpg%J;#dP>TKsD0nLe>< z7H4T>H*hujrz_3OCKjG)!p=H0Q_E?_IuxAh&j<3RiH02wMfyjmL^^;r#x8xbcU(k6 z>rMfaFxa2tp8N@zCIx)N>Vz{=<_MG-jJ8E%iYt`xsVCvQLT(}jxK&P7+8HPWbWHa- znwd-7cic6DV(wVDA~aQp87L;>XZ&@-gU^N4dxWQ;YI(|d!6Jd0SwAAMh5Qx6xUiT$ zv9CYi$BH)2Zue{Qm)g|$_-}o&X2$GO-dHKDG0FL`afc^EATz;qlub5&`L`Vv@AJtx z1uyTU^y8>v)jtnKv$Ry0ens_4iTT1%*j$RHL2)JSc|TmfUpGRg4bhD;@Q23O_qi;e zI>t)zjlLX*MMU?xKiW7e{<$R_o$Yj6X&lH8jK73H_$%Ok`32EU-ua>o!whGBPr$S$ zc3rIWve=Wk1%W7w3%?^+M1de&o)mQzKO1WXrQF*Tq=R6W5{=bN0bocTP4l_Dnfq zARIEuYJ6tR{!Qt_1I+$C{$J1BxRV00d zXeXY`hYIQZYeigTa%mP*;m(LR^Jq65wZ;KVXpTDLWbxsMK2}(xy;F|(dVWi+@KPTi z&@hM`Xs@b%khGAX4;u)X)_f^^gcez=5yon$BdGK&x;lDDy=is5e*T_oyn1@)Tsd%h zhtVx^hN_0JC>O$xu2KPozflxb!}7uZyw{FMhN49*B(N)m@T0viV>F5_^+$hXma%(xgh0+f$6bTldsEUQ_m2njOk%Q;8?#g|45gozj z7M3Ba@$|SBJYBCU52#V@;N63Yt;F#04jo?n_zq&Fdy1}|x`Y?o&x&qjlBH|O3zbAu zMw%j-`G$O%q8xsR5-CYN8~i&$cO0WJl}qQCf}9mgP*or)vcd3nuu8_aX5cMCzQjaO zS<-ck1RRq=MrN1_JW9IrETap{G%%1?w}`T&J|`yg&!u3b(J|3jM>-6aqH3i@ggT!4 z{87SB2?bR~iU{crR-j&XfJ=3G$qa_*$Q!MWIo!RADdMY!gox|W89;tjaiSfSNvSfp zQk|7?MbBBu^xNP=jdBUJ;T>h47zA2Qj2pr@f{9i%4OTQSl^i{Xi0wcY_1_L=6YDa{ z6X9vbnQF>b#QF--Brk#zJaD1*bn7v_%z;m@CU3f7w{5uL+?GkkFmUc0yHG>F-Zwe$$eWN@4pB|8?R&YlcYbE^mQ=w?G$5d#9PS@m`OAAXGn02!v2#!GSVmEcIfPHt)D=yrzpv$(&t81MpUuDMP~QrSgj8lf(%9ab3YT&R_gg zw18r?KbIA+!o4p|F<$1)|53*wF$SN4%lUAe9^`!AsXL}WgwjFasdE+mLv}S7BGgKM zNl0CvD5H(mwCc|JsdVrGvbZouD3;8L>1*3$J|seag!yWdSVN6rM&9#yc>8l9TJXRQ zY9kp~7|zXW#Q+XVK5qkM3K;1VBQdFXm!n(RLn=>%Ayrak4?`gB5mF-4#}=bh@X>Q1 z6x2U(D?azrVh)4V~WXR6183wl1Tdy87Xx$u<_UdP?^Rewk0vYmmeNh zR3Aq492{-F^-DUt=472#AH!fstNi`Xwwqmi-K$BUrD?uLhO8I`eM6;4qEN*!A+ns7 zm6G7QpN>(h@*v?c0v9TDSuVK6UrFRLK0umUN&`pTn`J^H$8@`i(BdN=FXQ!bM2$0| zXDhTuP|i+CQj?JSQ)=CndHJ~d>bg6fhDW3?$kPomy6*AVbz{r7o7KI?Z}7|ZWX{&@ z*5=bq$iMW-bwwm_(KaO0)~2ArQ`6c!j3sk z&XZ$4lRuzU)Ni6l$anSId9^Xx2G>M&(0?x?(sdoGkvXeiev zp$sq(@Rs^~d*-gMo3e-goLafKSXX$t@ZekFn!lU$Fh5P^*>w4eX1`vRGFd)oeyXt_ zj8*zbT!t%w+J?9`kQn;TFF{6k6PM*5Y;Ti@+E5I}7<(sJH?`x){(4JI+XNmV@%8qx zdbo>k?minAPmt`tu-%b}|BfMcd-TyFcD#3U$D<`|eSX~E*M1&>jisuen|$W}bgc|8 zFGMZa@$Vk|nf=kiglA7<$3_^pCk6EG+BMiEvd@D@V1Im~?;gDF5QM9{Q^mfjjK!=- z@P_+S+~Y?%9o8NiH1RtavZ)@~Wg4V0crhGOVvM*&%;GY9NiW19X)KtONItqYF-w}S zAKAbLpA<(R7dYN9xYfptZb^R6E~91!22>}VW6?3w0`OYQ^UBg`kASFEe^}})(CMcm zpt6D|^P+?_D!aN&$1})oImmW@_?pf9GNPXA^dk|r1uoltwEETWP){|pSN=sa$J8*X z*K&o0XJj2C`MR#gE#q|C5yn7QxoM5Akm*zE8(nRL-nw+KFx=gQh65dDE zbXX#k_1colh}KV4)h?~t<@^AJt2Z?iB<^R@w=52^pXhr1GwIS+Ro8}M11X0`4??Vl zBJ}-gDWu7kG8wv)r-M$cz%bLo*LI=R3RU@2>(lrZmex#^1+TJomm>B6#JTsJK8x_9 z@3k4@opd6xLjR^jHG`^yrAXIjfkfd-qZEt|-+aV}HgJDmUjWo}lQZPV7Wbi@gXudZyag@I#n1D@{CH%fEVYS z#84?+uHcS9&pf9vT%Dh;nnOEx07_hiPypvDENa>73M`Q;QgWq~Md4 z7>^x#R?1>i=LFW6kd4)~7;d|@>{{QIag z?L-+dDDNmip__UUTgi6c@j>8zSx!s7*K8K@H;ub=sxm^6-}os*U+qohs@WSlF_a4GM> zhILa+tD(*`&oiJ)1yNMvqra$LrpRC7tp>%*|6u?|v{EmPJRh_+=smm}6}CK*wq9~D<$oYD{jw{49_2@mCx4G+rjM2Nk9 zYH`sX1ZBdzH_FnD31V{!d<$zyHgk*I@ZRX?Ne-j*00UGU#g8UbF?@M2HO`2 zhbNRjQQX4OU*mLeiN+l|e@jYpLLr&Tl2)jZD?*Wmv`@SZ^y>303B*H2tx98Ih~3H< zY&1(P{XriXG}M+EF4a&amD=;6=yTpf7 z;EZ+~N(1kZQR+)-A7rE@7g-N(#w|BSI0|yM3HoIqtd)oAVVFwyFKT=qIiqF(=S?$+ z^CUVh7rK|m-#@rO1GooFc{3pNzXPTSLCw) zw`$b+Y}WQh&_ZnvVH(aa8u}_xB;oE0@mpb3D5{ri9t`dlk6Qp!Qh^(MqcEWqyGI*_ zD4M#dx2RTY9*1_9j{Dm3A%dT^2Ru^{aQS0ojP7>4A5Yfj{m$^?!Q_6Mez$OS2tKN* z&+;CQVxJp1Lj3k->0C#nesYjho}9I{7SR6AKDO0TXVB_-7d>jK+eZ*ZpJ$Y}v1tbO zl|x?E+brq`y)n;Zr@3va+KE@zIU1=fN0INOut)FM@sCuIfRMVNa`c%(^>zp<#tCN%mqR}uDaP~2K5BzG2hSs)qGs#s)>=4^Vx*X zy5D!J~WJzb0y(zX`X?*REmigRjP|D;g~fuXfVr^V4kEeK`eva_;_R!lY}_onj8J9mtaR z*ZkFU*;Eo=m&^XUDB79#1y7^@NjG?Q`Ssk8im@uR2sM85@^Xswr@KH5kX04Qg#WF! z4p~v$`%{|<@hftH3qLL!*uf!;i+;ii=Ka3CyTH}Q$HBH#o@Nwgdhz}dIP=1bWDSsm zL+ziY3Qs~CDH3|KYU4wTTzQ`;m6>Gf#z%aye|F`~#WaQrePHrk|fkbAEjWuq74kF z-dPAnnTa9B-?}~lT{7$E#~1(HB{&gdn1!V8H9a3H|9E-|$S0~Yg66kTZ#i%TAG0s;Y@dofkp1@bsvsk^zkCg!4;uB)I+>yBAfJr%$@|$E zKIgLEdzVql1qX5W(q}5Vpn(I{#Cznmj*D;Yess%vcr`=KUXMq#^&7%A!<|>~gC8$9 zw-uYdZx607kYTglf^^LDeEMCFL&vKcV8u+B9#BRnXJ`yd2zt0os?0m4QDbWsWhPD~ zelPN4mi6-`{5|)9GQ&Vi zJ=LSmx%=V}y;cxu3hU#HKTZC~+1lQ4eO6wm8!MOJ*O|#OVW@%i$Kam>x8(7hl+Et} z{vp(wlN=G+a{desRwl}yaz~>YeXsjMbss0+5>Mk#(*6;nb5WK5PydJixA%jpm!la8 zlY+6eimM$Qll)iMJM(|>?#?c*BwVa4a7_O@8lIJfh57%~J7QVe&S8@a?c>To$gr=O z7;u|wtOQ7)l4BB#X80;IH&T73z%prs>;B{~XT%GbQ0K%Ds~U zkQvAqdW*&HWar zrcK31DrAe61=bK60?{!o>;;zV`n}*g(E`zNupRd|n7AF3pi$_-3e0MQ843itjXXu7 zr}PJyVWhGmPZpv%*vzAOoj_6sl2sTu%!%q0Q`3hybwlAGX&fXA%1UbXZgX1xK)6C7 zr3~h=ABX*2@`I`0nRBQ@QJ@`FFk5C5_?c^(n2`*B!h4Gn=1jODljIaTvOa+MlU-9k z-$oz{BEx1ttH77-Bw;xMs5b|*B1Lh?%u&C?_<&)+vY++%&m4e7?;c@uM=PvFi*W$P zUIA!K>verk`o2oD2fKdzIr&O##bqUB1ytqcqBBWZMxH!K3KmS8!f6^0ZCIGMj7}l_ z^~P-FRGXpcw)Z@U3FcsqeBEW{4e&OPfofQV71If!``ESIj|L$`Q+OkcXpiyIIgpWN z@RFr(cAA_U6yqTdt87+|!);W5J{8%l8arykyM9{`-S}XcGJ@whDIVLS>tm&4h8v+u z;&W7d+lBZS&ts8XfRtJ%hb|Z5*BZjlo6(Hp)_HXl@>9uQ0;2jz6~fQDvasNInQB1k z+xm?&ZWcqiMa^#G(Mhx9 zN_iN>m6nSQ`DI0SXypY&`we#4mBZz->&e#S75n#2?t9We-+rfisxDUFje4W!6+LKE zjaGg?Yh*n&{AbwFyCBzkgwZMw-3IufxL@zI%sCJxEvbuvZd(W??g!jfoy;}Ze5tR2 z=R)`^Lh8^*_MRIJj@{?AYmhf0y%m2vdQ}K1^$G2{9j>l6gfKrfx*RWmr}jZ2ac+0* ztFYyb##23vkSl(V3#W7Mc|PCL<{zjk<|X2c*NYuyfsq^&($vGqM>F3XWDKr%KdV4L zF*)LQ^oZ~9r5BMO3gL+_`ANi}@qPJ2O{KN+7*u~~!{+v}OsHF2mOHu91h}|z4&TsL za9H=3Q*^iqfUdm#gJrwC89sNLU1D~QqL!XeW1<5JY{;u^HzmDzCQx&+Y4+9Qo8c8OPT7Uib|#TRm#SG zX!3ESF%ElWvB?HP`_t_n+x9<4_t^uWla~~ke&5vyw#R+U2r=-y8k_i}i8VVFo~nd& zQr|a^;m(X@8f#fj@y8=6ek_pll>s!(Via03l`uu5iJ>|5>)_e-I?9*3jxn=xf5A2_ z`)iWcv&u8d>O&x2hI;iVlBYO96 zgod6@@E_}kgfAAc2}wpf1!fo}2%v_h=AqA%q|bV4n<6R3$(eWuI}SBfm6Xs;Jl8uS zVWTUNDiL`8e9!h4;G~jvKT^fjOO(zzRb`o>@TaMmw(rLsWnfLc-Nsvdz(=$eG35%Y zMbfy>l=;-e;T@^=*tJz@#9wK!m04UjeD zj(s6oVP8c&(NQ{G*O?GZ-FUAGEMqL-pZg>AdL+zTS$(wbtw8o-Y@q?NWhzV+Kh~>b z!a!%glk?<3^hGNL9R!dC9?xAOqp=1?#Yb!{j4yS4A}_q4WkVG9`4eAYxpsXOU%Rcl z@F?i1t8+EfKB!9nUX6+4zW@2!GFKF;j73^BuXZ_AxGp0|qI%axg2}$oI0?D$5qM;& zJDMc3ebOo**Jn0DGXq+;bYX~OemZm4vzUp((J0SC?+1Y@F0(|E zf=y7;;vZ+b^6A3eNiYIbPi3daKU%_vDgb}onPC(r-%zgf(ju<)OJ;K{vN?ZKLFSDvI+d8EO zVV7^yev#j;b~Aa!lTeEiNg>(;tKxmYsv-%e?&}6UWB+Zhnag*=Gn`Y`j`i`de2tX zl^5}XPihP60Rc5fj(AKz4%6SQb&DBcfjr4oX-gu~`qO8Y#3R@yafz%haQebG;U}PR z2HCgP7H0p_;Q%n+!n~OOsjRSYasE$dO%`r0j{j42q()1}VRIDCf3O!8d@rbBbk2M)VhOt@2j~~u$^GA0)0$< znkwXt%&&J+Wf`K2wt?x~Dut>#dV)PcG&~xS5I?-F8b*y_Imz!n$q;Z?$^;bgRdryx z|1g%w_xz?UQM;j@SpW3|Z0x+hOex?*8tQZcDeM)DSSdvBcbCg=xieng(RHC>y<%rl zSWHu3S7J86ZMp@y<|UQVW->*A%APj_~iu!6lfpU)@|RY!gcV1boHyUEreiKc;9U`(=cx+24;ujHEACabcPv-Hg8u($=P@D)YS zAQ@aJb-RGKtw;A{9M7EUv0W`dqb{I$zaL;5r*vda(IpwZnJWBU0;1e!kdRK`FcPuw z2hqPY9&h~8NDS;)Y(LYG*|N6!yOQNf6u{Wzd#;yg8Uf3#iTYRvLr&Sb=XnxKTvCrG zFPbj9(Z>)2P! z%c#Ts08K+?02!$$ZM8TGDq4(_^j=a}mp*&YJZd2!90z06s7v-k1r6JY>Q5z?hp~XV ztPp?$6i9ahQaL79J_oP%>PSHLkD_8=MW_sCh~#i4aqT&%+Ez{`iy~&K`2p855LxtO zHoaYV4%Gec4rCEHx$%ZQc?Zp;i{V-4bF*T8t`n~)?J9a&xLF~b^1ESk?m(mds_ge- zv}?Nkz;L^CO+!nmf4MOYhO4Y({14LCLD)6*DbiTs#tq9s7a{T>;`)qe=P+7;hcK~o z8h5yMex<^_9@EvFNt>j6ZFhzn^DTq}|a>bY6%RCZ>H#|G@Vg;kJXHfZy6P-*7 zaLX9>a&gS&=*uSakRXJ5)RJh^m&S}>26nVo9vp(9JORL!W1n+ysikgnG-mM4adkrS zD{##h^cn)V_&|19!*iuNGj{vxhWR0TMsT?Edfx<2jKB5DT!PwZvs-03NLgsJdha%{ z%HlTTg3bGjW0)Z1JV$20zyryfsU&dwL;DrFIytWI+nwPzd)BvZv3Q0Av!WBBpGGdN zRI>kmm$e7B(v@dp;f15Y5=YanNC1%~C5$dfmj(7KZeS{ED_HwA7QOWHQZt|UHXodB z@OJ4DepxHLglG$(S$1g12DWnU2HxUG8bb*ZrMpXq)L1YokVrj9$?LGe;E+Dx{4FfE zeiV#P5rE;cJPz`ens(u=#i=WlRuf%7u#00Cd$z z4e}}205amkz6c8W@op*IOkfo?rVR0uboq};GV9+;GJ+Hp_9U2tL`2luIwO-q>41vH zvFvE>Q=twBrmA&s_}sk0JJX&=M>v*TL+e;BMAKut+sbxvk8GYtUHWX}Yp#cmQbHIc zq7S`GQhrv!((&@%gnFFXG}YMir{yCZ;AY33)D`e>z^3>|aZVXHVjsT_B-Ci%%r9Qd zc?=->CR(x>F@%@xqq~g14 z78I6KB=f{7TaWrq$N~zBVpT+J5)%^+ljPG;C3(_n3x))t@H2*~$^o65#jt!-zT>6h+&H>wwqLG^NoURRlEk6C`l4YgovMuSdg+LW(kkrGpx65FUCb&S(yWL5b$Kq(# z$rfD*_n=CR?Aohev}ln=`P6pr?@5}Hb*r&t@7*t7SD{5(^WL^5?($V)d$)iWN9XKp zN9_PbT(cdm%`F+cfBx7kJ0rO-OjqJd;y`@tQzvOu@N zA%#f3EBQk*z$!HoF>_Trph~;lo7#!^J-$1{Q?%2lr-wMae|~#~%%%LFDgi6&|GOB- z|F%%DakKs(TPXf7%HAnRlb~%AZriqP+qP}nwr$(yv~A;QOnchyp0=%>_m6)gHe%m{ zZ%-?uDi12N^1kZ6aHXf6y48*xuxmKvGM%2CmO1Rt#i;B@)qL#YHp=KhP=$RRQ_ij7 zDy1CY-wm83>cm7wCvTuqo#MgJJRo6c#rC^w%wIT`lCz9Vf?0o%HvLs3UAg4XHYR3( z_ubLSHvIoK2l>Tt?NG4$TPO+Rm6V?Cb6TqDU-lO59)L2u2jsSsz4=vXZ|(MGVdQJu zu-{uwMtkdKWI}u;0L-L3?R!3!^3Kxe?g5&QkdI2MXQXn(xhykVK8G*MA>wM=n7r<@}1gMzBRx*h}B_$eQ}kSyzcv{ z`axK`LPK@csU4x43-1Cv+}dj4eX*zvtwPwgI6=Q>C>SI zt8k_w?X8?oL^l_4zU(?}rc>Ck(|YLAICQ(k8Edn|g5YN9crL$)63$S5g%z@p;n~w& zu-cV8n)-7#f%y%8?uMYzSV!3Yaq`>l`s*bv=~acJ2Yz6ZG4u#ti@=tg5mKXVOP#PL zePHQB<4Vr5%HEL(a9AP$kLS>!>&?d&Y4c>&v`zTFHo(iVKhEkXF!hV^sCZFG)1rtu zlr073W874I(nFU0ET*^YmI3NR-d)NXESXx+70vQS_Y^;xnBDl1vN9f@l;D16%2ORu z*XPe%b+&(&D(+<*cbjIddy-9}68L`UjSqjj2Nuv@O@lWswZ_`X3!N2j>%MVbL7&c# znsv7dcTc+^jLq30{a$!OyPK4ex4#f-4Fd`3pd#rp2(4V0m}##|){T~?U{iMZqYPr1 z(}_cYujUNauV0%!>bsf>cpt!=Rhqf8v$dSF0ekliwv2T18^5}2buJt@PWy)}414(m z_=kR3SvvzwRxY5)6u!KajXcVVbyY_y2?SELe_t{Z;H#!v!2i=Dg? z%lN&{y7$N{u`V^oWTo;^Qdt*K;H-z_Kw1V+C#H=Nnb^xLDIirdi1I>mCZT4ao9 z3~aKtS(x@gW{4!2f-~AU#`(&aGR4E=7>I0zUF1Ivq3kd%^TK{h={qX@H> zab==58_xoD~KIzMr5?H85pXywVu-> zb0V}{rzBP?ru8@|e9EF-QNBJ5N(tBiN*PV{T#%U%r(;^GH_D>Qi18}59DYR=o*e%# z`3UvuA-4``BNt!*W z#J}S?+}h*ANYXouSBLC!hZ^goRD-!@OPBN*keWhF+PFEck^aY$Xl$6-%cKF@8?}+zt ztGyJfY+U-gW3gyye#K6YJmtl#4-lM((`qn$;Ids`^)Z5*GCz-4yOylei9Goe(oELT z%G5gS9u>p5|F&4*Q?tKIoz=0R{W*BiN!pqS>au^KYIkS@9**()c4+ZaslOl!rcv4? z=%dq^t~>C9cx)S0a=sGh#&e(wVF3_%pBpw%*|9H4UE*B@SZrmD-?1V zhaS|GhEq%h57eB{B{L)~Np`Z=SfTzV7(<3+c&mcNU&~fDKax9rz-W5`q4iB5+*+7` z1kshI2!x(mJT5zjh*;24=h#?*oKHx0G1#{Pg#cnT|C9#vflaC-x^+z%@wdsULM`M8 zo_vF8gcM*Jjuj1wHf-vlz1kap(aHoOnU1{yR$GPk->8tR6SsXtN;=#8Hic%MPz+)p zecM4=joQ&e9a6J>Cb9wXw+px{Ms~u0-oAjLMN-x@1G)nlT!k-bGbek4B6C35QSfDx68H#Xf|9TSIq!|^-MFOb&G)zn`kMmz3f%e0*^i!Ni!(3;3zM2gY1f9Mxc#N;;W zi||hLebD{?SwOQlLHmoy++1jBX2B(A)SAl6MHB&4%(|z=x)7w#f>$VN+(S?$&v0Tf z%gU`??ojAC84Hz=>&VD#4LFjf6YoLFvasX;fl_gXLKxI6s=)GNTov?f*hbB;0DC@c z4`rk@;Tg2ye0C<0L`N+x)@k%b7N_VSE(*FQy*~a-u;ZknpgVIT1qyK~Slex6i~Qj* zEs+NzN*6scaBHowtg0dvV*O|a(0dgHG2vOX)nsiwMKGVTG2(%kKMHDFRAnw9F?=)8 zMq#aKcvxa2SjynSnt%UYm;$%9h6+NqFmWl?T4SP6api&+myHNw4T)H2i`)Avs{U(}_CO1sCKtR};jww`<P+%zQj?4! zH*l5gloNwDZvWT9$Yx@Ts=}GK8@WQpyNQk-O93Y+4bB zJ4-cqd(UTgJga)pf!IapJuRHOX(F+;)ri@`B(V^Al2trIS`I@!IF03BJSn<5arsDz zS;nzq{yR~CGp6x%q$M0AF9G6E`F^6{V;^@Jf{&yzg29OZ0<}rQye#~IDf0mk~rr|{7Jv4+pD`Ovo|ver6~M0aJZb?tiWzd_I&!z z8*GeFV6#0)y)?fMiY*?}2QLzX0RN*fC1AUQpIg2ezg4*EMvM>D{9vzA{VUbHWn#wY z?Ns<;(c+OnNTO4};NRD3(M_|;Qgy3W>(z^^3yYP{o|cF? zt47@`�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

-K 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 `?iN)a-hO9!hl%@d$+V2CONl}J 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 Date: Mon, 21 Oct 2024 11:16:20 -0300 Subject: [PATCH 107/111] Add Certora specs (#48) * Certora setup (#37) * builtin sanity, pre_condition rule * folders * more rules with advanced CVL properties * document examples * more options for ClipperCallee concrete implementations * fix also fr Clipper * where am I taken from * fix * remove kick summarization * remove kick * update links * added rule sanity to configs * added basic multicall spec * adds a test solidity contract for multicall * final setup touches and multicall * added certora files and vim to gitignore --------- Co-authored-by: EyalHochCertora Co-authored-by: EyalHCertora <91595356+EyalHochCertora@users.noreply.github.com> * LockstakeUrn * Fix file header * Use real Vat code and some changes to urn conf * Use double quotes * Add LockstakeMkr spec * Fix * Revert removal of end line * LockstakeEngine (WIP) + Minor change for LockstakeUrn * Remove space * Remove factory from voteDelegate file * Add missing check for selectVoteDelegate + add selectFarm and selectFarm_revert specs * Add lock spec + minor changes to others * lock_revert spec * Improve comments * lockNgt and lockNgt_revert specs * Add free and feeeNgt specs * Add free_revert and freeNgt_revert specs * Add some new invariant rules + tidy up * Add freeNoFee and freeNoFee_revert specs * Simplify messages for non revert rules * Add draw and draw_revert specs * Add wipe, wipeAll and getReward specs * Add wipe_revert and onKick specs * Add wipeAll_revert spec * Add getReward_revert spec * farm renaming + minor changes * Add onKick_revert spec + minor comments fix * Fixes for inkMatchesLsmkrFarm * Add onTake and onTake_revert specs * Add onRemove and onRemove_revert specs * Separate Multicall rules to different file + fix wipe_revert * Minor changes * Comment change * First batch of LocstakeClipper specs * Add redo and redo_specs + fix in storageAffected * Clean up * Fix specs timeouts for LockstakeEngine * Add take spec + changes to storageAffected * stopped revert conditions better matching with code * Add take_revert spec * Clipper: some variables renaming * Add upchost upchost_revert specs * Fix for take spec + add yank and yank_revert specs * Engine: Rename several variables * Update specs to newer version * Remove unused function * Fix CI * Fix missing solc versions CI * Fix timeouts * Fix * Change * Some cleaning in multicall specs * Add more invariants to LockstakeEngine * Improve invariants for staking * Fix * Changes in invariants * Include view functions invariants as well * Fix --------- Co-authored-by: Shoham Shamir <123297287+shoham-certora@users.noreply.github.com> Co-authored-by: EyalHochCertora Co-authored-by: EyalHCertora <91595356+EyalHochCertora@users.noreply.github.com> --- .github/workflows/certora.yml | 54 + .gitignore | 6 + Makefile | 6 + certora/LockstakeClipper.conf | 88 + certora/LockstakeClipper.spec | 1034 +++++++++++ certora/LockstakeEngine.conf | 99 + certora/LockstakeEngine.spec | 2197 +++++++++++++++++++++++ certora/LockstakeEngineMulticall.conf | 57 + certora/LockstakeEngineMulticall.spec | 97 + certora/LockstakeMkr.conf | 12 + certora/LockstakeMkr.spec | 329 ++++ certora/LockstakeUrn.conf | 34 + certora/LockstakeUrn.spec | 180 ++ certora/harness/MulticallExecutor.sol | 43 + certora/harness/StakingRewards2Mock.sol | 11 + certora/harness/VoteDelegate2Mock.sol | 11 + certora/harness/dss/ClipperCallee.sol | 102 ++ certora/harness/dss/Dog.sol | 249 +++ certora/harness/dss/Jug.sol | 154 ++ certora/harness/dss/Spotter.sol | 133 ++ certora/harness/dss/Vat.sol | 270 +++ certora/harness/tokens/MkrMock.sol | 11 + certora/harness/tokens/RewardsMock.sol | 11 + certora/harness/tokens/SkyMock.sol | 11 + 24 files changed, 5199 insertions(+) create mode 100644 .github/workflows/certora.yml create mode 100644 Makefile create mode 100644 certora/LockstakeClipper.conf create mode 100644 certora/LockstakeClipper.spec create mode 100644 certora/LockstakeEngine.conf create mode 100644 certora/LockstakeEngine.spec create mode 100644 certora/LockstakeEngineMulticall.conf create mode 100644 certora/LockstakeEngineMulticall.spec create mode 100644 certora/LockstakeMkr.conf create mode 100644 certora/LockstakeMkr.spec create mode 100644 certora/LockstakeUrn.conf create mode 100644 certora/LockstakeUrn.spec create mode 100644 certora/harness/MulticallExecutor.sol create mode 100644 certora/harness/StakingRewards2Mock.sol create mode 100644 certora/harness/VoteDelegate2Mock.sol create mode 100644 certora/harness/dss/ClipperCallee.sol create mode 100644 certora/harness/dss/Dog.sol create mode 100644 certora/harness/dss/Jug.sol create mode 100644 certora/harness/dss/Spotter.sol create mode 100644 certora/harness/dss/Vat.sol create mode 100644 certora/harness/tokens/MkrMock.sol create mode 100644 certora/harness/tokens/RewardsMock.sol create mode 100644 certora/harness/tokens/SkyMock.sol diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml new file mode 100644 index 00000000..105bdf0e --- /dev/null +++ b/.github/workflows/certora.yml @@ -0,0 +1,54 @@ +name: Certora + +on: [push, pull_request] + +jobs: + certora: + name: Certora + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + lockstake: + - urn + - lsmkr + - engine + - engine-multicall + - clipper + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: '11' + java-package: jre + + - name: Set up Python 3.8 + uses: actions/setup-python@v3 + with: + python-version: 3.8 + + - name: Install solc-select + run: pip3 install solc-select + + - name: Solc Select 0.8.21 + run: solc-select install 0.8.21 + + - name: Solc Select 0.6.12 + run: solc-select install 0.6.12 + + - name: Solc Select 0.5.12 + run: solc-select install 0.5.12 + + - name: Install Certora + run: pip3 install certora-cli-beta + + - name: Verify ${{ matrix.lockstake }} + run: make certora-${{ matrix.lockstake }} results=1 + env: + CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/.gitignore b/.gitignore index 85198aaa..f665798a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,9 @@ docs/ # Dotenv file .env + +# Certora +.certora_internal + +# Vim +.*.swp diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..7d592993 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +PATH := ~/.solc-select/artifacts/solc-0.5.12:~/.solc-select/artifacts/solc-0.6.12:~/.solc-select/artifacts/solc-0.8.21:$(PATH) +certora-urn :; PATH=${PATH} certoraRun certora/LockstakeUrn.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) +certora-lsmkr :; PATH=${PATH} certoraRun certora/LockstakeMkr.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) +certora-engine :; PATH=${PATH} certoraRun certora/LockstakeEngine.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) +certora-engine-multicall :; PATH=${PATH} certoraRun certora/LockstakeEngineMulticall.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) +certora-clipper :; PATH=${PATH} certoraRun certora/LockstakeClipper.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) diff --git a/certora/LockstakeClipper.conf b/certora/LockstakeClipper.conf new file mode 100644 index 00000000..94c4b77f --- /dev/null +++ b/certora/LockstakeClipper.conf @@ -0,0 +1,88 @@ +{ + "files": [ + "src/LockstakeClipper.sol", + "src/LockstakeEngine.sol", + "src/LockstakeUrn.sol", + "src/LockstakeMkr.sol", + "certora/harness/dss/Vat.sol", + "certora/harness/dss/Spotter.sol", + "certora/harness/dss/Dog.sol", + "certora/harness/dss/ClipperCallee.sol:BadGuy", + "certora/harness/dss/ClipperCallee.sol:RedoGuy", + "certora/harness/dss/ClipperCallee.sol:KickGuy", + "certora/harness/dss/ClipperCallee.sol:FileUintGuy", + "certora/harness/dss/ClipperCallee.sol:FileAddrGuy", + "certora/harness/dss/ClipperCallee.sol:YankGuy", + "test/mocks/VoteDelegateMock.sol", + "certora/harness/tokens/MkrMock.sol", + "test/mocks/StakingRewardsMock.sol" + ], + "solc_map": { + "LockstakeClipper": "solc-0.8.21", + "LockstakeEngine": "solc-0.8.21", + "LockstakeUrn": "solc-0.8.21", + "LockstakeMkr": "solc-0.8.21", + "Vat": "solc-0.5.12", + "Spotter": "solc-0.5.12", + "Dog": "solc-0.6.12", + "BadGuy": "solc-0.8.21", + "RedoGuy": "solc-0.8.21", + "KickGuy": "solc-0.8.21", + "FileUintGuy": "solc-0.8.21", + "FileAddrGuy": "solc-0.8.21", + "YankGuy": "solc-0.8.21", + "VoteDelegateMock": "solc-0.8.21", + "MkrMock": "solc-0.8.21", + "StakingRewardsMock": "solc-0.8.21" + }, + "solc_optimize_map": { + "LockstakeClipper": "200", + "LockstakeEngine": "200", + "LockstakeUrn": "200", + "LockstakeMkr": "200", + "Vat": "0", + "Spotter": "0", + "Dog": "0", + "BadGuy": "0", + "RedoGuy": "0", + "KickGuy": "0", + "FileUintGuy": "0", + "FileAddrGuy": "0", + "YankGuy": "0", + "VoteDelegateMock": "0", + "MkrMock": "0", + "StakingRewardsMock": "0", + }, + "link": [ + "LockstakeClipper:vat=Vat", + "LockstakeClipper:engine=LockstakeEngine", + "LockstakeClipper:dog=Dog", + "LockstakeClipper:spotter=Spotter", + "LockstakeEngine:vat=Vat", + "LockstakeEngine:mkr=MkrMock", + "LockstakeEngine:lsmkr=LockstakeMkr", + "LockstakeUrn:engine=LockstakeEngine", + "VoteDelegateMock:gov=MkrMock", + "StakingRewardsMock:stakingToken=LockstakeMkr", + "Dog:vat=Vat", + "BadGuy:clip=LockstakeClipper", + "RedoGuy:clip=LockstakeClipper", + "KickGuy:clip=LockstakeClipper", + "FileUintGuy:clip=LockstakeClipper", + "FileAddrGuy:clip=LockstakeClipper", + "YankGuy:clip=LockstakeClipper" + ], + "verify": "LockstakeClipper:certora/LockstakeClipper.spec", + "prover_args": [ + "-rewriteMSizeAllocations true" + ], + "smt_timeout": "7000", + "rule_sanity": "basic", + "optimistic_loop": true, + "optimistic_contract_recursion": true, + "contract_recursion_limit": "2", + "multi_assert_check": true, + "parametric_contracts": ["LockstakeClipper"], + "build_cache": true, + "msg": "LockstakeClipper" +} diff --git a/certora/LockstakeClipper.spec b/certora/LockstakeClipper.spec new file mode 100644 index 00000000..3db6b1f8 --- /dev/null +++ b/certora/LockstakeClipper.spec @@ -0,0 +1,1034 @@ +// LockstakeClipper.spec + +using LockstakeEngine as lockstakeEngine; +using LockstakeUrn as lockstakeUrn; +using LockstakeMkr as lsmkr; +using MkrMock as mkr; +using Vat as vat; +using Spotter as spotter; +using Dog as dog; +using VoteDelegateMock as voteDelegate; +using StakingRewardsMock as stakingRewards; +using BadGuy as badGuy; +using RedoGuy as redoGuy; +using KickGuy as kickGuy; +using FileUintGuy as fileUintGuy; +using FileAddrGuy as fileAddrGuy; +using YankGuy as yankGuy; + +methods { + // storage variables + function wards(address) external returns (uint256) envfree; + function dog() external returns (address) envfree; + function vow() external returns (address) envfree; + function spotter() external returns (address) envfree; + function calc() external returns (address) envfree; + function buf() external returns (uint256) envfree; + function tail() external returns (uint256) envfree; + function cusp() external returns (uint256) envfree; + function chip() external returns (uint64) envfree; + function tip() external returns (uint192) envfree; + function chost() external returns (uint256) envfree; + function kicks() external returns (uint256) envfree; + function active(uint256) external returns (uint256) envfree; + function sales(uint256) external returns (uint256,uint256,uint256,uint256,address,uint96,uint256) envfree; + function stopped() external returns (uint256) envfree; + function count() external returns (uint256) envfree; + function active(uint256) external returns (uint256) envfree; + // immutables + function ilk() external returns (bytes32) envfree; + // + function lockstakeEngine.wards(address) external returns (uint256) envfree; + function lockstakeEngine.urnAuctions(address) external returns (uint256) envfree; + function lockstakeEngine.urnVoteDelegates(address) external returns (address) envfree; + function lockstakeEngine.urnFarms(address) external returns (address) envfree; + function lockstakeEngine.ilk() external returns (bytes32) envfree; + function lockstakeEngine.fee() external returns (uint256) envfree; + function mkr.totalSupply() external returns (uint256) envfree; + function mkr.balanceOf(address) external returns (uint256) envfree; + function lsmkr.wards(address) external returns (uint256) envfree; + function lsmkr.totalSupply() external returns (uint256) envfree; + function lsmkr.allowance(address,address) external returns (uint256) envfree; + function lsmkr.balanceOf(address) external returns (uint256) envfree; + function stakingRewards.balanceOf(address) external returns (uint256) envfree; + function stakingRewards.totalSupply() external returns (uint256) envfree; + function voteDelegate.stake(address) external returns (uint256) envfree; + function vat.wards(address) external returns (uint256) envfree; + function vat.live() external returns (uint256) envfree; + function vat.can(address, address) external returns (uint256) envfree; + function vat.debt() external returns (uint256) envfree; + function vat.vice() external returns (uint256) envfree; + function vat.dai(address) external returns (uint256) envfree; + function vat.sin(address) external returns (uint256) envfree; + function vat.gem(bytes32,address) external returns (uint256) envfree; + function vat.ilks(bytes32) external returns (uint256,uint256,uint256,uint256,uint256) envfree; + function vat.urns(bytes32, address) external returns (uint256,uint256) envfree; + function spotter.ilks(bytes32) external returns (address,uint256) envfree; + function spotter.par() external returns (uint256) envfree; + function dog.wards(address) external returns (uint256) envfree; + function dog.chop(bytes32) external returns (uint256) envfree; + function dog.Dirt() external returns (uint256) envfree; + function dog.ilks(bytes32) external returns (address,uint256,uint256,uint256) envfree; + // + function _.peek() external => peekSummary() expect (uint256, bool); + function _.price(uint256,uint256) external => calcPriceSummary() expect (uint256); + function _.free(uint256) external => DISPATCHER(true); + function _.withdraw(uint256) external => DISPATCHER(true); + function _.withdraw(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + // `ClipperCallee` + // NOTE: this might result in recursion, since we linked all the `ClipperCallee` + // to the `LockstakeClipper`. + function _.clipperCall( + address, uint256, uint256, bytes + ) external => DISPATCHER(true); +} + +definition addrZero() returns address = 0x0000000000000000000000000000000000000000; +definition max_int256() returns mathint = 2^255 - 1; +definition WAD() returns mathint = 10^18; +definition RAY() returns mathint = 10^27; +definition _min(mathint x, mathint y) returns mathint = x < y ? x : y; + +ghost uint256 pipVal; +ghost bool pipOk; +function peekSummary() returns (uint256, bool) { + return (pipVal, pipOk); +} + +ghost uint256 calcPrice; +function calcPriceSummary() returns uint256 { + return calcPrice; +} + +ghost lockedGhost() returns uint256; + +hook Sstore locked uint256 n_locked { + havoc lockedGhost assuming lockedGhost@new() == n_locked; +} + +hook Sload uint256 value locked { + require lockedGhost() == value; +} + +// Verify that each storage layout is only modified in the corresponding functions +rule storageAffected(method f) { + env e; + + address anyAddr; + uint256 anyUint256; + + mathint wardsBefore = wards(anyAddr); + address dogBefore = dog(); + address vowBefore = vow(); + address spotterBefore = spotter(); + address calcBefore = calc(); + mathint bufBefore = buf(); + mathint tailBefore = tail(); + mathint cuspBefore = cusp(); + mathint chipBefore = chip(); + mathint tipBefore = tip(); + mathint chostBefore = chost(); + mathint kicksBefore = kicks(); + mathint activeBefore = active(anyUint256); + mathint countBefore = count(); + mathint salesAnyPosBefore; mathint salesAnyTabBefore; mathint salesAnyLotBefore; mathint salesAnyTotBefore; address salesAnyUsrBefore; mathint salesAnyTicBefore; mathint salesAnyTopBefore; + salesAnyPosBefore, salesAnyTabBefore, salesAnyLotBefore, salesAnyTotBefore, salesAnyUsrBefore, salesAnyTicBefore, salesAnyTopBefore = sales(anyUint256); + mathint stoppedBefore = stopped(); + + calldataarg args; + f(e, args); + + mathint wardsAfter = wards(anyAddr); + address dogAfter = dog(); + address vowAfter = vow(); + address spotterAfter = spotter(); + address calcAfter = calc(); + mathint bufAfter = buf(); + mathint tailAfter = tail(); + mathint cuspAfter = cusp(); + mathint chipAfter = chip(); + mathint tipAfter = tip(); + mathint chostAfter = chost(); + mathint kicksAfter = kicks(); + mathint activeAfter = active(anyUint256); + mathint countAfter = count(); + mathint salesAnyPosAfter; mathint salesAnyTabAfter; mathint salesAnyLotAfter; mathint salesAnyTotAfter; address salesAnyUsrAfter; mathint salesAnyTicAfter; mathint salesAnyTopAfter; + salesAnyPosAfter, salesAnyTabAfter, salesAnyLotAfter, salesAnyTotAfter, salesAnyUsrAfter, salesAnyTicAfter, salesAnyTopAfter = sales(anyUint256); + mathint stoppedAfter = stopped(); + + assert wardsAfter != wardsBefore => f.selector == sig:rely(address).selector || f.selector == sig:deny(address).selector, "Assert 1"; + assert dogAfter != dogBefore => f.selector == sig:file(bytes32,address).selector, "Assert 2"; + assert vowAfter != vowBefore => f.selector == sig:file(bytes32,address).selector, "Assert 3"; + assert spotterAfter != spotterBefore => f.selector == sig:file(bytes32,address).selector, "Assert 4"; + assert calcAfter != calcBefore => f.selector == sig:file(bytes32,address).selector, "Assert 5"; + assert bufAfter != bufBefore => f.selector == sig:file(bytes32,uint256).selector, "Assert 6"; + assert tailAfter != tailBefore => f.selector == sig:file(bytes32,uint256).selector, "Assert 7"; + assert cuspAfter != cuspBefore => f.selector == sig:file(bytes32,uint256).selector, "Assert 8"; + assert chipAfter != chipBefore => f.selector == sig:file(bytes32,uint256).selector, "Assert 9"; + assert tipAfter != tipBefore => f.selector == sig:file(bytes32,uint256).selector, "Assert 10"; + assert chostAfter != chostBefore => f.selector == sig:upchost().selector, "Assert 11"; + assert kicksAfter != kicksBefore => f.selector == sig:kick(uint256,uint256,address,address).selector, "Assert 12"; + assert countAfter != countBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 13"; + assert activeAfter != activeBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 14"; + assert salesAnyPosAfter != salesAnyPosBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 15"; + assert salesAnyTabAfter != salesAnyTabBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 16"; + assert salesAnyLotAfter != salesAnyLotBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 17"; + assert salesAnyTotAfter != salesAnyTotBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 18"; + assert salesAnyUsrAfter != salesAnyUsrBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 19"; + assert salesAnyTicAfter != salesAnyTicBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:redo(uint256,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 20"; + assert salesAnyTopAfter != salesAnyTopBefore => f.selector == sig:kick(uint256,uint256,address,address).selector || f.selector == sig:redo(uint256,address).selector || f.selector == sig:take(uint256,uint256,uint256,address,bytes).selector || f.selector == sig:yank(uint256).selector, "Assert 21"; + assert stoppedAfter != stoppedBefore => f.selector == sig:file(bytes32,uint256).selector, "Assert 22"; +} + +// Verify correct storage changes for non reverting rely +rule rely(address usr) { + env e; + + address other; + require other != usr; + + mathint wardsOtherBefore = wards(other); + + rely(e, usr); + + mathint wardsUsrAfter = wards(usr); + mathint wardsOtherAfter = wards(other); + + assert wardsUsrAfter == 1, "Assert 1"; + assert wardsOtherAfter == wardsOtherBefore, "Assert 2"; +} + +// Verify revert rules on rely +rule rely_revert(address usr) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + rely@withrevert(e, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting deny +rule deny(address usr) { + env e; + + address other; + require other != usr; + + mathint wardsOtherBefore = wards(other); + + deny(e, usr); + + mathint wardsUsrAfter = wards(usr); + mathint wardsOtherAfter = wards(other); + + assert wardsUsrAfter == 0, "Assert 1"; + assert wardsOtherAfter == wardsOtherBefore, "Assert 2"; +} + +// Verify revert rules on deny +rule deny_revert(address usr) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + deny@withrevert(e, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting file +rule file_uint256(bytes32 what, uint256 data) { + env e; + + mathint bufBefore = buf(); + mathint tailBefore = tail(); + mathint cuspBefore = cusp(); + mathint chipBefore = chip(); + mathint tipBefore = tip(); + mathint stoppedBefore = stopped(); + + file(e, what, data); + + mathint bufAfter = buf(); + mathint tailAfter = tail(); + mathint cuspAfter = cusp(); + mathint chipAfter = chip(); + mathint tipAfter = tip(); + mathint stoppedAfter = stopped(); + + assert what == to_bytes32(0x6275660000000000000000000000000000000000000000000000000000000000) => bufAfter == to_mathint(data), "Assert 1"; + assert what != to_bytes32(0x6275660000000000000000000000000000000000000000000000000000000000) => bufAfter == bufBefore, "Assert 2"; + assert what == to_bytes32(0x7461696c00000000000000000000000000000000000000000000000000000000) => tailAfter == to_mathint(data), "Assert 3"; + assert what != to_bytes32(0x7461696c00000000000000000000000000000000000000000000000000000000) => tailAfter == tailBefore, "Assert 4"; + assert what == to_bytes32(0x6375737000000000000000000000000000000000000000000000000000000000) => cuspAfter == to_mathint(data), "Assert 5"; + assert what != to_bytes32(0x6375737000000000000000000000000000000000000000000000000000000000) => cuspAfter == cuspBefore, "Assert 6"; + assert what == to_bytes32(0x6368697000000000000000000000000000000000000000000000000000000000) => chipAfter == data % (max_uint64 + 1), "Assert 7"; + assert what != to_bytes32(0x6368697000000000000000000000000000000000000000000000000000000000) => chipAfter == chipBefore, "Assert 8"; + assert what == to_bytes32(0x7469700000000000000000000000000000000000000000000000000000000000) => tipAfter == data % (max_uint192 + 1), "Assert 9"; + assert what != to_bytes32(0x7469700000000000000000000000000000000000000000000000000000000000) => tipAfter == tipBefore, "Assert 10"; + assert what == to_bytes32(0x73746f7070656400000000000000000000000000000000000000000000000000) => stoppedAfter == to_mathint(data), "Assert 11"; + assert what != to_bytes32(0x73746f7070656400000000000000000000000000000000000000000000000000) => stoppedAfter == stoppedBefore, "Assert 12"; +} + +// Verify revert rules on file +rule file_uint256_revert(bytes32 what, uint256 data) { + env e; + + mathint wardsSender = wards(e.msg.sender); + mathint locked = lockedGhost(); + + file@withrevert(e, what, data); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = locked != 0; + bool revert4 = what != to_bytes32(0x6275660000000000000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x7461696c00000000000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x6375737000000000000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x6368697000000000000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x7469700000000000000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x73746f7070656400000000000000000000000000000000000000000000000000); + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting file +rule file_address(bytes32 what, address data) { + env e; + + address spotterBefore = spotter(); + address dogBefore = dog(); + address vowBefore = vow(); + address calcBefore = calc(); + + file(e, what, data); + + address spotterAfter = spotter(); + address dogAfter = dog(); + address vowAfter = vow(); + address calcAfter = calc(); + + assert what == to_bytes32(0x73706f7474657200000000000000000000000000000000000000000000000000) => spotterAfter == data, "Assert 1"; + assert what != to_bytes32(0x73706f7474657200000000000000000000000000000000000000000000000000) => spotterAfter == spotterBefore, "Assert 2"; + assert what == to_bytes32(0x646f670000000000000000000000000000000000000000000000000000000000) => dogAfter == data, "Assert 3"; + assert what != to_bytes32(0x646f670000000000000000000000000000000000000000000000000000000000) => dogAfter == dogBefore, "Assert 4"; + assert what == to_bytes32(0x766f770000000000000000000000000000000000000000000000000000000000) => vowAfter == data, "Assert 5"; + assert what != to_bytes32(0x766f770000000000000000000000000000000000000000000000000000000000) => vowAfter == vowBefore, "Assert 6"; + assert what == to_bytes32(0x63616c6300000000000000000000000000000000000000000000000000000000) => calcAfter == data, "Assert 7"; + assert what != to_bytes32(0x63616c6300000000000000000000000000000000000000000000000000000000) => calcAfter == calcBefore, "Assert 8"; +} + +// Verify revert rules on file +rule file_address_revert(bytes32 what, address data) { + env e; + + mathint wardsSender = wards(e.msg.sender); + mathint locked = lockedGhost(); + + file@withrevert(e, what, data); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = locked != 0; + bool revert4 = what != to_bytes32(0x73706f7474657200000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x646f670000000000000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x766f770000000000000000000000000000000000000000000000000000000000) && + what != to_bytes32(0x63616c6300000000000000000000000000000000000000000000000000000000); + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting kick +rule kick(uint256 tab, uint256 lot, address usr, address kpr) { + env e; + + mathint kicksBefore = kicks(); + mathint countBefore = count(); + mathint id = kicksBefore + 1; + uint256 otherUint256; + require to_mathint(otherUint256) != id; + mathint salesOtherPosBefore; mathint salesOtherTabBefore; mathint salesOtherLotBefore; mathint salesOtherTotBefore; address salesOtherUsrBefore; mathint salesOtherTicBefore; mathint salesOtherTopBefore; + salesOtherPosBefore, salesOtherTabBefore, salesOtherLotBefore, salesOtherTotBefore, salesOtherUsrBefore, salesOtherTicBefore, salesOtherTopBefore = sales(otherUint256); + mathint vatDaiKprBefore = vat.dai(kpr); + address vow = vow(); + mathint vatSinVowBefore = vat.sin(vow); + bytes32 ilk = ilk(); + mathint engineUrnAuctionsUsrBefore = lockstakeEngine.urnAuctions(usr); + + mathint par = spotter.par(); + // Avoid division by zero + require par > 0; + mathint val; bool b; + val, b = peekSummary(); + mathint feedPrice = val * 10^9 * RAY() / par; + mathint buf = buf(); + mathint coin = tip() + tab * chip() / WAD(); + + kick(e, tab, lot, usr, kpr); + + mathint kicksAfter = kicks(); + mathint countAfter = count(); + mathint activeCountAfter = active(require_uint256(countAfter - 1)); + mathint salesIdPosAfter; mathint salesIdTabAfter; mathint salesIdLotAfter; mathint salesIdTotAfter; address salesIdUsrAfter; mathint salesIdTicAfter; mathint salesIdTopAfter; + salesIdPosAfter, salesIdTabAfter, salesIdLotAfter, salesIdTotAfter, salesIdUsrAfter, salesIdTicAfter, salesIdTopAfter = sales(require_uint256(id)); + mathint salesOtherPosAfter; mathint salesOtherTabAfter; mathint salesOtherLotAfter; mathint salesOtherTotAfter; address salesOtherUsrAfter; mathint salesOtherTicAfter; mathint salesOtherTopAfter; + salesOtherPosAfter, salesOtherTabAfter, salesOtherLotAfter, salesOtherTotAfter, salesOtherUsrAfter, salesOtherTicAfter, salesOtherTopAfter = sales(otherUint256); + mathint vatDaiKprAfter= vat.dai(kpr); + mathint vatSinVowAfter= vat.sin(vow); + mathint engineUrnAuctionsUsrAfter = lockstakeEngine.urnAuctions(usr); + + assert kicksAfter == kicksBefore + 1, "Assert 1"; + assert countAfter == countBefore + 1, "Assert 2"; + assert activeCountAfter == id, "Assert 3"; + assert salesIdPosAfter == countAfter - 1, "Assert 4"; + assert salesIdTabAfter == to_mathint(tab), "Assert 5"; + assert salesIdLotAfter == to_mathint(lot), "Assert 6"; + assert salesIdTotAfter == to_mathint(lot), "Assert 7"; + assert salesIdUsrAfter == usr, "Assert 8"; + assert salesIdTicAfter == e.block.timestamp % (max_uint96 + 1), "Assert 9"; + assert salesIdTopAfter == feedPrice * buf / RAY(), "Assert 10"; + assert salesOtherPosAfter == salesOtherPosBefore, "Assert 11"; + assert salesOtherTabAfter == salesOtherTabBefore, "Assert 12"; + assert salesOtherLotAfter == salesOtherLotBefore, "Assert 13"; + assert salesOtherTotAfter == salesOtherTotBefore, "Assert 14"; + assert salesOtherUsrAfter == salesOtherUsrBefore, "Assert 15"; + assert salesOtherTicAfter == salesOtherTicBefore, "Assert 16"; + assert salesOtherTopAfter == salesOtherTopBefore, "Assert 17"; + assert vatDaiKprAfter == vatDaiKprBefore + coin, "Assert 18"; + assert vatSinVowAfter == vatSinVowBefore + coin, "Assert 19"; + assert engineUrnAuctionsUsrAfter == engineUrnAuctionsUsrBefore + 1, "Assert 20"; +} + +// Verify revert rules on kick +rule kick_revert(uint256 tab, uint256 lot, address usr, address kpr) { + env e; + + require usr == lockstakeUrn; + address prevVoteDelegate = lockstakeEngine.urnVoteDelegates(usr); + require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate; + address prevFarm = lockstakeEngine.urnFarms(usr); + require prevFarm == addrZero() || prevFarm == stakingRewards; + + mathint wardsSender = wards(e.msg.sender); + mathint locked = lockedGhost(); + mathint stopped = stopped(); + mathint kicks = kicks(); + mathint count = count(); + mathint buf = buf(); + mathint par = spotter.par(); + bytes32 ilk = ilk(); + mathint vatUrnsIlkUsrInk; mathint a; + vatUrnsIlkUsrInk, a = vat.urns(ilk, usr); + // Avoid division by zero + require par > 0; + mathint val; bool has; + val, has = peekSummary(); + mathint feedPrice = val * 10^9 * RAY() / par; + mathint chip = chip(); + mathint coin = tip() + tab * chip / WAD(); + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require lockstakeEngine.wards(currentContract) == 1; + // Happening in urn (usr) init + require lsmkr.allowance(usr, lockstakeEngine) == max_uint256; + // Tokens invariants + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(prevFarm) + lsmkr.balanceOf(usr) + lsmkr.balanceOf(lockstakeEngine); + require stakingRewards.totalSupply() >= stakingRewards.balanceOf(usr); + // VoteDelegate assumptions + require prevVoteDelegate == addrZero() || to_mathint(voteDelegate.stake(lockstakeEngine)) >= vatUrnsIlkUsrInk + lot; + require prevVoteDelegate == addrZero() || mkr.balanceOf(voteDelegate) >= voteDelegate.stake(lockstakeEngine); + // StakingRewards assumptions + require prevFarm == addrZero() && lsmkr.balanceOf(usr) >= lot || + prevFarm != addrZero() && to_mathint(stakingRewards.balanceOf(usr)) >= vatUrnsIlkUsrInk + lot && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUsrInk + lot; + // Practical Vat assumptions + require vat.sin(vow()) + coin <= max_uint256; + require vat.dai(kpr) + coin <= max_uint256; + require vat.vice() + coin <= max_uint256; + require vat.debt() + coin <= max_uint256; + // Practical assumption (vatUrnsIlkUsrInk + lot should be the same than the vatUrnsIlkUsrInk prev to the kick call) + require vatUrnsIlkUsrInk + lot <= max_uint256; + // LockstakeEngine assumption + require lockstakeEngine.urnAuctions(usr) < max_uint256; + require lockstakeEngine.ilk() == ilk; + + kick@withrevert(e, tab, lot, usr, kpr); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = locked != 0; + bool revert4 = stopped >= 1; + bool revert5 = tab == 0; + bool revert6 = lot == 0; + bool revert7 = to_mathint(lot) > max_int256(); + bool revert8 = usr == addrZero(); + bool revert9 = kicks == max_uint256; + bool revert10 = count == max_uint256; + bool revert11 = !has; + bool revert12 = val * 10^9 * RAY() > max_uint256; + bool revert13 = feedPrice * buf > max_uint256; + bool revert14 = feedPrice * buf / RAY() == 0; + bool revert15 = tab * chip > max_uint256; + bool revert16 = coin > max_uint256; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7 || revert8 || revert9 || + revert10 || revert11 || revert12 || + revert13 || revert14 || revert15 || + revert16, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting redo +rule redo(uint256 id, address kpr) { + env e; + + uint256 otherUint256; + require otherUint256 != id; + + mathint chost = chost(); + mathint a; address b; + mathint salesIdTab; mathint salesIdLot; mathint salesIdTicBefore; mathint salesIdTopBefore; + a, salesIdTab, salesIdLot, a, b, salesIdTicBefore, salesIdTopBefore = sales(id); + mathint salesOtherTicBefore; mathint salesOtherTopBefore; + a, a, a, a, b, salesOtherTicBefore, salesOtherTopBefore = sales(otherUint256); + mathint vatDaiKprBefore = vat.dai(kpr); + address vow = vow(); + mathint vatSinVowBefore = vat.sin(vow); + + mathint par = spotter.par(); + // Avoid division by zero + require par > 0; + mathint val; bool c; + val, c = peekSummary(); + mathint feedPrice = val * 10^9 * RAY() / par; + mathint buf = buf(); + mathint coin = tip() + salesIdTab * chip() / WAD(); + bool paysKpr = salesIdTab >= chost && salesIdLot * feedPrice >= chost; + + redo(e, id, kpr); + + mathint salesIdTicAfter; mathint salesIdTopAfter; + a, a, a, a, b, salesIdTicAfter, salesIdTopAfter = sales(id); + mathint salesOtherTicAfter; mathint salesOtherTopAfter; + a, a, a, a, b, salesOtherTicAfter, salesOtherTopAfter = sales(otherUint256); + mathint vatDaiKprAfter = vat.dai(kpr); + mathint vatSinVowAfter = vat.sin(vow); + + assert salesIdTicAfter == e.block.timestamp % (max_uint96 + 1), "Assert 1"; + assert salesIdTopAfter == feedPrice * buf / RAY(), "Assert 2"; + assert salesOtherTicAfter == salesOtherTicBefore, "Assert 3"; + assert salesOtherTopAfter == salesOtherTopBefore, "Assert 4"; + assert paysKpr => vatDaiKprAfter == vatDaiKprBefore + coin, "Assert 5"; + assert !paysKpr => vatDaiKprAfter == vatDaiKprBefore, "Assert 6"; + assert paysKpr => vatSinVowAfter == vatSinVowBefore + coin, "Assert 7"; + assert !paysKpr => vatSinVowAfter == vatSinVowBefore, "Assert 8"; +} + +// Verify revert rules on redo +rule redo_revert(uint256 id, address kpr) { + env e; + + mathint locked = lockedGhost(); + mathint stopped = stopped(); + mathint tail = tail(); + mathint cusp = cusp(); + mathint chost = chost(); + + mathint a; + mathint salesIdTab; mathint salesIdLot; address salesIdUsr; mathint salesIdTic; mathint salesIdTop; + a, salesIdTab, salesIdLot, a, salesIdUsr, salesIdTic, salesIdTop = sales(id); + + require to_mathint(e.block.timestamp) >= salesIdTic; + mathint price = calcPriceSummary(); + // Avoid division by zero + require salesIdTop > 0; + bool done = e.block.timestamp - salesIdTic > tail || price * RAY() / salesIdTop < cusp; + + mathint par = spotter.par(); + // Avoid division by zero + require par > 0; + mathint val; bool has; + val, has = peekSummary(); + mathint feedPrice = val * 10^9 * RAY() / par; + mathint buf = buf(); + mathint tip = tip(); + mathint chip = chip(); + mathint coin = tip + salesIdTab * chip() / WAD(); + bool paysKpr = salesIdTab >= chost && salesIdLot * feedPrice >= chost; + + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + // Practical Vat assumptions + require vat.sin(vow()) + coin <= max_uint256; + require vat.dai(kpr) + coin <= max_uint256; + require vat.vice() + coin <= max_uint256; + require vat.debt() + coin <= max_uint256; + + redo@withrevert(e, id, kpr); + + bool revert1 = e.msg.value > 0; + bool revert2 = locked != 0; + bool revert3 = stopped >= 2; + bool revert4 = salesIdUsr == addrZero(); + bool revert5 = to_mathint(e.block.timestamp) < salesIdTic; + bool revert6 = e.block.timestamp - salesIdTic <= tail && price * RAY() > max_uint256; + bool revert7 = !done; + bool revert8 = !has; + bool revert9 = val * 10^9 * RAY() > max_uint256; + bool revert10 = feedPrice * buf > max_uint256; + bool revert11 = feedPrice * buf / RAY() == 0; + bool revert12 = (tip > 0 || chip > 0) && salesIdTab >= chost && salesIdLot * feedPrice > max_uint256; + bool revert13 = paysKpr && salesIdTab * chip > max_uint256; + bool revert14 = paysKpr && coin > max_uint256; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7 || revert8 || revert9 || + revert10 || revert11 || revert12 || + revert13 || revert14, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting take +rule take(uint256 id, uint256 amt, uint256 max, address who, bytes data) { + env e; + + bytes32 ilk = ilk(); + address vow = vow(); + + mathint countBefore = count(); + uint256 otherUint256; + require otherUint256 != id; + mathint activeLastBefore; + if (countBefore > 0) { + activeLastBefore = active(assert_uint256(countBefore - 1)); + } else { + activeLastBefore = 0; + } + + mathint salesIdPosBefore; mathint salesIdTabBefore; mathint salesIdLotBefore; mathint salesIdTotBefore; address salesIdUsrBefore; mathint salesIdTicBefore; mathint salesIdTopBefore; + salesIdPosBefore, salesIdTabBefore, salesIdLotBefore, salesIdTotBefore, salesIdUsrBefore, salesIdTicBefore, salesIdTopBefore = sales(id); + require salesIdUsrBefore == lockstakeUrn; + mathint salesOtherPosBefore; mathint salesOtherTabBefore; mathint salesOtherLotBefore; mathint salesOtherTotBefore; address salesOtherUsrBefore; mathint salesOtherTicBefore; mathint salesOtherTopBefore; + salesOtherPosBefore, salesOtherTabBefore, salesOtherLotBefore, salesOtherTotBefore, salesOtherUsrBefore, salesOtherTicBefore, salesOtherTopBefore = sales(otherUint256); + mathint vatGemIlkClipperBefore = vat.gem(ilk, currentContract); + mathint mkrTotalSupplyBefore = mkr.totalSupply(); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(lockstakeEngine); + mathint mkrBalanceOfWhoBefore = mkr.balanceOf(who); + mathint vatDaiSenderBefore = vat.dai(e.msg.sender); + mathint vatDaiVowBefore = vat.dai(vow); + mathint dogDirtBefore = dog.Dirt(); + address a; mathint b; + mathint dogIlkDirtBefore; + a, b, b, dogIlkDirtBefore = dog.ilks(ilk); + mathint vatUrnsIlkUsrInkBefore; + vatUrnsIlkUsrInkBefore, b = vat.urns(ilk, salesIdUsrBefore); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUsrBefore = lsmkr.balanceOf(salesIdUsrBefore); + mathint engineUrnAuctionsUsrBefore = lockstakeEngine.urnAuctions(salesIdUsrBefore); + + mathint price = calcPriceSummary(); + // Avoid division by zero + require price > 0; + // Token invariants + require mkrTotalSupplyBefore >= mkrBalanceOfEngineBefore + mkrBalanceOfWhoBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfUsrBefore; + // LockstakeEngine assumption + require lockstakeEngine.ilk() == ilk; + // Governance setting assumption + require vow != e.msg.sender; + + mathint sliceAux = _min(salesIdLotBefore, amt); + mathint oweAux = sliceAux * price; + mathint chost = chost(); + mathint slice; mathint owe; + if (oweAux > salesIdTabBefore) { + owe = salesIdTabBefore; + slice = owe / price; + } else { + if (oweAux < salesIdTabBefore && sliceAux < salesIdLotBefore) { + if (salesIdTabBefore - oweAux < chost) { + owe = salesIdTabBefore - chost; + slice = owe / price; + } else { + owe = oweAux; + slice = sliceAux; + } + } else { + owe = oweAux; + slice = sliceAux; + } + } + mathint calcTabAfter = salesIdTabBefore - owe; + mathint calcLotAfter = salesIdLotBefore - slice; + bool isRemoved = calcLotAfter == 0 || calcTabAfter == 0; + mathint fee = lockstakeEngine.fee(); + // Happening in kick + require salesIdLotBefore <= max_int256(); + require salesIdTotBefore >= salesIdLotBefore; + // Happening in Engine constructor + require fee < WAD(); + mathint sold = calcLotAfter == 0 ? salesIdTotBefore : (calcTabAfter == 0 ? salesIdTotBefore - calcLotAfter : 0); + mathint left = calcTabAfter == 0 ? calcLotAfter : 0; + mathint burn = _min(sold * fee / (WAD() - fee), left); + mathint refund = left - burn; + + take(e, id, amt, max, who, data); + + mathint kicksAfter = kicks(); + mathint countAfter = count(); + mathint activeCountAfter = active(require_uint256(countAfter - 1)); + mathint salesIdPosAfter; mathint salesIdTabAfter; mathint salesIdLotAfter; mathint salesIdTotAfter; address salesIdUsrAfter; mathint salesIdTicAfter; mathint salesIdTopAfter; + salesIdPosAfter, salesIdTabAfter, salesIdLotAfter, salesIdTotAfter, salesIdUsrAfter, salesIdTicAfter, salesIdTopAfter = sales(id); + mathint salesOtherPosAfter; mathint salesOtherTabAfter; mathint salesOtherLotAfter; mathint salesOtherTotAfter; address salesOtherUsrAfter; mathint salesOtherTicAfter; mathint salesOtherTopAfter; + salesOtherPosAfter, salesOtherTabAfter, salesOtherLotAfter, salesOtherTotAfter, salesOtherUsrAfter, salesOtherTicAfter, salesOtherTopAfter = sales(otherUint256); + mathint vatGemIlkClipperAfter = vat.gem(ilk, currentContract); + mathint mkrTotalSupplyAfter = mkr.totalSupply(); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(lockstakeEngine); + mathint mkrBalanceOfWhoAfter = mkr.balanceOf(who); + mathint vatDaiSenderAfter = vat.dai(e.msg.sender); + mathint vatDaiVowAfter = vat.dai(vow); + mathint dogDirtAfter = dog.Dirt(); + mathint dogIlkDirtAfter; + a, b, b, dogIlkDirtAfter = dog.ilks(ilk); + mathint vatUrnsIlkUsrInkAfter; + vatUrnsIlkUsrInkAfter, b = vat.urns(ilk, salesIdUsrBefore); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUsrAfter = lsmkr.balanceOf(salesIdUsrBefore); + mathint engineUrnAuctionsUsrAfter = lockstakeEngine.urnAuctions(salesIdUsrBefore); + + assert countAfter == (isRemoved ? countBefore - 1 : countBefore), "Assert 1"; + assert salesIdPosAfter == (isRemoved ? 0 : salesIdPosBefore), "Assert 2"; + assert salesIdTabAfter == (isRemoved ? 0 : calcTabAfter), "Assert 3"; + assert salesIdLotAfter == (isRemoved ? 0 : calcLotAfter), "Assert 4"; + assert salesIdTotAfter == (isRemoved ? 0 : salesIdTotBefore), "Assert 5"; + assert salesIdUsrAfter == (isRemoved ? addrZero() : salesIdUsrBefore), "Assert 6"; + assert salesIdTicAfter == (isRemoved ? 0 : salesIdTicBefore), "Assert 7"; + assert salesIdTopAfter == (isRemoved ? 0 : salesIdTopBefore), "Assert 8"; + assert salesOtherPosAfter == (to_mathint(otherUint256) == activeLastBefore && isRemoved ? salesIdPosBefore : salesOtherPosBefore), "Assert 9"; + assert salesOtherTabAfter == salesOtherTabBefore, "Assert 10"; + assert salesOtherLotAfter == salesOtherLotBefore, "Assert 11"; + assert salesOtherTotAfter == salesOtherTotBefore, "Assert 12"; + assert salesOtherUsrAfter == salesOtherUsrBefore, "Assert 13"; + assert salesOtherTicAfter == salesOtherTicBefore, "Assert 14"; + assert salesOtherTopAfter == salesOtherTopBefore, "Assert 15"; + assert vatGemIlkClipperAfter == vatGemIlkClipperBefore - (calcLotAfter > 0 && calcTabAfter == 0 ? salesIdLotBefore : slice), "Assert 16"; + assert mkrTotalSupplyAfter == mkrTotalSupplyBefore - burn, "Assert 17"; + assert who == lockstakeEngine => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - burn, "Assert 18"; + assert who != lockstakeEngine => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - slice - burn, "Assert 19"; + assert who != lockstakeEngine && who != salesIdUsrBefore => mkrBalanceOfWhoAfter == mkrBalanceOfWhoBefore + slice, "Assert 20"; + assert vatDaiSenderAfter == vatDaiSenderBefore - owe, "Assert 21"; + assert vatDaiVowAfter == vatDaiVowBefore + owe, "Assert 22"; + assert dogDirtAfter == dogDirtBefore - (calcLotAfter == 0 ? salesIdTabBefore : owe), "Assert 23"; + assert dogIlkDirtAfter == dogIlkDirtBefore - (calcLotAfter == 0 ? salesIdTabBefore : owe), "Assert 24"; + assert vatUrnsIlkUsrInkAfter == vatUrnsIlkUsrInkBefore + refund, "Assert 25"; + assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore + refund, "Assert 26"; + assert lsmkrBalanceOfUsrAfter == lsmkrBalanceOfUsrBefore + refund, "Assert 27"; + assert engineUrnAuctionsUsrAfter == engineUrnAuctionsUsrBefore - (isRemoved ? 1 : 0), "Assert 28"; +} + +// Verify revert rules on take +rule take_revert(uint256 id, uint256 amt, uint256 max, address who, bytes data) { + env e; + + require e.msg.sender != currentContract; + + bytes32 ilk = ilk(); + address vow = vow(); + mathint locked = lockedGhost(); + mathint stopped = stopped(); + mathint tail = tail(); + mathint cusp = cusp(); + mathint chost = chost(); + mathint count = count(); + mathint activeLast; + if (count > 0) { + activeLast = active(assert_uint256(count - 1)); + } else { + activeLast = 0; + } + + mathint salesIdPos; mathint salesIdTab; mathint salesIdLot; mathint salesIdTot; address salesIdUsr; mathint salesIdTic; mathint salesIdTop; + salesIdPos, salesIdTab, salesIdLot, salesIdTot, salesIdUsr, salesIdTic, salesIdTop = sales(id); + + mathint vatGemIlkClipper = vat.gem(ilk, currentContract); + mathint vatCanSenderClipper = vat.can(e.msg.sender, currentContract); + mathint vatDaiSender = vat.dai(e.msg.sender); + + mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkDust; mathint a; + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, a, vatIlksIlkDust = vat.ilks(ilk); + mathint vatUrnsIlkUsrInk; mathint vatUrnsIlkUsrArt; + vatUrnsIlkUsrInk, vatUrnsIlkUsrArt = vat.urns(ilk, salesIdUsr); + + mathint dogDirt = dog.Dirt(); + address b; mathint dogIlkDirt; + b, a, a, dogIlkDirt = dog.ilks(ilk); + + require to_mathint(e.block.timestamp) >= salesIdTic; + mathint price = calcPriceSummary(); + // Avoid division by zero + require salesIdTop > 0; + bool done = e.block.timestamp - salesIdTic > tail || price * RAY() / salesIdTop < cusp; + + mathint sliceAux = _min(salesIdLot, amt); + mathint oweAux = sliceAux * price; + mathint slice; mathint owe; + if (oweAux > salesIdTab) { + owe = salesIdTab; + slice = owe / price; + } else { + if (oweAux < salesIdTab && sliceAux < salesIdLot) { + if (salesIdTab - oweAux < chost) { + owe = salesIdTab - chost; + slice = price > 0 ? owe / price : max_uint256; // Just a placeholder if price == 0 + } else { + owe = oweAux; + slice = sliceAux; + } + } else { + owe = oweAux; + slice = sliceAux; + } + } + mathint calcTabAfter = salesIdTab - owe; + mathint calcLotAfter = salesIdLot - slice; + mathint digAmt = calcLotAfter == 0 ? salesIdTab : owe; + bool isRemoved = calcLotAfter == 0 || calcTabAfter == 0; + mathint fee = lockstakeEngine.fee(); + + // Happening in kick + require salesIdLot <= max_int256(); + require salesIdTot >= salesIdLot; + // Happening in Engine constructor + require fee < WAD(); + require lsmkr.wards(lockstakeEngine) == 1; + mathint sold = calcLotAfter == 0 ? salesIdTot : (calcTabAfter == 0 ? salesIdTot - calcLotAfter : 0); + mathint left = calcTabAfter == 0 ? calcLotAfter : 0; + mathint burn = _min(sold * fee / (WAD() - fee), left); + mathint refund = left - burn; + // Happening in urn init + require vat.can(salesIdUsr, lockstakeEngine) == 1; + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkr.balanceOf(lockstakeEngine) + mkr.balanceOf(who); + require lsmkr.totalSupply() >= mkr.balanceOf(salesIdUsr); + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require vat.wards(lockstakeEngine) == 1; + require dog.wards(currentContract) == 1; + require lockstakeEngine.wards(currentContract) == 1; + // LockstakeEngine assumtions + require lockstakeEngine.ilk() == ilk; + require to_mathint(mkr.balanceOf(lockstakeEngine)) >= slice + burn; + require lockstakeEngine.urnAuctions(salesIdUsr) > 0; + require sold * fee <= max_uint256; + require refund <= max_int256(); + require vat.gem(ilk, salesIdUsr) + refund <= max_uint256; + require salesIdUsr != addrZero() && salesIdUsr != lsmkr; + require lsmkr.totalSupply() + refund <= max_uint256; + // Dog assumptions + require dogDirt >= digAmt; + require dogIlkDirt >= digAmt; + // Practical Vat assumptions + require vat.live() == 1; + require vat.dai(vow) + owe <= max_uint256; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require vatUrnsIlkUsrInk + refund <= max_uint256; + require (vatUrnsIlkUsrInk + refund) * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUsrArt; + require vatUrnsIlkUsrArt == 0 || vatIlksIlkRate * vatUrnsIlkUsrArt >= vatIlksIlkDust; + + take@withrevert(e, id, amt, max, who, data); + + bool revert1 = e.msg.value > 0; + bool revert2 = locked != 0; + bool revert3 = stopped >= 3; + bool revert4 = salesIdUsr == addrZero(); + bool revert5 = price * RAY() > max_uint256; + bool revert6 = done; + bool revert7 = to_mathint(max) < price; + bool revert8 = sliceAux * price > max_uint256; + bool revert9 = oweAux < salesIdTab && sliceAux < salesIdLot && salesIdTab - oweAux < chost && salesIdTab <= chost; + bool revert10 = oweAux < salesIdTab && sliceAux < salesIdLot && salesIdTab - oweAux < chost && price == 0; + bool revert11 = vatGemIlkClipper < slice; + bool revert12 = data.length > 0 && (who == badGuy || who == redoGuy || who == kickGuy || who == fileUintGuy || who == fileAddrGuy || who == yankGuy); + bool revert13 = vatCanSenderClipper != 1; + bool revert14 = vatDaiSender < owe; + bool revert15 = (calcLotAfter == 0 || calcTabAfter == 0) && count == 0; + bool revert16 = (calcLotAfter == 0 || calcTabAfter == 0) && to_mathint(id) != activeLast && salesIdPos > count - 1; + bool revert17 = calcLotAfter > 0 && calcTabAfter == 0 && vatGemIlkClipper < salesIdLot; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7 || revert8 || revert9 || + revert10 || revert11 || revert12 || + revert13 || revert14 || revert15 || + revert16 || revert17, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting upchost +rule upchost() { + env e; + + bytes32 ilk = ilk(); + + mathint vatIlksIlkDust; mathint a; + a, a, a, a, vatIlksIlkDust = vat.ilks(ilk); + + mathint dogChopIlk = dog.chop(ilk); + + upchost(e); + + mathint chostAfter = chost(); + + assert chostAfter == vatIlksIlkDust * dogChopIlk / WAD(), "Assert 1"; +} + +// Verify revert rules on upchost +rule upchost_revert() { + env e; + + bytes32 ilk = ilk(); + + mathint vatIlksIlkDust; mathint a; + a, a, a, a, vatIlksIlkDust = vat.ilks(ilk); + + mathint dogChopIlk = dog.chop(ilk); + + upchost@withrevert(e); + + bool revert1 = e.msg.value > 0; + bool revert2 = vatIlksIlkDust * dogChopIlk > max_uint256; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting yank +rule yank(uint256 id) { + env e; + + require e.msg.sender != currentContract; + + bytes32 ilk = ilk(); + // address vow = vow(); + + mathint countBefore = count(); + uint256 otherUint256; + require otherUint256 != id; + mathint activeLastBefore; + if (countBefore > 0) { + activeLastBefore = active(assert_uint256(countBefore - 1)); + } else { + activeLastBefore = 0; + } + mathint a; address b; + mathint salesIdPosBefore; mathint salesIdTabBefore; mathint salesIdLotBefore; address salesIdUsrBefore; + salesIdPosBefore, salesIdTabBefore, salesIdLotBefore, a, salesIdUsrBefore, a, a = sales(id); + mathint salesOtherPosBefore; mathint salesOtherTabBefore; mathint salesOtherLotBefore; mathint salesOtherTotBefore; address salesOtherUsrBefore; mathint salesOtherTicBefore; mathint salesOtherTopBefore; + salesOtherPosBefore, salesOtherTabBefore, salesOtherLotBefore, salesOtherTotBefore, salesOtherUsrBefore, salesOtherTicBefore, salesOtherTopBefore = sales(otherUint256); + mathint dogDirtBefore = dog.Dirt(); + mathint dogIlkDirtBefore; + b, a, a, dogIlkDirtBefore = dog.ilks(ilk); + mathint vatGemIlkClipperBefore = vat.gem(ilk, currentContract); + mathint vatGemIlkSenderBefore = vat.gem(ilk, e.msg.sender); + mathint engineUrnAuctionsUsrBefore = lockstakeEngine.urnAuctions(salesIdUsrBefore); + + yank(e, id); + + mathint countAfter = count(); + mathint salesIdPosAfter; mathint salesIdTabAfter; mathint salesIdLotAfter; mathint salesIdTotAfter; address salesIdUsrAfter; mathint salesIdTicAfter; mathint salesIdTopAfter; + salesIdPosAfter, salesIdTabAfter, salesIdLotAfter, salesIdTotAfter, salesIdUsrAfter, salesIdTicAfter, salesIdTopAfter = sales(id); + mathint salesOtherPosAfter; mathint salesOtherTabAfter; mathint salesOtherLotAfter; mathint salesOtherTotAfter; address salesOtherUsrAfter; mathint salesOtherTicAfter; mathint salesOtherTopAfter; + salesOtherPosAfter, salesOtherTabAfter, salesOtherLotAfter, salesOtherTotAfter, salesOtherUsrAfter, salesOtherTicAfter, salesOtherTopAfter = sales(otherUint256); + mathint dogDirtAfter = dog.Dirt(); + mathint dogIlkDirtAfter; + b, a, a, dogIlkDirtAfter = dog.ilks(ilk); + mathint vatGemIlkClipperAfter = vat.gem(ilk, currentContract); + mathint vatGemIlkSenderAfter = vat.gem(ilk, e.msg.sender); + mathint engineUrnAuctionsUsrAfter = lockstakeEngine.urnAuctions(salesIdUsrBefore); + + assert countAfter == countBefore - 1, "Assert 1"; + assert salesIdPosAfter == 0, "Assert 2"; + assert salesIdTabAfter == 0, "Assert 3"; + assert salesIdLotAfter == 0, "Assert 4"; + assert salesIdTotAfter == 0, "Assert 5"; + assert salesIdUsrAfter == addrZero(), "Assert 6"; + assert salesIdTicAfter == 0, "Assert 7"; + assert salesIdTopAfter == 0, "Assert 8"; + assert salesOtherPosAfter == (to_mathint(otherUint256) == activeLastBefore ? salesIdPosBefore : salesOtherPosBefore), "Assert 9"; + assert salesOtherTabAfter == salesOtherTabBefore, "Assert 10"; + assert salesOtherLotAfter == salesOtherLotBefore, "Assert 11"; + assert salesOtherTotAfter == salesOtherTotBefore, "Assert 12"; + assert salesOtherUsrAfter == salesOtherUsrBefore, "Assert 13"; + assert salesOtherTicAfter == salesOtherTicBefore, "Assert 14"; + assert salesOtherTopAfter == salesOtherTopBefore, "Assert 15"; + assert dogDirtAfter == dogDirtBefore - salesIdTabBefore, "Assert 16"; + assert dogIlkDirtAfter == dogIlkDirtBefore - salesIdTabBefore, "Assert 17"; + assert vatGemIlkClipperAfter == vatGemIlkClipperBefore - salesIdLotBefore, "Assert 18"; + assert vatGemIlkSenderAfter == vatGemIlkSenderBefore + salesIdLotBefore, "Assert 19"; + assert engineUrnAuctionsUsrAfter == engineUrnAuctionsUsrBefore - 1, "Assert 20"; +} + +// Verify revert rules on yank +rule yank_revert(uint256 id) { + env e; + + require e.msg.sender != currentContract; + + mathint wardsSender = wards(e.msg.sender); + mathint locked = lockedGhost(); + bytes32 ilk = ilk(); + + mathint count = count(); + mathint activeLast; + if (count > 0) { + activeLast = active(assert_uint256(count - 1)); + } else { + activeLast = 0; + } + + mathint salesIdPos; mathint salesIdTab; mathint salesIdLot; address salesIdUsr; mathint a; + salesIdPos, salesIdTab, salesIdLot, a, salesIdUsr, a, a = sales(id); + + mathint engineWardsClipper = lockstakeEngine.wards(currentContract); + + mathint dogWardsClipper = dog.wards(currentContract); + mathint dogDirt = dog.Dirt(); + address b; mathint dogIlkDirt; + b, a, a, dogIlkDirt = dog.ilks(ilk); + + mathint vatGemIlkClipper = vat.gem(ilk, currentContract); + mathint vatGemIlkSender = vat.gem(ilk, e.msg.sender); + + // LockstakeEngine assumptions + require engineWardsClipper == 1; + require lockstakeEngine.urnAuctions(salesIdUsr) > 0; + // Dog assumptions + require dogWardsClipper == 1; + require dogDirt >= salesIdTab; + require dogIlkDirt >= salesIdTab; + // Vat assumption + require vatGemIlkSender + salesIdLot <= max_uint256; + + yank@withrevert(e, id); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = locked != 0; + bool revert4 = salesIdUsr == addrZero(); + bool revert5 = vatGemIlkClipper < salesIdLot; + bool revert6 = count == 0 || to_mathint(id) != activeLast && salesIdPos > count - 1; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6, "Revert rules failed"; +} diff --git a/certora/LockstakeEngine.conf b/certora/LockstakeEngine.conf new file mode 100644 index 00000000..a6d5fb03 --- /dev/null +++ b/certora/LockstakeEngine.conf @@ -0,0 +1,99 @@ +{ + "files": [ + "src/LockstakeEngine.sol", + "src/LockstakeUrn.sol", + "src/LockstakeMkr.sol", + "certora/harness/dss/Jug.sol", + "certora/harness/dss/Vat.sol", + "test/mocks/VoteDelegateMock.sol", + "certora/harness/VoteDelegate2Mock.sol", + "test/mocks/VoteDelegateMock.sol:VoteDelegateFactoryMock", + "test/mocks/UsdsJoinMock.sol", + "test/mocks/UsdsMock.sol", + "certora/harness/tokens/MkrMock.sol", + "test/mocks/MkrSkyMock.sol", + "certora/harness/tokens/SkyMock.sol", + "certora/harness/tokens/RewardsMock.sol", + "test/mocks/StakingRewardsMock.sol", + "certora/harness/StakingRewards2Mock.sol" + ], + "solc_map": { + "LockstakeEngine": "solc-0.8.21", + "LockstakeUrn": "solc-0.8.21", + "LockstakeMkr": "solc-0.8.21", + "Jug": "solc-0.5.12", + "Vat": "solc-0.5.12", + "VoteDelegateMock": "solc-0.8.21", + "VoteDelegate2Mock": "solc-0.8.21", + "VoteDelegateFactoryMock": "solc-0.8.21", + "UsdsJoinMock": "solc-0.8.21", + "UsdsMock": "solc-0.8.21", + "MkrMock": "solc-0.8.21", + "MkrSkyMock": "solc-0.8.21", + "SkyMock": "solc-0.8.21", + "StakingRewardsMock": "solc-0.8.21", + "StakingRewards2Mock": "solc-0.8.21", + "RewardsMock": "solc-0.8.21" + }, + "solc_optimize_map": { + "LockstakeEngine": "200", + "LockstakeUrn": "200", + "LockstakeMkr": "200", + "Jug": "0", + "Vat": "0", + "UsdsJoinMock": "0", + "UsdsMock": "0", + "MkrMock": "0", + "MkrSkyMock": "0", + "SkyMock": "0", + "VoteDelegateMock": "0", + "VoteDelegate2Mock": "0", + "VoteDelegateFactoryMock": "0", + "StakingRewardsMock": "0", + "StakingRewards2Mock": "0", + "RewardsMock": "0" + }, + "link": [ + "LockstakeEngine:jug=Jug", + "LockstakeEngine:voteDelegateFactory=VoteDelegateFactoryMock", + "LockstakeEngine:vat=Vat", + "LockstakeEngine:usdsJoin=UsdsJoinMock", + "LockstakeEngine:usds=UsdsMock", + "LockstakeEngine:mkr=MkrMock", + "LockstakeEngine:lsmkr=LockstakeMkr", + "LockstakeEngine:mkrSky=MkrSkyMock", + "LockstakeEngine:sky=SkyMock", + "LockstakeEngine:urnImplementation=LockstakeUrn", + "LockstakeUrn:engine=LockstakeEngine", + "LockstakeUrn:lsmkr=LockstakeMkr", + "LockstakeUrn:vat=Vat", + "Jug:vat=Vat", + "UsdsJoinMock:vat=Vat", + "UsdsJoinMock:usds=UsdsMock", + "MkrSkyMock:mkr=MkrMock", + "MkrSkyMock:sky=SkyMock", + "VoteDelegateMock:gov=MkrMock", + "VoteDelegate2Mock:gov=MkrMock", + "VoteDelegateFactoryMock:gov=MkrMock", + "StakingRewardsMock:rewardsToken=RewardsMock", + "StakingRewardsMock:stakingToken=LockstakeMkr", + "StakingRewards2Mock:rewardsToken=RewardsMock", + "StakingRewards2Mock:stakingToken=LockstakeMkr" + ], + "verify": "LockstakeEngine:certora/LockstakeEngine.spec", + "prover_args": [ + "-rewriteMSizeAllocations true", + "-smt_easy_LIA true" + ], + "smt_timeout": "7000", + "rule_sanity": "basic", + "optimistic_loop": true, + "multi_assert_check": true, + "parametric_contracts": ["LockstakeEngine"], + "build_cache": true, + "dynamic_bound": "1", + "prototype": [ + "3d602d80600a3d3981f3363d3d373d3d3d363d73=LockstakeUrn" + ], + "msg": "LockstakeEngine" +} diff --git a/certora/LockstakeEngine.spec b/certora/LockstakeEngine.spec new file mode 100644 index 00000000..6e68a596 --- /dev/null +++ b/certora/LockstakeEngine.spec @@ -0,0 +1,2197 @@ +// LockstakeEngine.spec + +using LockstakeUrn as lockstakeUrn; +using Vat as vat; +using MkrMock as mkr; +using LockstakeMkr as lsmkr; +using VoteDelegateMock as voteDelegate; +using VoteDelegate2Mock as voteDelegate2; +using VoteDelegateFactoryMock as voteDelegateFactory; +using StakingRewardsMock as stakingRewards; +using StakingRewards2Mock as stakingRewards2; +using MkrSkyMock as mkrSky; +using SkyMock as sky; +using UsdsMock as usds; +using UsdsJoinMock as usdsJoin; +using Jug as jug; +using RewardsMock as rewardsToken; + +methods { + // storage variables + function wards(address) external returns (uint256) envfree; + function farms(address) external returns (LockstakeEngine.FarmStatus) envfree; + function ownerUrnsCount(address) external returns (uint256) envfree; + function ownerUrns(address,uint256) external returns (address) envfree; + function urnOwners(address) external returns (address) envfree; + function urnCan(address,address) external returns (uint256) envfree; + function urnVoteDelegates(address) external returns (address) envfree; + function urnFarms(address) external returns (address) envfree; + function urnAuctions(address) external returns (uint256) envfree; + function jug() external returns (address) envfree; + function fee() external returns (uint256) envfree; + // immutables + function voteDelegateFactory() external returns (address) envfree; + function vat() external returns (address) envfree; + function usdsJoin() external returns (address) envfree; + function usds() external returns (address) envfree; + function ilk() external returns (bytes32) envfree; + function mkr() external returns (address) envfree; + function lsmkr() external returns (address) envfree; + function usds() external returns (address) envfree; + function sky() external returns (address) envfree; + function mkrSkyRate() external returns (uint256) envfree; + function urnImplementation() external returns (address) envfree; + // + function lockstakeUrn.engine() external returns (address) envfree; + function vat.live() external returns (uint256) envfree; + function vat.Line() external returns (uint256) envfree; + function vat.debt() external returns (uint256) envfree; + function vat.ilks(bytes32) external returns (uint256,uint256,uint256,uint256,uint256) envfree; + function vat.dai(address) external returns (uint256) envfree; + function vat.gem(bytes32,address) external returns (uint256) envfree; + function vat.urns(bytes32,address) external returns (uint256,uint256) envfree; + function vat.can(address,address) external returns (uint256) envfree; + function vat.wards(address) external returns (uint256) envfree; + function mkr.allowance(address,address) external returns (uint256) envfree; + function mkr.balanceOf(address) external returns (uint256) envfree; + function mkr.totalSupply() external returns (uint256) envfree; + function sky.allowance(address,address) external returns (uint256) envfree; + function sky.balanceOf(address) external returns (uint256) envfree; + function sky.totalSupply() external returns (uint256) envfree; + function lsmkr.allowance(address,address) external returns (uint256) envfree; + function lsmkr.balanceOf(address) external returns (uint256) envfree; + function lsmkr.totalSupply() external returns (uint256) envfree; + function lsmkr.wards(address) external returns (uint256) envfree; + function stakingRewards.balanceOf(address) external returns (uint256) envfree; + function stakingRewards.totalSupply() external returns (uint256) envfree; + function stakingRewards.rewardsToken() external returns (address) envfree; + function stakingRewards.rewards(address) external returns (uint256) envfree; + function stakingRewards2.balanceOf(address) external returns (uint256) envfree; + function stakingRewards2.totalSupply() external returns (uint256) envfree; + function mkrSky.rate() external returns (uint256) envfree; + function usds.allowance(address,address) external returns (uint256) envfree; + function usds.balanceOf(address) external returns (uint256) envfree; + function usds.totalSupply() external returns (uint256) envfree; + function jug.vow() external returns (address) envfree; + function rewardsToken.balanceOf(address) external returns (uint256) envfree; + function rewardsToken.totalSupply() external returns (uint256) envfree; + function voteDelegate.stake(address) external returns (uint256) envfree; + function voteDelegate2.stake(address) external returns (uint256) envfree; + function voteDelegateFactory.created(address) external returns (uint256) envfree; + // + function jug.drip(bytes32 ilk) external returns (uint256) => dripSummary(ilk); + function _.mul(uint256 x,int256 y) internal => mulISummary(x,y) expect int256; + function _.mul(uint256 x,uint256 y) internal => mulSummary(x,y) expect uint256; + function _.hope(address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.init() external => DISPATCHER(true); + function _.lock(uint256) external => DISPATCHER(true); + function _.free(uint256) external => DISPATCHER(true); + function _.stake(address,uint256,uint16) external => DISPATCHER(true); + function _.withdraw(address,uint256) external => DISPATCHER(true); + function _.stake(uint256,uint16) external => DISPATCHER(true); + function _.withdraw(uint256) external => DISPATCHER(true); + function _.getReward(address,address) external => DISPATCHER(true); + function _.getReward() external => DISPATCHER(true); + function _.rewardsToken() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + +definition addrZero() returns address = 0x0000000000000000000000000000000000000000; +definition max_int256() returns mathint = 2^255 - 1; +definition min_int256() returns mathint = -2^255; +definition WAD() returns mathint = 10^18; +definition RAY() returns mathint = 10^27; +definition _divup(mathint x, mathint y) returns mathint = x != 0 ? ((x - 1) / y) + 1 : 0; +definition _min(mathint x, mathint y) returns mathint = x < y ? x : y; + +function mulISummary(uint256 x, int256 y) returns int256 { + require x <= max_int256(); + mathint z = x * y; + return require_int256(z); +} + +function mulSummary(uint256 x, uint256 y) returns uint256 { + mathint z = x * y; + return require_uint256(z); +} + +persistent ghost address createdUrn; +hook CREATE1(uint value, uint offset, uint length) address v { + createdUrn = v; +} + +persistent ghost address queriedUrn; +hook Sload address v ownerUrns[KEY address owner][KEY uint256 index] { + queriedUrn = v; +} + +persistent ghost address passedUrn; +hook Sload uint256 v urnAuctions[KEY address urn] { + passedUrn = urn; +} + +ghost mathint duty; +ghost mathint timeDiff; +function dripSummary(bytes32 ilk) returns uint256 { + env e; + require duty >= RAY(); + uint256 prev; uint256 a; + a, prev, a, a, a = vat.ilks(ilk); + uint256 rate = timeDiff == 0 ? prev : require_uint256(duty * timeDiff * prev / RAY()); + timeDiff = 0; + vat.fold(e, ilk, jug.vow(), require_int256(rate - prev)); + return rate; +} + +// Verify that each storage layout is only modified in the corresponding functions +rule storageAffected(method f) filtered { f -> f.selector != sig:multicall(bytes[]).selector } { + env e; + + address anyAddr; + address anyAddr2; + uint256 anyUint256; + + bytes32 ilk = ilk(); + + mathint wardsBefore = wards(anyAddr); + LockstakeEngine.FarmStatus farmsBefore = farms(anyAddr); + mathint ownerUrnsCountBefore = ownerUrnsCount(anyAddr); + address ownerUrnsBefore = ownerUrns(anyAddr, anyUint256); + address urnOwnersBefore = urnOwners(anyAddr); + mathint urnCanBefore = urnCan(anyAddr, anyAddr2); + address urnVoteDelegatesBefore = urnVoteDelegates(anyAddr); + address urnFarmsBefore = urnFarms(anyAddr); + mathint urnAuctionsBefore = urnAuctions(anyAddr); + address jugBefore = jug(); + + calldataarg args; + f(e, args); + + mathint wardsAfter = wards(anyAddr); + LockstakeEngine.FarmStatus farmsAfter = farms(anyAddr); + mathint ownerUrnsCountAfter = ownerUrnsCount(anyAddr); + address ownerUrnsAfter = ownerUrns(anyAddr, anyUint256); + address urnOwnersAfter = urnOwners(anyAddr); + mathint urnCanAfter = urnCan(anyAddr, anyAddr2); + address urnVoteDelegatesAfter = urnVoteDelegates(anyAddr); + address urnFarmsAfter = urnFarms(anyAddr); + mathint urnAuctionsAfter = urnAuctions(anyAddr); + address jugAfter = jug(); + + assert wardsAfter != wardsBefore => f.selector == sig:rely(address).selector || f.selector == sig:deny(address).selector, "Assert 1"; + assert farmsAfter != farmsBefore => f.selector == sig:addFarm(address).selector || f.selector == sig:delFarm(address).selector, "Assert 2"; + assert ownerUrnsCountAfter != ownerUrnsCountBefore => f.selector == sig:open(uint256).selector, "Assert 3"; + assert ownerUrnsAfter != ownerUrnsBefore => f.selector == sig:open(uint256).selector, "Assert 4"; + assert urnOwnersAfter != urnOwnersBefore => f.selector == sig:open(uint256).selector, "Assert 5"; + assert urnCanAfter != urnCanBefore => f.selector == sig:hope(address,uint256,address).selector || f.selector == sig:nope(address,uint256,address).selector, "Assert 6"; + assert urnVoteDelegatesAfter != urnVoteDelegatesBefore => f.selector == sig:selectVoteDelegate(address,uint256,address).selector || f.selector == sig:onKick(address,uint256).selector, "Assert 7"; + assert urnFarmsAfter != urnFarmsBefore => f.selector == sig:selectFarm(address,uint256,address,uint16).selector || f.selector == sig:onKick(address,uint256).selector, "Assert 8"; + assert urnAuctionsAfter != urnAuctionsBefore => f.selector == sig:onKick(address,uint256).selector || f.selector == sig:onRemove(address,uint256,uint256).selector, "Assert 9"; + assert jugAfter != jugBefore => f.selector == sig:file(bytes32,address).selector, "Assert 10"; +} + +rule vatGemKeepsUnchanged(method f) filtered { f -> f.selector != sig:multicall(bytes[]).selector } { + env e; + + address anyAddr; + + bytes32 ilk = ilk(); + + mathint vatGemIlkAnyBefore = vat.gem(ilk, anyAddr); + + calldataarg args; + f(e, args); + + mathint vatGemIlkAnyAfter = vat.gem(ilk, anyAddr); + + assert vatGemIlkAnyAfter == vatGemIlkAnyBefore, "Assert 1"; +} + +rule inkChangeMatchesMkrChange(method f) filtered { f -> f.selector != sig:multicall(bytes[]).selector } { + env e; + + createdUrn = 0; + queriedUrn = 0; + passedUrn = 0; + storage init = lastStorage; + address onTakeWho; + uint256 onTakeWad; + if (f.selector == sig:free(address,uint256,address,uint256).selector || + f.selector == sig:freeNoFee(address,uint256,address,uint256).selector) { + address owner; + uint256 index; + address to; + uint256 wad; + require to != currentContract && to != voteDelegate && to != voteDelegate2; + if (f.selector == sig:free(address,uint256,address,uint256).selector) { + free(e, owner, index, to, wad); + } else { + freeNoFee(e, owner, index, to, wad); + } + } else { + calldataarg args; + f(e, args); + } + storage final = lastStorage; + + address urn = createdUrn != 0 ? createdUrn : (queriedUrn != 0 ? queriedUrn : (passedUrn != 0 ? passedUrn : 0)); + require urn != currentContract && urn != voteDelegate && urn != voteDelegate2; + + address voteDelegateAfter = urnVoteDelegates(urn); + require voteDelegateAfter == addrZero() || voteDelegateAfter == voteDelegate || voteDelegateAfter == voteDelegate2; + + ilk() at init; + + require e.msg.sender != currentContract && e.msg.sender != voteDelegate && e.msg.sender != voteDelegate2; + require mkrSkyRate() == mkrSky.rate(); + + bytes32 ilk = ilk(); + + address voteDelegateBefore = urnVoteDelegates(urn); + require voteDelegateBefore == addrZero() || voteDelegateBefore == voteDelegate; + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyBefore = mkr.totalSupply(); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfVoteDelegateBeforeBefore = voteDelegateBefore == addrZero() ? 0 : mkr.balanceOf(voteDelegateBefore); + mathint mkrBalanceOfVoteDelegateAfterBefore = voteDelegateAfter == addrZero() ? 0 : mkr.balanceOf(voteDelegateAfter); + require mkr.balanceOf(e.msg.sender) + mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBeforeBefore + mkrBalanceOfVoteDelegateAfterBefore <= mkr.totalSupply(); + require mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBeforeBefore >= vatUrnsIlkUrnInkBefore; + + ilk() at final; + + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyAfter = mkr.totalSupply(); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfVoteDelegateBeforeAfter = voteDelegateBefore == addrZero() ? 0 : mkr.balanceOf(voteDelegateBefore); + mathint mkrBalanceOfVoteDelegateAfterAfter = voteDelegateAfter == addrZero() ? 0 : mkr.balanceOf(voteDelegateAfter); + require f.selector == sig:onRemove(address,uint256,uint256).selector => voteDelegateBefore == addrZero(); + mathint burntOnRemove = f.selector == sig:onRemove(address,uint256,uint256).selector ? mkrTotalSupplyBefore - mkrTotalSupplyAfter + vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore : 0; + mathint transferredOnTake = f.selector == sig:onTake(address,address,uint256).selector ? mkrBalanceOfEngineBefore - mkrBalanceOfEngineAfter : 0; + mathint receivedOnTake = f.selector == sig:onTake(address,address,uint256).selector ? mkrBalanceOfVoteDelegateBeforeAfter - mkrBalanceOfVoteDelegateBeforeBefore : 0; + + // It checks that the ink change matches the MKR balance change + that is all or nothing delegated + assert voteDelegateAfter == voteDelegateBefore && voteDelegateBefore == addrZero() => + vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == mkrBalanceOfEngineAfter - mkrBalanceOfEngineBefore + burntOnRemove + transferredOnTake, "Assert 1"; + assert voteDelegateAfter == voteDelegateBefore && voteDelegateBefore != addrZero() => + vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == mkrBalanceOfVoteDelegateBeforeAfter - mkrBalanceOfVoteDelegateBeforeBefore - receivedOnTake && + mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - transferredOnTake, "Assert 2"; + assert voteDelegateAfter != voteDelegateBefore && voteDelegateBefore != addrZero() && voteDelegateAfter != addrZero() => + mkrBalanceOfVoteDelegateBeforeAfter - mkrBalanceOfVoteDelegateBeforeBefore == mkrBalanceOfVoteDelegateAfterBefore - mkrBalanceOfVoteDelegateAfterAfter, "Assert3"; +} + +rule inkChangeMatchesLsmkrChange(method f) filtered { f -> f.selector != sig:multicall(bytes[]).selector } { + env e; + + createdUrn = 0; + queriedUrn = 0; + passedUrn = 0; + storage init = lastStorage; + calldataarg args; + f(e, args); + storage final = lastStorage; + + address urn = createdUrn != 0 ? createdUrn : (queriedUrn != 0 ? queriedUrn : (passedUrn != 0 ? passedUrn : 0)); + + address farmAfter = urnFarms(urn); + require farmAfter == addrZero() || farmAfter == stakingRewards || farmAfter == stakingRewards2; + + ilk() at init; + + bytes32 ilk = ilk(); + + address farmBefore = urnFarms(urn); + require farmBefore == addrZero() || farmBefore == stakingRewards; + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfFarmBeforeBefore = farmBefore == addrZero() ? 0 : lsmkr.balanceOf(farmBefore); + mathint lsmkrBalanceOfFarmAfterBefore = farmAfter == addrZero() ? 0 : lsmkr.balanceOf(farmAfter); + require lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBeforeBefore + lsmkrBalanceOfFarmAfterBefore <= lsmkrTotalSupplyBefore; + require vatUrnsIlkUrnInkBefore <= lsmkrTotalSupplyBefore; + mathint farmBeforeBalanceOfUrnBefore = 0; + if (farmBefore != addrZero()) { + farmBeforeBalanceOfUrnBefore = farmBefore.balanceOf(e, urn); + require farmBeforeBalanceOfUrnBefore <= to_mathint(farmBefore.totalSupply(e)); + } + mathint farmAfterBalanceOfUrnBefore = 0; + if (farmAfter != addrZero()) { + farmAfterBalanceOfUrnBefore = farmAfter.balanceOf(e, urn); + require farmAfterBalanceOfUrnBefore <= to_mathint(farmAfter.totalSupply(e)); + } + mathint stakingRewardsBalanceOfUrnBefore = stakingRewards.balanceOf(urn); + mathint stakingRewards2BalanceOfUrnBefore = stakingRewards2.balanceOf(urn); + + ilk() at final; + + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfFarmBeforAfter = farmBefore == addrZero() ? 0 : lsmkr.balanceOf(farmBefore); + mathint lsmkrBalanceOfFarmAfterAfter = farmAfter == addrZero() ? 0 : lsmkr.balanceOf(farmAfter); + mathint farmBeforeBalanceOfUrnAfter = 0; + if (farmBefore != addrZero()) { + farmBeforeBalanceOfUrnAfter = farmBefore.balanceOf(e, urn); + require farmBeforeBalanceOfUrnAfter <= to_mathint(farmBefore.totalSupply(e)); + } + mathint farmAfterBalanceOfUrnAfter = 0; + if (farmAfter != addrZero()) { + farmAfterBalanceOfUrnAfter = farmAfter.balanceOf(e, urn); + require farmAfterBalanceOfUrnAfter <= to_mathint(farmAfter.totalSupply(e)); + } + mathint stakingRewardsBalanceOfUrnAfter = stakingRewards.balanceOf(urn); + mathint stakingRewards2BalanceOfUrnAfter = stakingRewards2.balanceOf(urn); + + require farmBefore != addrZero() => lsmkrBalanceOfUrnBefore == 0; + require farmBefore == stakingRewards => stakingRewards2BalanceOfUrnBefore == 0; + require farmBefore == stakingRewards2 => stakingRewardsBalanceOfUrnBefore == 0; + require farmBefore == addrZero() => stakingRewardsBalanceOfUrnBefore == 0 && stakingRewards2BalanceOfUrnBefore == 0; + mathint burntOnKick = f.selector == sig:onKick(address,uint256).selector ? lsmkrTotalSupplyBefore - lsmkrTotalSupplyAfter : 0; + require vatUrnsIlkUrnInkBefore == lsmkrBalanceOfUrnBefore + stakingRewardsBalanceOfUrnBefore + stakingRewards2BalanceOfUrnBefore - burntOnKick; + require f.selector == sig:onRemove(address,uint256,uint256).selector => farmBefore == addrZero(); + + assert vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == lsmkrTotalSupplyAfter - lsmkrTotalSupplyBefore + burntOnKick, "Assert 1"; + assert farmAfter == farmBefore && farmBefore == addrZero() => + vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == lsmkrBalanceOfUrnAfter - lsmkrBalanceOfUrnBefore + burntOnKick, "Assert 2"; + assert farmAfter == farmBefore && farmBefore != addrZero() => + vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == lsmkrBalanceOfFarmBeforAfter - lsmkrBalanceOfFarmBeforeBefore, "Assert 3"; + assert farmAfter != farmBefore && farmBefore == addrZero() => + vatUrnsIlkUrnInkAfter == lsmkrBalanceOfFarmAfterAfter - lsmkrBalanceOfFarmAfterBefore && + vatUrnsIlkUrnInkBefore == lsmkrBalanceOfUrnBefore - lsmkrBalanceOfUrnAfter, "Assert 4"; + assert farmAfter != farmBefore && farmAfter == addrZero() => + vatUrnsIlkUrnInkAfter == lsmkrBalanceOfUrnAfter - lsmkrBalanceOfUrnBefore && + vatUrnsIlkUrnInkBefore == lsmkrBalanceOfFarmBeforeBefore - lsmkrBalanceOfFarmBeforAfter - burntOnKick, "Assert 5"; + assert farmAfter != farmBefore && farmBefore != addrZero() && farmAfter != addrZero() => + vatUrnsIlkUrnInkAfter == lsmkrBalanceOfFarmAfterAfter - lsmkrBalanceOfFarmAfterBefore && + vatUrnsIlkUrnInkBefore == lsmkrBalanceOfFarmBeforeBefore - lsmkrBalanceOfFarmBeforAfter, "Assert 6"; + assert farmAfter == addrZero() => + lsmkrBalanceOfUrnAfter == vatUrnsIlkUrnInkAfter && stakingRewardsBalanceOfUrnAfter == 0 && stakingRewards2BalanceOfUrnAfter == 0, "Assert 7"; + assert farmAfter == stakingRewards => + stakingRewardsBalanceOfUrnAfter == vatUrnsIlkUrnInkAfter && lsmkrBalanceOfUrnAfter == 0 && stakingRewards2BalanceOfUrnAfter == 0, "Assert 8"; + assert farmAfter == stakingRewards2 => + stakingRewards2BalanceOfUrnAfter == vatUrnsIlkUrnInkAfter && lsmkrBalanceOfUrnAfter == 0 && stakingRewardsBalanceOfUrnAfter == 0, "Assert 9"; +} + +rule inkMatchesLsmkrFarmOnKick(address urn, uint256 wad) { + env e; + + address anyUrn; + require anyUrn != stakingRewards && anyUrn != stakingRewards2; + + bytes32 ilk = ilk(); + + address farmBefore = urnFarms(anyUrn); + require farmBefore == addrZero() || farmBefore == stakingRewards; + + mathint vatUrnsIlkAnyUrnInkBefore; mathint a; + vatUrnsIlkAnyUrnInkBefore, a = vat.urns(ilk, anyUrn); + + mathint lsmkrBalanceOfAnyUrnBefore = lsmkr.balanceOf(anyUrn); + mathint farmBalanceOfAnyUrnBefore = farmBefore == addrZero() ? 0 : stakingRewards.balanceOf(anyUrn); + + require stakingRewards2.balanceOf(anyUrn) == 0; + require lsmkrBalanceOfAnyUrnBefore == 0 || farmBalanceOfAnyUrnBefore == 0; + require lsmkrBalanceOfAnyUrnBefore > 0 => farmBefore == addrZero(); + require farmBalanceOfAnyUrnBefore > 0 => farmBefore != addrZero(); + require vatUrnsIlkAnyUrnInkBefore == lsmkrBalanceOfAnyUrnBefore + farmBalanceOfAnyUrnBefore; + + onKick(e, urn, wad); + + address farmAfter = urnFarms(anyUrn); + require farmAfter == addrZero() || farmAfter == farmBefore || farmAfter != farmBefore && farmAfter == stakingRewards2; + + mathint vatUrnsIlkAnyUrnInkAfter; + vatUrnsIlkAnyUrnInkAfter, a = vat.urns(ilk, anyUrn); + + mathint lsmkrBalanceOfAnyUrnAfter = lsmkr.balanceOf(anyUrn); + mathint farmBalanceOfAnyUrnAfter = farmAfter == addrZero() ? 0 : (farmAfter == farmBefore ? stakingRewards.balanceOf(anyUrn) : stakingRewards2.balanceOf(anyUrn)); + + assert urn != anyUrn => vatUrnsIlkAnyUrnInkAfter == lsmkrBalanceOfAnyUrnAfter + farmBalanceOfAnyUrnAfter, "Assert 1"; + assert urn == anyUrn => vatUrnsIlkAnyUrnInkAfter == lsmkrBalanceOfAnyUrnAfter + farmBalanceOfAnyUrnAfter + wad, "Assert 2"; +} + +// Verify correct storage changes for non reverting rely +rule rely(address usr) { + env e; + + address other; + require other != usr; + + mathint wardsOtherBefore = wards(other); + + rely(e, usr); + + mathint wardsUsrAfter = wards(usr); + mathint wardsOtherAfter = wards(other); + + assert wardsUsrAfter == 1, "Assert 1"; + assert wardsOtherAfter == wardsOtherBefore, "Assert 2"; +} + +// Verify revert rules on rely +rule rely_revert(address usr) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + rely@withrevert(e, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting deny +rule deny(address usr) { + env e; + + address other; + require other != usr; + + mathint wardsOtherBefore = wards(other); + + deny(e, usr); + + mathint wardsUsrAfter = wards(usr); + mathint wardsOtherAfter = wards(other); + + assert wardsUsrAfter == 0, "Assert 1"; + assert wardsOtherAfter == wardsOtherBefore, "Assert 2"; +} + +// Verify revert rules on deny +rule deny_revert(address usr) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + deny@withrevert(e, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting file +rule file(bytes32 what, address data) { + env e; + + file(e, what, data); + + address jugAfter = jug(); + + assert jugAfter == data, "Assert 1"; +} + +// Verify revert rules on file +rule file_revert(bytes32 what, address data) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + file@withrevert(e, what, data); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = what != to_bytes32(0x6a75670000000000000000000000000000000000000000000000000000000000); + + assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting addFarm +rule addFarm(address farm) { + env e; + + address other; + require other != farm; + + LockstakeEngine.FarmStatus farmsOtherBefore = farms(other); + + addFarm(e, farm); + + LockstakeEngine.FarmStatus farmsFarmAfter = farms(farm); + LockstakeEngine.FarmStatus farmsOtherAfter = farms(other); + + assert farmsFarmAfter == LockstakeEngine.FarmStatus.ACTIVE, "Assert 1"; + assert farmsOtherAfter == farmsOtherBefore, "Assert 2"; +} + +// Verify revert rules on addFarm +rule addFarm_revert(address farm) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + addFarm@withrevert(e, farm); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting delFarm +rule delFarm(address farm) { + env e; + + address other; + require other != farm; + + LockstakeEngine.FarmStatus farmsOtherBefore = farms(other); + + delFarm(e, farm); + + LockstakeEngine.FarmStatus farmsFarmAfter = farms(farm); + LockstakeEngine.FarmStatus farmsOtherAfter = farms(other); + + assert farmsFarmAfter == LockstakeEngine.FarmStatus.DELETED, "Assert 1"; + assert farmsOtherAfter == farmsOtherBefore, "Assert 2"; +} + +// Verify revert rules on delFarm +rule delFarm_revert(address farm) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + delFarm@withrevert(e, farm); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting open +rule open(uint256 index) { + env e; + + address other; + require other != e.msg.sender; + address anyAddr; uint256 anyUint256; + require anyAddr != e.msg.sender || anyUint256 != index; + + mathint ownerUrnsCountSenderBefore = ownerUrnsCount(e.msg.sender); + mathint ownerUrnsCountOtherBefore = ownerUrnsCount(other); + address ownerUrnsOtherBefore = ownerUrns(anyAddr, anyUint256); + + address urn = open(e, index); + require urn.lsmkr(e) == lsmkr; + + mathint ownerUrnsCountSenderAfter = ownerUrnsCount(e.msg.sender); + mathint ownerUrnsCountOtherAfter = ownerUrnsCount(other); + address ownerUrnsSenderIndexAfter = ownerUrns(e.msg.sender, index); + address ownerUrnsOtherAfter = ownerUrns(anyAddr, anyUint256); + address urnOwnersUrnAfter = urnOwners(urn); + mathint vatCanUrnEngineAfter = vat.can(urn, currentContract); + mathint lsmkrAllowanceUrnEngine = lsmkr.allowance(urn, currentContract); + + assert ownerUrnsCountSenderAfter == ownerUrnsCountSenderBefore + 1, "Assert 1"; + assert ownerUrnsCountOtherAfter == ownerUrnsCountOtherBefore, "Assert 2"; + assert ownerUrnsSenderIndexAfter == urn, "Assert 3"; + assert ownerUrnsOtherAfter == ownerUrnsOtherBefore, "Assert 4"; + assert urnOwnersUrnAfter == e.msg.sender, "Assert 5"; + assert vatCanUrnEngineAfter == 1, "Assert 6"; + assert lsmkrAllowanceUrnEngine == max_uint256, "Assert 7"; +} + +// Verify revert rules on open +rule open_revert(uint256 index) { + env e; + + createdUrn = 0; // Now we can identify if the urn was created + + mathint ownerUrnsCountSender = ownerUrnsCount(e.msg.sender); + + open@withrevert(e, index); + bool reverted = lastReverted; // `lastReverted` will be modified by `createdUrn.engine(e)` + if (createdUrn != 0) { + require createdUrn.engine(e) == currentContract; + } + + bool revert1 = e.msg.value > 0; + bool revert2 = to_mathint(index) != ownerUrnsCountSender; + bool revert3 = ownerUrnsCountSender == max_uint256; + + assert reverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting hope +rule hope(address owner, uint256 index, address usr) { + env e; + + address other; + address other2; + address urn = ownerUrns(owner, index); + require other != urn || other2 != usr; + + mathint urnCanOtherBefore = urnCan(other, other2); + + hope(e, owner, index, usr); + + mathint urnCanUrnUsrAfter = urnCan(urn, usr); + mathint urnCanOtherAfter = urnCan(other, other2); + + assert urnCanUrnUsrAfter == 1, "Assert 1"; + assert urnCanOtherAfter == urnCanOtherBefore, "Assert 2"; +} + +// Verify revert rules on hope +rule hope_revert(address owner, uint256 index, address usr) { + env e; + + address urn = ownerUrns(owner, index); + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + + hope@withrevert(e, owner, index, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + + assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting nope +rule nope(address owner, uint256 index, address usr) { + env e; + + address other; + address other2; + address urn = ownerUrns(owner, index); + require other != urn || other2 != usr; + + mathint urnCanOtherBefore = urnCan(other, other2); + + nope(e, owner, index, usr); + + mathint urnCanUrnUsrAfter = urnCan(urn, usr); + mathint urnCanOtherAfter = urnCan(other, other2); + + assert urnCanUrnUsrAfter == 0, "Assert 1"; + assert urnCanOtherAfter == urnCanOtherBefore, "Assert 2"; +} + +// Verify revert rules on nope +rule nope_revert(address owner, uint256 index, address usr) { + env e; + + address urn = ownerUrns(owner, index); + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + + nope@withrevert(e, owner, index, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + + assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting selectVoteDelegate +rule selectVoteDelegate(address owner, uint256 index, address voteDelegate_) { + env e; + + address urn = ownerUrns(owner, index); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address prevVoteDelegate = urnVoteDelegates(urn); + require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate2; + + address other; + require other != urn; + address other2; + require other2 != voteDelegate_ && other2 != prevVoteDelegate && other2 != currentContract; + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint a; + vatUrnsIlkUrnInk, a = vat.urns(ilk, urn); + + address urnVoteDelegatesOtherBefore = urnVoteDelegates(other); + mathint mkrBalanceOfPrevVoteDelegateBefore = mkr.balanceOf(prevVoteDelegate); + mathint mkrBalanceOfNewVoteDelegateBefore = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other2); + + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkrBalanceOfPrevVoteDelegateBefore + mkrBalanceOfNewVoteDelegateBefore + mkrBalanceOfEngineBefore + mkrBalanceOfOtherBefore; + + selectVoteDelegate(e, owner, index, voteDelegate_); + + address urnVoteDelegatesUrnAfter = urnVoteDelegates(urn); + address urnVoteDelegatesOtherAfter = urnVoteDelegates(other); + mathint mkrBalanceOfPrevVoteDelegateAfter = mkr.balanceOf(prevVoteDelegate); + mathint mkrBalanceOfNewVoteDelegateAfter = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other2); + + assert urnVoteDelegatesUrnAfter == voteDelegate_, "Assert 1"; + assert urnVoteDelegatesOtherAfter == urnVoteDelegatesOtherBefore, "Assert 2"; + assert prevVoteDelegate == addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore, "Assert 3"; + assert prevVoteDelegate != addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore - vatUrnsIlkUrnInk, "Assert 4"; + assert voteDelegate_ == addrZero() => mkrBalanceOfNewVoteDelegateAfter == mkrBalanceOfNewVoteDelegateBefore, "Assert 5"; + assert voteDelegate_ != addrZero() => mkrBalanceOfNewVoteDelegateAfter == mkrBalanceOfNewVoteDelegateBefore + vatUrnsIlkUrnInk, "Assert 6"; + assert prevVoteDelegate == addrZero() && voteDelegate_ == addrZero() || prevVoteDelegate != addrZero() && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 7"; + assert prevVoteDelegate == addrZero() && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - vatUrnsIlkUrnInk, "Assert 8"; + assert prevVoteDelegate != addrZero() && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + vatUrnsIlkUrnInk, "Assert 9"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 10"; +} + +// Verify revert rules on selectVoteDelegate +rule selectVoteDelegate_revert(address owner, uint256 index, address voteDelegate_) { + env e; + + address urn = ownerUrns(owner, index); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address prevVoteDelegate = urnVoteDelegates(urn); + require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate2; + + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + mathint urnAuctions = urnAuctions(urn); + mathint voteDelegateFactoryCreatedVoteDelegate = voteDelegateFactory.created(voteDelegate_); + bytes32 ilk = ilk(); + mathint vatIlksIlkSpot; mathint a; + a, a, vatIlksIlkSpot, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + mathint calcVatIlksIlkRateAfter = dripSummary(ilk); + + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkr.balanceOf(prevVoteDelegate) + mkr.balanceOf(voteDelegate_) + mkr.balanceOf(currentContract); + // Practical Vat assumptions + require vatUrnsIlkUrnInk * vatIlksIlkSpot <= max_uint256; + require vatUrnsIlkUrnArt * calcVatIlksIlkRateAfter <= max_uint256; + // TODO: this might be nice to prove in some sort + require prevVoteDelegate == addrZero() && to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk || prevVoteDelegate != addrZero() && to_mathint(mkr.balanceOf(prevVoteDelegate)) >= vatUrnsIlkUrnInk && to_mathint(voteDelegate2.stake(currentContract)) >= vatUrnsIlkUrnInk; // TODO: this might be interesting to be proved + require voteDelegate.stake(currentContract) + vatUrnsIlkUrnInk <= max_uint256; + + selectVoteDelegate@withrevert(e, owner, index, voteDelegate_); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + bool revert4 = urnAuctions > 0; + bool revert5 = voteDelegate_ != addrZero() && voteDelegateFactoryCreatedVoteDelegate != 1; + bool revert6 = voteDelegate_ == prevVoteDelegate; + bool revert7 = vatUrnsIlkUrnArt > 0 && voteDelegate_ != addrZero() && vatUrnsIlkUrnInk * vatIlksIlkSpot < vatUrnsIlkUrnArt * calcVatIlksIlkRateAfter; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting selectFarm +rule selectFarm(address owner, uint256 index, address farm, uint16 ref) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + require farm == addrZero() || farm == stakingRewards; + address prevFarm = urnFarms(urn); + require prevFarm == addrZero() || prevFarm == stakingRewards2; + + address other; + require other != urn; + address other2; + require other2 != farm && other2 != prevFarm && other2 != urn; + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint a; + vatUrnsIlkUrnInk, a = vat.urns(ilk, urn); + + address urnFarmsOtherBefore = urnFarms(other); + mathint lsmkrBalanceOfPrevFarmBefore = lsmkr.balanceOf(prevFarm); + mathint lsmkrBalanceOfNewFarmBefore = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other2); + + // Tokens invariants + require to_mathint(lsmkr.totalSupply()) >= lsmkrBalanceOfPrevFarmBefore + lsmkrBalanceOfNewFarmBefore + lsmkrBalanceOfUrnBefore + lsmkrBalanceOfOtherBefore; + + selectFarm(e, owner, index, farm, ref); + + address urnFarmsUrnAfter = urnFarms(urn); + address urnFarmsOtherAfter = urnFarms(other); + mathint lsmkrBalanceOfPrevFarmAfter = lsmkr.balanceOf(prevFarm); + mathint lsmkrBalanceOfNewFarmAfter = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other2); + + assert urnFarmsUrnAfter == farm, "Assert 1"; + assert urnFarmsOtherAfter == urnFarmsOtherBefore, "Assert 2"; + assert prevFarm == addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore, "Assert 3"; + assert prevFarm != addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore - vatUrnsIlkUrnInk, "Assert 4"; + assert farm == addrZero() => lsmkrBalanceOfNewFarmAfter == lsmkrBalanceOfNewFarmBefore, "Assert 5"; + assert farm != addrZero() => lsmkrBalanceOfNewFarmAfter == lsmkrBalanceOfNewFarmBefore + vatUrnsIlkUrnInk, "Assert 6"; + assert prevFarm == addrZero() && farm == addrZero() || prevFarm != addrZero() && farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 7"; + assert prevFarm == addrZero() && farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - vatUrnsIlkUrnInk, "Assert 8"; + assert prevFarm != addrZero() && farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + vatUrnsIlkUrnInk, "Assert 9"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 10"; +} + +// Verify revert rules on selectFarm +rule selectFarm_revert(address owner, uint256 index, address farm, uint16 ref) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + require farm == addrZero() || farm == stakingRewards; + address prevFarm = urnFarms(urn); + require prevFarm == addrZero() || prevFarm == stakingRewards2; + + address urnOwnersUrn = urnOwners(urn); + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + mathint urnAuctions = urnAuctions(urn); + LockstakeEngine.FarmStatus farmsFarm = farms(farm); + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint a; + vatUrnsIlkUrnInk, a = vat.urns(ilk, urn); + + // TODO: this might be nice to prove in some sort + require prevFarm == addrZero() && to_mathint(lsmkr.balanceOf(urn)) >= vatUrnsIlkUrnInk || prevFarm != addrZero() && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUrnInk && to_mathint(stakingRewards2.balanceOf(urn)) >= vatUrnsIlkUrnInk; + // Token invariants + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(prevFarm) + lsmkr.balanceOf(farm) + lsmkr.balanceOf(urn); + require stakingRewards2.totalSupply() >= stakingRewards2.balanceOf(urn); + require stakingRewards.totalSupply() >= stakingRewards.balanceOf(urn); + // Assumption + require stakingRewards.totalSupply() + vatUrnsIlkUrnInk <= max_uint256; + + selectFarm@withrevert(e, owner, index, farm, ref); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + bool revert4 = urnAuctions > 0; + bool revert5 = farm != addrZero() && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; + bool revert6 = farm == prevFarm; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting lock +rule lock(address owner, uint256 index, uint256 wad, uint16 ref) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; + + address other; + require other != e.msg.sender && other != currentContract && other != voteDelegate_; + address other2; + require other2 != urn && other2 != farm; + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint mkrBalanceOfSenderBefore = mkr.balanceOf(e.msg.sender); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfVoteDelegateBefore = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfFarmBefore = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other2); + + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkrBalanceOfSenderBefore + mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBefore + mkrBalanceOfOtherBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBefore + lsmkrBalanceOfOtherBefore; + + lock(e, owner, index, wad, ref); + + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint mkrBalanceOfSenderAfter = mkr.balanceOf(e.msg.sender); + mathint mkrBalanceOfVoteDelegateAfter = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfFarmAfter = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other2); + + assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore + wad, "Assert 1"; + assert mkrBalanceOfSenderAfter == mkrBalanceOfSenderBefore - wad, "Assert 2"; + assert voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 3"; + assert voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore + wad, "Assert 4"; + assert voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + wad, "Assert 5"; + assert voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 6"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 7"; + assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore + wad, "Assert 8"; + assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 9"; + assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore + wad, "Assert 10"; + assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + wad, "Assert 11"; + assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 12"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 13"; +} + +// Verify revert rules on lock +rule lock_revert(address owner, uint256 index, uint256 wad, uint16 ref) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkDust; mathint a; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, a, vatIlksIlkDust = vat.ilks(ilk); + + // Happening in urn init + require vat.can(urn, currentContract) == 1; + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require lsmkr.wards(currentContract) == 1; + // User balance and approval + require mkr.balanceOf(e.msg.sender) >= wad && mkr.allowance(e.msg.sender, currentContract) >= wad; + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkr.balanceOf(e.msg.sender) + mkr.balanceOf(currentContract) + mkr.balanceOf(voteDelegate_); + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(urn) + lsmkr.balanceOf(farm); + // TODO: this might be nice to prove in some sort + require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); + require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); + require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); + require lsmkr.totalSupply() + wad <= to_mathint(mkr.totalSupply()); + // Practical Vat assumptions + require vat.live() == 1; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require (vatUrnsIlkUrnInk + wad) * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatUrnsIlkUrnArt == 0 || vatIlksIlkRate * vatUrnsIlkUrnArt >= vatIlksIlkDust; + // Safe to assume as Engine doesn't modify vat.gem(ilk,urn) (rule vatGemKeepsUnchanged) + require vat.gem(ilk, urn) == 0; + // Safe to assume as Engine keeps the invariant (rule inkMatchesLsmkrFarm) + require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; + require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); + + LockstakeEngine.FarmStatus farmsFarm = farms(farm); + + lock@withrevert(e, owner, index, wad, ref); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = to_mathint(wad) > max_int256(); + bool revert4 = farm != addrZero() && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; + bool revert5 = farm != addrZero() && wad == 0; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting lockSky +rule lockSky(address owner, uint256 index, uint256 skyWad, uint16 ref) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; + + address other; + require other != e.msg.sender && other != currentContract && other != voteDelegate_; + address other2; + require other2 != urn && other2 != farm; + + mathint mkrSkyRate = mkrSkyRate(); + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint skyTotalSupplyBefore = sky.totalSupply(); + mathint skyBalanceOfSenderBefore = sky.balanceOf(e.msg.sender); + mathint mkrTotalSupplyBefore = mkr.totalSupply(); + mathint mkrBalanceOfSenderBefore = mkr.balanceOf(e.msg.sender); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfVoteDelegateBefore = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfFarmBefore = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other2); + + // Happening in constructor + require mkrSkyRate == to_mathint(mkrSky.rate()); + // Tokens invariants + require skyTotalSupplyBefore >= skyBalanceOfSenderBefore + sky.balanceOf(currentContract) + sky.balanceOf(mkrSky); + require mkrTotalSupplyBefore >= mkrBalanceOfSenderBefore + mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBefore + mkrBalanceOfOtherBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBefore + lsmkrBalanceOfOtherBefore; + + lockSky(e, owner, index, skyWad, ref); + + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint skyTotalSupplyAfter = sky.totalSupply(); + mathint skyBalanceOfSenderAfter = sky.balanceOf(e.msg.sender); + mathint mkrTotalSupplyAfter = mkr.totalSupply(); + mathint mkrBalanceOfSenderAfter = mkr.balanceOf(e.msg.sender); + mathint mkrBalanceOfVoteDelegateAfter = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfFarmAfter = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other2); + + assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore + skyWad/mkrSkyRate, "Assert 1"; + assert skyTotalSupplyAfter == skyTotalSupplyBefore - skyWad, "Assert 2"; + assert skyBalanceOfSenderAfter == skyBalanceOfSenderBefore - skyWad, "Assert 3"; + assert mkrTotalSupplyAfter == mkrTotalSupplyBefore + skyWad/mkrSkyRate, "Assert 4"; + assert voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; + assert voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore + skyWad/mkrSkyRate, "Assert 6"; + assert voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + skyWad/mkrSkyRate, "Assert 7"; + assert voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 9"; + assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore + skyWad/mkrSkyRate, "Assert 10"; + assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; + assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore + skyWad/mkrSkyRate, "Assert 12"; + assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + skyWad/mkrSkyRate, "Assert 13"; + assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 15"; +} + +// Verify revert rules on lockSky +rule lockSky_revert(address owner, uint256 index, uint256 skyWad, uint16 ref) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; + + mathint mkrSkyRate = mkrSkyRate(); + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkDust; mathint a; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, a, vatIlksIlkDust = vat.ilks(ilk); + + // Happening in constructor + require mkrSkyRate == to_mathint(mkrSky.rate()); + // Avoid division by zero + require mkrSkyRate > 0; + // Happening in urn init + require vat.can(urn, currentContract) == 1; + require sky.allowance(currentContract, mkrSky) == max_uint256; + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require lsmkr.wards(currentContract) == 1; + // User balance and approval + require sky.balanceOf(e.msg.sender) >= skyWad && sky.allowance(e.msg.sender, currentContract) >= skyWad; + // Tokens invariants + require to_mathint(sky.totalSupply()) >= sky.balanceOf(e.msg.sender) + sky.balanceOf(currentContract) + sky.balanceOf(mkrSky); + require to_mathint(mkr.totalSupply()) >= mkr.balanceOf(e.msg.sender) + mkr.balanceOf(currentContract) + mkr.balanceOf(voteDelegate_); + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(urn) + lsmkr.balanceOf(farm); + // Assumption + require to_mathint(mkr.totalSupply()) <= max_uint256 - skyWad/mkrSkyRate; + // TODO: this might be nice to prove in some sort + require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); + require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); + require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); + require lsmkr.totalSupply() + skyWad/mkrSkyRate <= to_mathint(mkr.totalSupply()); + // Practical Vat assumptions + require vat.live() == 1; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require (vatUrnsIlkUrnInk + skyWad/mkrSkyRate) * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatUrnsIlkUrnArt == 0 || vatIlksIlkRate * vatUrnsIlkUrnArt >= vatIlksIlkDust; + // Safe to assume as Engine doesn't modify vat.gem(ilk,urn) (rule vatGemKeepsUnchanged) + require vat.gem(ilk, urn) == 0; + // Safe to assume as Engine keeps the invariant (rule vatUrnsIlkUrnInkMatchesLsmkrFarm) + require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; + require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); + + LockstakeEngine.FarmStatus farmsFarm = farms(farm); + + lockSky@withrevert(e, owner, index, skyWad, ref); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = skyWad/mkrSkyRate > max_int256(); + bool revert4 = farm != addrZero() && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; + bool revert5 = farm != addrZero() && skyWad/mkrSkyRate == 0; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting free +rule free(address owner, uint256 index, address to, uint256 wad) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + address other; + require other != to && other != currentContract && other != voteDelegate_; + address other2; + require other2 != urn && other2 != farm; + + mathint fee = fee(); + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyBefore = mkr.totalSupply(); + mathint mkrBalanceOfToBefore = mkr.balanceOf(to); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfVoteDelegateBefore = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfFarmBefore = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other2); + + // Happening in constructor + require fee < WAD(); + // Tokens invariants + require mkrTotalSupplyBefore >= mkrBalanceOfToBefore + mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBefore + mkrBalanceOfOtherBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBefore + lsmkrBalanceOfOtherBefore; + + free(e, owner, index, to, wad); + + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyAfter = mkr.totalSupply(); + mathint mkrBalanceOfToAfter = mkr.balanceOf(to); + mathint mkrBalanceOfVoteDelegateAfter = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfFarmAfter = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other2); + + assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore - wad, "Assert 1"; + assert mkrTotalSupplyAfter == mkrTotalSupplyBefore - wad * fee / WAD(), "Assert 2"; + assert to != currentContract && to != voteDelegate_ || + to == currentContract && voteDelegate_ != addrZero() || + to == voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore + (wad - wad * fee / WAD()), "Assert 3"; + assert to == currentContract && voteDelegate_ == addrZero() || + to == voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore - wad * fee / WAD(), "Assert 4"; + assert to != voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; + assert to != voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - wad, "Assert 6"; + assert to != currentContract && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - wad, "Assert 7"; + assert to != currentContract && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 9"; + assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - wad, "Assert 10"; + assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; + assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - wad, "Assert 12"; + assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 13"; + assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 15"; +} + +// Verify revert rules on free +rule free_revert(address owner, uint256 index, address to, uint256 wad) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; + + mathint fee = fee(); + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkDust; mathint a; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, a, vatIlksIlkDust = vat.ilks(ilk); + + // Hapenning in constructor + require fee < WAD(); + // Happening in urn init + require vat.can(urn, currentContract) == 1; + require lsmkr.allowance(urn, currentContract) == max_uint256; + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require lsmkr.wards(currentContract) == 1; + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkr.balanceOf(e.msg.sender) + mkr.balanceOf(currentContract) + mkr.balanceOf(voteDelegate_); + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(urn) + lsmkr.balanceOf(farm); + // TODO: this might be nice to prove in some sort + require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); + require voteDelegate_ != addrZero() => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ == addrZero() => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; + require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); + require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); + // Practical Vat assumptions + require vat.live() == 1; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require (vatUrnsIlkUrnInk - wad) * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatUrnsIlkUrnArt == 0 || vatIlksIlkRate * vatUrnsIlkUrnArt >= vatIlksIlkDust; + // Safe to assume as Engine doesn't modify vat.gem(ilk,urn) (rule vatGemKeepsUnchanged) + require vat.gem(ilk, urn) == 0; + // Safe to assume as Engine keeps the invariant (rule inkMatchesLsmkrFarm) + require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; + require lsmkr.balanceOf(urn) > 0 => farm == addrZero(); + require stakingRewards.balanceOf(urn) > 0 => farm != addrZero(); + require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); + + free@withrevert(e, owner, index, to, wad); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + bool revert4 = to_mathint(wad) > max_int256(); + bool revert5 = vatUrnsIlkUrnInk < to_mathint(wad) || wad > 0 && (vatUrnsIlkUrnInk - wad) * vatIlksIlkSpot < vatUrnsIlkUrnArt * vatIlksIlkRate; + bool revert6 = farm != 0 && wad == 0; + bool revert7 = wad * fee > max_uint256; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting freeSky +rule freeSky(address owner, uint256 index, address to, uint256 skyWad) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + address other; + require other != currentContract && other != voteDelegate_; + address other2; + require other2 != urn && other2 != farm; + address other3; + require other3 != to; + + mathint mkrSkyRate = mkrSkyRate(); + mathint fee = fee(); + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint skyTotalSupplyBefore = sky.totalSupply(); + mathint skyBalanceOfToBefore = sky.balanceOf(to); + mathint skyBalanceOfOtherBefore = sky.balanceOf(other3); + mathint mkrTotalSupplyBefore = mkr.totalSupply(); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfVoteDelegateBefore = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfFarmBefore = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other2); + + // Happening in constructor + require mkrSkyRate == to_mathint(mkrSky.rate()); + require fee < WAD(); + // Tokens invariants + require skyTotalSupplyBefore >= skyBalanceOfToBefore + skyBalanceOfOtherBefore; + require mkrTotalSupplyBefore >= mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBefore + mkrBalanceOfOtherBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBefore + lsmkrBalanceOfOtherBefore; + + freeSky(e, owner, index, to, skyWad); + + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint skyTotalSupplyAfter = sky.totalSupply(); + mathint skyBalanceOfToAfter = sky.balanceOf(to); + mathint skyBalanceOfOtherAfter = sky.balanceOf(other3); + mathint mkrTotalSupplyAfter = mkr.totalSupply(); + mathint mkrBalanceOfVoteDelegateAfter = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfFarmAfter = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other2); + + assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore - skyWad/mkrSkyRate, "Assert 1"; + assert skyTotalSupplyAfter == skyTotalSupplyBefore + (skyWad/mkrSkyRate - skyWad/mkrSkyRate * fee / WAD()) * mkrSkyRate, "Assert 2"; + assert skyBalanceOfToAfter == skyBalanceOfToBefore + (skyWad/mkrSkyRate - skyWad/mkrSkyRate * fee / WAD()) * mkrSkyRate, "Assert 3"; + assert skyBalanceOfOtherAfter == skyBalanceOfOtherBefore, "Assert 4"; + assert mkrTotalSupplyAfter == mkrTotalSupplyBefore - skyWad/mkrSkyRate, "Assert 5"; + assert to != voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 6"; + assert to != voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - skyWad/mkrSkyRate, "Assert 7"; + assert to != currentContract && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - skyWad/mkrSkyRate, "Assert 8"; + assert to != currentContract && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 9"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 10"; + assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - skyWad/mkrSkyRate, "Assert 11"; + assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 12"; + assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - skyWad/mkrSkyRate, "Assert 13"; + assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - skyWad/mkrSkyRate, "Assert 14"; + assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 15"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 16"; +} + +// Verify revert rules on freeSky +rule freeSky_revert(address owner, uint256 index, address to, uint256 skyWad) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; + + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + + mathint mkrSkyRate = mkrSkyRate(); + mathint fee = fee(); + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkDust; mathint a; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, a, vatIlksIlkDust = vat.ilks(ilk); + + // Happening in constructor + require mkrSkyRate == to_mathint(mkrSky.rate()); + require fee < WAD(); + require mkr.allowance(currentContract, mkrSky) == max_uint256; + // Avoid division by zero + require mkrSkyRate > 0; + // Happening in urn init + require vat.can(urn, currentContract) == 1; + require lsmkr.allowance(urn, currentContract) == max_uint256; + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require lsmkr.wards(currentContract) == 1; + // Tokens invariants + require sky.totalSupply() >= sky.balanceOf(to); + require to_mathint(mkr.totalSupply()) >= mkr.balanceOf(e.msg.sender) + mkr.balanceOf(currentContract) + mkr.balanceOf(voteDelegate_); + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(urn) + lsmkr.balanceOf(farm); + // Practical assumption + require sky.totalSupply() + skyWad <= max_uint256; + // TODO: this might be nice to prove in some sort + require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); + require voteDelegate_ != addrZero() => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ == addrZero() => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; + require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); + require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); + // Practical Vat assumptions + require vat.live() == 1; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require (vatUrnsIlkUrnInk - skyWad/mkrSkyRate) * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatUrnsIlkUrnArt == 0 || vatIlksIlkRate * vatUrnsIlkUrnArt >= vatIlksIlkDust; + // Safe to assume as Engine doesn't modify vat.gem(ilk,urn) (rule vatGemKeepsUnchanged) + require vat.gem(ilk, urn) == 0; + // Safe to assume as Engine keeps the invariant (rule inkMatchesLsmkrFarm) + require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; + require lsmkr.balanceOf(urn) > 0 => farm == addrZero(); + require stakingRewards.balanceOf(urn) > 0 => farm != addrZero(); + require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); + + freeSky@withrevert(e, owner, index, to, skyWad); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + bool revert4 = to_mathint(skyWad/mkrSkyRate) > max_int256(); + bool revert5 = vatUrnsIlkUrnInk < to_mathint(skyWad/mkrSkyRate) || skyWad/mkrSkyRate > 0 && (vatUrnsIlkUrnInk - skyWad/mkrSkyRate) * vatIlksIlkSpot < vatUrnsIlkUrnArt * vatIlksIlkRate; + bool revert6 = farm != 0 && skyWad/mkrSkyRate == 0; + bool revert7 = skyWad/mkrSkyRate * fee > max_uint256; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting freeNoFee +rule freeNoFee(address owner, uint256 index, address to, uint256 wad) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + address other; + require other != to && other != currentContract && other != voteDelegate_; + address other2; + require other2 != urn && other2 != farm; + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyBefore = mkr.totalSupply(); + mathint mkrBalanceOfToBefore = mkr.balanceOf(to); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfVoteDelegateBefore = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfFarmBefore = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other2); + + // Tokens invariants + require mkrTotalSupplyBefore >= mkrBalanceOfToBefore + mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBefore + mkrBalanceOfOtherBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBefore + lsmkrBalanceOfOtherBefore; + + freeNoFee(e, owner, index, to, wad); + + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyAfter = mkr.totalSupply(); + mathint mkrBalanceOfToAfter = mkr.balanceOf(to); + mathint mkrBalanceOfVoteDelegateAfter = mkr.balanceOf(voteDelegate_); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfFarmAfter = lsmkr.balanceOf(farm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other2); + + assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore - wad, "Assert 1"; + assert mkrTotalSupplyAfter == mkrTotalSupplyBefore, "Assert 2"; + assert to != currentContract && to != voteDelegate_ || + to == currentContract && voteDelegate_ != addrZero() || + to == voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore + wad, "Assert 3"; + assert to == currentContract && voteDelegate_ == addrZero() || + to == voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore, "Assert 4"; + assert to != voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; + assert to != voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - wad, "Assert 6"; + assert to != currentContract && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - wad, "Assert 7"; + assert to != currentContract && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 9"; + assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - wad, "Assert 10"; + assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; + assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - wad, "Assert 12"; + assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 13"; + assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 15"; +} + +// Verify revert rules on freeNoFee +rule freeNoFee_revert(address owner, uint256 index, address to, uint256 wad) { + env e; + + address urn = ownerUrns(owner, index); + require urn == lockstakeUrn; + + mathint wardsSender = wards(e.msg.sender); + + address voteDelegate_ = urnVoteDelegates(urn); + require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + address farm = urnFarms(urn); + require farm == addrZero() || farm == stakingRewards; + + require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; + + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkDust; mathint a; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, a, vatIlksIlkDust = vat.ilks(ilk); + + // Happening in urn init + require vat.can(urn, currentContract) == 1; + require lsmkr.allowance(urn, currentContract) == max_uint256; + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require lsmkr.wards(currentContract) == 1; + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkr.balanceOf(e.msg.sender) + mkr.balanceOf(currentContract) + mkr.balanceOf(voteDelegate_); + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(urn) + lsmkr.balanceOf(farm); + // TODO: this might be nice to prove in some sort + require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); + require voteDelegate_ != addrZero() => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ == addrZero() => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; + require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); + require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); + // Practical Vat assumptions + require vat.live() == 1; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require (vatUrnsIlkUrnInk - wad) * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatUrnsIlkUrnArt == 0 || vatIlksIlkRate * vatUrnsIlkUrnArt >= vatIlksIlkDust; + // Safe to assume as Engine doesn't modify vat.gem(ilk,urn) (rule vatGemKeepsUnchanged) + require vat.gem(ilk, urn) == 0; + // Safe to assume as Engine keeps the invariant (rule inkMatchesLsmkrFarm) + require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; + require lsmkr.balanceOf(urn) > 0 => farm == addrZero(); + require stakingRewards.balanceOf(urn) > 0 => farm != addrZero(); + require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); + + freeNoFee@withrevert(e, owner, index, to, wad); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = urn == addrZero(); + bool revert4 = owner != e.msg.sender && urnCanUrnSender != 1; + bool revert5 = to_mathint(wad) > max_int256(); + bool revert6 = vatUrnsIlkUrnInk < to_mathint(wad) || wad > 0 && (vatUrnsIlkUrnInk - wad) * vatIlksIlkSpot < vatUrnsIlkUrnArt * vatIlksIlkRate; + bool revert7 = farm != 0 && wad == 0; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting draw +rule draw(address owner, uint256 index, address to, uint256 wad) { + env e; + + address other; + require other != to; + + address urn = ownerUrns(owner, index); + bytes32 ilk = ilk(); + mathint vatIlksIlkArtBefore; mathint a; + vatIlksIlkArtBefore, a, a, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnArtBefore; + a, vatUrnsIlkUrnArtBefore = vat.urns(ilk, urn); + mathint usdsTotalSupplyBefore = usds.totalSupply(); + mathint usdsBalanceOfToBefore = usds.balanceOf(to); + mathint usdsBalanceOfOtherBefore = usds.balanceOf(other); + + // Tokens invariants + require usdsTotalSupplyBefore >= usdsBalanceOfToBefore + usdsBalanceOfOtherBefore; + + draw(e, owner, index, to, wad); + + mathint vatIlksIlkArtAfter; mathint vatIlksIlkRateAfter; + vatIlksIlkArtAfter, vatIlksIlkRateAfter, a, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnArtAfter; + a, vatUrnsIlkUrnArtAfter = vat.urns(ilk, urn); + mathint usdsTotalSupplyAfter = usds.totalSupply(); + mathint usdsBalanceOfToAfter = usds.balanceOf(to); + mathint usdsBalanceOfOtherAfter = usds.balanceOf(other); + + assert vatIlksIlkArtAfter == vatIlksIlkArtBefore + _divup(wad * RAY(), vatIlksIlkRateAfter), "Assert 1"; + assert vatUrnsIlkUrnArtAfter == vatUrnsIlkUrnArtBefore + _divup(wad * RAY(), vatIlksIlkRateAfter), "Assert 2"; + assert usdsTotalSupplyAfter == usdsTotalSupplyBefore + wad, "Assert 3"; + assert usdsBalanceOfToAfter == usdsBalanceOfToBefore + wad, "Assert 4"; + assert usdsBalanceOfOtherAfter == usdsBalanceOfOtherBefore, "Assert 5"; +} + +// Verify revert rules on draw +rule draw_revert(address owner, uint256 index, address to, uint256 wad) { + env e; + + address urn = ownerUrns(owner, index); + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + + bytes32 ilk = ilk(); + mathint vatDebt = vat.debt(); + mathint vatLine = vat.Line(); + mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkLine; mathint vatIlksIlkDust; mathint a; + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, vatIlksIlkLine, vatIlksIlkDust = vat.ilks(ilk); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + mathint usdsTotalSupply = usds.totalSupply(); + mathint usdsBalanceOfTo = usds.balanceOf(to); + + storage init = lastStorage; + mathint calcVatIlksIlkRateAfter = dripSummary(ilk); + // Avoid division by zero + require calcVatIlksIlkRateAfter > 0; + + mathint dart = _divup(wad * RAY(), calcVatIlksIlkRateAfter); + + // Happening in constructor + require vat.can(currentContract, usdsJoin) == 1; + // Happening in urn init + require vat.can(urn, currentContract) == 1; + // Tokens invariants + require usdsTotalSupply >= usdsBalanceOfTo; + // Practical token assumtiopns + require usdsTotalSupply + wad <= max_uint256; + // Practical Vat assumptions + require vat.live() == 1; + require vat.wards(jug) == 1; + require calcVatIlksIlkRateAfter >= RAY() && calcVatIlksIlkRateAfter <= max_int256(); + require vatUrnsIlkUrnInk * vatIlksIlkSpot <= max_uint256; + require calcVatIlksIlkRateAfter * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatIlksIlkArt + dart <= max_uint256; + require calcVatIlksIlkRateAfter * dart <= max_int256(); + require vatDebt + vatIlksIlkArt * (calcVatIlksIlkRateAfter - vatIlksIlkRate) + (calcVatIlksIlkRateAfter * dart) <= max_int256(); + require vat.dai(currentContract) + (dart * calcVatIlksIlkRateAfter) <= max_uint256; + require vat.dai(usdsJoin) + (dart * calcVatIlksIlkRateAfter) <= max_uint256; + // Other assumptions + require wad * RAY() <= max_uint256; + + draw@withrevert(e, owner, index, to, wad) at init; + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + bool revert4 = to_mathint(dart) > max_int256(); + bool revert5 = dart > 0 && ((vatIlksIlkArt + dart) * calcVatIlksIlkRateAfter > vatIlksIlkLine || vatDebt + vatIlksIlkArt * (calcVatIlksIlkRateAfter - vatIlksIlkRate) + (calcVatIlksIlkRateAfter * dart) > vatLine); + bool revert6 = dart > 0 && vatUrnsIlkUrnInk * vatIlksIlkSpot < (vatUrnsIlkUrnArt + dart) * calcVatIlksIlkRateAfter; + bool revert7 = vatUrnsIlkUrnArt + dart > 0 && calcVatIlksIlkRateAfter * (vatUrnsIlkUrnArt + dart) < vatIlksIlkDust; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5 || revert6 || + revert7, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting wipe +rule wipe(address owner, uint256 index, uint256 wad) { + env e; + + address other; + require other != e.msg.sender; + + address urn = ownerUrns(owner, index); + bytes32 ilk = ilk(); + mathint vatIlksIlkArtBefore; mathint vatIlksIlkRate; mathint a; + vatIlksIlkArtBefore, vatIlksIlkRate, a, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnArtBefore; + a, vatUrnsIlkUrnArtBefore = vat.urns(ilk, urn); + mathint usdsTotalSupplyBefore = usds.totalSupply(); + mathint usdsBalanceOfSenderBefore = usds.balanceOf(e.msg.sender); + mathint usdsBalanceOfOtherBefore = usds.balanceOf(other); + + // Tokens invariants + require usdsTotalSupplyBefore >= usdsBalanceOfSenderBefore + usdsBalanceOfOtherBefore; + + wipe(e, owner, index, wad); + + mathint vatIlksIlkArtAfter; + vatIlksIlkArtAfter, a, a, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnArtAfter; + a, vatUrnsIlkUrnArtAfter = vat.urns(ilk, urn); + mathint usdsTotalSupplyAfter = usds.totalSupply(); + mathint usdsBalanceOfSenderAfter = usds.balanceOf(e.msg.sender); + mathint usdsBalanceOfOtherAfter = usds.balanceOf(other); + + assert vatIlksIlkArtAfter == vatIlksIlkArtBefore - wad * RAY() / vatIlksIlkRate, "Assert 1"; + assert vatUrnsIlkUrnArtAfter == vatUrnsIlkUrnArtBefore - wad * RAY() / vatIlksIlkRate, "Assert 2"; + assert usdsTotalSupplyAfter == usdsTotalSupplyBefore - wad, "Assert 3"; + assert usdsBalanceOfSenderAfter == usdsBalanceOfSenderBefore - wad, "Assert 4"; + assert usdsBalanceOfOtherAfter == usdsBalanceOfOtherBefore, "Assert 5"; +} + +// Verify revert rules on wipe +rule wipe_revert(address owner, uint256 index, uint256 wad) { + env e; + + address urn = ownerUrns(owner, index); + bytes32 ilk = ilk(); + mathint vatDebt = vat.debt(); + mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkLine; mathint vatIlksIlkDust; mathint a; + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, vatIlksIlkLine, vatIlksIlkDust = vat.ilks(ilk); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + mathint usdsTotalSupply = usds.totalSupply(); + mathint usdsBalanceOfSender = usds.balanceOf(e.msg.sender); + + // Avoid division by zero + require vatIlksIlkRate > 0; + + mathint dart = wad * RAY() / vatIlksIlkRate; + + // Happening in constructor + require usds.allowance(currentContract, usdsJoin) == max_uint256; + // Happening in urn init + require vat.can(urn, currentContract) == 1; + // Tokens invariants + require usdsTotalSupply >= usdsBalanceOfSender + usds.balanceOf(currentContract) + usds.balanceOf(usdsJoin); + // Practical token assumtiopns + require usdsBalanceOfSender >= to_mathint(wad); + require usds.allowance(e.msg.sender, currentContract) >= wad; + // Practical Vat assumptions + require vat.live() == 1; + require vat.wards(jug) == 1; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require vatUrnsIlkUrnInk * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatIlksIlkRate * -dart >= min_int256(); + require vatDebt >= vatIlksIlkRate * dart; + require vat.dai(currentContract) + wad * RAY() <= max_uint256; + require to_mathint(vat.dai(usdsJoin)) >= wad * RAY(); + // Other assumptions + require wad * RAY() <= max_uint256; + + wipe@withrevert(e, owner, index, wad); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = to_mathint(dart) > max_int256(); + bool revert4 = vatUrnsIlkUrnArt < dart; + bool revert5 = vatUrnsIlkUrnArt - dart > 0 && vatIlksIlkRate * (vatUrnsIlkUrnArt - dart) < vatIlksIlkDust; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4 || revert5, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting wipeAll +rule wipeAll(address owner, uint256 index) { + env e; + + address other; + require other != e.msg.sender; + + address urn = ownerUrns(owner, index); + bytes32 ilk = ilk(); + mathint vatIlksIlkArtBefore; mathint vatIlksIlkRate; mathint a; + vatIlksIlkArtBefore, vatIlksIlkRate, a, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnArtBefore; + a, vatUrnsIlkUrnArtBefore = vat.urns(ilk, urn); + mathint wad = _divup(vatUrnsIlkUrnArtBefore * vatIlksIlkRate, RAY()); + mathint usdsTotalSupplyBefore = usds.totalSupply(); + mathint usdsBalanceOfSenderBefore = usds.balanceOf(e.msg.sender); + mathint usdsBalanceOfOtherBefore = usds.balanceOf(other); + + // Tokens invariants + require usdsTotalSupplyBefore >= usdsBalanceOfSenderBefore + usdsBalanceOfOtherBefore; + + wipeAll(e, owner, index); + + mathint vatIlksIlkArtAfter; + vatIlksIlkArtAfter, a, a, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnArtAfter; + a, vatUrnsIlkUrnArtAfter = vat.urns(ilk, urn); + mathint usdsTotalSupplyAfter = usds.totalSupply(); + mathint usdsBalanceOfSenderAfter = usds.balanceOf(e.msg.sender); + mathint usdsBalanceOfOtherAfter = usds.balanceOf(other); + + assert vatIlksIlkArtAfter == vatIlksIlkArtBefore - vatUrnsIlkUrnArtBefore, "Assert 1"; + assert vatUrnsIlkUrnArtAfter == 0, "Assert 2"; + assert usdsTotalSupplyAfter == usdsTotalSupplyBefore - wad, "Assert 3"; + assert usdsBalanceOfSenderAfter == usdsBalanceOfSenderBefore - wad, "Assert 4"; + assert usdsBalanceOfOtherAfter == usdsBalanceOfOtherBefore, "Assert 5"; +} + +// Verify revert rules on wipeAll +rule wipeAll_revert(address owner, uint256 index) { + env e; + + address urn = ownerUrns(owner, index); + bytes32 ilk = ilk(); + mathint vatDebt = vat.debt(); + mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint vatIlksIlkSpot; mathint vatIlksIlkLine; mathint vatIlksIlkDust; mathint a; + vatIlksIlkArt, vatIlksIlkRate, vatIlksIlkSpot, vatIlksIlkLine, vatIlksIlkDust = vat.ilks(ilk); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + mathint usdsTotalSupply = usds.totalSupply(); + mathint usdsBalanceOfSender = usds.balanceOf(e.msg.sender); + + mathint wad = _divup(vatUrnsIlkUrnArt * vatIlksIlkRate, RAY()); + + // Happening in constructor + require usds.allowance(currentContract, usdsJoin) == max_uint256; + // Happening in urn init + require vat.can(urn, currentContract) == 1; + // Tokens invariants + require usdsTotalSupply >= usdsBalanceOfSender + usds.balanceOf(currentContract) + usds.balanceOf(usdsJoin); + // Practical token assumtiopns + require usdsBalanceOfSender >= to_mathint(wad); + require to_mathint(usds.allowance(e.msg.sender, currentContract)) >= wad; + // Practical Vat assumptions + require vat.live() == 1; + require vat.wards(jug) == 1; + require vatIlksIlkRate >= RAY() && vatIlksIlkRate <= max_int256(); + require vatUrnsIlkUrnInk * vatIlksIlkSpot <= max_uint256; + require vatIlksIlkRate * vatIlksIlkArt <= max_uint256; + require vatIlksIlkArt >= vatUrnsIlkUrnArt; + require vatIlksIlkRate * -vatUrnsIlkUrnArt >= min_int256(); + require vatDebt >= vatIlksIlkRate * vatUrnsIlkUrnArt; + require vat.dai(currentContract) + wad * RAY() <= max_uint256; + require to_mathint(vat.dai(usdsJoin)) >= wad * RAY(); + // Other assumptions + require wad * RAY() <= max_uint256; + + wipeAll@withrevert(e, owner, index); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = to_mathint(vatUrnsIlkUrnArt) > max_int256(); + + assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting getReward +rule getReward(address owner, uint256 index, address farm, address to) { + env e; + + address urn = ownerUrns(owner, index); + address other; + require other != to && other != urn && other != farm; + + require urn == lockstakeUrn; + require farm == stakingRewards; + require stakingRewards.rewardsToken() == rewardsToken; + + mathint farmRewardsUrnBefore = stakingRewards.rewards(urn); + mathint rewardsTokenBalanceOfToBefore = rewardsToken.balanceOf(to); + mathint rewardsTokenBalanceOfUrnBefore = rewardsToken.balanceOf(urn); + mathint rewardsTokenBalanceOfFarmBefore = rewardsToken.balanceOf(farm); + mathint rewardsTokenBalanceOfOtherBefore = rewardsToken.balanceOf(other); + + // Tokens invariants + require to_mathint(rewardsToken.totalSupply()) >= rewardsTokenBalanceOfToBefore + rewardsTokenBalanceOfUrnBefore + rewardsTokenBalanceOfFarmBefore + rewardsTokenBalanceOfOtherBefore; + + getReward(e, owner, index, farm, to); + + mathint farmRewardsUrnAfter = stakingRewards.rewards(urn); + mathint rewardsTokenBalanceOfToAfter = rewardsToken.balanceOf(to); + mathint rewardsTokenBalanceOfUrnAfter = rewardsToken.balanceOf(urn); + mathint rewardsTokenBalanceOfFarmAfter = rewardsToken.balanceOf(farm); + mathint rewardsTokenBalanceOfOtherAfter = rewardsToken.balanceOf(other); + + assert farmRewardsUrnAfter == 0, "Assert 1"; + assert to != urn && to != farm => rewardsTokenBalanceOfToAfter == rewardsTokenBalanceOfToBefore + rewardsTokenBalanceOfUrnBefore + farmRewardsUrnBefore, "Assert 2"; + assert to == urn => rewardsTokenBalanceOfToAfter == rewardsTokenBalanceOfToBefore + farmRewardsUrnBefore, "Assert 3"; + assert to == farm => rewardsTokenBalanceOfToAfter == rewardsTokenBalanceOfToBefore + rewardsTokenBalanceOfUrnBefore, "Assert 4"; + assert to != urn => rewardsTokenBalanceOfUrnAfter == 0, "Assert 5"; + assert to != farm => rewardsTokenBalanceOfFarmAfter == rewardsTokenBalanceOfFarmBefore - farmRewardsUrnBefore, "Assert 6"; + assert rewardsTokenBalanceOfOtherAfter == rewardsTokenBalanceOfOtherBefore, "Assert 7"; +} + +// Verify revert rules on getReward +rule getReward_revert(address owner, uint256 index, address farm, address to) { + env e; + + address urn = ownerUrns(owner, index); + require farm == stakingRewards; + require stakingRewards.rewardsToken() == rewardsToken; + + mathint urnCanUrnSender = urnCan(urn, e.msg.sender); + LockstakeEngine.FarmStatus farmsFarm = farms(farm); + + // Tokens invariants + require to_mathint(rewardsToken.totalSupply()) >= rewardsToken.balanceOf(to) + rewardsToken.balanceOf(urn) + rewardsToken.balanceOf(farm); + + // Assumption from the farm + require rewardsToken.balanceOf(farm) >= stakingRewards.rewards(urn); + + getReward@withrevert(e, owner, index, farm, to); + + bool revert1 = e.msg.value > 0; + bool revert2 = urn == addrZero(); + bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; + bool revert4 = farmsFarm == LockstakeEngine.FarmStatus.UNSUPPORTED; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting onKick +rule onKick(address urn, uint256 wad) { + env e; + + require urn == lockstakeUrn; + address prevVoteDelegate = urnVoteDelegates(urn); + require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate; + address prevFarm = urnFarms(urn); + require prevFarm == addrZero() || prevFarm == stakingRewards; + + address other; + require other != urn; + address other2; + require other2 != prevVoteDelegate && other2 != currentContract; + address other3; + require other3 != prevFarm && other3 != urn; + + bytes32 ilk = ilk(); + mathint vatUrnsIlkUrnInk; mathint a; + vatUrnsIlkUrnInk, a = vat.urns(ilk, urn); + + address urnVoteDelegatesOtherBefore = urnVoteDelegates(other); + address urnFarmsOtherBefore = urnFarms(other); + mathint urnAuctionsUrnBefore = urnAuctions(urn); + mathint urnAuctionsOtherBefore = urnAuctions(other); + mathint mkrBalanceOfPrevVoteDelegateBefore = mkr.balanceOf(prevVoteDelegate); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other2); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfPrevFarmBefore = lsmkr.balanceOf(prevFarm); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other3); + + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkrBalanceOfPrevVoteDelegateBefore + mkrBalanceOfEngineBefore + mkrBalanceOfOtherBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfPrevFarmBefore + lsmkrBalanceOfUrnBefore + lsmkrBalanceOfOtherBefore; + + onKick(e, urn, wad); + + address urnVoteDelegatesUrnAfter = urnVoteDelegates(urn); + address urnVoteDelegatesOtherAfter = urnVoteDelegates(other); + address urnFarmsUrnAfter = urnFarms(urn); + address urnFarmsOtherAfter = urnFarms(other); + mathint urnAuctionsUrnAfter = urnAuctions(urn); + mathint urnAuctionsOtherAfter = urnAuctions(other); + mathint mkrBalanceOfPrevVoteDelegateAfter = mkr.balanceOf(prevVoteDelegate); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other2); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfPrevFarmAfter = lsmkr.balanceOf(prevFarm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other3); + + assert urnVoteDelegatesUrnAfter == addrZero(), "Assert 1"; + assert urnVoteDelegatesOtherAfter == urnVoteDelegatesOtherBefore, "Assert 2"; + assert urnFarmsUrnAfter == addrZero(), "Assert 3"; + assert urnFarmsOtherAfter == urnFarmsOtherBefore, "Assert 4"; + assert urnAuctionsUrnAfter == urnAuctionsUrnBefore + 1, "Assert 5"; + assert urnAuctionsOtherAfter == urnAuctionsOtherBefore, "Assert 6"; + assert prevVoteDelegate == addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore, "Assert 7"; + assert prevVoteDelegate != addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore - vatUrnsIlkUrnInk - wad, "Assert 8"; + assert prevVoteDelegate == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 9"; + assert prevVoteDelegate != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + vatUrnsIlkUrnInk + wad, "Assert 10"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 11"; + assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - wad, "Assert 12"; + assert prevFarm == addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore, "Assert 13"; + assert prevFarm != addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore - vatUrnsIlkUrnInk - wad, "Assert 14"; + assert prevFarm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 15"; + assert prevFarm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + vatUrnsIlkUrnInk, "Assert 16"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 17"; +} + +// Verify revert rules on onKick +rule onKick_revert(address urn, uint256 wad) { + env e; + + require urn == lockstakeUrn; + address prevVoteDelegate = urnVoteDelegates(urn); + require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate; + address prevFarm = urnFarms(urn); + require prevFarm == addrZero() || prevFarm == stakingRewards; + + mathint wardsSender = wards(e.msg.sender); + mathint urnAuctionsUrn = urnAuctions(urn); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk(), urn); + + // Happening in urn init + require lsmkr.allowance(urn, currentContract) == max_uint256; + // Tokens invariants + require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(prevFarm) + lsmkr.balanceOf(urn) + lsmkr.balanceOf(currentContract); + require stakingRewards.totalSupply() >= stakingRewards.balanceOf(urn); + // VoteDelegate assumptions + require prevVoteDelegate == addrZero() || to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk + wad; + require prevVoteDelegate == addrZero() || mkr.balanceOf(voteDelegate) >= voteDelegate.stake(currentContract); + // StakingRewards assumptions + require prevFarm == addrZero() && lsmkr.balanceOf(urn) >= wad || + prevFarm != addrZero() && to_mathint(stakingRewards.balanceOf(urn)) >= vatUrnsIlkUrnInk + wad && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUrnInk + wad; + // LockstakeClipper assumption + require wad > 0; + // Practical assumption (vatUrnsIlkUrnInk + wad should be the same than the vatUrnsIlkUrnInk prev to the kick call) + require vatUrnsIlkUrnInk + wad <= max_uint256; + + onKick@withrevert(e, urn, wad); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = urnAuctionsUrn == max_uint256; + + assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting onTake +rule onTake(address urn, address who, uint256 wad) { + env e; + + address other; + require other != currentContract && other != who; + + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfWhoBefore = mkr.balanceOf(who); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other); + + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkrBalanceOfEngineBefore + mkrBalanceOfWhoBefore + mkrBalanceOfOtherBefore; + + onTake(e, urn, who, wad); + + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfWhoAfter = mkr.balanceOf(who); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other); + + assert who != currentContract => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - wad, "Assert 1"; + assert who != currentContract => mkrBalanceOfWhoAfter == mkrBalanceOfWhoBefore + wad, "Assert 2"; + assert who == currentContract => mkrBalanceOfWhoAfter == mkrBalanceOfWhoBefore, "Assert 3"; +} + +// Verify revert rules on onTake +rule onTake_revert(address urn, address who, uint256 wad) { + env e; + + mathint wardsSender = wards(e.msg.sender); + mathint mkrBalanceOfEngine = mkr.balanceOf(currentContract); + + // Tokens invariants + require to_mathint(mkr.totalSupply()) >= mkrBalanceOfEngine + mkr.balanceOf(who); + // LockstakeClipper assumption + require mkrBalanceOfEngine >= to_mathint(wad); + + onTake@withrevert(e, urn, who, wad); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting onRemove +rule onRemove(address urn, uint256 sold, uint256 left) { + env e; + + address other; + require other != urn; + address other2; + require other2 != currentContract; + + bytes32 ilk = ilk(); + mathint fee = fee(); + mathint urnAuctionsUrnBefore = urnAuctions(urn); + mathint urnAuctionsOtherBefore = urnAuctions(other); + mathint vatUrnsIlkUrnInkBefore; mathint a; + vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyBefore = mkr.totalSupply(); + mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherBefore = mkr.balanceOf(other2); + mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherBefore = lsmkr.balanceOf(other); + + // Happening in constructor + require fee < WAD(); + // Tokens invariants + require mkrTotalSupplyBefore >= mkrBalanceOfEngineBefore + mkrBalanceOfOtherBefore; + require lsmkrTotalSupplyBefore >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfOtherBefore; + + mathint burn = _min(sold * fee / (WAD() - fee), left); + mathint refund = left - burn; + + onRemove(e, urn, sold, left); + + mathint urnAuctionsUrnAfter = urnAuctions(urn); + mathint urnAuctionsOtherAfter = urnAuctions(other); + mathint vatUrnsIlkUrnInkAfter; + vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); + mathint mkrTotalSupplyAfter = mkr.totalSupply(); + mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); + mathint mkrBalanceOfOtherAfter = mkr.balanceOf(other2); + mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); + mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other); + + assert urnAuctionsUrnAfter == urnAuctionsUrnBefore - 1, "Assert 1"; + assert urnAuctionsOtherAfter == urnAuctionsOtherBefore, "Assert 2"; + assert refund > 0 => vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore + refund, "Assert 3"; + assert refund == 0 => vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore, "Assert 4"; + assert mkrTotalSupplyAfter == mkrTotalSupplyBefore - burn, "Assert 5"; + assert mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - burn, "Assert 6"; + assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 7"; + assert refund > 0 => lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore + refund, "Assert 8"; + assert refund == 0 => lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore, "Assert 9"; + assert refund > 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + refund, "Assert 10"; + assert refund == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 11"; + assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 12"; +} + +// Verify revert rules on onRemove +rule onRemove_revert(address urn, uint256 sold, uint256 left) { + env e; + + mathint wardsSender = wards(e.msg.sender); + bytes32 ilk = ilk(); + mathint fee = fee(); + mathint urnAuctionsUrn = urnAuctions(urn); + mathint vatIlksIlkArt; mathint vatIlksIlkRate; mathint a; + vatIlksIlkArt, vatIlksIlkRate, a, a, a = vat.ilks(ilk); + mathint vatUrnsIlkUrnInk; mathint vatUrnsIlkUrnArt; + vatUrnsIlkUrnInk, vatUrnsIlkUrnArt = vat.urns(ilk, urn); + mathint mkrTotalSupply = mkr.totalSupply(); + mathint mkrBalanceOfEngine = mkr.balanceOf(currentContract); + mathint lsmkrTotalSupply = lsmkr.totalSupply(); + mathint lsmkrBalanceOfUrn = lsmkr.balanceOf(urn); + + // Happening in constructor + require fee < WAD(); + // Happening in urn init + require vat.can(urn, currentContract) == 1; + // Happening in deploy scripts + require vat.wards(currentContract) == 1; + require lsmkr.wards(currentContract) == 1; + // Tokens invariants + require mkrTotalSupply >= mkrBalanceOfEngine; + require lsmkrTotalSupply >= lsmkrBalanceOfUrn; + + require sold * fee < max_uint256; + mathint burn = _min(sold * fee / (WAD() - fee), left); + mathint refund = left - burn; + + // Practical Vat assumptions + require vat.live() == 1; + require vat.wards(currentContract) == 1; + require vatUrnsIlkUrnInk + refund <= max_uint256; + require vatIlksIlkRate <= max_int256(); + // Safe to assume as Engine doesn't modify vat.gem(ilk,urn) (rule vatGemKeepsUnchanged) + require vat.gem(ilk, urn) == 0; + // Practical token assumptions + require lsmkrTotalSupply + refund <= max_uint256; + // Assumption from LockstakeClipper + require mkrBalanceOfEngine >= burn; + require urn != lsmkr && urn != addrZero(); + + onRemove@withrevert(e, urn, sold, left); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = refund > max_int256(); + bool revert4 = urnAuctionsUrn == 0; + + assert lastReverted <=> revert1 || revert2 || revert3 || + revert4, "Revert rules failed"; +} diff --git a/certora/LockstakeEngineMulticall.conf b/certora/LockstakeEngineMulticall.conf new file mode 100644 index 00000000..5534cab3 --- /dev/null +++ b/certora/LockstakeEngineMulticall.conf @@ -0,0 +1,57 @@ +{ + "files": [ + "src/LockstakeEngine.sol", + "src/LockstakeUrn.sol", + "src/LockstakeMkr.sol", + "certora/harness/dss/Vat.sol", + "test/mocks/VoteDelegateMock.sol", + "certora/harness/tokens/MkrMock.sol", + "test/mocks/StakingRewardsMock.sol", + "certora/harness/MulticallExecutor.sol" + ], + "solc_map": { + "LockstakeEngine": "solc-0.8.21", + "LockstakeUrn": "solc-0.8.21", + "LockstakeMkr": "solc-0.8.21", + "Vat": "solc-0.5.12", + "VoteDelegateMock": "solc-0.8.21", + "MkrMock": "solc-0.8.21", + "StakingRewardsMock": "solc-0.8.21", + "MulticallExecutor": "solc-0.8.21" + }, + "solc_optimize_map": { + "LockstakeEngine": "200", + "LockstakeUrn": "200", + "LockstakeMkr": "200", + "Vat": "0", + "MkrMock": "0", + "VoteDelegateMock": "0", + "StakingRewardsMock": "0", + "MulticallExecutor": "0" + }, + "link": [ + "LockstakeEngine:vat=Vat", + "LockstakeEngine:mkr=MkrMock", + "LockstakeEngine:lsmkr=LockstakeMkr", + "LockstakeUrn:engine=LockstakeEngine", + "LockstakeUrn:lsmkr=LockstakeMkr", + "LockstakeUrn:vat=Vat", + "VoteDelegateMock:gov=MkrMock", + "MulticallExecutor:engine=LockstakeEngine" + ], + "verify": "LockstakeEngine:certora/LockstakeEngineMulticall.spec", + "prover_args": [ + "-rewriteMSizeAllocations true", + "-depth 0" + ], + "smt_timeout": "7000", + "rule_sanity": "basic", + "optimistic_loop": true, + // NOTE: The number of loop iterations should be at least the length of the arrays + // given to `multicall`. + "loop_iter": "4", + "multi_assert_check": true, + "parametric_contracts": ["LockstakeEngine"], + "build_cache": true, + "msg": "LockstakeEngine" +} diff --git a/certora/LockstakeEngineMulticall.spec b/certora/LockstakeEngineMulticall.spec new file mode 100644 index 00000000..72b07c7d --- /dev/null +++ b/certora/LockstakeEngineMulticall.spec @@ -0,0 +1,97 @@ +// Basic spec checking the `multicall` function + +using MulticallExecutor as multicallExecutor; +using MkrMock as mkr; +using LockstakeUrn as lockstakeUrn; + +methods { + function farms(address) external returns (LockstakeEngine.FarmStatus) envfree; + function ownerUrns(address,uint256) external returns (address) envfree; + function urnCan(address,address) external returns (uint256) envfree; + function urnFarms(address) external returns (address) envfree; + function mkr.allowance(address,address) external returns (uint256) envfree; + function mkr.balanceOf(address) external returns (uint256) envfree; + function mkr.totalSupply() external returns (uint256) envfree; + // + function _.lock(address, uint256, uint256, uint16) external => DISPATCHER(true); + function _.lock(uint256) external => DISPATCHER(true); + function _.stake(address,uint256,uint16) external => DISPATCHER(true); + function _.withdraw(address,uint256) external => DISPATCHER(true); + function _.stake(uint256,uint16) external => DISPATCHER(true); + function _.withdraw(uint256) external => DISPATCHER(true); + function _.mint(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); + // The Prover will attempt to dispatch to the following functions any unresolved + // call, if the signature fits. Otherwise it will use the summary defined by the + // `default` keyword. + function _._ external => DISPATCH [ + // currentContract.open(uint256), + currentContract.hope(address,uint256,address), + currentContract.nope(address,uint256,address), + // currentContract.selectVoteDelegate(address,uint256,address), + currentContract.selectFarm(address,uint256,address,uint16), + currentContract.lock(address,uint256,uint256,uint16), + // currentContract.lockSky(address,uint256,uint256,uint16), + // currentContract.free(address,uint256,address,uint256), + // currentContract.freeSky(address,uint256,address,uint256), + // currentContract.freeNoFee(address,uint256,address,uint256), + // currentContract.draw(address,uint256,address,uint256), + // currentContract.wipe(address,uint256,uint256), + // currentContract.wipeAll(address,uint256), + // currentContract.getReward(address,uint256,address,address) + ] default HAVOC_ALL; +} + +definition addrZero() returns address = 0x0000000000000000000000000000000000000000; + +rule hopeAndHope(address owner1, uint256 index1, address owner2, uint256 index2, address usr) { + env e; + + storage init = lastStorage; + + hope(e, owner1, index1, usr); + hope(e, owner2, index2, usr); + + storage twoCalls = lastStorage; + + multicallExecutor.hopeAndHope(e, owner1, index1, owner2, index2, usr) at init; + + assert twoCalls == lastStorage; +} + +rule hopeAndNope(address owner, uint256 index, address usr) { + env e; + + multicallExecutor.hopeAndNope(e, owner, index, usr); + + mathint urnCanUrnAfter = urnCan(ownerUrns(owner, index), usr); + + assert urnCanUrnAfter == 0; +} + + +rule selectFarmAndLock(address owner, uint256 index, address farm, uint16 ref, uint256 wad) { + env e; + + mathint mkrBalanceOfExecutorBefore = mkr.balanceOf(multicallExecutor); + mathint mkrAllowanceExecutorEngineBefore = mkr.allowance(multicallExecutor, currentContract); + + // Token invariants + require to_mathint(mkr.totalSupply()) >= mkrBalanceOfExecutorBefore + mkrAllowanceExecutorEngineBefore; + + multicallExecutor.selectFarmAndLock(e, owner, index, farm, ref, wad); + + mathint mkrBalanceOfExecutorAfter = mkr.balanceOf(multicallExecutor); + mathint mkrAllowanceExecutorEngineAfter = mkr.allowance(multicallExecutor, currentContract); + address urn = ownerUrns(owner, index); + require lockstakeUrn == urn; + address urnFarmsUrnAfter = urnFarms(urn); + + assert mkrBalanceOfExecutorAfter == mkrBalanceOfExecutorBefore - wad, "Assert 1"; + assert mkrAllowanceExecutorEngineBefore < max_uint256 => mkrAllowanceExecutorEngineAfter == mkrAllowanceExecutorEngineBefore - wad, "Assert 2"; + assert mkrAllowanceExecutorEngineBefore == max_uint256 => mkrAllowanceExecutorEngineAfter == mkrAllowanceExecutorEngineBefore, "Assert 3"; + assert urnFarmsUrnAfter == farm, "Assert 4"; + + assert farm == addrZero() || farms(farm) == LockstakeEngine.FarmStatus.ACTIVE, "farm is active"; +} diff --git a/certora/LockstakeMkr.conf b/certora/LockstakeMkr.conf new file mode 100644 index 00000000..fc9b72ad --- /dev/null +++ b/certora/LockstakeMkr.conf @@ -0,0 +1,12 @@ +{ + "files": [ + "src/LockstakeMkr.sol" + ], + "solc": "solc-0.8.21", + "solc_optimize": "200", + "verify": "LockstakeMkr:certora/LockstakeMkr.spec", + "rule_sanity": "basic", + "multi_assert_check": true, + "build_cache": true, + "msg": "LockstakeMkr" +} diff --git a/certora/LockstakeMkr.spec b/certora/LockstakeMkr.spec new file mode 100644 index 00000000..0d7037b8 --- /dev/null +++ b/certora/LockstakeMkr.spec @@ -0,0 +1,329 @@ +// LockstakeMkr.spec + +methods { + function wards(address) external returns (uint256) envfree; + function name() external returns (string) envfree; + function symbol() external returns (string) envfree; + function version() external returns (string) envfree; + function decimals() external returns (uint8) envfree; + function totalSupply() external returns (uint256) envfree; + function balanceOf(address) external returns (uint256) envfree; + function allowance(address, address) external returns (uint256) envfree; +} + +ghost balanceSum() returns mathint { + init_state axiom balanceSum() == 0; +} + +hook Sstore balanceOf[KEY address a] uint256 balance (uint256 old_balance) { + havoc balanceSum assuming balanceSum@new() == balanceSum@old() + balance - old_balance && balanceSum@new() >= 0; +} + +invariant balanceSum_equals_totalSupply() balanceSum() == to_mathint(totalSupply()); + +// Verify that each storage layout is only modified in the corresponding functions +rule storageAffected(method f) { + env e; + + address anyAddr; + address anyAddr2; + + mathint wardsBefore = wards(anyAddr); + mathint totalSupplyBefore = totalSupply(); + mathint balanceOfBefore = balanceOf(anyAddr); + mathint allowanceBefore = allowance(anyAddr, anyAddr2); + + calldataarg args; + f(e, args); + + mathint wardsAfter = wards(anyAddr); + mathint totalSupplyAfter = totalSupply(); + mathint balanceOfAfter = balanceOf(anyAddr); + mathint allowanceAfter = allowance(anyAddr, anyAddr2); + + assert wardsAfter != wardsBefore => f.selector == sig:rely(address).selector || f.selector == sig:deny(address).selector, "Assert 1"; + assert totalSupplyAfter != totalSupplyBefore => f.selector == sig:mint(address,uint256).selector || f.selector == sig:burn(address,uint256).selector, "Assert 2"; + assert balanceOfAfter != balanceOfBefore => f.selector == sig:mint(address,uint256).selector || f.selector == sig:burn(address,uint256).selector || f.selector == sig:transfer(address,uint256).selector || f.selector == sig:transferFrom(address,address,uint256).selector, "Assert 3"; + assert allowanceAfter != allowanceBefore => f.selector == sig:burn(address,uint256).selector || f.selector == sig:transferFrom(address,address,uint256).selector || f.selector == sig:approve(address,uint256).selector, "Assert 4"; +} + +// Verify correct storage changes for non reverting rely +rule rely(address usr) { + env e; + + address other; + require other != usr; + + mathint wardsOtherBefore = wards(other); + + rely(e, usr); + + mathint wardsUsrAfter = wards(usr); + mathint wardsOtherAfter = wards(other); + + assert wardsUsrAfter == 1, "Assert 1"; + assert wardsOtherAfter == wardsOtherBefore, "Assert 2"; +} + +// Verify revert rules on rely +rule rely_revert(address usr) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + rely@withrevert(e, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting deny +rule deny(address usr) { + env e; + + address other; + require other != usr; + + mathint wardsOtherBefore = wards(other); + + deny(e, usr); + + mathint wardsUsrAfter = wards(usr); + mathint wardsOtherAfter = wards(other); + + assert wardsUsrAfter == 0, "Assert 1"; + assert wardsOtherAfter == wardsOtherBefore, "Assert 2"; +} + +// Verify revert rules on deny +rule deny_revert(address usr) { + env e; + + mathint wardsSender = wards(e.msg.sender); + + deny@withrevert(e, usr); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting transfer +rule transfer(address to, uint256 value) { + env e; + + requireInvariant balanceSum_equals_totalSupply(); + + address other; + require other != e.msg.sender && other != to; + + mathint balanceOfSenderBefore = balanceOf(e.msg.sender); + mathint balanceOfToBefore = balanceOf(to); + mathint balanceOfOtherBefore = balanceOf(other); + + transfer(e, to, value); + + mathint balanceOfSenderAfter = balanceOf(e.msg.sender); + mathint balanceOfToAfter = balanceOf(to); + mathint balanceOfOtherAfter = balanceOf(other); + + assert e.msg.sender != to => balanceOfSenderAfter == balanceOfSenderBefore - value, "Assert 1"; + assert e.msg.sender != to => balanceOfToAfter == balanceOfToBefore + value, "Assert 2"; + assert e.msg.sender == to => balanceOfSenderAfter == balanceOfSenderBefore, "Assert 3"; + assert balanceOfOtherAfter == balanceOfOtherBefore, "Assert 4"; +} + +// Verify revert rules on transfer +rule transfer_revert(address to, uint256 value) { + env e; + + mathint balanceOfSender = balanceOf(e.msg.sender); + + transfer@withrevert(e, to, value); + + bool revert1 = e.msg.value > 0; + bool revert2 = to == 0 || to == currentContract; + bool revert3 = balanceOfSender < to_mathint(value); + + assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting transferFrom +rule transferFrom(address from, address to, uint256 value) { + env e; + + requireInvariant balanceSum_equals_totalSupply(); + + address other; + require other != from && other != to; + address other2; address other3; + require other2 != from || other3 != e.msg.sender; + address anyUsr; address anyUsr2; + + mathint balanceOfFromBefore = balanceOf(from); + mathint balanceOfToBefore = balanceOf(to); + mathint balanceOfOtherBefore = balanceOf(other); + mathint allowanceFromSenderBefore = allowance(from, e.msg.sender); + mathint allowanceOtherBefore = allowance(other2, other3); + + transferFrom(e, from, to, value); + + mathint balanceOfFromAfter = balanceOf(from); + mathint balanceOfToAfter = balanceOf(to); + mathint balanceOfOtherAfter = balanceOf(other); + mathint allowanceFromSenderAfter = allowance(from, e.msg.sender); + mathint allowanceOtherAfter = allowance(other2, other3); + + assert from != to => balanceOfFromAfter == balanceOfFromBefore - value, "Assert 1"; + assert from != to => balanceOfToAfter == balanceOfToBefore + value, "Assert 2"; + assert from == to => balanceOfFromAfter == balanceOfFromBefore, "Assert 3"; + assert balanceOfOtherAfter == balanceOfOtherBefore, "Assert 4"; + assert e.msg.sender != from && allowanceFromSenderBefore != max_uint256 => allowanceFromSenderAfter == allowanceFromSenderBefore - value, "Assert 5"; + assert e.msg.sender == from => allowanceFromSenderAfter == allowanceFromSenderBefore, "Assert 6"; + assert allowanceFromSenderBefore == max_uint256 => allowanceFromSenderAfter == allowanceFromSenderBefore, "Assert 7"; + assert allowanceOtherAfter == allowanceOtherBefore, "Assert 8"; +} + +// Verify revert rules on transferFrom +rule transferFrom_revert(address from, address to, uint256 value) { + env e; + + mathint balanceOfFrom = balanceOf(from); + mathint allowanceFromSender = allowance(from, e.msg.sender); + + transferFrom@withrevert(e, from, to, value); + + bool revert1 = e.msg.value > 0; + bool revert2 = to == 0 || to == currentContract; + bool revert3 = balanceOfFrom < to_mathint(value); + bool revert4 = allowanceFromSender < to_mathint(value) && e.msg.sender != from; + + assert lastReverted <=> revert1 || revert2 || revert3 || revert4, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting approve +rule approve(address spender, uint256 value) { + env e; + + address other; address other2; + require other != e.msg.sender || other2 != spender; + + mathint allowanceOtherBefore = allowance(other, other2); + + approve(e, spender, value); + + mathint allowanceSenderSpenderAfter = allowance(e.msg.sender, spender); + mathint allowanceOtherAfter = allowance(other, other2); + + assert allowanceSenderSpenderAfter == to_mathint(value), "Assert 1"; + assert allowanceOtherAfter == allowanceOtherBefore, "Assert 2"; +} + +// Verify revert rules on approve +rule approve_revert(address spender, uint256 value) { + env e; + + approve@withrevert(e, spender, value); + + bool revert1 = e.msg.value > 0; + + assert lastReverted <=> revert1, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting mint +rule mint(address to, uint256 value) { + env e; + + requireInvariant balanceSum_equals_totalSupply(); + + address other; + require other != to; + + bool senderSameAsTo = e.msg.sender == to; + + mathint totalSupplyBefore = totalSupply(); + mathint balanceOfToBefore = balanceOf(to); + mathint balanceOfOtherBefore = balanceOf(other); + + mint(e, to, value); + + mathint totalSupplyAfter = totalSupply(); + mathint balanceOfToAfter = balanceOf(to); + mathint balanceOfOtherAfter = balanceOf(other); + + assert totalSupplyAfter == totalSupplyBefore + value, "Assert 1"; + assert balanceOfToAfter == balanceOfToBefore + value, "Assert 2"; + assert balanceOfOtherAfter == balanceOfOtherBefore, "Assert 3"; +} + +// Verify revert rules on mint +rule mint_revert(address to, uint256 value) { + env e; + + // Save the totalSupply and sender balance before minting + mathint totalSupply = totalSupply(); + mathint wardsSender = wards(e.msg.sender); + + mint@withrevert(e, to, value); + + bool revert1 = e.msg.value > 0; + bool revert2 = wardsSender != 1; + bool revert3 = totalSupply + value > max_uint256; + bool revert4 = to == 0 || to == currentContract; + + assert lastReverted <=> revert1 || revert2 || revert3 || revert4, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting burn +rule burn(address from, uint256 value) { + env e; + + requireInvariant balanceSum_equals_totalSupply(); + + address other; + require other != from; + address other2; address other3; + require other2 != from || other3 != e.msg.sender; + + mathint totalSupplyBefore = totalSupply(); + mathint balanceOfFromBefore = balanceOf(from); + mathint balanceOfOtherBefore = balanceOf(other); + mathint allowanceFromSenderBefore = allowance(from, e.msg.sender); + mathint allowanceOtherBefore = allowance(other2, other3); + + burn(e, from, value); + + mathint totalSupplyAfter = totalSupply(); + mathint balanceOfSenderAfter = balanceOf(e.msg.sender); + mathint balanceOfFromAfter = balanceOf(from); + mathint balanceOfOtherAfter = balanceOf(other); + mathint allowanceFromSenderAfter = allowance(from, e.msg.sender); + mathint allowanceOtherAfter = allowance(other2, other3); + + assert totalSupplyAfter == totalSupplyBefore - value, "Assert 1"; + assert balanceOfFromAfter == balanceOfFromBefore - value, "Assert 2"; + assert balanceOfOtherAfter == balanceOfOtherBefore, "Assert 3"; + assert e.msg.sender != from && allowanceFromSenderBefore != max_uint256 => allowanceFromSenderAfter == allowanceFromSenderBefore - value, "Assert 4"; + assert e.msg.sender == from => allowanceFromSenderAfter == allowanceFromSenderBefore, "Assert 5"; + assert allowanceFromSenderBefore == max_uint256 => allowanceFromSenderAfter == allowanceFromSenderBefore, "Assert 6"; + assert allowanceOtherAfter == allowanceOtherBefore, "Assert 7"; +} + +// Verify revert rules on burn +rule burn_revert(address from, uint256 value) { + env e; + + mathint balanceOfFrom = balanceOf(from); + mathint allowanceFromSender = allowance(from, e.msg.sender); + + burn@withrevert(e, from, value); + + bool revert1 = e.msg.value > 0; + bool revert2 = balanceOfFrom < to_mathint(value); + bool revert3 = from != e.msg.sender && allowanceFromSender < to_mathint(value); + + assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; +} diff --git a/certora/LockstakeUrn.conf b/certora/LockstakeUrn.conf new file mode 100644 index 00000000..d6b43ebc --- /dev/null +++ b/certora/LockstakeUrn.conf @@ -0,0 +1,34 @@ +{ + "files": [ + "src/LockstakeUrn.sol", + "src/LockstakeMkr.sol", + "certora/harness/dss/Vat.sol", + "test/mocks/StakingRewardsMock.sol", + "certora/harness/tokens/RewardsMock.sol", + ], + "solc_map": { + "LockstakeUrn": "solc-0.8.21", + "LockstakeMkr": "solc-0.8.21", + "Vat": "solc-0.5.12", + "StakingRewardsMock": "solc-0.8.21", + "RewardsMock": "solc-0.8.21", + }, + "solc_optimize_map": { + "LockstakeUrn": "200", + "LockstakeMkr": "200", + "Vat": "0", + "StakingRewardsMock": "200", + "RewardsMock": "200", + }, + "link": [ + "StakingRewardsMock:rewardsToken=RewardsMock", + "StakingRewardsMock:stakingToken=LockstakeMkr", + "LockstakeUrn:lsmkr=LockstakeMkr", + "LockstakeUrn:vat=Vat" + ], + "verify": "LockstakeUrn:certora/LockstakeUrn.spec", + "rule_sanity": "basic", + "multi_assert_check": true, + "build_cache": true, + "msg": "LockstakeUrn" +} diff --git a/certora/LockstakeUrn.spec b/certora/LockstakeUrn.spec new file mode 100644 index 00000000..bd96ecc4 --- /dev/null +++ b/certora/LockstakeUrn.spec @@ -0,0 +1,180 @@ +// LockstakeUrn.spec + +using Vat as vat; +using LockstakeMkr as lsmkr; +using StakingRewardsMock as stakingRewards; +using RewardsMock as rewardsToken; + +methods { + function engine() external returns (address) envfree; + function vat.can(address,address) external returns (uint256) envfree; + function lsmkr.allowance(address,address) external returns (uint256) envfree; + function lsmkr.balanceOf(address) external returns (uint256) envfree; + function lsmkr.totalSupply() external returns (uint256) envfree; + function stakingRewards.balanceOf(address) external returns (uint256) envfree; + function stakingRewards.totalSupply() external returns (uint256) envfree; + function stakingRewards.rewards(address) external returns (uint256) envfree; + function _.stake(uint256,uint16) external => DISPATCHER(true); + function _.withdraw(uint256) external => DISPATCHER(true); + function _.getReward() external => DISPATCHER(true); + function _.rewardsToken() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function rewardsToken.balanceOf(address) external returns (uint256) envfree; + function rewardsToken.totalSupply() external returns (uint256) envfree; +} + +// Verify correct storage changes for non reverting init +rule init() { + env e; + + address engine = engine(); + + init(e); + + mathint vatCanUrnEngineAfter = vat.can(currentContract, engine); + mathint lsmkrAllowanceUrnEngineAfter = lsmkr.allowance(currentContract, engine); + + assert vatCanUrnEngineAfter == 1, "Assert 1"; + assert lsmkrAllowanceUrnEngineAfter == max_uint256, "Assert 2"; +} + +// Verify revert rules on init +rule init_revert() { + env e; + + address engine = engine(); + + init@withrevert(e); + + bool revert1 = e.msg.value > 0; + bool revert2 = engine != e.msg.sender; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting stake +rule stake(address farm, uint256 wad, uint16 ref) { + env e; + + require farm == stakingRewards; + + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(currentContract); + mathint lsmkrBalanceOfFarmBefore = lsmkr.balanceOf(farm); + require to_mathint(lsmkr.totalSupply()) >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBefore; + mathint farmBalanceOfUrnBefore = stakingRewards.balanceOf(currentContract); + + stake(e, farm, wad, ref); + + mathint lsmkrAllowanceUrnFarmAfter = lsmkr.allowance(currentContract, farm); + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(currentContract); + mathint lsmkrBalanceOfFarmAfter = lsmkr.balanceOf(farm); + mathint farmBalanceOfUrnAfter = stakingRewards.balanceOf(currentContract); + + assert lsmkrAllowanceUrnFarmAfter == 0 || lsmkrAllowanceUrnFarmAfter == max_uint256, "Assert 1"; + assert lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 2"; + assert lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore + wad, "Assert 3"; + assert farmBalanceOfUrnAfter == farmBalanceOfUrnBefore + wad, "Assert 4"; +} + +// Verify revert rules on stake +rule stake_revert(address farm, uint256 wad, uint16 ref) { + env e; + + require farm == stakingRewards; + + require wad > 0; + require lsmkr.balanceOf(currentContract) >= wad; + require lsmkr.totalSupply() >= wad; + require stakingRewards.balanceOf(currentContract) + wad <= max_uint256; + require stakingRewards.totalSupply() + wad <= max_uint256; + + address engine = engine(); + + stake@withrevert(e, farm, wad, ref); + + bool revert1 = e.msg.value > 0; + bool revert2 = engine != e.msg.sender; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting withdraw +rule withdraw(address farm, uint256 wad) { + env e; + + require farm == stakingRewards; + + mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(currentContract); + mathint lsmkrBalanceOfFarmBefore = lsmkr.balanceOf(farm); + require to_mathint(lsmkr.totalSupply()) >= lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBefore; + mathint farmBalanceOfUrnBefore = stakingRewards.balanceOf(currentContract); + + withdraw(e, farm, wad); + + mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(currentContract); + mathint lsmkrBalanceOfFarmAfter = lsmkr.balanceOf(farm); + mathint farmBalanceOfUrnAfter = stakingRewards.balanceOf(currentContract); + + assert lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + wad, "Assert 1"; + assert lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - wad, "Assert 2"; + assert farmBalanceOfUrnAfter == farmBalanceOfUrnBefore - wad, "Assert 3"; +} + +// Verify revert rules on withdraw +rule withdraw_revert(address farm, uint256 wad) { + env e; + + require farm == stakingRewards; + + require wad > 0; + require lsmkr.balanceOf(farm) >= wad; + require lsmkr.balanceOf(currentContract) + wad <= max_uint256; + require lsmkr.totalSupply() + wad <= max_uint256; + require stakingRewards.balanceOf(currentContract) >= wad; + require stakingRewards.totalSupply() >= wad; + + address engine = engine(); + + withdraw@withrevert(e, farm, wad); + + bool revert1 = e.msg.value > 0; + bool revert2 = engine != e.msg.sender; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} + +// Verify correct storage changes for non reverting getReward +rule getReward(address farm, address to) { + env e; + + require farm == stakingRewards; + + mathint rewardsTokenBalanceOfToBefore = rewardsToken.balanceOf(to); + require rewardsTokenBalanceOfToBefore <= to_mathint(rewardsToken.totalSupply()); + require rewardsTokenBalanceOfToBefore + rewardsToken.balanceOf(currentContract) + rewardsToken.balanceOf(stakingRewards) <= to_mathint(rewardsToken.totalSupply()); + + getReward(e, farm, to); + + mathint rewardsTokenBalanceOfToAfter = rewardsToken.balanceOf(to); + + assert rewardsTokenBalanceOfToAfter >= rewardsTokenBalanceOfToBefore, "Assert 1"; +} + +// Verify revert rules on getReward +rule getReward_revert(address farm, address to) { + env e; + + require farm == stakingRewards; + + require rewardsToken.balanceOf(stakingRewards) >= stakingRewards.rewards(currentContract); + + address engine = engine(); + + getReward@withrevert(e, farm, to); + + bool revert1 = e.msg.value > 0; + bool revert2 = engine != e.msg.sender; + + assert lastReverted <=> revert1 || revert2, "Revert rules failed"; +} diff --git a/certora/harness/MulticallExecutor.sol b/certora/harness/MulticallExecutor.sol new file mode 100644 index 00000000..36d5ccd7 --- /dev/null +++ b/certora/harness/MulticallExecutor.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +import { LockstakeEngine } from "../../src/LockstakeEngine.sol"; + + +contract MulticallExecutor { + + LockstakeEngine public engine; + + function hopeAndHope(address owner1, uint256 index1, address owner2, uint256 index2, address usr) public { + + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSignature("hope(address,uint256,address)", owner1, index1, usr); + calls[1] = abi.encodeWithSignature("hope(address,uint256,address)", owner2, index2, usr); + engine.multicall(calls); + } + + function hopeAndNope(address owner, uint256 index, address usr) public { + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSignature("hope(address,uint256,address)", owner, index, usr); + calls[1] = abi.encodeWithSignature("nope(address,uint256,address)", owner, index, usr); + engine.multicall(calls); + } + + function selectFarmAndLock( + address owner, + uint256 index, + address farm, + uint16 ref, + uint256 wad + ) public { + + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSignature( + "selectFarm(address,uint256,address,uint16)", owner, index, farm, ref + ); + calls[1] = abi.encodeWithSignature( + "lock(address,uint256,uint256,uint16)", owner, index, wad, ref + ); + engine.multicall(calls); + } +} diff --git a/certora/harness/StakingRewards2Mock.sol b/certora/harness/StakingRewards2Mock.sol new file mode 100644 index 00000000..251806b6 --- /dev/null +++ b/certora/harness/StakingRewards2Mock.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.21; + +import {StakingRewardsMock} from "../../test/mocks/StakingRewardsMock.sol"; + +contract StakingRewards2Mock is StakingRewardsMock { + + constructor(address rewardsToken, address stakingToken) StakingRewardsMock(rewardsToken, stakingToken) { + } +} diff --git a/certora/harness/VoteDelegate2Mock.sol b/certora/harness/VoteDelegate2Mock.sol new file mode 100644 index 00000000..83595f18 --- /dev/null +++ b/certora/harness/VoteDelegate2Mock.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.21; + +import {VoteDelegateMock} from "../../test/mocks/VoteDelegateMock.sol"; + +contract VoteDelegate2Mock is VoteDelegateMock { + + constructor(address gov) VoteDelegateMock(gov) { + } +} diff --git a/certora/harness/dss/ClipperCallee.sol b/certora/harness/dss/ClipperCallee.sol new file mode 100644 index 00000000..4ff4e53a --- /dev/null +++ b/certora/harness/dss/ClipperCallee.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// taken from: ./LockstakeClipper.t.sol + +pragma solidity ^0.8.21; + +import { LockstakeClipper } from "src/LockstakeClipper.sol"; + +contract BadGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall(address sender, uint256 owe, uint256 slice, bytes calldata data) + external { + sender; owe; slice; data; + clip.take({ // attempt reentrancy + id: 1, + amt: 25 ether, + max: 5 ether * 10E27, + who: address(this), + data: "" + }); + } +} + +contract RedoGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + owe; slice; data; + clip.redo(1, sender); + } +} + +contract KickGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.kick(1, 1, address(0), address(0)); + } +} + +contract FileUintGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.file("stopped", 1); + } +} + +contract FileAddrGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.file("vow", address(123)); + } +} + +contract YankGuy { + LockstakeClipper clip; + + constructor(LockstakeClipper clip_) { + clip = clip_; + } + + function clipperCall( + address sender, uint256 owe, uint256 slice, bytes calldata data + ) external { + sender; owe; slice; data; + clip.yank(1); + } +} diff --git a/certora/harness/dss/Dog.sol b/certora/harness/dss/Dog.sol new file mode 100644 index 00000000..d941d80f --- /dev/null +++ b/certora/harness/dss/Dog.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/// dog.sol -- Dai liquidation module 2.0 + +// Copyright (C) 2020-2022 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.6.12; + +interface ClipperLike { + function ilk() external view returns (bytes32); + function kick( + uint256 tab, + uint256 lot, + address usr, + address kpr + ) external returns (uint256); +} + +interface VatLike { + function ilks(bytes32) external view returns ( + uint256 Art, // [wad] + uint256 rate, // [ray] + uint256 spot, // [ray] + uint256 line, // [rad] + uint256 dust // [rad] + ); + function urns(bytes32,address) external view returns ( + uint256 ink, // [wad] + uint256 art // [wad] + ); + function grab(bytes32,address,address,address,int256,int256) external; + function hope(address) external; + function nope(address) external; +} + +interface VowLike { + function fess(uint256) external; +} + +contract Dog { + // --- Auth --- + mapping (address => uint256) public wards; + function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } + function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } + modifier auth { + require(wards[msg.sender] == 1, "Dog/not-authorized"); + _; + } + + // --- Data --- + struct Ilk { + address clip; // Liquidator + uint256 chop; // Liquidation Penalty [wad] + uint256 hole; // Max DAI needed to cover debt+fees of active auctions per ilk [rad] + uint256 dirt; // Amt DAI needed to cover debt+fees of active auctions per ilk [rad] + } + + VatLike immutable public vat; // CDP Engine + + mapping (bytes32 => Ilk) public ilks; + + VowLike public vow; // Debt Engine + uint256 public live; // Active Flag + uint256 public Hole; // Max DAI needed to cover debt+fees of active auctions [rad] + uint256 public Dirt; // Amt DAI needed to cover debt+fees of active auctions [rad] + + // --- Events --- + event Rely(address indexed usr); + event Deny(address indexed usr); + + event File(bytes32 indexed what, uint256 data); + event File(bytes32 indexed what, address data); + event File(bytes32 indexed ilk, bytes32 indexed what, uint256 data); + event File(bytes32 indexed ilk, bytes32 indexed what, address clip); + + event Bark( + bytes32 indexed ilk, + address indexed urn, + uint256 ink, + uint256 art, + uint256 due, + address clip, + uint256 indexed id + ); + event Digs(bytes32 indexed ilk, uint256 rad); + event Cage(); + + // --- Init --- + constructor(address vat_) public { + vat = VatLike(vat_); + live = 1; + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Math --- + uint256 constant WAD = 10 ** 18; + + function min(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = x <= y ? x : y; + } + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x); + } + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x); + } + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + require(y == 0 || (z = x * y) / y == x); + } + + // --- Administration --- + function file(bytes32 what, address data) external auth { + if (what == "vow") vow = VowLike(data); + else revert("Dog/file-unrecognized-param"); + emit File(what, data); + } + function file(bytes32 what, uint256 data) external auth { + if (what == "Hole") Hole = data; + else revert("Dog/file-unrecognized-param"); + emit File(what, data); + } + function file(bytes32 ilk, bytes32 what, uint256 data) external auth { + if (what == "chop") { + require(data >= WAD, "Dog/file-chop-lt-WAD"); + ilks[ilk].chop = data; + } else if (what == "hole") ilks[ilk].hole = data; + else revert("Dog/file-unrecognized-param"); + emit File(ilk, what, data); + } + function file(bytes32 ilk, bytes32 what, address clip) external auth { + if (what == "clip") { + require(ilk == ClipperLike(clip).ilk(), "Dog/file-ilk-neq-clip.ilk"); + ilks[ilk].clip = clip; + } else revert("Dog/file-unrecognized-param"); + emit File(ilk, what, clip); + } + + function chop(bytes32 ilk) external view returns (uint256) { + return ilks[ilk].chop; + } + + // --- CDP Liquidation: all bark and no bite --- + // + // Liquidate a Vault and start a Dutch auction to sell its collateral for DAI. + // + // The third argument is the address that will receive the liquidation reward, if any. + // + // The entire Vault will be liquidated except when the target amount of DAI to be raised in + // the resulting auction (debt of Vault + liquidation penalty) causes either Dirt to exceed + // Hole or ilk.dirt to exceed ilk.hole by an economically significant amount. In that + // case, a partial liquidation is performed to respect the global and per-ilk limits on + // outstanding DAI target. The one exception is if the resulting auction would likely + // have too little collateral to be interesting to Keepers (debt taken from Vault < ilk.dust), + // in which case the function reverts. Please refer to the code and comments within if + // more detail is desired. + function bark(bytes32 ilk, address urn, address kpr) external returns (uint256 id) { + require(live == 1, "Dog/not-live"); + + (uint256 ink, uint256 art) = vat.urns(ilk, urn); + Ilk memory milk = ilks[ilk]; + uint256 dart; + uint256 rate; + uint256 dust; + { + uint256 spot; + (,rate, spot,, dust) = vat.ilks(ilk); + require(spot > 0 && mul(ink, spot) < mul(art, rate), "Dog/not-unsafe"); + + // Get the minimum value between: + // 1) Remaining space in the general Hole + // 2) Remaining space in the collateral hole + require(Hole > Dirt && milk.hole > milk.dirt, "Dog/liquidation-limit-hit"); + uint256 room = min(Hole - Dirt, milk.hole - milk.dirt); + + // uint256.max()/(RAD*WAD) = 115,792,089,237,316 + dart = min(art, mul(room, WAD) / rate / milk.chop); + + // Partial liquidation edge case logic + if (art > dart) { + if (mul(art - dart, rate) < dust) { + + // If the leftover Vault would be dusty, just liquidate it entirely. + // This will result in at least one of dirt_i > hole_i or Dirt > Hole becoming true. + // The amount of excess will be bounded above by ceiling(dust_i * chop_i / WAD). + // This deviation is assumed to be small compared to both hole_i and Hole, so that + // the extra amount of target DAI over the limits intended is not of economic concern. + dart = art; + } else { + + // In a partial liquidation, the resulting auction should also be non-dusty. + require(mul(dart, rate) >= dust, "Dog/dusty-auction-from-partial-liquidation"); + } + } + } + + uint256 dink = mul(ink, dart) / art; + + require(dink > 0, "Dog/null-auction"); + require(dart <= 2**255 && dink <= 2**255, "Dog/overflow"); + + vat.grab( + ilk, urn, milk.clip, address(vow), -int256(dink), -int256(dart) + ); + + uint256 due = mul(dart, rate); + vow.fess(due); + + { // Avoid stack too deep + // This calcuation will overflow if dart*rate exceeds ~10^14 + uint256 tab = mul(due, milk.chop) / WAD; + Dirt = add(Dirt, tab); + ilks[ilk].dirt = add(milk.dirt, tab); + + id = ClipperLike(milk.clip).kick({ + tab: tab, + lot: dink, + usr: urn, + kpr: kpr + }); + } + + emit Bark(ilk, urn, dink, dart, due, milk.clip, id); + } + + function digs(bytes32 ilk, uint256 rad) external auth { + Dirt = sub(Dirt, rad); + ilks[ilk].dirt = sub(ilks[ilk].dirt, rad); + emit Digs(ilk, rad); + } + + function cage() external auth { + live = 0; + emit Cage(); + } +} diff --git a/certora/harness/dss/Jug.sol b/certora/harness/dss/Jug.sol new file mode 100644 index 00000000..c9ec7086 --- /dev/null +++ b/certora/harness/dss/Jug.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/// jug.sol -- Dai Lending Rate + +// Copyright (C) 2018 Rain +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.5.12; + +contract LibNote { + event LogNote( + bytes4 indexed sig, + address indexed usr, + bytes32 indexed arg1, + bytes32 indexed arg2, + bytes data + ) anonymous; + + modifier note { + _; + assembly { + // log an 'anonymous' event with a constant 6 words of calldata + // and four indexed topics: selector, caller, arg1 and arg2 + let mark := msize() // end of memory ensures zero + mstore(0x40, add(mark, 288)) // update free memory pointer + mstore(mark, 0x20) // bytes type data offset + mstore(add(mark, 0x20), 224) // bytes size (padded) + calldatacopy(add(mark, 0x40), 0, 224) // bytes payload + log4(mark, 288, // calldata + shl(224, shr(224, calldataload(0))), // msg.sig + caller(), // msg.sender + calldataload(4), // arg1 + calldataload(36) // arg2 + ) + } + } +} + +interface VatLike { + function ilks(bytes32) external returns ( + uint256 Art, // [wad] + uint256 rate // [ray] + ); + function fold(bytes32,address,int) external; +} + +contract Jug is LibNote { + // --- Auth --- + mapping (address => uint) public wards; + function rely(address usr) external note auth { wards[usr] = 1; } + function deny(address usr) external note auth { wards[usr] = 0; } + modifier auth { + require(wards[msg.sender] == 1, "Jug/not-authorized"); + _; + } + + // --- Data --- + struct Ilk { + uint256 duty; // Collateral-specific, per-second stability fee contribution [ray] + uint256 rho; // Time of last drip [unix epoch time] + } + + mapping (bytes32 => Ilk) public ilks; + VatLike public vat; // CDP Engine + address public vow; // Debt Engine + uint256 public base; // Global, per-second stability fee contribution [ray] + + // --- Init --- + constructor(address vat_) public { + wards[msg.sender] = 1; + vat = VatLike(vat_); + } + + // --- Math --- + function rpow(uint x, uint n, uint b) internal pure returns (uint z) { + assembly { + switch x case 0 {switch n case 0 {z := b} default {z := 0}} + default { + switch mod(n, 2) case 0 { z := b } default { z := x } + let half := div(b, 2) // for rounding. + for { n := div(n, 2) } n { n := div(n,2) } { + let xx := mul(x, x) + if iszero(eq(div(xx, x), x)) { revert(0,0) } + let xxRound := add(xx, half) + if lt(xxRound, xx) { revert(0,0) } + x := div(xxRound, b) + if mod(n,2) { + let zx := mul(z, x) + if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } + let zxRound := add(zx, half) + if lt(zxRound, zx) { revert(0,0) } + z := div(zxRound, b) + } + } + } + } + } + uint256 constant ONE = 10 ** 27; + function add(uint x, uint y) internal pure returns (uint z) { + z = x + y; + require(z >= x); + } + function diff(uint x, uint y) internal pure returns (int z) { + z = int(x) - int(y); + require(int(x) >= 0 && int(y) >= 0); + } + function rmul(uint x, uint y) internal pure returns (uint z) { + z = x * y; + require(y == 0 || z / y == x); + z = z / ONE; + } + + // --- Administration --- + function init(bytes32 ilk) external note auth { + Ilk storage i = ilks[ilk]; + require(i.duty == 0, "Jug/ilk-already-init"); + i.duty = ONE; + i.rho = now; + } + function file(bytes32 ilk, bytes32 what, uint data) external note auth { + require(now == ilks[ilk].rho, "Jug/rho-not-updated"); + if (what == "duty") ilks[ilk].duty = data; + else revert("Jug/file-unrecognized-param"); + } + function file(bytes32 what, uint data) external note auth { + if (what == "base") base = data; + else revert("Jug/file-unrecognized-param"); + } + function file(bytes32 what, address data) external note auth { + if (what == "vow") vow = data; + else revert("Jug/file-unrecognized-param"); + } + + // --- Stability Fee Collection --- + function drip(bytes32 ilk) external note returns (uint rate) { + require(now >= ilks[ilk].rho, "Jug/invalid-now"); + (, uint prev) = vat.ilks(ilk); + rate = rmul(rpow(add(base, ilks[ilk].duty), now - ilks[ilk].rho, ONE), prev); + vat.fold(ilk, vow, diff(rate, prev)); + ilks[ilk].rho = now; + } +} diff --git a/certora/harness/dss/Spotter.sol b/certora/harness/dss/Spotter.sol new file mode 100644 index 00000000..a7785d87 --- /dev/null +++ b/certora/harness/dss/Spotter.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/// spot.sol -- Spotter + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.5.12; + +contract LibNote { + event LogNote( + bytes4 indexed sig, + address indexed usr, + bytes32 indexed arg1, + bytes32 indexed arg2, + bytes data + ) anonymous; + + modifier note { + _; + assembly { + // log an 'anonymous' event with a constant 6 words of calldata + // and four indexed topics: selector, caller, arg1 and arg2 + let mark := msize() // end of memory ensures zero + mstore(0x40, add(mark, 288)) // update free memory pointer + mstore(mark, 0x20) // bytes type data offset + mstore(add(mark, 0x20), 224) // bytes size (padded) + calldatacopy(add(mark, 0x40), 0, 224) // bytes payload + log4(mark, 288, // calldata + shl(224, shr(224, calldataload(0))), // msg.sig + caller(), // msg.sender + calldataload(4), // arg1 + calldataload(36) // arg2 + ) + } + } +} + +interface VatLike { + function file(bytes32, bytes32, uint) external; +} + +interface PipLike { + function peek() external returns (bytes32, bool); +} + +contract Spotter is LibNote { + // --- Auth --- + mapping (address => uint) public wards; + function rely(address guy) external note auth { wards[guy] = 1; } + function deny(address guy) external note auth { wards[guy] = 0; } + modifier auth { + require(wards[msg.sender] == 1, "Spotter/not-authorized"); + _; + } + + // --- Data --- + struct Ilk { + PipLike pip; // Price Feed + uint256 mat; // Liquidation ratio [ray] + } + + mapping (bytes32 => Ilk) public ilks; + + VatLike public vat; // CDP Engine + uint256 public par; // ref per dai [ray] + + uint256 public live; + + // --- Events --- + event Poke( + bytes32 ilk, + bytes32 val, // [wad] + uint256 spot // [ray] + ); + + // --- Init --- + constructor(address vat_) public { + wards[msg.sender] = 1; + vat = VatLike(vat_); + par = ONE; + live = 1; + } + + // --- Math --- + uint constant ONE = 10 ** 27; + + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x); + } + function rdiv(uint x, uint y) internal pure returns (uint z) { + z = mul(x, ONE) / y; + } + + // --- Administration --- + function file(bytes32 ilk, bytes32 what, address pip_) external note auth { + require(live == 1, "Spotter/not-live"); + if (what == "pip") ilks[ilk].pip = PipLike(pip_); + else revert("Spotter/file-unrecognized-param"); + } + function file(bytes32 what, uint data) external note auth { + require(live == 1, "Spotter/not-live"); + if (what == "par") par = data; + else revert("Spotter/file-unrecognized-param"); + } + function file(bytes32 ilk, bytes32 what, uint data) external note auth { + require(live == 1, "Spotter/not-live"); + if (what == "mat") ilks[ilk].mat = data; + else revert("Spotter/file-unrecognized-param"); + } + + // --- Update value --- + function poke(bytes32 ilk) external { + (bytes32 val, bool has) = ilks[ilk].pip.peek(); + uint256 spot = has ? rdiv(rdiv(mul(uint(val), 10 ** 9), par), ilks[ilk].mat) : 0; + vat.file(ilk, "spot", spot); + emit Poke(ilk, val, spot); + } + + function cage() external note auth { + live = 0; + } +} diff --git a/certora/harness/dss/Vat.sol b/certora/harness/dss/Vat.sol new file mode 100644 index 00000000..b3f9cbd2 --- /dev/null +++ b/certora/harness/dss/Vat.sol @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/// vat.sol -- Dai CDP database + +// Copyright (C) 2018 Rain +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.5.12; + +contract Vat { + // --- Auth --- + mapping (address => uint) public wards; + function rely(address usr) external note auth { require(live == 1, "Vat/not-live"); wards[usr] = 1; } + function deny(address usr) external note auth { require(live == 1, "Vat/not-live"); wards[usr] = 0; } + modifier auth { + require(wards[msg.sender] == 1, "Vat/not-authorized"); + _; + } + + mapping(address => mapping (address => uint)) public can; + function hope(address usr) external note { can[msg.sender][usr] = 1; } + function nope(address usr) external note { can[msg.sender][usr] = 0; } + function wish(address bit, address usr) internal view returns (bool) { + return either(bit == usr, can[bit][usr] == 1); + } + + // --- Data --- + struct Ilk { + uint256 Art; // Total Normalised Debt [wad] + uint256 rate; // Accumulated Rates [ray] + uint256 spot; // Price with Safety Margin [ray] + uint256 line; // Debt Ceiling [rad] + uint256 dust; // Urn Debt Floor [rad] + } + struct Urn { + uint256 ink; // Locked Collateral [wad] + uint256 art; // Normalised Debt [wad] + } + + mapping (bytes32 => Ilk) public ilks; + mapping (bytes32 => mapping (address => Urn )) public urns; + mapping (bytes32 => mapping (address => uint)) public gem; // [wad] + mapping (address => uint256) public dai; // [rad] + mapping (address => uint256) public sin; // [rad] + + uint256 public debt; // Total Dai Issued [rad] + uint256 public vice; // Total Unbacked Dai [rad] + uint256 public Line; // Total Debt Ceiling [rad] + uint256 public live; // Active Flag + + // --- Logs --- + event LogNote( + bytes4 indexed sig, + bytes32 indexed arg1, + bytes32 indexed arg2, + bytes32 indexed arg3, + bytes data + ) anonymous; + + modifier note { + _; + assembly { + // log an 'anonymous' event with a constant 6 words of calldata + // and four indexed topics: the selector and the first three args + let mark := msize() // end of memory ensures zero + mstore(0x40, add(mark, 288)) // update free memory pointer + mstore(mark, 0x20) // bytes type data offset + mstore(add(mark, 0x20), 224) // bytes size (padded) + calldatacopy(add(mark, 0x40), 0, 224) // bytes payload + log4(mark, 288, // calldata + shl(224, shr(224, calldataload(0))), // msg.sig + calldataload(4), // arg1 + calldataload(36), // arg2 + calldataload(68) // arg3 + ) + } + } + + // --- Init --- + constructor() public { + wards[msg.sender] = 1; + live = 1; + } + + // --- Math --- + function add(uint x, int y) internal pure returns (uint z) { + z = x + uint(y); + require(y >= 0 || z <= x); + require(y <= 0 || z >= x); + } + function sub(uint x, int y) internal pure returns (uint z) { + z = x - uint(y); + require(y <= 0 || z <= x); + require(y >= 0 || z >= x); + } + function mul(uint x, int y) internal pure returns (int z) { + z = int(x) * y; + require(int(x) >= 0); + require(y == 0 || z / y == int(x)); + } + function add(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x); + } + function sub(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x); + } + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x); + } + + // --- Administration --- + function init(bytes32 ilk) external note auth { + require(ilks[ilk].rate == 0, "Vat/ilk-already-init"); + ilks[ilk].rate = 10 ** 27; + } + function file(bytes32 what, uint data) external note auth { + require(live == 1, "Vat/not-live"); + if (what == "Line") Line = data; + else revert("Vat/file-unrecognized-param"); + } + function file(bytes32 ilk, bytes32 what, uint data) external note auth { + require(live == 1, "Vat/not-live"); + if (what == "spot") ilks[ilk].spot = data; + else if (what == "line") ilks[ilk].line = data; + else if (what == "dust") ilks[ilk].dust = data; + else revert("Vat/file-unrecognized-param"); + } + function cage() external note auth { + live = 0; + } + + // --- Fungibility --- + function slip(bytes32 ilk, address usr, int256 wad) external note auth { + gem[ilk][usr] = add(gem[ilk][usr], wad); + } + function flux(bytes32 ilk, address src, address dst, uint256 wad) external note { + require(wish(src, msg.sender), "Vat/not-allowed"); + gem[ilk][src] = sub(gem[ilk][src], wad); + gem[ilk][dst] = add(gem[ilk][dst], wad); + } + function move(address src, address dst, uint256 rad) external note { + require(wish(src, msg.sender), "Vat/not-allowed"); + dai[src] = sub(dai[src], rad); + dai[dst] = add(dai[dst], rad); + } + + function either(bool x, bool y) internal pure returns (bool z) { + assembly{ z := or(x, y)} + } + function both(bool x, bool y) internal pure returns (bool z) { + assembly{ z := and(x, y)} + } + + // --- CDP Manipulation --- + function frob(bytes32 i, address u, address v, address w, int dink, int dart) external note { + // system is live + require(live == 1, "Vat/not-live"); + + Urn memory urn = urns[i][u]; + Ilk memory ilk = ilks[i]; + // ilk has been initialised + require(ilk.rate != 0, "Vat/ilk-not-init"); + + urn.ink = add(urn.ink, dink); + urn.art = add(urn.art, dart); + ilk.Art = add(ilk.Art, dart); + + int dtab = mul(ilk.rate, dart); + uint tab = mul(ilk.rate, urn.art); + debt = add(debt, dtab); + + // either debt has decreased, or debt ceilings are not exceeded + require(either(dart <= 0, both(mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded"); + // urn is either less risky than before, or it is safe + require(either(both(dart <= 0, dink >= 0), tab <= mul(urn.ink, ilk.spot)), "Vat/not-safe"); + + // urn is either more safe, or the owner consents + require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u"); + // collateral src consents + require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v"); + // debt dst consents + require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w"); + + // urn has no debt, or a non-dusty amount + require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust"); + + gem[i][v] = sub(gem[i][v], dink); + dai[w] = add(dai[w], dtab); + + urns[i][u] = urn; + ilks[i] = ilk; + } + // --- CDP Fungibility --- + function fork(bytes32 ilk, address src, address dst, int dink, int dart) external note { + Urn storage u = urns[ilk][src]; + Urn storage v = urns[ilk][dst]; + Ilk storage i = ilks[ilk]; + + u.ink = sub(u.ink, dink); + u.art = sub(u.art, dart); + v.ink = add(v.ink, dink); + v.art = add(v.art, dart); + + uint utab = mul(u.art, i.rate); + uint vtab = mul(v.art, i.rate); + + // both sides consent + require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed"); + + // both sides safe + require(utab <= mul(u.ink, i.spot), "Vat/not-safe-src"); + require(vtab <= mul(v.ink, i.spot), "Vat/not-safe-dst"); + + // both sides non-dusty + require(either(utab >= i.dust, u.art == 0), "Vat/dust-src"); + require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst"); + } + // --- CDP Confiscation --- + function grab(bytes32 i, address u, address v, address w, int dink, int dart) external note auth { + Urn storage urn = urns[i][u]; + Ilk storage ilk = ilks[i]; + + urn.ink = add(urn.ink, dink); + urn.art = add(urn.art, dart); + ilk.Art = add(ilk.Art, dart); + + int dtab = mul(ilk.rate, dart); + + gem[i][v] = sub(gem[i][v], dink); + sin[w] = sub(sin[w], dtab); + vice = sub(vice, dtab); + } + + // --- Settlement --- + function heal(uint rad) external note { + address u = msg.sender; + sin[u] = sub(sin[u], rad); + dai[u] = sub(dai[u], rad); + vice = sub(vice, rad); + debt = sub(debt, rad); + } + function suck(address u, address v, uint rad) external note auth { + sin[u] = add(sin[u], rad); + dai[v] = add(dai[v], rad); + vice = add(vice, rad); + debt = add(debt, rad); + } + + // --- Rates --- + function fold(bytes32 i, address u, int rate) external note auth { + require(live == 1, "Vat/not-live"); + Ilk storage ilk = ilks[i]; + ilk.rate = add(ilk.rate, rate); + int rad = mul(ilk.Art, rate); + dai[u] = add(dai[u], rad); + debt = add(debt, rad); + } +} diff --git a/certora/harness/tokens/MkrMock.sol b/certora/harness/tokens/MkrMock.sol new file mode 100644 index 00000000..aba48514 --- /dev/null +++ b/certora/harness/tokens/MkrMock.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.21; + +import {GemMock} from "../../../test/mocks/GemMock.sol"; + +contract MkrMock is GemMock { + + constructor(uint256 initialSupply) GemMock(initialSupply) { + } +} diff --git a/certora/harness/tokens/RewardsMock.sol b/certora/harness/tokens/RewardsMock.sol new file mode 100644 index 00000000..0e8e582b --- /dev/null +++ b/certora/harness/tokens/RewardsMock.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.21; + +import {GemMock} from "../../../test/mocks/GemMock.sol"; + +contract RewardsMock is GemMock { + + constructor(uint256 initialSupply) GemMock(initialSupply) { + } +} diff --git a/certora/harness/tokens/SkyMock.sol b/certora/harness/tokens/SkyMock.sol new file mode 100644 index 00000000..7ea9079b --- /dev/null +++ b/certora/harness/tokens/SkyMock.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.21; + +import {GemMock} from "../../../test/mocks/GemMock.sol"; + +contract SkyMock is GemMock { + + constructor(uint256 initialSupply) GemMock(initialSupply) { + } +} From 445b08a537cc1a586e116acad2f5c2351de8461a Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Mon, 21 Oct 2024 12:02:57 -0300 Subject: [PATCH 108/111] Fix tests after spell cast + minor update on README --- README.md | 8 ++++---- test/LockstakeEngine.t.sol | 32 ++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 78426fb1..2159c136 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,10 @@ It is assumed that the farm owner is trusted, the reward token implementation is **Liquidation Bark Gas Benchmarks** -Delegate: N, Staking: N - 483492 gas -Delegate: Y, Staking: Y, Yays: 1 - 614242 gas -Delegate: Y, Staking: Y, Yays: 5 - 646522 gas -Measured on: https://github.com/makerdao/lockstake/pull/38/commits/046b1a3c684b178dbd4a8dd8b3fb6e036a485115 +Delegate: N, Staking: N - 483456 gas +Delegate: Y, Staking: Y, Yays: 1 - 614201 gas +Delegate: Y, Staking: Y, Yays: 5 - 646481 gas +Measured on: https://github.com/makerdao/lockstake/commit/a9c7a3e16f1655bdb60f75253d986a9e70a61e51 For reference, a regular collateral bark cost is around 450K. Source: https://docs.google.com/spreadsheets/d/1ifb9ePno6KHNNGQA8s6u8KG7BRWa7fhUYH3Z5JGOxag/edit#gid=0 diff --git a/test/LockstakeEngine.t.sol b/test/LockstakeEngine.t.sol index 01d399bc..41aa44d1 100644 --- a/test/LockstakeEngine.t.sol +++ b/test/LockstakeEngine.t.sol @@ -39,7 +39,7 @@ contract LockstakeEngineTest is DssTest { LockstakeEngine engine; LockstakeClipper clip; address calc; - MedianAbstract pip; + OsmAbstract pip; VoteDelegateFactoryMock voteDelegateFactory; UsdsMock usds; UsdsJoinMock usdsJoin; @@ -84,13 +84,21 @@ contract LockstakeEngineTest is DssTest { } } + function _setMedianPrice(uint256 price) internal { + vm.store(pip.src(), bytes32(uint256(1)), bytes32(price)); + vm.warp(block.timestamp + 1 hours); + pip.poke(); + vm.warp(block.timestamp + 1 hours); + pip.poke(); + } + function setUp() public { vm.createSelectFork(vm.envString("ETH_RPC_URL")); dss = MCD.loadFromChainlog(LOG); pauseProxy = dss.chainlog.getAddress("MCD_PAUSE_PROXY"); - pip = MedianAbstract(dss.chainlog.getAddress("PIP_MKR")); + pip = OsmAbstract(dss.chainlog.getAddress("PIP_MKR")); mkr = DSTokenAbstract(dss.chainlog.getAddress("MCD_GOV")); usds = new UsdsMock(); usdsJoin = new UsdsJoinMock(address(dss.vat), address(usds)); @@ -106,7 +114,7 @@ contract LockstakeEngineTest is DssTest { vm.prank(voter); voteDelegate = voteDelegateFactory.create(); vm.prank(pauseProxy); pip.kiss(address(this)); - vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); + _setMedianPrice(1_500 * 10**18); LockstakeInstance memory instance = LockstakeDeploy.deployLockstake( address(this), @@ -1039,7 +1047,7 @@ contract LockstakeEngineTest is DssTest { } function _forceLiquidation(address urn) internal returns (uint256 id) { - vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(0.05 * 10**18))); // Force liquidation + _setMedianPrice(0.05 * 10**18); // Force liquidation dss.spotter.poke(ilk); assertEq(clip.kicks(), 0); assertEq(engine.urnAuctions(urn), 0); @@ -1065,7 +1073,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tot, 100_000 * 10**18); assertEq(sale.usr, address(urn)); assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); + assertEq(sale.top, uint256(pip.read()) * (1.25 * 10**9)); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); @@ -1114,7 +1122,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tot, 25_000 * 10**18); assertEq(sale.usr, address(urn)); assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); + assertEq(sale.top, uint256(pip.read()) * (1.25 * 10**9)); assertEq(_ink(ilk, urn), 75_000 * 10**18); assertEq(_art(ilk, urn), 1_500 * 10**18); @@ -1165,7 +1173,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tot, 100_000 * 10**18); assertEq(sale.usr, address(urn)); assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); + assertEq(sale.top, uint256(pip.read()) * (1.25 * 10**9)); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); @@ -1198,7 +1206,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tot, 100_000 * 10**18); assertEq(sale.usr, address(urn)); assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); + assertEq(sale.top, uint256(pip.read()) * (1.25 * 10**9)); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); @@ -1281,7 +1289,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tot, 100_000 * 10**18); assertEq(sale.usr, address(urn)); assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); + assertEq(sale.top, uint256(pip.read()) * (1.25 * 10**9)); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); @@ -1362,7 +1370,7 @@ contract LockstakeEngineTest is DssTest { assertEq(sale.tot, 100_000 * 10**18); assertEq(sale.usr, address(urn)); assertEq(sale.tic, block.timestamp); - assertEq(sale.top, pip.read() * (1.25 * 10**9)); + assertEq(sale.top, uint256(pip.read()) * (1.25 * 10**9)); assertEq(_ink(ilk, urn), 0); assertEq(_art(ilk, urn), 0); @@ -1493,7 +1501,7 @@ contract LockstakeEngineTest is DssTest { address voteDelegate2 = voteDelegateFactory.create(); - vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(0.05 * 10**18))); // Force urn unsafe + _setMedianPrice(0.05 * 10**18); // Force urn unsafe dss.spotter.poke(ilk); vm.expectRevert("LockstakeEngine/urn-unsafe"); @@ -1504,7 +1512,7 @@ contract LockstakeEngineTest is DssTest { vm.expectRevert("LockstakeEngine/urn-unsafe"); engine.selectVoteDelegate(address(this), 0, voteDelegate2); - vm.store(address(pip), bytes32(uint256(1)), bytes32(uint256(1_500 * 10**18))); // Back to safety + _setMedianPrice(1_500 * 10**18); // Back to safety dss.spotter.poke(ilk); engine.selectVoteDelegate(address(this), 0, voteDelegate2); From 444e723fd0832ad8b2eeb2afffec641c983ce064 Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 22 Oct 2024 13:30:14 -0300 Subject: [PATCH 109/111] Certora: minor changes --- certora/LockstakeClipper.spec | 27 +- certora/LockstakeEngine.spec | 357 +++++++++++++------------- certora/LockstakeEngineMulticall.conf | 2 +- certora/LockstakeEngineMulticall.spec | 4 +- 4 files changed, 193 insertions(+), 197 deletions(-) diff --git a/certora/LockstakeClipper.spec b/certora/LockstakeClipper.spec index 3db6b1f8..09d61168 100644 --- a/certora/LockstakeClipper.spec +++ b/certora/LockstakeClipper.spec @@ -84,7 +84,6 @@ methods { ) external => DISPATCHER(true); } -definition addrZero() returns address = 0x0000000000000000000000000000000000000000; definition max_int256() returns mathint = 2^255 - 1; definition WAD() returns mathint = 10^18; definition RAY() returns mathint = 10^27; @@ -416,9 +415,9 @@ rule kick_revert(uint256 tab, uint256 lot, address usr, address kpr) { require usr == lockstakeUrn; address prevVoteDelegate = lockstakeEngine.urnVoteDelegates(usr); - require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate; + require prevVoteDelegate == 0 || prevVoteDelegate == voteDelegate; address prevFarm = lockstakeEngine.urnFarms(usr); - require prevFarm == addrZero() || prevFarm == stakingRewards; + require prevFarm == 0 || prevFarm == stakingRewards; mathint wardsSender = wards(e.msg.sender); mathint locked = lockedGhost(); @@ -446,11 +445,11 @@ rule kick_revert(uint256 tab, uint256 lot, address usr, address kpr) { require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(prevFarm) + lsmkr.balanceOf(usr) + lsmkr.balanceOf(lockstakeEngine); require stakingRewards.totalSupply() >= stakingRewards.balanceOf(usr); // VoteDelegate assumptions - require prevVoteDelegate == addrZero() || to_mathint(voteDelegate.stake(lockstakeEngine)) >= vatUrnsIlkUsrInk + lot; - require prevVoteDelegate == addrZero() || mkr.balanceOf(voteDelegate) >= voteDelegate.stake(lockstakeEngine); + require prevVoteDelegate == 0 || to_mathint(voteDelegate.stake(lockstakeEngine)) >= vatUrnsIlkUsrInk + lot; + require prevVoteDelegate == 0 || mkr.balanceOf(voteDelegate) >= voteDelegate.stake(lockstakeEngine); // StakingRewards assumptions - require prevFarm == addrZero() && lsmkr.balanceOf(usr) >= lot || - prevFarm != addrZero() && to_mathint(stakingRewards.balanceOf(usr)) >= vatUrnsIlkUsrInk + lot && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUsrInk + lot; + require prevFarm == 0 && lsmkr.balanceOf(usr) >= lot || + prevFarm != 0 && to_mathint(stakingRewards.balanceOf(usr)) >= vatUrnsIlkUsrInk + lot && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUsrInk + lot; // Practical Vat assumptions require vat.sin(vow()) + coin <= max_uint256; require vat.dai(kpr) + coin <= max_uint256; @@ -471,7 +470,7 @@ rule kick_revert(uint256 tab, uint256 lot, address usr, address kpr) { bool revert5 = tab == 0; bool revert6 = lot == 0; bool revert7 = to_mathint(lot) > max_int256(); - bool revert8 = usr == addrZero(); + bool revert8 = usr == 0; bool revert9 = kicks == max_uint256; bool revert10 = count == max_uint256; bool revert11 = !has; @@ -580,7 +579,7 @@ rule redo_revert(uint256 id, address kpr) { bool revert1 = e.msg.value > 0; bool revert2 = locked != 0; bool revert3 = stopped >= 2; - bool revert4 = salesIdUsr == addrZero(); + bool revert4 = salesIdUsr == 0; bool revert5 = to_mathint(e.block.timestamp) < salesIdTic; bool revert6 = e.block.timestamp - salesIdTic <= tail && price * RAY() > max_uint256; bool revert7 = !done; @@ -712,7 +711,7 @@ rule take(uint256 id, uint256 amt, uint256 max, address who, bytes data) { assert salesIdTabAfter == (isRemoved ? 0 : calcTabAfter), "Assert 3"; assert salesIdLotAfter == (isRemoved ? 0 : calcLotAfter), "Assert 4"; assert salesIdTotAfter == (isRemoved ? 0 : salesIdTotBefore), "Assert 5"; - assert salesIdUsrAfter == (isRemoved ? addrZero() : salesIdUsrBefore), "Assert 6"; + assert salesIdUsrAfter == (isRemoved ? 0 : salesIdUsrBefore), "Assert 6"; assert salesIdTicAfter == (isRemoved ? 0 : salesIdTicBefore), "Assert 7"; assert salesIdTopAfter == (isRemoved ? 0 : salesIdTopBefore), "Assert 8"; assert salesOtherPosAfter == (to_mathint(otherUint256) == activeLastBefore && isRemoved ? salesIdPosBefore : salesOtherPosBefore), "Assert 9"; @@ -833,7 +832,7 @@ rule take_revert(uint256 id, uint256 amt, uint256 max, address who, bytes data) require sold * fee <= max_uint256; require refund <= max_int256(); require vat.gem(ilk, salesIdUsr) + refund <= max_uint256; - require salesIdUsr != addrZero() && salesIdUsr != lsmkr; + require salesIdUsr != 0 && salesIdUsr != lsmkr; require lsmkr.totalSupply() + refund <= max_uint256; // Dog assumptions require dogDirt >= digAmt; @@ -853,7 +852,7 @@ rule take_revert(uint256 id, uint256 amt, uint256 max, address who, bytes data) bool revert1 = e.msg.value > 0; bool revert2 = locked != 0; bool revert3 = stopped >= 3; - bool revert4 = salesIdUsr == addrZero(); + bool revert4 = salesIdUsr == 0; bool revert5 = price * RAY() > max_uint256; bool revert6 = done; bool revert7 = to_mathint(max) < price; @@ -962,7 +961,7 @@ rule yank(uint256 id) { assert salesIdTabAfter == 0, "Assert 3"; assert salesIdLotAfter == 0, "Assert 4"; assert salesIdTotAfter == 0, "Assert 5"; - assert salesIdUsrAfter == addrZero(), "Assert 6"; + assert salesIdUsrAfter == 0, "Assert 6"; assert salesIdTicAfter == 0, "Assert 7"; assert salesIdTopAfter == 0, "Assert 8"; assert salesOtherPosAfter == (to_mathint(otherUint256) == activeLastBefore ? salesIdPosBefore : salesOtherPosBefore), "Assert 9"; @@ -1025,7 +1024,7 @@ rule yank_revert(uint256 id) { bool revert1 = e.msg.value > 0; bool revert2 = wardsSender != 1; bool revert3 = locked != 0; - bool revert4 = salesIdUsr == addrZero(); + bool revert4 = salesIdUsr == 0; bool revert5 = vatGemIlkClipper < salesIdLot; bool revert6 = count == 0 || to_mathint(id) != activeLast && salesIdPos > count - 1; diff --git a/certora/LockstakeEngine.spec b/certora/LockstakeEngine.spec index 6e68a596..4d0a9bea 100644 --- a/certora/LockstakeEngine.spec +++ b/certora/LockstakeEngine.spec @@ -99,7 +99,6 @@ methods { function _.transferFrom(address,address,uint256) external => DISPATCHER(true); } -definition addrZero() returns address = 0x0000000000000000000000000000000000000000; definition max_int256() returns mathint = 2^255 - 1; definition min_int256() returns mathint = -2^255; definition WAD() returns mathint = 10^18; @@ -241,7 +240,7 @@ rule inkChangeMatchesMkrChange(method f) filtered { f -> f.selector != sig:multi require urn != currentContract && urn != voteDelegate && urn != voteDelegate2; address voteDelegateAfter = urnVoteDelegates(urn); - require voteDelegateAfter == addrZero() || voteDelegateAfter == voteDelegate || voteDelegateAfter == voteDelegate2; + require voteDelegateAfter == 0 || voteDelegateAfter == voteDelegate || voteDelegateAfter == voteDelegate2; ilk() at init; @@ -251,13 +250,13 @@ rule inkChangeMatchesMkrChange(method f) filtered { f -> f.selector != sig:multi bytes32 ilk = ilk(); address voteDelegateBefore = urnVoteDelegates(urn); - require voteDelegateBefore == addrZero() || voteDelegateBefore == voteDelegate; + require voteDelegateBefore == 0 || voteDelegateBefore == voteDelegate; mathint vatUrnsIlkUrnInkBefore; mathint a; vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); mathint mkrTotalSupplyBefore = mkr.totalSupply(); mathint mkrBalanceOfEngineBefore = mkr.balanceOf(currentContract); - mathint mkrBalanceOfVoteDelegateBeforeBefore = voteDelegateBefore == addrZero() ? 0 : mkr.balanceOf(voteDelegateBefore); - mathint mkrBalanceOfVoteDelegateAfterBefore = voteDelegateAfter == addrZero() ? 0 : mkr.balanceOf(voteDelegateAfter); + mathint mkrBalanceOfVoteDelegateBeforeBefore = voteDelegateBefore == 0 ? 0 : mkr.balanceOf(voteDelegateBefore); + mathint mkrBalanceOfVoteDelegateAfterBefore = voteDelegateAfter == 0 ? 0 : mkr.balanceOf(voteDelegateAfter); require mkr.balanceOf(e.msg.sender) + mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBeforeBefore + mkrBalanceOfVoteDelegateAfterBefore <= mkr.totalSupply(); require mkrBalanceOfEngineBefore + mkrBalanceOfVoteDelegateBeforeBefore >= vatUrnsIlkUrnInkBefore; @@ -267,20 +266,20 @@ rule inkChangeMatchesMkrChange(method f) filtered { f -> f.selector != sig:multi vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); mathint mkrTotalSupplyAfter = mkr.totalSupply(); mathint mkrBalanceOfEngineAfter = mkr.balanceOf(currentContract); - mathint mkrBalanceOfVoteDelegateBeforeAfter = voteDelegateBefore == addrZero() ? 0 : mkr.balanceOf(voteDelegateBefore); - mathint mkrBalanceOfVoteDelegateAfterAfter = voteDelegateAfter == addrZero() ? 0 : mkr.balanceOf(voteDelegateAfter); - require f.selector == sig:onRemove(address,uint256,uint256).selector => voteDelegateBefore == addrZero(); + mathint mkrBalanceOfVoteDelegateBeforeAfter = voteDelegateBefore == 0 ? 0 : mkr.balanceOf(voteDelegateBefore); + mathint mkrBalanceOfVoteDelegateAfterAfter = voteDelegateAfter == 0 ? 0 : mkr.balanceOf(voteDelegateAfter); + require f.selector == sig:onRemove(address,uint256,uint256).selector => voteDelegateBefore == 0; mathint burntOnRemove = f.selector == sig:onRemove(address,uint256,uint256).selector ? mkrTotalSupplyBefore - mkrTotalSupplyAfter + vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore : 0; mathint transferredOnTake = f.selector == sig:onTake(address,address,uint256).selector ? mkrBalanceOfEngineBefore - mkrBalanceOfEngineAfter : 0; mathint receivedOnTake = f.selector == sig:onTake(address,address,uint256).selector ? mkrBalanceOfVoteDelegateBeforeAfter - mkrBalanceOfVoteDelegateBeforeBefore : 0; // It checks that the ink change matches the MKR balance change + that is all or nothing delegated - assert voteDelegateAfter == voteDelegateBefore && voteDelegateBefore == addrZero() => + assert voteDelegateAfter == voteDelegateBefore && voteDelegateBefore == 0 => vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == mkrBalanceOfEngineAfter - mkrBalanceOfEngineBefore + burntOnRemove + transferredOnTake, "Assert 1"; - assert voteDelegateAfter == voteDelegateBefore && voteDelegateBefore != addrZero() => + assert voteDelegateAfter == voteDelegateBefore && voteDelegateBefore != 0 => vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == mkrBalanceOfVoteDelegateBeforeAfter - mkrBalanceOfVoteDelegateBeforeBefore - receivedOnTake && mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - transferredOnTake, "Assert 2"; - assert voteDelegateAfter != voteDelegateBefore && voteDelegateBefore != addrZero() && voteDelegateAfter != addrZero() => + assert voteDelegateAfter != voteDelegateBefore && voteDelegateBefore != 0 && voteDelegateAfter != 0 => mkrBalanceOfVoteDelegateBeforeAfter - mkrBalanceOfVoteDelegateBeforeBefore == mkrBalanceOfVoteDelegateAfterBefore - mkrBalanceOfVoteDelegateAfterAfter, "Assert3"; } @@ -298,29 +297,29 @@ rule inkChangeMatchesLsmkrChange(method f) filtered { f -> f.selector != sig:mul address urn = createdUrn != 0 ? createdUrn : (queriedUrn != 0 ? queriedUrn : (passedUrn != 0 ? passedUrn : 0)); address farmAfter = urnFarms(urn); - require farmAfter == addrZero() || farmAfter == stakingRewards || farmAfter == stakingRewards2; + require farmAfter == 0 || farmAfter == stakingRewards || farmAfter == stakingRewards2; ilk() at init; bytes32 ilk = ilk(); address farmBefore = urnFarms(urn); - require farmBefore == addrZero() || farmBefore == stakingRewards; + require farmBefore == 0 || farmBefore == stakingRewards; mathint vatUrnsIlkUrnInkBefore; mathint a; vatUrnsIlkUrnInkBefore, a = vat.urns(ilk, urn); mathint lsmkrTotalSupplyBefore = lsmkr.totalSupply(); mathint lsmkrBalanceOfUrnBefore = lsmkr.balanceOf(urn); - mathint lsmkrBalanceOfFarmBeforeBefore = farmBefore == addrZero() ? 0 : lsmkr.balanceOf(farmBefore); - mathint lsmkrBalanceOfFarmAfterBefore = farmAfter == addrZero() ? 0 : lsmkr.balanceOf(farmAfter); + mathint lsmkrBalanceOfFarmBeforeBefore = farmBefore == 0 ? 0 : lsmkr.balanceOf(farmBefore); + mathint lsmkrBalanceOfFarmAfterBefore = farmAfter == 0 ? 0 : lsmkr.balanceOf(farmAfter); require lsmkrBalanceOfUrnBefore + lsmkrBalanceOfFarmBeforeBefore + lsmkrBalanceOfFarmAfterBefore <= lsmkrTotalSupplyBefore; require vatUrnsIlkUrnInkBefore <= lsmkrTotalSupplyBefore; mathint farmBeforeBalanceOfUrnBefore = 0; - if (farmBefore != addrZero()) { + if (farmBefore != 0) { farmBeforeBalanceOfUrnBefore = farmBefore.balanceOf(e, urn); require farmBeforeBalanceOfUrnBefore <= to_mathint(farmBefore.totalSupply(e)); } mathint farmAfterBalanceOfUrnBefore = 0; - if (farmAfter != addrZero()) { + if (farmAfter != 0) { farmAfterBalanceOfUrnBefore = farmAfter.balanceOf(e, urn); require farmAfterBalanceOfUrnBefore <= to_mathint(farmAfter.totalSupply(e)); } @@ -333,44 +332,44 @@ rule inkChangeMatchesLsmkrChange(method f) filtered { f -> f.selector != sig:mul vatUrnsIlkUrnInkAfter, a = vat.urns(ilk, urn); mathint lsmkrTotalSupplyAfter = lsmkr.totalSupply(); mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); - mathint lsmkrBalanceOfFarmBeforAfter = farmBefore == addrZero() ? 0 : lsmkr.balanceOf(farmBefore); - mathint lsmkrBalanceOfFarmAfterAfter = farmAfter == addrZero() ? 0 : lsmkr.balanceOf(farmAfter); + mathint lsmkrBalanceOfFarmBeforAfter = farmBefore == 0 ? 0 : lsmkr.balanceOf(farmBefore); + mathint lsmkrBalanceOfFarmAfterAfter = farmAfter == 0 ? 0 : lsmkr.balanceOf(farmAfter); mathint farmBeforeBalanceOfUrnAfter = 0; - if (farmBefore != addrZero()) { + if (farmBefore != 0) { farmBeforeBalanceOfUrnAfter = farmBefore.balanceOf(e, urn); require farmBeforeBalanceOfUrnAfter <= to_mathint(farmBefore.totalSupply(e)); } mathint farmAfterBalanceOfUrnAfter = 0; - if (farmAfter != addrZero()) { + if (farmAfter != 0) { farmAfterBalanceOfUrnAfter = farmAfter.balanceOf(e, urn); require farmAfterBalanceOfUrnAfter <= to_mathint(farmAfter.totalSupply(e)); } mathint stakingRewardsBalanceOfUrnAfter = stakingRewards.balanceOf(urn); mathint stakingRewards2BalanceOfUrnAfter = stakingRewards2.balanceOf(urn); - require farmBefore != addrZero() => lsmkrBalanceOfUrnBefore == 0; + require farmBefore != 0 => lsmkrBalanceOfUrnBefore == 0; require farmBefore == stakingRewards => stakingRewards2BalanceOfUrnBefore == 0; require farmBefore == stakingRewards2 => stakingRewardsBalanceOfUrnBefore == 0; - require farmBefore == addrZero() => stakingRewardsBalanceOfUrnBefore == 0 && stakingRewards2BalanceOfUrnBefore == 0; + require farmBefore == 0 => stakingRewardsBalanceOfUrnBefore == 0 && stakingRewards2BalanceOfUrnBefore == 0; mathint burntOnKick = f.selector == sig:onKick(address,uint256).selector ? lsmkrTotalSupplyBefore - lsmkrTotalSupplyAfter : 0; require vatUrnsIlkUrnInkBefore == lsmkrBalanceOfUrnBefore + stakingRewardsBalanceOfUrnBefore + stakingRewards2BalanceOfUrnBefore - burntOnKick; - require f.selector == sig:onRemove(address,uint256,uint256).selector => farmBefore == addrZero(); + require f.selector == sig:onRemove(address,uint256,uint256).selector => farmBefore == 0; assert vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == lsmkrTotalSupplyAfter - lsmkrTotalSupplyBefore + burntOnKick, "Assert 1"; - assert farmAfter == farmBefore && farmBefore == addrZero() => + assert farmAfter == farmBefore && farmBefore == 0 => vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == lsmkrBalanceOfUrnAfter - lsmkrBalanceOfUrnBefore + burntOnKick, "Assert 2"; - assert farmAfter == farmBefore && farmBefore != addrZero() => + assert farmAfter == farmBefore && farmBefore != 0 => vatUrnsIlkUrnInkAfter - vatUrnsIlkUrnInkBefore == lsmkrBalanceOfFarmBeforAfter - lsmkrBalanceOfFarmBeforeBefore, "Assert 3"; - assert farmAfter != farmBefore && farmBefore == addrZero() => + assert farmAfter != farmBefore && farmBefore == 0 => vatUrnsIlkUrnInkAfter == lsmkrBalanceOfFarmAfterAfter - lsmkrBalanceOfFarmAfterBefore && vatUrnsIlkUrnInkBefore == lsmkrBalanceOfUrnBefore - lsmkrBalanceOfUrnAfter, "Assert 4"; - assert farmAfter != farmBefore && farmAfter == addrZero() => + assert farmAfter != farmBefore && farmAfter == 0 => vatUrnsIlkUrnInkAfter == lsmkrBalanceOfUrnAfter - lsmkrBalanceOfUrnBefore && vatUrnsIlkUrnInkBefore == lsmkrBalanceOfFarmBeforeBefore - lsmkrBalanceOfFarmBeforAfter - burntOnKick, "Assert 5"; - assert farmAfter != farmBefore && farmBefore != addrZero() && farmAfter != addrZero() => + assert farmAfter != farmBefore && farmBefore != 0 && farmAfter != 0 => vatUrnsIlkUrnInkAfter == lsmkrBalanceOfFarmAfterAfter - lsmkrBalanceOfFarmAfterBefore && vatUrnsIlkUrnInkBefore == lsmkrBalanceOfFarmBeforeBefore - lsmkrBalanceOfFarmBeforAfter, "Assert 6"; - assert farmAfter == addrZero() => + assert farmAfter == 0 => lsmkrBalanceOfUrnAfter == vatUrnsIlkUrnInkAfter && stakingRewardsBalanceOfUrnAfter == 0 && stakingRewards2BalanceOfUrnAfter == 0, "Assert 7"; assert farmAfter == stakingRewards => stakingRewardsBalanceOfUrnAfter == vatUrnsIlkUrnInkAfter && lsmkrBalanceOfUrnAfter == 0 && stakingRewards2BalanceOfUrnAfter == 0, "Assert 8"; @@ -387,30 +386,30 @@ rule inkMatchesLsmkrFarmOnKick(address urn, uint256 wad) { bytes32 ilk = ilk(); address farmBefore = urnFarms(anyUrn); - require farmBefore == addrZero() || farmBefore == stakingRewards; + require farmBefore == 0 || farmBefore == stakingRewards; mathint vatUrnsIlkAnyUrnInkBefore; mathint a; vatUrnsIlkAnyUrnInkBefore, a = vat.urns(ilk, anyUrn); mathint lsmkrBalanceOfAnyUrnBefore = lsmkr.balanceOf(anyUrn); - mathint farmBalanceOfAnyUrnBefore = farmBefore == addrZero() ? 0 : stakingRewards.balanceOf(anyUrn); + mathint farmBalanceOfAnyUrnBefore = farmBefore == 0 ? 0 : stakingRewards.balanceOf(anyUrn); require stakingRewards2.balanceOf(anyUrn) == 0; require lsmkrBalanceOfAnyUrnBefore == 0 || farmBalanceOfAnyUrnBefore == 0; - require lsmkrBalanceOfAnyUrnBefore > 0 => farmBefore == addrZero(); - require farmBalanceOfAnyUrnBefore > 0 => farmBefore != addrZero(); + require lsmkrBalanceOfAnyUrnBefore > 0 => farmBefore == 0; + require farmBalanceOfAnyUrnBefore > 0 => farmBefore != 0; require vatUrnsIlkAnyUrnInkBefore == lsmkrBalanceOfAnyUrnBefore + farmBalanceOfAnyUrnBefore; onKick(e, urn, wad); address farmAfter = urnFarms(anyUrn); - require farmAfter == addrZero() || farmAfter == farmBefore || farmAfter != farmBefore && farmAfter == stakingRewards2; + require farmAfter == 0 || farmAfter == farmBefore || farmAfter != farmBefore && farmAfter == stakingRewards2; mathint vatUrnsIlkAnyUrnInkAfter; vatUrnsIlkAnyUrnInkAfter, a = vat.urns(ilk, anyUrn); mathint lsmkrBalanceOfAnyUrnAfter = lsmkr.balanceOf(anyUrn); - mathint farmBalanceOfAnyUrnAfter = farmAfter == addrZero() ? 0 : (farmAfter == farmBefore ? stakingRewards.balanceOf(anyUrn) : stakingRewards2.balanceOf(anyUrn)); + mathint farmBalanceOfAnyUrnAfter = farmAfter == 0 ? 0 : (farmAfter == farmBefore ? stakingRewards.balanceOf(anyUrn) : stakingRewards2.balanceOf(anyUrn)); assert urn != anyUrn => vatUrnsIlkAnyUrnInkAfter == lsmkrBalanceOfAnyUrnAfter + farmBalanceOfAnyUrnAfter, "Assert 1"; assert urn == anyUrn => vatUrnsIlkAnyUrnInkAfter == lsmkrBalanceOfAnyUrnAfter + farmBalanceOfAnyUrnAfter + wad, "Assert 2"; @@ -654,7 +653,7 @@ rule hope_revert(address owner, uint256 index, address usr) { hope@withrevert(e, owner, index, usr); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; @@ -690,7 +689,7 @@ rule nope_revert(address owner, uint256 index, address usr) { nope@withrevert(e, owner, index, usr); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; @@ -701,9 +700,9 @@ rule selectVoteDelegate(address owner, uint256 index, address voteDelegate_) { env e; address urn = ownerUrns(owner, index); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address prevVoteDelegate = urnVoteDelegates(urn); - require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate2; + require prevVoteDelegate == 0 || prevVoteDelegate == voteDelegate2; address other; require other != urn; @@ -734,13 +733,13 @@ rule selectVoteDelegate(address owner, uint256 index, address voteDelegate_) { assert urnVoteDelegatesUrnAfter == voteDelegate_, "Assert 1"; assert urnVoteDelegatesOtherAfter == urnVoteDelegatesOtherBefore, "Assert 2"; - assert prevVoteDelegate == addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore, "Assert 3"; - assert prevVoteDelegate != addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore - vatUrnsIlkUrnInk, "Assert 4"; - assert voteDelegate_ == addrZero() => mkrBalanceOfNewVoteDelegateAfter == mkrBalanceOfNewVoteDelegateBefore, "Assert 5"; - assert voteDelegate_ != addrZero() => mkrBalanceOfNewVoteDelegateAfter == mkrBalanceOfNewVoteDelegateBefore + vatUrnsIlkUrnInk, "Assert 6"; - assert prevVoteDelegate == addrZero() && voteDelegate_ == addrZero() || prevVoteDelegate != addrZero() && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 7"; - assert prevVoteDelegate == addrZero() && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - vatUrnsIlkUrnInk, "Assert 8"; - assert prevVoteDelegate != addrZero() && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + vatUrnsIlkUrnInk, "Assert 9"; + assert prevVoteDelegate == 0 => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore, "Assert 3"; + assert prevVoteDelegate != 0 => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore - vatUrnsIlkUrnInk, "Assert 4"; + assert voteDelegate_ == 0 => mkrBalanceOfNewVoteDelegateAfter == mkrBalanceOfNewVoteDelegateBefore, "Assert 5"; + assert voteDelegate_ != 0 => mkrBalanceOfNewVoteDelegateAfter == mkrBalanceOfNewVoteDelegateBefore + vatUrnsIlkUrnInk, "Assert 6"; + assert prevVoteDelegate == 0 && voteDelegate_ == 0 || prevVoteDelegate != 0 && voteDelegate_ != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 7"; + assert prevVoteDelegate == 0 && voteDelegate_ != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - vatUrnsIlkUrnInk, "Assert 8"; + assert prevVoteDelegate != 0 && voteDelegate_ == 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + vatUrnsIlkUrnInk, "Assert 9"; assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 10"; } @@ -749,9 +748,9 @@ rule selectVoteDelegate_revert(address owner, uint256 index, address voteDelegat env e; address urn = ownerUrns(owner, index); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address prevVoteDelegate = urnVoteDelegates(urn); - require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate2; + require prevVoteDelegate == 0 || prevVoteDelegate == voteDelegate2; mathint urnCanUrnSender = urnCan(urn, e.msg.sender); mathint urnAuctions = urnAuctions(urn); @@ -769,18 +768,18 @@ rule selectVoteDelegate_revert(address owner, uint256 index, address voteDelegat require vatUrnsIlkUrnInk * vatIlksIlkSpot <= max_uint256; require vatUrnsIlkUrnArt * calcVatIlksIlkRateAfter <= max_uint256; // TODO: this might be nice to prove in some sort - require prevVoteDelegate == addrZero() && to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk || prevVoteDelegate != addrZero() && to_mathint(mkr.balanceOf(prevVoteDelegate)) >= vatUrnsIlkUrnInk && to_mathint(voteDelegate2.stake(currentContract)) >= vatUrnsIlkUrnInk; // TODO: this might be interesting to be proved + require prevVoteDelegate == 0 && to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk || prevVoteDelegate != 0 && to_mathint(mkr.balanceOf(prevVoteDelegate)) >= vatUrnsIlkUrnInk && to_mathint(voteDelegate2.stake(currentContract)) >= vatUrnsIlkUrnInk; // TODO: this might be interesting to be proved require voteDelegate.stake(currentContract) + vatUrnsIlkUrnInk <= max_uint256; selectVoteDelegate@withrevert(e, owner, index, voteDelegate_); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; bool revert4 = urnAuctions > 0; - bool revert5 = voteDelegate_ != addrZero() && voteDelegateFactoryCreatedVoteDelegate != 1; + bool revert5 = voteDelegate_ != 0 && voteDelegateFactoryCreatedVoteDelegate != 1; bool revert6 = voteDelegate_ == prevVoteDelegate; - bool revert7 = vatUrnsIlkUrnArt > 0 && voteDelegate_ != addrZero() && vatUrnsIlkUrnInk * vatIlksIlkSpot < vatUrnsIlkUrnArt * calcVatIlksIlkRateAfter; + bool revert7 = vatUrnsIlkUrnArt > 0 && voteDelegate_ != 0 && vatUrnsIlkUrnInk * vatIlksIlkSpot < vatUrnsIlkUrnArt * calcVatIlksIlkRateAfter; assert lastReverted <=> revert1 || revert2 || revert3 || revert4 || revert5 || revert6 || @@ -794,9 +793,9 @@ rule selectFarm(address owner, uint256 index, address farm, uint16 ref) { address urn = ownerUrns(owner, index); require urn == lockstakeUrn; - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; address prevFarm = urnFarms(urn); - require prevFarm == addrZero() || prevFarm == stakingRewards2; + require prevFarm == 0 || prevFarm == stakingRewards2; address other; require other != urn; @@ -827,13 +826,13 @@ rule selectFarm(address owner, uint256 index, address farm, uint16 ref) { assert urnFarmsUrnAfter == farm, "Assert 1"; assert urnFarmsOtherAfter == urnFarmsOtherBefore, "Assert 2"; - assert prevFarm == addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore, "Assert 3"; - assert prevFarm != addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore - vatUrnsIlkUrnInk, "Assert 4"; - assert farm == addrZero() => lsmkrBalanceOfNewFarmAfter == lsmkrBalanceOfNewFarmBefore, "Assert 5"; - assert farm != addrZero() => lsmkrBalanceOfNewFarmAfter == lsmkrBalanceOfNewFarmBefore + vatUrnsIlkUrnInk, "Assert 6"; - assert prevFarm == addrZero() && farm == addrZero() || prevFarm != addrZero() && farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 7"; - assert prevFarm == addrZero() && farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - vatUrnsIlkUrnInk, "Assert 8"; - assert prevFarm != addrZero() && farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + vatUrnsIlkUrnInk, "Assert 9"; + assert prevFarm == 0 => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore, "Assert 3"; + assert prevFarm != 0 => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore - vatUrnsIlkUrnInk, "Assert 4"; + assert farm == 0 => lsmkrBalanceOfNewFarmAfter == lsmkrBalanceOfNewFarmBefore, "Assert 5"; + assert farm != 0 => lsmkrBalanceOfNewFarmAfter == lsmkrBalanceOfNewFarmBefore + vatUrnsIlkUrnInk, "Assert 6"; + assert prevFarm == 0 && farm == 0 || prevFarm != 0 && farm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 7"; + assert prevFarm == 0 && farm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - vatUrnsIlkUrnInk, "Assert 8"; + assert prevFarm != 0 && farm == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + vatUrnsIlkUrnInk, "Assert 9"; assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 10"; } @@ -844,9 +843,9 @@ rule selectFarm_revert(address owner, uint256 index, address farm, uint16 ref) { address urn = ownerUrns(owner, index); require urn == lockstakeUrn; - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; address prevFarm = urnFarms(urn); - require prevFarm == addrZero() || prevFarm == stakingRewards2; + require prevFarm == 0 || prevFarm == stakingRewards2; address urnOwnersUrn = urnOwners(urn); mathint urnCanUrnSender = urnCan(urn, e.msg.sender); @@ -857,7 +856,7 @@ rule selectFarm_revert(address owner, uint256 index, address farm, uint16 ref) { vatUrnsIlkUrnInk, a = vat.urns(ilk, urn); // TODO: this might be nice to prove in some sort - require prevFarm == addrZero() && to_mathint(lsmkr.balanceOf(urn)) >= vatUrnsIlkUrnInk || prevFarm != addrZero() && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUrnInk && to_mathint(stakingRewards2.balanceOf(urn)) >= vatUrnsIlkUrnInk; + require prevFarm == 0 && to_mathint(lsmkr.balanceOf(urn)) >= vatUrnsIlkUrnInk || prevFarm != 0 && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUrnInk && to_mathint(stakingRewards2.balanceOf(urn)) >= vatUrnsIlkUrnInk; // Token invariants require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(prevFarm) + lsmkr.balanceOf(farm) + lsmkr.balanceOf(urn); require stakingRewards2.totalSupply() >= stakingRewards2.balanceOf(urn); @@ -868,10 +867,10 @@ rule selectFarm_revert(address owner, uint256 index, address farm, uint16 ref) { selectFarm@withrevert(e, owner, index, farm, ref); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; bool revert4 = urnAuctions > 0; - bool revert5 = farm != addrZero() && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; + bool revert5 = farm != 0 && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; bool revert6 = farm == prevFarm; assert lastReverted <=> revert1 || revert2 || revert3 || @@ -886,9 +885,9 @@ rule lock(address owner, uint256 index, uint256 wad, uint16 ref) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; @@ -928,16 +927,16 @@ rule lock(address owner, uint256 index, uint256 wad, uint16 ref) { assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore + wad, "Assert 1"; assert mkrBalanceOfSenderAfter == mkrBalanceOfSenderBefore - wad, "Assert 2"; - assert voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 3"; - assert voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore + wad, "Assert 4"; - assert voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + wad, "Assert 5"; - assert voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 6"; + assert voteDelegate_ == 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 3"; + assert voteDelegate_ != 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore + wad, "Assert 4"; + assert voteDelegate_ == 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + wad, "Assert 5"; + assert voteDelegate_ != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 6"; assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 7"; assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore + wad, "Assert 8"; - assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 9"; - assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore + wad, "Assert 10"; - assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + wad, "Assert 11"; - assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 12"; + assert farm == 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 9"; + assert farm != 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore + wad, "Assert 10"; + assert farm == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + wad, "Assert 11"; + assert farm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 12"; assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 13"; } @@ -949,9 +948,9 @@ rule lock_revert(address owner, uint256 index, uint256 wad, uint16 ref) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; @@ -993,10 +992,10 @@ rule lock_revert(address owner, uint256 index, uint256 wad, uint16 ref) { lock@withrevert(e, owner, index, wad, ref); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = to_mathint(wad) > max_int256(); - bool revert4 = farm != addrZero() && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; - bool revert5 = farm != addrZero() && wad == 0; + bool revert4 = farm != 0 && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; + bool revert5 = farm != 0 && wad == 0; assert lastReverted <=> revert1 || revert2 || revert3 || revert4 || revert5, "Revert rules failed"; @@ -1010,9 +1009,9 @@ rule lockSky(address owner, uint256 index, uint256 skyWad, uint16 ref) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; @@ -1065,16 +1064,16 @@ rule lockSky(address owner, uint256 index, uint256 skyWad, uint16 ref) { assert skyTotalSupplyAfter == skyTotalSupplyBefore - skyWad, "Assert 2"; assert skyBalanceOfSenderAfter == skyBalanceOfSenderBefore - skyWad, "Assert 3"; assert mkrTotalSupplyAfter == mkrTotalSupplyBefore + skyWad/mkrSkyRate, "Assert 4"; - assert voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; - assert voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore + skyWad/mkrSkyRate, "Assert 6"; - assert voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + skyWad/mkrSkyRate, "Assert 7"; - assert voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; + assert voteDelegate_ == 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; + assert voteDelegate_ != 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore + skyWad/mkrSkyRate, "Assert 6"; + assert voteDelegate_ == 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + skyWad/mkrSkyRate, "Assert 7"; + assert voteDelegate_ != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 9"; assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore + skyWad/mkrSkyRate, "Assert 10"; - assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; - assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore + skyWad/mkrSkyRate, "Assert 12"; - assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + skyWad/mkrSkyRate, "Assert 13"; - assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; + assert farm == 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; + assert farm != 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore + skyWad/mkrSkyRate, "Assert 12"; + assert farm == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + skyWad/mkrSkyRate, "Assert 13"; + assert farm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 15"; } @@ -1086,9 +1085,9 @@ rule lockSky_revert(address owner, uint256 index, uint256 skyWad, uint16 ref) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; @@ -1140,10 +1139,10 @@ rule lockSky_revert(address owner, uint256 index, uint256 skyWad, uint16 ref) { lockSky@withrevert(e, owner, index, skyWad, ref); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = skyWad/mkrSkyRate > max_int256(); - bool revert4 = farm != addrZero() && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; - bool revert5 = farm != addrZero() && skyWad/mkrSkyRate == 0; + bool revert4 = farm != 0 && farmsFarm != LockstakeEngine.FarmStatus.ACTIVE; + bool revert5 = farm != 0 && skyWad/mkrSkyRate == 0; assert lastReverted <=> revert1 || revert2 || revert3 || revert4 || revert5, "Revert rules failed"; @@ -1157,9 +1156,9 @@ rule free(address owner, uint256 index, address to, uint256 wad) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; address other; require other != to && other != currentContract && other != voteDelegate_; @@ -1204,20 +1203,20 @@ rule free(address owner, uint256 index, address to, uint256 wad) { assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore - wad, "Assert 1"; assert mkrTotalSupplyAfter == mkrTotalSupplyBefore - wad * fee / WAD(), "Assert 2"; assert to != currentContract && to != voteDelegate_ || - to == currentContract && voteDelegate_ != addrZero() || - to == voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore + (wad - wad * fee / WAD()), "Assert 3"; - assert to == currentContract && voteDelegate_ == addrZero() || - to == voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore - wad * fee / WAD(), "Assert 4"; - assert to != voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; - assert to != voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - wad, "Assert 6"; - assert to != currentContract && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - wad, "Assert 7"; - assert to != currentContract && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; + to == currentContract && voteDelegate_ != 0 || + to == voteDelegate_ && voteDelegate_ == 0 => mkrBalanceOfToAfter == mkrBalanceOfToBefore + (wad - wad * fee / WAD()), "Assert 3"; + assert to == currentContract && voteDelegate_ == 0 || + to == voteDelegate_ && voteDelegate_ != 0 => mkrBalanceOfToAfter == mkrBalanceOfToBefore - wad * fee / WAD(), "Assert 4"; + assert to != voteDelegate_ && voteDelegate_ == 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; + assert to != voteDelegate_ && voteDelegate_ != 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - wad, "Assert 6"; + assert to != currentContract && voteDelegate_ == 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - wad, "Assert 7"; + assert to != currentContract && voteDelegate_ != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 9"; assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - wad, "Assert 10"; - assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; - assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - wad, "Assert 12"; - assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 13"; - assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; + assert farm == 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; + assert farm != 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - wad, "Assert 12"; + assert farm == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 13"; + assert farm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 15"; } @@ -1229,9 +1228,9 @@ rule free_revert(address owner, uint256 index, address to, uint256 wad) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; @@ -1256,8 +1255,8 @@ rule free_revert(address owner, uint256 index, address to, uint256 wad) { require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(urn) + lsmkr.balanceOf(farm); // TODO: this might be nice to prove in some sort require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); - require voteDelegate_ != addrZero() => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; - require voteDelegate_ == addrZero() => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ != 0 => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ == 0 => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); // Practical Vat assumptions @@ -1271,14 +1270,14 @@ rule free_revert(address owner, uint256 index, address to, uint256 wad) { require vat.gem(ilk, urn) == 0; // Safe to assume as Engine keeps the invariant (rule inkMatchesLsmkrFarm) require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; - require lsmkr.balanceOf(urn) > 0 => farm == addrZero(); - require stakingRewards.balanceOf(urn) > 0 => farm != addrZero(); + require lsmkr.balanceOf(urn) > 0 => farm == 0; + require stakingRewards.balanceOf(urn) > 0 => farm != 0; require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); free@withrevert(e, owner, index, to, wad); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; bool revert4 = to_mathint(wad) > max_int256(); bool revert5 = vatUrnsIlkUrnInk < to_mathint(wad) || wad > 0 && (vatUrnsIlkUrnInk - wad) * vatIlksIlkSpot < vatUrnsIlkUrnArt * vatIlksIlkRate; @@ -1298,9 +1297,9 @@ rule freeSky(address owner, uint256 index, address to, uint256 skyWad) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; address other; require other != currentContract && other != voteDelegate_; @@ -1356,16 +1355,16 @@ rule freeSky(address owner, uint256 index, address to, uint256 skyWad) { assert skyBalanceOfToAfter == skyBalanceOfToBefore + (skyWad/mkrSkyRate - skyWad/mkrSkyRate * fee / WAD()) * mkrSkyRate, "Assert 3"; assert skyBalanceOfOtherAfter == skyBalanceOfOtherBefore, "Assert 4"; assert mkrTotalSupplyAfter == mkrTotalSupplyBefore - skyWad/mkrSkyRate, "Assert 5"; - assert to != voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 6"; - assert to != voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - skyWad/mkrSkyRate, "Assert 7"; - assert to != currentContract && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - skyWad/mkrSkyRate, "Assert 8"; - assert to != currentContract && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 9"; + assert to != voteDelegate_ && voteDelegate_ == 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 6"; + assert to != voteDelegate_ && voteDelegate_ != 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - skyWad/mkrSkyRate, "Assert 7"; + assert to != currentContract && voteDelegate_ == 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - skyWad/mkrSkyRate, "Assert 8"; + assert to != currentContract && voteDelegate_ != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 9"; assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 10"; assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - skyWad/mkrSkyRate, "Assert 11"; - assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 12"; - assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - skyWad/mkrSkyRate, "Assert 13"; - assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - skyWad/mkrSkyRate, "Assert 14"; - assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 15"; + assert farm == 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 12"; + assert farm != 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - skyWad/mkrSkyRate, "Assert 13"; + assert farm == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - skyWad/mkrSkyRate, "Assert 14"; + assert farm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 15"; assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 16"; } @@ -1377,9 +1376,9 @@ rule freeSky_revert(address owner, uint256 index, address to, uint256 skyWad) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; @@ -1413,8 +1412,8 @@ rule freeSky_revert(address owner, uint256 index, address to, uint256 skyWad) { require sky.totalSupply() + skyWad <= max_uint256; // TODO: this might be nice to prove in some sort require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); - require voteDelegate_ != addrZero() => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; - require voteDelegate_ == addrZero() => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ != 0 => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ == 0 => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); // Practical Vat assumptions @@ -1428,14 +1427,14 @@ rule freeSky_revert(address owner, uint256 index, address to, uint256 skyWad) { require vat.gem(ilk, urn) == 0; // Safe to assume as Engine keeps the invariant (rule inkMatchesLsmkrFarm) require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; - require lsmkr.balanceOf(urn) > 0 => farm == addrZero(); - require stakingRewards.balanceOf(urn) > 0 => farm != addrZero(); + require lsmkr.balanceOf(urn) > 0 => farm == 0; + require stakingRewards.balanceOf(urn) > 0 => farm != 0; require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); freeSky@withrevert(e, owner, index, to, skyWad); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; bool revert4 = to_mathint(skyWad/mkrSkyRate) > max_int256(); bool revert5 = vatUrnsIlkUrnInk < to_mathint(skyWad/mkrSkyRate) || skyWad/mkrSkyRate > 0 && (vatUrnsIlkUrnInk - skyWad/mkrSkyRate) * vatIlksIlkSpot < vatUrnsIlkUrnArt * vatIlksIlkRate; @@ -1455,9 +1454,9 @@ rule freeNoFee(address owner, uint256 index, address to, uint256 wad) { require urn == lockstakeUrn; address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; address other; require other != to && other != currentContract && other != voteDelegate_; @@ -1498,20 +1497,20 @@ rule freeNoFee(address owner, uint256 index, address to, uint256 wad) { assert vatUrnsIlkUrnInkAfter == vatUrnsIlkUrnInkBefore - wad, "Assert 1"; assert mkrTotalSupplyAfter == mkrTotalSupplyBefore, "Assert 2"; assert to != currentContract && to != voteDelegate_ || - to == currentContract && voteDelegate_ != addrZero() || - to == voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore + wad, "Assert 3"; - assert to == currentContract && voteDelegate_ == addrZero() || - to == voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfToAfter == mkrBalanceOfToBefore, "Assert 4"; - assert to != voteDelegate_ && voteDelegate_ == addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; - assert to != voteDelegate_ && voteDelegate_ != addrZero() => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - wad, "Assert 6"; - assert to != currentContract && voteDelegate_ == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - wad, "Assert 7"; - assert to != currentContract && voteDelegate_ != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; + to == currentContract && voteDelegate_ != 0 || + to == voteDelegate_ && voteDelegate_ == 0 => mkrBalanceOfToAfter == mkrBalanceOfToBefore + wad, "Assert 3"; + assert to == currentContract && voteDelegate_ == 0 || + to == voteDelegate_ && voteDelegate_ != 0 => mkrBalanceOfToAfter == mkrBalanceOfToBefore, "Assert 4"; + assert to != voteDelegate_ && voteDelegate_ == 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore, "Assert 5"; + assert to != voteDelegate_ && voteDelegate_ != 0 => mkrBalanceOfVoteDelegateAfter == mkrBalanceOfVoteDelegateBefore - wad, "Assert 6"; + assert to != currentContract && voteDelegate_ == 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore - wad, "Assert 7"; + assert to != currentContract && voteDelegate_ != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 8"; assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 9"; assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - wad, "Assert 10"; - assert farm == addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; - assert farm != addrZero() => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - wad, "Assert 12"; - assert farm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 13"; - assert farm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; + assert farm == 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore, "Assert 11"; + assert farm != 0 => lsmkrBalanceOfFarmAfter == lsmkrBalanceOfFarmBefore - wad, "Assert 12"; + assert farm == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 13"; + assert farm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore, "Assert 14"; assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 15"; } @@ -1525,9 +1524,9 @@ rule freeNoFee_revert(address owner, uint256 index, address to, uint256 wad) { mathint wardsSender = wards(e.msg.sender); address voteDelegate_ = urnVoteDelegates(urn); - require voteDelegate_ == addrZero() || voteDelegate_ == voteDelegate; + require voteDelegate_ == 0 || voteDelegate_ == voteDelegate; address farm = urnFarms(urn); - require farm == addrZero() || farm == stakingRewards; + require farm == 0 || farm == stakingRewards; require e.msg.sender != voteDelegate_ && e.msg.sender != currentContract; @@ -1549,8 +1548,8 @@ rule freeNoFee_revert(address owner, uint256 index, address to, uint256 wad) { require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(urn) + lsmkr.balanceOf(farm); // TODO: this might be nice to prove in some sort require mkr.balanceOf(voteDelegate_) >= voteDelegate.stake(currentContract); - require voteDelegate_ != addrZero() => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; - require voteDelegate_ == addrZero() => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ != 0 => to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk; + require voteDelegate_ == 0 => to_mathint(mkr.balanceOf(currentContract)) >= vatUrnsIlkUrnInk; require stakingRewards.totalSupply() == stakingRewards.balanceOf(urn); require lsmkr.balanceOf(farm) == stakingRewards.totalSupply(); // Practical Vat assumptions @@ -1564,15 +1563,15 @@ rule freeNoFee_revert(address owner, uint256 index, address to, uint256 wad) { require vat.gem(ilk, urn) == 0; // Safe to assume as Engine keeps the invariant (rule inkMatchesLsmkrFarm) require lsmkr.balanceOf(urn) == 0 || stakingRewards.balanceOf(urn) == 0; - require lsmkr.balanceOf(urn) > 0 => farm == addrZero(); - require stakingRewards.balanceOf(urn) > 0 => farm != addrZero(); + require lsmkr.balanceOf(urn) > 0 => farm == 0; + require stakingRewards.balanceOf(urn) > 0 => farm != 0; require vatUrnsIlkUrnInk == lsmkr.balanceOf(urn) + stakingRewards.balanceOf(urn); freeNoFee@withrevert(e, owner, index, to, wad); bool revert1 = e.msg.value > 0; bool revert2 = wardsSender != 1; - bool revert3 = urn == addrZero(); + bool revert3 = urn == 0; bool revert4 = owner != e.msg.sender && urnCanUrnSender != 1; bool revert5 = to_mathint(wad) > max_int256(); bool revert6 = vatUrnsIlkUrnInk < to_mathint(wad) || wad > 0 && (vatUrnsIlkUrnInk - wad) * vatIlksIlkSpot < vatUrnsIlkUrnArt * vatIlksIlkRate; @@ -1670,7 +1669,7 @@ rule draw_revert(address owner, uint256 index, address to, uint256 wad) { draw@withrevert(e, owner, index, to, wad) at init; bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; bool revert4 = to_mathint(dart) > max_int256(); bool revert5 = dart > 0 && ((vatIlksIlkArt + dart) * calcVatIlksIlkRateAfter > vatIlksIlkLine || vatDebt + vatIlksIlkArt * (calcVatIlksIlkRateAfter - vatIlksIlkRate) + (calcVatIlksIlkRateAfter * dart) > vatLine); @@ -1764,7 +1763,7 @@ rule wipe_revert(address owner, uint256 index, uint256 wad) { wipe@withrevert(e, owner, index, wad); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = to_mathint(dart) > max_int256(); bool revert4 = vatUrnsIlkUrnArt < dart; bool revert5 = vatUrnsIlkUrnArt - dart > 0 && vatIlksIlkRate * (vatUrnsIlkUrnArt - dart) < vatIlksIlkDust; @@ -1853,7 +1852,7 @@ rule wipeAll_revert(address owner, uint256 index) { wipeAll@withrevert(e, owner, index); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = to_mathint(vatUrnsIlkUrnArt) > max_int256(); assert lastReverted <=> revert1 || revert2 || revert3, "Revert rules failed"; @@ -1917,7 +1916,7 @@ rule getReward_revert(address owner, uint256 index, address farm, address to) { getReward@withrevert(e, owner, index, farm, to); bool revert1 = e.msg.value > 0; - bool revert2 = urn == addrZero(); + bool revert2 = urn == 0; bool revert3 = owner != e.msg.sender && urnCanUrnSender != 1; bool revert4 = farmsFarm == LockstakeEngine.FarmStatus.UNSUPPORTED; @@ -1931,9 +1930,9 @@ rule onKick(address urn, uint256 wad) { require urn == lockstakeUrn; address prevVoteDelegate = urnVoteDelegates(urn); - require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate; + require prevVoteDelegate == 0 || prevVoteDelegate == voteDelegate; address prevFarm = urnFarms(urn); - require prevFarm == addrZero() || prevFarm == stakingRewards; + require prevFarm == 0 || prevFarm == stakingRewards; address other; require other != urn; @@ -1978,22 +1977,22 @@ rule onKick(address urn, uint256 wad) { mathint lsmkrBalanceOfUrnAfter = lsmkr.balanceOf(urn); mathint lsmkrBalanceOfOtherAfter = lsmkr.balanceOf(other3); - assert urnVoteDelegatesUrnAfter == addrZero(), "Assert 1"; + assert urnVoteDelegatesUrnAfter == 0, "Assert 1"; assert urnVoteDelegatesOtherAfter == urnVoteDelegatesOtherBefore, "Assert 2"; - assert urnFarmsUrnAfter == addrZero(), "Assert 3"; + assert urnFarmsUrnAfter == 0, "Assert 3"; assert urnFarmsOtherAfter == urnFarmsOtherBefore, "Assert 4"; assert urnAuctionsUrnAfter == urnAuctionsUrnBefore + 1, "Assert 5"; assert urnAuctionsOtherAfter == urnAuctionsOtherBefore, "Assert 6"; - assert prevVoteDelegate == addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore, "Assert 7"; - assert prevVoteDelegate != addrZero() => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore - vatUrnsIlkUrnInk - wad, "Assert 8"; - assert prevVoteDelegate == addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 9"; - assert prevVoteDelegate != addrZero() => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + vatUrnsIlkUrnInk + wad, "Assert 10"; + assert prevVoteDelegate == 0 => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore, "Assert 7"; + assert prevVoteDelegate != 0 => mkrBalanceOfPrevVoteDelegateAfter == mkrBalanceOfPrevVoteDelegateBefore - vatUrnsIlkUrnInk - wad, "Assert 8"; + assert prevVoteDelegate == 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore, "Assert 9"; + assert prevVoteDelegate != 0 => mkrBalanceOfEngineAfter == mkrBalanceOfEngineBefore + vatUrnsIlkUrnInk + wad, "Assert 10"; assert mkrBalanceOfOtherAfter == mkrBalanceOfOtherBefore, "Assert 11"; assert lsmkrTotalSupplyAfter == lsmkrTotalSupplyBefore - wad, "Assert 12"; - assert prevFarm == addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore, "Assert 13"; - assert prevFarm != addrZero() => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore - vatUrnsIlkUrnInk - wad, "Assert 14"; - assert prevFarm == addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 15"; - assert prevFarm != addrZero() => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + vatUrnsIlkUrnInk, "Assert 16"; + assert prevFarm == 0 => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore, "Assert 13"; + assert prevFarm != 0 => lsmkrBalanceOfPrevFarmAfter == lsmkrBalanceOfPrevFarmBefore - vatUrnsIlkUrnInk - wad, "Assert 14"; + assert prevFarm == 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore - wad, "Assert 15"; + assert prevFarm != 0 => lsmkrBalanceOfUrnAfter == lsmkrBalanceOfUrnBefore + vatUrnsIlkUrnInk, "Assert 16"; assert lsmkrBalanceOfOtherAfter == lsmkrBalanceOfOtherBefore, "Assert 17"; } @@ -2003,9 +2002,9 @@ rule onKick_revert(address urn, uint256 wad) { require urn == lockstakeUrn; address prevVoteDelegate = urnVoteDelegates(urn); - require prevVoteDelegate == addrZero() || prevVoteDelegate == voteDelegate; + require prevVoteDelegate == 0 || prevVoteDelegate == voteDelegate; address prevFarm = urnFarms(urn); - require prevFarm == addrZero() || prevFarm == stakingRewards; + require prevFarm == 0 || prevFarm == stakingRewards; mathint wardsSender = wards(e.msg.sender); mathint urnAuctionsUrn = urnAuctions(urn); @@ -2018,11 +2017,11 @@ rule onKick_revert(address urn, uint256 wad) { require to_mathint(lsmkr.totalSupply()) >= lsmkr.balanceOf(prevFarm) + lsmkr.balanceOf(urn) + lsmkr.balanceOf(currentContract); require stakingRewards.totalSupply() >= stakingRewards.balanceOf(urn); // VoteDelegate assumptions - require prevVoteDelegate == addrZero() || to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk + wad; - require prevVoteDelegate == addrZero() || mkr.balanceOf(voteDelegate) >= voteDelegate.stake(currentContract); + require prevVoteDelegate == 0 || to_mathint(voteDelegate.stake(currentContract)) >= vatUrnsIlkUrnInk + wad; + require prevVoteDelegate == 0 || mkr.balanceOf(voteDelegate) >= voteDelegate.stake(currentContract); // StakingRewards assumptions - require prevFarm == addrZero() && lsmkr.balanceOf(urn) >= wad || - prevFarm != addrZero() && to_mathint(stakingRewards.balanceOf(urn)) >= vatUrnsIlkUrnInk + wad && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUrnInk + wad; + require prevFarm == 0 && lsmkr.balanceOf(urn) >= wad || + prevFarm != 0 && to_mathint(stakingRewards.balanceOf(urn)) >= vatUrnsIlkUrnInk + wad && to_mathint(lsmkr.balanceOf(prevFarm)) >= vatUrnsIlkUrnInk + wad; // LockstakeClipper assumption require wad > 0; // Practical assumption (vatUrnsIlkUrnInk + wad should be the same than the vatUrnsIlkUrnInk prev to the kick call) @@ -2183,7 +2182,7 @@ rule onRemove_revert(address urn, uint256 sold, uint256 left) { require lsmkrTotalSupply + refund <= max_uint256; // Assumption from LockstakeClipper require mkrBalanceOfEngine >= burn; - require urn != lsmkr && urn != addrZero(); + require urn != lsmkr && urn != 0; onRemove@withrevert(e, urn, sold, left); diff --git a/certora/LockstakeEngineMulticall.conf b/certora/LockstakeEngineMulticall.conf index 5534cab3..b539614d 100644 --- a/certora/LockstakeEngineMulticall.conf +++ b/certora/LockstakeEngineMulticall.conf @@ -53,5 +53,5 @@ "multi_assert_check": true, "parametric_contracts": ["LockstakeEngine"], "build_cache": true, - "msg": "LockstakeEngine" + "msg": "LockstakeEngine Multicall" } diff --git a/certora/LockstakeEngineMulticall.spec b/certora/LockstakeEngineMulticall.spec index 72b07c7d..0b9c2048 100644 --- a/certora/LockstakeEngineMulticall.spec +++ b/certora/LockstakeEngineMulticall.spec @@ -43,8 +43,6 @@ methods { ] default HAVOC_ALL; } -definition addrZero() returns address = 0x0000000000000000000000000000000000000000; - rule hopeAndHope(address owner1, uint256 index1, address owner2, uint256 index2, address usr) { env e; @@ -93,5 +91,5 @@ rule selectFarmAndLock(address owner, uint256 index, address farm, uint16 ref, u assert mkrAllowanceExecutorEngineBefore == max_uint256 => mkrAllowanceExecutorEngineAfter == mkrAllowanceExecutorEngineBefore, "Assert 3"; assert urnFarmsUrnAfter == farm, "Assert 4"; - assert farm == addrZero() || farms(farm) == LockstakeEngine.FarmStatus.ACTIVE, "farm is active"; + assert farm == 0 || farms(farm) == LockstakeEngine.FarmStatus.ACTIVE, "farm is active"; } From 2e6e3e8d166edda785feea2b108dc1b28ed6ab3b Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Fri, 25 Oct 2024 17:16:20 -0300 Subject: [PATCH 110/111] Certora: Improve PATH compatibility --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7d592993..b2f4c0b8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PATH := ~/.solc-select/artifacts/solc-0.5.12:~/.solc-select/artifacts/solc-0.6.12:~/.solc-select/artifacts/solc-0.8.21:$(PATH) +PATH := ~/.solc-select/artifacts/:~/.solc-select/artifacts/solc-0.5.12:~/.solc-select/artifacts/solc-0.6.12:~/.solc-select/artifacts/solc-0.8.21:$(PATH) certora-urn :; PATH=${PATH} certoraRun certora/LockstakeUrn.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) certora-lsmkr :; PATH=${PATH} certoraRun certora/LockstakeMkr.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) certora-engine :; PATH=${PATH} certoraRun certora/LockstakeEngine.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,) From 745ffd79108c97682fc39bc6757531c0dcb603db Mon Sep 17 00:00:00 2001 From: sunbreak1211 Date: Tue, 12 Nov 2024 08:10:47 -0300 Subject: [PATCH 111/111] Adjust Certora flags --- certora/LockstakeEngine.conf | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/certora/LockstakeEngine.conf b/certora/LockstakeEngine.conf index a6d5fb03..9a05a537 100644 --- a/certora/LockstakeEngine.conf +++ b/certora/LockstakeEngine.conf @@ -82,9 +82,11 @@ ], "verify": "LockstakeEngine:certora/LockstakeEngine.spec", "prover_args": [ - "-rewriteMSizeAllocations true", - "-smt_easy_LIA true" - ], + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-depth 0", + "-s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" + ], "smt_timeout": "7000", "rule_sanity": "basic", "optimistic_loop": true,